@liquidmetal-ai/precip 1.0.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 (78) hide show
  1. package/.prettierrc +9 -0
  2. package/CHANGELOG.md +8 -0
  3. package/eslint.config.mjs +28 -0
  4. package/package.json +53 -0
  5. package/src/engine/agent.ts +478 -0
  6. package/src/engine/llm-provider.test.ts +275 -0
  7. package/src/engine/llm-provider.ts +330 -0
  8. package/src/engine/stream-parser.ts +170 -0
  9. package/src/index.ts +142 -0
  10. package/src/mounts/mount-manager.test.ts +516 -0
  11. package/src/mounts/mount-manager.ts +327 -0
  12. package/src/mounts/mount-registry.ts +196 -0
  13. package/src/mounts/zod-to-string.test.ts +154 -0
  14. package/src/mounts/zod-to-string.ts +213 -0
  15. package/src/presets/agent-tools.ts +57 -0
  16. package/src/presets/index.ts +5 -0
  17. package/src/sandbox/README.md +1321 -0
  18. package/src/sandbox/bridges/README.md +571 -0
  19. package/src/sandbox/bridges/actor.test.ts +229 -0
  20. package/src/sandbox/bridges/actor.ts +195 -0
  21. package/src/sandbox/bridges/bridge-fixes.test.ts +614 -0
  22. package/src/sandbox/bridges/bucket.test.ts +300 -0
  23. package/src/sandbox/bridges/cleanup-reproduction.test.ts +225 -0
  24. package/src/sandbox/bridges/console-multiple.test.ts +187 -0
  25. package/src/sandbox/bridges/console.test.ts +157 -0
  26. package/src/sandbox/bridges/console.ts +122 -0
  27. package/src/sandbox/bridges/fetch.ts +93 -0
  28. package/src/sandbox/bridges/index.ts +78 -0
  29. package/src/sandbox/bridges/readable-stream.ts +323 -0
  30. package/src/sandbox/bridges/response.test.ts +154 -0
  31. package/src/sandbox/bridges/response.ts +123 -0
  32. package/src/sandbox/bridges/review-fixes.test.ts +331 -0
  33. package/src/sandbox/bridges/search.test.ts +475 -0
  34. package/src/sandbox/bridges/search.ts +264 -0
  35. package/src/sandbox/bridges/shared/body-methods.ts +93 -0
  36. package/src/sandbox/bridges/shared/cleanup.ts +112 -0
  37. package/src/sandbox/bridges/shared/convert.ts +76 -0
  38. package/src/sandbox/bridges/shared/headers.ts +181 -0
  39. package/src/sandbox/bridges/shared/index.ts +36 -0
  40. package/src/sandbox/bridges/shared/json-helpers.ts +77 -0
  41. package/src/sandbox/bridges/shared/path-parser.ts +109 -0
  42. package/src/sandbox/bridges/shared/promise-helper.ts +108 -0
  43. package/src/sandbox/bridges/shared/registry-setup.ts +84 -0
  44. package/src/sandbox/bridges/shared/response-object.ts +280 -0
  45. package/src/sandbox/bridges/shared/result-builder.ts +130 -0
  46. package/src/sandbox/bridges/shared/scope-helpers.ts +44 -0
  47. package/src/sandbox/bridges/shared/stream-reader.ts +90 -0
  48. package/src/sandbox/bridges/storage-bridge.test.ts +893 -0
  49. package/src/sandbox/bridges/storage.ts +421 -0
  50. package/src/sandbox/bridges/text-decoder.ts +190 -0
  51. package/src/sandbox/bridges/text-encoder.ts +102 -0
  52. package/src/sandbox/bridges/types.ts +39 -0
  53. package/src/sandbox/bridges/utils.ts +123 -0
  54. package/src/sandbox/index.ts +6 -0
  55. package/src/sandbox/quickjs-wasm.d.ts +9 -0
  56. package/src/sandbox/sandbox.test.ts +191 -0
  57. package/src/sandbox/sandbox.ts +831 -0
  58. package/src/sandbox/test-helper.ts +43 -0
  59. package/src/sandbox/test-mocks.ts +154 -0
  60. package/src/sandbox/user-stream.test.ts +77 -0
  61. package/src/skills/frontmatter.test.ts +305 -0
  62. package/src/skills/frontmatter.ts +200 -0
  63. package/src/skills/index.ts +9 -0
  64. package/src/skills/skills-loader.test.ts +237 -0
  65. package/src/skills/skills-loader.ts +200 -0
  66. package/src/tools/actor-storage-tools.ts +250 -0
  67. package/src/tools/code-tools.test.ts +199 -0
  68. package/src/tools/code-tools.ts +444 -0
  69. package/src/tools/file-tools.ts +206 -0
  70. package/src/tools/registry.ts +125 -0
  71. package/src/tools/script-tools.ts +145 -0
  72. package/src/tools/smartbucket-tools.ts +203 -0
  73. package/src/tools/sql-tools.ts +213 -0
  74. package/src/tools/tool-factory.ts +119 -0
  75. package/src/types.ts +512 -0
  76. package/tsconfig.eslint.json +5 -0
  77. package/tsconfig.json +15 -0
  78. package/vitest.config.ts +33 -0
@@ -0,0 +1,157 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { executeWithAsyncHost } from '../test-helper.js';
3
+
4
+ describe('Console Bridge', () => {
5
+ it('should capture console.log output', async () => {
6
+ const code = `
7
+ console.log('Hello World');
8
+ return 'done'
9
+ `;
10
+
11
+ const result = await executeWithAsyncHost(code, {});
12
+
13
+ expect(result.success).toBe(true);
14
+ expect(result.consoleOutput).toBeDefined();
15
+ expect(result.consoleOutput).toContain('Hello World');
16
+ expect(result.result).toBe('done');
17
+ });
18
+
19
+ it('should capture multiple console.log calls', async () => {
20
+ const code = `
21
+ console.log('Line 1');
22
+ console.log('Line 2');
23
+ console.log('Line 3');
24
+ return 'complete'
25
+ `;
26
+
27
+ const result = await executeWithAsyncHost(code, {});
28
+
29
+ expect(result.success).toBe(true);
30
+ expect(result.consoleOutput).toHaveLength(3);
31
+ expect(result.consoleOutput).toContain('Line 1');
32
+ expect(result.consoleOutput).toContain('Line 2');
33
+ expect(result.consoleOutput).toContain('Line 3');
34
+ });
35
+
36
+ it('should handle console.log with multiple arguments', async () => {
37
+ const code = `
38
+ console.log('User:', { name: 'John', age: 30 }, 'status:', 'active');
39
+ return 'done'
40
+ `;
41
+
42
+ const result = await executeWithAsyncHost(code, {});
43
+
44
+ expect(result.success).toBe(true);
45
+ expect(result.consoleOutput).toHaveLength(1);
46
+ expect(result.consoleOutput[0]).toContain('User:');
47
+ expect(result.consoleOutput[0]).toContain('name');
48
+ expect(result.consoleOutput[0]).toContain('John');
49
+ });
50
+
51
+ it('should capture console.error with prefix', async () => {
52
+ const code = `
53
+ console.error('Something went wrong');
54
+ return 'done'
55
+ `;
56
+
57
+ const result = await executeWithAsyncHost(code, {});
58
+
59
+ expect(result.success).toBe(true);
60
+ expect(result.consoleOutput).toHaveLength(1);
61
+ expect(result.consoleOutput[0]).toContain('[ERROR]');
62
+ expect(result.consoleOutput[0]).toContain('Something went wrong');
63
+ });
64
+
65
+ it('should capture console.warn with prefix', async () => {
66
+ const code = `
67
+ console.warn('This is a warning');
68
+ return 'done'
69
+ `;
70
+
71
+ const result = await executeWithAsyncHost(code, {});
72
+
73
+ expect(result.success).toBe(true);
74
+ expect(result.consoleOutput).toHaveLength(1);
75
+ expect(result.consoleOutput[0]).toContain('[WARN]');
76
+ expect(result.consoleOutput[0]).toContain('This is a warning');
77
+ });
78
+
79
+ it('should handle console.log in async code', async () => {
80
+ const code = `
81
+ console.log('Before async');
82
+ await (async () => {
83
+ // Simulate async operation
84
+ await Promise.resolve();
85
+ })();
86
+ console.log('After async');
87
+ return 'done'
88
+ `;
89
+
90
+ const result = await executeWithAsyncHost(code, {});
91
+
92
+ expect(result.success).toBe(true);
93
+ expect(result.consoleOutput).toHaveLength(2);
94
+ expect(result.consoleOutput).toContain('Before async');
95
+ expect(result.consoleOutput).toContain('After async');
96
+ });
97
+
98
+ it('should work without any console output', async () => {
99
+ const code = `
100
+ return 'no console'
101
+ `;
102
+
103
+ const result = await executeWithAsyncHost(code, {});
104
+
105
+ expect(result.success).toBe(true);
106
+ // consoleOutput will be an empty array if no logs
107
+ expect(result.consoleOutput || []).toHaveLength(0);
108
+ expect(result.result).toBe('no console');
109
+ });
110
+
111
+ it('should handle null and undefined in console.log', async () => {
112
+ const code = `
113
+ console.log(null, undefined);
114
+ return 'done'
115
+ `;
116
+
117
+ const result = await executeWithAsyncHost(code, {});
118
+
119
+ expect(result.success).toBe(true);
120
+ expect(result.consoleOutput).toHaveLength(1);
121
+ expect(result.consoleOutput[0]).toContain('null');
122
+ expect(result.consoleOutput[0]).toContain('undefined');
123
+ });
124
+
125
+ it('should handle numbers in console.log', async () => {
126
+ const code = `
127
+ console.log('Count:', 42, 'Ratio:', 3.14);
128
+ return 'done'
129
+ `;
130
+
131
+ const result = await executeWithAsyncHost(code, {});
132
+
133
+ expect(result.success).toBe(true);
134
+ expect(result.consoleOutput).toHaveLength(1);
135
+ expect(result.consoleOutput[0]).toContain('Count:');
136
+ expect(result.consoleOutput[0]).toContain('42');
137
+ expect(result.consoleOutput[0]).toContain('3.14');
138
+ });
139
+
140
+ it('should handle arrays in console.log', async () => {
141
+ const code = `
142
+ console.log('Items:', [1, 2, 3, 4, 5]);
143
+ return 'done'
144
+ `;
145
+
146
+ const result = await executeWithAsyncHost(code, {});
147
+
148
+ expect(result.success).toBe(true);
149
+ expect(result.consoleOutput).toHaveLength(1);
150
+ expect(result.consoleOutput[0]).toContain('Items:');
151
+ expect(result.consoleOutput[0]).toContain('1');
152
+ expect(result.consoleOutput[0]).toContain('2');
153
+ expect(result.consoleOutput[0]).toContain('3');
154
+ expect(result.consoleOutput[0]).toContain('4');
155
+ expect(result.consoleOutput[0]).toContain('5');
156
+ });
157
+ });
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Console API bridge - Provides console.log for sandbox
3
+ *
4
+ * Captures console.log output and returns it with the sandbox result
5
+ */
6
+
7
+ import type { QuickJSContext, QuickJSHandle } from 'quickjs-emscripten-core';
8
+ import type { BridgeContext } from './types.js';
9
+
10
+ /** Module-level store for console output, keyed by context (avoids monkey-patching) */
11
+ const consoleOutputMap = new WeakMap<QuickJSContext, string[]>();
12
+
13
+ /**
14
+ * Install console API in the sandbox
15
+ */
16
+ export function installConsole(ctx: BridgeContext): void {
17
+ const { context, logger } = ctx;
18
+
19
+ // Array to store console output
20
+ const output: string[] = [];
21
+ consoleOutputMap.set(context, output);
22
+
23
+ // Helper function to format arguments
24
+ const formatArgs = (...args: any[]): string => {
25
+ return args
26
+ .map(arg => {
27
+ if (arg === null) return 'null';
28
+ if (arg === undefined) return 'undefined';
29
+ if (typeof arg === 'object') {
30
+ try {
31
+ return JSON.stringify(arg, null, 2);
32
+ } catch {
33
+ return '[object Object]';
34
+ }
35
+ }
36
+ return String(arg);
37
+ })
38
+ .join(' ');
39
+ };
40
+
41
+ {
42
+ using consoleHandle = context.newObject();
43
+
44
+ // Create log method (also used as info and debug per spec)
45
+ {
46
+ using logMethod = context.newFunction('log', (...args: QuickJSHandle[]) => {
47
+ const jsArgs = args.map(arg => {
48
+ try {
49
+ return context.dump(arg);
50
+ } catch {
51
+ return '[Error converting argument]';
52
+ }
53
+ });
54
+
55
+ const outputLine = formatArgs(...jsArgs);
56
+ output.push(outputLine);
57
+ logger?.info?.('[Console]', outputLine);
58
+
59
+ return context.undefined;
60
+ });
61
+
62
+ context.setProp(consoleHandle, 'log', logMethod);
63
+ // Alias info and debug to log (per spec, they behave like console.log)
64
+ context.setProp(consoleHandle, 'info', logMethod);
65
+ context.setProp(consoleHandle, 'debug', logMethod);
66
+ }
67
+
68
+ // Create error method
69
+ {
70
+ using errorMethod = context.newFunction('error', (...args: QuickJSHandle[]) => {
71
+ const jsArgs = args.map(arg => {
72
+ try {
73
+ return context.dump(arg);
74
+ } catch {
75
+ return '[Error converting argument]';
76
+ }
77
+ });
78
+
79
+ const outputLine = formatArgs(...jsArgs);
80
+ output.push('[ERROR] ' + outputLine);
81
+ logger?.error?.('[Console]', outputLine);
82
+
83
+ return context.undefined;
84
+ });
85
+
86
+ context.setProp(consoleHandle, 'error', errorMethod);
87
+ }
88
+
89
+ // Create warn method
90
+ {
91
+ using warnMethod = context.newFunction('warn', (...args: QuickJSHandle[]) => {
92
+ const jsArgs = args.map(arg => {
93
+ try {
94
+ return context.dump(arg);
95
+ } catch {
96
+ return '[Error converting argument]';
97
+ }
98
+ });
99
+
100
+ const outputLine = formatArgs(...jsArgs);
101
+ output.push('[WARN] ' + outputLine);
102
+ logger?.warn?.('[Console]', outputLine);
103
+
104
+ return context.undefined;
105
+ });
106
+
107
+ context.setProp(consoleHandle, 'warn', warnMethod);
108
+ }
109
+
110
+ // Set console global
111
+ context.setProp(context.global, 'console', consoleHandle);
112
+ }
113
+
114
+ logger?.info?.('[Bridge] console installed');
115
+ }
116
+
117
+ /**
118
+ * Get console output from context
119
+ */
120
+ export function getConsoleOutput(context: QuickJSContext): string[] {
121
+ return consoleOutputMap.get(context) || [];
122
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Fetch API bridge - Bridges runtime's native fetch with Response, Headers, and Streams
3
+ */
4
+
5
+ import type { QuickJSHandle } from 'quickjs-emscripten-core';
6
+ import type { BridgeContext } from './types.js';
7
+ import {
8
+ withTrackedPromiseResult,
9
+ createResponseObject,
10
+ setupStreamRegistry
11
+ } from './shared/index.js';
12
+
13
+ /**
14
+ * Install fetch API in the sandbox
15
+ * Bridges to runtime's native fetch
16
+ */
17
+ export function installFetch(ctx: BridgeContext): void {
18
+ const { context, logger, cleanupHandlers } = ctx;
19
+
20
+ // Track AbortControllers for pending fetches so we can abort them on cleanup
21
+ const pendingAbortControllers = new Set<AbortController>();
22
+
23
+ // Flag to track if we're disposing - helps avoid operations on disposed context
24
+ let isDisposing = false;
25
+
26
+ // Create response object context for the factory (includes cleanup handler)
27
+ const objCtx = setupStreamRegistry(ctx, 'Fetch');
28
+
29
+ // Register cleanup handler to abort pending fetches (before stream cleanup)
30
+ cleanupHandlers.unshift(async () => {
31
+ isDisposing = true;
32
+
33
+ // Abort all pending fetches
34
+ logger?.info?.(`[Fetch] Aborting ${pendingAbortControllers.size} pending fetch(es)`);
35
+ for (const controller of pendingAbortControllers) {
36
+ try {
37
+ controller.abort('Sandbox cleanup');
38
+ } catch {
39
+ // Ignore abort errors
40
+ }
41
+ }
42
+ pendingAbortControllers.clear();
43
+ });
44
+
45
+ // Install fetch function
46
+ {
47
+ using fetchHandle = context.newFunction('fetch', (...args: QuickJSHandle[]) => {
48
+ const jsArgs = args.map(arg => context.dump(arg));
49
+
50
+ // Create AbortController for this fetch
51
+ const abortController = new AbortController();
52
+ pendingAbortControllers.add(abortController);
53
+
54
+ // Merge user's signal with our abort controller if they provided one
55
+ const userInit = jsArgs[1] || {};
56
+ // Strip signal from userInit — sandbox signals deserialize as plain objects (not real
57
+ // AbortSignal instances), so AbortSignal.any() would throw TypeError. Custom abort
58
+ // signaling from sandbox code is not supported; use fetch timeout instead.
59
+ if (userInit.signal) {
60
+ logger?.warn?.('[Fetch] Stripping unsupported AbortSignal from sandbox fetch init. ' +
61
+ 'Custom AbortSignal is not supported in sandbox code.');
62
+ }
63
+ const { signal: _ignoredSignal, ...safeInit } = userInit;
64
+ const fetchInit = {
65
+ ...safeInit,
66
+ signal: abortController.signal
67
+ };
68
+
69
+ logger?.info?.('[Fetch] fetch() called');
70
+
71
+ return withTrackedPromiseResult(ctx, async () => {
72
+ // Skip if disposing - let cleanup handle it
73
+ if (isDisposing) {
74
+ throw new Error('Sandbox is disposing');
75
+ }
76
+
77
+ try {
78
+ // Use runtime's native fetch with abort support
79
+ const response = await (globalThis.fetch as any)(jsArgs[0], fetchInit);
80
+
81
+ // Create QuickJS Response object using the unified factory
82
+ return createResponseObject(ctx, { response }, objCtx);
83
+ } finally {
84
+ pendingAbortControllers.delete(abortController);
85
+ }
86
+ });
87
+ });
88
+
89
+ context.setProp(context.global, 'fetch', fetchHandle);
90
+ }
91
+
92
+ logger?.info?.('[Bridge] fetch installed');
93
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * QuickJS Bridge System
3
+ *
4
+ * Modular bridge implementations for exposing runtime APIs to QuickJS sandbox.
5
+ */
6
+
7
+ export * from './types.js';
8
+ export * from './utils.js';
9
+ export * from './shared/index.js';
10
+ export { installTextEncoder } from './text-encoder.js';
11
+ export { installTextDecoder } from './text-decoder.js';
12
+ export { installFetch } from './fetch.js';
13
+ export { installResponse } from './response.js';
14
+ export { installConsole, getConsoleOutput } from './console.js';
15
+ export { createStreamObject, createReaderObject } from './readable-stream.js';
16
+ export { installStorage, type StorageMountInfo } from './storage.js';
17
+ export { installSearch, type SearchMountInfo } from './search.js';
18
+ export { installActor } from './actor.js';
19
+
20
+ import type { BridgeContext, PromiseTracker } from './types.js';
21
+ import type { Logger } from '../../types.js';
22
+ import { installTextEncoder } from './text-encoder.js';
23
+ import { installTextDecoder } from './text-decoder.js';
24
+ import { installFetch } from './fetch.js';
25
+ import { installResponse } from './response.js';
26
+ import { installConsole } from './console.js';
27
+
28
+ /**
29
+ * Install all standard bridges (fetch, Response, console, TextEncoder, TextDecoder)
30
+ */
31
+ export function installAllBridges(ctx: BridgeContext): void {
32
+ installFetch(ctx);
33
+ installResponse(ctx);
34
+ installConsole(ctx);
35
+ installTextEncoder(ctx);
36
+ installTextDecoder(ctx);
37
+
38
+ ctx.logger?.info?.('[Bridge] All bridges installed');
39
+ }
40
+
41
+ /**
42
+ * Create a promise tracker for async operations
43
+ */
44
+ export function createPromiseTracker(): PromiseTracker {
45
+ let notifyNewPromise: () => void;
46
+ const newPromiseSignal = new Promise<void>(resolve => {
47
+ notifyNewPromise = resolve;
48
+ });
49
+
50
+ return {
51
+ pendingPromises: new Set(),
52
+ notifyNewPromise: notifyNewPromise!,
53
+ newPromiseSignal,
54
+ deferredPromises: new Set()
55
+ };
56
+ }
57
+
58
+ /**
59
+ * Run all cleanup handlers registered by bridges
60
+ */
61
+ export async function runCleanupHandlers(
62
+ handlers: Array<() => void | Promise<void>>,
63
+ logger?: Logger
64
+ ): Promise<void> {
65
+ logger?.info?.(`[Bridge] Running ${handlers.length} cleanup handlers`);
66
+
67
+ for (let i = 0; i < handlers.length; i++) {
68
+ try {
69
+ const handler = handlers[i]!;
70
+ await handler();
71
+ logger?.info?.(`[Bridge] Cleanup handler ${i + 1}/${handlers.length} completed`);
72
+ } catch (e) {
73
+ logger?.warn?.(`[Bridge] Cleanup handler ${i + 1}/${handlers.length} failed: ${e}`);
74
+ }
75
+ }
76
+
77
+ logger?.info?.('[Bridge] All cleanup handlers completed');
78
+ }