@vellumai/assistant 0.8.5 → 0.8.6
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 +33 -1
- package/ARCHITECTURE.md +1 -1
- package/bunfig.toml +6 -1
- package/docs/credential-execution-service.md +6 -6
- package/docs/plugins.md +4 -3
- package/node_modules/@vellumai/skill-host-contracts/src/client.ts +12 -13
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +4 -1
- package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +16 -14
- package/openapi.yaml +1900 -166
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +3 -2
- package/src/__tests__/agent-loop-exit-reason.test.ts +102 -9
- package/src/__tests__/agent-loop-override-profile.test.ts +2 -1
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
- package/src/__tests__/annotate-risk-options.test.ts +1 -0
- package/src/__tests__/approval-cascade.test.ts +1 -0
- package/src/__tests__/approval-routes-http.test.ts +9 -13
- package/src/__tests__/assert-not-live-db.ts +79 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +9 -25
- package/src/__tests__/audit-log-rotation.test.ts +2 -2
- package/src/__tests__/auto-analysis-end-to-end.test.ts +6 -6
- package/src/__tests__/background-workers-disk-pressure.test.ts +5 -8
- package/src/__tests__/browser-skill-endstate.test.ts +3 -3
- package/src/__tests__/btw-routes.test.ts +3 -2
- package/src/__tests__/call-controller.test.ts +3 -2
- package/src/__tests__/channel-approval-routes.test.ts +3 -2
- package/src/__tests__/channel-guardian.test.ts +3 -2
- package/src/__tests__/channel-readiness-slack-remote.test.ts +175 -0
- package/src/__tests__/channel-reply-delivery.test.ts +35 -0
- package/src/__tests__/channel-retry-sweep.test.ts +320 -3
- package/src/__tests__/checker.test.ts +12 -12
- package/src/__tests__/compaction-events.test.ts +1 -0
- package/src/__tests__/compaction-trail-store.test.ts +264 -0
- package/src/__tests__/compactor-call-site-logging.test.ts +1 -0
- package/src/__tests__/compactor-preserved-tail-count.test.ts +1 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +7 -5
- package/src/__tests__/computer-use-tools.test.ts +12 -14
- package/src/__tests__/config-loader-backfill.test.ts +13 -28
- package/src/__tests__/config-loader-corrupt.test.ts +5 -5
- package/src/__tests__/config-loader-platform-defaults.test.ts +93 -26
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +3 -3
- package/src/__tests__/config-managed-gemini-defaults.test.ts +3 -4
- package/src/__tests__/config-schema.test.ts +10 -10
- package/src/__tests__/connection-model-compat.test.ts +83 -0
- package/src/__tests__/contacts-tools.test.ts +3 -2
- package/src/__tests__/context-token-estimator.test.ts +22 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +5 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-handlers-max-tokens.test.ts +55 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +34 -0
- package/src/__tests__/conversation-agent-loop.test.ts +488 -2
- package/src/__tests__/conversation-analysis-routes.test.ts +1 -0
- package/src/__tests__/conversation-app-control-instantiation.test.ts +29 -19
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -0
- package/src/__tests__/conversation-attention-store.test.ts +101 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +3 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
- package/src/__tests__/conversation-error.test.ts +30 -0
- package/src/__tests__/conversation-fork-crud.test.ts +69 -8
- package/src/__tests__/conversation-fork-route.test.ts +3 -2
- package/src/__tests__/conversation-history-web-search.test.ts +1 -0
- package/src/__tests__/conversation-inference-profile-list.test.ts +3 -2
- package/src/__tests__/conversation-inference-profile-route.test.ts +3 -2
- package/src/__tests__/conversation-lifecycle.test.ts +1 -0
- package/src/__tests__/conversation-list-source.test.ts +3 -2
- package/src/__tests__/conversation-load-history-repair.test.ts +2 -1
- package/src/__tests__/conversation-load-history-stripped.test.ts +1 -0
- package/src/__tests__/conversation-pairing.test.ts +53 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +26 -7
- package/src/__tests__/conversation-process-callsite.test.ts +1 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -0
- package/src/__tests__/conversation-queue.test.ts +333 -291
- package/src/__tests__/conversation-routes-disk-view.test.ts +3 -18
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +33 -8
- package/src/__tests__/conversation-routes-slash-commands.test.ts +33 -2
- package/src/__tests__/conversation-runtime-assembly.test.ts +78 -0
- package/src/__tests__/conversation-skill-tools.test.ts +38 -142
- package/src/__tests__/conversation-slash-queue.test.ts +84 -32
- package/src/__tests__/conversation-slash-unknown.test.ts +5 -0
- package/src/__tests__/conversation-speed-override.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +46 -0
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +6 -3
- package/src/__tests__/conversation-surfaces-standalone.test.ts +6 -3
- package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -3
- package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -17
- package/src/__tests__/conversation-sync-tags.test.ts +128 -12
- package/src/__tests__/conversation-title-service.test.ts +1 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +30 -0
- package/src/__tests__/conversation-usage.test.ts +1 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +5 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -0
- package/src/__tests__/credential-broker-browser-fill.test.ts +3 -3
- package/src/__tests__/credential-broker-server-use.test.ts +5 -5
- package/src/__tests__/credential-execution-client.test.ts +72 -1
- package/src/__tests__/credential-execution-feature-gates.test.ts +10 -12
- package/src/__tests__/credential-health-service.test.ts +252 -3
- package/src/__tests__/credential-security-invariants.test.ts +5 -5
- package/src/__tests__/credential-vault-unit.test.ts +19 -19
- package/src/__tests__/credential-vault.test.ts +5 -5
- package/src/__tests__/cross-provider-web-search.test.ts +56 -2
- package/src/__tests__/db-connection-isolation.test.ts +7 -6
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +8 -10
- package/src/__tests__/db-conversation-inference-profile-migration.test.ts +7 -10
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +9 -15
- package/src/__tests__/db-test-helpers.ts +58 -0
- package/src/__tests__/disk-pressure-guard.test.ts +58 -41
- package/src/__tests__/disk-pressure-lifecycle.test.ts +13 -10
- package/src/__tests__/disk-pressure-routes.test.ts +0 -33
- package/src/__tests__/disk-pressure-tools.test.ts +0 -4
- package/src/__tests__/dm-persistence.test.ts +26 -40
- package/src/__tests__/document-create-dedupe.test.ts +189 -0
- package/src/__tests__/document-find-replace.test.ts +3 -2
- package/src/__tests__/document-tool-security.test.ts +81 -2
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +5 -4
- package/src/__tests__/encrypted-store-test-helpers.ts +56 -0
- package/src/__tests__/encrypted-store.test.ts +11 -9
- package/src/__tests__/feature-flag-test-helpers.ts +53 -0
- package/src/__tests__/filing-service.test.ts +1 -0
- package/src/__tests__/first-greeting.test.ts +62 -12
- package/src/__tests__/gateway-flag-listener.test.ts +0 -1
- package/src/__tests__/gemini-provider.test.ts +26 -0
- package/src/__tests__/guardian-action-sweep.test.ts +3 -2
- package/src/__tests__/guardian-outbound-http.test.ts +3 -2
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +48 -3
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -0
- package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
- package/src/__tests__/heartbeat-service.test.ts +1 -0
- package/src/__tests__/helpers/mock-logger.ts +26 -0
- package/src/__tests__/host-bash-routes.test.ts +1 -0
- package/src/__tests__/host-cu-routes-targeted.test.ts +1 -0
- package/src/__tests__/host-file-routes-targeted.test.ts +1 -0
- package/src/__tests__/host-shell-tool.test.ts +5 -4
- package/src/__tests__/host-transfer-routes-targeted.test.ts +1 -0
- package/src/__tests__/http-conversation-lineage.test.ts +3 -2
- package/src/__tests__/http-user-message-parity.test.ts +29 -7
- package/src/__tests__/identity-intro-cache.test.ts +133 -22
- package/src/__tests__/inbound-slack-persistence.test.ts +44 -72
- package/src/__tests__/inference-profile-reaper.test.ts +3 -2
- package/src/__tests__/inference-profile-session-ipc.test.ts +3 -2
- package/src/__tests__/injector-disk-pressure.test.ts +3 -17
- package/src/__tests__/inline-skill-load-permissions.test.ts +4 -4
- package/src/__tests__/list-messages-hidden-metadata.test.ts +80 -0
- package/src/__tests__/llm-context-normalization.test.ts +42 -0
- package/src/__tests__/llm-resolver.test.ts +331 -0
- package/src/__tests__/llm-schema.test.ts +1 -1
- package/src/__tests__/manual-token-reconciliation.test.ts +76 -1
- package/src/__tests__/mcp-abort-signal.test.ts +14 -0
- package/src/__tests__/mcp-client-auth.test.ts +14 -0
- package/src/__tests__/messaging-send-tool.test.ts +1 -0
- package/src/__tests__/migration-import-from-url.test.ts +3 -3
- package/src/__tests__/mock-gateway-ipc.ts +18 -2
- package/src/__tests__/model-intents.test.ts +3 -3
- package/src/__tests__/native-web-search.test.ts +30 -2
- package/src/__tests__/notification-deep-link.test.ts +62 -0
- package/src/__tests__/oauth-commands-routes.test.ts +37 -0
- package/src/__tests__/oauth-provider-visibility.test.ts +8 -8
- package/src/__tests__/oauth-store.test.ts +3 -2
- package/src/__tests__/onboarding-template-contract.test.ts +3 -2
- package/src/__tests__/openai-provider.test.ts +8 -9
- package/src/__tests__/openai-responses-provider.test.ts +70 -10
- package/src/__tests__/openrouter-provider-only.test.ts +27 -5
- package/src/__tests__/outbound-slack-persistence.test.ts +46 -1
- package/src/__tests__/persistence-pipeline.test.ts +139 -1
- package/src/__tests__/persistence-secret-redaction.test.ts +83 -12
- package/src/__tests__/plugin-bootstrap.test.ts +9 -11
- package/src/__tests__/plugin-tool-contribution.test.ts +41 -38
- package/src/__tests__/process-message-background-slack.test.ts +21 -16
- package/src/__tests__/process-message-display-content.test.ts +19 -22
- package/src/__tests__/provider-catalog-visibility.test.ts +9 -9
- package/src/__tests__/provider-platform-proxy-integration.test.ts +216 -4
- package/src/__tests__/provider-registry-ollama.test.ts +45 -22
- package/src/__tests__/recording-handler.test.ts +1 -0
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
- package/src/__tests__/registry.test.ts +82 -76
- package/src/__tests__/relay-server.test.ts +10 -10
- package/src/__tests__/runtime-attachment-metadata.test.ts +3 -2
- package/src/__tests__/schedule-store.test.ts +16 -1
- package/src/__tests__/scheduler-reuse-conversation.test.ts +48 -3
- package/src/__tests__/secret-ingress-http.test.ts +5 -1
- package/src/__tests__/secure-keys.test.ts +3 -3
- package/src/__tests__/send-endpoint-busy.test.ts +81 -42
- package/src/__tests__/server-history-render.test.ts +4 -1
- package/src/__tests__/skill-feature-flags-integration.test.ts +8 -10
- package/src/__tests__/skill-feature-flags.test.ts +14 -16
- package/src/__tests__/skill-load-feature-flag.test.ts +5 -5
- package/src/__tests__/skill-projection-feature-flag.test.ts +44 -30
- package/src/__tests__/skill-projection.benchmark.test.ts +5 -7
- package/src/__tests__/skill-tool-factory.test.ts +96 -95
- package/src/__tests__/slack-channel-config.test.ts +3 -3
- package/src/__tests__/subagent-call-site-routing.test.ts +11 -3
- package/src/__tests__/subagent-disposal.test.ts +27 -8
- package/src/__tests__/subagent-fork-notifications.test.ts +24 -9
- package/src/__tests__/subagent-fork-spawn.test.ts +13 -4
- package/src/__tests__/subagent-manager-notify.test.ts +20 -8
- package/src/__tests__/subagent-notify-parent.test.ts +5 -4
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +58 -0
- package/src/__tests__/subagent-tools.test.ts +2 -1
- package/src/__tests__/suggestion-routes.test.ts +1 -0
- package/src/__tests__/system-prompt.test.ts +38 -0
- package/src/__tests__/test-preload-verifier.ts +68 -0
- package/src/__tests__/test-preload.ts +32 -39
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +20 -7
- package/src/__tests__/tool-executor.test.ts +55 -10
- package/src/__tests__/tool-preview-lifecycle.test.ts +1 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
- package/src/__tests__/twilio-routes.test.ts +3 -2
- package/src/__tests__/validate-input.test.ts +381 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +1 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -1
- package/src/__tests__/voice-session-bridge.test.ts +37 -28
- package/src/__tests__/workspace-migration-090-memory-router-cost-optimized-profile.test.ts +326 -0
- package/src/__tests__/workspace-migration-091-retighten-migration-onboarding-thread.test.ts +166 -0
- package/src/acp/session-manager.ts +5 -6
- package/src/agent/loop.ts +80 -0
- package/src/api/README.md +124 -2
- package/src/api/constants/call-sites.ts +27 -0
- package/src/api/events/assistant-outbound-attachment.ts +51 -0
- package/src/api/events/assistant-text-delta.ts +32 -0
- package/src/api/events/assistant-turn-start.ts +33 -0
- package/src/api/events/document-comment-created.ts +48 -0
- package/src/api/events/document-comment-deleted.ts +24 -0
- package/src/api/events/document-comment-reopened.ts +25 -0
- package/src/api/events/document-comment-resolved.ts +27 -0
- package/src/api/events/generation-cancelled.ts +24 -0
- package/src/api/events/generation-handoff.ts +41 -0
- package/src/api/events/message-complete.ts +42 -0
- package/src/api/events/open-url.ts +30 -0
- package/src/{events → api/events}/relationship-state-updated.ts +3 -3
- package/src/api/events/tool-use-start.ts +32 -0
- package/src/api/index.ts +128 -3
- package/src/api/responses/llm-context-response.ts +39 -0
- package/src/api/responses/llm-request-log-entry.ts +93 -0
- package/src/api/responses/memory-recall-log.ts +65 -0
- package/src/api/responses/memory-v2-activation-log.ts +78 -0
- package/src/background-wake/background-wake-routes.test.ts +687 -52
- package/src/background-wake/platform-client.test.ts +308 -0
- package/src/background-wake/platform-client.ts +167 -0
- package/src/background-wake/publisher.ts +91 -0
- package/src/background-wake/runtime-registry.ts +2 -2
- package/src/background-wake/wake-intent-hooks.test.ts +282 -0
- package/src/calls/guardian-dispatch.ts +1 -0
- package/src/calls/voice-session-bridge.ts +4 -4
- package/src/cli/commands/__tests__/conversations-slack.test.ts +16 -0
- package/src/cli/commands/__tests__/notifications.test.ts +184 -40
- package/src/cli/commands/channels/__tests__/channels.test.ts +143 -0
- package/src/cli/commands/channels/index.ts +229 -0
- package/src/cli/commands/memory-v3-render.ts +147 -0
- package/src/cli/commands/memory-v3.ts +255 -4
- package/src/cli/commands/notifications.ts +365 -55
- package/src/cli/lib/open-browser.ts +7 -2
- package/src/cli/program.ts +2 -0
- package/src/config/assistant-feature-flags.ts +23 -42
- package/src/config/bundled-skills/document-editor/SKILL.md +5 -1
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -0
- package/src/config/call-site-defaults.ts +1 -1
- package/src/config/feature-flag-cache.ts +86 -0
- package/src/config/feature-flag-registry.json +17 -17
- package/src/config/llm-context-resolution.ts +10 -1
- package/src/config/llm-resolver.ts +121 -15
- package/src/config/loader.ts +4 -5
- package/src/config/schemas/__tests__/memory-v2.test.ts +15 -0
- package/src/config/schemas/heartbeat.ts +1 -1
- package/src/config/schemas/llm.ts +90 -1
- package/src/config/schemas/memory-v2.ts +26 -0
- package/src/config/schemas/services.ts +6 -2
- package/src/config/seed-inference-profiles.ts +36 -16
- package/src/context/token-estimator.ts +10 -5
- package/src/credential-execution/executable-discovery.ts +40 -0
- package/src/credential-execution/process-manager.ts +6 -2
- package/src/credential-health/credential-health-service.ts +125 -40
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -6
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +13 -15
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -2
- package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -0
- package/src/daemon/__tests__/meet-manifest-loader.test.ts +25 -12
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +1 -0
- package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +107 -0
- package/src/daemon/__tests__/web-search-status-text.test.ts +1 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +389 -68
- package/src/daemon/conversation-agent-loop.ts +132 -28
- package/src/daemon/conversation-error.ts +33 -5
- package/src/daemon/conversation-messaging.ts +84 -43
- package/src/daemon/conversation-process.ts +74 -37
- package/src/daemon/conversation-runtime-assembly.ts +29 -9
- package/src/daemon/conversation-skill-tools.ts +14 -30
- package/src/daemon/conversation-surfaces.ts +69 -34
- package/src/daemon/conversation-tool-setup.ts +33 -48
- package/src/daemon/conversation.ts +26 -46
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/daemon-skill-host.ts +9 -2
- package/src/daemon/disk-pressure-guard.ts +27 -29
- package/src/daemon/first-greeting.ts +31 -13
- package/src/daemon/handlers/shared.ts +6 -1
- package/src/daemon/lifecycle.ts +12 -12
- package/src/daemon/mcp-reload-service.ts +1 -1
- package/src/daemon/meet-manifest-loader.ts +10 -17
- package/src/daemon/message-types/conversations.ts +20 -22
- package/src/daemon/message-types/document-comments.ts +8 -44
- package/src/daemon/message-types/home.ts +2 -2
- package/src/daemon/message-types/integrations.ts +2 -7
- package/src/daemon/message-types/messages.ts +23 -38
- package/src/daemon/message-types/subagents.ts +6 -0
- package/src/daemon/process-message.ts +9 -9
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/server.ts +16 -0
- package/src/daemon/switch-inference-profile-tool.ts +13 -3
- package/src/daemon/tool-setup-types.ts +0 -6
- package/src/daemon/wake-target-adapter.ts +10 -0
- package/src/documents/document-store.ts +38 -0
- package/src/export/__tests__/transcript-formatter.test.ts +1 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +29 -0
- package/src/heartbeat/heartbeat-service.ts +63 -0
- package/src/home/__tests__/feed-writer.test.ts +161 -0
- package/src/home/__tests__/post-connect-feed.test.ts +1 -0
- package/src/home/__tests__/suggested-prompts.test.ts +55 -59
- package/src/home/feed-writer.ts +146 -7
- package/src/home/suggested-prompts.ts +27 -145
- package/src/ipc/__tests__/cli-ipc.test.ts +1 -0
- package/src/ipc/gateway-client.test.ts +4 -1
- package/src/ipc/skill-routes/__tests__/memory.test.ts +1 -0
- package/src/ipc/skill-routes/__tests__/registries.test.ts +36 -7
- package/src/ipc/skill-routes/memory.ts +4 -3
- package/src/ipc/skill-routes/registries.ts +28 -29
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +1 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +26 -5
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +1 -0
- package/src/memory/__tests__/memory-retrospective-job.test.ts +1 -0
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +1 -0
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +31 -0
- package/src/memory/conversation-attention-store.ts +17 -3
- package/src/memory/conversation-crud.ts +352 -112
- package/src/memory/db-connection.ts +29 -19
- package/src/memory/db-init.ts +4 -0
- package/src/memory/db-singleton.ts +77 -0
- package/src/memory/delivery-channels.ts +82 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +2 -4
- package/src/memory/graph/retriever.test.ts +3 -3
- package/src/memory/job-handlers/embedding.test.ts +3 -2
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -2
- package/src/memory/jobs-worker.ts +12 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +80 -0
- package/src/memory/llm-request-log-source-local.ts +24 -0
- package/src/memory/llm-request-log-source.ts +31 -0
- package/src/memory/llm-request-log-store.ts +188 -3
- package/src/memory/memory-v2-activation-log-store.ts +95 -1
- package/src/memory/migrations/265-drop-provider-connection-status.ts +26 -0
- package/src/memory/migrations/266-messages-client-message-id.ts +43 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/schema/conversations.ts +9 -1
- package/src/memory/schema/inference.ts +0 -1
- package/src/memory/v2/__tests__/backfill-jobs.test.ts +5 -2
- package/src/memory/v2/__tests__/harness-metrics.test.ts +9 -0
- package/src/memory/v2/__tests__/harness-replay-input.test.ts +9 -4
- package/src/memory/v2/__tests__/harness-runner.test.ts +26 -0
- package/src/memory/v2/__tests__/sweep-job.test.ts +6 -3
- package/src/memory/v2/harness/metrics.ts +5 -1
- package/src/memory/v2/harness/replay-input.ts +19 -3
- package/src/memory/v2/harness/runner.ts +6 -0
- package/src/memory/v2/harness/trace.ts +6 -0
- package/src/memory/v3/__tests__/consolidation-job.test.ts +2 -4
- package/src/memory/v3/__tests__/coretrieval-seed.test.ts +270 -0
- package/src/memory/v3/__tests__/edges.test.ts +144 -1
- package/src/memory/v3/__tests__/filter.test.ts +48 -0
- package/src/memory/v3/__tests__/gate.test.ts +96 -33
- package/src/memory/v3/__tests__/index-composition.test.ts +58 -0
- package/src/memory/v3/__tests__/loop.test.ts +250 -5
- package/src/memory/v3/__tests__/scouts.test.ts +49 -0
- package/src/memory/v3/__tests__/shadow-diff.test.ts +225 -0
- package/src/memory/v3/__tests__/shadow-middleware.test.ts +88 -2
- package/src/memory/v3/__tests__/traversal.test.ts +39 -0
- package/src/memory/v3/__tests__/tree-walk.test.ts +77 -0
- package/src/memory/v3/__tests__/validate.test.ts +32 -0
- package/src/memory/v3/coretrieval-seed.ts +240 -0
- package/src/memory/v3/edges.ts +58 -21
- package/src/memory/v3/filter.ts +27 -22
- package/src/memory/v3/gate.ts +51 -36
- package/src/memory/v3/index-composition.ts +18 -5
- package/src/memory/v3/loop.ts +65 -17
- package/src/memory/v3/scouts.ts +15 -4
- package/src/memory/v3/shadow-diff.ts +287 -0
- package/src/memory/v3/shadow-middleware.ts +44 -2
- package/src/memory/v3/traversal.ts +6 -1
- package/src/memory/v3/tree-walk.ts +6 -1
- package/src/memory/v3/validate.ts +56 -33
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +1 -0
- package/src/notifications/adapters/slack.ts +45 -11
- package/src/notifications/broadcaster.ts +114 -63
- package/src/notifications/conversation-pairing.ts +23 -3
- package/src/notifications/decisions-store.ts +32 -1
- package/src/notifications/deliveries-store.ts +45 -0
- package/src/notifications/edit-notification.ts +201 -0
- package/src/notifications/emit-signal.ts +11 -1
- package/src/notifications/signal.ts +10 -0
- package/src/notifications/types.ts +37 -0
- package/src/oauth/byo-connection.test.ts +67 -3
- package/src/oauth/byo-connection.ts +32 -5
- package/src/oauth/connect-orchestrator.ts +9 -0
- package/src/oauth/connection-resolver.test.ts +76 -0
- package/src/oauth/connection-resolver.ts +49 -10
- package/src/oauth/manual-token-connection.ts +51 -3
- package/src/oauth/seed-providers.ts +3 -0
- package/src/permissions/approval-policy.test.ts +19 -5
- package/src/permissions/approval-policy.ts +14 -3
- package/src/permissions/checker.ts +21 -8
- package/src/platform/client.test.ts +24 -1
- package/src/platform/client.ts +8 -0
- package/src/platform/feature-gate.ts +15 -0
- package/src/plugins/defaults/injectors.ts +2 -8
- package/src/plugins/defaults/persistence.ts +25 -6
- package/src/plugins/types.ts +57 -13
- package/src/proactive-artifact/job.test.ts +1 -0
- package/src/prompts/__tests__/system-prompt.test.ts +4 -4
- package/src/prompts/system-prompt.ts +38 -40
- package/src/prompts/template-detection.ts +10 -4
- package/src/prompts/templates/BOOTSTRAP.md +7 -11
- package/src/prompts/templates/IDENTITY.md +0 -2
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -4
- package/src/providers/__tests__/registry-native-web-search.test.ts +122 -0
- package/src/providers/call-site-routing.ts +33 -9
- package/src/providers/connection-model-compat.ts +23 -0
- package/src/providers/connection-resolution.ts +39 -20
- package/src/providers/fireworks/client.ts +1 -0
- package/src/providers/gemini/client.ts +24 -3
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +0 -2
- package/src/providers/inference/__tests__/base-url-security.test.ts +2 -3
- package/src/providers/inference/__tests__/{connections-status-label.test.ts → connections-label.test.ts} +12 -111
- package/src/providers/inference/auth.ts +0 -8
- package/src/providers/inference/connections.ts +3 -66
- package/src/providers/inference/resolve-auth.ts +2 -3
- package/src/providers/model-catalog.ts +35 -1
- package/src/providers/model-intents.ts +3 -3
- package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +157 -5
- package/src/providers/openai/chat-completions-provider.ts +110 -12
- package/src/providers/openai/codex-models.ts +2 -0
- package/src/providers/openai/responses-provider.ts +53 -53
- package/src/providers/openrouter/client.ts +13 -8
- package/src/providers/provider-send-message.ts +18 -9
- package/src/providers/registry.ts +48 -8
- package/src/providers/retry.ts +16 -4
- package/src/providers/search-provider-catalog.ts +17 -9
- package/src/providers/types.ts +9 -0
- package/src/runtime/__tests__/agent-wake.test.ts +1 -0
- package/src/runtime/__tests__/background-job-runner.test.ts +1 -0
- package/src/runtime/access-request-helper.ts +1 -0
- package/src/runtime/auth/route-policy.ts +10 -0
- package/src/runtime/channel-readiness-service.ts +68 -0
- package/src/runtime/channel-reply-delivery.ts +23 -0
- package/src/runtime/channel-retry-sweep.ts +47 -14
- package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
- package/src/runtime/migrations/vbundle-builder.ts +3 -2
- package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -0
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +406 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +209 -1
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +13 -50
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +51 -3
- package/src/runtime/routes/__tests__/memory-v3-simulate-params.test.ts +35 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +3 -2
- package/src/runtime/routes/__tests__/surface-content-routes.test.ts +294 -0
- package/src/runtime/routes/__tests__/task-routes.test.ts +48 -3
- package/src/runtime/routes/acp-routes-list.test.ts +3 -0
- package/src/runtime/routes/app-management-routes.ts +111 -4
- package/src/runtime/routes/background-wake-routes.ts +188 -20
- package/src/runtime/routes/btw-routes.ts +4 -4
- package/src/runtime/routes/conversation-analysis-routes.ts +6 -0
- package/src/runtime/routes/conversation-compaction-routes.ts +263 -0
- package/src/runtime/routes/conversation-list-routes.ts +147 -0
- package/src/runtime/routes/conversation-management-routes.ts +39 -14
- package/src/runtime/routes/conversation-query-routes.ts +60 -10
- package/src/runtime/routes/conversation-routes.ts +186 -140
- package/src/runtime/routes/conversations-import-routes.ts +19 -6
- package/src/runtime/routes/documents-routes.ts +10 -1
- package/src/runtime/routes/group-routes.ts +11 -0
- package/src/runtime/routes/home-feed-routes.ts +129 -0
- package/src/runtime/routes/identity-intro-cache.ts +61 -16
- package/src/runtime/routes/identity-routes.ts +30 -9
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +530 -6
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +57 -8
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +5 -26
- package/src/runtime/routes/integrations/vercel.ts +15 -0
- package/src/runtime/routes/llm-context-normalization.ts +7 -2
- package/src/runtime/routes/memory-v3-routes.ts +160 -2
- package/src/runtime/routes/migration-routes.ts +20 -13
- package/src/runtime/routes/notification-routes.ts +63 -1
- package/src/runtime/routes/oauth-commands-routes.ts +6 -1
- package/src/runtime/routes/surface-action-routes.ts +1 -38
- package/src/runtime/routes/surface-content-routes.ts +12 -5
- package/src/runtime/routes/surface-conversation-resolver.ts +65 -0
- package/src/runtime/routes/wipe-conversation-routes.ts +3 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -0
- package/src/runtime/slack-dm-text-delivery.ts +177 -0
- package/src/runtime/sync/resource-sync-events.ts +1 -1
- package/src/runtime/tool-grant-request-helper.ts +1 -0
- package/src/schedule/schedule-store.ts +8 -1
- package/src/schedule/scheduler.ts +111 -15
- package/src/security/__tests__/provider-key-env-fallback.test.ts +3 -3
- package/src/security/encrypted-store.ts +7 -16
- package/src/security/store-path-override.ts +61 -0
- package/src/signals/user-message.ts +5 -8
- package/src/skills/validate-input.ts +177 -0
- package/src/subagent/manager.ts +13 -13
- package/src/subagent/types.ts +6 -0
- package/src/tasks/tool-sanitizer.ts +2 -2
- package/src/tools/apps/definitions.ts +35 -21
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +2 -8
- package/src/tools/computer-use/definitions.ts +268 -266
- package/src/tools/document/document-tool.ts +131 -8
- package/src/tools/execution-target.ts +2 -5
- package/src/tools/executor.ts +18 -55
- package/src/tools/host-filesystem/edit.test.ts +1 -0
- package/src/tools/host-filesystem/read.test.ts +1 -0
- package/src/tools/host-filesystem/transfer.test.ts +31 -6
- package/src/tools/host-filesystem/write.test.ts +1 -0
- package/src/tools/mcp/mcp-tool-factory.ts +0 -2
- package/src/tools/network/__tests__/managed-search-proxy.test.ts +282 -0
- package/src/tools/network/__tests__/web-search.test.ts +211 -3
- package/src/tools/network/managed-search-proxy.ts +183 -0
- package/src/tools/network/web-search.ts +199 -44
- package/src/tools/policy-context.ts +3 -1
- package/src/tools/registry.ts +146 -103
- package/src/tools/schedule/create.ts +1 -1
- package/src/tools/skills/skill-tool-factory.ts +17 -36
- package/src/tools/subagent/spawn.ts +3 -0
- package/src/tools/tool-approval-handler.ts +10 -4
- package/src/tools/tool-name-aliases.ts +72 -14
- package/src/tools/types.ts +17 -15
- package/src/tools/ui-surface/definitions.ts +98 -86
- package/src/types/onboarding-context.ts +6 -0
- package/src/usage/attribution.ts +32 -1
- package/src/util/browser.ts +7 -2
- package/src/workspace/migrations/090-memory-router-cost-optimized-profile.ts +109 -0
- package/src/workspace/migrations/091-retighten-migration-onboarding-thread.ts +41 -0
- package/src/workspace/migrations/registry.ts +4 -0
|
@@ -176,6 +176,12 @@ let mockConversationRow: Record<string, unknown> = {
|
|
|
176
176
|
title: null,
|
|
177
177
|
};
|
|
178
178
|
let mockMessageById: Record<string, unknown> | null = null;
|
|
179
|
+
const deleteMessageByIdMock = mock(() => ({
|
|
180
|
+
segmentIds: [],
|
|
181
|
+
deletedSummaryIds: [],
|
|
182
|
+
}));
|
|
183
|
+
const reserveMessageMock = mock(async () => ({ id: "msg-reserve" }));
|
|
184
|
+
const updateMessageContentMock = mock(() => {});
|
|
179
185
|
mock.module("../memory/conversation-crud.js", () => ({
|
|
180
186
|
setConversationOriginChannelIfUnset: () => {},
|
|
181
187
|
updateConversationUsage: () => {},
|
|
@@ -189,7 +195,7 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
189
195
|
}),
|
|
190
196
|
getConversationOriginInterface: () => null,
|
|
191
197
|
addMessage: () => ({ id: "mock-msg-id" }),
|
|
192
|
-
deleteMessageById:
|
|
198
|
+
deleteMessageById: deleteMessageByIdMock,
|
|
193
199
|
updateConversationContextWindow: () => {},
|
|
194
200
|
updateConversationSlackContextWatermark:
|
|
195
201
|
updateConversationSlackContextWatermarkMock,
|
|
@@ -197,6 +203,36 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
197
203
|
getConversationOriginChannel: () => null,
|
|
198
204
|
getMessageById: () => mockMessageById,
|
|
199
205
|
getLastUserTimestampBefore: () => 0,
|
|
206
|
+
reserveMessage: reserveMessageMock,
|
|
207
|
+
updateMessageContent: updateMessageContentMock,
|
|
208
|
+
// The real schema is a Zod object; tests don't exercise validation,
|
|
209
|
+
// so a passthrough is sufficient — the production code at
|
|
210
|
+
// `handleMessageComplete` only branches on `success` and reads two
|
|
211
|
+
// fields off `data`. `safeParse` of an empty object satisfies the
|
|
212
|
+
// schema (every field is optional).
|
|
213
|
+
messageMetadataSchema: {
|
|
214
|
+
safeParse: (input: unknown) => ({ success: true, data: input ?? {} }),
|
|
215
|
+
},
|
|
216
|
+
}));
|
|
217
|
+
|
|
218
|
+
// The B3 indexing-restoration path imports `indexMessageNow` from
|
|
219
|
+
// `../memory/indexer.js` and `projectAssistantMessage` from
|
|
220
|
+
// `../memory/conversation-attention-store.js`; without these stubs the
|
|
221
|
+
// real modules would try to open a SQLite DB and read a real config.
|
|
222
|
+
const indexMessageNowMock = mock(async () => ({
|
|
223
|
+
indexedSegments: 0,
|
|
224
|
+
enqueuedJobs: 0,
|
|
225
|
+
}));
|
|
226
|
+
const projectAssistantMessageMock = mock(() => false);
|
|
227
|
+
const publishSyncInvalidationMock = mock(async () => {});
|
|
228
|
+
mock.module("../memory/indexer.js", () => ({
|
|
229
|
+
indexMessageNow: indexMessageNowMock,
|
|
230
|
+
}));
|
|
231
|
+
mock.module("../memory/conversation-attention-store.js", () => ({
|
|
232
|
+
projectAssistantMessage: projectAssistantMessageMock,
|
|
233
|
+
}));
|
|
234
|
+
mock.module("../runtime/sync/sync-publisher.js", () => ({
|
|
235
|
+
publishSyncInvalidation: publishSyncInvalidationMock,
|
|
200
236
|
}));
|
|
201
237
|
|
|
202
238
|
afterAll(() => {
|
|
@@ -692,6 +728,13 @@ beforeEach(() => {
|
|
|
692
728
|
mockSlackChronologicalContext = null;
|
|
693
729
|
loadSlackChronologicalContextMock.mockClear();
|
|
694
730
|
getSlackCompactionWatermarkForPrefixMock.mockClear();
|
|
731
|
+
deleteMessageByIdMock.mockClear();
|
|
732
|
+
reserveMessageMock.mockClear();
|
|
733
|
+
updateMessageContentMock.mockClear();
|
|
734
|
+
indexMessageNowMock.mockClear();
|
|
735
|
+
projectAssistantMessageMock.mockClear();
|
|
736
|
+
publishSyncInvalidationMock.mockClear();
|
|
737
|
+
mockMessageById = null;
|
|
695
738
|
// Orchestrator pipelines (overflowReduce, persistence, …) run through the
|
|
696
739
|
// plugin registry; reset and re-register every default so the pipelines
|
|
697
740
|
// dispatch to middleware backed by the mocked collaborators these tests
|
|
@@ -783,6 +826,9 @@ describe("session-agent-loop", () => {
|
|
|
783
826
|
_requestId,
|
|
784
827
|
onCheckpoint,
|
|
785
828
|
) => {
|
|
829
|
+
// Prime the assistant row anchor for LLM call 1 — production code
|
|
830
|
+
// emits this from `AgentLoop.run` just before `provider.sendMessage`.
|
|
831
|
+
await onEvent({ type: "llm_call_started" });
|
|
786
832
|
await onEvent({
|
|
787
833
|
type: "message_complete",
|
|
788
834
|
message: {
|
|
@@ -808,6 +854,9 @@ describe("session-agent-loop", () => {
|
|
|
808
854
|
hasToolUse: true,
|
|
809
855
|
history: messages,
|
|
810
856
|
});
|
|
857
|
+
// Prime the anchor again for LLM call 2 — multi-call agent turns
|
|
858
|
+
// reserve a fresh assistant row per LLM call.
|
|
859
|
+
await onEvent({ type: "llm_call_started" });
|
|
811
860
|
await onEvent({
|
|
812
861
|
type: "message_complete",
|
|
813
862
|
message: {
|
|
@@ -1064,6 +1113,9 @@ describe("session-agent-loop", () => {
|
|
|
1064
1113
|
const events: ServerMessage[] = [];
|
|
1065
1114
|
|
|
1066
1115
|
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
1116
|
+
// Prime the assistant row anchor — production code emits this from
|
|
1117
|
+
// `AgentLoop.run` just before `provider.sendMessage`.
|
|
1118
|
+
await onEvent({ type: "llm_call_started" });
|
|
1067
1119
|
// Simulate tool_use + error during execution
|
|
1068
1120
|
onEvent({
|
|
1069
1121
|
type: "tool_use",
|
|
@@ -1113,6 +1165,9 @@ describe("session-agent-loop", () => {
|
|
|
1113
1165
|
const events: ServerMessage[] = [];
|
|
1114
1166
|
|
|
1115
1167
|
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
1168
|
+
// Prime the assistant row anchor — production code emits this from
|
|
1169
|
+
// `AgentLoop.run` just before `provider.sendMessage`.
|
|
1170
|
+
await onEvent({ type: "llm_call_started" });
|
|
1116
1171
|
onEvent({
|
|
1117
1172
|
type: "message_complete",
|
|
1118
1173
|
message: {
|
|
@@ -1173,6 +1228,9 @@ describe("session-agent-loop", () => {
|
|
|
1173
1228
|
};
|
|
1174
1229
|
|
|
1175
1230
|
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
1231
|
+
// Prime the assistant row anchor — production code emits this from
|
|
1232
|
+
// `AgentLoop.run` just before `provider.sendMessage`.
|
|
1233
|
+
await onEvent({ type: "llm_call_started" });
|
|
1176
1234
|
onEvent({
|
|
1177
1235
|
type: "message_complete",
|
|
1178
1236
|
message: {
|
|
@@ -1238,6 +1296,9 @@ describe("session-agent-loop", () => {
|
|
|
1238
1296
|
};
|
|
1239
1297
|
|
|
1240
1298
|
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
1299
|
+
// Prime the assistant row anchor — production code emits this from
|
|
1300
|
+
// `AgentLoop.run` just before `provider.sendMessage`.
|
|
1301
|
+
await onEvent({ type: "llm_call_started" });
|
|
1241
1302
|
onEvent({
|
|
1242
1303
|
type: "message_complete",
|
|
1243
1304
|
message: {
|
|
@@ -1320,6 +1381,9 @@ describe("session-agent-loop", () => {
|
|
|
1320
1381
|
};
|
|
1321
1382
|
|
|
1322
1383
|
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
1384
|
+
// Prime the assistant row anchor — production code emits this from
|
|
1385
|
+
// `AgentLoop.run` just before `provider.sendMessage`.
|
|
1386
|
+
await onEvent({ type: "llm_call_started" });
|
|
1323
1387
|
onEvent({
|
|
1324
1388
|
type: "message_complete",
|
|
1325
1389
|
message: {
|
|
@@ -1388,6 +1452,9 @@ describe("session-agent-loop", () => {
|
|
|
1388
1452
|
}> = [];
|
|
1389
1453
|
|
|
1390
1454
|
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
1455
|
+
// Prime the assistant row anchor — production code emits this from
|
|
1456
|
+
// `AgentLoop.run` just before `provider.sendMessage`.
|
|
1457
|
+
await onEvent({ type: "llm_call_started" });
|
|
1391
1458
|
onEvent({ type: "text_delta", text: "Hi." });
|
|
1392
1459
|
onEvent({
|
|
1393
1460
|
type: "message_complete",
|
|
@@ -1463,6 +1530,9 @@ describe("session-agent-loop", () => {
|
|
|
1463
1530
|
}> = [];
|
|
1464
1531
|
|
|
1465
1532
|
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
1533
|
+
// Prime the assistant row anchor — production code emits this from
|
|
1534
|
+
// `AgentLoop.run` just before `provider.sendMessage`.
|
|
1535
|
+
await onEvent({ type: "llm_call_started" });
|
|
1466
1536
|
// No text_delta — pure tool-call response
|
|
1467
1537
|
onEvent({
|
|
1468
1538
|
type: "message_complete",
|
|
@@ -1526,6 +1596,9 @@ describe("session-agent-loop", () => {
|
|
|
1526
1596
|
const events: ServerMessage[] = [];
|
|
1527
1597
|
|
|
1528
1598
|
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
1599
|
+
// Prime the assistant row anchor — production code emits this from
|
|
1600
|
+
// `AgentLoop.run` just before `provider.sendMessage`.
|
|
1601
|
+
await onEvent({ type: "llm_call_started" });
|
|
1529
1602
|
onEvent({
|
|
1530
1603
|
type: "message_complete",
|
|
1531
1604
|
message: {
|
|
@@ -1638,6 +1711,9 @@ describe("session-agent-loop", () => {
|
|
|
1638
1711
|
});
|
|
1639
1712
|
|
|
1640
1713
|
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
1714
|
+
// Prime the assistant row anchor — production code emits this from
|
|
1715
|
+
// `AgentLoop.run` just before `provider.sendMessage`.
|
|
1716
|
+
await onEvent({ type: "llm_call_started" });
|
|
1641
1717
|
onEvent({
|
|
1642
1718
|
type: "message_complete",
|
|
1643
1719
|
message: {
|
|
@@ -1728,6 +1804,11 @@ describe("session-agent-loop", () => {
|
|
|
1728
1804
|
};
|
|
1729
1805
|
|
|
1730
1806
|
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
1807
|
+
// Prime the assistant row anchor — production code emits this from
|
|
1808
|
+
// `AgentLoop.run` just before `provider.sendMessage`. Retry branches
|
|
1809
|
+
// need this on every invocation: each agent-loop iteration reserves
|
|
1810
|
+
// its own row.
|
|
1811
|
+
await onEvent({ type: "llm_call_started" });
|
|
1731
1812
|
callCount++;
|
|
1732
1813
|
if (callCount === 1) {
|
|
1733
1814
|
onEvent({
|
|
@@ -1855,6 +1936,11 @@ describe("session-agent-loop", () => {
|
|
|
1855
1936
|
};
|
|
1856
1937
|
|
|
1857
1938
|
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
1939
|
+
// Prime the assistant row anchor — production code emits this from
|
|
1940
|
+
// `AgentLoop.run` just before `provider.sendMessage`. Retry branches
|
|
1941
|
+
// need this on every invocation: each agent-loop iteration reserves
|
|
1942
|
+
// its own row.
|
|
1943
|
+
await onEvent({ type: "llm_call_started" });
|
|
1858
1944
|
callCount++;
|
|
1859
1945
|
if (callCount === 1) {
|
|
1860
1946
|
onEvent({
|
|
@@ -1940,6 +2026,11 @@ describe("session-agent-loop", () => {
|
|
|
1940
2026
|
mockOverflowAction = "auto_compress_latest_turn";
|
|
1941
2027
|
|
|
1942
2028
|
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
2029
|
+
// Prime the assistant row anchor — production code emits this from
|
|
2030
|
+
// `AgentLoop.run` just before `provider.sendMessage`. Retry branches
|
|
2031
|
+
// need this on every invocation: each agent-loop iteration reserves
|
|
2032
|
+
// its own row.
|
|
2033
|
+
await onEvent({ type: "llm_call_started" });
|
|
1943
2034
|
callCount++;
|
|
1944
2035
|
if (callCount <= 2) {
|
|
1945
2036
|
onEvent({
|
|
@@ -2237,6 +2328,9 @@ describe("session-agent-loop", () => {
|
|
|
2237
2328
|
|
|
2238
2329
|
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
2239
2330
|
agentLoopCalls++;
|
|
2331
|
+
// Prime the assistant row anchor — production code emits this from
|
|
2332
|
+
// `AgentLoop.run` just before `provider.sendMessage`.
|
|
2333
|
+
await onEvent({ type: "llm_call_started" });
|
|
2240
2334
|
onEvent({
|
|
2241
2335
|
type: "message_complete",
|
|
2242
2336
|
message: {
|
|
@@ -2285,6 +2379,11 @@ describe("session-agent-loop", () => {
|
|
|
2285
2379
|
let callCount = 0;
|
|
2286
2380
|
|
|
2287
2381
|
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
2382
|
+
// Prime the assistant row anchor — production code emits this from
|
|
2383
|
+
// `AgentLoop.run` just before `provider.sendMessage`. Retry branches
|
|
2384
|
+
// need this on every invocation: each agent-loop iteration reserves
|
|
2385
|
+
// its own row.
|
|
2386
|
+
await onEvent({ type: "llm_call_started" });
|
|
2288
2387
|
callCount++;
|
|
2289
2388
|
if (callCount === 1) {
|
|
2290
2389
|
onEvent({
|
|
@@ -2369,6 +2468,11 @@ describe("session-agent-loop", () => {
|
|
|
2369
2468
|
_reqId,
|
|
2370
2469
|
onCheckpoint,
|
|
2371
2470
|
) => {
|
|
2471
|
+
// Prime the assistant row anchor — production code emits this from
|
|
2472
|
+
// `AgentLoop.run` just before `provider.sendMessage`. Retry branches
|
|
2473
|
+
// need this on every invocation: each agent-loop iteration reserves
|
|
2474
|
+
// its own row.
|
|
2475
|
+
await onEvent({ type: "llm_call_started" });
|
|
2372
2476
|
// Simulate tool use followed by checkpoint
|
|
2373
2477
|
onEvent({ type: "tool_use", id: "tu-1", name: "file_read", input: {} });
|
|
2374
2478
|
onEvent({
|
|
@@ -2442,6 +2546,11 @@ describe("session-agent-loop", () => {
|
|
|
2442
2546
|
_reqId,
|
|
2443
2547
|
onCheckpoint,
|
|
2444
2548
|
) => {
|
|
2549
|
+
// Prime the assistant row anchor — production code emits this from
|
|
2550
|
+
// `AgentLoop.run` just before `provider.sendMessage`. Retry branches
|
|
2551
|
+
// need this on every invocation: each agent-loop iteration reserves
|
|
2552
|
+
// its own row.
|
|
2553
|
+
await onEvent({ type: "llm_call_started" });
|
|
2445
2554
|
onEvent({ type: "tool_use", id: "tu-1", name: "file_read", input: {} });
|
|
2446
2555
|
onEvent({
|
|
2447
2556
|
type: "tool_result",
|
|
@@ -2504,6 +2613,9 @@ describe("session-agent-loop", () => {
|
|
|
2504
2613
|
const abortController = new AbortController();
|
|
2505
2614
|
|
|
2506
2615
|
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
2616
|
+
// Prime the assistant row anchor — production code emits this from
|
|
2617
|
+
// `AgentLoop.run` just before `provider.sendMessage`.
|
|
2618
|
+
await onEvent({ type: "llm_call_started" });
|
|
2507
2619
|
onEvent({
|
|
2508
2620
|
type: "message_complete",
|
|
2509
2621
|
message: {
|
|
@@ -2564,6 +2676,9 @@ describe("session-agent-loop", () => {
|
|
|
2564
2676
|
resolveAssistantAttachmentsMock.mockClear();
|
|
2565
2677
|
|
|
2566
2678
|
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
2679
|
+
// Prime the assistant row anchor — production code emits this from
|
|
2680
|
+
// `AgentLoop.run` just before `provider.sendMessage`.
|
|
2681
|
+
await onEvent({ type: "llm_call_started" });
|
|
2567
2682
|
onEvent({
|
|
2568
2683
|
type: "message_complete",
|
|
2569
2684
|
message: {
|
|
@@ -2603,6 +2718,9 @@ describe("session-agent-loop", () => {
|
|
|
2603
2718
|
test("increments turnCount after successful run", async () => {
|
|
2604
2719
|
const ctx = makeCtx({
|
|
2605
2720
|
agentLoopRun: async (messages, onEvent) => {
|
|
2721
|
+
// Prime the assistant row anchor — production code emits this from
|
|
2722
|
+
// `AgentLoop.run` just before `provider.sendMessage`.
|
|
2723
|
+
await onEvent({ type: "llm_call_started" });
|
|
2606
2724
|
onEvent({
|
|
2607
2725
|
type: "message_complete",
|
|
2608
2726
|
message: {
|
|
@@ -2636,6 +2754,9 @@ describe("session-agent-loop", () => {
|
|
|
2636
2754
|
test("clears processing state and abort controller", async () => {
|
|
2637
2755
|
const ctx = makeCtx({
|
|
2638
2756
|
agentLoopRun: async (messages, onEvent) => {
|
|
2757
|
+
// Prime the assistant row anchor — production code emits this from
|
|
2758
|
+
// `AgentLoop.run` just before `provider.sendMessage`.
|
|
2759
|
+
await onEvent({ type: "llm_call_started" });
|
|
2639
2760
|
onEvent({
|
|
2640
2761
|
type: "message_complete",
|
|
2641
2762
|
message: {
|
|
@@ -2699,8 +2820,13 @@ describe("session-agent-loop", () => {
|
|
|
2699
2820
|
const ctx = makeCtx({
|
|
2700
2821
|
agentLoopRun: async (
|
|
2701
2822
|
messages: Message[],
|
|
2702
|
-
onEvent: (event: AgentEvent) => void
|
|
2823
|
+
onEvent: (event: AgentEvent) => void | Promise<void>,
|
|
2703
2824
|
) => {
|
|
2825
|
+
// Prime the assistant row anchor — production code emits this from
|
|
2826
|
+
// `AgentLoop.run` just before `provider.sendMessage`. Must be
|
|
2827
|
+
// awaited so the assistant row is reserved before message_complete
|
|
2828
|
+
// tries to write into it.
|
|
2829
|
+
await onEvent({ type: "llm_call_started" });
|
|
2704
2830
|
onEvent({
|
|
2705
2831
|
type: "message_complete",
|
|
2706
2832
|
message: {
|
|
@@ -3044,6 +3170,280 @@ describe("session-agent-loop", () => {
|
|
|
3044
3170
|
});
|
|
3045
3171
|
});
|
|
3046
3172
|
|
|
3173
|
+
describe("B3 pre-allocation: indexing + cleanup", () => {
|
|
3174
|
+
test("handleMessageComplete indexes and projects the finalized assistant row", async () => {
|
|
3175
|
+
// The pre-B3 path inserted assistant rows via `addMessage`, which ran
|
|
3176
|
+
// the memory indexer and the conversation-attention projector as
|
|
3177
|
+
// side-effects of the insert. B3 splits the write into
|
|
3178
|
+
// `reserveMessage` + `updateMessageContent`, both of which are CRUD-only,
|
|
3179
|
+
// so the indexing + projection calls had to be re-driven explicitly
|
|
3180
|
+
// after `updateContent` succeeds. Codex P1 caught a regression where
|
|
3181
|
+
// this path was missing entirely; this test pins it down.
|
|
3182
|
+
mockMessageById = {
|
|
3183
|
+
id: "msg-reserve",
|
|
3184
|
+
conversationId: "test-conv",
|
|
3185
|
+
createdAt: 1234567,
|
|
3186
|
+
role: "assistant",
|
|
3187
|
+
content: "[]",
|
|
3188
|
+
metadata: null,
|
|
3189
|
+
};
|
|
3190
|
+
// Force attention projection to report a state change so we also
|
|
3191
|
+
// observe the sync-invalidation publish path on the same turn.
|
|
3192
|
+
projectAssistantMessageMock.mockImplementationOnce(() => true);
|
|
3193
|
+
|
|
3194
|
+
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
3195
|
+
await onEvent({ type: "llm_call_started" });
|
|
3196
|
+
// `message_complete` is awaited so `handleMessageComplete` (and its
|
|
3197
|
+
// async indexer + projector chain) completes before the next event
|
|
3198
|
+
// or before the loop returns. Without the await the projector's
|
|
3199
|
+
// synchronous call still races against the test's assertion phase
|
|
3200
|
+
// because the indexer's `await` yields microtasks.
|
|
3201
|
+
await onEvent({
|
|
3202
|
+
type: "message_complete",
|
|
3203
|
+
message: {
|
|
3204
|
+
role: "assistant",
|
|
3205
|
+
content: [{ type: "text", text: "indexed reply" }],
|
|
3206
|
+
},
|
|
3207
|
+
});
|
|
3208
|
+
onEvent({
|
|
3209
|
+
type: "usage",
|
|
3210
|
+
inputTokens: 10,
|
|
3211
|
+
outputTokens: 5,
|
|
3212
|
+
model: "test",
|
|
3213
|
+
providerDurationMs: 50,
|
|
3214
|
+
});
|
|
3215
|
+
return [
|
|
3216
|
+
...messages,
|
|
3217
|
+
{
|
|
3218
|
+
role: "assistant" as const,
|
|
3219
|
+
content: [
|
|
3220
|
+
{ type: "text", text: "indexed reply" },
|
|
3221
|
+
] as ContentBlock[],
|
|
3222
|
+
},
|
|
3223
|
+
];
|
|
3224
|
+
};
|
|
3225
|
+
|
|
3226
|
+
const ctx = makeCtx({ agentLoopRun });
|
|
3227
|
+
await runAgentLoopImpl(ctx, "hi", "msg-1", () => {});
|
|
3228
|
+
|
|
3229
|
+
// Indexer fired with the reserved row's id + the finalized content.
|
|
3230
|
+
expect(indexMessageNowMock).toHaveBeenCalledTimes(1);
|
|
3231
|
+
const indexCallArgs = indexMessageNowMock.mock.calls[0] as unknown as [
|
|
3232
|
+
{
|
|
3233
|
+
messageId: string;
|
|
3234
|
+
conversationId: string;
|
|
3235
|
+
role: string;
|
|
3236
|
+
content: string;
|
|
3237
|
+
createdAt: number;
|
|
3238
|
+
scopeId: string;
|
|
3239
|
+
},
|
|
3240
|
+
unknown,
|
|
3241
|
+
];
|
|
3242
|
+
const indexCall = indexCallArgs[0];
|
|
3243
|
+
expect(indexCall).toMatchObject({
|
|
3244
|
+
messageId: "msg-reserve",
|
|
3245
|
+
conversationId: "test-conv",
|
|
3246
|
+
role: "assistant",
|
|
3247
|
+
createdAt: 1234567,
|
|
3248
|
+
scopeId: "default",
|
|
3249
|
+
});
|
|
3250
|
+
expect(indexCall.content).toContain("indexed reply");
|
|
3251
|
+
|
|
3252
|
+
// Attention projector fired with the same row coordinates.
|
|
3253
|
+
expect(projectAssistantMessageMock).toHaveBeenCalledTimes(1);
|
|
3254
|
+
const projectCall = projectAssistantMessageMock.mock
|
|
3255
|
+
.calls[0] as unknown as [
|
|
3256
|
+
{ conversationId: string; messageId: string; messageAt: number },
|
|
3257
|
+
];
|
|
3258
|
+
expect(projectCall[0]).toEqual({
|
|
3259
|
+
conversationId: "test-conv",
|
|
3260
|
+
messageId: "msg-reserve",
|
|
3261
|
+
messageAt: 1234567,
|
|
3262
|
+
});
|
|
3263
|
+
|
|
3264
|
+
// Projection reported a state change → sync invalidation fires with
|
|
3265
|
+
// the conversation `:metadata` tag. The mock also receives a
|
|
3266
|
+
// `:messages` invalidation from the orchestrator's
|
|
3267
|
+
// `publishLoopMessagesChanged` post-loop emit, so we filter by tag
|
|
3268
|
+
// rather than asserting a total call count.
|
|
3269
|
+
const metadataPublishes = (
|
|
3270
|
+
publishSyncInvalidationMock.mock.calls as unknown as Array<[string[]]>
|
|
3271
|
+
).filter((args) => args[0]?.includes("conversation:test-conv:metadata"));
|
|
3272
|
+
expect(metadataPublishes).toHaveLength(1);
|
|
3273
|
+
});
|
|
3274
|
+
|
|
3275
|
+
test("handleMessageComplete skips sync invalidation when attention state unchanged", async () => {
|
|
3276
|
+
// Mirror of the previous test but with the default projector return
|
|
3277
|
+
// (`false`). The projection still runs every turn, but the sync
|
|
3278
|
+
// invalidation publish must be gated on attention-state movement to
|
|
3279
|
+
// avoid flooding clients with no-op metadata refreshes.
|
|
3280
|
+
mockMessageById = {
|
|
3281
|
+
id: "msg-reserve",
|
|
3282
|
+
conversationId: "test-conv",
|
|
3283
|
+
createdAt: 999,
|
|
3284
|
+
role: "assistant",
|
|
3285
|
+
content: "[]",
|
|
3286
|
+
metadata: null,
|
|
3287
|
+
};
|
|
3288
|
+
|
|
3289
|
+
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
3290
|
+
await onEvent({ type: "llm_call_started" });
|
|
3291
|
+
// See sibling test — `message_complete` must be awaited so the
|
|
3292
|
+
// projector call lands before the assertion phase.
|
|
3293
|
+
await onEvent({
|
|
3294
|
+
type: "message_complete",
|
|
3295
|
+
message: {
|
|
3296
|
+
role: "assistant",
|
|
3297
|
+
content: [{ type: "text", text: "quiet" }],
|
|
3298
|
+
},
|
|
3299
|
+
});
|
|
3300
|
+
onEvent({
|
|
3301
|
+
type: "usage",
|
|
3302
|
+
inputTokens: 1,
|
|
3303
|
+
outputTokens: 1,
|
|
3304
|
+
model: "test",
|
|
3305
|
+
providerDurationMs: 1,
|
|
3306
|
+
});
|
|
3307
|
+
return [
|
|
3308
|
+
...messages,
|
|
3309
|
+
{
|
|
3310
|
+
role: "assistant" as const,
|
|
3311
|
+
content: [{ type: "text", text: "quiet" }] as ContentBlock[],
|
|
3312
|
+
},
|
|
3313
|
+
];
|
|
3314
|
+
};
|
|
3315
|
+
|
|
3316
|
+
const ctx = makeCtx({ agentLoopRun });
|
|
3317
|
+
await runAgentLoopImpl(ctx, "hi", "msg-1", () => {});
|
|
3318
|
+
|
|
3319
|
+
expect(projectAssistantMessageMock).toHaveBeenCalledTimes(1);
|
|
3320
|
+
// The mock will still receive a `:messages` invalidation from the
|
|
3321
|
+
// orchestrator's `publishLoopMessagesChanged` — filter to the
|
|
3322
|
+
// `:metadata` tag and assert it never landed.
|
|
3323
|
+
const metadataPublishes = (
|
|
3324
|
+
publishSyncInvalidationMock.mock.calls as unknown as Array<[string[]]>
|
|
3325
|
+
).filter((args) => args[0]?.includes("conversation:test-conv:metadata"));
|
|
3326
|
+
expect(metadataPublishes).toHaveLength(0);
|
|
3327
|
+
});
|
|
3328
|
+
|
|
3329
|
+
test("handleLlmCallStarted deletes a stranded reservation before reserving a new row", async () => {
|
|
3330
|
+
// Simulates a retry path: the first LLM call reserves an assistant row
|
|
3331
|
+
// but exits without `message_complete` (e.g. context-overflow rescue,
|
|
3332
|
+
// ordering-error rescue, image-overflow rescue). The next
|
|
3333
|
+
// `llm_call_started` must delete the stranded row so the transcript
|
|
3334
|
+
// does not accumulate empty assistant bubbles.
|
|
3335
|
+
reserveMessageMock
|
|
3336
|
+
.mockImplementationOnce(async () => ({ id: "msg-strand-A" }))
|
|
3337
|
+
.mockImplementationOnce(async () => ({ id: "msg-strand-B" }));
|
|
3338
|
+
// Indexer/projector mocks default to no-op; no finalized row in this
|
|
3339
|
+
// test, so `mockMessageById` stays null.
|
|
3340
|
+
|
|
3341
|
+
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
3342
|
+
// First LLM call: reserve msg-strand-A, never finalize.
|
|
3343
|
+
await onEvent({ type: "llm_call_started" });
|
|
3344
|
+
// Second LLM call: should delete msg-strand-A before reserving
|
|
3345
|
+
// msg-strand-B.
|
|
3346
|
+
await onEvent({ type: "llm_call_started" });
|
|
3347
|
+
// Finalize the second one so the loop has a valid assistant message
|
|
3348
|
+
// and exits cleanly.
|
|
3349
|
+
onEvent({
|
|
3350
|
+
type: "message_complete",
|
|
3351
|
+
message: {
|
|
3352
|
+
role: "assistant",
|
|
3353
|
+
content: [{ type: "text", text: "retry succeeded" }],
|
|
3354
|
+
},
|
|
3355
|
+
});
|
|
3356
|
+
onEvent({
|
|
3357
|
+
type: "usage",
|
|
3358
|
+
inputTokens: 5,
|
|
3359
|
+
outputTokens: 3,
|
|
3360
|
+
model: "test",
|
|
3361
|
+
providerDurationMs: 25,
|
|
3362
|
+
});
|
|
3363
|
+
return [
|
|
3364
|
+
...messages,
|
|
3365
|
+
{
|
|
3366
|
+
role: "assistant" as const,
|
|
3367
|
+
content: [
|
|
3368
|
+
{ type: "text", text: "retry succeeded" },
|
|
3369
|
+
] as ContentBlock[],
|
|
3370
|
+
},
|
|
3371
|
+
];
|
|
3372
|
+
};
|
|
3373
|
+
|
|
3374
|
+
const ctx = makeCtx({ agentLoopRun });
|
|
3375
|
+
await runAgentLoopImpl(ctx, "hi", "msg-1", () => {});
|
|
3376
|
+
|
|
3377
|
+
// Exactly one delete fires — for msg-strand-A, before the second
|
|
3378
|
+
// reserve. The second reservation is committed via `updateContent`
|
|
3379
|
+
// (not deleted), and after the run completes
|
|
3380
|
+
// `assistantRowAwaitingFinalization` is false, so no further delete
|
|
3381
|
+
// is attempted on shutdown.
|
|
3382
|
+
expect(deleteMessageByIdMock).toHaveBeenCalledTimes(1);
|
|
3383
|
+
const strandDeleteCall = deleteMessageByIdMock.mock
|
|
3384
|
+
.calls[0] as unknown as [string];
|
|
3385
|
+
expect(strandDeleteCall[0]).toBe("msg-strand-A");
|
|
3386
|
+
expect(reserveMessageMock).toHaveBeenCalledTimes(2);
|
|
3387
|
+
});
|
|
3388
|
+
|
|
3389
|
+
test("provider-error branch deletes the orphaned reservation and repoints lastAssistantMessageId", async () => {
|
|
3390
|
+
// Codex P2 regression: B3 reserves an empty assistant row at
|
|
3391
|
+
// `llm_call_started`. When the call exits via the provider-error
|
|
3392
|
+
// branch (no `message_complete`), the synthetic error message is
|
|
3393
|
+
// inserted separately. Without cleanup the transcript would carry
|
|
3394
|
+
// both the empty reserved row AND the error message, and
|
|
3395
|
+
// `syncLastAssistantMessageToDisk` (which reads
|
|
3396
|
+
// `state.lastAssistantMessageId`) would mis-target the deleted
|
|
3397
|
+
// reservation id.
|
|
3398
|
+
reserveMessageMock.mockImplementationOnce(async () => ({
|
|
3399
|
+
id: "msg-orphaned-reservation",
|
|
3400
|
+
}));
|
|
3401
|
+
|
|
3402
|
+
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
3403
|
+
// Reserve the orphan.
|
|
3404
|
+
await onEvent({ type: "llm_call_started" });
|
|
3405
|
+
// Provider rejects — writes the llm_request_log row and arms
|
|
3406
|
+
// `state.providerErrorUserMessage` via `handleError`.
|
|
3407
|
+
onEvent({
|
|
3408
|
+
type: "provider_error",
|
|
3409
|
+
error: new Error("upstream 500"),
|
|
3410
|
+
rawRequest: { model: "gpt-4.1", messages: [] },
|
|
3411
|
+
actualProvider: "openai",
|
|
3412
|
+
});
|
|
3413
|
+
onEvent({
|
|
3414
|
+
type: "error",
|
|
3415
|
+
error: new Error("upstream 500"),
|
|
3416
|
+
});
|
|
3417
|
+
// No assistant message in the result — the synthetic-error branch
|
|
3418
|
+
// below the agent loop fires.
|
|
3419
|
+
return messages;
|
|
3420
|
+
};
|
|
3421
|
+
|
|
3422
|
+
const ctx = makeCtx({ agentLoopRun });
|
|
3423
|
+
await runAgentLoopImpl(ctx, "hi", "msg-1", () => {});
|
|
3424
|
+
|
|
3425
|
+
// The orphan was deleted exactly once, before the synthetic error
|
|
3426
|
+
// message landed.
|
|
3427
|
+
expect(deleteMessageByIdMock).toHaveBeenCalledTimes(1);
|
|
3428
|
+
const deleteCall = deleteMessageByIdMock.mock.calls[0] as unknown as [
|
|
3429
|
+
string,
|
|
3430
|
+
];
|
|
3431
|
+
expect(deleteCall[0]).toBe("msg-orphaned-reservation");
|
|
3432
|
+
|
|
3433
|
+
// Post-loop `syncLastAssistantMessageToDisk` targets the synthetic
|
|
3434
|
+
// error row's id (`mock-msg-id` from the mocked `addMessage`), NOT
|
|
3435
|
+
// the deleted reservation id. This is the externally-observable
|
|
3436
|
+
// proof that `state.lastAssistantMessageId` was repointed.
|
|
3437
|
+
expect(syncMessageToDiskMock).toHaveBeenCalled();
|
|
3438
|
+
const syncCalls = syncMessageToDiskMock.mock.calls as unknown as Array<
|
|
3439
|
+
[string, string, number]
|
|
3440
|
+
>;
|
|
3441
|
+
const lastSync = syncCalls[syncCalls.length - 1];
|
|
3442
|
+
expect(lastSync?.[1]).toBe("mock-msg-id");
|
|
3443
|
+
expect(lastSync?.[1]).not.toBe("msg-orphaned-reservation");
|
|
3444
|
+
});
|
|
3445
|
+
});
|
|
3446
|
+
|
|
3047
3447
|
describe("pkbSystemReminderBlock metadata persistence", () => {
|
|
3048
3448
|
test("persists pkbSystemReminderBlock in full mode with PKB active", async () => {
|
|
3049
3449
|
const reminder = "<system_reminder>\npkb content\n</system_reminder>";
|
|
@@ -3908,6 +4308,11 @@ describe("session-agent-loop", () => {
|
|
|
3908
4308
|
let callCount = 0;
|
|
3909
4309
|
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
3910
4310
|
callCount++;
|
|
4311
|
+
// Prime the assistant row anchor — production code emits this from
|
|
4312
|
+
// `AgentLoop.run` just before `provider.sendMessage`. Retry branches
|
|
4313
|
+
// need this on every invocation: each agent-loop iteration reserves
|
|
4314
|
+
// its own row.
|
|
4315
|
+
await onEvent({ type: "llm_call_started" });
|
|
3911
4316
|
if (callCount === 1) {
|
|
3912
4317
|
// Trigger convergence path: error + appended assistant message so
|
|
3913
4318
|
// updatedHistory.length > preRunHistoryLength at the strip site.
|
|
@@ -3968,5 +4373,86 @@ describe("session-agent-loop", () => {
|
|
|
3968
4373
|
);
|
|
3969
4374
|
expect(stripCalls.length).toBeGreaterThanOrEqual(1);
|
|
3970
4375
|
});
|
|
4376
|
+
|
|
4377
|
+
test("strip-site marker write is non-fatal when the helper throws", async () => {
|
|
4378
|
+
setConversationHistoryStrippedAtMock.mockImplementation(() => {
|
|
4379
|
+
throw new Error("db write failed");
|
|
4380
|
+
});
|
|
4381
|
+
|
|
4382
|
+
mockReducerStepFn = (msgs: Message[]) => ({
|
|
4383
|
+
messages: msgs,
|
|
4384
|
+
tier: "forced_compaction",
|
|
4385
|
+
state: {
|
|
4386
|
+
appliedTiers: ["forced_compaction"],
|
|
4387
|
+
injectionMode: "full",
|
|
4388
|
+
exhausted: false,
|
|
4389
|
+
},
|
|
4390
|
+
estimatedTokens: 5000,
|
|
4391
|
+
});
|
|
4392
|
+
|
|
4393
|
+
let callCount = 0;
|
|
4394
|
+
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
4395
|
+
callCount++;
|
|
4396
|
+
// Prime the assistant row anchor — production code emits this from
|
|
4397
|
+
// `AgentLoop.run` just before `provider.sendMessage`. Retry branches
|
|
4398
|
+
// need this on every invocation: each agent-loop iteration reserves
|
|
4399
|
+
// its own row.
|
|
4400
|
+
await onEvent({ type: "llm_call_started" });
|
|
4401
|
+
if (callCount === 1) {
|
|
4402
|
+
onEvent({
|
|
4403
|
+
type: "error",
|
|
4404
|
+
error: new Error("context_length_exceeded"),
|
|
4405
|
+
});
|
|
4406
|
+
onEvent({
|
|
4407
|
+
type: "usage",
|
|
4408
|
+
inputTokens: 100,
|
|
4409
|
+
outputTokens: 0,
|
|
4410
|
+
model: "test-model",
|
|
4411
|
+
providerDurationMs: 50,
|
|
4412
|
+
});
|
|
4413
|
+
return [
|
|
4414
|
+
...messages,
|
|
4415
|
+
{
|
|
4416
|
+
role: "assistant" as const,
|
|
4417
|
+
content: [{ type: "text", text: "partial" }] as ContentBlock[],
|
|
4418
|
+
},
|
|
4419
|
+
];
|
|
4420
|
+
}
|
|
4421
|
+
onEvent({
|
|
4422
|
+
type: "message_complete",
|
|
4423
|
+
message: {
|
|
4424
|
+
role: "assistant",
|
|
4425
|
+
content: [{ type: "text", text: "recovered" }],
|
|
4426
|
+
},
|
|
4427
|
+
});
|
|
4428
|
+
onEvent({
|
|
4429
|
+
type: "usage",
|
|
4430
|
+
inputTokens: 50,
|
|
4431
|
+
outputTokens: 25,
|
|
4432
|
+
model: "test-model",
|
|
4433
|
+
providerDurationMs: 100,
|
|
4434
|
+
});
|
|
4435
|
+
return [
|
|
4436
|
+
...messages,
|
|
4437
|
+
{
|
|
4438
|
+
role: "assistant" as const,
|
|
4439
|
+
content: [{ type: "text", text: "recovered" }] as ContentBlock[],
|
|
4440
|
+
},
|
|
4441
|
+
];
|
|
4442
|
+
};
|
|
4443
|
+
|
|
4444
|
+
const ctx = makeCtx({
|
|
4445
|
+
agentLoopRun,
|
|
4446
|
+
contextWindowManager: {
|
|
4447
|
+
shouldCompact: () => ({ needed: false, estimatedTokens: 0 }),
|
|
4448
|
+
maybeCompact: async () => ({ compacted: false }),
|
|
4449
|
+
} as unknown as AgentLoopConversationContext["contextWindowManager"],
|
|
4450
|
+
});
|
|
4451
|
+
|
|
4452
|
+
// Must not throw — the strip-site marker write is wrapped in try/catch.
|
|
4453
|
+
await expect(
|
|
4454
|
+
runAgentLoopImpl(ctx, "hello", "msg-1", () => {}),
|
|
4455
|
+
).resolves.toBeUndefined();
|
|
4456
|
+
});
|
|
3971
4457
|
});
|
|
3972
4458
|
});
|