@vellumai/assistant 0.12.0 → 0.12.1-dev.202609142113.d1590bc
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 +20 -21
- package/docs/architecture/turn-actor.md +15 -8
- package/docs/guardian-request-flow.md +16 -0
- package/openapi.yaml +327 -52
- package/package.json +2 -1
- package/scripts/smoke-desktop-window-theme.ts +186 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
- package/src/__tests__/anthropic-provider.test.ts +0 -63
- 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__/assistant-event-hub-machine-name.test.ts +6 -7
- package/src/__tests__/assistant-event-hub.test.ts +44 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
- package/src/__tests__/computer-use-tools.test.ts +19 -7
- 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 +4 -2
- package/src/__tests__/cu-unified-flow.test.ts +8 -2
- package/src/__tests__/document-sync-tags.test.ts +25 -0
- 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__/heartbeat-service.test.ts +56 -40
- 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-cu-proxy.test.ts +539 -0
- 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__/list-messages-camera-frame.test.ts +121 -0
- 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__/notification-vellum-adapter.test.ts +85 -2
- package/src/__tests__/oauth-commands-routes.test.ts +113 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
- package/src/__tests__/openai-provider.test.ts +1 -0
- package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
- package/src/__tests__/platform-callback-registration.test.ts +8 -16
- package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
- package/src/__tests__/qdrant-manager.test.ts +132 -0
- package/src/__tests__/resolve-app.test.ts +135 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
- package/src/__tests__/script-proxy-certs.test.ts +35 -0
- package/src/__tests__/service-account-token.test.ts +247 -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__/skills.test.ts +9 -4
- 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__/test-support/computer-use-skill-harness.ts +3 -2
- package/src/__tests__/unicode.test.ts +13 -0
- 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 +96 -10
- 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/agent/loop.ts +1 -0
- 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/events/notification-intent.ts +2 -0
- package/src/api/events/user-message-echo.ts +7 -1
- package/src/api/index.ts +6 -0
- package/src/api/responses/conversation-message.ts +5 -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/avatar/identity-avatar.ts +7 -2
- 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__/bash.test.ts +53 -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/bash.help.ts +8 -13
- package/src/cli/commands/bash.ts +7 -25
- 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 +255 -0
- package/src/config/__tests__/platform-rehydration.test.ts +99 -9
- 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/computer-use/SKILL.md +20 -2
- package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- 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/playbooks/tools/playbook-create.ts +2 -1
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
- package/src/config/bundled-skills/schedule/SKILL.md +3 -4
- 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 +6 -2
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/env.ts +2 -2
- package/src/config/feature-flag-registry.json +24 -12
- package/src/config/loader.ts +1 -0
- 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 +201 -0
- package/src/config/platform-rehydration.ts +11 -27
- 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/services.ts +8 -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/credential-health/credential-health-service.ts +11 -17
- 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-error.ts +3 -2
- package/src/daemon/conversation-media-retry.ts +2 -1
- package/src/daemon/conversation-process.ts +35 -64
- package/src/daemon/conversation-runtime-assembly.ts +2 -1
- package/src/daemon/conversation-skill-tools.ts +13 -31
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +15 -9
- package/src/daemon/conversation-tool-setup.ts +37 -43
- package/src/daemon/conversation.ts +0 -3
- package/src/daemon/dictation-profile-store.ts +2 -1
- package/src/daemon/handlers/shared.ts +2 -1
- package/src/daemon/handlers/skills.ts +6 -3
- package/src/daemon/host-bash-proxy.ts +8 -12
- package/src/daemon/host-browser-proxy.ts +15 -1
- package/src/daemon/host-cu-proxy.ts +186 -19
- package/src/daemon/host-file-proxy.ts +8 -10
- package/src/daemon/host-proxy-preactivation.ts +2 -1
- 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/orphan-reaper.ts +1 -1
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/reaction-history-render.ts +2 -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/heartbeat/heartbeat-service.ts +21 -3
- package/src/home/__tests__/feed-writer.test.ts +22 -0
- package/src/home/feed-writer.ts +11 -2
- 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-sight-frame-echo.test.ts +238 -0
- 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-photo.ts +25 -6
- 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__/guardian-feed-projection.test.ts +158 -2
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
- package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
- package/src/notifications/adapters/macos.ts +3 -0
- 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/guardian-feed-projection.ts +125 -5
- package/src/notifications/home-feed-side-effect.ts +55 -7
- package/src/notifications/schedule-result-producer.ts +66 -22
- package/src/oauth/AGENTS.md +2 -0
- package/src/oauth/__tests__/seed-providers-managed.test.ts +54 -0
- package/src/oauth/byo-connection.test.ts +105 -0
- package/src/oauth/byo-connection.ts +36 -6
- package/src/oauth/connection-resolver.test.ts +73 -0
- package/src/oauth/connection-resolver.ts +95 -2
- package/src/oauth/scope-utils.ts +24 -0
- package/src/oauth/seed-providers.ts +204 -25
- 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-queries.ts +2 -2
- package/src/persistence/conversation-title-placeholders.ts +23 -0
- package/src/persistence/conversation-title-service.ts +9 -26
- package/src/persistence/embeddings/qdrant-manager.ts +163 -18
- 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 +4 -2
- 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-prompt.test.ts +5 -0
- 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/context-search/agent-runner.ts +3 -2
- package/src/plugins/defaults/memory/context-search/format.ts +3 -2
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
- package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
- package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
- 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/host-utils.ts +10 -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-prompt.ts +1 -1
- 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 +28 -2
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
- package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
- package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
- package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
- package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
- package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
- package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
- package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
- package/src/plugins/defaults/memory/v3/card.ts +3 -2
- package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
- package/src/plugins/defaults/memory/v3/sections.ts +9 -3
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
- 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/__tests__/retry-outbound-sanitize.test.ts +176 -0
- package/src/providers/anthropic/client.ts +0 -47
- 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/inference/auth.ts +2 -3
- package/src/providers/inference/connection-availability.ts +27 -8
- package/src/providers/inference/resolve-auth.ts +22 -4
- package/src/providers/inference/service-account-token.ts +248 -0
- package/src/providers/model-catalog.ts +34 -0
- package/src/providers/model-intents.ts +10 -5
- package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
- 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 +212 -79
- package/src/providers/openai/codex-models.ts +0 -4
- package/src/providers/openai/google-thought-signature.ts +320 -0
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/outbound-request-sanitize.ts +96 -0
- package/src/providers/retry.ts +24 -3
- package/src/providers/unparseable-tool-args.ts +5 -1
- package/src/runtime/AGENTS.md +3 -1
- 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__/host-observe.test.ts +40 -1
- 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/agent-wake.ts +2 -1
- package/src/runtime/assistant-event-hub.ts +21 -3
- package/src/runtime/auth/same-actor.ts +38 -3
- package/src/runtime/channel-send.ts +316 -0
- package/src/runtime/host-observe.ts +11 -1
- 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__/default-provider-routes.test.ts +2 -2
- 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/conversation-routes.ts +16 -3
- 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 +61 -4
- package/src/runtime/routes/events-routes.ts +4 -0
- package/src/runtime/routes/host-cu-routes.ts +6 -0
- package/src/runtime/routes/index.ts +4 -2
- 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 +6 -20
- package/src/runtime/routes/oauth-commands-routes.ts +36 -0
- package/src/runtime/routes/platform-routes.ts +18 -15
- package/src/runtime/routes/workspace-routes.test.ts +55 -26
- package/src/runtime/routes/workspace-routes.ts +46 -63
- package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
- package/src/runtime/routes/workspace-tree-walk.ts +240 -0
- package/src/runtime/routes/workspace-utils.ts +17 -0
- 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/run-script.ts +2 -1
- package/src/schedule/schedule-store.ts +9 -2
- 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/security/untrusted-content.ts +3 -1
- 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/__tests__/side-effect-abort-guard.test.ts +1 -0
- 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/browser/browser-execution.ts +1 -1
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
- package/src/tools/browser/network-recorder.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 +121 -2
- package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
- package/src/tools/credentials/metadata-store.ts +100 -0
- package/src/tools/filesystem/search.ts +2 -1
- 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/network/web-search.ts +6 -1
- package/src/tools/policy-context.ts +6 -7
- package/src/tools/skills/scaffold-managed.ts +61 -3
- package/src/tools/subagent/spawn.ts +5 -8
- package/src/tools/terminal/__tests__/safe-env.test.ts +6 -6
- package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
- package/src/tools/terminal/safe-env.ts +23 -3
- package/src/tools/terminal/sanitized-bash.ts +105 -0
- package/src/tools/watch/watch-retro-report.ts +0 -8
- package/src/types/host-capabilities.ts +5 -3
- 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/util/unicode.ts +15 -1
- package/src/watch/watch-timeline.ts +2 -2
- package/src/workflows/run-manager.ts +2 -1
- package/src/workspace/git-porcelain.ts +66 -0
- package/src/workspace/git-service.ts +6 -55
- 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/runtime/routes/debug-bash-routes.ts +0 -173
- package/src/util/fs-watcher-error.ts +0 -36
- /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isGemini3Model,
|
|
3
|
+
unsignedThoughtSignatureFallback,
|
|
4
|
+
} from "../gemini-thought-signature.js";
|
|
5
|
+
import type { Message, ToolUseContent } from "../types.js";
|
|
6
|
+
|
|
7
|
+
export type GoogleToolCallExtraContent = {
|
|
8
|
+
extra_content?: {
|
|
9
|
+
google?: {
|
|
10
|
+
thought_signature?: string;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type GoogleThoughtSignatureRetryKind =
|
|
16
|
+
| "missing-thought-signature"
|
|
17
|
+
| "unknown-extra-content";
|
|
18
|
+
|
|
19
|
+
function asNonEmptyString(value: unknown): string | undefined {
|
|
20
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function googleObjectThoughtSignature(google: unknown): string | undefined {
|
|
24
|
+
if (google === null || typeof google !== "object") {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
const record = google as Record<string, unknown>;
|
|
28
|
+
return (
|
|
29
|
+
asNonEmptyString(record.thought_signature) ??
|
|
30
|
+
asNonEmptyString(record.thoughtSignature)
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Gemini's OpenAI-compatible wire format puts the thought signature on
|
|
36
|
+
* `tool_calls[].extra_content.google.thought_signature`.
|
|
37
|
+
*/
|
|
38
|
+
export function googleThoughtSignatureFromUnknown(
|
|
39
|
+
value: unknown,
|
|
40
|
+
): string | undefined {
|
|
41
|
+
if (value === null || typeof value !== "object") {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
const extra = (value as { extra_content?: unknown }).extra_content;
|
|
45
|
+
if (extra === null || typeof extra !== "object") {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
return googleObjectThoughtSignature((extra as { google?: unknown }).google);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function toolUseMetadataFromChatCompletionsDelta(
|
|
52
|
+
delta: unknown,
|
|
53
|
+
): ToolUseContent["providerMetadata"] | undefined {
|
|
54
|
+
const thoughtSignature = googleThoughtSignatureFromUnknown(delta);
|
|
55
|
+
if (!thoughtSignature) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
return { gemini: { thoughtSignature } };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function thoughtSignatureFromToolUseMetadata(
|
|
62
|
+
metadata: ToolUseContent["providerMetadata"] | undefined,
|
|
63
|
+
): string | undefined {
|
|
64
|
+
return metadata?.gemini?.thoughtSignature;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function attachGoogleThoughtSignature<T extends object>(
|
|
68
|
+
toolCall: T,
|
|
69
|
+
thoughtSignature: string | undefined,
|
|
70
|
+
): T & GoogleToolCallExtraContent {
|
|
71
|
+
if (!thoughtSignature) {
|
|
72
|
+
return toolCall;
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
...toolCall,
|
|
76
|
+
extra_content: {
|
|
77
|
+
google: { thought_signature: thoughtSignature },
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Attach Gemini extra_content only when the destination model is Gemini 3.x.
|
|
84
|
+
* Non-Gemini Chat Completions endpoints never see the vendor field on the
|
|
85
|
+
* first request. Remapped model ids that still require a signature recover
|
|
86
|
+
* via {@link backfillGoogleThoughtSignatures} after a 4xx.
|
|
87
|
+
*/
|
|
88
|
+
export function attachGoogleThoughtSignatureIfNeeded<T extends object>(
|
|
89
|
+
toolCall: T,
|
|
90
|
+
thoughtSignature: string | undefined,
|
|
91
|
+
model: string,
|
|
92
|
+
): T & GoogleToolCallExtraContent {
|
|
93
|
+
if (!isGemini3Model(model)) {
|
|
94
|
+
return toolCall;
|
|
95
|
+
}
|
|
96
|
+
return attachGoogleThoughtSignature(toolCall, thoughtSignature);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function stampUnsignedGoogleThoughtSignature(
|
|
100
|
+
toolCalls: GoogleToolCallExtraContent[],
|
|
101
|
+
options?: { model?: string },
|
|
102
|
+
): boolean {
|
|
103
|
+
const fallback = unsignedThoughtSignatureFallback(
|
|
104
|
+
toolCalls.map((tc) => googleThoughtSignatureFromUnknown(tc)),
|
|
105
|
+
options,
|
|
106
|
+
);
|
|
107
|
+
if (!fallback) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
const target = toolCalls[fallback.index];
|
|
111
|
+
if (!target) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
target.extra_content = {
|
|
115
|
+
google: { thought_signature: fallback.signature },
|
|
116
|
+
};
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function stampCapturedGoogleThoughtSignatures(
|
|
121
|
+
toolCalls: Array<GoogleToolCallExtraContent & { id?: unknown }>,
|
|
122
|
+
capturedByCallId: ReadonlyMap<string, string>,
|
|
123
|
+
): boolean {
|
|
124
|
+
let added = false;
|
|
125
|
+
for (const tc of toolCalls) {
|
|
126
|
+
if (typeof tc.id !== "string") {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const captured = capturedByCallId.get(tc.id);
|
|
130
|
+
if (!captured || googleThoughtSignatureFromUnknown(tc)) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
tc.extra_content = {
|
|
134
|
+
google: { thought_signature: captured },
|
|
135
|
+
};
|
|
136
|
+
added = true;
|
|
137
|
+
}
|
|
138
|
+
return added;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Gemini 3.x validates the first function call of each step. When none of the
|
|
143
|
+
* serialized tool_calls carry a captured signature, attach the documented dummy
|
|
144
|
+
* so unsigned history (or a non-capturing earlier turn) does not 400.
|
|
145
|
+
*/
|
|
146
|
+
export function applyGemini3UnsignedToolCallFallback(
|
|
147
|
+
toolCalls: GoogleToolCallExtraContent[],
|
|
148
|
+
model: string,
|
|
149
|
+
): void {
|
|
150
|
+
stampUnsignedGoogleThoughtSignature(toolCalls, { model });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function paramsMessages(params: unknown): unknown[] | undefined {
|
|
154
|
+
const messages = (params as { messages?: unknown }).messages;
|
|
155
|
+
return Array.isArray(messages) ? messages : undefined;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function assistantToolCalls(
|
|
159
|
+
msg: unknown,
|
|
160
|
+
): Array<GoogleToolCallExtraContent & { id?: unknown }> | undefined {
|
|
161
|
+
if (msg === null || typeof msg !== "object") {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
const record = msg as { role?: unknown; tool_calls?: unknown };
|
|
165
|
+
if (record.role !== "assistant" || !Array.isArray(record.tool_calls)) {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
return record.tool_calls as Array<
|
|
169
|
+
GoogleToolCallExtraContent & { id?: unknown }
|
|
170
|
+
>;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function messagesCarryGoogleThoughtSignature(params: unknown): boolean {
|
|
174
|
+
const messages = paramsMessages(params);
|
|
175
|
+
if (!messages) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
return messages.some((msg) => {
|
|
179
|
+
const toolCalls = assistantToolCalls(msg);
|
|
180
|
+
return toolCalls?.some((tc) => googleThoughtSignatureFromUnknown(tc));
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function assistantToolCallsNeedThoughtSignatureBackfill(
|
|
185
|
+
params: unknown,
|
|
186
|
+
): boolean {
|
|
187
|
+
const messages = paramsMessages(params);
|
|
188
|
+
if (!messages) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
return messages.some((msg) => {
|
|
192
|
+
const toolCalls = assistantToolCalls(msg);
|
|
193
|
+
if (!toolCalls || toolCalls.length === 0) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
return !toolCalls.some((tc) => googleThoughtSignatureFromUnknown(tc));
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function stripGoogleThoughtSignatures(params: unknown): boolean {
|
|
201
|
+
const messages = paramsMessages(params);
|
|
202
|
+
if (!messages) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
let stripped = false;
|
|
206
|
+
for (const msg of messages) {
|
|
207
|
+
const toolCalls = assistantToolCalls(msg);
|
|
208
|
+
if (!toolCalls) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
for (const tc of toolCalls) {
|
|
212
|
+
if (tc.extra_content !== undefined) {
|
|
213
|
+
delete tc.extra_content;
|
|
214
|
+
stripped = true;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return stripped;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function geminiThoughtSignaturesByToolCallId(
|
|
222
|
+
messages: ReadonlyArray<Message>,
|
|
223
|
+
): Map<string, string> {
|
|
224
|
+
const out = new Map<string, string>();
|
|
225
|
+
for (const msg of messages) {
|
|
226
|
+
if (msg.role !== "assistant") {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
for (const block of msg.content) {
|
|
230
|
+
if (block.type !== "tool_use") {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const signature = block.providerMetadata?.gemini?.thoughtSignature;
|
|
234
|
+
if (signature) {
|
|
235
|
+
out.set(block.id, signature);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return out;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function backfillGoogleThoughtSignatures(
|
|
243
|
+
params: unknown,
|
|
244
|
+
capturedByCallId?: ReadonlyMap<string, string>,
|
|
245
|
+
): boolean {
|
|
246
|
+
const messages = paramsMessages(params);
|
|
247
|
+
if (!messages) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
let added = false;
|
|
251
|
+
for (const msg of messages) {
|
|
252
|
+
const toolCalls = assistantToolCalls(msg);
|
|
253
|
+
if (!toolCalls || toolCalls.length === 0) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (
|
|
257
|
+
capturedByCallId &&
|
|
258
|
+
stampCapturedGoogleThoughtSignatures(toolCalls, capturedByCallId)
|
|
259
|
+
) {
|
|
260
|
+
added = true;
|
|
261
|
+
}
|
|
262
|
+
if (stampUnsignedGoogleThoughtSignature(toolCalls)) {
|
|
263
|
+
added = true;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return added;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function backfillUnsignedGoogleThoughtSignatures(
|
|
270
|
+
params: unknown,
|
|
271
|
+
): boolean {
|
|
272
|
+
return backfillGoogleThoughtSignatures(params);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const UNKNOWN_EXTRA_CONTENT_REJECTION =
|
|
276
|
+
/unknown|unexpected|unrecognized|additional propert|extra (?:field|property)|not (?:a )?valid|invalid (?:argument|parameter|field|property)/i;
|
|
277
|
+
|
|
278
|
+
export function classifyGoogleThoughtSignatureRetry(
|
|
279
|
+
params: unknown,
|
|
280
|
+
options: {
|
|
281
|
+
isClientError: boolean;
|
|
282
|
+
haystack: string;
|
|
283
|
+
capturedByCallId?: ReadonlyMap<string, string>;
|
|
284
|
+
},
|
|
285
|
+
): {
|
|
286
|
+
kind: GoogleThoughtSignatureRetryKind;
|
|
287
|
+
message: string;
|
|
288
|
+
apply: () => void;
|
|
289
|
+
} | null {
|
|
290
|
+
if (!options.isClientError) {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
if (
|
|
294
|
+
assistantToolCallsNeedThoughtSignatureBackfill(params) &&
|
|
295
|
+
/thought[_\s-]?signature/i.test(options.haystack)
|
|
296
|
+
) {
|
|
297
|
+
return {
|
|
298
|
+
kind: "missing-thought-signature",
|
|
299
|
+
message:
|
|
300
|
+
"Upstream requires thought signature round-trip; retrying with signatures on unsigned tool_calls",
|
|
301
|
+
apply: () => {
|
|
302
|
+
backfillGoogleThoughtSignatures(params, options.capturedByCallId);
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
if (
|
|
307
|
+
messagesCarryGoogleThoughtSignature(params) &&
|
|
308
|
+
/extra_content/i.test(options.haystack) &&
|
|
309
|
+
UNKNOWN_EXTRA_CONTENT_REJECTION.test(options.haystack)
|
|
310
|
+
) {
|
|
311
|
+
return {
|
|
312
|
+
kind: "unknown-extra-content",
|
|
313
|
+
message: "Upstream rejected tool_call extra_content; retrying without it",
|
|
314
|
+
apply: () => {
|
|
315
|
+
stripGoogleThoughtSignatures(params);
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
@@ -36,6 +36,19 @@ const OPENROUTER_APP_ATTRIBUTION_HEADERS = {
|
|
|
36
36
|
|
|
37
37
|
const OPENROUTER_MODEL_EFFORT_CEILINGS = modelEffortCeilings("openrouter");
|
|
38
38
|
|
|
39
|
+
const OPENROUTER_MAX_COMPLETION_TOKENS_ONLY = /gpt-5\.[12]-(?:codex|chat)/;
|
|
40
|
+
|
|
41
|
+
/** Output-token-limit wire key for an OpenRouter model id. Most endpoints
|
|
42
|
+
* advertise `max_tokens`; GPT-5.1/5.2 Codex and Chat advertise only
|
|
43
|
+
* `max_completion_tokens`. Exported for tests. */
|
|
44
|
+
export function openRouterOutputTokenLimitField(
|
|
45
|
+
model: string,
|
|
46
|
+
): "max_completion_tokens" | "max_tokens" {
|
|
47
|
+
return OPENROUTER_MAX_COMPLETION_TOKENS_ONLY.test(model)
|
|
48
|
+
? "max_completion_tokens"
|
|
49
|
+
: "max_tokens";
|
|
50
|
+
}
|
|
51
|
+
|
|
39
52
|
/**
|
|
40
53
|
* Extract the normalized `openrouter.only` list from a per-call config. Returns
|
|
41
54
|
* an empty array when the field is absent, empty, or contains no usable string
|
|
@@ -143,6 +156,7 @@ export class OpenRouterProvider extends OpenAIChatCompletionsProvider {
|
|
|
143
156
|
streamTimeoutMs: options.streamTimeoutMs,
|
|
144
157
|
requestHeaders: OPENROUTER_APP_ATTRIBUTION_HEADERS,
|
|
145
158
|
assistantReasoningField: "reasoning",
|
|
159
|
+
outputTokenLimitField: "max_tokens",
|
|
146
160
|
});
|
|
147
161
|
this.openRouterApiKey = apiKey;
|
|
148
162
|
this.resolvedBaseURL = baseURL;
|
|
@@ -250,6 +264,12 @@ export class OpenRouterProvider extends OpenAIChatCompletionsProvider {
|
|
|
250
264
|
);
|
|
251
265
|
}
|
|
252
266
|
|
|
267
|
+
protected override resolveOutputTokenLimitField(
|
|
268
|
+
model: string,
|
|
269
|
+
): "max_completion_tokens" | "max_tokens" {
|
|
270
|
+
return openRouterOutputTokenLimitField(model);
|
|
271
|
+
}
|
|
272
|
+
|
|
253
273
|
private resolveEffectiveModel(options?: SendMessageOptions): string {
|
|
254
274
|
const config = options?.config as Record<string, unknown> | undefined;
|
|
255
275
|
const override =
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Outbound-request Unicode guard for every provider adapter.
|
|
3
|
+
*
|
|
4
|
+
* JavaScript strings are UTF-16, and any `.slice()` on a code-unit boundary
|
|
5
|
+
* can orphan half of a surrogate pair. `JSON.stringify` emits the orphan as a
|
|
6
|
+
* `\udXXX` escape, which strict API parsers reject with a 400 before the model
|
|
7
|
+
* runs (Anthropic: "no low surrogate in string"; OpenAI: `invalid_json`).
|
|
8
|
+
* `RetryProvider` sanitizes the caller's messages and options once at entry,
|
|
9
|
+
* so every attempt, corrective resend, credential refresh, and backup route
|
|
10
|
+
* sends a clean payload, and sanitizes token-count requests the same way.
|
|
11
|
+
* Nothing beneath the wrapper truncates text, so this is the one place the
|
|
12
|
+
* invariant is enforced.
|
|
13
|
+
*
|
|
14
|
+
* The replacement is lossy (U+FFFD), so the warning names the block types
|
|
15
|
+
* involved: the durable fix for any offender is a surrogate-safe slice at the
|
|
16
|
+
* truncation site (`safeStringSlice` in `util/unicode.ts`).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { getLogger } from "../util/logger.js";
|
|
20
|
+
import { stripOrphanedSurrogatesDeep } from "../util/unicode.js";
|
|
21
|
+
import type { Message, SendMessageOptions } from "./types.js";
|
|
22
|
+
|
|
23
|
+
const log = getLogger("outbound-request-sanitize");
|
|
24
|
+
|
|
25
|
+
/** Rate-limit the warning so a poisoned conversation cannot flood the log. */
|
|
26
|
+
const ORPHAN_WARNING_THROTTLE_MS = 60_000;
|
|
27
|
+
let lastOrphanWarningMs = 0;
|
|
28
|
+
|
|
29
|
+
export interface OutboundRequest {
|
|
30
|
+
messages: Message[];
|
|
31
|
+
options: SendMessageOptions | undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Fields that hold provider-bound base64 (an inline media source's payload,
|
|
36
|
+
* a redacted-thinking blob) are ASCII by construction and can run to over a
|
|
37
|
+
* hundred megabytes, so scanning them is pure event-loop cost.
|
|
38
|
+
*/
|
|
39
|
+
function isBase64Payload(
|
|
40
|
+
key: string,
|
|
41
|
+
parent: Record<string, unknown>,
|
|
42
|
+
): boolean {
|
|
43
|
+
return (
|
|
44
|
+
key === "data" &&
|
|
45
|
+
(parent.type === "base64" || parent.type === "redacted_thinking")
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Replace every orphaned UTF-16 surrogate in the request's messages and
|
|
51
|
+
* options with U+FFFD. Returns the same references when nothing needed
|
|
52
|
+
* fixing, so the happy path allocates nothing.
|
|
53
|
+
*/
|
|
54
|
+
export function sanitizeOutboundRequest(
|
|
55
|
+
providerName: string,
|
|
56
|
+
request: OutboundRequest,
|
|
57
|
+
): OutboundRequest {
|
|
58
|
+
const sanitized = stripOrphanedSurrogatesDeep(request, {
|
|
59
|
+
skipKey: isBase64Payload,
|
|
60
|
+
});
|
|
61
|
+
if (!sanitized.changed) {
|
|
62
|
+
return request;
|
|
63
|
+
}
|
|
64
|
+
logOrphanedSurrogateWarning(
|
|
65
|
+
providerName,
|
|
66
|
+
sanitized.fixedStringCount,
|
|
67
|
+
request.messages,
|
|
68
|
+
);
|
|
69
|
+
return sanitized.value;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function logOrphanedSurrogateWarning(
|
|
73
|
+
providerName: string,
|
|
74
|
+
fixedStringCount: number,
|
|
75
|
+
messages: Message[],
|
|
76
|
+
): void {
|
|
77
|
+
const now = Date.now();
|
|
78
|
+
if (now - lastOrphanWarningMs < ORPHAN_WARNING_THROTTLE_MS) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
lastOrphanWarningMs = now;
|
|
82
|
+
const blockTypes = new Set<string>();
|
|
83
|
+
for (const msg of messages) {
|
|
84
|
+
for (const block of msg.content) {
|
|
85
|
+
blockTypes.add(block.type);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
log.warn(
|
|
89
|
+
{
|
|
90
|
+
provider: providerName,
|
|
91
|
+
fixedStringCount,
|
|
92
|
+
blockTypes: Array.from(blockTypes),
|
|
93
|
+
},
|
|
94
|
+
"stripped orphaned UTF-16 surrogates from an outbound LLM request; an upstream truncation is not surrogate-aware",
|
|
95
|
+
);
|
|
96
|
+
}
|
package/src/providers/retry.ts
CHANGED
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
isAdaptiveThinkingUnsupportedModel,
|
|
45
45
|
} from "./model-catalog.js";
|
|
46
46
|
import { buildOpenCodeRequestHeaders } from "./opencode/client.js";
|
|
47
|
+
import { sanitizeOutboundRequest } from "./outbound-request-sanitize.js";
|
|
47
48
|
import { dispatchProviderResolvable } from "./provider-resolvability.js";
|
|
48
49
|
import {
|
|
49
50
|
isThinkingConfigAdaptive,
|
|
@@ -1142,8 +1143,9 @@ export class RetryProvider implements Provider {
|
|
|
1142
1143
|
|
|
1143
1144
|
// Forward the optional token-counting endpoint so the capability survives
|
|
1144
1145
|
// the wrapper chain (callers gate on its presence). Bound straight to the
|
|
1145
|
-
// inner provider
|
|
1146
|
-
//
|
|
1146
|
+
// inner provider behind the same surrogate sanitizer as `sendMessage` —
|
|
1147
|
+
// count_tokens is a cheap separate endpoint and its caller already falls
|
|
1148
|
+
// back on error, so it needs no retry wrapping.
|
|
1147
1149
|
// Deliberately not re-bound when a credential refresh swaps `inner`: every
|
|
1148
1150
|
// outer wrapper snapshots this the same way at construction, so a re-bind
|
|
1149
1151
|
// here would never reach callers. count_tokens on the pre-refresh credential
|
|
@@ -1188,7 +1190,18 @@ export class RetryProvider implements Provider {
|
|
|
1188
1190
|
this.inner = inner;
|
|
1189
1191
|
this.name = inner.name;
|
|
1190
1192
|
if (inner.countInputTokens) {
|
|
1191
|
-
|
|
1193
|
+
const countInputTokens = inner.countInputTokens.bind(inner);
|
|
1194
|
+
this.countInputTokens = (messages, systemPrompt, tools) => {
|
|
1195
|
+
const clean = sanitizeOutboundRequest(this.name, {
|
|
1196
|
+
messages,
|
|
1197
|
+
options: { systemPrompt, tools },
|
|
1198
|
+
});
|
|
1199
|
+
return countInputTokens(
|
|
1200
|
+
clean.messages,
|
|
1201
|
+
clean.options?.systemPrompt ?? systemPrompt,
|
|
1202
|
+
clean.options?.tools,
|
|
1203
|
+
);
|
|
1204
|
+
};
|
|
1192
1205
|
}
|
|
1193
1206
|
}
|
|
1194
1207
|
|
|
@@ -1262,6 +1275,14 @@ export class RetryProvider implements Provider {
|
|
|
1262
1275
|
let credentialRefreshAttempted = false;
|
|
1263
1276
|
let correctiveResendAttempted = false;
|
|
1264
1277
|
let fallbackAttempted = false;
|
|
1278
|
+
|
|
1279
|
+
// Every attempt below, the backup route included, sends what this
|
|
1280
|
+
// wrapper was handed, so an orphaned UTF-16 surrogate is stripped here
|
|
1281
|
+
// once rather than rejected by the upstream parser on every resend.
|
|
1282
|
+
({ messages, options } = sanitizeOutboundRequest(this.name, {
|
|
1283
|
+
messages,
|
|
1284
|
+
options,
|
|
1285
|
+
}));
|
|
1265
1286
|
let messagesForAttempt = messages;
|
|
1266
1287
|
|
|
1267
1288
|
const normalizedOptions = normalizeSendMessageOptions(this.name, options, {
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* an error tool result, so the model sees the failure and can retry, instead
|
|
13
13
|
* of a tool executing with garbage input.
|
|
14
14
|
*/
|
|
15
|
+
|
|
16
|
+
import { safeStringSlice } from "../util/unicode.js";
|
|
15
17
|
const UNPARSEABLE_TOOL_ARGS_KEY = "_raw";
|
|
16
18
|
|
|
17
19
|
/**
|
|
@@ -56,7 +58,9 @@ export function unparseableToolArgsMessage(
|
|
|
56
58
|
): string {
|
|
57
59
|
const PREVIEW_LIMIT = 200;
|
|
58
60
|
const preview =
|
|
59
|
-
raw.length > PREVIEW_LIMIT
|
|
61
|
+
raw.length > PREVIEW_LIMIT
|
|
62
|
+
? `${safeStringSlice(raw, 0, PREVIEW_LIMIT)}…`
|
|
63
|
+
: raw;
|
|
60
64
|
return (
|
|
61
65
|
`Error: the arguments for "${toolName}" were not valid JSON — the argument stream was malformed or truncated, so the tool was NOT executed. ` +
|
|
62
66
|
`Received: ${preview || "(empty)"}\n` +
|
package/src/runtime/AGENTS.md
CHANGED
|
@@ -12,6 +12,8 @@ The single HTTP send endpoint is `POST /v1/messages`. Key behaviors:
|
|
|
12
12
|
|
|
13
13
|
Do NOT add new send endpoints. All message ingress should go through `POST /v1/messages` (HTTP).
|
|
14
14
|
|
|
15
|
+
`POST /v1/channels/send` is not ingress: it is outbound delivery, the assistant posting text to a channel chat. It runs `sendChannelText` (`runtime/channel-send.ts`), the one send implementation for every channel whose transport declares proactive addressing (`ChannelTransport.addressFor`), and the messaging tool's channel branch runs the same function in-process, as the CLI's channel send door does over the route. A model-directed channel send goes through that function or it is not recorded; do not add a provider-side send for a channel that has a transport. The route names no sending turn: only the in-process tool call can claim a send landed in the turn's own chat, so everything sent through the route is recorded.
|
|
16
|
+
|
|
15
17
|
**`interrupt-on-send`: a send may stop the running turn instead of queueing behind it.** With the flag on, a busy conversation is handed to `interruptRunningTurn` (`daemon/conversation-interrupt.ts`), which aborts the turn with `preempted_by_new_message`, waits for it to release the processing lock AND for its turn-boundary commit to finish, repairs the abandoned `tool_use` blocks with a persisted synthetic `tool_result`, and answers `released`. The route then falls through to the code an idle conversation already uses, so there is one turn dispatch rather than a second one for interrupts. The queue is not discarded: it may hold another actor's messages.
|
|
16
18
|
|
|
17
19
|
**The handover runs off the response, not on it.** Fire-and-forget is not suspended for an interrupt. The eligibility decision is synchronous (`classifyInterruptEligibility`), so the route answers `202 { accepted, requestId }` the moment it accepts the message, then runs the abort, the release wait, the commit wait, the repair, the persist and the dispatch in a detached task. Holding the request would cost the abort budget (`ABORT_RELEASE_WAIT_MS`) plus the turn-boundary commit wait, long enough for a client to time out and retry a message the daemon is still placing. That 202 carries `messageId` as well as `requestId`, and they are the same value: a user turn persists its row under its `requestId` (`persistUserMessage` passes `id: requestId`), which is what lets the id be answered before the row exists. Both are required, not decorative: `postChatMessage` rejects an accepted, non-queued response without a string `messageId` and the client drops the optimistic row. Clients reconcile that row on `user_message_echo`. Two rules for anything added to that task: it must not be able to drop the message (every failure path falls back to `queueSend`, which never takes the enqueue's idle fast path, and a queue that refuses the fallback is reported to the sender as a `QUEUE_FULL` error keyed on the `requestId` the 202 carried, since the 429 itself reaches nobody), and a duplicate `clientMessageId` is answered from the existing row BEFORE the abort, since the insert's own dedup settles too late to un-abort a turn.
|
|
@@ -95,7 +97,7 @@ Host CU allows the assistant to proxy computer-use actions (screenshots, mouse/k
|
|
|
95
97
|
|
|
96
98
|
- **Discovery**: Clients discover pending host CU requests via SSE events (`host_cu_request`) which include a `requestId`.
|
|
97
99
|
- **Resolution**: Clients execute the CU action on the host and respond via:
|
|
98
|
-
- `POST /v1/host-cu-result
|
|
100
|
+
- `POST /v1/host-cu-result`: `{ requestId, axTree?, axDiff?, screenshot?, screenshotWidthPx?, screenshotHeightPx?, screenWidthPt?, screenHeightPt?, executionResult?, executionError?, secondaryWindows?, userGuidance?, timings? }`, where `timings` is optional per-phase helper timings in milliseconds
|
|
99
101
|
- **Tracking**: Uses the same `pending-interactions` tracker as the other host proxy types, with `kind: "host_cu"`. Registration happens in `conversation-routes.ts` and the route handler is in `host-cu-routes.ts`.
|
|
100
102
|
- **Conversation-agnostic observation**: `observeHostScreen()` in `host-observe.ts` issues a `computer_use_observe` request with no conversation, for callers outside an agent turn (`HostCuProxy` only exists inside one). It takes the initiating actor's principal id and reaches only that actor's own `host_cu` clients: `pickSameUserAutoResolve` picks the default target and `enforceSameActorOrErrorResult` gates an explicitly named one, both before the request is registered or broadcast. Its pending interaction carries no `conversationId`, so `host-cu-routes.ts` hands the raw observation fields straight to the waiting caller instead of routing through a conversation's CU proxy. On the desktop side the ordinary `host_cu` executor services the request.
|
|
101
103
|
|