@runfusion/fusion 0.13.0 → 0.14.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/README.md +13 -0
- package/dist/bin.js +1332 -528
- package/dist/client/assets/AgentDetailView-B3KAsP2O.js +18 -0
- package/dist/client/assets/{AgentsView-Dvf_xUkx.js → AgentsView-DoXb_amw.js} +4 -4
- package/dist/client/assets/ChatView-BJ2c7wvd.js +1 -0
- package/dist/client/assets/{DevServerView-C2qTJch7.js → DevServerView-DbgM4tlT.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-DRfhg9zz.js → DirectoryPicker-DfmtfMiu.js} +1 -1
- package/dist/client/assets/{DocumentsView-j8ic1xUw.js → DocumentsView-_-Efkx_W.js} +1 -1
- package/dist/client/assets/{InsightsView-CpAz3o0i.js → InsightsView-DUjcfW53.js} +1 -1
- package/dist/client/assets/{MemoryView-BcQsi_JK.js → MemoryView-DxMPBb0q.js} +1 -1
- package/dist/client/assets/{NodesView-Bo_Yhr4N.js → NodesView-BEBTI15s.js} +1 -1
- package/dist/client/assets/PiExtensionsManager-BpMYhHH_.js +11 -0
- package/dist/client/assets/PluginManager-CPv7yQd3.js +1 -0
- package/dist/client/assets/PluginManager-DA_T0GHn.css +1 -0
- package/dist/client/assets/{ResearchView-CLyyqAWE.js → ResearchView-BrFvdyXT.js} +1 -1
- package/dist/client/assets/{RoadmapsView-tG7IdOoc.js → RoadmapsView-BDjLrtcj.js} +1 -1
- package/dist/client/assets/SettingsModal-Cd-QGB0C.js +31 -0
- package/dist/client/assets/{SettingsModal-CXUGeZ0_.js → SettingsModal-CxDxiTRy.js} +1 -1
- package/dist/client/assets/SettingsModal-D_AFkDJa.css +1 -0
- package/dist/client/assets/{SetupWizardModal-BMJL6eNR.js → SetupWizardModal-DFUA4X3z.js} +1 -1
- package/dist/client/assets/{SkillMultiselect-ILMft-Kz.js → SkillMultiselect-BUWe5ujb.js} +1 -1
- package/dist/client/assets/{SkillsView-x4_YwBz6.js → SkillsView-RAkqGX3y.js} +1 -1
- package/dist/client/assets/TodoView-Ceb0wrg1.js +6 -0
- package/dist/client/assets/TodoView-SeO9o7km.css +1 -0
- package/dist/client/assets/{folder-open-DDdJt8aE.js → folder-open-DcM-Vd6r.js} +1 -1
- package/dist/client/assets/index-C1prPuSl.css +1 -0
- package/dist/client/assets/index-DH3aprf6.js +661 -0
- package/dist/client/assets/{list-checks-DFxQ9biT.js → list-checks-ByGHVQpZ.js} +1 -1
- package/dist/client/assets/{star-BKs1bgJN.js → star-DlEYI8GL.js} +1 -1
- package/dist/client/assets/{upload-Bb5Pidne.js → upload-DKshabz-.js} +1 -1
- package/dist/client/assets/{users-BImNn91Q.js → users-X6tYPPBV.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/sw.js +6 -0
- package/dist/client/version.json +1 -1
- package/dist/droid-cli/index.ts +127 -0
- package/dist/droid-cli/package.json +37 -0
- package/dist/droid-cli/src/__tests__/control-handler.test.ts +164 -0
- package/dist/droid-cli/src/__tests__/event-bridge.test.ts +1318 -0
- package/dist/droid-cli/src/__tests__/mcp-config.test.ts +310 -0
- package/dist/droid-cli/src/__tests__/process-manager.test.ts +818 -0
- package/dist/droid-cli/src/__tests__/prompt-builder.test.ts +1206 -0
- package/dist/droid-cli/src/__tests__/provider.test.ts +1894 -0
- package/dist/droid-cli/src/__tests__/setup-test-isolation.test.ts +32 -0
- package/dist/droid-cli/src/__tests__/setup-test-isolation.ts +14 -0
- package/dist/droid-cli/src/__tests__/stream-parser.test.ts +188 -0
- package/dist/droid-cli/src/__tests__/thinking-config.test.ts +141 -0
- package/dist/droid-cli/src/__tests__/tool-mapping.test.ts +253 -0
- package/dist/droid-cli/src/control-handler.ts +82 -0
- package/dist/droid-cli/src/event-bridge.ts +397 -0
- package/dist/droid-cli/src/mcp-config.ts +144 -0
- package/dist/droid-cli/src/mcp-schema-server.cjs +49 -0
- package/dist/droid-cli/src/process-manager.ts +358 -0
- package/dist/droid-cli/src/prompt-builder.ts +629 -0
- package/dist/droid-cli/src/provider.ts +447 -0
- package/dist/droid-cli/src/stream-parser.ts +37 -0
- package/dist/droid-cli/src/thinking-config.ts +83 -0
- package/dist/droid-cli/src/tool-mapping.ts +147 -0
- package/dist/droid-cli/src/types.ts +87 -0
- package/dist/extension.js +555 -125
- package/dist/pi-claude-cli/package.json +1 -1
- package/package.json +2 -1
- package/dist/client/assets/AgentDetailView-B7j297GT.js +0 -18
- package/dist/client/assets/ChatView-BgUt38ty.js +0 -1
- package/dist/client/assets/PiExtensionsManager-DHt2zFg8.js +0 -11
- package/dist/client/assets/PluginManager-BQhBHWrB.js +0 -1
- package/dist/client/assets/PluginManager-jyNkJZSz.css +0 -1
- package/dist/client/assets/SettingsModal-9HS8MnmW.css +0 -1
- package/dist/client/assets/SettingsModal-UziTDnLh.js +0 -31
- package/dist/client/assets/TodoView-BBYcMbXE.js +0 -6
- package/dist/client/assets/TodoView-C1g65hJo.css +0 -1
- package/dist/client/assets/index-B15xwijw.css +0 -1
- package/dist/client/assets/index-DmSs2FGE.js +0 -661
package/dist/client/index.html
CHANGED
|
@@ -92,11 +92,11 @@
|
|
|
92
92
|
}
|
|
93
93
|
})();
|
|
94
94
|
</script>
|
|
95
|
-
<script type="module" crossorigin src="/assets/index-
|
|
95
|
+
<script type="module" crossorigin src="/assets/index-DH3aprf6.js"></script>
|
|
96
96
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-K0fH_qHe.js">
|
|
97
97
|
<link rel="modulepreload" crossorigin href="/assets/vendor-xterm-DzcZoU0P.js">
|
|
98
98
|
<link rel="stylesheet" crossorigin href="/assets/vendor-xterm-LZoznX6r.css">
|
|
99
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
99
|
+
<link rel="stylesheet" crossorigin href="/assets/index-C1prPuSl.css">
|
|
100
100
|
</head>
|
|
101
101
|
<body>
|
|
102
102
|
<div id="root"></div>
|
package/dist/client/sw.js
CHANGED
|
@@ -8,6 +8,12 @@ const APP_SHELL_URLS = [
|
|
|
8
8
|
"/icons/icon-512.png",
|
|
9
9
|
];
|
|
10
10
|
|
|
11
|
+
self.addEventListener("message", (event) => {
|
|
12
|
+
if (event.data && event.data.type === "SKIP_WAITING") {
|
|
13
|
+
self.skipWaiting();
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
11
17
|
self.addEventListener("install", (event) => {
|
|
12
18
|
event.waitUntil((async () => {
|
|
13
19
|
try {
|
package/dist/client/version.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"
|
|
1
|
+
{"version":"monkm5qv-26aba899"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { streamViaCli } from "./src/provider.js";
|
|
3
|
+
import {
|
|
4
|
+
validateCliPresenceAsync,
|
|
5
|
+
validateCliAuthAsync,
|
|
6
|
+
killAllProcesses,
|
|
7
|
+
discoverDroidModels,
|
|
8
|
+
} from "./src/process-manager.js";
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
import {
|
|
11
|
+
getCustomToolDefs,
|
|
12
|
+
toolsFromContext,
|
|
13
|
+
writeMcpConfig,
|
|
14
|
+
type McpToolDef,
|
|
15
|
+
} from "./src/mcp-config.js";
|
|
16
|
+
|
|
17
|
+
process.on("exit", killAllProcesses);
|
|
18
|
+
|
|
19
|
+
const PROVIDER_ID = "droid-cli";
|
|
20
|
+
|
|
21
|
+
let cliValidationPromise: Promise<void> | undefined;
|
|
22
|
+
type DiscoveredModel = { id: string; name: string; reasoning: boolean; input: Array<"text" | "image">; cost: { input: number; output: number; cacheRead: number; cacheWrite: number }; contextWindow: number; maxTokens: number };
|
|
23
|
+
let discoveredModelsPromise: Promise<DiscoveredModel[]> | undefined;
|
|
24
|
+
|
|
25
|
+
function runCliValidationOnce(): Promise<void> {
|
|
26
|
+
if (cliValidationPromise) return cliValidationPromise;
|
|
27
|
+
cliValidationPromise = (async () => {
|
|
28
|
+
const presence = await validateCliPresenceAsync();
|
|
29
|
+
if (!presence.ok) {
|
|
30
|
+
console.warn(`[droid-cli] ${presence.error.message}`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
await validateCliAuthAsync();
|
|
34
|
+
})();
|
|
35
|
+
return cliValidationPromise;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function getDiscoveredModels() {
|
|
39
|
+
if (!discoveredModelsPromise) {
|
|
40
|
+
discoveredModelsPromise = (async () => {
|
|
41
|
+
try {
|
|
42
|
+
const ids = Array.from(new Set(await discoverDroidModels()));
|
|
43
|
+
if (ids.length === 0) return [];
|
|
44
|
+
return ids.map((id) => ({
|
|
45
|
+
id,
|
|
46
|
+
name: id,
|
|
47
|
+
reasoning: true,
|
|
48
|
+
input: ["text", "image"] as Array<"text" | "image">,
|
|
49
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
50
|
+
contextWindow: 200_000,
|
|
51
|
+
maxTokens: 8_192,
|
|
52
|
+
}));
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.warn("[droid-cli] model auto-discovery failed; registering provider with empty model list", error);
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
})();
|
|
58
|
+
}
|
|
59
|
+
return discoveredModelsPromise;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let cachedMcpConfig: { hash: string; configPath: string } | undefined;
|
|
63
|
+
|
|
64
|
+
function ensureMcpConfig(
|
|
65
|
+
pi: ExtensionAPI,
|
|
66
|
+
contextTools?: ReadonlyArray<{
|
|
67
|
+
name: string;
|
|
68
|
+
description: string;
|
|
69
|
+
parameters: Record<string, unknown>;
|
|
70
|
+
}>,
|
|
71
|
+
): string | undefined {
|
|
72
|
+
try {
|
|
73
|
+
let toolDefs: McpToolDef[] = toolsFromContext(contextTools);
|
|
74
|
+
|
|
75
|
+
if (toolDefs.length === 0) {
|
|
76
|
+
const allTools = pi.getAllTools();
|
|
77
|
+
if (!Array.isArray(allTools)) return cachedMcpConfig?.configPath;
|
|
78
|
+
toolDefs = getCustomToolDefs(pi);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (toolDefs.length === 0) {
|
|
82
|
+
cachedMcpConfig = undefined;
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const hash = createHash("sha1").update(JSON.stringify(toolDefs)).digest("hex").slice(0, 12);
|
|
87
|
+
if (cachedMcpConfig?.hash === hash) return cachedMcpConfig.configPath;
|
|
88
|
+
|
|
89
|
+
const configPath = writeMcpConfig(toolDefs, hash);
|
|
90
|
+
cachedMcpConfig = { hash, configPath };
|
|
91
|
+
return configPath;
|
|
92
|
+
} catch {
|
|
93
|
+
return cachedMcpConfig?.configPath;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export default function (pi: ExtensionAPI) {
|
|
98
|
+
void runCliValidationOnce();
|
|
99
|
+
|
|
100
|
+
pi.on("session_start", async () => {
|
|
101
|
+
const allTools = pi.getAllTools();
|
|
102
|
+
if (Array.isArray(allTools)) {
|
|
103
|
+
pi.setActiveTools(allTools.map((t: { name: string }) => t.name));
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
void (async () => {
|
|
108
|
+
const models = await getDiscoveredModels();
|
|
109
|
+
try {
|
|
110
|
+
pi.registerProvider(PROVIDER_ID, {
|
|
111
|
+
baseUrl: "droid-cli",
|
|
112
|
+
apiKey: "unused",
|
|
113
|
+
api: "droid-cli",
|
|
114
|
+
models,
|
|
115
|
+
streamSimple: (model, context, options) => {
|
|
116
|
+
const configPath = ensureMcpConfig(
|
|
117
|
+
pi,
|
|
118
|
+
(context as { tools?: ReadonlyArray<{ name: string; description: string; parameters: Record<string, unknown> }> }).tools,
|
|
119
|
+
);
|
|
120
|
+
return streamViaCli(model, context, { ...options, mcpConfigPath: configPath });
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
} catch (err) {
|
|
124
|
+
console.error("[droid-cli] Failed to register provider:", err);
|
|
125
|
+
}
|
|
126
|
+
})();
|
|
127
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fusion/droid-cli",
|
|
3
|
+
"version": "0.11.0",
|
|
4
|
+
"description": "First-party Fusion pi extension that routes LLM calls through the Droid CLI subprocess.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"private": true,
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "index.ts",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"pi-package",
|
|
11
|
+
"fusion",
|
|
12
|
+
"droid"
|
|
13
|
+
],
|
|
14
|
+
"pi": {
|
|
15
|
+
"extensions": [
|
|
16
|
+
"index.ts"
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/Runfusion/Fusion",
|
|
22
|
+
"directory": "packages/droid-cli"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@mariozechner/pi-ai": "*",
|
|
26
|
+
"@mariozechner/pi-coding-agent": "*"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^22.0.0",
|
|
30
|
+
"typescript": "^5.7.0",
|
|
31
|
+
"vitest": "^3.0.0"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"test": "vitest run --reporter=dot",
|
|
35
|
+
"typecheck": "tsc --noEmit"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import type { ClaudeControlRequest } from "../types";
|
|
3
|
+
import {
|
|
4
|
+
handleControlRequest,
|
|
5
|
+
TOOL_EXECUTION_DENIED_MESSAGE,
|
|
6
|
+
MCP_PREFIX,
|
|
7
|
+
} from "../control-handler";
|
|
8
|
+
|
|
9
|
+
function makeControlRequest(
|
|
10
|
+
toolName: string,
|
|
11
|
+
requestId = "req-test-001",
|
|
12
|
+
input: Record<string, unknown> = {},
|
|
13
|
+
): ClaudeControlRequest {
|
|
14
|
+
return {
|
|
15
|
+
type: "control_request",
|
|
16
|
+
request_id: requestId,
|
|
17
|
+
request: {
|
|
18
|
+
subtype: "can_use_tool",
|
|
19
|
+
tool_name: toolName,
|
|
20
|
+
input,
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("control-handler", () => {
|
|
26
|
+
describe("exported constants", () => {
|
|
27
|
+
it("exports TOOL_EXECUTION_DENIED_MESSAGE", () => {
|
|
28
|
+
expect(TOOL_EXECUTION_DENIED_MESSAGE).toBe(
|
|
29
|
+
"Tool execution is unavailable in this environment.",
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("exports MCP_PREFIX", () => {
|
|
34
|
+
expect(MCP_PREFIX).toBe("mcp__");
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("denies custom MCP tools (mcp__custom-tools__*)", () => {
|
|
39
|
+
it("denies mcp__custom-tools__weather", () => {
|
|
40
|
+
const msg = makeControlRequest("mcp__custom-tools__weather");
|
|
41
|
+
|
|
42
|
+
const result = handleControlRequest(msg);
|
|
43
|
+
|
|
44
|
+
expect(result.allowed).toBe(false);
|
|
45
|
+
expect(result.response.response.response.behavior).toBe("deny");
|
|
46
|
+
expect(result.response.response.response.message).toBe(
|
|
47
|
+
TOOL_EXECUTION_DENIED_MESSAGE,
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("denies mcp__custom-tools__deploy", () => {
|
|
52
|
+
const msg = makeControlRequest("mcp__custom-tools__deploy");
|
|
53
|
+
|
|
54
|
+
const result = handleControlRequest(msg);
|
|
55
|
+
|
|
56
|
+
expect(result.allowed).toBe(false);
|
|
57
|
+
expect(result.response.response.response.behavior).toBe("deny");
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("allows user MCP tools and other tools", () => {
|
|
62
|
+
it("allows user MCP tool mcp__database__query", () => {
|
|
63
|
+
const msg = makeControlRequest("mcp__database__query");
|
|
64
|
+
|
|
65
|
+
const result = handleControlRequest(msg);
|
|
66
|
+
|
|
67
|
+
expect(result.allowed).toBe(true);
|
|
68
|
+
expect(result.response.response.response.behavior).toBe("allow");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("allows built-in tool Read", () => {
|
|
72
|
+
const msg = makeControlRequest("Read");
|
|
73
|
+
|
|
74
|
+
const result = handleControlRequest(msg);
|
|
75
|
+
|
|
76
|
+
expect(result.allowed).toBe(true);
|
|
77
|
+
expect(result.response.response.response.behavior).toBe("allow");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("allows internal tools like ToolSearch", () => {
|
|
81
|
+
const msg = makeControlRequest("ToolSearch");
|
|
82
|
+
|
|
83
|
+
const result = handleControlRequest(msg);
|
|
84
|
+
|
|
85
|
+
expect(result.allowed).toBe(true);
|
|
86
|
+
expect(result.response.response.response.behavior).toBe("allow");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("allows unknown tools", () => {
|
|
90
|
+
const msg = makeControlRequest("SomeUnknownTool");
|
|
91
|
+
|
|
92
|
+
const result = handleControlRequest(msg);
|
|
93
|
+
|
|
94
|
+
expect(result.allowed).toBe(true);
|
|
95
|
+
expect(result.response.response.response.behavior).toBe("allow");
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("response format", () => {
|
|
100
|
+
it("includes matching request_id", () => {
|
|
101
|
+
const msg = makeControlRequest("Read", "custom-req-id-42");
|
|
102
|
+
|
|
103
|
+
const result = handleControlRequest(msg);
|
|
104
|
+
|
|
105
|
+
expect(result.response.request_id).toBe("custom-req-id-42");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("returns a JSON-serializable response object", () => {
|
|
109
|
+
const msg = makeControlRequest("Read");
|
|
110
|
+
|
|
111
|
+
const result = handleControlRequest(msg);
|
|
112
|
+
const serialized = JSON.stringify(result.response);
|
|
113
|
+
|
|
114
|
+
expect(() => JSON.parse(serialized)).not.toThrow();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("deny response includes message field", () => {
|
|
118
|
+
const msg = makeControlRequest("mcp__custom-tools__foo");
|
|
119
|
+
|
|
120
|
+
const result = handleControlRequest(msg);
|
|
121
|
+
|
|
122
|
+
expect(result.response.response.response.message).toBe(
|
|
123
|
+
TOOL_EXECUTION_DENIED_MESSAGE,
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("allow response does not include a message field", () => {
|
|
128
|
+
const msg = makeControlRequest("mcp__database__query");
|
|
129
|
+
|
|
130
|
+
const result = handleControlRequest(msg);
|
|
131
|
+
|
|
132
|
+
expect(result.response.response.response.message).toBeUndefined();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe("malformed input", () => {
|
|
137
|
+
it("returns denied decision object for missing request_id", () => {
|
|
138
|
+
const spy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
139
|
+
|
|
140
|
+
const msg = {
|
|
141
|
+
type: "control_request",
|
|
142
|
+
} as unknown as ClaudeControlRequest;
|
|
143
|
+
const result = handleControlRequest(msg);
|
|
144
|
+
|
|
145
|
+
expect(result.allowed).toBe(false);
|
|
146
|
+
expect(result.response.response.response.behavior).toBe("deny");
|
|
147
|
+
spy.mockRestore();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("returns denied decision object for missing request object", () => {
|
|
151
|
+
const spy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
152
|
+
|
|
153
|
+
const msg = {
|
|
154
|
+
type: "control_request",
|
|
155
|
+
request_id: "req-001",
|
|
156
|
+
} as unknown as ClaudeControlRequest;
|
|
157
|
+
const result = handleControlRequest(msg);
|
|
158
|
+
|
|
159
|
+
expect(result.allowed).toBe(false);
|
|
160
|
+
expect(result.response.response.response.behavior).toBe("deny");
|
|
161
|
+
spy.mockRestore();
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|