@vellumai/assistant 0.12.2 → 0.12.3-staging.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +2 -0
- package/AGENTS.md +1 -1
- package/docs/architecture/integrations.md +7 -0
- package/docs/architecture/memory.md +17 -2
- package/docs/credential-execution-service.md +1 -1
- package/docs/desktop-browser-cli.md +7 -3
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
- package/node_modules/@vellumai/environments/src/shell.test.ts +21 -0
- package/node_modules/@vellumai/environments/src/shell.ts +24 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +8 -2
- package/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
- package/openapi.yaml +54 -5
- package/package.json +1 -1
- package/scripts/smoke-desktop-browser-cli.ts +1 -0
- package/scripts/sync-llm-catalog.ts +3 -0
- package/scripts/voice-ttft-spike.ts +2 -2
- package/src/__tests__/agent-loop.test.ts +259 -0
- package/src/__tests__/agent-wake-delegation-prompt.test.ts +64 -2
- package/src/__tests__/anthropic-provider.test.ts +55 -0
- package/src/__tests__/approval-interception-trust-gates.test.ts +40 -0
- package/src/__tests__/attachments-store.test.ts +22 -3
- package/src/__tests__/channel-approval.test.ts +9 -14
- package/src/__tests__/channel-reply-delivery.test.ts +56 -0
- package/src/__tests__/chat-credential-redaction.test.ts +23 -0
- package/src/__tests__/computer-use-screenshot-attachments.test.ts +411 -0
- package/src/__tests__/computer-use-screenshot-selection.test.ts +50 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop.test.ts +257 -6
- package/src/__tests__/conversation-attachments.test.ts +106 -0
- package/src/__tests__/conversation-fork-crud.test.ts +191 -0
- package/src/__tests__/conversation-rendered-delegation-state.test.ts +276 -0
- package/src/__tests__/credential-execution-client.test.ts +70 -53
- package/src/__tests__/db-conversation-tool-surface.test.ts +221 -0
- package/src/__tests__/events-tail-route.test.ts +33 -0
- package/src/__tests__/history-repair.test.ts +53 -0
- package/src/__tests__/inference-profile-session-handler.test.ts +26 -0
- package/src/__tests__/list-messages-tool-merge.test.ts +85 -1
- package/src/__tests__/llm-catalog-parity.test.ts +27 -4
- package/src/__tests__/oauth-apps-routes.test.ts +1 -0
- package/src/__tests__/oauth-commands-routes.test.ts +114 -101
- package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
- package/src/__tests__/oauth-providers-routes.test.ts +2 -0
- package/src/__tests__/persist-media-references.test.ts +50 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
- package/src/__tests__/run-conversation-turn-persistence.test.ts +138 -1
- package/src/__tests__/schedule-routes.test.ts +20 -0
- package/src/__tests__/scheduler-result-notification.test.ts +23 -4
- package/src/__tests__/script-proxy-certs.test.ts +1 -1
- package/src/__tests__/secret-routes-platform-proxy.test.ts +86 -31
- package/src/__tests__/secret-routes-scrub.test.ts +22 -27
- package/src/__tests__/secret-scanner.test.ts +20 -0
- package/src/__tests__/secure-keys-managed-failover.test.ts +8 -0
- package/src/__tests__/secure-keys.test.ts +7 -3
- package/src/__tests__/server-tool-pairing.test.ts +107 -0
- package/src/__tests__/skills.test.ts +5 -4
- package/src/__tests__/subagent-tool-gate-mode.test.ts +226 -0
- package/src/__tests__/terminal-tools.test.ts +8 -0
- package/src/__tests__/tool-result-follow-up.test.ts +99 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +63 -0
- package/src/__tests__/unicode.test.ts +36 -0
- package/src/agent/loop.ts +37 -12
- package/src/agent/tool-result-follow-up.ts +70 -0
- package/src/api/attachment-provenance.test.ts +68 -0
- package/src/api/computer-use-tool.test.ts +55 -0
- package/src/api/computer-use-tool.ts +27 -0
- package/src/api/events/assistant-outbound-attachment.ts +3 -0
- package/src/api/events/desktop-activity-changed.ts +10 -0
- package/src/api/events/question-request.ts +1 -0
- package/src/api/index.ts +11 -0
- package/src/api/responses/conversation-message.ts +3 -0
- package/src/approvals/approval-primitive.ts +5 -2
- package/src/approvals/scoped-approval-grants.ts +6 -2
- package/src/browser/virtual-desktop-target.ts +1 -2
- package/src/cli/commands/__tests__/cli-test-harness.ts +21 -3
- package/src/cli/commands/__tests__/plugins.test.ts +20 -1
- package/src/cli/commands/__tests__/schedules.test.ts +14 -0
- package/src/cli/commands/bash.help.ts +4 -3
- package/src/cli/commands/browser.help.ts +5 -1
- package/src/cli/commands/credentials.help.ts +3 -3
- package/src/cli/commands/oauth/index.help.ts +10 -0
- package/src/cli/commands/oauth/providers.ts +12 -0
- package/src/cli/commands/oauth/request.test.ts +179 -2
- package/src/cli/commands/oauth/request.ts +41 -10
- package/src/cli/commands/plugins.ts +12 -5
- package/src/cli/commands/schedules.ts +2 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +41 -0
- package/src/cli/lib/__tests__/local-plugin-upgrade.test.ts +133 -51
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +8 -2
- package/src/cli/lib/bundled-marketplace.json +855 -0
- package/src/cli/lib/bundled-plugin-packages.json +783 -1
- package/src/cli/lib/inspect-plugin.ts +11 -4
- package/src/cli/lib/upgrade-plugin.ts +19 -11
- package/src/cli/output.ts +18 -4
- package/src/config/__tests__/default-provider.test.ts +4 -0
- package/src/config/bundled-skills/schedule/SKILL.md +6 -6
- package/src/config/loader.ts +9 -17
- package/src/config/platform-identity.ts +8 -8
- package/src/config/profile-text-generation.test.ts +51 -0
- package/src/config/profile-text-generation.ts +51 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +12 -0
- package/src/config/schemas/llm.ts +7 -2
- package/src/config/schemas/mcp.ts +5 -1
- package/src/config/schemas/memory-v3.ts +12 -0
- package/src/credential-execution/ces-runtime.ts +2 -2
- package/src/credential-execution/executable-discovery.ts +36 -134
- package/src/credential-execution/process-manager.test.ts +20 -24
- package/src/credential-execution/process-manager.ts +24 -19
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +43 -0
- package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +36 -0
- package/src/daemon/assistant-attachments.ts +20 -12
- package/src/daemon/chat-credential-redaction.ts +16 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +74 -3
- package/src/daemon/conversation-agent-loop.ts +12 -0
- package/src/daemon/conversation-attachments.ts +93 -12
- package/src/daemon/conversation-tool-setup.ts +71 -3
- package/src/daemon/conversation-turn-finalize.ts +26 -13
- package/src/daemon/conversation.ts +72 -17
- package/src/daemon/daemon-control.ts +3 -7
- package/src/daemon/lifecycle.ts +4 -4
- package/src/daemon/mcp-reload-service.ts +1 -1
- package/src/daemon/message-types/shared.ts +1 -0
- package/src/daemon/orphan-reaper.ts +4 -3
- package/src/daemon/persist-media-references.ts +20 -3
- package/src/daemon/tool-setup-types.ts +6 -0
- package/src/daemon/wake-conversation-ops.ts +50 -15
- package/src/desktop/desktop-automation-lease.test.ts +230 -0
- package/src/desktop/desktop-automation-lease.ts +90 -6
- package/src/desktop/desktop-help.ts +15 -0
- package/src/desktop/desktop-stream-bridge.test.ts +65 -10
- package/src/desktop/desktop-stream-bridge.ts +3 -3
- package/src/desktop/desktop-wallpaper-renderer.ts +132 -0
- package/src/desktop/desktop-wallpaper-worker.ts +17 -0
- package/src/desktop/desktop-wallpaper.test.ts +48 -7
- package/src/desktop/desktop-wallpaper.ts +34 -102
- package/src/desktop/virtual-desktop-feature.ts +1 -1
- package/src/mcp/__tests__/manager-tool-caps.test.ts +111 -0
- package/src/mcp/__tests__/startup.test.ts +30 -7
- package/src/mcp/__tests__/tool-caps.test.ts +107 -0
- package/src/mcp/manager.ts +168 -101
- package/src/mcp/startup.ts +33 -13
- package/src/mcp/tool-caps.ts +176 -0
- package/src/messaging/provider-message-metadata.ts +3 -3
- package/src/notifications/__tests__/copy-composer.test.ts +70 -0
- package/src/notifications/copy-composer.ts +11 -3
- package/src/notifications/schedule-result-producer.ts +4 -6
- package/src/oauth/AGENTS.md +2 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +36 -1
- package/src/oauth/identity-verifier.ts +24 -0
- package/src/oauth/oauth-store.ts +11 -1
- package/src/oauth/provider-serializer.ts +1 -0
- package/src/oauth/seed-providers.ts +12 -0
- package/src/permissions/confirmation-guardian-request.test.ts +16 -0
- package/src/permissions/confirmation-guardian-request.ts +2 -3
- package/src/permissions/question-prompter.test.ts +31 -0
- package/src/permissions/question-prompter.ts +2 -0
- package/src/persistence/attachments-store.ts +84 -44
- package/src/persistence/conversation-crud.ts +111 -20
- package/src/persistence/conversation-plugin-facade.ts +30 -0
- package/src/persistence/conversation-tool-surface.ts +135 -0
- package/src/persistence/conversation-types.test.ts +32 -0
- package/src/persistence/conversation-types.ts +26 -4
- package/src/persistence/migrations/378-create-conversation-tool-surfaces.test.ts +78 -0
- package/src/persistence/migrations/378-create-conversation-tool-surfaces.ts +29 -0
- package/src/persistence/migrations/379-oauth-providers-response-ok-field.test.ts +92 -0
- package/src/persistence/migrations/379-oauth-providers-response-ok-field.ts +32 -0
- package/src/persistence/migrations/380-conversation-tool-surfaces-delegate-independent-tasks.test.ts +90 -0
- package/src/persistence/migrations/380-conversation-tool-surfaces-delegate-independent-tasks.ts +34 -0
- package/src/persistence/schema/conversation-tool-surfaces.ts +32 -0
- package/src/persistence/schema/index.ts +1 -0
- package/src/persistence/schema/oauth.ts +1 -0
- package/src/persistence/steps.ts +24 -3
- package/src/plugin-api/conversation-turn.ts +31 -7
- package/src/plugin-api/index.ts +11 -1
- package/src/plugin-api/plugin-channel-turn-trust.test.ts +133 -0
- package/src/plugin-api/plugin-channel-turn-trust.ts +71 -0
- package/src/plugins/defaults/memory/AGENTS.md +39 -5
- package/src/plugins/defaults/memory/__tests__/buffer-file.test.ts +365 -0
- package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +43 -0
- package/src/plugins/defaults/memory/__tests__/conversation-memory-purge.test.ts +1 -0
- package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +2 -0
- package/src/plugins/defaults/memory/__tests__/fixtures/buffer-appender.ts +17 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +74 -0
- package/src/plugins/defaults/memory/__tests__/memory-run-evidence.test.ts +161 -0
- package/src/plugins/defaults/memory/__tests__/relocated-memory-test-rows.ts +10 -0
- package/src/plugins/defaults/memory/buffer-file.ts +354 -0
- package/src/plugins/defaults/memory/buffer-format.ts +40 -0
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +1 -2
- package/src/plugins/defaults/memory/context-search/format.ts +2 -1
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +2 -1
- package/src/plugins/defaults/memory/context-search/sources/workspace.ts +2 -1
- package/src/plugins/defaults/memory/conversation-memory-purge.ts +4 -0
- package/src/plugins/defaults/memory/graph/capability-seed.ts +1 -2
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -42
- package/src/plugins/defaults/memory/host-utils.ts +0 -10
- package/src/plugins/defaults/memory/injectors.ts +4 -3
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +72 -183
- package/src/plugins/defaults/memory/memory-run-evidence.ts +213 -0
- package/src/plugins/defaults/memory/src/memory-item-routes.test.ts +1 -1
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +407 -99
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +10 -0
- package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +107 -7
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +307 -86
- package/src/plugins/defaults/memory/substrate/consolidation-tool-surface.ts +34 -0
- package/src/plugins/defaults/memory/substrate/page-index.ts +2 -1
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +89 -49
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +1 -1
- package/src/plugins/defaults/memory/tools.ts +1 -1
- package/src/plugins/defaults/memory/v1/graph/consolidation.ts +2 -2
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +2 -1
- package/src/plugins/defaults/memory/v1/graph/retriever.ts +1 -1
- package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +5 -0
- package/src/plugins/defaults/memory/v2/__tests__/reranker.test.ts +5 -2
- package/src/plugins/defaults/memory/v2/reranker.ts +2 -1
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +81 -1
- package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +87 -0
- package/src/plugins/defaults/memory/v3/__tests__/plugin-schema.test.ts +12 -0
- package/src/plugins/defaults/memory/v3/__tests__/pool-log-store.test.ts +183 -4
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +103 -2
- package/src/plugins/defaults/memory/v3/card.ts +2 -1
- package/src/plugins/defaults/memory/v3/injector.ts +212 -178
- package/src/plugins/defaults/memory/v3/orchestrate.ts +112 -25
- package/src/plugins/defaults/memory/v3/plugin-schema.ts +54 -4
- package/src/plugins/defaults/memory/v3/pool-log-store.ts +253 -1
- package/src/plugins/defaults/memory/v3/pool-select.test.ts +35 -2
- package/src/plugins/defaults/memory/v3/pool-select.ts +31 -19
- package/src/plugins/defaults/memory/v3/sections.ts +2 -1
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +41 -14
- package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +4 -1
- package/src/plugins/defaults/tool-result-truncate/terminal.ts +1 -46
- package/src/prompts/__tests__/parallel-tasks-section.test.ts +25 -0
- package/src/prompts/delegation-gate.ts +57 -0
- package/src/prompts/system-prompt.ts +14 -32
- package/src/providers/inference/adapter-factory.ts +6 -0
- package/src/providers/jev/client.test.ts +260 -0
- package/src/providers/jev/client.ts +518 -0
- package/src/providers/model-catalog.ts +58 -3
- package/src/providers/server-tool-pairing.ts +16 -7
- package/src/runtime/AGENTS.md +2 -2
- package/src/runtime/__tests__/agent-wake.test.ts +94 -1
- package/src/runtime/agent-wake.ts +31 -4
- package/src/runtime/guardian-action-service.ts +2 -17
- package/src/runtime/guardian-reply-router.ts +1 -8
- package/src/runtime/http-server.ts +2 -2
- package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +89 -0
- package/src/runtime/migrations/vbundle-import-policy.ts +33 -11
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +24 -2
- package/src/runtime/routes/channel-route-shared.ts +1 -9
- package/src/runtime/routes/conversation-routes.ts +29 -4
- package/src/runtime/routes/desktop-setup-routes.test.ts +2 -2
- package/src/runtime/routes/desktop-setup-routes.ts +8 -5
- package/src/runtime/routes/guardian-approval-interception.ts +24 -0
- package/src/runtime/routes/inbound-message-handler.ts +2 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +1 -1
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +7 -4
- package/src/runtime/routes/inference-profile-session-handler.ts +11 -1
- package/src/runtime/routes/inference-profiles-routes.ts +9 -0
- package/src/runtime/routes/oauth-commands-routes.ts +57 -54
- package/src/runtime/routes/oauth-providers.ts +4 -0
- package/src/runtime/routes/oauth-request-hints.test.ts +246 -0
- package/src/runtime/routes/oauth-request-hints.ts +149 -0
- package/src/runtime/routes/schedule-routes.ts +2 -0
- package/src/runtime/routes/secret-routes.ts +99 -119
- package/src/schedule/__tests__/worker-mcp-bootstrap.test.ts +107 -0
- package/src/schedule/__tests__/worker-mcp-readiness.test.ts +173 -0
- package/src/schedule/__tests__/worker-mcp-tools.test.ts +13 -47
- package/src/schedule/run-script.ts +2 -2
- package/src/schedule/scheduler.ts +47 -11
- package/src/schedule/tool-surface-readiness.ts +65 -0
- package/src/schedule/worker-mcp.ts +85 -0
- package/src/schedule/worker.ts +9 -31
- package/src/security/secure-keys.ts +60 -48
- package/src/tools/ask-question/ask-question-tool.test.ts +167 -5
- package/src/tools/ask-question/ask-question-tool.ts +119 -23
- package/src/tools/browser/browser-execution.ts +7 -1
- package/src/tools/host-terminal/host-shell.ts +12 -6
- package/src/tools/shared/filesystem/file-ops-service.ts +1 -31
- package/src/tools/shared/shell-output.test.ts +10 -0
- package/src/tools/shared/shell-output.ts +14 -2
- package/src/tools/skills/sandbox-runner.ts +13 -2
- package/src/tools/skills/scaffold-managed.ts +2 -1
- package/src/tools/terminal/__tests__/safe-env.test.ts +33 -4
- package/src/tools/terminal/__tests__/sanitized-bash.test.ts +0 -14
- package/src/tools/terminal/safe-env.ts +35 -24
- package/src/tools/terminal/sanitized-bash.ts +15 -2
- package/src/tools/terminal/shell-launch.test.ts +162 -0
- package/src/tools/terminal/shell.test.ts +29 -0
- package/src/tools/terminal/shell.ts +13 -7
- package/src/util/browser-human-verification.ts +14 -0
- package/src/util/host-process.test.ts +17 -1
- package/src/util/host-process.ts +24 -0
- package/src/util/unicode.ts +29 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code-owned MCP tool-count caps and the selection used when they bind.
|
|
3
|
+
*
|
|
4
|
+
* Caps live here, not in workspace config: migration 153 strips
|
|
5
|
+
* `maxTools` / `globalMaxTools` from `config.json`. The numbers are a
|
|
6
|
+
* context-window budget, not a permission gate. Allowlists and risk
|
|
7
|
+
* still decide what a turn may call.
|
|
8
|
+
*
|
|
9
|
+
* The global cap is applied by a deterministic round-robin across
|
|
10
|
+
* servers sorted by id. Insertion order must not empty a later server
|
|
11
|
+
* while earlier ones keep every tool.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
MCP_GLOBAL_MAX_TOOLS,
|
|
16
|
+
MCP_MAX_TOOLS_PER_SERVER,
|
|
17
|
+
} from "../config/schemas/mcp.js";
|
|
18
|
+
|
|
19
|
+
export interface McpServerToolSet<T> {
|
|
20
|
+
serverId: string;
|
|
21
|
+
tools: readonly T[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface McpToolCapDecision {
|
|
25
|
+
serverId: string;
|
|
26
|
+
discovered: number;
|
|
27
|
+
kept: number;
|
|
28
|
+
droppedByPerServerCap: number;
|
|
29
|
+
droppedByGlobalCap: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface McpToolCapResult<T> {
|
|
33
|
+
servers: Array<{ serverId: string; tools: T[] }>;
|
|
34
|
+
discoveredToolCount: number;
|
|
35
|
+
keptToolCount: number;
|
|
36
|
+
droppedToolCount: number;
|
|
37
|
+
globalCap: number;
|
|
38
|
+
perServerCap: number;
|
|
39
|
+
decisions: McpToolCapDecision[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function applyMcpToolCaps<T>(
|
|
43
|
+
servers: ReadonlyArray<McpServerToolSet<T>>,
|
|
44
|
+
options?: { globalMax?: number; perServerMax?: number },
|
|
45
|
+
): McpToolCapResult<T> {
|
|
46
|
+
const globalCap = options?.globalMax ?? MCP_GLOBAL_MAX_TOOLS;
|
|
47
|
+
const perServerCap = options?.perServerMax ?? MCP_MAX_TOOLS_PER_SERVER;
|
|
48
|
+
const discoveredToolCount = servers.reduce(
|
|
49
|
+
(sum, server) => sum + server.tools.length,
|
|
50
|
+
0,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const afterPerServer = servers.map((server) => {
|
|
54
|
+
const droppedByPerServerCap = Math.max(
|
|
55
|
+
0,
|
|
56
|
+
server.tools.length - perServerCap,
|
|
57
|
+
);
|
|
58
|
+
return {
|
|
59
|
+
serverId: server.serverId,
|
|
60
|
+
tools: server.tools.slice(0, perServerCap),
|
|
61
|
+
discovered: server.tools.length,
|
|
62
|
+
droppedByPerServerCap,
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const afterPerServerCount = afterPerServer.reduce(
|
|
67
|
+
(sum, server) => sum + server.tools.length,
|
|
68
|
+
0,
|
|
69
|
+
);
|
|
70
|
+
if (afterPerServerCount <= globalCap) {
|
|
71
|
+
const decisions = afterPerServer.map((server) => ({
|
|
72
|
+
serverId: server.serverId,
|
|
73
|
+
discovered: server.discovered,
|
|
74
|
+
kept: server.tools.length,
|
|
75
|
+
droppedByPerServerCap: server.droppedByPerServerCap,
|
|
76
|
+
droppedByGlobalCap: 0,
|
|
77
|
+
}));
|
|
78
|
+
return {
|
|
79
|
+
servers: afterPerServer.map((server) => ({
|
|
80
|
+
serverId: server.serverId,
|
|
81
|
+
tools: [...server.tools],
|
|
82
|
+
})),
|
|
83
|
+
discoveredToolCount,
|
|
84
|
+
keptToolCount: afterPerServerCount,
|
|
85
|
+
droppedToolCount: discoveredToolCount - afterPerServerCount,
|
|
86
|
+
globalCap,
|
|
87
|
+
perServerCap,
|
|
88
|
+
decisions,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const order = [...afterPerServer].sort((a, b) =>
|
|
93
|
+
a.serverId.localeCompare(b.serverId),
|
|
94
|
+
);
|
|
95
|
+
const selected = new Map<string, T[]>(
|
|
96
|
+
order.map((server) => [server.serverId, []]),
|
|
97
|
+
);
|
|
98
|
+
const cursors = new Map<string, number>(
|
|
99
|
+
order.map((server) => [server.serverId, 0]),
|
|
100
|
+
);
|
|
101
|
+
let kept = 0;
|
|
102
|
+
let progressed = true;
|
|
103
|
+
while (kept < globalCap && progressed) {
|
|
104
|
+
progressed = false;
|
|
105
|
+
for (const server of order) {
|
|
106
|
+
if (kept >= globalCap) {
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
const cursor = cursors.get(server.serverId) ?? 0;
|
|
110
|
+
const tool = server.tools[cursor];
|
|
111
|
+
if (tool !== undefined) {
|
|
112
|
+
selected.get(server.serverId)?.push(tool);
|
|
113
|
+
cursors.set(server.serverId, cursor + 1);
|
|
114
|
+
kept += 1;
|
|
115
|
+
progressed = true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const decisions = afterPerServer.map((server) => {
|
|
121
|
+
const keptTools = selected.get(server.serverId) ?? [];
|
|
122
|
+
return {
|
|
123
|
+
serverId: server.serverId,
|
|
124
|
+
discovered: server.discovered,
|
|
125
|
+
kept: keptTools.length,
|
|
126
|
+
droppedByPerServerCap: server.droppedByPerServerCap,
|
|
127
|
+
droppedByGlobalCap: server.tools.length - keptTools.length,
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
servers: afterPerServer.map((server) => ({
|
|
133
|
+
serverId: server.serverId,
|
|
134
|
+
tools: [...(selected.get(server.serverId) ?? [])],
|
|
135
|
+
})),
|
|
136
|
+
discoveredToolCount,
|
|
137
|
+
keptToolCount: kept,
|
|
138
|
+
droppedToolCount: discoveredToolCount - kept,
|
|
139
|
+
globalCap,
|
|
140
|
+
perServerCap,
|
|
141
|
+
decisions,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface McpStartupSnapshot {
|
|
146
|
+
configuredServerCount: number;
|
|
147
|
+
connectedServerCount: number;
|
|
148
|
+
discoveredToolCount: number;
|
|
149
|
+
registeredToolCount: number;
|
|
150
|
+
droppedToolCount: number;
|
|
151
|
+
truncatedServerIds: string[];
|
|
152
|
+
errorServerCount: number;
|
|
153
|
+
needsAuthServerCount: number;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export const EMPTY_MCP_STARTUP_SNAPSHOT: McpStartupSnapshot = {
|
|
157
|
+
configuredServerCount: 0,
|
|
158
|
+
connectedServerCount: 0,
|
|
159
|
+
discoveredToolCount: 0,
|
|
160
|
+
registeredToolCount: 0,
|
|
161
|
+
droppedToolCount: 0,
|
|
162
|
+
truncatedServerIds: [],
|
|
163
|
+
errorServerCount: 0,
|
|
164
|
+
needsAuthServerCount: 0,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export function truncatedServerIdsFromCaps(
|
|
168
|
+
decisions: readonly McpToolCapDecision[],
|
|
169
|
+
): string[] {
|
|
170
|
+
return decisions
|
|
171
|
+
.filter(
|
|
172
|
+
(decision) =>
|
|
173
|
+
decision.droppedByPerServerCap > 0 || decision.droppedByGlobalCap > 0,
|
|
174
|
+
)
|
|
175
|
+
.map((decision) => decision.serverId);
|
|
176
|
+
}
|
|
@@ -30,9 +30,9 @@ import { CHANNEL_IDS } from "../channels/types.js";
|
|
|
30
30
|
* The schema passes through what it does not name, so a provider carries its
|
|
31
31
|
* own fields (Slack's file markers and timezone labels) on this same object
|
|
32
32
|
* and validates them with its own schema. This is how `SourceMetadataSchema`
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
33
|
+
* carries `botMentioned` and the email fields on the wire: one object per
|
|
34
|
+
* row, no per-provider sub-envelope, and no second copy of anything named
|
|
35
|
+
* here.
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
38
|
const providerReactionMetadataSchema = z.object({
|
|
@@ -503,6 +503,76 @@ describe("composeFallbackCopy plugin schedule templates", () => {
|
|
|
503
503
|
});
|
|
504
504
|
});
|
|
505
505
|
|
|
506
|
+
// ── activity.complete rendering ───────────────────────────────────────
|
|
507
|
+
|
|
508
|
+
describe("activity.complete copy", () => {
|
|
509
|
+
function completeSignal(
|
|
510
|
+
contextPayload: Record<string, unknown>,
|
|
511
|
+
): NotificationSignal {
|
|
512
|
+
return makeSignal({
|
|
513
|
+
sourceEventName: "activity.complete",
|
|
514
|
+
contextPayload,
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
test("a producer-authored title survives the fallback path", () => {
|
|
519
|
+
// The home feed reads the payload title first; the popup must say the
|
|
520
|
+
// same thing when the classifier was unreachable.
|
|
521
|
+
const copy = composeFallbackCopy(
|
|
522
|
+
completeSignal({
|
|
523
|
+
title: "Skill updated: Weekly Report Export",
|
|
524
|
+
summary: "Added the retry after an expired session.",
|
|
525
|
+
}),
|
|
526
|
+
CHANNELS,
|
|
527
|
+
);
|
|
528
|
+
expect(copy.vellum?.title).toBe("Skill updated: Weekly Report Export");
|
|
529
|
+
expect(copy.vellum?.body).toBe("Added the retry after an expired session.");
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
test("the pass-through's requestedTitle is honored when no title is set", () => {
|
|
533
|
+
const copy = composeFallbackCopy(
|
|
534
|
+
completeSignal({
|
|
535
|
+
requestedTitle: "Nightly export finished",
|
|
536
|
+
summary: "Wrote 12 files to the reports folder.",
|
|
537
|
+
}),
|
|
538
|
+
CHANNELS,
|
|
539
|
+
);
|
|
540
|
+
expect(copy.vellum?.title).toBe("Nightly export finished");
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
test("without an authored title the headline is the summary's first sentence", () => {
|
|
544
|
+
const copy = composeFallbackCopy(
|
|
545
|
+
completeSignal({
|
|
546
|
+
summary:
|
|
547
|
+
"Added the retry after an expired session. Dropped the login step.",
|
|
548
|
+
}),
|
|
549
|
+
CHANNELS,
|
|
550
|
+
);
|
|
551
|
+
expect(copy.vellum?.title).toBe(
|
|
552
|
+
"Added the retry after an expired session.",
|
|
553
|
+
);
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
test("an authored title the normalizer rejects falls back to the derived one", () => {
|
|
557
|
+
const copy = composeFallbackCopy(
|
|
558
|
+
completeSignal({
|
|
559
|
+
title: "Skill\nupdated",
|
|
560
|
+
summary: "Added the retry after an expired session.",
|
|
561
|
+
}),
|
|
562
|
+
CHANNELS,
|
|
563
|
+
);
|
|
564
|
+
expect(copy.vellum?.title).toBe(
|
|
565
|
+
"Added the retry after an expired session.",
|
|
566
|
+
);
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
test("neither a title nor a summary still says something happened", () => {
|
|
570
|
+
const copy = composeFallbackCopy(completeSignal({}), CHANNELS);
|
|
571
|
+
expect(copy.vellum?.title).toBe("Activity complete");
|
|
572
|
+
expect(copy.vellum?.body).toBe("An activity has completed");
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
|
|
506
576
|
// ── deriveTitle ───────────────────────────────────────────────────────
|
|
507
577
|
|
|
508
578
|
describe("deriveTitle", () => {
|
|
@@ -346,14 +346,22 @@ const TEMPLATES: Partial<Record<NotificationSourceEventName, CopyTemplate>> = {
|
|
|
346
346
|
|
|
347
347
|
// Titled by what was done rather than by the kind of event: the summary's
|
|
348
348
|
// first sentence is the outcome ("Finished the fuel-system diagnostic app"),
|
|
349
|
-
// which is what a reader scanning the bell wants to see. A
|
|
350
|
-
//
|
|
349
|
+
// which is what a reader scanning the bell wants to see. A producer that
|
|
350
|
+
// names its subject in the payload keeps that name here, read in the same
|
|
351
|
+
// order the home feed reads it, so the headline is the same whether or not
|
|
352
|
+
// the classifier ran. A summary with neither still gets a title that says
|
|
353
|
+
// something happened.
|
|
351
354
|
"activity.complete": (payload) => {
|
|
352
355
|
const summary = nonEmpty(
|
|
353
356
|
typeof payload.summary === "string" ? payload.summary : undefined,
|
|
354
357
|
);
|
|
358
|
+
const authored = normalizeTitle(
|
|
359
|
+
readPayloadString(payload, "title") ??
|
|
360
|
+
readPayloadString(payload, "requestedTitle") ??
|
|
361
|
+
"",
|
|
362
|
+
);
|
|
355
363
|
return {
|
|
356
|
-
title: summary ? deriveTitle(summary) : "Activity complete",
|
|
364
|
+
title: authored || (summary ? deriveTitle(summary) : "Activity complete"),
|
|
357
365
|
body: summary ?? "An activity has completed",
|
|
358
366
|
};
|
|
359
367
|
},
|
|
@@ -103,12 +103,10 @@ export interface ScheduleResultNotificationParams {
|
|
|
103
103
|
* conversation can come back too and are harmless, because only this run's
|
|
104
104
|
* own call ids are matched.
|
|
105
105
|
*
|
|
106
|
-
* A Slack Web API post through bash is deliberately not a route
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* tool result. A run that posts that way gets the fallback too: at worst a
|
|
111
|
-
* duplicate, never a silence.
|
|
106
|
+
* A Slack Web API post through bash is deliberately not a route. It is off
|
|
107
|
+
* the guided path and unrecorded, and recognizing it would mean reading a
|
|
108
|
+
* shell command for a method name. A run that posts that way gets the
|
|
109
|
+
* fallback too: at worst a duplicate, never a silence.
|
|
112
110
|
*/
|
|
113
111
|
function deliveredThroughMessagingTool(
|
|
114
112
|
conversationId: string,
|
package/src/oauth/AGENTS.md
CHANGED
|
@@ -10,6 +10,8 @@ Add an entry to `PROVIDER_SEED_DATA`. Required fields: `provider`, `authorizeUrl
|
|
|
10
10
|
|
|
11
11
|
`injectionTemplates` is also a security boundary, not only an injection rule. `assistant oauth request` and `assistant channels request` accept an absolute URL only when its host matches one of the entry's `hostPattern` values (`assertOAuthRequestUrlAllowed` in `../runtime/routes/oauth-commands-routes.ts`), so that list is the full set of hosts the credential may be sent to. A pattern matches exactly unless it starts with `*.`; reserve the wildcard for per-tenant hosts that cannot be enumerated (`*.salesforce.com`), and name a fixed host the provider documents (`files.slack.com`) by name. The guard sees only the URL the caller passes: the connection follows a redirect itself, and a cross-origin hop carries no `Authorization`, so a redirect target never receives the credential and never needs listing.
|
|
12
12
|
|
|
13
|
+
`responseOkField` declares the boolean a provider's API puts in every response body to report success (Slack: `ok`), and it is read in one place, `providerReportsFailure` in `identity-verifier.ts`. It is not `identityOkField`, which governs only the identity call and stays the documented `--identity-ok-field` contract. Both request doors and `oauth ping` treat an explicit `false` under a 2xx as a failed call, since such a provider hides refusals behind a successful status; a body without the field leaves the status as the verdict. Declare it on every row that calls such an API, the bot row included.
|
|
14
|
+
|
|
13
15
|
If the provider will support managed mode, set `managedServiceConfigKey` to a slug matching the key you will add to `ServicesSchema` (e.g. `"acme-oauth"`).
|
|
14
16
|
|
|
15
17
|
### 2. _(managed only)_ Add a service schema — `../config/schemas/services.ts`
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
providerReportsFailure,
|
|
5
|
+
verifyIdentity,
|
|
6
|
+
} from "../identity-verifier.js";
|
|
4
7
|
import type { OAuthProviderRow } from "../oauth-store.js";
|
|
5
8
|
|
|
6
9
|
// ---------------------------------------------------------------------------
|
|
@@ -66,6 +69,7 @@ function makeProviderRow(
|
|
|
66
69
|
identityResponsePaths: null,
|
|
67
70
|
identityFormat: null,
|
|
68
71
|
identityOkField: null,
|
|
72
|
+
responseOkField: null,
|
|
69
73
|
featureFlag: null,
|
|
70
74
|
createdAt: now,
|
|
71
75
|
updatedAt: now,
|
|
@@ -469,3 +473,34 @@ describe("verifyIdentity", () => {
|
|
|
469
473
|
});
|
|
470
474
|
});
|
|
471
475
|
});
|
|
476
|
+
|
|
477
|
+
describe("providerReportsFailure", () => {
|
|
478
|
+
test("reads an explicit false in the declared field as failure", () => {
|
|
479
|
+
expect(
|
|
480
|
+
providerReportsFailure(
|
|
481
|
+
{ responseOkField: "ok" },
|
|
482
|
+
{ ok: false, error: "invalid_auth" },
|
|
483
|
+
),
|
|
484
|
+
).toBe(true);
|
|
485
|
+
expect(
|
|
486
|
+
providerReportsFailure({ responseOkField: "ok" }, { ok: true }),
|
|
487
|
+
).toBe(false);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
test("a body without the field says nothing", () => {
|
|
491
|
+
// A download or an endpoint outside the envelope; the status decides.
|
|
492
|
+
expect(providerReportsFailure({ responseOkField: "ok" }, null)).toBe(false);
|
|
493
|
+
expect(
|
|
494
|
+
providerReportsFailure({ responseOkField: "ok" }, Buffer.from("png")),
|
|
495
|
+
).toBe(false);
|
|
496
|
+
expect(
|
|
497
|
+
providerReportsFailure({ responseOkField: "ok" }, { data: {} }),
|
|
498
|
+
).toBe(false);
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
test("a provider that declares no field never reports failure", () => {
|
|
502
|
+
expect(
|
|
503
|
+
providerReportsFailure({ responseOkField: null }, { ok: false }),
|
|
504
|
+
).toBe(false);
|
|
505
|
+
});
|
|
506
|
+
});
|
|
@@ -27,6 +27,30 @@ function getNestedValue(obj: unknown, dotPath: string): unknown {
|
|
|
27
27
|
return current;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Whether a response body says the call failed, read through the ok field the
|
|
32
|
+
* provider declares (`responseOkField`).
|
|
33
|
+
*
|
|
34
|
+
* Some APIs report failure inside a successful HTTP exchange: Slack documents
|
|
35
|
+
* that every Web API response is a JSON object with a top-level boolean `ok`,
|
|
36
|
+
* false on failure, so the status code alone reads a refused call as a
|
|
37
|
+
* success. The provider row is where that envelope is declared, and this is
|
|
38
|
+
* the one reader of it for calls whose endpoint is not known in advance (ping
|
|
39
|
+
* and the authenticated-request doors). Only an explicit `false` counts: a
|
|
40
|
+
* body without the field, such as a file download or an endpoint outside the
|
|
41
|
+
* envelope, says nothing, and the status stays the verdict. The identity
|
|
42
|
+
* verifier reads its own field, `identityOkField`, for the one call it makes.
|
|
43
|
+
*/
|
|
44
|
+
export function providerReportsFailure(
|
|
45
|
+
providerRow: Pick<OAuthProviderRow, "responseOkField">,
|
|
46
|
+
body: unknown,
|
|
47
|
+
): boolean {
|
|
48
|
+
if (!providerRow.responseOkField) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
return getNestedValue(body, providerRow.responseOkField) === false;
|
|
52
|
+
}
|
|
53
|
+
|
|
30
54
|
/** Safely parse a JSON string, returning a fallback on failure or null/undefined input. */
|
|
31
55
|
function safeJsonParse<T>(value: string | null | undefined, fallback: T): T {
|
|
32
56
|
if (value == null) {
|
package/src/oauth/oauth-store.ts
CHANGED
|
@@ -59,7 +59,7 @@ export type OAuthConnectionRow = typeof oauthConnections.$inferSelect;
|
|
|
59
59
|
* managedServiceConfigKey,
|
|
60
60
|
* loopbackPort, injectionTemplates, appType, setupNotes,
|
|
61
61
|
* identityUrl, identityMethod, identityHeaders, identityBody,
|
|
62
|
-
* identityResponsePaths, identityFormat, identityOkField, featureFlag,
|
|
62
|
+
* identityResponsePaths, identityFormat, identityOkField, responseOkField, featureFlag,
|
|
63
63
|
* scopeSeparator, defaultScopes, availableScopes)
|
|
64
64
|
* and display metadata (displayLabel, description, dashboardUrl,
|
|
65
65
|
* clientIdPlaceholder, logoUrl, requiresClientSecret) propagate to existing
|
|
@@ -111,6 +111,7 @@ export function seedProviders(
|
|
|
111
111
|
identityResponsePaths?: string[];
|
|
112
112
|
identityFormat?: string;
|
|
113
113
|
identityOkField?: string;
|
|
114
|
+
responseOkField?: string;
|
|
114
115
|
featureFlag?: string;
|
|
115
116
|
}>,
|
|
116
117
|
): void {
|
|
@@ -175,6 +176,7 @@ export function seedProviders(
|
|
|
175
176
|
: null;
|
|
176
177
|
const identityFormat = p.identityFormat ?? null;
|
|
177
178
|
const identityOkField = p.identityOkField ?? null;
|
|
179
|
+
const responseOkField = p.responseOkField ?? null;
|
|
178
180
|
const featureFlag = p.featureFlag ?? null;
|
|
179
181
|
|
|
180
182
|
db.insert(oauthProviders)
|
|
@@ -216,6 +218,7 @@ export function seedProviders(
|
|
|
216
218
|
identityResponsePaths,
|
|
217
219
|
identityFormat,
|
|
218
220
|
identityOkField,
|
|
221
|
+
responseOkField,
|
|
219
222
|
featureFlag,
|
|
220
223
|
createdAt: now,
|
|
221
224
|
updatedAt: now,
|
|
@@ -259,6 +262,7 @@ export function seedProviders(
|
|
|
259
262
|
identityResponsePaths,
|
|
260
263
|
identityFormat,
|
|
261
264
|
identityOkField,
|
|
265
|
+
responseOkField,
|
|
262
266
|
featureFlag,
|
|
263
267
|
updatedAt: now,
|
|
264
268
|
},
|
|
@@ -359,6 +363,7 @@ export function registerProvider(params: {
|
|
|
359
363
|
identityResponsePaths?: string[];
|
|
360
364
|
identityFormat?: string;
|
|
361
365
|
identityOkField?: string;
|
|
366
|
+
responseOkField?: string;
|
|
362
367
|
featureFlag?: string;
|
|
363
368
|
}): OAuthProviderRow {
|
|
364
369
|
const db = getDb();
|
|
@@ -425,6 +430,7 @@ export function registerProvider(params: {
|
|
|
425
430
|
: null,
|
|
426
431
|
identityFormat: params.identityFormat ?? null,
|
|
427
432
|
identityOkField: params.identityOkField ?? null,
|
|
433
|
+
responseOkField: params.responseOkField ?? null,
|
|
428
434
|
featureFlag: params.featureFlag ?? null,
|
|
429
435
|
createdAt: now,
|
|
430
436
|
updatedAt: now,
|
|
@@ -486,6 +492,7 @@ export function updateProvider(
|
|
|
486
492
|
identityResponsePaths: string[];
|
|
487
493
|
identityFormat: string;
|
|
488
494
|
identityOkField: string;
|
|
495
|
+
responseOkField: string;
|
|
489
496
|
featureFlag: string;
|
|
490
497
|
managedServiceIsPaid: boolean;
|
|
491
498
|
}>,
|
|
@@ -610,6 +617,9 @@ export function updateProvider(
|
|
|
610
617
|
if (params.identityOkField !== undefined) {
|
|
611
618
|
set.identityOkField = params.identityOkField;
|
|
612
619
|
}
|
|
620
|
+
if (params.responseOkField !== undefined) {
|
|
621
|
+
set.responseOkField = params.responseOkField;
|
|
622
|
+
}
|
|
613
623
|
if (params.featureFlag !== undefined) {
|
|
614
624
|
set.featureFlag = params.featureFlag;
|
|
615
625
|
}
|
|
@@ -141,6 +141,7 @@ function _serializeProvider(
|
|
|
141
141
|
: null,
|
|
142
142
|
identityFormat: row.identityFormat ?? null,
|
|
143
143
|
identityOkField: row.identityOkField ?? null,
|
|
144
|
+
responseOkField: row.responseOkField ?? null,
|
|
144
145
|
featureFlag: row.featureFlag ?? null,
|
|
145
146
|
redirectUri:
|
|
146
147
|
options?.redirectUri !== undefined ? options.redirectUri : null,
|
|
@@ -104,6 +104,14 @@ export const PROVIDER_SEED_DATA: Record<
|
|
|
104
104
|
identityResponsePaths?: string[];
|
|
105
105
|
identityFormat?: string;
|
|
106
106
|
identityOkField?: string;
|
|
107
|
+
/**
|
|
108
|
+
* Dot path of the boolean the provider's API puts in every response body
|
|
109
|
+
* to report success (Slack: `ok`). Ping and the authenticated-request
|
|
110
|
+
* doors read an explicit `false` under a 2xx as a failed call, since such
|
|
111
|
+
* a provider hides refusals behind a successful status. Distinct from
|
|
112
|
+
* `identityOkField`, which governs only the identity call.
|
|
113
|
+
*/
|
|
114
|
+
responseOkField?: string;
|
|
107
115
|
featureFlag?: string;
|
|
108
116
|
logoUrl?: string;
|
|
109
117
|
}
|
|
@@ -256,6 +264,7 @@ export const PROVIDER_SEED_DATA: Record<
|
|
|
256
264
|
appType: "Slack App",
|
|
257
265
|
identityUrl: "https://slack.com/api/auth.test",
|
|
258
266
|
identityOkField: "ok",
|
|
267
|
+
responseOkField: "ok",
|
|
259
268
|
identityResponsePaths: ["user", "team"],
|
|
260
269
|
identityFormat: "@${user} (${team})",
|
|
261
270
|
},
|
|
@@ -1221,6 +1230,9 @@ export const PROVIDER_SEED_DATA: Record<
|
|
|
1221
1230
|
authorizeUrl: "urn:manual-token",
|
|
1222
1231
|
tokenExchangeUrl: "urn:manual-token",
|
|
1223
1232
|
pingUrl: "https://slack.com/api/auth.test",
|
|
1233
|
+
// The bot answers through the same Web API envelope as the person's
|
|
1234
|
+
// integration, and auth.test refuses a revoked token inside an HTTP 200.
|
|
1235
|
+
responseOkField: "ok",
|
|
1224
1236
|
baseUrl: "https://slack.com/api",
|
|
1225
1237
|
displayLabel: "Slack Channel",
|
|
1226
1238
|
description: "Channel bot token",
|
|
@@ -111,6 +111,22 @@ describe("createGuardianRequestForConfirmation", () => {
|
|
|
111
111
|
expect(expiresAt).toBeLessThanOrEqual(after + APPROVAL_WINDOW_MS);
|
|
112
112
|
});
|
|
113
113
|
|
|
114
|
+
test("describes the request by the status sentence, never a tool's own reason argument", async () => {
|
|
115
|
+
await createGuardianRequestForConfirmation(
|
|
116
|
+
{ ...MSG, input: { reason: "Task complete" } },
|
|
117
|
+
"conv-1",
|
|
118
|
+
);
|
|
119
|
+
await createGuardianRequestForConfirmation(
|
|
120
|
+
{ ...MSG, input: { activity: "Stopping the app" } },
|
|
121
|
+
"conv-1",
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
expect(createCalls.map((call) => call.activityText)).toEqual([
|
|
125
|
+
undefined,
|
|
126
|
+
"Stopping the app",
|
|
127
|
+
]);
|
|
128
|
+
});
|
|
129
|
+
|
|
114
130
|
test("expires the row and skips the bridge when the confirmation resolved mid-create", async () => {
|
|
115
131
|
confirmationPending = false;
|
|
116
132
|
|
|
@@ -73,10 +73,9 @@ export async function createGuardianRequestForConfirmation(
|
|
|
73
73
|
const sourceChannel = trustContext?.sourceChannel ?? "vellum";
|
|
74
74
|
const inputRecord = msg.input as Record<string, unknown>;
|
|
75
75
|
const activityRaw =
|
|
76
|
-
|
|
76
|
+
typeof inputRecord.activity === "string"
|
|
77
77
|
? inputRecord.activity
|
|
78
|
-
: undefined
|
|
79
|
-
(typeof inputRecord.reason === "string" ? inputRecord.reason : undefined);
|
|
78
|
+
: undefined;
|
|
80
79
|
// Tool approvals are decisionable: without a bound principal nobody could
|
|
81
80
|
// ever decide them (mirrors the gateway create's integrity guard).
|
|
82
81
|
const guardianPrincipalId = trustContext?.guardianPrincipalId;
|
|
@@ -455,3 +455,34 @@ describe("QuestionPrompter", () => {
|
|
|
455
455
|
expect(sent).toHaveLength(0);
|
|
456
456
|
});
|
|
457
457
|
});
|
|
458
|
+
|
|
459
|
+
test("desktop help presentation survives broadcast and pending recovery until Done", async () => {
|
|
460
|
+
const { prompter, sent } = makePrompter();
|
|
461
|
+
const pending = prompter.prompt({
|
|
462
|
+
conversationId: "conv-123",
|
|
463
|
+
questions: [
|
|
464
|
+
{
|
|
465
|
+
question: "Please complete the CAPTCHA.",
|
|
466
|
+
presentation: "virtual_desktop",
|
|
467
|
+
options: [
|
|
468
|
+
{ id: "done", label: "Done" },
|
|
469
|
+
{ id: "skip", label: "Skip" },
|
|
470
|
+
],
|
|
471
|
+
},
|
|
472
|
+
],
|
|
473
|
+
});
|
|
474
|
+
const request = sent.find(
|
|
475
|
+
(event) => event.type === "question_request",
|
|
476
|
+
) as QuestionRequestEvent;
|
|
477
|
+
expect(request.questions[0]?.presentation).toBe("virtual_desktop");
|
|
478
|
+
expect(_piStore.get(request.requestId)?.questionDetails?.entries).toEqual(
|
|
479
|
+
request.questions,
|
|
480
|
+
);
|
|
481
|
+
resolveBatch(request.requestId, [
|
|
482
|
+
{ questionId: "q1", kind: "option", optionId: "done" },
|
|
483
|
+
]);
|
|
484
|
+
expect((await pending).entries).toEqual([
|
|
485
|
+
{ questionId: "q1", decision: "option", optionId: "done" },
|
|
486
|
+
]);
|
|
487
|
+
expect(_piStore.has(request.requestId)).toBe(false);
|
|
488
|
+
});
|
|
@@ -77,6 +77,7 @@ export interface QuestionPromptParamsEntry {
|
|
|
77
77
|
description?: string;
|
|
78
78
|
options: QuestionOption[];
|
|
79
79
|
freeTextPlaceholder?: string;
|
|
80
|
+
presentation?: QuestionEntry["presentation"];
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
export interface QuestionPromptParams {
|
|
@@ -201,6 +202,7 @@ export class QuestionPrompter {
|
|
|
201
202
|
description: q.description,
|
|
202
203
|
options: q.options,
|
|
203
204
|
freeTextPlaceholder: q.freeTextPlaceholder,
|
|
205
|
+
...(q.presentation ? { presentation: q.presentation } : {}),
|
|
204
206
|
}));
|
|
205
207
|
const orderedIds = entries.map((e) => e.id);
|
|
206
208
|
const optionsById: Record<string, string[]> = {};
|