@vellumai/assistant 0.11.3 → 0.11.4-dev.202608190019.b94dbf2
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 -3
- package/ARCHITECTURE.md +32 -10
- package/docs/architecture/memory.md +43 -5
- package/docs/architecture/security.md +96 -152
- package/docs/architecture/turn-actor.md +70 -0
- package/docs/browser-use-architecture-phase2.md +128 -56
- package/docs/flux-turn-detection-spike.md +248 -0
- package/docs/guardian-request-flow.md +35 -0
- package/docs/stt-provider-onboarding.md +3 -1
- package/knip.json +3 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +34 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
- package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
- package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/openapi.yaml +289 -113
- package/package.json +1 -1
- package/scripts/voice-ttft-spike.ts +3 -3
- package/scripts/write-plugin-api-shim.ts +10 -0
- package/src/__tests__/app-compiler.test.ts +38 -3
- package/src/__tests__/app-control-flow.test.ts +1 -0
- package/src/__tests__/approval-routes-http.test.ts +2 -2
- package/src/__tests__/assistant-feature-flag-guard.test.ts +25 -3
- package/src/__tests__/attachments-store.test.ts +21 -12
- package/src/__tests__/byok-default-profile-ensure.test.ts +17 -0
- package/src/__tests__/call-setup-flow-name-capture.test.ts +0 -1
- package/src/__tests__/call-site-routing-provider.test.ts +1 -1
- package/src/__tests__/channel-availability-routes.test.ts +14 -1
- package/src/__tests__/channel-capabilities-dedupe.test.ts +214 -0
- package/src/__tests__/channel-delivery-store.test.ts +14 -14
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +4 -4
- package/src/__tests__/channel-setup-panel-ack.test.ts +1 -1
- package/src/__tests__/checker.test.ts +20 -314
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
- package/src/__tests__/compaction-events.test.ts +8 -10
- package/src/__tests__/config-loader-backfill.test.ts +3 -3
- package/src/__tests__/config-schema.test.ts +25 -10
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +8 -11
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +8 -11
- package/src/__tests__/conversation-agent-loop.test.ts +32 -21
- package/src/__tests__/conversation-attachments.test.ts +0 -1
- package/src/__tests__/conversation-attention-store.test.ts +63 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +112 -0
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -4
- package/src/__tests__/conversation-fork-crud.test.ts +69 -0
- package/src/__tests__/conversation-fork-referential.test.ts +67 -0
- package/src/__tests__/conversation-fork-retrospective.test.ts +24 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +209 -0
- package/src/__tests__/conversation-notifiers-provenance.test.ts +59 -0
- package/src/__tests__/conversation-queue.test.ts +154 -4
- package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +9 -9
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +187 -102
- package/src/__tests__/conversation-runtime-workspace.test.ts +14 -10
- package/src/__tests__/conversation-slash-queue.test.ts +3 -0
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-app-open.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +21 -14
- package/src/__tests__/conversation-surfaces-queued-emit.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-standalone.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-state-update.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-table-action.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -1
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +1 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +1 -1
- package/src/__tests__/credential-prompt-route.test.ts +7 -10
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/cu-unified-flow.test.ts +1 -0
- package/src/__tests__/custom-profile-ensure.test.ts +5 -1
- package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
- package/src/__tests__/discord-access-request-privacy.test.ts +5 -1
- package/src/__tests__/discord-requester-notice-privacy.test.ts +3 -3
- package/src/__tests__/document-append-idempotency.test.ts +233 -0
- package/src/__tests__/document-sync-tags.test.ts +0 -75
- package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
- package/src/__tests__/edit-propagation.test.ts +107 -11
- package/src/__tests__/file-ops-service.test.ts +163 -30
- package/src/__tests__/filesystem-tools.test.ts +23 -24
- package/src/__tests__/gateway-only-guard.test.ts +2 -5
- package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
- package/src/__tests__/helpers/mock-actor-context.ts +49 -0
- package/src/__tests__/helpers/mock-conversation.ts +13 -1
- package/src/__tests__/host-file-read-tool.test.ts +16 -19
- package/src/__tests__/http-user-message-parity.test.ts +1 -1
- package/src/__tests__/init-feature-flag-overrides.test.ts +49 -0
- package/src/__tests__/injector-chain.test.ts +63 -41
- package/src/__tests__/injector-disk-pressure.test.ts +11 -23
- package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
- package/src/__tests__/llm-context-resolution.test.ts +73 -1
- package/src/__tests__/llm-schema.test.ts +5 -2
- package/src/__tests__/managed-skill-lifecycle.test.ts +7 -0
- package/src/__tests__/mcp-list-plugin-servers.test.ts +250 -0
- package/src/__tests__/media-generate-image.test.ts +131 -21
- package/src/__tests__/memory-retrieval-hook.test.ts +100 -7
- package/src/__tests__/messages-read-boundary-guard.test.ts +134 -0
- package/src/__tests__/mtime-cache.test.ts +3 -1
- package/src/__tests__/non-member-access-request.test.ts +0 -20
- package/src/__tests__/outbound-slack-persistence.test.ts +40 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
- package/src/__tests__/plugin-api-store-credential.test.ts +261 -0
- package/src/__tests__/plugin-api-webhook-url.test.ts +10 -7
- package/src/__tests__/plugin-disabled-state.test.ts +2 -0
- package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
- package/src/__tests__/plugin-execution-context.test.ts +73 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +7 -4
- package/src/__tests__/plugin-secret-pattern-contribution.test.ts +1 -1
- package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +14 -7
- package/src/__tests__/provider-commit-message-generator.test.ts +20 -0
- package/src/__tests__/proxy-approval-callback.test.ts +1 -0
- package/src/__tests__/qdrant-manager.test.ts +14 -1
- package/src/__tests__/reaction-persistence.test.ts +200 -7
- package/src/__tests__/require-fresh-approval.test.ts +0 -4
- package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +460 -105
- package/src/__tests__/run-due-schedules.test.ts +21 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +187 -18
- package/src/__tests__/schedule-routes.test.ts +23 -0
- package/src/__tests__/schedule-store.test.ts +17 -0
- package/src/__tests__/scoped-approval-grants.test.ts +11 -6
- package/src/__tests__/secret-ingress-channel.test.ts +0 -1
- package/src/__tests__/secret-ingress-http.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +3 -3
- package/src/__tests__/skills.test.ts +32 -0
- package/src/__tests__/slack-edit-ordering-characterization.test.ts +0 -1
- package/src/__tests__/starter-task-flow.test.ts +5 -4
- package/src/__tests__/subagent-call-site-routing.test.ts +31 -19
- package/src/__tests__/subagent-fork-prompt-role.test.ts +1 -1
- package/src/__tests__/subagent-spawn-and-await.test.ts +18 -17
- package/src/__tests__/subagent-tool-gate-mode.test.ts +1 -1
- package/src/__tests__/subagent-tools.test.ts +81 -101
- package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +1 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
- package/src/__tests__/tool-executor.test.ts +68 -39
- package/src/__tests__/turn-events-store.test.ts +43 -0
- package/src/__tests__/ui-choice-copy-surfaces.test.ts +1 -1
- package/src/__tests__/ui-shape-teaching.test.ts +33 -0
- package/src/__tests__/ui-visual-surface.test.ts +1 -1
- package/src/__tests__/ui-voice-picker-surface.test.ts +128 -0
- package/src/__tests__/ui-work-result-surface.test.ts +1 -1
- package/src/__tests__/user-plugin-loader.test.ts +3 -1
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/visible-app-context.test.ts +16 -9
- package/src/__tests__/voice-config-update.test.ts +40 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +5 -3
- package/src/__tests__/voice-session-bridge.test.ts +85 -29
- package/src/__tests__/worker-entrypoint-guards.test.ts +54 -0
- package/src/__tests__/worker-plugin-surface.test.ts +77 -0
- package/src/__tests__/workspace-migration-142-consolidate-voice-front-door.test.ts +158 -0
- package/src/__tests__/workspace-migration-143-repair-deprecated-codex-model-id.test.ts +133 -0
- package/src/__tests__/workspace-migration-144-convert-stranded-subscription-openai-profiles.test.ts +316 -0
- package/src/__tests__/workspace-migration-145-collapse-profile-bindings-to-entries.test.ts +325 -0
- package/src/__tests__/workspace-migration-146-repair-retired-fireworks-deepseek-flash-model-id.test.ts +235 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +10 -2
- package/src/acp/__tests__/auth-required.test.ts +161 -0
- package/src/acp/acp-claude-oauth.ts +19 -2
- package/src/acp/agent-process.test.ts +100 -0
- package/src/acp/agent-process.ts +29 -26
- package/src/acp/auth-required.ts +102 -0
- package/src/acp/session-manager.test.ts +119 -0
- package/src/acp/session-manager.ts +76 -3
- package/src/api/events/acp-auth-required.ts +55 -0
- package/src/api/events/host-file.ts +2 -2
- package/src/api/index.ts +7 -0
- package/src/api/surfaces.ts +12 -3
- package/src/apps/app-store.ts +3 -0
- package/src/bundler/package-resolver.ts +2 -30
- package/src/calls/__tests__/voice-session-bridge.test.ts +194 -11
- package/src/calls/__tests__/voice-triage-escalate.test.ts +102 -0
- package/src/calls/call-controller.ts +19 -3
- package/src/calls/call-setup-flow.ts +0 -1
- package/src/calls/media-stream-stt-session.ts +15 -0
- package/src/calls/voice-session-bridge.ts +115 -41
- package/src/calls/voice-triage-escalate.ts +105 -2
- package/src/channels/__tests__/plugin-channel-declarations.test.ts +161 -0
- package/src/channels/config.ts +13 -0
- package/src/channels/plugin-channel-declarations.ts +108 -0
- package/src/channels/types.ts +30 -0
- package/src/cli/AGENTS.md +5 -2
- package/src/cli/bundled-modules.ts +29 -0
- package/src/cli/commands/credentials.help.ts +2 -2
- package/src/cli/commands/db/repair.ts +4 -8
- package/src/cli/commands/domain.ts +6 -3
- package/src/cli/commands/email.ts +6 -3
- package/src/cli/commands/inference-providers.ts +1 -1
- package/src/cli/commands/keys.ts +8 -3
- package/src/cli/commands/mcp.help.ts +13 -4
- package/src/cli/commands/mcp.ts +9 -0
- package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
- package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
- package/src/cli/commands/memory/index.help.ts +84 -28
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/memory-v2.ts +58 -54
- package/src/cli/commands/memory/memory-v3.ts +64 -0
- package/src/cli/commands/memory/memory-validate.ts +18 -0
- package/src/cli/commands/plugins.ts +85 -36
- package/src/cli/commands/schedules.ts +35 -1
- package/src/cli/commands/stt.help.ts +27 -2
- package/src/cli/commands/trust.ts +3 -14
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
- package/src/cli/lib/bundled-marketplace.json +14 -1
- package/src/cli/lib/upgrade-plugin.ts +42 -0
- package/src/config/__tests__/balanced-model-experiment.test.ts +278 -0
- package/src/config/__tests__/default-profile-catalog.test.ts +34 -2
- package/src/config/__tests__/default-provider.test.ts +6 -1
- package/src/config/__tests__/profile-materialization.test.ts +75 -19
- package/src/config/assistant-feature-flags.ts +36 -15
- package/src/config/balanced-model-experiment.ts +35 -0
- package/src/config/bundled-skills/acp/SKILL.md +6 -7
- package/src/config/bundled-skills/document-editor/SKILL.md +2 -2
- package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
- package/src/config/bundled-skills/image-studio/SKILL.md +5 -4
- package/src/config/bundled-skills/image-studio/TOOLS.json +1 -1
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +101 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +14 -4
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.test.ts +65 -0
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +7 -1
- package/src/config/bundled-skills/settings/tools/shared.ts +16 -0
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +9 -3
- package/src/config/bundled-skills/subagent/SKILL.md +17 -12
- package/src/config/bundled-skills/subagent/TOOLS.json +4 -4
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +22 -1
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +9 -2
- package/src/config/call-site-defaults.ts +11 -5
- package/src/config/default-profile-catalog.ts +179 -16
- package/src/config/default-profile-names.ts +4 -1
- package/src/config/default-provider-resolution.ts +4 -0
- package/src/config/feature-flag-registry.json +12 -12
- package/src/config/llm-context-resolution.ts +11 -3
- package/src/config/llm-resolver.ts +28 -1
- package/src/config/profile-materialization.ts +70 -22
- package/src/config/schemas/__tests__/live-voice.test.ts +107 -4
- package/src/config/schemas/call-site-catalog.ts +4 -4
- package/src/config/schemas/live-voice.ts +57 -23
- package/src/config/schemas/llm.ts +59 -32
- package/src/config/schemas/mcp.ts +23 -0
- package/src/config/schemas/plugin-updates.ts +6 -2
- package/src/config/schemas/stt.ts +1 -0
- package/src/config/skills.ts +9 -2
- package/src/context/outbound-sanitize.ts +96 -1
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +1 -1
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +82 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +15 -10
- package/src/daemon/conversation-agent-loop.ts +31 -19
- package/src/daemon/conversation-messaging.ts +5 -1
- package/src/daemon/conversation-notifiers.ts +20 -10
- package/src/daemon/conversation-process.ts +20 -6
- package/src/daemon/conversation-runtime-assembly.ts +12 -6
- package/src/daemon/conversation-store.ts +14 -4
- package/src/daemon/conversation-surfaces.ts +75 -26
- package/src/daemon/conversation-tool-setup.ts +3 -6
- package/src/daemon/conversation.ts +154 -51
- package/src/daemon/doordash-steps.ts +2 -2
- package/src/daemon/lifecycle.ts +14 -1
- package/src/daemon/mcp-reload-service.ts +36 -6
- package/src/daemon/process-message.ts +13 -26
- package/src/daemon/providers-setup.ts +6 -3
- package/src/daemon/trust-context-types.ts +29 -0
- package/src/daemon/trust-context.ts +12 -7
- package/src/daemon/wake-conversation-ops.ts +3 -2
- package/src/daemon/windows-compiled-entry.ts +4 -0
- package/src/documents/document-store.ts +143 -240
- package/src/hooks/hook-loader.ts +3 -3
- package/src/hooks/registry.ts +103 -45
- package/src/hooks/types.ts +5 -0
- package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
- package/src/inbound/oauth-callback-url.ts +61 -0
- package/src/ipc/gateway-client.test.ts +1 -1
- package/src/ipc/gateway-client.ts +14 -21
- package/src/ipc/gateway-flag-listener.ts +17 -3
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +1 -104
- package/src/live-voice/__tests__/live-voice-events.test.ts +7 -8
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +1050 -0
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +115 -8
- package/src/live-voice/__tests__/live-voice-photo.test.ts +100 -0
- package/src/live-voice/__tests__/live-voice-progress.test.ts +60 -194
- package/src/live-voice/__tests__/live-voice-stt.test.ts +14 -0
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +29 -0
- package/src/live-voice/__tests__/live-voice-tts-session.test.ts +0 -483
- package/src/live-voice/__tests__/live-voice-vad.test.ts +0 -16
- package/src/live-voice/__tests__/progress-narration.test.ts +214 -0
- package/src/live-voice/live-voice-archive.ts +2 -0
- package/src/live-voice/live-voice-manager.ts +16 -3
- package/src/live-voice/live-voice-metrics.ts +57 -32
- package/src/live-voice/live-voice-photo.ts +1 -2
- package/src/live-voice/live-voice-session.ts +598 -323
- package/src/live-voice/progress-narration.ts +277 -0
- package/src/live-voice/protocol.ts +21 -1
- package/src/live-voice/windows-compiled-live-voice.ts +4 -0
- package/src/mcp/__tests__/effective-config.test.ts +238 -0
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
- package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +200 -0
- package/src/mcp/__tests__/mcp-oauth-provider.test.ts +9 -9
- package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +95 -0
- package/src/mcp/client.ts +16 -11
- package/src/mcp/effective-config.ts +113 -0
- package/src/mcp/manager.ts +11 -6
- package/src/mcp/mcp-auth-orchestrator.ts +13 -22
- package/src/mcp/mcp-oauth-provider.ts +205 -240
- package/src/monitoring/__tests__/plugin-auto-update.test.ts +166 -3
- package/src/monitoring/control.ts +1 -0
- package/src/monitoring/db-integrity-sample.ts +4 -5
- package/src/monitoring/plugin-auto-update.ts +128 -24
- package/src/notifications/AGENTS.md +2 -0
- package/src/notifications/approval-card-data.ts +33 -0
- package/src/notifications/signal.ts +1 -0
- package/src/permissions/AGENTS.md +16 -0
- package/src/permissions/checker.test.ts +75 -122
- package/src/permissions/checker.ts +44 -560
- package/src/permissions/confirmation-guardian-request.test.ts +15 -11
- package/src/permissions/confirmation-guardian-request.ts +2 -2
- package/src/permissions/prompter.ts +1 -5
- package/src/permissions/question-guardian-request.test.ts +14 -6
- package/src/permissions/question-guardian-request.ts +1 -2
- package/src/persistence/attachments-store.ts +8 -1
- package/src/persistence/bookmark-crud.ts +3 -7
- package/src/persistence/conversation-attention-store.ts +16 -45
- package/src/persistence/conversation-crud.ts +33 -4
- package/src/persistence/conversation-lineage.ts +9 -0
- package/src/persistence/conversation-queries.ts +182 -52
- package/src/persistence/delivery-crud.ts +150 -36
- package/src/persistence/embeddings/qdrant-manager.ts +84 -49
- package/src/persistence/external-conversation-store.ts +32 -4
- package/src/persistence/llm-request-log-store.ts +4 -10
- package/src/persistence/llm-usage-store.ts +8 -3
- package/src/persistence/message-reads.test.ts +197 -0
- package/src/persistence/message-reads.ts +211 -0
- package/src/persistence/migrations/360-add-document-workspace-path.ts +5 -14
- package/src/persistence/migrations/366-chatgpt-subscription-row-identity.test.ts +120 -0
- package/src/persistence/migrations/366-chatgpt-subscription-row-identity.ts +62 -0
- package/src/persistence/real-user-turn-filter.ts +27 -3
- package/src/persistence/schema/documents.ts +4 -4
- package/src/persistence/steps.ts +9 -0
- package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
- package/src/plugin-api/constants.ts +8 -0
- package/src/plugin-api/conversation-turn.ts +195 -5
- package/src/plugin-api/index.ts +38 -6
- package/src/plugin-api/resolve-credential.ts +3 -2
- package/src/plugin-api/store-credential.ts +146 -0
- package/src/plugin-api/vision-support.test.ts +1 -1
- package/src/plugin-api/webhook-url.ts +13 -11
- package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
- package/src/plugins/defaults/main.ts +21 -7
- package/src/plugins/defaults/memory/AGENTS.md +15 -3
- package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +204 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +72 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +4 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +4 -1
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
- package/src/plugins/defaults/memory/buffer-format.ts +165 -0
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
- package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +77 -0
- package/src/plugins/defaults/memory/graph/conversation-graph-memory.ts +24 -6
- package/src/plugins/defaults/memory/graph/image-ref-utils.ts +3 -0
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -30
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.test.ts +34 -0
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +17 -24
- package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
- package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +53 -5
- package/src/plugins/defaults/memory/indexer.ts +3 -1
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +19 -7
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +4 -4
- package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
- package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
- package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +147 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
- package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
- package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
- package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
- package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
- package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
- package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +195 -58
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +93 -24
- package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
- package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
- package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
- package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
- package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
- package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
- package/src/plugins/defaults/memory/substrate/static-context.ts +160 -33
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +2 -4
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +3 -1
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +18 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +66 -1
- package/src/plugins/defaults/memory/v3/card.ts +9 -9
- package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
- package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
- package/src/plugins/defaults/memory/v3/edge.ts +13 -57
- package/src/plugins/defaults/memory/v3/injector.ts +8 -0
- package/src/plugins/defaults/memory/v3/prune.ts +2 -0
- package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +23 -9
- package/src/plugins/defaults/memory/worker-control.ts +1 -0
- package/src/plugins/defaults/memory/worker.ts +6 -3
- package/src/plugins/defaults/worker-entrypoints.ts +3 -0
- package/src/plugins/external-plugin-loader.ts +47 -0
- package/src/plugins/mcp-servers.ts +361 -0
- package/src/plugins/mtime-cache.ts +37 -46
- package/src/plugins/pipeline.ts +14 -7
- package/src/plugins/plugin-execution-context.ts +35 -11
- package/src/plugins/worker-plugin-surface.ts +33 -0
- package/src/prompts/templates/system-sections.ts +0 -7
- package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
- package/src/providers/__tests__/context-overflow-error.test.ts +24 -0
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +214 -2
- package/src/providers/__tests__/preflight-resolved-config.test.ts +57 -0
- package/src/providers/__tests__/retry-callsite.test.ts +25 -2
- package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
- package/src/providers/anthropic/client.ts +332 -241
- package/src/providers/call-site-routing.ts +30 -3
- package/src/providers/connection-resolution.ts +194 -11
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +4 -0
- package/src/providers/inference/adapter-factory.ts +3 -0
- package/src/providers/inference/auth.ts +6 -0
- package/src/providers/inference/connection-availability.ts +24 -2
- package/src/providers/inference/connections.ts +2 -0
- package/src/providers/model-catalog.ts +3 -3
- package/src/providers/model-intents.ts +28 -8
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +163 -40
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
- package/src/providers/openai/chat-completions-provider.ts +133 -36
- package/src/providers/openai/codex-models.ts +2 -1
- package/src/providers/provider-send-message.ts +32 -3
- package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +433 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +625 -0
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +34 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +285 -6
- package/src/providers/speech-to-text/deepgram-flux-frames.ts +395 -0
- package/src/providers/speech-to-text/deepgram-flux-realtime.ts +679 -0
- package/src/providers/speech-to-text/provider-catalog.ts +99 -8
- package/src/providers/speech-to-text/resolve.ts +25 -2
- package/src/routes/control.ts +1 -0
- package/src/routes/route-host-client.ts +1 -0
- package/src/routes/route-host-protocol.ts +7 -0
- package/src/routes/worker.ts +48 -15
- package/src/runtime/AGENTS.md +16 -17
- package/src/runtime/access-request-helper.ts +9 -12
- package/src/runtime/agent-wake.ts +18 -15
- package/src/runtime/effective-capabilities.test.ts +43 -0
- package/src/runtime/effective-capabilities.ts +27 -0
- package/src/runtime/guardian-reply-router.ts +6 -2
- package/src/runtime/pre-first-message-gate.ts +4 -0
- package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +12 -4
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +522 -2
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +52 -0
- package/src/runtime/routes/__tests__/default-provider-routes.test.ts +61 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +44 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +102 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +132 -35
- package/src/runtime/routes/__tests__/schedule-routes-disarm-reason.test.ts +215 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
- package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +133 -1
- package/src/runtime/routes/channel-availability-routes.ts +32 -14
- package/src/runtime/routes/channel-route-shared.ts +0 -6
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +6 -6
- package/src/runtime/routes/conversation-list-routes.ts +181 -23
- package/src/runtime/routes/conversation-management-routes.ts +17 -12
- package/src/runtime/routes/conversation-query-routes.ts +40 -27
- package/src/runtime/routes/conversation-routes.ts +43 -29
- package/src/runtime/routes/credential-prompt-routes.ts +4 -7
- package/src/runtime/routes/credential-routes.ts +26 -82
- package/src/runtime/routes/default-provider-routes.ts +15 -0
- package/src/runtime/routes/documents-routes.ts +3 -222
- package/src/runtime/routes/inbound-message-handler.ts +21 -45
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -9
- package/src/runtime/routes/inbound-stages/admission-policy.ts +1 -17
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -79
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +12 -7
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -8
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -79
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +2 -3
- package/src/runtime/routes/inference-profiles-routes.ts +20 -11
- package/src/runtime/routes/inference-provider-connection-routes.ts +77 -15
- package/src/runtime/routes/log-export-routes.ts +3 -0
- package/src/runtime/routes/mcp-auth-routes.ts +148 -57
- package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +2 -0
- package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +3 -0
- package/src/runtime/routes/playground/inject-failures.ts +2 -2
- package/src/runtime/routes/playground/reset-circuit.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +38 -8
- package/src/runtime/routes/schedule-routes.ts +94 -7
- package/src/runtime/routes/settings-routes.ts +6 -6
- package/src/runtime/routes/stt-routes.ts +31 -25
- package/src/runtime/routes/surface-action-routes.ts +20 -19
- package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
- package/src/runtime/routes/user-route-dispatcher.ts +69 -15
- package/src/runtime/routes/user-route-import.ts +108 -0
- package/src/runtime/routes/user-route-resolution.ts +18 -1
- package/src/runtime/routes/workspace-routes.ts +0 -9
- package/src/runtime/routes/workspace-utils.ts +3 -13
- package/src/runtime/services/conversation-serializer.ts +7 -2
- package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +68 -5
- package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +81 -0
- package/src/schedule/plugin-schedule-availability.ts +58 -0
- package/src/schedule/plugin-schedule-declarations.ts +23 -27
- package/src/schedule/plugin-schedule-reconciler.ts +12 -3
- package/src/schedule/schedule-store.ts +5 -1
- package/src/schedule/scheduler.ts +9 -4
- package/src/schedule/worker-control.ts +1 -0
- package/src/schedule/worker.ts +6 -0
- package/src/security/oauth2.ts +6 -22
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +22 -0
- package/src/stt/__tests__/types.test.ts +94 -0
- package/src/stt/daemon-batch-transcriber.ts +10 -0
- package/src/stt/stt-stream-session.ts +8 -4
- package/src/stt/types.ts +103 -0
- package/src/subagent/__tests__/consult-prompt.test.ts +26 -15
- package/src/subagent/consult-context.ts +11 -11
- package/src/subagent/consult-prompt.ts +26 -35
- package/src/subagent/manager.ts +21 -40
- package/src/subagent/notify.ts +7 -1
- package/src/subagent/types.ts +15 -12
- package/src/tools/__tests__/tool-input-schemas.test.ts +7 -7
- package/src/tools/acp/spawn.test.ts +97 -0
- package/src/tools/acp/spawn.ts +38 -4
- package/src/tools/credentials/ref-parse.ts +35 -0
- package/src/tools/credentials/resolve.ts +4 -10
- package/src/tools/credentials/store.ts +168 -0
- package/src/tools/document/document-tool.ts +12 -3
- package/src/tools/executor.ts +53 -15
- package/src/tools/filesystem/read.ts +27 -10
- package/src/tools/host-filesystem/read.ts +27 -15
- package/src/tools/network/url-safety.ts +9 -16
- package/src/tools/permission-checker.ts +43 -52
- package/src/tools/registry.ts +2 -1
- package/src/tools/shared/filesystem/file-ops-service.ts +63 -35
- package/src/tools/shared/filesystem/legacy-read-args.ts +22 -0
- package/src/tools/shared/filesystem/types.ts +5 -5
- package/src/tools/skills/scaffold-managed.ts +25 -7
- package/src/tools/subagent/spawn.ts +28 -88
- package/src/tools/tool-types.ts +26 -13
- package/src/tools/types.ts +6 -6
- package/src/tools/ui-surface/surface-shape-docs.ts +12 -1
- package/src/tools/workflows/run-workflow.ts +1 -2
- package/src/tts/__tests__/reasoning-tag-filter.test.ts +78 -0
- package/src/tts/reasoning-tag-filter.ts +89 -0
- package/src/util/__tests__/worker-process-command.test.ts +37 -0
- package/src/util/logger.ts +16 -0
- package/src/util/think-tag-stream.ts +95 -0
- package/src/util/worker-process.ts +37 -4
- package/src/windows-compiled-cli.ts +32 -0
- package/src/windows-compiled-entry.ts +4 -0
- package/src/windows-compiled-logger.ts +6 -0
- package/src/windows-compiled-worker-entry.ts +29 -0
- package/src/workspace/byok-default-profile-ensure.ts +76 -24
- package/src/workspace/custom-profile-ensure.ts +4 -24
- package/src/workspace/migrations/142-consolidate-voice-front-door.ts +70 -0
- package/src/workspace/migrations/143-repair-deprecated-codex-model-id.ts +134 -0
- package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +265 -0
- package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +328 -0
- package/src/workspace/migrations/146-repair-retired-fireworks-deepseek-flash-model-id.ts +195 -0
- package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
- package/src/workspace/migrations/registry.ts +10 -0
- package/src/workspace/provider-commit-message-generator.ts +7 -5
- package/src/__tests__/document-workspace-file.test.ts +0 -467
- package/src/live-voice/__tests__/front-decision.test.ts +0 -645
- package/src/live-voice/front-decision.ts +0 -476
- package/src/permissions/ipc-risk-types.ts +0 -143
- package/src/permissions/risk-types.ts +0 -76
- package/src/subagent/__tests__/consult-transcript.test.ts +0 -184
- package/src/subagent/consult-transcript.ts +0 -90
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The schedule list and detail routes say why a plugin-sourced schedule is
|
|
3
|
+
* off, so a client never has to show an unexplained disabled row.
|
|
4
|
+
*
|
|
5
|
+
* The reason is derived from the row plus the plugin's files, so the fixtures
|
|
6
|
+
* here write real plugin directories under the per-test-process workspace
|
|
7
|
+
* plugins dir and converge them against the real schedule store.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
13
|
+
|
|
14
|
+
import { setOverridesForTesting } from "../../../__tests__/feature-flag-test-helpers.js";
|
|
15
|
+
import { getDb } from "../../../persistence/db-connection.js";
|
|
16
|
+
import { initializeDb } from "../../../persistence/db-init.js";
|
|
17
|
+
import {
|
|
18
|
+
createSchedule,
|
|
19
|
+
setUserEnabled,
|
|
20
|
+
upsertDeclaredSchedule,
|
|
21
|
+
} from "../../../schedule/schedule-store.js";
|
|
22
|
+
import { getWorkspacePluginsDir } from "../../../util/platform.js";
|
|
23
|
+
import { ROUTES } from "../schedule-routes.js";
|
|
24
|
+
|
|
25
|
+
await initializeDb();
|
|
26
|
+
|
|
27
|
+
const pluginsDir = getWorkspacePluginsDir();
|
|
28
|
+
|
|
29
|
+
function routeHandler(operationId: string) {
|
|
30
|
+
const route = ROUTES.find((r) => r.operationId === operationId);
|
|
31
|
+
if (!route) {
|
|
32
|
+
throw new Error(`${operationId} route not found`);
|
|
33
|
+
}
|
|
34
|
+
return route.handler;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const listHandler = routeHandler("listSchedules");
|
|
38
|
+
const getHandler = routeHandler("getSchedule");
|
|
39
|
+
|
|
40
|
+
interface SerializedSchedule {
|
|
41
|
+
id: string;
|
|
42
|
+
sourceKey: string | null;
|
|
43
|
+
enabled: boolean;
|
|
44
|
+
disarmReason: string | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function listed(id: string): Promise<SerializedSchedule> {
|
|
48
|
+
const { schedules } = (await listHandler({ queryParams: {} })) as {
|
|
49
|
+
schedules: SerializedSchedule[];
|
|
50
|
+
};
|
|
51
|
+
const found = schedules.find((s) => s.id === id);
|
|
52
|
+
if (!found) {
|
|
53
|
+
throw new Error(`schedule ${id} missing from the list response`);
|
|
54
|
+
}
|
|
55
|
+
return found;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function fetched(id: string): Promise<SerializedSchedule> {
|
|
59
|
+
const { schedule } = (await getHandler({ pathParams: { id } })) as {
|
|
60
|
+
schedule: SerializedSchedule;
|
|
61
|
+
};
|
|
62
|
+
return schedule;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Write a plugin dir holding one `schedules/<name>/` declaration. */
|
|
66
|
+
function writePlugin(
|
|
67
|
+
pluginName: string,
|
|
68
|
+
options: { declaration?: string; disabled?: boolean } = {},
|
|
69
|
+
): void {
|
|
70
|
+
const dir = join(pluginsDir, pluginName);
|
|
71
|
+
mkdirSync(dir, { recursive: true });
|
|
72
|
+
writeFileSync(
|
|
73
|
+
join(dir, "package.json"),
|
|
74
|
+
JSON.stringify({ name: pluginName, version: "1.0.0" }),
|
|
75
|
+
);
|
|
76
|
+
if (options.declaration) {
|
|
77
|
+
const declarationDir = join(dir, "schedules", options.declaration);
|
|
78
|
+
mkdirSync(declarationDir, { recursive: true });
|
|
79
|
+
writeFileSync(
|
|
80
|
+
join(declarationDir, "config.json"),
|
|
81
|
+
JSON.stringify({ expression: "* * * * *" }),
|
|
82
|
+
);
|
|
83
|
+
writeFileSync(join(declarationDir, "index.md"), "Summarize the day.\n");
|
|
84
|
+
}
|
|
85
|
+
if (options.disabled) {
|
|
86
|
+
writeFileSync(join(dir, ".disabled"), "");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** A declared row for `plugin:<pluginName>/digest`, armed or not. */
|
|
91
|
+
async function declaredRow(
|
|
92
|
+
pluginName: string,
|
|
93
|
+
enabled: boolean,
|
|
94
|
+
): Promise<string> {
|
|
95
|
+
const job = await upsertDeclaredSchedule(`plugin:${pluginName}/digest`, {
|
|
96
|
+
name: `${pluginName}-digest`,
|
|
97
|
+
description: "Daily digest",
|
|
98
|
+
syntax: "cron",
|
|
99
|
+
expression: "* * * * *",
|
|
100
|
+
message: "Summarize the day.",
|
|
101
|
+
mode: "execute",
|
|
102
|
+
enabled,
|
|
103
|
+
definitionHash: `hash-${pluginName}`,
|
|
104
|
+
});
|
|
105
|
+
return job.id;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Stamp the engine's exhaust latch onto a row: off, spent, and last run. */
|
|
109
|
+
function latchAsCompleted(id: string): void {
|
|
110
|
+
getDb().run(
|
|
111
|
+
`UPDATE cron_jobs SET enabled = 0, next_run_at = 0, last_run_at = ${Date.now()} WHERE id = '${id}'`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
beforeEach(() => {
|
|
116
|
+
getDb().run("DELETE FROM cron_runs");
|
|
117
|
+
getDb().run("DELETE FROM cron_jobs");
|
|
118
|
+
rmSync(pluginsDir, { recursive: true, force: true });
|
|
119
|
+
// The feature ships off, so every case states the flag it runs under rather
|
|
120
|
+
// than inheriting the registry default.
|
|
121
|
+
setOverridesForTesting({ "plugin-schedules": true });
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("schedule serialization: disarmReason", () => {
|
|
125
|
+
test("a user's own override outranks whatever the plugin's files say", async () => {
|
|
126
|
+
writePlugin("news", { declaration: "digest" });
|
|
127
|
+
const id = await declaredRow("news", true);
|
|
128
|
+
await setUserEnabled(id, false);
|
|
129
|
+
|
|
130
|
+
expect((await listed(id)).disarmReason).toBe("user_disabled");
|
|
131
|
+
expect((await fetched(id)).disarmReason).toBe("user_disabled");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("a plugin that is gone from disk reads as removed", async () => {
|
|
135
|
+
const id = await declaredRow("news", false);
|
|
136
|
+
|
|
137
|
+
expect((await listed(id)).disarmReason).toBe("plugin_removed");
|
|
138
|
+
expect((await fetched(id)).disarmReason).toBe("plugin_removed");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("a plugin carrying the .disabled sentinel reads as disabled", async () => {
|
|
142
|
+
writePlugin("news", { declaration: "digest", disabled: true });
|
|
143
|
+
const id = await declaredRow("news", false);
|
|
144
|
+
|
|
145
|
+
expect((await listed(id)).disarmReason).toBe("plugin_disabled");
|
|
146
|
+
expect((await fetched(id)).disarmReason).toBe("plugin_disabled");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("a declaration dropped from a plugin that is still installed", async () => {
|
|
150
|
+
writePlugin("news");
|
|
151
|
+
const id = await declaredRow("news", false);
|
|
152
|
+
|
|
153
|
+
expect((await listed(id)).disarmReason).toBe("declaration_removed");
|
|
154
|
+
expect((await fetched(id)).disarmReason).toBe("declaration_removed");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("a declaration still on disk means the plugin's files turned it off", async () => {
|
|
158
|
+
writePlugin("news", { declaration: "digest" });
|
|
159
|
+
const id = await declaredRow("news", false);
|
|
160
|
+
|
|
161
|
+
expect((await listed(id)).disarmReason).toBe("declaration_disabled");
|
|
162
|
+
expect((await fetched(id)).disarmReason).toBe("declaration_disabled");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("an armed plugin row has no reason to give", async () => {
|
|
166
|
+
writePlugin("news", { declaration: "digest" });
|
|
167
|
+
const id = await declaredRow("news", true);
|
|
168
|
+
|
|
169
|
+
const row = await listed(id);
|
|
170
|
+
expect(row.enabled).toBe(true);
|
|
171
|
+
expect(row.disarmReason).toBeNull();
|
|
172
|
+
expect((await fetched(id)).disarmReason).toBeNull();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("a run-to-completion row reads as finished, not paused", async () => {
|
|
176
|
+
writePlugin("news", { declaration: "digest" });
|
|
177
|
+
const id = await declaredRow("news", true);
|
|
178
|
+
latchAsCompleted(id);
|
|
179
|
+
|
|
180
|
+
const row = await listed(id);
|
|
181
|
+
expect(row.enabled).toBe(false);
|
|
182
|
+
expect(row.disarmReason).toBeNull();
|
|
183
|
+
expect((await fetched(id)).disarmReason).toBeNull();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("the kill switch being off is not a per-row cause", async () => {
|
|
187
|
+
writePlugin("news", { declaration: "digest" });
|
|
188
|
+
const id = await declaredRow("news", false);
|
|
189
|
+
setOverridesForTesting({ "plugin-schedules": false });
|
|
190
|
+
|
|
191
|
+
const row = await listed(id);
|
|
192
|
+
expect(row.enabled).toBe(false);
|
|
193
|
+
expect(row.disarmReason).toBeNull();
|
|
194
|
+
expect((await fetched(id)).disarmReason).toBeNull();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("a user-created schedule never carries one, off or on", async () => {
|
|
198
|
+
const job = await createSchedule({
|
|
199
|
+
name: "wt-imperative",
|
|
200
|
+
description: "d",
|
|
201
|
+
message: "do the thing",
|
|
202
|
+
mode: "execute",
|
|
203
|
+
enabled: false,
|
|
204
|
+
timezone: null,
|
|
205
|
+
expression: "* * * * *",
|
|
206
|
+
syntax: "cron",
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const row = await listed(job.id);
|
|
210
|
+
expect(row.sourceKey).toBeNull();
|
|
211
|
+
expect(row.enabled).toBe(false);
|
|
212
|
+
expect(row.disarmReason).toBeNull();
|
|
213
|
+
expect((await fetched(job.id)).disarmReason).toBeNull();
|
|
214
|
+
});
|
|
215
|
+
});
|
|
@@ -288,6 +288,31 @@ describe("stt-routes", () => {
|
|
|
288
288
|
expect(err.message).toContain("not available");
|
|
289
289
|
});
|
|
290
290
|
|
|
291
|
+
test("surfaces a typed resolver error verbatim instead of the generic copy", async () => {
|
|
292
|
+
// A streaming-only provider IS configured, so the "nothing configured"
|
|
293
|
+
// and "not available" strings would both send the caller looking for a
|
|
294
|
+
// problem that does not exist.
|
|
295
|
+
const reason =
|
|
296
|
+
'Deepgram Flux is streaming-only. Batch transcription requires the deepgram provider: set services.stt.provider to "deepgram".';
|
|
297
|
+
mockResolveError = new SttError("provider-error", reason, {
|
|
298
|
+
userFacing: true,
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const { handler } = getRoute("stt/transcribe");
|
|
302
|
+
const err = await expectRouteError(
|
|
303
|
+
() =>
|
|
304
|
+
handler(
|
|
305
|
+
makeArgs({
|
|
306
|
+
audioBase64: toBase64("audio-data"),
|
|
307
|
+
mimeType: "audio/wav",
|
|
308
|
+
}),
|
|
309
|
+
),
|
|
310
|
+
503,
|
|
311
|
+
"SERVICE_UNAVAILABLE",
|
|
312
|
+
);
|
|
313
|
+
expect(err.message).toBe(reason);
|
|
314
|
+
});
|
|
315
|
+
|
|
291
316
|
// -- Timeout --------------------------------------------------------------
|
|
292
317
|
|
|
293
318
|
test("throws 504 when transcription times out", async () => {
|
|
@@ -26,6 +26,10 @@ interface StubConversation {
|
|
|
26
26
|
handleSurfaceUndoCalled?: boolean;
|
|
27
27
|
handleSurfaceUndoThrows?: Error;
|
|
28
28
|
trustContext?: { trustClass: string; sourceChannel: string };
|
|
29
|
+
/** Drives `isProcessing()`; the route must not stamp trust either way. */
|
|
30
|
+
processing?: boolean;
|
|
31
|
+
/** Trust the route threaded into `handleSurfaceAction`. */
|
|
32
|
+
lastRequesterTrustContext?: { trustClass: string; sourceChannel: string };
|
|
29
33
|
surfaceActionCalls: Array<{
|
|
30
34
|
surfaceId: string;
|
|
31
35
|
actionId: string;
|
|
@@ -160,7 +164,9 @@ function makeStub(id: string): StubConversation {
|
|
|
160
164
|
actionId: string,
|
|
161
165
|
data: unknown,
|
|
162
166
|
sourceActorPrincipalId?: string,
|
|
167
|
+
requesterTrustContext?: { trustClass: string; sourceChannel: string },
|
|
163
168
|
) => {
|
|
169
|
+
stub.lastRequesterTrustContext = requesterTrustContext;
|
|
164
170
|
stub.surfaceActionCalls.push({
|
|
165
171
|
surfaceId,
|
|
166
172
|
actionId,
|
|
@@ -181,6 +187,7 @@ function makeStub(id: string): StubConversation {
|
|
|
181
187
|
setTrustContext: (ctx: { trustClass: string; sourceChannel: string }) => {
|
|
182
188
|
stub.trustContext = ctx;
|
|
183
189
|
},
|
|
190
|
+
isProcessing: () => stub.processing === true,
|
|
184
191
|
});
|
|
185
192
|
return stub;
|
|
186
193
|
}
|
|
@@ -487,8 +494,8 @@ describe("triggerSurfaceAction trust context", () => {
|
|
|
487
494
|
headers: { "x-vellum-actor-principal-id": GUARDIAN_PRINCIPAL },
|
|
488
495
|
});
|
|
489
496
|
|
|
490
|
-
expect(live.
|
|
491
|
-
expect(live.
|
|
497
|
+
expect(live.lastRequesterTrustContext?.trustClass).toBe("guardian");
|
|
498
|
+
expect(live.lastRequesterTrustContext?.sourceChannel).toBe("vellum");
|
|
492
499
|
// First-pass resolve already granted guardian, so the drift helper is skipped.
|
|
493
500
|
expect(reResolveCalls).toEqual([]);
|
|
494
501
|
});
|
|
@@ -506,7 +513,7 @@ describe("triggerSurfaceAction trust context", () => {
|
|
|
506
513
|
});
|
|
507
514
|
|
|
508
515
|
expect(reResolveCalls).toEqual([VELLUM_PRINCIPAL_OLD]);
|
|
509
|
-
expect(live.
|
|
516
|
+
expect(live.lastRequesterTrustContext?.trustClass).toBe("unknown");
|
|
510
517
|
});
|
|
511
518
|
|
|
512
519
|
test("reset drift: helper returns guardian → route adopts it", async () => {
|
|
@@ -522,7 +529,7 @@ describe("triggerSurfaceAction trust context", () => {
|
|
|
522
529
|
});
|
|
523
530
|
|
|
524
531
|
expect(reResolveCalls).toEqual([VELLUM_PRINCIPAL_OLD]);
|
|
525
|
-
expect(live.
|
|
532
|
+
expect(live.lastRequesterTrustContext?.trustClass).toBe("guardian");
|
|
526
533
|
});
|
|
527
534
|
|
|
528
535
|
test("dev-bypass resolves the real guardian principal from the gateway, helper not called", async () => {
|
|
@@ -539,7 +546,7 @@ describe("triggerSurfaceAction trust context", () => {
|
|
|
539
546
|
|
|
540
547
|
// The synthetic dev-bypass principal is translated to the real guardian,
|
|
541
548
|
// yielding a guardian trust context without consulting the drift helper.
|
|
542
|
-
expect(live.
|
|
549
|
+
expect(live.lastRequesterTrustContext?.trustClass).toBe("guardian");
|
|
543
550
|
expect(reResolveCalls).toEqual([]);
|
|
544
551
|
});
|
|
545
552
|
|
|
@@ -559,7 +566,49 @@ describe("triggerSurfaceAction trust context", () => {
|
|
|
559
566
|
headers: { "x-vellum-actor-principal-id": "dev-bypass" },
|
|
560
567
|
});
|
|
561
568
|
|
|
562
|
-
expect(live.
|
|
569
|
+
expect(live.lastRequesterTrustContext?.trustClass).toBe("unknown");
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
test("idle conversation: the requester is threaded to the action and the slot is left to the dispatch", async () => {
|
|
573
|
+
mockGuardianList = [guardianDelivery(GUARDIAN_PRINCIPAL)];
|
|
574
|
+
const live = makeStub("conv-idle");
|
|
575
|
+
live.processing = false;
|
|
576
|
+
memoryBySurface = live;
|
|
577
|
+
|
|
578
|
+
const handler = findHandler("triggerSurfaceAction");
|
|
579
|
+
await handler({
|
|
580
|
+
body: { surfaceId: "surf-idle", actionId: "act-idle" },
|
|
581
|
+
headers: { "x-vellum-actor-principal-id": GUARDIAN_PRINCIPAL },
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
expect(live.lastRequesterTrustContext?.trustClass).toBe("guardian");
|
|
585
|
+
// The route resolves; `handleSurfaceAction` stamps, and only where it
|
|
586
|
+
// commits to running the turn. Stamping here would repoint the actor of a
|
|
587
|
+
// turn that starts between this call and the dispatch.
|
|
588
|
+
expect(live.trustContext).toBeUndefined();
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
test("busy conversation: the running turn's actor is left alone and the click still travels as its own actor", async () => {
|
|
592
|
+
mockGuardianList = [guardianDelivery(GUARDIAN_PRINCIPAL)];
|
|
593
|
+
const live = makeStub("conv-busy");
|
|
594
|
+
// A turn is already running under a different actor.
|
|
595
|
+
const runningTurnActor = {
|
|
596
|
+
trustClass: "untrusted",
|
|
597
|
+
sourceChannel: "sms",
|
|
598
|
+
};
|
|
599
|
+
live.trustContext = runningTurnActor;
|
|
600
|
+
live.processing = true;
|
|
601
|
+
memoryBySurface = live;
|
|
602
|
+
|
|
603
|
+
const handler = findHandler("triggerSurfaceAction");
|
|
604
|
+
await handler({
|
|
605
|
+
body: { surfaceId: "surf-busy", actionId: "act-busy" },
|
|
606
|
+
headers: { "x-vellum-actor-principal-id": GUARDIAN_PRINCIPAL },
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
// Identical object, so a re-write of an equal value still fails this.
|
|
610
|
+
expect(live.trustContext).toBe(runningTurnActor);
|
|
611
|
+
expect(live.lastRequesterTrustContext?.trustClass).toBe("guardian");
|
|
563
612
|
});
|
|
564
613
|
});
|
|
565
614
|
|
|
@@ -3,7 +3,10 @@ import { join } from "node:path";
|
|
|
3
3
|
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
4
4
|
|
|
5
5
|
import type { RouteInvokeParams } from "../../../routes/route-host-protocol.js";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
getWorkspacePluginsDir,
|
|
8
|
+
getWorkspaceRoutesDir,
|
|
9
|
+
} from "../../../util/platform.js";
|
|
7
10
|
|
|
8
11
|
// The dispatcher constructs the route host client inline and reads the enabled
|
|
9
12
|
// flag from config, so both are mocked here (there is no injection seam).
|
|
@@ -58,6 +61,16 @@ function writeHandler(name: string, content: string): void {
|
|
|
58
61
|
writeFileSync(join(getWorkspaceRoutesDir(), name), content);
|
|
59
62
|
}
|
|
60
63
|
|
|
64
|
+
function writePluginHandler(
|
|
65
|
+
pluginName: string,
|
|
66
|
+
name: string,
|
|
67
|
+
content: string,
|
|
68
|
+
): void {
|
|
69
|
+
const dir = join(getWorkspacePluginsDir(), pluginName, "routes");
|
|
70
|
+
mkdirSync(dir, { recursive: true });
|
|
71
|
+
writeFileSync(join(dir, name), content);
|
|
72
|
+
}
|
|
73
|
+
|
|
61
74
|
function jsonResponse(status: number, value: unknown): RouteInvokeResponse {
|
|
62
75
|
return {
|
|
63
76
|
status,
|
|
@@ -75,6 +88,7 @@ beforeEach(() => {
|
|
|
75
88
|
|
|
76
89
|
afterEach(() => {
|
|
77
90
|
rmSync(getWorkspaceRoutesDir(), { recursive: true, force: true });
|
|
91
|
+
rmSync(getWorkspacePluginsDir(), { recursive: true, force: true });
|
|
78
92
|
});
|
|
79
93
|
|
|
80
94
|
describe("UserRouteDispatcher — route host delegation", () => {
|
|
@@ -101,6 +115,43 @@ describe("UserRouteDispatcher — route host delegation", () => {
|
|
|
101
115
|
expect(invokeCalls[0].params.url).toContain("/v1/x/foo");
|
|
102
116
|
});
|
|
103
117
|
|
|
118
|
+
test("names the owning plugin so the host executes in its context", async () => {
|
|
119
|
+
// The host marks this plugin as in context for the handler, so
|
|
120
|
+
// plugin-scoped host APIs behave the same in the host and in-thread.
|
|
121
|
+
writePluginHandler(
|
|
122
|
+
"my-plugin",
|
|
123
|
+
"status.ts",
|
|
124
|
+
`export function GET() { return new Response(); }`,
|
|
125
|
+
);
|
|
126
|
+
hostEnabled = true;
|
|
127
|
+
const dispatcher = makeDispatcher();
|
|
128
|
+
|
|
129
|
+
await dispatcher.dispatch(
|
|
130
|
+
"plugins/my-plugin/status",
|
|
131
|
+
new Request("http://localhost/v1/x/plugins/my-plugin/status", {
|
|
132
|
+
method: "GET",
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
expect(invokeCalls[0].params.pluginName).toBe("my-plugin");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("leaves a workspace route unowned", async () => {
|
|
140
|
+
writeHandler(
|
|
141
|
+
"plain.ts",
|
|
142
|
+
`export function GET() { return new Response(); }`,
|
|
143
|
+
);
|
|
144
|
+
hostEnabled = true;
|
|
145
|
+
const dispatcher = makeDispatcher();
|
|
146
|
+
|
|
147
|
+
await dispatcher.dispatch(
|
|
148
|
+
"plain",
|
|
149
|
+
new Request("http://localhost/v1/x/plain", { method: "GET" }),
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
expect(invokeCalls[0].params.pluginName).toBeUndefined();
|
|
153
|
+
});
|
|
154
|
+
|
|
104
155
|
test("runs in-band when disabled (host never called)", async () => {
|
|
105
156
|
writeHandler(
|
|
106
157
|
"bar.ts",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
3
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
4
4
|
|
|
5
5
|
// ---------------------------------------------------------------------------
|
|
@@ -56,6 +56,15 @@ function writePluginHandler(
|
|
|
56
56
|
return fullPath;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Absolute specifier for the plugin execution context, so a handler written to
|
|
61
|
+
* a temp routes directory imports the same module instance this process holds.
|
|
62
|
+
*/
|
|
63
|
+
const PLUGIN_CONTEXT_MODULE = new URL(
|
|
64
|
+
"../../../plugins/plugin-execution-context.ts",
|
|
65
|
+
import.meta.url,
|
|
66
|
+
).href;
|
|
67
|
+
|
|
59
68
|
async function readErrorBody(
|
|
60
69
|
response: Response,
|
|
61
70
|
): Promise<{ error: { code: string; message: string } }> {
|
|
@@ -466,6 +475,71 @@ describe("plugin routes", () => {
|
|
|
466
475
|
expect(body.plugin).toBe(true);
|
|
467
476
|
});
|
|
468
477
|
|
|
478
|
+
test("executes the handler as the plugin that owns the namespace", async () => {
|
|
479
|
+
// Plugin-scoped host APIs (resolveCredential, indexDocument) read the
|
|
480
|
+
// execution context to decide what the caller may touch. A route handler
|
|
481
|
+
// that ran outside it would fall through to their unscoped branch and
|
|
482
|
+
// reach every plugin's credentials.
|
|
483
|
+
writePluginHandler(
|
|
484
|
+
"my-plugin",
|
|
485
|
+
"whoami.ts",
|
|
486
|
+
`import { getCurrentPluginName } from ${JSON.stringify(PLUGIN_CONTEXT_MODULE)};
|
|
487
|
+
export function GET(request) {
|
|
488
|
+
return Response.json({ plugin: getCurrentPluginName() ?? null });
|
|
489
|
+
}`,
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
const dispatcher = makeDispatcher();
|
|
493
|
+
const res = await dispatcher.dispatch(
|
|
494
|
+
"plugins/my-plugin/whoami",
|
|
495
|
+
makeRequest("GET"),
|
|
496
|
+
);
|
|
497
|
+
expect(res.status).toBe(200);
|
|
498
|
+
const body = await res.json();
|
|
499
|
+
expect(body.plugin).toBe("my-plugin");
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
test("evaluates the route module in the plugin's context too", async () => {
|
|
503
|
+
// A module can reach a scoped host API at import time, and the import
|
|
504
|
+
// happens once per mtime. A load left outside the context would take the
|
|
505
|
+
// unscoped branch and never be re-run scoped, so the context has to cover
|
|
506
|
+
// module evaluation and not just the handler call.
|
|
507
|
+
writePluginHandler(
|
|
508
|
+
"my-plugin",
|
|
509
|
+
"at-import.ts",
|
|
510
|
+
`import { getCurrentPluginName } from ${JSON.stringify(PLUGIN_CONTEXT_MODULE)};
|
|
511
|
+
const atImport = getCurrentPluginName() ?? null;
|
|
512
|
+
export function GET(request) {
|
|
513
|
+
return Response.json({ atImport });
|
|
514
|
+
}`,
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
const dispatcher = makeDispatcher();
|
|
518
|
+
const res = await dispatcher.dispatch(
|
|
519
|
+
"plugins/my-plugin/at-import",
|
|
520
|
+
makeRequest("GET"),
|
|
521
|
+
);
|
|
522
|
+
expect(res.status).toBe(200);
|
|
523
|
+
const body = await res.json();
|
|
524
|
+
expect(body.atImport).toBe("my-plugin");
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
test("leaves a workspace route outside any plugin context", async () => {
|
|
528
|
+
writeHandler(
|
|
529
|
+
"whoami.ts",
|
|
530
|
+
`import { getCurrentPluginName } from ${JSON.stringify(PLUGIN_CONTEXT_MODULE)};
|
|
531
|
+
export function GET(request) {
|
|
532
|
+
return Response.json({ plugin: getCurrentPluginName() ?? null });
|
|
533
|
+
}`,
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
const dispatcher = makeDispatcher();
|
|
537
|
+
const res = await dispatcher.dispatch("whoami", makeRequest("GET"));
|
|
538
|
+
expect(res.status).toBe(200);
|
|
539
|
+
const body = await res.json();
|
|
540
|
+
expect(body.plugin).toBeNull();
|
|
541
|
+
});
|
|
542
|
+
|
|
469
543
|
test("resolves nested paths and the index (namespace root)", async () => {
|
|
470
544
|
writePluginHandler(
|
|
471
545
|
"my-plugin",
|
|
@@ -574,4 +648,62 @@ describe("plugin routes", () => {
|
|
|
574
648
|
);
|
|
575
649
|
expect(other.status).toBe(404);
|
|
576
650
|
});
|
|
651
|
+
|
|
652
|
+
test("a new route export is not bound to a stale helper after upgrade", async () => {
|
|
653
|
+
// Reproduces the browser plugin 500: `/frame` loaded `src/http.ts`
|
|
654
|
+
// first, then an upgrade added `requireNumber` to http.ts and a new
|
|
655
|
+
// `/input` that imports it. Cache-busting only the entry file rebound
|
|
656
|
+
// input.ts to the cached http.ts, which had no such export.
|
|
657
|
+
const plugin = "stale-helper";
|
|
658
|
+
const helperPath = join(getWorkspacePluginsDir(), plugin, "src", "http.ts");
|
|
659
|
+
mkdirSync(dirname(helperPath), { recursive: true });
|
|
660
|
+
writeFileSync(helperPath, `export function label() { return "v1"; }\n`);
|
|
661
|
+
writePluginHandler(
|
|
662
|
+
plugin,
|
|
663
|
+
"frame.ts",
|
|
664
|
+
`import { label } from "../src/http.js";
|
|
665
|
+
export function GET() { return Response.json({ label: label() }); }`,
|
|
666
|
+
);
|
|
667
|
+
writePluginHandler(
|
|
668
|
+
plugin,
|
|
669
|
+
"input.ts",
|
|
670
|
+
`import { label } from "../src/http.js";
|
|
671
|
+
export function POST() { return Response.json({ label: label() }); }`,
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
const dispatcher = makeDispatcher();
|
|
675
|
+
const frame1 = await dispatcher.dispatch(
|
|
676
|
+
`plugins/${plugin}/frame`,
|
|
677
|
+
makeRequest("GET"),
|
|
678
|
+
);
|
|
679
|
+
expect(frame1.status).toBe(200);
|
|
680
|
+
expect((await frame1.json()).label).toBe("v1");
|
|
681
|
+
|
|
682
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
683
|
+
writeFileSync(
|
|
684
|
+
helperPath,
|
|
685
|
+
`export function label() { return "v2"; }
|
|
686
|
+
export function extra() { return true; }\n`,
|
|
687
|
+
);
|
|
688
|
+
writePluginHandler(
|
|
689
|
+
plugin,
|
|
690
|
+
"input.ts",
|
|
691
|
+
`import { extra } from "../src/http.js";
|
|
692
|
+
export function POST() { return Response.json({ extra: extra() }); }`,
|
|
693
|
+
);
|
|
694
|
+
|
|
695
|
+
const input = await dispatcher.dispatch(
|
|
696
|
+
`plugins/${plugin}/input`,
|
|
697
|
+
makeRequest("POST"),
|
|
698
|
+
);
|
|
699
|
+
expect(input.status).toBe(200);
|
|
700
|
+
expect((await input.json()).extra).toBe(true);
|
|
701
|
+
|
|
702
|
+
const frame2 = await dispatcher.dispatch(
|
|
703
|
+
`plugins/${plugin}/frame`,
|
|
704
|
+
makeRequest("GET"),
|
|
705
|
+
);
|
|
706
|
+
expect(frame2.status).toBe(200);
|
|
707
|
+
expect((await frame2.json()).label).toBe("v2");
|
|
708
|
+
});
|
|
577
709
|
});
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* GET /v1/channels/available — return the channels this assistant can
|
|
5
5
|
* surface to clients (Contacts / GuardianChannels views, etc.) along with
|
|
6
6
|
* their display metadata (label, subtitle, icon, verification capability,
|
|
7
|
-
* setup-message copy).
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* switches.
|
|
7
|
+
* setup-message copy). A fixed base list, plus `email` when an inbox is
|
|
8
|
+
* registered, plus the channels installed plugins bring by declaring
|
|
9
|
+
* ingress. One list, because a plugin channel is a channel; each row's
|
|
10
|
+
* `source` says who contributes it. Clients should treat the response as
|
|
11
|
+
* authoritative and stop carrying their own per-channel switches.
|
|
12
12
|
*
|
|
13
13
|
* Distinct from `/v1/channels/readiness` (which answers "is this channel
|
|
14
14
|
* configured and working?"). Availability answers "could this channel be
|
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
import { z } from "zod";
|
|
19
19
|
|
|
20
20
|
import { isA2AEnabled } from "../../a2a/feature-gate.js";
|
|
21
|
+
import { discoverPluginChannels } from "../../channels/plugin-channel-declarations.js";
|
|
21
22
|
import {
|
|
22
|
-
|
|
23
|
+
type AvailableChannel,
|
|
23
24
|
CHANNEL_METADATA,
|
|
24
25
|
type ChannelId,
|
|
25
26
|
type ChannelInfo,
|
|
@@ -73,7 +74,7 @@ async function hasRegisteredInbox(): Promise<boolean> {
|
|
|
73
74
|
|
|
74
75
|
async function handleGetChannelAvailability(
|
|
75
76
|
_args: RouteHandlerArgs,
|
|
76
|
-
): Promise<{ channels:
|
|
77
|
+
): Promise<{ channels: AvailableChannel[] }> {
|
|
77
78
|
const ids: ChannelId[] = [...BASE_AVAILABLE_CHANNELS];
|
|
78
79
|
if (await hasRegisteredInbox()) {
|
|
79
80
|
ids.push("email");
|
|
@@ -86,14 +87,26 @@ async function handleGetChannelAvailability(
|
|
|
86
87
|
// contains channels that BASE_AVAILABLE_CHANNELS / the email branch
|
|
87
88
|
// explicitly chose, so the lookup is always defined — filter to satisfy
|
|
88
89
|
// the type system without a non-null assertion.
|
|
89
|
-
const
|
|
90
|
+
const builtIn: AvailableChannel[] = ids
|
|
90
91
|
.map((id) => CHANNEL_METADATA[id])
|
|
91
|
-
.filter((info): info is ChannelInfo => info !== undefined)
|
|
92
|
-
|
|
92
|
+
.filter((info): info is ChannelInfo => info !== undefined)
|
|
93
|
+
.map((info) => ({ ...info, source: "default" as const }));
|
|
94
|
+
// One list, because a plugin channel is a channel: what differs is who
|
|
95
|
+
// contributes it, which is what `source` says. Built-ins lead, so a client
|
|
96
|
+
// rendering in order gets a stable list that plugin installs append to.
|
|
97
|
+
return { channels: [...builtIn, ...(await discoverPluginChannels())] };
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
const channelInfoSchema = z.object({
|
|
96
|
-
|
|
101
|
+
// A string rather than the channel enum: a plugin channel's id is its
|
|
102
|
+
// plugin name, and `source` is what says which kind a row is.
|
|
103
|
+
id: z.string(),
|
|
104
|
+
source: z
|
|
105
|
+
.string()
|
|
106
|
+
.describe(
|
|
107
|
+
"`default` for a channel the assistant ships, `plugin:<name>` for one " +
|
|
108
|
+
"an installed plugin brings",
|
|
109
|
+
),
|
|
97
110
|
label: z.string(),
|
|
98
111
|
subtitle: z.string(),
|
|
99
112
|
icon: z.string(),
|
|
@@ -117,14 +130,19 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
117
130
|
description:
|
|
118
131
|
"Return the channels this assistant can surface to clients, with " +
|
|
119
132
|
"display metadata (label, icon, verification capability, setup " +
|
|
120
|
-
"copy).
|
|
121
|
-
"
|
|
133
|
+
"copy). A fixed base list plus `email` when an inbox is registered, " +
|
|
134
|
+
"plus the channels installed plugins bring by declaring ingress " +
|
|
135
|
+
"routes. Each carries a `source` saying which of those it is.",
|
|
122
136
|
tags: ["channels"],
|
|
123
137
|
handler: handleGetChannelAvailability,
|
|
124
138
|
responseBody: z.object({
|
|
125
139
|
channels: z
|
|
126
140
|
.array(channelInfoSchema)
|
|
127
|
-
.describe(
|
|
141
|
+
.describe(
|
|
142
|
+
"Available channels in display order: the ones the assistant " +
|
|
143
|
+
"ships, then the ones installed plugins bring by declaring " +
|
|
144
|
+
"ingress routes. `source` distinguishes them.",
|
|
145
|
+
),
|
|
128
146
|
}),
|
|
129
147
|
},
|
|
130
148
|
];
|