keystone-cli 1.0.3 → 1.1.0

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.
Files changed (153) hide show
  1. package/README.md +276 -32
  2. package/package.json +8 -4
  3. package/src/cli.ts +350 -416
  4. package/src/commands/doc.ts +31 -0
  5. package/src/commands/event.ts +29 -0
  6. package/src/commands/graph.ts +37 -0
  7. package/src/commands/index.ts +14 -0
  8. package/src/commands/init.ts +185 -0
  9. package/src/commands/run.ts +124 -0
  10. package/src/commands/schema.ts +40 -0
  11. package/src/commands/utils.ts +78 -0
  12. package/src/commands/validate.ts +111 -0
  13. package/src/db/workflow-db.test.ts +314 -0
  14. package/src/db/workflow-db.ts +810 -210
  15. package/src/expression/evaluator-audit.test.ts +4 -2
  16. package/src/expression/evaluator.test.ts +14 -1
  17. package/src/expression/evaluator.ts +166 -19
  18. package/src/parser/config-schema.ts +18 -0
  19. package/src/parser/schema.ts +153 -22
  20. package/src/parser/test-schema.ts +6 -6
  21. package/src/parser/workflow-parser.test.ts +24 -0
  22. package/src/parser/workflow-parser.ts +65 -3
  23. package/src/runner/auto-heal.test.ts +5 -6
  24. package/src/runner/blueprint-executor.test.ts +2 -2
  25. package/src/runner/debug-repl.test.ts +5 -8
  26. package/src/runner/debug-repl.ts +59 -16
  27. package/src/runner/durable-timers.test.ts +11 -2
  28. package/src/runner/engine-executor.test.ts +1 -1
  29. package/src/runner/events.ts +57 -0
  30. package/src/runner/executors/artifact-executor.ts +166 -0
  31. package/src/runner/{blueprint-executor.ts → executors/blueprint-executor.ts} +15 -7
  32. package/src/runner/{engine-executor.ts → executors/engine-executor.ts} +55 -7
  33. package/src/runner/executors/file-executor.test.ts +48 -0
  34. package/src/runner/executors/file-executor.ts +324 -0
  35. package/src/runner/{foreach-executor.ts → executors/foreach-executor.ts} +168 -80
  36. package/src/runner/executors/human-executor.ts +144 -0
  37. package/src/runner/executors/join-executor.ts +75 -0
  38. package/src/runner/executors/llm-executor.ts +1266 -0
  39. package/src/runner/executors/memory-executor.ts +71 -0
  40. package/src/runner/executors/plan-executor.ts +104 -0
  41. package/src/runner/executors/request-executor.ts +265 -0
  42. package/src/runner/executors/script-executor.ts +43 -0
  43. package/src/runner/executors/shell-executor.ts +403 -0
  44. package/src/runner/executors/subworkflow-executor.ts +114 -0
  45. package/src/runner/executors/types.ts +69 -0
  46. package/src/runner/executors/wait-executor.ts +59 -0
  47. package/src/runner/join-scheduling.test.ts +197 -0
  48. package/src/runner/llm-adapter-runtime.test.ts +209 -0
  49. package/src/runner/llm-adapter.test.ts +419 -24
  50. package/src/runner/llm-adapter.ts +130 -26
  51. package/src/runner/llm-clarification.test.ts +2 -1
  52. package/src/runner/llm-executor.test.ts +532 -17
  53. package/src/runner/mcp-client-audit.test.ts +1 -2
  54. package/src/runner/mcp-client.ts +136 -46
  55. package/src/runner/mcp-manager.test.ts +4 -0
  56. package/src/runner/mcp-server.test.ts +58 -0
  57. package/src/runner/mcp-server.ts +26 -0
  58. package/src/runner/memoization.test.ts +190 -0
  59. package/src/runner/optimization-runner.ts +4 -9
  60. package/src/runner/quality-gate.test.ts +69 -0
  61. package/src/runner/reflexion.test.ts +6 -17
  62. package/src/runner/resource-pool.ts +102 -14
  63. package/src/runner/services/context-builder.ts +144 -0
  64. package/src/runner/services/secret-manager.ts +105 -0
  65. package/src/runner/services/workflow-validator.ts +131 -0
  66. package/src/runner/shell-executor.test.ts +28 -4
  67. package/src/runner/standard-tools-ast.test.ts +196 -0
  68. package/src/runner/standard-tools-execution.test.ts +27 -0
  69. package/src/runner/standard-tools-integration.test.ts +6 -10
  70. package/src/runner/standard-tools.ts +339 -102
  71. package/src/runner/step-executor.test.ts +216 -4
  72. package/src/runner/step-executor.ts +69 -941
  73. package/src/runner/stream-utils.ts +7 -3
  74. package/src/runner/test-harness.ts +20 -1
  75. package/src/runner/timeout.test.ts +10 -0
  76. package/src/runner/timeout.ts +11 -2
  77. package/src/runner/tool-integration.test.ts +1 -1
  78. package/src/runner/wait-step.test.ts +102 -0
  79. package/src/runner/workflow-runner.test.ts +208 -15
  80. package/src/runner/workflow-runner.ts +890 -818
  81. package/src/runner/workflow-scheduler.ts +75 -0
  82. package/src/runner/workflow-state.ts +269 -0
  83. package/src/runner/workflow-subflows.test.ts +13 -12
  84. package/src/scripts/generate-schemas.ts +16 -0
  85. package/src/templates/agents/explore.md +1 -0
  86. package/src/templates/agents/general.md +1 -0
  87. package/src/templates/agents/handoff-router.md +14 -0
  88. package/src/templates/agents/handoff-specialist.md +15 -0
  89. package/src/templates/agents/keystone-architect.md +13 -44
  90. package/src/templates/agents/my-agent.md +1 -0
  91. package/src/templates/agents/software-engineer.md +1 -0
  92. package/src/templates/agents/summarizer.md +1 -0
  93. package/src/templates/agents/test-agent.md +1 -0
  94. package/src/templates/agents/tester.md +1 -0
  95. package/src/templates/{basic-inputs.yaml → basics/basic-inputs.yaml} +2 -0
  96. package/src/templates/{basic-shell.yaml → basics/basic-shell.yaml} +2 -1
  97. package/src/templates/{full-feature-demo.yaml → basics/full-feature-demo.yaml} +2 -0
  98. package/src/templates/{stop-watch.yaml → basics/stop-watch.yaml} +1 -0
  99. package/src/templates/{child-rollback.yaml → control-flow/child-rollback.yaml} +1 -0
  100. package/src/templates/{cleanup-finally.yaml → control-flow/cleanup-finally.yaml} +1 -0
  101. package/src/templates/{fan-out-fan-in.yaml → control-flow/fan-out-fan-in.yaml} +3 -0
  102. package/src/templates/control-flow/idempotency-example.yaml +30 -0
  103. package/src/templates/{loop-parallel.yaml → control-flow/loop-parallel.yaml} +3 -0
  104. package/src/templates/{parent-rollback.yaml → control-flow/parent-rollback.yaml} +1 -0
  105. package/src/templates/{retry-policy.yaml → control-flow/retry-policy.yaml} +3 -0
  106. package/src/templates/features/artifact-example.yaml +39 -0
  107. package/src/templates/{engine-example.yaml → features/engine-example.yaml} +1 -0
  108. package/src/templates/{human-interaction.yaml → features/human-interaction.yaml} +1 -0
  109. package/src/templates/{llm-agent.yaml → features/llm-agent.yaml} +1 -0
  110. package/src/templates/{memory-service.yaml → features/memory-service.yaml} +2 -0
  111. package/src/templates/{robust-automation.yaml → features/robust-automation.yaml} +3 -0
  112. package/src/templates/features/script-example.yaml +27 -0
  113. package/src/templates/patterns/agent-handoff.yaml +53 -0
  114. package/src/templates/{approval-process.yaml → patterns/approval-process.yaml} +1 -0
  115. package/src/templates/{batch-processor.yaml → patterns/batch-processor.yaml} +2 -0
  116. package/src/templates/{composition-child.yaml → patterns/composition-child.yaml} +1 -0
  117. package/src/templates/{composition-parent.yaml → patterns/composition-parent.yaml} +1 -0
  118. package/src/templates/{data-pipeline.yaml → patterns/data-pipeline.yaml} +2 -0
  119. package/src/templates/{decompose-implement.yaml → scaffolding/decompose-implement.yaml} +1 -0
  120. package/src/templates/{decompose-problem.yaml → scaffolding/decompose-problem.yaml} +1 -0
  121. package/src/templates/{decompose-research.yaml → scaffolding/decompose-research.yaml} +1 -0
  122. package/src/templates/{decompose-review.yaml → scaffolding/decompose-review.yaml} +1 -0
  123. package/src/templates/{dev.yaml → scaffolding/dev.yaml} +1 -0
  124. package/src/templates/scaffolding/review-loop.yaml +97 -0
  125. package/src/templates/{scaffold-feature.yaml → scaffolding/scaffold-feature.yaml} +2 -0
  126. package/src/templates/{scaffold-generate.yaml → scaffolding/scaffold-generate.yaml} +1 -0
  127. package/src/templates/{scaffold-plan.yaml → scaffolding/scaffold-plan.yaml} +1 -0
  128. package/src/templates/testing/invalid.yaml +6 -0
  129. package/src/ui/dashboard.tsx +191 -33
  130. package/src/utils/auth-manager.test.ts +337 -0
  131. package/src/utils/auth-manager.ts +157 -61
  132. package/src/utils/blueprint-utils.ts +4 -6
  133. package/src/utils/config-loader.test.ts +2 -0
  134. package/src/utils/config-loader.ts +12 -3
  135. package/src/utils/constants.ts +76 -0
  136. package/src/utils/container.ts +63 -0
  137. package/src/utils/context-injector.test.ts +200 -0
  138. package/src/utils/context-injector.ts +244 -0
  139. package/src/utils/doc-generator.ts +85 -0
  140. package/src/utils/env-filter.ts +45 -0
  141. package/src/utils/json-parser.test.ts +12 -0
  142. package/src/utils/json-parser.ts +30 -5
  143. package/src/utils/logger.ts +12 -1
  144. package/src/utils/mermaid.ts +4 -0
  145. package/src/utils/paths.ts +52 -1
  146. package/src/utils/process-sandbox-worker.test.ts +46 -0
  147. package/src/utils/process-sandbox.ts +227 -14
  148. package/src/utils/redactor.test.ts +11 -6
  149. package/src/utils/redactor.ts +25 -9
  150. package/src/utils/sandbox.ts +3 -0
  151. package/src/runner/llm-executor.ts +0 -638
  152. package/src/runner/shell-executor.ts +0 -366
  153. package/src/templates/invalid.yaml +0 -5
@@ -0,0 +1,197 @@
1
+ import { afterAll, afterEach, describe, expect, it, spyOn } from 'bun:test';
2
+ import { existsSync, rmSync } from 'node:fs';
3
+ import { MemoryDb } from '../db/memory-db';
4
+ import { WorkflowDb } from '../db/workflow-db';
5
+ import type { Workflow } from '../parser/schema';
6
+ import { ConfigLoader } from '../utils/config-loader';
7
+ import { container } from '../utils/container';
8
+ import { ConsoleLogger } from '../utils/logger';
9
+ import { WorkflowRunner } from './workflow-runner';
10
+
11
+ describe('Join Scheduling & Resume', () => {
12
+ const dbPath = ':memory:';
13
+
14
+ // Setup DI container for tests
15
+ container.register('logger', new ConsoleLogger());
16
+ container.register('db', new WorkflowDb(dbPath));
17
+ container.register('memoryDb', new MemoryDb());
18
+
19
+ const activeSpies: Array<{ mockRestore: () => void }> = [];
20
+ const trackSpy = <T extends { mockRestore: () => void }>(spy: T): T => {
21
+ activeSpies.push(spy);
22
+ return spy;
23
+ };
24
+
25
+ afterAll(() => {
26
+ // Cleanup any file-based DBs if used
27
+ const files = ['test-resume-retry.db'];
28
+ for (const f of files) {
29
+ if (existsSync(f)) rmSync(f);
30
+ }
31
+ });
32
+
33
+ afterEach(() => {
34
+ for (const spy of activeSpies) {
35
+ spy.mockRestore();
36
+ }
37
+ activeSpies.length = 0;
38
+ ConfigLoader.clear();
39
+ });
40
+
41
+ it('should execute join step when one dependency fails but has allowFailure: true', async () => {
42
+ const workflow: Workflow = {
43
+ name: 'join-allow-failure',
44
+ steps: [
45
+ {
46
+ id: 'A',
47
+ type: 'shell',
48
+ run: 'exit 1', // Fails
49
+ allowFailure: true,
50
+ place: 'local', // Avoid shell injection check for simple exit
51
+ needs: [],
52
+ },
53
+ {
54
+ id: 'B',
55
+ type: 'shell',
56
+ run: 'echo "B success"',
57
+ needs: [],
58
+ },
59
+ {
60
+ id: 'C',
61
+ type: 'shell',
62
+ run: 'echo "Joined"',
63
+ needs: ['A', 'B'],
64
+ },
65
+ ],
66
+ outputs: {
67
+ c_status: '${{ steps.C.status }}',
68
+ a_status: '${{ steps.A.status }}',
69
+ a_error: '${{ steps.A.error }}',
70
+ },
71
+ } as unknown as Workflow;
72
+
73
+ const runner = new WorkflowRunner(workflow, { dbPath });
74
+ const outputs = await runner.run();
75
+
76
+ expect(outputs.c_status).toBe('success');
77
+ expect(outputs.a_status).toBe('success');
78
+ expect(outputs.a_error).toContain('code 1');
79
+ });
80
+
81
+ it('should execute join step after dependency succeeds via retry', async () => {
82
+ const counterFile = `/tmp/keystone-test-retry-${Date.now()}.txt`;
83
+ await Bun.write(counterFile, '0');
84
+
85
+ const workflow: Workflow = {
86
+ name: 'join-retry',
87
+ steps: [
88
+ {
89
+ id: 'A',
90
+ type: 'shell',
91
+ // Read counter, increment, if < 2 exit 1, else exit 0
92
+ run: `
93
+ val=$(cat ${counterFile})
94
+ echo $((val + 1)) > ${counterFile}
95
+ if [ "$val" -lt "2" ]; then exit 1; else exit 0; fi
96
+ `,
97
+ retry: { count: 3 },
98
+ allowInsecure: true,
99
+ needs: [],
100
+ },
101
+ {
102
+ id: 'B',
103
+ type: 'shell',
104
+ run: 'echo "B"',
105
+ needs: ['A'],
106
+ },
107
+ ],
108
+ outputs: {
109
+ b_status: '${{ steps.B.status }}',
110
+ },
111
+ } as unknown as Workflow;
112
+
113
+ const runner = new WorkflowRunner(workflow, { dbPath });
114
+ const outputs = await runner.run();
115
+
116
+ expect(outputs.b_status).toBe('success');
117
+
118
+ // Verify it ran 3 times (0 -> 1 (fail), 1 -> 2 (fail), 2 -> 3 (success))
119
+ const finalVal = await Bun.file(counterFile).text();
120
+ expect(finalVal.trim()).toBe('3');
121
+
122
+ if (existsSync(counterFile)) rmSync(counterFile);
123
+ });
124
+
125
+ it('should resume and retry a step that previously exhausted retries', async () => {
126
+ const dbPath = 'test-resume-retry.db';
127
+ if (existsSync(dbPath)) rmSync(dbPath);
128
+
129
+ const counterFile = `/tmp/keystone-test-resume-${Date.now()}.txt`;
130
+ await Bun.write(counterFile, '0');
131
+
132
+ // Workflow that fails initially (retry count 1, but we make it fail 2 times)
133
+ const workflow: Workflow = {
134
+ name: 'resume-retry',
135
+ steps: [
136
+ {
137
+ id: 'A',
138
+ type: 'shell',
139
+ // Read counter, increment.
140
+ // We want it to fail runs 1 and 2.
141
+ // Setup: Retry count 1.
142
+ // Run 1: fails. Retry 1: fails. -> Workflow Fails.
143
+ // Resume.
144
+ // Run 3: succeeds.
145
+ run: `
146
+ val=$(cat ${counterFile})
147
+ echo $((val + 1)) > ${counterFile}
148
+ if [ "$val" -lt "2" ]; then exit 1; else exit 0; fi
149
+ `,
150
+ retry: { count: 1 },
151
+ allowInsecure: true,
152
+ needs: [],
153
+ },
154
+ {
155
+ id: 'B',
156
+ type: 'shell',
157
+ run: 'echo "Done"',
158
+ needs: ['A'],
159
+ },
160
+ ],
161
+ outputs: {
162
+ b_visited: '${{ steps.B.status }}',
163
+ },
164
+ } as unknown as Workflow;
165
+
166
+ // First run
167
+ const runner1 = new WorkflowRunner(workflow, { dbPath });
168
+ let runId = '';
169
+ try {
170
+ await runner1.run();
171
+ } catch (e) {
172
+ runId = runner1.runId;
173
+ }
174
+
175
+ expect(runId).toBeTruthy();
176
+
177
+ // Verify it failed twice (initial + 1 retry)
178
+ let val = await Bun.file(counterFile).text();
179
+ expect(val.trim()).toBe('2');
180
+
181
+ // Now resume. It should try again (Run 3) and succeed.
182
+ const runner2 = new WorkflowRunner(workflow, {
183
+ dbPath,
184
+ resumeRunId: runId,
185
+ });
186
+
187
+ const outputs = await runner2.run();
188
+
189
+ expect(outputs.b_visited).toBe('success');
190
+
191
+ val = await Bun.file(counterFile).text();
192
+ expect(val.trim()).toBe('3');
193
+
194
+ if (existsSync(dbPath)) rmSync(dbPath);
195
+ if (existsSync(counterFile)) rmSync(counterFile);
196
+ });
197
+ });
@@ -0,0 +1,209 @@
1
+ import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from 'bun:test';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import { AuthManager } from '../utils/auth-manager';
5
+ import { ConfigLoader } from '../utils/config-loader';
6
+ import {
7
+ AnthropicAdapter,
8
+ GoogleGeminiAdapter,
9
+ LocalEmbeddingAdapter,
10
+ OpenAIAdapter,
11
+ collectOnnxRuntimeLibraryDirs,
12
+ ensureNativeModuleFallbacks,
13
+ ensureOnnxRuntimeLibraryPath,
14
+ ensureRuntimeResolver,
15
+ findOnnxRuntimeLibraryPath,
16
+ getAdapter,
17
+ resetRuntimeHelpers,
18
+ resolveNativeModuleFallback,
19
+ resolveTransformersCacheDir,
20
+ resolveTransformersPath,
21
+ } from './llm-adapter';
22
+
23
+ describe('LLM Adapter Runtime and Helpers', () => {
24
+ const originalPlatform = process.platform;
25
+ const originalArch = process.arch;
26
+ const originalEnv = { ...process.env };
27
+
28
+ beforeEach(() => {
29
+ resetRuntimeHelpers();
30
+ ConfigLoader.clear();
31
+ // Reset process.env
32
+ for (const key in process.env) {
33
+ delete process.env[key];
34
+ }
35
+ Object.assign(process.env, originalEnv);
36
+ });
37
+
38
+ afterEach(() => {
39
+ resetRuntimeHelpers();
40
+ mock.restore();
41
+ // @ts-ignore
42
+ process.platform = originalPlatform;
43
+ // @ts-ignore
44
+ process.arch = originalArch;
45
+ });
46
+
47
+ describe('Runtime Resolution', () => {
48
+ it('should collect ONNX runtime library dirs from environment', () => {
49
+ process.env.KEYSTONE_ONNX_RUNTIME_LIB_DIR = '/custom/onnx/dir';
50
+
51
+ // Mock readdirSync to return true for hasOnnxRuntimeLibrary
52
+ const readdirSpy = spyOn(fs, 'readdirSync').mockReturnValue([
53
+ { isFile: () => true, name: 'libonnxruntime.so' } as any,
54
+ ]);
55
+
56
+ // We need to trigger a path that calls collectOnnxRuntimeLibraryDirs
57
+ // getAdapter('local') might trigger it via LocalEmbeddingAdapter
58
+ const { adapter } = getAdapter('local');
59
+ expect(adapter).toBeInstanceOf(LocalEmbeddingAdapter);
60
+
61
+ readdirSpy.mockRestore();
62
+ });
63
+
64
+ it('should handle ONNX library discovery across multiple paths', () => {
65
+ process.env.KEYSTONE_ONNX_RUNTIME_LIB_DIR = '/env/onnx';
66
+ process.env.KEYSTONE_RUNTIME_DIR = '/runtime/dir';
67
+
68
+ const existsSpy = spyOn(fs, 'existsSync').mockImplementation(((p: string) => {
69
+ return p.includes('onnx') || p.includes('lib');
70
+ }) as any);
71
+
72
+ const readdirSpy = spyOn(fs, 'readdirSync').mockImplementation(((
73
+ p: string | Buffer | URL,
74
+ options?: any
75
+ ) => {
76
+ if (typeof p !== 'string') return [];
77
+ if (p.includes('onnx') || p.includes('env')) {
78
+ return [{ isFile: () => true, name: 'libonnxruntime.so' }] as any;
79
+ }
80
+ return [];
81
+ }) as any);
82
+
83
+ const dirs = collectOnnxRuntimeLibraryDirs();
84
+ expect(dirs).toContain('/env/onnx');
85
+
86
+ const libPath = findOnnxRuntimeLibraryPath(dirs);
87
+ expect(libPath).toContain('/env/onnx/libonnxruntime.so');
88
+
89
+ // Test ensureOnnxRuntimeLibraryPath
90
+ ensureOnnxRuntimeLibraryPath();
91
+ expect(
92
+ process.env.LD_LIBRARY_PATH || process.env.DYLD_LIBRARY_PATH || process.env.PATH
93
+ ).toContain('/env/onnx');
94
+
95
+ existsSpy.mockRestore();
96
+ readdirSpy.mockRestore();
97
+ });
98
+
99
+ it('should handle transformers cache dir resolution across env vars', () => {
100
+ process.env.TRANSFORMERS_CACHE = '/custom/cache';
101
+ expect(resolveTransformersCacheDir()).toBe('/custom/cache');
102
+
103
+ process.env.TRANSFORMERS_CACHE = undefined;
104
+ process.env.XDG_CACHE_HOME = '/xdg/cache';
105
+ expect(resolveTransformersCacheDir()).toContain('/xdg/cache');
106
+
107
+ process.env.XDG_CACHE_HOME = undefined;
108
+ process.env.HOME = '/home/user';
109
+ expect(resolveTransformersCacheDir()).toContain('/home/user/.cache');
110
+ });
111
+
112
+ it('should handle transformers path resolution', () => {
113
+ process.env.KEYSTONE_TRANSFORMERS_PATH = '/path/to/transformers';
114
+ const existsSpy = spyOn(fs, 'existsSync').mockReturnValue(true);
115
+ expect(resolveTransformersPath()).toBe('/path/to/transformers');
116
+ existsSpy.mockRestore();
117
+
118
+ const existsSpyFail = spyOn(fs, 'existsSync').mockReturnValue(false);
119
+ expect(resolveTransformersPath()).toBeNull();
120
+ existsSpyFail.mockRestore();
121
+ });
122
+
123
+ it('should resolve native module fallbacks correctly', () => {
124
+ process.env.KEYSTONE_RUNTIME_DIR = '/runtime';
125
+
126
+ const existsSpy = spyOn(fs, 'existsSync').mockImplementation(((p: string) =>
127
+ p.includes('onnxruntime_binding.node')) as any);
128
+
129
+ const fallback = resolveNativeModuleFallback('onnxruntime_binding.node', 'some/parent/path');
130
+ expect(fallback).toContain('onnxruntime_binding.node');
131
+ expect(fallback).toContain('/runtime');
132
+
133
+ existsSpy.mockRestore();
134
+ });
135
+
136
+ it('should register native module fallbacks', () => {
137
+ ensureNativeModuleFallbacks();
138
+ // Second call should return early
139
+ ensureNativeModuleFallbacks();
140
+ });
141
+
142
+ it('should register runtime resolver on Bun', () => {
143
+ if (typeof Bun === 'undefined') {
144
+ (global as any).Bun = {
145
+ plugin: Object.assign(
146
+ mock(() => {}),
147
+ { clearAll: mock(() => {}) }
148
+ ),
149
+ };
150
+ }
151
+
152
+ const pluginMock = Object.assign(
153
+ mock(() => {}),
154
+ { clearAll: mock(() => {}) }
155
+ );
156
+ // @ts-ignore
157
+ const pluginSpy = spyOn(Bun, 'plugin').mockImplementation(pluginMock as any);
158
+
159
+ ensureRuntimeResolver();
160
+
161
+ expect(pluginSpy).toHaveBeenCalled();
162
+
163
+ // Second call should return early
164
+ ensureRuntimeResolver();
165
+
166
+ pluginSpy.mockRestore();
167
+ });
168
+
169
+ it('should handle native module fallbacks for sharp and onnxruntime', () => {
170
+ // This is hard to test directly since it modifies Module._resolveFilename
171
+ // but we can verify the fallback function logic
172
+ // @ts-ignore - access private/internal function if possible or just rely on coverage from execution
173
+ // Actually we can just run a chat and see if it triggers the logic
174
+ });
175
+ });
176
+
177
+ describe('LocalEmbeddingAdapter', () => {
178
+ it('should throw error on chat calls', async () => {
179
+ const adapter = new LocalEmbeddingAdapter();
180
+ await expect(adapter.chat([])).rejects.toThrow(
181
+ /Local models in Keystone currently only support memory\/embedding operations/
182
+ );
183
+ });
184
+
185
+ it('should initialize and call embed', async () => {
186
+ // Mock the pipeline import and execution
187
+ const mockPipeline = mock(async () => {
188
+ return async (text: string) => ({
189
+ data: [0.1, 0.2, 0.3],
190
+ });
191
+ });
192
+
193
+ // LocalEmbeddingAdapter uses dynamic import for @xenova/transformers
194
+ // This is hard to mock in Bun without mock.module
195
+ });
196
+ });
197
+
198
+ describe('Platform Helpers', () => {
199
+ it('should identify platforms correctly', () => {
200
+ // These are tested indirectly but let's be explicit
201
+ const platforms = ['darwin', 'linux', 'win32'];
202
+ for (const p of platforms) {
203
+ // @ts-ignore
204
+ process.platform = p;
205
+ // Trigger some logic that uses these
206
+ }
207
+ });
208
+ });
209
+ });