@vellumai/assistant 0.11.4 → 0.11.5-dev.202608220019.a778cf0
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/AGENTS.md +6 -2
- package/ARCHITECTURE.md +29 -4
- package/README.md +1 -1
- package/docs/architecture/memory.md +17 -5
- package/docs/architecture/security.md +96 -152
- package/docs/guardian-request-flow.md +13 -2
- 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/__tests__/ingress.test.ts +118 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -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/ingress.ts +103 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -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/__tests__/ingress.test.ts +118 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -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/ingress.ts +103 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
- package/node_modules/@vellumai/gateway-client/src/http-delivery.ts +7 -14
- package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +30 -32
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +118 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +103 -0
- package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/openapi.yaml +487 -11
- package/package.json +1 -1
- package/scripts/sync-web-search-catalog.ts +6 -0
- package/src/__tests__/anthropic-provider.test.ts +10 -3
- package/src/__tests__/approval-interception-trust-gates.test.ts +40 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
- package/src/__tests__/channel-approval-routes.test.ts +4 -0
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -6
- package/src/__tests__/channel-readiness-service.test.ts +126 -0
- package/src/__tests__/channel-reply-delivery.test.ts +30 -17
- package/src/__tests__/checker.test.ts +20 -314
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
- package/src/__tests__/client-os-metadata-persistence.test.ts +16 -0
- package/src/__tests__/compactor-retained-image-validation.test.ts +142 -0
- package/src/__tests__/config-loader-backfill.test.ts +19 -10
- package/src/__tests__/container-cpu-sampler.test.ts +153 -0
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
- package/src/__tests__/conversation-attachments.test.ts +0 -1
- package/src/__tests__/conversation-delete-watch-timeline.test.ts +231 -0
- package/src/__tests__/conversation-error.test.ts +17 -0
- package/src/__tests__/conversation-lifecycle.test.ts +20 -26
- package/src/__tests__/conversation-pairing.test.ts +186 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +21 -2
- package/src/__tests__/credential-broker-server-use.test.ts +24 -5
- package/src/__tests__/credential-routes.test.ts +22 -3
- package/src/__tests__/credential-security-invariants.test.ts +2 -1
- package/src/__tests__/daemon-credential-client.test.ts +88 -0
- package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
- package/src/__tests__/dm-backfill.test.ts +63 -0
- package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
- package/src/__tests__/edit-propagation.test.ts +107 -4
- package/src/__tests__/events-client-registration.test.ts +28 -0
- package/src/__tests__/file-write-tool.test.ts +4 -2
- package/src/__tests__/guardian-prompt-notice-privacy.test.ts +133 -0
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +143 -97
- package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
- package/src/__tests__/host-proxy-interface.test.ts +11 -1
- package/src/__tests__/host-shell-tool.test.ts +23 -4
- package/src/__tests__/image-conversion.test.ts +143 -1
- package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
- package/src/__tests__/input-repairs.test.ts +207 -0
- package/src/__tests__/live-workspace-guard.test.ts +75 -0
- package/src/__tests__/managed-profile-guard.test.ts +4 -2
- package/src/__tests__/mcp-abort-signal.test.ts +1 -1
- package/src/__tests__/mcp-client-auth.test.ts +1 -1
- package/src/__tests__/mcp-tool-annotations-risk.test.ts +1 -1
- package/src/__tests__/media-resolve-image-validation.test.ts +310 -0
- package/src/__tests__/mtime-cache.test.ts +2 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +124 -1
- package/src/__tests__/openai-provider.test.ts +40 -0
- package/src/__tests__/openai-responses-provider.test.ts +18 -0
- package/src/__tests__/personal-memory-auth-bypass.test.ts +22 -6
- package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
- package/src/__tests__/platform.test.ts +16 -1
- package/src/__tests__/plugin-api-resolve-credential.test.ts +22 -13
- package/src/__tests__/plugin-api-shim.test.ts +5 -0
- package/src/__tests__/plugin-api-store-credential.test.ts +268 -0
- package/src/__tests__/plugin-config-data-migration.test.ts +8 -1
- package/src/__tests__/plugin-disabled-state.test.ts +2 -0
- package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
- package/src/__tests__/plugin-execution-context.test.ts +73 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +0 -1
- package/src/__tests__/reaction-persistence.test.ts +200 -7
- package/src/__tests__/require-fresh-approval.test.ts +0 -4
- package/src/__tests__/resource-pressure-guard.test.ts +428 -0
- package/src/__tests__/resource-pressure-routes.test.ts +113 -0
- package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +27 -1
- package/src/__tests__/sanitize-config-for-transfer.test.ts +28 -0
- package/src/__tests__/secret-routes-platform-proxy.test.ts +7 -0
- package/src/__tests__/settings-routes.test.ts +85 -3
- package/src/__tests__/skill-tool-factory.test.ts +161 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
- package/src/__tests__/tool-executor.test.ts +63 -38
- package/src/__tests__/tool-policy.test.ts +97 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +7 -4
- package/src/__tests__/user-plugin-loader.test.ts +2 -0
- package/src/__tests__/validate-input.test.ts +243 -7
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -0
- package/src/__tests__/voice-session-bridge.test.ts +42 -0
- package/src/__tests__/web-search-catalog-parity.test.ts +8 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +97 -29
- package/src/acp/__tests__/prepare-agent-env.test.ts +376 -153
- package/src/acp/acp-claude-oauth.ts +38 -21
- package/src/acp/prepare-agent-env.ts +146 -55
- package/src/agent/loop-tool-dedup.test.ts +161 -0
- package/src/agent/loop.ts +68 -22
- package/src/api/constants/app-tools.ts +34 -0
- package/src/api/constants/profile-config-validation.ts +60 -0
- package/src/api/events/resource-pressure-status-changed.ts +53 -0
- package/src/api/events/tool-result.ts +6 -1
- package/src/api/events/watch-retro-completed.ts +52 -0
- package/src/api/index.ts +30 -0
- package/src/api/responses/resource-pressure-status.ts +25 -0
- package/src/approvals/guardian-channel-delivery.ts +70 -6
- package/src/approvals/guardian-request-resolvers.ts +32 -66
- package/src/calls/__tests__/voice-session-bridge.test.ts +114 -0
- package/src/calls/voice-session-bridge.ts +60 -6
- package/src/channels/__tests__/message-audience.test.ts +49 -0
- package/src/channels/__tests__/types.test.ts +17 -3
- package/src/channels/message-audience.ts +40 -0
- package/src/channels/types.ts +16 -12
- package/src/cli/__tests__/catalog-search-help.test.ts +50 -0
- package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +31 -0
- package/src/cli/commands/__tests__/conversations-search.test.ts +289 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +2 -1
- package/src/cli/commands/__tests__/inference-profiles.test.ts +16 -4
- package/src/cli/commands/__tests__/inference-providers.test.ts +135 -2
- package/src/cli/commands/__tests__/keys.test.ts +89 -8
- package/src/cli/commands/__tests__/plugins.test.ts +57 -1
- package/src/cli/commands/channel-verification-sessions.help.ts +11 -9
- package/src/cli/commands/channel-verification-sessions.ts +11 -21
- package/src/cli/commands/conversations.help.ts +34 -0
- package/src/cli/commands/conversations.ts +125 -0
- package/src/cli/commands/inference-profiles.ts +56 -3
- package/src/cli/commands/inference-providers.ts +37 -6
- package/src/cli/commands/inference.help.ts +9 -4
- package/src/cli/commands/keys.help.ts +13 -1
- package/src/cli/commands/keys.ts +26 -15
- package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
- package/src/cli/commands/memory/index.help.ts +41 -27
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/memory-v2.ts +58 -54
- package/src/cli/commands/memory/memory-validate.ts +18 -0
- package/src/cli/commands/monitoring.ts +1 -0
- package/src/cli/commands/oauth/request.test.ts +93 -0
- package/src/cli/commands/oauth/request.ts +23 -9
- package/src/cli/commands/plugins.help.ts +28 -31
- package/src/cli/commands/plugins.ts +10 -0
- package/src/cli/commands/skills.help.ts +13 -16
- package/src/cli/commands/trust.ts +3 -14
- package/src/cli/lib/__tests__/install-from-github.test.ts +38 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +34 -0
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +32 -1
- package/src/cli/lib/__tests__/toggle-plugin.test.ts +2 -1
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +64 -0
- package/src/cli/lib/bundled-marketplace.json +26 -0
- package/src/cli/lib/daemon-credential-client.ts +25 -3
- package/src/cli/lib/install-from-github.ts +35 -24
- package/src/cli/lib/merge-plugin-tree.ts +22 -4
- package/src/cli/lib/open-browser.test.ts +67 -0
- package/src/cli/lib/open-browser.ts +24 -5
- package/src/cli/lib/plugin-surfaces.ts +27 -0
- package/src/config/__tests__/balanced-model-experiment.test.ts +7 -7
- package/src/config/__tests__/default-profile-catalog.test.ts +3 -3
- package/src/config/__tests__/profile-materialization.test.ts +26 -0
- package/src/config/default-profile-catalog.ts +5 -6
- package/src/config/feature-flag-registry.json +36 -28
- package/src/config/profile-materialization.ts +29 -0
- package/src/config/sanitize-for-transfer.ts +16 -0
- package/src/config/schemas/llm.ts +7 -0
- package/src/config/schemas/services.ts +6 -0
- package/src/config/webhook-routing.ts +8 -0
- package/src/context/compactor.ts +31 -2
- package/src/context/outbound-sanitize.ts +6 -0
- package/src/daemon/__tests__/lifecycle-watch-timeline-sweep.test.ts +98 -0
- package/src/daemon/__tests__/provider-rejection-log-fields.test.ts +272 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +4 -1
- package/src/daemon/conversation-error.ts +19 -2
- package/src/daemon/conversation-messaging.ts +22 -2
- package/src/daemon/conversation-process.ts +11 -0
- package/src/daemon/conversation-store.ts +10 -0
- package/src/daemon/conversation-surfaces.ts +21 -8
- package/src/daemon/conversation.ts +12 -10
- package/src/daemon/daemon-control.ts +1 -0
- package/src/daemon/handlers/__tests__/config-ingress-tunnel-records.test.ts +208 -0
- package/src/daemon/handlers/config-ingress.ts +115 -5
- package/src/daemon/lifecycle.ts +30 -2
- package/src/daemon/message-types/conversations.ts +5 -7
- package/src/daemon/message-types/web-activity.ts +3 -2
- package/src/daemon/provider-rejection-log-fields.ts +124 -0
- package/src/daemon/resource-pressure-guard-lifecycle.ts +66 -0
- package/src/daemon/resource-pressure-guard.ts +387 -0
- package/src/daemon/shutdown-handlers.ts +2 -0
- package/src/daemon/startup-error.ts +16 -0
- package/src/daemon/trust-context.ts +14 -10
- package/src/daemon/unsendable-image-notice.ts +76 -0
- package/src/hooks/registry.ts +65 -51
- package/src/inbound/__tests__/tunnel-probe.test.ts +448 -0
- package/src/inbound/tunnel-probe.ts +261 -0
- package/src/ipc/__tests__/socket-path.test.ts +15 -7
- package/src/ipc/gateway-client.test.ts +1 -1
- package/src/ipc/gateway-client.ts +14 -21
- package/src/ipc/socket-cleanup.ts +2 -11
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -1
- package/src/messaging/provider-message-metadata.ts +128 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +138 -73
- package/src/messaging/providers/channel-transport.ts +100 -16
- package/src/messaging/providers/discord/send.ts +16 -0
- package/src/messaging/providers/discord/transport.ts +23 -1
- package/src/messaging/providers/index.ts +84 -16
- package/src/messaging/providers/slack/message-metadata.ts +45 -0
- package/src/messaging/providers/slack/render-transcript.ts +1 -4
- package/src/messaging/providers/slack/send.test.ts +88 -34
- package/src/messaging/providers/slack/send.ts +99 -50
- package/src/messaging/providers/slack/transport.ts +35 -37
- package/src/messaging/providers/telegram-bot/send.test.ts +88 -18
- package/src/messaging/providers/telegram-bot/send.ts +36 -0
- package/src/messaging/providers/telegram-bot/transport.ts +22 -8
- package/src/messaging/read-provider-metadata.test.ts +157 -0
- package/src/messaging/read-provider-metadata.ts +51 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +94 -0
- package/src/notifications/__tests__/edit-notification.test.ts +174 -2
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +536 -4
- package/src/notifications/adapters/macos.ts +55 -0
- package/src/notifications/adapters/slack.ts +10 -5
- package/src/notifications/assistant-reply-producer.ts +37 -4
- package/src/notifications/conversation-pairing.ts +111 -21
- package/src/notifications/edit-notification.ts +38 -8
- package/src/notifications/emit-signal.ts +12 -14
- package/src/notifications/home-feed-side-effect.ts +218 -15
- package/src/notifications/types.ts +5 -0
- package/src/permissions/AGENTS.md +16 -0
- package/src/permissions/checker.test.ts +75 -122
- package/src/permissions/checker.ts +44 -560
- package/src/permissions/confirmation-guardian-request.test.ts +28 -3
- package/src/permissions/confirmation-guardian-request.ts +5 -1
- package/src/persistence/conversation-crud.ts +69 -20
- package/src/persistence/conversation-queries.ts +14 -1
- package/src/persistence/conversation-types.ts +22 -0
- package/src/persistence/db-async-query.ts +2 -1
- package/src/persistence/db-maintenance.ts +15 -0
- package/src/persistence/delivery-crud.ts +122 -15
- package/src/persistence/embeddings/qdrant-manager.ts +1 -0
- package/src/persistence/migrations/367-create-watch-timeline-entries.ts +46 -0
- package/src/persistence/migrations/368-watch-timeline-screenshot-blob.ts +33 -0
- package/src/persistence/migrations/__tests__/367-create-watch-timeline-entries.test.ts +98 -0
- package/src/persistence/migrations/__tests__/368-watch-timeline-screenshot-blob.test.ts +98 -0
- package/src/persistence/schema/index.ts +1 -0
- package/src/persistence/schema/watch.ts +29 -0
- package/src/persistence/steps.ts +4 -0
- package/src/plugin-api/__tests__/import-graph-partial-mock.test.ts +67 -0
- package/src/plugin-api/conversation-turn.ts +27 -0
- package/src/plugin-api/credential-scope.test.ts +15 -0
- package/src/plugin-api/credential-scope.ts +14 -0
- package/src/plugin-api/index.ts +19 -1
- package/src/plugin-api/resolve-credential.ts +15 -10
- package/src/plugin-api/store-credential.ts +148 -0
- package/src/plugin-api/system-card.ts +36 -0
- package/src/plugin-api/vision-support.test.ts +82 -0
- package/src/plugin-api/vision-support.ts +14 -2
- package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +361 -110
- package/src/plugins/defaults/image-fallback/__tests__/vision-recovery.test.ts +4 -0
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +58 -0
- package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +64 -23
- package/src/plugins/defaults/image-recovery/detect.ts +24 -1
- package/src/plugins/defaults/main.ts +15 -0
- package/src/plugins/defaults/memory/AGENTS.md +11 -3
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +9 -12
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
- package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +114 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
- package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
- package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
- package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
- package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
- package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
- package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +1 -61
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +68 -6
- package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
- package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
- package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
- package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
- package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
- package/src/plugins/defaults/memory/substrate/static-context.ts +0 -29
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +3 -1
- package/src/plugins/defaults/memory/v3/card.ts +9 -9
- package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
- package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
- package/src/plugins/defaults/memory/v3/edge.ts +13 -57
- package/src/plugins/mtime-cache.ts +1 -1
- package/src/plugins/pipeline.ts +14 -7
- package/src/plugins/plugin-execution-context.ts +35 -11
- package/src/plugins/plugin-tree-walk.ts +63 -4
- package/src/providers/__tests__/retry-network-error.test.ts +84 -0
- package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
- package/src/providers/anthropic/client.ts +332 -241
- package/src/providers/connection-resolution.ts +25 -2
- package/src/providers/content-blocks.ts +22 -0
- package/src/providers/fetch-provider-catalog.ts +19 -0
- package/src/providers/gemini/client.ts +5 -2
- package/src/providers/inference/__tests__/adapter-factory-litellm.test.ts +47 -1
- package/src/providers/inference/__tests__/adapter-factory-ollama.test.ts +83 -0
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +98 -1
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +38 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +10 -0
- package/src/providers/inference/__tests__/connections-ollama.test.ts +90 -0
- package/src/providers/inference/__tests__/endpoint-probe.test.ts +92 -0
- package/src/providers/inference/__tests__/missing-credential-guard.test.ts +117 -0
- package/src/providers/inference/__tests__/profile-config-validation.test.ts +39 -0
- package/src/providers/inference/__tests__/profile-probe-classify.test.ts +66 -0
- package/src/providers/inference/adapter-factory.ts +33 -2
- package/src/providers/inference/auth.ts +13 -2
- package/src/providers/inference/credential-usage.ts +37 -0
- package/src/providers/inference/endpoint-probe.ts +115 -0
- package/src/providers/inference/missing-credential-guard.ts +110 -0
- package/src/providers/inference/profile-probe.ts +256 -0
- package/src/providers/inference/resolve-auth.ts +7 -7
- package/src/providers/media-resolve.ts +176 -15
- package/src/providers/model-catalog.ts +10 -0
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +17 -1
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +576 -46
- package/src/providers/openai/__tests__/connection-error-wrap.test.ts +44 -0
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
- package/src/providers/openai/api-error-normalization.ts +16 -2
- package/src/providers/openai/chat-completions-provider.ts +326 -44
- package/src/providers/openai/responses-provider.ts +5 -2
- package/src/providers/provider-send-message.ts +11 -0
- package/src/providers/retry.ts +6 -0
- package/src/providers/search-provider-catalog.ts +20 -0
- package/src/routes/route-host-protocol.ts +7 -0
- package/src/routes/worker.ts +31 -10
- package/src/runtime/AGENTS.md +36 -0
- package/src/runtime/__tests__/host-observe.test.ts +302 -0
- package/src/runtime/__tests__/runtime-http-port-collision.test.ts +131 -0
- package/src/runtime/__tests__/web-presence.test.ts +234 -0
- package/src/runtime/assistant-event-hub.ts +38 -0
- package/src/runtime/channel-readiness-service.ts +39 -13
- package/src/runtime/channel-readiness-types.ts +49 -2
- package/src/runtime/channel-reply-delivery.ts +55 -32
- package/src/runtime/channel-retry-sweep.ts +6 -4
- package/src/runtime/effective-capabilities.test.ts +19 -0
- package/src/runtime/effective-capabilities.ts +25 -0
- package/src/runtime/guardian-reply-router.ts +6 -2
- package/src/runtime/host-observe.ts +246 -0
- package/src/runtime/http-errors.ts +1 -0
- package/src/runtime/http-server.ts +185 -6
- package/src/runtime/routes/__tests__/client-routes.test.ts +150 -0
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +133 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +80 -0
- package/src/runtime/routes/__tests__/credential-delete-in-use.test.ts +155 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +118 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +72 -1
- package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +508 -0
- package/src/runtime/routes/__tests__/monitoring-routes.test.ts +1 -0
- package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +175 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +103 -71
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
- package/src/runtime/routes/__tests__/system-card-turn-termination.test.ts +107 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +75 -4
- package/src/runtime/routes/__tests__/watch-routes-guardian-cache.test.ts +139 -0
- package/src/runtime/routes/__tests__/watch-routes.test.ts +598 -0
- package/src/runtime/routes/approval-routes.ts +37 -4
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +16 -22
- package/src/runtime/routes/canned-message-complete.ts +68 -29
- package/src/runtime/routes/channel-readiness-routes.ts +14 -2
- package/src/runtime/routes/client-routes.ts +113 -0
- package/src/runtime/routes/conversation-list-routes.ts +22 -7
- package/src/runtime/routes/conversation-management-routes.ts +15 -9
- package/src/runtime/routes/conversation-query-routes.ts +10 -0
- package/src/runtime/routes/conversation-routes.ts +36 -22
- package/src/runtime/routes/credential-in-use.ts +85 -0
- package/src/runtime/routes/credential-routes.ts +55 -85
- package/src/runtime/routes/guardian-approval-interception.ts +44 -53
- package/src/runtime/routes/guardian-approval-reply-helpers.ts +14 -12
- package/src/runtime/routes/host-cu-routes.ts +18 -0
- package/src/runtime/routes/identity-routes.ts +5 -121
- package/src/runtime/routes/inbound-message-handler.ts +74 -30
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -17
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +182 -297
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +173 -386
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -77
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +17 -8
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -7
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -70
- package/src/runtime/routes/index.ts +4 -0
- package/src/runtime/routes/inference-profiles-routes.ts +232 -31
- package/src/runtime/routes/inference-provider-connection-routes.ts +33 -11
- package/src/runtime/routes/ingress-status-routes.ts +180 -0
- package/src/runtime/routes/monitoring-routes.ts +9 -1
- package/src/runtime/routes/oauth-commands-routes.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +28 -32
- package/src/runtime/routes/resource-pressure-routes.ts +22 -0
- package/src/runtime/routes/secret-routes.ts +39 -8
- package/src/runtime/routes/settings-routes.ts +15 -12
- package/src/runtime/routes/surface-action-routes.ts +20 -19
- package/src/runtime/routes/user-route-dispatcher.ts +31 -2
- package/src/runtime/routes/user-route-resolution.ts +18 -1
- package/src/runtime/routes/watch-routes.ts +807 -0
- package/src/runtime/slack-reply-session.test.ts +23 -13
- package/src/runtime/slack-reply-session.ts +38 -55
- package/src/runtime/web-presence.ts +90 -0
- package/src/security/__tests__/untrusted-content.test.ts +42 -0
- package/src/security/untrusted-content.ts +28 -9
- package/src/skills/validate-input.ts +177 -40
- package/src/subagent/__tests__/consult-context-gating.test.ts +6 -10
- package/src/subagent/consult-context.ts +7 -23
- package/src/tools/credentials/broker.ts +24 -79
- package/src/tools/credentials/ref-parse.ts +35 -0
- package/src/tools/credentials/resolve.ts +4 -10
- package/src/tools/credentials/store.ts +168 -0
- package/src/tools/credentials/tool-policy.ts +67 -0
- package/src/tools/executor.ts +53 -15
- package/src/tools/network/__tests__/firecrawl-compat.test.ts +77 -0
- package/src/tools/network/__tests__/web-fetch-fastcrw.test.ts +169 -0
- package/src/tools/network/__tests__/web-search.test.ts +138 -3
- package/src/tools/network/firecrawl-compat.ts +90 -0
- package/src/tools/network/url-safety.ts +9 -16
- package/src/tools/network/web-fetch.ts +142 -62
- package/src/tools/network/web-search.ts +162 -58
- package/src/tools/permission-checker.ts +43 -52
- package/src/tools/schema-transforms.ts +40 -5
- package/src/tools/shared/input-repairs.ts +160 -0
- package/src/tools/skills/skill-tool-factory.ts +18 -4
- package/src/tools/subagent/spawn.ts +0 -1
- package/src/tools/tool-approval-handler.ts +16 -10
- package/src/tools/tool-types.ts +26 -13
- package/src/tools/types.ts +6 -6
- package/src/util/__tests__/cgroup-memory.test.ts +3 -0
- package/src/util/cgroup-memory.ts +3 -0
- package/src/util/container-cpu-sampler.ts +250 -0
- package/src/util/image-conversion.ts +178 -14
- package/src/util/platform.ts +93 -10
- package/src/util/worker-process.ts +1 -0
- package/src/watch/__tests__/watch-retro.test.ts +665 -0
- package/src/watch/__tests__/watch-session-manager.test.ts +566 -0
- package/src/watch/__tests__/watch-timeline.test.ts +670 -0
- package/src/watch/watch-retro.ts +480 -0
- package/src/watch/watch-session-manager.ts +575 -0
- package/src/watch/watch-timeline.ts +848 -0
- package/src/permissions/ipc-risk-types.ts +0 -143
- package/src/permissions/risk-types.ts +0 -76
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for the confirmation
|
|
3
|
-
* in-flight race
|
|
4
|
-
* create is still pending must not strand a pending tool_approval row
|
|
2
|
+
* Tests for the confirmation to guardian-request promotion, including the
|
|
3
|
+
* in-flight race (a confirmation resolved while the fire-and-forget gateway
|
|
4
|
+
* create is still pending must not strand a pending tool_approval row) and
|
|
5
|
+
* the deadline, which is derived from the approval-window resolver rather
|
|
6
|
+
* than restated as a constant.
|
|
5
7
|
*/
|
|
6
8
|
|
|
7
9
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
@@ -52,6 +54,15 @@ mock.module("../runtime/confirmation-request-guardian-bridge.js", () => ({
|
|
|
52
54
|
},
|
|
53
55
|
}));
|
|
54
56
|
|
|
57
|
+
// Deliberately not 300_000, the resolver's default. A fixture on the default
|
|
58
|
+
// passes whether the deadline is read from the resolver or restated as a
|
|
59
|
+
// constant beside it; this value only matches if it is read.
|
|
60
|
+
const APPROVAL_WINDOW_MS = 900_000;
|
|
61
|
+
|
|
62
|
+
mock.module("../tools/tool-approval-handler.js", () => ({
|
|
63
|
+
resolveInlineGrantWaitMs: () => APPROVAL_WINDOW_MS,
|
|
64
|
+
}));
|
|
65
|
+
|
|
55
66
|
import { asConversation } from "../__tests__/helpers/mock-conversation.js";
|
|
56
67
|
import type { AssistantEvent } from "../api/index.js";
|
|
57
68
|
import type { TrustContext } from "../daemon/trust-context-types.js";
|
|
@@ -86,6 +97,20 @@ describe("createGuardianRequestForConfirmation", () => {
|
|
|
86
97
|
expect(bridgeCalls).toHaveLength(1);
|
|
87
98
|
});
|
|
88
99
|
|
|
100
|
+
test("takes the row deadline from the approval-window resolver", async () => {
|
|
101
|
+
const before = Date.now();
|
|
102
|
+
await createGuardianRequestForConfirmation(MSG, "conv-1");
|
|
103
|
+
const after = Date.now();
|
|
104
|
+
|
|
105
|
+
expect(createCalls).toHaveLength(1);
|
|
106
|
+
const expiresAt = createCalls[0].expiresAt as number;
|
|
107
|
+
|
|
108
|
+
// Bracketed against the clock either side of the call rather than compared
|
|
109
|
+
// to a single Date.now(), so the assertion cannot flake on a slow tick.
|
|
110
|
+
expect(expiresAt).toBeGreaterThanOrEqual(before + APPROVAL_WINDOW_MS);
|
|
111
|
+
expect(expiresAt).toBeLessThanOrEqual(after + APPROVAL_WINDOW_MS);
|
|
112
|
+
});
|
|
113
|
+
|
|
89
114
|
test("expires the row and skips the bridge when the confirmation resolved mid-create", async () => {
|
|
90
115
|
confirmationPending = false;
|
|
91
116
|
|
|
@@ -55,6 +55,7 @@ export async function createGuardianRequestForConfirmation(
|
|
|
55
55
|
{ summarizeToolInput },
|
|
56
56
|
{ DAEMON_INTERNAL_ASSISTANT_ID },
|
|
57
57
|
{ bridgeConfirmationRequestToGuardian },
|
|
58
|
+
{ resolveInlineGrantWaitMs },
|
|
58
59
|
] = await Promise.all([
|
|
59
60
|
import("../daemon/conversation-registry.js"),
|
|
60
61
|
import("../channels/gateway-guardian-requests.js"),
|
|
@@ -62,6 +63,7 @@ export async function createGuardianRequestForConfirmation(
|
|
|
62
63
|
import("../tools/tool-input-summary.js"),
|
|
63
64
|
import("../runtime/assistant-scope.js"),
|
|
64
65
|
import("../runtime/confirmation-request-guardian-bridge.js"),
|
|
66
|
+
import("../tools/tool-approval-handler.js"),
|
|
65
67
|
]);
|
|
66
68
|
|
|
67
69
|
const conversation = findConversation(conversationId);
|
|
@@ -100,7 +102,9 @@ export async function createGuardianRequestForConfirmation(
|
|
|
100
102
|
activityText: activityRaw ? redactSecrets(activityRaw) : undefined,
|
|
101
103
|
executionTarget: msg.executionTarget,
|
|
102
104
|
status: "pending",
|
|
103
|
-
|
|
105
|
+
// The row stays decidable exactly as long as the prompt stays parked:
|
|
106
|
+
// one budget, so raising `timeouts.permissionTimeoutSec` moves both.
|
|
107
|
+
expiresAt: Date.now() + resolveInlineGrantWaitMs(),
|
|
104
108
|
});
|
|
105
109
|
|
|
106
110
|
// The prompt is actionable before this fire-and-forget create lands, so
|
|
@@ -50,6 +50,10 @@ import {
|
|
|
50
50
|
isRetryableSqliteError,
|
|
51
51
|
withSqliteRetry,
|
|
52
52
|
} from "../util/sqlite-retry.js";
|
|
53
|
+
import {
|
|
54
|
+
purgeAllWatchTimelines,
|
|
55
|
+
purgeWatchTimelineForConversation,
|
|
56
|
+
} from "../watch/watch-timeline.js";
|
|
53
57
|
import {
|
|
54
58
|
deleteOrphanAttachments,
|
|
55
59
|
linkAttachmentToMessage,
|
|
@@ -87,6 +91,7 @@ import {
|
|
|
87
91
|
type ConversationCreateType,
|
|
88
92
|
type ConversationOrigin,
|
|
89
93
|
isHiddenMessageMetadata,
|
|
94
|
+
isSystemCardMetadata,
|
|
90
95
|
PINNED_GROUP_ID,
|
|
91
96
|
UNGROUPED_GROUP_ID,
|
|
92
97
|
} from "./conversation-types.js";
|
|
@@ -190,6 +195,36 @@ function dedicatedTableExists(db: DrizzleDb, table: string): boolean {
|
|
|
190
195
|
* keys on `conversation_id` regardless of event name, so this covers every
|
|
191
196
|
* conversation-scoped pending event.
|
|
192
197
|
*/
|
|
198
|
+
/**
|
|
199
|
+
* Remove a conversation's watch-session timeline: the narration, the screens,
|
|
200
|
+
* and the frames of them the entries carry.
|
|
201
|
+
*
|
|
202
|
+
* The rows are keyed by conversation id with no cascade behind them, so a
|
|
203
|
+
* delete that skipped them would keep pictures of the user's screen for a
|
|
204
|
+
* conversation they asked to remove. It runs after the row deletes, which is
|
|
205
|
+
* the order the watch store expects: once `conversations` no longer has the
|
|
206
|
+
* row, a later append is refused rather than left for a purge that has already
|
|
207
|
+
* been.
|
|
208
|
+
*
|
|
209
|
+
* Best-effort, like the rest of the post-transaction cleanup. The conversation
|
|
210
|
+
* row is already gone by this point, so throwing here would turn a completed
|
|
211
|
+
* delete into an error the caller cannot usefully retry. A swallowed failure
|
|
212
|
+
* is not the end of the story: `sweepOrphanedWatchTimelineEntries` runs from
|
|
213
|
+
* daemon startup and from database maintenance and deletes entries whose
|
|
214
|
+
* conversation no longer exists, so whatever this pass leaves behind is
|
|
215
|
+
* reclaimed on a later one.
|
|
216
|
+
*/
|
|
217
|
+
function purgeWatchTimelineForDeletedConversation(id: string): void {
|
|
218
|
+
try {
|
|
219
|
+
purgeWatchTimelineForConversation(id);
|
|
220
|
+
} catch (err) {
|
|
221
|
+
log.warn(
|
|
222
|
+
{ err, conversationId: id },
|
|
223
|
+
"Failed to purge the watch timeline for a deleted conversation",
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
193
228
|
function deletePendingTelemetryEventsForConversation(id: string): void {
|
|
194
229
|
const telemetry = getTelemetryDb({ createIfMissing: false });
|
|
195
230
|
if (!telemetry || !dedicatedTableExists(telemetry, "telemetry_events")) {
|
|
@@ -259,6 +294,14 @@ const backgroundToolCompletionMetadataSchema = z.object({
|
|
|
259
294
|
|
|
260
295
|
export const messageMetadataSchema = z
|
|
261
296
|
.object({
|
|
297
|
+
/**
|
|
298
|
+
* Epoch ms the content actually happened, when that differs from when
|
|
299
|
+
* the row was written. Set wherever persistence lags the event: a queued
|
|
300
|
+
* turn draining, or channel history imported long after the fact.
|
|
301
|
+
* History serialization prefers it over `createdAt` for the display
|
|
302
|
+
* timestamp.
|
|
303
|
+
*/
|
|
304
|
+
sentAt: z.number().optional(),
|
|
262
305
|
userMessageChannel: channelIdSchema.optional(),
|
|
263
306
|
assistantMessageChannel: channelIdSchema.optional(),
|
|
264
307
|
userMessageInterface: interfaceIdSchema.optional(),
|
|
@@ -413,13 +456,15 @@ export {
|
|
|
413
456
|
} from "./conversation-types.js";
|
|
414
457
|
|
|
415
458
|
/**
|
|
416
|
-
* `messageKind`
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
* display turns.
|
|
459
|
+
* The system-card `messageKind` marker and its predicate live in the
|
|
460
|
+
* `conversation-types` leaf so a caller that only stamps or classifies the
|
|
461
|
+
* marker does not pull in this module's DB graph, and are re-exported here
|
|
462
|
+
* alongside the schema that carries them.
|
|
421
463
|
*/
|
|
422
|
-
export
|
|
464
|
+
export {
|
|
465
|
+
isSystemCardMetadata,
|
|
466
|
+
SYSTEM_CARD_MESSAGE_KIND,
|
|
467
|
+
} from "./conversation-types.js";
|
|
423
468
|
|
|
424
469
|
/**
|
|
425
470
|
* `messageKind` value marking the synthetic assistant row the agent loop
|
|
@@ -431,18 +476,6 @@ export const SYSTEM_CARD_MESSAGE_KIND = "system_card";
|
|
|
431
476
|
*/
|
|
432
477
|
export const PROVIDER_ERROR_MESSAGE_KIND = "provider_error";
|
|
433
478
|
|
|
434
|
-
/**
|
|
435
|
-
* Shared predicate for the system-card marker on assistant-message metadata
|
|
436
|
-
* (see the `messageKind` field on {@link messageMetadataSchema}). One
|
|
437
|
-
* definition so display merging, transcript rendering, and turn grouping
|
|
438
|
-
* cannot drift.
|
|
439
|
-
*/
|
|
440
|
-
export function isSystemCardMetadata(
|
|
441
|
-
metadata: Record<string, unknown> | null | undefined,
|
|
442
|
-
): boolean {
|
|
443
|
-
return metadata?.messageKind === SYSTEM_CARD_MESSAGE_KIND;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
479
|
/**
|
|
447
480
|
* Shared predicate for the provider-error marker on assistant-message
|
|
448
481
|
* metadata (see the `messageKind` field on {@link messageMetadataSchema}).
|
|
@@ -2110,6 +2143,8 @@ export function deleteConversation(id: string): DeletedMemoryIds {
|
|
|
2110
2143
|
removeConversationDir(id, createdAtForDiskCleanup);
|
|
2111
2144
|
}
|
|
2112
2145
|
|
|
2146
|
+
purgeWatchTimelineForDeletedConversation(id);
|
|
2147
|
+
|
|
2113
2148
|
// Notify `conversation-deleted` hooks (e.g. the memory plugin failing its
|
|
2114
2149
|
// still-pending jobs for this conversation). Fire-and-forget from this
|
|
2115
2150
|
// synchronous primitive — the pipeline contains per-hook failures, and
|
|
@@ -2260,6 +2295,8 @@ export async function deleteConversationGently(
|
|
|
2260
2295
|
removeConversationDir(id, createdAtForDiskCleanup);
|
|
2261
2296
|
}
|
|
2262
2297
|
|
|
2298
|
+
purgeWatchTimelineForDeletedConversation(id);
|
|
2299
|
+
|
|
2263
2300
|
// Notify `conversation-deleted` hooks — fire-and-forget, same contract as
|
|
2264
2301
|
// the synchronous delete primitive.
|
|
2265
2302
|
void runHook(HOOKS.CONVERSATION_DELETED, {
|
|
@@ -2467,7 +2504,7 @@ export function getMessages(conversationId: string): MessageRow[] {
|
|
|
2467
2504
|
* offsets until the target is reached (capped at a reasonable maximum to bound
|
|
2468
2505
|
* scan cost).
|
|
2469
2506
|
*/
|
|
2470
|
-
export function
|
|
2507
|
+
export function selectProviderMetaCandidateMetadata(
|
|
2471
2508
|
conversationId: string,
|
|
2472
2509
|
limit: number,
|
|
2473
2510
|
offset = 0,
|
|
@@ -2482,10 +2519,14 @@ export function selectSlackMetaCandidateMetadata(
|
|
|
2482
2519
|
lineageFilter(conversationId),
|
|
2483
2520
|
opts?.includeFlatLegacy
|
|
2484
2521
|
? or(
|
|
2522
|
+
like(messages.metadata, '%"providerMeta"%'),
|
|
2485
2523
|
like(messages.metadata, '%"slackMeta"%'),
|
|
2486
2524
|
like(messages.metadata, '%"source":"slack"%'),
|
|
2487
2525
|
)
|
|
2488
|
-
:
|
|
2526
|
+
: or(
|
|
2527
|
+
like(messages.metadata, '%"providerMeta"%'),
|
|
2528
|
+
like(messages.metadata, '%"slackMeta"%'),
|
|
2529
|
+
),
|
|
2489
2530
|
),
|
|
2490
2531
|
)
|
|
2491
2532
|
.orderBy(asc(messages.createdAt))
|
|
@@ -3585,6 +3626,14 @@ export async function clearAll(): Promise<{
|
|
|
3585
3626
|
// cascade; wipe them explicitly so labels/objectives don't survive (or
|
|
3586
3627
|
// rehydrate after) a clear-all.
|
|
3587
3628
|
await runOrThrow("DELETE FROM subagents");
|
|
3629
|
+
// Watch-session timelines are conversation-keyed rows the cascade does not
|
|
3630
|
+
// reach. They come after `conversations` so this statement is the last thing
|
|
3631
|
+
// that needs to reach one: an append arriving from here on finds no
|
|
3632
|
+
// conversation to key itself to and is refused, and an append that already
|
|
3633
|
+
// ran is a row this deletes. The bulk deletes above run in a sqlite3
|
|
3634
|
+
// subprocess, so the event loop is free while they work; this one is a
|
|
3635
|
+
// synchronous in-process statement, which nothing can interleave with.
|
|
3636
|
+
purgeAllWatchTimelines();
|
|
3588
3637
|
|
|
3589
3638
|
// The memory feature's relocated conversation-keyed tables lost their main-DB
|
|
3590
3639
|
// cascade. Signal the wipe through the hook rather than reaching into the
|
|
@@ -199,7 +199,8 @@ function surfacedVisibilitySql(alias = "conversations"): string {
|
|
|
199
199
|
* explicitly surfaced. {@link standardListingVisibilitySql} already admits
|
|
200
200
|
* such rows through its surfaced and custom-group arms, so a caller that
|
|
201
201
|
* must distinguish "visible in the listing" from "user-facing foreground"
|
|
202
|
-
* (unread counting
|
|
202
|
+
* (unread counting, the section index, a `foregroundOnly` list read) applies
|
|
203
|
+
* this on top.
|
|
203
204
|
*
|
|
204
205
|
* The SQL twin of `isBackgroundConversation` in the web client's
|
|
205
206
|
* `utils/conversation-predicates.ts`.
|
|
@@ -282,6 +283,16 @@ export interface ConversationListFilter {
|
|
|
282
283
|
* the rows its attention surfaces need instead of scanning for them.
|
|
283
284
|
*/
|
|
284
285
|
needsAttention?: true;
|
|
286
|
+
/**
|
|
287
|
+
* Restrict to user-facing foreground conversations: drop the automated
|
|
288
|
+
* background/scheduled rows the standard listing admits through its
|
|
289
|
+
* custom-group arm ({@link notBackgroundVisibilitySql}, the same predicate
|
|
290
|
+
* the unread count and the section index apply on top of visibility).
|
|
291
|
+
* Only `true` narrows; omit to keep every visible row. A client that
|
|
292
|
+
* needs the newest conversation a user can open can ask for it in one
|
|
293
|
+
* row instead of paging past runs it would skip.
|
|
294
|
+
*/
|
|
295
|
+
foregroundOnly?: true;
|
|
285
296
|
}
|
|
286
297
|
|
|
287
298
|
export interface ConversationListQuery extends ConversationListFilter {
|
|
@@ -364,6 +375,7 @@ function conversationListWhere(filter: ConversationListFilter) {
|
|
|
364
375
|
originChannel,
|
|
365
376
|
groupId,
|
|
366
377
|
needsAttention,
|
|
378
|
+
foregroundOnly,
|
|
367
379
|
} = filter;
|
|
368
380
|
return and(
|
|
369
381
|
conversationTypeClause(conversationType),
|
|
@@ -371,6 +383,7 @@ function conversationListWhere(filter: ConversationListFilter) {
|
|
|
371
383
|
originChannel ? originChannelClause(originChannel) : undefined,
|
|
372
384
|
groupId ? groupIdClause(groupId) : undefined,
|
|
373
385
|
...(needsAttention ? unseenAttentionStateConditions() : []),
|
|
386
|
+
foregroundOnly ? sql.raw(notBackgroundVisibilitySql()) : undefined,
|
|
374
387
|
);
|
|
375
388
|
}
|
|
376
389
|
|
|
@@ -135,6 +135,28 @@ export function isBackgroundEventMetadata(
|
|
|
135
135
|
return typeof metadata?.backgroundEventSource === "string";
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
/**
|
|
139
|
+
* `messageKind` value marking a daemon-authored system card: a notice that
|
|
140
|
+
* bypasses the agent loop (the /compact, /clean, and summarize-up-to result
|
|
141
|
+
* cards, and plugin notices about what a turn did to the user's input). Cards
|
|
142
|
+
* render as standalone system notices, never as the assistant persona
|
|
143
|
+
* speaking, and never merge into adjacent assistant display turns.
|
|
144
|
+
*
|
|
145
|
+
* Lives in this leaf so a caller that only stamps or classifies the marker
|
|
146
|
+
* does not pull in `conversation-crud`'s DB graph.
|
|
147
|
+
*/
|
|
148
|
+
export const SYSTEM_CARD_MESSAGE_KIND = "system_card";
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Shared predicate for the system-card marker on assistant-message metadata,
|
|
152
|
+
* so display merging, transcript rendering, and turn grouping cannot drift.
|
|
153
|
+
*/
|
|
154
|
+
export function isSystemCardMetadata(
|
|
155
|
+
metadata: Record<string, unknown> | null | undefined,
|
|
156
|
+
): boolean {
|
|
157
|
+
return metadata?.messageKind === SYSTEM_CARD_MESSAGE_KIND;
|
|
158
|
+
}
|
|
159
|
+
|
|
138
160
|
/**
|
|
139
161
|
* True when a role-`"user"` row is internal scaffolding rather than a person's
|
|
140
162
|
* prompt: a daemon-injected run lifecycle notification (subagent
|
|
@@ -216,6 +216,7 @@ async function runViaCli(
|
|
|
216
216
|
// never an issue regardless of the statement complexity.
|
|
217
217
|
const proc = Bun.spawn({
|
|
218
218
|
cmd: [sqlite3Path, dbPath],
|
|
219
|
+
windowsHide: true,
|
|
219
220
|
stdin: "pipe",
|
|
220
221
|
stdout: "pipe",
|
|
221
222
|
stderr: "pipe",
|
|
@@ -396,7 +397,7 @@ export function spawnDetachedWalCheckpoint(): boolean {
|
|
|
396
397
|
getDbPath(),
|
|
397
398
|
`PRAGMA busy_timeout=${SQLITE_BUSY_TIMEOUT_MS}; PRAGMA wal_checkpoint(TRUNCATE);`,
|
|
398
399
|
],
|
|
399
|
-
{ detached: true, stdio: "ignore" },
|
|
400
|
+
{ detached: true, stdio: "ignore", windowsHide: true },
|
|
400
401
|
);
|
|
401
402
|
// A post-spawn failure can't be acted on — the daemon is exiting — but an
|
|
402
403
|
// unlistened ChildProcess "error" event would throw as an uncaught
|
|
@@ -3,6 +3,7 @@ import { statSync } from "node:fs";
|
|
|
3
3
|
import { getConfig } from "../config/loader.js";
|
|
4
4
|
import { getLogger } from "../util/logger.js";
|
|
5
5
|
import { getDbPath } from "../util/platform.js";
|
|
6
|
+
import { sweepOrphanedWatchTimelineEntries } from "../watch/watch-timeline.js";
|
|
6
7
|
import { pruneRuns } from "../workflows/journal-store.js";
|
|
7
8
|
import { getMemoryCheckpoint, setMemoryCheckpoint } from "./checkpoints.js";
|
|
8
9
|
import { getLastInteractiveUserMessageTimestamp } from "./conversation-crud.js";
|
|
@@ -81,6 +82,20 @@ async function runDbMaintenance(): Promise<void> {
|
|
|
81
82
|
log.warn({ err }, "Workflow run pruning failed (non-fatal)");
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
// Reclaim watch-timeline entries whose conversation is gone. Their purge runs
|
|
86
|
+
// after the conversation row is already deleted and cannot be retried by the
|
|
87
|
+
// caller, and nothing cascades into the table, so a failed purge or a crash
|
|
88
|
+
// between the two writes would otherwise keep screenshots of the user's
|
|
89
|
+
// screen indefinitely. Bounded per pass and best-effort inside the sweep,
|
|
90
|
+
// which reports zero rather than throwing, so it needs no guard here.
|
|
91
|
+
const sweptWatchEntries = sweepOrphanedWatchTimelineEntries();
|
|
92
|
+
if (sweptWatchEntries > 0) {
|
|
93
|
+
log.info(
|
|
94
|
+
{ sweptWatchEntries },
|
|
95
|
+
"Swept watch timeline entries for deleted conversations",
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
84
99
|
// Refresh the query planner's statistics (see PLANNER_OPTIMIZE_PRAGMA for
|
|
85
100
|
// what the mask buys here). Routed through the async path to keep it off the
|
|
86
101
|
// main thread when the sqlite3 CLI backend is available.
|
|
@@ -5,15 +5,15 @@
|
|
|
5
5
|
* finding messages by source identifiers, and managing raw payload storage.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { and, desc, eq, isNotNull, ne, or, sql } from "drizzle-orm";
|
|
8
|
+
import { and, desc, eq, isNotNull, like, ne, or, sql } from "drizzle-orm";
|
|
9
9
|
import { v4 as uuid } from "uuid";
|
|
10
10
|
|
|
11
11
|
import type { ChannelId } from "../channels/types.js";
|
|
12
|
-
import {
|
|
12
|
+
import { readProviderMetadata } from "../messaging/read-provider-metadata.js";
|
|
13
13
|
import type { SlackInboundMessageMetadata } from "../runtime/http-types.js";
|
|
14
14
|
import { parseJsonSafe } from "../util/json.js";
|
|
15
15
|
import { isPlainObject } from "../util/object.js";
|
|
16
|
-
import {
|
|
16
|
+
import { selectProviderMetaCandidateMetadata } from "./conversation-crud.js";
|
|
17
17
|
import {
|
|
18
18
|
getConversationByKey,
|
|
19
19
|
getOrCreateConversation,
|
|
@@ -21,7 +21,12 @@ import {
|
|
|
21
21
|
} from "./conversation-key-store.js";
|
|
22
22
|
import type { NonScheduledConversationType } from "./conversation-types.js";
|
|
23
23
|
import { getDb } from "./db-connection.js";
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
channelInboundEvents,
|
|
26
|
+
conversationKeys,
|
|
27
|
+
conversations,
|
|
28
|
+
messages,
|
|
29
|
+
} from "./schema.js";
|
|
25
30
|
|
|
26
31
|
export interface InboundResult {
|
|
27
32
|
accepted: boolean;
|
|
@@ -33,11 +38,26 @@ export interface InboundResult {
|
|
|
33
38
|
export interface RecordInboundOptions {
|
|
34
39
|
sourceMessageId?: string;
|
|
35
40
|
sourceThreadId?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Record the event against this conversation instead of resolving one from
|
|
43
|
+
* the address. For events that belong to a message rather than to a chat: a
|
|
44
|
+
* reaction lives in the conversation of the message it was attached to, and
|
|
45
|
+
* resolving from its own address would mint a second one.
|
|
46
|
+
*/
|
|
47
|
+
conversationId?: string;
|
|
36
48
|
}
|
|
37
49
|
|
|
38
50
|
const SLACK_LEGACY_THREAD_EVIDENCE_BATCH_SIZE = 50;
|
|
39
51
|
const SLACK_LEGACY_THREAD_EVIDENCE_MAX_SCAN = 500;
|
|
40
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Rows examined when locating a Slack message the assistant itself posted.
|
|
55
|
+
* Bounded on purpose: the scan runs on the inbound path while the gateway
|
|
56
|
+
* waits for its ack, and reactions land on recent messages, so a cap costs
|
|
57
|
+
* almost no recall and keeps the cost flat as the database grows.
|
|
58
|
+
*/
|
|
59
|
+
const SLACK_OUTBOUND_TS_MAX_SCAN = 400;
|
|
60
|
+
|
|
41
61
|
/**
|
|
42
62
|
* Channels where an inbound thread id scopes the conversation: a Slack thread
|
|
43
63
|
* or a Telegram private-chat topic each maps to its own conversation. A
|
|
@@ -99,7 +119,7 @@ function legacySlackConversationHasThreadEvidence(
|
|
|
99
119
|
SLACK_LEGACY_THREAD_EVIDENCE_BATCH_SIZE,
|
|
100
120
|
remaining,
|
|
101
121
|
);
|
|
102
|
-
const metadataRows =
|
|
122
|
+
const metadataRows = selectProviderMetaCandidateMetadata(
|
|
103
123
|
conversationId,
|
|
104
124
|
batchLimit,
|
|
105
125
|
offset,
|
|
@@ -110,12 +130,10 @@ function legacySlackConversationHasThreadEvidence(
|
|
|
110
130
|
return false;
|
|
111
131
|
}
|
|
112
132
|
for (const metadata of metadataRows) {
|
|
113
|
-
const
|
|
114
|
-
allowFlatLegacy: true,
|
|
115
|
-
});
|
|
133
|
+
const meta = readProviderMetadata(metadata, { allowFlatLegacy: true });
|
|
116
134
|
if (
|
|
117
|
-
|
|
118
|
-
|
|
135
|
+
meta?.conversationExternalId === externalChatId &&
|
|
136
|
+
meta.threadId === sourceThreadId
|
|
119
137
|
) {
|
|
120
138
|
return true;
|
|
121
139
|
}
|
|
@@ -248,6 +266,93 @@ export function findInboundConversationId(
|
|
|
248
266
|
* the conversation and tracks the reply. Both must key on the same three
|
|
249
267
|
* fields for either to mean anything.
|
|
250
268
|
*/
|
|
269
|
+
/**
|
|
270
|
+
* The inbound event already recorded for this address, if any. Read-only twin
|
|
271
|
+
* of the dedup check inside {@link recordInbound}, for callers that must know
|
|
272
|
+
* an event is a redelivery before doing work that recording would otherwise
|
|
273
|
+
* gate (routing a guardian decision, for one).
|
|
274
|
+
*/
|
|
275
|
+
export function findInboundEvent(
|
|
276
|
+
sourceChannel: string,
|
|
277
|
+
externalChatId: string,
|
|
278
|
+
externalMessageId: string,
|
|
279
|
+
): { eventId: string; conversationId: string } | null {
|
|
280
|
+
const row = getDb()
|
|
281
|
+
.select({
|
|
282
|
+
id: channelInboundEvents.id,
|
|
283
|
+
conversationId: channelInboundEvents.conversationId,
|
|
284
|
+
})
|
|
285
|
+
.from(channelInboundEvents)
|
|
286
|
+
.where(
|
|
287
|
+
and(
|
|
288
|
+
eq(channelInboundEvents.sourceChannel, sourceChannel),
|
|
289
|
+
eq(channelInboundEvents.externalChatId, externalChatId),
|
|
290
|
+
eq(channelInboundEvents.externalMessageId, externalMessageId),
|
|
291
|
+
),
|
|
292
|
+
)
|
|
293
|
+
.get();
|
|
294
|
+
return row ? { eventId: row.id, conversationId: row.conversationId } : null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* The conversation holding the message with this provider id, found by reading
|
|
299
|
+
* the metadata the assistant's own posts carry.
|
|
300
|
+
*
|
|
301
|
+
* Reads through `readProviderMetadata`, so it matches any channel that
|
|
302
|
+
* describes its rows in the neutral shape as well as Slack's own envelope.
|
|
303
|
+
*
|
|
304
|
+
* `findMessageBySourceId` covers every message that arrived as an inbound
|
|
305
|
+
* event. It cannot see what the assistant posted, because an outbound reply
|
|
306
|
+
* opens no inbound event, so a reaction on the assistant's own message needs
|
|
307
|
+
* this. The search is confined to conversations already bound to the same
|
|
308
|
+
* Slack channel and to the most recent {@link SLACK_OUTBOUND_TS_MAX_SCAN}
|
|
309
|
+
* rows among them; beyond that it gives up and the caller drops the
|
|
310
|
+
* annotation, which is the same outcome as never finding it at all.
|
|
311
|
+
*/
|
|
312
|
+
export function findSlackConversationByMessageTs(
|
|
313
|
+
externalChatId: string,
|
|
314
|
+
channelTs: string,
|
|
315
|
+
): string | null {
|
|
316
|
+
const db = getDb();
|
|
317
|
+
const keyPrefix = `${CONVERSATION_KEY_SCOPE}:slack:${externalChatId}`;
|
|
318
|
+
const rows = db
|
|
319
|
+
.select({
|
|
320
|
+
conversationId: messages.conversationId,
|
|
321
|
+
metadata: messages.metadata,
|
|
322
|
+
})
|
|
323
|
+
.from(messages)
|
|
324
|
+
.innerJoin(
|
|
325
|
+
conversationKeys,
|
|
326
|
+
eq(conversationKeys.conversationId, messages.conversationId),
|
|
327
|
+
)
|
|
328
|
+
.where(
|
|
329
|
+
and(
|
|
330
|
+
or(
|
|
331
|
+
eq(conversationKeys.conversationKey, keyPrefix),
|
|
332
|
+
like(conversationKeys.conversationKey, `${keyPrefix}:thread:%`),
|
|
333
|
+
),
|
|
334
|
+
or(
|
|
335
|
+
like(messages.metadata, '%"providerMeta"%'),
|
|
336
|
+
like(messages.metadata, '%"slackMeta"%'),
|
|
337
|
+
),
|
|
338
|
+
),
|
|
339
|
+
)
|
|
340
|
+
.orderBy(desc(messages.createdAt))
|
|
341
|
+
.limit(SLACK_OUTBOUND_TS_MAX_SCAN)
|
|
342
|
+
.all();
|
|
343
|
+
|
|
344
|
+
for (const row of rows) {
|
|
345
|
+
const meta = readProviderMetadata(row.metadata, { allowFlatLegacy: true });
|
|
346
|
+
if (
|
|
347
|
+
meta?.conversationExternalId === externalChatId &&
|
|
348
|
+
meta.messageId === channelTs
|
|
349
|
+
) {
|
|
350
|
+
return row.conversationId;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
|
|
251
356
|
export function recordInbound(
|
|
252
357
|
sourceChannel: string,
|
|
253
358
|
externalChatId: string,
|
|
@@ -280,11 +385,13 @@ export function recordInbound(
|
|
|
280
385
|
};
|
|
281
386
|
}
|
|
282
387
|
|
|
283
|
-
const mapping =
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
388
|
+
const mapping = options?.conversationId
|
|
389
|
+
? { conversationId: options.conversationId }
|
|
390
|
+
: resolveInboundConversation(
|
|
391
|
+
sourceChannel,
|
|
392
|
+
externalChatId,
|
|
393
|
+
options?.sourceThreadId,
|
|
394
|
+
);
|
|
288
395
|
const now = Date.now();
|
|
289
396
|
const eventId = uuid();
|
|
290
397
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates `watch_timeline_entries`: the per-session record of what a watch
|
|
5
|
+
* session's user narrated and what was on their screen.
|
|
6
|
+
*
|
|
7
|
+
* The table is the timeline's only home. Entries live outside conversation
|
|
8
|
+
* message history, so nothing here participates in turn alternation or
|
|
9
|
+
* provider-facing sanitization, and only the retrospective's rendered summary
|
|
10
|
+
* reaches a model.
|
|
11
|
+
*
|
|
12
|
+
* `screenshot_attachment_id` holds the id of a row in `attachments`, so the
|
|
13
|
+
* pixels stay on disk behind the attachment store rather than in this table.
|
|
14
|
+
* Timeline rows and the screenshots they own are removed together by the
|
|
15
|
+
* watch store's purge.
|
|
16
|
+
*
|
|
17
|
+
* The two indexes cover the two access paths: reads are scoped to one session
|
|
18
|
+
* and ordered by offset, and purges are scoped to one conversation.
|
|
19
|
+
*
|
|
20
|
+
* Idempotent: `IF NOT EXISTS` on the table and both indexes.
|
|
21
|
+
*/
|
|
22
|
+
export function migrateCreateWatchTimelineEntries(db: DrizzleDb): void {
|
|
23
|
+
const raw = getSqliteFrom(db);
|
|
24
|
+
|
|
25
|
+
raw.exec(/*sql*/ `
|
|
26
|
+
CREATE TABLE IF NOT EXISTS watch_timeline_entries (
|
|
27
|
+
id TEXT PRIMARY KEY,
|
|
28
|
+
session_id TEXT NOT NULL,
|
|
29
|
+
conversation_id TEXT NOT NULL,
|
|
30
|
+
at_ms INTEGER NOT NULL,
|
|
31
|
+
kind TEXT NOT NULL,
|
|
32
|
+
text TEXT NOT NULL,
|
|
33
|
+
ax_tree TEXT,
|
|
34
|
+
ax_diff TEXT,
|
|
35
|
+
screenshot_attachment_id TEXT,
|
|
36
|
+
created_at INTEGER NOT NULL
|
|
37
|
+
)
|
|
38
|
+
`);
|
|
39
|
+
|
|
40
|
+
raw.exec(
|
|
41
|
+
`CREATE INDEX IF NOT EXISTS idx_watch_timeline_entries_session ON watch_timeline_entries(session_id, at_ms)`,
|
|
42
|
+
);
|
|
43
|
+
raw.exec(
|
|
44
|
+
`CREATE INDEX IF NOT EXISTS idx_watch_timeline_entries_conversation ON watch_timeline_entries(conversation_id)`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { tableHasColumn } from "./schema-introspection.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Move a watch timeline entry's screenshot into the row that owns it.
|
|
6
|
+
*
|
|
7
|
+
* `screenshot` holds the frame's JPEG bytes directly, so an entry is one row
|
|
8
|
+
* with one lifetime: a single `DELETE` takes the narration, the screen, and
|
|
9
|
+
* the pixels together, with nothing staged elsewhere to coordinate against.
|
|
10
|
+
* `screenshot_attachment_id`, the id of a separately staged file, goes with
|
|
11
|
+
* it.
|
|
12
|
+
*
|
|
13
|
+
* Idempotent: each `ALTER TABLE` is guarded by a column check, so a crash
|
|
14
|
+
* between the two leaves the next boot able to finish the job.
|
|
15
|
+
*/
|
|
16
|
+
export function migrateWatchTimelineScreenshotBlob(database: DrizzleDb): void {
|
|
17
|
+
if (!tableHasColumn(database, "watch_timeline_entries", "screenshot")) {
|
|
18
|
+
database.run(
|
|
19
|
+
/*sql*/ `ALTER TABLE watch_timeline_entries ADD COLUMN screenshot BLOB`,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
if (
|
|
23
|
+
tableHasColumn(
|
|
24
|
+
database,
|
|
25
|
+
"watch_timeline_entries",
|
|
26
|
+
"screenshot_attachment_id",
|
|
27
|
+
)
|
|
28
|
+
) {
|
|
29
|
+
database.run(
|
|
30
|
+
/*sql*/ `ALTER TABLE watch_timeline_entries DROP COLUMN screenshot_attachment_id`,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|