@koi-language/koi 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 (85) hide show
  1. package/QUICKSTART.md +89 -0
  2. package/README.md +545 -0
  3. package/examples/actions-demo.koi +177 -0
  4. package/examples/cache-test.koi +29 -0
  5. package/examples/calculator.koi +61 -0
  6. package/examples/clear-registry.js +33 -0
  7. package/examples/clear-registry.koi +30 -0
  8. package/examples/code-introspection-test.koi +149 -0
  9. package/examples/counter.koi +132 -0
  10. package/examples/delegation-test.koi +52 -0
  11. package/examples/directory-import-test.koi +84 -0
  12. package/examples/hello-world-claude.koi +52 -0
  13. package/examples/hello-world.koi +52 -0
  14. package/examples/hello.koi +24 -0
  15. package/examples/mcp-example.koi +70 -0
  16. package/examples/multi-event-handler-test.koi +144 -0
  17. package/examples/new-import-test.koi +89 -0
  18. package/examples/pipeline.koi +162 -0
  19. package/examples/registry-demo.koi +184 -0
  20. package/examples/registry-playbook-demo.koi +162 -0
  21. package/examples/registry-playbook-email-compositor-2.koi +140 -0
  22. package/examples/registry-playbook-email-compositor.koi +140 -0
  23. package/examples/sentiment.koi +90 -0
  24. package/examples/simple.koi +48 -0
  25. package/examples/skill-import-test.koi +76 -0
  26. package/examples/skills/advanced/index.koi +95 -0
  27. package/examples/skills/math-operations.koi +69 -0
  28. package/examples/skills/string-operations.koi +56 -0
  29. package/examples/task-chaining-demo.koi +244 -0
  30. package/examples/test-await.koi +22 -0
  31. package/examples/test-crypto-sha256.koi +196 -0
  32. package/examples/test-delegation.koi +41 -0
  33. package/examples/test-multi-team-routing.koi +258 -0
  34. package/examples/test-no-handler.koi +35 -0
  35. package/examples/test-npm-import.koi +67 -0
  36. package/examples/test-parse.koi +10 -0
  37. package/examples/test-peers-with-team.koi +59 -0
  38. package/examples/test-permissions-fail.koi +20 -0
  39. package/examples/test-permissions.koi +36 -0
  40. package/examples/test-simple-registry.koi +31 -0
  41. package/examples/test-typescript-import.koi +64 -0
  42. package/examples/test-uses-team-syntax.koi +25 -0
  43. package/examples/test-uses-team.koi +31 -0
  44. package/examples/utils/calculator.test.ts +144 -0
  45. package/examples/utils/calculator.ts +56 -0
  46. package/examples/utils/math-helpers.js +50 -0
  47. package/examples/utils/math-helpers.ts +55 -0
  48. package/examples/web-delegation-demo.koi +165 -0
  49. package/package.json +78 -0
  50. package/src/cli/koi.js +793 -0
  51. package/src/compiler/build-optimizer.js +447 -0
  52. package/src/compiler/cache-manager.js +274 -0
  53. package/src/compiler/import-resolver.js +369 -0
  54. package/src/compiler/parser.js +7542 -0
  55. package/src/compiler/transpiler.js +1105 -0
  56. package/src/compiler/typescript-transpiler.js +148 -0
  57. package/src/grammar/koi.pegjs +767 -0
  58. package/src/runtime/action-registry.js +172 -0
  59. package/src/runtime/actions/call-skill.js +45 -0
  60. package/src/runtime/actions/format.js +115 -0
  61. package/src/runtime/actions/print.js +42 -0
  62. package/src/runtime/actions/registry-delete.js +37 -0
  63. package/src/runtime/actions/registry-get.js +37 -0
  64. package/src/runtime/actions/registry-keys.js +33 -0
  65. package/src/runtime/actions/registry-search.js +34 -0
  66. package/src/runtime/actions/registry-set.js +50 -0
  67. package/src/runtime/actions/return.js +31 -0
  68. package/src/runtime/actions/send-message.js +58 -0
  69. package/src/runtime/actions/update-state.js +36 -0
  70. package/src/runtime/agent.js +1368 -0
  71. package/src/runtime/cli-logger.js +205 -0
  72. package/src/runtime/incremental-json-parser.js +201 -0
  73. package/src/runtime/index.js +33 -0
  74. package/src/runtime/llm-provider.js +1372 -0
  75. package/src/runtime/mcp-client.js +1171 -0
  76. package/src/runtime/planner.js +273 -0
  77. package/src/runtime/registry-backends/keyv-sqlite.js +215 -0
  78. package/src/runtime/registry-backends/local.js +260 -0
  79. package/src/runtime/registry.js +162 -0
  80. package/src/runtime/role.js +14 -0
  81. package/src/runtime/router.js +395 -0
  82. package/src/runtime/runtime.js +113 -0
  83. package/src/runtime/skill-selector.js +173 -0
  84. package/src/runtime/skill.js +25 -0
  85. package/src/runtime/team.js +162 -0
@@ -0,0 +1,1105 @@
1
+ import { SourceMapGenerator } from 'source-map';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ export class KoiTranspiler {
6
+ constructor(sourceFile = 'source.zs', options = {}) {
7
+ this.sourceFile = sourceFile;
8
+ this.sourceMap = new SourceMapGenerator({ file: sourceFile + '.js' });
9
+ this.currentLine = 1;
10
+ this.currentColumn = 0;
11
+ this.indent = 0;
12
+ this.inEventHandler = false;
13
+ this.cacheData = options.cacheData || null; // Build-time optimizations
14
+ this.outputPath = options.outputPath || null;
15
+ this.runtimePath = options.runtimePath || null;
16
+ this.externalImports = options.externalImports || []; // TypeScript/JavaScript imports
17
+ }
18
+
19
+ transpile(ast) {
20
+ let code = this.generateProgram(ast);
21
+ return {
22
+ code,
23
+ map: this.sourceMap.toString()
24
+ };
25
+ }
26
+
27
+ addMapping(node) {
28
+ if (node?.location) {
29
+ this.sourceMap.addMapping({
30
+ generated: { line: this.currentLine, column: this.currentColumn },
31
+ source: this.sourceFile,
32
+ original: { line: node.location.start.line, column: node.location.start.column - 1 }
33
+ });
34
+ }
35
+ }
36
+
37
+ emit(code, node = null) {
38
+ if (node) this.addMapping(node);
39
+
40
+ for (const char of code) {
41
+ if (char === '\n') {
42
+ this.currentLine++;
43
+ this.currentColumn = 0;
44
+ } else {
45
+ this.currentColumn++;
46
+ }
47
+ }
48
+ return code;
49
+ }
50
+
51
+ getIndent() {
52
+ return ' '.repeat(this.indent);
53
+ }
54
+
55
+ /**
56
+ * Generate a safe JavaScript identifier from an import path
57
+ * e.g., "./utils/helpers" -> "utils_helpers"
58
+ * "lodash" -> "lodash"
59
+ * "@types/node" -> "types_node"
60
+ */
61
+ generateSafeImportName(importPath) {
62
+ // Remove file extension
63
+ let name = importPath.replace(/\.(ts|tsx|js|jsx|mjs)$/, '');
64
+
65
+ // Remove leading ./ and ../
66
+ name = name.replace(/^\.\.?\//g, '');
67
+
68
+ // Replace special characters with underscores
69
+ name = name.replace(/[^a-zA-Z0-9_$]/g, '_');
70
+
71
+ // Remove leading underscores
72
+ name = name.replace(/^_+/, '');
73
+
74
+ // If starts with a number, prepend with underscore
75
+ if (/^\d/.test(name)) {
76
+ name = '_' + name;
77
+ }
78
+
79
+ // If empty after sanitization, use a default
80
+ if (!name) {
81
+ name = 'external_module';
82
+ }
83
+
84
+ return name;
85
+ }
86
+
87
+ // ============================================================
88
+ // Program
89
+ // ============================================================
90
+
91
+ generateProgram(node) {
92
+ let code = this.emit(`// Generated from ${this.sourceFile}\n`);
93
+
94
+ // Support KOI_RUNTIME_PATH for local development
95
+ // This allows developers to work on Koi itself without reinstalling
96
+ const koiRuntimePath = process.env.KOI_RUNTIME_PATH;
97
+
98
+ let runtimeImportPath;
99
+ let routerImportPath;
100
+
101
+ if (koiRuntimePath) {
102
+ // Development mode: use local runtime
103
+ const runtimeIndexPath = path.join(koiRuntimePath, 'index.js');
104
+ const routerPath = path.join(koiRuntimePath, 'router.js');
105
+
106
+ runtimeImportPath = 'file://' + path.resolve(runtimeIndexPath);
107
+ routerImportPath = 'file://' + path.resolve(routerPath);
108
+
109
+ code += this.emit(`// Using local runtime from KOI_RUNTIME_PATH: ${koiRuntimePath}\n`);
110
+ } else {
111
+ // Production mode: use package imports
112
+ runtimeImportPath = 'koi-lang';
113
+ routerImportPath = 'koi-lang/router';
114
+ }
115
+
116
+ // Store routerImportPath for later use
117
+ this.routerImportPath = routerImportPath;
118
+
119
+ code += this.emit(`import { Agent, Team, Skill, Role, Runtime, SkillRegistry, skillSelector, registry } from '${runtimeImportPath}';\n\n`);
120
+
121
+ // Generate imports for external TypeScript/JavaScript modules
122
+ if (this.externalImports && this.externalImports.length > 0) {
123
+ code += this.emit(`// External TypeScript/JavaScript imports\n`);
124
+
125
+ for (const extImport of this.externalImports) {
126
+ // Use the resolved path (which points to transpiled .js for TypeScript files)
127
+ let importPath = extImport.originalPath;
128
+
129
+ // Check if this is a node_modules package (resolved path contains node_modules)
130
+ const isNodeModule = extImport.resolvedPath.includes('node_modules');
131
+
132
+ // For relative imports, recalculate path from output location
133
+ if (extImport.originalPath.startsWith('./') || extImport.originalPath.startsWith('../')) {
134
+ if (this.outputPath) {
135
+ const outputDir = path.dirname(this.outputPath);
136
+ const relPath = path.relative(outputDir, extImport.resolvedPath);
137
+ importPath = relPath.split(path.sep).join('/');
138
+ if (!importPath.startsWith('.')) {
139
+ importPath = './' + importPath;
140
+ }
141
+ }
142
+ } else if (isNodeModule) {
143
+ // For node_modules packages, keep original package name
144
+ importPath = extImport.originalPath;
145
+ } else {
146
+ // For other absolute imports, use resolved path
147
+ importPath = extImport.resolvedPath;
148
+ }
149
+
150
+ // Generate a safe identifier from the original import path (not resolved)
151
+ const safeName = this.generateSafeImportName(extImport.originalPath);
152
+
153
+ // For node_modules packages, use default import first, then also get named exports
154
+ if (isNodeModule) {
155
+ code += this.emit(`import ${safeName}_default from '${importPath}';\n`);
156
+ code += this.emit(`import * as ${safeName}_named from '${importPath}';\n`);
157
+ // Prefer default export if it exists, otherwise use named exports
158
+ code += this.emit(`const ${safeName} = ${safeName}_default || ${safeName}_named;\n`);
159
+ } else {
160
+ // For local files, use namespace import
161
+ code += this.emit(`import * as ${safeName} from '${importPath}';\n`);
162
+ }
163
+
164
+ // Make it available globally
165
+ code += this.emit(`globalThis.${safeName} = ${safeName};\n`);
166
+ }
167
+
168
+ code += this.emit(`\n`);
169
+ }
170
+
171
+ // Make SkillRegistry and registry available globally
172
+ code += this.emit(`globalThis.SkillRegistry = SkillRegistry;\n`);
173
+ code += this.emit(`globalThis.skillSelector = skillSelector;\n`);
174
+ code += this.emit(`globalThis.registry = registry;\n\n`);
175
+
176
+ // Inject build-time cache if available
177
+ if (this.cacheData && this.cacheData.affordances) {
178
+ code += this.emit(this.generateCacheCode());
179
+ }
180
+
181
+ // Separate run statements from other declarations
182
+ const agentDecls = [];
183
+ const skillDecls = [];
184
+ const runStatements = [];
185
+ const otherDecls = [];
186
+
187
+ for (const decl of node.declarations) {
188
+ if (decl.type === 'RunStatement') {
189
+ runStatements.push(decl);
190
+ } else if (decl.type === 'AgentDecl') {
191
+ agentDecls.push(decl);
192
+ } else if (decl.type === 'SkillDecl') {
193
+ skillDecls.push(decl);
194
+ } else {
195
+ otherDecls.push(decl);
196
+ }
197
+ }
198
+
199
+ // Generate all declarations in original order (except RunStatements)
200
+ // This preserves dependencies between roles, teams, and agents
201
+ this.skipAgentRegistration = true;
202
+ for (const decl of node.declarations) {
203
+ if (decl.type !== 'RunStatement') {
204
+ code += this.generateDeclaration(decl);
205
+ }
206
+ }
207
+ this.skipAgentRegistration = false;
208
+
209
+ // Generate main async function that coordinates everything
210
+ if (agentDecls.length > 0 || skillDecls.length > 0 || runStatements.length > 0) {
211
+ code += this.emit(`\n// Main execution function\n`);
212
+ code += this.emit(`(async () => {\n`);
213
+ this.indent++;
214
+
215
+ // Register all agents
216
+ if (agentDecls.length > 0) {
217
+ code += this.emit(`${this.getIndent()}// Register agents with router\n`);
218
+ code += this.emit(`${this.getIndent()}const { agentRouter } = await import('${this.routerImportPath}');\n\n`);
219
+
220
+ for (const decl of agentDecls) {
221
+ const agentName = decl.name.name;
222
+ const hasCachedAffordances = this.cacheData && this.cacheData.affordances && this.cacheData.affordances[agentName];
223
+
224
+ if (hasCachedAffordances) {
225
+ code += this.emit(`${this.getIndent()}await agentRouter.register(${agentName}, CACHED_AFFORDANCES['${agentName}']);\n`);
226
+ } else {
227
+ code += this.emit(`${this.getIndent()}await agentRouter.register(${agentName});\n`);
228
+ }
229
+ }
230
+
231
+ code += this.emit(`\n`);
232
+ }
233
+
234
+ // Register all skills with skillSelector
235
+ if (skillDecls.length > 0) {
236
+ code += this.emit(`${this.getIndent()}// Register skills with skillSelector\n`);
237
+
238
+ for (const decl of skillDecls) {
239
+ const skillName = decl.name.name;
240
+ const hasCachedAffordance = this.cacheData && this.cacheData.skillAffordances && this.cacheData.skillAffordances[skillName];
241
+
242
+ // Get function names for this skill
243
+ const functionNames = decl.functions
244
+ ? decl.functions.filter(f => f.isExport).map(f => f.name.name)
245
+ : [];
246
+
247
+ if (functionNames.length > 0) {
248
+ // Build functions array
249
+ code += this.emit(`${this.getIndent()}const ${skillName}Functions = [${functionNames.map(fn => `{ name: '${fn}', fn: ${fn}, description: SkillRegistry.get('${skillName}', '${fn}')?.metadata?.affordance || 'Function from ${skillName}' }`).join(', ')}];\n`);
250
+
251
+ // Register with cached affordance if available
252
+ if (hasCachedAffordance) {
253
+ code += this.emit(`${this.getIndent()}await skillSelector.register('${skillName}', ${skillName}Functions, CACHED_SKILL_AFFORDANCES['${skillName}']);\n`);
254
+ } else {
255
+ code += this.emit(`${this.getIndent()}await skillSelector.register('${skillName}', ${skillName}Functions);\n`);
256
+ }
257
+ }
258
+ }
259
+
260
+ code += this.emit(`\n`);
261
+ }
262
+
263
+ // Execute run statements
264
+ for (const runStmt of runStatements) {
265
+ code += this.generateRunBody(runStmt);
266
+ }
267
+
268
+ // Clean exit
269
+ code += this.emit(`${this.getIndent()}process.exit(0);\n`);
270
+
271
+ this.indent--;
272
+ code += this.emit(`})().catch(err => {\n`);
273
+ this.indent++;
274
+ code += this.emit(`${this.getIndent()}console.error('Error:', err.message);\n`);
275
+ code += this.emit(`${this.getIndent()}process.exit(1);\n`);
276
+ this.indent--;
277
+ code += this.emit(`});\n`);
278
+ }
279
+
280
+ return code;
281
+ }
282
+
283
+ generateCacheCode() {
284
+ const meta = this.cacheData.metadata;
285
+ let code = this.emit('// ============================================================\n');
286
+ code += this.emit('// Pre-computed Affordances (Build-time Cache)\n');
287
+ code += this.emit(`// Generated at: ${new Date(meta.generatedAt).toISOString()}\n`);
288
+ code += this.emit(`// Total agents: ${meta.totalAgents || 0}\n`);
289
+ code += this.emit(`// Total agent affordances: ${meta.totalAffordances || 0}\n`);
290
+ code += this.emit(`// Total skills: ${meta.totalSkills || 0}\n`);
291
+ code += this.emit(`// Total skill affordances: ${meta.totalSkillAffordances || 0}\n`);
292
+ code += this.emit('// This avoids embedding API calls at runtime\n');
293
+ code += this.emit('// ============================================================\n\n');
294
+ code += this.emit(`const CACHED_AFFORDANCES = ${JSON.stringify(this.cacheData.affordances || {}, null, 2)};\n\n`);
295
+ code += this.emit(`const CACHED_SKILL_AFFORDANCES = ${JSON.stringify(this.cacheData.skillAffordances || {}, null, 2)};\n\n`);
296
+ return code;
297
+ }
298
+
299
+ // ============================================================
300
+ // Declarations
301
+ // ============================================================
302
+
303
+ generateDeclaration(node) {
304
+ switch (node.type) {
305
+ case 'PackageDecl':
306
+ return this.emit(`// Package: ${node.name.value}\n\n`, node);
307
+ case 'ImportDecl':
308
+ return this.generateImport(node);
309
+ case 'RoleDecl':
310
+ return this.generateRole(node);
311
+ case 'TeamDecl':
312
+ return this.generateTeam(node);
313
+ case 'AgentDecl':
314
+ return this.generateAgent(node);
315
+ case 'SkillDecl':
316
+ return this.generateSkill(node);
317
+ case 'RunStatement':
318
+ return this.generateRun(node);
319
+ default:
320
+ return this.emit(`/* Unknown declaration: ${node.type} */\n`);
321
+ }
322
+ }
323
+
324
+ generateImport(node) {
325
+ return this.emit(`// Import ${node.what}: ${node.name.value}\n`, node);
326
+ }
327
+
328
+ generateRole(node) {
329
+ const caps = node.capabilities.map(c => `'${c.name.name}'`).join(', ');
330
+ return this.emit(
331
+ `const ${node.name.name} = new Role('${node.name.name}', [${caps}]);\n\n`,
332
+ node
333
+ );
334
+ }
335
+
336
+ generateTeam(node) {
337
+ let code = this.emit(`const ${node.name.name} = new Team('${node.name.name}', {\n`, node);
338
+ this.indent++;
339
+ for (const member of node.members) {
340
+ let value;
341
+
342
+ // Handle MCP addresses
343
+ if (member.value.type === 'MCPAddress') {
344
+ value = `'${member.value.address}'`;
345
+ }
346
+ // Handle AgentReference
347
+ else if (member.value.type === 'AgentReference') {
348
+ value = member.value.agent.name;
349
+ }
350
+ // Handle Identifier
351
+ else if (member.value.type === 'Identifier') {
352
+ value = member.value.name;
353
+ }
354
+ // Handle regular values
355
+ else if (typeof member.value === 'string') {
356
+ value = `'${member.value}'`;
357
+ }
358
+ else if (member.value.value) {
359
+ value = `'${member.value.value}'`;
360
+ }
361
+ else {
362
+ value = member.value.name || 'undefined';
363
+ }
364
+
365
+ code += this.emit(`${this.getIndent()}${member.name.name}: ${value},\n`);
366
+ }
367
+ this.indent--;
368
+ code += this.emit(`});\n\n`);
369
+ return code;
370
+ }
371
+
372
+ generateAgent(node) {
373
+ let code = this.emit(`const ${node.name.name} = new Agent({\n`, node);
374
+ this.indent++;
375
+ code += this.emit(`${this.getIndent()}name: '${node.name.name}',\n`);
376
+ code += this.emit(`${this.getIndent()}role: ${node.role.name},\n`);
377
+
378
+ // Extract body items
379
+ const skills = node.body.filter(b => b.type === 'UsesSkill');
380
+ const usesTeams = node.body.filter(b => b.type === 'UsesTeam');
381
+ const llmConfig = node.body.find(b => b.type === 'LLMConfig');
382
+ const eventHandlers = node.body.filter(b => b.type === 'EventHandler');
383
+ const state = node.body.find(b => b.type === 'StateDecl');
384
+ const playbooks = node.body.filter(b => b.type === 'PlaybookDecl');
385
+ const resilience = node.body.find(b => b.type === 'ResilienceDecl');
386
+ const peers = node.body.find(b => b.type === 'PeersDecl');
387
+
388
+ // Track if agent has handlers for auto-registration
389
+ this.agentHasHandlers = eventHandlers.length > 0;
390
+
391
+ if (skills.length > 0) {
392
+ code += this.emit(`${this.getIndent()}skills: [${skills.map(s => `'${s.skill.name}'`).join(', ')}],\n`);
393
+ }
394
+
395
+ if (usesTeams.length > 0) {
396
+ code += this.emit(`${this.getIndent()}usesTeams: [${usesTeams.map(t => t.team.name).join(', ')}],\n`);
397
+
398
+ // For backward compatibility: if no explicit peers and usesTeams exists,
399
+ // set peers to the first team for 'peers.event()' syntax to work
400
+ if (!peers && usesTeams.length > 0) {
401
+ code += this.emit(`${this.getIndent()}peers: ${usesTeams[0].team.name}, // Auto-assigned from uses Team\n`);
402
+ }
403
+ }
404
+
405
+ if (llmConfig) {
406
+ code += this.emit(`${this.getIndent()}llm: ${this.generateExpression(llmConfig.config)},\n`);
407
+ }
408
+
409
+ if (state) {
410
+ code += this.emit(`${this.getIndent()}state: {\n`);
411
+ this.indent++;
412
+ for (const field of state.fields) {
413
+ const init = field.init ? this.generateExpression(field.init) : 'null';
414
+ code += this.emit(`${this.getIndent()}${field.name.name}: ${init},\n`);
415
+ }
416
+ this.indent--;
417
+ code += this.emit(`${this.getIndent()}},\n`);
418
+ }
419
+
420
+ if (playbooks.length > 0) {
421
+ code += this.emit(`${this.getIndent()}playbooks: {\n`);
422
+ this.indent++;
423
+ for (const pb of playbooks) {
424
+ code += this.emit(`${this.getIndent()}${pb.name.value}: ${this.generateExpression(pb.content)},\n`);
425
+ }
426
+ this.indent--;
427
+ code += this.emit(`${this.getIndent()}},\n`);
428
+ }
429
+
430
+ if (resilience) {
431
+ code += this.emit(`${this.getIndent()}resilience: {\n`);
432
+ this.indent++;
433
+ code += this.emit(`${this.getIndent()}name: ${resilience.name.value},\n`);
434
+ for (const prop of resilience.properties) {
435
+ const val = this.generateExpression(prop.value);
436
+ code += this.emit(`${this.getIndent()}${prop.name.name}: ${val},\n`);
437
+ }
438
+ this.indent--;
439
+ code += this.emit(`${this.getIndent()}},\n`);
440
+ }
441
+
442
+ if (peers) {
443
+ let teamName;
444
+ if (peers.team && typeof peers.team === 'object') {
445
+ // Handle TeamReference with override
446
+ teamName = peers.team.name ? peers.team.name.name : peers.team;
447
+ } else {
448
+ teamName = peers.team;
449
+ }
450
+ code += this.emit(`${this.getIndent()}peers: ${teamName},\n`);
451
+ }
452
+
453
+ if (eventHandlers.length > 0) {
454
+ code += this.emit(`${this.getIndent()}handlers: {\n`);
455
+ this.indent++;
456
+ for (const handler of eventHandlers) {
457
+ code += this.generateEventHandler(handler);
458
+ }
459
+ this.indent--;
460
+ code += this.emit(`${this.getIndent()}}\n`);
461
+ }
462
+
463
+ this.indent--;
464
+ code += this.emit(`});\n`);
465
+
466
+ // Auto-register agent with router if it has handlers (only if not skipping)
467
+ if (this.agentHasHandlers && !this.skipAgentRegistration) {
468
+ const agentName = node.name.name;
469
+ const hasCachedAffordances = this.cacheData && this.cacheData.affordances && this.cacheData.affordances[agentName];
470
+
471
+ code += this.emit(`\n// Auto-register agent with router for dynamic discovery\n`);
472
+ code += this.emit(`(async () => {\n`);
473
+ this.indent++;
474
+ code += this.emit(`${this.getIndent()}const { agentRouter } = await import('${this.routerImportPath}');\n`);
475
+
476
+ if (hasCachedAffordances) {
477
+ // Use cached affordances (no embedding generation needed)
478
+ code += this.emit(`${this.getIndent()}// Using pre-computed embeddings from build cache\n`);
479
+ code += this.emit(`${this.getIndent()}await agentRouter.register(${agentName}, CACHED_AFFORDANCES['${agentName}']);\n`);
480
+ } else {
481
+ // No cache, generate at runtime
482
+ code += this.emit(`${this.getIndent()}await agentRouter.register(${agentName});\n`);
483
+ }
484
+
485
+ this.indent--;
486
+ code += this.emit(`})();\n`);
487
+ }
488
+
489
+ code += this.emit(`\n`);
490
+ return code;
491
+ }
492
+
493
+ generateEventHandler(node) {
494
+ const params = node.params.map(p => p.name.name).join(', ');
495
+
496
+ // Check if this is a playbook-only handler (only has PlaybookStatement, no other code)
497
+ const hasOnlyPlaybook = node.body.length === 1 && node.body[0].type === 'PlaybookStatement';
498
+
499
+ if (hasOnlyPlaybook) {
500
+ // Generate playbook-only handler
501
+ const playbook = node.body[0].content.value;
502
+ const escapedPlaybook = JSON.stringify(playbook);
503
+
504
+ let code = this.emit(`${this.getIndent()}${node.event.name}: (() => {\n`);
505
+ this.indent++;
506
+ code += this.emit(`${this.getIndent()}const handler = async function(${params}) {\n`);
507
+ this.indent++;
508
+ code += this.emit(`${this.getIndent()}// This should not be called - playbook will be executed by LLM\n`);
509
+ code += this.emit(`${this.getIndent()}throw new Error('Playbook-only handler called directly');\n`);
510
+ this.indent--;
511
+ code += this.emit(`${this.getIndent()}};\n`);
512
+ code += this.emit(`${this.getIndent()}handler.__playbookOnly__ = true;\n`);
513
+ code += this.emit(`${this.getIndent()}handler.__playbook__ = ${escapedPlaybook};\n`);
514
+ code += this.emit(`${this.getIndent()}return handler;\n`);
515
+ this.indent--;
516
+ code += this.emit(`${this.getIndent()}})(),\n`);
517
+ return code;
518
+ }
519
+
520
+ // Regular handler with code
521
+ let code = this.emit(`${this.getIndent()}${node.event.name}: async function(${params}) {\n`);
522
+ this.indent++;
523
+ this.inEventHandler = true;
524
+ for (const stmt of node.body) {
525
+ code += this.generateStatement(stmt);
526
+ }
527
+ this.inEventHandler = false;
528
+ this.indent--;
529
+ code += this.emit(`${this.getIndent()}},\n`);
530
+ return code;
531
+ }
532
+
533
+ generateSkill(node) {
534
+ let code = '';
535
+ const skillName = node.name.name;
536
+
537
+ // Generate skill comment header
538
+ code += this.emit(`// ============================================================\n`);
539
+ code += this.emit(`// Skill: ${skillName}\n`);
540
+ if (node.affordance) {
541
+ code += this.emit(`// ${node.affordance.replace(/\n/g, '\\n// ')}\n`);
542
+ }
543
+ code += this.emit(`// ============================================================\n\n`);
544
+
545
+ // Generate internal agents
546
+ if (node.agents && node.agents.length > 0) {
547
+ for (const agent of node.agents) {
548
+ code += this.generateAgent(agent);
549
+ }
550
+ }
551
+
552
+ // Generate internal teams
553
+ if (node.teams && node.teams.length > 0) {
554
+ for (const team of node.teams) {
555
+ code += this.generateTeam(team);
556
+ }
557
+ }
558
+
559
+ // Generate exported functions
560
+ const functionNames = [];
561
+ if (node.functions && node.functions.length > 0) {
562
+ for (const func of node.functions) {
563
+ code += this.generateFunction(func);
564
+ if (func.isExport) {
565
+ functionNames.push(func.name.name);
566
+ }
567
+ }
568
+ }
569
+
570
+ // Register exported functions in SkillRegistry
571
+ if (functionNames.length > 0) {
572
+ code += this.emit(`// Register skill functions\n`);
573
+ for (const funcName of functionNames) {
574
+ code += this.emit(`SkillRegistry.register('${skillName}', '${funcName}', ${funcName}, { affordance: ${JSON.stringify(node.affordance || '')} });\n`);
575
+ }
576
+ code += this.emit(`\n`);
577
+ }
578
+
579
+ code += this.emit(`\n`);
580
+ return code;
581
+ }
582
+
583
+ generateFunction(node) {
584
+ let code = '';
585
+
586
+ // Function signature (strip TypeScript type annotations for JavaScript output)
587
+ const exportKeyword = node.isExport ? 'export ' : '';
588
+ const asyncKeyword = node.isAsync ? 'async ' : '';
589
+ const params = node.params ? node.params.map(p => p.name.name).join(', ') : '';
590
+
591
+ code += this.emit(`${exportKeyword}${asyncKeyword}function ${node.name.name}(${params}) {\n`, node);
592
+
593
+ // Function body - emit as raw code
594
+ if (node.body && node.body.code) {
595
+ this.indent++;
596
+ const bodyLines = node.body.code.split('\n');
597
+ for (const line of bodyLines) {
598
+ if (line.trim()) {
599
+ code += this.emit(`${this.getIndent()}${line}\n`);
600
+ } else {
601
+ code += this.emit(`\n`);
602
+ }
603
+ }
604
+ this.indent--;
605
+ }
606
+
607
+ code += this.emit(`}\n\n`);
608
+ return code;
609
+ }
610
+
611
+ generateTypeExpression(typeNode) {
612
+ if (!typeNode) return 'any';
613
+
614
+ switch (typeNode.type) {
615
+ case 'AnyType': return 'any';
616
+ case 'StringType': return 'string';
617
+ case 'NumberType': return 'number';
618
+ case 'BooleanType': return 'boolean';
619
+ case 'JsonType': return 'any';
620
+ case 'PromiseType':
621
+ return `Promise<${this.generateTypeExpression(typeNode.inner)}>`;
622
+ case 'CustomType':
623
+ return typeNode.name;
624
+ default:
625
+ return 'any';
626
+ }
627
+ }
628
+
629
+ generateRun(node) {
630
+ // Check if target is MemberExpression (Agent.event)
631
+ if (node.target.type === 'MemberExpression') {
632
+ const agent = this.generateExpression(node.target.object);
633
+ const event = typeof node.target.property === 'string'
634
+ ? node.target.property
635
+ : node.target.property.name;
636
+ const args = node.arguments.map(arg => this.generateExpression(arg)).join(', ');
637
+
638
+ return this.emit(
639
+ `\n// Run\n(async () => {\n const result = await ${agent}.handle('${event}', ${args});\n // Result handled by actions\n})();\n`,
640
+ node
641
+ );
642
+ } else {
643
+ // Direct function call
644
+ const target = this.generateExpression(node.target);
645
+ const args = node.arguments.map(arg => this.generateExpression(arg)).join(', ');
646
+ return this.emit(`\n// Run\n(async () => {\n const result = await ${target}(${args});\n // Result handled by actions\n})();\n`, node);
647
+ }
648
+ }
649
+
650
+ generateRunBody(node) {
651
+ // Generate just the body of a run statement (without IIFE wrapper)
652
+ // Used when generating coordinated main function
653
+ let code = '';
654
+
655
+ if (node.target.type === 'MemberExpression') {
656
+ const agent = this.generateExpression(node.target.object);
657
+ const event = typeof node.target.property === 'string'
658
+ ? node.target.property
659
+ : node.target.property.name;
660
+ const args = node.arguments.map(arg => this.generateExpression(arg)).join(', ');
661
+
662
+ code += this.emit(`${this.getIndent()}// Execute\n`);
663
+ code += this.emit(`${this.getIndent()}const result = await ${agent}.handle('${event}', ${args});\n`);
664
+ code += this.emit(`${this.getIndent()}// Result handled by actions\n\n`);
665
+ } else {
666
+ // Direct function call
667
+ const target = this.generateExpression(node.target);
668
+ const args = node.arguments.map(arg => this.generateExpression(arg)).join(', ');
669
+
670
+ code += this.emit(`${this.getIndent()}// Execute\n`);
671
+ code += this.emit(`${this.getIndent()}const result = await ${target}(${args});\n`);
672
+ code += this.emit(`${this.getIndent()}// Result handled by actions\n\n`);
673
+ }
674
+
675
+ return code;
676
+ }
677
+
678
+ // ============================================================
679
+ // Statements
680
+ // ============================================================
681
+
682
+ generateStatement(node) {
683
+ switch (node.type) {
684
+ case 'PlaybookStatement':
685
+ // Replace newlines with spaces and truncate safely
686
+ const playbookText = node.content.value.replace(/\n/g, ' ').replace(/\s+/g, ' ').trim();
687
+ const truncated = playbookText.length > 80 ? playbookText.substring(0, 80) + '...' : playbookText;
688
+ return this.emit(`${this.getIndent()}// Playbook: ${truncated}\n`, node);
689
+ case 'VariableDeclaration':
690
+ return this.generateVarDecl(node);
691
+ case 'ConstDeclaration':
692
+ return this.generateConstDecl(node);
693
+ case 'IfStatement':
694
+ return this.generateIf(node);
695
+ case 'ForStatement':
696
+ return this.generateFor(node);
697
+ case 'ForOfStatement':
698
+ return this.generateForOf(node);
699
+ case 'ForInStatement':
700
+ return this.generateForIn(node);
701
+ case 'WhileStatement':
702
+ return this.generateWhile(node);
703
+ case 'ReturnStatement':
704
+ return this.generateReturn(node);
705
+ case 'SendStatement':
706
+ return this.generateSend(node);
707
+ case 'UsePlaybookStatement':
708
+ return this.emit(`${this.getIndent()}// Use playbook: ${node.name.name || node.name.value}\n`, node);
709
+ case 'ExpressionStatement':
710
+ return this.emit(`${this.getIndent()}${this.generateExpression(node.expression)};\n`, node);
711
+ case 'CodeBlockStatement':
712
+ case 'RawCodeBlock':
713
+ // Emit raw code block with proper indentation
714
+ // Transform await send expressions to Runtime.send calls
715
+ const rawCode = this.transformSendExpressions(node.code);
716
+ const lines = rawCode.split('\n');
717
+ let code = '';
718
+ for (const line of lines) {
719
+ if (line.trim()) {
720
+ code += this.emit(`${this.getIndent()}${line}\n`);
721
+ } else {
722
+ code += this.emit('\n');
723
+ }
724
+ }
725
+ return code;
726
+ default:
727
+ return this.emit(`${this.getIndent()}/* Unknown statement: ${node.type} */\n`);
728
+ }
729
+ }
730
+
731
+ generateVarDecl(node) {
732
+ const init = node.init ? ` = ${this.generateExpression(node.init)}` : '';
733
+ return this.emit(`${this.getIndent()}let ${node.name.name}${init};\n`, node);
734
+ }
735
+
736
+ generateConstDecl(node) {
737
+ return this.emit(`${this.getIndent()}const ${node.name.name} = ${this.generateExpression(node.value)};\n`, node);
738
+ }
739
+
740
+ generateIf(node) {
741
+ let code = this.emit(`${this.getIndent()}if (${this.generateExpression(node.condition)}) {\n`, node);
742
+ this.indent++;
743
+ for (const stmt of node.then) {
744
+ code += this.generateStatement(stmt);
745
+ }
746
+ this.indent--;
747
+ code += this.emit(`${this.getIndent()}}`);
748
+
749
+ if (node.else && node.else.length > 0) {
750
+ code += this.emit(` else {\n`);
751
+ this.indent++;
752
+ for (const stmt of node.else) {
753
+ code += this.generateStatement(stmt);
754
+ }
755
+ this.indent--;
756
+ code += this.emit(`${this.getIndent()}}`);
757
+ }
758
+
759
+ code += this.emit(`\n`);
760
+ return code;
761
+ }
762
+
763
+ generateFor(node) {
764
+ const init = node.init ? this.generateExpression(node.init) : '';
765
+ const condition = node.condition ? this.generateExpression(node.condition) : '';
766
+ const update = node.update ? this.generateExpression(node.update) : '';
767
+ let code = this.emit(`${this.getIndent()}for (${init}; ${condition}; ${update}) {\n`, node);
768
+ this.indent++;
769
+ for (const stmt of node.body) {
770
+ code += this.generateStatement(stmt);
771
+ }
772
+ this.indent--;
773
+ code += this.emit(`${this.getIndent()}}\n`);
774
+ return code;
775
+ }
776
+
777
+ generateForOf(node) {
778
+ const decl = node.declaration || 'const';
779
+ const id = node.id.name || node.id;
780
+ const expr = this.generateExpression(node.expression);
781
+ let code = this.emit(`${this.getIndent()}for (${decl} ${id} of ${expr}) {\n`, node);
782
+ this.indent++;
783
+ for (const stmt of node.body) {
784
+ code += this.generateStatement(stmt);
785
+ }
786
+ this.indent--;
787
+ code += this.emit(`${this.getIndent()}}\n`);
788
+ return code;
789
+ }
790
+
791
+ generateForIn(node) {
792
+ const decl = node.declaration || 'const';
793
+ const id = node.id.name || node.id;
794
+ const expr = this.generateExpression(node.expression);
795
+ let code = this.emit(`${this.getIndent()}for (${decl} ${id} in ${expr}) {\n`, node);
796
+ this.indent++;
797
+ for (const stmt of node.body) {
798
+ code += this.generateStatement(stmt);
799
+ }
800
+ this.indent--;
801
+ code += this.emit(`${this.getIndent()}}\n`);
802
+ return code;
803
+ }
804
+
805
+ generateWhile(node) {
806
+ let code = this.emit(`${this.getIndent()}while (${this.generateExpression(node.condition)}) {\n`, node);
807
+ this.indent++;
808
+ for (const stmt of node.body) {
809
+ code += this.generateStatement(stmt);
810
+ }
811
+ this.indent--;
812
+ code += this.emit(`${this.getIndent()}}\n`);
813
+ return code;
814
+ }
815
+
816
+ generateReturn(node) {
817
+ const value = node.value ? ` ${this.generateExpression(node.value)}` : '';
818
+ return this.emit(`${this.getIndent()}return${value};\n`, node);
819
+ }
820
+
821
+ generateSend(node) {
822
+ let code = `await Runtime.send({\n`;
823
+ this.indent++;
824
+ code += `${this.getIndent()}base: ${this.generateExpression(node.target.base)},\n`;
825
+
826
+ if (node.target.filters.length > 0) {
827
+ code += `${this.getIndent()}filters: [\n`;
828
+ this.indent++;
829
+ for (const filter of node.target.filters) {
830
+ if (filter.type === 'EventFilter') {
831
+ code += `${this.getIndent()}{ type: 'event', name: ${this.generateExpression(filter.event)} },\n`;
832
+ } else if (filter.type === 'RoleFilter') {
833
+ code += `${this.getIndent()}{ type: 'role', role: ${filter.role.name} },\n`;
834
+ } else if (filter.type === 'SelectionFilter') {
835
+ code += `${this.getIndent()}{ type: 'select', mode: '${filter.mode}' },\n`;
836
+ }
837
+ }
838
+ this.indent--;
839
+ code += `${this.getIndent()}],\n`;
840
+ }
841
+
842
+ // Pass arguments: single argument directly, multiple as array
843
+ if (node.arguments.length === 1) {
844
+ code += `${this.getIndent()}args: ${this.generateExpression(node.arguments[0])},\n`;
845
+ } else if (node.arguments.length > 1) {
846
+ code += `${this.getIndent()}args: [${node.arguments.map(arg => this.generateExpression(arg)).join(', ')}],\n`;
847
+ } else {
848
+ code += `${this.getIndent()}args: {},\n`;
849
+ }
850
+
851
+ if (node.timeout) {
852
+ code += `${this.getIndent()}timeout: ${node.timeout.value}${node.timeout.unit === 's' ? '000' : ''}\n`;
853
+ }
854
+
855
+ this.indent--;
856
+ code += `${this.getIndent()}})`;
857
+ return this.emit(`${this.getIndent()}${code};\n`, node);
858
+ }
859
+
860
+ // ============================================================
861
+ // Expressions
862
+ // ============================================================
863
+
864
+ generateExpression(node) {
865
+ if (!node) return 'null';
866
+
867
+ switch (node.type) {
868
+ case 'BinaryExpression':
869
+ return `(${this.generateExpression(node.left)} ${node.operator} ${this.generateExpression(node.right)})`;
870
+ case 'UnaryExpression':
871
+ return `${node.operator}${this.generateExpression(node.argument)}`;
872
+ case 'NewExpression':
873
+ return this.generateNewExpression(node);
874
+ case 'CallExpression':
875
+ return this.generateCall(node);
876
+ case 'MemberExpression':
877
+ return this.generateMember(node);
878
+ case 'AwaitExpression':
879
+ return this.generateAwaitExpression(node);
880
+ case 'Identifier':
881
+ // Add 'this.' prefix for agent properties when inside event handler
882
+ if (this.inEventHandler && (node.name === 'peers' || node.name === 'state')) {
883
+ return `this.${node.name}`;
884
+ }
885
+ return node.name;
886
+ case 'StringLiteral':
887
+ return JSON.stringify(node.value);
888
+ case 'NumberLiteral':
889
+ return String(node.value);
890
+ case 'BooleanLiteral':
891
+ return String(node.value);
892
+ case 'NullLiteral':
893
+ return 'null';
894
+ case 'ObjectLiteral':
895
+ return this.generateObject(node);
896
+ case 'ArrayLiteral':
897
+ return this.generateArray(node);
898
+ case 'ArrowFunction':
899
+ return this.generateArrowFunction(node);
900
+ case 'TemplateLiteral':
901
+ return this.generateTemplateLiteral(node);
902
+ case 'AssignmentExpression':
903
+ return this.generateAssignment(node);
904
+ default:
905
+ return `/* Unknown expr: ${node.type} */`;
906
+ }
907
+ }
908
+
909
+ transformSendExpressions(code) {
910
+ // Transform: await send target.event("name").role(Role).any()(args) timeout Xs
911
+ // To: await Runtime.send({ base: this.target, filters: [...], args: args, timeout: X000 })
912
+
913
+ const sendRegex = /await\s+send\s+(\w+)\.event\("([^"]+)"\)((?:\.\w+\([^)]*\))*)\s*\(([^)]*)\)(?:\s+timeout\s+(\d+)(s|ms))?/g;
914
+
915
+ return code.replace(sendRegex, (match, target, eventName, chain, args, timeoutVal, timeoutUnit) => {
916
+ const filters = [`{ type: 'event', name: "${eventName}" }`];
917
+
918
+ // Parse the chain (.role(X).any())
919
+ if (chain) {
920
+ const roleMatch = chain.match(/\.role\((\w+)\)/);
921
+ if (roleMatch) {
922
+ filters.push(`{ type: 'role', role: ${roleMatch[1]} }`);
923
+ }
924
+
925
+ const selectMatch = chain.match(/\.(any|all|first)\(\)/);
926
+ if (selectMatch) {
927
+ filters.push(`{ type: 'select', mode: '${selectMatch[1]}' }`);
928
+ }
929
+ }
930
+
931
+ // If target is 'peers', it refers to this.peers in the agent context
932
+ const targetExpr = target === 'peers' ? 'this.peers' : target;
933
+
934
+ let result = `await Runtime.send({ base: ${targetExpr}, filters: [${filters.join(', ')}], args: ${args || '{}'}`;
935
+
936
+ if (timeoutVal) {
937
+ const timeout = timeoutUnit === 's' ? parseInt(timeoutVal) * 1000 : parseInt(timeoutVal);
938
+ result += `, timeout: ${timeout}`;
939
+ }
940
+
941
+ result += ' })';
942
+ return result;
943
+ });
944
+ }
945
+
946
+ generateAwaitExpression(node) {
947
+ // Check if this is a regular await expression (await someFunction())
948
+ // or a send expression (await send ...)
949
+ if (node.argument) {
950
+ // Regular await expression - just generate: await <expression>
951
+ return `await ${this.generateExpression(node.argument)}`;
952
+ }
953
+
954
+ // Send expression - generate Runtime.send(...)
955
+ let code = `await Runtime.send({\n`;
956
+ this.indent++;
957
+ code += `${this.getIndent()}base: ${this.generateExpression(node.target.base)},\n`;
958
+
959
+ if (node.target.filters.length > 0) {
960
+ code += `${this.getIndent()}filters: [\n`;
961
+ this.indent++;
962
+ for (const filter of node.target.filters) {
963
+ if (filter.type === 'EventFilter') {
964
+ code += `${this.getIndent()}{ type: 'event', name: ${this.generateExpression(filter.event)} },\n`;
965
+ } else if (filter.type === 'RoleFilter') {
966
+ code += `${this.getIndent()}{ type: 'role', role: ${filter.role.name} },\n`;
967
+ } else if (filter.type === 'SelectionFilter') {
968
+ code += `${this.getIndent()}{ type: 'select', mode: '${filter.mode}' },\n`;
969
+ }
970
+ }
971
+ this.indent--;
972
+ code += `${this.getIndent()}],\n`;
973
+ }
974
+
975
+ // Pass arguments: single argument directly, multiple as array
976
+ if (node.arguments.length === 1) {
977
+ code += `${this.getIndent()}args: ${this.generateExpression(node.arguments[0])},\n`;
978
+ } else if (node.arguments.length > 1) {
979
+ code += `${this.getIndent()}args: [${node.arguments.map(arg => this.generateExpression(arg)).join(', ')}],\n`;
980
+ } else {
981
+ code += `${this.getIndent()}args: {},\n`;
982
+ }
983
+
984
+ if (node.timeout) {
985
+ code += `${this.getIndent()}timeout: ${node.timeout.value}${node.timeout.unit === 's' ? '000' : ''}\n`;
986
+ }
987
+
988
+ this.indent--;
989
+ code += `${this.getIndent()}})`;
990
+ return code;
991
+ }
992
+
993
+ generateNewExpression(node) {
994
+ const callee = this.generateExpression(node.callee);
995
+ const args = node.arguments.map(arg => this.generateExpression(arg)).join(', ');
996
+ return `new ${callee}(${args})`;
997
+ }
998
+
999
+ generateCall(node) {
1000
+ // Special handling for peers(TeamName) - access specific team
1001
+ if (this.inEventHandler &&
1002
+ node.callee.type === 'Identifier' &&
1003
+ node.callee.name === 'peers' &&
1004
+ node.arguments.length === 1) {
1005
+ // peers(TeamName) → this._getTeam(TeamName)
1006
+ const teamName = this.generateExpression(node.arguments[0]);
1007
+ return `this._getTeam(${teamName})`;
1008
+ }
1009
+
1010
+ const callee = this.generateExpression(node.callee);
1011
+ const args = node.arguments.map(arg => this.generateExpression(arg)).join(', ');
1012
+ return `${callee}(${args})`;
1013
+ }
1014
+
1015
+ generateMember(node) {
1016
+ const obj = this.generateExpression(node.object);
1017
+
1018
+ // Check if property is computed (array access with brackets)
1019
+ if (node.computed || node.property.type === 'NumberLiteral') {
1020
+ const prop = this.generateExpression(node.property);
1021
+ return `${obj}[${prop}]`;
1022
+ }
1023
+
1024
+ // Regular property access with dot notation
1025
+ const prop = typeof node.property === 'string'
1026
+ ? node.property
1027
+ : node.property.name || this.generateExpression(node.property);
1028
+ return `${obj}.${prop}`;
1029
+ }
1030
+
1031
+ generateObject(node) {
1032
+ if (node.properties.length === 0) return '{}';
1033
+
1034
+ const props = node.properties.map(prop => {
1035
+ // Handle spread properties (...expr)
1036
+ if (prop.type === 'SpreadProperty') {
1037
+ return `...${this.generateExpression(prop.argument)}`;
1038
+ }
1039
+ // Regular key: value properties
1040
+ const key = prop.key.name || prop.key.value;
1041
+ const value = this.generateExpression(prop.value);
1042
+ return `${key}: ${value}`;
1043
+ }).join(', ');
1044
+
1045
+ return `{ ${props} }`;
1046
+ }
1047
+
1048
+ generateArray(node) {
1049
+ const elements = node.elements.map(el => this.generateExpression(el)).join(', ');
1050
+ return `[${elements}]`;
1051
+ }
1052
+
1053
+ generateArrowFunction(node) {
1054
+ // Generate parameters
1055
+ const params = node.params.map(p => p.name || p).join(', ');
1056
+
1057
+ // Generate body
1058
+ if (node.body.type === 'BlockStatement') {
1059
+ // Multi-statement body with {}
1060
+ let body = '{\n';
1061
+ this.indent++;
1062
+ for (const stmt of node.body.statements) {
1063
+ body += this.generateStatement(stmt);
1064
+ }
1065
+ this.indent--;
1066
+ body += `${this.getIndent()}}`;
1067
+ return `(${params}) => ${body}`;
1068
+ } else {
1069
+ // Expression body (implicit return)
1070
+ const body = this.generateExpression(node.body);
1071
+
1072
+ // If body is an object literal, wrap it in parentheses to avoid ambiguity
1073
+ if (node.body.type === 'ObjectLiteral') {
1074
+ return `(${params}) => (${body})`;
1075
+ }
1076
+
1077
+ return `(${params}) => ${body}`;
1078
+ }
1079
+ }
1080
+
1081
+ generateTemplateLiteral(node) {
1082
+ if (node.parts.length === 0) {
1083
+ return '``';
1084
+ }
1085
+
1086
+ const parts = node.parts.map(part => {
1087
+ if (part.type === 'TemplateString') {
1088
+ // Raw string part - keep as-is but escape backticks
1089
+ return part.value.replace(/`/g, '\\`');
1090
+ } else if (part.type === 'TemplateExpression') {
1091
+ // Expression part ${...}
1092
+ return '${' + this.generateExpression(part.expression) + '}';
1093
+ }
1094
+ return '';
1095
+ }).join('');
1096
+
1097
+ return `\`${parts}\``;
1098
+ }
1099
+
1100
+ generateAssignment(node) {
1101
+ const left = this.generateExpression(node.left);
1102
+ const right = this.generateExpression(node.right);
1103
+ return `${left} ${node.operator} ${right}`;
1104
+ }
1105
+ }