@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
|
@@ -10,6 +10,7 @@ import { getDb } from "../../../../persistence/db-connection.js";
|
|
|
10
10
|
import { initializeDb } from "../../../../persistence/db-init.js";
|
|
11
11
|
import { messages } from "../../../../persistence/schema/index.js";
|
|
12
12
|
import {
|
|
13
|
+
countRetrospectiveMessagesAfter,
|
|
13
14
|
getRetrospectiveMessagesAfter,
|
|
14
15
|
hasQualifyingUserMessageAfter,
|
|
15
16
|
messagesHaveUserActivity,
|
|
@@ -28,12 +29,61 @@ const MIXED = JSON.stringify([
|
|
|
28
29
|
{ type: "text", text: "note" },
|
|
29
30
|
]);
|
|
30
31
|
|
|
32
|
+
/** A kept camera frame's row: a user-role image the call sampled by itself. */
|
|
33
|
+
const SIGHT_FRAME_CONTENT = JSON.stringify([
|
|
34
|
+
{ type: "text", text: "(camera frame)" },
|
|
35
|
+
{
|
|
36
|
+
type: "image",
|
|
37
|
+
source: {
|
|
38
|
+
type: "workspace_ref",
|
|
39
|
+
media_type: "image/jpeg",
|
|
40
|
+
attachmentId: "att-frame",
|
|
41
|
+
sizeBytes: 1024,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* What `persistLiveVoiceSightFrame` writes: the gate sampled this, nobody
|
|
48
|
+
* spoke it, so `scripted` rides along with the tag.
|
|
49
|
+
*/
|
|
50
|
+
function sightFrameMetadata(attachmentId: string): string {
|
|
51
|
+
return JSON.stringify({
|
|
52
|
+
voiceSessionTurn: true,
|
|
53
|
+
provenanceTrustClass: "guardian",
|
|
54
|
+
scripted: true,
|
|
55
|
+
sightFrameAttachmentIds: [attachmentId],
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* What the voice bridge writes when a parked frame rides real speech
|
|
61
|
+
* (`calls/voice-session-bridge.ts`, matching its persist test's shape). The
|
|
62
|
+
* bridge leaves `scripted` absent for a genuine turn and the persist stamps
|
|
63
|
+
* the `false` default durably, which is what tells the two apart.
|
|
64
|
+
*/
|
|
65
|
+
function riddenTurnMetadata(attachmentId: string): string {
|
|
66
|
+
return JSON.stringify({
|
|
67
|
+
voiceSessionTurn: true,
|
|
68
|
+
provenanceTrustClass: "guardian",
|
|
69
|
+
scripted: false,
|
|
70
|
+
sightFrameAttachmentIds: [attachmentId],
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** An auto-sent row with no camera involvement, e.g. an onboarding prompt. */
|
|
75
|
+
const SCRIPTED_UNTAGGED_METADATA = JSON.stringify({
|
|
76
|
+
provenanceTrustClass: "guardian",
|
|
77
|
+
scripted: true,
|
|
78
|
+
});
|
|
79
|
+
|
|
31
80
|
let seq = 0;
|
|
32
81
|
function insertRaw(opts: {
|
|
33
82
|
role: string;
|
|
34
83
|
content: string;
|
|
35
84
|
createdAt: number;
|
|
36
85
|
id?: string;
|
|
86
|
+
metadata?: string;
|
|
37
87
|
}): string {
|
|
38
88
|
const id = opts.id ?? `msg-${String(++seq).padStart(4, "0")}`;
|
|
39
89
|
getDb()
|
|
@@ -44,7 +94,7 @@ function insertRaw(opts: {
|
|
|
44
94
|
role: opts.role,
|
|
45
95
|
content: opts.content,
|
|
46
96
|
createdAt: opts.createdAt,
|
|
47
|
-
metadata: null,
|
|
97
|
+
metadata: opts.metadata ?? null,
|
|
48
98
|
})
|
|
49
99
|
.run();
|
|
50
100
|
return id;
|
|
@@ -269,3 +319,118 @@ describe("getRetrospectiveMessagesAfter", () => {
|
|
|
269
319
|
expect(slice.map((row) => row.id)).not.toContain(streaming);
|
|
270
320
|
});
|
|
271
321
|
});
|
|
322
|
+
|
|
323
|
+
describe("ambient camera frames are not retrospective work", () => {
|
|
324
|
+
beforeEach(() => {
|
|
325
|
+
const db = getDb();
|
|
326
|
+
db.run(`DELETE FROM messages`);
|
|
327
|
+
db.run(`DELETE FROM conversations`);
|
|
328
|
+
createConversation({ id: CONV });
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
/** One kept frame, as the live-voice persist writes it. */
|
|
332
|
+
function insertSightFrame(createdAt: number, attachmentId: string): string {
|
|
333
|
+
return insertRaw({
|
|
334
|
+
role: "user",
|
|
335
|
+
content: SIGHT_FRAME_CONTENT,
|
|
336
|
+
createdAt,
|
|
337
|
+
metadata: sightFrameMetadata(attachmentId),
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
test("a camera-only tail does not qualify as user activity", () => {
|
|
342
|
+
// A phone lying face up on a desk persists one of these every few
|
|
343
|
+
// seconds. Nobody said anything, so there is nothing to review.
|
|
344
|
+
insertSightFrame(1_000, "att-a");
|
|
345
|
+
insertSightFrame(2_000, "att-b");
|
|
346
|
+
insertSightFrame(3_000, "att-c");
|
|
347
|
+
|
|
348
|
+
expect(hasQualifyingUserMessageAfter(CONV, null)).toBe(false);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test("one real message among the frames does qualify", () => {
|
|
352
|
+
insertSightFrame(1_000, "att-a");
|
|
353
|
+
insertRaw({ role: "user", content: TEXT, createdAt: 2_000 });
|
|
354
|
+
insertSightFrame(3_000, "att-b");
|
|
355
|
+
|
|
356
|
+
expect(hasQualifyingUserMessageAfter(CONV, null)).toBe(true);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
test("frames are left out of the count that decides a run is due", () => {
|
|
360
|
+
insertSightFrame(1_000, "att-a");
|
|
361
|
+
insertSightFrame(2_000, "att-b");
|
|
362
|
+
|
|
363
|
+
expect(countRetrospectiveMessagesAfter(CONV, null)).toBe(0);
|
|
364
|
+
|
|
365
|
+
insertRaw({ role: "user", content: TEXT, createdAt: 3_000 });
|
|
366
|
+
|
|
367
|
+
expect(countRetrospectiveMessagesAfter(CONV, null)).toBe(1);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test("frames are left out of the slice a run would review", () => {
|
|
371
|
+
insertSightFrame(1_000, "att-a");
|
|
372
|
+
const real = insertRaw({
|
|
373
|
+
role: "user",
|
|
374
|
+
content: TEXT,
|
|
375
|
+
createdAt: 2_000,
|
|
376
|
+
});
|
|
377
|
+
insertSightFrame(3_000, "att-b");
|
|
378
|
+
|
|
379
|
+
const slice = getRetrospectiveMessagesAfter(CONV, null);
|
|
380
|
+
|
|
381
|
+
// The cutoff lands on the real message, so the frames are neither
|
|
382
|
+
// reviewed nor skipped over.
|
|
383
|
+
expect(slice.map((row) => row.id)).toEqual([real]);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test("a spoken turn that carried a frame is real work", () => {
|
|
387
|
+
// Camera mode parks a frame continuously, so in a camera call EVERY
|
|
388
|
+
// spoken turn carries the tag. Excluding on the tag alone would mean a
|
|
389
|
+
// conversation held entirely in camera mode never earns a retrospective.
|
|
390
|
+
const spoken = insertRaw({
|
|
391
|
+
role: "user",
|
|
392
|
+
content: TEXT,
|
|
393
|
+
createdAt: 1_000,
|
|
394
|
+
metadata: riddenTurnMetadata("att-frame-1"),
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
expect(hasQualifyingUserMessageAfter(CONV, null)).toBe(true);
|
|
398
|
+
expect(countRetrospectiveMessagesAfter(CONV, null)).toBe(1);
|
|
399
|
+
expect(getRetrospectiveMessagesAfter(CONV, null).map((r) => r.id)).toEqual([
|
|
400
|
+
spoken,
|
|
401
|
+
]);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test("an auto-sent row without the tag is untouched by this exclusion", () => {
|
|
405
|
+
// `scripted` covers onboarding sends and other machine-authored turns.
|
|
406
|
+
// They counted before the camera existed and must keep counting.
|
|
407
|
+
const onboarding = insertRaw({
|
|
408
|
+
role: "user",
|
|
409
|
+
content: TEXT,
|
|
410
|
+
createdAt: 1_000,
|
|
411
|
+
metadata: SCRIPTED_UNTAGGED_METADATA,
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
expect(countRetrospectiveMessagesAfter(CONV, null)).toBe(1);
|
|
415
|
+
expect(getRetrospectiveMessagesAfter(CONV, null).map((r) => r.id)).toEqual([
|
|
416
|
+
onboarding,
|
|
417
|
+
]);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
test("keeps are excluded even among the speech they punctuate", () => {
|
|
421
|
+
insertSightFrame(1_000, "att-a");
|
|
422
|
+
const spoken = insertRaw({
|
|
423
|
+
role: "user",
|
|
424
|
+
content: TEXT,
|
|
425
|
+
createdAt: 2_000,
|
|
426
|
+
metadata: riddenTurnMetadata("att-frame-1"),
|
|
427
|
+
});
|
|
428
|
+
insertSightFrame(3_000, "att-b");
|
|
429
|
+
|
|
430
|
+
expect(countRetrospectiveMessagesAfter(CONV, null)).toBe(1);
|
|
431
|
+
expect(getRetrospectiveMessagesAfter(CONV, null).map((r) => r.id)).toEqual([
|
|
432
|
+
spoken,
|
|
433
|
+
]);
|
|
434
|
+
expect(hasQualifyingUserMessageAfter(CONV, null)).toBe(true);
|
|
435
|
+
});
|
|
436
|
+
});
|
|
@@ -338,6 +338,9 @@ function makeForkConversationDouble(): Conversation {
|
|
|
338
338
|
const conversation = {
|
|
339
339
|
conversationId: FORK_ID,
|
|
340
340
|
messages,
|
|
341
|
+
// The wake trims its own run input; nothing here is tagged as a camera
|
|
342
|
+
// frame, so the real pass would return the array unchanged too.
|
|
343
|
+
trimAgedSightFrames: (msgs: Message[]) => msgs,
|
|
341
344
|
getMessages: () => messages,
|
|
342
345
|
isProcessing: () => false,
|
|
343
346
|
setProcessing: (_on: boolean) => {},
|
|
@@ -176,6 +176,9 @@ function makeForkConversationDouble(forkId: string): Conversation {
|
|
|
176
176
|
provider: scriptedProvider,
|
|
177
177
|
usageStats: {},
|
|
178
178
|
messages,
|
|
179
|
+
// The wake trims its own run input; nothing here is tagged as a camera
|
|
180
|
+
// frame, so the real pass would return the array unchanged too.
|
|
181
|
+
trimAgedSightFrames: (msgs: Message[]) => msgs,
|
|
179
182
|
getMessages: () => messages,
|
|
180
183
|
isProcessing: () => processing,
|
|
181
184
|
setProcessing: (on: boolean) => {
|
|
@@ -192,7 +192,7 @@ export interface MemoryJobsWorker {
|
|
|
192
192
|
|
|
193
193
|
/**
|
|
194
194
|
* Daemon-lifecycle entry point: spawn the memory jobs worker as a child of the
|
|
195
|
-
* daemon (
|
|
195
|
+
* daemon (so it appears in the daemon's process tree and is torn down
|
|
196
196
|
* on shutdown). The worker process is the sole drainer of the memory job queue.
|
|
197
197
|
* Fire-and-forget — a worker failure must never block boot. A worker that comes
|
|
198
198
|
* up late is still the desired sole drainer, so `terminateOnTimeout` is
|
|
@@ -205,7 +205,6 @@ export interface MemoryJobsWorker {
|
|
|
205
205
|
export function startMemoryJobsWorker(): void {
|
|
206
206
|
void spawnMemoryWorkerProcess({
|
|
207
207
|
terminateOnTimeout: false,
|
|
208
|
-
detached: false,
|
|
209
208
|
})
|
|
210
209
|
.then(({ pid, alreadyRunning }) =>
|
|
211
210
|
log.info(
|
|
@@ -16,6 +16,14 @@
|
|
|
16
16
|
// zero-new-messages early-out) and the job's new-message slice — without
|
|
17
17
|
// touching the generic persistence helpers' semantics for other callers.
|
|
18
18
|
//
|
|
19
|
+
// Ambient camera frames are excluded on the same three paths and for the same
|
|
20
|
+
// reason, one step further out: a live-voice call with the camera up persists
|
|
21
|
+
// a frame every few seconds whether or not anyone speaks, so counting them
|
|
22
|
+
// would let a phone lying face up on a desk keep declaring its conversation
|
|
23
|
+
// worth reviewing, and reviewing them would hand the model a wall to remember.
|
|
24
|
+
// The user-activity probe excludes them too, so a tail of nothing but frames
|
|
25
|
+
// never satisfies the `requireUserActivity` gate.
|
|
26
|
+
//
|
|
19
27
|
// The cursor is never blindly advanced over a card: the job still takes its
|
|
20
28
|
// cutoff from the last NON-card row of the filtered slice, so a real message
|
|
21
29
|
// that lands between the cutoff snapshot and the card insert can never be
|
|
@@ -30,10 +38,31 @@ import {
|
|
|
30
38
|
getMessagesAfter,
|
|
31
39
|
type MessageRow,
|
|
32
40
|
} from "../../../persistence/conversation-crud.js";
|
|
41
|
+
import {
|
|
42
|
+
messageMetadataIsAmbientSightKeep,
|
|
43
|
+
SIGHT_FRAME_ATTACHMENT_IDS_KEY,
|
|
44
|
+
} from "../../../persistence/conversation-types.js";
|
|
33
45
|
import { getDb } from "../../../persistence/db-connection.js";
|
|
34
46
|
import { messages } from "../../../persistence/schema/index.js";
|
|
35
47
|
import { SKILL_CARD_MESSAGE_KIND } from "./memory-retrospective-constants.js";
|
|
36
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Rows the retrospective must not account for: a prior run's own skill card,
|
|
51
|
+
* and ambient camera frames.
|
|
52
|
+
*
|
|
53
|
+
* A frame is machine-sampled, one every few seconds for as long as the camera
|
|
54
|
+
* is up, so counting them would let an idle camera pointed at a wall keep
|
|
55
|
+
* declaring a conversation worth reviewing. They are also the wrong thing to
|
|
56
|
+
* review: nobody said anything.
|
|
57
|
+
*/
|
|
58
|
+
function isExcludedFromRetrospectiveAccounting(row: {
|
|
59
|
+
metadata: string | null;
|
|
60
|
+
}): boolean {
|
|
61
|
+
return (
|
|
62
|
+
isSkillCardMessage(row) || messageMetadataIsAmbientSightKeep(row.metadata)
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
37
66
|
/** True when a message row's metadata carries the skill-card kind. */
|
|
38
67
|
export function isSkillCardMessage(row: { metadata: string | null }): boolean {
|
|
39
68
|
if (!row.metadata) {
|
|
@@ -75,7 +104,7 @@ export function getRetrospectiveMessagesAfter(
|
|
|
75
104
|
const firstUnfinalized = rows.findIndex((row) => row.finalized !== 1);
|
|
76
105
|
const bounded =
|
|
77
106
|
firstUnfinalized === -1 ? rows : rows.slice(0, firstUnfinalized);
|
|
78
|
-
return bounded.filter((row) => !
|
|
107
|
+
return bounded.filter((row) => !isExcludedFromRetrospectiveAccounting(row));
|
|
79
108
|
}
|
|
80
109
|
|
|
81
110
|
/**
|
|
@@ -93,8 +122,8 @@ export function countRetrospectiveMessagesAfter(
|
|
|
93
122
|
if (total === 0) {
|
|
94
123
|
return 0;
|
|
95
124
|
}
|
|
96
|
-
const
|
|
97
|
-
return Math.max(0, total -
|
|
125
|
+
const excluded = countExcludedMessagesAfter(conversationId, afterMessageId);
|
|
126
|
+
return Math.max(0, total - excluded);
|
|
98
127
|
}
|
|
99
128
|
|
|
100
129
|
/**
|
|
@@ -117,6 +146,12 @@ function blocksCarryNonToolResult(
|
|
|
117
146
|
* user-role row whose resolved content carries a non-tool_result block. Tool
|
|
118
147
|
* results ride on user-role rows, so a bare role check would count any
|
|
119
148
|
* tool-using assistant stretch as user activity.
|
|
149
|
+
*
|
|
150
|
+
* Takes rows rather than reading any itself, and its one caller passes the
|
|
151
|
+
* slice {@link getRetrospectiveMessagesAfter} already filtered, so ambient
|
|
152
|
+
* camera frames never reach it. A future caller assembling its own rows has to
|
|
153
|
+
* filter them first: a frame is a user-role row carrying an image block, which
|
|
154
|
+
* is indistinguishable from real activity by role and content alone.
|
|
120
155
|
*/
|
|
121
156
|
export function messagesHaveUserActivity(
|
|
122
157
|
rows: ReadonlyArray<Pick<MessageRow, "role" | "content">>,
|
|
@@ -136,7 +171,9 @@ export function messagesHaveUserActivity(
|
|
|
136
171
|
*
|
|
137
172
|
* Operates on the raw `content` column: rows that do not parse to a block
|
|
138
173
|
* array (file-backed `{ ref }` rows, legacy plain strings) count as
|
|
139
|
-
* user-authored, so the gate fails toward running the retrospective.
|
|
174
|
+
* user-authored, so the gate fails toward running the retrospective. Ambient
|
|
175
|
+
* camera frames are the one user-role row that does NOT count: the camera
|
|
176
|
+
* sampled them, so they must not be what wakes a review.
|
|
140
177
|
*/
|
|
141
178
|
export function hasQualifyingUserMessageAfter(
|
|
142
179
|
conversationId: string,
|
|
@@ -171,7 +208,7 @@ export function hasQualifyingUserMessageAfter(
|
|
|
171
208
|
]
|
|
172
209
|
: [];
|
|
173
210
|
const rows = db
|
|
174
|
-
.select({ content: messages.content })
|
|
211
|
+
.select({ content: messages.content, metadata: messages.metadata })
|
|
175
212
|
.from(messages)
|
|
176
213
|
.where(
|
|
177
214
|
and(
|
|
@@ -182,7 +219,11 @@ export function hasQualifyingUserMessageAfter(
|
|
|
182
219
|
)
|
|
183
220
|
.all();
|
|
184
221
|
|
|
185
|
-
return rows.some(
|
|
222
|
+
return rows.some(
|
|
223
|
+
(row) =>
|
|
224
|
+
!messageMetadataIsAmbientSightKeep(row.metadata) &&
|
|
225
|
+
rawUserContentCarriesActivity(row.content),
|
|
226
|
+
);
|
|
186
227
|
}
|
|
187
228
|
|
|
188
229
|
/** Raw-column twin of the block check used by {@link messagesHaveUserActivity}. */
|
|
@@ -211,7 +252,7 @@ function rawUserContentCarriesActivity(raw: string | null): boolean {
|
|
|
211
252
|
* tie-breaker semantics, including the null/`""` "count everything" cases
|
|
212
253
|
* and the vanished-reference "no new work" case.
|
|
213
254
|
*/
|
|
214
|
-
function
|
|
255
|
+
function countExcludedMessagesAfter(
|
|
215
256
|
conversationId: string,
|
|
216
257
|
afterMessageId: string | null,
|
|
217
258
|
): number {
|
|
@@ -230,7 +271,10 @@ function countSkillCardMessagesAfter(
|
|
|
230
271
|
.where(
|
|
231
272
|
and(
|
|
232
273
|
eq(messages.conversationId, conversationId),
|
|
233
|
-
|
|
274
|
+
or(
|
|
275
|
+
like(messages.metadata, `%"kind":"${SKILL_CARD_MESSAGE_KIND}"%`),
|
|
276
|
+
like(messages.metadata, `%"${SIGHT_FRAME_ATTACHMENT_IDS_KEY}"%`),
|
|
277
|
+
),
|
|
234
278
|
),
|
|
235
279
|
)
|
|
236
280
|
.all();
|
|
@@ -254,7 +298,7 @@ function countSkillCardMessagesAfter(
|
|
|
254
298
|
|
|
255
299
|
let count = 0;
|
|
256
300
|
for (const row of candidates) {
|
|
257
|
-
if (!
|
|
301
|
+
if (!isExcludedFromRetrospectiveAccounting(row)) {
|
|
258
302
|
continue;
|
|
259
303
|
}
|
|
260
304
|
if (cursorId !== null && ref) {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* The route handlers run inside the daemon and manage the worker process. We
|
|
5
5
|
* mock worker-control + the embedding backend so the tests assert handler
|
|
6
6
|
* behaviour:
|
|
7
|
-
* - start spawns
|
|
7
|
+
* - start spawns the worker and throws on failure.
|
|
8
8
|
* - stop signals the worker and reports its prior state.
|
|
9
9
|
* - status reports the worker process liveness + the embedding backend.
|
|
10
10
|
*/
|
|
@@ -16,7 +16,7 @@ import { getMemoryWorkerPidPath } from "../../../../../util/platform.js";
|
|
|
16
16
|
class FakeSpawnError extends Error {}
|
|
17
17
|
|
|
18
18
|
let spawnImpl: () => Promise<{ pid: number; alreadyRunning: boolean }>;
|
|
19
|
-
let spawnArgs: Array<{
|
|
19
|
+
let spawnArgs: Array<{ terminateOnTimeout?: boolean } | undefined> = [];
|
|
20
20
|
let stopImpl: () => { status: "running" | "not_running"; pid?: number };
|
|
21
21
|
let workerProbe: { status: "running" | "not_running"; pid?: number } = {
|
|
22
22
|
status: "not_running",
|
|
@@ -37,10 +37,7 @@ let backendStatus: {
|
|
|
37
37
|
|
|
38
38
|
mock.module("../../worker-control.js", () => ({
|
|
39
39
|
MemoryWorkerSpawnError: FakeSpawnError,
|
|
40
|
-
spawnMemoryWorkerProcess: async (opts: {
|
|
41
|
-
detached?: boolean;
|
|
42
|
-
terminateOnTimeout?: boolean;
|
|
43
|
-
}) => {
|
|
40
|
+
spawnMemoryWorkerProcess: async (opts: { terminateOnTimeout?: boolean }) => {
|
|
44
41
|
spawnArgs.push(opts);
|
|
45
42
|
return spawnImpl();
|
|
46
43
|
},
|
|
@@ -86,7 +83,7 @@ describe("memory_worker_start", () => {
|
|
|
86
83
|
|
|
87
84
|
const res = await handler("memory_worker_start")();
|
|
88
85
|
|
|
89
|
-
expect(spawnArgs).toEqual([
|
|
86
|
+
expect(spawnArgs).toEqual([undefined]);
|
|
90
87
|
expect(res).toEqual({
|
|
91
88
|
pid: 4242,
|
|
92
89
|
alreadyRunning: false,
|
|
@@ -62,7 +62,7 @@ const statusResponseSchema = z.object({
|
|
|
62
62
|
async function startMemoryWorker() {
|
|
63
63
|
let result: { pid: number; alreadyRunning: boolean };
|
|
64
64
|
try {
|
|
65
|
-
result = await spawnMemoryWorkerProcess(
|
|
65
|
+
result = await spawnMemoryWorkerProcess();
|
|
66
66
|
} catch (err) {
|
|
67
67
|
const message =
|
|
68
68
|
err instanceof MemoryWorkerSpawnError || err instanceof Error
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
} from "@vellumai/plugin-api";
|
|
5
5
|
import { and, eq, isNotNull, like, ne } from "drizzle-orm";
|
|
6
6
|
|
|
7
|
+
import { messageMetadataIsAmbientSightKeep } from "../../../../../persistence/conversation-types.js";
|
|
7
8
|
import { getDb } from "../../../../../persistence/db-connection.js";
|
|
8
9
|
import { withQdrantBreaker } from "../../../../../persistence/embeddings/qdrant-circuit-breaker.js";
|
|
9
10
|
import {
|
|
@@ -66,7 +67,11 @@ export async function rebuildIndexJob(): Promise<void> {
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
const imageMessages = db
|
|
69
|
-
.select({
|
|
70
|
+
.select({
|
|
71
|
+
id: messages.id,
|
|
72
|
+
content: messages.content,
|
|
73
|
+
metadata: messages.metadata,
|
|
74
|
+
})
|
|
70
75
|
.from(messages)
|
|
71
76
|
.where(
|
|
72
77
|
and(
|
|
@@ -76,6 +81,15 @@ export async function rebuildIndexJob(): Promise<void> {
|
|
|
76
81
|
)
|
|
77
82
|
.all();
|
|
78
83
|
for (const msg of imageMessages) {
|
|
84
|
+
// Ambient camera keeps were persisted with indexing skipped, and a
|
|
85
|
+
// rebuild must not be the thing that quietly puts them into memory
|
|
86
|
+
// after all. `skipIndexing` is a per-write option with no column behind
|
|
87
|
+
// it, so the row's own tag plus its `scripted` marker is the durable
|
|
88
|
+
// record of that choice. A spoken turn that merely CARRIED a frame is
|
|
89
|
+
// indexed by the live path and so is rebuilt like any other message.
|
|
90
|
+
if (messageMetadataIsAmbientSightKeep(msg.metadata)) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
79
93
|
const blocks = extractMediaBlockMeta(msg.content);
|
|
80
94
|
for (const block of blocks) {
|
|
81
95
|
enqueueMemoryJob("embed_attachment", {
|
|
@@ -37,8 +37,7 @@ export class MemoryWorkerSpawnError extends WorkerProcessSpawnError {}
|
|
|
37
37
|
* failed to start).
|
|
38
38
|
*
|
|
39
39
|
* See {@link SpawnWorkerProcessOptions} for the generic option semantics. The
|
|
40
|
-
* daemon's boot spawn
|
|
41
|
-
* `assistant ps` and is torn down with the daemon) and leaves
|
|
40
|
+
* daemon's boot spawn leaves
|
|
42
41
|
* `terminateOnTimeout` unset (a worker that comes up late is still the desired
|
|
43
42
|
* sole drainer).
|
|
44
43
|
*/
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
modelEffortCeilings,
|
|
3
|
+
modelSupportedEfforts,
|
|
4
|
+
} from "../model-catalog.js";
|
|
2
5
|
import { OpenAIChatCompletionsProvider } from "../openai/chat-completions-provider.js";
|
|
3
6
|
|
|
4
7
|
export interface FireworksProviderOptions {
|
|
@@ -10,6 +13,7 @@ export interface FireworksProviderOptions {
|
|
|
10
13
|
const DEFAULT_FIREWORKS_BASE_URL = "https://api.fireworks.ai/inference/v1";
|
|
11
14
|
|
|
12
15
|
const FIREWORKS_MODEL_EFFORT_CEILINGS = modelEffortCeilings("fireworks");
|
|
16
|
+
const FIREWORKS_MODEL_SUPPORTED_EFFORTS = modelSupportedEfforts("fireworks");
|
|
13
17
|
|
|
14
18
|
export class FireworksProvider extends OpenAIChatCompletionsProvider {
|
|
15
19
|
constructor(
|
|
@@ -39,4 +43,8 @@ export class FireworksProvider extends OpenAIChatCompletionsProvider {
|
|
|
39
43
|
): "high" | "xhigh" | "max" {
|
|
40
44
|
return FIREWORKS_MODEL_EFFORT_CEILINGS.get(model) ?? "high";
|
|
41
45
|
}
|
|
46
|
+
|
|
47
|
+
protected override resolveSupportedReasoningEfforts(model: string) {
|
|
48
|
+
return FIREWORKS_MODEL_SUPPORTED_EFFORTS.get(model);
|
|
49
|
+
}
|
|
42
50
|
}
|
|
@@ -62,23 +62,36 @@ const THINKING_LEVEL_BY_NAME: Record<ThinkingLevelName, ThinkingLevel> = {
|
|
|
62
62
|
*/
|
|
63
63
|
const GEMINI_PRO_DEFAULT_THINKING_LEVEL: ThinkingLevelName = "high";
|
|
64
64
|
|
|
65
|
+
function normalizeGeminiModelId(model: string): string {
|
|
66
|
+
return model.startsWith("models/") ? model.slice("models/".length) : model;
|
|
67
|
+
}
|
|
68
|
+
|
|
65
69
|
/**
|
|
66
70
|
* Gemini 3.x Pro family accepts only `low`/`medium`/`high` (no `"minimal"`) and
|
|
67
71
|
* cannot fully disable thinking. Matches `gemini-3.1-pro-preview`,
|
|
68
72
|
* `gemini-3.1-pro-preview-customtools`, and future `gemini-3*pro*`.
|
|
69
73
|
*/
|
|
70
74
|
function isGeminiProModel(model: string): boolean {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
+
return /^gemini-3.*pro/.test(normalizeGeminiModelId(model));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function findGeminiCatalogModel(model: string) {
|
|
79
|
+
const normalized = normalizeGeminiModelId(model);
|
|
80
|
+
return PROVIDER_CATALOG.find(
|
|
81
|
+
(provider) => provider.id === "gemini",
|
|
82
|
+
)?.models.find((m) => m.id === normalized);
|
|
75
83
|
}
|
|
76
84
|
|
|
77
85
|
/**
|
|
78
|
-
* Lowest thinking level the model accepts.
|
|
86
|
+
* Lowest thinking level the model accepts. Catalog `thinkingFloor` wins when
|
|
87
|
+
* present. Uncatalogued Gemini 3.x Pro IDs fall back to `"low"`; every other
|
|
79
88
|
* thinking-capable Gemini model accepts `"minimal"`.
|
|
80
89
|
*/
|
|
81
90
|
function geminiThinkingFloor(model: string): ThinkingLevelName {
|
|
91
|
+
const catalogFloor = findGeminiCatalogModel(model)?.thinkingFloor;
|
|
92
|
+
if (catalogFloor === "low" || catalogFloor === "minimal") {
|
|
93
|
+
return catalogFloor;
|
|
94
|
+
}
|
|
82
95
|
return isGeminiProModel(model) ? "low" : "minimal";
|
|
83
96
|
}
|
|
84
97
|
|
|
@@ -102,9 +115,9 @@ function clampThinkingLevelToFloor(
|
|
|
102
115
|
* Google's per-model default apply (e.g. `gemini-3.5-flash` defaults to
|
|
103
116
|
* dynamic medium-level thinking).
|
|
104
117
|
*
|
|
105
|
-
* - `enabled: false` maps to the model's floor
|
|
106
|
-
* allows
|
|
107
|
-
*
|
|
118
|
+
* - `enabled: false` maps to the model's floor (the most "off" state it
|
|
119
|
+
* allows): `"minimal"` for most models, `"low"` for Pro and Gemini 3.7
|
|
120
|
+
* Flash, which reject `"minimal"`.
|
|
108
121
|
* - An explicit `level` below the floor is raised to the floor.
|
|
109
122
|
* - When no `level` is pinned, Pro models get the documented default (`"high"`)
|
|
110
123
|
* because an absent level resolves to the unsupported `"minimal"` upstream;
|
|
@@ -161,13 +174,7 @@ function buildThinkingConfig(
|
|
|
161
174
|
* behavior); only an explicit `supportsThinking: false` suppresses it.
|
|
162
175
|
*/
|
|
163
176
|
function geminiModelSupportsThinking(model: string): boolean {
|
|
164
|
-
|
|
165
|
-
? model.slice("models/".length)
|
|
166
|
-
: model;
|
|
167
|
-
const catalogModel = PROVIDER_CATALOG.find(
|
|
168
|
-
(provider) => provider.id === "gemini",
|
|
169
|
-
)?.models.find((m) => m.id === normalized);
|
|
170
|
-
return catalogModel?.supportsThinking !== false;
|
|
177
|
+
return findGeminiCatalogModel(model)?.supportsThinking !== false;
|
|
171
178
|
}
|
|
172
179
|
|
|
173
180
|
function stripGeminiHttpOptions(
|
|
@@ -28,13 +28,14 @@ import {
|
|
|
28
28
|
sniffImageMimeType,
|
|
29
29
|
} from "../util/image-conversion.js";
|
|
30
30
|
import { getLogger } from "../util/logger.js";
|
|
31
|
-
import
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
import {
|
|
32
|
+
attachmentIdFragment,
|
|
33
|
+
type Base64MediaSource,
|
|
34
|
+
type ContentBlock,
|
|
35
|
+
type FileContent,
|
|
36
|
+
type ImageContent,
|
|
37
|
+
type MediaSource,
|
|
38
|
+
type Message,
|
|
38
39
|
} from "./types.js";
|
|
39
40
|
|
|
40
41
|
const log = getLogger("media-resolve");
|
|
@@ -143,6 +144,21 @@ export function mediaSourceByteLength(source: MediaSource): number {
|
|
|
143
144
|
return Math.floor((source.data.length * 3) / 4);
|
|
144
145
|
}
|
|
145
146
|
|
|
147
|
+
/**
|
|
148
|
+
* What a media source was, as the text that stands in for it once the bytes are
|
|
149
|
+
* gone: its media type and payload size.
|
|
150
|
+
*
|
|
151
|
+
* Every path that replaces a media block with a text stub naming what it
|
|
152
|
+
* dropped reports these same two facts, so they are derived once here. Each
|
|
153
|
+
* stub keeps its own surrounding sentence (which path dropped it, and why) and
|
|
154
|
+
* embeds this for the what. Reads the size through
|
|
155
|
+
* {@link mediaSourceByteLength}, so a reference and an inline block describe
|
|
156
|
+
* themselves the same way.
|
|
157
|
+
*/
|
|
158
|
+
export function mediaSourceDescriptor(source: MediaSource): string {
|
|
159
|
+
return `${source.media_type}, ${mediaSourceByteLength(source)} bytes`;
|
|
160
|
+
}
|
|
161
|
+
|
|
146
162
|
/**
|
|
147
163
|
* Resolve a media source to inline base64, reading a reference source back from
|
|
148
164
|
* its workspace location. Returns `null` when a reference can no longer be
|
|
@@ -280,9 +296,7 @@ function resolveFileBlock(block: FileContent): ContentBlock {
|
|
|
280
296
|
...(block.extracted_text !== undefined
|
|
281
297
|
? { extracted_text: block.extracted_text }
|
|
282
298
|
: {}),
|
|
283
|
-
...(block._attachmentId
|
|
284
|
-
? { _attachmentId: block._attachmentId }
|
|
285
|
-
: {}),
|
|
299
|
+
...attachmentIdFragment(block._attachmentId),
|
|
286
300
|
};
|
|
287
301
|
}
|
|
288
302
|
|