@vellumai/assistant 0.6.5 → 0.6.6
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 +9 -1
- package/ARCHITECTURE.md +15 -17
- package/Dockerfile +6 -4
- package/__tests__/permissions/gateway-threshold-reader.test.ts +283 -0
- package/docs/architecture/integrations.md +32 -39
- package/docs/architecture/memory.md +25 -30
- package/docs/architecture/security.md +7 -6
- package/docs/browser-use-architecture-phase2.md +63 -20
- package/docs/plugins.md +761 -0
- package/examples/plugins/echo/README.md +132 -0
- package/examples/plugins/echo/package.json +17 -0
- package/examples/plugins/echo/register.ts +187 -0
- package/node_modules/@vellumai/egress-proxy/src/types.ts +19 -0
- package/openapi.yaml +212 -68
- package/package.json +1 -1
- package/src/__tests__/app-compiler.test.ts +57 -0
- package/src/__tests__/approval-cascade.test.ts +7 -2
- package/src/__tests__/auto-analysis-end-to-end.test.ts +1 -0
- package/src/__tests__/avatar-generator.test.ts +4 -2
- package/src/__tests__/bundled-asset.test.ts +6 -6
- package/src/__tests__/catalog-cache.test.ts +69 -0
- package/src/__tests__/checker.test.ts +459 -171
- package/src/__tests__/circuit-breaker-pipeline.test.ts +406 -0
- package/src/__tests__/compaction-events.test.ts +501 -0
- package/src/__tests__/compaction-pipeline.test.ts +210 -0
- package/src/__tests__/compaction-strip-metadata-clear.test.ts +181 -0
- package/src/__tests__/compaction-timeout-recovery.test.ts +262 -0
- package/src/__tests__/config-model-image-provider.test.ts +110 -0
- package/src/__tests__/config-schema.test.ts +22 -9
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +0 -4
- package/src/__tests__/contacts-tools.test.ts +26 -0
- package/src/__tests__/context-overflow-policy.test.ts +7 -7
- package/src/__tests__/context-window-manager.test.ts +355 -4
- package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +26 -30
- package/src/__tests__/conversation-agent-loop.test.ts +30 -141
- package/src/__tests__/conversation-confirmation-signals.test.ts +6 -1
- package/src/__tests__/conversation-history-web-search.test.ts +1 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +2 -16
- package/src/__tests__/conversation-pairing.test.ts +174 -10
- package/src/__tests__/conversation-pre-run-repair.test.ts +4 -1
- package/src/__tests__/conversation-process-callsite.test.ts +3 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +16 -7
- package/src/__tests__/conversation-queue.test.ts +29 -14
- package/src/__tests__/conversation-routes-disk-view.test.ts +7 -6
- package/src/__tests__/conversation-runtime-assembly.test.ts +155 -110
- package/src/__tests__/conversation-runtime-workspace.test.ts +23 -38
- package/src/__tests__/conversation-seed-composer.test.ts +2 -2
- package/src/__tests__/conversation-slash-queue.test.ts +7 -2
- package/src/__tests__/conversation-slash-unknown.test.ts +25 -2
- package/src/__tests__/conversation-speed-override.test.ts +6 -1
- package/src/__tests__/conversation-title-service.test.ts +116 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +41 -2
- package/src/__tests__/conversation-usage.test.ts +1 -1
- package/src/__tests__/conversation-workspace-cache-state.test.ts +4 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +3 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +4 -1
- package/src/__tests__/credential-health-service.test.ts +78 -9
- package/src/__tests__/credential-security-invariants.test.ts +2 -2
- package/src/__tests__/db-schedule-syntax-migration.test.ts +1 -0
- package/src/__tests__/empty-response-pipeline.test.ts +305 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +3 -3
- package/src/__tests__/first-greeting.test.ts +247 -5
- package/src/__tests__/headless-browser-mode.test.ts +57 -0
- package/src/__tests__/history-repair-pipeline.test.ts +399 -0
- package/src/__tests__/host-browser-e2e-cloud.test.ts +307 -0
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +3 -3
- package/src/__tests__/host-proxy-interface.test.ts +36 -2
- package/src/__tests__/image-credentials.test.ts +137 -0
- package/src/__tests__/image-service-dispatcher.test.ts +186 -0
- package/src/__tests__/injector-chain.test.ts +526 -0
- package/src/__tests__/intent-routing.test.ts +0 -26
- package/src/__tests__/llm-call-pipeline.test.ts +285 -0
- package/src/__tests__/llm-schema.test.ts +1 -1
- package/src/__tests__/media-generate-image.test.ts +119 -13
- package/src/__tests__/memory-retrieval-pipeline.test.ts +401 -0
- package/src/__tests__/memory-upsert-concurrency.test.ts +1 -0
- package/src/__tests__/migration-import-from-url.test.ts +5 -68
- package/src/__tests__/model-intents.test.ts +4 -2
- package/src/__tests__/notification-broadcaster.test.ts +3 -3
- package/src/__tests__/notification-decision-strategy.test.ts +0 -11
- package/src/__tests__/notification-schedule-notify-dedup.test.ts +108 -0
- package/src/__tests__/oauth-apps-routes.test.ts +1 -1
- package/src/__tests__/oauth-cli.test.ts +14 -12
- package/src/__tests__/oauth-connect-orchestrator.test.ts +4 -13
- package/src/__tests__/oauth-provider-serializer.test.ts +6 -4
- package/src/__tests__/oauth-provider-visibility.test.ts +3 -5
- package/src/__tests__/oauth-providers-routes.test.ts +3 -2
- package/src/__tests__/oauth-store.test.ts +41 -76
- package/src/__tests__/onboarding-template-contract.test.ts +16 -64
- package/src/__tests__/openai-image-service.test.ts +368 -0
- package/src/__tests__/overflow-reduce-pipeline.test.ts +676 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +0 -24
- package/src/__tests__/persist-onboarding-artifacts.test.ts +266 -0
- package/src/__tests__/persistence-pipeline.test.ts +377 -0
- package/src/__tests__/pipeline-runner.test.ts +565 -0
- package/src/__tests__/platform.test.ts +5 -2
- package/src/__tests__/plugin-bootstrap.test.ts +483 -0
- package/src/__tests__/plugin-registry.test.ts +273 -0
- package/src/__tests__/plugin-route-contribution.test.ts +288 -0
- package/src/__tests__/plugin-skill-contribution.test.ts +367 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +286 -0
- package/src/__tests__/plugin-types.test.ts +320 -0
- package/src/__tests__/pricing.test.ts +44 -12
- package/src/__tests__/proxy-approval-callback.test.ts +69 -8
- package/src/__tests__/reaction-persistence.test.ts +1 -0
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
- package/src/__tests__/registry.test.ts +0 -2
- package/src/__tests__/schedule-routes.test.ts +131 -1
- package/src/__tests__/scheduler-recurrence.test.ts +14 -70
- package/src/__tests__/scheduler-reuse-conversation.test.ts +10 -50
- package/src/__tests__/secret-detection-handler.test.ts +0 -10
- package/src/__tests__/shell-identity.test.ts +0 -134
- package/src/__tests__/suggestion-routes.test.ts +103 -4
- package/src/__tests__/task-memory-cleanup.test.ts +1 -0
- package/src/__tests__/task-scheduler.test.ts +3 -15
- package/src/__tests__/test-preload.ts +11 -0
- package/src/__tests__/title-generate-pipeline.test.ts +224 -0
- package/src/__tests__/token-estimate-pipeline.test.ts +431 -0
- package/src/__tests__/tool-error-pipeline.test.ts +244 -0
- package/src/__tests__/tool-execute-pipeline.test.ts +431 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -6
- package/src/__tests__/tool-executor-shell-integration.test.ts +7 -10
- package/src/__tests__/tool-executor.test.ts +141 -0
- package/src/__tests__/tool-result-truncate-pipeline.test.ts +356 -0
- package/src/__tests__/tool-result-truncation.test.ts +0 -110
- package/src/__tests__/user-plugin-loader.test.ts +191 -0
- package/src/__tests__/workspace-migration-046-seed-conversation-starters-callsite.test.ts +185 -0
- package/src/__tests__/workspace-migration-049-release-notes-default-sonnet.test.ts +100 -0
- package/src/__tests__/workspace-migration-050-seed-main-agent-opus-callsite.test.ts +171 -0
- package/src/__tests__/workspace-migration-051-seed-conversation-summarization-callsite.test.ts +252 -0
- package/src/__tests__/workspace-migration-remove-hooks.test.ts +99 -0
- package/src/__tests__/workspace-policy.test.ts +21 -3
- package/src/agent/loop.ts +340 -102
- package/src/approvals/__tests__/guardian-feed-event.test.ts +304 -0
- package/src/approvals/guardian-request-resolvers.ts +80 -0
- package/src/backup/__tests__/backup-worker.test.ts +2 -13
- package/src/backup/backup-worker.ts +3 -15
- package/src/bundler/app-compiler.ts +84 -1
- package/src/calls/call-state.ts +2 -2
- package/src/channels/__tests__/types.test.ts +3 -3
- package/src/channels/types.ts +6 -4
- package/src/cli/__tests__/notifications.test.ts +87 -211
- package/src/cli/commands/__tests__/backup.test.ts +1 -1
- package/src/cli/commands/__tests__/image-generation.test.ts +255 -35
- package/src/cli/commands/__tests__/inference-send.test.ts +12 -0
- package/src/cli/commands/__tests__/tts-synthesize.test.ts +12 -0
- package/src/cli/commands/backup.ts +2 -2
- package/src/cli/commands/clients.ts +138 -0
- package/src/cli/commands/completions.ts +2 -9
- package/src/cli/commands/conversations.ts +55 -7
- package/src/cli/commands/image-generation.ts +33 -34
- package/src/cli/commands/notifications.ts +68 -103
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -1
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -1
- package/src/cli/commands/oauth/connect.ts +2 -2
- package/src/cli/commands/oauth/providers.ts +176 -8
- package/src/cli/commands/oauth/status.ts +46 -36
- package/src/cli/commands/skills.ts +3 -4
- package/src/cli/program.ts +25 -29
- package/src/config/__tests__/backup-schema.test.ts +7 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
- package/src/config/bundled-skills/app-builder/references/WIDGETS.md +10 -10
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +66 -87
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +28 -51
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +22 -40
- package/src/config/bundled-skills/image-studio/SKILL.md +2 -1
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -1
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +23 -39
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +207 -0
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +12 -0
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +58 -0
- package/src/config/bundled-skills/schedule/SKILL.md +8 -3
- package/src/config/bundled-skills/schedule/TOOLS.json +15 -7
- package/src/config/bundled-skills/schedule/references/SCRIPT_MODE_PATTERNS.md +59 -0
- package/src/config/bundled-tool-registry.ts +0 -15
- package/src/config/feature-flag-registry.json +17 -1
- package/src/config/schema.ts +19 -0
- package/src/config/schemas/backup.ts +1 -1
- package/src/config/schemas/conversations.ts +16 -0
- package/src/config/schemas/llm.ts +2 -3
- package/src/config/schemas/security.ts +6 -6
- package/src/config/schemas/tts.ts +11 -0
- package/src/config/skill-state.ts +6 -2
- package/src/config/skills.ts +94 -5
- package/src/context/__tests__/compact-prompt.test.ts +27 -9
- package/src/context/prompts/compact.md +26 -12
- package/src/context/tool-result-truncation.ts +3 -63
- package/src/context/window-manager.ts +190 -16
- package/src/credential-health/credential-health-service.ts +19 -6
- package/src/daemon/__tests__/conversation-feed-event.test.ts +317 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +4 -12
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +14 -15
- package/src/daemon/config-watcher.ts +0 -2
- package/src/daemon/context-overflow-policy.ts +4 -13
- package/src/daemon/conversation-agent-loop-handlers.ts +83 -22
- package/src/daemon/conversation-agent-loop.ts +984 -683
- package/src/daemon/conversation-history.ts +10 -19
- package/src/daemon/conversation-lifecycle.ts +37 -19
- package/src/daemon/conversation-notifiers.ts +2 -110
- package/src/daemon/conversation-process.ts +14 -7
- package/src/daemon/conversation-runtime-assembly.ts +532 -411
- package/src/daemon/conversation-tool-setup.ts +41 -4
- package/src/daemon/conversation.ts +80 -35
- package/src/daemon/external-plugins-bootstrap.ts +478 -0
- package/src/daemon/first-greeting.ts +191 -14
- package/src/daemon/handlers/config-model.ts +11 -0
- package/src/daemon/handlers/skills.ts +5 -1
- package/src/daemon/lifecycle.ts +33 -68
- package/src/daemon/message-types/computer-use.ts +2 -34
- package/src/daemon/message-types/conversations.ts +49 -0
- package/src/daemon/message-types/messages.ts +12 -0
- package/src/daemon/server.ts +5 -3
- package/src/daemon/shutdown-handlers.ts +2 -12
- package/src/daemon/tool-side-effects.ts +14 -56
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +160 -0
- package/src/heartbeat/heartbeat-service.ts +24 -1
- package/src/home/__tests__/feed-population-integration.test.ts +312 -0
- package/src/home/emit-feed-event.ts +7 -0
- package/src/home/feed-types.ts +41 -2
- package/src/home/rewrite-command-preview.ts +66 -0
- package/src/ipc/__tests__/socket-path.test.ts +11 -50
- package/src/ipc/cli-client.ts +1 -1
- package/src/ipc/cli-server.ts +3 -3
- package/src/ipc/gateway-client.ts +4 -1
- package/src/ipc/routes/browser-context.ts +2 -0
- package/src/ipc/routes/browser.ts +1 -0
- package/src/ipc/routes/get-contact.ts +16 -0
- package/src/ipc/routes/index.ts +14 -0
- package/src/ipc/routes/list-clients.ts +31 -0
- package/src/ipc/routes/merge-contacts.ts +17 -0
- package/src/ipc/routes/notification.ts +133 -0
- package/src/ipc/routes/rename-conversation.ts +59 -0
- package/src/ipc/routes/search-contacts.ts +19 -0
- package/src/ipc/routes/upsert-contact.ts +25 -0
- package/src/ipc/socket-path.ts +14 -38
- package/src/media/app-icon-generator.ts +23 -46
- package/src/media/avatar-router.ts +26 -41
- package/src/media/gemini-image-service.ts +8 -41
- package/src/media/image-credentials.ts +73 -0
- package/src/media/image-service.ts +85 -0
- package/src/media/openai-image-service.ts +131 -0
- package/src/media/types.ts +46 -0
- package/src/memory/conversation-crud.ts +48 -18
- package/src/memory/conversation-queries.ts +57 -4
- package/src/memory/conversation-title-service.ts +25 -0
- package/src/memory/db-init.ts +8 -0
- package/src/memory/embedding-gemini.test.ts +41 -2
- package/src/memory/embedding-gemini.ts +6 -1
- package/src/memory/graph/bootstrap.test.ts +282 -0
- package/src/memory/graph/bootstrap.ts +8 -5
- package/src/memory/graph/extraction.ts +10 -2
- package/src/memory/graph/graph-search.test.ts +1 -0
- package/src/memory/graph/inspect.ts +2 -2
- package/src/memory/graph/retriever.ts +10 -3
- package/src/memory/migrations/041-approval-prompt-ts-tracker.ts +26 -0
- package/src/memory/migrations/149-oauth-tables.ts +1 -0
- package/src/memory/migrations/223-schedule-script-column.ts +11 -0
- package/src/memory/migrations/224-oauth-providers-managed-service-is-paid.ts +24 -0
- package/src/memory/migrations/225-oauth-providers-available-scopes.ts +13 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/pkb/pkb-index.test.ts +1 -0
- package/src/memory/pkb/pkb-reconcile.test.ts +1 -0
- package/src/memory/pkb/pkb-search.test.ts +65 -4
- package/src/memory/pkb/pkb-search.ts +40 -18
- package/src/memory/qdrant-client.test.ts +60 -0
- package/src/memory/qdrant-client.ts +25 -0
- package/src/memory/schema/infrastructure.ts +1 -0
- package/src/memory/schema/oauth.ts +4 -1
- package/src/messaging/providers/slack/render-transcript.test.ts +77 -29
- package/src/messaging/providers/slack/render-transcript.ts +58 -0
- package/src/notifications/conversation-pairing.ts +78 -19
- package/src/notifications/copy-composer.ts +0 -5
- package/src/notifications/emit-signal.ts +1 -1
- package/src/notifications/signal.ts +1 -2
- package/src/oauth/AGENTS.md +1 -1
- package/src/oauth/__tests__/identity-verifier.test.ts +2 -1
- package/src/oauth/connect-orchestrator.ts +8 -34
- package/src/oauth/connect-types.ts +6 -10
- package/src/oauth/manual-token-connection.ts +23 -0
- package/src/oauth/oauth-store.ts +30 -14
- package/src/oauth/provider-serializer.ts +6 -1
- package/src/oauth/seed-providers.ts +56 -108
- package/src/outbound-proxy/http-forwarder.ts +9 -0
- package/src/permissions/approval-policy.test.ts +293 -18
- package/src/permissions/approval-policy.ts +110 -58
- package/src/permissions/arg-parser.test.ts +161 -0
- package/src/permissions/arg-parser.ts +141 -0
- package/src/permissions/bash-risk-classifier.test.ts +414 -2
- package/src/permissions/bash-risk-classifier.ts +303 -60
- package/src/permissions/checker.ts +157 -29
- package/src/permissions/command-registry.test.ts +239 -0
- package/src/permissions/command-registry.ts +234 -54
- package/src/permissions/defaults.ts +5 -4
- package/src/permissions/gateway-threshold-reader.ts +196 -0
- package/src/permissions/prompter.ts +4 -0
- package/src/permissions/risk-types.ts +61 -4
- package/src/permissions/schedule-risk-classifier.test.ts +129 -0
- package/src/permissions/schedule-risk-classifier.ts +85 -0
- package/src/permissions/shell-identity.ts +2 -42
- package/src/permissions/types.ts +2 -0
- package/src/permissions/workspace-policy.ts +8 -3
- package/src/plugins/defaults/circuit-breaker.ts +146 -0
- package/src/plugins/defaults/compaction.ts +145 -0
- package/src/plugins/defaults/empty-response.ts +126 -0
- package/src/plugins/defaults/history-repair.ts +85 -0
- package/src/plugins/defaults/index.ts +116 -0
- package/src/plugins/defaults/injectors.ts +491 -0
- package/src/plugins/defaults/llm-call.ts +82 -0
- package/src/plugins/defaults/memory-retrieval.ts +226 -0
- package/src/plugins/defaults/overflow-reduce.ts +181 -0
- package/src/plugins/defaults/persistence.ts +129 -0
- package/src/plugins/defaults/title-generate.ts +95 -0
- package/src/plugins/defaults/token-estimate.ts +104 -0
- package/src/plugins/defaults/tool-error.ts +126 -0
- package/src/plugins/defaults/tool-execute.ts +89 -0
- package/src/plugins/defaults/tool-result-truncate.ts +88 -0
- package/src/plugins/pipeline.ts +316 -0
- package/src/plugins/plugin-skill-contributions.ts +292 -0
- package/src/plugins/registry.ts +241 -0
- package/src/plugins/types.ts +1134 -0
- package/src/plugins/user-loader.ts +177 -0
- package/src/prompts/templates/BOOTSTRAP.md +27 -77
- package/src/providers/model-catalog.ts +52 -29
- package/src/providers/model-intents.ts +1 -1
- package/src/providers/openrouter/client.ts +5 -1
- package/src/providers/speech-to-text/deepgram-realtime.test.ts +61 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +57 -0
- package/src/providers/speech-to-text/xai-realtime.test.ts +72 -4
- package/src/providers/speech-to-text/xai-realtime.ts +39 -14
- package/src/runtime/AGENTS.md +25 -16
- package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +3 -3
- package/src/runtime/__tests__/client-registry.test.ts +293 -0
- package/src/runtime/client-registry.ts +261 -0
- package/src/runtime/http-server.ts +77 -8
- package/src/runtime/http-types.ts +0 -2
- package/src/runtime/migrations/vbundle-builder.ts +1 -22
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +51 -31
- package/src/runtime/routes/approval-routes.ts +17 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +27 -8
- package/src/runtime/routes/conversation-routes.ts +223 -116
- package/src/runtime/routes/inbound-message-handler.ts +88 -13
- package/src/runtime/routes/memory-item-routes.test.ts +1 -0
- package/src/runtime/routes/migration-routes.ts +0 -3
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +284 -0
- package/src/runtime/routes/playground/__tests__/guard.test.ts +80 -0
- package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +294 -0
- package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +271 -0
- package/src/runtime/routes/playground/__tests__/seed-conversation.test.ts +202 -0
- package/src/runtime/routes/playground/__tests__/seeded-conversations.test.ts +309 -0
- package/src/runtime/routes/playground/__tests__/state.test.ts +224 -0
- package/src/runtime/routes/playground/conversation-not-found.ts +29 -0
- package/src/runtime/routes/playground/deps.ts +56 -0
- package/src/runtime/routes/playground/force-compact.ts +73 -0
- package/src/runtime/routes/playground/guard.ts +37 -0
- package/src/runtime/routes/playground/index.ts +28 -0
- package/src/runtime/routes/playground/inject-failures.ts +159 -0
- package/src/runtime/routes/playground/reset-circuit.ts +115 -0
- package/src/runtime/routes/playground/seed-conversation.ts +139 -0
- package/src/runtime/routes/playground/seeded-conversations.ts +78 -0
- package/src/runtime/routes/playground/state.ts +78 -0
- package/src/runtime/routes/schedule-routes.ts +89 -8
- package/src/runtime/skill-route-registry.ts +75 -15
- package/src/schedule/run-script.ts +68 -0
- package/src/schedule/schedule-store.ts +7 -1
- package/src/schedule/scheduler.ts +48 -8
- package/src/skills/catalog-cache.ts +12 -5
- package/src/tools/browser/__tests__/browser-status.test.ts +189 -0
- package/src/tools/browser/browser-execution.ts +88 -19
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +230 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +146 -3
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +54 -3
- package/src/tools/browser/cdp-client/factory.ts +15 -4
- package/src/tools/executor.ts +126 -74
- package/src/tools/network/script-proxy/session-manager.ts +37 -1
- package/src/tools/permission-checker.ts +98 -49
- package/src/tools/policy-context.ts +4 -0
- package/src/tools/registry.ts +140 -3
- package/src/tools/schedule/create.ts +23 -8
- package/src/tools/schedule/update.ts +3 -1
- package/src/tools/secret-detection-handler.ts +0 -51
- package/src/tools/system/avatar-generator.ts +6 -2
- package/src/tools/types.ts +28 -2
- package/src/util/platform.ts +7 -2
- package/src/util/pricing.ts +26 -3
- package/src/workspace/migrations/006-services-config.ts +2 -4
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +2 -3
- package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +3 -4
- package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +108 -0
- package/src/workspace/migrations/047-remove-watch-callsites.ts +54 -0
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +81 -0
- package/src/workspace/migrations/049-release-notes-default-sonnet.ts +80 -0
- package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +86 -0
- package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +128 -0
- package/src/workspace/migrations/registry.ts +12 -0
- package/tsconfig.json +1 -1
- package/hook-templates/debug-prompt-logger/hook.json +0 -7
- package/hook-templates/debug-prompt-logger/run.sh +0 -66
- package/src/__tests__/compaction-circuit-breaker.test.ts +0 -336
- package/src/__tests__/context-overflow-approval.test.ts +0 -156
- package/src/__tests__/hooks-blocking.test.ts +0 -178
- package/src/__tests__/hooks-cli.test.ts +0 -182
- package/src/__tests__/hooks-config.test.ts +0 -108
- package/src/__tests__/hooks-discovery.test.ts +0 -211
- package/src/__tests__/hooks-integration.test.ts +0 -196
- package/src/__tests__/hooks-manager.test.ts +0 -226
- package/src/__tests__/hooks-runner.test.ts +0 -175
- package/src/__tests__/hooks-settings.test.ts +0 -160
- package/src/__tests__/hooks-templates.test.ts +0 -169
- package/src/__tests__/hooks-ts-runner.test.ts +0 -170
- package/src/__tests__/hooks-watch.test.ts +0 -112
- package/src/__tests__/notification-schedule-dedup.test.ts +0 -213
- package/src/__tests__/oauth-scope-policy.test.ts +0 -180
- package/src/__tests__/send-notification-tool.test.ts +0 -83
- package/src/cli/commands/shotgun.ts +0 -266
- package/src/config/bundled-skills/conversations/SKILL.md +0 -20
- package/src/config/bundled-skills/conversations/TOOLS.json +0 -23
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +0 -88
- package/src/config/bundled-skills/heartbeat/SKILL.md +0 -43
- package/src/config/bundled-skills/notifications/SKILL.md +0 -40
- package/src/config/bundled-skills/notifications/TOOLS.json +0 -80
- package/src/config/bundled-skills/notifications/tools/send-notification.ts +0 -152
- package/src/config/bundled-skills/notifications/tools/shared.ts +0 -13
- package/src/config/bundled-skills/screen-watch/SKILL.md +0 -27
- package/src/config/bundled-skills/screen-watch/TOOLS.json +0 -35
- package/src/config/bundled-skills/screen-watch/tools/start-screen-watch.ts +0 -12
- package/src/config/bundled-skills/skills-catalog/SKILL.md +0 -84
- package/src/daemon/context-overflow-approval.ts +0 -52
- package/src/daemon/watch-handler.ts +0 -399
- package/src/hooks/cli.ts +0 -253
- package/src/hooks/config.ts +0 -100
- package/src/hooks/discovery.ts +0 -135
- package/src/hooks/manager.ts +0 -179
- package/src/hooks/runner.ts +0 -117
- package/src/hooks/templates.ts +0 -77
- package/src/hooks/types.ts +0 -75
- package/src/oauth/scope-policy.ts +0 -89
- package/src/runtime/gateway-internal-client.ts +0 -94
- package/src/runtime/routes/watch-routes.ts +0 -156
- package/src/signals/shotgun.ts +0 -203
- package/src/tools/watch/screen-watch.ts +0 -144
- package/src/tools/watch/watch-state.ts +0 -142
|
@@ -12,6 +12,7 @@ import { describe, expect, test } from "bun:test";
|
|
|
12
12
|
import type { Message } from "../../../providers/types.js";
|
|
13
13
|
import {
|
|
14
14
|
extractTagLineTexts,
|
|
15
|
+
isReactionTagLine,
|
|
15
16
|
parentAlias,
|
|
16
17
|
type RenderableSlackMessage,
|
|
17
18
|
renderSlackTranscript,
|
|
@@ -228,11 +229,17 @@ describe("renderSlackTranscript — basics", () => {
|
|
|
228
229
|
]);
|
|
229
230
|
});
|
|
230
231
|
|
|
231
|
-
test("
|
|
232
|
+
test("assistant-role message emits content with no tag-line wrapper", () => {
|
|
233
|
+
// Rationale: the `role` slot already conveys identity, and the
|
|
234
|
+
// assistant responds ~immediately after the triggering user message
|
|
235
|
+
// so the timestamp would add little beyond chronological adjacency.
|
|
236
|
+
// Keeping a bracketed tag on assistant rows caused the model to
|
|
237
|
+
// mimic the `[MM/DD/YY HH:MM]:` format as a literal prefix in new
|
|
238
|
+
// outbound Slack replies.
|
|
232
239
|
const out = renderSlackTranscript([
|
|
233
240
|
userMsg(TS_14_25, null, "yo 👋", { role: "assistant" }),
|
|
234
241
|
]);
|
|
235
|
-
expect(out).toEqual([textMsg("assistant", "
|
|
242
|
+
expect(out).toEqual([textMsg("assistant", "yo 👋")]);
|
|
236
243
|
});
|
|
237
244
|
|
|
238
245
|
test("omits sender label for user-role message with null senderLabel (no displayName)", () => {
|
|
@@ -245,41 +252,38 @@ describe("renderSlackTranscript — basics", () => {
|
|
|
245
252
|
expect(out).toEqual([textMsg("user", "[11/14/23 14:25]: hi")]);
|
|
246
253
|
});
|
|
247
254
|
|
|
248
|
-
test("
|
|
249
|
-
const alias = parentAlias(TS_14_25);
|
|
255
|
+
test("thread-reply assistant row emits content-only — no tag wrapper, no thread arrow", () => {
|
|
250
256
|
const out = renderSlackTranscript([
|
|
251
257
|
userMsg(TS_14_28, null, "got it", {
|
|
252
258
|
threadTs: TS_14_25,
|
|
253
259
|
role: "assistant",
|
|
254
260
|
}),
|
|
255
261
|
]);
|
|
256
|
-
expect(out).toEqual([
|
|
257
|
-
textMsg("assistant", `[11/14/23 14:28 → ${alias}]: got it`),
|
|
258
|
-
]);
|
|
262
|
+
expect(out).toEqual([textMsg("assistant", "got it")]);
|
|
259
263
|
});
|
|
260
264
|
|
|
261
|
-
test("
|
|
265
|
+
test("deleted assistant row collapses to the `[deleted]` sentinel", () => {
|
|
266
|
+
// Chronology must still be preserved — we emit a stable short sentinel
|
|
267
|
+
// rather than eliding the row entirely. The sentinel is intentionally
|
|
268
|
+
// different from the user-row `[MM/DD/YY — deleted MM/DD/YY]` form so
|
|
269
|
+
// the model has no timestamp pattern to mimic in new outbound replies.
|
|
262
270
|
const out = renderSlackTranscript([
|
|
263
271
|
userMsg(TS_14_25, null, "(removed)", {
|
|
264
272
|
deletedAt: MS_14_32,
|
|
265
273
|
role: "assistant",
|
|
266
274
|
}),
|
|
267
275
|
]);
|
|
268
|
-
expect(out).toEqual([
|
|
269
|
-
textMsg("assistant", "[11/14/23 14:25 — deleted 11/14/23 14:32]"),
|
|
270
|
-
]);
|
|
276
|
+
expect(out).toEqual([textMsg("assistant", "[deleted]")]);
|
|
271
277
|
});
|
|
272
278
|
|
|
273
|
-
test("
|
|
279
|
+
test("edited assistant row emits the latest content verbatim — no edit suffix", () => {
|
|
274
280
|
const out = renderSlackTranscript([
|
|
275
281
|
userMsg(TS_14_25, null, "v2", {
|
|
276
282
|
editedAt: MS_14_30,
|
|
277
283
|
role: "assistant",
|
|
278
284
|
}),
|
|
279
285
|
]);
|
|
280
|
-
expect(out).toEqual([
|
|
281
|
-
textMsg("assistant", "[11/14/23 14:25, edited 11/14/23 14:30]: v2"),
|
|
282
|
-
]);
|
|
286
|
+
expect(out).toEqual([textMsg("assistant", "v2")]);
|
|
283
287
|
});
|
|
284
288
|
|
|
285
289
|
test("reaction with null senderLabel falls back on role-derived subject", () => {
|
|
@@ -381,6 +385,50 @@ describe("parentAlias", () => {
|
|
|
381
385
|
});
|
|
382
386
|
});
|
|
383
387
|
|
|
388
|
+
// ── isReactionTagLine ────────────────────────────────────────────────────────
|
|
389
|
+
|
|
390
|
+
describe("isReactionTagLine", () => {
|
|
391
|
+
// Pinned to the exact shapes `renderReaction` and the overflow trailer
|
|
392
|
+
// produce. The helper is the public contract that lets consumers
|
|
393
|
+
// re-label the transcript without double-attributing reaction lines,
|
|
394
|
+
// so drift here silently breaks `buildActiveThreadBlockFromRenderable`.
|
|
395
|
+
const alias = parentAlias("1700000000.000100");
|
|
396
|
+
|
|
397
|
+
test("matches reaction-add line", () => {
|
|
398
|
+
expect(
|
|
399
|
+
isReactionTagLine(`[11/14/23 14:28 @bob reacted 👍 to ${alias}]`),
|
|
400
|
+
).toBe(true);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
test("matches reaction-remove line", () => {
|
|
404
|
+
expect(
|
|
405
|
+
isReactionTagLine(`[11/14/23 14:28 @bob removed 👍 from ${alias}]`),
|
|
406
|
+
).toBe(true);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test("matches overflow trailer line", () => {
|
|
410
|
+
expect(isReactionTagLine(`[…and 2 more reactions to ${alias}]`)).toBe(true);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
test("does not match a regular message tag line", () => {
|
|
414
|
+
expect(isReactionTagLine("[11/14/23 14:25 @alice]: hi")).toBe(false);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test("does not match content-only assistant output", () => {
|
|
418
|
+
expect(isReactionTagLine("on it. here's the answer")).toBe(false);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
test("does not match the `[deleted]` sentinel", () => {
|
|
422
|
+
expect(isReactionTagLine("[deleted]")).toBe(false);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
test("does not match a user-deleted marker", () => {
|
|
426
|
+
expect(
|
|
427
|
+
isReactionTagLine("[11/14/23 14:25 @alice — deleted 11/14/23 14:32]"),
|
|
428
|
+
).toBe(false);
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
|
|
384
432
|
// ── reaction cap ─────────────────────────────────────────────────────────────
|
|
385
433
|
|
|
386
434
|
describe("renderSlackTranscript — reaction cap", () => {
|
|
@@ -709,11 +757,11 @@ describe("renderSlackTranscript — mixed legacy + post-upgrade", () => {
|
|
|
709
757
|
expect(texts[1].includes("→")).toBe(false);
|
|
710
758
|
});
|
|
711
759
|
|
|
712
|
-
test("legacy assistant row carries assistant role", () => {
|
|
760
|
+
test("legacy assistant row carries assistant role and emits content verbatim", () => {
|
|
713
761
|
const out = renderSlackTranscript([
|
|
714
762
|
legacyMsg(MS_14_25, "@bot", "ack", "assistant"),
|
|
715
763
|
]);
|
|
716
|
-
expect(out).toEqual([textMsg("assistant", "
|
|
764
|
+
expect(out).toEqual([textMsg("assistant", "ack")]);
|
|
717
765
|
});
|
|
718
766
|
|
|
719
767
|
test("preserves message role faithfully across mixed inputs", () => {
|
|
@@ -873,7 +921,7 @@ describe("renderSlackTranscript — replayable content-block preservation", () =
|
|
|
873
921
|
expect(out[0]).toEqual({
|
|
874
922
|
role: "assistant",
|
|
875
923
|
content: [
|
|
876
|
-
{ type: "text", text: "
|
|
924
|
+
{ type: "text", text: "looking it up" },
|
|
877
925
|
{ type: "tool_use", id: "tu_1", name: "search", input: { q: "x" } },
|
|
878
926
|
],
|
|
879
927
|
});
|
|
@@ -921,7 +969,7 @@ describe("renderSlackTranscript — replayable content-block preservation", () =
|
|
|
921
969
|
role: "assistant",
|
|
922
970
|
content: [
|
|
923
971
|
{ type: "thinking", thinking: "let me think", signature: "sig-abc" },
|
|
924
|
-
{ type: "text", text: "
|
|
972
|
+
{ type: "text", text: "here's the answer" },
|
|
925
973
|
],
|
|
926
974
|
},
|
|
927
975
|
]);
|
|
@@ -946,7 +994,7 @@ describe("renderSlackTranscript — replayable content-block preservation", () =
|
|
|
946
994
|
{
|
|
947
995
|
role: "assistant",
|
|
948
996
|
content: [
|
|
949
|
-
{ type: "text", text: "
|
|
997
|
+
{ type: "text", text: "doing a thing" },
|
|
950
998
|
{ type: "tool_use", id: "tu_A", name: "op", input: {} },
|
|
951
999
|
{ type: "tool_result", tool_use_id: "tu_A", content: "ok" },
|
|
952
1000
|
],
|
|
@@ -954,7 +1002,7 @@ describe("renderSlackTranscript — replayable content-block preservation", () =
|
|
|
954
1002
|
]);
|
|
955
1003
|
});
|
|
956
1004
|
|
|
957
|
-
test("[text, ui_surface] assistant row strips ui_surface — only
|
|
1005
|
+
test("[text, ui_surface] assistant row strips ui_surface — only content remains", () => {
|
|
958
1006
|
const base: RenderableSlackMessage = {
|
|
959
1007
|
...userMsg(TS_14_25, null, "reply body", { role: "assistant" }),
|
|
960
1008
|
contentBlocks: [
|
|
@@ -969,7 +1017,7 @@ describe("renderSlackTranscript — replayable content-block preservation", () =
|
|
|
969
1017
|
expect(out).toEqual([
|
|
970
1018
|
{
|
|
971
1019
|
role: "assistant",
|
|
972
|
-
content: [{ type: "text", text: "
|
|
1020
|
+
content: [{ type: "text", text: "reply body" }],
|
|
973
1021
|
},
|
|
974
1022
|
]);
|
|
975
1023
|
});
|
|
@@ -991,7 +1039,7 @@ describe("renderSlackTranscript — replayable content-block preservation", () =
|
|
|
991
1039
|
expect(out).toEqual([
|
|
992
1040
|
{
|
|
993
1041
|
role: "assistant",
|
|
994
|
-
content: [{ type: "text", text: "
|
|
1042
|
+
content: [{ type: "text", text: "web search" }],
|
|
995
1043
|
},
|
|
996
1044
|
]);
|
|
997
1045
|
});
|
|
@@ -1077,7 +1125,7 @@ describe("renderSlackTranscript — replayable content-block preservation", () =
|
|
|
1077
1125
|
content: [
|
|
1078
1126
|
{
|
|
1079
1127
|
type: "text",
|
|
1080
|
-
text: "
|
|
1128
|
+
text: "ran a web search [stripped non-replayable: server_tool_use(web_search), ui_surface]",
|
|
1081
1129
|
},
|
|
1082
1130
|
],
|
|
1083
1131
|
},
|
|
@@ -1155,7 +1203,7 @@ describe("renderSlackTranscript — orphan tool_use / tool_result filter", () =>
|
|
|
1155
1203
|
expect(out).toEqual([
|
|
1156
1204
|
{
|
|
1157
1205
|
role: "assistant",
|
|
1158
|
-
content: [{ type: "text", text: "
|
|
1206
|
+
content: [{ type: "text", text: "looking it up" }],
|
|
1159
1207
|
},
|
|
1160
1208
|
]);
|
|
1161
1209
|
});
|
|
@@ -1206,7 +1254,7 @@ describe("renderSlackTranscript — orphan tool_use / tool_result filter", () =>
|
|
|
1206
1254
|
{
|
|
1207
1255
|
role: "assistant",
|
|
1208
1256
|
content: [
|
|
1209
|
-
{ type: "text", text: "
|
|
1257
|
+
{ type: "text", text: "running op" },
|
|
1210
1258
|
{ type: "tool_use", id: "tu_paired", name: "op", input: { a: 1 } },
|
|
1211
1259
|
],
|
|
1212
1260
|
},
|
|
@@ -1296,7 +1344,7 @@ describe("renderSlackTranscript — orphan tool_use / tool_result filter", () =>
|
|
|
1296
1344
|
{
|
|
1297
1345
|
role: "assistant",
|
|
1298
1346
|
content: [
|
|
1299
|
-
{ type: "text", text: "
|
|
1347
|
+
{ type: "text", text: "running op" },
|
|
1300
1348
|
{ type: "tool_use", id: "tu_paired", name: "op", input: {} },
|
|
1301
1349
|
],
|
|
1302
1350
|
},
|
|
@@ -1308,7 +1356,7 @@ describe("renderSlackTranscript — orphan tool_use / tool_result filter", () =>
|
|
|
1308
1356
|
},
|
|
1309
1357
|
{
|
|
1310
1358
|
role: "assistant",
|
|
1311
|
-
content: [{ type: "text", text: "
|
|
1359
|
+
content: [{ type: "text", text: "looking" }],
|
|
1312
1360
|
},
|
|
1313
1361
|
{
|
|
1314
1362
|
role: "user",
|
|
@@ -1365,7 +1413,7 @@ describe("renderSlackTranscript — orphan tool_use / tool_result filter", () =>
|
|
|
1365
1413
|
filename: "doc.pdf",
|
|
1366
1414
|
},
|
|
1367
1415
|
},
|
|
1368
|
-
{ type: "text", text: "
|
|
1416
|
+
{ type: "text", text: "here you go" },
|
|
1369
1417
|
],
|
|
1370
1418
|
},
|
|
1371
1419
|
]);
|
|
@@ -95,6 +95,31 @@ export function parentAlias(channelTs: string): string {
|
|
|
95
95
|
return `M${hash.slice(0, 6)}`;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Trailing signature of a reaction or reaction-overflow line, both of
|
|
100
|
+
* which end with a `parentAlias` target and a closing bracket:
|
|
101
|
+
* `[... reacted 👍 to M1a2b3c]`, `[... removed 👍 from M1a2b3c]`,
|
|
102
|
+
* `[…and N more reactions to M1a2b3c]`. Regular message tag lines end
|
|
103
|
+
* with the message body, not with `]`, so they never match.
|
|
104
|
+
*/
|
|
105
|
+
const REACTION_TAG_LINE_SUFFIX = /M[0-9a-f]{6}\]$/;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Whether a rendered tag-line string was produced by the reaction or
|
|
109
|
+
* reaction-overflow code paths (`renderReaction` / the overflow trailer).
|
|
110
|
+
*
|
|
111
|
+
* Reaction lines already embed the actor attribution inline
|
|
112
|
+
* (`[11/14/23 14:28 @assistant reacted 👍 to M1a2b3c]`), so consumers
|
|
113
|
+
* that flatten the rendered transcript and re-apply role labels should
|
|
114
|
+
* skip these lines to avoid double-attribution.
|
|
115
|
+
*
|
|
116
|
+
* Co-located with `renderReaction` and `parentAlias` so the format
|
|
117
|
+
* knowledge lives with the functions that own the line shape.
|
|
118
|
+
*/
|
|
119
|
+
export function isReactionTagLine(text: string): boolean {
|
|
120
|
+
return REACTION_TAG_LINE_SUFFIX.test(text);
|
|
121
|
+
}
|
|
122
|
+
|
|
98
123
|
/**
|
|
99
124
|
* Format a Slack ts (`"1700000000.000100"`) as `MM/DD/YY HH:MM` (UTC).
|
|
100
125
|
*
|
|
@@ -140,8 +165,41 @@ function sortKey(msg: RenderableSlackMessage): number {
|
|
|
140
165
|
/**
|
|
141
166
|
* Render a single non-reaction message (post-upgrade or legacy) as one
|
|
142
167
|
* tagged line.
|
|
168
|
+
*
|
|
169
|
+
* Assistant rows emit their content verbatim with no bracketed wrapper.
|
|
170
|
+
* The `role` slot already conveys identity, and the assistant replies
|
|
171
|
+
* ~immediately after the triggering user message so the chronological
|
|
172
|
+
* adjacency carries the same information as a timestamp. Keeping a
|
|
173
|
+
* `[MM/DD/YY HH:MM]:` prefix on the assistant's own past turns caused
|
|
174
|
+
* the model to mimic the exact format as a literal prefix in new
|
|
175
|
+
* outbound Slack replies (`[04/22/26 21:25]: on it. ...`). Deleted
|
|
176
|
+
* assistant rows collapse to the short `[deleted]` sentinel so chronology
|
|
177
|
+
* is preserved without carrying a mimickable timestamp.
|
|
178
|
+
*
|
|
179
|
+
* Tradeoffs deliberately accepted by this simplification:
|
|
180
|
+
* - Thread arrows (`→ Mxxxxxx`) are dropped from assistant rows. In the
|
|
181
|
+
* common single-thread-at-a-time case, role alternation + chronological
|
|
182
|
+
* adjacency tells the model which user turn each assistant reply answers,
|
|
183
|
+
* so no attribution is lost. The degenerate case is a channel where the
|
|
184
|
+
* assistant is fielding two thread conversations in parallel and the
|
|
185
|
+
* model has to disambiguate which reply lands in which thread from the
|
|
186
|
+
* full chronological transcript — the bracketed arrow previously carried
|
|
187
|
+
* that signal and is now absent. The `<active_thread>` focus block
|
|
188
|
+
* (single thread by construction) is unaffected.
|
|
189
|
+
* - Edited assistant rows render only the latest content, not an edit
|
|
190
|
+
* marker. Edits are rare for the assistant and the latest content is the
|
|
191
|
+
* only replayable signal anyway.
|
|
192
|
+
*
|
|
193
|
+
* Any alternative "subtle" marker (e.g. an unbracketed `→ Mxxxxxx`) would
|
|
194
|
+
* reintroduce a consistent, mimickable prefix pattern — the very problem
|
|
195
|
+
* this function is designed to avoid — so we keep the content-only form.
|
|
143
196
|
*/
|
|
144
197
|
function renderMessage(msg: RenderableSlackMessage): string {
|
|
198
|
+
if (msg.role === "assistant") {
|
|
199
|
+
if (msg.metadata?.deletedAt !== undefined) return "[deleted]";
|
|
200
|
+
return msg.content;
|
|
201
|
+
}
|
|
202
|
+
|
|
145
203
|
const meta = msg.metadata;
|
|
146
204
|
const senderPart = msg.senderLabel ? ` ${msg.senderLabel}` : "";
|
|
147
205
|
if (!meta) {
|
|
@@ -8,8 +8,12 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Resolution order:
|
|
10
10
|
* 1. Explicit `reuse_existing` conversation action — highest precedence.
|
|
11
|
-
* 2. Binding-key reuse — for `continue_existing_conversation` channels
|
|
12
|
-
*
|
|
11
|
+
* 2. Binding-key reuse — for `continue_existing_conversation` channels:
|
|
12
|
+
* a. Inbound conversation lookup — checks the un-prefixed binding
|
|
13
|
+
* (sourceChannel, externalChatId) for a conversation created by
|
|
14
|
+
* the inbound message handler. Preferred for reply continuity.
|
|
15
|
+
* b. Notification-scoped binding — checks the `notification:`-prefixed
|
|
16
|
+
* binding for a prior notification conversation.
|
|
13
17
|
* 3. Default — creates a fresh conversation and, when binding context is
|
|
14
18
|
* present, upserts it into the external-conversation store for future reuse.
|
|
15
19
|
*/
|
|
@@ -78,8 +82,11 @@ export interface PairingOptions {
|
|
|
78
82
|
*
|
|
79
83
|
* Resolution precedence:
|
|
80
84
|
* 1. `options.conversationAction === "reuse_existing"` — reuse the explicit target.
|
|
81
|
-
* 2. `continue_existing_conversation` strategy with binding context
|
|
82
|
-
*
|
|
85
|
+
* 2. `continue_existing_conversation` strategy with binding context:
|
|
86
|
+
* a. Un-prefixed (inbound) binding — preferred for reply continuity so
|
|
87
|
+
* the user's replies include the notification in their history.
|
|
88
|
+
* b. `notification:`-prefixed binding — used when no inbound conversation
|
|
89
|
+
* exists yet (e.g. first notification before the user has messaged).
|
|
83
90
|
* 3. Create a new conversation (and upsert the binding when context is present).
|
|
84
91
|
*
|
|
85
92
|
* Invalid/stale targets at any level fall through to the next.
|
|
@@ -228,21 +235,73 @@ export async function pairDeliveryWithConversation(
|
|
|
228
235
|
bindingContext?.sourceChannel &&
|
|
229
236
|
bindingContext?.externalChatId
|
|
230
237
|
) {
|
|
231
|
-
//
|
|
232
|
-
//
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
238
|
+
// ── Step 1: Prefer the inbound conversation for reply continuity ──
|
|
239
|
+
//
|
|
240
|
+
// When the user has previously messaged in this channel, the inbound
|
|
241
|
+
// pipeline created a binding at the un-prefixed (sourceChannel,
|
|
242
|
+
// externalChatId) key. Posting to that conversation means the
|
|
243
|
+
// user's subsequent replies will include the notification in their
|
|
244
|
+
// conversation history — avoiding "split brain" where proactive
|
|
245
|
+
// messages live in one conversation and replies route to another.
|
|
246
|
+
//
|
|
247
|
+
// The source check is intentionally skipped here: the inbound
|
|
248
|
+
// conversation will have a different source (typically null) from
|
|
249
|
+
// notifications, but it is the correct target for reply continuity.
|
|
250
|
+
const inboundBinding = getBindingByChannelChat(
|
|
251
|
+
bindingContext.sourceChannel,
|
|
252
|
+
bindingContext.externalChatId,
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
if (inboundBinding) {
|
|
256
|
+
const inboundConversation = getConversation(
|
|
257
|
+
inboundBinding.conversationId,
|
|
241
258
|
);
|
|
242
259
|
|
|
243
|
-
|
|
260
|
+
if (inboundConversation) {
|
|
261
|
+
const message = await addMessage(
|
|
262
|
+
inboundConversation.id,
|
|
263
|
+
"assistant",
|
|
264
|
+
messageContent,
|
|
265
|
+
undefined,
|
|
266
|
+
{ skipIndexing: true },
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
log.info(
|
|
270
|
+
{
|
|
271
|
+
signalId: signal.signalId,
|
|
272
|
+
channel,
|
|
273
|
+
strategy,
|
|
274
|
+
conversationId: inboundConversation.id,
|
|
275
|
+
messageId: message.id,
|
|
276
|
+
bindingKey: `${bindingContext.sourceChannel}:${bindingContext.externalChatId}`,
|
|
277
|
+
},
|
|
278
|
+
"Appended notification to inbound conversation for reply continuity",
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
conversationId: inboundConversation.id,
|
|
283
|
+
messageId: message.id,
|
|
284
|
+
strategy,
|
|
285
|
+
createdNewConversation: false,
|
|
286
|
+
conversationFallbackUsed: false,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ── Step 2: Fall back to notification-scoped binding ──
|
|
292
|
+
//
|
|
293
|
+
// Before the user has ever messaged in this channel, there is no
|
|
294
|
+
// inbound binding. Check the notification-prefixed namespace for a
|
|
295
|
+
// prior notification conversation so successive deliveries still
|
|
296
|
+
// accumulate in the same thread.
|
|
297
|
+
const notificationBinding = getBindingByChannelChat(
|
|
298
|
+
notificationChannel(bindingContext.sourceChannel),
|
|
299
|
+
bindingContext.externalChatId,
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
if (notificationBinding) {
|
|
244
303
|
const boundConversation = getConversation(
|
|
245
|
-
|
|
304
|
+
notificationBinding.conversationId,
|
|
246
305
|
);
|
|
247
306
|
|
|
248
307
|
const effectiveSource =
|
|
@@ -272,7 +331,7 @@ export async function pairDeliveryWithConversation(
|
|
|
272
331
|
messageId: message.id,
|
|
273
332
|
bindingKey: `${bindingContext.sourceChannel}:${bindingContext.externalChatId}`,
|
|
274
333
|
},
|
|
275
|
-
"Reused bound conversation for channel destination",
|
|
334
|
+
"Reused bound notification conversation for channel destination",
|
|
276
335
|
);
|
|
277
336
|
|
|
278
337
|
return {
|
|
@@ -290,11 +349,11 @@ export async function pairDeliveryWithConversation(
|
|
|
290
349
|
{
|
|
291
350
|
signalId: signal.signalId,
|
|
292
351
|
channel,
|
|
293
|
-
boundConversationId:
|
|
352
|
+
boundConversationId: notificationBinding.conversationId,
|
|
294
353
|
boundConversationExists: !!boundConversation,
|
|
295
354
|
boundConversationSource: boundConversation?.source,
|
|
296
355
|
},
|
|
297
|
-
"Bound conversation stale or invalid — creating fresh conversation",
|
|
356
|
+
"Bound notification conversation stale or invalid — creating fresh conversation",
|
|
298
357
|
);
|
|
299
358
|
}
|
|
300
359
|
}
|
|
@@ -313,11 +313,6 @@ const TEMPLATES: Partial<Record<NotificationSourceEventName, CopyTemplate>> = {
|
|
|
313
313
|
body: str(payload.message, str(payload.label, "A reminder has fired")),
|
|
314
314
|
}),
|
|
315
315
|
|
|
316
|
-
"schedule.complete": (payload) => ({
|
|
317
|
-
title: "Schedule Complete",
|
|
318
|
-
body: `${str(payload.name, "A schedule")} has finished running`,
|
|
319
|
-
}),
|
|
320
|
-
|
|
321
316
|
"guardian.question": (payload) => {
|
|
322
317
|
const question = str(
|
|
323
318
|
payload.questionText,
|
|
@@ -146,7 +146,7 @@ function getConnectedChannels(): NotificationChannel[] {
|
|
|
146
146
|
// ── Public API ─────────────────────────────────────────────────────────
|
|
147
147
|
|
|
148
148
|
export interface EmitSignalParams<TEventName extends string = string> {
|
|
149
|
-
/** Free-form event name, e.g. 'schedule.notify', '
|
|
149
|
+
/** Free-form event name, e.g. 'schedule.notify', 'guardian.question'. */
|
|
150
150
|
sourceEventName: TEventName;
|
|
151
151
|
/** Source channel that produced the event — must be a registered channel. */
|
|
152
152
|
sourceChannel: NotificationSourceChannel;
|
|
@@ -41,7 +41,6 @@ export const NOTIFICATION_SOURCE_EVENT_NAMES = [
|
|
|
41
41
|
id: "schedule.notify",
|
|
42
42
|
description: "Scheduled notification triggered (one-shot or recurring)",
|
|
43
43
|
},
|
|
44
|
-
{ id: "schedule.complete", description: "Scheduled task finished running" },
|
|
45
44
|
{
|
|
46
45
|
id: "guardian.question",
|
|
47
46
|
description: "Guardian approval question requiring response",
|
|
@@ -191,7 +190,7 @@ export interface NotificationSignal<TEventName extends string = string> {
|
|
|
191
190
|
createdAt: number; // epoch ms
|
|
192
191
|
sourceChannel: NotificationSourceChannel; // see NOTIFICATION_SOURCE_CHANNELS registry
|
|
193
192
|
sourceContextId: string;
|
|
194
|
-
sourceEventName: TEventName; // free-form: 'reminder_fired', '
|
|
193
|
+
sourceEventName: TEventName; // free-form: 'reminder_fired', 'guardian_question', etc.
|
|
195
194
|
contextPayload: NotificationContextPayload<TEventName>;
|
|
196
195
|
attentionHints: AttentionHints;
|
|
197
196
|
/** Routing intent from the source (e.g. reminder). Controls post-decision channel enforcement. */
|
package/src/oauth/AGENTS.md
CHANGED
|
@@ -6,7 +6,7 @@ When introducing a new built-in OAuth integration (one that appears in `seed-pro
|
|
|
6
6
|
|
|
7
7
|
### 1. Seed the provider — `seed-providers.ts`
|
|
8
8
|
|
|
9
|
-
Add an entry to `PROVIDER_SEED_DATA`. Required fields: `provider`, `authorizeUrl`, `tokenExchangeUrl`, `defaultScopes`, `
|
|
9
|
+
Add an entry to `PROVIDER_SEED_DATA`. Required fields: `provider`, `authorizeUrl`, `tokenExchangeUrl`, `defaultScopes`, `displayLabel`, `description`, `dashboardUrl`, `clientIdPlaceholder`, `logoUrl`, and `injectionTemplates`. Optional: `availableScopes` — either a structured array of `{scope, description?}` objects or a URL string pointing to the provider's scope documentation. See existing entries for the full shape. The `provider` key must be snake_case and is used as the canonical identifier everywhere else.
|
|
10
10
|
|
|
11
11
|
If the provider will support managed mode, set `managedServiceConfigKey` to a slug matching the key you will add to `ServicesSchema` (e.g. `"acme-oauth"`).
|
|
12
12
|
|
|
@@ -38,7 +38,7 @@ function makeProviderRow(
|
|
|
38
38
|
userinfoUrl: null,
|
|
39
39
|
baseUrl: null,
|
|
40
40
|
defaultScopes: "[]",
|
|
41
|
-
|
|
41
|
+
availableScopes: null,
|
|
42
42
|
scopeSeparator: " ",
|
|
43
43
|
authorizeParams: null,
|
|
44
44
|
pingUrl: null,
|
|
@@ -48,6 +48,7 @@ function makeProviderRow(
|
|
|
48
48
|
revokeUrl: null,
|
|
49
49
|
revokeBodyTemplate: null,
|
|
50
50
|
managedServiceConfigKey: null,
|
|
51
|
+
managedServiceIsPaid: false,
|
|
51
52
|
displayLabel: null,
|
|
52
53
|
description: null,
|
|
53
54
|
dashboardUrl: null,
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
*
|
|
14
14
|
* The orchestrator handles:
|
|
15
15
|
* - Provider config resolution (from DB)
|
|
16
|
-
* - Scope policy enforcement
|
|
17
16
|
* - Building the OAuth2Config
|
|
18
17
|
* - Running the interactive or deferred flow
|
|
19
18
|
* - Storing tokens on completion
|
|
@@ -23,10 +22,9 @@
|
|
|
23
22
|
import type { TokenEndpointAuthMethod } from "../security/oauth2.js";
|
|
24
23
|
import { prepareOAuth2Flow, startOAuth2Flow } from "../security/oauth2.js";
|
|
25
24
|
import { getLogger } from "../util/logger.js";
|
|
26
|
-
import type { OAuthConnectResult
|
|
25
|
+
import type { OAuthConnectResult } from "./connect-types.js";
|
|
27
26
|
import { verifyIdentity } from "./identity-verifier.js";
|
|
28
27
|
import { getProvider } from "./oauth-store.js";
|
|
29
|
-
import { resolveScopes } from "./scope-policy.js";
|
|
30
28
|
import { storeOAuth2Tokens } from "./token-persistence.js";
|
|
31
29
|
|
|
32
30
|
const log = getLogger("oauth-connect-orchestrator");
|
|
@@ -52,7 +50,7 @@ function safeJsonParse<T>(value: string | null | undefined, fallback: T): T {
|
|
|
52
50
|
export interface OAuthConnectOptions {
|
|
53
51
|
/** Canonical service name (e.g. "google", "slack"). */
|
|
54
52
|
service: string;
|
|
55
|
-
/** Scopes to request
|
|
53
|
+
/** Scopes to request. When provided, used instead of the provider's default scopes. */
|
|
56
54
|
requestedScopes?: string[];
|
|
57
55
|
/** OAuth2 client ID (required). */
|
|
58
56
|
clientId: string;
|
|
@@ -122,18 +120,6 @@ export async function orchestrateOAuthConnect(
|
|
|
122
120
|
}
|
|
123
121
|
|
|
124
122
|
// Deserialize JSON fields from the DB row
|
|
125
|
-
const dbDefaultScopes = safeJsonParse<string[]>(
|
|
126
|
-
providerRow.defaultScopes,
|
|
127
|
-
[],
|
|
128
|
-
);
|
|
129
|
-
const dbScopePolicy = safeJsonParse<OAuthScopePolicy>(
|
|
130
|
-
providerRow.scopePolicy,
|
|
131
|
-
{
|
|
132
|
-
allowAdditionalScopes: false,
|
|
133
|
-
allowedOptionalScopes: [],
|
|
134
|
-
forbiddenScopes: [],
|
|
135
|
-
},
|
|
136
|
-
);
|
|
137
123
|
const dbAuthorizeParams = safeJsonParse<Record<string, string> | undefined>(
|
|
138
124
|
providerRow.authorizeParams,
|
|
139
125
|
undefined,
|
|
@@ -151,24 +137,12 @@ export async function orchestrateOAuthConnect(
|
|
|
151
137
|
options.callbackTransport ?? "loopback";
|
|
152
138
|
const loopbackPort = providerRow.loopbackPort ?? undefined;
|
|
153
139
|
|
|
154
|
-
// Resolve scopes
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const scopeResult = resolveScopes(scopeProfile, options.requestedScopes);
|
|
161
|
-
if (!scopeResult.ok) {
|
|
162
|
-
const guidance = scopeResult.allowedScopes
|
|
163
|
-
? ` Allowed scopes: ${scopeResult.allowedScopes.join(", ")}`
|
|
164
|
-
: "";
|
|
165
|
-
return {
|
|
166
|
-
success: false,
|
|
167
|
-
error: `${scopeResult.error}${guidance}`,
|
|
168
|
-
safeError: true,
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
const finalScopes = scopeResult.scopes;
|
|
140
|
+
// Resolve scopes: use requested scopes when provided, otherwise fall back
|
|
141
|
+
// to the provider's default scopes. No validation layer — the OAuth
|
|
142
|
+
// provider itself rejects invalid scopes.
|
|
143
|
+
const finalScopes = options.requestedScopes?.length
|
|
144
|
+
? options.requestedScopes
|
|
145
|
+
: safeJsonParse<string[]>(providerRow.defaultScopes, []);
|
|
172
146
|
|
|
173
147
|
if (!authorizeUrl) {
|
|
174
148
|
return {
|
|
@@ -12,18 +12,14 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
|
-
//
|
|
15
|
+
// Available scopes
|
|
16
16
|
// ---------------------------------------------------------------------------
|
|
17
17
|
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
allowedOptionalScopes: string[];
|
|
24
|
-
/** Scopes that must never be requested, regardless of other settings. */
|
|
25
|
-
forbiddenScopes: string[];
|
|
26
|
-
}
|
|
18
|
+
/** Informational scope metadata for the assistant. Either a structured list
|
|
19
|
+
* of scopes with optional descriptions, or a URL to the provider's scope docs. */
|
|
20
|
+
export type AvailableScopes =
|
|
21
|
+
| Array<{ scope: string; description?: string }>
|
|
22
|
+
| string;
|
|
27
23
|
|
|
28
24
|
// ---------------------------------------------------------------------------
|
|
29
25
|
// Connect result
|
|
@@ -21,6 +21,29 @@ import {
|
|
|
21
21
|
/** Sentinel client_id used for non-OAuth providers that don't have a real app. */
|
|
22
22
|
const MANUAL_TOKEN_CLIENT_ID = "manual-config";
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Return the secure-store key holding the primary access token for a
|
|
26
|
+
* manual-token provider, or null for OAuth providers whose tokens live at
|
|
27
|
+
* `oauth_connection/<id>/access_token`.
|
|
28
|
+
*
|
|
29
|
+
* Manual-token providers store their tokens under `credential/<provider>/<field>`
|
|
30
|
+
* via the generic credential store, so any code that validates tokens for these
|
|
31
|
+
* providers (e.g. credential-health checks) must resolve the path through here
|
|
32
|
+
* rather than assuming the OAuth access-token path.
|
|
33
|
+
*/
|
|
34
|
+
export function manualTokenAccessCredentialKey(
|
|
35
|
+
provider: string,
|
|
36
|
+
): string | null {
|
|
37
|
+
switch (provider) {
|
|
38
|
+
case "slack_channel":
|
|
39
|
+
return credentialKey("slack_channel", "bot_token");
|
|
40
|
+
case "telegram":
|
|
41
|
+
return credentialKey("telegram", "bot_token");
|
|
42
|
+
default:
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
24
47
|
/**
|
|
25
48
|
* Ensure an active oauth_connection row exists for the given manual-token
|
|
26
49
|
* provider. Creates the synthetic oauth_app row on first use.
|