@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
|
@@ -6,12 +6,19 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
|
8
8
|
|
|
9
|
-
import { and, desc, eq, inArray, notInArray } from "drizzle-orm";
|
|
9
|
+
import { and, desc, eq, inArray, isNotNull, notInArray } from "drizzle-orm";
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
|
|
12
|
+
import {
|
|
13
|
+
acpAuthMarkerStillCurrent,
|
|
14
|
+
MARKER_SCAN_LIMIT,
|
|
15
|
+
} from "../../acp/acp-auth-marker-store.js";
|
|
12
16
|
import { resolveAgentWithAutoInstall } from "../../acp/auto-install.js";
|
|
13
17
|
import { getAcpSessionManager } from "../../acp/index.js";
|
|
14
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
prepareAgentEnv,
|
|
20
|
+
resolvedClaudeCredentialDigest,
|
|
21
|
+
} from "../../acp/prepare-agent-env.js";
|
|
15
22
|
import { formatResolveFailure } from "../../acp/resolve-agent.js";
|
|
16
23
|
import {
|
|
17
24
|
AcpResumeError,
|
|
@@ -58,6 +65,9 @@ const sessionEntrySchema = z.object({
|
|
|
58
65
|
stopReason: z.string().nullable().optional(),
|
|
59
66
|
task: z.string().optional(),
|
|
60
67
|
parentToolUseId: z.string().optional(),
|
|
68
|
+
/** Credential failure that ended the run, when one did. Drives the inline
|
|
69
|
+
* Connect card on reopen; cleared when a replacement token is stored. */
|
|
70
|
+
authErrorCode: z.string().optional(),
|
|
61
71
|
usedTokens: z.number().optional(),
|
|
62
72
|
contextSize: z.number().optional(),
|
|
63
73
|
costAmount: z.number().optional(),
|
|
@@ -69,6 +79,24 @@ const sessionEntrySchema = z.object({
|
|
|
69
79
|
|
|
70
80
|
type SessionEntry = z.infer<typeof sessionEntrySchema>;
|
|
71
81
|
|
|
82
|
+
/**
|
|
83
|
+
* A merged session before its marker has been judged.
|
|
84
|
+
*
|
|
85
|
+
* `authErrorCredential` names the credential the failure was refused on. It is
|
|
86
|
+
* what the comparison needs and is dropped before the response goes out: a
|
|
87
|
+
* client has no use for it and no way to resolve the other side of the
|
|
88
|
+
* comparison, so serving it would only widen what leaves the daemon.
|
|
89
|
+
*/
|
|
90
|
+
type MergedSession = SessionEntry & { authErrorCredential?: string };
|
|
91
|
+
|
|
92
|
+
/** Drop the comparison's own input before the session goes out on the wire. */
|
|
93
|
+
function stripMarkerCredential({
|
|
94
|
+
authErrorCredential: _dropped,
|
|
95
|
+
...session
|
|
96
|
+
}: MergedSession): SessionEntry {
|
|
97
|
+
return session;
|
|
98
|
+
}
|
|
99
|
+
|
|
72
100
|
// ---------------------------------------------------------------------------
|
|
73
101
|
// Handlers
|
|
74
102
|
// ---------------------------------------------------------------------------
|
|
@@ -424,11 +452,102 @@ function closeSession({ pathParams }: RouteHandlerArgs) {
|
|
|
424
452
|
return { acpSessionId: id, closed: true };
|
|
425
453
|
}
|
|
426
454
|
|
|
427
|
-
function listSessions({ queryParams }: RouteHandlerArgs) {
|
|
455
|
+
async function listSessions({ queryParams }: RouteHandlerArgs) {
|
|
428
456
|
const limit = parseLimit(queryParams?.limit);
|
|
429
457
|
const conversationId = queryParams?.conversationId;
|
|
430
|
-
|
|
431
|
-
|
|
458
|
+
// Resolved per agent, because precedence is per agent: one alias can carry a
|
|
459
|
+
// configured token while another falls through to the vault. Memoised across
|
|
460
|
+
// the request, since each resolution costs a vault read and a conversation's
|
|
461
|
+
// marked runs are nearly always one agent.
|
|
462
|
+
const resolvedByAgent = new Map<string, string | undefined>();
|
|
463
|
+
const resolvedFor = async (agentId: string) => {
|
|
464
|
+
if (!resolvedByAgent.has(agentId)) {
|
|
465
|
+
resolvedByAgent.set(
|
|
466
|
+
agentId,
|
|
467
|
+
await resolvedClaudeCredentialDigest(agentId),
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
return resolvedByAgent.get(agentId);
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
const { sessions: merged, sawEveryHistoryRow } = listMergedSessions({
|
|
474
|
+
limit,
|
|
475
|
+
conversationId,
|
|
476
|
+
});
|
|
477
|
+
// Judged before paging, never after. A stale marker that escaped the page
|
|
478
|
+
// first would stay in the response as an ordinary row once its code was
|
|
479
|
+
// struck, so the retained markers would pile up past the limit.
|
|
480
|
+
const judged = await withCurrentMarkersOnly(merged, resolvedFor);
|
|
481
|
+
const page = judged.slice(0, limit);
|
|
482
|
+
if (!conversationId || page.some((s) => s.authErrorCode !== undefined)) {
|
|
483
|
+
return { sessions: page };
|
|
484
|
+
}
|
|
485
|
+
// Rows the page cut that are already in hand. In-memory sessions merge in on
|
|
486
|
+
// top of the history read, so the merged list can overflow the page even
|
|
487
|
+
// when the query reached the end of the table, and the marker the client
|
|
488
|
+
// needs can be sitting in that overflow. Newest-first, so the first match is
|
|
489
|
+
// the one to surface.
|
|
490
|
+
const overflowMarker = judged
|
|
491
|
+
.slice(limit)
|
|
492
|
+
.find((s) => s.authErrorCode !== undefined);
|
|
493
|
+
if (overflowMarker) {
|
|
494
|
+
return { sessions: [...page, overflowMarker] };
|
|
495
|
+
}
|
|
496
|
+
if (sawEveryHistoryRow) {
|
|
497
|
+
// Every row this conversation has has now been looked at, page and
|
|
498
|
+
// overflow alike, so a marker elsewhere is not a thing that exists. Most
|
|
499
|
+
// conversations have never had a credential failure, and this is what
|
|
500
|
+
// keeps the lookup off their path rather than asking the database to
|
|
501
|
+
// confirm the absence every time.
|
|
502
|
+
return { sessions: page };
|
|
503
|
+
}
|
|
504
|
+
// The page holds no live marker, so the row a client restores the card from
|
|
505
|
+
// is either outside it or absent. One row, fetched only now, rather than
|
|
506
|
+
// every marked row loaded on the chance one of them is needed.
|
|
507
|
+
const marker = await findRecoveryMarker(conversationId, resolvedFor);
|
|
508
|
+
if (!marker || page.some((s) => s.id === marker.id)) {
|
|
509
|
+
return { sessions: page };
|
|
510
|
+
}
|
|
511
|
+
return { sessions: [...page, stripMarkerCredential(marker)] };
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Blank the failure code on any session whose marker no longer describes the
|
|
516
|
+
* credential its agent would resolve.
|
|
517
|
+
*
|
|
518
|
+
* This comparison is what retires a Connect card: not a sweep that has to run
|
|
519
|
+
* at the right moment, but the marker no longer describing the credential in
|
|
520
|
+
* use. Applied after merging rather than inside the query, so live sessions
|
|
521
|
+
* and history rows are judged by exactly the same rule.
|
|
522
|
+
*
|
|
523
|
+
* Resolved per agent, because precedence is per agent: one alias can carry a
|
|
524
|
+
* configured token while another falls through to the vault. Memoised across
|
|
525
|
+
* the request, since a conversation's marked runs are nearly always one agent
|
|
526
|
+
* and each resolution costs a vault read.
|
|
527
|
+
*/
|
|
528
|
+
async function withCurrentMarkersOnly(
|
|
529
|
+
sessions: MergedSession[],
|
|
530
|
+
resolvedFor: (agentId: string) => Promise<string | undefined>,
|
|
531
|
+
): Promise<SessionEntry[]> {
|
|
532
|
+
const strip = stripMarkerCredential;
|
|
533
|
+
if (!sessions.some((s) => s.authErrorCode !== undefined)) {
|
|
534
|
+
return sessions.map(strip);
|
|
535
|
+
}
|
|
536
|
+
const judged: SessionEntry[] = [];
|
|
537
|
+
for (const session of sessions) {
|
|
538
|
+
if (session.authErrorCode === undefined) {
|
|
539
|
+
judged.push(strip(session));
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
const current = acpAuthMarkerStillCurrent(
|
|
543
|
+
session.authErrorCredential,
|
|
544
|
+
await resolvedFor(session.agentId),
|
|
545
|
+
);
|
|
546
|
+
judged.push(
|
|
547
|
+
strip(current ? session : { ...session, authErrorCode: undefined }),
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
return judged;
|
|
432
551
|
}
|
|
433
552
|
|
|
434
553
|
function bulkDeleteSessions({ queryParams }: RouteHandlerArgs) {
|
|
@@ -701,14 +820,20 @@ function parseLimit(raw: string | null | undefined): number {
|
|
|
701
820
|
return Math.min(Math.floor(n), MAX_SESSION_LIMIT);
|
|
702
821
|
}
|
|
703
822
|
|
|
704
|
-
function listMergedSessions(opts: {
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
823
|
+
function listMergedSessions(opts: { limit: number; conversationId?: string }): {
|
|
824
|
+
sessions: MergedSession[];
|
|
825
|
+
/**
|
|
826
|
+
* Whether the history query reached the end of this conversation's rows.
|
|
827
|
+
*
|
|
828
|
+
* A short read means there is nothing beyond what was returned, which is the
|
|
829
|
+
* proof that no marker is hiding outside the page.
|
|
830
|
+
*/
|
|
831
|
+
sawEveryHistoryRow: boolean;
|
|
832
|
+
} {
|
|
708
833
|
const manager = getAcpSessionManager();
|
|
709
834
|
const inMemory = manager.getStatus() as AcpSessionState[];
|
|
710
835
|
|
|
711
|
-
const merged = new Map<string,
|
|
836
|
+
const merged = new Map<string, MergedSession>();
|
|
712
837
|
for (const s of inMemory) {
|
|
713
838
|
if (opts.conversationId && s.parentConversationId !== opts.conversationId) {
|
|
714
839
|
continue;
|
|
@@ -725,6 +850,8 @@ function listMergedSessions(opts: {
|
|
|
725
850
|
stopReason: s.stopReason ?? null,
|
|
726
851
|
task: s.task,
|
|
727
852
|
parentToolUseId: s.parentToolUseId,
|
|
853
|
+
authErrorCode: s.authErrorCode,
|
|
854
|
+
authErrorCredential: s.authErrorCredential,
|
|
728
855
|
usedTokens: s.latestUsage?.usedTokens,
|
|
729
856
|
contextSize: s.latestUsage?.contextSize,
|
|
730
857
|
costAmount: s.latestUsage?.costAmount,
|
|
@@ -748,52 +875,131 @@ function listMergedSessions(opts: {
|
|
|
748
875
|
// guarantee we still surface `limit` distinct rows even when every
|
|
749
876
|
// in-memory session shadows a DB row — without over-fetching when many
|
|
750
877
|
// unrelated sessions are in memory.
|
|
878
|
+
const historyLimit = opts.limit + merged.size;
|
|
751
879
|
const historyRows = filtered
|
|
752
880
|
.orderBy(desc(acpSessionHistory.startedAt))
|
|
753
|
-
.limit(
|
|
881
|
+
.limit(historyLimit)
|
|
754
882
|
.all();
|
|
755
883
|
|
|
756
884
|
for (const row of historyRows) {
|
|
757
885
|
if (merged.has(row.id)) {
|
|
758
886
|
continue;
|
|
759
887
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
888
|
+
merged.set(row.id, toMergedSession(row));
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
return {
|
|
892
|
+
sessions: Array.from(merged.values()).sort(
|
|
893
|
+
(a, b) => b.startedAt - a.startedAt,
|
|
894
|
+
),
|
|
895
|
+
sawEveryHistoryRow: historyRows.length < historyLimit,
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
/** Shape a history row for the response, parsing its stored event log. */
|
|
900
|
+
function toMergedSession(
|
|
901
|
+
row: typeof acpSessionHistory.$inferSelect,
|
|
902
|
+
): MergedSession {
|
|
903
|
+
let eventLog: unknown[] = [];
|
|
904
|
+
try {
|
|
905
|
+
const parsed = JSON.parse(row.eventLogJson) as unknown;
|
|
906
|
+
if (Array.isArray(parsed)) {
|
|
907
|
+
eventLog = parsed;
|
|
771
908
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
acpSessionId: row.acpSessionId,
|
|
778
|
-
parentConversationId: row.parentConversationId,
|
|
779
|
-
status: row.status,
|
|
780
|
-
startedAt: row.startedAt,
|
|
781
|
-
completedAt: row.completedAt,
|
|
782
|
-
error: row.error,
|
|
783
|
-
stopReason: row.stopReason,
|
|
784
|
-
task: row.task ?? undefined,
|
|
785
|
-
parentToolUseId: row.parentToolUseId ?? undefined,
|
|
786
|
-
usedTokens: row.usedTokens ?? undefined,
|
|
787
|
-
contextSize: row.contextSize ?? undefined,
|
|
788
|
-
costAmount: row.costAmount ?? undefined,
|
|
789
|
-
costCurrency: row.costCurrency ?? undefined,
|
|
790
|
-
inputTokens: row.inputTokens ?? undefined,
|
|
791
|
-
outputTokens: row.outputTokens ?? undefined,
|
|
792
|
-
eventLog,
|
|
793
|
-
});
|
|
909
|
+
} catch (err) {
|
|
910
|
+
log.warn(
|
|
911
|
+
{ id: row.id, err },
|
|
912
|
+
"Failed to parse event_log_json for ACP session history row",
|
|
913
|
+
);
|
|
794
914
|
}
|
|
915
|
+
// Rows predating the usage migration carry NULLs for these columns and
|
|
916
|
+
// degrade to undefined.
|
|
917
|
+
return {
|
|
918
|
+
id: row.id,
|
|
919
|
+
agentId: row.agentId,
|
|
920
|
+
acpSessionId: row.acpSessionId,
|
|
921
|
+
parentConversationId: row.parentConversationId,
|
|
922
|
+
status: row.status,
|
|
923
|
+
startedAt: row.startedAt,
|
|
924
|
+
completedAt: row.completedAt,
|
|
925
|
+
error: row.error,
|
|
926
|
+
stopReason: row.stopReason,
|
|
927
|
+
task: row.task ?? undefined,
|
|
928
|
+
parentToolUseId: row.parentToolUseId ?? undefined,
|
|
929
|
+
authErrorCode: row.authErrorCode ?? undefined,
|
|
930
|
+
authErrorCredential: row.authErrorCredential ?? undefined,
|
|
931
|
+
usedTokens: row.usedTokens ?? undefined,
|
|
932
|
+
contextSize: row.contextSize ?? undefined,
|
|
933
|
+
costAmount: row.costAmount ?? undefined,
|
|
934
|
+
costCurrency: row.costCurrency ?? undefined,
|
|
935
|
+
inputTokens: row.inputTokens ?? undefined,
|
|
936
|
+
outputTokens: row.outputTokens ?? undefined,
|
|
937
|
+
eventLog,
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* Reach past the page for the one marked run a client would restore the card
|
|
943
|
+
* from, when the page itself holds none.
|
|
944
|
+
*
|
|
945
|
+
* Paging it out is the difference between a user having a way back to auth and
|
|
946
|
+
* not: a conversation with more recent runs than the page holds would
|
|
947
|
+
* otherwise hide the one row that matters.
|
|
948
|
+
*
|
|
949
|
+
* Asks the database for that row rather than filtering one that was loaded.
|
|
950
|
+
* Nothing clears a marker, so repeated failures against a credential that is
|
|
951
|
+
* still current accumulate them, and loading every marked row would pull a
|
|
952
|
+
* full event log apiece into memory to parse and then discard. This reads the
|
|
953
|
+
* credential of each marked row (a column, no event log), resolves what its
|
|
954
|
+
* agent would use now, and only then loads the single row that wins.
|
|
955
|
+
*
|
|
956
|
+
* Bounded by the number of distinct agents a conversation has failed under,
|
|
957
|
+
* which is one in practice, rather than by how often it has failed.
|
|
958
|
+
*/
|
|
959
|
+
async function findRecoveryMarker(
|
|
960
|
+
conversationId: string,
|
|
961
|
+
resolvedFor: (agentId: string) => Promise<string | undefined>,
|
|
962
|
+
): Promise<MergedSession | undefined> {
|
|
963
|
+
const db = getDb();
|
|
964
|
+
// One ordered read of the newest markers, then the answer is decided in
|
|
965
|
+
// memory. Filtering by agent and credential in SQL meant the planner could
|
|
966
|
+
// only seek to the conversation and then walk its marked history looking for
|
|
967
|
+
// a match, so a run of failures against a replaced credential put that walk
|
|
968
|
+
// back on every snapshot. This shape is the one the index serves: seek,
|
|
969
|
+
// read in order, stop.
|
|
970
|
+
const markers = db
|
|
971
|
+
.select({
|
|
972
|
+
id: acpSessionHistory.id,
|
|
973
|
+
agentId: acpSessionHistory.agentId,
|
|
974
|
+
credential: acpSessionHistory.authErrorCredential,
|
|
975
|
+
})
|
|
976
|
+
.from(acpSessionHistory)
|
|
977
|
+
.where(
|
|
978
|
+
and(
|
|
979
|
+
eq(acpSessionHistory.parentConversationId, conversationId),
|
|
980
|
+
isNotNull(acpSessionHistory.authErrorCode),
|
|
981
|
+
),
|
|
982
|
+
)
|
|
983
|
+
.orderBy(desc(acpSessionHistory.startedAt))
|
|
984
|
+
.limit(MARKER_SCAN_LIMIT)
|
|
985
|
+
.all();
|
|
795
986
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
987
|
+
for (const marker of markers) {
|
|
988
|
+
if (
|
|
989
|
+
acpAuthMarkerStillCurrent(
|
|
990
|
+
marker.credential,
|
|
991
|
+
await resolvedFor(marker.agentId),
|
|
992
|
+
)
|
|
993
|
+
) {
|
|
994
|
+
// Newest-first, so the first match is the one a client would restore
|
|
995
|
+
// from. Only now is the full row worth loading, by primary key.
|
|
996
|
+
const row = db
|
|
997
|
+
.select()
|
|
998
|
+
.from(acpSessionHistory)
|
|
999
|
+
.where(eq(acpSessionHistory.id, marker.id))
|
|
1000
|
+
.get();
|
|
1001
|
+
return row ? toMergedSession(row) : undefined;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
return undefined;
|
|
799
1005
|
}
|
|
@@ -54,6 +54,7 @@ import { verifyBundleSignature } from "../../bundler/signature-verifier.js";
|
|
|
54
54
|
import { compareSemver } from "../../daemon/handlers/shared.js";
|
|
55
55
|
import { computeContentId } from "../../util/content-id.js";
|
|
56
56
|
import { getLogger } from "../../util/logger.js";
|
|
57
|
+
import { getUserAppDataDir } from "../../util/platform.js";
|
|
57
58
|
import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
|
|
58
59
|
import {
|
|
59
60
|
getOriginClientId,
|
|
@@ -73,14 +74,26 @@ const log = getLogger("app-management-routes");
|
|
|
73
74
|
// Helpers
|
|
74
75
|
// ---------------------------------------------------------------------------
|
|
75
76
|
|
|
76
|
-
function
|
|
77
|
-
|
|
77
|
+
function getSharedAppsDirs(): string[] {
|
|
78
|
+
const canonical = join(
|
|
79
|
+
getUserAppDataDir(),
|
|
80
|
+
"vellum-assistant",
|
|
81
|
+
"shared-apps",
|
|
82
|
+
);
|
|
83
|
+
const legacy = join(
|
|
78
84
|
homedir(),
|
|
79
85
|
"Library",
|
|
80
86
|
"Application Support",
|
|
81
87
|
"vellum-assistant",
|
|
82
88
|
"shared-apps",
|
|
83
89
|
);
|
|
90
|
+
return legacy === canonical ? [canonical] : [canonical, legacy];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function resolveSharedAppDir(appUuid: string, dirs: string[]): string | null {
|
|
94
|
+
return (
|
|
95
|
+
dirs.find((dir) => existsSync(join(dir, `${appUuid}-meta.json`))) ?? null
|
|
96
|
+
);
|
|
84
97
|
}
|
|
85
98
|
|
|
86
99
|
// ---------------------------------------------------------------------------
|
|
@@ -218,13 +231,9 @@ function getAppDataResult(
|
|
|
218
231
|
}
|
|
219
232
|
}
|
|
220
233
|
|
|
221
|
-
function listSharedApps(
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
return [];
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const files = readdirSync(dir).filter((f) => f.endsWith("-meta.json"));
|
|
234
|
+
function listSharedApps(
|
|
235
|
+
dirs = getSharedAppsDirs(),
|
|
236
|
+
): Array<Record<string, unknown>> {
|
|
228
237
|
const apps: Array<{
|
|
229
238
|
uuid: string;
|
|
230
239
|
name: string;
|
|
@@ -240,43 +249,54 @@ function listSharedApps(): Array<Record<string, unknown>> {
|
|
|
240
249
|
contentId?: string;
|
|
241
250
|
forked?: boolean;
|
|
242
251
|
}> = [];
|
|
252
|
+
const seen = new Set<string>();
|
|
243
253
|
|
|
244
|
-
for (const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const manifest = JSON.parse(manifestRaw);
|
|
256
|
-
version = manifest.version;
|
|
257
|
-
contentId = manifest.content_id;
|
|
258
|
-
} catch {
|
|
259
|
-
// ignore malformed manifest
|
|
254
|
+
for (const dir of dirs) {
|
|
255
|
+
if (!existsSync(dir)) {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
const files = readdirSync(dir).filter((f) => f.endsWith("-meta.json"));
|
|
259
|
+
for (const file of files) {
|
|
260
|
+
try {
|
|
261
|
+
const raw = readFileSync(join(dir, file), "utf-8");
|
|
262
|
+
const meta = JSON.parse(raw);
|
|
263
|
+
if (seen.has(meta.uuid)) {
|
|
264
|
+
continue;
|
|
260
265
|
}
|
|
261
|
-
}
|
|
262
266
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
267
|
+
let version: string | undefined;
|
|
268
|
+
let contentId: string | undefined;
|
|
269
|
+
const manifestPath = join(dir, meta.uuid, "manifest.json");
|
|
270
|
+
if (existsSync(manifestPath)) {
|
|
271
|
+
try {
|
|
272
|
+
const manifestRaw = readFileSync(manifestPath, "utf-8");
|
|
273
|
+
const manifest = JSON.parse(manifestRaw);
|
|
274
|
+
version = manifest.version;
|
|
275
|
+
contentId = manifest.content_id;
|
|
276
|
+
} catch {
|
|
277
|
+
// ignore malformed manifest
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
apps.push({
|
|
282
|
+
uuid: meta.uuid,
|
|
283
|
+
name: meta.name,
|
|
284
|
+
description: meta.description,
|
|
285
|
+
icon: meta.icon,
|
|
286
|
+
preview: meta.preview,
|
|
287
|
+
entry: meta.entry,
|
|
288
|
+
trustTier: meta.trustTier,
|
|
289
|
+
signerDisplayName: meta.signerDisplayName,
|
|
290
|
+
bundleSizeBytes: meta.bundleSizeBytes ?? 0,
|
|
291
|
+
installedAt: meta.installedAt,
|
|
292
|
+
version,
|
|
293
|
+
contentId,
|
|
294
|
+
forked: meta.forked,
|
|
295
|
+
});
|
|
296
|
+
seen.add(meta.uuid);
|
|
297
|
+
} catch {
|
|
298
|
+
log.warn({ file }, "Failed to read shared app metadata file");
|
|
299
|
+
}
|
|
280
300
|
}
|
|
281
301
|
}
|
|
282
302
|
|
|
@@ -314,6 +334,7 @@ function listSharedApps(): Array<Record<string, unknown>> {
|
|
|
314
334
|
|
|
315
335
|
function forkSharedApp(
|
|
316
336
|
appUuid: string,
|
|
337
|
+
dirs = getSharedAppsDirs(),
|
|
317
338
|
):
|
|
318
339
|
| { success: true; appId: string; name: string }
|
|
319
340
|
| { success: false; error: string } {
|
|
@@ -326,12 +347,11 @@ function forkSharedApp(
|
|
|
326
347
|
return { success: false, error: "Invalid UUID" };
|
|
327
348
|
}
|
|
328
349
|
|
|
329
|
-
const dir =
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (!existsSync(metaFile)) {
|
|
350
|
+
const dir = resolveSharedAppDir(appUuid, dirs);
|
|
351
|
+
if (!dir) {
|
|
333
352
|
return { success: false, error: "Shared app not found" };
|
|
334
353
|
}
|
|
354
|
+
const metaFile = join(dir, `${appUuid}-meta.json`);
|
|
335
355
|
|
|
336
356
|
const metaRaw = readFileSync(metaFile, "utf-8");
|
|
337
357
|
const meta = JSON.parse(metaRaw);
|
|
@@ -392,6 +412,19 @@ function forkSharedApp(
|
|
|
392
412
|
return { success: true, appId: newApp.id, name: newApp.name };
|
|
393
413
|
}
|
|
394
414
|
|
|
415
|
+
export function _listSharedAppsForTests(
|
|
416
|
+
dirs: string[],
|
|
417
|
+
): Array<Record<string, unknown>> {
|
|
418
|
+
return listSharedApps(dirs);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export function _resolveSharedAppDirForTests(
|
|
422
|
+
appUuid: string,
|
|
423
|
+
dirs: string[],
|
|
424
|
+
): string | null {
|
|
425
|
+
return resolveSharedAppDir(appUuid, dirs);
|
|
426
|
+
}
|
|
427
|
+
|
|
395
428
|
async function openBundle(filePath: string): Promise<Record<string, unknown>> {
|
|
396
429
|
const fileStat = await stat(filePath);
|
|
397
430
|
const bundleSizeBytes = fileStat.size;
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
|
|
12
12
|
import { PendingToolQuestionSchema } from "../../api/responses/conversation-message.js";
|
|
13
|
+
import { syncTerminalGuardianRequestStatus } from "../../approvals/guardian-request-status-sync.js";
|
|
13
14
|
import { findConversation } from "../../daemon/conversation-registry.js";
|
|
14
15
|
import type {
|
|
15
16
|
SecretDelivery,
|
|
@@ -91,6 +92,14 @@ function handleConfirm({ body }: RouteHandlerArgs) {
|
|
|
91
92
|
effectiveDecision === "allow" ? "approved" : "rejected",
|
|
92
93
|
);
|
|
93
94
|
interaction.directResolve(effectiveDecision as UserDecision);
|
|
95
|
+
// When a guardian request was promoted for this confirmation, bring its
|
|
96
|
+
// row to the matching terminal status and withdraw its cards; with no
|
|
97
|
+
// such row the CAS misses and this is a no-op.
|
|
98
|
+
void syncTerminalGuardianRequestStatus({
|
|
99
|
+
requestId,
|
|
100
|
+
status: effectiveDecision === "allow" ? "approved" : "denied",
|
|
101
|
+
syncContext: "confirm-direct-resolve",
|
|
102
|
+
});
|
|
94
103
|
return { accepted: true };
|
|
95
104
|
}
|
|
96
105
|
|
|
@@ -25,7 +25,12 @@ export async function createTarGz(
|
|
|
25
25
|
const { stdout } = await execFileAsync(
|
|
26
26
|
"tar",
|
|
27
27
|
["czf", "-", "-C", staging, "."],
|
|
28
|
-
{
|
|
28
|
+
{
|
|
29
|
+
maxBuffer: maxBytes,
|
|
30
|
+
timeout: 30_000,
|
|
31
|
+
encoding: "buffer",
|
|
32
|
+
windowsHide: true,
|
|
33
|
+
},
|
|
29
34
|
);
|
|
30
35
|
const buf = Buffer.isBuffer(stdout) ? stdout : Buffer.from(stdout);
|
|
31
36
|
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
|
|
4
|
+
import { getCharacterComponents } from "@vellumai/avatar-catalog";
|
|
4
5
|
import { AVATAR_TRAITS_FILENAME } from "@vellumai/avatar-manifest";
|
|
5
6
|
import { z } from "zod";
|
|
6
7
|
|
|
@@ -16,7 +17,6 @@ import {
|
|
|
16
17
|
setCharacter,
|
|
17
18
|
setImage,
|
|
18
19
|
} from "../../avatar/avatar-store.js";
|
|
19
|
-
import { getCharacterComponents } from "../../avatar/character-components.js";
|
|
20
20
|
import {
|
|
21
21
|
ensureAvatarRaster,
|
|
22
22
|
ensureAvatarRasterPath,
|
|
@@ -52,24 +52,22 @@ export function parseCallbackData(
|
|
|
52
52
|
return null;
|
|
53
53
|
}
|
|
54
54
|
const source =
|
|
55
|
-
sourceChannel === "
|
|
56
|
-
? ("
|
|
57
|
-
:
|
|
58
|
-
? ("slack_button" as const)
|
|
59
|
-
: sourceChannel === "vellum"
|
|
60
|
-
? ("vellum_surface" as const)
|
|
61
|
-
: ("telegram_button" as const);
|
|
55
|
+
sourceChannel === "vellum"
|
|
56
|
+
? ("vellum_surface" as const)
|
|
57
|
+
: ("button" as const);
|
|
62
58
|
return { action, source, requestId };
|
|
63
59
|
}
|
|
64
60
|
|
|
65
61
|
// ---------------------------------------------------------------------------
|
|
66
|
-
// Reaction
|
|
62
|
+
// Reaction decision vocabulary
|
|
67
63
|
// ---------------------------------------------------------------------------
|
|
68
64
|
|
|
69
65
|
/**
|
|
70
|
-
* Map of
|
|
71
|
-
*
|
|
72
|
-
* both
|
|
66
|
+
* Map of reaction emoji to approval actions, in each channel's own
|
|
67
|
+
* vocabulary: Slack reactions carry colon names (with aliasing, e.g. `+1`
|
|
68
|
+
* and `thumbsup` are both the thumbs-up emoji), Telegram and Discord carry
|
|
69
|
+
* the unicode character itself. Telegram's fixed reaction set offers the
|
|
70
|
+
* thumbs but not the check mark or alarm clock; Discord offers all four.
|
|
73
71
|
*/
|
|
74
72
|
const REACTION_EMOJI_MAP: ReadonlyMap<string, ApprovalAction> = new Map([
|
|
75
73
|
["+1", "approve_once"],
|
|
@@ -78,24 +76,24 @@ const REACTION_EMOJI_MAP: ReadonlyMap<string, ApprovalAction> = new Map([
|
|
|
78
76
|
["alarm_clock", "approve_once"],
|
|
79
77
|
["-1", "reject"],
|
|
80
78
|
["thumbsdown", "reject"],
|
|
79
|
+
["\u{1F44D}", "approve_once"], // thumbs up
|
|
80
|
+
["\u2705", "approve_once"], // check mark button
|
|
81
|
+
["\u23F0", "approve_once"], // alarm clock
|
|
82
|
+
["\u{1F44E}", "reject"], // thumbs down
|
|
81
83
|
]);
|
|
82
84
|
|
|
83
85
|
/**
|
|
84
|
-
*
|
|
85
|
-
* decision. Returns null if the emoji is not mapped to any action.
|
|
86
|
+
* Map a reaction emoji, in the source channel's own vocabulary, to an
|
|
87
|
+
* approval decision. Returns null if the emoji is not mapped to any action.
|
|
86
88
|
*/
|
|
87
|
-
export function
|
|
88
|
-
|
|
89
|
+
export function reactionDecisionForEmoji(
|
|
90
|
+
emoji: string,
|
|
89
91
|
): ApprovalDecisionResult | null {
|
|
90
|
-
if (!data.startsWith("reaction:")) {
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
const emoji = data.slice("reaction:".length);
|
|
94
92
|
const action = REACTION_EMOJI_MAP.get(emoji);
|
|
95
93
|
if (!action) {
|
|
96
94
|
return null;
|
|
97
95
|
}
|
|
98
|
-
return { action, source: "
|
|
96
|
+
return { action, source: "reaction" };
|
|
99
97
|
}
|
|
100
98
|
|
|
101
99
|
// ---------------------------------------------------------------------------
|