@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
|
@@ -104,10 +104,6 @@ import {
|
|
|
104
104
|
getSqliteFrom,
|
|
105
105
|
getTelemetryDb,
|
|
106
106
|
} from "./db-connection.js";
|
|
107
|
-
import {
|
|
108
|
-
copyForkMessagesViaSubprocess,
|
|
109
|
-
type ForkIdPair,
|
|
110
|
-
} from "./fork-message-copy.js";
|
|
111
107
|
import {
|
|
112
108
|
clearMessagesLexicalIndex,
|
|
113
109
|
enqueueDeleteMessageLexical,
|
|
@@ -653,7 +649,7 @@ export interface ConversationRow {
|
|
|
653
649
|
originInterface: string | null;
|
|
654
650
|
forkParentConversationId: string | null;
|
|
655
651
|
forkParentMessageId: string | null;
|
|
656
|
-
/** `"reference"` on referential forks;
|
|
652
|
+
/** `"reference"` on referential forks; null on copied ones. */
|
|
657
653
|
forkStrategy: string | null;
|
|
658
654
|
isAutoTitle: number;
|
|
659
655
|
scheduleJobId: string | null;
|
|
@@ -1723,25 +1719,36 @@ function populateForkContentsInProcess(args: PopulateForkContentsArgs): void {
|
|
|
1723
1719
|
}
|
|
1724
1720
|
|
|
1725
1721
|
/**
|
|
1726
|
-
*
|
|
1727
|
-
*
|
|
1728
|
-
*
|
|
1722
|
+
* How many leading rows of the inherited window sit behind the source's
|
|
1723
|
+
* compaction boundary, counted in `getMessages` load order (`createdAt`
|
|
1724
|
+
* only). The retrospective fork is referential: it reads the source's rows
|
|
1725
|
+
* through the fork point, prefix included, so it must carry this count for
|
|
1726
|
+
* the render to hide the same rows the source hides. Getting the count
|
|
1727
|
+
* wrong does not lose data; it re-shows compacted history the summary
|
|
1728
|
+
* already covers, or hides a visible row when a cutoff drops part of the
|
|
1729
|
+
* hidden prefix from the lineage.
|
|
1730
|
+
*
|
|
1731
|
+
* Rows after the cutoff are skipped. The first in-window row that is not
|
|
1732
|
+
* hidden ends the prefix, matching the source render's slice.
|
|
1729
1733
|
*/
|
|
1730
|
-
function
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1734
|
+
function compactedPrefixLengthInWindow(
|
|
1735
|
+
loadOrderIds: readonly string[],
|
|
1736
|
+
compactedCount: number,
|
|
1737
|
+
windowIds: ReadonlySet<string>,
|
|
1738
|
+
): number {
|
|
1739
|
+
const hiddenIds = new Set(loadOrderIds.slice(0, compactedCount));
|
|
1740
|
+
let count = 0;
|
|
1741
|
+
for (const id of loadOrderIds) {
|
|
1742
|
+
if (!windowIds.has(id)) {
|
|
1737
1743
|
continue;
|
|
1738
1744
|
}
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1745
|
+
if (hiddenIds.has(id)) {
|
|
1746
|
+
count += 1;
|
|
1747
|
+
continue;
|
|
1742
1748
|
}
|
|
1749
|
+
break;
|
|
1743
1750
|
}
|
|
1744
|
-
return
|
|
1751
|
+
return count;
|
|
1745
1752
|
}
|
|
1746
1753
|
|
|
1747
1754
|
/**
|
|
@@ -1749,35 +1756,30 @@ function latestForkedAssistantFrom(
|
|
|
1749
1756
|
* which forks the source conversation's visible window into a throwaway
|
|
1750
1757
|
* background conversation on a hot path that must not stall the daemon.
|
|
1751
1758
|
*
|
|
1752
|
-
*
|
|
1753
|
-
*
|
|
1754
|
-
*
|
|
1755
|
-
*
|
|
1756
|
-
*
|
|
1757
|
-
* the source
|
|
1758
|
-
*
|
|
1759
|
-
* history: user forks are long-lived and browsable, and
|
|
1760
|
-
* views render the persisted history unsliced.
|
|
1759
|
+
* The fork is referential: it holds only the rows written after it is
|
|
1760
|
+
* created and reads the inherited window through `fork_parent_message_id`.
|
|
1761
|
+
* The retrospective wake always runs under guardian trust, whose history
|
|
1762
|
+
* render slices `contextCompactedMessageCount` rows off the front and
|
|
1763
|
+
* prepends the summary on `contextSummary` presence alone, so the fork
|
|
1764
|
+
* carries the source's own count and renders identically to the source
|
|
1765
|
+
* (summary + tail). The user-facing {@link forkConversation} keeps the full
|
|
1766
|
+
* physical history: user forks are long-lived and browsable, and
|
|
1767
|
+
* untrusted-actor views render the persisted history unsliced.
|
|
1761
1768
|
*
|
|
1762
|
-
*
|
|
1763
|
-
*
|
|
1764
|
-
*
|
|
1765
|
-
*
|
|
1766
|
-
*
|
|
1767
|
-
* {@link populateForkContentsInProcess}, the same helper the synchronous fork
|
|
1768
|
-
* uses, so the two paths cannot drift on that logic. The cutoff/boundary
|
|
1769
|
-
* computation mirrors {@link forkConversation} and is pinned by a parity
|
|
1770
|
-
* test.
|
|
1769
|
+
* Creating the fork is a single conversation row plus memory-state
|
|
1770
|
+
* seeding. There is no message-row copy and no attachment relink: lineage
|
|
1771
|
+
* reads keep the source message ids, so `getAttachmentsForMessage` resolves
|
|
1772
|
+
* against the source rows. The cutoff/boundary computation mirrors
|
|
1773
|
+
* {@link forkConversation} and is pinned by a parity test.
|
|
1771
1774
|
*
|
|
1772
1775
|
* The disk-view projection (`syncMessageToDisk`) is intentionally skipped: the
|
|
1773
1776
|
* fork is GC'd after the retrospective pass and never browsed, and the agent
|
|
1774
1777
|
* reads it from the database, not the on-disk JSONL.
|
|
1775
1778
|
*
|
|
1776
|
-
*
|
|
1777
|
-
*
|
|
1778
|
-
*
|
|
1779
|
-
*
|
|
1780
|
-
* because the returned promise resolves after every phase commits.
|
|
1779
|
+
* A mid-flight failure deletes the partial best-effort; a crash after the
|
|
1780
|
+
* conversation row lands is reclaimed by the worker's startup orphan sweep.
|
|
1781
|
+
* Callers only ever observe a fully-built fork because the returned promise
|
|
1782
|
+
* resolves after every phase commits.
|
|
1781
1783
|
*/
|
|
1782
1784
|
export async function forkConversationForRetrospective(params: {
|
|
1783
1785
|
conversationId: string;
|
|
@@ -1850,10 +1852,6 @@ export async function forkConversationForRetrospective(params: {
|
|
|
1850
1852
|
const inheritsLatestCompaction =
|
|
1851
1853
|
inheritedCompaction != null &&
|
|
1852
1854
|
inheritedCompaction.compactedAt === sourceConversation.contextCompactedAt;
|
|
1853
|
-
// Copy only the visible tail: rows behind the inherited summary are never
|
|
1854
|
-
// rendered on this fork (the retrospective wake runs under guardian trust,
|
|
1855
|
-
// which slices `contextCompactedMessageCount` rows off the front), so
|
|
1856
|
-
// copying them would hold the write lock for rows the agent cannot see.
|
|
1857
1855
|
// The drop-set is the first `compactedMessageCount` rows in LOAD order —
|
|
1858
1856
|
// the exact rows the source's render hides — rather than a positional
|
|
1859
1857
|
// slice of the re-sorted array, whose tie order can differ at the
|
|
@@ -1862,43 +1860,29 @@ export async function forkConversationForRetrospective(params: {
|
|
|
1862
1860
|
const hiddenRowIds = new Set(
|
|
1863
1861
|
loadOrderIds.slice(0, inheritedCompaction?.compactedMessageCount ?? 0),
|
|
1864
1862
|
);
|
|
1865
|
-
|
|
1866
|
-
//
|
|
1867
|
-
//
|
|
1868
|
-
//
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
//
|
|
1877
|
-
//
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
// already covers.
|
|
1882
|
-
const forkCompactedMessageCount = isReferential
|
|
1883
|
-
? (inheritedCompaction?.compactedMessageCount ?? 0)
|
|
1884
|
-
: 0;
|
|
1863
|
+
const windowIds = new Set(messagesToCopy.map((message) => message.id));
|
|
1864
|
+
// A referential fork reads the source's rows through the fork point,
|
|
1865
|
+
// prefix included, so it carries the hidden-prefix length of that
|
|
1866
|
+
// inherited window (not the source's raw count, which can hide a visible
|
|
1867
|
+
// row when a cutoff drops part of the prefix from the lineage).
|
|
1868
|
+
const forkCompactedMessageCount = compactedPrefixLengthInWindow(
|
|
1869
|
+
loadOrderIds,
|
|
1870
|
+
inheritedCompaction?.compactedMessageCount ?? 0,
|
|
1871
|
+
windowIds,
|
|
1872
|
+
);
|
|
1873
|
+
// Visible source rows in the inherited window. Passed to memory seeding
|
|
1874
|
+
// so a truncated fork re-derives `everInjected` from the child's rendered
|
|
1875
|
+
// tail; attachments stay on the source ids and are not relinked.
|
|
1876
|
+
const visibleSourceMessages = messagesToCopy.filter(
|
|
1877
|
+
(message) => !hiddenRowIds.has(message.id),
|
|
1878
|
+
);
|
|
1885
1879
|
const forkParentMessageId = messagesToCopy.at(-1)?.id ?? null;
|
|
1886
1880
|
const forkTitle =
|
|
1887
1881
|
params.title ?? `${sourceConversation.title ?? "Untitled"} (Fork)`;
|
|
1888
1882
|
const parentGroupId = getConversationGroupId(conversationId);
|
|
1883
|
+
const forkedMessageIds = new Map<string, string>();
|
|
1889
1884
|
|
|
1890
|
-
//
|
|
1891
|
-
// the in-process attachment relink that follows.
|
|
1892
|
-
const idPairs: ForkIdPair[] = rowsToCopy.map((message) => ({
|
|
1893
|
-
oldId: message.id,
|
|
1894
|
-
newId: uuidv7(),
|
|
1895
|
-
}));
|
|
1896
|
-
const forkedMessageIds = new Map<string, string>(
|
|
1897
|
-
idPairs.map((pair) => [pair.oldId, pair.newId]),
|
|
1898
|
-
);
|
|
1899
|
-
|
|
1900
|
-
// Phase 1 (in-process, tiny): create the fork conversation row + lineage so
|
|
1901
|
-
// the subprocess connection sees it before inserting messages. The whole
|
|
1885
|
+
// Phase 1: create the fork conversation row + lineage. The whole
|
|
1902
1886
|
// transaction is the retry unit — it rolls back atomically on contention, so
|
|
1903
1887
|
// re-running it (with a fresh conversation id) is safe.
|
|
1904
1888
|
const fork = await withSqliteRetry(
|
|
@@ -1916,18 +1900,13 @@ export async function forkConversationForRetrospective(params: {
|
|
|
1916
1900
|
forkParentMessageId,
|
|
1917
1901
|
// Stamped at creation so the startup orphan sweep (which only
|
|
1918
1902
|
// considers rows with a non-null `lastMessageAt`) can age this
|
|
1919
|
-
// fork even if the daemon crashes before the
|
|
1920
|
-
//
|
|
1921
|
-
//
|
|
1922
|
-
// value from the copied rows when the tail is non-empty.
|
|
1903
|
+
// fork even if the daemon crashes before the retrospective
|
|
1904
|
+
// instruction lands — including the empty-tail fork, which
|
|
1905
|
+
// copies no rows at all.
|
|
1923
1906
|
lastMessageAt: boundaryMessageCreatedAt,
|
|
1924
1907
|
contextSummary: inheritedCompaction?.summary ?? null,
|
|
1925
|
-
// On a copied fork zero of its own rows sit behind the boundary
|
|
1926
|
-
// (the compacted prefix is not copied). The summary still
|
|
1927
|
-
// renders: the history render keys on `contextSummary` presence,
|
|
1928
|
-
// not on this count.
|
|
1929
1908
|
contextCompactedMessageCount: forkCompactedMessageCount,
|
|
1930
|
-
forkStrategy:
|
|
1909
|
+
forkStrategy: REFERENTIAL_FORK_STRATEGY,
|
|
1931
1910
|
contextCompactedAt: inheritedCompaction?.compactedAt ?? null,
|
|
1932
1911
|
slackContextCompactionWatermarkTs: inheritsLatestCompaction
|
|
1933
1912
|
? sourceConversation.slackContextCompactionWatermarkTs
|
|
@@ -1951,39 +1930,13 @@ export async function forkConversationForRetrospective(params: {
|
|
|
1951
1930
|
);
|
|
1952
1931
|
|
|
1953
1932
|
try {
|
|
1954
|
-
// Phase 2 (
|
|
1955
|
-
//
|
|
1956
|
-
//
|
|
1957
|
-
// the write burst this phase exists to keep off the event loop does not
|
|
1958
|
-
// happen at all.
|
|
1959
|
-
if (!isReferential) {
|
|
1960
|
-
const copy = await copyForkMessagesViaSubprocess({
|
|
1961
|
-
forkConversationId: fork.id,
|
|
1962
|
-
idPairs,
|
|
1963
|
-
});
|
|
1964
|
-
if (!copy.ok) {
|
|
1965
|
-
throw new Error(
|
|
1966
|
-
`fork message copy failed (${copy.backend}): ${copy.error ?? "unknown"}`,
|
|
1967
|
-
);
|
|
1968
|
-
}
|
|
1969
|
-
}
|
|
1970
|
-
|
|
1971
|
-
// Phase 3 (in-process): attachments + memory-state seeding, reusing the
|
|
1972
|
-
// same helper as the synchronous fork. Disk-view projection is skipped.
|
|
1933
|
+
// Phase 2 (in-process): memory-state seeding. Disk-view projection and
|
|
1934
|
+
// attachment relink are skipped: the fork owns no copied rows, and
|
|
1935
|
+
// lineage reads keep the source message ids.
|
|
1973
1936
|
//
|
|
1974
|
-
//
|
|
1975
|
-
//
|
|
1976
|
-
//
|
|
1977
|
-
// read→write upgrade with SQLITE_BUSY_SNAPSHOT — a hard error
|
|
1978
|
-
// `busy_timeout` cannot wait out, which would discard the entire batch
|
|
1979
|
-
// copy from phase 2. `behavior: "immediate"` acquires the write lock at
|
|
1980
|
-
// BEGIN so the snapshot postdates it, and the retry absorbs residual
|
|
1981
|
-
// contention: the transaction rolls back atomically, so re-running it is
|
|
1982
|
-
// safe.
|
|
1983
|
-
const latestForkedAssistant = latestForkedAssistantFrom(
|
|
1984
|
-
rowsToCopy,
|
|
1985
|
-
forkedMessageIds,
|
|
1986
|
-
);
|
|
1937
|
+
// `behavior: "immediate"` acquires the write lock at BEGIN so the
|
|
1938
|
+
// snapshot postdates it, and the retry absorbs residual contention: the
|
|
1939
|
+
// transaction rolls back atomically, so re-running it is safe.
|
|
1987
1940
|
await withSqliteRetry(
|
|
1988
1941
|
() =>
|
|
1989
1942
|
db.transaction(
|
|
@@ -1991,30 +1944,29 @@ export async function forkConversationForRetrospective(params: {
|
|
|
1991
1944
|
populateForkContentsInProcess({
|
|
1992
1945
|
fork,
|
|
1993
1946
|
sourceConversationId: sourceConversation.id,
|
|
1994
|
-
messagesToCopy:
|
|
1947
|
+
messagesToCopy: visibleSourceMessages,
|
|
1995
1948
|
forkedMessageIds,
|
|
1996
|
-
latestForkedAssistant,
|
|
1949
|
+
latestForkedAssistant: null,
|
|
1997
1950
|
// Both conditions, for the same reason as `forkConversation`:
|
|
1998
1951
|
// a tip boundary no longer implies an equal window once the
|
|
1999
1952
|
// unfinalized filter dropped rows from the slice.
|
|
2000
1953
|
isFullHistoryFork:
|
|
2001
1954
|
copyBoundaryIndex === sourceMessages.length - 1 &&
|
|
2002
1955
|
messagesToCopy.length === copyBoundaryIndex + 1,
|
|
2003
|
-
// The
|
|
2004
|
-
inheritedCompactedMessageCount:
|
|
1956
|
+
// The seeding range already starts at the visible window.
|
|
1957
|
+
inheritedCompactedMessageCount: 0,
|
|
2005
1958
|
skipCompactionLedgerCopy: inheritedCompaction != null,
|
|
2006
1959
|
});
|
|
2007
1960
|
// The fork owns none of the source's ledger events — their
|
|
2008
|
-
// counts index source-space row positions
|
|
2009
|
-
//
|
|
2010
|
-
//
|
|
2011
|
-
//
|
|
2012
|
-
//
|
|
2013
|
-
//
|
|
2014
|
-
//
|
|
2015
|
-
//
|
|
2016
|
-
//
|
|
2017
|
-
// so the retry unit stays atomic.
|
|
1961
|
+
// counts index source-space row positions. Seed a single event
|
|
1962
|
+
// mirroring the fork row's compaction fields whenever a
|
|
1963
|
+
// compaction is inherited (even when the hidden prefix fell
|
|
1964
|
+
// outside the cutoff and nothing was dropped), so the ledger
|
|
1965
|
+
// and the row cache agree and a fork of this fork inherits the
|
|
1966
|
+
// summary with the fork-local count. With no inherited
|
|
1967
|
+
// compaction the default ledger copy is a natural no-op (no
|
|
1968
|
+
// source event is at-or-before the boundary). Rolls back with
|
|
1969
|
+
// the transaction, so the retry unit stays atomic.
|
|
2018
1970
|
if (inheritedCompaction) {
|
|
2019
1971
|
appendCompactionEvent(fork.id, {
|
|
2020
1972
|
compactedAt: inheritedCompaction.compactedAt,
|
|
@@ -4047,7 +3999,9 @@ export function updateMessageMetadata(
|
|
|
4047
3999
|
.from(messages)
|
|
4048
4000
|
.where(eq(messages.id, messageId))
|
|
4049
4001
|
.get();
|
|
4050
|
-
|
|
4002
|
+
// Sanitized like the transactional sibling above: a malformed stored
|
|
4003
|
+
// envelope must not fail the update that is trying to stamp the row.
|
|
4004
|
+
const existing = row?.metadata ? safeParseRecord(row.metadata) : {};
|
|
4051
4005
|
db.update(messages)
|
|
4052
4006
|
.set({ metadata: JSON.stringify({ ...existing, ...updates }) })
|
|
4053
4007
|
.where(eq(messages.id, messageId))
|
|
@@ -146,21 +146,22 @@ export function deriveDeterministicTitle(context: TitleContext): string {
|
|
|
146
146
|
* inbound whose only content is a guardian access-request card: without this
|
|
147
147
|
* the sidebar shows a permanent "Generating title…". Persisted as
|
|
148
148
|
* `AUTO_TITLE_DETERMINISTIC` so a later genuine turn can still upgrade it, and
|
|
149
|
-
* broadcast so connected clients converge. Returns
|
|
149
|
+
* broadcast so connected clients converge. Returns the title the conversation
|
|
150
|
+
* carries once it settles, and whether this call is what set it.
|
|
150
151
|
*/
|
|
151
152
|
export function applyDeterministicTitleIfReplaceable(
|
|
152
153
|
conversationId: string,
|
|
153
154
|
title: string,
|
|
154
|
-
): boolean {
|
|
155
|
+
): { title: string; updated: boolean } {
|
|
155
156
|
const conversation = getConversation(conversationId);
|
|
156
157
|
if (conversation && !isReplaceableTitle(conversation.title)) {
|
|
157
|
-
return false;
|
|
158
|
+
return { title: conversation.title!, updated: false };
|
|
158
159
|
}
|
|
159
160
|
const cleaned =
|
|
160
161
|
truncateTitle(title.replace(/\s+/g, " ").trim()) || UNTITLED_FALLBACK;
|
|
161
162
|
updateConversationTitle(conversationId, cleaned, AUTO_TITLE_DETERMINISTIC);
|
|
162
163
|
publishConversationTitleChanged(conversationId, cleaned);
|
|
163
|
-
return true;
|
|
164
|
+
return { title: cleaned, updated: true };
|
|
164
165
|
}
|
|
165
166
|
|
|
166
167
|
// ── Title generation ─────────────────────────────────────────────────
|
|
@@ -195,19 +196,21 @@ export async function generateAndPersistConversationTitle(
|
|
|
195
196
|
return { title: conversation.title!, updated: false };
|
|
196
197
|
}
|
|
197
198
|
|
|
199
|
+
const prompt = buildTitlePrompt(context, userMessage, assistantResponse);
|
|
200
|
+
// A prompt carrying no text gives the model nothing to title from: an
|
|
201
|
+
// image-only turn submits an empty prompt, and the forced title tool makes
|
|
202
|
+
// the model fabricate a topic rather than decline.
|
|
203
|
+
if (prompt.trim() === "") {
|
|
204
|
+
return settleForDeterministicTitle(conversationId, context);
|
|
205
|
+
}
|
|
206
|
+
|
|
198
207
|
const provider =
|
|
199
208
|
params.provider ?? (await getConfiguredProvider("conversationTitle"));
|
|
200
209
|
if (!provider) {
|
|
201
|
-
// No provider available — fall back to context-derived title or untitled.
|
|
202
|
-
// Deterministic, so keep it upgradeable by a later generation pass.
|
|
203
|
-
const fallback = deriveFallbackTitle(context) ?? UNTITLED_FALLBACK;
|
|
204
|
-
updateConversationTitle(conversationId, fallback, AUTO_TITLE_DETERMINISTIC);
|
|
205
|
-
publishConversationTitleChanged(conversationId, fallback);
|
|
206
210
|
logRetryableFallback(params, "no_provider");
|
|
207
|
-
return
|
|
211
|
+
return settleForDeterministicTitle(conversationId, context);
|
|
208
212
|
}
|
|
209
213
|
|
|
210
|
-
const prompt = buildTitlePrompt(context, userMessage, assistantResponse);
|
|
211
214
|
const title = await generateTitleViaLLM(
|
|
212
215
|
provider,
|
|
213
216
|
prompt,
|
|
@@ -227,21 +230,26 @@ export async function generateAndPersistConversationTitle(
|
|
|
227
230
|
return { title, updated: true };
|
|
228
231
|
}
|
|
229
232
|
|
|
230
|
-
// No text in response — use fallback
|
|
231
|
-
// Re-check replaceability before persisting (race guard — same as the
|
|
232
|
-
// text-response path above). A concurrent custom rename may have landed
|
|
233
|
-
// while the LLM request was in-flight; writing unconditionally would
|
|
234
|
-
// clobber the user's intent.
|
|
235
|
-
const currentForFallback = getConversation(conversationId);
|
|
236
|
-
if (currentForFallback && !canReplaceTitle(currentForFallback)) {
|
|
237
|
-
return { title: currentForFallback.title!, updated: false };
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const fallback = deriveFallbackTitle(context) ?? UNTITLED_FALLBACK;
|
|
241
|
-
updateConversationTitle(conversationId, fallback, AUTO_TITLE_DETERMINISTIC);
|
|
242
|
-
publishConversationTitleChanged(conversationId, fallback);
|
|
243
233
|
logRetryableFallback(params, "empty_output");
|
|
244
|
-
return
|
|
234
|
+
return settleForDeterministicTitle(conversationId, context);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Settle a conversation on a title derived from its creation context, for the
|
|
239
|
+
* outcomes that produce no LLM title: the provider is unreachable, the model
|
|
240
|
+
* declined, or the prompt had no text to title from. Clearing a loading
|
|
241
|
+
* placeholder is worth doing; overwriting a title that already names the work
|
|
242
|
+
* (a background job's bootstrap title) is not, which is why this defers to
|
|
243
|
+
* `applyDeterministicTitleIfReplaceable` rather than writing directly.
|
|
244
|
+
*/
|
|
245
|
+
function settleForDeterministicTitle(
|
|
246
|
+
conversationId: string,
|
|
247
|
+
context: TitleContext | undefined,
|
|
248
|
+
): { title: string; updated: boolean } {
|
|
249
|
+
return applyDeterministicTitleIfReplaceable(
|
|
250
|
+
conversationId,
|
|
251
|
+
deriveFallbackTitle(context) ?? UNTITLED_FALLBACK,
|
|
252
|
+
);
|
|
245
253
|
}
|
|
246
254
|
|
|
247
255
|
// ── Serial title-generation queue ────────────────────────────────────
|
|
@@ -531,11 +539,17 @@ function buildTitlePrompt(
|
|
|
531
539
|
}
|
|
532
540
|
}
|
|
533
541
|
|
|
534
|
-
|
|
535
|
-
|
|
542
|
+
// Trim after stripping so a turn carrying nothing but thinking tags reads as
|
|
543
|
+
// no text at all, the same as an image-only or whitespace-only prompt.
|
|
544
|
+
const userText = userMessage ? stripThinkingTags(userMessage).trim() : "";
|
|
545
|
+
if (userText) {
|
|
546
|
+
parts.push(`User: ${userText}`);
|
|
536
547
|
}
|
|
537
|
-
|
|
538
|
-
|
|
548
|
+
const assistantText = assistantResponse
|
|
549
|
+
? stripThinkingTags(assistantResponse).trim()
|
|
550
|
+
: "";
|
|
551
|
+
if (assistantText) {
|
|
552
|
+
parts.push(`Assistant: ${assistantText}`);
|
|
539
553
|
}
|
|
540
554
|
|
|
541
555
|
return parts.join("\n");
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
2
|
existsSync,
|
|
3
|
-
readdirSync,
|
|
4
3
|
readFileSync,
|
|
5
4
|
rmSync,
|
|
6
5
|
unlinkSync,
|
|
@@ -14,6 +13,10 @@ import {
|
|
|
14
13
|
getEmbeddingModelsDir,
|
|
15
14
|
getEmbedWorkerPidPath,
|
|
16
15
|
} from "../../util/platform.js";
|
|
16
|
+
import {
|
|
17
|
+
listProcessTable,
|
|
18
|
+
readRawProcessCommand,
|
|
19
|
+
} from "../../util/process-table.js";
|
|
17
20
|
import { PromiseGuard } from "../../util/promise-guard.js";
|
|
18
21
|
import { workerComputeEnv } from "../../util/worker-compute.js";
|
|
19
22
|
import { workerMemoryEnv } from "../../util/worker-memory.js";
|
|
@@ -155,77 +158,7 @@ function pid1OwnsEmbedWorkers(): boolean {
|
|
|
155
158
|
if (process.pid === 1) {
|
|
156
159
|
return true;
|
|
157
160
|
}
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
cmd = readFileSync("/proc/1/cmdline", "utf8").split("\0").join(" ");
|
|
161
|
-
} catch {
|
|
162
|
-
const result = Bun.spawnSync({
|
|
163
|
-
cmd: ["ps", "-ww", "-p", "1", "-o", "command="],
|
|
164
|
-
stdout: "pipe",
|
|
165
|
-
stderr: "ignore",
|
|
166
|
-
});
|
|
167
|
-
if (result.exitCode !== 0) {
|
|
168
|
-
return false;
|
|
169
|
-
}
|
|
170
|
-
cmd = new TextDecoder().decode(result.stdout);
|
|
171
|
-
}
|
|
172
|
-
return cmd.includes(DAEMON_ENTRYPOINT_MARKER);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/** Enumerate `(pid, ppid, rawCommand)` rows from Linux `/proc`. */
|
|
176
|
-
function listProcessRowsFromProc(): {
|
|
177
|
-
pid: number;
|
|
178
|
-
ppid: number;
|
|
179
|
-
cmd: string;
|
|
180
|
-
}[] {
|
|
181
|
-
const rows: { pid: number; ppid: number; cmd: string }[] = [];
|
|
182
|
-
for (const entry of readdirSync("/proc")) {
|
|
183
|
-
const pid = Number(entry);
|
|
184
|
-
if (!Number.isInteger(pid) || pid <= 0) {
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
try {
|
|
188
|
-
// `stat` field 4 is ppid, but `comm` (field 2) may contain spaces or
|
|
189
|
-
// parens, so parse from the last ')' and a weird comm cannot shift fields.
|
|
190
|
-
const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
191
|
-
const after = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
|
|
192
|
-
const ppid = Number(after[1]);
|
|
193
|
-
const cmd = readFileSync(`/proc/${pid}/cmdline`, "utf8")
|
|
194
|
-
.split("\0")
|
|
195
|
-
.filter(Boolean)
|
|
196
|
-
.join(" ");
|
|
197
|
-
if (Number.isInteger(ppid)) {
|
|
198
|
-
rows.push({ pid, ppid, cmd });
|
|
199
|
-
}
|
|
200
|
-
} catch {
|
|
201
|
-
// Process exited between readdir and read, so skip it.
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
return rows;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/** Enumerate `(pid, ppid, rawCommand)` rows via `ps` (macOS / no `/proc`). */
|
|
208
|
-
function listProcessRowsFromPs(): { pid: number; ppid: number; cmd: string }[] {
|
|
209
|
-
// `-ww` disables column-width truncation. Without it, macOS `ps` clips the
|
|
210
|
-
// command field to the terminal width, which can cut off the workerPath
|
|
211
|
-
// argument and hide a genuine match. Same flag is used by
|
|
212
|
-
// daemon-control.ts:123 for exactly this reason.
|
|
213
|
-
const result = Bun.spawnSync({
|
|
214
|
-
cmd: ["ps", "-A", "-ww", "-o", "pid=,ppid=,command="],
|
|
215
|
-
stdout: "pipe",
|
|
216
|
-
stderr: "ignore",
|
|
217
|
-
});
|
|
218
|
-
if (result.exitCode !== 0) {
|
|
219
|
-
return [];
|
|
220
|
-
}
|
|
221
|
-
const rows: { pid: number; ppid: number; cmd: string }[] = [];
|
|
222
|
-
for (const line of new TextDecoder().decode(result.stdout).split("\n")) {
|
|
223
|
-
const m = line.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);
|
|
224
|
-
if (m) {
|
|
225
|
-
rows.push({ pid: Number(m[1]), ppid: Number(m[2]), cmd: m[3].trim() });
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
return rows;
|
|
161
|
+
return readRawProcessCommand(1)?.includes(DAEMON_ENTRYPOINT_MARKER) ?? false;
|
|
229
162
|
}
|
|
230
163
|
|
|
231
164
|
/**
|
|
@@ -248,22 +181,17 @@ export function listWorkerProcesses(
|
|
|
248
181
|
workerPath: string,
|
|
249
182
|
model?: string,
|
|
250
183
|
): WorkerProcess[] {
|
|
251
|
-
let rows: { pid: number; ppid: number; cmd: string }[];
|
|
252
184
|
try {
|
|
253
|
-
|
|
185
|
+
return listProcessTable()
|
|
186
|
+
.filter(
|
|
187
|
+
(row) =>
|
|
188
|
+
row.command.includes(workerPath) &&
|
|
189
|
+
(!model || row.command.split(/\s+/).includes(model)),
|
|
190
|
+
)
|
|
191
|
+
.map(({ pid, ppid }) => ({ pid, ppid }));
|
|
254
192
|
} catch {
|
|
255
|
-
|
|
193
|
+
return [];
|
|
256
194
|
}
|
|
257
|
-
return rows
|
|
258
|
-
.filter(
|
|
259
|
-
(r) =>
|
|
260
|
-
r.cmd.includes(workerPath) &&
|
|
261
|
-
// An argv token, not a substring: `foo/bar-small` must not match a
|
|
262
|
-
// `foo/bar-small-v2` worker, or a backend for the shorter name would
|
|
263
|
-
// reclaim the longer one's live worker. Model names carry no spaces.
|
|
264
|
-
(!model || r.cmd.split(/\s+/).includes(model)),
|
|
265
|
-
)
|
|
266
|
-
.map((r) => ({ pid: r.pid, ppid: r.ppid }));
|
|
267
195
|
}
|
|
268
196
|
|
|
269
197
|
/**
|
|
@@ -510,6 +438,7 @@ export class LocalEmbeddingBackend implements EmbeddingBackend {
|
|
|
510
438
|
|
|
511
439
|
const proc = Bun.spawn({
|
|
512
440
|
cmd: [bunPath, "--smol", workerPath, this.model, modelCacheDir],
|
|
441
|
+
windowsHide: true,
|
|
513
442
|
env: { ...workerMemoryEnv(), ...workerComputeEnv() },
|
|
514
443
|
stdin: "pipe",
|
|
515
444
|
stdout: "pipe",
|
|
@@ -102,6 +102,7 @@ async function downloadAndExtract(
|
|
|
102
102
|
// Extract tarball, stripping the leading "package/" directory
|
|
103
103
|
const proc = Bun.spawn({
|
|
104
104
|
cmd: ["tar", "xzf", tmpTar, "-C", targetDir, "--strip-components=1"],
|
|
105
|
+
windowsHide: true,
|
|
105
106
|
stdout: "ignore",
|
|
106
107
|
stderr: "pipe",
|
|
107
108
|
});
|
|
@@ -58,6 +58,56 @@ describe("lifecycle-events-store", () => {
|
|
|
58
58
|
});
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
+
test("carries the optional attributes onto the wire payload", () => {
|
|
62
|
+
const event = recordLifecycleEvent("permission_prompt:bash", {
|
|
63
|
+
toolName: "bash",
|
|
64
|
+
riskLevel: "high",
|
|
65
|
+
riskThreshold: "low",
|
|
66
|
+
surface: "slack",
|
|
67
|
+
conversationId: "conv-xyz",
|
|
68
|
+
});
|
|
69
|
+
expect(event).not.toBeNull();
|
|
70
|
+
|
|
71
|
+
const rows = outboxRows();
|
|
72
|
+
expect(rows).toHaveLength(1);
|
|
73
|
+
// The conversation is stamped on the row too, so deleting the conversation
|
|
74
|
+
// deletes the pending event with it.
|
|
75
|
+
expect(rows[0]!.conversation_id).toBe("conv-xyz");
|
|
76
|
+
expect(JSON.parse(rows[0]!.payload)).toEqual({
|
|
77
|
+
type: "lifecycle",
|
|
78
|
+
daemon_event_id: event!.id,
|
|
79
|
+
event_name: "permission_prompt:bash",
|
|
80
|
+
recorded_at: event!.createdAt,
|
|
81
|
+
assistant_version: APP_VERSION,
|
|
82
|
+
tool_name: "bash",
|
|
83
|
+
risk_level: "high",
|
|
84
|
+
risk_threshold: "low",
|
|
85
|
+
surface: "slack",
|
|
86
|
+
conversation_id: "conv-xyz",
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("omits attributes that are unset or empty", () => {
|
|
91
|
+
// The wire schema requires at least one character, and one failing field
|
|
92
|
+
// makes the server drop the whole event.
|
|
93
|
+
const event = recordLifecycleEvent("permission_prompt:bash", {
|
|
94
|
+
toolName: "bash",
|
|
95
|
+
riskLevel: "high",
|
|
96
|
+
surface: "",
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(JSON.parse(outboxRows()[0]!.payload)).toEqual({
|
|
100
|
+
type: "lifecycle",
|
|
101
|
+
daemon_event_id: event!.id,
|
|
102
|
+
event_name: "permission_prompt:bash",
|
|
103
|
+
recorded_at: event!.createdAt,
|
|
104
|
+
assistant_version: APP_VERSION,
|
|
105
|
+
tool_name: "bash",
|
|
106
|
+
risk_level: "high",
|
|
107
|
+
});
|
|
108
|
+
expect(outboxRows()[0]!.conversation_id).toBeNull();
|
|
109
|
+
});
|
|
110
|
+
|
|
61
111
|
test("buildLifecycleTelemetryEvent stamps the record-time binary version", () => {
|
|
62
112
|
expect(buildLifecycleTelemetryEvent("id-1", "hatch", 1234)).toEqual({
|
|
63
113
|
type: "lifecycle",
|