@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
|
@@ -6,52 +6,7 @@
|
|
|
6
6
|
* This module is side-effect free: importing it does not register any plugin.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
const HIGH_SURROGATE_END = 0xdbff;
|
|
11
|
-
const LOW_SURROGATE_START = 0xdc00;
|
|
12
|
-
const LOW_SURROGATE_END = 0xdfff;
|
|
13
|
-
|
|
14
|
-
function isHighSurrogate(code: number): boolean {
|
|
15
|
-
return code >= HIGH_SURROGATE_START && code <= HIGH_SURROGATE_END;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function isLowSurrogate(code: number): boolean {
|
|
19
|
-
return code >= LOW_SURROGATE_START && code <= LOW_SURROGATE_END;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Slice a string by code-unit indices without splitting a surrogate pair, so
|
|
24
|
-
* the result never ends or begins mid-emoji. Indices are clamped to the
|
|
25
|
-
* string bounds; a cut that would land between a high/low surrogate is nudged
|
|
26
|
-
* inward to the nearest whole code point.
|
|
27
|
-
*/
|
|
28
|
-
function safeStringSlice(
|
|
29
|
-
str: string,
|
|
30
|
-
start = 0,
|
|
31
|
-
end: number = str.length,
|
|
32
|
-
): string {
|
|
33
|
-
let safeStart = Math.max(0, Math.min(str.length, start));
|
|
34
|
-
let safeEnd = Math.max(safeStart, Math.min(str.length, end));
|
|
35
|
-
|
|
36
|
-
if (safeEnd < str.length && safeEnd > safeStart) {
|
|
37
|
-
const lastCode = str.charCodeAt(safeEnd - 1);
|
|
38
|
-
if (isHighSurrogate(lastCode)) {
|
|
39
|
-
safeEnd--;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (safeStart > 0 && safeStart < str.length) {
|
|
44
|
-
const firstCode = str.charCodeAt(safeStart);
|
|
45
|
-
if (isLowSurrogate(firstCode)) {
|
|
46
|
-
safeStart++;
|
|
47
|
-
if (safeStart > safeEnd) {
|
|
48
|
-
safeEnd = safeStart;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return str.slice(safeStart, safeEnd);
|
|
54
|
-
}
|
|
9
|
+
import { safeStringSlice } from "@vellumai/plugin-api";
|
|
55
10
|
|
|
56
11
|
/**
|
|
57
12
|
* Minimum number of characters to preserve when truncating.
|
|
@@ -103,6 +103,31 @@ describe("parallel-tasks system prompt section", () => {
|
|
|
103
103
|
}
|
|
104
104
|
});
|
|
105
105
|
|
|
106
|
+
test("an explicit rendered state replaces the derivation", () => {
|
|
107
|
+
// A fork wake replaying its source's recorded surface renders what the
|
|
108
|
+
// source's live turn rendered, whatever its own scope could spawn.
|
|
109
|
+
expect(
|
|
110
|
+
buildSystemPrompt({
|
|
111
|
+
canSpawnSubagents: false,
|
|
112
|
+
delegateIndependentTasks: true,
|
|
113
|
+
}),
|
|
114
|
+
).toContain(HEADING);
|
|
115
|
+
expect(
|
|
116
|
+
buildSystemPrompt({
|
|
117
|
+
canSpawnSubagents: true,
|
|
118
|
+
delegateIndependentTasks: false,
|
|
119
|
+
}),
|
|
120
|
+
).not.toContain(HEADING);
|
|
121
|
+
// It is the rendered state, not a capability claim: the channel gate has
|
|
122
|
+
// already been applied by the turn that recorded it.
|
|
123
|
+
expect(
|
|
124
|
+
buildSystemPrompt({
|
|
125
|
+
channelCapabilities: channel("slack"),
|
|
126
|
+
delegateIndependentTasks: true,
|
|
127
|
+
}),
|
|
128
|
+
).toContain(HEADING);
|
|
129
|
+
});
|
|
130
|
+
|
|
106
131
|
test("delegates only independent tasks and keeps small requests inline", () => {
|
|
107
132
|
const prompt = buildSystemPrompt({ canSpawnSubagents: true });
|
|
108
133
|
expect(prompt).toContain("several independent things at once");
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { ChannelCapabilities } from "../daemon/conversation-runtime-assembly.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Whether this turn arrived on an external messaging surface (Slack, Telegram,
|
|
5
|
+
* email, a plugin channel) rather than the app itself.
|
|
6
|
+
*
|
|
7
|
+
* `resolveChannelCapabilities` folds every first-party client (macOS, web,
|
|
8
|
+
* iOS, CLI, the HTTP API) onto the `vellum` channel, and a prompt built
|
|
9
|
+
* outside a turn carries no capabilities at all, so those two cases are the
|
|
10
|
+
* app and everything else is a channel the gateway delivers to over a reply
|
|
11
|
+
* callback.
|
|
12
|
+
*/
|
|
13
|
+
export function isExternalChannelTurn(
|
|
14
|
+
capabilities: ChannelCapabilities | undefined,
|
|
15
|
+
): boolean {
|
|
16
|
+
const channel = capabilities?.channel;
|
|
17
|
+
return channel !== undefined && channel !== "vellum";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The two inputs the delegation section's gate reads. */
|
|
21
|
+
export interface DelegationGateInputs {
|
|
22
|
+
/**
|
|
23
|
+
* Whether the turn can actually spawn subagents, read off its resolved
|
|
24
|
+
* tool surface (`canSpawnSubagentsForTurn`). Absent means no.
|
|
25
|
+
*/
|
|
26
|
+
canSpawnSubagents?: boolean;
|
|
27
|
+
channelCapabilities?: ChannelCapabilities;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The delegation section's gate: the turn can spawn AND a subagent's answer
|
|
32
|
+
* would reach whoever asked.
|
|
33
|
+
*
|
|
34
|
+
* Off unless a caller states it can spawn, and the caller that states it
|
|
35
|
+
* derives the answer from the turn's resolved tool surface rather than
|
|
36
|
+
* assuming (`canSpawnSubagentsForTurn`). Guidance about handing work to
|
|
37
|
+
* subagents is worth nothing to a turn that cannot spawn, and worse than
|
|
38
|
+
* nothing when it makes that turn defer work it has to do inline.
|
|
39
|
+
*
|
|
40
|
+
* Off on an external channel for the second reason: a subagent's terminal
|
|
41
|
+
* summary reaches the parent through the conversation's event sink, which an
|
|
42
|
+
* app client reads and a channel does not, so a delegated answer would never
|
|
43
|
+
* be delivered to the person who asked for it.
|
|
44
|
+
*
|
|
45
|
+
* Its own module so the conversation's prompt build and the prompt builder
|
|
46
|
+
* share one derivation: the conversation resolves the state once, hands it to
|
|
47
|
+
* the builder, and keeps it for the wire-surface recorder, so the prompt the
|
|
48
|
+
* provider receives and the state a fork replays are the same value.
|
|
49
|
+
*/
|
|
50
|
+
export function resolveDelegateIndependentTasks(
|
|
51
|
+
options: DelegationGateInputs | undefined,
|
|
52
|
+
): boolean {
|
|
53
|
+
return (
|
|
54
|
+
options?.canSpawnSubagents === true &&
|
|
55
|
+
!isExternalChannelTurn(options?.channelCapabilities)
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
import { stripCommentLines } from "../util/strip-comment-lines.js";
|
|
27
27
|
import { cleanupBootstrapFiles } from "./bootstrap-cleanup.js";
|
|
28
28
|
import { SYSTEM_PROMPT_CACHE_BOUNDARY } from "./cache-boundary.js";
|
|
29
|
+
import { resolveDelegateIndependentTasks } from "./delegation-gate.js";
|
|
29
30
|
import { resolveGuardianPersona, resolveUserSlug } from "./persona-resolver.js";
|
|
30
31
|
import { renderWorkspaceSections } from "./sections.js";
|
|
31
32
|
import { isTemplateContent } from "./template-detection.js";
|
|
@@ -440,23 +441,15 @@ export interface BuildSystemPromptOptions {
|
|
|
440
441
|
* pass), rather than from a caller's assumption.
|
|
441
442
|
*/
|
|
442
443
|
canSpawnSubagents?: boolean;
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
* app and everything else is a channel the gateway delivers to over a reply
|
|
453
|
-
* callback.
|
|
454
|
-
*/
|
|
455
|
-
function isExternalChannelTurn(
|
|
456
|
-
capabilities: ChannelCapabilities | undefined,
|
|
457
|
-
): boolean {
|
|
458
|
-
const channel = capabilities?.channel;
|
|
459
|
-
return channel !== undefined && channel !== "vellum";
|
|
444
|
+
/**
|
|
445
|
+
* The delegation section's rendered state, replacing the derivation from
|
|
446
|
+
* `canSpawnSubagents` and `channelCapabilities` when set. A fork wake
|
|
447
|
+
* replaying its source conversation's recorded surface passes the state
|
|
448
|
+
* the source's live turn rendered (`ConversationToolSurface`), so the
|
|
449
|
+
* fork's system prompt matches the source's byte for byte even though the
|
|
450
|
+
* wake's own scope cannot spawn; a spawn is still rejected at execution.
|
|
451
|
+
*/
|
|
452
|
+
delegateIndependentTasks?: boolean;
|
|
460
453
|
}
|
|
461
454
|
|
|
462
455
|
/**
|
|
@@ -525,22 +518,11 @@ export function buildSystemPrompt(options?: BuildSystemPromptOptions): string {
|
|
|
525
518
|
const ctx = {
|
|
526
519
|
...options,
|
|
527
520
|
hasNoClient,
|
|
528
|
-
// The delegation section's gate
|
|
529
|
-
//
|
|
530
|
-
//
|
|
531
|
-
// Off unless a caller states it can spawn, and the caller that states it
|
|
532
|
-
// derives the answer from the turn's resolved tool surface rather than
|
|
533
|
-
// assuming (`canSpawnSubagentsForTurn`). Guidance about handing work to
|
|
534
|
-
// subagents is worth nothing to a turn that cannot spawn, and worse than
|
|
535
|
-
// nothing when it makes that turn defer work it has to do inline.
|
|
536
|
-
//
|
|
537
|
-
// Off on an external channel for the second reason: a subagent's terminal
|
|
538
|
-
// summary reaches the parent through the conversation's event sink, which
|
|
539
|
-
// an app client reads and a channel does not, so a delegated answer would
|
|
540
|
-
// never be delivered to the person who asked for it.
|
|
521
|
+
// The delegation section's gate, or the rendered state a replaying wake
|
|
522
|
+
// carries from its source (see `resolveDelegateIndependentTasks`).
|
|
541
523
|
delegateIndependentTasks:
|
|
542
|
-
options?.
|
|
543
|
-
|
|
524
|
+
options?.delegateIndependentTasks ??
|
|
525
|
+
resolveDelegateIndependentTasks(options),
|
|
544
526
|
isContainerized: getIsContainerized(),
|
|
545
527
|
sendUserMessageTool: options?.sendUserMessageTool === true,
|
|
546
528
|
workspaceDir: getWorkspaceDir(),
|
|
@@ -35,6 +35,7 @@ import { AtlasCloudProvider } from "../atlascloud/client.js";
|
|
|
35
35
|
import { BasetenProvider } from "../baseten/client.js";
|
|
36
36
|
import { FireworksProvider } from "../fireworks/client.js";
|
|
37
37
|
import { GeminiProvider } from "../gemini/client.js";
|
|
38
|
+
import { JevProvider } from "../jev/client.js";
|
|
38
39
|
import { MinimaxProvider } from "../minimax/client.js";
|
|
39
40
|
import { PROVIDER_CATALOG } from "../model-catalog.js";
|
|
40
41
|
import { OllamaProvider } from "../ollama/client.js";
|
|
@@ -205,6 +206,11 @@ const ADAPTER_FACTORIES: Record<string, AdapterFactory> = {
|
|
|
205
206
|
streamTimeoutMs,
|
|
206
207
|
...(baseURL ? { baseURL } : {}),
|
|
207
208
|
}),
|
|
209
|
+
jev: ({ apiKey, model, streamTimeoutMs, baseURL }) =>
|
|
210
|
+
new JevProvider(apiKey, model, {
|
|
211
|
+
streamTimeoutMs,
|
|
212
|
+
...(baseURL ? { baseURL } : {}),
|
|
213
|
+
}),
|
|
208
214
|
vellum: ({ apiKey, model, streamTimeoutMs, baseURL }) =>
|
|
209
215
|
new VellumProvider(apiKey, model, {
|
|
210
216
|
streamTimeoutMs,
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { ProviderError } from "../../util/errors.js";
|
|
4
|
+
import {
|
|
5
|
+
conversationToState,
|
|
6
|
+
DEFAULT_JEV_MODEL,
|
|
7
|
+
JevProvider,
|
|
8
|
+
parseSystemOneOverride,
|
|
9
|
+
validateJevApiKey,
|
|
10
|
+
} from "./client.js";
|
|
11
|
+
|
|
12
|
+
const originalFetch = globalThis.fetch;
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
globalThis.fetch = originalFetch;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
function jsonResponse(body: unknown, status = 200): Response {
|
|
19
|
+
return new Response(JSON.stringify(body), {
|
|
20
|
+
status,
|
|
21
|
+
headers: { "Content-Type": "application/json" },
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function stubFetch(
|
|
26
|
+
impl: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>,
|
|
27
|
+
): typeof fetch {
|
|
28
|
+
return impl as unknown as typeof fetch;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe("parseSystemOneOverride", () => {
|
|
32
|
+
test("reads a TypeSafe questions payload from the last user message", () => {
|
|
33
|
+
const parsed = parseSystemOneOverride(
|
|
34
|
+
JSON.stringify({
|
|
35
|
+
questions: {
|
|
36
|
+
urgency: {
|
|
37
|
+
type: "noul",
|
|
38
|
+
instructions: "Does this message express urgency?",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
expect(parsed?.questions.urgency?.type).toBe("noul");
|
|
44
|
+
expect(parsed?.state).toBeUndefined();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("forwards an explicit state when present", () => {
|
|
48
|
+
const parsed = parseSystemOneOverride(
|
|
49
|
+
JSON.stringify({
|
|
50
|
+
state: "Cancel my subscription",
|
|
51
|
+
questions: {
|
|
52
|
+
intent: {
|
|
53
|
+
type: "choice",
|
|
54
|
+
instructions: "What does the user want?",
|
|
55
|
+
criteria: { cancel: "Cancellation", refund: "Refund" },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
expect(parsed?.state).toBe("Cancel my subscription");
|
|
61
|
+
expect(parsed?.questions.intent?.type).toBe("choice");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("ignores ordinary chat text", () => {
|
|
65
|
+
expect(parseSystemOneOverride("Cancel my subscription")).toBeNull();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("conversationToState", () => {
|
|
70
|
+
test("flattens system prompt and turns into a string", () => {
|
|
71
|
+
const state = conversationToState(
|
|
72
|
+
[
|
|
73
|
+
{
|
|
74
|
+
role: "user",
|
|
75
|
+
content: [{ type: "text", text: "Cancel my subscription" }],
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
"You are a helpful assistant.",
|
|
79
|
+
);
|
|
80
|
+
expect(state).toContain("system:\nYou are a helpful assistant.");
|
|
81
|
+
expect(state).toContain("user:\nCancel my subscription");
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("validateJevApiKey", () => {
|
|
86
|
+
test("rejects a 401 from the models endpoint", async () => {
|
|
87
|
+
globalThis.fetch = stubFetch(async () =>
|
|
88
|
+
jsonResponse({ error: "nope" }, 401),
|
|
89
|
+
);
|
|
90
|
+
const result = await validateJevApiKey("bad-key");
|
|
91
|
+
expect(result).toEqual({
|
|
92
|
+
valid: false,
|
|
93
|
+
reason: "API key is invalid or expired.",
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("falls back to System One when /v1/models is unpublished", async () => {
|
|
98
|
+
const urls: string[] = [];
|
|
99
|
+
globalThis.fetch = stubFetch(async (input) => {
|
|
100
|
+
const url = String(input);
|
|
101
|
+
urls.push(url);
|
|
102
|
+
if (url.endsWith("/v1/models")) {
|
|
103
|
+
return jsonResponse({ error: "missing" }, 404);
|
|
104
|
+
}
|
|
105
|
+
return jsonResponse({
|
|
106
|
+
model: DEFAULT_JEV_MODEL,
|
|
107
|
+
answers: { probe: { type: "noul", noul: 1 } },
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
const result = await validateJevApiKey("sk-test");
|
|
111
|
+
expect(result).toEqual({ valid: true });
|
|
112
|
+
expect(urls.some((url) => url.endsWith("/v1/systemone"))).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("allows the key on transient 429s", async () => {
|
|
116
|
+
globalThis.fetch = stubFetch(async () =>
|
|
117
|
+
jsonResponse({ error: "slow down" }, 429),
|
|
118
|
+
);
|
|
119
|
+
const result = await validateJevApiKey("sk-test");
|
|
120
|
+
expect(result).toEqual({ valid: true });
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("JevProvider.sendMessage", () => {
|
|
125
|
+
test("evaluates conversation state with the default experiment noul", async () => {
|
|
126
|
+
let captured: { url: string; body: unknown } | undefined;
|
|
127
|
+
globalThis.fetch = stubFetch(async (input, init) => {
|
|
128
|
+
captured = {
|
|
129
|
+
url: String(input),
|
|
130
|
+
body: JSON.parse(String(init?.body ?? "{}")) as unknown,
|
|
131
|
+
};
|
|
132
|
+
return jsonResponse({
|
|
133
|
+
model: "jev-latest",
|
|
134
|
+
answers: { actionable: { type: "noul", noul: 0.91 } },
|
|
135
|
+
usage: { input_tokens: 12, output_tokens: 4 },
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const provider = new JevProvider("sk-test", "jev-latest");
|
|
140
|
+
const deltas: string[] = [];
|
|
141
|
+
const response = await provider.sendMessage(
|
|
142
|
+
[
|
|
143
|
+
{
|
|
144
|
+
role: "user",
|
|
145
|
+
content: [{ type: "text", text: "Please cancel this charge." }],
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
{
|
|
149
|
+
onEvent: (event) => {
|
|
150
|
+
if (event.type === "text_delta") {
|
|
151
|
+
deltas.push(event.text);
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
expect(captured?.url).toBe("https://api.typesafe.ai/v1/systemone");
|
|
158
|
+
expect(captured?.body).toMatchObject({
|
|
159
|
+
model: "jev-latest",
|
|
160
|
+
questions: {
|
|
161
|
+
actionable: {
|
|
162
|
+
type: "noul",
|
|
163
|
+
instructions:
|
|
164
|
+
"Does the latest user message ask the assistant to take a specific action?",
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
expect(String((captured?.body as { state: string }).state)).toContain(
|
|
169
|
+
"Please cancel this charge.",
|
|
170
|
+
);
|
|
171
|
+
expect(response.model).toBe("jev-latest");
|
|
172
|
+
expect(response.usage).toEqual({ inputTokens: 12, outputTokens: 4 });
|
|
173
|
+
expect(response.content[0]).toEqual({
|
|
174
|
+
type: "text",
|
|
175
|
+
text: JSON.stringify(
|
|
176
|
+
{ actionable: { type: "noul", noul: 0.91 } },
|
|
177
|
+
null,
|
|
178
|
+
2,
|
|
179
|
+
),
|
|
180
|
+
});
|
|
181
|
+
expect(deltas.join("")).toBe(
|
|
182
|
+
response.content[0] && "text" in response.content[0]
|
|
183
|
+
? response.content[0].text
|
|
184
|
+
: "",
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("forwards an explicit System One payload from the last user message", async () => {
|
|
189
|
+
let captured: unknown;
|
|
190
|
+
globalThis.fetch = stubFetch(async (_input, init) => {
|
|
191
|
+
captured = JSON.parse(String(init?.body ?? "{}")) as unknown;
|
|
192
|
+
return jsonResponse({
|
|
193
|
+
model: "jev-latest",
|
|
194
|
+
answers: {
|
|
195
|
+
department: {
|
|
196
|
+
type: "choice",
|
|
197
|
+
choice: "billing",
|
|
198
|
+
probabilities: { billing: 0.8, technical: 0.2 },
|
|
199
|
+
confidence: 0.6,
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const provider = new JevProvider("sk-test", "jev-latest");
|
|
206
|
+
await provider.sendMessage([
|
|
207
|
+
{
|
|
208
|
+
role: "user",
|
|
209
|
+
content: [
|
|
210
|
+
{
|
|
211
|
+
type: "text",
|
|
212
|
+
text: JSON.stringify({
|
|
213
|
+
state: "I was charged twice.",
|
|
214
|
+
questions: {
|
|
215
|
+
department: {
|
|
216
|
+
type: "choice",
|
|
217
|
+
instructions: "Which team should handle this?",
|
|
218
|
+
criteria: {
|
|
219
|
+
billing: "Payments",
|
|
220
|
+
technical: "Bugs",
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
}),
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
},
|
|
228
|
+
]);
|
|
229
|
+
|
|
230
|
+
expect(captured).toMatchObject({
|
|
231
|
+
model: "jev-latest",
|
|
232
|
+
state: "I was charged twice.",
|
|
233
|
+
questions: {
|
|
234
|
+
department: {
|
|
235
|
+
type: "choice",
|
|
236
|
+
instructions: "Which team should handle this?",
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("throws ProviderError with invalid_credentials on 401", async () => {
|
|
243
|
+
globalThis.fetch = stubFetch(async () =>
|
|
244
|
+
jsonResponse({ error: { message: "bad key" } }, 401),
|
|
245
|
+
);
|
|
246
|
+
const provider = new JevProvider("sk-test", "jev-latest");
|
|
247
|
+
try {
|
|
248
|
+
await provider.sendMessage([
|
|
249
|
+
{ role: "user", content: [{ type: "text", text: "hello" }] },
|
|
250
|
+
]);
|
|
251
|
+
throw new Error("expected sendMessage to throw");
|
|
252
|
+
} catch (error) {
|
|
253
|
+
expect(error).toBeInstanceOf(ProviderError);
|
|
254
|
+
const providerError = error as ProviderError;
|
|
255
|
+
expect(providerError.statusCode).toBe(401);
|
|
256
|
+
expect(providerError.reason).toBe("invalid_credentials");
|
|
257
|
+
expect(providerError.message).toBe("bad key");
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
});
|