@vellumai/assistant 0.8.10 → 0.8.11-staging.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bun.lock +62 -1
- package/docs/workspace-tools.md +196 -0
- package/examples/plugins/echo/README.md +3 -3
- package/knip.json +1 -0
- package/openapi.yaml +460 -128
- package/package.json +2 -1
- package/scripts/build-plugin-api.ts +299 -0
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +7 -0
- package/src/__tests__/agent-loop-compaction-events.test.ts +197 -0
- package/src/__tests__/agent-loop-exit-reason.test.ts +93 -96
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +2 -0
- package/src/__tests__/agent-loop-output-hooks.test.ts +274 -1
- package/src/__tests__/agent-loop-override-profile.test.ts +3 -0
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +4 -0
- package/src/__tests__/agent-loop-thinking.test.ts +4 -0
- package/src/__tests__/agent-loop.test.ts +578 -5
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -1
- package/src/__tests__/approval-cascade.test.ts +1 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +0 -2
- package/src/__tests__/btw-routes.test.ts +0 -1
- package/src/__tests__/build-persisted-content.test.ts +75 -1
- package/src/__tests__/catalog-install-normalize.test.ts +141 -0
- package/src/__tests__/ces-startup-timeout.test.ts +60 -0
- package/src/__tests__/compaction-events.test.ts +1 -0
- package/src/__tests__/config-managed-gemini-defaults.test.ts +2 -46
- package/src/__tests__/context-overflow-reducer.test.ts +264 -124
- package/src/__tests__/context-window-manager-overflow-rung.test.ts +351 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +13 -5
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +284 -455
- package/src/__tests__/conversation-agent-loop.test.ts +131 -551
- package/src/__tests__/conversation-app-control-instantiation.test.ts +13 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
- package/src/__tests__/conversation-fork-crud.test.ts +259 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-lifecycle.test.ts +257 -1
- package/src/__tests__/conversation-process-callsite.test.ts +1 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +38 -377
- package/src/__tests__/conversation-queue.test.ts +1 -39
- package/src/__tests__/conversation-runtime-assembly.test.ts +119 -8
- package/src/__tests__/conversation-skill-tools.test.ts +491 -5
- package/src/__tests__/conversation-slash-queue.test.ts +1 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
- package/src/__tests__/conversation-speed-override.test.ts +1 -0
- package/src/__tests__/conversation-store.test.ts +74 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +4 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +323 -0
- package/src/__tests__/conversation-tool-setup-tools-disabled.test.ts +34 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -0
- package/src/__tests__/corrected-target.test.ts +93 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +3 -5
- package/src/__tests__/credential-execution-tools.test.ts +23 -11
- package/src/__tests__/credential-security-invariants.test.ts +6 -1
- package/src/__tests__/db-schedule-syntax-migration.test.ts +80 -0
- package/src/__tests__/device-id.test.ts +70 -1
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +6 -40
- package/src/__tests__/empty-response-hook.test.ts +242 -66
- package/src/__tests__/external-plugin-loader.test.ts +0 -31
- package/src/__tests__/get-skill-detail-audit.test.ts +43 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +91 -0
- package/src/__tests__/history-repair-hook.test.ts +228 -3
- package/src/__tests__/host-app-control-proxy.test.ts +45 -0
- package/src/__tests__/host-browser-proxy.test.ts +254 -9
- package/src/__tests__/identity-routes.test.ts +1 -0
- package/src/__tests__/image-recovery-hook.test.ts +387 -0
- package/src/__tests__/injector-chain.test.ts +5 -4
- package/src/__tests__/injector-v3-suppression.test.ts +373 -47
- package/src/__tests__/intent-routing.test.ts +7 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +117 -15
- package/src/__tests__/notification-decision-strategy.test.ts +3 -3
- package/src/__tests__/oauth-store.test.ts +0 -85
- package/src/__tests__/{context-overflow-policy.test.ts → overflow-policy.test.ts} +1 -1
- package/src/__tests__/parallel-tool.benchmark.test.ts +4 -0
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +29 -9
- package/src/__tests__/persist-unsendable-image.test.ts +4 -4
- package/src/__tests__/persistence-secret-redaction.test.ts +78 -0
- package/src/__tests__/plugin-bootstrap.test.ts +82 -73
- package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
- package/src/__tests__/plugin-types.test.ts +0 -8
- package/src/__tests__/provider-catalog-visibility.test.ts +1 -9
- package/src/__tests__/prune-old-conversations-job.test.ts +99 -0
- package/src/__tests__/registry.test.ts +240 -1
- package/src/__tests__/require-fresh-approval.test.ts +3 -0
- package/src/__tests__/schedule-routes.test.ts +116 -1
- package/src/__tests__/schedule-store.test.ts +28 -0
- package/src/__tests__/schedule-tools.test.ts +94 -1
- package/src/__tests__/server-history-render.test.ts +39 -0
- package/src/__tests__/skill-projection-feature-flag.test.ts +13 -0
- package/src/__tests__/skill-projection.benchmark.test.ts +25 -7
- package/src/__tests__/skills.test.ts +202 -0
- package/src/__tests__/slim-skill-category.test.ts +195 -0
- package/src/__tests__/strip-memory-injections.test.ts +33 -39
- package/src/__tests__/test-support/tool-invocation-seed.ts +79 -0
- package/src/__tests__/title-generate-hook.test.ts +9 -7
- package/src/__tests__/tool-audit-listener.test.ts +264 -1
- package/src/__tests__/tool-error-hook.test.ts +4 -3
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +273 -0
- package/src/__tests__/tool-result-truncate-hook.test.ts +1 -0
- package/src/__tests__/tool-start-timestamp.test.ts +218 -0
- package/src/__tests__/tools-get-route.test.ts +202 -0
- package/src/__tests__/workspace-tool-loader.test.ts +319 -0
- package/src/__tests__/workspace-tools-watcher-flag.test.ts +70 -0
- package/src/agent/loop.ts +569 -319
- package/src/api/events/tool-result.ts +9 -0
- package/src/api/events/tool-use-start.ts +7 -0
- package/src/api/index.ts +10 -0
- package/src/api/responses/conversation-message.ts +135 -27
- package/src/api/responses/memory-v3-selection-log.ts +4 -4
- package/src/approvals/guardian-request-resolvers.ts +26 -0
- package/src/browser-session/backends/host-bridge.ts +29 -0
- package/src/browser-session/index.ts +1 -0
- package/src/browser-session/types.ts +5 -1
- package/src/cli/commands/__tests__/schedules.test.ts +62 -4
- package/src/cli/commands/__tests__/skills.test.ts +53 -0
- package/src/cli/commands/channel-verification-sessions.ts +6 -6
- package/src/cli/commands/inference-providers.ts +0 -8
- package/src/cli/commands/plugins.ts +2 -2
- package/src/cli/commands/schedules.ts +27 -4
- package/src/cli/commands/skills.ts +187 -146
- package/src/cli/commands/tools.ts +106 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +256 -328
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +6 -2
- package/src/cli/lib/__tests__/plugin-details.test.ts +10 -16
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +2 -2
- package/src/cli/lib/__tests__/search-plugins.test.ts +145 -240
- package/src/cli/lib/install-from-github.ts +187 -117
- package/src/cli/lib/plugin-catalog-cache.ts +9 -9
- package/src/cli/lib/plugin-details.ts +38 -68
- package/src/cli/lib/plugin-marketplace.ts +42 -14
- package/src/cli/lib/search-plugins.ts +29 -129
- package/src/cli/program.ts +2 -0
- package/src/config/bundled-skills/acp/SKILL.md +1 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -0
- package/src/config/bundled-skills/app-control/SKILL.md +1 -0
- package/src/config/bundled-skills/computer-use/SKILL.md +1 -0
- package/src/config/bundled-skills/contacts/SKILL.md +1 -0
- package/src/config/bundled-skills/document-editor/SKILL.md +1 -0
- package/src/config/bundled-skills/followups/SKILL.md +1 -0
- package/src/config/bundled-skills/image-studio/SKILL.md +1 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +1 -0
- package/src/config/bundled-skills/messaging/SKILL.md +1 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -0
- package/src/config/bundled-skills/playbooks/SKILL.md +1 -0
- package/src/config/bundled-skills/schedule/SKILL.md +1 -0
- package/src/config/bundled-skills/schedule/TOOLS.json +11 -3
- package/src/config/bundled-skills/sequences/SKILL.md +1 -0
- package/src/config/bundled-skills/settings/SKILL.md +1 -0
- package/src/config/bundled-skills/skill-management/SKILL.md +101 -1
- package/src/config/bundled-skills/subagent/SKILL.md +1 -0
- package/src/config/bundled-skills/transcribe/SKILL.md +1 -0
- package/src/config/env-registry.ts +23 -0
- package/src/config/feature-flag-registry.json +17 -81
- package/src/config/loader.ts +5 -22
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/compaction-logs.test.ts +56 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +0 -1
- package/src/config/schemas/__tests__/memory-v3.test.ts +61 -1
- package/src/config/schemas/compaction-logs.ts +79 -0
- package/src/config/schemas/memory-v2.ts +0 -8
- package/src/config/schemas/memory-v3.ts +104 -33
- package/src/config/seed-inference-profiles.ts +1 -1
- package/src/config/skills.ts +117 -47
- package/src/context/compactor.ts +11 -0
- package/src/context/strip-injections.ts +38 -4
- package/src/credential-execution/feature-gates.ts +0 -21
- package/src/credential-execution/startup-timeout.ts +32 -4
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +18 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +140 -91
- package/src/daemon/conversation-agent-loop.ts +123 -658
- package/src/daemon/conversation-error.ts +6 -33
- package/src/daemon/conversation-lifecycle.ts +1 -1
- package/src/daemon/conversation-runtime-assembly.ts +183 -22
- package/src/daemon/conversation-skill-tools.ts +137 -8
- package/src/daemon/conversation-slash.ts +0 -14
- package/src/daemon/conversation-store.ts +2 -19
- package/src/daemon/conversation-tool-setup.ts +87 -1
- package/src/daemon/conversation.ts +119 -50
- package/src/daemon/external-plugins-bootstrap.ts +36 -95
- package/src/daemon/handlers/config-channels.ts +11 -2
- package/src/daemon/handlers/shared.ts +9 -1
- package/src/daemon/handlers/skills.ts +10 -4
- package/src/daemon/host-app-control-proxy.ts +72 -57
- package/src/daemon/host-browser-proxy.ts +117 -22
- package/src/daemon/lifecycle.ts +34 -5
- package/src/daemon/message-protocol.ts +0 -7
- package/src/daemon/message-types/schedules.ts +1 -0
- package/src/daemon/message-types/skills.ts +17 -0
- package/src/daemon/providers-setup.ts +3 -0
- package/src/daemon/server.ts +3 -3
- package/src/daemon/tool-setup-types.ts +9 -3
- package/src/daemon/trust-context.ts +23 -0
- package/src/daemon/workspace-tools-watcher.ts +324 -0
- package/src/events/tool-audit-listener.ts +78 -16
- package/src/events/tool-metrics-listener.ts +2 -5
- package/src/memory/__tests__/compaction-log-writer-clickhouse.test.ts +227 -0
- package/src/memory/__tests__/conversation-queries.test.ts +176 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +20 -32
- package/src/memory/compaction-log-writer-clickhouse.ts +418 -0
- package/src/memory/conversation-crud.ts +134 -3
- package/src/memory/conversation-queries.ts +64 -7
- package/src/memory/db-init.ts +14 -0
- package/src/memory/embedding-backend.test.ts +130 -1
- package/src/memory/embedding-backend.ts +79 -106
- package/src/memory/embedding-gemini.ts +5 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +12 -0
- package/src/memory/graph/__tests__/handle-remember-v2.test.ts +19 -0
- package/src/memory/graph/conversation-graph-memory.ts +36 -25
- package/src/memory/graph/tool-handlers.ts +3 -0
- package/src/memory/job-handlers/cleanup.ts +3 -1
- package/src/memory/jobs-store.ts +0 -28
- package/src/memory/jobs-worker.ts +10 -22
- package/src/memory/memory-marker.ts +29 -0
- package/src/memory/memory-retrospective-startup-cleanup.ts +1 -1
- package/src/memory/migrations/268-add-memory-v3-selections.ts +6 -0
- package/src/memory/migrations/270-schedule-description.ts +36 -0
- package/src/memory/migrations/275-tool-invocations-add-skill-id.test.ts +81 -0
- package/src/memory/migrations/275-tool-invocations-add-skill-id.ts +20 -0
- package/src/memory/migrations/276-tool-invocations-created-at-id-index.test.ts +68 -0
- package/src/memory/migrations/276-tool-invocations-created-at-id-index.ts +20 -0
- package/src/memory/migrations/277-add-memory-v3-ever-injected.ts +29 -0
- package/src/memory/migrations/278-tool-invocations-telemetry-columns.test.ts +96 -0
- package/src/memory/migrations/278-tool-invocations-telemetry-columns.ts +39 -0
- package/src/memory/migrations/279-create-skill-loaded-events.test.ts +84 -0
- package/src/memory/migrations/279-create-skill-loaded-events.ts +26 -0
- package/src/memory/migrations/280-conversations-surfaced-at.test.ts +88 -0
- package/src/memory/migrations/280-conversations-surfaced-at.ts +24 -0
- package/src/memory/migrations/index.ts +10 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +16 -0
- package/src/memory/schema/infrastructure.ts +26 -0
- package/src/memory/skill-loaded-events-store.test.ts +160 -0
- package/src/memory/skill-loaded-events-store.ts +95 -0
- package/src/memory/tool-executed-events-store.test.ts +219 -0
- package/src/memory/tool-executed-events-store.ts +102 -0
- package/src/memory/tool-usage-store.ts +15 -3
- package/src/memory/v2/__tests__/consolidation-job.test.ts +117 -12
- package/src/memory/v2/__tests__/consolidation-prompt-flag-gating-guard.test.ts +189 -0
- package/src/memory/v2/__tests__/injected-block-slugs.test.ts +90 -0
- package/src/memory/v2/__tests__/page-store.test.ts +33 -0
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +88 -15
- package/src/memory/v2/activation-store.ts +50 -1
- package/src/memory/v2/consolidation-job.ts +113 -29
- package/src/memory/v2/injected-block-slugs.ts +79 -0
- package/src/memory/v2/injection.ts +6 -1
- package/src/memory/v2/prompts/consolidation.ts +414 -13
- package/src/memory/v2/router.ts +2 -28
- package/src/memory/v2/static-context.ts +1 -1
- package/src/memory/v2/types.ts +16 -0
- package/src/notifications/__tests__/copy-composer.test.ts +244 -0
- package/src/notifications/access-request-copy.ts +298 -0
- package/src/notifications/adapters/slack.ts +3 -3
- package/src/notifications/adapters/telegram.ts +2 -1
- package/src/notifications/copy-composer.ts +49 -267
- package/src/notifications/decision-engine.ts +16 -35
- package/src/notifications/home-feed-side-effect.ts +1 -6
- package/src/oauth/oauth-store.ts +0 -9
- package/src/permissions/checker.test.ts +83 -1
- package/src/permissions/checker.ts +25 -2
- package/src/permissions/gateway-threshold-reader.test.ts +182 -0
- package/src/permissions/gateway-threshold-reader.ts +80 -0
- package/src/platform/client.ts +1 -3
- package/src/platform/feature-gate.ts +3 -12
- package/src/plugin-api/constants.ts +4 -2
- package/src/plugin-api/index.ts +63 -11
- package/src/plugin-api/types.ts +236 -71
- package/src/plugins/defaults/compaction/compact.ts +66 -2
- package/src/plugins/defaults/compaction/context-overflow-reducer.ts +240 -32
- package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
- package/src/{daemon/context-overflow-policy.ts → plugins/defaults/compaction/overflow-policy.ts} +1 -1
- package/src/plugins/defaults/compaction/window-manager.ts +303 -1
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +173 -0
- package/src/plugins/defaults/empty-response/hooks/stop.ts +11 -115
- package/src/plugins/defaults/empty-response/nudge-state-store.ts +46 -0
- package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +50 -0
- package/src/plugins/defaults/history-repair/hooks/stop.ts +22 -0
- package/src/plugins/defaults/history-repair/repair-state-store.ts +51 -0
- package/src/plugins/defaults/history-repair/terminal.ts +39 -2
- package/src/plugins/defaults/image-recovery/detect.ts +25 -0
- package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +73 -0
- package/src/plugins/defaults/image-recovery/hooks/stop.ts +22 -0
- package/src/plugins/defaults/image-recovery/image-recovery-state-store.ts +48 -0
- package/src/plugins/defaults/image-recovery/package.json +14 -0
- package/src/{daemon/persist-unsendable-image.ts → plugins/defaults/image-recovery/recover.ts} +67 -14
- package/src/plugins/defaults/index.ts +71 -5
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +76 -112
- package/src/plugins/defaults/memory-retrieval/hooks/{user-prompt-submit-temp.ts → user-prompt-submit.ts} +83 -74
- package/src/plugins/defaults/memory-retrieval/injector-chain.ts +14 -8
- package/src/plugins/defaults/memory-retrieval/injectors.ts +2 -18
- package/src/plugins/defaults/memory-retrieval/package.json +14 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1157 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +683 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +161 -140
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +160 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +335 -316
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +145 -53
- package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +38 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +18 -8
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +112 -71
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +192 -51
- package/src/plugins/defaults/memory-v3-shadow/__tests__/types.test.ts +4 -16
- package/src/plugins/defaults/memory-v3-shadow/card.test.ts +173 -0
- package/src/plugins/defaults/memory-v3-shadow/card.ts +116 -0
- package/src/plugins/defaults/memory-v3-shadow/core-set.test.ts +104 -0
- package/src/plugins/defaults/memory-v3-shadow/core-set.ts +59 -0
- package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.test.ts +305 -0
- package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.ts +278 -0
- package/src/plugins/defaults/memory-v3-shadow/hot-set.test.ts +138 -0
- package/src/plugins/defaults/memory-v3-shadow/hot-set.ts +85 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +331 -24
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +119 -13
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +169 -114
- package/src/plugins/defaults/memory-v3-shadow/page-content.ts +47 -16
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +144 -66
- package/src/plugins/defaults/memory-v3-shadow/prune.test.ts +758 -0
- package/src/plugins/defaults/memory-v3-shadow/prune.ts +471 -0
- package/src/plugins/defaults/memory-v3-shadow/render-injection.ts +68 -16
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +19 -12
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +96 -43
- package/src/plugins/defaults/memory-v3-shadow/types.ts +34 -17
- package/src/plugins/defaults/title-generate/hooks/stop.ts +9 -11
- package/src/plugins/pipeline.ts +8 -5
- package/src/plugins/types.ts +5 -51
- package/src/providers/cache-control.ts +26 -0
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +1 -2
- package/src/providers/model-catalog.ts +13 -1
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +147 -0
- package/src/providers/openai/chat-completions-provider.ts +46 -0
- package/src/providers/openai/responses-provider.ts +45 -0
- package/src/providers/registry.ts +0 -8
- package/src/runtime/__tests__/agent-wake.test.ts +0 -1
- package/src/runtime/agent-wake.ts +13 -0
- package/src/runtime/routes/__tests__/acp-routes.test.ts +151 -0
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +12 -50
- package/src/runtime/routes/__tests__/conversation-surface-routes.test.ts +322 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +0 -62
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +31 -11
- package/src/runtime/routes/acp-routes.test.ts +106 -0
- package/src/runtime/routes/acp-routes.ts +248 -2
- package/src/runtime/routes/browser-tabs-routes.ts +1 -1
- package/src/runtime/routes/channel-verification-routes.ts +14 -5
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +0 -14
- package/src/runtime/routes/consolidation-routes.ts +6 -82
- package/src/runtime/routes/conversation-list-routes.ts +6 -0
- package/src/runtime/routes/conversation-management-routes.ts +71 -0
- package/src/runtime/routes/identity-routes.ts +8 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
- package/src/runtime/routes/inference-provider-connection-routes.ts +0 -45
- package/src/runtime/routes/plugins-routes.ts +34 -45
- package/src/runtime/routes/schedule-routes.ts +43 -5
- package/src/runtime/routes/settings-routes.ts +140 -15
- package/src/runtime/routes/skills-routes.ts +18 -6
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +140 -0
- package/src/runtime/services/conversation-serializer.ts +38 -1
- package/src/runtime/verification-outbound-actions.ts +147 -2
- package/src/runtime/verification-templates.ts +29 -3
- package/src/schedule/schedule-store.ts +19 -0
- package/src/skills/catalog-install.ts +77 -13
- package/src/tasks/task-scheduler.ts +1 -0
- package/src/telemetry/types.ts +66 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +542 -13
- package/src/telemetry/usage-telemetry-reporter.ts +213 -20
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +49 -2
- package/src/tools/browser/__tests__/browser-status.test.ts +29 -5
- package/src/tools/browser/browser-execution.ts +27 -9
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +380 -4
- package/src/tools/browser/cdp-client/__tests__/host-bridge-cdp-client.test.ts +107 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +6 -1
- package/src/tools/browser/cdp-client/factory.ts +217 -17
- package/src/tools/browser/cdp-client/host-bridge-cdp-client.ts +67 -0
- package/src/tools/browser/cdp-client/types.ts +22 -2
- package/src/tools/credential-execution/make-authenticated-request.ts +2 -1
- package/src/tools/credential-execution/manage-secure-command-tool.ts +169 -164
- package/src/tools/credential-execution/run-authenticated-command.ts +2 -1
- package/src/tools/executor.ts +39 -7
- package/src/tools/registry.ts +387 -5
- package/src/tools/schedule/create.ts +16 -0
- package/src/tools/schedule/list.ts +12 -4
- package/src/tools/schedule/update.ts +12 -0
- package/src/tools/skills/load.ts +11 -6
- package/src/tools/terminal/safe-env.ts +2 -0
- package/src/tools/types.ts +65 -9
- package/src/tools/workspace-tools/loader.ts +673 -0
- package/src/usage/attribution.ts +28 -0
- package/src/util/device-id.ts +17 -3
- package/src/util/platform.ts +16 -0
- package/tsconfig.plugin-api.json +13 -0
- package/src/__tests__/plugin-external-api.test.ts +0 -68
- package/src/__tests__/plugin-skill-contribution.test.ts +0 -355
- package/src/daemon/message-types/browser.ts +0 -10
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/eval-turns.json +0 -36
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/live-turns.json +0 -37
- package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-eviction.test.ts +0 -106
- package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-skeleton.test.ts +0 -44
- package/src/plugins/defaults/memory-v3-shadow/working-set.ts +0 -91
- package/src/plugins/external-api.ts +0 -114
- package/src/plugins/plugin-skill-contributions.ts +0 -292
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from "../../config/env.js";
|
|
16
16
|
import { loadRawConfig, saveRawConfig } from "../../config/loader.js";
|
|
17
17
|
import { loadSkillCatalog } from "../../config/skills.js";
|
|
18
|
+
import { findConversation } from "../../daemon/conversation-registry.js";
|
|
18
19
|
import {
|
|
19
20
|
computeGatewayTarget,
|
|
20
21
|
getIngressConfigResult,
|
|
@@ -41,7 +42,7 @@ import {
|
|
|
41
42
|
type ManifestOverride,
|
|
42
43
|
resolveExecutionTarget,
|
|
43
44
|
} from "../../tools/execution-target.js";
|
|
44
|
-
import { getAllTools, getTool } from "../../tools/registry.js";
|
|
45
|
+
import { getAllTools, getTool, getToolOwner } from "../../tools/registry.js";
|
|
45
46
|
import {
|
|
46
47
|
ACTIVITY_SKIP_SET,
|
|
47
48
|
injectActivityField,
|
|
@@ -80,10 +81,7 @@ function handleVoiceConfigUpdate({ body = {} }: RouteHandlerArgs) {
|
|
|
80
81
|
// Avatar generation
|
|
81
82
|
// ---------------------------------------------------------------------------
|
|
82
83
|
|
|
83
|
-
async function handleGenerateAvatar({
|
|
84
|
-
body = {},
|
|
85
|
-
headers,
|
|
86
|
-
}: RouteHandlerArgs) {
|
|
84
|
+
async function handleGenerateAvatar({ body = {}, headers }: RouteHandlerArgs) {
|
|
87
85
|
const { description } = body as { description?: string };
|
|
88
86
|
if (!description?.trim()) {
|
|
89
87
|
throw new BadRequestError("Description is required.");
|
|
@@ -380,14 +378,32 @@ function resolveManifestOverride(
|
|
|
380
378
|
return undefined;
|
|
381
379
|
}
|
|
382
380
|
|
|
383
|
-
|
|
381
|
+
type SchemaShape = {
|
|
382
|
+
type: string;
|
|
383
|
+
properties?: Record<string, unknown>;
|
|
384
|
+
required?: string[];
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
interface ToolNamesListResponse {
|
|
388
|
+
names: string[];
|
|
389
|
+
schemas: Record<string, SchemaShape>;
|
|
390
|
+
tools: ToolListEntry[];
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Tool inventory. With no `conversationId`, reports every tool in the
|
|
395
|
+
* global registry (plus skill-manifest tools not yet loaded, for the
|
|
396
|
+
* permission-simulator catalog). With a `conversationId`, scopes the
|
|
397
|
+
* inventory to the tools available to that conversation as of its most
|
|
398
|
+
* recent turn — see {@link handleConversationToolList}.
|
|
399
|
+
*/
|
|
400
|
+
function handleToolNamesList(conversationId?: string): ToolNamesListResponse {
|
|
401
|
+
if (conversationId) {
|
|
402
|
+
return handleConversationToolList(conversationId);
|
|
403
|
+
}
|
|
404
|
+
|
|
384
405
|
const tools = getAllTools();
|
|
385
406
|
const nameSet = new Set(tools.map((t) => t.name));
|
|
386
|
-
type SchemaShape = {
|
|
387
|
-
type: string;
|
|
388
|
-
properties?: Record<string, unknown>;
|
|
389
|
-
required?: string[];
|
|
390
|
-
};
|
|
391
407
|
const schemas: Record<string, SchemaShape> = {};
|
|
392
408
|
|
|
393
409
|
const rawDefs: ToolDefinition[] = tools;
|
|
@@ -419,7 +435,89 @@ function handleToolNamesList() {
|
|
|
419
435
|
}
|
|
420
436
|
|
|
421
437
|
const names = Array.from(nameSet).sort((a, b) => a.localeCompare(b));
|
|
422
|
-
return { names, schemas };
|
|
438
|
+
return { names, schemas, tools: buildRegisteredToolEntries() };
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Scope the tool inventory to a single conversation. Conversations gain
|
|
443
|
+
* tools over their lifecycle (skill loads, MCP reloads), so the global
|
|
444
|
+
* registry over-reports what a given conversation can actually call. We
|
|
445
|
+
* read the conversation's turn snapshot (`getRegisteredToolNames()`) — a
|
|
446
|
+
* pure read that does not re-run the side-effecting `resolveTools`
|
|
447
|
+
* callback — and resolve each name's metadata/schema from the registry.
|
|
448
|
+
*/
|
|
449
|
+
function handleConversationToolList(
|
|
450
|
+
conversationId: string,
|
|
451
|
+
): ToolNamesListResponse {
|
|
452
|
+
const conversation = findConversation(conversationId);
|
|
453
|
+
if (!conversation) {
|
|
454
|
+
throw new NotFoundError(
|
|
455
|
+
`No active conversation "${conversationId}". Run 'assistant conversations list' to see active conversations.`,
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const names = Array.from(conversation.getRegisteredToolNames()).sort((a, b) =>
|
|
460
|
+
a.localeCompare(b),
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
const schemaByName = new Map<string, SchemaShape>(
|
|
464
|
+
injectActivityField(getAllTools(), ACTIVITY_SKIP_SET).map((d) => [
|
|
465
|
+
d.name,
|
|
466
|
+
d.input_schema as SchemaShape,
|
|
467
|
+
]),
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
const schemas: Record<string, SchemaShape> = {};
|
|
471
|
+
const tools: ToolListEntry[] = [];
|
|
472
|
+
for (const name of names) {
|
|
473
|
+
const schema = schemaByName.get(name);
|
|
474
|
+
if (schema) schemas[name] = schema;
|
|
475
|
+
tools.push(toolEntryForName(name));
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return { names, schemas, tools };
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
interface ToolListEntry {
|
|
482
|
+
name: string;
|
|
483
|
+
description: string;
|
|
484
|
+
riskLevel: string;
|
|
485
|
+
category: string;
|
|
486
|
+
/** Tool origin: "core" for built-ins, otherwise "<kind>:<id>" (e.g. "plugin:echo"). */
|
|
487
|
+
source: string;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Build a catalog entry for one tool name, reading metadata and ownership
|
|
492
|
+
* from the registry (the single source of truth) rather than off any
|
|
493
|
+
* caller-supplied object, so `source` cannot be spoofed by a manifest
|
|
494
|
+
* field. `source` is `core` for built-ins, `<kind>:<id>` for an owned tool
|
|
495
|
+
* (e.g. `plugin:echo`), and `unknown` for a name no longer in the registry
|
|
496
|
+
* (e.g. a conversation snapshot referencing a since-unloaded skill tool).
|
|
497
|
+
*/
|
|
498
|
+
function toolEntryForName(name: string): ToolListEntry {
|
|
499
|
+
const tool = getTool(name);
|
|
500
|
+
const owner = getToolOwner(name);
|
|
501
|
+
return {
|
|
502
|
+
name,
|
|
503
|
+
description: tool?.description ?? "",
|
|
504
|
+
riskLevel: tool?.defaultRiskLevel ?? "unknown",
|
|
505
|
+
category: tool?.category ?? "",
|
|
506
|
+
source: owner ? `${owner.kind}:${owner.id}` : tool ? "core" : "unknown",
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Build the registered-tool inventory with the metadata a catalog view
|
|
512
|
+
* needs: description, author-asserted risk band, category, and the
|
|
513
|
+
* extension that contributed the tool. Sorted by name for stable output.
|
|
514
|
+
* Covers only tools loaded into the registry; skill tools whose manifests
|
|
515
|
+
* are present but not yet loaded appear in `names`/`schemas` but not here.
|
|
516
|
+
*/
|
|
517
|
+
function buildRegisteredToolEntries(): ToolListEntry[] {
|
|
518
|
+
return getAllTools()
|
|
519
|
+
.map((tool) => toolEntryForName(tool.name))
|
|
520
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
423
521
|
}
|
|
424
522
|
|
|
425
523
|
async function handleToolPermissionSimulate({ body = {} }: RouteHandlerArgs) {
|
|
@@ -766,11 +864,38 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
766
864
|
requiredScopes: ["settings.read"],
|
|
767
865
|
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
768
866
|
},
|
|
769
|
-
summary: "List tools",
|
|
867
|
+
summary: "List registered tools with metadata and schemas",
|
|
770
868
|
description:
|
|
771
|
-
"Return
|
|
869
|
+
"Return registered tools. Without `conversationId`, returns every tool in the global registry; `tools` carries per-tool metadata (description, author-asserted risk level, category, and contributing source: core, skill, plugin, or MCP server) and `names`/`schemas` additionally cover skill tools whose manifests are present but not yet loaded, for the permission-simulator catalog. With `conversationId`, scopes the result to the tools available to that conversation as of its most recent turn (including skill/MCP tools registered over its lifecycle); 404 if no such conversation is active.",
|
|
772
870
|
tags: ["tools"],
|
|
773
|
-
|
|
871
|
+
queryParams: [
|
|
872
|
+
{
|
|
873
|
+
name: "conversationId",
|
|
874
|
+
type: "string",
|
|
875
|
+
required: false,
|
|
876
|
+
description:
|
|
877
|
+
"When set, scope the tool inventory to this conversation's most recent turn instead of the global registry.",
|
|
878
|
+
},
|
|
879
|
+
],
|
|
880
|
+
responseBody: z.object({
|
|
881
|
+
names: z.array(z.string()),
|
|
882
|
+
schemas: z.record(z.string(), z.record(z.string(), z.unknown())),
|
|
883
|
+
tools: z.array(
|
|
884
|
+
z.object({
|
|
885
|
+
name: z.string(),
|
|
886
|
+
description: z.string(),
|
|
887
|
+
riskLevel: z.string(),
|
|
888
|
+
category: z.string(),
|
|
889
|
+
source: z
|
|
890
|
+
.string()
|
|
891
|
+
.describe(
|
|
892
|
+
'Tool origin: "core" for built-ins, otherwise "<kind>:<id>" (e.g. "plugin:echo", "skill:my-skill", "mcp:server").',
|
|
893
|
+
),
|
|
894
|
+
}),
|
|
895
|
+
),
|
|
896
|
+
}),
|
|
897
|
+
handler: ({ queryParams }) =>
|
|
898
|
+
handleToolNamesList(queryParams?.conversationId),
|
|
774
899
|
},
|
|
775
900
|
{
|
|
776
901
|
operationId: "tools_simulate_permission_post",
|
|
@@ -49,10 +49,22 @@ const slimSkillBase = {
|
|
|
49
49
|
category: z.string(),
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
+
// Extension that ships a skill, mirroring the tool registry's OwnerInfo.
|
|
53
|
+
// Plugin-resident skills carry `{ kind: "plugin", id: <plugin dir name> }` so
|
|
54
|
+
// clients can attribute them instead of collapsing to their kind/origin.
|
|
55
|
+
const skillOwnerSchema = z
|
|
56
|
+
.object({
|
|
57
|
+
kind: z.enum(["skill", "mcp", "plugin", "workspace"]),
|
|
58
|
+
id: z.string(),
|
|
59
|
+
})
|
|
60
|
+
.optional();
|
|
61
|
+
|
|
62
|
+
const slimSkillBaseWithOwner = { ...slimSkillBase, owner: skillOwnerSchema };
|
|
63
|
+
|
|
52
64
|
const slimSkillSchema = z.discriminatedUnion("origin", [
|
|
53
|
-
z.object({ ...
|
|
65
|
+
z.object({ ...slimSkillBaseWithOwner, origin: z.literal("vellum") }),
|
|
54
66
|
z.object({
|
|
55
|
-
...
|
|
67
|
+
...slimSkillBaseWithOwner,
|
|
56
68
|
origin: z.literal("clawhub"),
|
|
57
69
|
slug: z.string(),
|
|
58
70
|
author: z.string(),
|
|
@@ -63,18 +75,18 @@ const slimSkillSchema = z.discriminatedUnion("origin", [
|
|
|
63
75
|
version: z.string(),
|
|
64
76
|
}),
|
|
65
77
|
z.object({
|
|
66
|
-
...
|
|
78
|
+
...slimSkillBaseWithOwner,
|
|
67
79
|
origin: z.literal("skillssh"),
|
|
68
80
|
slug: z.string(),
|
|
69
81
|
sourceRepo: z.string(),
|
|
70
82
|
installs: z.number(),
|
|
71
83
|
audit: z.record(z.string(), partnerAuditSchema).optional(),
|
|
72
84
|
}),
|
|
73
|
-
z.object({ ...
|
|
85
|
+
z.object({ ...slimSkillBaseWithOwner, origin: z.literal("custom") }),
|
|
74
86
|
]);
|
|
75
87
|
|
|
76
88
|
const skillDetailSchema = z.discriminatedUnion("origin", [
|
|
77
|
-
z.object({ ...
|
|
89
|
+
z.object({ ...slimSkillBaseWithOwner, origin: z.literal("vellum") }),
|
|
78
90
|
z.object({
|
|
79
91
|
...slimSkillBase,
|
|
80
92
|
origin: z.literal("clawhub"),
|
|
@@ -120,7 +132,7 @@ const skillDetailSchema = z.discriminatedUnion("origin", [
|
|
|
120
132
|
installs: z.number(),
|
|
121
133
|
audit: z.record(z.string(), partnerAuditSchema).optional(),
|
|
122
134
|
}),
|
|
123
|
-
z.object({ ...
|
|
135
|
+
z.object({ ...slimSkillBaseWithOwner, origin: z.literal("custom") }),
|
|
124
136
|
]);
|
|
125
137
|
|
|
126
138
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for `serializeConversationSummary` — specifically the wire-level
|
|
3
|
+
* `groupId` normalization for surfaced conversations.
|
|
4
|
+
*
|
|
5
|
+
* Surfaced rows (`surfaced_at IS NOT NULL`) must serialize with
|
|
6
|
+
* `groupId: "system:all"` when their persisted group is the system
|
|
7
|
+
* Background/Scheduled bucket (or null), so legacy clients that bucket purely
|
|
8
|
+
* by `groupId` (macOS Swift app) render them in Recents without decoding
|
|
9
|
+
* `surfacedAt`. The persisted DB value is untouched — clearing `surfaced_at`
|
|
10
|
+
* (demotion) makes serialization return the original group again.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, expect, test } from "bun:test";
|
|
14
|
+
|
|
15
|
+
import type { ConversationRow } from "../../../memory/conversation-crud.js";
|
|
16
|
+
import { serializeConversationSummary } from "../conversation-serializer.js";
|
|
17
|
+
|
|
18
|
+
function makeConversationRow(
|
|
19
|
+
overrides: Partial<ConversationRow> = {},
|
|
20
|
+
): ConversationRow {
|
|
21
|
+
return {
|
|
22
|
+
id: "conv-123",
|
|
23
|
+
title: "Background run",
|
|
24
|
+
createdAt: 1000,
|
|
25
|
+
updatedAt: 2000,
|
|
26
|
+
totalInputTokens: 0,
|
|
27
|
+
totalOutputTokens: 0,
|
|
28
|
+
totalEstimatedCost: 0,
|
|
29
|
+
contextSummary: null,
|
|
30
|
+
contextCompactedMessageCount: 0,
|
|
31
|
+
contextCompactedAt: null,
|
|
32
|
+
historyStrippedAt: null,
|
|
33
|
+
slackContextCompactionWatermarkTs: null,
|
|
34
|
+
slackContextCompactionWatermarkAt: null,
|
|
35
|
+
conversationType: "background",
|
|
36
|
+
source: "task",
|
|
37
|
+
memoryScopeId: "default",
|
|
38
|
+
originChannel: null,
|
|
39
|
+
originInterface: null,
|
|
40
|
+
forkParentConversationId: null,
|
|
41
|
+
forkParentMessageId: null,
|
|
42
|
+
isAutoTitle: 0,
|
|
43
|
+
scheduleJobId: null,
|
|
44
|
+
lastMessageAt: 3000,
|
|
45
|
+
archivedAt: null,
|
|
46
|
+
surfacedAt: null,
|
|
47
|
+
inferenceProfile: null,
|
|
48
|
+
inferenceProfileSessionId: null,
|
|
49
|
+
inferenceProfileExpiresAt: null,
|
|
50
|
+
lastNotifiedInferenceProfile: null,
|
|
51
|
+
...overrides,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function serialize(
|
|
56
|
+
conversation: ConversationRow,
|
|
57
|
+
groupId: string | null = null,
|
|
58
|
+
isPinned = false,
|
|
59
|
+
) {
|
|
60
|
+
return serializeConversationSummary({
|
|
61
|
+
conversation,
|
|
62
|
+
displayMeta: { displayOrder: null, isPinned, groupId },
|
|
63
|
+
parentCache: new Map(),
|
|
64
|
+
isProcessing: false,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
describe("serializeConversationSummary · surfaced groupId normalization", () => {
|
|
69
|
+
test("surfaced row with persisted system:background serializes as system:all", () => {
|
|
70
|
+
const summary = serialize(
|
|
71
|
+
makeConversationRow({ surfacedAt: 1704067200000 }),
|
|
72
|
+
"system:background",
|
|
73
|
+
);
|
|
74
|
+
expect(summary.groupId).toBe("system:all");
|
|
75
|
+
expect(summary.surfacedAt).toBe(1704067200000);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("surfaced row with persisted system:scheduled serializes as system:all", () => {
|
|
79
|
+
const summary = serialize(
|
|
80
|
+
makeConversationRow({
|
|
81
|
+
conversationType: "scheduled",
|
|
82
|
+
source: "schedule",
|
|
83
|
+
surfacedAt: 1704067200000,
|
|
84
|
+
}),
|
|
85
|
+
"system:scheduled",
|
|
86
|
+
);
|
|
87
|
+
expect(summary.groupId).toBe("system:all");
|
|
88
|
+
expect(summary.surfacedAt).toBe(1704067200000);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("surfaced row with null persisted group serializes as system:all", () => {
|
|
92
|
+
// Legacy clients re-derive a null groupId into Background/Scheduled from
|
|
93
|
+
// `source`, so the surfaced normalization must cover the null case too.
|
|
94
|
+
const summary = serialize(
|
|
95
|
+
makeConversationRow({ surfacedAt: 1704067200000 }),
|
|
96
|
+
null,
|
|
97
|
+
);
|
|
98
|
+
expect(summary.groupId).toBe("system:all");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("after demotion (surfaced_at cleared) the original group comes back", () => {
|
|
102
|
+
const summary = serialize(
|
|
103
|
+
makeConversationRow({ surfacedAt: null }),
|
|
104
|
+
"system:background",
|
|
105
|
+
);
|
|
106
|
+
expect(summary.groupId).toBe("system:background");
|
|
107
|
+
expect(summary.surfacedAt).toBeUndefined();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("a custom group wins over surfacing", () => {
|
|
111
|
+
// Mirrors web's getEffectiveGroupId precedence: user-created groups
|
|
112
|
+
// take priority over the surfaced promotion.
|
|
113
|
+
const summary = serialize(
|
|
114
|
+
makeConversationRow({ surfacedAt: 1704067200000 }),
|
|
115
|
+
"group-custom-1",
|
|
116
|
+
);
|
|
117
|
+
expect(summary.groupId).toBe("group-custom-1");
|
|
118
|
+
expect(summary.surfacedAt).toBe(1704067200000);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("a pin wins over surfacing", () => {
|
|
122
|
+
const summary = serialize(
|
|
123
|
+
makeConversationRow({ surfacedAt: 1704067200000 }),
|
|
124
|
+
"system:pinned",
|
|
125
|
+
true,
|
|
126
|
+
);
|
|
127
|
+
expect(summary.groupId).toBe("system:pinned");
|
|
128
|
+
expect("isPinned" in summary && summary.isPinned).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("non-surfaced rows pass the persisted group through untouched", () => {
|
|
132
|
+
expect(serialize(makeConversationRow(), null).groupId).toBeNull();
|
|
133
|
+
expect(serialize(makeConversationRow(), "system:scheduled").groupId).toBe(
|
|
134
|
+
"system:scheduled",
|
|
135
|
+
);
|
|
136
|
+
expect(serialize(makeConversationRow(), "group-custom-1").groupId).toBe(
|
|
137
|
+
"group-custom-1",
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -94,6 +94,37 @@ function buildForkParent(
|
|
|
94
94
|
};
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Resolve the wire-level `groupId` for a conversation summary.
|
|
99
|
+
*
|
|
100
|
+
* Surfaced conversations (`surfaced_at IS NOT NULL`) render in the Recents
|
|
101
|
+
* grouping on every client, but legacy clients (the macOS Swift app) bucket
|
|
102
|
+
* purely by `groupId` and do not decode `surfacedAt`. Normalize the
|
|
103
|
+
* *serialized* `groupId` to `"system:all"` for surfaced rows so those
|
|
104
|
+
* clients render them in Recents without code changes — the persisted
|
|
105
|
+
* `group_id` is untouched, so clearing `surfaced_at` (demotion) makes
|
|
106
|
+
* serialization return the original group again.
|
|
107
|
+
*
|
|
108
|
+
* Mirrors web's `getEffectiveGroupId` precedence: an explicit pin
|
|
109
|
+
* (`system:pinned`) or a user-created custom group wins over surfacing, so
|
|
110
|
+
* only the system Background/Scheduled groups (and the null fallback, which
|
|
111
|
+
* legacy clients re-derive into those buckets from `source`) are rewritten.
|
|
112
|
+
*/
|
|
113
|
+
function resolveSerializedGroupId(
|
|
114
|
+
conversation: ConversationRow,
|
|
115
|
+
persistedGroupId: string | null,
|
|
116
|
+
): string | null {
|
|
117
|
+
if (conversation.surfacedAt == null) return persistedGroupId;
|
|
118
|
+
if (
|
|
119
|
+
persistedGroupId == null ||
|
|
120
|
+
persistedGroupId === "system:background" ||
|
|
121
|
+
persistedGroupId === "system:scheduled"
|
|
122
|
+
) {
|
|
123
|
+
return "system:all";
|
|
124
|
+
}
|
|
125
|
+
return persistedGroupId;
|
|
126
|
+
}
|
|
127
|
+
|
|
97
128
|
function buildChannelBinding(binding: ExternalConversationBinding) {
|
|
98
129
|
const externalChatName =
|
|
99
130
|
binding.externalChatName?.trim() ||
|
|
@@ -213,11 +244,17 @@ export function serializeConversationSummary(params: {
|
|
|
213
244
|
displayOrder: displayMeta.displayOrder,
|
|
214
245
|
}
|
|
215
246
|
: {}),
|
|
216
|
-
groupId:
|
|
247
|
+
groupId: resolveSerializedGroupId(
|
|
248
|
+
conversation,
|
|
249
|
+
displayMeta?.groupId ?? null,
|
|
250
|
+
),
|
|
217
251
|
...(forkParent ? { forkParent } : {}),
|
|
218
252
|
...(conversation.archivedAt != null
|
|
219
253
|
? { archivedAt: conversation.archivedAt }
|
|
220
254
|
: {}),
|
|
255
|
+
...(conversation.surfacedAt != null
|
|
256
|
+
? { surfacedAt: conversation.surfacedAt }
|
|
257
|
+
: {}),
|
|
221
258
|
...(conversation.inferenceProfile != null
|
|
222
259
|
? { inferenceProfile: conversation.inferenceProfile }
|
|
223
260
|
: {}),
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Shared outbound verification action logic.
|
|
3
3
|
*
|
|
4
4
|
* These pure functions encapsulate the business logic for starting, resending,
|
|
5
|
-
* and cancelling outbound verification flows (Telegram, voice, Slack).
|
|
5
|
+
* and cancelling outbound verification flows (Telegram, voice, Slack, email).
|
|
6
6
|
* They return transport-agnostic result objects and are consumed by both the
|
|
7
7
|
* message handler (config-channels.ts) and the HTTP route layer (channel-verification-routes.ts).
|
|
8
8
|
*/
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
updateSessionStatus,
|
|
27
27
|
} from "./channel-verification-service.js";
|
|
28
28
|
import {
|
|
29
|
+
composeVerificationEmail,
|
|
29
30
|
composeVerificationSlack,
|
|
30
31
|
composeVerificationTelegram,
|
|
31
32
|
GUARDIAN_VERIFY_TEMPLATE_KEYS,
|
|
@@ -52,6 +53,8 @@ export const DESTINATION_RATE_WINDOW_MS = 3_600_000;
|
|
|
52
53
|
/** Session TTL in seconds (matches challenge TTL of 10 minutes). */
|
|
53
54
|
const SESSION_TTL_SECONDS = 600;
|
|
54
55
|
|
|
56
|
+
const EMAIL_VERIFICATION_SUBJECT = "Vellum Assistant Guardian Verification";
|
|
57
|
+
|
|
55
58
|
// ---------------------------------------------------------------------------
|
|
56
59
|
// Telegram destination classification
|
|
57
60
|
// ---------------------------------------------------------------------------
|
|
@@ -122,6 +125,8 @@ interface OutboundActionResult {
|
|
|
122
125
|
* reach the gateway. The daemon HTTP route handler calls
|
|
123
126
|
* deliverVerificationSlack() after receiving this payload. */
|
|
124
127
|
_pendingSlackDm?: { userId: string; text: string; assistantId: string };
|
|
128
|
+
/** Internal: email delivery payload for the caller to dispatch (same pattern as Slack). */
|
|
129
|
+
_pendingEmail?: { to: string; text: string; subject: string; assistantId: string };
|
|
125
130
|
}
|
|
126
131
|
|
|
127
132
|
// ---------------------------------------------------------------------------
|
|
@@ -512,6 +517,80 @@ export function deliverVerificationSlack(
|
|
|
512
517
|
deliverVerificationSlackAsync(userId, text, assistantId);
|
|
513
518
|
}
|
|
514
519
|
|
|
520
|
+
// ---------------------------------------------------------------------------
|
|
521
|
+
// Email delivery helper
|
|
522
|
+
// ---------------------------------------------------------------------------
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Deliver a verification email via the platform email send API.
|
|
526
|
+
* Fire-and-forget wrapper for use in the daemon process (HTTP route handlers).
|
|
527
|
+
*/
|
|
528
|
+
export function deliverVerificationEmail(
|
|
529
|
+
to: string,
|
|
530
|
+
text: string,
|
|
531
|
+
subject: string,
|
|
532
|
+
_assistantId: string,
|
|
533
|
+
): void {
|
|
534
|
+
(async () => {
|
|
535
|
+
try {
|
|
536
|
+
const { VellumPlatformClient } = await import(
|
|
537
|
+
"../platform/client.js"
|
|
538
|
+
);
|
|
539
|
+
const client = await VellumPlatformClient.create();
|
|
540
|
+
if (!client?.platformAssistantId) {
|
|
541
|
+
log.error("Cannot deliver verification email: platform client not configured");
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const listResponse = await client.fetch(
|
|
546
|
+
`/v1/assistants/${client.platformAssistantId}/email-addresses/`,
|
|
547
|
+
);
|
|
548
|
+
if (!listResponse.ok) {
|
|
549
|
+
log.error({ status: listResponse.status }, "Failed to list email addresses for verification");
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
const listData = (await listResponse.json()) as {
|
|
553
|
+
results: { address: string }[];
|
|
554
|
+
};
|
|
555
|
+
const addresses = listData.results ?? [];
|
|
556
|
+
if (addresses.length === 0) {
|
|
557
|
+
log.error("No email address registered — cannot deliver verification email");
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
const fromAddress = addresses[0].address;
|
|
561
|
+
|
|
562
|
+
const { markdownToEmailHtml } = await import(
|
|
563
|
+
"../email/html-renderer.js"
|
|
564
|
+
);
|
|
565
|
+
const html = markdownToEmailHtml(text);
|
|
566
|
+
|
|
567
|
+
const response = await client.fetch("/v1/runtime-proxy/email/send/", {
|
|
568
|
+
method: "POST",
|
|
569
|
+
headers: { "Content-Type": "application/json" },
|
|
570
|
+
body: JSON.stringify({
|
|
571
|
+
to: [to],
|
|
572
|
+
from_address: fromAddress,
|
|
573
|
+
text,
|
|
574
|
+
subject,
|
|
575
|
+
html,
|
|
576
|
+
}),
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
if (response.ok) {
|
|
580
|
+
log.info({ to }, "Verification email delivered");
|
|
581
|
+
} else {
|
|
582
|
+
const respBody = await response.json().catch(() => ({}));
|
|
583
|
+
log.error(
|
|
584
|
+
{ to, status: response.status, respBody },
|
|
585
|
+
"Failed to deliver verification email",
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
} catch (err) {
|
|
589
|
+
log.error({ err, to }, "Failed to deliver verification email");
|
|
590
|
+
}
|
|
591
|
+
})();
|
|
592
|
+
}
|
|
593
|
+
|
|
515
594
|
function startOutboundSlack(
|
|
516
595
|
destination: string | undefined,
|
|
517
596
|
assistantId: string,
|
|
@@ -644,13 +723,35 @@ function startOutboundEmail(
|
|
|
644
723
|
verificationPurpose: "guardian",
|
|
645
724
|
});
|
|
646
725
|
|
|
726
|
+
const emailBody = composeVerificationEmail(
|
|
727
|
+
GUARDIAN_VERIFY_TEMPLATE_KEYS.EMAIL_CHALLENGE_REQUEST,
|
|
728
|
+
{
|
|
729
|
+
code: sessionResult.secret,
|
|
730
|
+
expiresInMinutes: Math.floor(SESSION_TTL_SECONDS / 60),
|
|
731
|
+
},
|
|
732
|
+
);
|
|
733
|
+
|
|
734
|
+
const now = Date.now();
|
|
735
|
+
const nextResendAt = now + RESEND_COOLDOWN_MS;
|
|
736
|
+
const sendCount = 1;
|
|
737
|
+
|
|
738
|
+
updateSessionDelivery(sessionResult.sessionId, now, sendCount, nextResendAt);
|
|
739
|
+
|
|
647
740
|
return {
|
|
648
741
|
success: true,
|
|
649
742
|
verificationSessionId: sessionResult.sessionId,
|
|
650
743
|
secret: sessionResult.secret,
|
|
651
744
|
expiresAt: sessionResult.expiresAt,
|
|
745
|
+
nextResendAt,
|
|
746
|
+
sendCount,
|
|
652
747
|
channel,
|
|
653
748
|
originConversationId,
|
|
749
|
+
_pendingEmail: {
|
|
750
|
+
to: normalizedEmail,
|
|
751
|
+
text: emailBody,
|
|
752
|
+
subject: EMAIL_VERIFICATION_SUBJECT,
|
|
753
|
+
assistantId,
|
|
754
|
+
},
|
|
654
755
|
};
|
|
655
756
|
}
|
|
656
757
|
|
|
@@ -851,12 +952,56 @@ export function resendOutbound(
|
|
|
851
952
|
originConversationId,
|
|
852
953
|
_pendingSlackDm: { userId: destination, text: slackBody, assistantId },
|
|
853
954
|
};
|
|
955
|
+
} else if (channel === "email") {
|
|
956
|
+
const newSession = createOutboundSession({
|
|
957
|
+
channel,
|
|
958
|
+
expectedExternalUserId: destination,
|
|
959
|
+
expectedChatId: destination,
|
|
960
|
+
identityBindingStatus: "bound",
|
|
961
|
+
destinationAddress: destination,
|
|
962
|
+
verificationPurpose: "guardian",
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
const emailBody = composeVerificationEmail(
|
|
966
|
+
GUARDIAN_VERIFY_TEMPLATE_KEYS.EMAIL_RESEND,
|
|
967
|
+
{
|
|
968
|
+
code: newSession.secret,
|
|
969
|
+
expiresInMinutes: Math.floor(SESSION_TTL_SECONDS / 60),
|
|
970
|
+
},
|
|
971
|
+
);
|
|
972
|
+
|
|
973
|
+
const now = Date.now();
|
|
974
|
+
const newSendCount = currentSendCount + 1;
|
|
975
|
+
const nextResendAt = now + RESEND_COOLDOWN_MS;
|
|
976
|
+
|
|
977
|
+
updateSessionDelivery(
|
|
978
|
+
newSession.sessionId,
|
|
979
|
+
now,
|
|
980
|
+
newSendCount,
|
|
981
|
+
nextResendAt,
|
|
982
|
+
);
|
|
983
|
+
|
|
984
|
+
return {
|
|
985
|
+
success: true,
|
|
986
|
+
verificationSessionId: newSession.sessionId,
|
|
987
|
+
secret: newSession.secret,
|
|
988
|
+
nextResendAt,
|
|
989
|
+
sendCount: newSendCount,
|
|
990
|
+
channel,
|
|
991
|
+
originConversationId,
|
|
992
|
+
_pendingEmail: {
|
|
993
|
+
to: destination,
|
|
994
|
+
text: emailBody,
|
|
995
|
+
subject: EMAIL_VERIFICATION_SUBJECT,
|
|
996
|
+
assistantId,
|
|
997
|
+
},
|
|
998
|
+
};
|
|
854
999
|
}
|
|
855
1000
|
|
|
856
1001
|
return {
|
|
857
1002
|
success: false,
|
|
858
1003
|
error: "unsupported_channel",
|
|
859
|
-
message: `Resend is only supported for Telegram, phone, and
|
|
1004
|
+
message: `Resend is only supported for Telegram, phone, Slack, and email. Got: ${channel}`,
|
|
860
1005
|
channel,
|
|
861
1006
|
};
|
|
862
1007
|
}
|