@vellumai/assistant 0.8.1 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +2 -7
- package/Dockerfile +75 -1
- package/bun.lock +11 -1
- package/docker-entrypoint.sh +5 -0
- package/docker-init-apt-root.sh +94 -0
- package/docker-kata-apt-env.sh +39 -0
- package/docs/plugins.md +88 -47
- package/docs/skills.md +9 -7
- package/examples/plugins/echo/README.md +27 -27
- package/examples/plugins/echo/package.json +3 -0
- package/examples/plugins/echo/register.ts +31 -31
- package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
- package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
- package/openapi.yaml +325 -3
- package/package.json +3 -1
- package/scripts/generate-openapi.ts +83 -10
- package/scripts/sync-llm-catalog.ts +2 -2
- package/scripts/sync-web-search-catalog.ts +47 -25
- package/src/__tests__/agent-image-optimize.test.ts +11 -3
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
- package/src/__tests__/anthropic-provider.test.ts +45 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
- package/src/__tests__/app-executors.test.ts +220 -4
- package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
- package/src/__tests__/bundled-asset.test.ts +6 -6
- package/src/__tests__/channel-availability-routes.test.ts +206 -0
- package/src/__tests__/channel-delivery-store.test.ts +289 -1
- package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
- package/src/__tests__/clawhub.test.ts +75 -16
- package/src/__tests__/compactor-tail-resolution.test.ts +41 -0
- package/src/__tests__/config-schema.test.ts +21 -0
- package/src/__tests__/config-set-route.test.ts +80 -0
- package/src/__tests__/config-sounds-sync.test.ts +97 -0
- package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
- package/src/__tests__/context-search-conversations-source.test.ts +117 -2
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
- package/src/__tests__/context-search-workspace-source.test.ts +7 -0
- package/src/__tests__/context-token-estimator.test.ts +1 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
- package/src/__tests__/conversation-agent-loop.test.ts +2 -0
- package/src/__tests__/conversation-error.test.ts +42 -3
- package/src/__tests__/conversation-fork-crud.test.ts +82 -0
- package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
- package/src/__tests__/conversation-lifecycle.test.ts +173 -0
- package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
- package/src/__tests__/conversation-pairing.test.ts +54 -0
- package/src/__tests__/conversation-process-callsite.test.ts +4 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
- package/src/__tests__/conversation-queue.test.ts +4 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +76 -9
- package/src/__tests__/conversation-slash-queue.test.ts +59 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
- package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
- package/src/__tests__/conversation-sync-tags.test.ts +235 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
- package/src/__tests__/credential-security-invariants.test.ts +3 -2
- package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
- package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
- package/src/__tests__/disk-pressure-tools.test.ts +1 -0
- package/src/__tests__/dm-backfill.test.ts +121 -10
- package/src/__tests__/document-tool-security.test.ts +258 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- package/src/__tests__/edit-propagation.test.ts +33 -0
- package/src/__tests__/empty-response-pipeline.test.ts +0 -4
- package/src/__tests__/external-plugin-loader.test.ts +60 -36
- package/src/__tests__/filing-service.test.ts +140 -0
- package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
- package/src/__tests__/helpers/tar-fixtures.ts +39 -0
- package/src/__tests__/helpers/wait-for.ts +21 -0
- package/src/__tests__/history-repair-pipeline.test.ts +0 -3
- package/src/__tests__/history-repair.test.ts +73 -0
- package/src/__tests__/host-app-control-proxy.test.ts +266 -10
- package/src/__tests__/image-credentials.test.ts +1 -1
- package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
- package/src/__tests__/inference-profile-reaper.test.ts +4 -2
- package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
- package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
- package/src/__tests__/injector-chain.test.ts +10 -8
- package/src/__tests__/install-skill-routing.test.ts +155 -37
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +92 -3
- package/src/__tests__/list-messages-page-latest.test.ts +55 -0
- package/src/__tests__/llm-call-pipeline.test.ts +0 -3
- package/src/__tests__/llm-catalog-parity.test.ts +55 -13
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +34 -0
- package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
- package/src/__tests__/llm-usage-store.test.ts +114 -0
- package/src/__tests__/managed-profile-guard.test.ts +31 -29
- package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
- package/src/__tests__/managed-store.test.ts +84 -192
- package/src/__tests__/media-generate-image.test.ts +1 -1
- package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
- package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
- package/src/__tests__/oauth-commands-routes.test.ts +168 -16
- package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
- package/src/__tests__/openai-provider.test.ts +24 -0
- package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
- package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
- package/src/__tests__/persistence-pipeline.test.ts +0 -2
- package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +1 -1
- package/src/__tests__/platform.test.ts +2 -0
- package/src/__tests__/plugin-api-shim.test.ts +125 -0
- package/src/__tests__/plugin-bootstrap.test.ts +10 -36
- package/src/__tests__/plugin-external-api.test.ts +68 -0
- package/src/__tests__/plugin-registry.test.ts +0 -77
- package/src/__tests__/plugin-route-contribution.test.ts +0 -1
- package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
- package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
- package/src/__tests__/plugin-types.test.ts +3 -13
- package/src/__tests__/process-message-background-slack.test.ts +8 -1
- package/src/__tests__/process-message-display-content.test.ts +421 -0
- package/src/__tests__/provider-catalog-visibility.test.ts +142 -0
- package/src/__tests__/provider-error-scenarios.test.ts +111 -0
- package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +8 -8
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
- package/src/__tests__/schedule-routes.test.ts +50 -3
- package/src/__tests__/schedule-store.test.ts +94 -0
- package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
- package/src/__tests__/schema-transforms.test.ts +20 -0
- package/src/__tests__/search-skills-unified.test.ts +0 -5
- package/src/__tests__/server-history-render.test.ts +43 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
- package/src/__tests__/skill-load-tool.test.ts +27 -89
- package/src/__tests__/skill-memory.test.ts +23 -3
- package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
- package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
- package/src/__tests__/skills-install-extract.test.ts +49 -38
- package/src/__tests__/skills-install-staging.test.ts +159 -0
- package/src/__tests__/skills-uninstall.test.ts +9 -41
- package/src/__tests__/skills.test.ts +51 -58
- package/src/__tests__/slack-channel-config.test.ts +9 -0
- package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
- package/src/__tests__/system-prompt.test.ts +737 -63
- package/src/__tests__/terminal-tools.test.ts +28 -1
- package/src/__tests__/thread-backfill.test.ts +557 -27
- package/src/__tests__/title-generate-pipeline.test.ts +0 -13
- package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
- package/src/__tests__/tool-error-pipeline.test.ts +0 -3
- package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
- package/src/__tests__/tool-executor.test.ts +16 -4
- package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
- package/src/__tests__/turn-events-store.test.ts +256 -0
- package/src/__tests__/twilio-routes.test.ts +4 -0
- package/src/__tests__/user-plugin-loader.test.ts +0 -7
- package/src/__tests__/voice-session-bridge.test.ts +198 -0
- package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
- package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
- package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
- package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
- package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
- package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
- package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
- package/src/acp/resolve-agent.ts +1 -1
- package/src/agent/image-optimize.ts +13 -5
- package/src/calls/voice-session-bridge.ts +61 -42
- package/src/channels/types.ts +108 -0
- package/src/cli/__tests__/unknown-command.test.ts +24 -0
- package/src/cli/commands/__tests__/changelog.test.ts +304 -319
- package/src/cli/commands/__tests__/schedules.test.ts +491 -0
- package/src/cli/commands/changelog.ts +106 -42
- package/src/cli/commands/conversations.ts +102 -17
- package/src/cli/commands/default-action.ts +10 -53
- package/src/cli/commands/notifications.ts +329 -317
- package/src/cli/commands/plugins.ts +185 -0
- package/src/cli/commands/schedules.ts +391 -0
- package/src/cli/commands/telemetry.ts +40 -0
- package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
- package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
- package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
- package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
- package/src/cli/lib/cli-colors.ts +12 -0
- package/src/cli/lib/confirm-prompt.ts +79 -0
- package/src/cli/lib/install-from-github.ts +304 -0
- package/src/cli/lib/list-installed-plugins.ts +137 -0
- package/src/cli/lib/uninstall-plugin.ts +82 -0
- package/src/cli/lib/unknown-command.ts +111 -0
- package/src/cli/program.ts +38 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +23 -21
- package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
- package/src/config/bundled-skills/document/SKILL.md +23 -3
- package/src/config/bundled-skills/document/TOOLS.json +53 -0
- package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
- package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
- package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
- package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
- package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
- package/src/config/bundled-tool-registry.ts +6 -0
- package/src/config/feature-flag-registry.json +41 -1
- package/src/config/loader.ts +64 -38
- package/src/config/schema.ts +7 -10
- package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
- package/src/config/schemas/channels.ts +8 -0
- package/src/config/schemas/compaction.ts +28 -0
- package/src/config/schemas/heartbeat.ts +9 -0
- package/src/config/schemas/llm-request-logs.ts +31 -7
- package/src/config/schemas/llm.ts +3 -0
- package/src/config/schemas/memory-retrieval.ts +18 -0
- package/src/config/schemas/tools.ts +14 -0
- package/src/config/skills.ts +3 -96
- package/src/context/compactor.ts +1047 -0
- package/src/context/token-estimator.ts +2 -2
- package/src/context/window-manager.ts +197 -1520
- package/src/credential-execution/managed-catalog.ts +37 -0
- package/src/credential-health/credential-health-service.ts +280 -19
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +34 -0
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
- package/src/daemon/approval-generators.ts +8 -6
- package/src/daemon/config-watcher.ts +94 -31
- package/src/daemon/conversation-agent-loop.ts +169 -9
- package/src/daemon/conversation-error.ts +171 -37
- package/src/daemon/conversation-lifecycle.ts +53 -40
- package/src/daemon/conversation-messaging.ts +25 -6
- package/src/daemon/conversation-process.ts +49 -12
- package/src/daemon/conversation-runtime-assembly.ts +16 -1
- package/src/daemon/conversation-slash.ts +12 -5
- package/src/daemon/conversation-store.ts +11 -4
- package/src/daemon/conversation-tool-setup.ts +39 -7
- package/src/daemon/conversation.ts +33 -1
- package/src/daemon/external-plugins-bootstrap.ts +217 -181
- package/src/daemon/first-greeting.ts +22 -2
- package/src/daemon/handlers/config-model.ts +6 -5
- package/src/daemon/handlers/config-slack-channel.ts +15 -3
- package/src/daemon/handlers/shared.ts +14 -5
- package/src/daemon/handlers/skills.ts +111 -108
- package/src/daemon/history-repair.ts +28 -1
- package/src/daemon/host-app-control-proxy.ts +98 -23
- package/src/daemon/lifecycle.ts +45 -35
- package/src/daemon/meet-host-supervisor.ts +5 -4
- package/src/daemon/memory-v2-startup.ts +49 -0
- package/src/daemon/message-protocol.ts +1 -0
- package/src/daemon/message-types/conversations.ts +25 -0
- package/src/daemon/message-types/messages.ts +61 -0
- package/src/daemon/message-types/subagents.ts +1 -0
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/pkb-reminder-builder.test.ts +1 -1
- package/src/daemon/pkb-reminder-builder.ts +1 -1
- package/src/daemon/plugin-source-watcher.ts +146 -0
- package/src/daemon/process-message.ts +21 -3
- package/src/daemon/server.ts +11 -2
- package/src/daemon/skill-memory-refresh.ts +29 -0
- package/src/documents/document-store.ts +221 -3
- package/src/embedded/plugin-api.ts +40 -0
- package/src/filing/filing-service.ts +39 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +91 -6
- package/src/heartbeat/heartbeat-run-store.ts +2 -1
- package/src/heartbeat/heartbeat-service.ts +41 -0
- package/src/home/__tests__/feed-types.test.ts +40 -0
- package/src/home/feed-types.ts +22 -0
- package/src/home/post-connect-feed.ts +1 -0
- package/src/index.ts +18 -1
- package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
- package/src/mcp/client.ts +20 -4
- package/src/media/image-credentials.ts +3 -3
- package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
- package/src/memory/__tests__/conversation-queries.test.ts +263 -0
- package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
- package/src/memory/__tests__/message-content.test.ts +35 -0
- package/src/memory/bookmark-crud.ts +42 -10
- package/src/memory/context-search/sources/conversations.ts +62 -2
- package/src/memory/context-search/sources/workspace.ts +4 -0
- package/src/memory/conversation-crud.ts +63 -19
- package/src/memory/conversation-queries.ts +110 -10
- package/src/memory/db-init.ts +6 -0
- package/src/memory/delivery-crud.ts +152 -5
- package/src/memory/embedding-backend.ts +4 -4
- package/src/memory/external-conversation-store.ts +66 -5
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +66 -9
- package/src/memory/graph/conversation-graph-memory.ts +31 -15
- package/src/memory/graph/tools.ts +3 -3
- package/src/memory/indexer.ts +34 -29
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
- package/src/memory/jobs/embed-concept-page.ts +20 -11
- package/src/memory/jobs-worker.ts +6 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +17 -10
- package/src/memory/llm-request-log-source.ts +19 -52
- package/src/memory/llm-usage-store.ts +125 -5
- package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
- package/src/memory/message-content.ts +1 -1
- package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
- package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
- package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
- package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
- package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
- package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/onboarding-events-store.ts +106 -0
- package/src/memory/schema/bookmarks.ts +0 -2
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/inference.ts +1 -3
- package/src/memory/schema/infrastructure.ts +12 -0
- package/src/memory/turn-events-store.ts +127 -2
- package/src/memory/v2/__tests__/activation.test.ts +0 -8
- package/src/memory/v2/__tests__/injection.test.ts +98 -8
- package/src/memory/v2/__tests__/migration.test.ts +87 -0
- package/src/memory/v2/__tests__/page-index.test.ts +83 -0
- package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
- package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
- package/src/memory/v2/__tests__/router.test.ts +15 -0
- package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
- package/src/memory/v2/injection.ts +32 -6
- package/src/memory/v2/migration.ts +49 -19
- package/src/memory/v2/page-index.ts +35 -5
- package/src/memory/v2/prompts/router.ts +11 -8
- package/src/memory/v2/prompts/sweep.ts +2 -2
- package/src/memory/v2/qdrant.ts +135 -7
- package/src/memory/v2/router.ts +9 -8
- package/src/memory/v2/skill-store.ts +120 -35
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
- package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
- package/src/messaging/providers/slack/adapter.ts +43 -5
- package/src/messaging/providers/slack/client.ts +27 -0
- package/src/messaging/providers/slack/deep-link.ts +65 -0
- package/src/messaging/providers/slack/download.ts +104 -0
- package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
- package/src/messaging/providers/slack/message-metadata.ts +27 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
- package/src/messaging/providers/slack/render-transcript.ts +69 -5
- package/src/messaging/providers/slack/types.ts +20 -1
- package/src/notifications/conversation-pairing.ts +2 -1
- package/src/notifications/decision-engine.ts +2 -1
- package/src/notifications/emit-signal.ts +20 -1
- package/src/notifications/home-feed-side-effect.ts +54 -0
- package/src/notifications/signal.ts +3 -1
- package/src/oauth/connection-resolver.ts +8 -4
- package/src/oauth/platform-connection.ts +6 -2
- package/src/oauth/seed-providers.ts +10 -1
- package/src/permissions/checker.ts +2 -0
- package/src/permissions/ipc-risk-types.ts +1 -0
- package/src/permissions/question-prompter.test.ts +416 -0
- package/src/permissions/question-prompter.ts +294 -0
- package/src/platform/client.test.ts +1 -1
- package/src/platform/client.ts +1 -1
- package/src/plugin-api/constants.ts +26 -0
- package/src/plugin-api/index.ts +34 -1
- package/src/plugin-api/types.ts +104 -22
- package/src/plugins/defaults/circuit-breaker.ts +0 -5
- package/src/plugins/defaults/compaction.ts +0 -4
- package/src/plugins/defaults/empty-response.ts +0 -2
- package/src/plugins/defaults/history-repair.ts +0 -2
- package/src/plugins/defaults/injectors.ts +36 -3
- package/src/plugins/defaults/llm-call.ts +0 -2
- package/src/plugins/defaults/memory-retrieval.ts +0 -1
- package/src/plugins/defaults/overflow-reduce.ts +0 -1
- package/src/plugins/defaults/persistence.ts +0 -2
- package/src/plugins/defaults/title-generate.ts +0 -5
- package/src/plugins/defaults/token-estimate.ts +0 -2
- package/src/plugins/defaults/tool-error.ts +0 -7
- package/src/plugins/defaults/tool-execute.ts +0 -2
- package/src/plugins/defaults/tool-result-truncate.ts +0 -4
- package/src/plugins/ensure-plugin-api-shim.ts +96 -0
- package/src/plugins/external-api.ts +104 -0
- package/src/plugins/external-plugin-loader.ts +105 -32
- package/src/plugins/feature-gate.ts +22 -0
- package/src/plugins/pipeline.ts +37 -0
- package/src/plugins/registry.ts +48 -80
- package/src/plugins/types.ts +31 -26
- package/src/plugins/user-loader.ts +21 -2
- package/src/proactive-artifact/aux-message-injector.ts +11 -0
- package/src/proactive-artifact/job.test.ts +37 -5
- package/src/prompts/__tests__/system-prompt.test.ts +12 -0
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +99 -0
- package/src/prompts/normalize-onboarding.ts +27 -0
- package/src/prompts/sections.ts +302 -0
- package/src/prompts/system-prompt.ts +63 -166
- package/src/prompts/templates/BOOTSTRAP.md +17 -1
- package/src/prompts/templates/system-sections.ts +173 -0
- package/src/providers/__tests__/inference.test.ts +22 -7
- package/src/providers/anthropic/client.ts +28 -28
- package/src/providers/connection-resolution.ts +7 -0
- package/src/providers/inference/adapter-factory.ts +41 -4
- package/src/providers/inference/connections.ts +74 -29
- package/src/providers/inference/resolve-auth.ts +12 -4
- package/src/providers/model-catalog.ts +294 -12
- package/src/providers/openai/chat-completions-provider.ts +10 -2
- package/src/providers/openrouter/client.ts +7 -0
- package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -1
- package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
- package/src/providers/provider-availability.ts +17 -2
- package/src/providers/provider-catalog-visibility.ts +36 -0
- package/src/providers/registry.ts +22 -14
- package/src/providers/retry.ts +47 -1
- package/src/runtime/__tests__/agent-wake.test.ts +152 -0
- package/src/runtime/agent-wake.ts +42 -14
- package/src/runtime/auth/route-policy.ts +8 -1
- package/src/runtime/btw-sidechain.ts +2 -0
- package/src/runtime/http-types.ts +19 -0
- package/src/runtime/migrations/origin-mode.ts +1 -1
- package/src/runtime/pending-interactions.ts +1 -0
- package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +107 -20
- package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
- package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
- package/src/runtime/routes/acp-routes-list.test.ts +143 -0
- package/src/runtime/routes/acp-routes.ts +5 -3
- package/src/runtime/routes/auth-routes.ts +1 -1
- package/src/runtime/routes/bookmark-routes.ts +5 -3
- package/src/runtime/routes/btw-routes.ts +5 -1
- package/src/runtime/routes/channel-availability-routes.ts +121 -0
- package/src/runtime/routes/conversation-cli-routes.ts +44 -3
- package/src/runtime/routes/conversation-list-routes.ts +3 -20
- package/src/runtime/routes/conversation-management-routes.ts +17 -42
- package/src/runtime/routes/conversation-query-routes.ts +40 -35
- package/src/runtime/routes/conversation-routes.ts +90 -11
- package/src/runtime/routes/documents-routes.ts +25 -86
- package/src/runtime/routes/group-routes.ts +5 -0
- package/src/runtime/routes/inbound-conversation.ts +28 -8
- package/src/runtime/routes/inbound-message-handler.ts +236 -41
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
- package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
- package/src/runtime/routes/inference-provider-connection-routes.ts +65 -21
- package/src/runtime/routes/integrations/slack/share.ts +4 -52
- package/src/runtime/routes/integrations/slack/token.ts +43 -0
- package/src/runtime/routes/integrations/twilio.ts +6 -13
- package/src/runtime/routes/notification-routes.ts +1 -1
- package/src/runtime/routes/oauth-commands-routes.ts +105 -15
- package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
- package/src/runtime/routes/question-routes.ts +259 -0
- package/src/runtime/routes/rename-conversation-routes.ts +2 -33
- package/src/runtime/routes/schedule-routes.ts +4 -7
- package/src/runtime/routes/subagents-routes.ts +57 -18
- package/src/runtime/routes/telemetry-routes.ts +27 -0
- package/src/runtime/routes/tts-routes.ts +27 -2
- package/src/runtime/routes/workspace-routes.test.ts +43 -0
- package/src/runtime/routes/workspace-routes.ts +28 -0
- package/src/runtime/services/conversation-serializer.ts +39 -7
- package/src/runtime/sync/resource-sync-events.ts +93 -1
- package/src/schedule/schedule-store.ts +27 -2
- package/src/schedule/scheduler.ts +9 -1
- package/src/security/__tests__/untrusted-content.test.ts +86 -0
- package/src/security/untrusted-content.ts +93 -8
- package/src/skills/catalog-files.ts +1 -1
- package/src/skills/catalog-install.ts +233 -116
- package/src/skills/clawhub.ts +70 -13
- package/src/skills/managed-store.ts +4 -119
- package/src/skills/skillssh-registry.ts +27 -48
- package/src/subagent/manager.ts +15 -7
- package/src/telemetry/types.ts +113 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
- package/src/telemetry/usage-telemetry-reporter.ts +113 -7
- package/src/tools/apps/executors.ts +58 -7
- package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
- package/src/tools/ask-question/ask-question-tool.ts +304 -0
- package/src/tools/browser/browser-execution.ts +15 -11
- package/src/tools/computer-use/definitions.ts +3 -3
- package/src/tools/credentials/vault.ts +1 -1
- package/src/tools/document/document-tool.ts +124 -1
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +1 -1
- package/src/tools/filesystem/write.ts +5 -2
- package/src/tools/host-filesystem/transfer.ts +1 -1
- package/src/tools/host-terminal/host-shell.ts +1 -1
- package/src/tools/permission-checker.ts +1 -1
- package/src/tools/registry.ts +17 -7
- package/src/tools/schedule/create.ts +2 -2
- package/src/tools/schema-transforms.ts +7 -2
- package/src/tools/side-effects.ts +1 -0
- package/src/tools/skills/delete-managed.ts +4 -4
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/skills/scaffold-managed.ts +3 -2
- package/src/tools/subagent/notify-parent.ts +1 -1
- package/src/tools/system/request-permission.ts +2 -2
- package/src/tools/terminal/safe-env.ts +60 -1
- package/src/tools/tool-manifest.ts +2 -0
- package/src/tools/types.ts +72 -21
- package/src/tools/ui-surface/definitions.ts +6 -5
- package/src/tts/__tests__/provider-adapters.test.ts +76 -2
- package/src/tts/providers/elevenlabs-provider.ts +75 -1
- package/src/types/onboarding-context.ts +2 -0
- package/src/util/errors.ts +17 -0
- package/src/util/platform.ts +10 -0
- package/src/watcher/__tests__/engine.test.ts +22 -0
- package/src/watcher/engine.ts +6 -2
- package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
- package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
- package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
- package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
- package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
- package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
- package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/workspace/migrations/runner.ts +39 -9
- package/src/workspace/migrations/types.ts +4 -0
- package/examples/plugins/echo/bun.lock +0 -25
- package/src/__tests__/context-window-manager.test.ts +0 -2481
- package/src/context/__tests__/compact-prompt.test.ts +0 -63
- package/src/context/prompts/compact.md +0 -26
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
- /package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +0 -0
|
@@ -7,7 +7,12 @@
|
|
|
7
7
|
|
|
8
8
|
import { readFileSync } from "node:fs";
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
getConfig,
|
|
12
|
+
loadRawConfig,
|
|
13
|
+
saveRawConfig,
|
|
14
|
+
setNestedValue,
|
|
15
|
+
} from "../../config/loader.js";
|
|
11
16
|
import {
|
|
12
17
|
getServiceMode,
|
|
13
18
|
type Services,
|
|
@@ -26,9 +31,11 @@ import {
|
|
|
26
31
|
getProvider,
|
|
27
32
|
listActiveConnectionsByProvider,
|
|
28
33
|
listConnections,
|
|
34
|
+
type OAuthProviderRow,
|
|
29
35
|
} from "../../oauth/oauth-store.js";
|
|
30
36
|
import { VellumPlatformClient } from "../../platform/client.js";
|
|
31
37
|
import { withValidToken } from "../../security/token-manager.js";
|
|
38
|
+
import { matchHostPattern } from "../../tools/credentials/host-pattern-match.js";
|
|
32
39
|
import { getLogger } from "../../util/logger.js";
|
|
33
40
|
import { BadRequestError, InternalError, NotFoundError } from "./errors.js";
|
|
34
41
|
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
@@ -121,6 +128,82 @@ async function countManagedConnections(provider: string): Promise<number> {
|
|
|
121
128
|
}
|
|
122
129
|
}
|
|
123
130
|
|
|
131
|
+
function parseUrl(value: string | null | undefined): URL | undefined {
|
|
132
|
+
if (!value) return undefined;
|
|
133
|
+
try {
|
|
134
|
+
return new URL(value);
|
|
135
|
+
} catch {
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function getAllowedRequestHostPatterns(
|
|
141
|
+
providerRow: OAuthProviderRow,
|
|
142
|
+
): string[] {
|
|
143
|
+
const patterns: string[] = [];
|
|
144
|
+
|
|
145
|
+
if (providerRow.injectionTemplates) {
|
|
146
|
+
try {
|
|
147
|
+
const parsed = JSON.parse(providerRow.injectionTemplates) as unknown;
|
|
148
|
+
if (Array.isArray(parsed)) {
|
|
149
|
+
for (const entry of parsed) {
|
|
150
|
+
if (
|
|
151
|
+
entry &&
|
|
152
|
+
typeof entry === "object" &&
|
|
153
|
+
typeof (entry as { hostPattern?: unknown }).hostPattern === "string"
|
|
154
|
+
) {
|
|
155
|
+
const hostPattern = (
|
|
156
|
+
entry as { hostPattern: string }
|
|
157
|
+
).hostPattern.trim();
|
|
158
|
+
if (hostPattern) patterns.push(hostPattern);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} catch {
|
|
163
|
+
// Fall back to the provider's base URL host below.
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (patterns.length === 0) {
|
|
168
|
+
const baseUrl = parseUrl(providerRow.baseUrl);
|
|
169
|
+
if (baseUrl) patterns.push(baseUrl.hostname);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return [...new Set(patterns)];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function assertOAuthRequestUrlAllowed(
|
|
176
|
+
providerRow: OAuthProviderRow,
|
|
177
|
+
parsedUrl: URL,
|
|
178
|
+
): void {
|
|
179
|
+
const providerBaseUrl = parseUrl(providerRow.baseUrl);
|
|
180
|
+
const allowedProtocol = providerBaseUrl?.protocol ?? "https:";
|
|
181
|
+
if (parsedUrl.protocol !== allowedProtocol) {
|
|
182
|
+
throw new BadRequestError(
|
|
183
|
+
`OAuth request URL for "${providerRow.provider}" must use ${allowedProtocol.replace(/:$/, "")}.`,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const allowedHostPatterns = getAllowedRequestHostPatterns(providerRow);
|
|
188
|
+
if (allowedHostPatterns.length === 0) {
|
|
189
|
+
throw new BadRequestError(
|
|
190
|
+
`OAuth provider "${providerRow.provider}" does not define an allowed request host.`,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const allowed = allowedHostPatterns.some(
|
|
195
|
+
(pattern) =>
|
|
196
|
+
matchHostPattern(parsedUrl.hostname, pattern, {
|
|
197
|
+
includeApexForWildcard: true,
|
|
198
|
+
}) !== "none",
|
|
199
|
+
);
|
|
200
|
+
if (!allowed) {
|
|
201
|
+
throw new BadRequestError(
|
|
202
|
+
`OAuth request URL host "${parsedUrl.hostname}" is not allowed for "${providerRow.provider}". Allowed hosts: ${allowedHostPatterns.join(", ")}.`,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
124
207
|
// ---------------------------------------------------------------------------
|
|
125
208
|
// Disconnect handler
|
|
126
209
|
// ---------------------------------------------------------------------------
|
|
@@ -176,7 +259,9 @@ async function handleDisconnect({ body = {} }: RouteHandlerArgs) {
|
|
|
176
259
|
accountLabel = match.account_label;
|
|
177
260
|
} else {
|
|
178
261
|
if (entries.length === 0) {
|
|
179
|
-
throw new NotFoundError(
|
|
262
|
+
throw new NotFoundError(
|
|
263
|
+
`No active connections found for "${b.provider}".`,
|
|
264
|
+
);
|
|
180
265
|
}
|
|
181
266
|
if (entries.length > 1) {
|
|
182
267
|
throw new BadRequestError(
|
|
@@ -235,7 +320,9 @@ async function handleDisconnect({ body = {} }: RouteHandlerArgs) {
|
|
|
235
320
|
} else {
|
|
236
321
|
const active = listActiveConnectionsByProvider(b.provider);
|
|
237
322
|
if (active.length === 0) {
|
|
238
|
-
throw new NotFoundError(
|
|
323
|
+
throw new NotFoundError(
|
|
324
|
+
`No active connections found for "${b.provider}".`,
|
|
325
|
+
);
|
|
239
326
|
}
|
|
240
327
|
if (active.length > 1) {
|
|
241
328
|
throw new BadRequestError(
|
|
@@ -582,11 +669,7 @@ async function handleToken({ body = {} }: RouteHandlerArgs) {
|
|
|
582
669
|
tokenOpts = { connectionId: conn.id };
|
|
583
670
|
}
|
|
584
671
|
|
|
585
|
-
const token = await withValidToken(
|
|
586
|
-
b.provider,
|
|
587
|
-
async (t) => t,
|
|
588
|
-
tokenOpts,
|
|
589
|
-
);
|
|
672
|
+
const token = await withValidToken(b.provider, async (t) => t, tokenOpts);
|
|
590
673
|
|
|
591
674
|
return { ok: true, token };
|
|
592
675
|
}
|
|
@@ -666,6 +749,7 @@ async function handleRequest({ body = {} }: RouteHandlerArgs) {
|
|
|
666
749
|
|
|
667
750
|
if (b.url.startsWith("http://") || b.url.startsWith("https://")) {
|
|
668
751
|
const parsed = new URL(b.url);
|
|
752
|
+
assertOAuthRequestUrlAllowed(providerRow, parsed);
|
|
669
753
|
baseUrl = `${parsed.protocol}//${parsed.host}`;
|
|
670
754
|
requestPath = parsed.pathname;
|
|
671
755
|
for (const [key, value] of parsed.searchParams.entries()) {
|
|
@@ -717,7 +801,12 @@ async function handleRequest({ body = {} }: RouteHandlerArgs) {
|
|
|
717
801
|
const query: Record<string, string | string[]> = { ...queryFromUrl };
|
|
718
802
|
|
|
719
803
|
// Use pre-parsed data from CLI, or fall back to raw data string for direct API callers
|
|
720
|
-
const resolvedData =
|
|
804
|
+
const resolvedData =
|
|
805
|
+
b.parsed_data !== undefined
|
|
806
|
+
? b.parsed_data
|
|
807
|
+
: b.data !== undefined
|
|
808
|
+
? readBodyData(b.data)
|
|
809
|
+
: undefined;
|
|
721
810
|
|
|
722
811
|
if (resolvedData !== undefined) {
|
|
723
812
|
const rawBody = resolvedData;
|
|
@@ -855,7 +944,9 @@ async function handleManagedConnect({ body = {} }: RouteHandlerArgs) {
|
|
|
855
944
|
return { ok: true, connect_url: result.connect_url };
|
|
856
945
|
}
|
|
857
946
|
|
|
858
|
-
async function handleManagedConnectPoll({
|
|
947
|
+
async function handleManagedConnectPoll({
|
|
948
|
+
queryParams = {},
|
|
949
|
+
}: RouteHandlerArgs) {
|
|
859
950
|
const provider = queryParams.provider;
|
|
860
951
|
if (!provider) throw new BadRequestError("provider query param is required");
|
|
861
952
|
|
|
@@ -894,7 +985,8 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
894
985
|
endpoint: "oauth/mode",
|
|
895
986
|
method: "GET",
|
|
896
987
|
summary: "Get OAuth mode",
|
|
897
|
-
description:
|
|
988
|
+
description:
|
|
989
|
+
"Get the current OAuth mode (managed or your-own) for a provider.",
|
|
898
990
|
tags: ["oauth"],
|
|
899
991
|
requirePolicyEnforcement: true,
|
|
900
992
|
queryParams: [
|
|
@@ -913,8 +1005,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
913
1005
|
method: "POST",
|
|
914
1006
|
policyKey: "oauth/mode.set",
|
|
915
1007
|
summary: "Set OAuth mode",
|
|
916
|
-
description:
|
|
917
|
-
"Set the OAuth mode (managed or your-own) for a provider.",
|
|
1008
|
+
description: "Set the OAuth mode (managed or your-own) for a provider.",
|
|
918
1009
|
tags: ["oauth"],
|
|
919
1010
|
requirePolicyEnforcement: true,
|
|
920
1011
|
handler: handleModeSet,
|
|
@@ -955,8 +1046,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
955
1046
|
method: "POST",
|
|
956
1047
|
policyKey: "oauth/token",
|
|
957
1048
|
summary: "Get OAuth token",
|
|
958
|
-
description:
|
|
959
|
-
"Retrieve a valid OAuth access token for a BYO-mode provider.",
|
|
1049
|
+
description: "Retrieve a valid OAuth access token for a BYO-mode provider.",
|
|
960
1050
|
tags: ["oauth"],
|
|
961
1051
|
requirePolicyEnforcement: true,
|
|
962
1052
|
handler: handleToken,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport-agnostic routes for OAuth lifecycle events.
|
|
3
|
+
*
|
|
4
|
+
* Allows CLI commands to notify the daemon when OAuth state changes
|
|
5
|
+
* (e.g. after `assistant oauth connect`) so the daemon can refresh its
|
|
6
|
+
* cached config and credential state.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
|
|
11
|
+
import { getConfig, invalidateConfigCache } from "../../config/loader.js";
|
|
12
|
+
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
13
|
+
|
|
14
|
+
// ── Handlers ──────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
function handleOAuthConnectionChanged(_args: RouteHandlerArgs): {
|
|
17
|
+
refreshed: boolean;
|
|
18
|
+
} {
|
|
19
|
+
invalidateConfigCache();
|
|
20
|
+
// Force re-read from disk so subsequent resolveOAuthConnection() calls
|
|
21
|
+
// in this process see the current mode setting.
|
|
22
|
+
getConfig();
|
|
23
|
+
return { refreshed: true };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ── Routes ────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
export const ROUTES: RouteDefinition[] = [
|
|
29
|
+
{
|
|
30
|
+
operationId: "oauth_connection_changed",
|
|
31
|
+
endpoint: "oauth/connection-changed",
|
|
32
|
+
method: "POST",
|
|
33
|
+
policyKey: "oauth/connection-changed",
|
|
34
|
+
handler: handleOAuthConnectionChanged,
|
|
35
|
+
summary: "Notify the assistant that an OAuth connection changed",
|
|
36
|
+
description:
|
|
37
|
+
"Invalidates the config cache so the assistant picks up mode and credential changes immediately.",
|
|
38
|
+
tags: ["oauth"],
|
|
39
|
+
responseBody: z.object({
|
|
40
|
+
refreshed: z.boolean(),
|
|
41
|
+
}),
|
|
42
|
+
},
|
|
43
|
+
];
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route handler for resolving pending question prompts.
|
|
3
|
+
*
|
|
4
|
+
* POST /v1/question-response — a client (UI or remote channel) submits the
|
|
5
|
+
* user's selection for a pending ask-question interaction registered by
|
|
6
|
+
* {@link QuestionPrompter}. Two top-level shapes are accepted:
|
|
7
|
+
*
|
|
8
|
+
* - `kind: "submit"` carries a `responses` array — one entry per question in
|
|
9
|
+
* the original batch. The web client builds this locally and POSTs it once
|
|
10
|
+
* the user is done revising the card.
|
|
11
|
+
* - `kind: "close"` records that the user dismissed the card without
|
|
12
|
+
* answering; every entry is reported as `skipped`.
|
|
13
|
+
*
|
|
14
|
+
* For backwards-compat we also accept the prior single-question shape
|
|
15
|
+
* (`{ kind: "option" | "free_text", ... }`) as syntactic sugar for a
|
|
16
|
+
* one-element batch. That branch only succeeds against a single-question
|
|
17
|
+
* batch — multi-question batches reject it with a helpful error.
|
|
18
|
+
*
|
|
19
|
+
* Cross-talk safety: pending interactions of other kinds (`confirmation`,
|
|
20
|
+
* `secret`, host_*, etc.) return 404 here rather than being mis-resolved.
|
|
21
|
+
*/
|
|
22
|
+
import { z } from "zod";
|
|
23
|
+
|
|
24
|
+
import {
|
|
25
|
+
buildBatchEntries,
|
|
26
|
+
type QuestionBatchMetadata,
|
|
27
|
+
type QuestionBatchSubmission,
|
|
28
|
+
QuestionBatchValidationError,
|
|
29
|
+
type QuestionPromptResult,
|
|
30
|
+
} from "../../permissions/question-prompter.js";
|
|
31
|
+
import { getLogger } from "../../util/logger.js";
|
|
32
|
+
import * as pendingInteractions from "../pending-interactions.js";
|
|
33
|
+
import { BadRequestError, NotFoundError } from "./errors.js";
|
|
34
|
+
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
35
|
+
|
|
36
|
+
const log = getLogger("question-routes");
|
|
37
|
+
|
|
38
|
+
// ── Batched (current) body shape ────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
const SubmitEntry = z.discriminatedUnion("kind", [
|
|
41
|
+
z.object({
|
|
42
|
+
questionId: z.string(),
|
|
43
|
+
kind: z.literal("option"),
|
|
44
|
+
optionId: z.string(),
|
|
45
|
+
}),
|
|
46
|
+
z.object({
|
|
47
|
+
questionId: z.string(),
|
|
48
|
+
kind: z.literal("free_text"),
|
|
49
|
+
text: z.string(),
|
|
50
|
+
}),
|
|
51
|
+
z.object({
|
|
52
|
+
questionId: z.string(),
|
|
53
|
+
kind: z.literal("skip"),
|
|
54
|
+
}),
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
const SubmitBody = z.object({
|
|
58
|
+
requestId: z.string(),
|
|
59
|
+
kind: z.literal("submit"),
|
|
60
|
+
responses: z.array(SubmitEntry).min(1),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const CloseBody = z.object({
|
|
64
|
+
requestId: z.string(),
|
|
65
|
+
kind: z.literal("close"),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// ── Legacy single-question body shape (sugar for one-element batch) ──
|
|
69
|
+
|
|
70
|
+
const LegacyOptionBody = z.object({
|
|
71
|
+
requestId: z.string(),
|
|
72
|
+
kind: z.literal("option"),
|
|
73
|
+
optionId: z.string(),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const LegacyFreeTextBody = z.object({
|
|
77
|
+
requestId: z.string(),
|
|
78
|
+
kind: z.literal("free_text"),
|
|
79
|
+
text: z.string(),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// All four variants are mutually exclusive by their `kind` literal, so use
|
|
83
|
+
// `discriminatedUnion` rather than plain `union`. The generated OpenAPI then
|
|
84
|
+
// emits `oneOf` for the body (matching the pre-batched-shape spec) instead
|
|
85
|
+
// of the looser `anyOf` that `z.union` produces.
|
|
86
|
+
const QuestionResponseBody = z.discriminatedUnion("kind", [
|
|
87
|
+
SubmitBody,
|
|
88
|
+
CloseBody,
|
|
89
|
+
LegacyOptionBody,
|
|
90
|
+
LegacyFreeTextBody,
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
type SubmitBody = z.infer<typeof SubmitBody>;
|
|
94
|
+
type CloseBody = z.infer<typeof CloseBody>;
|
|
95
|
+
type LegacyOptionBody = z.infer<typeof LegacyOptionBody>;
|
|
96
|
+
type LegacyFreeTextBody = z.infer<typeof LegacyFreeTextBody>;
|
|
97
|
+
type QuestionResponseBody = z.infer<typeof QuestionResponseBody>;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* POST /v1/question-response — resolve a pending ask-question interaction.
|
|
101
|
+
*/
|
|
102
|
+
function handleQuestionResponse({ body }: RouteHandlerArgs) {
|
|
103
|
+
const parsed = QuestionResponseBody.safeParse(body);
|
|
104
|
+
if (!parsed.success) {
|
|
105
|
+
throw new BadRequestError(
|
|
106
|
+
`Invalid question response body: ${parsed.error.message}`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const response: QuestionResponseBody = parsed.data;
|
|
111
|
+
const { requestId } = response;
|
|
112
|
+
|
|
113
|
+
const interaction = pendingInteractions.get(requestId);
|
|
114
|
+
if (!interaction || interaction.kind !== "question") {
|
|
115
|
+
log.warn(
|
|
116
|
+
{ requestId, foundKind: interaction?.kind },
|
|
117
|
+
"Question response for unknown or wrong-kind requestId",
|
|
118
|
+
);
|
|
119
|
+
throw new NotFoundError(
|
|
120
|
+
"No pending question interaction found for this requestId",
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Build + validate the result BEFORE touching `pendingInteractions`, so a
|
|
125
|
+
// bad payload leaves the pending interaction (and its timer) intact and the
|
|
126
|
+
// user gets another chance to submit a correct batch.
|
|
127
|
+
let result: QuestionPromptResult;
|
|
128
|
+
try {
|
|
129
|
+
if (response.kind === "close") {
|
|
130
|
+
const { orderedIds } = readBatchMetadata(interaction);
|
|
131
|
+
result = {
|
|
132
|
+
entries: orderedIds.map((id) => ({
|
|
133
|
+
questionId: id,
|
|
134
|
+
decision: "skipped" as const,
|
|
135
|
+
})),
|
|
136
|
+
overall: "closed",
|
|
137
|
+
};
|
|
138
|
+
} else {
|
|
139
|
+
const submissions = buildSubmissions(response, interaction);
|
|
140
|
+
result = buildCompletedResult(submissions, interaction);
|
|
141
|
+
}
|
|
142
|
+
} catch (err) {
|
|
143
|
+
if (err instanceof QuestionBatchValidationError) {
|
|
144
|
+
throw new BadRequestError(err.message);
|
|
145
|
+
}
|
|
146
|
+
throw err;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Validation passed — deregister now to clear the prompter timer, then
|
|
150
|
+
// hand the result to the prompter's caller via rpcResolve.
|
|
151
|
+
pendingInteractions.resolve(requestId);
|
|
152
|
+
|
|
153
|
+
log.info(
|
|
154
|
+
{
|
|
155
|
+
requestId,
|
|
156
|
+
overall: result.overall,
|
|
157
|
+
conversationId: interaction.conversationId,
|
|
158
|
+
},
|
|
159
|
+
"Question resolved",
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
(interaction.rpcResolve as
|
|
163
|
+
| ((value: QuestionPromptResult) => void)
|
|
164
|
+
| undefined)?.(result);
|
|
165
|
+
|
|
166
|
+
return { success: true };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Normalize the incoming body to a `QuestionBatchSubmission[]` for the
|
|
171
|
+
* submit/legacy paths. Returns `null` for the `close` path (no submissions).
|
|
172
|
+
*/
|
|
173
|
+
function buildSubmissions(
|
|
174
|
+
body: SubmitBody | LegacyOptionBody | LegacyFreeTextBody,
|
|
175
|
+
interaction: ReturnType<typeof pendingInteractions.get>,
|
|
176
|
+
): QuestionBatchSubmission[] {
|
|
177
|
+
if (body.kind === "submit") return body.responses;
|
|
178
|
+
|
|
179
|
+
// Legacy single-question shim: synthesize a one-element batch. The
|
|
180
|
+
// prompter stashed the ordered ids on the interaction metadata so we can
|
|
181
|
+
// pick the (single) target questionId here.
|
|
182
|
+
const { orderedIds } = readBatchMetadata(interaction);
|
|
183
|
+
if (orderedIds.length === 0) {
|
|
184
|
+
throw new QuestionBatchValidationError(
|
|
185
|
+
"Legacy single-question payload requires a registered batch with at least one question",
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
if (orderedIds.length > 1) {
|
|
189
|
+
throw new QuestionBatchValidationError(
|
|
190
|
+
'Legacy single-question payload cannot answer a multi-question batch; submit `{ kind: "submit", responses: [...] }` covering every question instead.',
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
const questionId = orderedIds[0]!;
|
|
194
|
+
if (body.kind === "option") {
|
|
195
|
+
return [{ questionId, kind: "option", optionId: body.optionId }];
|
|
196
|
+
}
|
|
197
|
+
return [{ questionId, kind: "free_text", text: body.text }];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Build a `completed` QuestionPromptResult from a batched submission and the
|
|
202
|
+
* per-question metadata the prompter stashed on the interaction. Delegates
|
|
203
|
+
* the validation + ordering loop to {@link buildBatchEntries} so the
|
|
204
|
+
* prompter and the route share a single implementation.
|
|
205
|
+
*/
|
|
206
|
+
function buildCompletedResult(
|
|
207
|
+
submissions: QuestionBatchSubmission[],
|
|
208
|
+
interaction: ReturnType<typeof pendingInteractions.get>,
|
|
209
|
+
): QuestionPromptResult {
|
|
210
|
+
const { orderedIds, optionsById } = readBatchMetadata(interaction);
|
|
211
|
+
if (orderedIds.length === 0) {
|
|
212
|
+
throw new QuestionBatchValidationError(
|
|
213
|
+
"No registered question ids for this batch",
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
const entries = buildBatchEntries(
|
|
217
|
+
orderedIds,
|
|
218
|
+
(qid, oid) => (optionsById[qid] ?? []).includes(oid),
|
|
219
|
+
new Set(Object.keys(optionsById)),
|
|
220
|
+
submissions,
|
|
221
|
+
);
|
|
222
|
+
return { entries, overall: "completed" };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Pull the prompter-stashed batch bookkeeping off a pending interaction.
|
|
227
|
+
* Returns empty defaults if the metadata is absent.
|
|
228
|
+
*/
|
|
229
|
+
function readBatchMetadata(
|
|
230
|
+
interaction: ReturnType<typeof pendingInteractions.get>,
|
|
231
|
+
): QuestionBatchMetadata {
|
|
232
|
+
const meta = interaction?.metadata as Partial<QuestionBatchMetadata> | undefined;
|
|
233
|
+
return {
|
|
234
|
+
orderedIds: meta?.orderedIds ?? [],
|
|
235
|
+
optionsById: meta?.optionsById ?? {},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
// Route definitions
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
|
|
243
|
+
export const ROUTES: RouteDefinition[] = [
|
|
244
|
+
{
|
|
245
|
+
operationId: "question_response",
|
|
246
|
+
endpoint: "question-response",
|
|
247
|
+
method: "POST",
|
|
248
|
+
handler: handleQuestionResponse,
|
|
249
|
+
requireGuardian: true,
|
|
250
|
+
summary: "Resolve a pending ask-question prompt",
|
|
251
|
+
description:
|
|
252
|
+
"Submit the user's batched response (or close the card) for a pending question prompt by requestId. Legacy single-question payloads remain accepted as syntactic sugar for a one-element batch.",
|
|
253
|
+
tags: ["approvals"],
|
|
254
|
+
requestBody: QuestionResponseBody,
|
|
255
|
+
responseBody: z.object({
|
|
256
|
+
success: z.boolean(),
|
|
257
|
+
}),
|
|
258
|
+
},
|
|
259
|
+
];
|
|
@@ -13,9 +13,7 @@ import {
|
|
|
13
13
|
getConversation,
|
|
14
14
|
updateConversationTitle,
|
|
15
15
|
} from "../../memory/conversation-crud.js";
|
|
16
|
-
import {
|
|
17
|
-
import { buildAssistantEvent } from "../assistant-event.js";
|
|
18
|
-
import { assistantEventHub } from "../assistant-event-hub.js";
|
|
16
|
+
import { publishConversationTitleChanged } from "../sync/resource-sync-events.js";
|
|
19
17
|
import { BadRequestError, NotFoundError } from "./errors.js";
|
|
20
18
|
import type { RouteDefinition } from "./types.js";
|
|
21
19
|
|
|
@@ -24,8 +22,6 @@ const RenameConversationBody = z.object({
|
|
|
24
22
|
title: z.string().min(1),
|
|
25
23
|
});
|
|
26
24
|
|
|
27
|
-
const log = getLogger("rename-conversation-routes");
|
|
28
|
-
|
|
29
25
|
export const ROUTES: RouteDefinition[] = [
|
|
30
26
|
{
|
|
31
27
|
operationId: "rename_conversation",
|
|
@@ -50,34 +46,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
50
46
|
|
|
51
47
|
updateConversationTitle(conversationId, title, 0);
|
|
52
48
|
|
|
53
|
-
|
|
54
|
-
.publish(
|
|
55
|
-
buildAssistantEvent(
|
|
56
|
-
{
|
|
57
|
-
type: "conversation_title_updated",
|
|
58
|
-
conversationId,
|
|
59
|
-
title,
|
|
60
|
-
},
|
|
61
|
-
conversationId,
|
|
62
|
-
),
|
|
63
|
-
)
|
|
64
|
-
.catch((err) => {
|
|
65
|
-
log.warn({ err }, "Failed to publish conversation_title_updated");
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
assistantEventHub
|
|
69
|
-
.publish(
|
|
70
|
-
buildAssistantEvent({
|
|
71
|
-
type: "conversation_list_invalidated",
|
|
72
|
-
reason: "renamed",
|
|
73
|
-
}),
|
|
74
|
-
)
|
|
75
|
-
.catch((err) => {
|
|
76
|
-
log.warn(
|
|
77
|
-
{ err },
|
|
78
|
-
"Failed to publish conversation_list_invalidated for rename",
|
|
79
|
-
);
|
|
80
|
-
});
|
|
49
|
+
publishConversationTitleChanged(conversationId, title);
|
|
81
50
|
|
|
82
51
|
return { ok: true };
|
|
83
52
|
},
|
|
@@ -78,7 +78,8 @@ function handleCreateSchedule(body: Record<string, unknown>) {
|
|
|
78
78
|
const expression =
|
|
79
79
|
typeof body.expression === "string" ? body.expression.trim() : "";
|
|
80
80
|
const message = typeof body.message === "string" ? body.message : "";
|
|
81
|
-
const timezoneRaw =
|
|
81
|
+
const timezoneRaw =
|
|
82
|
+
typeof body.timezone === "string" ? body.timezone.trim() : "";
|
|
82
83
|
const timezone = timezoneRaw === "" ? null : timezoneRaw;
|
|
83
84
|
const enabled = body.enabled !== false;
|
|
84
85
|
const mode = (body.mode as string | undefined) ?? "execute";
|
|
@@ -298,16 +299,12 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
298
299
|
.boolean()
|
|
299
300
|
.describe("Whether the schedule starts active (default true)")
|
|
300
301
|
.optional(),
|
|
301
|
-
mode: z
|
|
302
|
-
.string()
|
|
303
|
-
.describe("Currently must be 'execute'")
|
|
304
|
-
.optional(),
|
|
302
|
+
mode: z.string().describe("Currently must be 'execute'").optional(),
|
|
305
303
|
}),
|
|
306
304
|
responseBody: z.object({
|
|
307
305
|
schedules: z.array(z.unknown()).describe("Updated schedule list"),
|
|
308
306
|
}),
|
|
309
|
-
handler: ({ body }: RouteHandlerArgs) =>
|
|
310
|
-
handleCreateSchedule(body ?? {}),
|
|
307
|
+
handler: ({ body }: RouteHandlerArgs) => handleCreateSchedule(body ?? {}),
|
|
311
308
|
},
|
|
312
309
|
{
|
|
313
310
|
operationId: "listScheduleRuns",
|