@vellumai/assistant 0.12.0 → 0.12.1-staging.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +5 -1
- package/ARCHITECTURE.md +11 -12
- package/docs/architecture/turn-actor.md +15 -8
- package/openapi.yaml +292 -2
- package/package.json +1 -1
- package/scripts/smoke-desktop-window-theme.ts +186 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
- package/src/__tests__/app-compiler.test.ts +6 -0
- package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
- package/src/__tests__/app-dir-path-guard.test.ts +1 -3
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
- package/src/__tests__/computer-use-tools.test.ts +17 -5
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +28 -29
- package/src/__tests__/config-watcher.test.ts +23 -8
- package/src/__tests__/connection-model-compat.test.ts +2 -2
- package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-pairing.test.ts +26 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
- package/src/__tests__/conversation-queue.test.ts +48 -207
- package/src/__tests__/conversation-retry-discard.test.ts +54 -1
- package/src/__tests__/conversation-skill-tools.test.ts +27 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
- package/src/__tests__/conversation-title-service.test.ts +14 -10
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
- package/src/__tests__/credential-record-retire.test.ts +171 -1
- package/src/__tests__/credential-routes.test.ts +212 -3
- package/src/__tests__/credential-security-invariants.test.ts +2 -1
- package/src/__tests__/cu-unified-flow.test.ts +8 -2
- package/src/__tests__/drain-kick-guard.test.ts +0 -2
- package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
- package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
- package/src/__tests__/git-porcelain.test.ts +47 -0
- package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
- package/src/__tests__/host-bash-proxy.test.ts +26 -45
- package/src/__tests__/host-browser-proxy.test.ts +6 -17
- package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
- package/src/__tests__/host-file-proxy.test.ts +11 -23
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
- package/src/__tests__/llm-catalog-parity.test.ts +25 -0
- package/src/__tests__/mcp-auth-routes.test.ts +6 -9
- package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
- package/src/__tests__/mcp-health-check.test.ts +8 -11
- package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
- package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
- package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
- package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
- package/src/__tests__/messaging-send-tool.test.ts +355 -548
- package/src/__tests__/model-intents.test.ts +3 -0
- package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
- package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +13 -0
- package/src/__tests__/resolve-app.test.ts +135 -0
- package/src/__tests__/script-proxy-certs.test.ts +35 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/source-fingerprint.test.ts +184 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +19 -3
- package/src/__tests__/subagent-tools.test.ts +3 -2
- package/src/__tests__/telephony-tts-guard.test.ts +38 -3
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
- package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
- package/src/__tests__/workspace-git-service.test.ts +26 -0
- package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
- package/src/acp/__tests__/agent-process.test.ts +258 -4
- package/src/acp/__tests__/auth-required.test.ts +96 -0
- package/src/acp/__tests__/client-handler.test.ts +90 -2
- package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
- package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
- package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
- package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
- package/src/acp/__tests__/session-manager.test.ts +895 -4
- package/src/acp/agent-process.test.ts +4 -1
- package/src/acp/agent-process.ts +94 -9
- package/src/acp/auth-required.ts +50 -1
- package/src/acp/auto-install.test.ts +9 -7
- package/src/acp/client-handler.ts +16 -3
- package/src/acp/model-config.test.ts +166 -0
- package/src/acp/model-config.ts +138 -0
- package/src/acp/resolve-agent.test.ts +220 -6
- package/src/acp/resolve-agent.ts +88 -23
- package/src/acp/session-manager.test.ts +196 -3
- package/src/acp/session-manager.ts +441 -18
- package/src/acp/types.ts +50 -5
- package/src/api/events/acp-session-model-update.test.ts +88 -0
- package/src/api/events/acp-session-model-update.ts +46 -0
- package/src/api/events/app-files-changed.ts +4 -3
- package/src/api/index.ts +6 -0
- package/src/apps/app-store.ts +1 -1
- package/src/apps/resolve-app.ts +81 -0
- package/src/apps/source-fingerprint.ts +266 -0
- package/src/bundler/app-compiler.ts +15 -0
- package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
- package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
- package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
- package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
- package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
- package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
- package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
- package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
- package/src/calls/__tests__/voice-control-protocol.test.ts +58 -25
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
- package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
- package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
- package/src/calls/__tests__/voice-session-bridge.test.ts +37 -1
- package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
- package/src/calls/barge-in-guard.ts +118 -0
- package/src/calls/call-constants.ts +24 -4
- package/src/calls/call-controller.ts +499 -199
- package/src/calls/call-transport.ts +16 -0
- package/src/calls/media-stream-output.ts +32 -0
- package/src/calls/media-stream-server.ts +82 -30
- package/src/calls/media-stream-stt-session.ts +15 -0
- package/src/{live-voice → calls}/progress-narration.ts +2 -2
- package/src/calls/resolve-call-tts-provider.ts +33 -2
- package/src/calls/tts-call-strategy.ts +9 -5
- package/src/calls/types.ts +2 -0
- package/src/calls/voice-control-protocol.ts +53 -21
- package/src/calls/voice-leg-coordinator.ts +189 -0
- package/src/calls/voice-progress-cadence.ts +423 -0
- package/src/calls/voice-session-bridge.ts +59 -46
- package/src/calls/voice-triage-escalate.ts +175 -56
- package/src/cli/commands/__tests__/apps.test.ts +245 -0
- package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
- package/src/cli/commands/apps.help.ts +67 -13
- package/src/cli/commands/apps.ts +183 -0
- package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
- package/src/cli/commands/channels/index.help.ts +70 -2
- package/src/cli/commands/channels/index.ts +16 -3
- package/src/cli/commands/channels/send.ts +128 -0
- package/src/cli/commands/credentials.help.ts +6 -2
- package/src/cli/commands/credentials.ts +2 -2
- package/src/cli/commands/mcp.help.ts +8 -8
- package/src/cli/commands/mcp.ts +1 -1
- package/src/cli/commands/roadmap.help.ts +5 -1
- package/src/cli/commands/roadmap.ts +1 -1
- package/src/cli/lib/bundled-marketplace.json +103 -90
- package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
- package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
- package/src/config/__tests__/platform-identity.test.ts +117 -0
- package/src/config/__tests__/platform-rehydration.test.ts +93 -0
- package/src/config/acp-agent-write.ts +43 -0
- package/src/config/acp-defaults.test.ts +13 -3
- package/src/config/acp-defaults.ts +23 -6
- package/src/config/acp-schema.test.ts +81 -0
- package/src/config/acp-schema.ts +30 -2
- package/src/config/bundled-skills/acp/SKILL.md +22 -14
- package/src/config/bundled-skills/acp/TOOLS.json +5 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
- package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
- package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
- package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/env.ts +2 -2
- package/src/config/feature-flag-registry.json +8 -12
- package/src/config/memory-v3-gate.test.ts +31 -0
- package/src/config/memory-v3-gate.ts +20 -16
- package/src/config/platform-identity.ts +144 -0
- package/src/config/platform-rehydration.ts +75 -21
- package/src/config/schema.ts +2 -2
- package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
- package/src/config/schemas/__tests__/voice.test.ts +148 -0
- package/src/config/schemas/calls.ts +1 -28
- package/src/config/schemas/live-voice.ts +1 -173
- package/src/config/schemas/mcp.ts +1 -1
- package/src/config/schemas/memory-retrospective.ts +10 -0
- package/src/config/schemas/skills.ts +0 -14
- package/src/config/schemas/voice.ts +150 -0
- package/src/credential-execution/process-manager.test.ts +18 -19
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
- package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
- package/src/daemon/app-change-notify.ts +45 -0
- package/src/daemon/config-watcher.ts +13 -194
- package/src/daemon/conversation-process.ts +35 -64
- package/src/daemon/conversation-skill-tools.ts +13 -31
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +10 -8
- package/src/daemon/conversation-tool-setup.ts +37 -43
- package/src/daemon/conversation.ts +0 -3
- package/src/daemon/host-bash-proxy.ts +8 -12
- package/src/daemon/host-browser-proxy.ts +15 -1
- package/src/daemon/host-file-proxy.ts +8 -10
- package/src/daemon/host-transfer-proxy.ts +14 -18
- package/src/daemon/lifecycle.ts +0 -5
- package/src/daemon/mcp-reload-service.ts +5 -5
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/shutdown-handlers.ts +1 -9
- package/src/daemon/skill-memory-refresh.ts +64 -0
- package/src/daemon/tool-side-effects.ts +7 -18
- package/src/desktop/__tests__/fake-desktop.ts +119 -0
- package/src/desktop/desktop-chrome-policy.test.ts +71 -0
- package/src/desktop/desktop-chrome-policy.ts +42 -0
- package/src/desktop/desktop-dependencies.test.ts +102 -0
- package/src/desktop/desktop-dependencies.ts +306 -0
- package/src/desktop/desktop-feature.test.ts +18 -0
- package/src/desktop/desktop-feature.ts +19 -0
- package/src/desktop/desktop-panel-config.test.ts +135 -0
- package/src/desktop/desktop-panel-config.ts +149 -0
- package/src/desktop/desktop-session-manager.test.ts +627 -0
- package/src/desktop/desktop-session-manager.ts +814 -0
- package/src/desktop/desktop-stream-bridge.test.ts +395 -0
- package/src/desktop/desktop-stream-bridge.ts +214 -0
- package/src/desktop/desktop-wallpaper.test.ts +191 -0
- package/src/desktop/desktop-wallpaper.ts +113 -0
- package/src/desktop/desktop-window-theme.ts +170 -0
- package/src/hooks/types.ts +33 -0
- package/src/i18n/AGENTS.md +22 -0
- package/src/i18n/__tests__/i18n.test.ts +108 -0
- package/src/i18n/index.ts +16 -0
- package/src/i18n/locales.ts +93 -0
- package/src/i18n/messages.ts +61 -0
- package/src/i18n/title.ts +25 -0
- package/src/inbound/platform-callback-registration.ts +4 -5
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
- package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
- package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
- package/src/live-voice/live-voice-session.ts +213 -693
- package/src/live-voice/protocol.ts +0 -2
- package/src/mcp/__tests__/effective-config.test.ts +2 -2
- package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
- package/src/mcp/effective-config.ts +3 -3
- package/src/mcp/mcp-header-store.ts +29 -45
- package/src/mcp/spec-schema.ts +111 -0
- package/src/mcp/startup.ts +8 -4
- package/src/mcp/workspace-mcp-config.ts +188 -0
- package/src/messaging/provider.ts +10 -3
- package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
- package/src/messaging/providers/callback-routing.ts +18 -0
- package/src/messaging/providers/channel-transport.ts +56 -0
- package/src/messaging/providers/discord/transport.ts +17 -0
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
- package/src/messaging/providers/slack/adapter.ts +7 -44
- package/src/messaging/providers/slack/transport.ts +16 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
- package/src/messaging/providers/telegram-bot/client.ts +0 -26
- package/src/messaging/providers/telegram-bot/transport.ts +20 -0
- package/src/messaging/providers/whatsapp/adapter.ts +1 -48
- package/src/messaging/providers/whatsapp/transport.ts +18 -0
- package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
- package/src/notifications/assistant-reply-producer.ts +8 -3
- package/src/notifications/conversation-pairing.ts +36 -16
- package/src/notifications/delivered-post-record.ts +11 -1
- package/src/notifications/home-feed-side-effect.ts +55 -7
- package/src/outbound-proxy/certs.ts +3 -5
- package/src/permissions/checker.test.ts +11 -6
- package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
- package/src/persistence/conversation-crud.ts +71 -63
- package/src/persistence/conversation-lineage.ts +12 -4
- package/src/persistence/conversation-title-placeholders.ts +23 -0
- package/src/persistence/conversation-title-service.ts +9 -26
- package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
- package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
- package/src/persistence/message-cursor.ts +78 -0
- package/src/persistence/message-reads.ts +30 -0
- package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
- package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
- package/src/persistence/schema/memory-core.ts +5 -0
- package/src/platform/client.test.ts +2 -0
- package/src/platform/client.ts +2 -10
- package/src/plugin-api/constants.ts +2 -0
- package/src/plugin-api/index.ts +1 -0
- package/src/plugin-api/types.ts +2 -0
- package/src/plugins/defaults/index.ts +2 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
- package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
- package/src/plugins/defaults/memory/graph/capability-seed.ts +4 -6
- package/src/plugins/defaults/memory/hooks/init.ts +10 -0
- package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
- package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
- package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
- package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
- package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
- package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
- package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
- package/src/plugins/defaults/memory/startup.ts +6 -1
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
- package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +89 -0
- package/src/plugins/defaults/memory/worker.ts +6 -0
- package/src/plugins/mcp-servers.ts +15 -75
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
- package/src/providers/gemini/client.ts +9 -23
- package/src/providers/gemini-thought-signature.test.ts +63 -0
- package/src/providers/gemini-thought-signature.ts +44 -0
- package/src/providers/model-catalog.ts +34 -0
- package/src/providers/model-intents.ts +10 -5
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
- package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
- package/src/providers/openai/chat-completions-provider.ts +192 -78
- package/src/providers/openai/codex-models.ts +0 -4
- package/src/providers/openai/google-thought-signature.ts +320 -0
- package/src/runtime/AGENTS.md +2 -0
- package/src/runtime/__tests__/channel-send.test.ts +443 -0
- package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
- package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
- package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
- package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
- package/src/runtime/auth/same-actor.ts +38 -3
- package/src/runtime/channel-send.ts +316 -0
- package/src/runtime/http-server.ts +160 -151
- package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +205 -0
- package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
- package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
- package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
- package/src/runtime/routes/acp-routes.test.ts +15 -0
- package/src/runtime/routes/acp-routes.ts +72 -9
- package/src/runtime/routes/app-management-routes.ts +73 -0
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/channel-send-routes.ts +85 -0
- package/src/runtime/routes/conversation-list-routes.ts +14 -3
- package/src/runtime/routes/conversation-query-routes.ts +40 -0
- package/src/runtime/routes/credential-routes.ts +19 -10
- package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
- package/src/runtime/routes/desktop-setup-routes.ts +35 -0
- package/src/runtime/routes/diagnostics-routes.ts +55 -3
- package/src/runtime/routes/index.ts +4 -0
- package/src/runtime/routes/log-export-routes.ts +2 -0
- package/src/runtime/routes/mcp-auth-routes.ts +39 -47
- package/src/runtime/routes/migration-routes.ts +8 -21
- package/src/runtime/routes/platform-routes.ts +18 -15
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
- package/src/runtime/services/conversation-serializer.ts +16 -3
- package/src/runtime/sync/resource-sync-events.ts +2 -1
- package/src/schedule/worker.ts +1 -1
- package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
- package/src/security/ces-http-record-backend.ts +120 -0
- package/src/security/ces-rpc-record-backend.ts +7 -0
- package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
- package/src/skills/workspace-skill-md-signature.ts +50 -0
- package/src/subagent/consult-context.ts +12 -25
- package/src/tools/acp/list-agents.test.ts +3 -1
- package/src/tools/acp/spawn.test.ts +146 -11
- package/src/tools/acp/spawn.ts +21 -3
- package/src/tools/acp/status.test.ts +94 -0
- package/src/tools/acp/status.ts +40 -3
- package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
- package/src/tools/capability-offer.test.ts +22 -2
- package/src/tools/capability-offer.ts +16 -1
- package/src/tools/computer-use/definitions.ts +4 -2
- package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
- package/src/tools/credentials/metadata-store.ts +100 -0
- package/src/tools/host-filesystem/edit.test.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +2 -2
- package/src/tools/host-filesystem/read.test.ts +2 -1
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/transfer.test.ts +2 -1
- package/src/tools/host-filesystem/transfer.ts +2 -2
- package/src/tools/host-filesystem/write.test.ts +2 -1
- package/src/tools/host-filesystem/write.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +16 -0
- package/src/tools/host-terminal/host-shell.ts +4 -20
- package/src/tools/policy-context.ts +6 -7
- package/src/tools/subagent/spawn.ts +5 -8
- package/src/tools/terminal/__tests__/safe-env.test.ts +6 -6
- package/src/tools/terminal/safe-env.ts +23 -3
- package/src/tools/watch/watch-retro-report.ts +0 -8
- package/src/util/__tests__/language-subtag.test.ts +23 -1
- package/src/util/ca-bundle.ts +13 -0
- package/src/util/language-subtag.ts +16 -0
- package/src/util/platform.ts +8 -0
- package/src/workspace/git-porcelain.ts +66 -0
- package/src/workspace/git-service.ts +5 -54
- package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
- package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
- package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
- package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/app-source-watcher.test.ts +0 -218
- package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
- package/src/__tests__/speaker-identification.test.ts +0 -56
- package/src/calls/speaker-identification.ts +0 -233
- package/src/daemon/app-source-watcher.ts +0 -218
- package/src/messaging/providers/whatsapp/client.ts +0 -29
- package/src/util/fs-watcher-error.ts +0 -36
- /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
6
6
|
|
|
7
7
|
import { setOverridesForTesting } from "../../__tests__/feature-flag-test-helpers.js";
|
|
8
|
+
import { MESSAGE_KEYS } from "../../i18n/index.js";
|
|
8
9
|
import type { AttentionState } from "../../persistence/conversation-attention-store.js";
|
|
9
10
|
import type {
|
|
10
11
|
ConversationRow,
|
|
@@ -589,6 +590,60 @@ describe("emitAssistantReplyNotification", () => {
|
|
|
589
590
|
});
|
|
590
591
|
});
|
|
591
592
|
|
|
593
|
+
// A conversation whose title is still being written stores the message key
|
|
594
|
+
// itself, which is a non-empty string the sanitizer has no reason to reject.
|
|
595
|
+
// Sending it would put a raw key on the lock screen, so it counts as absent.
|
|
596
|
+
test("omits requestedTitle while the title is still generating", async () => {
|
|
597
|
+
conversationRow = makeConversation({
|
|
598
|
+
title: MESSAGE_KEYS.CONVERSATION_TITLE_GENERATING,
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
await run();
|
|
602
|
+
|
|
603
|
+
expect(emitCalls).toHaveLength(1);
|
|
604
|
+
expect(emitCalls[0].contextPayload).toEqual({
|
|
605
|
+
requestedMessage: "Sure, here is the plan.",
|
|
606
|
+
});
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
test("omits requestedTitle for the legacy generating placeholder", async () => {
|
|
610
|
+
conversationRow = makeConversation({ title: "Generating title..." });
|
|
611
|
+
|
|
612
|
+
await run();
|
|
613
|
+
|
|
614
|
+
expect(emitCalls).toHaveLength(1);
|
|
615
|
+
expect(emitCalls[0].contextPayload).toEqual({
|
|
616
|
+
requestedMessage: "Sure, here is the plan.",
|
|
617
|
+
});
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
test("omits requestedTitle for the untitled placeholder key", async () => {
|
|
621
|
+
conversationRow = makeConversation({
|
|
622
|
+
title: MESSAGE_KEYS.CONVERSATION_TITLE_UNTITLED,
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
await run();
|
|
626
|
+
|
|
627
|
+
expect(emitCalls).toHaveLength(1);
|
|
628
|
+
expect(emitCalls[0].contextPayload).toEqual({
|
|
629
|
+
requestedMessage: "Sure, here is the plan.",
|
|
630
|
+
});
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
// Only an exact key is a system constant. A title the user or the model
|
|
634
|
+
// wrote that happens to contain one is their copy and travels as written.
|
|
635
|
+
test("keeps a title that merely contains a message key", async () => {
|
|
636
|
+
conversationRow = makeConversation({
|
|
637
|
+
title: `notes on ${MESSAGE_KEYS.CONVERSATION_TITLE_GENERATING}`,
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
await run();
|
|
641
|
+
|
|
642
|
+
expect(emitCalls[0].contextPayload.requestedTitle).toBe(
|
|
643
|
+
"notes on conversation.title.generating",
|
|
644
|
+
);
|
|
645
|
+
});
|
|
646
|
+
|
|
592
647
|
test("caps the preview at 200 chars", async () => {
|
|
593
648
|
assistantRow = makeAssistantRow([
|
|
594
649
|
{ type: "text", text: "a".repeat(300) },
|
|
@@ -26,6 +26,15 @@ const messageRewrites: Array<{ messageId: string; content: string }> = [];
|
|
|
26
26
|
/** messageId -> the conversation it belongs to, for the scoped lookup. */
|
|
27
27
|
const messageOwners = new Map<string, string>();
|
|
28
28
|
let conversationRow: { conversationType: string } | null = null;
|
|
29
|
+
/**
|
|
30
|
+
* Rows for specific ids, consulted before the catch-all `conversationRow`,
|
|
31
|
+
* so a test can hand the signal's source and the delivery's paired
|
|
32
|
+
* conversation different shapes.
|
|
33
|
+
*/
|
|
34
|
+
const conversationRowsById = new Map<
|
|
35
|
+
string,
|
|
36
|
+
{ conversationType: string; source?: string }
|
|
37
|
+
>();
|
|
29
38
|
let conversationLookupShouldThrow = false;
|
|
30
39
|
let messageAppendShouldThrow = false;
|
|
31
40
|
let messageRewriteShouldThrow = false;
|
|
@@ -71,7 +80,7 @@ mock.module("../../persistence/conversation-crud.js", () => ({
|
|
|
71
80
|
if (conversationLookupShouldThrow) {
|
|
72
81
|
throw new Error("simulated conversation lookup failure");
|
|
73
82
|
}
|
|
74
|
-
return conversationRow;
|
|
83
|
+
return conversationRowsById.get(id) ?? conversationRow;
|
|
75
84
|
},
|
|
76
85
|
addMessage: async (
|
|
77
86
|
conversationId: string,
|
|
@@ -164,6 +173,7 @@ beforeEach(() => {
|
|
|
164
173
|
messageRewrites.length = 0;
|
|
165
174
|
messageOwners.clear();
|
|
166
175
|
conversationRow = null;
|
|
176
|
+
conversationRowsById.clear();
|
|
167
177
|
conversationLookupShouldThrow = false;
|
|
168
178
|
messageAppendShouldThrow = false;
|
|
169
179
|
messageRewriteShouldThrow = false;
|
|
@@ -258,7 +268,11 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
258
268
|
},
|
|
259
269
|
});
|
|
260
270
|
|
|
261
|
-
const item = await writeHomeFeedItemForSignal(
|
|
271
|
+
const item = await writeHomeFeedItemForSignal(
|
|
272
|
+
signal,
|
|
273
|
+
decision,
|
|
274
|
+
makeVellumDelivery(),
|
|
275
|
+
);
|
|
262
276
|
|
|
263
277
|
expect(item).not.toBeNull();
|
|
264
278
|
expect(appendCalls).toHaveLength(1);
|
|
@@ -267,7 +281,10 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
267
281
|
"The deployment completed successfully.",
|
|
268
282
|
);
|
|
269
283
|
expect(appendCalls[0]!.conversationId).toBe("conv-source-1");
|
|
270
|
-
expect(
|
|
284
|
+
expect(appendCalls[0]!.metadata?.notificationConversationMessageId).toBeUndefined();
|
|
285
|
+
expect(messageAppends).toEqual([]);
|
|
286
|
+
expect(messagesInvalidated).toEqual([]);
|
|
287
|
+
expect(conversationLookups).toEqual(["conv-source-1", "conv-source-1"]);
|
|
271
288
|
});
|
|
272
289
|
|
|
273
290
|
test("isAsyncBackground hint writes even when sourceContextId does not resolve", async () => {
|
|
@@ -402,6 +419,139 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
402
419
|
expect(appendCalls[0]!.conversationId).toBe("paired-delivery-conv-id");
|
|
403
420
|
});
|
|
404
421
|
|
|
422
|
+
describe("assistant-initiated thread deliveries", () => {
|
|
423
|
+
// Under the assistant-initiated-threads flag, conversation pairing
|
|
424
|
+
// promotes a background assistant.share into a standard conversation
|
|
425
|
+
// stamped `assistant_initiated`, which the sidebar's assistant section
|
|
426
|
+
// renders. That thread is the share's surface; a feed item for the
|
|
427
|
+
// same signal put it in the bell a second time.
|
|
428
|
+
const assistantThreadDelivery = () =>
|
|
429
|
+
makeVellumDelivery({ conversationId: "assistant-thread-1" });
|
|
430
|
+
|
|
431
|
+
function stampAssistantThread(): void {
|
|
432
|
+
conversationRowsById.set("assistant-thread-1", {
|
|
433
|
+
conversationType: "standard",
|
|
434
|
+
source: "assistant_initiated",
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function shareFromHeartbeat(): NotificationSignal {
|
|
439
|
+
return makeSignal({
|
|
440
|
+
sourceChannel: "assistant_tool",
|
|
441
|
+
sourceEventName: "assistant.share",
|
|
442
|
+
sourceContextId: "conv-source-1",
|
|
443
|
+
contextPayload: { title: "Doctor checklist built" },
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const shareDecision = () =>
|
|
448
|
+
makeDecision({
|
|
449
|
+
selectedChannels: ["vellum"],
|
|
450
|
+
renderedCopy: {
|
|
451
|
+
vellum: {
|
|
452
|
+
title: "Doctor checklist built",
|
|
453
|
+
body: "Your doctor-visit one-pager is ready.",
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
test("a background share promoted into an assistant thread does not mirror", async () => {
|
|
459
|
+
conversationRow = { conversationType: "background" };
|
|
460
|
+
stampAssistantThread();
|
|
461
|
+
|
|
462
|
+
const item = await writeHomeFeedItemForSignal(
|
|
463
|
+
shareFromHeartbeat(),
|
|
464
|
+
shareDecision(),
|
|
465
|
+
assistantThreadDelivery(),
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
expect(item).toBeNull();
|
|
469
|
+
expect(appendCalls).toHaveLength(0);
|
|
470
|
+
expect(messageAppends).toHaveLength(0);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
test("the async-background hint does not override the assistant-thread suppression", async () => {
|
|
474
|
+
conversationRow = null;
|
|
475
|
+
stampAssistantThread();
|
|
476
|
+
const signal = makeSignal({
|
|
477
|
+
sourceChannel: "assistant_tool",
|
|
478
|
+
sourceEventName: "assistant.share",
|
|
479
|
+
sourceContextId: "heartbeat",
|
|
480
|
+
contextPayload: { title: "Bilbao solo trip is planned" },
|
|
481
|
+
attentionHints: {
|
|
482
|
+
requiresAction: false,
|
|
483
|
+
urgency: "medium",
|
|
484
|
+
isAsyncBackground: true,
|
|
485
|
+
visibleInSourceNow: false,
|
|
486
|
+
},
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
const item = await writeHomeFeedItemForSignal(
|
|
490
|
+
signal,
|
|
491
|
+
shareDecision(),
|
|
492
|
+
assistantThreadDelivery(),
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
expect(item).toBeNull();
|
|
496
|
+
expect(appendCalls).toHaveLength(0);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
test("a share paired with an ordinary notification thread still mirrors", async () => {
|
|
500
|
+
// Flag off, or any producer that did not promote: pairing writes a
|
|
501
|
+
// `notification`-sourced row, which stays in Chats and the bell.
|
|
502
|
+
conversationRow = { conversationType: "background" };
|
|
503
|
+
conversationRowsById.set("assistant-thread-1", {
|
|
504
|
+
conversationType: "standard",
|
|
505
|
+
source: "notification",
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
const item = await writeHomeFeedItemForSignal(
|
|
509
|
+
shareFromHeartbeat(),
|
|
510
|
+
shareDecision(),
|
|
511
|
+
assistantThreadDelivery(),
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
expect(item).not.toBeNull();
|
|
515
|
+
expect(appendCalls).toHaveLength(1);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
test("a guardian request keeps its bell item even when paired with an assistant thread", async () => {
|
|
519
|
+
// The bell is the request's canonical home: nothing else renders the
|
|
520
|
+
// pending card, so the assistant-thread suppression must not reach it.
|
|
521
|
+
conversationRow = { conversationType: "standard" };
|
|
522
|
+
stampAssistantThread();
|
|
523
|
+
const signal = makeSignal({
|
|
524
|
+
sourceChannel: "assistant_tool",
|
|
525
|
+
sourceEventName: "guardian.question",
|
|
526
|
+
contextPayload: {
|
|
527
|
+
title: "Approve this?",
|
|
528
|
+
questionText: "May I send the email?",
|
|
529
|
+
requestId: "req-1",
|
|
530
|
+
},
|
|
531
|
+
attentionHints: {
|
|
532
|
+
requiresAction: true,
|
|
533
|
+
urgency: "medium",
|
|
534
|
+
isAsyncBackground: false,
|
|
535
|
+
visibleInSourceNow: false,
|
|
536
|
+
},
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
const item = await writeHomeFeedItemForSignal(
|
|
540
|
+
signal,
|
|
541
|
+
makeDecision({
|
|
542
|
+
selectedChannels: ["vellum"],
|
|
543
|
+
renderedCopy: {
|
|
544
|
+
vellum: { title: "Approve this?", body: "May I send the email?" },
|
|
545
|
+
},
|
|
546
|
+
}),
|
|
547
|
+
assistantThreadDelivery(),
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
expect(item).not.toBeNull();
|
|
551
|
+
expect(appendCalls).toHaveLength(1);
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
|
|
405
555
|
test("source conversation id wins over the paired delivery fallback when both are available", async () => {
|
|
406
556
|
// When the producer's `sourceContextId` already points at a real
|
|
407
557
|
// conversation (the canonical "where the work happened"), prefer it
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
getMessageById,
|
|
23
23
|
parseMessageMetadata,
|
|
24
24
|
} from "../persistence/conversation-crud.js";
|
|
25
|
+
import { isReplaceableTitle } from "../persistence/conversation-title-placeholders.js";
|
|
25
26
|
import {
|
|
26
27
|
isDesktopOriginatedUserMessage,
|
|
27
28
|
isReplyPushIneligibleUserMessage,
|
|
@@ -247,9 +248,13 @@ export async function emitAssistantReplyNotification(params: {
|
|
|
247
248
|
// lock screen. Absent `requestedTitle` lets the decision branch derive a
|
|
248
249
|
// title from the body, which reads better than an empty or placeholder
|
|
249
250
|
// conversation title.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
251
|
+
//
|
|
252
|
+
// Placeholder titles are plausible non-empty strings and survive
|
|
253
|
+
// sanitizing. They count as absent so the body supplies the title instead.
|
|
254
|
+
const storedTitle = conversation.title?.trim() ?? "";
|
|
255
|
+
const requestedTitle = isReplaceableTitle(storedTitle)
|
|
256
|
+
? ""
|
|
257
|
+
: sanitizeNotificationTitle(flattenTitleWhitespace(storedTitle));
|
|
253
258
|
|
|
254
259
|
// Read as close to the emit as possible: nothing short-circuits on it.
|
|
255
260
|
// Presence only speaks for a turn the desktop itself opened, on that row's
|
|
@@ -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();
|
|
@@ -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
|
|
@@ -2,6 +2,8 @@ import { X509Certificate } from "node:crypto";
|
|
|
2
2
|
import { chmod, mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
5
|
+
import { writeCombinedCABundle } from "../util/ca-bundle.js";
|
|
6
|
+
|
|
5
7
|
const CA_CERT_FILENAME = "ca.pem";
|
|
6
8
|
const CA_KEY_FILENAME = "ca-key.pem";
|
|
7
9
|
const COMBINED_CA_FILENAME = "combined-ca-bundle.pem";
|
|
@@ -263,11 +265,7 @@ export async function ensureCombinedCABundle(
|
|
|
263
265
|
}
|
|
264
266
|
|
|
265
267
|
try {
|
|
266
|
-
|
|
267
|
-
readFile(systemBundlePath, "utf-8"),
|
|
268
|
-
readFile(caCertPath, "utf-8"),
|
|
269
|
-
]);
|
|
270
|
-
await writeFile(combinedPath, systemCAs + "\n" + proxyCACert);
|
|
268
|
+
await writeCombinedCABundle(systemBundlePath, caCertPath, combinedPath);
|
|
271
269
|
return combinedPath;
|
|
272
270
|
} catch {
|
|
273
271
|
return null;
|
|
@@ -15,13 +15,16 @@ mock.module("../config/assistant-feature-flags.js", () => ({
|
|
|
15
15
|
}));
|
|
16
16
|
|
|
17
17
|
// `buildPolicyContext` (used by the integration tests below) precomputes the
|
|
18
|
-
//
|
|
19
|
-
//
|
|
18
|
+
// retrospective skill-improvement gate. Keep the v3 tier active while the
|
|
19
|
+
// dedicated gate changes so the production threading test covers the new
|
|
20
|
+
// opt-out rather than the old v3 condition.
|
|
20
21
|
let mockV3TierActive = true;
|
|
22
|
+
let mockSkillImprovementActive = true;
|
|
21
23
|
mock.module("../config/memory-v3-gate.js", () => ({
|
|
22
24
|
isMemoryEnabled: (config?: { memory?: { enabled?: boolean } }) =>
|
|
23
25
|
config?.memory?.enabled !== false,
|
|
24
26
|
isV3TierActive: () => mockV3TierActive,
|
|
27
|
+
isSkillImprovementActive: () => mockSkillImprovementActive,
|
|
25
28
|
isMemoryV3Live: () => mockV3TierActive,
|
|
26
29
|
usesConceptPageMemory: (memory?: {
|
|
27
30
|
enabled?: boolean;
|
|
@@ -219,6 +222,7 @@ describe("Permission Checker (gateway IPC)", () => {
|
|
|
219
222
|
mockCachedThreshold = "low";
|
|
220
223
|
mockRefreshedThreshold = null;
|
|
221
224
|
mockV3TierActive = true;
|
|
225
|
+
mockSkillImprovementActive = true;
|
|
222
226
|
thresholdCallLog.length = 0;
|
|
223
227
|
mockSkillCatalog = [];
|
|
224
228
|
mockResolvedSkill = null;
|
|
@@ -1173,10 +1177,11 @@ describe("Permission Checker (gateway IPC)", () => {
|
|
|
1173
1177
|
});
|
|
1174
1178
|
|
|
1175
1179
|
test("grant does NOT fire when proc-to-skills is inactive, even for the retrospective turn", async () => {
|
|
1176
|
-
//
|
|
1177
|
-
//
|
|
1178
|
-
//
|
|
1179
|
-
mockV3TierActive =
|
|
1180
|
+
// The retrospective keeps its live v3 tier, but this independent flag is
|
|
1181
|
+
// off. buildPolicyContext stamps `procToSkillsActive: false`, so the
|
|
1182
|
+
// grant is dead and the high-risk scaffold prompts.
|
|
1183
|
+
mockV3TierActive = true;
|
|
1184
|
+
mockSkillImprovementActive = false;
|
|
1180
1185
|
mockIpcClassifyRiskResult = {
|
|
1181
1186
|
risk: "high",
|
|
1182
1187
|
reason: "Skill scaffold",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
*/
|
|
27
27
|
import type { AgentEvent } from "../agent/loop.js";
|
|
28
28
|
import { getConfig } from "../config/loader.js";
|
|
29
|
+
import { resolvePlatformAssistantIdOrNull } from "../config/platform-identity.js";
|
|
29
30
|
import type { CompactionLogsClickHouseConfig } from "../config/schemas/compaction-logs.js";
|
|
30
31
|
import { credentialKey } from "../security/credential-key.js";
|
|
31
32
|
import { getSecureKeyAsync } from "../security/secure-keys.js";
|
|
@@ -136,7 +137,7 @@ export interface ClickHouseCompactionLogStoreDeps {
|
|
|
136
137
|
resolveUrl?: () => Promise<string | null>;
|
|
137
138
|
/** Override the credential read for `clickhouse:password`. */
|
|
138
139
|
resolvePassword?: () => Promise<string | null>;
|
|
139
|
-
/** Override the
|
|
140
|
+
/** Override the platform assistant id (in-memory identity, then vault). */
|
|
140
141
|
resolveAssistantId?: () => Promise<string | null>;
|
|
141
142
|
/** Override fetch for testing. */
|
|
142
143
|
fetchImpl?: CompactionLogFetch;
|
|
@@ -163,8 +164,7 @@ export class ClickHouseCompactionLogStore {
|
|
|
163
164
|
deps.resolvePassword ??
|
|
164
165
|
(() => readCredentialOrNull("clickhouse", "password"));
|
|
165
166
|
this.resolveAssistantId =
|
|
166
|
-
deps.resolveAssistantId ??
|
|
167
|
-
(() => readCredentialOrNull("vellum", "platform_assistant_id"));
|
|
167
|
+
deps.resolveAssistantId ?? resolvePlatformAssistantIdOrNull;
|
|
168
168
|
this.fetchImpl = deps.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
169
169
|
}
|
|
170
170
|
|