@vellumai/assistant 0.12.0 → 0.12.1-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/AGENTS.md +5 -1
- package/ARCHITECTURE.md +11 -12
- package/docs/architecture/turn-actor.md +15 -8
- package/openapi.yaml +292 -2
- package/package.json +1 -1
- package/scripts/smoke-desktop-window-theme.ts +186 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
- package/src/__tests__/app-compiler.test.ts +6 -0
- package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
- package/src/__tests__/app-dir-path-guard.test.ts +1 -3
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
- package/src/__tests__/computer-use-tools.test.ts +17 -5
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +28 -29
- package/src/__tests__/config-watcher.test.ts +23 -8
- package/src/__tests__/connection-model-compat.test.ts +2 -2
- package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-pairing.test.ts +26 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
- package/src/__tests__/conversation-queue.test.ts +48 -207
- package/src/__tests__/conversation-retry-discard.test.ts +54 -1
- package/src/__tests__/conversation-skill-tools.test.ts +27 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
- package/src/__tests__/conversation-title-service.test.ts +14 -10
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
- package/src/__tests__/credential-record-retire.test.ts +171 -1
- package/src/__tests__/credential-routes.test.ts +212 -3
- package/src/__tests__/credential-security-invariants.test.ts +2 -1
- package/src/__tests__/cu-unified-flow.test.ts +8 -2
- package/src/__tests__/drain-kick-guard.test.ts +0 -2
- package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
- package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
- package/src/__tests__/git-porcelain.test.ts +47 -0
- package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
- package/src/__tests__/host-bash-proxy.test.ts +26 -45
- package/src/__tests__/host-browser-proxy.test.ts +6 -17
- package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
- package/src/__tests__/host-file-proxy.test.ts +11 -23
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
- package/src/__tests__/llm-catalog-parity.test.ts +25 -0
- package/src/__tests__/mcp-auth-routes.test.ts +6 -9
- package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
- package/src/__tests__/mcp-health-check.test.ts +8 -11
- package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
- package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
- package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
- package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
- package/src/__tests__/messaging-send-tool.test.ts +355 -548
- package/src/__tests__/model-intents.test.ts +3 -0
- package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
- package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +13 -0
- package/src/__tests__/resolve-app.test.ts +135 -0
- package/src/__tests__/script-proxy-certs.test.ts +35 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/source-fingerprint.test.ts +184 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +19 -3
- package/src/__tests__/subagent-tools.test.ts +3 -2
- package/src/__tests__/telephony-tts-guard.test.ts +38 -3
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
- package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
- package/src/__tests__/workspace-git-service.test.ts +26 -0
- package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
- package/src/acp/__tests__/agent-process.test.ts +258 -4
- package/src/acp/__tests__/auth-required.test.ts +96 -0
- package/src/acp/__tests__/client-handler.test.ts +90 -2
- package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
- package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
- package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
- package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
- package/src/acp/__tests__/session-manager.test.ts +895 -4
- package/src/acp/agent-process.test.ts +4 -1
- package/src/acp/agent-process.ts +94 -9
- package/src/acp/auth-required.ts +50 -1
- package/src/acp/auto-install.test.ts +9 -7
- package/src/acp/client-handler.ts +16 -3
- package/src/acp/model-config.test.ts +166 -0
- package/src/acp/model-config.ts +138 -0
- package/src/acp/resolve-agent.test.ts +220 -6
- package/src/acp/resolve-agent.ts +88 -23
- package/src/acp/session-manager.test.ts +196 -3
- package/src/acp/session-manager.ts +441 -18
- package/src/acp/types.ts +50 -5
- package/src/api/events/acp-session-model-update.test.ts +88 -0
- package/src/api/events/acp-session-model-update.ts +46 -0
- package/src/api/events/app-files-changed.ts +4 -3
- package/src/api/index.ts +6 -0
- package/src/apps/app-store.ts +1 -1
- package/src/apps/resolve-app.ts +81 -0
- package/src/apps/source-fingerprint.ts +266 -0
- package/src/bundler/app-compiler.ts +15 -0
- package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
- package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
- package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
- package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
- package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
- package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
- package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
- package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
- package/src/calls/__tests__/voice-control-protocol.test.ts +58 -25
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
- package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
- package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
- package/src/calls/__tests__/voice-session-bridge.test.ts +37 -1
- package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
- package/src/calls/barge-in-guard.ts +118 -0
- package/src/calls/call-constants.ts +24 -4
- package/src/calls/call-controller.ts +499 -199
- package/src/calls/call-transport.ts +16 -0
- package/src/calls/media-stream-output.ts +32 -0
- package/src/calls/media-stream-server.ts +82 -30
- package/src/calls/media-stream-stt-session.ts +15 -0
- package/src/{live-voice → calls}/progress-narration.ts +2 -2
- package/src/calls/resolve-call-tts-provider.ts +33 -2
- package/src/calls/tts-call-strategy.ts +9 -5
- package/src/calls/types.ts +2 -0
- package/src/calls/voice-control-protocol.ts +53 -21
- package/src/calls/voice-leg-coordinator.ts +189 -0
- package/src/calls/voice-progress-cadence.ts +423 -0
- package/src/calls/voice-session-bridge.ts +59 -46
- package/src/calls/voice-triage-escalate.ts +175 -56
- package/src/cli/commands/__tests__/apps.test.ts +245 -0
- package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
- package/src/cli/commands/apps.help.ts +67 -13
- package/src/cli/commands/apps.ts +183 -0
- package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
- package/src/cli/commands/channels/index.help.ts +70 -2
- package/src/cli/commands/channels/index.ts +16 -3
- package/src/cli/commands/channels/send.ts +128 -0
- package/src/cli/commands/credentials.help.ts +6 -2
- package/src/cli/commands/credentials.ts +2 -2
- package/src/cli/commands/mcp.help.ts +8 -8
- package/src/cli/commands/mcp.ts +1 -1
- package/src/cli/commands/roadmap.help.ts +5 -1
- package/src/cli/commands/roadmap.ts +1 -1
- package/src/cli/lib/bundled-marketplace.json +103 -90
- package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
- package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
- package/src/config/__tests__/platform-identity.test.ts +117 -0
- package/src/config/__tests__/platform-rehydration.test.ts +93 -0
- package/src/config/acp-agent-write.ts +43 -0
- package/src/config/acp-defaults.test.ts +13 -3
- package/src/config/acp-defaults.ts +23 -6
- package/src/config/acp-schema.test.ts +81 -0
- package/src/config/acp-schema.ts +30 -2
- package/src/config/bundled-skills/acp/SKILL.md +22 -14
- package/src/config/bundled-skills/acp/TOOLS.json +5 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
- package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
- package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
- package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/env.ts +2 -2
- package/src/config/feature-flag-registry.json +8 -12
- package/src/config/memory-v3-gate.test.ts +31 -0
- package/src/config/memory-v3-gate.ts +20 -16
- package/src/config/platform-identity.ts +144 -0
- package/src/config/platform-rehydration.ts +75 -21
- package/src/config/schema.ts +2 -2
- package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
- package/src/config/schemas/__tests__/voice.test.ts +148 -0
- package/src/config/schemas/calls.ts +1 -28
- package/src/config/schemas/live-voice.ts +1 -173
- package/src/config/schemas/mcp.ts +1 -1
- package/src/config/schemas/memory-retrospective.ts +10 -0
- package/src/config/schemas/skills.ts +0 -14
- package/src/config/schemas/voice.ts +150 -0
- package/src/credential-execution/process-manager.test.ts +18 -19
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
- package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
- package/src/daemon/app-change-notify.ts +45 -0
- package/src/daemon/config-watcher.ts +13 -194
- package/src/daemon/conversation-process.ts +35 -64
- package/src/daemon/conversation-skill-tools.ts +13 -31
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +10 -8
- package/src/daemon/conversation-tool-setup.ts +37 -43
- package/src/daemon/conversation.ts +0 -3
- package/src/daemon/host-bash-proxy.ts +8 -12
- package/src/daemon/host-browser-proxy.ts +15 -1
- package/src/daemon/host-file-proxy.ts +8 -10
- package/src/daemon/host-transfer-proxy.ts +14 -18
- package/src/daemon/lifecycle.ts +0 -5
- package/src/daemon/mcp-reload-service.ts +5 -5
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/shutdown-handlers.ts +1 -9
- package/src/daemon/skill-memory-refresh.ts +64 -0
- package/src/daemon/tool-side-effects.ts +7 -18
- package/src/desktop/__tests__/fake-desktop.ts +119 -0
- package/src/desktop/desktop-chrome-policy.test.ts +71 -0
- package/src/desktop/desktop-chrome-policy.ts +42 -0
- package/src/desktop/desktop-dependencies.test.ts +102 -0
- package/src/desktop/desktop-dependencies.ts +306 -0
- package/src/desktop/desktop-feature.test.ts +18 -0
- package/src/desktop/desktop-feature.ts +19 -0
- package/src/desktop/desktop-panel-config.test.ts +135 -0
- package/src/desktop/desktop-panel-config.ts +149 -0
- package/src/desktop/desktop-session-manager.test.ts +627 -0
- package/src/desktop/desktop-session-manager.ts +814 -0
- package/src/desktop/desktop-stream-bridge.test.ts +395 -0
- package/src/desktop/desktop-stream-bridge.ts +214 -0
- package/src/desktop/desktop-wallpaper.test.ts +191 -0
- package/src/desktop/desktop-wallpaper.ts +113 -0
- package/src/desktop/desktop-window-theme.ts +170 -0
- package/src/hooks/types.ts +33 -0
- package/src/i18n/AGENTS.md +22 -0
- package/src/i18n/__tests__/i18n.test.ts +108 -0
- package/src/i18n/index.ts +16 -0
- package/src/i18n/locales.ts +93 -0
- package/src/i18n/messages.ts +61 -0
- package/src/i18n/title.ts +25 -0
- package/src/inbound/platform-callback-registration.ts +4 -5
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
- package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
- package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
- package/src/live-voice/live-voice-session.ts +213 -693
- package/src/live-voice/protocol.ts +0 -2
- package/src/mcp/__tests__/effective-config.test.ts +2 -2
- package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
- package/src/mcp/effective-config.ts +3 -3
- package/src/mcp/mcp-header-store.ts +29 -45
- package/src/mcp/spec-schema.ts +111 -0
- package/src/mcp/startup.ts +8 -4
- package/src/mcp/workspace-mcp-config.ts +188 -0
- package/src/messaging/provider.ts +10 -3
- package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
- package/src/messaging/providers/callback-routing.ts +18 -0
- package/src/messaging/providers/channel-transport.ts +56 -0
- package/src/messaging/providers/discord/transport.ts +17 -0
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
- package/src/messaging/providers/slack/adapter.ts +7 -44
- package/src/messaging/providers/slack/transport.ts +16 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
- package/src/messaging/providers/telegram-bot/client.ts +0 -26
- package/src/messaging/providers/telegram-bot/transport.ts +20 -0
- package/src/messaging/providers/whatsapp/adapter.ts +1 -48
- package/src/messaging/providers/whatsapp/transport.ts +18 -0
- package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
- package/src/notifications/assistant-reply-producer.ts +8 -3
- package/src/notifications/conversation-pairing.ts +36 -16
- package/src/notifications/delivered-post-record.ts +11 -1
- package/src/notifications/home-feed-side-effect.ts +55 -7
- package/src/outbound-proxy/certs.ts +3 -5
- package/src/permissions/checker.test.ts +11 -6
- package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
- package/src/persistence/conversation-crud.ts +71 -63
- package/src/persistence/conversation-lineage.ts +12 -4
- package/src/persistence/conversation-title-placeholders.ts +23 -0
- package/src/persistence/conversation-title-service.ts +9 -26
- package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
- package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
- package/src/persistence/message-cursor.ts +78 -0
- package/src/persistence/message-reads.ts +30 -0
- package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
- package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
- package/src/persistence/schema/memory-core.ts +5 -0
- package/src/platform/client.test.ts +2 -0
- package/src/platform/client.ts +2 -10
- package/src/plugin-api/constants.ts +2 -0
- package/src/plugin-api/index.ts +1 -0
- package/src/plugin-api/types.ts +2 -0
- package/src/plugins/defaults/index.ts +2 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
- package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
- package/src/plugins/defaults/memory/graph/capability-seed.ts +4 -6
- package/src/plugins/defaults/memory/hooks/init.ts +10 -0
- package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
- package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
- package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
- package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
- package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
- package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
- package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
- package/src/plugins/defaults/memory/startup.ts +6 -1
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
- package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +89 -0
- package/src/plugins/defaults/memory/worker.ts +6 -0
- package/src/plugins/mcp-servers.ts +15 -75
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
- package/src/providers/gemini/client.ts +9 -23
- package/src/providers/gemini-thought-signature.test.ts +63 -0
- package/src/providers/gemini-thought-signature.ts +44 -0
- package/src/providers/model-catalog.ts +34 -0
- package/src/providers/model-intents.ts +10 -5
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
- package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
- package/src/providers/openai/chat-completions-provider.ts +192 -78
- package/src/providers/openai/codex-models.ts +0 -4
- package/src/providers/openai/google-thought-signature.ts +320 -0
- package/src/runtime/AGENTS.md +2 -0
- package/src/runtime/__tests__/channel-send.test.ts +443 -0
- package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
- package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
- package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
- package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
- package/src/runtime/auth/same-actor.ts +38 -3
- package/src/runtime/channel-send.ts +316 -0
- package/src/runtime/http-server.ts +160 -151
- package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +205 -0
- package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
- package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
- package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
- package/src/runtime/routes/acp-routes.test.ts +15 -0
- package/src/runtime/routes/acp-routes.ts +72 -9
- package/src/runtime/routes/app-management-routes.ts +73 -0
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/channel-send-routes.ts +85 -0
- package/src/runtime/routes/conversation-list-routes.ts +14 -3
- package/src/runtime/routes/conversation-query-routes.ts +40 -0
- package/src/runtime/routes/credential-routes.ts +19 -10
- package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
- package/src/runtime/routes/desktop-setup-routes.ts +35 -0
- package/src/runtime/routes/diagnostics-routes.ts +55 -3
- package/src/runtime/routes/index.ts +4 -0
- package/src/runtime/routes/log-export-routes.ts +2 -0
- package/src/runtime/routes/mcp-auth-routes.ts +39 -47
- package/src/runtime/routes/migration-routes.ts +8 -21
- package/src/runtime/routes/platform-routes.ts +18 -15
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
- package/src/runtime/services/conversation-serializer.ts +16 -3
- package/src/runtime/sync/resource-sync-events.ts +2 -1
- package/src/schedule/worker.ts +1 -1
- package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
- package/src/security/ces-http-record-backend.ts +120 -0
- package/src/security/ces-rpc-record-backend.ts +7 -0
- package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
- package/src/skills/workspace-skill-md-signature.ts +50 -0
- package/src/subagent/consult-context.ts +12 -25
- package/src/tools/acp/list-agents.test.ts +3 -1
- package/src/tools/acp/spawn.test.ts +146 -11
- package/src/tools/acp/spawn.ts +21 -3
- package/src/tools/acp/status.test.ts +94 -0
- package/src/tools/acp/status.ts +40 -3
- package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
- package/src/tools/capability-offer.test.ts +22 -2
- package/src/tools/capability-offer.ts +16 -1
- package/src/tools/computer-use/definitions.ts +4 -2
- package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
- package/src/tools/credentials/metadata-store.ts +100 -0
- package/src/tools/host-filesystem/edit.test.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +2 -2
- package/src/tools/host-filesystem/read.test.ts +2 -1
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/transfer.test.ts +2 -1
- package/src/tools/host-filesystem/transfer.ts +2 -2
- package/src/tools/host-filesystem/write.test.ts +2 -1
- package/src/tools/host-filesystem/write.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +16 -0
- package/src/tools/host-terminal/host-shell.ts +4 -20
- package/src/tools/policy-context.ts +6 -7
- package/src/tools/subagent/spawn.ts +5 -8
- package/src/tools/terminal/__tests__/safe-env.test.ts +6 -6
- package/src/tools/terminal/safe-env.ts +23 -3
- package/src/tools/watch/watch-retro-report.ts +0 -8
- package/src/util/__tests__/language-subtag.test.ts +23 -1
- package/src/util/ca-bundle.ts +13 -0
- package/src/util/language-subtag.ts +16 -0
- package/src/util/platform.ts +8 -0
- package/src/workspace/git-porcelain.ts +66 -0
- package/src/workspace/git-service.ts +5 -54
- package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
- package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
- package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
- package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/app-source-watcher.test.ts +0 -218
- package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
- package/src/__tests__/speaker-identification.test.ts +0 -56
- package/src/calls/speaker-identification.ts +0 -233
- package/src/daemon/app-source-watcher.ts +0 -218
- package/src/messaging/providers/whatsapp/client.ts +0 -29
- package/src/util/fs-watcher-error.ts +0 -36
- /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
|
@@ -35,7 +35,10 @@ import { findConversation } from "../daemon/conversation-registry.js";
|
|
|
35
35
|
import { conversationMetadataSyncTag } from "../daemon/message-types/sync.js";
|
|
36
36
|
import type { TrustContext } from "../daemon/trust-context-types.js";
|
|
37
37
|
import { clearAllConversationIds } from "../home/feed-writer.js";
|
|
38
|
-
import type {
|
|
38
|
+
import type {
|
|
39
|
+
ConversationDeletedInputContext,
|
|
40
|
+
MessageDeletedInputContext,
|
|
41
|
+
} from "../hooks/types.js";
|
|
39
42
|
import { readProviderMetadata } from "../messaging/read-provider-metadata.js";
|
|
40
43
|
import { HOOKS } from "../plugin-api/constants.js";
|
|
41
44
|
import { forkConversationMemory } from "../plugins/defaults/memory/fork-conversation-memory.js";
|
|
@@ -81,7 +84,6 @@ import { ensureDisplayOrderMigration } from "./conversation-display-order-migrat
|
|
|
81
84
|
import { ensureGroupMigration } from "./conversation-group-migration.js";
|
|
82
85
|
import {
|
|
83
86
|
isReferentialFork,
|
|
84
|
-
type LineageBound,
|
|
85
87
|
type LineageConversationRow,
|
|
86
88
|
lineageMessageFilter,
|
|
87
89
|
lineageMessagesAfterFilter,
|
|
@@ -121,6 +123,11 @@ import {
|
|
|
121
123
|
} from "./job-handlers/message-lexical.js";
|
|
122
124
|
import { buildLifecycleTelemetryEvent } from "./lifecycle-events-store.js";
|
|
123
125
|
import { resolveMessageContentBlocks } from "./message-content-file.js";
|
|
126
|
+
import {
|
|
127
|
+
loadMessageBound,
|
|
128
|
+
type MessagesAfterRef,
|
|
129
|
+
resolveMessagesAfterBound,
|
|
130
|
+
} from "./message-cursor.js";
|
|
124
131
|
import { mergeMessageMetadata } from "./message-metadata.js";
|
|
125
132
|
import {
|
|
126
133
|
rawAll,
|
|
@@ -2641,16 +2648,6 @@ function loadLineageRow(id: string): LineageConversationRow | null {
|
|
|
2641
2648
|
return row ?? null;
|
|
2642
2649
|
}
|
|
2643
2650
|
|
|
2644
|
-
/** The `(createdAt, id)` bound of a single message, or null when it is gone. */
|
|
2645
|
-
function loadMessageBound(messageId: string): LineageBound | null {
|
|
2646
|
-
const row = getDb()
|
|
2647
|
-
.select({ createdAt: messages.createdAt, id: messages.id })
|
|
2648
|
-
.from(messages)
|
|
2649
|
-
.where(eq(messages.id, messageId))
|
|
2650
|
-
.get();
|
|
2651
|
-
return row ?? null;
|
|
2652
|
-
}
|
|
2653
|
-
|
|
2654
2651
|
/**
|
|
2655
2652
|
* The segments making up a conversation's logical message set. Non-forks and
|
|
2656
2653
|
* copied forks resolve to a single unbounded segment after one row lookup, so
|
|
@@ -2972,81 +2969,67 @@ export function selectNewestSightFrameCapture(
|
|
|
2972
2969
|
}
|
|
2973
2970
|
|
|
2974
2971
|
/**
|
|
2975
|
-
* Count messages in a conversation
|
|
2976
|
-
* `
|
|
2977
|
-
*
|
|
2978
|
-
*
|
|
2979
|
-
*
|
|
2980
|
-
*
|
|
2972
|
+
* Count messages in a conversation created strictly after the `after`
|
|
2973
|
+
* reference. `null`, `""`, or a cursor with an empty id counts every message.
|
|
2974
|
+
* The reference resolves through `resolveMessagesAfterBound`: a live row's
|
|
2975
|
+
* `(createdAt, id)` is authoritative, a `MessageCursor` whose row has been
|
|
2976
|
+
* deleted still bounds the count from the `createdAt` it carries, and a bare
|
|
2977
|
+
* id whose row is gone counts 0: a caller holding only an id has no
|
|
2978
|
+
* defensible starting point, and the conservative answer is "no new work."
|
|
2981
2979
|
*
|
|
2982
2980
|
* Used by the memory-retrospective trigger check to decide whether to fire
|
|
2983
2981
|
* the message-count trigger without loading message bodies.
|
|
2984
2982
|
*/
|
|
2985
2983
|
export function countMessagesAfter(
|
|
2986
2984
|
conversationId: string,
|
|
2987
|
-
|
|
2985
|
+
after: MessagesAfterRef,
|
|
2988
2986
|
): number {
|
|
2989
2987
|
const db = getDb();
|
|
2990
2988
|
const segments = resolveLineage(conversationId);
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
.select({ c: count() })
|
|
2994
|
-
.from(messages)
|
|
2995
|
-
.where(lineageMessageFilter(segments))
|
|
2996
|
-
.get();
|
|
2997
|
-
return row?.c ?? 0;
|
|
2998
|
-
}
|
|
2999
|
-
const ref = loadMessageBound(afterMessageId);
|
|
3000
|
-
if (!ref) {
|
|
2989
|
+
const start = resolveMessagesAfterBound(after);
|
|
2990
|
+
if (start.kind === "vanished") {
|
|
3001
2991
|
return 0;
|
|
3002
2992
|
}
|
|
3003
|
-
// Tie-breaker on `messages.id` so rows that share a millisecond timestamp
|
|
3004
|
-
// with the reference are not permanently skipped. Mirrors the
|
|
3005
|
-
// `(createdAt, id)` cursor pattern used by the backfill job-handler and
|
|
3006
|
-
// turn-events-store.
|
|
3007
2993
|
const row = db
|
|
3008
2994
|
.select({ c: count() })
|
|
3009
2995
|
.from(messages)
|
|
3010
|
-
.where(
|
|
2996
|
+
.where(
|
|
2997
|
+
start.kind === "all"
|
|
2998
|
+
? lineageMessageFilter(segments)
|
|
2999
|
+
: lineageMessagesAfterFilter(segments, start.bound),
|
|
3000
|
+
)
|
|
3011
3001
|
.get();
|
|
3012
3002
|
return row?.c ?? 0;
|
|
3013
3003
|
}
|
|
3014
3004
|
|
|
3015
3005
|
/**
|
|
3016
|
-
* Return messages in a conversation created strictly after the
|
|
3017
|
-
* `
|
|
3018
|
-
*
|
|
3019
|
-
*
|
|
3020
|
-
*
|
|
3006
|
+
* Return messages in a conversation created strictly after the `after`
|
|
3007
|
+
* reference, in `(createdAt, id)` order. Resolves the reference exactly as
|
|
3008
|
+
* `countMessagesAfter` does, returning an empty array where that returns 0.
|
|
3009
|
+
* Used by the memory-retrospective job handler to load the message slice it
|
|
3010
|
+
* processes.
|
|
3021
3011
|
*/
|
|
3022
3012
|
export function getMessagesAfter(
|
|
3023
3013
|
conversationId: string,
|
|
3024
|
-
|
|
3014
|
+
after: MessagesAfterRef,
|
|
3025
3015
|
): MessageRow[] {
|
|
3026
3016
|
const db = getDb();
|
|
3027
3017
|
const segments = resolveLineage(conversationId);
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
// ordering, so callers tracking `cutoffMessageId` across runs see a
|
|
3031
|
-
// consistent ordering when multiple rows share a millisecond timestamp.
|
|
3032
|
-
return db
|
|
3033
|
-
.select()
|
|
3034
|
-
.from(messages)
|
|
3035
|
-
.where(lineageMessageFilter(segments))
|
|
3036
|
-
.orderBy(asc(messages.createdAt), asc(messages.id))
|
|
3037
|
-
.all()
|
|
3038
|
-
.map(parseMessage);
|
|
3039
|
-
}
|
|
3040
|
-
const ref = loadMessageBound(afterMessageId);
|
|
3041
|
-
if (!ref) {
|
|
3018
|
+
const start = resolveMessagesAfterBound(after);
|
|
3019
|
+
if (start.kind === "vanished") {
|
|
3042
3020
|
return [];
|
|
3043
3021
|
}
|
|
3044
|
-
//
|
|
3045
|
-
//
|
|
3022
|
+
// Secondary `asc(messages.id)` mirrors the bound's tie-breaker, so callers
|
|
3023
|
+
// tracking `cutoffMessageId` across runs see a consistent ordering when
|
|
3024
|
+
// multiple rows share a millisecond timestamp.
|
|
3046
3025
|
return db
|
|
3047
3026
|
.select()
|
|
3048
3027
|
.from(messages)
|
|
3049
|
-
.where(
|
|
3028
|
+
.where(
|
|
3029
|
+
start.kind === "all"
|
|
3030
|
+
? lineageMessageFilter(segments)
|
|
3031
|
+
: lineageMessagesAfterFilter(segments, start.bound),
|
|
3032
|
+
)
|
|
3050
3033
|
.orderBy(asc(messages.createdAt), asc(messages.id))
|
|
3051
3034
|
.all()
|
|
3052
3035
|
.map(parseMessage);
|
|
@@ -4304,12 +4287,12 @@ export function deleteLastExchange(conversationId: string): number {
|
|
|
4304
4287
|
|
|
4305
4288
|
// Collect attachment IDs linked to the messages being deleted so we can
|
|
4306
4289
|
// scope orphan cleanup to only those candidates (not freshly uploaded ones).
|
|
4307
|
-
const
|
|
4308
|
-
.select({ id: messages.id })
|
|
4290
|
+
const deletedRows = db
|
|
4291
|
+
.select({ id: messages.id, createdAt: messages.createdAt })
|
|
4309
4292
|
.from(messages)
|
|
4310
4293
|
.where(condition)
|
|
4311
|
-
.all()
|
|
4312
|
-
|
|
4294
|
+
.all();
|
|
4295
|
+
const messageIds = deletedRows.map((r) => r.id);
|
|
4313
4296
|
const candidateAttachmentIds =
|
|
4314
4297
|
messageIds.length > 0
|
|
4315
4298
|
? db
|
|
@@ -4356,6 +4339,17 @@ export function deleteLastExchange(conversationId: string): number {
|
|
|
4356
4339
|
enqueueDeleteMessageLexical(deletedMessageId);
|
|
4357
4340
|
}
|
|
4358
4341
|
|
|
4342
|
+
// Notify `message-deleted` hooks for each removed row, as the
|
|
4343
|
+
// single-message primitive does: undo removes the same tail a regenerate
|
|
4344
|
+
// does, and a hook keeping a cursor on one of these rows needs its position.
|
|
4345
|
+
for (const row of deletedRows) {
|
|
4346
|
+
void runHook(HOOKS.MESSAGE_DELETED, {
|
|
4347
|
+
conversationId,
|
|
4348
|
+
messageId: row.id,
|
|
4349
|
+
createdAt: row.createdAt,
|
|
4350
|
+
} satisfies MessageDeletedInputContext);
|
|
4351
|
+
}
|
|
4352
|
+
|
|
4359
4353
|
return deleted;
|
|
4360
4354
|
}
|
|
4361
4355
|
|
|
@@ -4594,9 +4588,13 @@ export function deleteMessageById(
|
|
|
4594
4588
|
.map((r) => r.attachmentId)
|
|
4595
4589
|
.filter((id): id is string => id !== undefined);
|
|
4596
4590
|
|
|
4597
|
-
// Look up the conversation before the transaction so we can recalculate
|
|
4591
|
+
// Look up the conversation before the transaction so we can recalculate
|
|
4592
|
+
// lastMessageAt, and the row's createdAt for the `message-deleted` hook.
|
|
4598
4593
|
const msgRow = db
|
|
4599
|
-
.select({
|
|
4594
|
+
.select({
|
|
4595
|
+
conversationId: messages.conversationId,
|
|
4596
|
+
createdAt: messages.createdAt,
|
|
4597
|
+
})
|
|
4600
4598
|
.from(messages)
|
|
4601
4599
|
.where(eq(messages.id, messageId))
|
|
4602
4600
|
.get();
|
|
@@ -4652,6 +4650,16 @@ export function deleteMessageById(
|
|
|
4652
4650
|
// not go through the conversation-level purge.
|
|
4653
4651
|
if (msgRow) {
|
|
4654
4652
|
enqueueDeleteMessageLexical(messageId);
|
|
4653
|
+
|
|
4654
|
+
// Notify `message-deleted` hooks (e.g. the memory plugin stamping a
|
|
4655
|
+
// retrospective cursor that sat on this row). Fire-and-forget from this
|
|
4656
|
+
// synchronous primitive, like `conversation-deleted`; the context carries
|
|
4657
|
+
// the row's position because the row itself is gone.
|
|
4658
|
+
void runHook(HOOKS.MESSAGE_DELETED, {
|
|
4659
|
+
conversationId: msgRow.conversationId,
|
|
4660
|
+
messageId,
|
|
4661
|
+
createdAt: msgRow.createdAt,
|
|
4662
|
+
} satisfies MessageDeletedInputContext);
|
|
4655
4663
|
}
|
|
4656
4664
|
|
|
4657
4665
|
return result;
|
|
@@ -227,9 +227,17 @@ export function lineageMessagesAfterFilter(
|
|
|
227
227
|
): SQL {
|
|
228
228
|
return and(
|
|
229
229
|
lineageMessageFilter(segments),
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
230
|
+
messagesAfterBoundFilter(after),
|
|
231
|
+
) as SQL;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Predicate for rows strictly after a `(createdAt, id)` bound, with the id
|
|
236
|
+
* tie-breaker so rows sharing the bound's millisecond keep their order.
|
|
237
|
+
*/
|
|
238
|
+
export function messagesAfterBoundFilter(after: LineageBound): SQL {
|
|
239
|
+
return or(
|
|
240
|
+
gt(messages.createdAt, after.createdAt),
|
|
241
|
+
and(eq(messages.createdAt, after.createdAt), gt(messages.id, after.id)),
|
|
234
242
|
) as SQL;
|
|
235
243
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { isMessageKey } from "../i18n/index.js";
|
|
2
|
+
|
|
3
|
+
const REPLACEABLE_PATTERNS = [
|
|
4
|
+
/^Runtime:\s/,
|
|
5
|
+
/^New Conversation$/,
|
|
6
|
+
/^Untitled$/,
|
|
7
|
+
/^Untitled Conversation$/,
|
|
8
|
+
/^Generating title\.\.\.$/,
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check whether a title is a system-generated placeholder that can be
|
|
13
|
+
* replaced or omitted without discarding user-provided copy.
|
|
14
|
+
*/
|
|
15
|
+
export function isReplaceableTitle(title: string | null): boolean {
|
|
16
|
+
if (title == null || title.trim() === "") {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (isMessageKey(title.trim())) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
return REPLACEABLE_PATTERNS.some((pattern) => pattern.test(title));
|
|
23
|
+
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { SEND_USER_MESSAGE_DELIVERED_ACK } from "../config/send-user-message-constants.js";
|
|
11
|
+
import { MESSAGE_KEYS } from "../i18n/index.js";
|
|
11
12
|
import {
|
|
12
13
|
requestShortLabel,
|
|
13
14
|
type ShortLabelTool,
|
|
@@ -24,8 +25,11 @@ import {
|
|
|
24
25
|
type MessageRow,
|
|
25
26
|
updateConversationTitle,
|
|
26
27
|
} from "./conversation-crud.js";
|
|
28
|
+
import { isReplaceableTitle } from "./conversation-title-placeholders.js";
|
|
27
29
|
import { projectPersistedAssistantContent } from "./user-facing-content.js";
|
|
28
30
|
|
|
31
|
+
export { isReplaceableTitle } from "./conversation-title-placeholders.js";
|
|
32
|
+
|
|
29
33
|
const log = getLogger("conversation-title-service");
|
|
30
34
|
|
|
31
35
|
// ── Types ────────────────────────────────────────────────────────────
|
|
@@ -65,8 +69,8 @@ export interface TitleContext {
|
|
|
65
69
|
|
|
66
70
|
// ── Placeholder / loading state ──────────────────────────────────────
|
|
67
71
|
|
|
68
|
-
export const GENERATING_TITLE =
|
|
69
|
-
const UNTITLED_FALLBACK =
|
|
72
|
+
export const GENERATING_TITLE = MESSAGE_KEYS.CONVERSATION_TITLE_GENERATING;
|
|
73
|
+
const UNTITLED_FALLBACK = MESSAGE_KEYS.CONVERSATION_TITLE_UNTITLED;
|
|
70
74
|
|
|
71
75
|
// ── `conversations.isAutoTitle` values ───────────────────────────────
|
|
72
76
|
//
|
|
@@ -83,28 +87,6 @@ export const AUTO_TITLE_LLM = 1;
|
|
|
83
87
|
*/
|
|
84
88
|
export const AUTO_TITLE_DETERMINISTIC = 2;
|
|
85
89
|
|
|
86
|
-
// ── Replaceability check ─────────────────────────────────────────────
|
|
87
|
-
|
|
88
|
-
const REPLACEABLE_PATTERNS = [
|
|
89
|
-
/^Runtime:\s/,
|
|
90
|
-
/^New Conversation$/,
|
|
91
|
-
/^Untitled$/,
|
|
92
|
-
/^Untitled Conversation$/,
|
|
93
|
-
/^Generating title\.\.\.$/,
|
|
94
|
-
];
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Check whether a title is a system-generated placeholder that can be
|
|
98
|
-
* safely overwritten by auto-generated titles. Returns `false` for
|
|
99
|
-
* user-provided custom titles.
|
|
100
|
-
*/
|
|
101
|
-
export function isReplaceableTitle(title: string | null): boolean {
|
|
102
|
-
if (title == null || title.trim() === "") {
|
|
103
|
-
return true;
|
|
104
|
-
}
|
|
105
|
-
return REPLACEABLE_PATTERNS.some((pattern) => pattern.test(title));
|
|
106
|
-
}
|
|
107
|
-
|
|
108
90
|
/**
|
|
109
91
|
* Whether auto-generation may overwrite the conversation's current title.
|
|
110
92
|
* True for placeholder/system titles and for deterministic bootstrap titles
|
|
@@ -255,7 +237,7 @@ function settleForDeterministicTitle(
|
|
|
255
237
|
* conversation creation (e.g. 5 new chats in quick succession) fires N
|
|
256
238
|
* concurrent requests that can hit provider rate limits or contend for
|
|
257
239
|
* API capacity, causing later calls to time out and fall back to
|
|
258
|
-
*
|
|
240
|
+
* the untitled catalog key.
|
|
259
241
|
*
|
|
260
242
|
* A serial queue ensures at most one title-generation LLM call is
|
|
261
243
|
* in-flight at a time. Each call is lightweight (~1–3 s for a ≤5-word
|
|
@@ -289,7 +271,7 @@ export function queueGenerateConversationTitle(
|
|
|
289
271
|
// Replace loading placeholder with a retryable fallback.
|
|
290
272
|
try {
|
|
291
273
|
const conversation = getConversation(params.conversationId);
|
|
292
|
-
if (conversation && conversation.title
|
|
274
|
+
if (conversation && isReplaceableTitle(conversation.title)) {
|
|
293
275
|
const fallback =
|
|
294
276
|
deriveFallbackTitle(params.context) ?? UNTITLED_FALLBACK;
|
|
295
277
|
updateConversationTitle(
|
|
@@ -424,6 +406,7 @@ function buildTitleSystemPrompt(): string {
|
|
|
424
406
|
"- Think: what would make a scannable sidebar label?",
|
|
425
407
|
"- Do NOT echo back what the user asked you to do",
|
|
426
408
|
"- Do NOT respond to the conversation content",
|
|
409
|
+
"- Write the title in the same language as the conversation content",
|
|
427
410
|
"- Do NOT assess feasibility or comment on capabilities",
|
|
428
411
|
"- If input is sparse or references external context, extract a topic from the words that ARE present (e.g. 'so about that t-shirt...' → 'T-Shirt Discussion'). Never describe the absence, emptiness, or insufficiency of context — titles like 'Missing Context', 'Unclear Request', 'No Topic' are forbidden",
|
|
429
412
|
].join("\n");
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
* can never abort a turn.
|
|
31
31
|
*/
|
|
32
32
|
import { getConfigReadOnly } from "../config/loader.js";
|
|
33
|
+
import { resolvePlatformAssistantIdOrNull } from "../config/platform-identity.js";
|
|
33
34
|
import type { LlmRequestLogsClickHouseConfig } from "../config/schemas/llm-request-logs.js";
|
|
34
35
|
import { credentialKey } from "../security/credential-key.js";
|
|
35
36
|
import { getSecureKeyAsync } from "../security/secure-keys.js";
|
|
@@ -71,7 +72,7 @@ export interface ClickHouseLlmRequestLogSinkDeps {
|
|
|
71
72
|
resolveUrl?: () => Promise<string | null>;
|
|
72
73
|
/** Override the credential read for `clickhouse:password`. */
|
|
73
74
|
resolvePassword?: () => Promise<string | null>;
|
|
74
|
-
/** Override the
|
|
75
|
+
/** Override the platform assistant id (in-memory identity, then vault). */
|
|
75
76
|
resolveAssistantId?: () => Promise<string | null>;
|
|
76
77
|
/** Override fetch for testing. */
|
|
77
78
|
fetchImpl?: ClickHouseSinkFetch;
|
|
@@ -98,8 +99,7 @@ export class ClickHouseLlmRequestLogSink implements LlmRequestLogWriter {
|
|
|
98
99
|
deps.resolvePassword ??
|
|
99
100
|
(() => readCredentialOrNull("clickhouse", "password"));
|
|
100
101
|
this.resolveAssistantId =
|
|
101
|
-
deps.resolveAssistantId ??
|
|
102
|
-
(() => readCredentialOrNull("vellum", "platform_assistant_id"));
|
|
102
|
+
deps.resolveAssistantId ?? resolvePlatformAssistantIdOrNull;
|
|
103
103
|
this.fetchImpl = deps.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
104
104
|
}
|
|
105
105
|
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* than the local source. Acceptable for the "internal use while we
|
|
16
16
|
* finetune prompts" use case; revisit when mirror updates are added.
|
|
17
17
|
*/
|
|
18
|
+
import { resolvePlatformAssistantIdOrNull } from "../config/platform-identity.js";
|
|
18
19
|
import type { LlmRequestLogsClickHouseConfig } from "../config/schemas/llm-request-logs.js";
|
|
19
20
|
import { credentialKey } from "../security/credential-key.js";
|
|
20
21
|
import { getSecureKeyAsync } from "../security/secure-keys.js";
|
|
@@ -104,7 +105,7 @@ export interface ClickHouseLlmRequestLogSourceDeps {
|
|
|
104
105
|
resolveUrl?: () => Promise<string | null>;
|
|
105
106
|
/** Override the credential read for `clickhouse:password`. */
|
|
106
107
|
resolvePassword?: () => Promise<string | null>;
|
|
107
|
-
/** Override the
|
|
108
|
+
/** Override the platform assistant id (in-memory identity, then vault). */
|
|
108
109
|
resolveAssistantId?: () => Promise<string | null>;
|
|
109
110
|
/** Override the turn-id resolver (default: `getAssistantMessageIdsInTurn`). */
|
|
110
111
|
resolveTurnMessageIds?: (messageId: string) => string[];
|
|
@@ -138,8 +139,7 @@ export class ClickHouseLlmRequestLogSource implements LlmRequestLogSource {
|
|
|
138
139
|
deps.resolvePassword ??
|
|
139
140
|
(() => readCredentialOrNull("clickhouse", "password"));
|
|
140
141
|
this.resolveAssistantId =
|
|
141
|
-
deps.resolveAssistantId ??
|
|
142
|
-
(() => readCredentialOrNull("vellum", "platform_assistant_id"));
|
|
142
|
+
deps.resolveAssistantId ?? resolvePlatformAssistantIdOrNull;
|
|
143
143
|
this.resolveTurnMessageIds =
|
|
144
144
|
deps.resolveTurnMessageIds ?? getAssistantMessageIdsInTurn;
|
|
145
145
|
this.resolveMessage = deps.resolveMessage ?? getMessageById;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Message cursors: the "(createdAt, id) after which" reference behind the
|
|
3
|
+
// messages-after reads, resilient to the referenced row being deleted.
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
//
|
|
6
|
+
// A bare message id bounds a read only while its row exists. A regenerated
|
|
7
|
+
// reply (the conversation retry route discards the latest assistant turn) or a
|
|
8
|
+
// retried turn deletes the row, and a caller holding just the id can no longer
|
|
9
|
+
// say where it sat. A `MessageCursor` carries the row's `createdAt` alongside
|
|
10
|
+
// its id, so the `(createdAt, id)` bound is reconstructible from the cursor
|
|
11
|
+
// alone. The live row stays authoritative whenever it exists.
|
|
12
|
+
|
|
13
|
+
import { eq } from "drizzle-orm";
|
|
14
|
+
|
|
15
|
+
import type { LineageBound } from "./conversation-lineage.js";
|
|
16
|
+
import { getDb } from "./db-connection.js";
|
|
17
|
+
import { messages } from "./schema/index.js";
|
|
18
|
+
|
|
19
|
+
export { messagesAfterBoundFilter } from "./conversation-lineage.js";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A `(createdAt, id)` message reference. `createdAt` is null when the holder
|
|
23
|
+
* never recorded it; such a cursor bounds a read only while its row exists.
|
|
24
|
+
*/
|
|
25
|
+
export interface MessageCursor {
|
|
26
|
+
readonly id: string;
|
|
27
|
+
readonly createdAt: number | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Accepted "after" references: a message id, a {@link MessageCursor}, or
|
|
32
|
+
* null. `null`, `""`, and a cursor whose id is `""` all mean "no lower bound".
|
|
33
|
+
*/
|
|
34
|
+
export type MessagesAfterRef = string | MessageCursor | null;
|
|
35
|
+
|
|
36
|
+
/** Where a messages-after read starts. */
|
|
37
|
+
export type MessagesAfterBound =
|
|
38
|
+
/** No lower bound: every row the conversation owns. */
|
|
39
|
+
| { kind: "all" }
|
|
40
|
+
/** Rows strictly after `bound` in `(createdAt, id)` order. */
|
|
41
|
+
| { kind: "after"; bound: LineageBound }
|
|
42
|
+
/**
|
|
43
|
+
* The referenced row is gone and the reference carried no timestamp, so
|
|
44
|
+
* the read has no defensible starting point. Readers return nothing.
|
|
45
|
+
*/
|
|
46
|
+
| { kind: "vanished" };
|
|
47
|
+
|
|
48
|
+
/** The `(createdAt, id)` bound of a single message, or null when it is gone. */
|
|
49
|
+
export function loadMessageBound(messageId: string): LineageBound | null {
|
|
50
|
+
const row = getDb()
|
|
51
|
+
.select({ createdAt: messages.createdAt, id: messages.id })
|
|
52
|
+
.from(messages)
|
|
53
|
+
.where(eq(messages.id, messageId))
|
|
54
|
+
.get();
|
|
55
|
+
return row ?? null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Resolve an "after" reference to the bound a read filters on. A live row
|
|
60
|
+
* wins; a cursor whose row is gone falls back to the `createdAt` it carries,
|
|
61
|
+
* paired with its id so same-millisecond neighbours keep their order.
|
|
62
|
+
*/
|
|
63
|
+
export function resolveMessagesAfterBound(
|
|
64
|
+
after: MessagesAfterRef,
|
|
65
|
+
): MessagesAfterBound {
|
|
66
|
+
const id = typeof after === "string" ? after : (after?.id ?? "");
|
|
67
|
+
if (id === "") {
|
|
68
|
+
return { kind: "all" };
|
|
69
|
+
}
|
|
70
|
+
const live = loadMessageBound(id);
|
|
71
|
+
if (live) {
|
|
72
|
+
return { kind: "after", bound: live };
|
|
73
|
+
}
|
|
74
|
+
if (typeof after === "object" && after !== null && after.createdAt !== null) {
|
|
75
|
+
return { kind: "after", bound: { createdAt: after.createdAt, id } };
|
|
76
|
+
}
|
|
77
|
+
return { kind: "vanished" };
|
|
78
|
+
}
|
|
@@ -190,6 +190,36 @@ export function existingMessageIds(
|
|
|
190
190
|
);
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
/**
|
|
194
|
+
* `createdAt` of each of `messageIds` that exists as a row, keyed by id, in
|
|
195
|
+
* any state.
|
|
196
|
+
*
|
|
197
|
+
* Any-state deliberately: a row's `createdAt` is fixed at insert, so its
|
|
198
|
+
* completeness does not change the answer, and the callers (retrospective
|
|
199
|
+
* cursor bookkeeping) hold ids taken from rows they have already reviewed.
|
|
200
|
+
* An empty input returns an empty map without querying.
|
|
201
|
+
*/
|
|
202
|
+
export function messageCreatedAtByIds(
|
|
203
|
+
messageIds: readonly string[],
|
|
204
|
+
opts?: { db?: MessageReadHandle },
|
|
205
|
+
): Map<string, number> {
|
|
206
|
+
const db = opts?.db ?? getDb();
|
|
207
|
+
const result = new Map<string, number>();
|
|
208
|
+
// Chunked so a whole-conversation id list stays under SQLite's bound
|
|
209
|
+
// parameter limit.
|
|
210
|
+
for (let offset = 0; offset < messageIds.length; offset += 500) {
|
|
211
|
+
const rows = db
|
|
212
|
+
.select({ id: messages.id, createdAt: messages.createdAt })
|
|
213
|
+
.from(messages)
|
|
214
|
+
.where(inArray(messages.id, messageIds.slice(offset, offset + 500)))
|
|
215
|
+
.all();
|
|
216
|
+
for (const row of rows) {
|
|
217
|
+
result.set(row.id, row.createdAt);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
222
|
+
|
|
193
223
|
/**
|
|
194
224
|
* The conversation that owns `messageId`, in any state, or null when the
|
|
195
225
|
* message does not exist.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
|
|
2
|
+
import { tableExists } from "./schema-introspection.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Add a required contact_id column to assistant_ingress_invites.
|
|
@@ -10,8 +11,16 @@ import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
|
|
|
10
11
|
* 1. Add the column as nullable.
|
|
11
12
|
* 2. Delete legacy rows that have no contact binding (NULL).
|
|
12
13
|
* 3. Rebuild the table to enforce NOT NULL (SQLite cannot ALTER COLUMN).
|
|
14
|
+
*
|
|
15
|
+
* The rebuild copies whichever session-id column is live:
|
|
16
|
+
* `source_conversation_id` (after migrateRenameCreatedBySessionIdColumns)
|
|
17
|
+
* or `created_by_session_id`.
|
|
13
18
|
*/
|
|
14
19
|
export function migrateInviteContactId(database: DrizzleDb): void {
|
|
20
|
+
if (!tableExists(database, "assistant_ingress_invites")) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
15
24
|
const raw = getSqliteFrom(database);
|
|
16
25
|
|
|
17
26
|
const cols = (
|
|
@@ -20,25 +29,28 @@ export function migrateInviteContactId(database: DrizzleDb): void {
|
|
|
20
29
|
notnull: number;
|
|
21
30
|
}>
|
|
22
31
|
).map((c) => ({ name: c.name, notnull: c.notnull }));
|
|
32
|
+
const colNames = new Set(cols.map((c) => c.name));
|
|
23
33
|
|
|
24
34
|
const col = cols.find((c) => c.name === "contact_id");
|
|
25
35
|
|
|
26
|
-
// Step 1: Add the column as nullable
|
|
27
36
|
if (!col) {
|
|
28
37
|
raw.exec(
|
|
29
38
|
/*sql*/ `ALTER TABLE assistant_ingress_invites ADD COLUMN contact_id TEXT`,
|
|
30
39
|
);
|
|
31
40
|
}
|
|
32
41
|
|
|
33
|
-
// Step 2: Delete legacy rows with no contact binding
|
|
34
42
|
raw.exec(
|
|
35
43
|
/*sql*/ `DELETE FROM assistant_ingress_invites WHERE contact_id IS NULL OR contact_id = ''`,
|
|
36
44
|
);
|
|
37
45
|
|
|
38
|
-
// Step 3: Rebuild the table with NOT NULL on contact_id
|
|
39
46
|
if (col && col.notnull === 1) {
|
|
40
47
|
return;
|
|
41
|
-
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const sessionCol = colNames.has("source_conversation_id")
|
|
51
|
+
? "source_conversation_id"
|
|
52
|
+
: "created_by_session_id";
|
|
53
|
+
const sessionSelect = colNames.has(sessionCol) ? sessionCol : "NULL";
|
|
42
54
|
|
|
43
55
|
raw.exec("PRAGMA foreign_keys = OFF");
|
|
44
56
|
try {
|
|
@@ -49,7 +61,7 @@ export function migrateInviteContactId(database: DrizzleDb): void {
|
|
|
49
61
|
id TEXT PRIMARY KEY,
|
|
50
62
|
source_channel TEXT NOT NULL,
|
|
51
63
|
token_hash TEXT NOT NULL,
|
|
52
|
-
|
|
64
|
+
${sessionCol} TEXT,
|
|
53
65
|
note TEXT,
|
|
54
66
|
max_uses INTEGER NOT NULL DEFAULT 1 CHECK (max_uses > 0),
|
|
55
67
|
use_count INTEGER NOT NULL DEFAULT 0 CHECK (use_count >= 0),
|
|
@@ -70,7 +82,7 @@ export function migrateInviteContactId(database: DrizzleDb): void {
|
|
|
70
82
|
);
|
|
71
83
|
|
|
72
84
|
INSERT INTO assistant_ingress_invites_new
|
|
73
|
-
SELECT id, source_channel, token_hash,
|
|
85
|
+
SELECT id, source_channel, token_hash, ${sessionSelect}, note,
|
|
74
86
|
max_uses, use_count, expires_at, status,
|
|
75
87
|
redeemed_by_external_user_id, redeemed_by_external_chat_id,
|
|
76
88
|
redeemed_at, expected_external_user_id, voice_code_hash,
|