@vellumai/assistant 0.12.2-staging.1 → 0.12.2-staging.3
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/docs/desktop-browser-cli.md +33 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +13 -8
- package/node_modules/@vellumai/slack-text/src/label-resolution-entities.test.ts +95 -0
- package/openapi.yaml +50 -4
- package/package.json +1 -1
- package/scripts/smoke-desktop-browser-cli.ts +279 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +5 -5
- package/src/__tests__/config-schema.test.ts +1 -1
- package/src/__tests__/config-sounds-sync.test.ts +23 -0
- package/src/__tests__/conversation-error.test.ts +4 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +33 -3
- package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-point-at-capability.test.ts +1 -0
- package/src/__tests__/credential-routes.test.ts +38 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/cu-unified-flow.test.ts +6 -2
- package/src/__tests__/external-plugin-loader.test.ts +52 -0
- package/src/__tests__/headless-browser-mode.test.ts +48 -0
- package/src/__tests__/host-cu-proxy.test.ts +185 -30
- package/src/__tests__/list-all-apps.test.ts +40 -8
- package/src/__tests__/mcp-auth-routes.test.ts +99 -0
- package/src/__tests__/mcp-client-auth.test.ts +54 -0
- package/src/__tests__/mcp-health-check.test.ts +32 -32
- package/src/__tests__/mcp-list-plugin-servers.test.ts +77 -32
- package/src/__tests__/mcp-tool-annotations-risk.test.ts +4 -2
- package/src/__tests__/oauth-commands-routes.test.ts +55 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +22 -0
- package/src/__tests__/oauth-providers-routes.test.ts +1 -0
- package/src/__tests__/secret-routes-acp-guard.test.ts +59 -1
- package/src/__tests__/skills.test.ts +10 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +51 -0
- package/src/__tests__/ui-channel-variants.test.ts +1 -56
- package/src/__tests__/user-plugin-loader.test.ts +21 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +257 -6
- package/src/acp/__tests__/acp-credentials.test.ts +13 -0
- package/src/acp/__tests__/claude-token-refresh.test.ts +257 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +60 -1
- package/src/acp/acp-claude-oauth.ts +328 -14
- package/src/acp/acp-credentials.ts +19 -0
- package/src/acp/claude-token-refresh.ts +150 -0
- package/src/acp/prepare-agent-env.ts +21 -6
- package/src/apps/app-store.ts +6 -5
- package/src/browser/operations.ts +2 -1
- package/src/browser/types.ts +7 -0
- package/src/browser/virtual-desktop-target.ts +34 -0
- package/src/calls/__tests__/progress-narration.test.ts +23 -0
- package/src/calls/__tests__/voice-control-protocol.test.ts +69 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +81 -0
- package/src/calls/progress-narration.ts +15 -2
- package/src/calls/voice-control-protocol.ts +132 -0
- package/src/calls/voice-session-bridge.ts +56 -26
- package/src/calls/voice-triage-escalate.ts +3 -3
- package/src/cli/commands/__tests__/browser.test.ts +84 -3
- package/src/cli/commands/browser.help.ts +43 -0
- package/src/cli/commands/browser.ts +52 -8
- package/src/cli/commands/oauth/status.ts +5 -0
- package/src/cli/commands/plugins.ts +16 -5
- package/src/cli/lib/__tests__/install-from-github.test.ts +111 -0
- package/src/cli/lib/__tests__/install-from-platform.test.ts +77 -2
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +45 -3
- package/src/cli/lib/__tests__/plugin-details.test.ts +26 -0
- package/src/cli/lib/__tests__/uninstall-plugin.test.ts +122 -5
- package/src/cli/lib/bundled-marketplace.json +13 -0
- package/src/cli/lib/install-from-github.ts +17 -18
- package/src/cli/lib/install-from-platform.ts +14 -9
- package/src/cli/lib/list-installed-plugins.ts +21 -35
- package/src/cli/lib/plugin-details.ts +21 -8
- package/src/cli/lib/uninstall-plugin.ts +59 -1
- package/src/config/__tests__/plugin-resident-skill-discovery.test.ts +21 -0
- package/src/config/bundled-skills/computer-use/SKILL.md +43 -4
- package/src/config/bundled-skills/computer-use/TOOLS.json +3 -3
- package/src/config/bundled-skills/screen-annotation/SKILL.md +2 -2
- package/src/config/feature-flag-registry.json +11 -3
- package/src/config/loader.ts +1 -0
- package/src/config/schemas/__tests__/voice.test.ts +2 -2
- package/src/config/schemas/mcp.ts +9 -7
- package/src/config/schemas/monitoring.ts +9 -0
- package/src/config/schemas/services.ts +10 -0
- package/src/config/schemas/voice.ts +2 -2
- package/src/config/skills.ts +13 -21
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +47 -0
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +1 -0
- package/src/daemon/conversation-client-surface.ts +26 -0
- package/src/daemon/conversation-runtime-assembly.ts +19 -7
- package/src/daemon/conversation-surfaces.ts +6 -1
- package/src/daemon/conversation-tool-setup.ts +37 -43
- package/src/daemon/host-cu-proxy.ts +94 -31
- package/src/daemon/host-proxy-preactivation.ts +2 -0
- package/src/daemon/mcp-reload-service.ts +7 -0
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/providers-setup.ts +4 -0
- package/src/desktop/__tests__/fake-desktop.ts +1 -0
- package/src/desktop/desktop-automation-lease.test.ts +208 -0
- package/src/desktop/desktop-automation-lease.ts +270 -0
- package/src/desktop/desktop-browser-client.test.ts +413 -0
- package/src/desktop/desktop-browser-client.ts +430 -0
- package/src/desktop/desktop-browser-cursor.ts +40 -0
- package/src/desktop/desktop-browser-endpoint.test.ts +78 -0
- package/src/desktop/desktop-browser-endpoint.ts +145 -0
- package/src/desktop/desktop-browser-operations.ts +130 -0
- package/src/desktop/desktop-chrome-session.test.ts +48 -0
- package/src/desktop/desktop-chrome-session.ts +58 -8
- package/src/desktop/desktop-dependencies.test.ts +29 -0
- package/src/desktop/desktop-dependencies.ts +26 -5
- package/src/desktop/desktop-display.ts +9 -0
- package/src/desktop/desktop-panel-config.ts +14 -1
- package/src/desktop/desktop-session-manager.test.ts +86 -3
- package/src/desktop/desktop-session-manager.ts +144 -24
- package/src/desktop/desktop-stream-bridge.ts +1 -1
- package/src/desktop/virtual-desktop-feature.ts +18 -0
- package/src/desktop/virtual-desktop-platform.test.ts +61 -0
- package/src/i18n/__tests__/i18n.test.ts +9 -5
- package/src/i18n/messages.ts +13 -4
- package/src/ipc/__tests__/browser-ipc.test.ts +230 -2
- package/src/ipc/__tests__/cancel-on-disconnect.test.ts +54 -0
- package/src/ipc/assistant-server.ts +15 -1
- package/src/ipc/cli-client.ts +13 -5
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +143 -0
- package/src/live-voice/__tests__/live-voice-events.test.ts +9 -7
- package/src/live-voice/__tests__/live-voice-progress.test.ts +79 -0
- package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +19 -0
- package/src/live-voice/__tests__/protocol.test.ts +47 -0
- package/src/live-voice/__tests__/session-controls.test.ts +113 -0
- package/src/live-voice/live-voice-metrics.ts +9 -0
- package/src/live-voice/live-voice-session.ts +88 -7
- package/src/live-voice/protocol.ts +69 -0
- package/src/live-voice/session-controls.ts +139 -0
- package/src/mcp/__tests__/credential-target.test.ts +59 -0
- package/src/mcp/__tests__/effective-config.test.ts +11 -0
- package/src/mcp/__tests__/manager-state.test.ts +128 -0
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +20 -6
- package/src/mcp/__tests__/mcp-auth-state-target.test.ts +49 -0
- package/src/mcp/__tests__/plugin-mcp-oauth-cleanup.test.ts +107 -0
- package/src/mcp/__tests__/plugin-mcp-oauth-provider-isolation.test.ts +89 -0
- package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +63 -11
- package/src/mcp/__tests__/reload-signal-emission.test.ts +39 -2
- package/src/mcp/client.ts +64 -16
- package/src/mcp/credential-target.ts +105 -0
- package/src/mcp/effective-config.ts +42 -15
- package/src/mcp/manager.ts +61 -8
- package/src/mcp/mcp-auth-orchestrator.ts +17 -4
- package/src/mcp/mcp-auth-state.ts +38 -6
- package/src/mcp/mcp-oauth-provider.ts +101 -33
- package/src/monitoring/__tests__/mount-watch.test.ts +236 -0
- package/src/monitoring/mount-watch.ts +348 -0
- package/src/monitoring/worker.ts +9 -0
- package/src/oauth/__tests__/seed-providers-managed.test.ts +95 -0
- package/src/oauth/connection-resolver.test.ts +27 -0
- package/src/oauth/connection-resolver.ts +25 -1
- package/src/oauth/provider-serializer.ts +9 -0
- package/src/oauth/seed-providers.ts +121 -2
- package/src/onboarding/checkin-event.ts +2 -1
- package/src/plugins/__tests__/installed-plugin-dirs.test.ts +14 -1
- package/src/plugins/__tests__/mcp-servers.test.ts +38 -8
- package/src/plugins/external-plugin-loader.ts +49 -86
- package/src/plugins/installed-plugin-dirs.ts +6 -4
- package/src/plugins/mcp-servers.ts +9 -15
- package/src/plugins/mtime-cache.ts +2 -1
- package/src/plugins/user-loader.ts +2 -1
- package/src/providers/inference/__tests__/endpoint-probe.test.ts +45 -0
- package/src/providers/inference/endpoint-probe.ts +27 -12
- package/src/providers/openai/responses-provider.ts +9 -2
- package/src/providers/opencode/client.test.ts +167 -0
- package/src/providers/opencode/client.ts +73 -4
- package/src/runtime/AGENTS.md +4 -0
- package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +45 -2
- package/src/runtime/http-server.ts +6 -6
- package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +16 -5
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +74 -4
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +25 -3
- package/src/runtime/routes/acp-claude-auth-routes.ts +11 -3
- package/src/runtime/routes/app-management-routes.ts +17 -2
- package/src/runtime/routes/browser-context.ts +39 -0
- package/src/runtime/routes/browser-routes.ts +16 -47
- package/src/runtime/routes/browser-tabs-routes.ts +72 -29
- package/src/runtime/routes/credential-routes.ts +1 -4
- package/src/runtime/routes/desktop-setup-routes.test.ts +16 -1
- package/src/runtime/routes/desktop-setup-routes.ts +7 -5
- package/src/runtime/routes/mcp-auth-routes.ts +106 -94
- package/src/runtime/routes/oauth-commands-routes.ts +14 -0
- package/src/runtime/routes/oauth-providers.ts +7 -0
- package/src/runtime/routes/plugins-routes.ts +14 -1
- package/src/runtime/sync/resource-sync-events.ts +4 -0
- package/src/tools/browser/browser-execution.ts +44 -13
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +69 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +1 -0
- package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +5 -0
- package/src/tools/computer-use/definitions.ts +3 -4
- package/src/tools/ui-surface/channel-variants.ts +10 -59
- package/src/util/abort-reasons.ts +12 -1
- package/src/util/plugin-manifest.ts +203 -0
- package/src/watch/watch-retro.ts +6 -8
- package/src/desktop/desktop-feature.test.ts +0 -18
- package/src/desktop/desktop-feature.ts +0 -19
|
@@ -1490,6 +1490,7 @@ describe("classifyConversationError", () => {
|
|
|
1490
1490
|
"subagent_aborted",
|
|
1491
1491
|
"signal_cancel",
|
|
1492
1492
|
"voice_session_aborted",
|
|
1493
|
+
"voice_progress_narration_timeout",
|
|
1493
1494
|
];
|
|
1494
1495
|
|
|
1495
1496
|
for (const kind of taggedKinds) {
|
|
@@ -1727,7 +1728,9 @@ describe("ConnectionResolutionError classification", () => {
|
|
|
1727
1728
|
expect(result.userMessage).toContain("qwen/qwen3-8b");
|
|
1728
1729
|
expect(result.userMessage).toContain("Vellum GPU route");
|
|
1729
1730
|
expect(result.userMessage).toContain('profile "steer"');
|
|
1730
|
-
expect(result.userMessage).toContain(
|
|
1731
|
+
expect(result.userMessage).toContain(
|
|
1732
|
+
"was not sent through another provider",
|
|
1733
|
+
);
|
|
1731
1734
|
});
|
|
1732
1735
|
|
|
1733
1736
|
it("classifies missing_credential naming the connection and fix", () => {
|
|
@@ -119,6 +119,7 @@ import {
|
|
|
119
119
|
applyRuntimeInjections,
|
|
120
120
|
assembleSlackActiveThreadFocusBlock,
|
|
121
121
|
assembleSlackChronologicalMessages,
|
|
122
|
+
buildChannelCapabilityBlock,
|
|
122
123
|
buildSubagentStatusBlock,
|
|
123
124
|
getSlackCompactionWatermarkForPrefix,
|
|
124
125
|
getSlackWatermarkAdvanceForRowPrefix,
|
|
@@ -586,6 +587,35 @@ describe("injectChannelCapabilityContext", () => {
|
|
|
586
587
|
expect(text).not.toContain("Do NOT use ui_show, ui_update, or app_create");
|
|
587
588
|
});
|
|
588
589
|
|
|
590
|
+
test("guides non-interactive turns to persist UI for a later capable client", () => {
|
|
591
|
+
const slack: ChannelCapabilities = {
|
|
592
|
+
channel: "slack",
|
|
593
|
+
dashboardCapable: false,
|
|
594
|
+
supportsDynamicUi: false,
|
|
595
|
+
supportsVoiceInput: false,
|
|
596
|
+
};
|
|
597
|
+
const phone: ChannelCapabilities = {
|
|
598
|
+
channel: "phone",
|
|
599
|
+
dashboardCapable: false,
|
|
600
|
+
supportsDynamicUi: false,
|
|
601
|
+
supportsVoiceInput: false,
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
for (const caps of [slack, phone]) {
|
|
605
|
+
const text = buildChannelCapabilityBlock(caps, undefined, true)!;
|
|
606
|
+
expect(text).toContain(
|
|
607
|
+
"ui_show, ui_update, and ui_dismiss persist conversation content",
|
|
608
|
+
);
|
|
609
|
+
expect(text).not.toContain("Only use ui_show/ui_update");
|
|
610
|
+
expect(text).not.toContain(
|
|
611
|
+
"Do NOT use ui_show, ui_update, or app_create",
|
|
612
|
+
);
|
|
613
|
+
expect(text).not.toContain(
|
|
614
|
+
"Present information as well-formatted text instead of dynamic UI.",
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
|
|
589
619
|
test("keeps blanket ui_show/ui_update prohibition for other non-dynamic channels", () => {
|
|
590
620
|
const caps: ChannelCapabilities = {
|
|
591
621
|
channel: "phone",
|
|
@@ -870,12 +900,12 @@ describe("trust-gating via channel capabilities", () => {
|
|
|
870
900
|
|
|
871
901
|
const result = injectChannelCapabilityContext(message, caps);
|
|
872
902
|
|
|
873
|
-
// macOS clients
|
|
903
|
+
// macOS clients get app-scripting guidance injected
|
|
874
904
|
expect(result).not.toBe(message);
|
|
875
905
|
const injected = (result.content[0] as { type: "text"; text: string }).text;
|
|
876
906
|
expect(injected).toContain("client_os: macos");
|
|
877
|
-
expect(injected).toContain("
|
|
878
|
-
expect(injected).toContain("host_bash");
|
|
907
|
+
expect(injected).toContain("drive apps with the computer-use skill");
|
|
908
|
+
expect(injected).toContain("`host_bash` is for shell commands");
|
|
879
909
|
// No channel constraints — full desktop capabilities
|
|
880
910
|
expect(injected).not.toContain("CHANNEL CONSTRAINTS");
|
|
881
911
|
});
|
|
@@ -532,6 +532,28 @@ describe("credentials routes", () => {
|
|
|
532
532
|
);
|
|
533
533
|
});
|
|
534
534
|
|
|
535
|
+
test("stores a pasted Claude token without touching leftover refresh material", async () => {
|
|
536
|
+
secureStore.set("acp:claude_oauth_refresh_token", "stale-refresh");
|
|
537
|
+
secureStore.set("acp:claude_oauth_expires_at", "111");
|
|
538
|
+
|
|
539
|
+
await setRoute!.handler({
|
|
540
|
+
body: {
|
|
541
|
+
service: "acp",
|
|
542
|
+
field: "claude_oauth_token",
|
|
543
|
+
value: "sk-ant-oat01-pasted-token",
|
|
544
|
+
},
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
expect(secureStore.get("acp:claude_oauth_token")).toBe(
|
|
548
|
+
"sk-ant-oat01-pasted-token",
|
|
549
|
+
);
|
|
550
|
+
expect(secureStore.get("acp:claude_oauth_refresh_token")).toBe(
|
|
551
|
+
"stale-refresh",
|
|
552
|
+
);
|
|
553
|
+
expect(secureStore.get("acp:claude_oauth_expires_at")).toBe("111");
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
|
|
535
557
|
test("a successful set scrubs the normalized value from transcripts exactly once", async () => {
|
|
536
558
|
/**
|
|
537
559
|
* The pasted plaintext may already sit in recent transcripts, so a
|
|
@@ -1301,5 +1323,21 @@ describe("credentials routes", () => {
|
|
|
1301
1323
|
// THEN it rejects with a BadRequestError
|
|
1302
1324
|
await expect(call).rejects.toBeInstanceOf(BadRequestError);
|
|
1303
1325
|
});
|
|
1326
|
+
|
|
1327
|
+
test("deletes only the requested Claude access-token field", async () => {
|
|
1328
|
+
secureStore.set("acp:claude_oauth_token", "sk-ant-oat01-connected");
|
|
1329
|
+
secureStore.set("acp:claude_oauth_refresh_token", "refresh-leftover");
|
|
1330
|
+
secureStore.set("acp:claude_oauth_expires_at", "111");
|
|
1331
|
+
|
|
1332
|
+
await deleteRoute!.handler({
|
|
1333
|
+
body: { service: "acp", field: "claude_oauth_token" },
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
expect(secureStore.has("acp:claude_oauth_token")).toBe(false);
|
|
1337
|
+
expect(secureStore.get("acp:claude_oauth_refresh_token")).toBe(
|
|
1338
|
+
"refresh-leftover",
|
|
1339
|
+
);
|
|
1340
|
+
expect(secureStore.get("acp:claude_oauth_expires_at")).toBe("111");
|
|
1341
|
+
});
|
|
1304
1342
|
});
|
|
1305
1343
|
});
|
|
@@ -190,7 +190,7 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
|
|
|
190
190
|
"oauth/credential-token-resolver.ts", // centralized access-token key resolution for OAuth and manual-token providers
|
|
191
191
|
"oauth/connection-resolver.ts", // resolve OAuthConnection from oauth-store (access_token lookup)
|
|
192
192
|
"runtime/routes/secret-routes.ts", // HTTP secret management routes (set/delete secrets)
|
|
193
|
-
"acp/acp-claude-oauth.ts", // Connect Claude OAuth token vault-store helper (stores
|
|
193
|
+
"acp/acp-claude-oauth.ts", // Connect Claude OAuth token vault-store helper (stores access token plus broker-owned refresh/expiry companions via setSecureKeyAsync)
|
|
194
194
|
"runtime/routes/acp-claude-auth-routes.ts", // Connect Claude Code daemon OAuth flow (stores OAuth token in vault)
|
|
195
195
|
"runtime/routes/migration-routes.ts", // migration import credential restore
|
|
196
196
|
"daemon/conversation-messaging.ts", // credential storage during session messaging
|
|
@@ -485,8 +485,12 @@ describe("surfaceProxyResolver — CU tool routing", () => {
|
|
|
485
485
|
// Proxy state is clean after done
|
|
486
486
|
expect(proxy.stepCount).toBe(0);
|
|
487
487
|
expect(proxy.actionHistory).toHaveLength(0);
|
|
488
|
-
//
|
|
489
|
-
|
|
488
|
+
// screenshot + click, then done tells the client the task ended so the
|
|
489
|
+
// helper returns the pointer; done itself never reaches the client
|
|
490
|
+
expect(sentMessages).toHaveLength(3);
|
|
491
|
+
expect((sentMessages[2] as Record<string, unknown>).type).toBe(
|
|
492
|
+
"host_cu_cancel",
|
|
493
|
+
);
|
|
490
494
|
});
|
|
491
495
|
});
|
|
492
496
|
|
|
@@ -39,6 +39,20 @@ function writePackageJson(dir: string, pkg: Record<string, unknown>): void {
|
|
|
39
39
|
writeFileSync(join(dir, "package.json"), JSON.stringify(pkg, null, 2));
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
function writePluginJson(dir: string, manifest: Record<string, unknown>): void {
|
|
43
|
+
writeFileSync(
|
|
44
|
+
join(dir, "plugin.json"),
|
|
45
|
+
JSON.stringify(
|
|
46
|
+
{
|
|
47
|
+
$schema: "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
48
|
+
...manifest,
|
|
49
|
+
},
|
|
50
|
+
null,
|
|
51
|
+
2,
|
|
52
|
+
),
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
42
56
|
function writeSurfaceFile(dir: string, relPath: string, body: string): void {
|
|
43
57
|
const parts = relPath.split("/");
|
|
44
58
|
parts.pop();
|
|
@@ -104,6 +118,31 @@ describe("loadExternalPlugin — manifest", () => {
|
|
|
104
118
|
expect(registered).toBeDefined();
|
|
105
119
|
expect(registered?.manifest.version).toBe("0.0.0");
|
|
106
120
|
});
|
|
121
|
+
|
|
122
|
+
test("loads a standard plugin.json when package.json is absent", async () => {
|
|
123
|
+
const dir = freshPluginDir("standard-only");
|
|
124
|
+
writePluginJson(dir, { name: "standard-only", version: "2.1.0" });
|
|
125
|
+
|
|
126
|
+
await loadExternalPlugin(dir);
|
|
127
|
+
|
|
128
|
+
const registered = getRegisteredPlugins().find(
|
|
129
|
+
(plugin) => plugin.manifest.name === "standard-only",
|
|
130
|
+
);
|
|
131
|
+
expect(registered?.manifest.version).toBe("2.1.0");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("keeps package.json authoritative when plugin.json is also present", async () => {
|
|
135
|
+
const dir = freshPluginDir("mixed-manifests");
|
|
136
|
+
writePackageJson(dir, { name: "legacy-name", version: "1.2.3" });
|
|
137
|
+
writeFileSync(join(dir, "plugin.json"), "{ foreign and malformed }");
|
|
138
|
+
|
|
139
|
+
await loadExternalPlugin(dir);
|
|
140
|
+
|
|
141
|
+
const registered = getRegisteredPlugins().find(
|
|
142
|
+
(plugin) => plugin.manifest.name === "mixed-manifests",
|
|
143
|
+
);
|
|
144
|
+
expect(registered?.manifest.version).toBe("1.2.3");
|
|
145
|
+
});
|
|
107
146
|
});
|
|
108
147
|
|
|
109
148
|
describe("credentialKeyPatterns manifest field", () => {
|
|
@@ -277,6 +316,19 @@ describe("loadExternalPlugin — plugin-api peerDependency", () => {
|
|
|
277
316
|
test("malformed package.json is logged and skipped (registry untouched)", async () => {
|
|
278
317
|
const dir = freshPluginDir("malformed-pkg");
|
|
279
318
|
writeFileSync(join(dir, "package.json"), "{ this is not json");
|
|
319
|
+
writePluginJson(dir, { name: "valid-standard-fallback" });
|
|
320
|
+
|
|
321
|
+
await loadExternalPlugin(dir);
|
|
322
|
+
|
|
323
|
+
expect(registeredNames()).toHaveLength(0);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test("malformed selected plugin.json is logged and skipped", async () => {
|
|
327
|
+
const dir = freshPluginDir("malformed-standard");
|
|
328
|
+
writeFileSync(
|
|
329
|
+
join(dir, "plugin.json"),
|
|
330
|
+
JSON.stringify({ name: "missing-schema" }),
|
|
331
|
+
);
|
|
280
332
|
|
|
281
333
|
await loadExternalPlugin(dir);
|
|
282
334
|
|
|
@@ -41,6 +41,16 @@ function makeFakeCdp(
|
|
|
41
41
|
dispose() {
|
|
42
42
|
_cdpDisposed = true;
|
|
43
43
|
},
|
|
44
|
+
setCdpSessionId() {},
|
|
45
|
+
async listTabs() {
|
|
46
|
+
return [];
|
|
47
|
+
},
|
|
48
|
+
async selectTab(tabId: number) {
|
|
49
|
+
return { tabId };
|
|
50
|
+
},
|
|
51
|
+
async closeTab(tabId: number) {
|
|
52
|
+
return { tabId, closed: true };
|
|
53
|
+
},
|
|
44
54
|
};
|
|
45
55
|
}
|
|
46
56
|
|
|
@@ -142,6 +152,8 @@ mock.module("../tools/network/url-safety.js", () => ({
|
|
|
142
152
|
|
|
143
153
|
import {
|
|
144
154
|
executeBrowserClick,
|
|
155
|
+
executeBrowserClose,
|
|
156
|
+
executeBrowserDetach,
|
|
145
157
|
executeBrowserNavigate,
|
|
146
158
|
executeBrowserScreenshot,
|
|
147
159
|
executeBrowserSnapshot,
|
|
@@ -687,6 +699,42 @@ describe("browser_mode wiring through tool execution", () => {
|
|
|
687
699
|
|
|
688
700
|
// ── Per-conversation sticky backend kind ─────────────────────────
|
|
689
701
|
|
|
702
|
+
for (const release of [executeBrowserDetach, executeBrowserClose]) {
|
|
703
|
+
test.each(["local", "extension", "cdp-inspect"])(
|
|
704
|
+
`${release.name} on desktop preserves the normal %s session's backend`,
|
|
705
|
+
async (mode) => {
|
|
706
|
+
const first = await executeBrowserSnapshot({ browser_mode: mode }, ctx);
|
|
707
|
+
expect(first.isError).toBe(false);
|
|
708
|
+
|
|
709
|
+
factoryModeCalls.length = 0;
|
|
710
|
+
const desktop = await release(
|
|
711
|
+
{},
|
|
712
|
+
{
|
|
713
|
+
...ctx,
|
|
714
|
+
cdpClient: makeFakeCdp(
|
|
715
|
+
"cdp-inspect",
|
|
716
|
+
`desktop-browser:${ctx.conversationId}`,
|
|
717
|
+
),
|
|
718
|
+
},
|
|
719
|
+
);
|
|
720
|
+
expect(desktop.isError).toBe(false);
|
|
721
|
+
expect(factoryModeCalls).toEqual([]);
|
|
722
|
+
|
|
723
|
+
const resumed = await executeBrowserSnapshot({}, ctx);
|
|
724
|
+
expect(resumed.isError).toBe(false);
|
|
725
|
+
expect(factoryModeCalls).toEqual([mode]);
|
|
726
|
+
},
|
|
727
|
+
);
|
|
728
|
+
|
|
729
|
+
test(`${release.name} on the normal browser still resets automatic selection`, async () => {
|
|
730
|
+
await executeBrowserSnapshot({ browser_mode: "extension" }, ctx);
|
|
731
|
+
expect((await release({}, ctx)).isError).toBe(false);
|
|
732
|
+
factoryModeCalls.length = 0;
|
|
733
|
+
expect((await executeBrowserSnapshot({}, ctx)).isError).toBe(false);
|
|
734
|
+
expect(factoryModeCalls).toEqual(["auto"]);
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
|
|
690
738
|
test("auto-mode call after an explicit pin sticks to the pinned kind", async () => {
|
|
691
739
|
const first = await executeBrowserNavigate(
|
|
692
740
|
{ url: "https://example.com", browser_mode: "local" },
|