@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
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the daemon `contacts_record_prompt` route.
|
|
3
|
+
*
|
|
4
|
+
* The route is the parked half of a guardian-confirmed contact write: it
|
|
5
|
+
* broadcasts the proposal and waits. It must never write, and the promise it
|
|
6
|
+
* parks must be resolvable only by the gateway's `resolve_contact_prompt`
|
|
7
|
+
* callback, which is what keeps an unattended assistant from changing the
|
|
8
|
+
* contact graph.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
12
|
+
|
|
13
|
+
type Broadcast = Record<string, unknown>;
|
|
14
|
+
|
|
15
|
+
let broadcasts: Broadcast[] = [];
|
|
16
|
+
|
|
17
|
+
const broadcastMock = mock((message: Broadcast) => {
|
|
18
|
+
broadcasts.push(message);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const actualHub = await import("../../assistant-event-hub.js");
|
|
22
|
+
mock.module("../../assistant-event-hub.js", () => ({
|
|
23
|
+
...actualHub,
|
|
24
|
+
broadcastMessage: broadcastMock,
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
const { CONTACT_PROMPT_ROUTES } = await import("../contact-prompt-routes.js");
|
|
28
|
+
|
|
29
|
+
function routeFor(operationId: string) {
|
|
30
|
+
const route = CONTACT_PROMPT_ROUTES.find(
|
|
31
|
+
(r) => r.operationId === operationId,
|
|
32
|
+
);
|
|
33
|
+
if (!route) {
|
|
34
|
+
throw new Error(`route ${operationId} not registered`);
|
|
35
|
+
}
|
|
36
|
+
return route;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const recordPrompt = routeFor("contacts_record_prompt");
|
|
40
|
+
const resolvePrompt = routeFor("resolve_contact_prompt");
|
|
41
|
+
const claimPrompt = routeFor("contact_prompt_claim");
|
|
42
|
+
|
|
43
|
+
/** The broadcast for the only prompt parked so far. */
|
|
44
|
+
function parkedRequestId(): string {
|
|
45
|
+
const message = broadcasts.find((b) => b.type === "contact_record_request");
|
|
46
|
+
expect(message).toBeDefined();
|
|
47
|
+
return message!.requestId as string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
describe("contacts_record_prompt", () => {
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
broadcasts = [];
|
|
53
|
+
broadcastMock.mockClear();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("broadcasts the proposal and stays parked until the guardian answers", async () => {
|
|
57
|
+
const pending = recordPrompt.handler({
|
|
58
|
+
body: {
|
|
59
|
+
operation: "create",
|
|
60
|
+
displayName: "Alice",
|
|
61
|
+
notes: "Dentist",
|
|
62
|
+
label: "Add a contact",
|
|
63
|
+
},
|
|
64
|
+
}) as Promise<Record<string, unknown>>;
|
|
65
|
+
|
|
66
|
+
// Settled only by the gateway callback: a bare await here would hang.
|
|
67
|
+
let settled = false;
|
|
68
|
+
void pending.then(() => {
|
|
69
|
+
settled = true;
|
|
70
|
+
});
|
|
71
|
+
await Promise.resolve();
|
|
72
|
+
expect(settled).toBe(false);
|
|
73
|
+
|
|
74
|
+
const message = broadcasts.find((b) => b.type === "contact_record_request");
|
|
75
|
+
expect(message).toBeDefined();
|
|
76
|
+
expect(message!.operation).toBe("create");
|
|
77
|
+
expect(message!.displayName).toBe("Alice");
|
|
78
|
+
expect(message!.notes).toBe("Dentist");
|
|
79
|
+
expect(message!.label).toBe("Add a contact");
|
|
80
|
+
expect(typeof message!.requestId).toBe("string");
|
|
81
|
+
|
|
82
|
+
resolvePrompt.handler({
|
|
83
|
+
body: { requestId: parkedRequestId(), contactId: "ct_new" },
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
expect(await pending).toEqual({ ok: true, contactId: "ct_new" });
|
|
87
|
+
|
|
88
|
+
// Every client saw this form, so every client is told it is over.
|
|
89
|
+
expect(
|
|
90
|
+
broadcasts.find((b) => b.type === "contact_form_closed"),
|
|
91
|
+
).toMatchObject({ reason: "answered" });
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("a dismissal closes the form on the clients that did not dismiss it", async () => {
|
|
95
|
+
const pending = recordPrompt.handler({
|
|
96
|
+
body: { operation: "create", displayName: "Alice" },
|
|
97
|
+
}) as Promise<Record<string, unknown>>;
|
|
98
|
+
const requestId = parkedRequestId();
|
|
99
|
+
|
|
100
|
+
resolvePrompt.handler({
|
|
101
|
+
body: { requestId, error: "Cancelled by user" },
|
|
102
|
+
});
|
|
103
|
+
await pending;
|
|
104
|
+
|
|
105
|
+
expect(
|
|
106
|
+
broadcasts.find((b) => b.type === "contact_form_closed"),
|
|
107
|
+
).toMatchObject({ requestId, reason: "cancelled" });
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("carries the gateway's error back to the caller", async () => {
|
|
111
|
+
const pending = recordPrompt.handler({
|
|
112
|
+
body: { operation: "delete", contactId: "ct_1" },
|
|
113
|
+
}) as Promise<Record<string, unknown>>;
|
|
114
|
+
|
|
115
|
+
resolvePrompt.handler({
|
|
116
|
+
body: { requestId: parkedRequestId(), error: "Cancelled by user" },
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
expect(await pending).toEqual({ ok: false, error: "Cancelled by user" });
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("update and delete require a contact id, and broadcast nothing without one", async () => {
|
|
123
|
+
for (const operation of ["update", "delete"]) {
|
|
124
|
+
broadcasts = [];
|
|
125
|
+
const result = (await recordPrompt.handler({
|
|
126
|
+
body: { operation, displayName: "Alice" },
|
|
127
|
+
})) as Record<string, unknown>;
|
|
128
|
+
|
|
129
|
+
expect(result.ok).toBe(false);
|
|
130
|
+
expect(result.error).toBe(`contactId is required to ${operation}`);
|
|
131
|
+
expect(broadcasts).toHaveLength(0);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("rejects an operation outside create/update/delete", async () => {
|
|
136
|
+
expect(() =>
|
|
137
|
+
recordPrompt.handler({ body: { operation: "promote" } }),
|
|
138
|
+
).toThrow();
|
|
139
|
+
expect(broadcasts).toHaveLength(0);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("only the first claim on a form is granted", async () => {
|
|
143
|
+
const pending = recordPrompt.handler({
|
|
144
|
+
body: { operation: "create", displayName: "Alice" },
|
|
145
|
+
}) as Promise<Record<string, unknown>>;
|
|
146
|
+
const requestId = parkedRequestId();
|
|
147
|
+
|
|
148
|
+
// Two clients answering the same broadcast. The daemon holds the only
|
|
149
|
+
// record of which forms are open, so it is what settles the race.
|
|
150
|
+
// A granted claim carries the window its write has to report back in.
|
|
151
|
+
expect(claimPrompt.handler({ body: { requestId } })).toMatchObject({
|
|
152
|
+
claimed: true,
|
|
153
|
+
settleMs: expect.any(Number),
|
|
154
|
+
});
|
|
155
|
+
expect(claimPrompt.handler({ body: { requestId } })).toEqual({
|
|
156
|
+
claimed: false,
|
|
157
|
+
reason: "already_claimed",
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
resolvePrompt.handler({ body: { requestId, contactId: "ct_new" } });
|
|
161
|
+
await pending;
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("a claim on a form nobody is waiting on is refused as unknown", () => {
|
|
165
|
+
expect(
|
|
166
|
+
claimPrompt.handler({ body: { requestId: "never-parked" } }),
|
|
167
|
+
).toEqual({
|
|
168
|
+
claimed: false,
|
|
169
|
+
reason: "unknown",
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("a resolved form can no longer be claimed", async () => {
|
|
174
|
+
const pending = recordPrompt.handler({
|
|
175
|
+
body: { operation: "create", displayName: "Alice" },
|
|
176
|
+
}) as Promise<Record<string, unknown>>;
|
|
177
|
+
const requestId = parkedRequestId();
|
|
178
|
+
|
|
179
|
+
claimPrompt.handler({ body: { requestId } });
|
|
180
|
+
resolvePrompt.handler({ body: { requestId, contactId: "ct_new" } });
|
|
181
|
+
await pending;
|
|
182
|
+
|
|
183
|
+
// The entry is gone once the call it was holding has returned, so a late
|
|
184
|
+
// submission has nothing to write for.
|
|
185
|
+
expect(claimPrompt.handler({ body: { requestId } })).toEqual({
|
|
186
|
+
claimed: false,
|
|
187
|
+
reason: "unknown",
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("the caller's timeout is what closes the form", async () => {
|
|
192
|
+
const pending = recordPrompt.handler({
|
|
193
|
+
body: { operation: "create", displayName: "Alice", timeoutMs: 60 },
|
|
194
|
+
}) as Promise<Record<string, unknown>>;
|
|
195
|
+
const requestId = parkedRequestId();
|
|
196
|
+
|
|
197
|
+
// A form that outlived the command that opened it could still be answered,
|
|
198
|
+
// writing something the caller was already told had failed.
|
|
199
|
+
expect(await pending).toEqual({ ok: false, error: "Prompt timed out" });
|
|
200
|
+
|
|
201
|
+
// And it is gone, so a late answer has nothing to claim.
|
|
202
|
+
expect(claimPrompt.handler({ body: { requestId } })).toEqual({
|
|
203
|
+
claimed: false,
|
|
204
|
+
reason: "unknown",
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Clients are told, so the card comes down rather than offering to submit
|
|
208
|
+
// an answer that would now be refused.
|
|
209
|
+
expect(
|
|
210
|
+
broadcasts.find((b) => b.type === "contact_form_closed"),
|
|
211
|
+
).toMatchObject({ requestId, reason: "timed_out" });
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("claiming a form stops its deadline, so a slow write is not reported as a timeout", async () => {
|
|
215
|
+
const pending = recordPrompt.handler({
|
|
216
|
+
body: { operation: "delete", contactId: "ct_1", timeoutMs: 60 },
|
|
217
|
+
}) as Promise<Record<string, unknown>>;
|
|
218
|
+
const requestId = parkedRequestId();
|
|
219
|
+
|
|
220
|
+
// Somebody answered right at the deadline and the write is in flight. The
|
|
221
|
+
// original timer firing now would tell the caller nothing happened while
|
|
222
|
+
// the delete went on to commit.
|
|
223
|
+
expect(claimPrompt.handler({ body: { requestId } })).toMatchObject({
|
|
224
|
+
claimed: true,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
let settled = false;
|
|
228
|
+
void pending.then(() => {
|
|
229
|
+
settled = true;
|
|
230
|
+
});
|
|
231
|
+
await new Promise((r) => setTimeout(r, 120));
|
|
232
|
+
expect(settled).toBe(false);
|
|
233
|
+
expect(broadcasts.some((b) => b.type === "contact_form_closed")).toBe(
|
|
234
|
+
false,
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
resolvePrompt.handler({ body: { requestId, contactId: "ct_1" } });
|
|
238
|
+
expect(await pending).toEqual({ ok: true, contactId: "ct_1" });
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("a second form is refused rather than replacing the first", async () => {
|
|
242
|
+
const first = recordPrompt.handler({
|
|
243
|
+
body: { operation: "create", displayName: "Alice" },
|
|
244
|
+
}) as Promise<Record<string, unknown>>;
|
|
245
|
+
const firstRequestId = parkedRequestId();
|
|
246
|
+
|
|
247
|
+
// A client shows one contact form at a time, so a second broadcast would
|
|
248
|
+
// take the first's card away and leave its command waiting on a form
|
|
249
|
+
// nobody can answer.
|
|
250
|
+
const second = (await recordPrompt.handler({
|
|
251
|
+
body: { operation: "create", displayName: "Bob" },
|
|
252
|
+
})) as Record<string, unknown>;
|
|
253
|
+
|
|
254
|
+
expect(second.ok).toBe(false);
|
|
255
|
+
expect(String(second.error)).toContain("already open");
|
|
256
|
+
expect(
|
|
257
|
+
broadcasts.filter((b) => b.type === "contact_record_request"),
|
|
258
|
+
).toHaveLength(1);
|
|
259
|
+
|
|
260
|
+
resolvePrompt.handler({
|
|
261
|
+
body: { requestId: firstRequestId, contactId: "ct_new" },
|
|
262
|
+
});
|
|
263
|
+
await first;
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("a form that has been answered does not block the next one", async () => {
|
|
267
|
+
const first = recordPrompt.handler({
|
|
268
|
+
body: { operation: "create", displayName: "Alice" },
|
|
269
|
+
}) as Promise<Record<string, unknown>>;
|
|
270
|
+
const firstRequestId = parkedRequestId();
|
|
271
|
+
claimPrompt.handler({ body: { requestId: firstRequestId } });
|
|
272
|
+
|
|
273
|
+
// Claimed means somebody answered it; its write is on its way and the
|
|
274
|
+
// guardian's card is done. The next command should not be refused.
|
|
275
|
+
const second = recordPrompt.handler({
|
|
276
|
+
body: { operation: "create", displayName: "Bob" },
|
|
277
|
+
}) as Promise<Record<string, unknown>>;
|
|
278
|
+
|
|
279
|
+
expect(
|
|
280
|
+
broadcasts.filter((b) => b.type === "contact_record_request"),
|
|
281
|
+
).toHaveLength(2);
|
|
282
|
+
|
|
283
|
+
resolvePrompt.handler({
|
|
284
|
+
body: { requestId: firstRequestId, contactId: "ct_new" },
|
|
285
|
+
});
|
|
286
|
+
await first;
|
|
287
|
+
const secondId = (
|
|
288
|
+
broadcasts.filter((b) => b.type === "contact_record_request")[1] as {
|
|
289
|
+
requestId: string;
|
|
290
|
+
}
|
|
291
|
+
).requestId;
|
|
292
|
+
resolvePrompt.handler({ body: { requestId: secondId, contactId: "ct_2" } });
|
|
293
|
+
await second;
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("a resolve for an unknown request is ignored", () => {
|
|
297
|
+
expect(
|
|
298
|
+
resolvePrompt.handler({
|
|
299
|
+
body: { requestId: "never-parked", contactId: "ct_x" },
|
|
300
|
+
}),
|
|
301
|
+
).toEqual({ resolved: false });
|
|
302
|
+
});
|
|
303
|
+
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* The route handlers run inside the daemon and own the monitor process. We mock
|
|
5
5
|
* monitoring-control, the config loader, and the sample ring buffer so the
|
|
6
6
|
* tests assert handler behaviour:
|
|
7
|
-
* - start spawns
|
|
7
|
+
* - start spawns the worker and throws on spawn
|
|
8
8
|
* failure.
|
|
9
9
|
* - stop signals the monitor (a runtime pause — the daemon respawns it at
|
|
10
10
|
* the next boot).
|
|
@@ -19,7 +19,7 @@ import { getMonitoringPidPath } from "../../../util/platform.js";
|
|
|
19
19
|
class FakeSpawnError extends Error {}
|
|
20
20
|
|
|
21
21
|
let spawnImpl: () => Promise<{ pid: number; alreadyRunning: boolean }>;
|
|
22
|
-
let spawnArgs: Array<{
|
|
22
|
+
let spawnArgs: Array<{ terminateOnTimeout?: boolean } | undefined> = [];
|
|
23
23
|
let stopImpl: () => { status: "running" | "not_running"; pid?: number };
|
|
24
24
|
let monitoringProbe: { status: "running" | "not_running"; pid?: number } = {
|
|
25
25
|
status: "not_running",
|
|
@@ -29,7 +29,6 @@ let latestSample: ResourceSample | null = null;
|
|
|
29
29
|
mock.module("../../../monitoring/control.js", () => ({
|
|
30
30
|
MonitoringWorkerSpawnError: FakeSpawnError,
|
|
31
31
|
spawnMonitoringWorkerProcess: async (opts: {
|
|
32
|
-
detached?: boolean;
|
|
33
32
|
terminateOnTimeout?: boolean;
|
|
34
33
|
}) => {
|
|
35
34
|
spawnArgs.push(opts);
|
|
@@ -71,7 +70,7 @@ describe("monitoring_start", () => {
|
|
|
71
70
|
|
|
72
71
|
const res = await handler("monitoring_start")();
|
|
73
72
|
|
|
74
|
-
expect(spawnArgs).toEqual([{
|
|
73
|
+
expect(spawnArgs).toEqual([{ terminateOnTimeout: true }]);
|
|
75
74
|
expect(res).toEqual({
|
|
76
75
|
pid: 4242,
|
|
77
76
|
alreadyRunning: false,
|
|
@@ -23,7 +23,13 @@ import type { RouteDefinition, RouteHandlerArgs } from "../types.js";
|
|
|
23
23
|
// The conversation-key lookup is the only path into the unresolvable-conversation
|
|
24
24
|
// early return, and it reads SQLite. Stub it so this stays a unit test of the
|
|
25
25
|
// response shape. Imported dynamically below so the stub is in place first.
|
|
26
|
+
// The factory spreads the real module: the route module's import graph pulls
|
|
27
|
+
// other named exports from this store, and a partial factory fails at import
|
|
28
|
+
// time the moment any transitive module adds one.
|
|
29
|
+
const actualConversationKeyStore =
|
|
30
|
+
await import("../../../persistence/conversation-key-store.js");
|
|
26
31
|
mock.module("../../../persistence/conversation-key-store.js", () => ({
|
|
32
|
+
...actualConversationKeyStore,
|
|
27
33
|
getConversationByKey: () => undefined,
|
|
28
34
|
}));
|
|
29
35
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The handlers run inside the daemon and manage the route host process. We mock
|
|
5
5
|
* the route host control surface so the tests assert handler behaviour:
|
|
6
|
-
* - start spawns
|
|
6
|
+
* - start spawns the worker and throws on failure.
|
|
7
7
|
* - stop signals the host and reports its prior state.
|
|
8
8
|
* - status reports the host process liveness.
|
|
9
9
|
*/
|
|
@@ -15,7 +15,7 @@ import { getProcPidPath } from "../../../util/platform.js";
|
|
|
15
15
|
class FakeSpawnError extends Error {}
|
|
16
16
|
|
|
17
17
|
let spawnImpl: () => Promise<{ pid: number; alreadyRunning: boolean }>;
|
|
18
|
-
let spawnArgs: Array<{
|
|
18
|
+
let spawnArgs: Array<{ terminateOnTimeout?: boolean } | undefined> = [];
|
|
19
19
|
let stopImpl: () => { status: "running" | "not_running"; pid?: number };
|
|
20
20
|
let workerProbe: { status: "running" | "not_running"; pid?: number } = {
|
|
21
21
|
status: "not_running",
|
|
@@ -24,7 +24,6 @@ let workerProbe: { status: "running" | "not_running"; pid?: number } = {
|
|
|
24
24
|
mock.module("../../../routes/control.js", () => ({
|
|
25
25
|
RouteHostSpawnError: FakeSpawnError,
|
|
26
26
|
spawnRouteHostWorkerProcess: async (opts: {
|
|
27
|
-
detached?: boolean;
|
|
28
27
|
terminateOnTimeout?: boolean;
|
|
29
28
|
}) => {
|
|
30
29
|
spawnArgs.push(opts);
|
|
@@ -57,7 +56,7 @@ describe("routes_worker_start", () => {
|
|
|
57
56
|
|
|
58
57
|
const res = await handler("routes_worker_start")();
|
|
59
58
|
|
|
60
|
-
expect(spawnArgs).toEqual([
|
|
59
|
+
expect(spawnArgs).toEqual([undefined]);
|
|
61
60
|
expect(res).toEqual({
|
|
62
61
|
pid: 4242,
|
|
63
62
|
alreadyRunning: false,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The route handlers run inside the daemon and manage the worker process. We
|
|
5
5
|
* mock schedule worker-control so the tests assert handler behaviour:
|
|
6
|
-
* - start spawns
|
|
6
|
+
* - start spawns the worker and throws on failure.
|
|
7
7
|
* - stop signals the worker and reports its prior state.
|
|
8
8
|
* - status reports the worker process liveness.
|
|
9
9
|
*/
|
|
@@ -15,7 +15,7 @@ import { getScheduleWorkerPidPath } from "../../../util/platform.js";
|
|
|
15
15
|
class FakeSpawnError extends Error {}
|
|
16
16
|
|
|
17
17
|
let spawnImpl: () => Promise<{ pid: number; alreadyRunning: boolean }>;
|
|
18
|
-
let spawnArgs: Array<{
|
|
18
|
+
let spawnArgs: Array<{ terminateOnTimeout?: boolean } | undefined> = [];
|
|
19
19
|
let stopImpl: () => { status: "running" | "not_running"; pid?: number };
|
|
20
20
|
let workerProbe: { status: "running" | "not_running"; pid?: number } = {
|
|
21
21
|
status: "not_running",
|
|
@@ -24,8 +24,7 @@ const adminStoppedCalls: boolean[] = [];
|
|
|
24
24
|
|
|
25
25
|
mock.module("../../../schedule/worker-control.js", () => ({
|
|
26
26
|
ScheduleWorkerSpawnError: FakeSpawnError,
|
|
27
|
-
spawnScheduleWorkerProcess: async (opts
|
|
28
|
-
detached?: boolean;
|
|
27
|
+
spawnScheduleWorkerProcess: async (opts?: {
|
|
29
28
|
terminateOnTimeout?: boolean;
|
|
30
29
|
}) => {
|
|
31
30
|
spawnArgs.push(opts);
|
|
@@ -57,12 +56,12 @@ beforeEach(() => {
|
|
|
57
56
|
});
|
|
58
57
|
|
|
59
58
|
describe("schedules_worker_start", () => {
|
|
60
|
-
test("spawns
|
|
59
|
+
test("spawns the worker and returns the PID", async () => {
|
|
61
60
|
spawnImpl = async () => ({ pid: 4242, alreadyRunning: false });
|
|
62
61
|
|
|
63
62
|
const res = await handler("schedules_worker_start")();
|
|
64
63
|
|
|
65
|
-
expect(spawnArgs).toEqual([
|
|
64
|
+
expect(spawnArgs).toEqual([undefined]);
|
|
66
65
|
expect(res).toEqual({
|
|
67
66
|
pid: 4242,
|
|
68
67
|
alreadyRunning: false,
|
|
@@ -153,3 +153,61 @@ describe("GET /v1/acp/sessions — SQL pad amount", () => {
|
|
|
153
153
|
expect(capturedLimits).toEqual([24]);
|
|
154
154
|
});
|
|
155
155
|
});
|
|
156
|
+
|
|
157
|
+
describe("GET /v1/acp/sessions: the marker lookup stays off the common path", () => {
|
|
158
|
+
test("a conversation whose history fits the page issues no extra query", async () => {
|
|
159
|
+
// Most conversations have never had a credential failure. A short read
|
|
160
|
+
// means every row is already in hand, which is proof enough that no marker
|
|
161
|
+
// is hiding outside the page, so the database is not asked to confirm the
|
|
162
|
+
// absence on every list.
|
|
163
|
+
inMemoryStates.clear();
|
|
164
|
+
capturedLimits.length = 0;
|
|
165
|
+
getDb()
|
|
166
|
+
.insert(acpSessionHistory)
|
|
167
|
+
.values({
|
|
168
|
+
id: "short-1",
|
|
169
|
+
agentId: "claude",
|
|
170
|
+
acpSessionId: "proto-short-1",
|
|
171
|
+
parentConversationId: "conv-short",
|
|
172
|
+
startedAt: 1,
|
|
173
|
+
status: "completed",
|
|
174
|
+
eventLogJson: "[]",
|
|
175
|
+
})
|
|
176
|
+
.run();
|
|
177
|
+
|
|
178
|
+
const handler = getListHandler();
|
|
179
|
+
await handler({
|
|
180
|
+
queryParams: { conversationId: "conv-short", limit: "10" },
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
expect(capturedLimits).toEqual([10]);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("a full page does look, because a marker could be beyond it", async () => {
|
|
187
|
+
inMemoryStates.clear();
|
|
188
|
+
capturedLimits.length = 0;
|
|
189
|
+
for (let i = 0; i < 3; i++) {
|
|
190
|
+
getDb()
|
|
191
|
+
.insert(acpSessionHistory)
|
|
192
|
+
.values({
|
|
193
|
+
id: `full-${i}`,
|
|
194
|
+
agentId: "claude",
|
|
195
|
+
acpSessionId: `proto-full-${i}`,
|
|
196
|
+
parentConversationId: "conv-full",
|
|
197
|
+
startedAt: 10 + i,
|
|
198
|
+
status: "completed",
|
|
199
|
+
eventLogJson: "[]",
|
|
200
|
+
})
|
|
201
|
+
.run();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const handler = getListHandler();
|
|
205
|
+
await handler({
|
|
206
|
+
queryParams: { conversationId: "conv-full", limit: "2" },
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// The page query, then the bounded scan for a marker beyond it.
|
|
210
|
+
expect(capturedLimits.length).toBeGreaterThan(1);
|
|
211
|
+
expect(capturedLimits[0]).toBe(2);
|
|
212
|
+
});
|
|
213
|
+
});
|