@vellumai/assistant 0.12.1 → 0.12.2-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 +4 -0
- 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 +3 -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 +122 -0
- package/src/desktop/desktop-chrome-policy.test.ts +79 -0
- package/src/desktop/desktop-chrome-policy.ts +47 -0
- package/src/desktop/desktop-chrome-session.ts +37 -0
- package/src/desktop/desktop-dependencies.test.ts +102 -0
- package/src/desktop/desktop-dependencies.ts +308 -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 +730 -0
- package/src/desktop/desktop-session-manager.ts +850 -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-drag.ts +177 -0
- package/src/desktop/desktop-window-manager-config.test.ts +230 -0
- package/src/desktop/desktop-window-manager-config.ts +238 -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-callsite.test.ts +41 -0
- 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/__tests__/endpoint-probe.test.ts +42 -3
- package/src/providers/inference/auth.ts +2 -3
- package/src/providers/inference/connection-availability.ts +27 -8
- package/src/providers/inference/endpoint-probe.ts +10 -1
- 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/opencode/client.test.ts +73 -4
- package/src/providers/opencode/client.ts +60 -3
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/outbound-request-sanitize.ts +96 -0
- package/src/providers/retry.ts +30 -19
- 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__/sanitized-bash.test.ts +57 -0
- 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
|
@@ -130,6 +130,7 @@ export interface PairingOptions {
|
|
|
130
130
|
* transactional request or a system alert.
|
|
131
131
|
*/
|
|
132
132
|
const ASSISTANT_SHARE_EVENT = "assistant.share";
|
|
133
|
+
const ASSISTANT_REPLY_EVENT = "chat.assistant_reply";
|
|
133
134
|
|
|
134
135
|
/**
|
|
135
136
|
* Promote a background share into an assistant-initiated thread, under the
|
|
@@ -263,7 +264,25 @@ export async function pairDeliveryWithConversation(
|
|
|
263
264
|
// notification can appear. The home feed aims its "Go to Conversation"
|
|
264
265
|
// button at the same row whenever it mirrors the signal.
|
|
265
266
|
//
|
|
266
|
-
//
|
|
267
|
+
// `chat.assistant_reply` already has its complete reply in that transcript.
|
|
268
|
+
// Its notification body is a lock-screen preview, so appending it would
|
|
269
|
+
// create a second, truncated assistant row. Keep the conversation target
|
|
270
|
+
// for deep links without writing the preview into the transcript.
|
|
271
|
+
if (
|
|
272
|
+
strategy === "start_new_conversation" &&
|
|
273
|
+
!signal.requiresConversation &&
|
|
274
|
+
signal.sourceEventName === ASSISTANT_REPLY_EVENT
|
|
275
|
+
) {
|
|
276
|
+
return {
|
|
277
|
+
conversationId: resolveSourceConversationId(signal),
|
|
278
|
+
messageId: null,
|
|
279
|
+
strategy,
|
|
280
|
+
createdNewConversation: false,
|
|
281
|
+
conversationFallbackUsed: false,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// This is deliberately not gated on home-feed eligibility: a signal the
|
|
267
286
|
// feed declines to mirror still reaches the user through the banner, and
|
|
268
287
|
// this row is what makes that landing honest.
|
|
269
288
|
if (strategy === "start_new_conversation" && !signal.requiresConversation) {
|
|
@@ -748,44 +767,45 @@ async function resolveChannelDeliveryHome(params: {
|
|
|
748
767
|
* Indexing is skipped for parity with the other notification write paths:
|
|
749
768
|
* notification copy is delivery audit, not conversational memory.
|
|
750
769
|
*/
|
|
751
|
-
|
|
752
|
-
signal
|
|
753
|
-
channel: NotificationChannel,
|
|
754
|
-
messageContent: string,
|
|
755
|
-
): Promise<{ conversationId: string; messageId: string } | null> {
|
|
756
|
-
const sourceContextId = signal.sourceContextId;
|
|
757
|
-
if (!sourceContextId) {
|
|
770
|
+
function resolveSourceConversationId(signal: NotificationSignal): string | null {
|
|
771
|
+
if (!signal.sourceContextId) {
|
|
758
772
|
return null;
|
|
759
773
|
}
|
|
760
|
-
|
|
761
|
-
let existing: ReturnType<typeof getConversation>;
|
|
762
774
|
try {
|
|
763
|
-
|
|
775
|
+
return getConversation(signal.sourceContextId)?.id ?? null;
|
|
764
776
|
} catch {
|
|
765
777
|
return null;
|
|
766
778
|
}
|
|
767
|
-
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
async function appendBodyToSourceConversation(
|
|
782
|
+
signal: NotificationSignal,
|
|
783
|
+
channel: NotificationChannel,
|
|
784
|
+
messageContent: string,
|
|
785
|
+
): Promise<{ conversationId: string; messageId: string } | null> {
|
|
786
|
+
const conversationId = resolveSourceConversationId(signal);
|
|
787
|
+
if (!conversationId) {
|
|
768
788
|
return null;
|
|
769
789
|
}
|
|
770
790
|
|
|
771
|
-
const message = await addMessage(
|
|
791
|
+
const message = await addMessage(conversationId, "assistant", messageContent, {
|
|
772
792
|
skipIndexing: true,
|
|
773
793
|
});
|
|
774
794
|
// `addMessage` projects attention metadata alone, so a client with this
|
|
775
795
|
// conversation open needs the messages tag to refetch the transcript. A
|
|
776
796
|
// notification the user taps through to has every chance of landing on an
|
|
777
797
|
// already-open conversation.
|
|
778
|
-
publishConversationMessagesChanged(
|
|
798
|
+
publishConversationMessagesChanged(conversationId);
|
|
779
799
|
|
|
780
800
|
log.info(
|
|
781
801
|
{
|
|
782
802
|
signalId: signal.signalId,
|
|
783
803
|
channel,
|
|
784
|
-
conversationId
|
|
804
|
+
conversationId,
|
|
785
805
|
messageId: message.id,
|
|
786
806
|
},
|
|
787
807
|
"Appended notification body to producing conversation",
|
|
788
808
|
);
|
|
789
809
|
|
|
790
|
-
return { conversationId
|
|
810
|
+
return { conversationId, messageId: message.id };
|
|
791
811
|
}
|
|
@@ -39,6 +39,12 @@ export interface DeliveredChannelPost {
|
|
|
39
39
|
text: string;
|
|
40
40
|
/** The message id the channel assigned when it acknowledged the post. */
|
|
41
41
|
providerMessageId: string;
|
|
42
|
+
/**
|
|
43
|
+
* The ids of the further posts the same text became when the channel
|
|
44
|
+
* split it, in send order. Each is reconciled onto the row like the first,
|
|
45
|
+
* so a reaction, edit, or delete naming any chunk resolves to this row.
|
|
46
|
+
*/
|
|
47
|
+
additionalProviderMessageIds?: readonly string[];
|
|
42
48
|
/**
|
|
43
49
|
* The conversation whose turn made the post, when a different one from
|
|
44
50
|
* the home it is recorded in (the messaging tool sending from a scheduled
|
|
@@ -75,7 +81,11 @@ export async function recordDeliveredChannelPost(
|
|
|
75
81
|
: {}),
|
|
76
82
|
},
|
|
77
83
|
});
|
|
78
|
-
|
|
84
|
+
const reconcile = makeSentMessageIdReconciler(row.id);
|
|
85
|
+
await reconcile(post.providerMessageId);
|
|
86
|
+
for (const id of post.additionalProviderMessageIds ?? []) {
|
|
87
|
+
await reconcile(id);
|
|
88
|
+
}
|
|
79
89
|
// A resident conversation reloads its history on the next turn so the
|
|
80
90
|
// post is in context; the client refetches the transcript.
|
|
81
91
|
findConversation(post.conversationId)?.markHistoryStale();
|
|
@@ -31,6 +31,10 @@ import {
|
|
|
31
31
|
patchFeedItemContent,
|
|
32
32
|
readHomeFeed,
|
|
33
33
|
} from "../home/feed-writer.js";
|
|
34
|
+
import {
|
|
35
|
+
getMemoryCheckpoint,
|
|
36
|
+
setMemoryCheckpoint,
|
|
37
|
+
} from "../persistence/checkpoints.js";
|
|
34
38
|
import { getLogger } from "../util/logger.js";
|
|
35
39
|
import {
|
|
36
40
|
buildToolApprovalSourceView,
|
|
@@ -166,11 +170,17 @@ export interface GuardianFeedReceiptParams {
|
|
|
166
170
|
*
|
|
167
171
|
* The item keeps its title and summary; `guardianRequest` flips to the
|
|
168
172
|
* terminal status (which is what removes the client's action buttons),
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
173
|
+
* urgency drops to `medium` so the item leaves the protected "Needs
|
|
174
|
+
* attention" treatment and becomes an ordinary clearable notification,
|
|
175
|
+
* and an item still marked `new` is marked `seen`: a request settled on
|
|
176
|
+
* any surface is no longer something the user has to review, and leaving
|
|
177
|
+
* it unread is a false attention signal. That clear happens only on the
|
|
178
|
+
* edge into terminal, so a status the user set later (including marking
|
|
179
|
+
* the receipt unread again) survives a caller that receipts twice.
|
|
180
|
+
*
|
|
181
|
+
* Returns false only when the write itself failed, so the withdrawal
|
|
182
|
+
* fan-out can hold its per-request receipt back and retry (same
|
|
183
|
+
* contract as the other surfaces it settles). A request with no
|
|
174
184
|
* projection item resolves true: the pending writer converges the
|
|
175
185
|
* write-vs-resolve race by re-checking canonical status after its
|
|
176
186
|
* append, so there is nothing here for a retry to fix.
|
|
@@ -197,6 +207,15 @@ export async function writeGuardianFeedReceipt(
|
|
|
197
207
|
}
|
|
198
208
|
const updated = await patchFeedItemContent(itemId, {
|
|
199
209
|
urgency: "medium",
|
|
210
|
+
// Only on the pending-to-terminal edge. A later receipt for the
|
|
211
|
+
// same request (the fan-out retries per surface, reconciliation
|
|
212
|
+
// heals drift) must not undo a user who marked the receipt unread
|
|
213
|
+
// again; from `new` specifically, because a pending item the user
|
|
214
|
+
// dismissed outright stays dismissed.
|
|
215
|
+
status: (existing) =>
|
|
216
|
+
existing.status === "new" && isPendingGuardianFeedItem(existing)
|
|
217
|
+
? "seen"
|
|
218
|
+
: existing.status,
|
|
200
219
|
guardianRequest: (existing) => ({
|
|
201
220
|
...existing,
|
|
202
221
|
status: params.status,
|
|
@@ -268,6 +287,103 @@ export async function receiptGuardianFeedItemIfRequestTerminal(
|
|
|
268
287
|
*/
|
|
269
288
|
const RECONCILE_BACKFILL_LIMIT = 50;
|
|
270
289
|
|
|
290
|
+
/** Ceiling on rows healed per round, mirroring the backfill bound. */
|
|
291
|
+
const LEGACY_UNREAD_HEAL_LIMIT = 50;
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Completion sentinel for the one-time heal below, in the daemon's
|
|
295
|
+
* checkpoint ledger (the same shape as the lexical backfill's
|
|
296
|
+
* `lexical:messages:backfill_complete`). It is the only thing that
|
|
297
|
+
* separates the two kinds of terminal receipt stored at `status: "new"`:
|
|
298
|
+
* one that never passed the pending-to-terminal edge, and one the user
|
|
299
|
+
* marked unread after it did. The rows are byte-identical (terminal
|
|
300
|
+
* projection, urgency `medium`), so the boundary is recorded rather
|
|
301
|
+
* than inferred.
|
|
302
|
+
*/
|
|
303
|
+
const LEGACY_UNREAD_HEAL_COMPLETE_KEY =
|
|
304
|
+
"guardian_feed:receipt_unread_heal_complete";
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Clear unread on terminal guardian receipts that never passed the
|
|
308
|
+
* pending-to-terminal edge in `writeGuardianFeedReceipt`.
|
|
309
|
+
*
|
|
310
|
+
* Nothing else reaches those rows: the edge runs only while a
|
|
311
|
+
* projection still reads pending, reconciliation skips items whose
|
|
312
|
+
* projection is already terminal, and guardian items carry no
|
|
313
|
+
* `expiresAt`. Without this pass they read unread indefinitely.
|
|
314
|
+
*
|
|
315
|
+
* Runs once per assistant, not once per boot. A recurring pass would
|
|
316
|
+
* take back a deliberate "mark unread" every minute, and a per-boot pass
|
|
317
|
+
* would take it back on the next restart; the sentinel is what makes a
|
|
318
|
+
* later `new` unambiguously the user's, so it is never touched.
|
|
319
|
+
*
|
|
320
|
+
* Same user-choice semantics as the edge, enforced the same way: the
|
|
321
|
+
* transition is re-evaluated inside the writer's coalescing queue, moves
|
|
322
|
+
* only `new`, and leaves `seen`, `acted_on` and `dismissed` alone.
|
|
323
|
+
* Bounded per round, and the sentinel is written only once a round drains
|
|
324
|
+
* with nothing left and nothing failed, so an interrupted pass resumes.
|
|
325
|
+
*/
|
|
326
|
+
export async function healLegacyGuardianReceiptUnread(): Promise<void> {
|
|
327
|
+
try {
|
|
328
|
+
if (getMemoryCheckpoint(LEGACY_UNREAD_HEAL_COMPLETE_KEY) === "1") {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
} catch (err) {
|
|
332
|
+
// A DB that cannot answer skips the round rather than risk re-healing
|
|
333
|
+
// a row the user has since marked unread.
|
|
334
|
+
log.warn({ err }, "Guardian receipt unread heal: checkpoint unreadable");
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const stale = readHomeFeed().items.filter(
|
|
339
|
+
(item) =>
|
|
340
|
+
item.guardianRequest !== undefined &&
|
|
341
|
+
!isPendingGuardianFeedItem(item) &&
|
|
342
|
+
item.status === "new",
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
const batch = stale.slice(0, LEGACY_UNREAD_HEAL_LIMIT);
|
|
346
|
+
let failed = 0;
|
|
347
|
+
for (const item of batch) {
|
|
348
|
+
// Re-tested at write time rather than trusted from the read above,
|
|
349
|
+
// so a row the edge transition or the user changed in between keeps
|
|
350
|
+
// whatever it was given.
|
|
351
|
+
const updated = await patchFeedItemContent(item.id, {
|
|
352
|
+
status: (existing) =>
|
|
353
|
+
existing.status === "new" && !isPendingGuardianFeedItem(existing)
|
|
354
|
+
? "seen"
|
|
355
|
+
: existing.status,
|
|
356
|
+
});
|
|
357
|
+
if (!updated) {
|
|
358
|
+
failed++;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (batch.length > 0) {
|
|
363
|
+
log.info(
|
|
364
|
+
{
|
|
365
|
+
healed: batch.length - failed,
|
|
366
|
+
failed,
|
|
367
|
+
remaining: stale.length - batch.length,
|
|
368
|
+
},
|
|
369
|
+
"Cleared unread on terminal guardian receipts that never passed the edge",
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (failed > 0 || stale.length > batch.length) {
|
|
374
|
+
// Not finished: leave the sentinel unset so the next round resumes.
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
try {
|
|
379
|
+
setMemoryCheckpoint(LEGACY_UNREAD_HEAL_COMPLETE_KEY, "1");
|
|
380
|
+
} catch (err) {
|
|
381
|
+
// The heal itself landed; only the sentinel is missing, so the next
|
|
382
|
+
// round repeats a pass that now has nothing to do.
|
|
383
|
+
log.warn({ err }, "Guardian receipt unread heal: checkpoint not persisted");
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
271
387
|
/**
|
|
272
388
|
* Converge the feed against canonical guardian state. Run from the
|
|
273
389
|
* guardian expiry sweep, so drift heals on the same cadence expiry does:
|
|
@@ -285,6 +401,10 @@ const RECONCILE_BACKFILL_LIMIT = 50;
|
|
|
285
401
|
* not be mistaken for "everything resolved".
|
|
286
402
|
*/
|
|
287
403
|
export async function reconcileGuardianFeedProjections(): Promise<void> {
|
|
404
|
+
// Ahead of the gateway read: the heal heads off stored rows alone and
|
|
405
|
+
// must not be held up by an unreachable gateway.
|
|
406
|
+
await healLegacyGuardianReceiptUnread();
|
|
407
|
+
|
|
288
408
|
let pending: GuardianRequestWire[];
|
|
289
409
|
try {
|
|
290
410
|
pending = await listGuardianRequests({ status: "pending" });
|
|
@@ -23,7 +23,10 @@ import {
|
|
|
23
23
|
getMessageById,
|
|
24
24
|
updateMessageContent,
|
|
25
25
|
} from "../persistence/conversation-crud.js";
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
ASSISTANT_INITIATED_SOURCE,
|
|
28
|
+
isBackgroundConversationType,
|
|
29
|
+
} from "../persistence/conversation-types.js";
|
|
27
30
|
import { publishConversationMessagesChanged } from "../runtime/sync/resource-sync-events.js";
|
|
28
31
|
import { getLogger } from "../util/logger.js";
|
|
29
32
|
import { normalizeTitle, stripMarkdown } from "../util/short-title.js";
|
|
@@ -272,6 +275,11 @@ async function resolveOwnedConversationMessageId(
|
|
|
272
275
|
sourceConversationId: string | undefined,
|
|
273
276
|
summary: string,
|
|
274
277
|
): Promise<string | undefined> {
|
|
278
|
+
// The completed reply is already the source conversation's canonical row.
|
|
279
|
+
// This signal's body is a compact push preview, not conversation content.
|
|
280
|
+
if (signal.sourceEventName === "chat.assistant_reply") {
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
275
283
|
if (vellumDelivery?.conversationId) {
|
|
276
284
|
return vellumDelivery.conversationId === sourceConversationId
|
|
277
285
|
? vellumDelivery.messageId
|
|
@@ -495,6 +503,18 @@ function deriveDetailPanelKind(
|
|
|
495
503
|
* `isAsyncBackground` hint. `chat.assistant_reply` also mirrors: it is the
|
|
496
504
|
* durable in-app record for the push sent after a user leaves a chat, while
|
|
497
505
|
* retaining the normal interactive conversation as its navigation target.
|
|
506
|
+
*
|
|
507
|
+
* A delivery that materialized an assistant-initiated thread never mirrors.
|
|
508
|
+
* Under the `assistant-initiated-threads` flag, `conversation-pairing.ts`
|
|
509
|
+
* promotes a background `assistant.share` into a standard conversation
|
|
510
|
+
* stamped {@link ASSISTANT_INITIATED_SOURCE}, and that thread's row in the
|
|
511
|
+
* sidebar's assistant section is the share's one surface. Mirroring it here
|
|
512
|
+
* as well put the same share in the bell a second time, labeled by its
|
|
513
|
+
* source channel ("Heartbeat"). The check reads the paired conversation's
|
|
514
|
+
* `source` rather than re-deriving the promotion rule, so it holds however
|
|
515
|
+
* that rule narrows or widens. Guardian requests are checked first on
|
|
516
|
+
* purpose: the bell is their canonical home whatever conversation they pair
|
|
517
|
+
* with.
|
|
498
518
|
*/
|
|
499
519
|
function resolveHomeFeedMirror(
|
|
500
520
|
signal: NotificationSignal,
|
|
@@ -525,6 +545,15 @@ function resolveHomeFeedMirror(
|
|
|
525
545
|
: fallbackConversationId;
|
|
526
546
|
const sourceScheduleJobId = sourceRow?.scheduleJobId ?? undefined;
|
|
527
547
|
|
|
548
|
+
// Guardian requests always project into the feed: the "Needs
|
|
549
|
+
// attention" item is the request's canonical home, and the request
|
|
550
|
+
// blocks on the guardian whatever kind of conversation raised it.
|
|
551
|
+
if (isGuardianRequestSignalEvent(signal.sourceEventName)) {
|
|
552
|
+
return { mirror: true, sourceConversationId, sourceScheduleJobId };
|
|
553
|
+
}
|
|
554
|
+
if (isAssistantInitiatedThreadDelivery(fallbackConversationId)) {
|
|
555
|
+
return { mirror: false };
|
|
556
|
+
}
|
|
528
557
|
if (
|
|
529
558
|
signal.sourceChannel === "assistant_tool" ||
|
|
530
559
|
signal.sourceEventName === "chat.assistant_reply"
|
|
@@ -534,18 +563,37 @@ function resolveHomeFeedMirror(
|
|
|
534
563
|
if (signal.attentionHints.isAsyncBackground) {
|
|
535
564
|
return { mirror: true, sourceConversationId, sourceScheduleJobId };
|
|
536
565
|
}
|
|
537
|
-
// Guardian requests always project into the feed: the "Needs
|
|
538
|
-
// attention" item is the request's canonical home, and the request
|
|
539
|
-
// blocks on the guardian whatever kind of conversation raised it.
|
|
540
|
-
if (isGuardianRequestSignalEvent(signal.sourceEventName)) {
|
|
541
|
-
return { mirror: true, sourceConversationId, sourceScheduleJobId };
|
|
542
|
-
}
|
|
543
566
|
if (isBackgroundConversationType(sourceRow?.conversationType)) {
|
|
544
567
|
return { mirror: true, sourceConversationId, sourceScheduleJobId };
|
|
545
568
|
}
|
|
546
569
|
return { mirror: false };
|
|
547
570
|
}
|
|
548
571
|
|
|
572
|
+
/**
|
|
573
|
+
* Whether the vellum delivery this signal produced landed in a thread the
|
|
574
|
+
* sidebar's assistant section already shows.
|
|
575
|
+
*
|
|
576
|
+
* Best-effort like the source lookup above: a missing id, a missing row, or
|
|
577
|
+
* a lookup failure all read as "not an assistant thread", so a storage
|
|
578
|
+
* hiccup degrades to the pre-existing behavior (a bell row) rather than
|
|
579
|
+
* dropping the notification everywhere.
|
|
580
|
+
*/
|
|
581
|
+
function isAssistantInitiatedThreadDelivery(
|
|
582
|
+
deliveryConversationId: string | undefined,
|
|
583
|
+
): boolean {
|
|
584
|
+
if (!deliveryConversationId) {
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
try {
|
|
588
|
+
return (
|
|
589
|
+
getConversation(deliveryConversationId)?.source ===
|
|
590
|
+
ASSISTANT_INITIATED_SOURCE
|
|
591
|
+
);
|
|
592
|
+
} catch {
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
549
597
|
/**
|
|
550
598
|
* Routing-intent enforcement can prune `selectedChannels` without also
|
|
551
599
|
* pruning `renderedCopy`, so iterating `renderedCopy` directly risks
|
|
@@ -32,6 +32,7 @@ import { getAttentionStateByConversationIds } from "../persistence/conversation-
|
|
|
32
32
|
import {
|
|
33
33
|
getAssistantMessageIdsInTurn,
|
|
34
34
|
getMessageById,
|
|
35
|
+
getMessagesAfter,
|
|
35
36
|
type MessageRow,
|
|
36
37
|
} from "../persistence/conversation-crud.js";
|
|
37
38
|
import { stringifyMessageContent } from "../persistence/message-content.js";
|
|
@@ -39,7 +40,6 @@ import {
|
|
|
39
40
|
isPrivateAssistantText,
|
|
40
41
|
projectPersistedAssistantContent,
|
|
41
42
|
} from "../persistence/user-facing-content.js";
|
|
42
|
-
import type { ContentBlock } from "../providers/types.js";
|
|
43
43
|
import { emitNotificationSignal } from "./emit-signal.js";
|
|
44
44
|
import { hasNotifiedSourceContextSince } from "./events-store.js";
|
|
45
45
|
import {
|
|
@@ -79,28 +79,72 @@ export interface ScheduleResultNotificationParams {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
/**
|
|
82
|
-
* Whether
|
|
83
|
-
*
|
|
82
|
+
* Whether the run delivered its result through the messaging tool, and the
|
|
83
|
+
* call succeeded.
|
|
84
84
|
*
|
|
85
|
-
* The
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* cannot see them. Without this check a well-authored Slack digest would post
|
|
85
|
+
* The messaging tool is the route the schedule skill prescribes for rich
|
|
86
|
+
* content, and it writes no `notification_events` row, so the pipeline probe
|
|
87
|
+
* cannot see it. Without this check a well-authored Slack digest would post
|
|
89
88
|
* its summary and then get a second notification whose body is "Posted the
|
|
90
|
-
* digest to #general."
|
|
91
|
-
*
|
|
92
|
-
*
|
|
89
|
+
* digest to #general."
|
|
90
|
+
*
|
|
91
|
+
* Both conditions are load-bearing. The route has to be recognized, because
|
|
92
|
+
* this is a known-routes list rather than a general "did the run do anything?"
|
|
93
|
+
* heuristic. And the call has to have succeeded: a `tool_use` block records
|
|
94
|
+
* only that the model called the tool, so a send the channel refused leaves
|
|
95
|
+
* the same block as one that landed, and suppressing on it costs the user both
|
|
96
|
+
* the digest and the run's explanation of the failure. The executor's verdict
|
|
97
|
+
* is on the `tool_result` the call produced, so success is read from there,
|
|
98
|
+
* and a call with no result counts as undelivered. Either condition unmet gets
|
|
99
|
+
* the fallback, which is the safe failure.
|
|
100
|
+
*
|
|
101
|
+
* The result read starts at the run's first row, so it covers the results
|
|
102
|
+
* interleaved through the turn. Rows from a later turn in a reused
|
|
103
|
+
* conversation can come back too and are harmless, because only this run's
|
|
104
|
+
* own call ids are matched.
|
|
105
|
+
*
|
|
106
|
+
* A Slack Web API post through bash is deliberately not a route, because its
|
|
107
|
+
* success proves nothing about the post. Slack refuses a call with HTTP 200
|
|
108
|
+
* and `ok: false` in the body, and the authenticated-request command fails
|
|
109
|
+
* only on a non-2xx status, so a post Slack refused still leaves a successful
|
|
110
|
+
* tool result. A run that posts that way gets the fallback too: at worst a
|
|
111
|
+
* duplicate, never a silence.
|
|
93
112
|
*/
|
|
94
|
-
function
|
|
95
|
-
|
|
96
|
-
|
|
113
|
+
function deliveredThroughMessagingTool(
|
|
114
|
+
conversationId: string,
|
|
115
|
+
runRows: readonly MessageRow[],
|
|
116
|
+
): boolean {
|
|
117
|
+
const callIds = new Set<string>();
|
|
118
|
+
for (const row of runRows) {
|
|
119
|
+
for (const block of row.content) {
|
|
120
|
+
if (block.type === "tool_use" && block.name === "messaging_send") {
|
|
121
|
+
callIds.add(block.id);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
97
124
|
}
|
|
98
|
-
if (
|
|
99
|
-
return
|
|
125
|
+
if (callIds.size === 0) {
|
|
126
|
+
return false;
|
|
100
127
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
128
|
+
const [firstRunRow] = runRows;
|
|
129
|
+
for (const row of getMessagesAfter(conversationId, {
|
|
130
|
+
id: firstRunRow.id,
|
|
131
|
+
createdAt: firstRunRow.createdAt,
|
|
132
|
+
})) {
|
|
133
|
+
if (row.role !== "user") {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
for (const block of row.content) {
|
|
137
|
+
// guard:allow-tool-result-only: the local executor's verdict on a
|
|
138
|
+
// delivery it ran. A server-side `web_search_tool_result` carries no
|
|
139
|
+
// `is_error` and never delivers anything.
|
|
140
|
+
if (
|
|
141
|
+
block.type === "tool_result" &&
|
|
142
|
+
block.is_error !== true &&
|
|
143
|
+
callIds.has(block.tool_use_id)
|
|
144
|
+
) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
104
148
|
}
|
|
105
149
|
return false;
|
|
106
150
|
}
|
|
@@ -237,11 +281,11 @@ export async function emitScheduleResultNotification(
|
|
|
237
281
|
return;
|
|
238
282
|
}
|
|
239
283
|
|
|
240
|
-
// The run delivered around the pipeline
|
|
241
|
-
//
|
|
242
|
-
//
|
|
284
|
+
// The run delivered around the pipeline through the messaging tool, and
|
|
285
|
+
// the call succeeded. The user has the result; a notification reading
|
|
286
|
+
// "posted it" on top would be the duplicate.
|
|
243
287
|
const runRows = collectRunRows(latestRow, conversationId, runStartedAt);
|
|
244
|
-
if (
|
|
288
|
+
if (deliveredThroughMessagingTool(conversationId, runRows)) {
|
|
245
289
|
return;
|
|
246
290
|
}
|
|
247
291
|
|
package/src/oauth/AGENTS.md
CHANGED
|
@@ -8,6 +8,8 @@ When introducing a new built-in OAuth integration (one that appears in `seed-pro
|
|
|
8
8
|
|
|
9
9
|
Add an entry to `PROVIDER_SEED_DATA`. Required fields: `provider`, `authorizeUrl`, `tokenExchangeUrl`, `defaultScopes`, `displayLabel`, `description`, `dashboardUrl`, `clientIdPlaceholder`, `logoUrl`, and `injectionTemplates`. Optional: `availableScopes` — either a structured array of `{scope, description?}` objects or a URL string pointing to the provider's scope documentation. See existing entries for the full shape. The `provider` key must be snake_case and is used as the canonical identifier everywhere else.
|
|
10
10
|
|
|
11
|
+
`injectionTemplates` is also a security boundary, not only an injection rule. `assistant oauth request` and `assistant channels request` accept an absolute URL only when its host matches one of the entry's `hostPattern` values (`assertOAuthRequestUrlAllowed` in `../runtime/routes/oauth-commands-routes.ts`), so that list is the full set of hosts the credential may be sent to. A pattern matches exactly unless it starts with `*.`; reserve the wildcard for per-tenant hosts that cannot be enumerated (`*.salesforce.com`), and name a fixed host the provider documents (`files.slack.com`) by name. The guard sees only the URL the caller passes: the connection follows a redirect itself, and a cross-origin hop carries no `Authorization`, so a redirect target never receives the credential and never needs listing.
|
|
12
|
+
|
|
11
13
|
If the provider will support managed mode, set `managedServiceConfigKey` to a slug matching the key you will add to `ServicesSchema` (e.g. `"acme-oauth"`).
|
|
12
14
|
|
|
13
15
|
### 2. _(managed only)_ Add a service schema — `../config/schemas/services.ts`
|
|
@@ -127,6 +127,60 @@ describe("PROVIDER_SEED_DATA managed mode wiring", () => {
|
|
|
127
127
|
expect(figma.featureFlag).toBe("figma-oauth");
|
|
128
128
|
});
|
|
129
129
|
|
|
130
|
+
test("shopify keeps its per-tenant URL templates", () => {
|
|
131
|
+
// Shopify has no global OAuth host: every endpoint lives on the
|
|
132
|
+
// merchant's own domain. Hardcoding any host here would send every
|
|
133
|
+
// merchant's authorization to the wrong store, so the {tenant_host}
|
|
134
|
+
// placeholder the platform substitutes must survive.
|
|
135
|
+
const shopify = PROVIDER_SEED_DATA.shopify;
|
|
136
|
+
expect(shopify).toBeDefined();
|
|
137
|
+
for (const url of [
|
|
138
|
+
shopify.authorizeUrl,
|
|
139
|
+
shopify.tokenExchangeUrl,
|
|
140
|
+
shopify.refreshUrl,
|
|
141
|
+
shopify.baseUrl,
|
|
142
|
+
shopify.identityUrl,
|
|
143
|
+
]) {
|
|
144
|
+
expect(url).toContain("{tenant_host}");
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("shopify requests comma-separated scopes", () => {
|
|
149
|
+
// Shopify parses `scope` as a comma-separated list; the OAuth2 default
|
|
150
|
+
// space separator is read as one malformed scope and the whole
|
|
151
|
+
// authorization request is rejected.
|
|
152
|
+
expect(PROVIDER_SEED_DATA.shopify.scopeSeparator).toBe(",");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("shopify does not request approval-gated scopes", () => {
|
|
156
|
+
// Shopify rejects the entire authorization request when the app cannot
|
|
157
|
+
// grant a requested scope -- the same failure that broke monday and
|
|
158
|
+
// Figma. These scopes all require Shopify's prior approval.
|
|
159
|
+
const gated = [
|
|
160
|
+
"read_all_orders",
|
|
161
|
+
"read_customer_payment_methods",
|
|
162
|
+
"read_own_subscription_contracts",
|
|
163
|
+
"write_own_subscription_contracts",
|
|
164
|
+
"read_shopify_payments_dispute_evidences",
|
|
165
|
+
"write_shopify_payments_dispute_evidences",
|
|
166
|
+
];
|
|
167
|
+
for (const scope of gated) {
|
|
168
|
+
expect(PROVIDER_SEED_DATA.shopify.defaultScopes).not.toContain(scope);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("shopify injects the Admin API's own auth header", () => {
|
|
173
|
+
// The Admin API reads X-Shopify-Access-Token and ignores an
|
|
174
|
+
// Authorization: Bearer header entirely, so a Bearer template here would
|
|
175
|
+
// make every proxied request unauthenticated.
|
|
176
|
+
const templates = PROVIDER_SEED_DATA.shopify.injectionTemplates;
|
|
177
|
+
expect(templates).toBeDefined();
|
|
178
|
+
const [template] = templates ?? [];
|
|
179
|
+
expect(template?.headerName).toBe("X-Shopify-Access-Token");
|
|
180
|
+
expect(template?.valuePrefix).toBe("");
|
|
181
|
+
expect(template?.hostPattern).toBe("*.myshopify.com");
|
|
182
|
+
});
|
|
183
|
+
|
|
130
184
|
test("every managedServiceConfigKey resolves to a ServicesSchema key", () => {
|
|
131
185
|
// Cross-repo invariant: a provider with managedServiceConfigKey but no
|
|
132
186
|
// matching ServicesSchema entry silently falls back to BYO mode in
|