@prostgles/prostgles 2.3.6-dev.1786565331 → 2.3.6-dev.1786613371

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 (99) hide show
  1. package/dist/common/DBGeneratedSchema.d.ts +5 -9
  2. package/dist/common/DBGeneratedSchema.d.ts.map +1 -1
  3. package/dist/common/llmUtils.d.ts.map +1 -1
  4. package/dist/server/package.json +3 -2
  5. package/dist/server/src/BackupManager/BackupManager.d.ts +0 -1
  6. package/dist/server/src/BackupManager/BackupManager.d.ts.map +1 -1
  7. package/dist/server/src/ConnectionManager/ConnectionManager.d.ts +1 -2
  8. package/dist/server/src/ConnectionManager/ConnectionManager.d.ts.map +1 -1
  9. package/dist/server/src/ConnectionManager/ConnectionManager.js +9 -5
  10. package/dist/server/src/ConnectionManager/ConnectionManager.js.map +1 -1
  11. package/dist/server/src/ConnectionManager/connectionManagerUtils.d.ts +0 -4
  12. package/dist/server/src/ConnectionManager/connectionManagerUtils.d.ts.map +1 -1
  13. package/dist/server/src/ConnectionManager/connectionManagerUtils.js +1 -8
  14. package/dist/server/src/ConnectionManager/connectionManagerUtils.js.map +1 -1
  15. package/dist/server/src/ConnectionManager/getHotReloadConfigs.d.ts.map +1 -1
  16. package/dist/server/src/ConnectionManager/initConnectionManager.js +1 -1
  17. package/dist/server/src/ConnectionManager/initConnectionManager.js.map +1 -1
  18. package/dist/server/src/ConnectionManager/startConnection.d.ts.map +1 -1
  19. package/dist/server/src/ConnectionManager/startConnection.js +1 -5
  20. package/dist/server/src/ConnectionManager/startConnection.js.map +1 -1
  21. package/dist/server/src/McpHub/DockerSandbox/getDockerRunArgs.d.ts.map +1 -1
  22. package/dist/server/src/McpHub/DockerSandbox/getDockerRunArgs.js +4 -2
  23. package/dist/server/src/McpHub/DockerSandbox/getDockerRunArgs.js.map +1 -1
  24. package/dist/server/src/McpHub/ProstglesMcpHub/ProstglesMCPServers/Prostgles/agenticWorkflow/definitionValidation/validateAgenticWorkflowDefinitions.d.ts.map +1 -1
  25. package/dist/server/src/McpHub/ProstglesMcpHub/ProstglesMCPServers/Prostgles/agenticWorkflow/proxyHandlers/createWorkflowExecutionHandlers.d.ts.map +1 -1
  26. package/dist/server/src/McpHub/ProstglesMcpHub/ProstglesMCPServers/Prostgles/agenticWorkflow/proxyHandlers/getAgentConfigWithDefaults.d.ts.map +1 -1
  27. package/dist/server/src/McpHub/ProstglesMcpHub/ProstglesMCPServers/Prostgles/agenticWorkflow/proxyHandlers/setupOrchestrationToolPermissions.d.ts.map +1 -1
  28. package/dist/server/src/McpHub/ProstglesMcpHub/ProstglesMCPServers/WebDev/tools/getWebDevChatAndConnection.d.ts +0 -1
  29. package/dist/server/src/McpHub/ProstglesMcpHub/ProstglesMCPServers/WebDev/tools/getWebDevChatAndConnection.d.ts.map +1 -1
  30. package/dist/server/src/authConfig/sessionUtils.d.ts.map +1 -1
  31. package/dist/server/src/authConfig/subscribeToAuthSetupChanges.d.ts.map +1 -1
  32. package/dist/server/src/cli/cli.js +2 -1
  33. package/dist/server/src/cli/cli.js.map +1 -1
  34. package/dist/server/src/cli/cliUtils.d.ts +1 -0
  35. package/dist/server/src/cli/cliUtils.d.ts.map +1 -1
  36. package/dist/server/src/cli/cliUtils.js +5 -0
  37. package/dist/server/src/cli/cliUtils.js.map +1 -1
  38. package/dist/server/src/connectionUtils/testDBConnection.d.ts +1 -1
  39. package/dist/server/src/connectionUtils/testDBConnection.d.ts.map +1 -1
  40. package/dist/server/src/connectionUtils/testDBConnection.js +12 -9
  41. package/dist/server/src/connectionUtils/testDBConnection.js.map +1 -1
  42. package/dist/server/src/init/initUsers.d.ts.map +1 -1
  43. package/dist/server/src/init/saveTypescriptFilesForProduction.d.ts.map +1 -1
  44. package/dist/server/src/init/saveTypescriptFilesForProduction.js +3 -1
  45. package/dist/server/src/init/saveTypescriptFilesForProduction.js.map +1 -1
  46. package/dist/server/src/init/startProstgles.d.ts.map +1 -1
  47. package/dist/server/src/init/startProstgles.js +5 -2
  48. package/dist/server/src/init/startProstgles.js.map +1 -1
  49. package/dist/server/src/publish/getPublishLLM.d.ts.map +1 -1
  50. package/dist/server/src/publish/getPublishLlmChats.d.ts.map +1 -1
  51. package/dist/server/src/serverFunctions/adminServerFunctions/getAdminServerFunctions.d.ts +0 -10
  52. package/dist/server/src/serverFunctions/adminServerFunctions/getAdminServerFunctions.d.ts.map +1 -1
  53. package/dist/server/src/serverFunctions/adminServerFunctions/getAdminServerFunctions.js +0 -1
  54. package/dist/server/src/serverFunctions/adminServerFunctions/getAdminServerFunctions.js.map +1 -1
  55. package/dist/server/src/serverFunctions/adminServerFunctions/getAgenticWorkflowFunctions.d.ts.map +1 -1
  56. package/dist/server/src/serverFunctions/adminServerFunctions/getBackupServerFunctions.d.ts.map +1 -1
  57. package/dist/server/src/serverFunctions/adminServerFunctions/getDefineAdminFunction.d.ts.map +1 -1
  58. package/dist/server/src/serverFunctions/adminServerFunctions/getMcpServerFunctions.d.ts.map +1 -1
  59. package/dist/server/src/serverFunctions/adminServerFunctions/getWebAppServerFunctions.d.ts.map +1 -1
  60. package/dist/server/src/serverFunctions/adminServerFunctions/webApp/getTemplatedWebAppConnection.d.ts +0 -1
  61. package/dist/server/src/serverFunctions/adminServerFunctions/webApp/getTemplatedWebAppConnection.d.ts.map +1 -1
  62. package/dist/server/src/serverFunctions/applySampleSchema.d.ts +0 -1
  63. package/dist/server/src/serverFunctions/applySampleSchema.d.ts.map +1 -1
  64. package/dist/server/src/serverFunctions/askLLM/askLLM.d.ts.map +1 -1
  65. package/dist/server/src/serverFunctions/askLLM/getPastMessages.d.ts.map +1 -1
  66. package/dist/server/src/serverFunctions/askLLM/getValidatedAskLLMChatOptions.d.ts.map +1 -1
  67. package/dist/server/src/serverFunctions/askLLM/handleToolUseResultConfirmation.d.ts.map +1 -1
  68. package/dist/server/src/serverFunctions/askLLM/prostglesLLMTools/getMCPServerTools.d.ts.map +1 -1
  69. package/dist/server/src/serverFunctions/askLLM/prostglesLLMTools/getMcpToolsWithDynamicDescription.d.ts.map +1 -1
  70. package/dist/server/src/serverFunctions/deleteConnection.d.ts +0 -1
  71. package/dist/server/src/serverFunctions/deleteConnection.d.ts.map +1 -1
  72. package/dist/server/src/serverFunctions/getConnectionAndDatabaseConfig.d.ts +0 -1
  73. package/dist/server/src/serverFunctions/getConnectionAndDatabaseConfig.d.ts.map +1 -1
  74. package/dist/server/src/serverFunctions/getServerFunctions.d.ts +0 -10
  75. package/dist/server/src/serverFunctions/getServerFunctions.d.ts.map +1 -1
  76. package/dist/server/src/serverFunctions/getServerFunctionsContext.d.ts.map +1 -1
  77. package/dist/server/src/serverFunctions/userServerFunctions/getLLMAccessParams.d.ts.map +1 -1
  78. package/dist/server/src/serverFunctions/userServerFunctions/getUserServerFunctions.d.ts.map +1 -1
  79. package/dist/server/src/tableConfig/tableConfigConnections.d.ts.map +1 -1
  80. package/dist/server/src/tableConfig/tableConfigConnections.js +0 -6
  81. package/dist/server/src/tableConfig/tableConfigConnections.js.map +1 -1
  82. package/dist/server/src/upsertConnection.d.ts +0 -1
  83. package/dist/server/src/upsertConnection.d.ts.map +1 -1
  84. package/package.json +3 -2
  85. package/sample_schemas/crypto/tsconfig.json +4 -0
  86. package/sample_schemas/food_delivery/tsconfig.json +1 -1
  87. package/sample_schemas/property_management/tsconfig.json +1 -1
  88. package/sample_schemas/weather/tsconfig.json +1 -1
  89. package/src/McpHub/ProstglesMcpHub/ProstglesMCPServers/Prostgles/agenticWorkflow/runtimeSdk/addInstrumentationToTsLogic.ts +350 -0
  90. package/src/McpHub/ProstglesMcpHub/ProstglesMCPServers/Prostgles/agenticWorkflow/runtimeSdk/callWorkflowProxy.ts +99 -0
  91. package/src/McpHub/ProstglesMcpHub/ProstglesMCPServers/Prostgles/agenticWorkflow/runtimeSdk/defineAgenticWorkflow.spec.ts +110 -0
  92. package/src/McpHub/ProstglesMcpHub/ProstglesMCPServers/Prostgles/agenticWorkflow/runtimeSdk/defineAgenticWorkflow.ts +576 -0
  93. package/src/McpHub/ProstglesMcpHub/ProstglesMCPServers/Prostgles/agenticWorkflow/runtimeSdk/defineAgenticWorkflowHandlers.ts +127 -0
  94. package/src/McpHub/ProstglesMcpHub/ProstglesMCPServers/Prostgles/agenticWorkflow/runtimeSdk/defineAgenticWorkflowHandlers.types.ts +72 -0
  95. package/src/McpHub/ProstglesMcpHub/ProstglesMCPServers/Prostgles/agenticWorkflow/runtimeSdk/definitionHandler.ts +104 -0
  96. package/src/McpHub/ProstglesMcpHub/ProstglesMCPServers/Prostgles/agenticWorkflow/runtimeSdk/ensureWorkflowIsExecuted.ts +53 -0
  97. package/src/McpHub/ProstglesMcpHub/ProstglesMCPServers/Prostgles/agenticWorkflow/runtimeSdk/getTsLogicSummary.ts +639 -0
  98. package/src/McpHub/ProstglesMcpHub/ProstglesMCPServers/Prostgles/agenticWorkflow/runtimeSdk/orchestrationToolHandlers.ts +55 -0
  99. package/src/McpHub/ProstglesMcpHub/ProstglesMCPServers/Prostgles/agenticWorkflow/runtimeSdk/tableHandlers.ts +108 -0
@@ -0,0 +1,639 @@
1
+ import * as ts from "typescript";
2
+
3
+ // ─── Position ─────────────────────────────────────────────────────────────────
4
+
5
+ export type Position = {
6
+ /** 1-indexed line number */
7
+ line: number;
8
+ /** 1-indexed column number */
9
+ col: number;
10
+ };
11
+
12
+ // ─── Function registry ────────────────────────────────────────────────────────
13
+
14
+ export type FunctionRisk = "safe" | "warn" | "danger";
15
+
16
+ export type FunctionInfo = {
17
+ /** Stable key — also used as `fnId` on call/await nodes */
18
+ id: string;
19
+ /** Human-readable callee label, e.g. `fetch`, `db.query` */
20
+ label: string;
21
+ risk: FunctionRisk;
22
+ /** Short human-readable explanation of *why* this is risky */
23
+ riskReason?: string;
24
+ /** How many times this function is called in the summarised scope */
25
+ callCount: number;
26
+ };
27
+
28
+ /** All functions encountered during traversal, keyed by their `id`. */
29
+ export type FunctionRegistry = Record<string, FunctionInfo>;
30
+
31
+ // ─── Risk classification ──────────────────────────────────────────────────────
32
+
33
+ type RiskRule = {
34
+ pattern: RegExp;
35
+ risk: FunctionRisk;
36
+ reason: string;
37
+ };
38
+
39
+ /**
40
+ * Ordered list of rules — first match wins.
41
+ * Rules are matched against the full callee label (e.g. `"fs.writeFile"`).
42
+ */
43
+ const RISK_RULES: RiskRule[] = [
44
+ // ── Danger ───────────────────────────────────────────────────────────────
45
+ {
46
+ pattern: /\beval\b/,
47
+ risk: "danger",
48
+ reason: "eval() executes arbitrary code and is a common injection vector",
49
+ },
50
+ {
51
+ pattern: /\b(execSync|spawnSync|exec|spawn|fork)\b/,
52
+ risk: "danger",
53
+ reason:
54
+ "Spawns child processes; command-injection risk if inputs are unsanitised",
55
+ },
56
+ {
57
+ pattern: /\b(unlink|rmdir|rm)\b/,
58
+ risk: "danger",
59
+ reason: "Permanently deletes files or directories",
60
+ },
61
+ {
62
+ pattern: /\bwriteFile(Sync)?\b/,
63
+ risk: "danger",
64
+ reason: "Writes arbitrary data to the filesystem",
65
+ },
66
+ // ── Warn ─────────────────────────────────────────────────────────────────
67
+ {
68
+ pattern: /\bfetch\b/,
69
+ risk: "warn",
70
+ reason:
71
+ "Makes an outbound HTTP request; review URL and credential handling",
72
+ },
73
+ {
74
+ pattern: /\b(axios|got|request|http\.get|https\.get)\b/,
75
+ risk: "warn",
76
+ reason: "Outbound HTTP call; review URL source and authentication",
77
+ },
78
+ {
79
+ pattern: /\breadFile(Sync)?\b/,
80
+ risk: "warn",
81
+ reason: "Reads from the filesystem; verify path is not user-controlled",
82
+ },
83
+ {
84
+ pattern: /\b(readdir|glob|stat)\b/,
85
+ risk: "warn",
86
+ reason: "Filesystem enumeration; may expose directory structure",
87
+ },
88
+ {
89
+ pattern: /\b(db|sql|pg|mysql|mongo|redis|prisma)\./i,
90
+ risk: "warn",
91
+ reason: "Database operation; verify inputs are sanitised / parameterised",
92
+ },
93
+ {
94
+ pattern: /\brequire\b/,
95
+ risk: "warn",
96
+ reason:
97
+ "Dynamic module load; ensure the module path is not user-controlled",
98
+ },
99
+ ];
100
+
101
+ const classifyRisk = (
102
+ label: string,
103
+ ): Pick<FunctionInfo, "risk" | "riskReason"> => {
104
+ for (const rule of RISK_RULES) {
105
+ if (rule.pattern.test(label)) {
106
+ return { risk: rule.risk, reason: rule.reason } as Pick<
107
+ FunctionInfo,
108
+ "risk" | "riskReason"
109
+ >;
110
+ }
111
+ }
112
+ return { risk: "safe" };
113
+ };
114
+
115
+ // ─── Visitor context ──────────────────────────────────────────────────────────
116
+
117
+ type VisitorContext = {
118
+ sf: ts.SourceFile;
119
+ registry: FunctionRegistry;
120
+ };
121
+
122
+ // ─── Types ────────────────────────────────────────────────────────────────────
123
+
124
+ export type SummaryNode =
125
+ | {
126
+ kind: "await";
127
+ label: string;
128
+ /** Key into `SummaryResult.functions` */
129
+ fnId: string;
130
+ pos: Position;
131
+ children?: SummaryNode[];
132
+ }
133
+ | {
134
+ kind: "call";
135
+ label: string;
136
+ /** Key into `SummaryResult.functions` */
137
+ fnId: string;
138
+ pos: Position;
139
+ children?: SummaryNode[];
140
+ }
141
+ | { kind: "assign"; name: string; value: SummaryNode; pos: Position }
142
+ | { kind: "fn-def"; name: string; body: SummaryNode[]; pos: Position }
143
+ | {
144
+ kind: "if";
145
+ condition: string;
146
+ then: SummaryNode[];
147
+ else?: SummaryNode[];
148
+ pos: Position;
149
+ }
150
+ | { kind: "loop"; over: string; body: SummaryNode[]; pos: Position }
151
+ | { kind: "return"; value?: string; pos: Position }
152
+ | { kind: "block"; nodes: SummaryNode[]; pos: Position };
153
+
154
+ // ─── Public result type ───────────────────────────────────────────────────────
155
+
156
+ export type SummaryResult = {
157
+ nodes: SummaryNode[];
158
+ /** All functions encountered, keyed by their stable `fnId` */
159
+ functions: FunctionRegistry;
160
+ };
161
+
162
+ // ─── Expression helpers ───────────────────────────────────────────────────────
163
+
164
+ const truncate = (s: string, max = 60): string =>
165
+ s.length > max ? s.slice(0, max - 1) + "…" : s;
166
+
167
+ const shortExpr = (node: ts.Node): string => {
168
+ if (ts.isStringLiteral(node)) return JSON.stringify(node.text);
169
+ if (ts.isNumericLiteral(node)) return node.text;
170
+ if (ts.isIdentifier(node)) return node.text;
171
+ if (ts.isPropertyAccessExpression(node))
172
+ return `${shortExpr(node.expression)}.${node.name.text}`;
173
+ if (ts.isCallExpression(node)) return `${shortExpr(node.expression)}(…)`;
174
+ if (ts.isAwaitExpression(node)) return `await ${shortExpr(node.expression)}`;
175
+ if (ts.isObjectLiteralExpression(node)) {
176
+ const keys = node.properties
177
+ .slice(0, 3)
178
+ .map((p) => (ts.isPropertyAssignment(p) ? shortExpr(p.name) : "…"))
179
+ .join(", ");
180
+ return `{ ${keys}${node.properties.length > 3 ? ", …" : ""} }`;
181
+ }
182
+ if (ts.isArrayLiteralExpression(node))
183
+ return `[${node.elements.length} item${node.elements.length !== 1 ? "s" : ""}]`;
184
+ if (ts.isTemplateExpression(node) || ts.isNoSubstitutionTemplateLiteral(node))
185
+ return "`…`";
186
+ if (ts.isBinaryExpression(node))
187
+ return `${shortExpr(node.left)} ${ts.tokenToString(node.operatorToken.kind)} ${shortExpr(node.right)}`;
188
+ if (ts.isPrefixUnaryExpression(node))
189
+ return (ts.tokenToString(node.operator) ?? "") + shortExpr(node.operand);
190
+ return "…";
191
+ };
192
+
193
+ const callLabel = (node: ts.CallExpression): string => {
194
+ const callee = shortExpr(node.expression);
195
+ const args = node.arguments
196
+ .slice(0, 3)
197
+ .map((a) =>
198
+ ts.isArrowFunction(a) || ts.isFunctionExpression(a) ? "…" : shortExpr(a),
199
+ )
200
+ .join(", ");
201
+ return truncate(`${callee}(${args})`);
202
+ };
203
+
204
+ // ─── Position helpers ─────────────────────────────────────────────────────────
205
+
206
+ const getPos = (node: ts.Node, sf: ts.SourceFile): Position => {
207
+ const { line, character } = sf.getLineAndCharacterOfPosition(
208
+ node.getStart(sf),
209
+ );
210
+ return { line: line + 1, col: character + 1 };
211
+ };
212
+
213
+ // ─── Function registry helpers ────────────────────────────────────────────────
214
+
215
+ /**
216
+ * Derives a stable, normalised key from a callee label.
217
+ * Strips argument lists so `fetch(url)` and `fetch(other)` share one entry.
218
+ */
219
+ const toFnId = (label: string): string =>
220
+ label
221
+ .replace(/\(.*$/, "") // strip args
222
+ .replace(/\s+/g, "") // strip whitespace
223
+ .toLowerCase();
224
+
225
+ const registerCall = (label: string, registry: FunctionRegistry): string => {
226
+ const id = toFnId(label);
227
+ if (registry[id]) {
228
+ registry[id].callCount += 1;
229
+ } else {
230
+ const { risk, riskReason } = classifyRisk(label);
231
+ registry[id] = {
232
+ id,
233
+ label: label.replace(/\(.*$/, ""),
234
+ risk,
235
+ riskReason,
236
+ callCount: 1,
237
+ };
238
+ }
239
+ return id;
240
+ };
241
+
242
+ // ─── AST Visitor ──────────────────────────────────────────────────────────────
243
+
244
+ const extractCallbackChildren = (
245
+ call: ts.CallExpression,
246
+ ctx: VisitorContext,
247
+ ): SummaryNode[] | undefined => {
248
+ const children: SummaryNode[] = [];
249
+ for (const arg of call.arguments) {
250
+ if (
251
+ (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) &&
252
+ ts.isBlock(arg.body)
253
+ ) {
254
+ const paramName =
255
+ arg.parameters[0] && ts.isIdentifier(arg.parameters[0].name) ?
256
+ arg.parameters[0].name.text
257
+ : "…";
258
+ children.push({
259
+ kind: "fn-def",
260
+ name: `(${paramName}) =>`,
261
+ body: visitBlock(arg.body, ctx),
262
+ pos: getPos(arg, ctx.sf),
263
+ });
264
+ }
265
+ }
266
+ return children.length > 0 ? children : undefined;
267
+ };
268
+
269
+ const extractValue = (
270
+ expr: ts.Expression,
271
+ ctx: VisitorContext,
272
+ ): SummaryNode => {
273
+ if (ts.isAwaitExpression(expr)) {
274
+ const inner = expr.expression;
275
+ if (ts.isCallExpression(inner)) {
276
+ const label = callLabel(inner);
277
+ const fnId = registerCall(label, ctx.registry);
278
+ return {
279
+ kind: "await",
280
+ label,
281
+ fnId,
282
+ pos: getPos(expr, ctx.sf),
283
+ children: extractCallbackChildren(inner, ctx),
284
+ };
285
+ }
286
+ const label = truncate(shortExpr(inner));
287
+ const fnId = registerCall(label, ctx.registry);
288
+ return { kind: "await", label, fnId, pos: getPos(expr, ctx.sf) };
289
+ }
290
+ if (ts.isCallExpression(expr)) {
291
+ const label = callLabel(expr);
292
+ const fnId = registerCall(label, ctx.registry);
293
+ return {
294
+ kind: "call",
295
+ label,
296
+ fnId,
297
+ pos: getPos(expr, ctx.sf),
298
+ children: extractCallbackChildren(expr, ctx),
299
+ };
300
+ }
301
+ // Non-call expression wrapped as a call node (e.g. bare identifier)
302
+ const label = truncate(shortExpr(expr));
303
+ const fnId = registerCall(label, ctx.registry);
304
+ return { kind: "call", label, fnId, pos: getPos(expr, ctx.sf) };
305
+ };
306
+
307
+ const visitStatement = (
308
+ stmt: ts.Statement,
309
+ ctx: VisitorContext,
310
+ ): SummaryNode | null => {
311
+ if (ts.isVariableStatement(stmt)) {
312
+ const decls = stmt.declarationList.declarations;
313
+ if (decls.length !== 1) return null;
314
+ const decl = decls[0]!;
315
+ if (!decl.initializer) return null;
316
+ const name = ts.isIdentifier(decl.name) ? decl.name.text : "…";
317
+
318
+ if (
319
+ (ts.isArrowFunction(decl.initializer) ||
320
+ ts.isFunctionExpression(decl.initializer)) &&
321
+ ts.isBlock(decl.initializer.body)
322
+ ) {
323
+ return {
324
+ kind: "fn-def",
325
+ name,
326
+ body: visitBlock(decl.initializer.body, ctx),
327
+ pos: getPos(stmt, ctx.sf),
328
+ };
329
+ }
330
+
331
+ return {
332
+ kind: "assign",
333
+ name,
334
+ value: extractValue(decl.initializer, ctx),
335
+ pos: getPos(stmt, ctx.sf),
336
+ };
337
+ }
338
+
339
+ if (ts.isExpressionStatement(stmt)) return extractValue(stmt.expression, ctx);
340
+
341
+ if (ts.isIfStatement(stmt)) {
342
+ const condition = truncate(shortExpr(stmt.expression));
343
+ const thenBlock =
344
+ ts.isBlock(stmt.thenStatement) ?
345
+ visitBlock(stmt.thenStatement, ctx)
346
+ : ([visitStatement(stmt.thenStatement, ctx)].filter(
347
+ Boolean,
348
+ ) as SummaryNode[]);
349
+ const elseBlock =
350
+ stmt.elseStatement ?
351
+ ts.isBlock(stmt.elseStatement) ?
352
+ visitBlock(stmt.elseStatement, ctx)
353
+ : ([visitStatement(stmt.elseStatement, ctx)].filter(
354
+ Boolean,
355
+ ) as SummaryNode[])
356
+ : undefined;
357
+ return {
358
+ kind: "if",
359
+ condition,
360
+ then: thenBlock,
361
+ else: elseBlock,
362
+ pos: getPos(stmt, ctx.sf),
363
+ };
364
+ }
365
+
366
+ if (ts.isReturnStatement(stmt)) {
367
+ return {
368
+ kind: "return",
369
+ value: stmt.expression ? truncate(shortExpr(stmt.expression)) : undefined,
370
+ pos: getPos(stmt, ctx.sf),
371
+ };
372
+ }
373
+
374
+ if (ts.isForOfStatement(stmt) || ts.isForInStatement(stmt)) {
375
+ const over = truncate(shortExpr(stmt.expression));
376
+ const body =
377
+ ts.isBlock(stmt.statement) ? visitBlock(stmt.statement, ctx) : [];
378
+ return { kind: "loop", over, body, pos: getPos(stmt, ctx.sf) };
379
+ }
380
+
381
+ return null;
382
+ };
383
+
384
+ const visitBlock = (block: ts.Block, ctx: VisitorContext): SummaryNode[] =>
385
+ block.statements
386
+ .map((s) => visitStatement(s, ctx))
387
+ .filter((n): n is SummaryNode => n !== null);
388
+
389
+ // ─── Source-file factory ──────────────────────────────────────────────────────
390
+
391
+ const makeSourceFile = (
392
+ source: string,
393
+ fileName = "workflow.ts",
394
+ ): ts.SourceFile =>
395
+ ts.createSourceFile(
396
+ fileName,
397
+ source,
398
+ ts.ScriptTarget.Latest,
399
+ true,
400
+ ts.ScriptKind.TS,
401
+ );
402
+
403
+ const makeCtx = (sf: ts.SourceFile): VisitorContext => ({ sf, registry: {} });
404
+
405
+ // ─── Public API ───────────────────────────────────────────────────────────────
406
+
407
+ /**
408
+ * Parse and summarise the body of an async arrow/function expression.
409
+ *
410
+ * @example
411
+ * const result = summariseWorkflowSource(`async ({ researcher }, db) => { ... }`);
412
+ * console.log(result.nodes, result.functions);
413
+ */
414
+ export const summariseWorkflowSource = (source: string): SummaryResult => {
415
+ const wrapped = `const __fn = ${source};`;
416
+ const sf = makeSourceFile(wrapped);
417
+ const ctx = makeCtx(sf);
418
+
419
+ const stmt = sf.statements[0];
420
+ if (!stmt || !ts.isVariableStatement(stmt))
421
+ return { nodes: [], functions: {} };
422
+ const decl = stmt.declarationList.declarations[0];
423
+ if (!decl?.initializer) return { nodes: [], functions: {} };
424
+ const fn = decl.initializer;
425
+ if (!ts.isArrowFunction(fn) && !ts.isFunctionExpression(fn))
426
+ return { nodes: [], functions: {} };
427
+ if (!ts.isBlock(fn.body)) return { nodes: [], functions: {} };
428
+
429
+ const nodes = visitBlock(fn.body, ctx);
430
+ return { nodes, functions: ctx.registry };
431
+ };
432
+
433
+ /**
434
+ * Parse and summarise a block of TypeScript statements not wrapped in a function.
435
+ */
436
+ export const summariseStatements = (source: string): SummaryResult => {
437
+ const sf = makeSourceFile(source);
438
+ const ctx = makeCtx(sf);
439
+ const nodes = sf.statements
440
+ .map((s) => visitStatement(s, ctx))
441
+ .filter((n): n is SummaryNode => n !== null);
442
+ return { nodes, functions: ctx.registry };
443
+ };
444
+
445
+ /**
446
+ * Parses a full TypeScript file, finds `defineAgenticWorkflow(config, workflowFn)`,
447
+ * and summarises the body of the workflow function (second argument).
448
+ *
449
+ * @example
450
+ * const source = fs.readFileSync("my-workflow.ts", "utf8");
451
+ * const { nodes, functions } = summariseWorkflowFile(source);
452
+ * console.log(renderSummary(nodes));
453
+ */
454
+ export const summariseWorkflowFile = (fileSource: string): SummaryResult => {
455
+ const sf = makeSourceFile(fileSource);
456
+ const ctx = makeCtx(sf);
457
+
458
+ let workflowFn = null as ts.ArrowFunction | ts.FunctionExpression | null;
459
+
460
+ const find = (node: ts.Node): void => {
461
+ if (
462
+ ts.isCallExpression(node) &&
463
+ ts.isIdentifier(node.expression) &&
464
+ node.expression.text === "defineAgenticWorkflow" &&
465
+ node.arguments.length >= 2
466
+ ) {
467
+ const second = node.arguments[1]!;
468
+ if (
469
+ (ts.isArrowFunction(second) || ts.isFunctionExpression(second)) &&
470
+ ts.isBlock(second.body)
471
+ ) {
472
+ workflowFn = second;
473
+ }
474
+ }
475
+ ts.forEachChild(node, find);
476
+ };
477
+
478
+ find(sf);
479
+
480
+ if (!workflowFn) return { nodes: [], functions: {} };
481
+ const nodes = visitBlock(
482
+ (workflowFn as ts.ArrowFunction).body as ts.Block,
483
+ ctx,
484
+ );
485
+ return { nodes, functions: ctx.registry };
486
+ };
487
+
488
+ // ─── Structured text renderer ─────────────────────────────────────────────────
489
+
490
+ const INDENT = " ";
491
+
492
+ const riskBadge = (risk: FunctionRisk): string => {
493
+ switch (risk) {
494
+ case "danger":
495
+ return " ⛔";
496
+ case "warn":
497
+ return " ⚠️";
498
+ case "safe":
499
+ return "";
500
+ }
501
+ };
502
+
503
+ export const renderSummaryNode = (
504
+ node: SummaryNode,
505
+ functions: FunctionRegistry,
506
+ depth = 0,
507
+ ): string => {
508
+ const pad = INDENT.repeat(depth);
509
+ const posHint = ` [${node.pos.line}:${node.pos.col}]`;
510
+
511
+ switch (node.kind) {
512
+ case "await": {
513
+ const badge = riskBadge(functions[node.fnId]?.risk ?? "safe");
514
+ return (
515
+ `${pad}⏳ await ${node.label}${badge}${posHint}` +
516
+ (node.children ?
517
+ "\n" +
518
+ node.children
519
+ .map((c) => renderSummaryNode(c, functions, depth + 1))
520
+ .join("\n")
521
+ : "")
522
+ );
523
+ }
524
+ case "call": {
525
+ const badge = riskBadge(functions[node.fnId]?.risk ?? "safe");
526
+ return (
527
+ `${pad}▶ ${node.label}${badge}${posHint}` +
528
+ (node.children ?
529
+ "\n" +
530
+ node.children
531
+ .map((c) => renderSummaryNode(c, functions, depth + 1))
532
+ .join("\n")
533
+ : "")
534
+ );
535
+ }
536
+ case "assign":
537
+ return `${pad}📦 ${node.name}${posHint} = ${renderSummaryNode(node.value, functions, 0).trimStart()}`;
538
+ case "fn-def":
539
+ return (
540
+ `${pad}🔧 define ${node.name}${posHint}:\n` +
541
+ node.body
542
+ .map((c) => renderSummaryNode(c, functions, depth + 1))
543
+ .join("\n")
544
+ );
545
+ case "if":
546
+ return (
547
+ `${pad}🔀 if (${node.condition})${posHint}:\n` +
548
+ node.then
549
+ .map((c) => renderSummaryNode(c, functions, depth + 1))
550
+ .join("\n") +
551
+ (node.else ?
552
+ `\n${pad} else:\n` +
553
+ node.else
554
+ .map((c) => renderSummaryNode(c, functions, depth + 1))
555
+ .join("\n")
556
+ : "")
557
+ );
558
+ case "loop":
559
+ return (
560
+ `${pad}🔁 forEach ${node.over}${posHint}:\n` +
561
+ node.body
562
+ .map((c) => renderSummaryNode(c, functions, depth + 1))
563
+ .join("\n")
564
+ );
565
+ case "return":
566
+ return `${pad}↩ return${node.value ? ` ${node.value}` : ""}${posHint}`;
567
+ case "block":
568
+ return node.nodes
569
+ .map((c) => renderSummaryNode(c, functions, depth))
570
+ .join("\n");
571
+ }
572
+ };
573
+
574
+ export const renderSummary = (result: SummaryResult, depth = 0): string =>
575
+ result.nodes
576
+ .map((n) => renderSummaryNode(n, result.functions, depth))
577
+ .join("\n");
578
+
579
+ // ─── Compact one-liner renderer ───────────────────────────────────────────────
580
+
581
+ const walkCompact = (
582
+ node: SummaryNode,
583
+ functions: FunctionRegistry,
584
+ ): string => {
585
+ switch (node.kind) {
586
+ case "await":
587
+ case "call": {
588
+ const badge = riskBadge(functions[node.fnId]?.risk ?? "safe");
589
+ const inner =
590
+ node.children?.length ?
591
+ ` → [${node.children.map((c) => walkCompact(c, functions)).join(", ")}]`
592
+ : "";
593
+ return `${node.label}${badge}${inner}`;
594
+ }
595
+ case "assign":
596
+ return `${node.name} = ${walkCompact(node.value, functions)}`;
597
+ case "fn-def":
598
+ return `${node.name} { ${node.body.map((c) => walkCompact(c, functions)).join("; ")} }`;
599
+ case "if":
600
+ return `if (${node.condition}) { ${node.then.map((c) => walkCompact(c, functions)).join("; ")} }`;
601
+ case "loop":
602
+ return `forEach(${node.over}) { ${node.body.map((c) => walkCompact(c, functions)).join("; ")} }`;
603
+ case "return":
604
+ return `return${node.value ? ` ${node.value}` : ""}`;
605
+ case "block":
606
+ return node.nodes.map((c) => walkCompact(c, functions)).join(" → ");
607
+ }
608
+ };
609
+
610
+ /**
611
+ * Render nodes as a single chained line:
612
+ * `doResearch() → fetch(url) ⚠️ → if (x < 10) { … } → …`
613
+ */
614
+ export const renderCompact = (result: SummaryResult): string =>
615
+ result.nodes.map((n) => walkCompact(n, result.functions)).join(" → ");
616
+
617
+ // ─── Registry helpers (useful for React UIs) ──────────────────────────────────
618
+
619
+ /** Returns all functions with risk ≥ the given threshold, sorted by risk then label. */
620
+ export const filterByRisk = (
621
+ registry: FunctionRegistry,
622
+ minRisk: FunctionRisk = "warn",
623
+ ): FunctionInfo[] => {
624
+ const order: Record<FunctionRisk, number> = { danger: 2, warn: 1, safe: 0 };
625
+ const threshold = order[minRisk];
626
+ return Object.values(registry)
627
+ .filter((f) => order[f.risk] >= threshold)
628
+ .sort(
629
+ (a, b) => order[b.risk] - order[a.risk] || a.label.localeCompare(b.label),
630
+ );
631
+ };
632
+
633
+ /** Returns a deduplicated list of all risks present in a registry. */
634
+ export const highestRisk = (registry: FunctionRegistry): FunctionRisk => {
635
+ const values = Object.values(registry);
636
+ if (values.some((f) => f.risk === "danger")) return "danger";
637
+ if (values.some((f) => f.risk === "warn")) return "warn";
638
+ return "safe";
639
+ };
@@ -0,0 +1,55 @@
1
+ import { callWorkflowProxy } from "./callWorkflowProxy";
2
+ import type { DefineAgenticWorkflow } from "./defineAgenticWorkflow";
3
+
4
+ export const getOrchestrationToolHandlers = (
5
+ definitions: Parameters<DefineAgenticWorkflow>[0],
6
+ ) => {
7
+ return new Proxy(
8
+ {},
9
+ {
10
+ get(_target, mcpServerName: string) {
11
+ if (typeof mcpServerName !== "string") return undefined;
12
+
13
+ if (!definitions.orchestrationTools) {
14
+ throw new Error(
15
+ `No tools are allowed for this workflow, but tried to access tool server "${mcpServerName}"`,
16
+ );
17
+ }
18
+
19
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
20
+ const serverTools = definitions.orchestrationTools[
21
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
22
+ mcpServerName as keyof typeof definitions.orchestrationTools
23
+ ] as Record<string, 1> | undefined;
24
+ if (!serverTools) {
25
+ throw new Error(
26
+ `MCP server "${mcpServerName}" is not defined in orchestrationTools`,
27
+ );
28
+ }
29
+
30
+ return new Proxy(
31
+ {},
32
+ {
33
+ get(_serverTarget, toolName: string) {
34
+ if (typeof toolName !== "string") return undefined;
35
+
36
+ if (!(toolName in serverTools) || serverTools[toolName] !== 1) {
37
+ throw new Error(
38
+ `Tool "${toolName}" is not allowed on MCP server "${mcpServerName}"`,
39
+ );
40
+ }
41
+
42
+ return (input?: Record<string, unknown>) =>
43
+ callWorkflowProxy({
44
+ type: "tool",
45
+ serverName: mcpServerName,
46
+ toolName,
47
+ input,
48
+ });
49
+ },
50
+ },
51
+ );
52
+ },
53
+ },
54
+ );
55
+ };