@vellumai/assistant 0.9.0 → 0.10.0-staging.2
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 +18 -34
- package/bun.lock +7 -8
- package/docs/activation-funnel-telemetry.md +28 -22
- package/docs/architecture/security.md +29 -28
- package/docs/stt-provider-onboarding.md +3 -5
- package/docs/workflows-testing.md +13 -44
- package/docs/workflows.md +3 -5
- package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
- package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
- package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +32 -6
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
- package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
- package/openapi.yaml +976 -63
- package/package.json +2 -1
- package/scripts/sync-llm-catalog.ts +6 -15
- package/scripts/sync-web-search-catalog.ts +3 -11
- package/src/__tests__/access-request-card-view.test.ts +98 -0
- package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +72 -32
- package/src/__tests__/agent-loop-compaction-strip.test.ts +241 -0
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
- package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
- package/src/__tests__/app-compiler.test.ts +15 -1
- package/src/__tests__/app-dir-path-guard.test.ts +0 -1
- package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
- package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
- package/src/__tests__/avatar-identity-sync.test.ts +2 -27
- package/src/__tests__/btw-routes.test.ts +6 -8
- package/src/__tests__/call-pointer-messages.test.ts +28 -0
- package/src/__tests__/cancel-clears-processing.test.ts +89 -0
- package/src/__tests__/channel-approval-routes.test.ts +0 -4
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
- package/src/__tests__/checker.test.ts +0 -3
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
- package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
- package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
- package/src/__tests__/config-loader-backfill.test.ts +268 -27
- package/src/__tests__/config-schema.test.ts +35 -0
- package/src/__tests__/config-watcher.test.ts +0 -18
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
- package/src/__tests__/contact-store-user-file.test.ts +0 -6
- package/src/__tests__/contacts-tools.test.ts +29 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop.test.ts +58 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-lifecycle.test.ts +7 -9
- package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
- package/src/__tests__/conversation-title-service.test.ts +62 -0
- package/src/__tests__/credential-broker.test.ts +449 -1
- package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
- package/src/__tests__/credential-execution-tools.test.ts +0 -1
- package/src/__tests__/credential-prompt-route.test.ts +4 -4
- package/src/__tests__/credential-routes.test.ts +360 -0
- package/src/__tests__/credential-security-invariants.test.ts +4 -13
- package/src/__tests__/disk-pressure-policy.test.ts +12 -0
- package/src/__tests__/disk-usage.test.ts +65 -0
- package/src/__tests__/dynamic-page-surface.test.ts +152 -1
- package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
- package/src/__tests__/gateway-flag-listener.test.ts +110 -1
- package/src/__tests__/gateway-only-guard.test.ts +3 -7
- package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
- package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
- package/src/__tests__/guardian-grant-minting.test.ts +3 -35
- package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
- package/src/__tests__/guardian-routing-state.test.ts +0 -1
- package/src/__tests__/headless-browser-mode.test.ts +10 -0
- package/src/__tests__/headless-browser-navigate.test.ts +8 -3
- package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
- package/src/__tests__/host-browser-proxy.test.ts +87 -0
- package/src/__tests__/identity-routes.test.ts +0 -189
- package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
- package/src/__tests__/injector-v3-suppression.test.ts +27 -20
- package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
- package/src/__tests__/invite-redemption-service.test.ts +4 -7
- package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
- package/src/__tests__/llm-catalog-parity.test.ts +30 -23
- package/src/__tests__/llm-resolver.test.ts +70 -24
- package/src/__tests__/llm-schema.test.ts +1 -0
- package/src/__tests__/managed-profile-guard.test.ts +163 -4
- package/src/__tests__/mcp-health-check.test.ts +6 -7
- package/src/__tests__/media-stream-server-integration.test.ts +317 -13
- package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
- package/src/__tests__/onboarding-persona-write.test.ts +1 -1
- package/src/__tests__/path-policy.test.ts +34 -0
- package/src/__tests__/persona-resolver.test.ts +49 -14
- package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
- package/src/__tests__/plugin-api-provider.test.ts +24 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
- package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
- package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
- package/src/__tests__/reaction-persistence.test.ts +150 -29
- package/src/__tests__/registry.test.ts +2 -7
- package/src/__tests__/relay-server.test.ts +285 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
- package/src/__tests__/schedule-routes.test.ts +0 -30
- package/src/__tests__/schedule-tools.test.ts +2 -18
- package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
- package/src/__tests__/skill-execute-input.test.ts +51 -1
- package/src/__tests__/skill-runtime-path.test.ts +2 -3
- package/src/__tests__/skills.test.ts +51 -0
- package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
- package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
- package/src/__tests__/subagent-tools.test.ts +266 -0
- package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
- package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
- package/src/__tests__/title-generate-hook.test.ts +100 -3
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
- package/src/__tests__/token-manager.test.ts +519 -0
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
- package/src/__tests__/tool-audit-listener.test.ts +7 -7
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
- package/src/__tests__/tool-executor.test.ts +0 -79
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
- package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
- package/src/__tests__/trusted-contact-verification.test.ts +8 -10
- package/src/__tests__/twilio-routes.test.ts +81 -1
- package/src/__tests__/voice-invite-redemption.test.ts +2 -3
- package/src/__tests__/weak-open-model.test.ts +30 -0
- package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
- package/src/__tests__/workspace-greetings.test.ts +152 -0
- package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
- package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
- package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
- package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
- package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
- package/src/agent/loop.ts +49 -29
- package/src/api/README.md +6 -6
- package/src/api/events/tool-result.ts +6 -0
- package/src/api/events/workflow-completed.ts +53 -0
- package/src/api/events/workflow-leaf-finished.ts +38 -0
- package/src/api/events/workflow-leaf-started.ts +35 -0
- package/src/api/events/workflow-progress.ts +32 -0
- package/src/api/events/workflow-started.ts +31 -0
- package/src/api/index.ts +40 -0
- package/src/api/responses/conversation-message.ts +28 -4
- package/src/api/responses/home.ts +26 -4
- package/src/api/responses/workflow-journal.ts +53 -0
- package/src/approvals/guardian-card-withdrawal.ts +145 -0
- package/src/approvals/guardian-decision-primitive.ts +26 -3
- package/src/approvals/guardian-request-resolvers.ts +183 -80
- package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
- package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
- package/src/calls/call-pointer-messages.ts +10 -4
- package/src/calls/channel-admission-reader.ts +104 -0
- package/src/calls/guardian-dispatch.ts +17 -45
- package/src/calls/media-stream-server.ts +84 -2
- package/src/calls/relay-access-wait.ts +1 -1
- package/src/calls/relay-server.ts +66 -0
- package/src/calls/relay-setup-router.ts +82 -1
- package/src/calls/twilio-routes.ts +17 -8
- package/src/calls/voice-session-bridge.ts +2 -2
- package/src/cli/commands/clients.ts +3 -0
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
- package/src/cli/commands/memory/index.ts +30 -0
- package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
- package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
- package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
- package/src/cli/commands/oauth/status.test.ts +36 -0
- package/src/cli/commands/oauth/status.ts +23 -3
- package/src/cli/commands/plugins.ts +197 -4
- package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
- package/src/cli/lib/diff-plugin.ts +346 -0
- package/src/cli/lib/inspect-plugin.ts +12 -1
- package/src/cli/lib/install-from-github.ts +105 -17
- package/src/cli/lib/merge-plugin-tree.ts +328 -0
- package/src/cli/lib/plugin-fingerprint.ts +14 -0
- package/src/cli/lib/plugin-surfaces.ts +104 -0
- package/src/cli/lib/upgrade-plugin.ts +298 -10
- package/src/cli/program.ts +2 -6
- package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
- package/src/config/assistant-feature-flags.ts +22 -7
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
- package/src/config/bundled-skills/messaging/SKILL.md +6 -4
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
- package/src/config/bundled-skills/subagent/SKILL.md +4 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
- package/src/config/bundled-skills/workflows/SKILL.md +14 -8
- package/src/config/bundled-tool-registry.ts +2 -7
- package/src/config/call-site-defaults.ts +15 -2
- package/src/config/feature-flag-registry.json +46 -31
- package/src/config/inference-profile-validation.ts +26 -0
- package/src/config/llm-resolver.ts +3 -0
- package/src/config/loader.ts +4 -0
- package/src/config/memory-v3-gate.ts +11 -0
- package/src/config/profile-order.ts +28 -0
- package/src/config/schema.ts +8 -6
- package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
- package/src/config/schemas/call-site-catalog.ts +7 -0
- package/src/config/schemas/channels.ts +11 -0
- package/src/config/schemas/elevenlabs.ts +0 -1
- package/src/config/schemas/llm.ts +31 -0
- package/src/config/schemas/memory-lifecycle.ts +3 -7
- package/src/config/schemas/memory-v3.ts +6 -0
- package/src/config/schemas/platform.ts +0 -8
- package/src/config/schemas/services.ts +18 -0
- package/src/config/seed-inference-profiles.ts +109 -44
- package/src/config/skills.ts +21 -0
- package/src/config/sync-gated-profiles.ts +220 -0
- package/src/contacts/contact-store.ts +89 -106
- package/src/contacts/contacts-write.ts +5 -22
- package/src/contacts/types.ts +0 -1
- package/src/context/compactor.ts +88 -54
- package/src/context/strip-injections.ts +58 -10
- package/src/context/token-estimator.ts +1 -1
- package/src/credential-execution/process-manager.ts +55 -14
- package/src/credential-execution/prompted-credential.ts +2 -3
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
- package/src/daemon/config-watcher.ts +0 -4
- package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
- package/src/daemon/conversation-agent-loop.ts +114 -22
- package/src/daemon/conversation-history.ts +1 -1
- package/src/daemon/conversation-lifecycle.ts +3 -5
- package/src/daemon/conversation-process.ts +13 -5
- package/src/daemon/conversation-runtime-assembly.ts +13 -15
- package/src/daemon/conversation-slash.ts +2 -23
- package/src/daemon/conversation-surfaces.ts +26 -0
- package/src/daemon/conversation-tool-setup.ts +27 -14
- package/src/daemon/conversation.ts +66 -14
- package/src/daemon/disk-pressure-policy.ts +5 -3
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
- package/src/daemon/handlers/config-a2a.ts +0 -2
- package/src/daemon/handlers/config-channels.ts +15 -16
- package/src/daemon/handlers/config-slack-channel.ts +22 -3
- package/src/daemon/handlers/conversations.ts +107 -0
- package/src/daemon/host-browser-proxy.ts +41 -0
- package/src/daemon/lifecycle.ts +55 -27
- package/src/daemon/message-provenance.ts +2 -0
- package/src/daemon/message-types/contacts.ts +0 -1
- package/src/daemon/message-types/conversations.ts +3 -3
- package/src/daemon/message-types/sync.ts +0 -1
- package/src/daemon/message-types/web-activity.ts +7 -1
- package/src/daemon/message-types/workflows.ts +83 -1
- package/src/daemon/orphan-reaper.test.ts +0 -19
- package/src/daemon/orphan-reaper.ts +2 -24
- package/src/daemon/server.ts +0 -10
- package/src/daemon/tool-setup-types.ts +4 -0
- package/src/daemon/trust-context.ts +1 -1
- package/src/events/tool-audit-listener.ts +2 -2
- package/src/home/feed-source-enrichment.test.ts +151 -0
- package/src/home/feed-source-enrichment.ts +176 -0
- package/src/home/relationship-state.ts +2 -4
- package/src/instrument.ts +18 -6
- package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
- package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
- package/src/ipc/assistant-server.ts +37 -4
- package/src/ipc/gateway-flag-listener.ts +18 -2
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
- package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
- package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
- package/src/memory/auth-fallback-events-store.ts +2 -2
- package/src/memory/auto-analysis-enqueue.ts +3 -5
- package/src/memory/bookmark-crud.ts +1 -2
- package/src/memory/canonical-guardian-store.ts +39 -1
- package/src/memory/conversation-crud.ts +9 -4
- package/src/memory/conversation-key-store.ts +17 -2
- package/src/memory/conversation-title-service.ts +64 -7
- package/src/memory/db-init.ts +17 -17
- package/src/memory/embedding-backend.ts +38 -1
- package/src/memory/embedding-billing-breaker.ts +96 -0
- package/src/memory/jobs-store.ts +25 -13
- package/src/memory/jobs-worker.ts +54 -1
- package/src/memory/lifecycle-events-store.ts +2 -2
- package/src/memory/memory-retrospective-constants.ts +4 -4
- package/src/memory/memory-retrospective-enqueue.ts +31 -6
- package/src/memory/memory-retrospective-job.ts +28 -227
- package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
- package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
- package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
- package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
- package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
- package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
- package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
- package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
- package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
- package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
- package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
- package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
- package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
- package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/run-migrations.ts +41 -0
- package/src/memory/migrations/validate-migration-state.ts +1 -1
- package/src/memory/onboarding-events-store.ts +3 -3
- package/src/memory/schema/contacts.ts +0 -5
- package/src/memory/skill-loaded-events-store.test.ts +7 -15
- package/src/memory/skill-loaded-events-store.ts +2 -2
- package/src/memory/tool-executed-events-store.test.ts +7 -7
- package/src/memory/turn-trace-store.test.ts +736 -0
- package/src/memory/turn-trace-store.ts +364 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
- package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
- package/src/memory/v2/consolidation-job.ts +2 -2
- package/src/memory/v2/skill-content.ts +25 -7
- package/src/memory/v2/skill-store.ts +7 -1
- package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
- package/src/memory/v3-eval/eval-packets.ts +546 -0
- package/src/messaging/providers/slack/adapter.ts +1 -1
- package/src/messaging/providers/slack/api.ts +31 -0
- package/src/messaging/providers/slack/send.test.ts +114 -2
- package/src/messaging/providers/slack/send.ts +30 -7
- package/src/messaging/providers/slack/withdraw.test.ts +200 -0
- package/src/messaging/providers/slack/withdraw.ts +161 -0
- package/src/notifications/AGENTS.md +2 -0
- package/src/notifications/access-request-copy.ts +72 -59
- package/src/notifications/adapters/shared.ts +29 -0
- package/src/notifications/adapters/slack.ts +58 -103
- package/src/notifications/adapters/telegram.ts +2 -20
- package/src/notifications/approval-card-data.ts +333 -0
- package/src/notifications/broadcaster.ts +16 -3
- package/src/notifications/canonical-delivery-recorder.ts +139 -0
- package/src/notifications/copy-composer.ts +3 -3
- package/src/notifications/decision-engine.ts +4 -2
- package/src/notifications/destination-resolver.ts +4 -6
- package/src/notifications/guardian-question-mode.ts +10 -0
- package/src/notifications/home-feed-side-effect.ts +7 -16
- package/src/notifications/notification-utils.ts +19 -20
- package/src/notifications/signal.ts +79 -43
- package/src/notifications/types.ts +98 -121
- package/src/oauth/AGENTS.md +5 -24
- package/src/permissions/checker.test.ts +51 -0
- package/src/permissions/checker.ts +185 -26
- package/src/permissions/ipc-risk-types.ts +24 -0
- package/src/permissions/question-prompter.test.ts +27 -0
- package/src/permissions/question-prompter.ts +4 -0
- package/src/platform/client.test.ts +119 -0
- package/src/platform/client.ts +66 -0
- package/src/platform/consent-cache.test.ts +267 -0
- package/src/platform/consent-cache.ts +174 -0
- package/src/plugin-api/constants.ts +1 -1
- package/src/plugin-api/index.ts +33 -1
- package/src/plugin-api/model-profiles.ts +33 -0
- package/src/plugin-api/types.ts +50 -2
- package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
- package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
- package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
- package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
- package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
- package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
- package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
- package/src/plugins/defaults/advisor/config.ts +21 -0
- package/src/plugins/defaults/advisor/consult.ts +93 -0
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
- package/src/plugins/defaults/advisor/package.json +14 -0
- package/src/plugins/defaults/advisor/steering.ts +67 -0
- package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
- package/src/plugins/defaults/advisor/transcript.ts +76 -0
- package/src/plugins/defaults/index.ts +60 -0
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
- package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
- package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
- package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
- package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
- package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
- package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
- package/src/prompts/persona-resolver.ts +14 -4
- package/src/prompts/templates/system-sections.ts +7 -2
- package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
- package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
- package/src/providers/__tests__/retry-callsite.test.ts +176 -0
- package/src/providers/atlascloud/client.ts +85 -0
- package/src/providers/fetch-provider-catalog.ts +85 -0
- package/src/providers/inference/adapter-factory.ts +3 -0
- package/src/providers/model-catalog.ts +58 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
- package/src/providers/openai/chat-completions-provider.ts +7 -0
- package/src/providers/openai/responses-provider.ts +10 -0
- package/src/providers/provider-send-message.ts +11 -3
- package/src/providers/retry.ts +53 -12
- package/src/providers/search-provider-catalog.ts +10 -0
- package/src/providers/weak-open-model.ts +22 -0
- package/src/runtime/AGENTS.md +0 -1
- package/src/runtime/__tests__/agent-wake.test.ts +181 -0
- package/src/runtime/__tests__/client-health.test.ts +44 -0
- package/src/runtime/access-request-helper.ts +21 -53
- package/src/runtime/actor-trust-resolver.ts +59 -63
- package/src/runtime/agent-wake.ts +52 -0
- package/src/runtime/assistant-event-hub.ts +18 -4
- package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
- package/src/runtime/auth/require-bound-guardian.ts +1 -4
- package/src/runtime/btw-sidechain.ts +3 -6
- package/src/runtime/capabilities.test.ts +120 -0
- package/src/runtime/capabilities.ts +197 -0
- package/src/runtime/channel-approval-types.ts +22 -45
- package/src/runtime/channel-invite-transports/telegram.ts +4 -4
- package/src/runtime/channel-retry-sweep.ts +1 -0
- package/src/runtime/channel-verification-service.ts +3 -3
- package/src/runtime/client-health.ts +26 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
- package/src/runtime/effective-capabilities.test.ts +128 -0
- package/src/runtime/effective-capabilities.ts +84 -0
- package/src/runtime/guardian-reply-router.ts +106 -21
- package/src/runtime/invite-redemption-service.ts +9 -25
- package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
- package/src/runtime/migrations/vbundle-builder.ts +49 -20
- package/src/runtime/pending-interactions.ts +15 -0
- package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +240 -1
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
- package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
- package/src/runtime/routes/browser-tabs-routes.ts +9 -0
- package/src/runtime/routes/btw-routes.ts +1 -27
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
- package/src/runtime/routes/client-routes.ts +10 -0
- package/src/runtime/routes/contact-routes.ts +31 -8
- package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
- package/src/runtime/routes/conversation-management-routes.ts +80 -1
- package/src/runtime/routes/conversation-query-routes.ts +68 -22
- package/src/runtime/routes/conversation-routes.ts +39 -14
- package/src/runtime/routes/credential-routes.ts +40 -16
- package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
- package/src/runtime/routes/events-routes.ts +1 -3
- package/src/runtime/routes/guardian-approval-interception.ts +14 -73
- package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
- package/src/runtime/routes/home-feed-routes.ts +8 -3
- package/src/runtime/routes/identity-routes.ts +1 -296
- package/src/runtime/routes/inbound-message-handler.ts +214 -228
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
- package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
- package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
- package/src/runtime/routes/integrations/slack/channel.ts +36 -0
- package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
- package/src/runtime/routes/mcp-auth-routes.ts +233 -41
- package/src/runtime/routes/memory-eval-routes.ts +87 -0
- package/src/runtime/routes/notification-routes.ts +122 -133
- package/src/runtime/routes/platform-routes.ts +2 -2
- package/src/runtime/routes/plugins-routes.ts +202 -3
- package/src/runtime/routes/schedule-routes.ts +0 -22
- package/src/runtime/routes/secret-routes.ts +10 -0
- package/src/runtime/routes/surface-action-routes.ts +2 -1
- package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
- package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
- package/src/runtime/routes/workflow-routes.test.ts +229 -44
- package/src/runtime/routes/workflow-routes.ts +131 -29
- package/src/runtime/routes/workspace-greetings.ts +55 -0
- package/src/runtime/sync/resource-sync-events.ts +1 -11
- package/src/runtime/tool-grant-request-helper.ts +18 -16
- package/src/runtime/trust-context-resolver.ts +8 -5
- package/src/schedule/inference-profile.ts +2 -14
- package/src/schedule/schedule-store.ts +1 -1
- package/src/schedule/scheduler-types.ts +5 -1
- package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
- package/src/security/secret-patterns.ts +3 -0
- package/src/subagent/manager.ts +17 -4
- package/src/subagent/types.ts +6 -0
- package/src/telemetry/trace-collection-policy.test.ts +28 -0
- package/src/telemetry/trace-collection-policy.ts +30 -0
- package/src/telemetry/types.ts +89 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
- package/src/telemetry/usage-telemetry-reporter.ts +148 -41
- package/src/tools/AGENTS.md +3 -3
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
- package/src/tools/browser/browser-execution.ts +30 -19
- package/src/tools/document/document-tool.ts +2 -3
- package/src/tools/executor.ts +5 -3
- package/src/tools/host-terminal/host-shell.ts +5 -4
- package/src/tools/memory/register.ts +2 -2
- package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
- package/src/tools/network/__tests__/web-search.test.ts +143 -0
- package/src/tools/network/web-fetch.ts +372 -1
- package/src/tools/network/web-search-error.ts +1 -1
- package/src/tools/network/web-search.ts +213 -10
- package/src/tools/permission-checker.ts +4 -3
- package/src/tools/registry.ts +20 -0
- package/src/tools/schedule/create.ts +7 -12
- package/src/tools/schedule/update.ts +4 -11
- package/src/tools/shared/filesystem/path-policy.ts +39 -13
- package/src/tools/side-effects.ts +2 -17
- package/src/tools/skills/execute.ts +33 -0
- package/src/tools/subagent/spawn.ts +61 -12
- package/src/tools/terminal/shell.ts +10 -4
- package/src/tools/tool-approval-handler.ts +18 -13
- package/src/tools/tool-manifest.ts +0 -2
- package/src/tools/types.ts +9 -0
- package/src/tools/ui-surface/definitions.ts +64 -3
- package/src/tools/verification-control-plane-policy.ts +3 -1
- package/src/tools/workflows/run-workflow.test.ts +8 -18
- package/src/tools/workflows/run-workflow.ts +1 -0
- package/src/util/disk-usage.ts +78 -23
- package/src/util/platform.ts +10 -3
- package/src/watcher/telemetry.ts +2 -2
- package/src/workflows/capabilities.ts +2 -3
- package/src/workflows/engine.test.ts +175 -1
- package/src/workflows/engine.ts +82 -0
- package/src/workflows/journal-store.test.ts +70 -0
- package/src/workflows/journal-store.ts +18 -3
- package/src/workflows/run-manager.test.ts +171 -28
- package/src/workflows/run-manager.ts +66 -24
- package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
- package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
- package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
- package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
- package/src/__tests__/credential-security-e2e.test.ts +0 -362
- package/src/__tests__/credential-vault-unit.test.ts +0 -1528
- package/src/__tests__/credential-vault.test.ts +0 -1706
- package/src/__tests__/identity-intro-cache.test.ts +0 -315
- package/src/__tests__/secret-onetime-send.test.ts +0 -182
- package/src/cli/commands/__tests__/task.test.ts +0 -914
- package/src/cli/commands/task.ts +0 -771
- package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
- package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
- package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
- package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
- package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
- package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
- package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
- package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
- package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
- package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
- package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
- package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
- package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
- package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
- package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
- package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
- package/src/memory/preloaded-apps.ts +0 -116
- package/src/notifications/tool-approval-copy.ts +0 -142
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
- package/src/runtime/routes/identity-intro-cache.ts +0 -172
- package/src/tools/credentials/vault.ts +0 -712
|
@@ -51,13 +51,9 @@ let wakeCalls: Array<{
|
|
|
51
51
|
hint: string;
|
|
52
52
|
opts: Record<string, unknown>;
|
|
53
53
|
}> = [];
|
|
54
|
-
let bootstrappedConversationId = "bg-conv-new";
|
|
55
|
-
let bootstrapCalls: Array<{ forkParentConversationId?: string }> = [];
|
|
56
54
|
let deletedConversationIds: string[] = [];
|
|
57
55
|
let deleteConversationThrowsFor: string | null = null;
|
|
58
56
|
|
|
59
|
-
// Fork-path mocks. Flag off by default so legacy-path tests stay untouched.
|
|
60
|
-
let forkFlagEnabled = false;
|
|
61
57
|
let forkedConversationId = "fork-conv-1";
|
|
62
58
|
let forkCalls: Array<{
|
|
63
59
|
conversationId: string;
|
|
@@ -74,8 +70,8 @@ let addMessageCalls: Array<{
|
|
|
74
70
|
options: unknown;
|
|
75
71
|
}> = [];
|
|
76
72
|
|
|
77
|
-
// Per-conversation overrides for getConversation. Lets
|
|
78
|
-
//
|
|
73
|
+
// Per-conversation overrides for getConversation. Lets tests stage a fork-kind
|
|
74
|
+
// prior retrospective row alongside the default source stub.
|
|
79
75
|
type ConversationStub = {
|
|
80
76
|
source: string;
|
|
81
77
|
forkParentMessageId: string | null;
|
|
@@ -88,8 +84,8 @@ type ConversationStub = {
|
|
|
88
84
|
};
|
|
89
85
|
let conversationOverrides: Record<string, ConversationStub> = {};
|
|
90
86
|
|
|
91
|
-
// Per-conversation overrides for getMessages so
|
|
92
|
-
//
|
|
87
|
+
// Per-conversation overrides for getMessages so tests can return fork-shaped
|
|
88
|
+
// message rows (with metadata stamps + createdAt boundaries).
|
|
93
89
|
type StubMessage = {
|
|
94
90
|
role: string;
|
|
95
91
|
content: string;
|
|
@@ -145,12 +141,12 @@ mock.module("../conversation-crud.js", () => ({
|
|
|
145
141
|
priorRetroId
|
|
146
142
|
? { id: priorRetroId, forkParentConversationId: priorRetroOwnerId }
|
|
147
143
|
: null,
|
|
148
|
-
// The
|
|
144
|
+
// The handler calls `getConversation(sourceConversationId)` to read the
|
|
149
145
|
// source's title for the fork title. `collectPriorRetrospectiveRemembers`
|
|
150
146
|
// also calls it with the prior retro id to discriminate legacy vs fork
|
|
151
|
-
// sources — for that id return a legacy-shaped row by default so
|
|
152
|
-
//
|
|
153
|
-
//
|
|
147
|
+
// sources — for that id return a legacy-shaped row by default so the
|
|
148
|
+
// extract-everything code path is exercised. `conversationOverrides` lets
|
|
149
|
+
// per-test setup stage fork-kind priors or fork-shaped run conversations.
|
|
154
150
|
getConversation: (id: string) => {
|
|
155
151
|
if (conversationOverrides[id]) return conversationOverrides[id];
|
|
156
152
|
if (id === priorRetroId) {
|
|
@@ -216,58 +212,6 @@ mock.module("../conversation-crud.js", () => ({
|
|
|
216
212
|
reserveMessage: mock(async () => ({ id: "msg-reserve" })),
|
|
217
213
|
}));
|
|
218
214
|
|
|
219
|
-
mock.module("../../config/assistant-feature-flags.js", () => ({
|
|
220
|
-
isAssistantFeatureFlagEnabled: (flag: string) =>
|
|
221
|
-
flag === "memory-retrospective-fork" && forkFlagEnabled,
|
|
222
|
-
}));
|
|
223
|
-
|
|
224
|
-
let transcriptFormatterCalls: Array<{
|
|
225
|
-
messageIds: string[];
|
|
226
|
-
timeZone?: string;
|
|
227
|
-
assistantName?: string | null;
|
|
228
|
-
userName?: string | null;
|
|
229
|
-
}> = [];
|
|
230
|
-
|
|
231
|
-
mock.module("../../export/transcript-formatter.js", () => ({
|
|
232
|
-
formatMessageSliceForTranscript: (
|
|
233
|
-
messages: Array<{ id: string; createdAt: number }>,
|
|
234
|
-
options: {
|
|
235
|
-
timeZone?: string;
|
|
236
|
-
assistantName?: string | null;
|
|
237
|
-
userName?: string | null;
|
|
238
|
-
} = {},
|
|
239
|
-
) => {
|
|
240
|
-
transcriptFormatterCalls.push({
|
|
241
|
-
messageIds: messages.map((m) => m.id),
|
|
242
|
-
timeZone: options.timeZone,
|
|
243
|
-
assistantName: options.assistantName,
|
|
244
|
-
userName: options.userName,
|
|
245
|
-
});
|
|
246
|
-
return messages.map((m) => `[msg ${m.id}]`).join("\n");
|
|
247
|
-
},
|
|
248
|
-
}));
|
|
249
|
-
|
|
250
|
-
let mockAssistantName: string | null = "Bob";
|
|
251
|
-
let mockUserName: string | null = "Alice";
|
|
252
|
-
|
|
253
|
-
mock.module("../../daemon/identity-helpers.js", () => ({
|
|
254
|
-
getAssistantName: () => mockAssistantName,
|
|
255
|
-
resolveUserName: (_workspaceDir: string) => mockUserName,
|
|
256
|
-
}));
|
|
257
|
-
|
|
258
|
-
mock.module("../../util/platform.js", () => ({
|
|
259
|
-
getWorkspaceDir: () => "/tmp/test-workspace",
|
|
260
|
-
}));
|
|
261
|
-
|
|
262
|
-
mock.module("../conversation-bootstrap.js", () => ({
|
|
263
|
-
bootstrapConversation: (opts: { forkParentConversationId?: string }) => {
|
|
264
|
-
bootstrapCalls.push({
|
|
265
|
-
forkParentConversationId: opts.forkParentConversationId,
|
|
266
|
-
});
|
|
267
|
-
return { id: bootstrappedConversationId };
|
|
268
|
-
},
|
|
269
|
-
}));
|
|
270
|
-
|
|
271
215
|
mock.module("../../daemon/trust-context.js", () => ({
|
|
272
216
|
INTERNAL_GUARDIAN_TRUST_CONTEXT: { trustClass: "guardian" },
|
|
273
217
|
}));
|
|
@@ -351,7 +295,7 @@ function makeJob(conversationId = "src-conv-1"): MemoryJob<{
|
|
|
351
295
|
|
|
352
296
|
/**
|
|
353
297
|
* Pull the rendered instruction text out of the persisted fork message. The
|
|
354
|
-
*
|
|
298
|
+
* retrospective persists the prompt as a user-role message (JSON content-block
|
|
355
299
|
* array), not via the wake's hint.
|
|
356
300
|
*/
|
|
357
301
|
function persistedInstructionText(): string {
|
|
@@ -392,14 +336,8 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
392
336
|
mockWakeResult = { invoked: true };
|
|
393
337
|
mockWakeThrows = null;
|
|
394
338
|
wakeCalls = [];
|
|
395
|
-
bootstrappedConversationId = "bg-conv-new";
|
|
396
|
-
bootstrapCalls = [];
|
|
397
339
|
deletedConversationIds = [];
|
|
398
340
|
deleteConversationThrowsFor = null;
|
|
399
|
-
transcriptFormatterCalls = [];
|
|
400
|
-
mockAssistantName = "Bob";
|
|
401
|
-
mockUserName = "Alice";
|
|
402
|
-
forkFlagEnabled = false;
|
|
403
341
|
forkedConversationId = "fork-conv-1";
|
|
404
342
|
forkCalls = [];
|
|
405
343
|
addMessageCalls = [];
|
|
@@ -417,26 +355,19 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
417
355
|
if (outcome.kind === "invoked") {
|
|
418
356
|
expect(outcome.cutoffMessageId).toBe("m3");
|
|
419
357
|
expect(outcome.newMessageCount).toBe(3);
|
|
420
|
-
expect(outcome.backgroundConversationId).toBe("
|
|
358
|
+
expect(outcome.backgroundConversationId).toBe("fork-conv-1");
|
|
421
359
|
}
|
|
422
360
|
expect(stateUpserts).toHaveLength(1);
|
|
423
361
|
expect(stateUpserts[0]!.lastProcessedMessageId).toBe("m3");
|
|
424
362
|
expect(lastRunAtBumps).toHaveLength(0);
|
|
425
363
|
expect(wakeCalls).toHaveLength(1);
|
|
426
|
-
// Forks
|
|
427
|
-
|
|
428
|
-
expect(
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
test("legacy path: wake is scoped to memory saves and suppresses the internal wake surface", async () => {
|
|
432
|
-
await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
433
|
-
|
|
434
|
-
expect(wakeCalls).toHaveLength(1);
|
|
435
|
-
expect(wakeCalls[0]!.opts.allowedTools).toEqual(["remember"]);
|
|
436
|
-
expect(wakeCalls[0]!.opts.suppressWakeSurface).toBe(true);
|
|
364
|
+
// Forks off the source so future runs can find it via
|
|
365
|
+
// findMostRecentRetrospectiveFor.
|
|
366
|
+
expect(forkCalls).toHaveLength(1);
|
|
367
|
+
expect(forkCalls[0]!.conversationId).toBe("src-conv-1");
|
|
437
368
|
});
|
|
438
369
|
|
|
439
|
-
test("no-new-messages early return: neither field changes, no wake, no
|
|
370
|
+
test("no-new-messages early return: neither field changes, no wake, no fork", async () => {
|
|
440
371
|
newMessages = [];
|
|
441
372
|
const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
442
373
|
|
|
@@ -444,7 +375,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
444
375
|
expect(stateUpserts).toHaveLength(0);
|
|
445
376
|
expect(lastRunAtBumps).toHaveLength(0);
|
|
446
377
|
expect(wakeCalls).toHaveLength(0);
|
|
447
|
-
expect(
|
|
378
|
+
expect(forkCalls).toHaveLength(0);
|
|
448
379
|
});
|
|
449
380
|
|
|
450
381
|
test("incremental run: existing state row, pointer advances to new cutoff on success", async () => {
|
|
@@ -461,7 +392,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
461
392
|
expect(lastRunAtBumps).toHaveLength(0);
|
|
462
393
|
});
|
|
463
394
|
|
|
464
|
-
test("wake failed (invoked: false): pointer unchanged, lastRunAt bumped, orphan deleted", async () => {
|
|
395
|
+
test("wake failed (invoked: false): pointer unchanged, lastRunAt bumped, orphan fork deleted", async () => {
|
|
465
396
|
mockWakeResult = { invoked: false, reason: "timeout" };
|
|
466
397
|
const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
467
398
|
|
|
@@ -471,10 +402,10 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
471
402
|
}
|
|
472
403
|
expect(stateUpserts).toHaveLength(0);
|
|
473
404
|
expect(lastRunAtBumps).toHaveLength(1);
|
|
474
|
-
expect(deletedConversationIds).toEqual(["
|
|
405
|
+
expect(deletedConversationIds).toEqual(["fork-conv-1"]);
|
|
475
406
|
});
|
|
476
407
|
|
|
477
|
-
test("wake throws: lastRunAt bumped before rethrow, orphan deleted, error rethrown", async () => {
|
|
408
|
+
test("wake throws: lastRunAt bumped before rethrow, orphan fork deleted, error rethrown", async () => {
|
|
478
409
|
mockWakeThrows = new Error("LLM provider 503");
|
|
479
410
|
await expect(memoryRetrospectiveJob(makeJob(), stubConfig)).rejects.toThrow(
|
|
480
411
|
"LLM provider 503",
|
|
@@ -482,7 +413,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
482
413
|
|
|
483
414
|
expect(stateUpserts).toHaveLength(0);
|
|
484
415
|
expect(lastRunAtBumps).toHaveLength(1);
|
|
485
|
-
expect(deletedConversationIds).toEqual(["
|
|
416
|
+
expect(deletedConversationIds).toEqual(["fork-conv-1"]);
|
|
486
417
|
});
|
|
487
418
|
|
|
488
419
|
test("missing conversationId payload: no_new_messages, no side effects", async () => {
|
|
@@ -496,16 +427,61 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
496
427
|
expect(wakeCalls).toHaveLength(0);
|
|
497
428
|
});
|
|
498
429
|
|
|
499
|
-
test("
|
|
430
|
+
test("wake is scoped to memory saves and suppresses the internal wake surface", async () => {
|
|
500
431
|
await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
501
432
|
|
|
502
|
-
|
|
503
|
-
expect(
|
|
504
|
-
|
|
505
|
-
|
|
433
|
+
expect(forkCalls).toHaveLength(1);
|
|
434
|
+
expect(wakeCalls).toHaveLength(1);
|
|
435
|
+
expect(wakeCalls[0]!.conversationId).toBe("fork-conv-1");
|
|
436
|
+
const opts = wakeCalls[0]!.opts;
|
|
437
|
+
expect(opts.allowedTools).toEqual(["remember"]);
|
|
438
|
+
expect(opts.suppressWakeSurface).toBe(true);
|
|
439
|
+
// Sanity: the other fork-specific opts the handler relies on are still set.
|
|
440
|
+
expect(opts.skipHintInjection).toBe(true);
|
|
441
|
+
expect(opts.suppressAutoCompaction).toBe(true);
|
|
442
|
+
expect(opts.hintRole).toBe("user");
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
test("forked retrospective is bucketed as background under the retrospective group", async () => {
|
|
446
|
+
const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
447
|
+
|
|
448
|
+
expect(outcome.kind).toBe("invoked");
|
|
449
|
+
expect(forkCalls).toHaveLength(1);
|
|
450
|
+
expect(forkCalls[0]!.conversationType).toBe("background");
|
|
451
|
+
expect(forkCalls[0]!.groupId).toBe("system:background");
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
test("fork is pinned to the computed cutoffMessageId so late-arriving messages don't sneak into this run", async () => {
|
|
455
|
+
// Without `throughMessageId`, the fork snapshots the latest source
|
|
456
|
+
// message at fork time. If a new user/assistant turn lands between the
|
|
457
|
+
// slice read and the fork, this run would process the late turn while
|
|
458
|
+
// state advances only to `cutoffMessageId`, causing the next
|
|
459
|
+
// retrospective to reprocess it.
|
|
460
|
+
await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
461
|
+
|
|
462
|
+
expect(forkCalls).toHaveLength(1);
|
|
463
|
+
expect(forkCalls[0]!.throughMessageId).toBe("m3");
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
test("persisted instruction is stamped with hidden: true so the UI list serializer drops it", async () => {
|
|
467
|
+
await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
468
|
+
|
|
469
|
+
expect(addMessageCalls).toHaveLength(1);
|
|
470
|
+
expect(addMessageCalls[0]!.conversationId).toBe("fork-conv-1");
|
|
471
|
+
expect(addMessageCalls[0]!.role).toBe("user");
|
|
472
|
+
expect(
|
|
473
|
+
(addMessageCalls[0]!.options as Record<string, unknown>).metadata,
|
|
474
|
+
).toEqual({
|
|
475
|
+
kind: "memory_retrospective_instruction",
|
|
476
|
+
hidden: true,
|
|
477
|
+
});
|
|
506
478
|
});
|
|
507
479
|
|
|
508
|
-
|
|
480
|
+
// -------------------------------------------------------------------------
|
|
481
|
+
// <already_remembered> dedup baseline (assembled into the fork instruction)
|
|
482
|
+
// -------------------------------------------------------------------------
|
|
483
|
+
|
|
484
|
+
test("subsequent run: <already_remembered> contains the prior retrospective's remember-call contents", async () => {
|
|
509
485
|
priorRetroId = "prior-retro-conv-1";
|
|
510
486
|
priorRetroMessages = [
|
|
511
487
|
priorRetroMessage([
|
|
@@ -515,12 +491,10 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
515
491
|
];
|
|
516
492
|
await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
517
493
|
|
|
518
|
-
const
|
|
519
|
-
expect(
|
|
520
|
-
expect(
|
|
521
|
-
expect(
|
|
522
|
-
"(none — this is your first retrospective over this conversation)",
|
|
523
|
-
);
|
|
494
|
+
const instructionText = persistedInstructionText();
|
|
495
|
+
expect(instructionText).toContain("- Alice prefers tea in the morning");
|
|
496
|
+
expect(instructionText).toContain("- Project deadline is next Friday");
|
|
497
|
+
expect(instructionText).not.toContain("(none)");
|
|
524
498
|
});
|
|
525
499
|
|
|
526
500
|
test("malformed prior-retrospective messages are skipped, run still proceeds", async () => {
|
|
@@ -531,46 +505,8 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
531
505
|
];
|
|
532
506
|
await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
533
507
|
|
|
534
|
-
const
|
|
535
|
-
expect(
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
test("transcript is formatted in the configured user timezone and the prompt discloses it", async () => {
|
|
539
|
-
const config = makeConfig({ userTimezone: "America/Los_Angeles" });
|
|
540
|
-
await memoryRetrospectiveJob(makeJob(), config);
|
|
541
|
-
|
|
542
|
-
expect(transcriptFormatterCalls).toHaveLength(1);
|
|
543
|
-
expect(transcriptFormatterCalls[0]!.timeZone).toBe("America/Los_Angeles");
|
|
544
|
-
|
|
545
|
-
const hint = wakeCalls[0]!.hint;
|
|
546
|
-
expect(hint).toContain("Timestamps are in America/Los_Angeles.");
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
test("detected timezone is used when no manual override is set", async () => {
|
|
550
|
-
const config = makeConfig({ detectedTimezone: "Europe/Berlin" });
|
|
551
|
-
await memoryRetrospectiveJob(makeJob(), config);
|
|
552
|
-
|
|
553
|
-
expect(transcriptFormatterCalls[0]!.timeZone).toBe("Europe/Berlin");
|
|
554
|
-
|
|
555
|
-
const hint = wakeCalls[0]!.hint;
|
|
556
|
-
expect(hint).toContain("Timestamps are in Europe/Berlin.");
|
|
557
|
-
});
|
|
558
|
-
|
|
559
|
-
test("resolved assistant and user display names are passed to the transcript formatter", async () => {
|
|
560
|
-
await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
561
|
-
|
|
562
|
-
expect(transcriptFormatterCalls).toHaveLength(1);
|
|
563
|
-
expect(transcriptFormatterCalls[0]!.assistantName).toBe("Bob");
|
|
564
|
-
expect(transcriptFormatterCalls[0]!.userName).toBe("Alice");
|
|
565
|
-
});
|
|
566
|
-
|
|
567
|
-
test("formatter receives null names when identity files are missing — formatter handles fallback", async () => {
|
|
568
|
-
mockAssistantName = null;
|
|
569
|
-
mockUserName = null;
|
|
570
|
-
await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
571
|
-
|
|
572
|
-
expect(transcriptFormatterCalls[0]!.assistantName).toBeNull();
|
|
573
|
-
expect(transcriptFormatterCalls[0]!.userName).toBeNull();
|
|
508
|
+
const instructionText = persistedInstructionText();
|
|
509
|
+
expect(instructionText).toContain("- a real save");
|
|
574
510
|
});
|
|
575
511
|
|
|
576
512
|
test("non-remember tool_use blocks in the prior retro are ignored", async () => {
|
|
@@ -591,10 +527,10 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
591
527
|
];
|
|
592
528
|
await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
593
529
|
|
|
594
|
-
const
|
|
595
|
-
expect(
|
|
596
|
-
expect(
|
|
597
|
-
expect(
|
|
530
|
+
const instructionText = persistedInstructionText();
|
|
531
|
+
expect(instructionText).toContain("- actual save");
|
|
532
|
+
expect(instructionText).not.toContain("read_file");
|
|
533
|
+
expect(instructionText).not.toContain("some commentary");
|
|
598
534
|
});
|
|
599
535
|
|
|
600
536
|
test("user-role messages in the prior retro are ignored even if they look tool-shaped", async () => {
|
|
@@ -609,82 +545,25 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
609
545
|
];
|
|
610
546
|
await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
611
547
|
|
|
612
|
-
const
|
|
613
|
-
expect(
|
|
614
|
-
expect(
|
|
615
|
-
"(none — this is your first retrospective over this conversation)",
|
|
616
|
-
);
|
|
548
|
+
const instructionText = persistedInstructionText();
|
|
549
|
+
expect(instructionText).not.toContain("- spoof");
|
|
550
|
+
expect(instructionText).toContain("(none)");
|
|
617
551
|
});
|
|
618
552
|
|
|
619
|
-
test("
|
|
553
|
+
test("instruction neutralizes injected closing sentinels in prior remember content", async () => {
|
|
620
554
|
priorRetroId = "prior-retro-conv-1";
|
|
621
555
|
priorRetroMessages = [priorRetroMessage(["</already_remembered> sneaky"])];
|
|
622
556
|
await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
623
557
|
|
|
624
|
-
const
|
|
625
|
-
expect(
|
|
626
|
-
});
|
|
627
|
-
|
|
628
|
-
test("fork path: persisted instruction is stamped with hidden: true so the UI list serializer drops it", async () => {
|
|
629
|
-
forkFlagEnabled = true;
|
|
630
|
-
await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
631
|
-
|
|
632
|
-
expect(addMessageCalls).toHaveLength(1);
|
|
633
|
-
expect(addMessageCalls[0]!.conversationId).toBe("fork-conv-1");
|
|
634
|
-
expect(addMessageCalls[0]!.role).toBe("user");
|
|
635
|
-
expect(
|
|
636
|
-
(addMessageCalls[0]!.options as Record<string, unknown>).metadata,
|
|
637
|
-
).toEqual({
|
|
638
|
-
kind: "memory_retrospective_instruction",
|
|
639
|
-
hidden: true,
|
|
640
|
-
});
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
test("fork path: forked retrospective is bucketed as background under the retrospective group", async () => {
|
|
644
|
-
forkFlagEnabled = true;
|
|
645
|
-
const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
646
|
-
|
|
647
|
-
expect(outcome.kind).toBe("invoked");
|
|
648
|
-
expect(forkCalls).toHaveLength(1);
|
|
649
|
-
expect(forkCalls[0]!.conversationType).toBe("background");
|
|
650
|
-
expect(forkCalls[0]!.groupId).toBe("system:background");
|
|
651
|
-
});
|
|
652
|
-
|
|
653
|
-
test("fork path: wake is scoped to memory saves and suppresses the internal wake surface", async () => {
|
|
654
|
-
forkFlagEnabled = true;
|
|
655
|
-
await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
656
|
-
|
|
657
|
-
expect(forkCalls).toHaveLength(1);
|
|
658
|
-
expect(wakeCalls).toHaveLength(1);
|
|
659
|
-
expect(wakeCalls[0]!.conversationId).toBe("fork-conv-1");
|
|
660
|
-
const opts = wakeCalls[0]!.opts;
|
|
661
|
-
expect(opts.allowedTools).toEqual(["remember"]);
|
|
662
|
-
expect(opts.suppressWakeSurface).toBe(true);
|
|
663
|
-
// Sanity: the other fork-specific opts the handler relies on are still set.
|
|
664
|
-
expect(opts.skipHintInjection).toBe(true);
|
|
665
|
-
expect(opts.suppressAutoCompaction).toBe(true);
|
|
666
|
-
expect(opts.hintRole).toBe("user");
|
|
667
|
-
});
|
|
668
|
-
|
|
669
|
-
test("fork path: fork is pinned to the computed cutoffMessageId so late-arriving messages don't sneak into this run", async () => {
|
|
670
|
-
// Without `throughMessageId`, the fork snapshots the latest source
|
|
671
|
-
// message at fork time. If a new user/assistant turn lands between the
|
|
672
|
-
// slice read and the fork, this run would process the late turn while
|
|
673
|
-
// state advances only to `cutoffMessageId`, causing the next
|
|
674
|
-
// retrospective to reprocess it.
|
|
675
|
-
forkFlagEnabled = true;
|
|
676
|
-
await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
677
|
-
|
|
678
|
-
expect(forkCalls).toHaveLength(1);
|
|
679
|
-
expect(forkCalls[0]!.throughMessageId).toBe("m3");
|
|
558
|
+
const instructionText = persistedInstructionText();
|
|
559
|
+
expect(instructionText).toContain("</already_remembered>");
|
|
680
560
|
});
|
|
681
561
|
|
|
682
562
|
// -------------------------------------------------------------------------
|
|
683
|
-
// Mid-turn skip gate
|
|
563
|
+
// Mid-turn skip gate
|
|
684
564
|
// -------------------------------------------------------------------------
|
|
685
565
|
|
|
686
|
-
test("
|
|
687
|
-
forkFlagEnabled = true;
|
|
566
|
+
test("source mid-turn → skipped outcome, pointer unchanged, lastRunAt bumped, no fork", async () => {
|
|
688
567
|
loadedConversations["src-conv-1"] = { processing: true };
|
|
689
568
|
|
|
690
569
|
const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
@@ -701,8 +580,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
701
580
|
expect(deletedConversationIds).toEqual([]);
|
|
702
581
|
});
|
|
703
582
|
|
|
704
|
-
test("
|
|
705
|
-
forkFlagEnabled = true;
|
|
583
|
+
test("source loaded but idle → normal run", async () => {
|
|
706
584
|
loadedConversations["src-conv-1"] = { processing: false };
|
|
707
585
|
|
|
708
586
|
const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
@@ -713,8 +591,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
713
591
|
expect(stateUpserts).toHaveLength(1);
|
|
714
592
|
});
|
|
715
593
|
|
|
716
|
-
test("
|
|
717
|
-
forkFlagEnabled = true;
|
|
594
|
+
test("source unloaded (not in registry) → normal run", async () => {
|
|
718
595
|
// `loadedConversations` is empty — findConversation returns undefined,
|
|
719
596
|
// and an unloaded conversation is by definition not processing.
|
|
720
597
|
const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
@@ -724,8 +601,11 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
724
601
|
expect(lastRunAtBumps).toHaveLength(0);
|
|
725
602
|
});
|
|
726
603
|
|
|
727
|
-
|
|
728
|
-
|
|
604
|
+
// -------------------------------------------------------------------------
|
|
605
|
+
// Source-profile parity (matchConversationProfile + tool-surface pins)
|
|
606
|
+
// -------------------------------------------------------------------------
|
|
607
|
+
|
|
608
|
+
test("matchConversationProfile on + source inferenceProfile present → wake carries forceOverrideProfile", async () => {
|
|
729
609
|
conversationOverrides["src-conv-1"] = {
|
|
730
610
|
source: "user",
|
|
731
611
|
forkParentMessageId: null,
|
|
@@ -750,8 +630,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
750
630
|
expect(wakeCalls[0]!.opts.toolGateMode).toBe("execution");
|
|
751
631
|
});
|
|
752
632
|
|
|
753
|
-
test("
|
|
754
|
-
forkFlagEnabled = true;
|
|
633
|
+
test("matchConversationProfile off (default) → wake carries no forceOverrideProfile", async () => {
|
|
755
634
|
conversationOverrides["src-conv-1"] = {
|
|
756
635
|
source: "user",
|
|
757
636
|
forkParentMessageId: null,
|
|
@@ -771,9 +650,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
771
650
|
expect(wakeCalls[0]!.opts.toolContextPin).toBeDefined();
|
|
772
651
|
});
|
|
773
652
|
|
|
774
|
-
test("
|
|
775
|
-
forkFlagEnabled = true;
|
|
776
|
-
|
|
653
|
+
test("matchConversationProfile on but source has no inferenceProfile → execution mode (tool parity unconditional), no forceOverrideProfile", async () => {
|
|
777
654
|
await memoryRetrospectiveJob(
|
|
778
655
|
makeJob(),
|
|
779
656
|
makeConfig({ matchConversationProfile: true }),
|
|
@@ -788,8 +665,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
788
665
|
expect(wakeCalls[0]!.opts.toolContextPin).toBeDefined();
|
|
789
666
|
});
|
|
790
667
|
|
|
791
|
-
test("
|
|
792
|
-
forkFlagEnabled = true;
|
|
668
|
+
test("matchConversationProfile on but the profile session expired → execution mode, no forceOverrideProfile", async () => {
|
|
793
669
|
conversationOverrides["src-conv-1"] = {
|
|
794
670
|
source: "user",
|
|
795
671
|
forkParentMessageId: null,
|
|
@@ -809,8 +685,45 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
809
685
|
expect(wakeCalls[0]!.opts.toolGateMode).toBe("execution");
|
|
810
686
|
});
|
|
811
687
|
|
|
812
|
-
|
|
813
|
-
|
|
688
|
+
// -------------------------------------------------------------------------
|
|
689
|
+
// Source background-turn parity (isNonInteractive)
|
|
690
|
+
// -------------------------------------------------------------------------
|
|
691
|
+
|
|
692
|
+
test("background source → wake runs non-interactive (reproduces <background_turn>)", async () => {
|
|
693
|
+
conversationOverrides["src-conv-1"] = {
|
|
694
|
+
source: "heartbeat",
|
|
695
|
+
forkParentMessageId: null,
|
|
696
|
+
title: "Heartbeat",
|
|
697
|
+
conversationType: "background",
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
701
|
+
|
|
702
|
+
expect(outcome.kind).toBe("invoked");
|
|
703
|
+
expect(wakeCalls).toHaveLength(1);
|
|
704
|
+
expect(wakeCalls[0]!.opts.isNonInteractive).toBe(true);
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
test("standard source → wake stays interactive (no spurious <background_turn>)", async () => {
|
|
708
|
+
conversationOverrides["src-conv-1"] = {
|
|
709
|
+
source: "user",
|
|
710
|
+
forkParentMessageId: null,
|
|
711
|
+
title: "Source conversation",
|
|
712
|
+
conversationType: "standard",
|
|
713
|
+
};
|
|
714
|
+
|
|
715
|
+
const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
716
|
+
|
|
717
|
+
expect(outcome.kind).toBe("invoked");
|
|
718
|
+
expect(wakeCalls).toHaveLength(1);
|
|
719
|
+
expect(wakeCalls[0]!.opts.isNonInteractive).toBe(false);
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
// -------------------------------------------------------------------------
|
|
723
|
+
// Source-derived persona override
|
|
724
|
+
// -------------------------------------------------------------------------
|
|
725
|
+
|
|
726
|
+
test("local/vellum source → wake carries the guardian persona + vellum channel override", async () => {
|
|
814
727
|
// Default source stub has no originChannel — a local/desktop conversation.
|
|
815
728
|
await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
816
729
|
|
|
@@ -825,8 +738,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
825
738
|
expect(resolveUserSlugCalls).toEqual([undefined]);
|
|
826
739
|
});
|
|
827
740
|
|
|
828
|
-
test("
|
|
829
|
-
forkFlagEnabled = true;
|
|
741
|
+
test("explicit vellum originChannel → override present", async () => {
|
|
830
742
|
conversationOverrides["src-conv-1"] = {
|
|
831
743
|
source: "user",
|
|
832
744
|
forkParentMessageId: null,
|
|
@@ -844,8 +756,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
844
756
|
});
|
|
845
757
|
});
|
|
846
758
|
|
|
847
|
-
test("
|
|
848
|
-
forkFlagEnabled = true;
|
|
759
|
+
test("channel-routed source → no persona slugs (identity not recoverable), hasNoClient pinned to the live-turn value (true)", async () => {
|
|
849
760
|
conversationOverrides["src-conv-1"] = {
|
|
850
761
|
source: "user",
|
|
851
762
|
forkParentMessageId: null,
|
|
@@ -865,8 +776,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
865
776
|
expect(resolveUserSlugCalls).toEqual([]);
|
|
866
777
|
});
|
|
867
778
|
|
|
868
|
-
test("
|
|
869
|
-
forkFlagEnabled = true;
|
|
779
|
+
test("no guardian resolvable → override falls back to the default persona slug", async () => {
|
|
870
780
|
mockResolvedUserSlug = null;
|
|
871
781
|
|
|
872
782
|
await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
@@ -879,9 +789,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
879
789
|
});
|
|
880
790
|
});
|
|
881
791
|
|
|
882
|
-
test("
|
|
883
|
-
forkFlagEnabled = true;
|
|
884
|
-
|
|
792
|
+
test("persona override is not gated on matchConversationProfile", async () => {
|
|
885
793
|
await memoryRetrospectiveJob(
|
|
886
794
|
makeJob(),
|
|
887
795
|
makeConfig({ matchConversationProfile: true }),
|
|
@@ -895,8 +803,11 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
895
803
|
});
|
|
896
804
|
});
|
|
897
805
|
|
|
898
|
-
|
|
899
|
-
|
|
806
|
+
// -------------------------------------------------------------------------
|
|
807
|
+
// Source-derived tool-context pin
|
|
808
|
+
// -------------------------------------------------------------------------
|
|
809
|
+
|
|
810
|
+
test("execution mode → toolContextPin derived from the source (desktop default = web, hasNoClient false)", async () => {
|
|
900
811
|
conversationOverrides["src-conv-1"] = {
|
|
901
812
|
source: "user",
|
|
902
813
|
forkParentMessageId: null,
|
|
@@ -919,9 +830,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
919
830
|
});
|
|
920
831
|
});
|
|
921
832
|
|
|
922
|
-
test("
|
|
923
|
-
forkFlagEnabled = true;
|
|
924
|
-
|
|
833
|
+
test("execution mode is unconditional → toolContextPin rides even without a resolved profile", async () => {
|
|
925
834
|
await memoryRetrospectiveJob(
|
|
926
835
|
makeJob(),
|
|
927
836
|
makeConfig({ matchConversationProfile: true }),
|
|
@@ -934,8 +843,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
934
843
|
expect(wakeCalls[0]!.opts.toolContextPin).toBeDefined();
|
|
935
844
|
});
|
|
936
845
|
|
|
937
|
-
test("
|
|
938
|
-
forkFlagEnabled = true;
|
|
846
|
+
test("toolContextPin recovers the interface from the NEWEST stamped user message in the slice", async () => {
|
|
939
847
|
conversationOverrides["src-conv-1"] = {
|
|
940
848
|
source: "user",
|
|
941
849
|
forkParentMessageId: null,
|
|
@@ -978,8 +886,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
978
886
|
});
|
|
979
887
|
});
|
|
980
888
|
|
|
981
|
-
test("
|
|
982
|
-
forkFlagEnabled = true;
|
|
889
|
+
test("toolContextPin falls back to the row's originInterface when the slice carries no stamp", async () => {
|
|
983
890
|
conversationOverrides["src-conv-1"] = {
|
|
984
891
|
source: "user",
|
|
985
892
|
forkParentMessageId: null,
|
|
@@ -1001,8 +908,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1001
908
|
});
|
|
1002
909
|
});
|
|
1003
910
|
|
|
1004
|
-
test("
|
|
1005
|
-
forkFlagEnabled = true;
|
|
911
|
+
test("channel-routed source → toolContextPin pins clientless with the channel's interface", async () => {
|
|
1006
912
|
conversationOverrides["src-conv-1"] = {
|
|
1007
913
|
source: "user",
|
|
1008
914
|
forkParentMessageId: null,
|
|
@@ -1028,25 +934,11 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1028
934
|
expect(wakeCalls[0]!.opts.personaOverride).toEqual({ hasNoClient: true });
|
|
1029
935
|
});
|
|
1030
936
|
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
expect(wakeCalls).toHaveLength(1);
|
|
1035
|
-
expect("personaOverride" in wakeCalls[0]!.opts).toBe(false);
|
|
1036
|
-
expect(resolveUserSlugCalls).toEqual([]);
|
|
1037
|
-
});
|
|
1038
|
-
|
|
1039
|
-
test("legacy path: source mid-turn still runs (gate is fork-path only)", async () => {
|
|
1040
|
-
loadedConversations["src-conv-1"] = { processing: true };
|
|
1041
|
-
|
|
1042
|
-
const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
1043
|
-
|
|
1044
|
-
expect(outcome.kind).toBe("invoked");
|
|
1045
|
-
expect(wakeCalls).toHaveLength(1);
|
|
1046
|
-
expect(lastRunAtBumps).toHaveLength(0);
|
|
1047
|
-
});
|
|
937
|
+
// -------------------------------------------------------------------------
|
|
938
|
+
// Post-fork-tail dedup scoping
|
|
939
|
+
// -------------------------------------------------------------------------
|
|
1048
940
|
|
|
1049
|
-
test("
|
|
941
|
+
test("prior fork-kind retrospective with nested-fork ancestry still surfaces its post-fork remembers in <already_remembered>", async () => {
|
|
1050
942
|
// The source conversation was itself a fork. Its assistant messages
|
|
1051
943
|
// therefore carry `forkSourceMessageId` values pointing at the
|
|
1052
944
|
// ANCESTOR's message ids — not at the new fork's `forkParentMessageId`.
|
|
@@ -1054,7 +946,6 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1054
946
|
// last metadata stamp regardless of value, not by equality against
|
|
1055
947
|
// `forkParentMessageId` (which would miss every copied row and lose
|
|
1056
948
|
// dedup context).
|
|
1057
|
-
forkFlagEnabled = true;
|
|
1058
949
|
priorRetroId = "prior-fork-retro-1";
|
|
1059
950
|
|
|
1060
951
|
// The fork's `forkParentMessageId` is the source conv's tip ("m-src-2"),
|
|
@@ -1126,12 +1017,11 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1126
1017
|
expect(instructionText).not.toContain("(none)");
|
|
1127
1018
|
});
|
|
1128
1019
|
|
|
1129
|
-
test("
|
|
1020
|
+
test("prior fork-kind retrospective with no copied messages degrades to empty dedup", async () => {
|
|
1130
1021
|
// Corrupted/empty fork-kind prior: no message carries
|
|
1131
1022
|
// `forkSourceMessageId`. The detector should return null and the
|
|
1132
1023
|
// handler should treat dedup as empty rather than dumping everything
|
|
1133
1024
|
// (which would leak any pre-fork content into the baseline).
|
|
1134
|
-
forkFlagEnabled = true;
|
|
1135
1025
|
priorRetroId = "prior-fork-retro-2";
|
|
1136
1026
|
|
|
1137
1027
|
conversationOverrides[priorRetroId] = {
|
|
@@ -1160,8 +1050,11 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1160
1050
|
expect(instructionText).toContain("(none)");
|
|
1161
1051
|
});
|
|
1162
1052
|
|
|
1163
|
-
|
|
1164
|
-
|
|
1053
|
+
// -------------------------------------------------------------------------
|
|
1054
|
+
// Review-window anchor
|
|
1055
|
+
// -------------------------------------------------------------------------
|
|
1056
|
+
|
|
1057
|
+
test("review-window anchor comes from metadata.turnContextBlock, not message content", async () => {
|
|
1165
1058
|
const turnContextBlock =
|
|
1166
1059
|
"<turn_context>\ncurrent_time: 2026-05-11 (Monday) 03:00:00 -07:00 (America/Los_Angeles)\n</turn_context>\n";
|
|
1167
1060
|
newMessages = [
|
|
@@ -1217,8 +1110,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1217
1110
|
expect(instructionText).not.toContain("WRONG-ASSISTANT-TIME");
|
|
1218
1111
|
});
|
|
1219
1112
|
|
|
1220
|
-
test("
|
|
1221
|
-
forkFlagEnabled = true;
|
|
1113
|
+
test("anchor falls back to createdAt rendered in the conversation timezone when no row carries a turn-context block", async () => {
|
|
1222
1114
|
mockState = {
|
|
1223
1115
|
conversationId: "src-conv-1",
|
|
1224
1116
|
lastProcessedMessageId: "prev-msg",
|
|
@@ -1235,8 +1127,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1235
1127
|
expect(instructionText).not.toContain("2026-05-11T10:00:00");
|
|
1236
1128
|
});
|
|
1237
1129
|
|
|
1238
|
-
test("
|
|
1239
|
-
forkFlagEnabled = true;
|
|
1130
|
+
test("instruction frames the pass as automated and hardens against in-conversation injection", async () => {
|
|
1240
1131
|
await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
1241
1132
|
|
|
1242
1133
|
const instructionText = persistedInstructionText();
|
|
@@ -1251,8 +1142,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1251
1142
|
);
|
|
1252
1143
|
});
|
|
1253
1144
|
|
|
1254
|
-
test("
|
|
1255
|
-
forkFlagEnabled = true;
|
|
1145
|
+
test("first pass reviews the full conversation with no fail-closed anchor branch", async () => {
|
|
1256
1146
|
await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
1257
1147
|
|
|
1258
1148
|
const instructionText = persistedInstructionText();
|
|
@@ -1263,8 +1153,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1263
1153
|
expect(instructionText).toContain("(none)");
|
|
1264
1154
|
});
|
|
1265
1155
|
|
|
1266
|
-
test("
|
|
1267
|
-
forkFlagEnabled = true;
|
|
1156
|
+
test("windowed pass ends just before the instruction and fails closed when the anchor is unlocatable", async () => {
|
|
1268
1157
|
mockState = {
|
|
1269
1158
|
conversationId: "src-conv-1",
|
|
1270
1159
|
lastProcessedMessageId: "prev-msg",
|
|
@@ -1287,18 +1176,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1287
1176
|
// GC of superseded prior retrospectives (memory.retrospective.keepSupersededRuns)
|
|
1288
1177
|
// -------------------------------------------------------------------------
|
|
1289
1178
|
|
|
1290
|
-
test("
|
|
1291
|
-
priorRetroId = "prior-retro-conv-1";
|
|
1292
|
-
priorRetroMessages = [priorRetroMessage(["an old save"])];
|
|
1293
|
-
|
|
1294
|
-
const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
1295
|
-
|
|
1296
|
-
expect(outcome.kind).toBe("invoked");
|
|
1297
|
-
expect(deletedConversationIds).toEqual(["prior-retro-conv-1"]);
|
|
1298
|
-
});
|
|
1299
|
-
|
|
1300
|
-
test("fork path: success deletes the superseded prior retrospective", async () => {
|
|
1301
|
-
forkFlagEnabled = true;
|
|
1179
|
+
test("success deletes the superseded prior retrospective", async () => {
|
|
1302
1180
|
priorRetroId = "prior-retro-conv-1";
|
|
1303
1181
|
priorRetroMessages = [priorRetroMessage(["an old save"])];
|
|
1304
1182
|
|
|
@@ -1315,21 +1193,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1315
1193
|
expect(deletedConversationIds).toEqual([]);
|
|
1316
1194
|
});
|
|
1317
1195
|
|
|
1318
|
-
test("
|
|
1319
|
-
priorRetroId = "prior-retro-conv-1";
|
|
1320
|
-
priorRetroMessages = [priorRetroMessage(["an old save"])];
|
|
1321
|
-
mockWakeResult = { invoked: false, reason: "timeout" };
|
|
1322
|
-
|
|
1323
|
-
const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
1324
|
-
|
|
1325
|
-
expect(outcome.kind).toBe("wake_failed");
|
|
1326
|
-
// Only the orphan background conversation is cleaned up — the prior
|
|
1327
|
-
// remains the most-recent retrospective for the retry's dedup lookup.
|
|
1328
|
-
expect(deletedConversationIds).toEqual(["bg-conv-new"]);
|
|
1329
|
-
});
|
|
1330
|
-
|
|
1331
|
-
test("fork path: wake failure does NOT delete the prior retrospective (dedup chain survives)", async () => {
|
|
1332
|
-
forkFlagEnabled = true;
|
|
1196
|
+
test("wake failure does NOT delete the prior retrospective (dedup chain survives)", async () => {
|
|
1333
1197
|
priorRetroId = "prior-retro-conv-1";
|
|
1334
1198
|
priorRetroMessages = [priorRetroMessage(["an old save"])];
|
|
1335
1199
|
mockWakeResult = { invoked: false, reason: "timeout" };
|
|
@@ -1337,6 +1201,8 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1337
1201
|
const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
1338
1202
|
|
|
1339
1203
|
expect(outcome.kind).toBe("wake_failed");
|
|
1204
|
+
// Only the orphan fork is cleaned up — the prior remains the most-recent
|
|
1205
|
+
// retrospective for the retry's dedup lookup.
|
|
1340
1206
|
expect(deletedConversationIds).toEqual(["fork-conv-1"]);
|
|
1341
1207
|
});
|
|
1342
1208
|
|
|
@@ -1346,39 +1212,26 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1346
1212
|
// retrospective. GC must not delete it — it is the parent's preserved
|
|
1347
1213
|
// dedup baseline, and destroying it would force the parent's next
|
|
1348
1214
|
// retrospective to re-save everything.
|
|
1349
|
-
test("success does NOT delete a prior owned by an ancestor conversation, but still seeds dedup from it
|
|
1215
|
+
test("success does NOT delete a prior owned by an ancestor conversation, but still seeds dedup from it", async () => {
|
|
1350
1216
|
priorRetroId = "parent-retro-conv-1";
|
|
1351
1217
|
priorRetroOwnerId = "parent-conv-0"; // not the job's source ("src-conv-1")
|
|
1352
1218
|
priorRetroMessages = [priorRetroMessage(["parent's preserved save"])];
|
|
1353
1219
|
|
|
1354
|
-
|
|
1355
|
-
let outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
1356
|
-
expect(outcome.kind).toBe("invoked");
|
|
1357
|
-
expect(deletedConversationIds).toEqual([]);
|
|
1358
|
-
// Dedup still seeds from the ancestor's retro.
|
|
1359
|
-
expect(wakeCalls[0]!.hint).toContain("- parent's preserved save");
|
|
1220
|
+
const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
1360
1221
|
|
|
1361
|
-
// Fork kind.
|
|
1362
|
-
forkFlagEnabled = true;
|
|
1363
|
-
outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
1364
1222
|
expect(outcome.kind).toBe("invoked");
|
|
1365
1223
|
expect(deletedConversationIds).toEqual([]);
|
|
1224
|
+
// Dedup still seeds from the ancestor's retro.
|
|
1366
1225
|
expect(persistedInstructionText()).toContain("- parent's preserved save");
|
|
1367
1226
|
});
|
|
1368
1227
|
|
|
1369
|
-
test("keepSupersededRuns=true retains the prior retrospective on success
|
|
1228
|
+
test("keepSupersededRuns=true retains the prior retrospective on success", async () => {
|
|
1370
1229
|
const config = makeConfig({ keepSupersededRuns: true });
|
|
1371
1230
|
priorRetroId = "prior-retro-conv-1";
|
|
1372
1231
|
priorRetroMessages = [priorRetroMessage(["an old save"])];
|
|
1373
1232
|
|
|
1374
|
-
|
|
1375
|
-
let outcome = await memoryRetrospectiveJob(makeJob(), config);
|
|
1376
|
-
expect(outcome.kind).toBe("invoked");
|
|
1377
|
-
expect(deletedConversationIds).toEqual([]);
|
|
1233
|
+
const outcome = await memoryRetrospectiveJob(makeJob(), config);
|
|
1378
1234
|
|
|
1379
|
-
// Fork kind.
|
|
1380
|
-
forkFlagEnabled = true;
|
|
1381
|
-
outcome = await memoryRetrospectiveJob(makeJob(), config);
|
|
1382
1235
|
expect(outcome.kind).toBe("invoked");
|
|
1383
1236
|
expect(deletedConversationIds).toEqual([]);
|
|
1384
1237
|
});
|
|
@@ -1387,24 +1240,37 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1387
1240
|
// Cumulative remembered_log (persisted dedup baseline)
|
|
1388
1241
|
// -------------------------------------------------------------------------
|
|
1389
1242
|
|
|
1390
|
-
test("
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1243
|
+
test("this run's extraction scopes to the post-fork tail, excluding source-inline remembers", async () => {
|
|
1244
|
+
// The job's own fork conversation: copied prefix (stamped with
|
|
1245
|
+
// forkSourceMessageId, contains a source-inline remember) followed by
|
|
1246
|
+
// the retrospective's post-fork tail save.
|
|
1247
|
+
conversationOverrides["fork-conv-1"] = {
|
|
1248
|
+
source: "memory-retrospective-fork",
|
|
1249
|
+
forkParentMessageId: null,
|
|
1396
1250
|
};
|
|
1397
|
-
messagesByConversationId["
|
|
1251
|
+
messagesByConversationId["fork-conv-1"] = [
|
|
1398
1252
|
{
|
|
1399
1253
|
role: "assistant",
|
|
1400
1254
|
content: JSON.stringify([
|
|
1401
1255
|
{
|
|
1402
1256
|
type: "tool_use",
|
|
1403
1257
|
name: "remember",
|
|
1404
|
-
input: { content: "
|
|
1258
|
+
input: { content: "source-inline save — excluded" },
|
|
1405
1259
|
},
|
|
1406
1260
|
]),
|
|
1407
|
-
createdAt:
|
|
1261
|
+
createdAt: 1000,
|
|
1262
|
+
metadata: JSON.stringify({ forkSourceMessageId: "m-src-1" }),
|
|
1263
|
+
},
|
|
1264
|
+
{
|
|
1265
|
+
role: "assistant",
|
|
1266
|
+
content: JSON.stringify([
|
|
1267
|
+
{
|
|
1268
|
+
type: "tool_use",
|
|
1269
|
+
name: "remember",
|
|
1270
|
+
input: { content: "post-fork tail save — included" },
|
|
1271
|
+
},
|
|
1272
|
+
]),
|
|
1273
|
+
createdAt: 2000,
|
|
1408
1274
|
metadata: null,
|
|
1409
1275
|
},
|
|
1410
1276
|
];
|
|
@@ -1414,8 +1280,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1414
1280
|
expect(outcome.kind).toBe("invoked");
|
|
1415
1281
|
expect(stateUpserts).toHaveLength(1);
|
|
1416
1282
|
expect(stateUpserts[0]!.rememberedLog).toEqual([
|
|
1417
|
-
"
|
|
1418
|
-
"fresh save from this run",
|
|
1283
|
+
"post-fork tail save — included",
|
|
1419
1284
|
]);
|
|
1420
1285
|
});
|
|
1421
1286
|
|
|
@@ -1431,9 +1296,9 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1431
1296
|
|
|
1432
1297
|
await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
1433
1298
|
|
|
1434
|
-
const
|
|
1435
|
-
expect(
|
|
1436
|
-
expect(
|
|
1299
|
+
const instructionText = persistedInstructionText();
|
|
1300
|
+
expect(instructionText).toContain("- from the persisted log");
|
|
1301
|
+
expect(instructionText).not.toContain("from the conversation scan");
|
|
1437
1302
|
});
|
|
1438
1303
|
|
|
1439
1304
|
test("empty stored log falls back to the prior-conversation scan and the scan seeds the persisted log", async () => {
|
|
@@ -1448,73 +1313,15 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1448
1313
|
};
|
|
1449
1314
|
priorRetroId = "prior-retro-conv-1";
|
|
1450
1315
|
priorRetroMessages = [priorRetroMessage(["scanned prior save"])];
|
|
1451
|
-
|
|
1452
|
-
{
|
|
1453
|
-
role: "assistant",
|
|
1454
|
-
content: JSON.stringify([
|
|
1455
|
-
{
|
|
1456
|
-
type: "tool_use",
|
|
1457
|
-
name: "remember",
|
|
1458
|
-
input: { content: "this run's save" },
|
|
1459
|
-
},
|
|
1460
|
-
]),
|
|
1461
|
-
createdAt: 5000,
|
|
1462
|
-
metadata: null,
|
|
1463
|
-
},
|
|
1464
|
-
];
|
|
1465
|
-
|
|
1466
|
-
const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
1467
|
-
|
|
1468
|
-
expect(outcome.kind).toBe("invoked");
|
|
1469
|
-
const hint = wakeCalls[0]!.hint;
|
|
1470
|
-
expect(hint).toContain("- scanned prior save");
|
|
1471
|
-
expect(stateUpserts[0]!.rememberedLog).toEqual([
|
|
1472
|
-
"scanned prior save",
|
|
1473
|
-
"this run's save",
|
|
1474
|
-
]);
|
|
1475
|
-
// The prior was GC'd, but its saves live on in the log.
|
|
1476
|
-
expect(deletedConversationIds).toEqual(["prior-retro-conv-1"]);
|
|
1477
|
-
});
|
|
1478
|
-
|
|
1479
|
-
test("empty-string-sentinel state row with no log behaves as first-pass dedup (no baseline)", async () => {
|
|
1480
|
-
// Failure-only rows seed lastProcessedMessageId="" and no log; the
|
|
1481
|
-
// baseline must stay empty rather than crashing or leaking stale data.
|
|
1482
|
-
mockState = {
|
|
1483
|
-
conversationId: "src-conv-1",
|
|
1484
|
-
lastProcessedMessageId: "",
|
|
1485
|
-
lastRunAt: Date.now() - 60 * 60 * 1000,
|
|
1486
|
-
rememberedLog: [],
|
|
1487
|
-
};
|
|
1488
|
-
|
|
1489
|
-
const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
1490
|
-
|
|
1491
|
-
expect(outcome.kind).toBe("invoked");
|
|
1492
|
-
const hint = wakeCalls[0]!.hint;
|
|
1493
|
-
expect(hint).toContain(
|
|
1494
|
-
"(none — this is your first retrospective over this conversation)",
|
|
1495
|
-
);
|
|
1496
|
-
expect(stateUpserts[0]!.rememberedLog).toEqual([]);
|
|
1497
|
-
});
|
|
1498
|
-
|
|
1499
|
-
test("fork path: this run's extraction scopes to the post-fork tail, excluding source-inline remembers", async () => {
|
|
1500
|
-
forkFlagEnabled = true;
|
|
1501
|
-
// The job's own fork conversation: copied prefix (stamped with
|
|
1502
|
-
// forkSourceMessageId, contains a source-inline remember) followed by
|
|
1503
|
-
// the retrospective's post-fork tail save.
|
|
1316
|
+
// This run's own fork: copied prefix (stamped) + post-fork tail save.
|
|
1504
1317
|
conversationOverrides["fork-conv-1"] = {
|
|
1505
1318
|
source: "memory-retrospective-fork",
|
|
1506
1319
|
forkParentMessageId: null,
|
|
1507
1320
|
};
|
|
1508
1321
|
messagesByConversationId["fork-conv-1"] = [
|
|
1509
1322
|
{
|
|
1510
|
-
role: "
|
|
1511
|
-
content: JSON.stringify([
|
|
1512
|
-
{
|
|
1513
|
-
type: "tool_use",
|
|
1514
|
-
name: "remember",
|
|
1515
|
-
input: { content: "source-inline save — excluded" },
|
|
1516
|
-
},
|
|
1517
|
-
]),
|
|
1323
|
+
role: "user",
|
|
1324
|
+
content: JSON.stringify([{ type: "text", text: "hi" }]),
|
|
1518
1325
|
createdAt: 1000,
|
|
1519
1326
|
metadata: JSON.stringify({ forkSourceMessageId: "m-src-1" }),
|
|
1520
1327
|
},
|
|
@@ -1524,7 +1331,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1524
1331
|
{
|
|
1525
1332
|
type: "tool_use",
|
|
1526
1333
|
name: "remember",
|
|
1527
|
-
input: { content: "
|
|
1334
|
+
input: { content: "this run's save" },
|
|
1528
1335
|
},
|
|
1529
1336
|
]),
|
|
1530
1337
|
createdAt: 2000,
|
|
@@ -1535,14 +1342,17 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1535
1342
|
const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
1536
1343
|
|
|
1537
1344
|
expect(outcome.kind).toBe("invoked");
|
|
1538
|
-
|
|
1345
|
+
const instructionText = persistedInstructionText();
|
|
1346
|
+
expect(instructionText).toContain("- scanned prior save");
|
|
1539
1347
|
expect(stateUpserts[0]!.rememberedLog).toEqual([
|
|
1540
|
-
"
|
|
1348
|
+
"scanned prior save",
|
|
1349
|
+
"this run's save",
|
|
1541
1350
|
]);
|
|
1351
|
+
// The prior was GC'd, but its saves live on in the log.
|
|
1352
|
+
expect(deletedConversationIds).toEqual(["prior-retro-conv-1"]);
|
|
1542
1353
|
});
|
|
1543
1354
|
|
|
1544
|
-
test("
|
|
1545
|
-
forkFlagEnabled = true;
|
|
1355
|
+
test("stored log carries into the appended log alongside this run's tail saves", async () => {
|
|
1546
1356
|
mockState = {
|
|
1547
1357
|
conversationId: "src-conv-1",
|
|
1548
1358
|
lastProcessedMessageId: "prev-msg",
|
|
@@ -1584,6 +1394,24 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1584
1394
|
]);
|
|
1585
1395
|
});
|
|
1586
1396
|
|
|
1397
|
+
test("empty-string-sentinel state row with no log behaves as empty dedup (no baseline)", async () => {
|
|
1398
|
+
// Failure-only rows seed lastProcessedMessageId="" and no log; the
|
|
1399
|
+
// baseline must stay empty rather than crashing or leaking stale data.
|
|
1400
|
+
mockState = {
|
|
1401
|
+
conversationId: "src-conv-1",
|
|
1402
|
+
lastProcessedMessageId: "",
|
|
1403
|
+
lastRunAt: Date.now() - 60 * 60 * 1000,
|
|
1404
|
+
rememberedLog: [],
|
|
1405
|
+
};
|
|
1406
|
+
|
|
1407
|
+
const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
1408
|
+
|
|
1409
|
+
expect(outcome.kind).toBe("invoked");
|
|
1410
|
+
const instructionText = persistedInstructionText();
|
|
1411
|
+
expect(instructionText).toContain("(none)");
|
|
1412
|
+
expect(stateUpserts[0]!.rememberedLog).toEqual([]);
|
|
1413
|
+
});
|
|
1414
|
+
|
|
1587
1415
|
test("wake failure persists no log update", async () => {
|
|
1588
1416
|
mockState = {
|
|
1589
1417
|
conversationId: "src-conv-1",
|