@superatomai/sdk-node 0.0.2 → 0.0.3-dsp

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.
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,286 @@
1
+ "use strict";
2
+
3
+ // src/userResponse/scripts/script-bootstrap.ts
4
+ var import_url = require("url");
5
+
6
+ // src/userResponse/scripts/script-ipc.ts
7
+ function encodeMessage(msg) {
8
+ return JSON.stringify(msg) + "\n";
9
+ }
10
+ var LineOverflowError = class extends Error {
11
+ constructor(buffered, limit) {
12
+ super(`IPC message exceeded ${limit} bytes without a newline (${buffered} buffered) \u2014 aborting to avoid unbounded memory.`);
13
+ this.buffered = buffered;
14
+ this.limit = limit;
15
+ this.name = "LineOverflowError";
16
+ }
17
+ };
18
+ var DEFAULT_MAX_IPC_BYTES = (() => {
19
+ const envVal = Number(process.env.SCRIPT_MAX_IPC_BYTES);
20
+ return Number.isFinite(envVal) && envVal > 0 ? Math.floor(envVal) : 64 * 1024 * 1024;
21
+ })();
22
+ var LineSplitter = class {
23
+ constructor(maxBytes = DEFAULT_MAX_IPC_BYTES) {
24
+ this.maxBytes = maxBytes;
25
+ this.buffer = "";
26
+ }
27
+ push(chunk) {
28
+ this.buffer += chunk;
29
+ const lines = [];
30
+ let idx;
31
+ while ((idx = this.buffer.indexOf("\n")) !== -1) {
32
+ const line = this.buffer.slice(0, idx);
33
+ this.buffer = this.buffer.slice(idx + 1);
34
+ if (line.length > 0) lines.push(line);
35
+ }
36
+ if (this.buffer.length > this.maxBytes) {
37
+ const buffered = this.buffer.length;
38
+ this.buffer = "";
39
+ throw new LineOverflowError(buffered, this.maxBytes);
40
+ }
41
+ return lines;
42
+ }
43
+ /** Flush any remaining partial data (useful on stream close) */
44
+ flush() {
45
+ if (this.buffer.length === 0) return null;
46
+ const rest = this.buffer;
47
+ this.buffer = "";
48
+ return rest;
49
+ }
50
+ };
51
+
52
+ // src/userResponse/scripts/script-bootstrap.ts
53
+ var SCRIPT_ARG = process.argv[2];
54
+ if (!SCRIPT_ARG) {
55
+ sendError("Bootstrap was invoked without a script path.", "compile");
56
+ process.exit(1);
57
+ }
58
+ var BRIDGE_URL = process.env.SCRIPT_BRIDGE_URL;
59
+ var executedQueries = [];
60
+ var PREVIEW_MAX_ROWS = 10;
61
+ var PREVIEW_MAX_CHARS = 200;
62
+ function previewRows(data) {
63
+ return data.slice(0, PREVIEW_MAX_ROWS).map((row) => {
64
+ if (!row || typeof row !== "object") return row;
65
+ const out = {};
66
+ for (const [k, v] of Object.entries(row)) {
67
+ out[k] = typeof v === "string" && v.length > PREVIEW_MAX_CHARS ? v.slice(0, PREVIEW_MAX_CHARS) + "\u2026" : v;
68
+ }
69
+ return out;
70
+ });
71
+ }
72
+ var initResolve = null;
73
+ var initPromise = new Promise((resolve) => {
74
+ initResolve = resolve;
75
+ });
76
+ function buildCtx(init) {
77
+ return {
78
+ now: init.now,
79
+ async query(toolId, sql) {
80
+ if (!BRIDGE_URL) throw new Error("SCRIPT_BRIDGE_URL is not set \u2014 script bridge server is not running");
81
+ const startedAt = Date.now();
82
+ send({ type: "stream", chunk: `
83
+ \u{1F4DD} **Querying ${toolId}:**
84
+ \`\`\`sql
85
+ ${sql}
86
+ \`\`\`
87
+
88
+ ` });
89
+ send({ type: "stream", chunk: `__QUERY_TIMER_START_Executing query__` });
90
+ const res = await fetch(`${BRIDGE_URL}/query`, {
91
+ method: "POST",
92
+ headers: { "content-type": "application/json" },
93
+ body: JSON.stringify({ toolId, sql })
94
+ });
95
+ const json = await res.json();
96
+ const executionTimeMs = Date.now() - startedAt;
97
+ send({ type: "stream", chunk: `__QUERY_TIMER_DONE_${(executionTimeMs / 1e3).toFixed(1)}__
98
+
99
+ ` });
100
+ if (!res.ok || !json.success) {
101
+ const errMsg = json.error || `Bridge query failed (${res.status})`;
102
+ send({ type: "stream", chunk: `\u274C **Query failed on ${toolId}:** ${errMsg}
103
+
104
+ ` });
105
+ throw new Error(errMsg);
106
+ }
107
+ const data = json.data ?? [];
108
+ const count = json.count ?? data.length;
109
+ send({ type: "stream", chunk: `\u2705 **${count} rows from ${toolId}**
110
+
111
+ ` });
112
+ if (data.length > 0) {
113
+ send({ type: "stream", chunk: `<DataTable>${JSON.stringify(previewRows(data))}</DataTable>
114
+
115
+ ` });
116
+ }
117
+ executedQueries.push({
118
+ sourceId: toolId,
119
+ sourceName: toolId,
120
+ // enriched by runner after result arrives
121
+ sql,
122
+ data,
123
+ count,
124
+ executionTimeMs
125
+ });
126
+ return { data, count };
127
+ },
128
+ /**
129
+ * Call a tool with structured params (typically a direct tool whose
130
+ * fn(params) does not expect a SQL string). Returns whatever the tool
131
+ * returned — shape is up to the tool.
132
+ *
133
+ * Use this when you need a pre-built business-logic tool (e.g.,
134
+ * "Calculate Shelf Life") that takes structured input rather than SQL.
135
+ * For SQL source tools, keep using ctx.query.
136
+ *
137
+ * runTool results do NOT auto-flow into executedQueries. If you want
138
+ * the result visualised, register it via ctx.emit() after extracting
139
+ * the array you want to chart.
140
+ */
141
+ async runTool(toolId, params = {}) {
142
+ if (!BRIDGE_URL) throw new Error("SCRIPT_BRIDGE_URL is not set \u2014 script bridge server is not running");
143
+ const res = await fetch(`${BRIDGE_URL}/run-tool`, {
144
+ method: "POST",
145
+ headers: { "content-type": "application/json" },
146
+ body: JSON.stringify({ toolId, params })
147
+ });
148
+ const json = await res.json();
149
+ if (!res.ok || !json.success) throw new Error(json.error || `Bridge run-tool failed (${res.status})`);
150
+ return json.result;
151
+ },
152
+ /**
153
+ * Register a computed dataset. Flows into executedQueries alongside
154
+ * real SQL query results so component generation can render post-SQL
155
+ * transforms (weighted averages, clustering, joins) as charts.
156
+ */
157
+ emit(name, rows, meta) {
158
+ executedQueries.push({
159
+ sourceId: `computed:${name}`,
160
+ sourceName: meta?.description || name,
161
+ sql: `-- computed dataset: ${name}`,
162
+ data: Array.isArray(rows) ? rows : [],
163
+ count: Array.isArray(rows) ? rows.length : 0,
164
+ executionTimeMs: 0,
165
+ virtual: true
166
+ });
167
+ },
168
+ /**
169
+ * Forward a short progress message to the parent's StreamBuffer so the UI
170
+ * continues to see per-script feedback during execution.
171
+ */
172
+ log(chunk) {
173
+ send({ type: "stream", chunk });
174
+ }
175
+ };
176
+ }
177
+ function send(msg) {
178
+ process.stdout.write(encodeMessage(msg));
179
+ }
180
+ function sendError(message, phase, stack) {
181
+ send({ type: "error", message, stack, phase });
182
+ }
183
+ var splitter = new LineSplitter();
184
+ process.stdin.setEncoding("utf-8");
185
+ process.stdin.on("data", (chunk) => {
186
+ let lines;
187
+ try {
188
+ lines = splitter.push(chunk);
189
+ } catch (err) {
190
+ const msg = err instanceof Error ? err.message : String(err);
191
+ sendError(msg, "runtime");
192
+ process.exit(1);
193
+ }
194
+ for (const line of lines) {
195
+ let msg;
196
+ try {
197
+ msg = JSON.parse(line);
198
+ } catch {
199
+ continue;
200
+ }
201
+ handleMessage(msg);
202
+ }
203
+ });
204
+ function handleMessage(msg) {
205
+ switch (msg.type) {
206
+ case "init": {
207
+ if (initResolve) {
208
+ const now = new Date(msg.now || (/* @__PURE__ */ new Date()).toISOString());
209
+ const params = restoreParamTypes(msg.params || {}, msg.paramSchema);
210
+ initResolve({ params, now });
211
+ initResolve = null;
212
+ }
213
+ break;
214
+ }
215
+ }
216
+ }
217
+ (async () => {
218
+ let getData;
219
+ try {
220
+ const mod = await import((0, import_url.pathToFileURL)(SCRIPT_ARG).href);
221
+ getData = mod.getData ?? mod.default;
222
+ if (typeof getData !== "function") {
223
+ throw new Error(
224
+ `Script at ${SCRIPT_ARG} does not export getData \u2014 expected \`export async function getData(ctx, params)\``
225
+ );
226
+ }
227
+ } catch (err) {
228
+ const e = err;
229
+ sendError(e.message || String(err), "compile", e.stack);
230
+ process.exit(1);
231
+ return;
232
+ }
233
+ const init = await initPromise;
234
+ const ctx = buildCtx(init);
235
+ const startedAt = Date.now();
236
+ try {
237
+ const raw = await getData(ctx, init.params);
238
+ const executionTimeMs = Date.now() - startedAt;
239
+ const data = normalizeResultData(raw);
240
+ const count = typeof raw?.count === "number" ? raw.count : data.length;
241
+ const sampleRows = data.slice(0, 5);
242
+ const result = {
243
+ type: "result",
244
+ data,
245
+ count,
246
+ executedQueries,
247
+ executionTimeMs,
248
+ sampleRows
249
+ };
250
+ send(result);
251
+ process.stdout.once("drain", () => process.exit(0));
252
+ setImmediate(() => process.exit(0));
253
+ } catch (err) {
254
+ const e = err;
255
+ sendError(e.message || String(err), "runtime", e.stack);
256
+ process.exit(1);
257
+ }
258
+ })();
259
+ function normalizeResultData(raw) {
260
+ if (!raw) return [];
261
+ if (Array.isArray(raw)) return raw;
262
+ if (Array.isArray(raw.data)) return raw.data;
263
+ if (raw.data && typeof raw.data === "object") return [raw.data];
264
+ return [];
265
+ }
266
+ function restoreParamTypes(params, schema) {
267
+ if (!schema) return params;
268
+ const out = { ...params };
269
+ for (const [name, type] of Object.entries(schema)) {
270
+ const v = out[name];
271
+ if (v === void 0 || v === null) continue;
272
+ if (type === "date" && typeof v === "string") {
273
+ const d = new Date(v);
274
+ if (!isNaN(d.getTime())) out[name] = d;
275
+ } else if (type === "date_range" && typeof v === "object") {
276
+ const from = v.from;
277
+ const to = v.to;
278
+ out[name] = {
279
+ from: typeof from === "string" ? new Date(from) : from,
280
+ to: typeof to === "string" ? new Date(to) : to
281
+ };
282
+ }
283
+ }
284
+ return out;
285
+ }
286
+ //# sourceMappingURL=script-bootstrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/userResponse/scripts/script-bootstrap.ts","../../../src/userResponse/scripts/script-ipc.ts"],"sourcesContent":["/**\n * script-bootstrap.ts — Child-side entrypoint for subprocess script execution.\n *\n * Runs as a separate process spawned by ScriptRunner via tsx. Reads the\n * target user-script path from argv[2], listens for INIT on stdin, builds\n * the ctx object, dynamic-imports the user script's `getData` export,\n * and reports the result (or error) back via stdout.\n *\n * See backend/docs/SCRIPT-FLOW-IMPLEMENTATION.md § IPC Bridge.\n */\n\nimport { pathToFileURL } from 'url';\nimport {\n\tencodeMessage,\n\tLineSplitter,\n\ttype ChildToParentMessage,\n\ttype ParentToChildMessage,\n\ttype ExecutedQuery,\n} from './script-ipc';\n\nconst SCRIPT_ARG = process.argv[2];\nif (!SCRIPT_ARG) {\n\tsendError('Bootstrap was invoked without a script path.', 'compile');\n\tprocess.exit(1);\n}\n\nconst BRIDGE_URL = process.env.SCRIPT_BRIDGE_URL;\n\n/** Completed queries — reported to the parent as part of RESULT */\nconst executedQueries: ExecutedQuery[] = [];\n\n// Stream-preview limits — kept in sync with the SDK's STREAM_PREVIEW_* constants.\n// Inlined (not imported) so this child-process bundle stays minimal.\nconst PREVIEW_MAX_ROWS = 10;\nconst PREVIEW_MAX_CHARS = 200;\n\n/** Bounded, char-truncated preview of result rows for the live <DataTable> stream. */\nfunction previewRows(data: any[]): any[] {\n\treturn data.slice(0, PREVIEW_MAX_ROWS).map((row) => {\n\t\tif (!row || typeof row !== 'object') return row;\n\t\tconst out: Record<string, any> = {};\n\t\tfor (const [k, v] of Object.entries(row)) {\n\t\t\tout[k] = (typeof v === 'string' && v.length > PREVIEW_MAX_CHARS)\n\t\t\t\t? v.slice(0, PREVIEW_MAX_CHARS) + '…'\n\t\t\t\t: v;\n\t\t}\n\t\treturn out;\n\t});\n}\n\n/** Resolvers for the INIT handshake — getData waits for this */\nlet initResolve: ((init: { params: Record<string, any>; now: Date }) => void) | null = null;\nconst initPromise = new Promise<{ params: Record<string, any>; now: Date }>((resolve) => {\n\tinitResolve = resolve;\n});\n\n/**\n * Source name lookup is not available in the child (the parent holds the\n * ExternalTool list). We use toolId as sourceName and let the parent fill\n * it in when building the final ScriptResult, if needed.\n */\nfunction buildCtx(init: { params: Record<string, any>; now: Date }) {\n\treturn {\n\t\tnow: init.now,\n\n\t\tasync query(toolId: string, sql: string): Promise<{ data: any[]; count: number }> {\n\t\t\tif (!BRIDGE_URL) throw new Error('SCRIPT_BRIDGE_URL is not set — script bridge server is not running');\n\n\t\t\tconst startedAt = Date.now();\n\n\t\t\tsend({ type: 'stream', chunk: `\\n📝 **Querying ${toolId}:**\\n\\`\\`\\`sql\\n${sql}\\n\\`\\`\\`\\n\\n` });\n\t\t\tsend({ type: 'stream', chunk: `__QUERY_TIMER_START_Executing query__` });\n\n\t\t\tconst res = await fetch(`${BRIDGE_URL}/query`, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'content-type': 'application/json' },\n\t\t\t\tbody: JSON.stringify({ toolId, sql }),\n\t\t\t});\n\n\t\t\tconst json = await res.json() as any;\n\t\t\tconst executionTimeMs = Date.now() - startedAt;\n\n\t\t\tsend({ type: 'stream', chunk: `__QUERY_TIMER_DONE_${(executionTimeMs / 1000).toFixed(1)}__\\n\\n` });\n\n\t\t\tif (!res.ok || !json.success) {\n\t\t\t\tconst errMsg = json.error || `Bridge query failed (${res.status})`;\n\t\t\t\tsend({ type: 'stream', chunk: `❌ **Query failed on ${toolId}:** ${errMsg}\\n\\n` });\n\t\t\t\tthrow new Error(errMsg);\n\t\t\t}\n\n\t\t\tconst data: any[] = json.data ?? [];\n\t\t\tconst count: number = json.count ?? data.length;\n\n\t\t\tsend({ type: 'stream', chunk: `✅ **${count} rows from ${toolId}**\\n\\n` });\n\t\t\tif (data.length > 0) {\n\t\t\t\tsend({ type: 'stream', chunk: `<DataTable>${JSON.stringify(previewRows(data))}</DataTable>\\n\\n` });\n\t\t\t}\n\n\t\t\texecutedQueries.push({\n\t\t\t\tsourceId: toolId,\n\t\t\t\tsourceName: toolId, // enriched by runner after result arrives\n\t\t\t\tsql,\n\t\t\t\tdata,\n\t\t\t\tcount,\n\t\t\t\texecutionTimeMs,\n\t\t\t});\n\n\t\t\treturn { data, count };\n\t\t},\n\n\t\t/**\n\t\t * Call a tool with structured params (typically a direct tool whose\n\t\t * fn(params) does not expect a SQL string). Returns whatever the tool\n\t\t * returned — shape is up to the tool.\n\t\t *\n\t\t * Use this when you need a pre-built business-logic tool (e.g.,\n\t\t * \"Calculate Shelf Life\") that takes structured input rather than SQL.\n\t\t * For SQL source tools, keep using ctx.query.\n\t\t *\n\t\t * runTool results do NOT auto-flow into executedQueries. If you want\n\t\t * the result visualised, register it via ctx.emit() after extracting\n\t\t * the array you want to chart.\n\t\t */\n\t\tasync runTool<T = any>(toolId: string, params: Record<string, any> = {}): Promise<T> {\n\t\t\tif (!BRIDGE_URL) throw new Error('SCRIPT_BRIDGE_URL is not set — script bridge server is not running');\n\n\t\t\tconst res = await fetch(`${BRIDGE_URL}/run-tool`, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'content-type': 'application/json' },\n\t\t\t\tbody: JSON.stringify({ toolId, params }),\n\t\t\t});\n\n\t\t\tconst json = await res.json() as any;\n\t\t\tif (!res.ok || !json.success) throw new Error(json.error || `Bridge run-tool failed (${res.status})`);\n\n\t\t\treturn json.result as T;\n\t\t},\n\n\t\t/**\n\t\t * Register a computed dataset. Flows into executedQueries alongside\n\t\t * real SQL query results so component generation can render post-SQL\n\t\t * transforms (weighted averages, clustering, joins) as charts.\n\t\t */\n\t\temit(name: string, rows: any[], meta?: { description?: string }): void {\n\t\t\texecutedQueries.push({\n\t\t\t\tsourceId: `computed:${name}`,\n\t\t\t\tsourceName: meta?.description || name,\n\t\t\t\tsql: `-- computed dataset: ${name}`,\n\t\t\t\tdata: Array.isArray(rows) ? rows : [],\n\t\t\t\tcount: Array.isArray(rows) ? rows.length : 0,\n\t\t\t\texecutionTimeMs: 0,\n\t\t\t\tvirtual: true,\n\t\t\t});\n\t\t},\n\n\t\t/**\n\t\t * Forward a short progress message to the parent's StreamBuffer so the UI\n\t\t * continues to see per-script feedback during execution.\n\t\t */\n\t\tlog(chunk: string): void {\n\t\t\tsend({ type: 'stream', chunk });\n\t\t},\n\t};\n}\n\nfunction send(msg: ChildToParentMessage): void {\n\tprocess.stdout.write(encodeMessage(msg));\n}\n\nfunction sendError(message: string, phase: 'compile' | 'runtime', stack?: string): void {\n\tsend({ type: 'error', message, stack, phase });\n}\n\n// ============================================\n// stdin reader — parse NDJSON messages from the parent (INIT only)\n// ============================================\n\nconst splitter = new LineSplitter();\n\nprocess.stdin.setEncoding('utf-8');\nprocess.stdin.on('data', (chunk: string) => {\n\tlet lines: string[];\n\ttry {\n\t\tlines = splitter.push(chunk);\n\t} catch (err) {\n\t\tconst msg = err instanceof Error ? err.message : String(err);\n\t\tsendError(msg, 'runtime');\n\t\tprocess.exit(1);\n\t}\n\tfor (const line of lines) {\n\t\tlet msg: ParentToChildMessage;\n\t\ttry {\n\t\t\tmsg = JSON.parse(line);\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\thandleMessage(msg);\n\t}\n});\n\nfunction handleMessage(msg: ParentToChildMessage): void {\n\tswitch (msg.type) {\n\t\tcase 'init': {\n\t\t\tif (initResolve) {\n\t\t\t\tconst now = new Date(msg.now || new Date().toISOString());\n\t\t\t\tconst params = restoreParamTypes(msg.params || {}, msg.paramSchema);\n\t\t\t\tinitResolve({ params, now });\n\t\t\t\tinitResolve = null;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n// ============================================\n// Main — import the user script and run getData\n// ============================================\n\n(async () => {\n\tlet getData: (ctx: any, params: any) => Promise<any>;\n\n\ttry {\n\t\t// Dynamic import — tsx transpiles the user .ts file on the fly\n\t\tconst mod = await import(pathToFileURL(SCRIPT_ARG).href);\n\t\tgetData = mod.getData ?? mod.default;\n\t\tif (typeof getData !== 'function') {\n\t\t\tthrow new Error(\n\t\t\t\t`Script at ${SCRIPT_ARG} does not export getData — expected \\`export async function getData(ctx, params)\\``,\n\t\t\t);\n\t\t}\n\t} catch (err) {\n\t\tconst e = err as Error;\n\t\tsendError(e.message || String(err), 'compile', e.stack);\n\t\tprocess.exit(1);\n\t\treturn;\n\t}\n\n\tconst init = await initPromise;\n\tconst ctx = buildCtx(init);\n\tconst startedAt = Date.now();\n\n\ttry {\n\t\tconst raw = await getData(ctx, init.params);\n\t\tconst executionTimeMs = Date.now() - startedAt;\n\n\t\tconst data = normalizeResultData(raw);\n\t\tconst count = typeof raw?.count === 'number' ? raw.count : data.length;\n\t\tconst sampleRows = data.slice(0, 5);\n\n\t\tconst result: ChildToParentMessage = {\n\t\t\ttype: 'result',\n\t\t\tdata,\n\t\t\tcount,\n\t\t\texecutedQueries,\n\t\t\texecutionTimeMs,\n\t\t\tsampleRows,\n\t\t};\n\t\tsend(result);\n\t\t// Give stdout a tick to flush before we exit\n\t\tprocess.stdout.once('drain', () => process.exit(0));\n\t\tsetImmediate(() => process.exit(0));\n\t} catch (err) {\n\t\tconst e = err as Error;\n\t\tsendError(e.message || String(err), 'runtime', e.stack);\n\t\tprocess.exit(1);\n\t}\n})();\n\n/**\n * Normalize whatever getData returned into an array. Mirrors the old\n * ScriptRunner semantics: direct array, { data: [] }, or { data: {...} }\n * wrapped as single-element array.\n */\nfunction normalizeResultData(raw: any): any[] {\n\tif (!raw) return [];\n\tif (Array.isArray(raw)) return raw;\n\tif (Array.isArray(raw.data)) return raw.data;\n\tif (raw.data && typeof raw.data === 'object') return [raw.data];\n\treturn [];\n}\n\n/**\n * Restore typed values that JSON stripped during IPC transport. Date and\n * date_range params arrive as ISO strings; re-wrap them so scripts can call\n * `.toISOString()` and other Date methods the same way they would in-process.\n */\nfunction restoreParamTypes(\n\tparams: Record<string, any>,\n\tschema?: Record<string, 'string' | 'number' | 'date' | 'date_range' | 'enum' | 'boolean'>,\n): Record<string, any> {\n\tif (!schema) return params;\n\tconst out = { ...params };\n\tfor (const [name, type] of Object.entries(schema)) {\n\t\tconst v = out[name];\n\t\tif (v === undefined || v === null) continue;\n\t\tif (type === 'date' && typeof v === 'string') {\n\t\t\tconst d = new Date(v);\n\t\t\tif (!isNaN(d.getTime())) out[name] = d;\n\t\t} else if (type === 'date_range' && typeof v === 'object') {\n\t\t\tconst from = v.from;\n\t\t\tconst to = v.to;\n\t\t\tout[name] = {\n\t\t\t\tfrom: typeof from === 'string' ? new Date(from) : from,\n\t\t\t\tto: typeof to === 'string' ? new Date(to) : to,\n\t\t\t};\n\t\t}\n\t}\n\treturn out;\n}\n","/**\n * script-ipc.ts — Protocol definitions for parent/child IPC.\n *\n * Transport: newline-delimited JSON over the child's stdin/stdout.\n * stderr is passed through for uncaught exceptions and compile errors.\n *\n * See backend/docs/SCRIPT-FLOW-IMPLEMENTATION.md § IPC Bridge.\n */\n\n// ============================================\n// Parent → Child\n// ============================================\n\nexport interface InitMessage {\n\ttype: 'init';\n\t/**\n\t * Params passed to getData(). Date/date_range values cross the IPC boundary\n\t * as ISO strings and are restored to Date objects in the child via paramSchema.\n\t */\n\tparams: Record<string, any>;\n\t/** Frozen timestamp for ctx.now — ISO string */\n\tnow: string;\n\t/**\n\t * Maps each parameter name to its declared type so the child can restore\n\t * Date objects (lost during JSON serialization).\n\t */\n\tparamSchema?: Record<string, 'string' | 'number' | 'date' | 'date_range' | 'enum' | 'boolean'>;\n}\n\nexport interface QueryResultMessage {\n\ttype: 'query_result';\n\t/** Matches the `id` from the child's QUERY message */\n\tid: string;\n\tdata: any[];\n\tcount: number;\n\tmetadata?: {\n\t\ttotalCount?: number;\n\t\texecutionTimeMs?: number;\n\t};\n}\n\nexport interface QueryErrorMessage {\n\ttype: 'query_error';\n\tid: string;\n\terror: string;\n}\n\n/**\n * Response to a `run_tool` request — carries whatever the direct-tool's fn()\n * returned. Shape is arbitrary; the script is expected to know what its chosen\n * tool yields and unwrap accordingly.\n */\nexport interface RunToolResultMessage {\n\ttype: 'run_tool_result';\n\tid: string;\n\tresult: any;\n}\n\nexport type ParentToChildMessage =\n\t| InitMessage\n\t| QueryResultMessage\n\t| QueryErrorMessage\n\t| RunToolResultMessage;\n\n// ============================================\n// Child → Parent\n// ============================================\n\nexport interface QueryMessage {\n\ttype: 'query';\n\t/** Short uuid — parent uses this to correlate its response */\n\tid: string;\n\ttoolId: string;\n\tsql: string;\n}\n\n/**\n * Run an arbitrary tool with structured params. Used by ctx.runTool() to call\n * direct tools (and any tool whose `fn(params)` doesn't expect a SQL string).\n * Reply is RunToolResultMessage or QueryErrorMessage.\n */\nexport interface RunToolMessage {\n\ttype: 'run_tool';\n\tid: string;\n\ttoolId: string;\n\tparams: Record<string, any>;\n}\n\nexport interface StreamMessage {\n\ttype: 'stream';\n\tchunk: string;\n}\n\nexport interface ExecutedQuery {\n\tsourceId: string;\n\tsourceName: string;\n\tsql: string;\n\tdata: any[];\n\tcount: number;\n\ttotalCount?: number;\n\texecutionTimeMs: number;\n\t/**\n\t * When the script registered a computed dataset via ctx.emit(), this is\n\t * marked virtual so component generation can distinguish it from a real SQL query.\n\t */\n\tvirtual?: boolean;\n}\n\nexport interface ResultMessage {\n\ttype: 'result';\n\tdata: any[];\n\tcount: number;\n\texecutedQueries: ExecutedQuery[];\n\texecutionTimeMs: number;\n\t/** First 5 rows of the final `data` — lets the parent judge intent-match cheaply */\n\tsampleRows: any[];\n}\n\nexport interface ErrorMessage {\n\ttype: 'error';\n\tmessage: string;\n\tstack?: string;\n\t/** \"compile\" → tsx couldn't parse; \"runtime\" → thrown during getData */\n\tphase?: 'compile' | 'runtime';\n}\n\nexport type ChildToParentMessage =\n\t| QueryMessage\n\t| RunToolMessage\n\t| StreamMessage\n\t| ResultMessage\n\t| ErrorMessage;\n\n// ============================================\n// Utilities\n// ============================================\n\n/**\n * Encode a message for transport: JSON + newline.\n */\nexport function encodeMessage(msg: ParentToChildMessage | ChildToParentMessage): string {\n\treturn JSON.stringify(msg) + '\\n';\n}\n\n/**\n * Thrown when a single NDJSON line grows past the byte ceiling without a\n * newline — a runaway writer or a pathologically large message. Callers abort\n * (kill the child / exit) instead of buffering toward OOM. See #9 in\n * backend/docs/SCRIPT-FLOW-SCALING-ISSUES.md.\n */\nexport class LineOverflowError extends Error {\n\tconstructor(public readonly buffered: number, public readonly limit: number) {\n\t\tsuper(`IPC message exceeded ${limit} bytes without a newline (${buffered} buffered) — aborting to avoid unbounded memory.`);\n\t\tthis.name = 'LineOverflowError';\n\t}\n}\n\n/** Default per-message ceiling — generous enough for large result sets, low\n * enough to catch a runaway/infinite writer. Override via SCRIPT_MAX_IPC_BYTES. */\nconst DEFAULT_MAX_IPC_BYTES = (() => {\n\tconst envVal = Number(process.env.SCRIPT_MAX_IPC_BYTES);\n\treturn Number.isFinite(envVal) && envVal > 0 ? Math.floor(envVal) : 64 * 1024 * 1024;\n})();\n\n/**\n * Split a stream of data chunks on newlines and emit each complete JSON line.\n * Partial lines are buffered until the next chunk arrives, up to `maxBytes` —\n * past which `push` throws `LineOverflowError` rather than grow unbounded.\n */\nexport class LineSplitter {\n\tprivate buffer = '';\n\n\tconstructor(private readonly maxBytes: number = DEFAULT_MAX_IPC_BYTES) {}\n\n\tpush(chunk: string): string[] {\n\t\tthis.buffer += chunk;\n\t\tconst lines: string[] = [];\n\t\tlet idx: number;\n\t\twhile ((idx = this.buffer.indexOf('\\n')) !== -1) {\n\t\t\tconst line = this.buffer.slice(0, idx);\n\t\t\tthis.buffer = this.buffer.slice(idx + 1);\n\t\t\tif (line.length > 0) lines.push(line);\n\t\t}\n\t\t// A partial line past the ceiling means no newline is coming (or the\n\t\t// message is pathologically large). Drop the buffer and signal abort.\n\t\tif (this.buffer.length > this.maxBytes) {\n\t\t\tconst buffered = this.buffer.length;\n\t\t\tthis.buffer = '';\n\t\t\tthrow new LineOverflowError(buffered, this.maxBytes);\n\t\t}\n\t\treturn lines;\n\t}\n\n\t/** Flush any remaining partial data (useful on stream close) */\n\tflush(): string | null {\n\t\tif (this.buffer.length === 0) return null;\n\t\tconst rest = this.buffer;\n\t\tthis.buffer = '';\n\t\treturn rest;\n\t}\n}\n\n/**\n * Generate a short correlation id for in-flight queries.\n * Collision within a single execution is astronomically unlikely.\n */\nexport function shortId(): string {\n\treturn Math.random().toString(36).slice(2, 10);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,284 @@
1
+ // src/userResponse/scripts/script-bootstrap.ts
2
+ import { pathToFileURL } from "url";
3
+
4
+ // src/userResponse/scripts/script-ipc.ts
5
+ function encodeMessage(msg) {
6
+ return JSON.stringify(msg) + "\n";
7
+ }
8
+ var LineOverflowError = class extends Error {
9
+ constructor(buffered, limit) {
10
+ super(`IPC message exceeded ${limit} bytes without a newline (${buffered} buffered) \u2014 aborting to avoid unbounded memory.`);
11
+ this.buffered = buffered;
12
+ this.limit = limit;
13
+ this.name = "LineOverflowError";
14
+ }
15
+ };
16
+ var DEFAULT_MAX_IPC_BYTES = (() => {
17
+ const envVal = Number(process.env.SCRIPT_MAX_IPC_BYTES);
18
+ return Number.isFinite(envVal) && envVal > 0 ? Math.floor(envVal) : 64 * 1024 * 1024;
19
+ })();
20
+ var LineSplitter = class {
21
+ constructor(maxBytes = DEFAULT_MAX_IPC_BYTES) {
22
+ this.maxBytes = maxBytes;
23
+ this.buffer = "";
24
+ }
25
+ push(chunk) {
26
+ this.buffer += chunk;
27
+ const lines = [];
28
+ let idx;
29
+ while ((idx = this.buffer.indexOf("\n")) !== -1) {
30
+ const line = this.buffer.slice(0, idx);
31
+ this.buffer = this.buffer.slice(idx + 1);
32
+ if (line.length > 0) lines.push(line);
33
+ }
34
+ if (this.buffer.length > this.maxBytes) {
35
+ const buffered = this.buffer.length;
36
+ this.buffer = "";
37
+ throw new LineOverflowError(buffered, this.maxBytes);
38
+ }
39
+ return lines;
40
+ }
41
+ /** Flush any remaining partial data (useful on stream close) */
42
+ flush() {
43
+ if (this.buffer.length === 0) return null;
44
+ const rest = this.buffer;
45
+ this.buffer = "";
46
+ return rest;
47
+ }
48
+ };
49
+
50
+ // src/userResponse/scripts/script-bootstrap.ts
51
+ var SCRIPT_ARG = process.argv[2];
52
+ if (!SCRIPT_ARG) {
53
+ sendError("Bootstrap was invoked without a script path.", "compile");
54
+ process.exit(1);
55
+ }
56
+ var BRIDGE_URL = process.env.SCRIPT_BRIDGE_URL;
57
+ var executedQueries = [];
58
+ var PREVIEW_MAX_ROWS = 10;
59
+ var PREVIEW_MAX_CHARS = 200;
60
+ function previewRows(data) {
61
+ return data.slice(0, PREVIEW_MAX_ROWS).map((row) => {
62
+ if (!row || typeof row !== "object") return row;
63
+ const out = {};
64
+ for (const [k, v] of Object.entries(row)) {
65
+ out[k] = typeof v === "string" && v.length > PREVIEW_MAX_CHARS ? v.slice(0, PREVIEW_MAX_CHARS) + "\u2026" : v;
66
+ }
67
+ return out;
68
+ });
69
+ }
70
+ var initResolve = null;
71
+ var initPromise = new Promise((resolve) => {
72
+ initResolve = resolve;
73
+ });
74
+ function buildCtx(init) {
75
+ return {
76
+ now: init.now,
77
+ async query(toolId, sql) {
78
+ if (!BRIDGE_URL) throw new Error("SCRIPT_BRIDGE_URL is not set \u2014 script bridge server is not running");
79
+ const startedAt = Date.now();
80
+ send({ type: "stream", chunk: `
81
+ \u{1F4DD} **Querying ${toolId}:**
82
+ \`\`\`sql
83
+ ${sql}
84
+ \`\`\`
85
+
86
+ ` });
87
+ send({ type: "stream", chunk: `__QUERY_TIMER_START_Executing query__` });
88
+ const res = await fetch(`${BRIDGE_URL}/query`, {
89
+ method: "POST",
90
+ headers: { "content-type": "application/json" },
91
+ body: JSON.stringify({ toolId, sql })
92
+ });
93
+ const json = await res.json();
94
+ const executionTimeMs = Date.now() - startedAt;
95
+ send({ type: "stream", chunk: `__QUERY_TIMER_DONE_${(executionTimeMs / 1e3).toFixed(1)}__
96
+
97
+ ` });
98
+ if (!res.ok || !json.success) {
99
+ const errMsg = json.error || `Bridge query failed (${res.status})`;
100
+ send({ type: "stream", chunk: `\u274C **Query failed on ${toolId}:** ${errMsg}
101
+
102
+ ` });
103
+ throw new Error(errMsg);
104
+ }
105
+ const data = json.data ?? [];
106
+ const count = json.count ?? data.length;
107
+ send({ type: "stream", chunk: `\u2705 **${count} rows from ${toolId}**
108
+
109
+ ` });
110
+ if (data.length > 0) {
111
+ send({ type: "stream", chunk: `<DataTable>${JSON.stringify(previewRows(data))}</DataTable>
112
+
113
+ ` });
114
+ }
115
+ executedQueries.push({
116
+ sourceId: toolId,
117
+ sourceName: toolId,
118
+ // enriched by runner after result arrives
119
+ sql,
120
+ data,
121
+ count,
122
+ executionTimeMs
123
+ });
124
+ return { data, count };
125
+ },
126
+ /**
127
+ * Call a tool with structured params (typically a direct tool whose
128
+ * fn(params) does not expect a SQL string). Returns whatever the tool
129
+ * returned — shape is up to the tool.
130
+ *
131
+ * Use this when you need a pre-built business-logic tool (e.g.,
132
+ * "Calculate Shelf Life") that takes structured input rather than SQL.
133
+ * For SQL source tools, keep using ctx.query.
134
+ *
135
+ * runTool results do NOT auto-flow into executedQueries. If you want
136
+ * the result visualised, register it via ctx.emit() after extracting
137
+ * the array you want to chart.
138
+ */
139
+ async runTool(toolId, params = {}) {
140
+ if (!BRIDGE_URL) throw new Error("SCRIPT_BRIDGE_URL is not set \u2014 script bridge server is not running");
141
+ const res = await fetch(`${BRIDGE_URL}/run-tool`, {
142
+ method: "POST",
143
+ headers: { "content-type": "application/json" },
144
+ body: JSON.stringify({ toolId, params })
145
+ });
146
+ const json = await res.json();
147
+ if (!res.ok || !json.success) throw new Error(json.error || `Bridge run-tool failed (${res.status})`);
148
+ return json.result;
149
+ },
150
+ /**
151
+ * Register a computed dataset. Flows into executedQueries alongside
152
+ * real SQL query results so component generation can render post-SQL
153
+ * transforms (weighted averages, clustering, joins) as charts.
154
+ */
155
+ emit(name, rows, meta) {
156
+ executedQueries.push({
157
+ sourceId: `computed:${name}`,
158
+ sourceName: meta?.description || name,
159
+ sql: `-- computed dataset: ${name}`,
160
+ data: Array.isArray(rows) ? rows : [],
161
+ count: Array.isArray(rows) ? rows.length : 0,
162
+ executionTimeMs: 0,
163
+ virtual: true
164
+ });
165
+ },
166
+ /**
167
+ * Forward a short progress message to the parent's StreamBuffer so the UI
168
+ * continues to see per-script feedback during execution.
169
+ */
170
+ log(chunk) {
171
+ send({ type: "stream", chunk });
172
+ }
173
+ };
174
+ }
175
+ function send(msg) {
176
+ process.stdout.write(encodeMessage(msg));
177
+ }
178
+ function sendError(message, phase, stack) {
179
+ send({ type: "error", message, stack, phase });
180
+ }
181
+ var splitter = new LineSplitter();
182
+ process.stdin.setEncoding("utf-8");
183
+ process.stdin.on("data", (chunk) => {
184
+ let lines;
185
+ try {
186
+ lines = splitter.push(chunk);
187
+ } catch (err) {
188
+ const msg = err instanceof Error ? err.message : String(err);
189
+ sendError(msg, "runtime");
190
+ process.exit(1);
191
+ }
192
+ for (const line of lines) {
193
+ let msg;
194
+ try {
195
+ msg = JSON.parse(line);
196
+ } catch {
197
+ continue;
198
+ }
199
+ handleMessage(msg);
200
+ }
201
+ });
202
+ function handleMessage(msg) {
203
+ switch (msg.type) {
204
+ case "init": {
205
+ if (initResolve) {
206
+ const now = new Date(msg.now || (/* @__PURE__ */ new Date()).toISOString());
207
+ const params = restoreParamTypes(msg.params || {}, msg.paramSchema);
208
+ initResolve({ params, now });
209
+ initResolve = null;
210
+ }
211
+ break;
212
+ }
213
+ }
214
+ }
215
+ (async () => {
216
+ let getData;
217
+ try {
218
+ const mod = await import(pathToFileURL(SCRIPT_ARG).href);
219
+ getData = mod.getData ?? mod.default;
220
+ if (typeof getData !== "function") {
221
+ throw new Error(
222
+ `Script at ${SCRIPT_ARG} does not export getData \u2014 expected \`export async function getData(ctx, params)\``
223
+ );
224
+ }
225
+ } catch (err) {
226
+ const e = err;
227
+ sendError(e.message || String(err), "compile", e.stack);
228
+ process.exit(1);
229
+ return;
230
+ }
231
+ const init = await initPromise;
232
+ const ctx = buildCtx(init);
233
+ const startedAt = Date.now();
234
+ try {
235
+ const raw = await getData(ctx, init.params);
236
+ const executionTimeMs = Date.now() - startedAt;
237
+ const data = normalizeResultData(raw);
238
+ const count = typeof raw?.count === "number" ? raw.count : data.length;
239
+ const sampleRows = data.slice(0, 5);
240
+ const result = {
241
+ type: "result",
242
+ data,
243
+ count,
244
+ executedQueries,
245
+ executionTimeMs,
246
+ sampleRows
247
+ };
248
+ send(result);
249
+ process.stdout.once("drain", () => process.exit(0));
250
+ setImmediate(() => process.exit(0));
251
+ } catch (err) {
252
+ const e = err;
253
+ sendError(e.message || String(err), "runtime", e.stack);
254
+ process.exit(1);
255
+ }
256
+ })();
257
+ function normalizeResultData(raw) {
258
+ if (!raw) return [];
259
+ if (Array.isArray(raw)) return raw;
260
+ if (Array.isArray(raw.data)) return raw.data;
261
+ if (raw.data && typeof raw.data === "object") return [raw.data];
262
+ return [];
263
+ }
264
+ function restoreParamTypes(params, schema) {
265
+ if (!schema) return params;
266
+ const out = { ...params };
267
+ for (const [name, type] of Object.entries(schema)) {
268
+ const v = out[name];
269
+ if (v === void 0 || v === null) continue;
270
+ if (type === "date" && typeof v === "string") {
271
+ const d = new Date(v);
272
+ if (!isNaN(d.getTime())) out[name] = d;
273
+ } else if (type === "date_range" && typeof v === "object") {
274
+ const from = v.from;
275
+ const to = v.to;
276
+ out[name] = {
277
+ from: typeof from === "string" ? new Date(from) : from,
278
+ to: typeof to === "string" ? new Date(to) : to
279
+ };
280
+ }
281
+ }
282
+ return out;
283
+ }
284
+ //# sourceMappingURL=script-bootstrap.mjs.map