@vellumai/assistant 0.8.1 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +13 -19
- package/Dockerfile +75 -1
- package/bun.lock +11 -1
- package/docker-entrypoint.sh +17 -0
- package/docker-init-apt-root.sh +167 -0
- package/docker-kata-apt-env.sh +39 -0
- package/docs/plugins.md +88 -47
- package/docs/skills.md +9 -7
- package/examples/plugins/echo/README.md +27 -27
- package/examples/plugins/echo/package.json +3 -0
- package/examples/plugins/echo/register.ts +31 -31
- package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
- package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
- package/openapi.yaml +642 -5
- package/package.json +3 -1
- package/scripts/generate-openapi.ts +83 -10
- package/scripts/sync-llm-catalog.ts +2 -2
- package/scripts/sync-web-search-catalog.ts +47 -25
- package/src/__tests__/agent-image-optimize.test.ts +11 -3
- package/src/__tests__/agent-loop-exit-reason.test.ts +272 -0
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
- package/src/__tests__/anthropic-provider.test.ts +45 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
- package/src/__tests__/app-executors.test.ts +220 -4
- package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
- package/src/__tests__/bundled-asset.test.ts +6 -6
- package/src/__tests__/channel-availability-routes.test.ts +206 -0
- package/src/__tests__/channel-delivery-store.test.ts +289 -1
- package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
- package/src/__tests__/clawhub.test.ts +75 -16
- package/src/__tests__/compactor-tail-resolution.test.ts +147 -0
- package/src/__tests__/config-get-vision-flag.test.ts +136 -0
- package/src/__tests__/config-loader-backfill.test.ts +115 -18
- package/src/__tests__/config-schema.test.ts +21 -0
- package/src/__tests__/config-set-route.test.ts +80 -0
- package/src/__tests__/config-sounds-sync.test.ts +97 -0
- package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
- package/src/__tests__/context-search-conversations-source.test.ts +117 -2
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
- package/src/__tests__/context-search-workspace-source.test.ts +7 -0
- package/src/__tests__/context-token-estimator.test.ts +31 -65
- package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
- package/src/__tests__/conversation-agent-loop.test.ts +59 -1
- package/src/__tests__/conversation-error.test.ts +42 -3
- package/src/__tests__/conversation-fork-crud.test.ts +82 -0
- package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
- package/src/__tests__/conversation-lifecycle.test.ts +173 -0
- package/src/__tests__/conversation-media-retry.test.ts +19 -8
- package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
- package/src/__tests__/conversation-pairing.test.ts +54 -0
- package/src/__tests__/conversation-process-callsite.test.ts +4 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
- package/src/__tests__/conversation-queue.test.ts +4 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +102 -13
- package/src/__tests__/conversation-slash-queue.test.ts +59 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
- package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
- package/src/__tests__/conversation-sync-tags.test.ts +235 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
- package/src/__tests__/credential-security-invariants.test.ts +3 -2
- package/src/__tests__/date-context.test.ts +45 -0
- package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
- package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
- package/src/__tests__/disk-pressure-tools.test.ts +1 -0
- package/src/__tests__/dm-backfill.test.ts +121 -10
- package/src/__tests__/document-tool-security.test.ts +258 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- package/src/__tests__/edit-propagation.test.ts +33 -0
- package/src/__tests__/empty-response-pipeline.test.ts +0 -4
- package/src/__tests__/external-plugin-loader.test.ts +151 -55
- package/src/__tests__/filing-service.test.ts +140 -0
- package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
- package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
- package/src/__tests__/guardian-dispatch.test.ts +1 -0
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
- package/src/__tests__/heartbeat-service.test.ts +24 -164
- package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
- package/src/__tests__/helpers/tar-fixtures.ts +39 -0
- package/src/__tests__/helpers/wait-for.ts +21 -0
- package/src/__tests__/history-repair-pipeline.test.ts +0 -3
- package/src/__tests__/history-repair.test.ts +73 -0
- package/src/__tests__/host-app-control-proxy.test.ts +507 -10
- package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
- package/src/__tests__/image-credentials.test.ts +1 -1
- package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
- package/src/__tests__/inference-profile-reaper.test.ts +4 -2
- package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
- package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
- package/src/__tests__/injector-background-turn.test.ts +153 -0
- package/src/__tests__/injector-chain.test.ts +15 -8
- package/src/__tests__/install-skill-routing.test.ts +155 -37
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +99 -3
- package/src/__tests__/list-messages-page-latest.test.ts +55 -0
- package/src/__tests__/llm-call-pipeline.test.ts +0 -3
- package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
- package/src/__tests__/llm-catalog-parity.test.ts +58 -13
- package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
- package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +36 -0
- package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
- package/src/__tests__/llm-resolver.test.ts +255 -2
- package/src/__tests__/llm-usage-store.test.ts +114 -0
- package/src/__tests__/managed-profile-guard.test.ts +41 -29
- package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
- package/src/__tests__/managed-store.test.ts +84 -192
- package/src/__tests__/media-generate-image.test.ts +1 -1
- package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
- package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
- package/src/__tests__/notification-decision-fallback.test.ts +0 -91
- package/src/__tests__/notification-decision-strategy.test.ts +14 -31
- package/src/__tests__/notification-deep-link.test.ts +15 -0
- package/src/__tests__/notification-guardian-path.test.ts +1 -2
- package/src/__tests__/notification-platform-adapter.test.ts +5 -4
- package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
- package/src/__tests__/oauth-commands-routes.test.ts +168 -16
- package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
- package/src/__tests__/openai-provider.test.ts +242 -3
- package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
- package/src/__tests__/openrouter-provider-only.test.ts +51 -3
- package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
- package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
- package/src/__tests__/persistence-pipeline.test.ts +0 -2
- package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +7 -2
- package/src/__tests__/platform.test.ts +2 -0
- package/src/__tests__/plugin-api-shim.test.ts +125 -0
- package/src/__tests__/plugin-bootstrap.test.ts +10 -36
- package/src/__tests__/plugin-external-api.test.ts +68 -0
- package/src/__tests__/plugin-registry.test.ts +0 -77
- package/src/__tests__/plugin-route-contribution.test.ts +0 -1
- package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
- package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
- package/src/__tests__/plugin-types.test.ts +3 -13
- package/src/__tests__/process-message-background-slack.test.ts +8 -1
- package/src/__tests__/process-message-display-content.test.ts +421 -0
- package/src/__tests__/provider-catalog-visibility.test.ts +158 -0
- package/src/__tests__/provider-error-scenarios.test.ts +111 -0
- package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +33 -31
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
- package/src/__tests__/schedule-routes.test.ts +50 -3
- package/src/__tests__/schedule-store.test.ts +94 -0
- package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
- package/src/__tests__/schema-transforms.test.ts +20 -0
- package/src/__tests__/search-skills-unified.test.ts +0 -5
- package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +1 -1
- package/src/__tests__/server-history-render.test.ts +43 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
- package/src/__tests__/skill-load-tool.test.ts +27 -89
- package/src/__tests__/skill-memory.test.ts +23 -3
- package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
- package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
- package/src/__tests__/skills-install-extract.test.ts +49 -38
- package/src/__tests__/skills-install-staging.test.ts +159 -0
- package/src/__tests__/skills-uninstall.test.ts +9 -41
- package/src/__tests__/skills.test.ts +51 -58
- package/src/__tests__/slack-channel-config.test.ts +9 -0
- package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
- package/src/__tests__/system-prompt.test.ts +670 -63
- package/src/__tests__/terminal-tools.test.ts +28 -1
- package/src/__tests__/thread-backfill.test.ts +557 -27
- package/src/__tests__/title-generate-pipeline.test.ts +0 -13
- package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
- package/src/__tests__/tool-error-pipeline.test.ts +0 -3
- package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
- package/src/__tests__/tool-executor.test.ts +16 -4
- package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
- package/src/__tests__/turn-events-store.test.ts +256 -0
- package/src/__tests__/twilio-routes.test.ts +4 -0
- package/src/__tests__/user-plugin-loader.test.ts +0 -7
- package/src/__tests__/voice-session-bridge.test.ts +198 -0
- package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
- package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
- package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
- package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
- package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
- package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
- package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
- package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
- package/src/a2a/__tests__/agent-card.test.ts +98 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
- package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
- package/src/a2a/__tests__/task-store.test.ts +246 -0
- package/src/a2a/agent-card.ts +58 -0
- package/src/a2a/feature-gate.ts +8 -0
- package/src/a2a/protocol-constants.ts +21 -0
- package/src/a2a/protocol-errors.ts +50 -0
- package/src/a2a/protocol-types.ts +162 -0
- package/src/a2a/task-store.ts +168 -0
- package/src/acp/resolve-agent.ts +1 -1
- package/src/agent/image-optimize.ts +13 -5
- package/src/agent/loop.ts +167 -18
- package/src/calls/voice-session-bridge.ts +61 -42
- package/src/channels/config.ts +9 -0
- package/src/channels/types.ts +122 -0
- package/src/cli/__tests__/unknown-command.test.ts +24 -0
- package/src/cli/commands/__tests__/changelog.test.ts +304 -319
- package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
- package/src/cli/commands/__tests__/schedules.test.ts +960 -0
- package/src/cli/commands/changelog.ts +106 -42
- package/src/cli/commands/conversations.ts +102 -17
- package/src/cli/commands/default-action.ts +10 -53
- package/src/cli/commands/notifications.ts +388 -346
- package/src/cli/commands/plugins.ts +252 -0
- package/src/cli/commands/schedules.ts +683 -0
- package/src/cli/commands/telemetry.ts +40 -0
- package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
- package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
- package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
- package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
- package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
- package/src/cli/lib/cli-colors.ts +12 -0
- package/src/cli/lib/confirm-prompt.ts +79 -0
- package/src/cli/lib/install-from-github.ts +303 -0
- package/src/cli/lib/list-installed-plugins.ts +137 -0
- package/src/cli/lib/search-plugins.ts +163 -0
- package/src/cli/lib/uninstall-plugin.ts +82 -0
- package/src/cli/lib/unknown-command.ts +111 -0
- package/src/cli/program.ts +52 -2
- package/src/config/assistant-feature-flags.ts +24 -54
- package/src/config/bundled-skills/app-builder/SKILL.md +140 -22
- package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
- package/src/config/bundled-skills/document/SKILL.md +23 -3
- package/src/config/bundled-skills/document/TOOLS.json +53 -0
- package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
- package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
- package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
- package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
- package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
- package/src/config/bundled-tool-registry.ts +6 -0
- package/src/config/call-site-defaults.ts +105 -0
- package/src/config/feature-flag-registry.json +41 -9
- package/src/config/llm-resolver.ts +52 -1
- package/src/config/loader.ts +64 -38
- package/src/config/schema.ts +9 -10
- package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +3 -3
- package/src/config/schemas/channels.ts +17 -0
- package/src/config/schemas/compaction.ts +28 -0
- package/src/config/schemas/conversations.ts +10 -0
- package/src/config/schemas/heartbeat.ts +23 -0
- package/src/config/schemas/llm-request-logs.ts +31 -7
- package/src/config/schemas/llm.ts +1 -0
- package/src/config/schemas/memory-retrieval.ts +18 -0
- package/src/config/schemas/memory-retrospective.ts +1 -1
- package/src/config/schemas/memory-v2.ts +4 -4
- package/src/config/schemas/memory.ts +3 -1
- package/src/config/schemas/tools.ts +14 -0
- package/src/config/seed-inference-profiles.ts +99 -29
- package/src/config/skills.ts +3 -96
- package/src/context/compactor.ts +1107 -0
- package/src/context/token-estimator.ts +34 -36
- package/src/context/window-manager.ts +197 -1520
- package/src/credential-execution/managed-catalog.ts +37 -0
- package/src/credential-health/credential-health-service.ts +280 -19
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +33 -18
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
- package/src/daemon/approval-generators.ts +8 -6
- package/src/daemon/config-watcher.ts +94 -31
- package/src/daemon/conversation-agent-loop-handlers.ts +78 -0
- package/src/daemon/conversation-agent-loop.ts +198 -11
- package/src/daemon/conversation-error.ts +171 -37
- package/src/daemon/conversation-lifecycle.ts +53 -40
- package/src/daemon/conversation-messaging.ts +25 -6
- package/src/daemon/conversation-process.ts +49 -12
- package/src/daemon/conversation-runtime-assembly.ts +25 -1
- package/src/daemon/conversation-slash.ts +12 -5
- package/src/daemon/conversation-store.ts +11 -4
- package/src/daemon/conversation-tool-setup.ts +39 -7
- package/src/daemon/conversation.ts +33 -8
- package/src/daemon/date-context.ts +40 -0
- package/src/daemon/external-plugins-bootstrap.ts +217 -181
- package/src/daemon/first-greeting.ts +22 -2
- package/src/daemon/guardian-action-generators.ts +1 -125
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
- package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
- package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
- package/src/daemon/handlers/config-a2a.ts +289 -0
- package/src/daemon/handlers/config-model.ts +6 -5
- package/src/daemon/handlers/config-slack-channel.ts +15 -3
- package/src/daemon/handlers/conversations.ts +1 -0
- package/src/daemon/handlers/shared.ts +14 -5
- package/src/daemon/handlers/skills.ts +111 -108
- package/src/daemon/history-repair.ts +28 -1
- package/src/daemon/host-app-control-proxy.ts +153 -27
- package/src/daemon/host-proxy-preactivation.ts +85 -18
- package/src/daemon/lifecycle.ts +89 -91
- package/src/daemon/meet-host-supervisor.ts +5 -4
- package/src/daemon/memory-v2-startup.ts +85 -0
- package/src/daemon/message-protocol.ts +1 -0
- package/src/daemon/message-types/conversations.ts +25 -0
- package/src/daemon/message-types/messages.ts +61 -0
- package/src/daemon/message-types/notifications.ts +21 -0
- package/src/daemon/message-types/subagents.ts +1 -0
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/pkb-reminder-builder.test.ts +11 -54
- package/src/daemon/pkb-reminder-builder.ts +5 -20
- package/src/daemon/plugin-source-watcher.ts +146 -0
- package/src/daemon/process-message.ts +24 -3
- package/src/daemon/server.ts +11 -2
- package/src/daemon/skill-memory-refresh.ts +33 -0
- package/src/daemon/wake-target-adapter.ts +2 -0
- package/src/documents/document-store.ts +221 -3
- package/src/embedded/plugin-api.ts +40 -0
- package/src/export/__tests__/transcript-formatter.test.ts +121 -0
- package/src/export/transcript-formatter.ts +54 -20
- package/src/filing/filing-service.ts +39 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +135 -6
- package/src/heartbeat/heartbeat-run-store.ts +2 -1
- package/src/heartbeat/heartbeat-service.ts +73 -189
- package/src/home/__tests__/feed-types.test.ts +80 -0
- package/src/home/feed-types.ts +36 -2
- package/src/home/post-connect-feed.ts +1 -0
- package/src/index.ts +18 -1
- package/src/ipc/cli-client.ts +147 -45
- package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
- package/src/mcp/client.ts +20 -4
- package/src/media/image-credentials.ts +3 -3
- package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
- package/src/memory/__tests__/conversation-queries.test.ts +483 -0
- package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
- package/src/memory/__tests__/memory-retrospective-job.test.ts +87 -4
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
- package/src/memory/__tests__/message-content.test.ts +35 -0
- package/src/memory/bookmark-crud.ts +42 -10
- package/src/memory/context-search/sources/conversations.ts +62 -2
- package/src/memory/context-search/sources/workspace.ts +4 -0
- package/src/memory/conversation-crud.ts +63 -19
- package/src/memory/conversation-queries.ts +197 -11
- package/src/memory/conversation-title-service.ts +26 -4
- package/src/memory/db-init.ts +12 -0
- package/src/memory/delivery-crud.ts +152 -5
- package/src/memory/embedding-backend.ts +4 -4
- package/src/memory/external-conversation-store.ts +66 -5
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +150 -12
- package/src/memory/graph/conversation-graph-memory.ts +49 -21
- package/src/memory/graph/tools.ts +9 -40
- package/src/memory/indexer.ts +34 -29
- package/src/memory/invite-store.ts +53 -0
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
- package/src/memory/jobs/embed-concept-page.ts +20 -11
- package/src/memory/jobs-worker.ts +6 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +24 -12
- package/src/memory/llm-request-log-source.ts +19 -52
- package/src/memory/llm-request-log-store.ts +92 -1
- package/src/memory/llm-usage-store.ts +125 -5
- package/src/memory/memory-retrospective-enqueue.ts +1 -20
- package/src/memory/memory-retrospective-job.ts +33 -6
- package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
- package/src/memory/message-content.ts +1 -1
- package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
- package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
- package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
- package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
- package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
- package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
- package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
- package/src/memory/migrations/251-a2a-tasks.ts +49 -0
- package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
- package/src/memory/migrations/index.ts +9 -0
- package/src/memory/migrations/registry.ts +16 -0
- package/src/memory/onboarding-events-store.ts +106 -0
- package/src/memory/schema/a2a.ts +15 -0
- package/src/memory/schema/bookmarks.ts +0 -2
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/inference.ts +3 -3
- package/src/memory/schema/infrastructure.ts +13 -0
- package/src/memory/turn-events-store.ts +127 -2
- package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
- package/src/memory/v2/__tests__/activation.test.ts +0 -8
- package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
- package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
- package/src/memory/v2/__tests__/injection.test.ts +288 -11
- package/src/memory/v2/__tests__/migration.test.ts +87 -0
- package/src/memory/v2/__tests__/page-index.test.ts +83 -0
- package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
- package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
- package/src/memory/v2/__tests__/router.test.ts +15 -0
- package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
- package/src/memory/v2/__tests__/static-context.test.ts +12 -1
- package/src/memory/v2/activation-store.ts +14 -16
- package/src/memory/v2/cli-command-content.ts +19 -0
- package/src/memory/v2/cli-command-store.ts +304 -0
- package/src/memory/v2/frontmatter-sweep.ts +7 -1
- package/src/memory/v2/injection.ts +81 -26
- package/src/memory/v2/migration.ts +49 -19
- package/src/memory/v2/page-index.ts +63 -8
- package/src/memory/v2/prompts/router.ts +11 -8
- package/src/memory/v2/prompts/sweep.ts +2 -2
- package/src/memory/v2/qdrant.ts +135 -7
- package/src/memory/v2/router.ts +9 -8
- package/src/memory/v2/skill-store.ts +120 -35
- package/src/memory/v2/static-context.ts +4 -4
- package/src/memory/v2/types.ts +23 -0
- package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
- package/src/messaging/providers/a2a/deliver.ts +156 -0
- package/src/messaging/providers/gmail/client.ts +9 -2
- package/src/messaging/providers/index.ts +11 -2
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
- package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
- package/src/messaging/providers/slack/adapter.ts +43 -5
- package/src/messaging/providers/slack/client.ts +27 -0
- package/src/messaging/providers/slack/deep-link.ts +65 -0
- package/src/messaging/providers/slack/download.ts +104 -0
- package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
- package/src/messaging/providers/slack/message-metadata.ts +27 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
- package/src/messaging/providers/slack/render-transcript.ts +69 -5
- package/src/messaging/providers/slack/types.ts +20 -1
- package/src/notifications/__tests__/broadcaster.test.ts +203 -0
- package/src/notifications/__tests__/decision-engine.test.ts +283 -0
- package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +430 -7
- package/src/notifications/adapters/macos.ts +12 -2
- package/src/notifications/broadcaster.ts +29 -4
- package/src/notifications/conversation-pairing.ts +2 -1
- package/src/notifications/copy-composer.ts +17 -64
- package/src/notifications/decision-engine.ts +113 -45
- package/src/notifications/deterministic-checks.ts +96 -0
- package/src/notifications/emit-signal.ts +21 -1
- package/src/notifications/home-feed-side-effect.ts +138 -5
- package/src/notifications/signal.ts +3 -5
- package/src/notifications/types.ts +8 -0
- package/src/oauth/connection-resolver.ts +8 -4
- package/src/oauth/platform-connection.test.ts +43 -3
- package/src/oauth/platform-connection.ts +19 -6
- package/src/oauth/seed-providers.ts +10 -1
- package/src/permissions/checker.ts +2 -0
- package/src/permissions/ipc-risk-types.ts +1 -0
- package/src/permissions/question-prompter.test.ts +416 -0
- package/src/permissions/question-prompter.ts +294 -0
- package/src/platform/client.test.ts +1 -1
- package/src/platform/client.ts +1 -1
- package/src/plugin-api/constants.ts +26 -0
- package/src/plugin-api/index.ts +34 -1
- package/src/plugin-api/types.ts +104 -22
- package/src/plugins/defaults/circuit-breaker.ts +0 -5
- package/src/plugins/defaults/compaction.ts +0 -4
- package/src/plugins/defaults/empty-response.ts +0 -2
- package/src/plugins/defaults/history-repair.ts +0 -2
- package/src/plugins/defaults/injectors.ts +74 -22
- package/src/plugins/defaults/llm-call.ts +0 -2
- package/src/plugins/defaults/memory-retrieval.ts +0 -1
- package/src/plugins/defaults/overflow-reduce.ts +0 -1
- package/src/plugins/defaults/persistence.ts +0 -2
- package/src/plugins/defaults/title-generate.ts +0 -5
- package/src/plugins/defaults/token-estimate.ts +0 -2
- package/src/plugins/defaults/tool-error.ts +0 -7
- package/src/plugins/defaults/tool-execute.ts +0 -2
- package/src/plugins/defaults/tool-result-truncate.ts +0 -4
- package/src/plugins/ensure-plugin-api-shim.ts +96 -0
- package/src/plugins/external-api.ts +104 -0
- package/src/plugins/external-plugin-loader.ts +187 -42
- package/src/plugins/feature-gate.ts +22 -0
- package/src/plugins/pipeline.ts +37 -0
- package/src/plugins/registry.ts +48 -80
- package/src/plugins/types.ts +40 -26
- package/src/plugins/user-loader.ts +21 -2
- package/src/proactive-artifact/aux-message-injector.ts +11 -0
- package/src/proactive-artifact/job.test.ts +37 -5
- package/src/prompts/__tests__/system-prompt.test.ts +10 -43
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +95 -0
- package/src/prompts/normalize-onboarding.ts +27 -0
- package/src/prompts/sections.ts +302 -0
- package/src/prompts/system-prompt.ts +63 -174
- package/src/prompts/templates/BOOTSTRAP.md +17 -1
- package/src/prompts/templates/system-sections.ts +164 -0
- package/src/providers/__tests__/inference.test.ts +24 -7
- package/src/providers/anthropic/client.ts +28 -28
- package/src/providers/call-site-routing.ts +24 -6
- package/src/providers/connection-resolution.ts +68 -11
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
- package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
- package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
- package/src/providers/inference/adapter-factory.ts +32 -6
- package/src/providers/inference/auth.ts +12 -0
- package/src/providers/inference/backfill.ts +14 -1
- package/src/providers/inference/connections.ts +159 -34
- package/src/providers/inference/resolve-auth.ts +14 -4
- package/src/providers/model-catalog.ts +249 -12
- package/src/providers/model-intents.ts +3 -3
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
- package/src/providers/openai/chat-completions-provider.ts +169 -8
- package/src/providers/openrouter/client.ts +49 -4
- package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -2
- package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
- package/src/providers/provider-availability.ts +17 -2
- package/src/providers/provider-catalog-visibility.ts +38 -0
- package/src/providers/provider-send-message.ts +27 -12
- package/src/providers/registry.ts +52 -15
- package/src/providers/retry.ts +47 -1
- package/src/runtime/__tests__/agent-wake.test.ts +152 -0
- package/src/runtime/agent-wake.ts +103 -15
- package/src/runtime/auth/route-policy.ts +21 -1
- package/src/runtime/btw-sidechain.ts +2 -0
- package/src/runtime/http-server.ts +7 -16
- package/src/runtime/http-types.ts +19 -47
- package/src/runtime/migrations/origin-mode.ts +1 -1
- package/src/runtime/pending-interactions.ts +1 -0
- package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +172 -23
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
- package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
- package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
- package/src/runtime/routes/acp-routes-list.test.ts +143 -0
- package/src/runtime/routes/acp-routes.ts +5 -3
- package/src/runtime/routes/auth-routes.ts +1 -1
- package/src/runtime/routes/bookmark-routes.ts +5 -3
- package/src/runtime/routes/btw-routes.ts +5 -1
- package/src/runtime/routes/channel-availability-routes.ts +126 -0
- package/src/runtime/routes/consolidation-routes.ts +100 -0
- package/src/runtime/routes/conversation-cli-routes.ts +44 -3
- package/src/runtime/routes/conversation-list-routes.ts +3 -20
- package/src/runtime/routes/conversation-management-routes.ts +17 -42
- package/src/runtime/routes/conversation-query-routes.ts +99 -35
- package/src/runtime/routes/conversation-routes.ts +97 -11
- package/src/runtime/routes/documents-routes.ts +25 -86
- package/src/runtime/routes/group-routes.ts +5 -0
- package/src/runtime/routes/inbound-conversation.ts +28 -8
- package/src/runtime/routes/inbound-message-handler.ts +236 -41
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
- package/src/runtime/routes/index.ts +8 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
- package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
- package/src/runtime/routes/inference-provider-connection-routes.ts +199 -22
- package/src/runtime/routes/integrations/a2a.ts +235 -0
- package/src/runtime/routes/integrations/slack/share.ts +4 -52
- package/src/runtime/routes/integrations/slack/token.ts +43 -0
- package/src/runtime/routes/integrations/twilio.ts +6 -13
- package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
- package/src/runtime/routes/notification-routes.ts +1 -1
- package/src/runtime/routes/oauth-commands-routes.ts +105 -15
- package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
- package/src/runtime/routes/question-routes.ts +259 -0
- package/src/runtime/routes/rename-conversation-routes.ts +2 -33
- package/src/runtime/routes/schedule-routes.ts +4 -7
- package/src/runtime/routes/subagents-routes.ts +98 -18
- package/src/runtime/routes/telemetry-routes.ts +27 -0
- package/src/runtime/routes/tts-routes.ts +27 -2
- package/src/runtime/routes/workspace-routes.test.ts +43 -0
- package/src/runtime/routes/workspace-routes.ts +28 -0
- package/src/runtime/services/conversation-serializer.ts +39 -7
- package/src/runtime/sync/resource-sync-events.ts +93 -1
- package/src/schedule/schedule-store.ts +27 -2
- package/src/schedule/scheduler.ts +9 -1
- package/src/security/__tests__/untrusted-content.test.ts +86 -0
- package/src/security/untrusted-content.ts +93 -8
- package/src/skills/catalog-files.ts +1 -1
- package/src/skills/catalog-install.ts +233 -116
- package/src/skills/clawhub.ts +70 -13
- package/src/skills/managed-store.ts +4 -119
- package/src/skills/skillssh-registry.ts +27 -48
- package/src/subagent/manager.ts +17 -7
- package/src/telemetry/types.ts +113 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
- package/src/telemetry/usage-telemetry-reporter.ts +113 -7
- package/src/tools/apps/executors.ts +58 -7
- package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
- package/src/tools/ask-question/ask-question-tool.ts +304 -0
- package/src/tools/browser/browser-execution.ts +15 -11
- package/src/tools/computer-use/definitions.ts +3 -3
- package/src/tools/credentials/vault.ts +1 -1
- package/src/tools/document/document-tool.ts +124 -1
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +1 -1
- package/src/tools/filesystem/write.ts +5 -2
- package/src/tools/host-filesystem/transfer.ts +1 -1
- package/src/tools/host-terminal/host-shell.ts +1 -1
- package/src/tools/memory/register.ts +1 -9
- package/src/tools/permission-checker.ts +1 -1
- package/src/tools/registry.ts +17 -7
- package/src/tools/schedule/create.ts +2 -2
- package/src/tools/schema-transforms.ts +7 -2
- package/src/tools/side-effects.ts +1 -0
- package/src/tools/skills/delete-managed.ts +4 -4
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/skills/scaffold-managed.ts +3 -2
- package/src/tools/subagent/notify-parent.ts +1 -1
- package/src/tools/system/request-permission.ts +2 -2
- package/src/tools/terminal/safe-env.ts +60 -1
- package/src/tools/tool-manifest.ts +2 -0
- package/src/tools/types.ts +107 -21
- package/src/tools/ui-surface/definitions.ts +6 -5
- package/src/tts/__tests__/provider-adapters.test.ts +76 -2
- package/src/tts/providers/elevenlabs-provider.ts +75 -1
- package/src/types/onboarding-context.ts +2 -0
- package/src/util/errors.ts +17 -0
- package/src/util/platform.ts +10 -0
- package/src/watcher/__tests__/engine.test.ts +22 -0
- package/src/watcher/engine.ts +6 -2
- package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
- package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
- package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
- package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
- package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
- package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
- package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
- package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
- package/src/workspace/migrations/registry.ts +10 -0
- package/src/workspace/migrations/runner.ts +39 -9
- package/src/workspace/migrations/types.ts +4 -0
- package/examples/plugins/echo/bun.lock +0 -25
- package/src/__tests__/context-window-manager.test.ts +0 -2481
- package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
- package/src/context/__tests__/compact-prompt.test.ts +0 -63
- package/src/context/prompts/compact.md +0 -26
- package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
- package/src/runtime/guardian-action-conversation-turn.ts +0 -99
|
@@ -32,51 +32,6 @@ export function nonEmpty(value: string | undefined): string | undefined {
|
|
|
32
32
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
export function looksLikeIntermediaryInstruction(text: string): boolean {
|
|
36
|
-
const normalized = text.replace(/\s+/g, " ").trim();
|
|
37
|
-
const intermediaryAction =
|
|
38
|
-
"(?:tell|telling|ask|asking|remind|reminding|nudge|nudging|prompt|prompting|notify|notifying|encourage|encouraging|prime|priming|brief|briefing|coach|coaching)";
|
|
39
|
-
const target = "(?:the\\s+)?(?:guardian|recipient|user)";
|
|
40
|
-
return (
|
|
41
|
-
/\b(?:assistant|agent|system|model|watcher)\s+(?:should|needs?\s+to|must|can|could)\b/i.test(
|
|
42
|
-
normalized,
|
|
43
|
-
) ||
|
|
44
|
-
new RegExp(
|
|
45
|
-
`\\b(?:consider|try|please)\\s+${intermediaryAction}\\s+${target}\\b`,
|
|
46
|
-
"i",
|
|
47
|
-
).test(normalized) ||
|
|
48
|
-
new RegExp(
|
|
49
|
-
`\\b${intermediaryAction}\\s+${target}\\s+(?:to|that|about|with)\\b`,
|
|
50
|
-
"i",
|
|
51
|
-
).test(normalized) ||
|
|
52
|
-
new RegExp(
|
|
53
|
-
`\\b${target}\\s+(?:should|needs?\\s+to|must|might\\s+want\\s+to)\\b`,
|
|
54
|
-
"i",
|
|
55
|
-
).test(normalized) ||
|
|
56
|
-
new RegExp(`\\b(?:for|to)\\s+${target}\\s+to\\b`, "i").test(normalized)
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function buildHeartbeatAlertCopy(
|
|
61
|
-
payload: Record<string, unknown>,
|
|
62
|
-
): RenderedChannelCopy {
|
|
63
|
-
const summary = str(
|
|
64
|
-
payload.summary,
|
|
65
|
-
str(payload.body, "Your assistant found something worth your attention."),
|
|
66
|
-
).trim();
|
|
67
|
-
const safePopupBody = looksLikeIntermediaryInstruction(summary)
|
|
68
|
-
? "I found something worth your attention in a heartbeat check. Open the conversation for details."
|
|
69
|
-
: summary;
|
|
70
|
-
|
|
71
|
-
return {
|
|
72
|
-
title: str(payload.title, "Heartbeat Alert"),
|
|
73
|
-
body: safePopupBody,
|
|
74
|
-
deliveryText: safePopupBody,
|
|
75
|
-
conversationTitle: str(payload.conversationTitle, "Heartbeat"),
|
|
76
|
-
conversationSeedMessage: summary,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
35
|
// ── Access-request copy contract ─────────────────────────────────────────────
|
|
81
36
|
//
|
|
82
37
|
// Deterministic helpers for building guardian-facing access-request copy.
|
|
@@ -550,8 +505,6 @@ const TEMPLATES: Partial<Record<NotificationSourceEventName, CopyTemplate>> = {
|
|
|
550
505
|
body: str(payload.body, "A watcher event requires your attention"),
|
|
551
506
|
}),
|
|
552
507
|
|
|
553
|
-
"heartbeat.alert": buildHeartbeatAlertCopy,
|
|
554
|
-
|
|
555
508
|
"tool_confirmation.required_action": (payload) => ({
|
|
556
509
|
title: "Tool Confirmation",
|
|
557
510
|
body: str(payload.toolName, "A tool") + " requires your confirmation",
|
|
@@ -605,11 +558,11 @@ export function composeFallbackCopy(
|
|
|
605
558
|
|
|
606
559
|
const baseCopy: RenderedChannelCopy = template
|
|
607
560
|
? template(signal.contextPayload)
|
|
608
|
-
: buildGenericCopy(
|
|
561
|
+
: buildGenericCopy();
|
|
609
562
|
|
|
610
563
|
const result: Partial<Record<NotificationChannel, RenderedChannelCopy>> = {};
|
|
611
564
|
for (const ch of channels) {
|
|
612
|
-
result[ch] = applyChannelDefaults(ch, baseCopy
|
|
565
|
+
result[ch] = applyChannelDefaults(ch, baseCopy);
|
|
613
566
|
}
|
|
614
567
|
return result;
|
|
615
568
|
}
|
|
@@ -617,12 +570,11 @@ export function composeFallbackCopy(
|
|
|
617
570
|
function applyChannelDefaults(
|
|
618
571
|
channel: NotificationChannel,
|
|
619
572
|
baseCopy: RenderedChannelCopy,
|
|
620
|
-
signal: NotificationSignal,
|
|
621
573
|
): RenderedChannelCopy {
|
|
622
574
|
const copy: RenderedChannelCopy = { ...baseCopy };
|
|
623
575
|
|
|
624
576
|
if (channel === "telegram") {
|
|
625
|
-
copy.deliveryText = buildChatSurfaceFallbackDeliveryText(baseCopy
|
|
577
|
+
copy.deliveryText = buildChatSurfaceFallbackDeliveryText(baseCopy);
|
|
626
578
|
}
|
|
627
579
|
|
|
628
580
|
return copy;
|
|
@@ -630,7 +582,6 @@ function applyChannelDefaults(
|
|
|
630
582
|
|
|
631
583
|
function buildChatSurfaceFallbackDeliveryText(
|
|
632
584
|
baseCopy: RenderedChannelCopy,
|
|
633
|
-
signal: NotificationSignal,
|
|
634
585
|
): string {
|
|
635
586
|
const explicit = nonEmpty(baseCopy.deliveryText);
|
|
636
587
|
if (explicit) return explicit;
|
|
@@ -641,23 +592,25 @@ function buildChatSurfaceFallbackDeliveryText(
|
|
|
641
592
|
const title = nonEmpty(baseCopy.title);
|
|
642
593
|
if (title) return title;
|
|
643
594
|
|
|
644
|
-
return
|
|
595
|
+
// No usable text: return empty string. The broadcaster's empty-body skip in
|
|
596
|
+
// `broadcaster.ts` suppresses fallback-derived empty bodies; the
|
|
597
|
+
// deterministic `checkRenderedCopyQuality` (see deterministic-checks.ts)
|
|
598
|
+
// covers the same case when the empty body originates in
|
|
599
|
+
// `decision.renderedCopy`.
|
|
600
|
+
return "";
|
|
645
601
|
}
|
|
646
602
|
|
|
647
603
|
/**
|
|
648
|
-
* Build generic copy when no template matches.
|
|
649
|
-
*
|
|
604
|
+
* Build generic copy when no template matches. Returns an empty body so the
|
|
605
|
+
* notification is suppressed rather than rendering an event-name placeholder.
|
|
606
|
+
* The broadcaster's empty-body skip in `broadcaster.ts` catches fallback-derived
|
|
607
|
+
* empty bodies; the deterministic `checkRenderedCopyQuality` (see
|
|
608
|
+
* deterministic-checks.ts) covers the same case when the empty body originates
|
|
609
|
+
* in `decision.renderedCopy`.
|
|
650
610
|
*/
|
|
651
|
-
function buildGenericCopy(
|
|
652
|
-
const humanName = signal.sourceEventName.replace(/[._]/g, " ");
|
|
653
|
-
const urgencyPrefix =
|
|
654
|
-
signal.attentionHints.urgency === "high" ? "Urgent: " : "";
|
|
655
|
-
const actionSuffix = signal.attentionHints.requiresAction
|
|
656
|
-
? " — action required"
|
|
657
|
-
: "";
|
|
658
|
-
|
|
611
|
+
function buildGenericCopy(): RenderedChannelCopy {
|
|
659
612
|
return {
|
|
660
613
|
title: "Notification",
|
|
661
|
-
body:
|
|
614
|
+
body: "",
|
|
662
615
|
};
|
|
663
616
|
}
|
|
@@ -35,7 +35,6 @@ import {
|
|
|
35
35
|
composeFallbackCopy,
|
|
36
36
|
hasAccessRequestInstructions,
|
|
37
37
|
hasInviteFlowDirective,
|
|
38
|
-
looksLikeIntermediaryInstruction,
|
|
39
38
|
} from "./copy-composer.js";
|
|
40
39
|
import { createDecision } from "./decisions-store.js";
|
|
41
40
|
import {
|
|
@@ -58,6 +57,21 @@ const log = getLogger("notification-decision-engine");
|
|
|
58
57
|
const DECISION_TIMEOUT_MS = 15_000;
|
|
59
58
|
const PROMPT_VERSION = "v4";
|
|
60
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Derive a short notification title from a message body. Used when an
|
|
62
|
+
* assistant_tool-sourced signal supplies `requestedMessage` without an
|
|
63
|
+
* explicit `requestedTitle`: trims to the first sentence terminator when
|
|
64
|
+
* present, then caps the result at 60 characters with an ellipsis.
|
|
65
|
+
*/
|
|
66
|
+
function deriveTitle(body: string): string {
|
|
67
|
+
const firstSentenceEnd = body.search(/[.!?](\s|$)/);
|
|
68
|
+
const candidate =
|
|
69
|
+
firstSentenceEnd > 0 ? body.slice(0, firstSentenceEnd + 1) : body;
|
|
70
|
+
return candidate.length > 60
|
|
71
|
+
? candidate.slice(0, 60).trim() + "…"
|
|
72
|
+
: candidate.trim();
|
|
73
|
+
}
|
|
74
|
+
|
|
61
75
|
/**
|
|
62
76
|
* Maximum character budget for identity context injected into the notification
|
|
63
77
|
* decision prompt. We truncate to prevent oversized prompts when SOUL.md /
|
|
@@ -326,8 +340,9 @@ function buildFallbackDecision(
|
|
|
326
340
|
signal: NotificationSignal,
|
|
327
341
|
availableChannels: NotificationChannel[],
|
|
328
342
|
): NotificationDecision {
|
|
343
|
+
const urgency = signal.attentionHints.urgency;
|
|
329
344
|
const isHighUrgencyAction =
|
|
330
|
-
|
|
345
|
+
(urgency === "high" || urgency === "critical") &&
|
|
331
346
|
signal.attentionHints.requiresAction;
|
|
332
347
|
|
|
333
348
|
// Always include the vellum channel in the fallback — it's a local
|
|
@@ -667,47 +682,6 @@ function enforceAccessRequestInstructions(
|
|
|
667
682
|
};
|
|
668
683
|
}
|
|
669
684
|
|
|
670
|
-
function enforceHeartbeatAlertCopy(
|
|
671
|
-
decision: NotificationDecision,
|
|
672
|
-
signal: NotificationSignal,
|
|
673
|
-
): NotificationDecision {
|
|
674
|
-
if (signal.sourceEventName !== "heartbeat.alert") return decision;
|
|
675
|
-
if (!decision.shouldNotify || decision.selectedChannels.length === 0)
|
|
676
|
-
return decision;
|
|
677
|
-
|
|
678
|
-
const fallbackCopy = composeFallbackCopy(signal, decision.selectedChannels);
|
|
679
|
-
const nextCopy: Partial<Record<NotificationChannel, RenderedChannelCopy>> = {
|
|
680
|
-
...decision.renderedCopy,
|
|
681
|
-
};
|
|
682
|
-
|
|
683
|
-
for (const channel of decision.selectedChannels) {
|
|
684
|
-
const currentCopy = nextCopy[channel];
|
|
685
|
-
if (
|
|
686
|
-
currentCopy &&
|
|
687
|
-
!heartbeatCopyLooksLikeIntermediaryInstruction(currentCopy)
|
|
688
|
-
) {
|
|
689
|
-
continue;
|
|
690
|
-
}
|
|
691
|
-
const safeCopy = fallbackCopy[channel];
|
|
692
|
-
if (!safeCopy) continue;
|
|
693
|
-
nextCopy[channel] = safeCopy;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
return {
|
|
697
|
-
...decision,
|
|
698
|
-
renderedCopy: nextCopy,
|
|
699
|
-
};
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
function heartbeatCopyLooksLikeIntermediaryInstruction(
|
|
703
|
-
copy: RenderedChannelCopy,
|
|
704
|
-
): boolean {
|
|
705
|
-
return [copy.title, copy.body, copy.deliveryText].some(
|
|
706
|
-
(value) =>
|
|
707
|
-
typeof value === "string" && looksLikeIntermediaryInstruction(value),
|
|
708
|
-
);
|
|
709
|
-
}
|
|
710
|
-
|
|
711
685
|
function ensureAccessRequestInstructionsInCopy(
|
|
712
686
|
copy: RenderedChannelCopy,
|
|
713
687
|
requestCode: string,
|
|
@@ -790,6 +764,102 @@ export async function evaluateSignal(
|
|
|
790
764
|
);
|
|
791
765
|
}
|
|
792
766
|
|
|
767
|
+
// Assistant-tool pass-through: when a producer hands us a verbatim
|
|
768
|
+
// message body via contextPayload.requestedMessage, skip the LLM
|
|
769
|
+
// classifier entirely. The producer has already done the routing and
|
|
770
|
+
// copy decisions — we just enforce the standard post-decision guards
|
|
771
|
+
// and persist the result.
|
|
772
|
+
if (
|
|
773
|
+
signal.sourceChannel === "assistant_tool" &&
|
|
774
|
+
typeof signal.contextPayload === "object" &&
|
|
775
|
+
signal.contextPayload != null &&
|
|
776
|
+
typeof (signal.contextPayload as Record<string, unknown>)
|
|
777
|
+
.requestedMessage === "string" &&
|
|
778
|
+
(
|
|
779
|
+
(signal.contextPayload as Record<string, unknown>)
|
|
780
|
+
.requestedMessage as string
|
|
781
|
+
).trim().length > 0
|
|
782
|
+
) {
|
|
783
|
+
const payload = signal.contextPayload as Record<string, unknown>;
|
|
784
|
+
const body = (payload.requestedMessage as string).trim();
|
|
785
|
+
const title =
|
|
786
|
+
typeof payload.requestedTitle === "string" &&
|
|
787
|
+
payload.requestedTitle.trim().length > 0
|
|
788
|
+
? (payload.requestedTitle as string).trim()
|
|
789
|
+
: deriveTitle(body);
|
|
790
|
+
const isUrgent =
|
|
791
|
+
signal.attentionHints.urgency === "critical" ||
|
|
792
|
+
signal.attentionHints.urgency === "high";
|
|
793
|
+
const defaultChannels: NotificationChannel[] = isUrgent
|
|
794
|
+
? [...availableChannels]
|
|
795
|
+
: availableChannels.includes("vellum")
|
|
796
|
+
? ["vellum" as NotificationChannel]
|
|
797
|
+
: [];
|
|
798
|
+
// Honor `--preferred-channels` as ADDITIVE push targets on top of
|
|
799
|
+
// the default channel set. The notification center (vellum) is the
|
|
800
|
+
// always-on canonical inbox; preferred channels add push surfaces
|
|
801
|
+
// on top, they never replace vellum. Disconnected channels are
|
|
802
|
+
// filtered out so we never try to deliver on something unavailable.
|
|
803
|
+
const preferredChannelsRaw = Array.isArray(payload.preferredChannels)
|
|
804
|
+
? (payload.preferredChannels as unknown[]).filter(
|
|
805
|
+
(c): c is string => typeof c === "string",
|
|
806
|
+
)
|
|
807
|
+
: undefined;
|
|
808
|
+
let selectedChannels = defaultChannels;
|
|
809
|
+
if (preferredChannelsRaw && preferredChannelsRaw.length > 0) {
|
|
810
|
+
const availableSet = new Set<string>(availableChannels);
|
|
811
|
+
const preferredAvailable = preferredChannelsRaw.filter((c) =>
|
|
812
|
+
availableSet.has(c),
|
|
813
|
+
) as NotificationChannel[];
|
|
814
|
+
if (preferredAvailable.length > 0) {
|
|
815
|
+
selectedChannels = Array.from(
|
|
816
|
+
new Set<NotificationChannel>([
|
|
817
|
+
...selectedChannels,
|
|
818
|
+
...preferredAvailable,
|
|
819
|
+
]),
|
|
820
|
+
);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
// Thread `--deep-link-metadata` through when supplied as a plain object.
|
|
824
|
+
const deepLinkTarget =
|
|
825
|
+
payload.deepLinkMetadata != null &&
|
|
826
|
+
typeof payload.deepLinkMetadata === "object" &&
|
|
827
|
+
!Array.isArray(payload.deepLinkMetadata)
|
|
828
|
+
? (payload.deepLinkMetadata as Record<string, unknown>)
|
|
829
|
+
: undefined;
|
|
830
|
+
// Populate renderedCopy and conversationActions for every available
|
|
831
|
+
// channel — not just `selectedChannels`. Downstream guards
|
|
832
|
+
// (routing-intent expansion in `enforceRoutingIntent`, urgency-forced
|
|
833
|
+
// vellum prepending in `emit-signal`) may widen `selectedChannels`
|
|
834
|
+
// beyond what we picked here. Pre-seeding copy for all channels ensures
|
|
835
|
+
// the verbatim message survives those expansions rather than falling
|
|
836
|
+
// back to an empty `composeFallbackCopy` body.
|
|
837
|
+
let decision: NotificationDecision = {
|
|
838
|
+
shouldNotify: selectedChannels.length > 0,
|
|
839
|
+
selectedChannels,
|
|
840
|
+
reasoningSummary: "assistant_tool pass-through",
|
|
841
|
+
renderedCopy: Object.fromEntries(
|
|
842
|
+
availableChannels.map((ch) => [ch, { title, body }]),
|
|
843
|
+
) as NotificationDecision["renderedCopy"],
|
|
844
|
+
conversationActions: Object.fromEntries(
|
|
845
|
+
availableChannels.map((ch) => [ch, { action: "start_new" as const }]),
|
|
846
|
+
) as NotificationDecision["conversationActions"],
|
|
847
|
+
dedupeKey: signal.signalId,
|
|
848
|
+
confidence: 1.0,
|
|
849
|
+
fallbackUsed: false,
|
|
850
|
+
...(deepLinkTarget ? { deepLinkTarget } : {}),
|
|
851
|
+
};
|
|
852
|
+
decision = enforceGuardianRequestCode(decision, signal);
|
|
853
|
+
decision = enforceAccessRequestInstructions(decision, signal);
|
|
854
|
+
decision = enforceGuardianCallConversationAffinity(decision, signal);
|
|
855
|
+
decision = enforceConversationAffinity(
|
|
856
|
+
decision,
|
|
857
|
+
signal.conversationAffinityHint,
|
|
858
|
+
);
|
|
859
|
+
decision.persistedDecisionId = persistDecision(signal, decision);
|
|
860
|
+
return decision;
|
|
861
|
+
}
|
|
862
|
+
|
|
793
863
|
const provider = await getConfiguredProvider("notificationDecision");
|
|
794
864
|
if (!provider) {
|
|
795
865
|
log.warn(
|
|
@@ -798,7 +868,6 @@ export async function evaluateSignal(
|
|
|
798
868
|
let decision = buildFallbackDecision(signal, availableChannels);
|
|
799
869
|
decision = enforceGuardianRequestCode(decision, signal);
|
|
800
870
|
decision = enforceAccessRequestInstructions(decision, signal);
|
|
801
|
-
decision = enforceHeartbeatAlertCopy(decision, signal);
|
|
802
871
|
decision = enforceGuardianCallConversationAffinity(decision, signal);
|
|
803
872
|
decision = enforceConversationAffinity(
|
|
804
873
|
decision,
|
|
@@ -828,7 +897,6 @@ export async function evaluateSignal(
|
|
|
828
897
|
|
|
829
898
|
decision = enforceGuardianRequestCode(decision, signal);
|
|
830
899
|
decision = enforceAccessRequestInstructions(decision, signal);
|
|
831
|
-
decision = enforceHeartbeatAlertCopy(decision, signal);
|
|
832
900
|
decision = enforceGuardianCallConversationAffinity(decision, signal);
|
|
833
901
|
decision = enforceConversationAffinity(
|
|
834
902
|
decision,
|
|
@@ -12,6 +12,7 @@ import { and, eq } from "drizzle-orm";
|
|
|
12
12
|
import { getDb } from "../memory/db-connection.js";
|
|
13
13
|
import { notificationEvents } from "../memory/schema.js";
|
|
14
14
|
import { getLogger } from "../util/logger.js";
|
|
15
|
+
import { composeFallbackCopy } from "./copy-composer.js";
|
|
15
16
|
import type { NotificationSignal } from "./signal.js";
|
|
16
17
|
import type { NotificationChannel, NotificationDecision } from "./types.js";
|
|
17
18
|
|
|
@@ -88,6 +89,16 @@ export async function runDeterministicChecks(
|
|
|
88
89
|
return dedupeCheck;
|
|
89
90
|
}
|
|
90
91
|
|
|
92
|
+
// Check 5: Rendered copy quality (fail-closed)
|
|
93
|
+
const copyCheck = checkRenderedCopyQuality(signal, decision);
|
|
94
|
+
if (!copyCheck.passed) {
|
|
95
|
+
log.info(
|
|
96
|
+
{ signalId: signal.signalId, reason: copyCheck.reason },
|
|
97
|
+
"Deterministic check failed: rendered copy quality",
|
|
98
|
+
);
|
|
99
|
+
return copyCheck;
|
|
100
|
+
}
|
|
101
|
+
|
|
91
102
|
return { passed: true };
|
|
92
103
|
}
|
|
93
104
|
|
|
@@ -232,3 +243,88 @@ function checkDedupe(
|
|
|
232
243
|
|
|
233
244
|
return { passed: true };
|
|
234
245
|
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Fail-closed check that the rendered copy is real text and not an
|
|
249
|
+
* accidental fallback leak (empty body, or body that is just the raw
|
|
250
|
+
* source event name like "user.send_notification").
|
|
251
|
+
*
|
|
252
|
+
* Only validates channels that the decision engine actually emitted
|
|
253
|
+
* copy for. Channels appended after the decision (urgency-forced
|
|
254
|
+
* `vellum` prepend, `enforceRoutingIntent` expansion) have no entry
|
|
255
|
+
* in `renderedCopy` and are left for the broadcaster's
|
|
256
|
+
* `composeFallbackCopy` rescue at delivery time.
|
|
257
|
+
*
|
|
258
|
+
* If `renderedCopy` is empty for every selected channel, the
|
|
259
|
+
* broadcaster's fallback must produce a usable body — otherwise the
|
|
260
|
+
* signal would be silently dropped at delivery (broadcaster skips
|
|
261
|
+
* empty-body channels, `dispatchDecision` reports 0/N sent). In that
|
|
262
|
+
* case, require `composeFallbackCopy` to yield a non-empty body for
|
|
263
|
+
* at least one selected channel; otherwise fail-closed.
|
|
264
|
+
*
|
|
265
|
+
* The event-name-match branch is skipped for `assistant_tool`
|
|
266
|
+
* pass-through decisions because the producer supplied the body
|
|
267
|
+
* verbatim — a coincidental match with the event name is the user's
|
|
268
|
+
* intent, not a fallback leak.
|
|
269
|
+
*/
|
|
270
|
+
function checkRenderedCopyQuality(
|
|
271
|
+
signal: NotificationSignal,
|
|
272
|
+
decision: NotificationDecision,
|
|
273
|
+
): CheckResult {
|
|
274
|
+
if (!decision.shouldNotify) {
|
|
275
|
+
return { passed: true };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const isAssistantToolPassthrough =
|
|
279
|
+
decision.reasoningSummary === "assistant_tool pass-through";
|
|
280
|
+
const normalizedEventName = signal.sourceEventName
|
|
281
|
+
.replace(/[._]/g, " ")
|
|
282
|
+
.toLowerCase()
|
|
283
|
+
.trim();
|
|
284
|
+
const rawEventName = signal.sourceEventName.toLowerCase();
|
|
285
|
+
|
|
286
|
+
let anyChannelHasCopy = false;
|
|
287
|
+
for (const channel of decision.selectedChannels) {
|
|
288
|
+
const copy = decision.renderedCopy[channel];
|
|
289
|
+
if (!copy) {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
anyChannelHasCopy = true;
|
|
293
|
+
const trimmedBody = copy.body.trim();
|
|
294
|
+
if (trimmedBody.length === 0) {
|
|
295
|
+
return {
|
|
296
|
+
passed: false,
|
|
297
|
+
reason: "rendered copy body is empty",
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
if (isAssistantToolPassthrough) {
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
const normalizedBody = trimmedBody.toLowerCase();
|
|
304
|
+
if (
|
|
305
|
+
normalizedBody === normalizedEventName ||
|
|
306
|
+
normalizedBody === rawEventName
|
|
307
|
+
) {
|
|
308
|
+
return {
|
|
309
|
+
passed: false,
|
|
310
|
+
reason: "rendered copy body is the source event name (fallback leak)",
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (!anyChannelHasCopy && decision.selectedChannels.length > 0) {
|
|
316
|
+
const fallback = composeFallbackCopy(signal, decision.selectedChannels);
|
|
317
|
+
const fallbackUsable = decision.selectedChannels.some(
|
|
318
|
+
(ch) => (fallback[ch]?.body ?? "").trim().length > 0,
|
|
319
|
+
);
|
|
320
|
+
if (!fallbackUsable) {
|
|
321
|
+
return {
|
|
322
|
+
passed: false,
|
|
323
|
+
reason:
|
|
324
|
+
"rendered copy missing for all selected channels and fallback body is empty (would silently drop)",
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return { passed: true };
|
|
330
|
+
}
|
|
@@ -13,6 +13,7 @@ import { v4 as uuid } from "uuid";
|
|
|
13
13
|
|
|
14
14
|
import { getDeliverableChannels } from "../channels/config.js";
|
|
15
15
|
import { findGuardianForChannel } from "../contacts/contact-store.js";
|
|
16
|
+
import type { ConversationCreateType } from "../memory/conversation-crud.js";
|
|
16
17
|
import { getLogger } from "../util/logger.js";
|
|
17
18
|
import { type BroadcastFn, VellumAdapter } from "./adapters/macos.js";
|
|
18
19
|
import { PlatformPushAdapter } from "./adapters/platform.js";
|
|
@@ -87,6 +88,7 @@ function getBroadcaster(): NotificationBroadcaster {
|
|
|
87
88
|
targetGuardianPrincipalId: info.targetGuardianPrincipalId,
|
|
88
89
|
groupId: info.groupId,
|
|
89
90
|
source: info.source,
|
|
91
|
+
silent: info.silent,
|
|
90
92
|
});
|
|
91
93
|
log.info(
|
|
92
94
|
{
|
|
@@ -204,6 +206,7 @@ export interface EmitSignalParams<TEventName extends string = string> {
|
|
|
204
206
|
groupId?: string;
|
|
205
207
|
scheduleJobId?: string;
|
|
206
208
|
source?: string;
|
|
209
|
+
conversationType?: ConversationCreateType;
|
|
207
210
|
};
|
|
208
211
|
}
|
|
209
212
|
|
|
@@ -280,7 +283,24 @@ export async function emitNotificationSignal<TEventName extends string>(
|
|
|
280
283
|
|
|
281
284
|
let decision = await evaluateSignal(signal, connectedChannels);
|
|
282
285
|
|
|
283
|
-
// Step 2.
|
|
286
|
+
// Step 2.5a: High/critical urgency signals always get a system
|
|
287
|
+
// notification via the vellum channel, regardless of what the
|
|
288
|
+
// decision engine selected. This ensures macOS surfaces a banner
|
|
289
|
+
// even when the app is focused.
|
|
290
|
+
const urgency = signal.attentionHints.urgency;
|
|
291
|
+
if (
|
|
292
|
+
(urgency === "high" || urgency === "critical") &&
|
|
293
|
+
decision.shouldNotify &&
|
|
294
|
+
!decision.selectedChannels.includes("vellum")
|
|
295
|
+
) {
|
|
296
|
+
decision = {
|
|
297
|
+
...decision,
|
|
298
|
+
selectedChannels: ["vellum", ...decision.selectedChannels],
|
|
299
|
+
reasoningSummary: `${decision.reasoningSummary} (vellum forced: ${urgency} urgency)`,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Step 2.5b: Enforce routing intent policy (fire-time guard)
|
|
284
304
|
const preEnforcementDecision = decision;
|
|
285
305
|
decision = enforceRoutingIntent(
|
|
286
306
|
decision,
|