@vellumai/assistant 0.11.6 → 0.11.7-staging.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +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/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/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/openapi.yaml +419 -55
- package/package.json +4 -1
- 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-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__/discord-callback-round-trip.test.ts +93 -0
- package/src/__tests__/discord-channel-config.test.ts +237 -0
- 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-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__/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-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 +20 -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 +6 -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 +18 -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 +259 -11
- 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-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 +213 -8
- 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/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/seed-providers.ts +261 -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 +85 -133
- 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__/retry-callsite.test.ts +55 -0
- 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 +231 -3
- package/src/providers/inference/auth.ts +1 -0
- 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 +75 -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/provider-resolvability.ts +20 -0
- 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/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 +21 -9
- 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/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/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/oauth2.ts +7 -0
- package/src/security/openai-device-auth.ts +364 -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/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/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
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* End-to-end guards over referential forking
|
|
3
|
-
*
|
|
4
|
-
* through `fork_parent_message_id`.
|
|
2
|
+
* End-to-end guards over referential forking. A retrospective fork copies no
|
|
3
|
+
* rows and reads its inherited window back through `fork_parent_message_id`.
|
|
5
4
|
*
|
|
6
5
|
* The load-bearing claim is equivalence. A referential fork must present the
|
|
7
|
-
* same message list
|
|
8
|
-
* reads the fork natively and every downstream consumer
|
|
9
|
-
* the fork-boundary scan) reads it through the same
|
|
10
|
-
*
|
|
6
|
+
* same message list the source presents (through the fork point), because the
|
|
7
|
+
* retrospective agent reads the fork natively and every downstream consumer
|
|
8
|
+
* (accounting, dedup, the fork-boundary scan) reads it through the same
|
|
9
|
+
* helpers.
|
|
11
10
|
*/
|
|
12
11
|
|
|
13
|
-
import {
|
|
14
|
-
import { afterAll, beforeEach, describe, expect, test } from "bun:test";
|
|
12
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
15
13
|
|
|
16
14
|
import { eq } from "drizzle-orm";
|
|
17
15
|
|
|
18
|
-
import { invalidateConfigCache } from "../config/loader.js";
|
|
19
16
|
import {
|
|
20
17
|
addMessage,
|
|
21
18
|
countMessagesAfter,
|
|
22
19
|
createConversation,
|
|
23
20
|
deleteConversation,
|
|
24
21
|
deleteConversationGently,
|
|
22
|
+
forkConversation,
|
|
25
23
|
forkConversationForRetrospective,
|
|
26
24
|
getMessages,
|
|
27
25
|
getMessagesAfter,
|
|
@@ -38,36 +36,14 @@ import {
|
|
|
38
36
|
memoryRetrospectiveState,
|
|
39
37
|
messages,
|
|
40
38
|
} from "../persistence/schema/index.js";
|
|
41
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
MEMORY_RETROSPECTIVE_FORK_SOURCE,
|
|
41
|
+
MEMORY_RETROSPECTIVE_INSTRUCTION_KIND,
|
|
42
|
+
} from "../plugins/defaults/memory/memory-retrospective-constants.js";
|
|
43
|
+
import { loadRetrospectiveRunMessages } from "../plugins/defaults/memory/memory-retrospective-fork-boundary.js";
|
|
42
44
|
|
|
43
45
|
await initializeDb();
|
|
44
46
|
|
|
45
|
-
const configPath = getWorkspaceConfigPath();
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Point `memory.retrospective.forkStrategy` at a strategy. The fork path reads
|
|
49
|
-
* it from config rather than taking a parameter, so exercising both branches
|
|
50
|
-
* means writing the workspace config the loader reads.
|
|
51
|
-
*/
|
|
52
|
-
function setForkStrategy(strategy: "cloning" | "reference"): void {
|
|
53
|
-
writeFileSync(
|
|
54
|
-
configPath,
|
|
55
|
-
JSON.stringify(
|
|
56
|
-
{ memory: { retrospective: { forkStrategy: strategy } } },
|
|
57
|
-
null,
|
|
58
|
-
2,
|
|
59
|
-
) + "\n",
|
|
60
|
-
);
|
|
61
|
-
invalidateConfigCache();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function clearConfig(): void {
|
|
65
|
-
rmSync(configPath, { force: true });
|
|
66
|
-
invalidateConfigCache();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
afterAll(clearConfig);
|
|
70
|
-
|
|
71
47
|
function resetTables(): void {
|
|
72
48
|
const db = getDb();
|
|
73
49
|
getMemoryDb()!.delete(memoryRetrospectiveState).run();
|
|
@@ -133,13 +109,11 @@ function textOf(rows: Array<{ content: unknown }>): string[] {
|
|
|
133
109
|
describe("referential forking", () => {
|
|
134
110
|
beforeEach(() => {
|
|
135
111
|
resetTables();
|
|
136
|
-
clearConfig();
|
|
137
112
|
});
|
|
138
113
|
|
|
139
114
|
test("copies no message rows and stamps the strategy", async () => {
|
|
140
115
|
const source = await seedSource("Launch");
|
|
141
116
|
|
|
142
|
-
setForkStrategy("reference");
|
|
143
117
|
const fork = await forkConversationForRetrospective({
|
|
144
118
|
conversationId: source.id,
|
|
145
119
|
});
|
|
@@ -153,7 +127,6 @@ describe("referential forking", () => {
|
|
|
153
127
|
test("reads the source's history through the fork pointer", async () => {
|
|
154
128
|
const source = await seedSource("Launch");
|
|
155
129
|
|
|
156
|
-
setForkStrategy("reference");
|
|
157
130
|
const fork = await forkConversationForRetrospective({
|
|
158
131
|
conversationId: source.id,
|
|
159
132
|
});
|
|
@@ -167,29 +140,23 @@ describe("referential forking", () => {
|
|
|
167
140
|
expect(hasMessages(fork.id)).toBe(true);
|
|
168
141
|
});
|
|
169
142
|
|
|
170
|
-
test("presents the same messages
|
|
143
|
+
test("presents the same messages as the source through the fork point", async () => {
|
|
171
144
|
const source = await seedSource("Launch");
|
|
172
145
|
|
|
173
|
-
setForkStrategy("cloning");
|
|
174
|
-
const cloned = await forkConversationForRetrospective({
|
|
175
|
-
conversationId: source.id,
|
|
176
|
-
});
|
|
177
|
-
setForkStrategy("reference");
|
|
178
146
|
const referenced = await forkConversationForRetrospective({
|
|
179
147
|
conversationId: source.id,
|
|
180
148
|
});
|
|
181
149
|
|
|
182
150
|
expect(textOf(getMessages(referenced.id))).toEqual(
|
|
183
|
-
textOf(getMessages(
|
|
151
|
+
textOf(getMessages(source.id)),
|
|
184
152
|
);
|
|
185
153
|
expect(countMessagesAfter(referenced.id, null)).toBe(
|
|
186
|
-
countMessagesAfter(
|
|
154
|
+
countMessagesAfter(source.id, null),
|
|
187
155
|
);
|
|
188
156
|
});
|
|
189
157
|
|
|
190
158
|
test("rows written on the fork interleave after the inherited ones", async () => {
|
|
191
159
|
const source = await seedSource("Launch");
|
|
192
|
-
setForkStrategy("reference");
|
|
193
160
|
const fork = await forkConversationForRetrospective({
|
|
194
161
|
conversationId: source.id,
|
|
195
162
|
});
|
|
@@ -206,7 +173,6 @@ describe("referential forking", () => {
|
|
|
206
173
|
|
|
207
174
|
test("messages written on the source after the fork point are excluded", async () => {
|
|
208
175
|
const source = await seedSource("Launch");
|
|
209
|
-
setForkStrategy("reference");
|
|
210
176
|
const fork = await forkConversationForRetrospective({
|
|
211
177
|
conversationId: source.id,
|
|
212
178
|
});
|
|
@@ -224,7 +190,6 @@ describe("referential forking", () => {
|
|
|
224
190
|
const source = await seedSource("Launch");
|
|
225
191
|
const sourceRows = getMessages(source.id);
|
|
226
192
|
|
|
227
|
-
setForkStrategy("reference");
|
|
228
193
|
const fork = await forkConversationForRetrospective({
|
|
229
194
|
conversationId: source.id,
|
|
230
195
|
throughMessageId: sourceRows[1]!.id,
|
|
@@ -239,7 +204,6 @@ describe("referential forking", () => {
|
|
|
239
204
|
test("getMessagesAfter and countMessagesAfter cross the lineage boundary", async () => {
|
|
240
205
|
const source = await seedSource("Launch");
|
|
241
206
|
const sourceRows = getMessages(source.id);
|
|
242
|
-
setForkStrategy("reference");
|
|
243
207
|
const fork = await forkConversationForRetrospective({
|
|
244
208
|
conversationId: source.id,
|
|
245
209
|
});
|
|
@@ -255,7 +219,6 @@ describe("referential forking", () => {
|
|
|
255
219
|
|
|
256
220
|
test("pagination returns the inherited rows", async () => {
|
|
257
221
|
const source = await seedSource("Launch");
|
|
258
|
-
setForkStrategy("reference");
|
|
259
222
|
const fork = await forkConversationForRetrospective({
|
|
260
223
|
conversationId: source.id,
|
|
261
224
|
});
|
|
@@ -272,7 +235,6 @@ describe("referential forking", () => {
|
|
|
272
235
|
|
|
273
236
|
test("deleting the source orphans the fork instead of blocking or cascading", async () => {
|
|
274
237
|
const source = await seedSource("Launch");
|
|
275
|
-
setForkStrategy("reference");
|
|
276
238
|
const fork = await forkConversationForRetrospective({
|
|
277
239
|
conversationId: source.id,
|
|
278
240
|
});
|
|
@@ -291,7 +253,6 @@ describe("referential forking", () => {
|
|
|
291
253
|
|
|
292
254
|
test("the gentle delete path orphans the same way", async () => {
|
|
293
255
|
const source = await seedSource("Launch");
|
|
294
|
-
setForkStrategy("reference");
|
|
295
256
|
const fork = await forkConversationForRetrospective({
|
|
296
257
|
conversationId: source.id,
|
|
297
258
|
});
|
|
@@ -306,7 +267,6 @@ describe("referential forking", () => {
|
|
|
306
267
|
|
|
307
268
|
test("an orphaned fork reports the loss so clients can explain it", async () => {
|
|
308
269
|
const source = await seedSource("Launch");
|
|
309
|
-
setForkStrategy("reference");
|
|
310
270
|
const fork = await forkConversationForRetrospective({
|
|
311
271
|
conversationId: source.id,
|
|
312
272
|
});
|
|
@@ -322,10 +282,9 @@ describe("referential forking", () => {
|
|
|
322
282
|
expect(isReferentialHistoryOrphaned(orphan)).toBe(true);
|
|
323
283
|
});
|
|
324
284
|
|
|
325
|
-
test("a
|
|
285
|
+
test("a copied user fork whose source is deleted is not reported as orphaned", async () => {
|
|
326
286
|
const source = await seedSource("Launch");
|
|
327
|
-
|
|
328
|
-
const cloned = await forkConversationForRetrospective({
|
|
287
|
+
const cloned = forkConversation({
|
|
329
288
|
conversationId: source.id,
|
|
330
289
|
});
|
|
331
290
|
|
|
@@ -339,7 +298,6 @@ describe("referential forking", () => {
|
|
|
339
298
|
|
|
340
299
|
test("deleting a fork leaves its source alone", async () => {
|
|
341
300
|
const source = await seedSource("Launch");
|
|
342
|
-
setForkStrategy("reference");
|
|
343
301
|
const fork = await forkConversationForRetrospective({
|
|
344
302
|
conversationId: source.id,
|
|
345
303
|
});
|
|
@@ -349,12 +307,66 @@ describe("referential forking", () => {
|
|
|
349
307
|
expect(conversationIds()).toContain(source.id);
|
|
350
308
|
expect(textOf(getMessages(source.id))).toHaveLength(4);
|
|
351
309
|
});
|
|
310
|
+
|
|
311
|
+
test("loadRetrospectiveRunMessages attributes only owned rows on a reference fork", async () => {
|
|
312
|
+
const source = await seedSource("Launch");
|
|
313
|
+
const tip = getMessages(source.id).at(-1)!;
|
|
314
|
+
const fork = await forkConversationForRetrospective({
|
|
315
|
+
conversationId: source.id,
|
|
316
|
+
throughMessageId: tip.id,
|
|
317
|
+
conversationType: "background",
|
|
318
|
+
source: MEMORY_RETROSPECTIVE_FORK_SOURCE,
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
const instruction = await addMessage(
|
|
322
|
+
fork.id,
|
|
323
|
+
"user",
|
|
324
|
+
"review this conversation",
|
|
325
|
+
{
|
|
326
|
+
metadata: {
|
|
327
|
+
kind: MEMORY_RETROSPECTIVE_INSTRUCTION_KIND,
|
|
328
|
+
hidden: true,
|
|
329
|
+
},
|
|
330
|
+
skipIndexing: true,
|
|
331
|
+
},
|
|
332
|
+
);
|
|
333
|
+
const remember = await addMessage(fork.id, "assistant", "remembered a fact", {
|
|
334
|
+
skipIndexing: true,
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
const runRows = await loadRetrospectiveRunMessages(
|
|
338
|
+
fork.id,
|
|
339
|
+
MEMORY_RETROSPECTIVE_FORK_SOURCE,
|
|
340
|
+
);
|
|
341
|
+
expect(runRows?.map((m) => m.id)).toEqual([instruction.id, remember.id]);
|
|
342
|
+
expect(runRows?.every((m) => m.conversationId === fork.id)).toBe(true);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test("loadRetrospectiveRunMessages returns an empty owned tail on a reference fork with no run rows", async () => {
|
|
346
|
+
const source = await seedSource("Launch");
|
|
347
|
+
const tip = getMessages(source.id).at(-1)!;
|
|
348
|
+
const fork = await forkConversationForRetrospective({
|
|
349
|
+
conversationId: source.id,
|
|
350
|
+
throughMessageId: tip.id,
|
|
351
|
+
conversationType: "background",
|
|
352
|
+
source: MEMORY_RETROSPECTIVE_FORK_SOURCE,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// Lineage still presents the source prefix (those rows keep the source
|
|
356
|
+
// conversationId). Empty owned set is determined output, not
|
|
357
|
+
// indeterminate, and does not need a conversation-row strategy lookup.
|
|
358
|
+
expect(getMessages(fork.id).length).toBeGreaterThan(0);
|
|
359
|
+
const runRows = await loadRetrospectiveRunMessages(
|
|
360
|
+
fork.id,
|
|
361
|
+
MEMORY_RETROSPECTIVE_FORK_SOURCE,
|
|
362
|
+
);
|
|
363
|
+
expect(runRows).toEqual([]);
|
|
364
|
+
});
|
|
352
365
|
});
|
|
353
366
|
|
|
354
367
|
describe("referential forking with unfinalized rows", () => {
|
|
355
368
|
beforeEach(() => {
|
|
356
369
|
resetTables();
|
|
357
|
-
clearConfig();
|
|
358
370
|
});
|
|
359
371
|
|
|
360
372
|
test("ancestor rows are hidden while unfinalized and appear once finalized", async () => {
|
|
@@ -366,7 +378,6 @@ describe("referential forking with unfinalized rows", () => {
|
|
|
366
378
|
.where(eq(messages.id, midRow.id))
|
|
367
379
|
.run();
|
|
368
380
|
|
|
369
|
-
setForkStrategy("reference");
|
|
370
381
|
const fork = await forkConversationForRetrospective({
|
|
371
382
|
conversationId: source.id,
|
|
372
383
|
});
|
|
@@ -404,7 +415,6 @@ describe("referential forking with unfinalized rows", () => {
|
|
|
404
415
|
.where(eq(messages.id, tail.id))
|
|
405
416
|
.run();
|
|
406
417
|
|
|
407
|
-
setForkStrategy("reference");
|
|
408
418
|
const fork = await forkConversationForRetrospective({
|
|
409
419
|
conversationId: source.id,
|
|
410
420
|
});
|
|
@@ -76,19 +76,25 @@ function resetTables(): void {
|
|
|
76
76
|
db.run("DELETE FROM conversations");
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
/**
|
|
80
|
-
function
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
79
|
+
/** Rows physically stored on a conversation, ignoring anything inherited. */
|
|
80
|
+
function ownedRowCount(conversationId: string): number {
|
|
81
|
+
return getDb()
|
|
82
|
+
.select()
|
|
83
|
+
.from(messages)
|
|
84
|
+
.where(eq(messages.conversationId, conversationId))
|
|
85
|
+
.all().length;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function contentOf(m: { role: string; content: unknown; createdAt: number }) {
|
|
89
|
+
return { role: m.role, content: m.content, createdAt: m.createdAt };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function renderWindow(
|
|
93
|
+
summary: string | null,
|
|
94
|
+
rows: Array<{ role: string; content: unknown; createdAt: number }>,
|
|
95
|
+
count: number,
|
|
96
|
+
) {
|
|
97
|
+
return [summary, ...rows.slice(Math.min(count, rows.length)).map(contentOf)];
|
|
92
98
|
}
|
|
93
99
|
|
|
94
100
|
async function seedSource(title: string): Promise<{ id: string }> {
|
|
@@ -113,7 +119,7 @@ describe("forkConversationForRetrospective", () => {
|
|
|
113
119
|
resetTables();
|
|
114
120
|
});
|
|
115
121
|
|
|
116
|
-
test("full fork
|
|
122
|
+
test("full fork presents the source window without copying rows", async () => {
|
|
117
123
|
const source = await seedSource("Planning thread");
|
|
118
124
|
|
|
119
125
|
const syncFork = forkConversation({
|
|
@@ -127,21 +133,20 @@ describe("forkConversationForRetrospective", () => {
|
|
|
127
133
|
source: "memory-retrospective-fork",
|
|
128
134
|
});
|
|
129
135
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
expect(getMessages(asyncFork.id).map(
|
|
133
|
-
getMessages(
|
|
136
|
+
expect(asyncFork.forkStrategy).toBe("reference");
|
|
137
|
+
expect(ownedRowCount(asyncFork.id)).toBe(0);
|
|
138
|
+
expect(getMessages(asyncFork.id).map(contentOf)).toEqual(
|
|
139
|
+
getMessages(source.id).map(contentOf),
|
|
134
140
|
);
|
|
135
141
|
expect(asyncFork.forkParentConversationId).toBe(source.id);
|
|
136
142
|
expect(asyncFork.forkParentMessageId).toBe(syncFork.forkParentMessageId);
|
|
137
|
-
// Fresh ids — not the source's.
|
|
138
143
|
const sourceIds = new Set(getMessages(source.id).map((m) => m.id));
|
|
139
|
-
expect(getMessages(asyncFork.id).every((m) =>
|
|
144
|
+
expect(getMessages(asyncFork.id).every((m) => sourceIds.has(m.id))).toBe(
|
|
140
145
|
true,
|
|
141
146
|
);
|
|
142
147
|
});
|
|
143
148
|
|
|
144
|
-
test("through-cutoff (truncated) fork
|
|
149
|
+
test("through-cutoff (truncated) fork bounds the inherited window", async () => {
|
|
145
150
|
const source = await seedSource("Truncated thread");
|
|
146
151
|
const sourceMessages = getMessages(source.id);
|
|
147
152
|
const cutoff = sourceMessages[1]!.id;
|
|
@@ -159,8 +164,10 @@ describe("forkConversationForRetrospective", () => {
|
|
|
159
164
|
source: "memory-retrospective-fork",
|
|
160
165
|
});
|
|
161
166
|
|
|
162
|
-
expect(
|
|
163
|
-
|
|
167
|
+
expect(asyncFork.forkStrategy).toBe("reference");
|
|
168
|
+
expect(ownedRowCount(asyncFork.id)).toBe(0);
|
|
169
|
+
expect(getMessages(asyncFork.id).map(contentOf)).toEqual(
|
|
170
|
+
sourceMessages.slice(0, 2).map(contentOf),
|
|
164
171
|
);
|
|
165
172
|
expect(asyncFork.forkParentMessageId).toBe(syncFork.forkParentMessageId);
|
|
166
173
|
});
|
|
@@ -193,7 +200,7 @@ describe("forkConversationForRetrospective", () => {
|
|
|
193
200
|
).toBe(false);
|
|
194
201
|
});
|
|
195
202
|
|
|
196
|
-
test("
|
|
203
|
+
test("resolves attachments through source message ids", async () => {
|
|
197
204
|
const source = createConversation("Attachment thread");
|
|
198
205
|
const assistant = await addMessage(source.id, "assistant", "see mockup", {
|
|
199
206
|
skipIndexing: true,
|
|
@@ -212,10 +219,11 @@ describe("forkConversationForRetrospective", () => {
|
|
|
212
219
|
(m) => m.role === "assistant",
|
|
213
220
|
);
|
|
214
221
|
expect(forkAssistant).toBeDefined();
|
|
222
|
+
expect(forkAssistant!.id).toBe(assistant.id);
|
|
223
|
+
expect(ownedRowCount(asyncFork.id)).toBe(0);
|
|
215
224
|
const forkAttachments = getAttachmentsForMessage(forkAssistant!.id);
|
|
216
225
|
expect(forkAttachments).toHaveLength(1);
|
|
217
|
-
|
|
218
|
-
expect(forkAttachments[0]?.id).not.toBe(
|
|
226
|
+
expect(forkAttachments[0]?.id).toBe(
|
|
219
227
|
getAttachmentsForMessage(assistant.id)[0]?.id,
|
|
220
228
|
);
|
|
221
229
|
});
|
|
@@ -321,11 +329,7 @@ describe("forkConversationForRetrospective — compacted source", () => {
|
|
|
321
329
|
return { id: source.id, summary, compactedAt, compactedCount: 4, base };
|
|
322
330
|
}
|
|
323
331
|
|
|
324
|
-
|
|
325
|
-
return { role: m.role, content: m.content, createdAt: m.createdAt };
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
test("copies only the visible tail and renders identically to the source", async () => {
|
|
332
|
+
test("copies no rows and renders identically to the source", async () => {
|
|
329
333
|
const source = await seedCompactedSource();
|
|
330
334
|
const sourceRows = getMessages(source.id);
|
|
331
335
|
const tip = sourceRows.at(-1)!;
|
|
@@ -337,33 +341,25 @@ describe("forkConversationForRetrospective — compacted source", () => {
|
|
|
337
341
|
source: MEMORY_RETROSPECTIVE_FORK_SOURCE,
|
|
338
342
|
});
|
|
339
343
|
|
|
340
|
-
|
|
344
|
+
expect(fork.forkStrategy).toBe("reference");
|
|
345
|
+
expect(ownedRowCount(fork.id)).toBe(0);
|
|
341
346
|
const forkRows = getMessages(fork.id);
|
|
342
|
-
expect(forkRows.map(
|
|
343
|
-
expect(
|
|
344
|
-
forkRows.map(
|
|
345
|
-
(m) =>
|
|
346
|
-
(JSON.parse(m.metadata!) as { forkSourceMessageId: string })
|
|
347
|
-
.forkSourceMessageId,
|
|
348
|
-
),
|
|
349
|
-
).toEqual(sourceRows.slice(4).map((m) => m.id));
|
|
347
|
+
expect(forkRows.map((m) => m.id)).toEqual(sourceRows.map((m) => m.id));
|
|
350
348
|
|
|
351
|
-
// Fork row: inherited summary + timestamp, fork-local count of 0.
|
|
352
349
|
expect(fork.contextSummary).toBe(source.summary);
|
|
353
|
-
expect(fork.contextCompactedMessageCount).toBe(
|
|
350
|
+
expect(fork.contextCompactedMessageCount).toBe(4);
|
|
354
351
|
expect(fork.contextCompactedAt).toBe(source.compactedAt);
|
|
355
352
|
expect(fork.forkParentMessageId).toBe(tip.id);
|
|
356
353
|
|
|
357
|
-
// Rendered history — summary + post-slice rows, assembled the way
|
|
358
|
-
// `loadFromDb` does — is identical to the source's.
|
|
359
|
-
const render = (
|
|
360
|
-
summary: string | null,
|
|
361
|
-
rows: Array<{ role: string; content: unknown; createdAt: number }>,
|
|
362
|
-
count: number,
|
|
363
|
-
) => [summary, ...rows.slice(Math.min(count, rows.length)).map(contentOf)];
|
|
364
354
|
expect(
|
|
365
|
-
|
|
366
|
-
|
|
355
|
+
renderWindow(
|
|
356
|
+
fork.contextSummary,
|
|
357
|
+
forkRows,
|
|
358
|
+
fork.contextCompactedMessageCount,
|
|
359
|
+
),
|
|
360
|
+
).toEqual(
|
|
361
|
+
renderWindow(source.summary, sourceRows, source.compactedCount),
|
|
362
|
+
);
|
|
367
363
|
|
|
368
364
|
// The synchronous user fork keeps the full physical history.
|
|
369
365
|
const syncFork = forkConversation({
|
|
@@ -455,9 +451,21 @@ describe("forkConversationForRetrospective — compacted source", () => {
|
|
|
455
451
|
source: MEMORY_RETROSPECTIVE_FORK_SOURCE,
|
|
456
452
|
});
|
|
457
453
|
|
|
458
|
-
//
|
|
454
|
+
// Lineage includes the cutoff window; the inherited count hides the
|
|
455
|
+
// compacted prefix so the rendered tail is source row 5 only.
|
|
459
456
|
const forkRows = getMessages(fork.id);
|
|
460
|
-
expect(
|
|
457
|
+
expect(ownedRowCount(fork.id)).toBe(0);
|
|
458
|
+
expect(fork.contextCompactedMessageCount).toBe(4);
|
|
459
|
+
expect(forkRows.map((m) => m.id)).toEqual(
|
|
460
|
+
sourceRows.slice(0, 5).map((m) => m.id),
|
|
461
|
+
);
|
|
462
|
+
expect(
|
|
463
|
+
renderWindow(
|
|
464
|
+
fork.contextSummary,
|
|
465
|
+
forkRows,
|
|
466
|
+
fork.contextCompactedMessageCount,
|
|
467
|
+
).slice(1),
|
|
468
|
+
).toEqual([contentOf(sourceRows[4]!)]);
|
|
461
469
|
expect(fork.forkParentMessageId).toBe(sourceRows[4]!.id);
|
|
462
470
|
|
|
463
471
|
// Derived seeding sees only the copied tail's slug; the wholesale graph
|
|
@@ -499,24 +507,24 @@ describe("forkConversationForRetrospective — compacted source", () => {
|
|
|
499
507
|
expect(forkEvents[0]).toMatchObject({
|
|
500
508
|
compactedAt: source.compactedAt,
|
|
501
509
|
summary: source.summary,
|
|
502
|
-
compactedMessageCount:
|
|
510
|
+
compactedMessageCount: 4,
|
|
503
511
|
});
|
|
504
512
|
|
|
505
|
-
// A fork of the fork at its tip inherits the
|
|
506
|
-
//
|
|
513
|
+
// A user fork of the retrospective fork at its tip inherits the same
|
|
514
|
+
// summary and hidden-prefix count.
|
|
507
515
|
const forkTip = getMessages(fork.id).at(-1)!;
|
|
508
516
|
const grandchild = forkConversation({
|
|
509
517
|
conversationId: fork.id,
|
|
510
518
|
throughMessageId: forkTip.id,
|
|
511
519
|
});
|
|
512
520
|
expect(grandchild.contextSummary).toBe(source.summary);
|
|
513
|
-
expect(grandchild.contextCompactedMessageCount).toBe(
|
|
521
|
+
expect(grandchild.contextCompactedMessageCount).toBe(4);
|
|
514
522
|
expect(getMessages(grandchild.id)).toHaveLength(
|
|
515
523
|
getMessages(fork.id).length,
|
|
516
524
|
);
|
|
517
525
|
});
|
|
518
526
|
|
|
519
|
-
test("post-fork tail extraction
|
|
527
|
+
test("post-fork tail extraction attributes only owned rows", async () => {
|
|
520
528
|
const source = await seedCompactedSource();
|
|
521
529
|
const tip = getMessages(source.id).at(-1)!;
|
|
522
530
|
const fork = await forkConversationForRetrospective({
|
|
@@ -526,11 +534,10 @@ describe("forkConversationForRetrospective — compacted source", () => {
|
|
|
526
534
|
source: MEMORY_RETROSPECTIVE_FORK_SOURCE,
|
|
527
535
|
});
|
|
528
536
|
|
|
529
|
-
|
|
530
|
-
|
|
537
|
+
// Referential forks stamp no copied-row metadata. The boundary helper
|
|
538
|
+
// returns null; ownership is what scopes the run.
|
|
539
|
+
expect(findForkBoundaryCreatedAt(getMessages(fork.id))).toBeNull();
|
|
531
540
|
|
|
532
|
-
// A run-authored message lands after the boundary and is the only row
|
|
533
|
-
// attributed to the run.
|
|
534
541
|
const runMessage = await addMessage(fork.id, "user", "retro instruction", {
|
|
535
542
|
skipIndexing: true,
|
|
536
543
|
});
|
|
@@ -544,6 +551,7 @@ describe("forkConversationForRetrospective — compacted source", () => {
|
|
|
544
551
|
MEMORY_RETROSPECTIVE_FORK_SOURCE,
|
|
545
552
|
);
|
|
546
553
|
expect(runRows?.map((m) => m.id)).toEqual([runMessage.id]);
|
|
554
|
+
expect(runRows?.every((m) => m.conversationId === fork.id)).toBe(true);
|
|
547
555
|
});
|
|
548
556
|
|
|
549
557
|
test("succeeds with an empty tail when the compaction covers the whole cutoff range", async () => {
|
|
@@ -573,18 +581,25 @@ describe("forkConversationForRetrospective — compacted source", () => {
|
|
|
573
581
|
source: MEMORY_RETROSPECTIVE_FORK_SOURCE,
|
|
574
582
|
});
|
|
575
583
|
|
|
576
|
-
expect(
|
|
584
|
+
expect(ownedRowCount(fork.id)).toBe(0);
|
|
585
|
+
expect(getMessages(fork.id)).toHaveLength(sourceRows.length);
|
|
577
586
|
expect(fork.contextSummary).toBe("Everything summarized");
|
|
578
|
-
expect(fork.contextCompactedMessageCount).toBe(
|
|
587
|
+
expect(fork.contextCompactedMessageCount).toBe(sourceRows.length);
|
|
579
588
|
expect(fork.contextCompactedAt).toBe(tip.createdAt);
|
|
580
589
|
expect(fork.forkParentMessageId).toBe(tip.id);
|
|
590
|
+
expect(
|
|
591
|
+
renderWindow(
|
|
592
|
+
fork.contextSummary,
|
|
593
|
+
getMessages(fork.id),
|
|
594
|
+
fork.contextCompactedMessageCount,
|
|
595
|
+
),
|
|
596
|
+
).toEqual(["Everything summarized"]);
|
|
581
597
|
// Ageable despite copying no rows, so the startup orphan sweep (which
|
|
582
598
|
// skips null `lastMessageAt` rows) can reclaim it after a crash.
|
|
583
599
|
expect(fork.lastMessageAt).toBe(tip.createdAt);
|
|
584
600
|
|
|
585
|
-
//
|
|
586
|
-
//
|
|
587
|
-
// the success bookkeeping (dedup baseline, skill cards).
|
|
601
|
+
// The run's instruction is the first owned row. Lineage still presents
|
|
602
|
+
// the compacted source prefix, so attribution is by ownership.
|
|
588
603
|
const runMessage = await addMessage(fork.id, "user", "retro instruction", {
|
|
589
604
|
metadata: {
|
|
590
605
|
kind: MEMORY_RETROSPECTIVE_INSTRUCTION_KIND,
|
|
@@ -653,8 +668,14 @@ describe("forkConversationForRetrospective — compacted source", () => {
|
|
|
653
668
|
});
|
|
654
669
|
|
|
655
670
|
// The source renders ["still visible", "fresh tail"] after its slice;
|
|
656
|
-
// the fork must
|
|
657
|
-
expect(
|
|
671
|
+
// the fork must render the same window.
|
|
672
|
+
expect(ownedRowCount(fork.id)).toBe(0);
|
|
673
|
+
expect(fork.contextCompactedMessageCount).toBe(1);
|
|
674
|
+
expect(
|
|
675
|
+
getMessages(fork.id)
|
|
676
|
+
.slice(fork.contextCompactedMessageCount)
|
|
677
|
+
.map((m) => m.content),
|
|
678
|
+
).toEqual([
|
|
658
679
|
[{ type: "text", text: "still visible" }],
|
|
659
680
|
[{ type: "text", text: "fresh tail" }],
|
|
660
681
|
]);
|
|
@@ -705,9 +726,12 @@ describe("forkConversationForRetrospective — compacted source", () => {
|
|
|
705
726
|
source: MEMORY_RETROSPECTIVE_FORK_SOURCE,
|
|
706
727
|
});
|
|
707
728
|
|
|
708
|
-
expect(
|
|
709
|
-
|
|
710
|
-
|
|
729
|
+
expect(ownedRowCount(fork.id)).toBe(0);
|
|
730
|
+
expect(
|
|
731
|
+
getMessages(fork.id)
|
|
732
|
+
.slice(fork.contextCompactedMessageCount)
|
|
733
|
+
.map((m) => m.content),
|
|
734
|
+
).toEqual([[{ type: "text", text: "visible" }]]);
|
|
711
735
|
expect(fork.contextSummary).toBe("Tie summary");
|
|
712
736
|
expect(fork.contextCompactedMessageCount).toBe(0);
|
|
713
737
|
const forkEvents = getDb()
|
|
@@ -740,8 +764,9 @@ describe("forkConversationForRetrospective with unfinalized rows", () => {
|
|
|
740
764
|
});
|
|
741
765
|
const forkMessages = getMessages(fork.id);
|
|
742
766
|
|
|
743
|
-
//
|
|
744
|
-
// row the
|
|
767
|
+
// Lineage excludes the ancestor's in-flight row; the anchor is a
|
|
768
|
+
// finalized row the inherited window actually holds.
|
|
769
|
+
expect(ownedRowCount(fork.id)).toBe(0);
|
|
745
770
|
expect(forkMessages).toHaveLength(3);
|
|
746
771
|
expect(forkMessages.every((row) => row.finalized === 1)).toBe(true);
|
|
747
772
|
expect(fork.forkParentMessageId).not.toBe(tail.id);
|
|
@@ -862,6 +862,29 @@ describe("trust-gating via channel capabilities", () => {
|
|
|
862
862
|
expect(injected).not.toContain("CHANNEL CONSTRAINTS");
|
|
863
863
|
});
|
|
864
864
|
|
|
865
|
+
test("vellum channel with Windows client OS injects PowerShell guidance", () => {
|
|
866
|
+
const caps: ChannelCapabilities = {
|
|
867
|
+
channel: "vellum",
|
|
868
|
+
dashboardCapable: true,
|
|
869
|
+
supportsDynamicUi: true,
|
|
870
|
+
supportsVoiceInput: true,
|
|
871
|
+
clientOS: "web",
|
|
872
|
+
};
|
|
873
|
+
const message: Message = {
|
|
874
|
+
role: "user",
|
|
875
|
+
content: [{ type: "text", text: "List my downloads" }],
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
const result = injectChannelCapabilityContext(message, caps, "windows");
|
|
879
|
+
|
|
880
|
+
expect(result).not.toBe(message);
|
|
881
|
+
const injected = (result.content[0] as { type: "text"; text: string }).text;
|
|
882
|
+
expect(injected).toContain("client_os: windows");
|
|
883
|
+
expect(injected).toContain("`host_bash` runs PowerShell");
|
|
884
|
+
expect(injected).toContain("Windows paths");
|
|
885
|
+
expect(injected).not.toContain("CHANNEL CONSTRAINTS");
|
|
886
|
+
});
|
|
887
|
+
|
|
865
888
|
test("non-dashboard channel adds constraint rules preventing UI references", () => {
|
|
866
889
|
const caps = resolveChannelCapabilities("telegram");
|
|
867
890
|
const message: Message = {
|
|
@@ -36,6 +36,7 @@ describe("/model list", () => {
|
|
|
36
36
|
expect(message).toContain("`balanced`");
|
|
37
37
|
expect(message).toContain("`quality-optimized`");
|
|
38
38
|
expect(message).toContain("`cost-optimized`");
|
|
39
|
+
expect(message).not.toContain("`balanced-backup`");
|
|
39
40
|
});
|
|
40
41
|
|
|
41
42
|
test("default profile descriptions follow the default provider's column", async () => {
|
|
@@ -71,6 +72,14 @@ describe("/model list", () => {
|
|
|
71
72
|
});
|
|
72
73
|
|
|
73
74
|
describe("/model switch", () => {
|
|
75
|
+
test("rejects a managed backup profile as a direct selection", async () => {
|
|
76
|
+
const result = await resolveSlash("/model balanced-backup");
|
|
77
|
+
expect(result.kind).toBe("unknown");
|
|
78
|
+
expect((result as { message: string }).message).toContain(
|
|
79
|
+
"Profile `balanced-backup` not found",
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
|
|
74
83
|
test("rejects an unknown profile with the available set", async () => {
|
|
75
84
|
const result = await resolveSlash("/model nope");
|
|
76
85
|
expect(result.kind).toBe("unknown");
|