@vellumai/assistant 0.11.6 → 0.11.7-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 +31 -19
- package/Dockerfile +1 -0
- package/docs/architecture/security.md +3 -3
- package/docs/flux-turn-detection-spike.md +8 -7
- package/docs/runbook-trusted-contacts.md +14 -0
- package/docs/stt-provider-onboarding.md +8 -4
- package/knip.json +1 -0
- package/node_modules/@vellumai/avatar-manifest/package.json +18 -0
- package/node_modules/@vellumai/avatar-manifest/src/__tests__/manifest.test.ts +173 -0
- package/node_modules/@vellumai/avatar-manifest/src/__tests__/read.test.ts +114 -0
- package/node_modules/@vellumai/avatar-manifest/src/index.ts +29 -0
- package/node_modules/@vellumai/avatar-manifest/src/layout.ts +18 -0
- package/node_modules/@vellumai/avatar-manifest/src/manifest.ts +156 -0
- package/node_modules/@vellumai/avatar-manifest/src/read.ts +109 -0
- package/node_modules/@vellumai/avatar-manifest/tsconfig.json +20 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
- package/node_modules/@vellumai/ces-client/src/http-credentials.ts +161 -0
- package/node_modules/@vellumai/ces-client/src/index.ts +1 -0
- package/node_modules/@vellumai/environments/package.json +2 -1
- package/node_modules/@vellumai/environments/src/shell.test.ts +56 -0
- package/node_modules/@vellumai/environments/src/shell.ts +65 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +32 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +1 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +82 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/plugin-admission-denied-contract.test.ts +56 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +18 -0
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +5 -11
- package/node_modules/@vellumai/gateway-client/src/channel-permission-contract.ts +3 -1
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +9 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +4 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +75 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +17 -0
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +8 -0
- package/node_modules/@vellumai/gateway-client/src/plugin-admission-denied-contract.ts +47 -0
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +11 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
- package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
- package/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
- package/openapi.yaml +419 -55
- package/package.json +4 -1
- package/scripts/sync-llm-catalog.ts +6 -0
- package/src/__tests__/approval-card-edit-inbound-handoff.test.ts +199 -0
- package/src/__tests__/approval-card-edit.test.ts +153 -0
- package/src/__tests__/attachments-store-heic-normalize.test.ts +1 -1
- package/src/__tests__/available-skills.test.ts +54 -0
- package/src/__tests__/avatar-identity-sync.test.ts +5 -1
- package/src/__tests__/catalog-install-normalize.test.ts +9 -1
- package/src/__tests__/channel-policy.test.ts +1 -3
- package/src/__tests__/channel-readiness-discord.test.ts +9 -70
- package/src/__tests__/compactor-call-site-logging.test.ts +61 -0
- package/src/__tests__/config-loader-backfill.test.ts +69 -0
- package/src/__tests__/config-schema.test.ts +2 -3
- package/src/__tests__/conversation-agent-loop.test.ts +238 -0
- package/src/__tests__/conversation-attachments.test.ts +0 -1
- package/src/__tests__/conversation-error.test.ts +44 -0
- package/src/__tests__/conversation-evictor.test.ts +19 -0
- package/src/__tests__/conversation-fork-referential.test.ts +73 -63
- package/src/__tests__/conversation-fork-retrospective.test.ts +99 -74
- package/src/__tests__/conversation-runtime-assembly.test.ts +23 -0
- package/src/__tests__/conversation-slash.test.ts +9 -0
- package/src/__tests__/conversation-title-service.test.ts +97 -0
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +16 -0
- package/src/__tests__/credential-broker-server-use.test.ts +0 -218
- package/src/__tests__/credential-broker.test.ts +43 -420
- package/src/__tests__/credential-record-write-through.test.ts +78 -0
- package/src/__tests__/credential-routes.test.ts +17 -1
- package/src/__tests__/credential-security-invariants.test.ts +8 -9
- package/src/__tests__/default-profile-catalog-fallback.test.ts +289 -0
- package/src/__tests__/delete-propagation.test.ts +92 -2
- package/src/__tests__/discord-callback-round-trip.test.ts +93 -0
- package/src/__tests__/discord-channel-config.test.ts +237 -0
- package/src/__tests__/edit-propagation.test.ts +42 -2
- package/src/__tests__/evict-conversations-for-reload.test.ts +51 -1
- package/src/__tests__/fallback-breaker.test.ts +1335 -0
- package/src/__tests__/gateway-threshold-reader-mock.ts +6 -0
- package/src/__tests__/guardian-card-withdrawal.test.ts +30 -0
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -2
- package/src/__tests__/image-conversion.test.ts +38 -4
- package/src/__tests__/install-skill-routing.test.ts +25 -0
- package/src/__tests__/llm-catalog-parity.test.ts +4 -0
- package/src/__tests__/llm-resolver.test.ts +74 -0
- package/src/__tests__/llm-schema-fallback-profile.test.ts +359 -0
- package/src/__tests__/managed-fallback-dispatch.test.ts +430 -0
- package/src/__tests__/managed-profile-guard.test.ts +28 -0
- package/src/__tests__/managed-speech-defaults.test.ts +196 -1
- package/src/__tests__/mcp-cli.test.ts +23 -1
- package/src/__tests__/mcp-tool-annotations-risk.test.ts +43 -34
- package/src/__tests__/media-stream-stt-session.test.ts +2 -0
- package/src/__tests__/notification-decision-fallback.test.ts +106 -0
- package/src/__tests__/notification-discord-adapter.test.ts +162 -0
- package/src/__tests__/oauth-connect-orchestrator.test.ts +95 -0
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +6 -8
- package/src/__tests__/persist-unsendable-image.test.ts +3 -3
- package/src/__tests__/platform.test.ts +19 -1
- package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
- package/src/__tests__/provider-catalog-visibility.test.ts +17 -0
- package/src/__tests__/provider-platform-proxy-integration.test.ts +8 -1
- package/src/__tests__/provider-usage-tracking.test.ts +56 -0
- package/src/__tests__/require-fresh-approval.test.ts +5 -2
- package/src/__tests__/retry-fallback-escalation.test.ts +1093 -0
- package/src/__tests__/schedule-retry.test.ts +183 -4
- package/src/__tests__/scheduler-reuse-conversation.test.ts +6 -6
- package/src/__tests__/secret-routes-platform-proxy.test.ts +55 -0
- package/src/__tests__/skill-load-tool.test.ts +34 -0
- package/src/__tests__/skill-tool-factory.test.ts +6 -1
- package/src/__tests__/skill-tool-manifest.test.ts +20 -0
- package/src/__tests__/skills.test.ts +25 -0
- package/src/__tests__/subagent-manager-notify.test.ts +38 -0
- package/src/__tests__/terminal-tools.test.ts +2 -0
- package/src/__tests__/tool-approval-handler.test.ts +146 -2
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +108 -2
- package/src/__tests__/tool-audit.test.ts +111 -40
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +228 -18
- package/src/__tests__/ui-shape-teaching.test.ts +1 -1
- package/src/__tests__/verification-control-plane-policy.test.ts +6 -2
- package/src/__tests__/voice-config-update.test.ts +43 -0
- package/src/__tests__/workspace-migration-147-rename-colliding-backup-profile-names.test.ts +480 -0
- package/src/__tests__/workspace-migration-148-strip-unsupported-fallback-profiles.test.ts +167 -0
- package/src/__tests__/workspace-migration-149-repoint-backup-profile-selections.test.ts +144 -0
- package/src/agent/loop.ts +31 -2
- package/src/approvals/guardian-card-withdrawal.ts +22 -0
- package/src/avatar/__tests__/ensure-raster.test.ts +291 -0
- package/src/avatar/avatar-manifest.ts +48 -100
- package/src/avatar/avatar-store.ts +7 -7
- package/src/avatar/ensure-raster.ts +146 -0
- package/src/avatar/resvg-lazy.test.ts +0 -1
- package/src/avatar/traits-png-sync.ts +9 -10
- package/src/background-wake/wake-intent-hooks.test.ts +0 -1
- package/src/backup/__tests__/paths.test.ts +6 -2
- package/src/backup/paths.ts +6 -4
- package/src/bundler/app-compiler.ts +2 -2
- package/src/bundler/compiler-tools.test.ts +36 -0
- package/src/bundler/compiler-tools.ts +54 -18
- package/src/calls/__tests__/telephony-synthesis-language.test.ts +81 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +57 -0
- package/src/calls/media-stream-stt-session.ts +2 -1
- package/src/calls/telephony-synthesis-language.ts +13 -5
- package/src/calls/voice-session-bridge.ts +19 -4
- package/src/channels/config.ts +23 -12
- package/src/cli/AGENTS.md +3 -1
- package/src/cli/commands/backup.help.ts +10 -13
- package/src/cli/commands/contacts.help.ts +16 -4
- package/src/cli/commands/contacts.ts +5 -1
- package/src/cli/commands/credentials.help.ts +1 -1
- package/src/cli/commands/inference-providers.ts +18 -53
- package/src/cli/commands/mcp.help.ts +6 -5
- package/src/cli/commands/mcp.ts +1 -1
- package/src/cli/commands/memory/memory-retrospective.ts +1 -0
- package/src/cli/commands/stt.help.ts +2 -2
- package/src/config/__tests__/backup-schema.test.ts +1 -1
- package/src/config/__tests__/default-profile-catalog.test.ts +83 -2
- package/src/config/__tests__/loader-sparse-array-cleanup.test.ts +1 -1
- package/src/config/__tests__/memory-retrospective-schema.test.ts +12 -15
- package/src/config/bundled-skills/computer-use/SKILL.md +6 -6
- package/src/config/bundled-skills/computer-use/TOOLS.json +9 -6
- package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +3 -1
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +1 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +16 -15
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +2 -1
- package/src/config/bundled-skills/settings/TOOLS.json +11 -5
- package/src/config/bundled-skills/settings/tools/open-system-settings.test.ts +85 -0
- package/src/config/bundled-skills/settings/tools/open-system-settings.ts +33 -8
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +54 -3
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +1 -1
- package/src/config/default-profile-catalog.ts +195 -10
- package/src/config/default-profile-names.ts +41 -0
- package/src/config/feature-flag-registry.json +34 -2
- package/src/config/inference-profile-validation.ts +11 -2
- package/src/config/llm-resolver.ts +25 -0
- package/src/config/loader.ts +3 -0
- package/src/config/managed-speech-defaults.ts +160 -10
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/live-voice.test.ts +7 -6
- package/src/config/schemas/__tests__/stt.test.ts +94 -0
- package/src/config/schemas/backup.ts +1 -1
- package/src/config/schemas/channels.ts +32 -0
- package/src/config/schemas/live-voice.ts +13 -4
- package/src/config/schemas/llm.ts +263 -15
- package/src/config/schemas/mcp.ts +10 -2
- package/src/config/schemas/memory-retrospective.ts +1 -19
- package/src/config/schemas/services.ts +24 -0
- package/src/config/schemas/stt.ts +128 -5
- package/src/config/seed-inference-profiles.ts +32 -3
- package/src/config/skill-state.ts +5 -0
- package/src/config/skills.ts +21 -0
- package/src/contacts/notify-contacts-changed.ts +5 -2
- package/src/context/compactor.ts +74 -1
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +138 -1
- package/src/daemon/config-watcher.ts +2 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +27 -0
- package/src/daemon/conversation-agent-loop.ts +86 -10
- package/src/daemon/conversation-error.ts +25 -0
- package/src/daemon/conversation-evictor.ts +2 -4
- package/src/daemon/conversation-runtime-assembly.ts +19 -7
- package/src/daemon/conversation-skill-tools.ts +5 -1
- package/src/daemon/conversation-slash.ts +2 -2
- package/src/daemon/conversation-store.ts +8 -1
- package/src/daemon/conversation-tool-setup.ts +37 -0
- package/src/daemon/conversation.ts +0 -4
- package/src/daemon/handlers/channel-config-result.ts +30 -0
- package/src/daemon/handlers/config-discord-channel.ts +301 -0
- package/src/daemon/handlers/config-ingress.ts +9 -0
- package/src/daemon/handlers/config-slack-channel.ts +13 -15
- package/src/daemon/handlers/config-telegram.ts +2 -5
- package/src/daemon/handlers/conversations.ts +0 -2
- package/src/daemon/handlers/skills.ts +25 -7
- package/src/daemon/lifecycle.ts +3 -1
- package/src/daemon/message-types/messages.ts +0 -2
- package/src/daemon/tool-setup-types.ts +5 -3
- package/src/daemon/trust-context-types.test.ts +33 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +58 -4
- package/src/live-voice/__tests__/live-voice-managed-speech-fallback.test.ts +8 -3
- package/src/live-voice/__tests__/live-voice-session-manager.test.ts +73 -11
- package/src/live-voice/__tests__/live-voice-session-preflight.test.ts +148 -2
- package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +64 -0
- package/src/live-voice/__tests__/live-voice-stt.test.ts +3 -0
- package/src/live-voice/__tests__/live-voice-text-turn.test.ts +391 -0
- package/src/live-voice/__tests__/protocol.test.ts +224 -0
- package/src/live-voice/live-voice-credential-preflight.ts +5 -1
- package/src/live-voice/live-voice-session-manager.ts +39 -2
- package/src/live-voice/live-voice-session.ts +224 -9
- package/src/live-voice/protocol.ts +187 -1
- package/src/messaging/providers/discord/api.ts +2 -1
- package/src/messaging/providers/discord/withdraw.ts +62 -0
- package/src/messaging/read-provider-metadata.ts +32 -0
- package/src/monitoring/__tests__/file-descriptors.test.ts +32 -0
- package/src/monitoring/__tests__/process-memory.test.ts +34 -1
- package/src/monitoring/db-integrity-sample.ts +1 -0
- package/src/monitoring/file-descriptors.ts +39 -6
- package/src/monitoring/process-memory.ts +37 -5
- package/src/monitoring/resource-sampler.ts +25 -8
- package/src/notifications/__tests__/activity-failed-dedupe.test.ts +161 -0
- package/src/notifications/__tests__/background-failure-signal.test.ts +107 -0
- package/src/notifications/__tests__/connected-channels.test.ts +12 -0
- package/src/notifications/__tests__/copy-composer.test.ts +83 -0
- package/src/notifications/__tests__/destination-resolver.test.ts +38 -0
- package/src/notifications/activity-failed-dedupe.ts +95 -0
- package/src/notifications/adapters/discord.ts +132 -0
- package/src/notifications/adapters/shared.ts +16 -0
- package/src/notifications/adapters/slack.ts +6 -1
- package/src/notifications/adapters/telegram.ts +5 -8
- package/src/notifications/approval-card-data.ts +94 -1
- package/src/notifications/background-failure-signal.ts +116 -0
- package/src/notifications/broadcaster.ts +3 -9
- package/src/notifications/copy-composer.ts +41 -8
- package/src/notifications/decision-engine.ts +55 -0
- package/src/notifications/destination-resolver.ts +45 -5
- package/src/notifications/emit-signal.ts +19 -5
- package/src/notifications/guardian-question-mode.ts +57 -0
- package/src/notifications/types.ts +2 -8
- package/src/oauth/__tests__/seed-providers-managed.test.ts +18 -0
- package/src/oauth/connect-orchestrator.ts +16 -0
- package/src/oauth/seed-providers.ts +262 -0
- package/src/permissions/checker.test.ts +21 -0
- package/src/permissions/checker.ts +6 -0
- package/src/permissions/gateway-threshold-reader.test.ts +184 -0
- package/src/permissions/gateway-threshold-reader.ts +144 -10
- package/src/permissions/prompter.ts +0 -6
- package/src/permissions/types.ts +5 -4
- package/src/persistence/__tests__/bulk-write-gate.test.ts +0 -38
- package/src/persistence/bulk-write-gate.ts +9 -10
- package/src/persistence/conversation-crud.ts +88 -134
- package/src/persistence/conversation-title-service.ts +43 -29
- package/src/persistence/embeddings/embedding-local.ts +14 -85
- package/src/persistence/embeddings/embedding-runtime-manager.ts +1 -0
- package/src/persistence/lifecycle-events-store.test.ts +50 -0
- package/src/persistence/lifecycle-events-store.ts +75 -5
- package/src/platform/platform-patch-queue.test.ts +368 -0
- package/src/platform/platform-patch-queue.ts +190 -0
- package/src/platform/sync-avatar.test.ts +527 -0
- package/src/platform/sync-avatar.ts +209 -0
- package/src/platform/sync-identity.test.ts +75 -0
- package/src/platform/sync-identity.ts +17 -110
- package/src/plugin-api/model-profiles.test.ts +2 -2
- package/src/plugin-api/model-profiles.ts +7 -7
- package/src/plugin-api/transcription-session.ts +1 -1
- package/src/plugins/defaults/compaction/window-manager.ts +4 -0
- package/src/plugins/defaults/image-recovery/recover.ts +2 -2
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +133 -34
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +21 -31
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +13 -9
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +31 -4
- package/src/plugins/defaults/memory/memory-retrospective-constants.ts +0 -11
- package/src/plugins/defaults/memory/memory-retrospective-fork-boundary.ts +27 -13
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +183 -113
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +13 -13
- package/src/plugins/defaults/memory/src/memory-v2-routes.ts +2 -2
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +1 -1
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +4 -2
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +9 -42
- package/src/plugins/defaults/memory/v2/rerank-local.ts +1 -0
- package/src/plugins/defaults/memory/worker.ts +7 -0
- package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
- package/src/providers/__tests__/retry-callsite.test.ts +55 -0
- package/src/providers/__tests__/vellum-connection-routing.test.ts +21 -3
- package/src/providers/connection-resolution.ts +2 -17
- package/src/providers/fallback-breaker.ts +389 -0
- package/src/providers/inference/__tests__/adapter-factory-opencode.test.ts +92 -0
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +16 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +12 -0
- package/src/providers/inference/adapter-factory.ts +246 -3
- package/src/providers/inference/auth.ts +15 -14
- package/src/providers/inference/chatgpt-subscription-auth.ts +70 -0
- package/src/providers/inference/chatgpt-subscription-credentials.ts +84 -0
- package/src/providers/inference/resolve-auth.ts +1 -1
- package/src/providers/media-resolve.ts +2 -3
- package/src/providers/model-catalog.ts +100 -0
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +22 -0
- package/src/providers/openai/__tests__/chat-template-flatten.test.ts +198 -0
- package/src/providers/openai/api-error-normalization.ts +13 -0
- package/src/providers/openai/chat-completions-provider.ts +84 -0
- package/src/providers/opencode/client.test.ts +150 -0
- package/src/providers/opencode/client.ts +61 -0
- package/src/providers/platform-proxy/constants.ts +5 -0
- package/src/providers/provider-resolvability.ts +20 -0
- package/src/providers/provider-secret-catalog.ts +3 -2
- package/src/providers/retry.ts +956 -68
- package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +42 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +136 -0
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +120 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +316 -12
- package/src/providers/speech-to-text/deepgram-flux-frames.ts +83 -3
- package/src/providers/speech-to-text/deepgram-flux-realtime.ts +132 -4
- package/src/providers/speech-to-text/provider-catalog.ts +191 -3
- package/src/providers/speech-to-text/resolve.ts +132 -24
- package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +296 -0
- package/src/providers/types.ts +39 -0
- package/src/providers/usage-tracking.ts +12 -1
- package/src/providers/vellum/client.ts +29 -0
- package/src/providers/vellum-model-routing.test.ts +2 -0
- package/src/providers/vellum-model-routing.ts +3 -3
- package/src/runtime/AGENTS.md +1 -2
- package/src/runtime/__tests__/agent-wake.test.ts +105 -3
- package/src/runtime/__tests__/background-job-runner.test.ts +89 -3
- package/src/runtime/agent-wake.ts +44 -5
- package/src/runtime/background-job-runner.ts +68 -50
- package/src/runtime/channel-readiness-service.ts +0 -39
- package/src/runtime/http-server.ts +12 -4
- package/src/runtime/routes/__tests__/avatar-state-routes.test.ts +18 -18
- package/src/runtime/routes/__tests__/backup-routes.test.ts +9 -5
- package/src/runtime/routes/__tests__/chatgpt-subscription-auth-routes.test.ts +372 -0
- package/src/runtime/routes/__tests__/contact-routes.test.ts +7 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +96 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +37 -0
- package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +2 -1
- package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +87 -0
- package/src/runtime/routes/acp-claude-auth-routes.ts +25 -54
- package/src/runtime/routes/approval-routes.ts +0 -12
- package/src/runtime/routes/avatar-routes.ts +17 -30
- package/src/runtime/routes/backup-routes.ts +2 -2
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +296 -113
- package/src/runtime/routes/contact-prompt-routes.ts +65 -6
- package/src/runtime/routes/contact-routes.ts +20 -15
- package/src/runtime/routes/conversation-query-routes.ts +98 -10
- package/src/runtime/routes/conversation-routes.ts +2 -2
- package/src/runtime/routes/credential-routes.ts +2 -1
- package/src/runtime/routes/guardian-approval-interception.ts +12 -9
- package/src/runtime/routes/inbound-message-handler.ts +47 -48
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +18 -13
- package/src/runtime/routes/inbound-stages/admission-policy.ts +2 -2
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +20 -2
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +9 -8
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -1
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +2 -2
- package/src/runtime/routes/inference-profiles-routes.ts +12 -7
- package/src/runtime/routes/inference-provider-connection-routes.ts +2 -2
- package/src/runtime/routes/inference-send-routes.ts +2 -2
- package/src/runtime/routes/ingress-status-routes.ts +2 -2
- package/src/runtime/routes/integrations/discord.ts +119 -0
- package/src/runtime/routes/llm-call-sites-routes.ts +2 -2
- package/src/runtime/routes/mcp-auth-routes.ts +12 -6
- package/src/runtime/routes/migration-routes.ts +3 -1
- package/src/runtime/routes/oauth-pending-flows.ts +136 -0
- package/src/runtime/routes/secret-routes.ts +7 -0
- package/src/runtime/routes/stt-routes.ts +38 -6
- package/src/runtime/routes/wake-conversation-routes.ts +8 -1
- package/src/runtime/routes/watch-routes.ts +9 -5
- package/src/runtime/sync/resource-sync-events.ts +2 -0
- package/src/runtime/trust-verdict-consumer.ts +3 -3
- package/src/schedule/__tests__/worker-feature-flags.test.ts +58 -0
- package/src/schedule/retry-policy.ts +8 -8
- package/src/schedule/run-script.ts +14 -23
- package/src/schedule/schedule-recovery.ts +0 -1
- package/src/schedule/scheduler.ts +89 -51
- package/src/schedule/worker.ts +57 -0
- package/src/security/__tests__/openai-device-auth.test.ts +355 -0
- package/src/security/ces-rpc-record-backend.ts +123 -0
- package/src/security/oauth2.ts +7 -0
- package/src/security/openai-device-auth.ts +364 -0
- package/src/security/secure-keys.ts +15 -0
- package/src/skills/available-skills.ts +10 -1
- package/src/skills/catalog-install.ts +19 -1
- package/src/skills/inline-command-runner.ts +6 -2
- package/src/skills/platform-compatibility.test.ts +49 -0
- package/src/skills/platform-compatibility.ts +65 -0
- package/src/skills/tool-manifest.ts +20 -0
- package/src/stt/__tests__/roles.test.ts +152 -0
- package/src/stt/daemon-batch-transcriber.ts +4 -1
- package/src/stt/roles.ts +180 -0
- package/src/stt/stt-stream-session.ts +3 -2
- package/src/stt/types.ts +33 -3
- package/src/subagent/manager.ts +12 -0
- package/src/telemetry/__tests__/live-voice-funnel.test.ts +34 -0
- package/src/telemetry/live-voice-funnel.ts +12 -2
- package/src/telemetry/telemetry-wire.generated.ts +6 -0
- package/src/telemetry/tool-audit.ts +83 -30
- package/src/telemetry/tool-executed-events-store.test.ts +0 -1
- package/src/telemetry/turn-outcome.ts +75 -4
- package/src/tools/client-os.ts +115 -0
- package/src/tools/computer-use/definitions.ts +11 -7
- package/src/tools/computer-use/skill-proxy-bridge.ts +2 -2
- package/src/tools/credentials/broker-types.ts +0 -71
- package/src/tools/credentials/broker.ts +6 -201
- package/src/tools/credentials/metadata-store.ts +65 -16
- package/src/tools/credentials/store.ts +2 -0
- package/src/tools/executor.ts +2 -8
- package/src/tools/host-shell.test.ts +75 -0
- package/src/tools/host-shell.ts +24 -0
- package/src/tools/host-terminal/host-shell.ts +15 -44
- package/src/tools/mcp/mcp-tool-factory.ts +44 -9
- package/src/tools/permission-checker.ts +73 -21
- package/src/tools/policy-context.ts +1 -0
- package/src/tools/shared/filesystem/image-read.ts +1 -1
- package/src/tools/skills/find-similar-skills.ts +10 -2
- package/src/tools/skills/load.ts +41 -6
- package/src/tools/skills/sandbox-runner.ts +8 -16
- package/src/tools/skills/skill-tool-factory.ts +1 -0
- package/src/tools/terminal/__tests__/safe-env.test.ts +64 -1
- package/src/tools/terminal/safe-env.ts +39 -9
- package/src/tools/terminal/shell.ts +12 -31
- package/src/tools/tool-approval-handler.ts +64 -10
- package/src/tools/tool-types.ts +10 -0
- package/src/tools/types.ts +19 -3
- package/src/tools/ui-surface/surface-shape-docs.ts +4 -3
- package/src/tools/workflows/manage-workflows.ts +2 -2
- package/src/tools/workflows/run-workflow.test.ts +2 -1
- package/src/util/__tests__/file-use.test.ts +61 -0
- package/src/util/__tests__/process-table.test.ts +66 -0
- package/src/util/__tests__/process-tree.test.ts +46 -0
- package/src/util/errors.ts +1 -0
- package/src/util/file-use.ts +91 -0
- package/src/util/host-process.test.ts +17 -0
- package/src/util/host-process.ts +75 -0
- package/src/util/image-conversion.ts +39 -7
- package/src/util/platform.ts +32 -13
- package/src/util/process-table.ts +249 -0
- package/src/util/process-tree.ts +85 -129
- package/src/util/provider-error-patterns.ts +20 -0
- package/src/util/spawn.ts +9 -2
- package/src/watch/__tests__/watch-retro.test.ts +48 -1
- package/src/watch/watch-retro.ts +46 -4
- package/src/workspace/git-service.ts +4 -14
- package/src/workspace/migrations/147-rename-colliding-backup-profile-names.ts +360 -0
- package/src/workspace/migrations/148-strip-unsupported-fallback-profiles.ts +117 -0
- package/src/workspace/migrations/149-repoint-backup-profile-selections.ts +204 -0
- package/src/workspace/migrations/150-stt-flux-provider-to-model-family.ts +139 -0
- package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +160 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/__tests__/permission-types.test.ts +0 -17
- package/src/channels/gateway-discord-admission.ts +0 -32
- package/src/persistence/fork-message-copy.ts +0 -239
- package/src/plugins/defaults/memory/__tests__/fork-message-copy.test.ts +0 -221
- package/src/security/__tests__/oauth2-device-code.test.ts +0 -431
- package/src/security/oauth2-device-code.ts +0 -312
|
@@ -2,6 +2,7 @@ import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
4
4
|
|
|
5
|
+
import type { AgentLoopExitReason } from "../../../../hooks/types.js";
|
|
5
6
|
import { getWorkspaceDir } from "../paths.js";
|
|
6
7
|
|
|
7
8
|
// ---------------------------------------------------------------------------
|
|
@@ -48,7 +49,14 @@ let priorRetroId: string | null = null;
|
|
|
48
49
|
let priorRetroOwnerId = "src-conv-1";
|
|
49
50
|
let priorRetroMessages: Array<{ role: string; content: string }> = [];
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
// `exitReason` mirrors what the real wake reports for a completed run: the
|
|
53
|
+
// model answered without asking for another tool. The finalizer reads it to
|
|
54
|
+
// tell a finished review from one something else cut short.
|
|
55
|
+
let mockWakeResult: {
|
|
56
|
+
invoked: boolean;
|
|
57
|
+
reason?: string;
|
|
58
|
+
exitReason?: AgentLoopExitReason;
|
|
59
|
+
} = { invoked: true, exitReason: "no_tool_calls" };
|
|
52
60
|
let mockWakeThrows: Error | null = null;
|
|
53
61
|
let wakeCalls: Array<{
|
|
54
62
|
conversationId: string;
|
|
@@ -481,7 +489,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
481
489
|
priorRetroId = null;
|
|
482
490
|
priorRetroOwnerId = "src-conv-1";
|
|
483
491
|
priorRetroMessages = [];
|
|
484
|
-
mockWakeResult = { invoked: true };
|
|
492
|
+
mockWakeResult = { invoked: true, exitReason: "no_tool_calls" };
|
|
485
493
|
mockWakeThrows = null;
|
|
486
494
|
wakeCalls = [];
|
|
487
495
|
deletedConversationIds = [];
|
|
@@ -822,7 +830,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
822
830
|
// -------------------------------------------------------------------------
|
|
823
831
|
|
|
824
832
|
test("invoked wake with NO persisted run output: no_usable_output, cursor and log untouched, window retryable", async () => {
|
|
825
|
-
mockWakeResult = { invoked: true };
|
|
833
|
+
mockWakeResult = { invoked: true, exitReason: "no_tool_calls" };
|
|
826
834
|
// The fork persisted nothing (e.g. a swallowed provider rejection or a
|
|
827
835
|
// zero-output max-tokens stop).
|
|
828
836
|
messagesByConversationId["fork-conv-1"] = [];
|
|
@@ -843,14 +851,19 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
843
851
|
value: 1,
|
|
844
852
|
detail: {
|
|
845
853
|
outcome: "no_usable_output",
|
|
846
|
-
reason
|
|
854
|
+
// The failure reason names what was missing, so an operator reading
|
|
855
|
+
// the job row can tell a lost write from an unfinished review.
|
|
856
|
+
reason: "run committed neither a memory write nor a concluding reply",
|
|
847
857
|
},
|
|
848
858
|
},
|
|
849
859
|
]);
|
|
850
860
|
});
|
|
851
861
|
|
|
852
|
-
test("
|
|
853
|
-
|
|
862
|
+
test("a run whose reply reads as a conclusion but that the provider cut short: no_usable_output, prior retrospective preserved", async () => {
|
|
863
|
+
// Same persisted text as a finished review, but the loop ended on the
|
|
864
|
+
// output-token ceiling rather than on the model's own stop, so the reply
|
|
865
|
+
// is a fragment of a review, not a verdict on the window.
|
|
866
|
+
mockWakeResult = { invoked: true, exitReason: "max_tokens_reached" };
|
|
854
867
|
priorRetroId = "prior-retro-1";
|
|
855
868
|
messagesByConversationId["fork-conv-1"] = [
|
|
856
869
|
{
|
|
@@ -866,14 +879,38 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
866
879
|
const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
867
880
|
|
|
868
881
|
expect(outcome.kind).toBe("no_usable_output");
|
|
882
|
+
if (outcome.kind === "no_usable_output") {
|
|
883
|
+
expect(outcome.reason).toBe(
|
|
884
|
+
"run replied without saving anything, but ended on max_tokens_reached rather than a completed review",
|
|
885
|
+
);
|
|
886
|
+
}
|
|
869
887
|
expect(stateUpserts).toHaveLength(0);
|
|
870
888
|
// Only this run's own fork is deleted; the prior retrospective (the
|
|
871
889
|
// dedup baseline for the retry) is preserved.
|
|
872
890
|
expect(deletedConversationIds).toEqual(["fork-conv-1"]);
|
|
873
891
|
});
|
|
874
892
|
|
|
875
|
-
test("run
|
|
893
|
+
test("a run the loop never reported a terminal exit for cannot advance on a reply alone", async () => {
|
|
894
|
+
// A checkpoint handoff runs no terminal exit event, so the run resumes
|
|
895
|
+
// elsewhere and this fork holds an unfinished review.
|
|
876
896
|
mockWakeResult = { invoked: true };
|
|
897
|
+
messagesByConversationId["fork-conv-1"] = [
|
|
898
|
+
{
|
|
899
|
+
role: "assistant",
|
|
900
|
+
content: JSON.stringify([{ type: "text", text: "Nothing to save." }]),
|
|
901
|
+
createdAt: Date.parse("2026-05-11T10:20:00Z"),
|
|
902
|
+
metadata: null,
|
|
903
|
+
},
|
|
904
|
+
];
|
|
905
|
+
|
|
906
|
+
const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
907
|
+
|
|
908
|
+
expect(outcome.kind).toBe("no_usable_output");
|
|
909
|
+
expect(stateUpserts).toHaveLength(0);
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
test("run-message load failure classifies as no_usable_output (fail-closed), not success", async () => {
|
|
913
|
+
mockWakeResult = { invoked: true, exitReason: "no_tool_calls" };
|
|
877
914
|
// Fork-kind conversation with no stamps and no leading instruction row:
|
|
878
915
|
// `loadRetrospectiveRunMessages` returns null (indeterminate).
|
|
879
916
|
conversationOverrides["fork-conv-1"] = {
|
|
@@ -896,7 +933,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
896
933
|
});
|
|
897
934
|
|
|
898
935
|
test("checkpoint-persisted saves on a fork-kind tail count as usable output (rebase-after-live cannot fake no-output)", async () => {
|
|
899
|
-
mockWakeResult = { invoked: true };
|
|
936
|
+
mockWakeResult = { invoked: true, exitReason: "no_tool_calls" };
|
|
900
937
|
conversationOverrides["fork-conv-1"] = {
|
|
901
938
|
source: "memory-retrospective-fork",
|
|
902
939
|
forkParentMessageId: null,
|
|
@@ -945,7 +982,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
945
982
|
});
|
|
946
983
|
|
|
947
984
|
test("scaffold_managed_skill counts as durable work even with zero remembers", async () => {
|
|
948
|
-
mockWakeResult = { invoked: true };
|
|
985
|
+
mockWakeResult = { invoked: true, exitReason: "no_tool_calls" };
|
|
949
986
|
messagesByConversationId["fork-conv-1"] = [
|
|
950
987
|
{
|
|
951
988
|
role: "assistant",
|
|
@@ -982,7 +1019,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
982
1019
|
});
|
|
983
1020
|
|
|
984
1021
|
test("a remember whose execution FAILED (is_error tool_result) is not durable evidence and its facts stay out of the log", async () => {
|
|
985
|
-
mockWakeResult = { invoked: true };
|
|
1022
|
+
mockWakeResult = { invoked: true, exitReason: "no_tool_calls" };
|
|
986
1023
|
messagesByConversationId["fork-conv-1"] = [
|
|
987
1024
|
{
|
|
988
1025
|
role: "assistant",
|
|
@@ -1023,41 +1060,91 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1023
1060
|
expect(lastRunAtBumps).toHaveLength(1);
|
|
1024
1061
|
});
|
|
1025
1062
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1063
|
+
// A finished review that found nothing advances on the SHAPE of the run,
|
|
1064
|
+
// never on the wording of the reply. Each phrasing below is a correct
|
|
1065
|
+
// conclusion the model reached on its own; the window it reviewed is
|
|
1066
|
+
// consumed, and the health counter records which kind of success it was.
|
|
1067
|
+
test.each([
|
|
1068
|
+
"Nothing new to save.",
|
|
1069
|
+
"Nothing new to save beyond those two facts.",
|
|
1070
|
+
"Nothing else new to save.",
|
|
1071
|
+
"Done.",
|
|
1072
|
+
"I reviewed the window. Nothing new to save. Here is my reasoning about why each item was already covered by prior passes.",
|
|
1073
|
+
])(
|
|
1074
|
+
"a completed review that saved nothing advances the cursor: %s",
|
|
1075
|
+
async (reply) => {
|
|
1076
|
+
mockWakeResult = { invoked: true, exitReason: "no_tool_calls" };
|
|
1077
|
+
messagesByConversationId["fork-conv-1"] = [
|
|
1078
|
+
{
|
|
1079
|
+
role: "assistant",
|
|
1080
|
+
content: JSON.stringify([{ type: "text", text: reply }]),
|
|
1081
|
+
createdAt: Date.parse("2026-05-11T10:20:00Z"),
|
|
1082
|
+
metadata: null,
|
|
1083
|
+
},
|
|
1084
|
+
];
|
|
1085
|
+
|
|
1086
|
+
const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
1087
|
+
|
|
1088
|
+
expect(outcome.kind).toBe("invoked");
|
|
1089
|
+
if (outcome.kind === "invoked") {
|
|
1090
|
+
expect(outcome.noFindings).toBe(true);
|
|
1091
|
+
}
|
|
1092
|
+
expect(stateUpserts).toHaveLength(1);
|
|
1093
|
+
expect(stateUpserts[0]!.lastProcessedMessageId).toBe("m3");
|
|
1094
|
+
expect(stateUpserts[0]!.rememberedLog).toEqual([]);
|
|
1095
|
+
expect(
|
|
1096
|
+
watchdogEvents.filter(
|
|
1097
|
+
(e) => e.checkName === "memory_retrospective_run",
|
|
1098
|
+
),
|
|
1099
|
+
).toEqual([
|
|
1100
|
+
{
|
|
1101
|
+
checkName: "memory_retrospective_run",
|
|
1102
|
+
value: 1,
|
|
1103
|
+
detail: { outcome: "invoked", noFindings: true },
|
|
1104
|
+
},
|
|
1105
|
+
]);
|
|
1106
|
+
},
|
|
1107
|
+
);
|
|
1108
|
+
|
|
1109
|
+
test("narration followed by an empty final response has not concluded and does not advance", async () => {
|
|
1110
|
+
// The reply that proves an empty-handed review must be the run's final
|
|
1111
|
+
// word: text that went live mid-run followed by an empty last response
|
|
1112
|
+
// is an unfinished review, not a conclusion.
|
|
1113
|
+
mockWakeResult = { invoked: true, exitReason: "no_tool_calls" };
|
|
1028
1114
|
messagesByConversationId["fork-conv-1"] = [
|
|
1029
1115
|
{
|
|
1030
1116
|
role: "assistant",
|
|
1031
1117
|
content: JSON.stringify([
|
|
1032
|
-
{ type: "text", text: "
|
|
1118
|
+
{ type: "text", text: "Let me look through the window carefully." },
|
|
1033
1119
|
]),
|
|
1034
1120
|
createdAt: Date.parse("2026-05-11T10:20:00Z"),
|
|
1035
1121
|
metadata: null,
|
|
1036
1122
|
},
|
|
1123
|
+
{
|
|
1124
|
+
role: "assistant",
|
|
1125
|
+
content: JSON.stringify([]),
|
|
1126
|
+
createdAt: Date.parse("2026-05-11T10:20:05Z"),
|
|
1127
|
+
metadata: null,
|
|
1128
|
+
},
|
|
1037
1129
|
];
|
|
1038
1130
|
|
|
1039
1131
|
const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
1040
1132
|
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
expect(stateUpserts
|
|
1133
|
+
expect(outcome.kind).toBe("no_usable_output");
|
|
1134
|
+
if (outcome.kind === "no_usable_output") {
|
|
1135
|
+
expect(outcome.reason).toBe(
|
|
1136
|
+
"run committed neither a memory write nor a concluding reply",
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
expect(stateUpserts).toHaveLength(0);
|
|
1048
1140
|
});
|
|
1049
1141
|
|
|
1050
|
-
test("
|
|
1051
|
-
mockWakeResult = { invoked: true };
|
|
1142
|
+
test("a run whose only text is whitespace has committed nothing and does not advance", async () => {
|
|
1143
|
+
mockWakeResult = { invoked: true, exitReason: "no_tool_calls" };
|
|
1052
1144
|
messagesByConversationId["fork-conv-1"] = [
|
|
1053
1145
|
{
|
|
1054
1146
|
role: "assistant",
|
|
1055
|
-
content: JSON.stringify([
|
|
1056
|
-
{
|
|
1057
|
-
type: "text",
|
|
1058
|
-
text: "I reviewed the window. Nothing new to save. Here is my reasoning about why each item was already covered by prior passes.",
|
|
1059
|
-
},
|
|
1060
|
-
]),
|
|
1147
|
+
content: JSON.stringify([{ type: "text", text: " \n " }]),
|
|
1061
1148
|
createdAt: Date.parse("2026-05-11T10:20:00Z"),
|
|
1062
1149
|
metadata: null,
|
|
1063
1150
|
},
|
|
@@ -1069,8 +1156,8 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1069
1156
|
expect(stateUpserts).toHaveLength(0);
|
|
1070
1157
|
});
|
|
1071
1158
|
|
|
1072
|
-
test("
|
|
1073
|
-
mockWakeResult = { invoked: true };
|
|
1159
|
+
test("a spoken conclusion cannot advance a run that attempted a save and failed", async () => {
|
|
1160
|
+
mockWakeResult = { invoked: true, exitReason: "no_tool_calls" };
|
|
1074
1161
|
messagesByConversationId["fork-conv-1"] = [
|
|
1075
1162
|
{
|
|
1076
1163
|
role: "assistant",
|
|
@@ -1112,13 +1199,18 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1112
1199
|
|
|
1113
1200
|
// The run demonstrably had findings (it attempted a save); the failed
|
|
1114
1201
|
// write must stay retryable rather than being papered over by a
|
|
1115
|
-
// no-findings claim.
|
|
1202
|
+
// no-findings claim, and the job row must say a write was lost.
|
|
1116
1203
|
expect(outcome.kind).toBe("no_usable_output");
|
|
1204
|
+
if (outcome.kind === "no_usable_output") {
|
|
1205
|
+
expect(outcome.reason).toBe(
|
|
1206
|
+
"run attempted 1 memory write(s), none of which persisted a successful result",
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1117
1209
|
expect(stateUpserts).toHaveLength(0);
|
|
1118
1210
|
});
|
|
1119
1211
|
|
|
1120
1212
|
test("a remember tool_use with NO persisted tool_result is not durable evidence", async () => {
|
|
1121
|
-
mockWakeResult = { invoked: true };
|
|
1213
|
+
mockWakeResult = { invoked: true, exitReason: "no_tool_calls" };
|
|
1122
1214
|
messagesByConversationId["fork-conv-1"] = [
|
|
1123
1215
|
{
|
|
1124
1216
|
role: "assistant",
|
|
@@ -1142,7 +1234,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1142
1234
|
});
|
|
1143
1235
|
|
|
1144
1236
|
test("retry after a no_usable_output run advances only once durable output exists", async () => {
|
|
1145
|
-
mockWakeResult = { invoked: true };
|
|
1237
|
+
mockWakeResult = { invoked: true, exitReason: "no_tool_calls" };
|
|
1146
1238
|
messagesByConversationId["fork-conv-1"] = [];
|
|
1147
1239
|
|
|
1148
1240
|
const first = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
@@ -1735,6 +1827,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1735
1827
|
expect(wakeCalls[0]!.opts.toolContextPin).toEqual({
|
|
1736
1828
|
hasNoClient: false,
|
|
1737
1829
|
transportInterface: "web",
|
|
1830
|
+
clientOs: "web",
|
|
1738
1831
|
requestOrigin: "memory_retrospective",
|
|
1739
1832
|
});
|
|
1740
1833
|
});
|
|
@@ -1771,7 +1864,11 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1771
1864
|
id: "m2",
|
|
1772
1865
|
createdAt: Date.parse("2026-05-11T10:05:00Z"),
|
|
1773
1866
|
role: "user",
|
|
1774
|
-
metadata: JSON.stringify({
|
|
1867
|
+
metadata: JSON.stringify({
|
|
1868
|
+
userMessageInterface: "macos",
|
|
1869
|
+
client: { os: "macos" },
|
|
1870
|
+
clientOsFromRequest: true,
|
|
1871
|
+
}),
|
|
1775
1872
|
},
|
|
1776
1873
|
{
|
|
1777
1874
|
id: "m3",
|
|
@@ -1792,6 +1889,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1792
1889
|
expect(wakeCalls[0]!.opts.toolContextPin).toEqual({
|
|
1793
1890
|
hasNoClient: false,
|
|
1794
1891
|
transportInterface: "macos",
|
|
1892
|
+
clientOs: "macos",
|
|
1795
1893
|
requestOrigin: "memory_retrospective",
|
|
1796
1894
|
});
|
|
1797
1895
|
});
|
|
@@ -1815,6 +1913,7 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1815
1913
|
expect(wakeCalls[0]!.opts.toolContextPin).toEqual({
|
|
1816
1914
|
hasNoClient: false,
|
|
1817
1915
|
transportInterface: "macos",
|
|
1916
|
+
clientOs: "macos",
|
|
1818
1917
|
requestOrigin: "memory_retrospective",
|
|
1819
1918
|
});
|
|
1820
1919
|
});
|
|
@@ -3,7 +3,6 @@ import { tmpdir } from "node:os";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { describe, expect, test } from "bun:test";
|
|
5
5
|
|
|
6
|
-
import { MEMORY_RETROSPECTIVE_NO_FINDINGS_TEXT } from "../memory-retrospective-constants.js";
|
|
7
6
|
import {
|
|
8
7
|
buildForkInstruction,
|
|
9
8
|
type ForkInstructionArgs,
|
|
@@ -11,6 +10,14 @@ import {
|
|
|
11
10
|
} from "../memory-retrospective-prompt.js";
|
|
12
11
|
import { getWorkspaceDir } from "../paths.js";
|
|
13
12
|
|
|
13
|
+
/**
|
|
14
|
+
* The mandate sentence as the prompt module builds it. Spelled out here
|
|
15
|
+
* rather than imported so a reworded prompt fails these expectations instead
|
|
16
|
+
* of silently agreeing with itself.
|
|
17
|
+
*/
|
|
18
|
+
const NO_FINDINGS_MANDATE =
|
|
19
|
+
"If nothing new is worth saving, say so briefly and stop.";
|
|
20
|
+
|
|
14
21
|
function makeArgs(
|
|
15
22
|
overrides: Partial<ForkInstructionArgs> = {},
|
|
16
23
|
): ForkInstructionArgs {
|
|
@@ -48,7 +55,7 @@ Two dedup sources to skip:
|
|
|
48
55
|
1. Anything semantically captured in <already_remembered> above (from prior retrospective passes).
|
|
49
56
|
2. Anything you already called \`remember\` on inline within your review window — those appear as \`tool_use\` blocks with \`name: "remember"\` in your history.
|
|
50
57
|
|
|
51
|
-
For everything else in your review window, use the \`remember\` tool on facts, plans, decisions, preferences, names, dates, felt moments, corrections, commitments, or anything else concrete and worth carrying forward. When several facts are worth saving, pass them all as an array to a single \`remember\` call rather than calling it once per fact. If nothing new is worth saving,
|
|
58
|
+
For everything else in your review window, use the \`remember\` tool on facts, plans, decisions, preferences, names, dates, felt moments, corrections, commitments, or anything else concrete and worth carrying forward. When several facts are worth saving, pass them all as an array to a single \`remember\` call rather than calling it once per fact. If nothing new is worth saving, say so briefly and stop.
|
|
52
59
|
`);
|
|
53
60
|
});
|
|
54
61
|
|
|
@@ -119,11 +126,7 @@ For everything else in your review window, use the \`remember\` tool on facts, p
|
|
|
119
126
|
test("proc-to-skills inactive: no authoring section, instruction ends at the remember guidance", () => {
|
|
120
127
|
const out = buildForkInstruction(makeArgs());
|
|
121
128
|
expect(out).not.toContain("PROCEDURE");
|
|
122
|
-
expect(
|
|
123
|
-
out.endsWith(
|
|
124
|
-
'If nothing new is worth saving, reply with exactly "Nothing new to save." and stop.\n',
|
|
125
|
-
),
|
|
126
|
-
).toBe(true);
|
|
129
|
+
expect(out.endsWith(`${NO_FINDINGS_MANDATE}\n`)).toBe(true);
|
|
127
130
|
});
|
|
128
131
|
|
|
129
132
|
test("bundled template carries each placeholder exactly once", () => {
|
|
@@ -176,17 +179,13 @@ describe("promptOverridePath", () => {
|
|
|
176
179
|
writeFileSync(overridePath, "Just remember the good parts.\n");
|
|
177
180
|
expect(
|
|
178
181
|
buildForkInstruction(makeArgs({ promptOverridePath: overridePath })),
|
|
179
|
-
).toBe(
|
|
180
|
-
"Just remember the good parts.\n\n\n" +
|
|
181
|
-
`If nothing new is worth saving, reply with exactly "${MEMORY_RETROSPECTIVE_NO_FINDINGS_TEXT}" and stop.`,
|
|
182
|
-
);
|
|
182
|
+
).toBe(`Just remember the good parts.\n\n\n${NO_FINDINGS_MANDATE}`);
|
|
183
183
|
});
|
|
184
184
|
|
|
185
|
-
test("an override that
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
// no-findings window forever, so the mandate must survive any override.
|
|
185
|
+
test("an override that tells a no-findings pass to stay silent still carries the reply mandate", () => {
|
|
186
|
+
// A pass that saves nothing advances only on a committed reply, so an
|
|
187
|
+
// override telling it to do nothing would stall every no-findings window
|
|
188
|
+
// forever. The mandate must survive any override.
|
|
190
189
|
const overridePath = join(dir, "mandate-free.md");
|
|
191
190
|
writeFileSync(
|
|
192
191
|
overridePath,
|
|
@@ -195,13 +194,7 @@ describe("promptOverridePath", () => {
|
|
|
195
194
|
const out = buildForkInstruction(
|
|
196
195
|
makeArgs({ promptOverridePath: overridePath }),
|
|
197
196
|
);
|
|
198
|
-
expect(out).
|
|
199
|
-
`reply with exactly "${MEMORY_RETROSPECTIVE_NO_FINDINGS_TEXT}"`,
|
|
200
|
-
);
|
|
201
|
-
// The mandate references the same constant the finalizer compares
|
|
202
|
-
// against, so the two cannot drift; guard the constant's exact value here
|
|
203
|
-
// to make an accidental rewording loud.
|
|
204
|
-
expect(MEMORY_RETROSPECTIVE_NO_FINDINGS_TEXT).toBe("Nothing new to save.");
|
|
197
|
+
expect(out.endsWith(NO_FINDINGS_MANDATE)).toBe(true);
|
|
205
198
|
});
|
|
206
199
|
|
|
207
200
|
test("missing override file falls back to the bundled rendering", () => {
|
|
@@ -227,12 +220,9 @@ describe("promptOverridePath", () => {
|
|
|
227
220
|
});
|
|
228
221
|
});
|
|
229
222
|
|
|
230
|
-
//
|
|
231
|
-
//
|
|
232
|
-
//
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
expect(RETROSPECTIVE_INSTRUCTION_TEMPLATE).toContain(
|
|
236
|
-
`reply with exactly "${MEMORY_RETROSPECTIVE_NO_FINDINGS_TEXT}" and stop.`,
|
|
237
|
-
);
|
|
223
|
+
// A pass that saves nothing advances its window only if it replies at all,
|
|
224
|
+
// and the bundled instruction is what asks for that reply. The wording is
|
|
225
|
+
// free; a template that stopped asking for a reply is not.
|
|
226
|
+
test("bundled template asks a no-findings pass to answer rather than stay silent", () => {
|
|
227
|
+
expect(RETROSPECTIVE_INSTRUCTION_TEMPLATE).toContain(NO_FINDINGS_MANDATE);
|
|
238
228
|
});
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
* - `invoked: true` alone must therefore NOT advance
|
|
18
18
|
* `memory_retrospective_state`: the handler re-reads the fork's persisted
|
|
19
19
|
* rows (`collectRetrospectiveRunEvidence` → `loadRetrospectiveRunMessages`
|
|
20
|
-
* → `getMessages`) and only advances when a durable
|
|
21
|
-
* `tool_use` (`remember` / `scaffold_managed_skill`)
|
|
22
|
-
*
|
|
20
|
+
* → `getMessages`) and only advances when THIS run persisted a durable
|
|
21
|
+
* memory-writing `tool_use` (`remember` / `scaffold_managed_skill`) or
|
|
22
|
+
* replied and stopped on its own (a completed no-findings review).
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
@@ -481,16 +481,20 @@ describe("memoryRetrospectiveJob through the real wake + real agent loop", () =>
|
|
|
481
481
|
expect(lastRunAtBumps).toHaveLength(0);
|
|
482
482
|
});
|
|
483
483
|
|
|
484
|
-
test("
|
|
485
|
-
// Scenario C: the model answers
|
|
486
|
-
//
|
|
487
|
-
//
|
|
484
|
+
test("a text-only conclusion through the real loop advances as a no-findings pass", async () => {
|
|
485
|
+
// Scenario C: the model answers in its own words and stops without any
|
|
486
|
+
// tool call. The real loop exits on `no_tool_calls`, so the committed
|
|
487
|
+
// reply is a finished empty-handed review and the window is consumed.
|
|
488
488
|
providerImpl = async () => textOnlyResponse("Nothing worth saving.");
|
|
489
489
|
|
|
490
490
|
const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
491
491
|
|
|
492
|
-
expect(outcome.kind).toBe("
|
|
492
|
+
expect(outcome.kind).toBe("invoked");
|
|
493
|
+
if (outcome.kind === "invoked") {
|
|
494
|
+
expect(outcome.noFindings).toBe(true);
|
|
495
|
+
}
|
|
493
496
|
expect(providerCalls.length).toBeGreaterThanOrEqual(1);
|
|
494
|
-
expect(stateUpserts).toHaveLength(
|
|
497
|
+
expect(stateUpserts).toHaveLength(1);
|
|
498
|
+
expect(stateUpserts[0]!.rememberedLog).toEqual([]);
|
|
495
499
|
});
|
|
496
500
|
});
|
|
@@ -24,7 +24,9 @@
|
|
|
24
24
|
* 2. The failed window stays retryable: a later run over the same window
|
|
25
25
|
* succeeds and advances state.
|
|
26
26
|
* 3. A usable-output control advances state (cursor, remembered log) and
|
|
27
|
-
* GCs the superseded prior retrospective
|
|
27
|
+
* GCs the superseded prior retrospective, and a pass that reviewed its
|
|
28
|
+
* window and had nothing to save advances it too, in whatever words the
|
|
29
|
+
* model chose.
|
|
28
30
|
* 4. A run whose checkpoint went live before a later rejection stays
|
|
29
31
|
* `invoked: true` (its side effects landed and are honored).
|
|
30
32
|
*/
|
|
@@ -409,9 +411,8 @@ describe("memory retrospective wake state chain (real AgentLoop)", () => {
|
|
|
409
411
|
expect(listRetroForksOf(fixture.sourceId)).toEqual([fixture.priorRetroId]);
|
|
410
412
|
|
|
411
413
|
// (2) The window stays retryable: the same slice succeeds on retry.
|
|
412
|
-
// The finalizer
|
|
413
|
-
//
|
|
414
|
-
// is the other accepted evidence. Use the sentinel for the control.
|
|
414
|
+
// The finalizer accepts a text-only reply as a completed empty-handed
|
|
415
|
+
// review; a verified remember write is the other accepted evidence.
|
|
415
416
|
providerScript = [{ response: textResponse("Nothing new to save.") }];
|
|
416
417
|
providerCallCount = 0;
|
|
417
418
|
const retry = await runForkBasedRetrospective(
|
|
@@ -472,6 +473,31 @@ describe("memory retrospective wake state chain (real AgentLoop)", () => {
|
|
|
472
473
|
).toBe(fixture.newMessageId);
|
|
473
474
|
});
|
|
474
475
|
|
|
476
|
+
test("a paraphrased empty-handed conclusion consumes the window", async () => {
|
|
477
|
+
const fixture = await stageChainFixture();
|
|
478
|
+
|
|
479
|
+
// The instruction asks a pass with nothing to save to say so; it does not
|
|
480
|
+
// dictate the sentence. The real loop ends this run on `no_tool_calls`,
|
|
481
|
+
// which is what proves the model reached its own conclusion.
|
|
482
|
+
providerScript = [
|
|
483
|
+
{ response: textResponse("Nothing further to save, all covered above.") },
|
|
484
|
+
];
|
|
485
|
+
|
|
486
|
+
const outcome = await runForkBasedRetrospective(
|
|
487
|
+
fixture.sourceId,
|
|
488
|
+
chainConfig,
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
expect(outcome.kind).toBe("invoked");
|
|
492
|
+
if (outcome.kind === "invoked") {
|
|
493
|
+
expect(outcome.noFindings).toBe(true);
|
|
494
|
+
}
|
|
495
|
+
const state = getRetrospectiveState(fixture.sourceId);
|
|
496
|
+
expect(state?.lastProcessedMessageId).toBe(fixture.newMessageId);
|
|
497
|
+
// A pass that saved nothing adds nothing to the dedup baseline.
|
|
498
|
+
expect(state?.rememberedLog).toEqual([PRIOR_FACT]);
|
|
499
|
+
});
|
|
500
|
+
|
|
475
501
|
test("usable-output control: cursor advances, remembered log grows, prior retrospective is GC'd", async () => {
|
|
476
502
|
const fixture = await stageChainFixture();
|
|
477
503
|
|
|
@@ -488,6 +514,7 @@ describe("memory retrospective wake state chain (real AgentLoop)", () => {
|
|
|
488
514
|
expect(outcome.kind).toBe("invoked");
|
|
489
515
|
if (outcome.kind === "invoked") {
|
|
490
516
|
expect(outcome.cutoffMessageId).toBe(fixture.newMessageId);
|
|
517
|
+
expect(outcome.noFindings).toBe(false);
|
|
491
518
|
}
|
|
492
519
|
|
|
493
520
|
// (3) Real finalization against real rows: cursor advanced, this run's
|
|
@@ -78,14 +78,3 @@ export const MEMORY_RETROSPECTIVE_ORIGIN = "memory_retrospective";
|
|
|
78
78
|
* set from turn 1, and matched by the permission checker's origin-scoped grant.
|
|
79
79
|
*/
|
|
80
80
|
export const SKILL_MANAGEMENT_SKILL_ID = "skill-management";
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* The exact reply the fork instruction mandates when a reviewed window
|
|
84
|
-
* contains nothing worth saving. The finalizer treats a persisted assistant
|
|
85
|
-
* text block that trims to exactly this phrase (with no memory-writing tool
|
|
86
|
-
* attempts in the run) as the positive artifact of a legitimate no-findings
|
|
87
|
-
* review, advancing the cursor without fabricating a memory write. Compared
|
|
88
|
-
* by strict whole-block equality so analysis prose that merely mentions the
|
|
89
|
-
* phrase does not qualify.
|
|
90
|
-
*/
|
|
91
|
-
export const MEMORY_RETROSPECTIVE_NO_FINDINGS_TEXT = "Nothing new to save.";
|
|
@@ -76,21 +76,27 @@ function isRetrospectiveInstructionRow(metadata: string | null): boolean {
|
|
|
76
76
|
* Load the messages a retrospective run produced itself, given the
|
|
77
77
|
* retrospective conversation's `source` kind:
|
|
78
78
|
*
|
|
79
|
-
* - **Fork-kind
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* tail
|
|
86
|
-
*
|
|
87
|
-
*
|
|
79
|
+
* - **Fork-kind, referential**: the fork copies no prefix. Inherited
|
|
80
|
+
* history is read through lineage and keeps the source `conversationId`.
|
|
81
|
+
* When no copied-row stamp is present, a mixed-ownership list is that
|
|
82
|
+
* shape: rows owned by this conversation are the run, including when
|
|
83
|
+
* that set is empty. No separate conversation-row lookup is required.
|
|
84
|
+
* - **Fork-kind, cloning**: rows carry the copied source prefix (the
|
|
85
|
+
* source's visible tail), so only the post-fork tail (messages strictly
|
|
86
|
+
* after the fork boundary) counts. Scanning the whole list would
|
|
87
|
+
* attribute the source conversation's own turns to the retrospective.
|
|
88
|
+
* When no row carries a `forkSourceMessageId` stamp, the fork is
|
|
89
|
+
* run-authored end-to-end only if its first row is the run's own
|
|
90
|
+
* instruction message (the empty-prefix tail-only fork). A stampless
|
|
91
|
+
* list where every row is owned by this conversation is the
|
|
92
|
+
* indeterminate cloning shape: attributing it would mine copied source
|
|
93
|
+
* tool calls as run output, so the helper degrades to "produced none".
|
|
88
94
|
* - **Legacy-kind** rows start empty, so every message is the run's own.
|
|
89
95
|
*
|
|
90
96
|
* Returns `null` when the run's output cannot be determined (message load
|
|
91
|
-
* failure, or the indeterminate stampless shape above)
|
|
92
|
-
* (empty dedup baseline / "no output"). Best-effort: failures are
|
|
93
|
-
* never thrown.
|
|
97
|
+
* failure, or the indeterminate stampless cloning shape above). Callers
|
|
98
|
+
* degrade (empty dedup baseline / "no output"). Best-effort: failures are
|
|
99
|
+
* logged, never thrown.
|
|
94
100
|
*/
|
|
95
101
|
export async function loadRetrospectiveRunMessages(
|
|
96
102
|
conversationId: string,
|
|
@@ -114,10 +120,18 @@ export async function loadRetrospectiveRunMessages(
|
|
|
114
120
|
return messages;
|
|
115
121
|
}
|
|
116
122
|
if (isRetrospectiveInstructionRow(messages[0]?.metadata ?? null)) {
|
|
117
|
-
// Empty copied prefix
|
|
123
|
+
// Empty copied prefix. The run's instruction opens the conversation,
|
|
118
124
|
// so every message is the run's own output.
|
|
119
125
|
return messages;
|
|
120
126
|
}
|
|
127
|
+
// Referential forks copy nothing. Lineage rows keep the source
|
|
128
|
+
// conversationId, so a mixed-ownership list is a reference fork and
|
|
129
|
+
// the owned rows are the run. A stampless list where every row is
|
|
130
|
+
// owned by this conversation is the indeterminate cloning shape.
|
|
131
|
+
const owned = messages.filter((m) => m.conversationId === conversationId);
|
|
132
|
+
if (owned.length < messages.length) {
|
|
133
|
+
return owned;
|
|
134
|
+
}
|
|
121
135
|
log.warn(
|
|
122
136
|
{ retrospectiveConversationId: conversationId },
|
|
123
137
|
"memory-retrospective: fork-kind retrospective has no forkSourceMessageId stamps and no leading instruction row; treating run as having produced none",
|