@tyvm/knowhow 0.0.90 → 0.0.91
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/.depcheckrc +31 -0
- package/bin/knowhow.js +1 -1
- package/package.json +4 -32
- package/src/agents/tools/executeScript/index.ts +5 -0
- package/src/agents/tools/googleSearch.ts +2 -2
- package/src/agents/tools/index.ts +0 -3
- package/src/agents/tools/list.ts +0 -147
- package/src/agents/tools/loadWebpage.ts +3 -113
- package/src/auth/browserLogin.ts +10 -13
- package/src/cli.ts +63 -3
- package/src/clients/gemini.ts +96 -25
- package/src/clients/http.ts +7 -11
- package/src/clients/pricing/google.ts +122 -26
- package/src/conversion.ts +24 -54
- package/src/index.ts +8 -1
- package/src/login.ts +5 -6
- package/src/plugins/language.ts +0 -4
- package/src/plugins/plugins.ts +0 -14
- package/src/plugins/url.ts +31 -12
- package/src/services/GitHub.ts +2 -2
- package/src/services/KnowhowClient.ts +34 -34
- package/src/{plugins/downloader/downloader.ts → services/MediaProcessorService.ts} +109 -267
- package/src/services/S3.ts +16 -16
- package/src/services/index.ts +4 -4
- package/src/services/modules/index.ts +10 -2
- package/src/services/modules/types.ts +5 -2
- package/src/services/script-execution/ScriptExecutor.ts +29 -10
- package/src/services/script-execution/ScriptPolicy.ts +6 -2
- package/src/types.ts +1 -0
- package/src/utils/http.ts +127 -0
- package/src/workers/auth/PasskeySetup.ts +7 -11
- package/tests/clients/AIClient.test.ts +24 -21
- package/tests/manual/file-edits/figma.test.ts +3 -70
- package/tests/plugins/language/languagePlugin-content-triggers.test.ts +2 -0
- package/tests/plugins/language/languagePlugin.test.ts +2 -0
- package/tests/processors/ToolResponseCache.test.ts +2 -2
- package/tests/test.spec.ts +0 -14
- package/tests/unit/modules/moduleLoading.test.ts +7 -4
- package/tests/unit/plugins/pluginLoading.test.ts +6 -6
- package/ts_build/package.json +4 -32
- package/ts_build/src/agents/tools/ast/astAppendNode.d.ts +1 -1
- package/ts_build/src/agents/tools/ast/astAppendNode.js +2 -90
- package/ts_build/src/agents/tools/ast/astAppendNode.js.map +1 -1
- package/ts_build/src/agents/tools/ast/astDeleteNode.d.ts +1 -1
- package/ts_build/src/agents/tools/ast/astDeleteNode.js +2 -88
- package/ts_build/src/agents/tools/ast/astDeleteNode.js.map +1 -1
- package/ts_build/src/agents/tools/ast/astEditNode.d.ts +1 -1
- package/ts_build/src/agents/tools/ast/astEditNode.js +2 -90
- package/ts_build/src/agents/tools/ast/astEditNode.js.map +1 -1
- package/ts_build/src/agents/tools/ast/astGetPathForLine.d.ts +1 -1
- package/ts_build/src/agents/tools/ast/astGetPathForLine.js +2 -72
- package/ts_build/src/agents/tools/ast/astGetPathForLine.js.map +1 -1
- package/ts_build/src/agents/tools/ast/astListPaths.d.ts +1 -1
- package/ts_build/src/agents/tools/ast/astListPaths.js +2 -72
- package/ts_build/src/agents/tools/ast/astListPaths.js.map +1 -1
- package/ts_build/src/agents/tools/executeScript/index.d.ts +3 -2
- package/ts_build/src/agents/tools/executeScript/index.js +4 -1
- package/ts_build/src/agents/tools/executeScript/index.js.map +1 -1
- package/ts_build/src/agents/tools/googleSearch.js +2 -2
- package/ts_build/src/agents/tools/googleSearch.js.map +1 -1
- package/ts_build/src/agents/tools/index.d.ts +0 -3
- package/ts_build/src/agents/tools/index.js +0 -3
- package/ts_build/src/agents/tools/index.js.map +1 -1
- package/ts_build/src/agents/tools/list.js +0 -138
- package/ts_build/src/agents/tools/list.js.map +1 -1
- package/ts_build/src/agents/tools/loadWebpage.js +1 -89
- package/ts_build/src/agents/tools/loadWebpage.js.map +1 -1
- package/ts_build/src/agents/tools/textSearch.d.ts +1 -1
- package/ts_build/src/auth/browserLogin.js +7 -7
- package/ts_build/src/auth/browserLogin.js.map +1 -1
- package/ts_build/src/cli.d.ts +1 -1
- package/ts_build/src/cli.js +47 -1
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/gemini.d.ts +1 -73
- package/ts_build/src/clients/gemini.js +57 -19
- package/ts_build/src/clients/gemini.js.map +1 -1
- package/ts_build/src/clients/http.js +5 -9
- package/ts_build/src/clients/http.js.map +1 -1
- package/ts_build/src/clients/pricing/google.d.ts +17 -73
- package/ts_build/src/clients/pricing/google.js +47 -10
- package/ts_build/src/clients/pricing/google.js.map +1 -1
- package/ts_build/src/conversion.d.ts +1 -4
- package/ts_build/src/conversion.js +12 -27
- package/ts_build/src/conversion.js.map +1 -1
- package/ts_build/src/index.d.ts +4 -0
- package/ts_build/src/index.js +7 -1
- package/ts_build/src/index.js.map +1 -1
- package/ts_build/src/login.js +5 -4
- package/ts_build/src/login.js.map +1 -1
- package/ts_build/src/plugins/downloader/downloader.js +3 -3
- package/ts_build/src/plugins/downloader/downloader.js.map +1 -1
- package/ts_build/src/plugins/language.js.map +1 -1
- package/ts_build/src/plugins/plugins.js +0 -14
- package/ts_build/src/plugins/plugins.js.map +1 -1
- package/ts_build/src/plugins/tree-sitter/editor.d.ts +3 -32
- package/ts_build/src/plugins/tree-sitter/editor.js +6 -208
- package/ts_build/src/plugins/tree-sitter/editor.js.map +1 -1
- package/ts_build/src/plugins/tree-sitter/parser.d.ts +19 -54
- package/ts_build/src/plugins/tree-sitter/parser.js +19 -293
- package/ts_build/src/plugins/tree-sitter/parser.js.map +1 -1
- package/ts_build/src/plugins/tree-sitter/simple-paths.d.ts +2 -15
- package/ts_build/src/plugins/tree-sitter/simple-paths.js +2 -324
- package/ts_build/src/plugins/tree-sitter/simple-paths.js.map +1 -1
- package/ts_build/src/plugins/url.js +27 -8
- package/ts_build/src/plugins/url.js.map +1 -1
- package/ts_build/src/services/GitHub.js +2 -2
- package/ts_build/src/services/GitHub.js.map +1 -1
- package/ts_build/src/services/KnowhowClient.d.ts +29 -29
- package/ts_build/src/services/KnowhowClient.js +33 -33
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/MediaProcessorService.d.ts +22 -0
- package/ts_build/src/services/MediaProcessorService.js +215 -0
- package/ts_build/src/services/MediaProcessorService.js.map +1 -0
- package/ts_build/src/services/S3.js +12 -18
- package/ts_build/src/services/S3.js.map +1 -1
- package/ts_build/src/services/index.d.ts +3 -2
- package/ts_build/src/services/index.js +3 -3
- package/ts_build/src/services/index.js.map +1 -1
- package/ts_build/src/services/modules/index.js +10 -2
- package/ts_build/src/services/modules/index.js.map +1 -1
- package/ts_build/src/services/modules/types.d.ts +5 -2
- package/ts_build/src/services/script-execution/ScriptExecutor.js +22 -7
- package/ts_build/src/services/script-execution/ScriptExecutor.js.map +1 -1
- package/ts_build/src/services/script-execution/ScriptPolicy.d.ts +1 -1
- package/ts_build/src/services/script-execution/ScriptPolicy.js +4 -2
- package/ts_build/src/services/script-execution/ScriptPolicy.js.map +1 -1
- package/ts_build/src/types.d.ts +1 -0
- package/ts_build/src/types.js +1 -0
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/src/utils/http.d.ts +27 -0
- package/ts_build/src/utils/http.js +98 -0
- package/ts_build/src/utils/http.js.map +1 -0
- package/ts_build/src/workers/auth/PasskeySetup.js +6 -7
- package/ts_build/src/workers/auth/PasskeySetup.js.map +1 -1
- package/ts_build/tests/clients/AIClient.test.js +11 -14
- package/ts_build/tests/clients/AIClient.test.js.map +1 -1
- package/ts_build/tests/manual/file-edits/figma.test.d.ts +0 -1
- package/ts_build/tests/manual/file-edits/figma.test.js +1 -46
- package/ts_build/tests/manual/file-edits/figma.test.js.map +1 -1
- package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js +2 -0
- package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js.map +1 -1
- package/ts_build/tests/plugins/language/languagePlugin.test.js +2 -0
- package/ts_build/tests/plugins/language/languagePlugin.test.js.map +1 -1
- package/ts_build/tests/processors/ToolResponseCache.test.js +2 -2
- package/ts_build/tests/processors/ToolResponseCache.test.js.map +1 -1
- package/ts_build/tests/test.spec.js +0 -14
- package/ts_build/tests/test.spec.js.map +1 -1
- package/ts_build/tests/tree-sitter/tree-sitter.test.d.ts +0 -1
- package/ts_build/tests/tree-sitter/tree-sitter.test.js +2 -183
- package/ts_build/tests/tree-sitter/tree-sitter.test.js.map +1 -1
- package/ts_build/tests/unit/modules/moduleLoading.test.js +6 -4
- package/ts_build/tests/unit/modules/moduleLoading.test.js.map +1 -1
- package/ts_build/tests/unit/plugins/pluginLoading.test.js +4 -4
- package/ts_build/tests/unit/plugins/pluginLoading.test.js.map +1 -1
- package/benchmarks/.dockerignore +0 -7
- package/benchmarks/README.md +0 -166
- package/benchmarks/docker/Dockerfile +0 -68
- package/benchmarks/example-config.yml +0 -27
- package/benchmarks/jest.config.js +0 -13
- package/benchmarks/package-lock.json +0 -4297
- package/benchmarks/package.json +0 -39
- package/benchmarks/results/27b0a06/2025-09-27/xai/xai-grok-code-fast-1.json +0 -2909
- package/benchmarks/results/4057aed/2025-08-14/anthropic/anthropic-claude-sonnet-4-20250514.json +0 -1671
- package/benchmarks/results/4542435/2025-08-05/lms/lms-openai-gpt-oss-20b.json +0 -2814
- package/benchmarks/results/4542435/2025-08-05/lms/lms-qwen-qwen3-30b-a3b-2507.json +0 -2014
- package/benchmarks/results/4fb9125/2025-08-07/anthropic/anthropic-claude-sonnet-4-20250514.json +0 -3121
- package/benchmarks/results/5766aee/2025-08-02/lms-qwen/qwen3-coder-30b.json +0 -98
- package/benchmarks/results/6d73808/2025-08-07/openai/openai-gpt-5.json +0 -3256
- package/benchmarks/results/77bf0a6/2025-08-02/lms-qwen/qwen3-30b-a3b-2507.json +0 -4298
- package/benchmarks/results/8c0d445/2025-08-03/anthropic/anthropic-claude-sonnet-4-20250514.json +0 -3031
- package/benchmarks/results/8c0d445/2025-08-03/openai/openai-gpt-4.1-2025-04-14.json +0 -2990
- package/benchmarks/results/ac6b2ab/2025-08-03/anthropic/anthropic-claude-sonnet-4-20250514.json +0 -3256
- package/benchmarks/results/ac6b2ab/2025-08-03/lms/lms-qwen-qwen3-coder-30b.json +0 -3007
- package/benchmarks/results/ac6b2ab/2025-08-03/openai/openai-gpt-4.1-2025-04-14.json +0 -3256
- package/benchmarks/results/ac6b2ab/2025-08-03/openai/openai-gpt-4.1-mini-2025-04-14.json +0 -3036
- package/benchmarks/results/ac6b2ab/2025-08-03/openai/openai-gpt-4.1-nano-2025-04-14.json +0 -3280
- package/benchmarks/results/adff675/2025-08-04/lms/lms-qwen-qwen3-30b-a3b-2507.json +0 -1920
- package/benchmarks/results/adff675/2025-08-04/lms/lms-qwen-qwen3-coder-30b.json +0 -3281
- package/benchmarks/results/b502ed9/2025-08-03/lms-qwen/qwen3-coder-30b.json +0 -2896
- package/benchmarks/results/d1a8129/2025-08-03/lms/lms-qwen-qwen3-coder-30b.json +0 -3011
- package/benchmarks/results/e60471c/2025-08-03/lms/qwen3-30b-a3b-2507.json +0 -3003
- package/benchmarks/scripts/build-and-run.sh +0 -47
- package/benchmarks/scripts/clone-exercism.sh +0 -92
- package/benchmarks/scripts/validate.sh +0 -48
- package/benchmarks/src/__tests__/runner.test.ts +0 -27
- package/benchmarks/src/cli.ts +0 -90
- package/benchmarks/src/evaluators/EvaluatorRegistry.ts +0 -64
- package/benchmarks/src/evaluators/JavaScriptEvaluator.ts +0 -183
- package/benchmarks/src/evaluators/index.ts +0 -3
- package/benchmarks/src/evaluators/types.ts +0 -22
- package/benchmarks/src/index.ts +0 -3
- package/benchmarks/src/providers.ts +0 -13
- package/benchmarks/src/runner.ts +0 -824
- package/benchmarks/src/types.ts +0 -63
- package/benchmarks/tsconfig.json +0 -19
- package/leaderboard/README.md +0 -148
- package/leaderboard/app/api/benchmark-data/route.ts +0 -131
- package/leaderboard/app/api/benchmark-detail/route.ts +0 -172
- package/leaderboard/app/details/[model]/[provider]/[language]/page.tsx +0 -501
- package/leaderboard/app/exercise/[model]/[provider]/[language]/[exercise]/page.tsx +0 -375
- package/leaderboard/app/globals.css +0 -27
- package/leaderboard/app/layout.tsx +0 -21
- package/leaderboard/app/page.tsx +0 -170
- package/leaderboard/components/LeaderboardTable.tsx +0 -168
- package/leaderboard/components/PerformanceChart.tsx +0 -109
- package/leaderboard/next-env.d.ts +0 -5
- package/leaderboard/next.config.js +0 -4
- package/leaderboard/package-lock.json +0 -6363
- package/leaderboard/package.json +0 -28
- package/leaderboard/postcss.config.js +0 -6
- package/leaderboard/tailwind.config.js +0 -17
- package/leaderboard/tsconfig.json +0 -28
- package/leaderboard/types/benchmark.ts +0 -67
- package/leaderboard/utils/dataProcessor.ts +0 -33
- package/src/agents/tools/asana/definitions.ts +0 -199
- package/src/agents/tools/asana/index.ts +0 -108
- package/src/agents/tools/ast/astAppendNode.ts +0 -90
- package/src/agents/tools/ast/astDeleteNode.ts +0 -88
- package/src/agents/tools/ast/astEditNode.ts +0 -95
- package/src/agents/tools/ast/astGetPathForLine.ts +0 -73
- package/src/agents/tools/ast/astListPaths.ts +0 -66
- package/src/agents/tools/ast/index.ts +0 -7
- package/src/agents/tools/github/definitions.ts +0 -89
- package/src/agents/tools/github/index.ts +0 -67
- package/src/chat-old.ts +0 -446
- package/src/plugins/asana.ts +0 -146
- package/src/plugins/downloader/plugin.ts +0 -103
- package/src/plugins/downloader/types.ts +0 -92
- package/src/plugins/figma.ts +0 -158
- package/src/plugins/github.ts +0 -219
- package/src/plugins/jira.ts +0 -115
- package/src/plugins/linear.ts +0 -230
- package/src/plugins/notion.ts +0 -179
- package/src/plugins/tree-sitter/editor.ts +0 -369
- package/src/plugins/tree-sitter/lang-packs/index.ts +0 -23
- package/src/plugins/tree-sitter/lang-packs/java.ts +0 -59
- package/src/plugins/tree-sitter/lang-packs/javascript.ts +0 -57
- package/src/plugins/tree-sitter/lang-packs/python.ts +0 -45
- package/src/plugins/tree-sitter/lang-packs/types.ts +0 -79
- package/src/plugins/tree-sitter/lang-packs/typescript.ts +0 -49
- package/src/plugins/tree-sitter/parser.ts +0 -470
- package/src/plugins/tree-sitter/simple-paths.ts +0 -467
- package/tests/tree-sitter/editor.test.ts +0 -113
- package/tests/tree-sitter/invalid.test.ts +0 -299
- package/tests/tree-sitter/paths/common-edits.test.ts +0 -564
- package/tests/tree-sitter/paths/debug-exact-position.test.ts +0 -44
- package/tests/tree-sitter/paths/debug-line-indexing.test.ts +0 -49
- package/tests/tree-sitter/paths/debug-paths.test.ts +0 -90
- package/tests/tree-sitter/paths/paths.test.ts +0 -170
- package/tests/tree-sitter/paths/simple-paths.test.ts +0 -367
- package/tests/tree-sitter/sample-after.ts +0 -48
- package/tests/tree-sitter/sample-before.ts +0 -25
- package/tests/tree-sitter/test-files/completely-broken.ts +0 -7
- package/tests/tree-sitter/test-files/duplicate-braces.ts +0 -39
- package/tests/tree-sitter/test-files/invalid-nesting.ts +0 -39
- package/tests/tree-sitter/test-files/malformed-signature.ts +0 -39
- package/tests/tree-sitter/test-files/mismatched-parens.ts +0 -39
- package/tests/tree-sitter/test-files/missing-semicolon.ts +0 -39
- package/tests/tree-sitter/test-files/partially-broken.ts +0 -20
- package/tests/tree-sitter/test-files/specific-errors.ts +0 -14
- package/tests/tree-sitter/test-files/unclosed-string.ts +0 -39
- package/tests/tree-sitter/tree-sitter.test.ts +0 -251
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Plugin } from "../../plugins/types";
|
|
1
|
+
import { Plugin, PluginContext } from "../../plugins/types";
|
|
2
2
|
import { IAgent } from "../../agents/interface";
|
|
3
3
|
import { Tool } from "../../clients/types";
|
|
4
4
|
import { Config } from "../../types";
|
|
@@ -7,6 +7,7 @@ import { AgentService } from "../AgentService";
|
|
|
7
7
|
import { PluginService } from "../../plugins/plugins";
|
|
8
8
|
import { AIClient } from "../../clients";
|
|
9
9
|
import { ToolsService } from "../Tools";
|
|
10
|
+
import { MediaProcessorService } from "../MediaProcessorService";
|
|
10
11
|
|
|
11
12
|
/*
|
|
12
13
|
*
|
|
@@ -29,7 +30,8 @@ export interface ModuleTool {
|
|
|
29
30
|
|
|
30
31
|
export type ModuleAgent = IAgent;
|
|
31
32
|
|
|
32
|
-
export type
|
|
33
|
+
export type PluginConstructor = new (context: PluginContext) => Plugin;
|
|
34
|
+
export type ModulePlugin = { name: string; plugin: PluginConstructor };
|
|
33
35
|
|
|
34
36
|
export type ModuleClient = {
|
|
35
37
|
client: GenericClient;
|
|
@@ -47,6 +49,7 @@ export interface ModuleContext {
|
|
|
47
49
|
Plugins: PluginService;
|
|
48
50
|
Clients: AIClient;
|
|
49
51
|
Tools: ToolsService;
|
|
52
|
+
MediaProcessor?: MediaProcessorService;
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
export interface KnowhowModule {
|
|
@@ -96,7 +96,7 @@ export class ScriptExecutor {
|
|
|
96
96
|
|
|
97
97
|
try {
|
|
98
98
|
// Validate script
|
|
99
|
-
const validation = policyEnforcer.validateScript(request.script);
|
|
99
|
+
const validation = policyEnforcer.validateScript(request.script, policy.allowNetworkAccess);
|
|
100
100
|
if (!validation.valid) {
|
|
101
101
|
tracer.emitEvent("script_validation_failed", {
|
|
102
102
|
issues: validation.issues,
|
|
@@ -240,8 +240,9 @@ export class ScriptExecutor {
|
|
|
240
240
|
tracer.emitEvent("script_execution_start", {});
|
|
241
241
|
|
|
242
242
|
// Execute the script and get the result
|
|
243
|
+
// Note: do NOT set timeout here — it kills the isolate while awaiting host async promises.
|
|
244
|
+
// The outer executeWithTimeout wrapper handles wall-clock timeout instead.
|
|
243
245
|
const result = await compiledScript.run(vmContext, {
|
|
244
|
-
timeout: policyEnforcer.getQuotas().maxExecutionTimeMs,
|
|
245
246
|
promise: true,
|
|
246
247
|
copy: true,
|
|
247
248
|
});
|
|
@@ -280,13 +281,29 @@ export class ScriptExecutor {
|
|
|
280
281
|
`__host_${name}`,
|
|
281
282
|
new ivm.Reference(async (...args: any[]) => {
|
|
282
283
|
const result = await fn(...args);
|
|
283
|
-
|
|
284
|
+
const safeResult = result !== undefined ? result : null;
|
|
285
|
+
const plainResult =
|
|
286
|
+
safeResult !== null && typeof safeResult === 'object'
|
|
287
|
+
? JSON.parse(JSON.stringify(safeResult))
|
|
288
|
+
: safeResult;
|
|
289
|
+
// copyInto() transfers the value into the isolate heap so it's directly usable
|
|
290
|
+
return new ivm.ExternalCopy(plainResult).copyInto();
|
|
284
291
|
})
|
|
285
292
|
);
|
|
293
|
+
// Use applySyncPromise so the script isolate suspends and yields the Node.js event loop
|
|
294
|
+
// back to the host while waiting for async host operations (MCP stdio calls etc.).
|
|
295
|
+
// Without this, the ivm isolate blocks the event loop and stdio-based MCP transports
|
|
296
|
+
// can never deliver their responses → deadlock.
|
|
286
297
|
await vmContext.eval(`
|
|
287
298
|
globalThis.${name} = (...a) =>
|
|
288
|
-
|
|
289
|
-
|
|
299
|
+
new Promise((resolve, reject) => {
|
|
300
|
+
try {
|
|
301
|
+
// applySyncPromise does not support result options — the Reference fn returns ExternalCopy
|
|
302
|
+
const result = __host_${name}.applySyncPromise(undefined, a,
|
|
303
|
+
{ arguments: { copy: true } });
|
|
304
|
+
resolve(result);
|
|
305
|
+
} catch(e) { reject(e); }
|
|
306
|
+
});
|
|
290
307
|
`);
|
|
291
308
|
};
|
|
292
309
|
|
|
@@ -308,11 +325,13 @@ export class ScriptExecutor {
|
|
|
308
325
|
|
|
309
326
|
// Expose async sandbox functions
|
|
310
327
|
await exposeAsync("callTool", async (tool, params) => {
|
|
311
|
-
|
|
312
|
-
tool as string,
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
328
|
+
try {
|
|
329
|
+
const result = await sandboxContext.callTool(tool as string, params);
|
|
330
|
+
const { functionResp } = result;
|
|
331
|
+
return functionResp !== undefined ? functionResp : null;
|
|
332
|
+
} catch (err) {
|
|
333
|
+
throw err;
|
|
334
|
+
}
|
|
316
335
|
});
|
|
317
336
|
await exposeAsync("llm", (messages, options) =>
|
|
318
337
|
sandboxContext.llm(messages, options || {})
|
|
@@ -163,7 +163,7 @@ export class ScriptPolicyEnforcer {
|
|
|
163
163
|
/**
|
|
164
164
|
* Validate script content for security issues
|
|
165
165
|
*/
|
|
166
|
-
validateScript(scriptContent: string): { valid: boolean; issues: string[] } {
|
|
166
|
+
validateScript(scriptContent: string, allowNetworkAccess?: boolean): { valid: boolean; issues: string[] } {
|
|
167
167
|
const issues: string[] = [];
|
|
168
168
|
|
|
169
169
|
// Check for dangerous patterns
|
|
@@ -176,11 +176,15 @@ export class ScriptPolicyEnforcer {
|
|
|
176
176
|
/Function\s*\(/gi, // Function constructor
|
|
177
177
|
/setTimeout/gi, // setTimeout
|
|
178
178
|
/setInterval/gi, // setInterval
|
|
179
|
-
/fetch\s*\(/gi, // Direct fetch calls
|
|
180
179
|
/XMLHttpRequest/gi, // XHR
|
|
181
180
|
/WebSocket/gi, // WebSocket
|
|
182
181
|
];
|
|
183
182
|
|
|
183
|
+
// Block direct fetch calls when network access is not explicitly allowed
|
|
184
|
+
if (!allowNetworkAccess) {
|
|
185
|
+
dangerousPatterns.push(/fetch\s*\(/gi);
|
|
186
|
+
}
|
|
187
|
+
|
|
184
188
|
for (const pattern of dangerousPatterns) {
|
|
185
189
|
if (pattern.test(scriptContent)) {
|
|
186
190
|
issues.push(`Potentially dangerous pattern detected: ${pattern.source}`);
|
package/src/types.ts
CHANGED
|
@@ -265,6 +265,7 @@ export const Models = {
|
|
|
265
265
|
Gemini_31_Pro_Preview: "gemini-3.1-pro-preview",
|
|
266
266
|
Gemini_31_Flash_Image_Preview: "gemini-3.1-flash-image-preview",
|
|
267
267
|
Gemini_31_Flash_Lite_Preview: "gemini-3.1-flash-lite-preview",
|
|
268
|
+
Gemini_31_Flash_Live_Preview: "gemini-3.1-flash-live-preview",
|
|
268
269
|
Gemini_3_Flash_Preview: "gemini-3-flash-preview",
|
|
269
270
|
Gemini_3_Pro_Image_Preview: "gemini-3-pro-image-preview",
|
|
270
271
|
// Gemini 2.5
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin native-fetch wrapper that returns { data: T } for backward compatibility.
|
|
3
|
+
* Replaces axios throughout the codebase.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface HttpResponse<T = any> {
|
|
7
|
+
data: T;
|
|
8
|
+
status: number;
|
|
9
|
+
headers: Headers;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class HttpError extends Error {
|
|
13
|
+
constructor(
|
|
14
|
+
public status: number,
|
|
15
|
+
public response: Response,
|
|
16
|
+
message: string
|
|
17
|
+
) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = "HttpError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function parseBody<T>(response: Response, responseType?: string): Promise<T> {
|
|
24
|
+
if (responseType === "arraybuffer") {
|
|
25
|
+
return (await response.arrayBuffer()) as unknown as T;
|
|
26
|
+
}
|
|
27
|
+
if (responseType === "stream") {
|
|
28
|
+
return response.body as unknown as T;
|
|
29
|
+
}
|
|
30
|
+
const text = await response.text();
|
|
31
|
+
if (!text) return undefined as unknown as T;
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(text) as T;
|
|
34
|
+
} catch {
|
|
35
|
+
return text as unknown as T;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function request<T = any>(
|
|
40
|
+
url: string,
|
|
41
|
+
options: {
|
|
42
|
+
method?: string;
|
|
43
|
+
headers?: Record<string, string>;
|
|
44
|
+
body?: any;
|
|
45
|
+
responseType?: "json" | "arraybuffer" | "stream" | "text";
|
|
46
|
+
params?: Record<string, any>;
|
|
47
|
+
/** Timeout in milliseconds. Default: 30000 (30s). Use 0 to disable. */
|
|
48
|
+
timeout?: number;
|
|
49
|
+
} = {}
|
|
50
|
+
): Promise<HttpResponse<T>> {
|
|
51
|
+
const { method = "GET", headers = {}, body, responseType, params, timeout = 30000 } = options;
|
|
52
|
+
|
|
53
|
+
let fullUrl = url;
|
|
54
|
+
if (params && Object.keys(params).length > 0) {
|
|
55
|
+
const searchParams = new URLSearchParams();
|
|
56
|
+
for (const [k, v] of Object.entries(params)) {
|
|
57
|
+
if (v !== undefined && v !== null) {
|
|
58
|
+
searchParams.set(k, String(v));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
fullUrl = `${url}?${searchParams.toString()}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const fetchOptions: RequestInit = {
|
|
65
|
+
method,
|
|
66
|
+
headers: { ...headers },
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
if (body !== undefined && body !== null) {
|
|
70
|
+
if (body instanceof Buffer || body instanceof Uint8Array || typeof (body as any).pipe === "function") {
|
|
71
|
+
// Stream or buffer — pass directly, Node fetch supports ReadableStream
|
|
72
|
+
fetchOptions.body = body;
|
|
73
|
+
} else if (typeof body === "object" && !(body instanceof FormData)) {
|
|
74
|
+
fetchOptions.body = JSON.stringify(body);
|
|
75
|
+
(fetchOptions.headers as Record<string, string>)["Content-Type"] = "application/json";
|
|
76
|
+
} else {
|
|
77
|
+
fetchOptions.body = body;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Apply timeout via AbortController
|
|
82
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
83
|
+
if (timeout > 0) {
|
|
84
|
+
const controller = new AbortController();
|
|
85
|
+
fetchOptions.signal = controller.signal;
|
|
86
|
+
timeoutId = setTimeout(() => {
|
|
87
|
+
controller.abort();
|
|
88
|
+
}, timeout);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let response: Response;
|
|
92
|
+
try {
|
|
93
|
+
response = await fetch(fullUrl, fetchOptions);
|
|
94
|
+
} catch (e: any) {
|
|
95
|
+
if (e?.name === "AbortError") {
|
|
96
|
+
throw new HttpError(408, undefined as any, `Request timeout after ${timeout}ms: ${url}`);
|
|
97
|
+
}
|
|
98
|
+
throw e;
|
|
99
|
+
} finally {
|
|
100
|
+
if (timeoutId !== undefined) clearTimeout(timeoutId);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!response.ok) {
|
|
104
|
+
const text = await response.text().catch(() => "");
|
|
105
|
+
throw new HttpError(response.status, response, `HTTP ${response.status}: ${text}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const data = await parseBody<T>(response, responseType);
|
|
109
|
+
|
|
110
|
+
return { data, status: response.status, headers: response.headers };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const http = {
|
|
114
|
+
get: <T = any>(url: string, options?: Omit<Parameters<typeof request>[1], "method">) =>
|
|
115
|
+
request<T>(url, { ...options, method: "GET" }),
|
|
116
|
+
post: <T = any>(url: string, body?: any, options?: Omit<Parameters<typeof request>[1], "method" | "body">) =>
|
|
117
|
+
request<T>(url, { ...options, method: "POST", body }),
|
|
118
|
+
put: <T = any>(url: string, body?: any, options?: Omit<Parameters<typeof request>[1], "method" | "body">) =>
|
|
119
|
+
request<T>(url, { ...options, method: "PUT", body }),
|
|
120
|
+
patch: <T = any>(url: string, body?: any, options?: Omit<Parameters<typeof request>[1], "method" | "body">) =>
|
|
121
|
+
request<T>(url, { ...options, method: "PATCH", body }),
|
|
122
|
+
delete: <T = any>(url: string, options?: Omit<Parameters<typeof request>[1], "method">) =>
|
|
123
|
+
request<T>(url, { ...options, method: "DELETE" }),
|
|
124
|
+
isHttpError: (e: any): e is HttpError => e instanceof HttpError,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export default http;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import http from "../../utils/http";
|
|
2
2
|
import { KNOWHOW_API_URL } from "../../services/KnowhowClient";
|
|
3
3
|
import { openBrowser } from "../../auth/browserLogin";
|
|
4
4
|
import { Spinner } from "../../auth/spinner";
|
|
@@ -93,21 +93,18 @@ export class PasskeySetupService {
|
|
|
93
93
|
|
|
94
94
|
private async createSetupSession(jwt: string): Promise<PasskeySetupSession> {
|
|
95
95
|
try {
|
|
96
|
-
const response = await
|
|
96
|
+
const response = await http.post<PasskeySetupSession>(
|
|
97
97
|
`${this.baseUrl}/api/worker/passkey/setup/session`,
|
|
98
98
|
{},
|
|
99
99
|
{
|
|
100
100
|
headers: { Authorization: `Bearer ${jwt}` },
|
|
101
|
-
timeout: 10000,
|
|
102
101
|
}
|
|
103
102
|
);
|
|
104
103
|
return response.data;
|
|
105
104
|
} catch (error) {
|
|
106
|
-
if (
|
|
105
|
+
if (http.isHttpError(error)) {
|
|
107
106
|
throw new Error(
|
|
108
|
-
`Failed to create passkey setup session: ${
|
|
109
|
-
error.response?.data?.message || error.message
|
|
110
|
-
}`
|
|
107
|
+
`Failed to create passkey setup session: ${error.message}`
|
|
111
108
|
);
|
|
112
109
|
}
|
|
113
110
|
throw error;
|
|
@@ -125,9 +122,8 @@ export class PasskeySetupService {
|
|
|
125
122
|
attempt++;
|
|
126
123
|
|
|
127
124
|
try {
|
|
128
|
-
const response = await
|
|
129
|
-
`${this.baseUrl}/api/worker/passkey/setup/status/${sessionId}
|
|
130
|
-
{ timeout: 10000 }
|
|
125
|
+
const response = await http.get<PasskeySetupStatus>(
|
|
126
|
+
`${this.baseUrl}/api/worker/passkey/setup/status/${sessionId}`
|
|
131
127
|
);
|
|
132
128
|
|
|
133
129
|
const { status, credential } = response.data;
|
|
@@ -140,7 +136,7 @@ export class PasskeySetupService {
|
|
|
140
136
|
);
|
|
141
137
|
}
|
|
142
138
|
} catch (error) {
|
|
143
|
-
if (
|
|
139
|
+
if (http.isHttpError(error) && error.status === 408) {
|
|
144
140
|
// Timeout — keep polling
|
|
145
141
|
} else if (!(error instanceof Error && error.message.includes("expired"))) {
|
|
146
142
|
// Re-throw non-timeout, non-expected errors
|
|
@@ -154,12 +154,18 @@ describe("AIClient", () => {
|
|
|
154
154
|
});
|
|
155
155
|
|
|
156
156
|
it("should find model by detection in registered providers", () => {
|
|
157
|
-
|
|
158
|
-
aiClient.
|
|
157
|
+
// Register an explicit myopenai provider with a gpt-4 prefix model
|
|
158
|
+
aiClient.registerClient("myopenai", new FakeClient());
|
|
159
|
+
aiClient.registerModels("myopenai", ["gpt-4-turbo", "gpt-4-vision"]);
|
|
159
160
|
|
|
161
|
+
// "another" provider (registered in beforeEach) has exact "gpt-4" match
|
|
162
|
+
// "myopenai" has prefix-matches "gpt-4-turbo" and "gpt-4-vision"
|
|
163
|
+
// findModel finds the first provider with a model matching the prefix "gpt-4"
|
|
160
164
|
const result = aiClient.detectProviderModel("", "gpt-4");
|
|
161
|
-
|
|
162
|
-
expect(result.
|
|
165
|
+
// Some provider should be found that has a gpt-4 model
|
|
166
|
+
expect(result.provider).toBeTruthy();
|
|
167
|
+
// The found model should start with "gpt-4"
|
|
168
|
+
expect(result.model).toMatch(/^gpt-4/);
|
|
163
169
|
});
|
|
164
170
|
|
|
165
171
|
it("should handle model with provider prefix when provider is empty", () => {
|
|
@@ -175,15 +181,16 @@ describe("AIClient", () => {
|
|
|
175
181
|
});
|
|
176
182
|
|
|
177
183
|
it("should detect real provider when model exists", () => {
|
|
178
|
-
|
|
179
|
-
aiClient.
|
|
184
|
+
// Register an anthropic provider explicitly so we don't rely on env vars
|
|
185
|
+
aiClient.registerClient("anthropic", new FakeClient());
|
|
186
|
+
aiClient.registerModels("anthropic", ["claude-3-opus-20240229"]);
|
|
180
187
|
|
|
181
188
|
// Test with provider prefix that gets stripped
|
|
182
189
|
const result = aiClient.detectProviderModel(
|
|
183
190
|
"",
|
|
184
191
|
"anthropic/claude-3-opus-20240229"
|
|
185
192
|
);
|
|
186
|
-
expect(result.provider).toBe("anthropic"); //
|
|
193
|
+
expect(result.provider).toBe("anthropic"); // anthropic provider found
|
|
187
194
|
expect(result.model).toBe("claude-3-opus-20240229");
|
|
188
195
|
});
|
|
189
196
|
});
|
|
@@ -208,15 +215,13 @@ describe("AIClient", () => {
|
|
|
208
215
|
it("should return all registered models from all providers", () => {
|
|
209
216
|
const allModels = aiClient.listAllModels();
|
|
210
217
|
expect(typeof allModels).toBe("object");
|
|
211
|
-
//
|
|
212
|
-
// Our test clients are not included in the listAllModels() output
|
|
213
|
-
// But we can verify real providers are present
|
|
218
|
+
// Verify our registered test providers are present
|
|
214
219
|
expect(Object.keys(allModels).length).toBeGreaterThan(0);
|
|
215
|
-
//
|
|
220
|
+
// The test providers registered in beforeEach should be present
|
|
216
221
|
const providers = Object.keys(allModels);
|
|
217
222
|
expect(
|
|
218
223
|
providers.some((p) =>
|
|
219
|
-
["
|
|
224
|
+
["fake", "another"].includes(p)
|
|
220
225
|
)
|
|
221
226
|
).toBe(true);
|
|
222
227
|
});
|
|
@@ -470,8 +475,10 @@ describe("AIClient", () => {
|
|
|
470
475
|
});
|
|
471
476
|
|
|
472
477
|
it("should handle provider stripping with complex model names", () => {
|
|
473
|
-
//
|
|
474
|
-
|
|
478
|
+
// Register providers explicitly so we don't rely on env vars
|
|
479
|
+
aiClient.registerClient("anthropic", new FakeClient());
|
|
480
|
+
aiClient.registerModels("anthropic", ["claude-3-opus-20240229"]);
|
|
481
|
+
|
|
475
482
|
const detection1 = aiClient.detectProviderModel(
|
|
476
483
|
"",
|
|
477
484
|
"anthropic/claude-3-opus-20240229"
|
|
@@ -484,14 +491,10 @@ describe("AIClient", () => {
|
|
|
484
491
|
"",
|
|
485
492
|
"openai/non-existent-model"
|
|
486
493
|
);
|
|
487
|
-
// Should
|
|
494
|
+
// Should return original values when no match found
|
|
488
495
|
expect(detection2).toBeDefined();
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
} else {
|
|
492
|
-
expect(detection2?.provider).toBe("openai");
|
|
493
|
-
expect(detection2?.model).toBe("gpt-5");
|
|
494
|
-
}
|
|
496
|
+
expect(detection2?.provider).toBe("");
|
|
497
|
+
expect(detection2?.model).toBe("openai/non-existent-model");
|
|
495
498
|
});
|
|
496
499
|
|
|
497
500
|
it("should handle model prefix matching edge cases", () => {
|
|
@@ -1,70 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const figmaPlugin = new FigmaPlugin();
|
|
5
|
-
|
|
6
|
-
const exampleFigmaFileUrl =
|
|
7
|
-
"https://www.figma.com/file/iK2ggDLxl94Q4q0FKUdlmM/Guilds";
|
|
8
|
-
const exampleNodeId = "52-717";
|
|
9
|
-
const exampleNodeIds = [exampleNodeId];
|
|
10
|
-
|
|
11
|
-
const expectedApiResponse = {
|
|
12
|
-
name: "Guilds",
|
|
13
|
-
nodes: { "52-717": { document: {} } },
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
describe("FigmaPlugin", () => {
|
|
17
|
-
describe("extractUrls", () => {
|
|
18
|
-
it("should extract Figma file URLs from a given text", () => {
|
|
19
|
-
const text = `Design can be found here: ${exampleFigmaFileUrl}`;
|
|
20
|
-
expect(figmaPlugin.extractUrls(text)).toEqual([exampleFigmaFileUrl]);
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
describe("extractFileIdFromUrl", () => {
|
|
25
|
-
it("should extract file ID from Figma URL", () => {
|
|
26
|
-
expect(figmaPlugin.extractFileIdFromUrl(exampleFigmaFileUrl)).toBe(
|
|
27
|
-
"iK2ggDLxl94Q4q0FKUdlmM"
|
|
28
|
-
);
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
describe("parseNodeIdsFromUrl", () => {
|
|
33
|
-
it("should extract node IDs from Figma URL", () => {
|
|
34
|
-
const urlWithNode = exampleFigmaFileUrl + "?node-id=" + exampleNodeId;
|
|
35
|
-
expect(figmaPlugin.parseNodeIdsFromUrl(urlWithNode)).toEqual(
|
|
36
|
-
exampleNodeIds
|
|
37
|
-
);
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
describe("loadFigmaData", () => {
|
|
42
|
-
it("should make an API call and return Figma data", async () => {
|
|
43
|
-
const data = await figmaPlugin.loadFigmaData(
|
|
44
|
-
exampleFigmaFileUrl + "?node-id=" + exampleNodeId
|
|
45
|
-
);
|
|
46
|
-
expect(data).toBeDefined;
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
describe("embed", () => {
|
|
51
|
-
it("should return a minimal embedding array", async () => {
|
|
52
|
-
const embeddings = await figmaPlugin.embed(
|
|
53
|
-
`Here's the design file: ${exampleFigmaFileUrl}?node-id=${exampleNodeId}`
|
|
54
|
-
);
|
|
55
|
-
expect(embeddings).toBeInstanceOf(Array);
|
|
56
|
-
expect(embeddings).toHaveLength(1);
|
|
57
|
-
const nodeId = exampleNodeId.replace("-", ":");
|
|
58
|
-
console.log(JSON.parse(embeddings[0].text));
|
|
59
|
-
/*
|
|
60
|
-
*expect(embeddings[0].text).toEqual(
|
|
61
|
-
* JSON.stringify({
|
|
62
|
-
* id: expectedApiResponse.name,
|
|
63
|
-
* metadata: {},
|
|
64
|
-
* text: JSON.stringify(expectedApiResponse),
|
|
65
|
-
* })
|
|
66
|
-
*);
|
|
67
|
-
*/
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
});
|
|
1
|
+
// FigmaPlugin tests moved to @tyvm/knowhow-plugin-figma package
|
|
2
|
+
// Install and configure @tyvm/knowhow-plugin-figma to use Figma integration
|
|
3
|
+
describe.skip("FigmaPlugin (moved to @tyvm/knowhow-plugin-figma)", () => {});
|
|
@@ -18,6 +18,7 @@ jest.mock("../../../src/services/EventService", () => ({
|
|
|
18
18
|
EventService: jest.fn().mockImplementation(() => ({
|
|
19
19
|
on: jest.fn(),
|
|
20
20
|
emit: jest.fn(),
|
|
21
|
+
log: jest.fn(),
|
|
21
22
|
})),
|
|
22
23
|
}));
|
|
23
24
|
|
|
@@ -54,6 +55,7 @@ describe("LanguagePlugin - Content-Based Triggering", () => {
|
|
|
54
55
|
eventHandlers.set(event, handler);
|
|
55
56
|
}),
|
|
56
57
|
emit: jest.fn(),
|
|
58
|
+
log: jest.fn(),
|
|
57
59
|
};
|
|
58
60
|
|
|
59
61
|
mockPluginService = {
|
|
@@ -73,6 +73,7 @@ describe("LanguagePlugin", () => {
|
|
|
73
73
|
const mockEventService = {
|
|
74
74
|
on: jest.fn(),
|
|
75
75
|
emit: jest.fn(),
|
|
76
|
+
log: jest.fn(),
|
|
76
77
|
};
|
|
77
78
|
const mockListPlugins = jest.fn().mockReturnValue(["github", "asana"]);
|
|
78
79
|
const mockCall = jest.fn().mockResolvedValue(["mocked plugin response"]);
|
|
@@ -362,6 +363,7 @@ describe("LanguagePlugin", () => {
|
|
|
362
363
|
const mockEventService = {
|
|
363
364
|
on: jest.fn(),
|
|
364
365
|
emit: jest.fn(),
|
|
366
|
+
log: jest.fn(),
|
|
365
367
|
};
|
|
366
368
|
const mockPluginService = new MockedPluginService({} as any);
|
|
367
369
|
const mockListPlugins = jest.fn().mockReturnValue(["github"]);
|
|
@@ -371,7 +371,7 @@ describe("ToolResponseCache", () => {
|
|
|
371
371
|
const result = await cache.queryToolResponse("missing_id", ".test");
|
|
372
372
|
expect(result).toContain("Error: No tool response found");
|
|
373
373
|
expect(result).toContain("missing_id");
|
|
374
|
-
expect(result).toContain("Available
|
|
374
|
+
expect(result).toContain("Available toolCallIds:");
|
|
375
375
|
});
|
|
376
376
|
|
|
377
377
|
it("should handle invalid JQ queries with error message", async () => {
|
|
@@ -698,7 +698,7 @@ describe("ToolResponseCache", () => {
|
|
|
698
698
|
const result = await cache.tailToolResponse("missing_id");
|
|
699
699
|
expect(result).toContain("Error: No tool response found");
|
|
700
700
|
expect(result).toContain("missing_id");
|
|
701
|
-
expect(result).toContain("Available
|
|
701
|
+
expect(result).toContain("Available toolCallIds:");
|
|
702
702
|
});
|
|
703
703
|
|
|
704
704
|
it("should handle lines option of 0", async () => {
|
package/tests/test.spec.ts
CHANGED
|
@@ -1,18 +1,4 @@
|
|
|
1
1
|
// Global mocks that need to be hoisted before any imports
|
|
2
|
-
jest.mock("tree-sitter", () => {
|
|
3
|
-
return jest.fn().mockImplementation(() => ({
|
|
4
|
-
setLanguage: jest.fn(),
|
|
5
|
-
parse: jest.fn(() => ({ rootNode: { toString: () => "mock tree" } })),
|
|
6
|
-
}));
|
|
7
|
-
});
|
|
8
|
-
jest.mock("tree-sitter-typescript", () => ({ typescript: jest.fn() }));
|
|
9
|
-
jest.mock("tree-sitter-javascript", () => ({ javascript: jest.fn() }));
|
|
10
|
-
jest.mock("../src/plugins/tree-sitter/parser", () => ({
|
|
11
|
-
LanguageAgnosticParser: jest.fn(),
|
|
12
|
-
}));
|
|
13
|
-
jest.mock("../src/plugins/tree-sitter/editor", () => ({
|
|
14
|
-
TreeEditor: jest.fn(),
|
|
15
|
-
}));
|
|
16
2
|
jest.mock("../src/services/S3", () => ({
|
|
17
3
|
S3Service: jest.fn().mockImplementation(() => ({
|
|
18
4
|
uploadFile: jest.fn(),
|
|
@@ -132,7 +132,7 @@ describe("ModulesService.loadModulesFromConfig", () => {
|
|
|
132
132
|
});
|
|
133
133
|
|
|
134
134
|
it("should load plugins from a module", async () => {
|
|
135
|
-
const
|
|
135
|
+
const mockPluginInstance = {
|
|
136
136
|
meta: { key: "test-plugin", name: "Test Plugin" },
|
|
137
137
|
isEnabled: () => true,
|
|
138
138
|
enable: () => {},
|
|
@@ -141,8 +141,10 @@ describe("ModulesService.loadModulesFromConfig", () => {
|
|
|
141
141
|
callMany: () => Promise.resolve(""),
|
|
142
142
|
embed: () => Promise.resolve([]),
|
|
143
143
|
};
|
|
144
|
+
// ModulePlugin expects a constructor (class), not an instance
|
|
145
|
+
const MockPluginClass = jest.fn().mockImplementation(() => mockPluginInstance);
|
|
144
146
|
const mockModule = makeModule({
|
|
145
|
-
plugins: [{ name: "test-plugin", plugin:
|
|
147
|
+
plugins: [{ name: "test-plugin", plugin: MockPluginClass as any }],
|
|
146
148
|
});
|
|
147
149
|
|
|
148
150
|
const service = new ModulesService();
|
|
@@ -153,13 +155,14 @@ describe("ModulesService.loadModulesFromConfig", () => {
|
|
|
153
155
|
const resolvedCtx = ctx || context;
|
|
154
156
|
await mockModule.init({ config: {} as Config, cwd: process.cwd() });
|
|
155
157
|
for (const plugin of mockModule.plugins) {
|
|
156
|
-
|
|
158
|
+
const instance = new (plugin.plugin as any)(resolvedCtx);
|
|
159
|
+
resolvedCtx.Plugins.registerPlugin(plugin.name, instance);
|
|
157
160
|
}
|
|
158
161
|
});
|
|
159
162
|
|
|
160
163
|
await service.loadModulesFromConfig(context);
|
|
161
164
|
|
|
162
|
-
expect(context.Plugins.registerPlugin).toHaveBeenCalledWith("test-plugin",
|
|
165
|
+
expect(context.Plugins.registerPlugin).toHaveBeenCalledWith("test-plugin", mockPluginInstance);
|
|
163
166
|
spy.mockRestore();
|
|
164
167
|
});
|
|
165
168
|
|
|
@@ -11,6 +11,7 @@ function makeContext(): PluginContext {
|
|
|
11
11
|
emit: jest.fn(),
|
|
12
12
|
emitBlocking: jest.fn(),
|
|
13
13
|
emitNonBlocking: jest.fn(),
|
|
14
|
+
log: jest.fn(),
|
|
14
15
|
} as any,
|
|
15
16
|
} as PluginContext;
|
|
16
17
|
}
|
|
@@ -110,8 +111,8 @@ describe("PluginService.loadPluginsFromConfig", () => {
|
|
|
110
111
|
});
|
|
111
112
|
|
|
112
113
|
it("should log a warning and not crash when a plugin fails to load", async () => {
|
|
113
|
-
const
|
|
114
|
-
const
|
|
114
|
+
const context = makeContext();
|
|
115
|
+
const service = new PluginService(context);
|
|
115
116
|
|
|
116
117
|
service.loadPlugin = jest.fn().mockRejectedValue(new Error("Module not found"));
|
|
117
118
|
|
|
@@ -122,12 +123,11 @@ describe("PluginService.loadPluginsFromConfig", () => {
|
|
|
122
123
|
} as unknown as Config;
|
|
123
124
|
|
|
124
125
|
await expect(service.loadPluginsFromConfig(config)).resolves.toBeUndefined();
|
|
125
|
-
expect(
|
|
126
|
+
expect(context.Events.log).toHaveBeenCalledWith(
|
|
127
|
+
"PluginService",
|
|
126
128
|
expect.stringContaining("broken"),
|
|
127
|
-
|
|
129
|
+
"warn"
|
|
128
130
|
);
|
|
129
|
-
|
|
130
|
-
warnSpy.mockRestore();
|
|
131
131
|
});
|
|
132
132
|
|
|
133
133
|
it("should load each plugin with the correct spec string", async () => {
|