@koi-language/koi 1.0.5 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +4 -125
  2. package/examples/.build/agent-dialogue.ts +138 -0
  3. package/examples/.build/agent-dialogue.ts.map +1 -0
  4. package/examples/.build/chess.ts +77 -0
  5. package/examples/.build/chess.ts.map +1 -0
  6. package/examples/.build/delegation-test.ts +140 -0
  7. package/examples/.build/delegation-test.ts.map +1 -0
  8. package/examples/.build/dialog-demo.ts +77 -0
  9. package/examples/.build/dialog-demo.ts.map +1 -0
  10. package/examples/.build/hello-world.ts +77 -0
  11. package/examples/.build/hello-world.ts.map +1 -0
  12. package/examples/.build/lover-dialog-demo.ts +77 -0
  13. package/examples/.build/lover-dialog-demo.ts.map +1 -0
  14. package/examples/.build/package.json +3 -0
  15. package/examples/.build/registry-interactive-demo.ts +202 -0
  16. package/examples/.build/registry-interactive-demo.ts.map +1 -0
  17. package/examples/.build/registry-playbook-demo.ts +201 -0
  18. package/examples/.build/registry-playbook-demo.ts.map +1 -0
  19. package/examples/.build/tic-tac-toe.ts +77 -0
  20. package/examples/.build/tic-tac-toe.ts.map +1 -0
  21. package/examples/actions-demo.koi +8 -9
  22. package/examples/activists-dialogue.koi +75 -0
  23. package/examples/agent-dialogue.koi +66 -0
  24. package/examples/chess.koi +19 -0
  25. package/examples/counter.koi +20 -69
  26. package/examples/delegation-test.koi +16 -18
  27. package/examples/dialog-demo.koi +20 -0
  28. package/examples/hello-world.koi +7 -43
  29. package/examples/mcp-stdio-demo.koi +29 -0
  30. package/examples/memory-test.koi +49 -0
  31. package/examples/mobile-mcp-demo.koi +32 -0
  32. package/examples/multi-event-handler-test.koi +16 -18
  33. package/examples/pipeline.koi +15 -17
  34. package/examples/prompt-demo.koi +20 -0
  35. package/examples/{registry-playbook-email-compositor.koi → registry-interactive-demo.koi} +27 -27
  36. package/examples/registry-playbook-demo.koi +28 -28
  37. package/examples/skill-import-test.koi +7 -9
  38. package/examples/skills/.build/math-operations.ts +1656 -0
  39. package/examples/skills/.build/math-operations.ts.map +1 -0
  40. package/examples/skills/.build/package.json +3 -0
  41. package/examples/skills/.build/string-operations.ts +1643 -0
  42. package/examples/skills/.build/string-operations.ts.map +1 -0
  43. package/examples/skills/advanced/.build/index.ts +3223 -0
  44. package/examples/skills/advanced/.build/index.ts.map +1 -0
  45. package/examples/skills/advanced/.build/package.json +3 -0
  46. package/examples/skills/advanced/index.koi +3 -5
  47. package/examples/skills/math-operations.koi +1 -3
  48. package/examples/skills/string-operations.koi +1 -3
  49. package/examples/tic-tac-toe.koi +19 -0
  50. package/examples/utils/echo-mcp-server.js +141 -0
  51. package/examples/web-delegation-demo.koi +15 -17
  52. package/package.json +2 -1
  53. package/src/cli/koi.js +30 -41
  54. package/src/compiler/build-optimizer.js +204 -289
  55. package/src/compiler/cache-manager.js +1 -1
  56. package/src/compiler/import-resolver.js +5 -9
  57. package/src/compiler/parser.js +6072 -3476
  58. package/src/compiler/transpiler.js +346 -38
  59. package/src/grammar/koi.pegjs +302 -62
  60. package/src/runtime/actions/{format.js → call-llm.js} +37 -44
  61. package/src/runtime/actions/call-mcp.js +97 -0
  62. package/src/runtime/actions/if.js +179 -0
  63. package/src/runtime/actions/print.js +3 -1
  64. package/src/runtime/actions/prompt-user.js +75 -0
  65. package/src/runtime/actions/repeat.js +147 -0
  66. package/src/runtime/actions/shell.js +185 -0
  67. package/src/runtime/actions/while.js +205 -0
  68. package/src/runtime/agent.js +592 -178
  69. package/src/runtime/cli-display.js +26 -0
  70. package/src/runtime/cli-input.js +421 -0
  71. package/src/runtime/cli-logger.js +2 -5
  72. package/src/runtime/cli-markdown.js +61 -0
  73. package/src/runtime/cli-select.js +106 -0
  74. package/src/runtime/incremental-json-parser.js +51 -17
  75. package/src/runtime/index.js +1 -0
  76. package/src/runtime/llm-provider.js +1083 -572
  77. package/src/runtime/mcp-registry.js +141 -0
  78. package/src/runtime/mcp-stdio-client.js +334 -0
  79. package/src/runtime/planner.js +1 -1
  80. package/src/runtime/playbook-session.js +259 -0
  81. package/src/runtime/registry-backends/keyv-sqlite.js +1 -1
  82. package/src/runtime/registry-backends/local.js +1 -1
  83. package/src/runtime/router.js +22 -26
  84. package/src/runtime/runtime.js +7 -1
  85. package/examples/cache-test.koi +0 -29
  86. package/examples/calculator.koi +0 -61
  87. package/examples/clear-registry.js +0 -33
  88. package/examples/clear-registry.koi +0 -30
  89. package/examples/code-introspection-test.koi +0 -149
  90. package/examples/directory-import-test.koi +0 -84
  91. package/examples/hello-world-claude.koi +0 -52
  92. package/examples/hello.koi +0 -24
  93. package/examples/mcp-example.koi +0 -70
  94. package/examples/new-import-test.koi +0 -89
  95. package/examples/registry-demo.koi +0 -184
  96. package/examples/registry-playbook-email-compositor-2.koi +0 -140
  97. package/examples/sentiment.koi +0 -90
  98. package/examples/simple.koi +0 -48
  99. package/examples/task-chaining-demo.koi +0 -244
  100. package/examples/test-await.koi +0 -22
  101. package/examples/test-crypto-sha256.koi +0 -196
  102. package/examples/test-delegation.koi +0 -41
  103. package/examples/test-multi-team-routing.koi +0 -258
  104. package/examples/test-no-handler.koi +0 -35
  105. package/examples/test-npm-import.koi +0 -67
  106. package/examples/test-parse.koi +0 -10
  107. package/examples/test-peers-with-team.koi +0 -59
  108. package/examples/test-permissions-fail.koi +0 -20
  109. package/examples/test-permissions.koi +0 -36
  110. package/examples/test-simple-registry.koi +0 -31
  111. package/examples/test-typescript-import.koi +0 -64
  112. package/examples/test-uses-team-syntax.koi +0 -25
  113. package/examples/test-uses-team.koi +0 -31
@@ -116,7 +116,11 @@ export class KoiTranspiler {
116
116
  // Store routerImportPath for later use
117
117
  this.routerImportPath = routerImportPath;
118
118
 
119
- code += this.emit(`import { Agent, Team, Skill, Role, Runtime, SkillRegistry, skillSelector, registry } from '${runtimeImportPath}';\n\n`);
119
+ code += this.emit(`import { Agent, Team, Skill, Role, Runtime, SkillRegistry, skillSelector, registry, mcpRegistry } from '${runtimeImportPath}';\n`);
120
+
121
+ // Add CommonJS compatibility for require() in ES modules
122
+ code += this.emit(`import { createRequire } from 'module';\n`);
123
+ code += this.emit(`const require = createRequire(import.meta.url);\n\n`);
120
124
 
121
125
  // Generate imports for external TypeScript/JavaScript modules
122
126
  if (this.externalImports && this.externalImports.length > 0) {
@@ -168,10 +172,11 @@ export class KoiTranspiler {
168
172
  code += this.emit(`\n`);
169
173
  }
170
174
 
171
- // Make SkillRegistry and registry available globally
175
+ // Make SkillRegistry, registry, and mcpRegistry available globally
172
176
  code += this.emit(`globalThis.SkillRegistry = SkillRegistry;\n`);
173
177
  code += this.emit(`globalThis.skillSelector = skillSelector;\n`);
174
- code += this.emit(`globalThis.registry = registry;\n\n`);
178
+ code += this.emit(`globalThis.registry = registry;\n`);
179
+ code += this.emit(`globalThis.mcpRegistry = mcpRegistry;\n\n`);
175
180
 
176
181
  // Inject build-time cache if available
177
182
  if (this.cacheData && this.cacheData.affordances) {
@@ -182,28 +187,30 @@ export class KoiTranspiler {
182
187
  const agentDecls = [];
183
188
  const skillDecls = [];
184
189
  const runStatements = [];
185
- const otherDecls = [];
190
+ const sortableDecls = [];
186
191
 
187
192
  for (const decl of node.declarations) {
188
193
  if (decl.type === 'RunStatement') {
189
194
  runStatements.push(decl);
190
- } else if (decl.type === 'AgentDecl') {
191
- agentDecls.push(decl);
192
- } else if (decl.type === 'SkillDecl') {
193
- skillDecls.push(decl);
194
195
  } else {
195
- otherDecls.push(decl);
196
+ sortableDecls.push(decl);
197
+ if (decl.type === 'AgentDecl') agentDecls.push(decl);
198
+ else if (decl.type === 'SkillDecl') skillDecls.push(decl);
196
199
  }
197
200
  }
198
201
 
199
- // Generate all declarations in original order (except RunStatements)
200
- // This preserves dependencies between roles, teams, and agents
202
+ // Generate declarations in dependency order using topological sort.
203
+ // Dependencies:
204
+ // AgentDecl → depends on teams from "uses team X" body items
205
+ // TeamDecl → depends on its member agents
206
+ // RoleDecl, SkillDecl, MCPDecl → no deps (generated first)
201
207
  this.skipAgentRegistration = true;
202
- for (const decl of node.declarations) {
203
- if (decl.type !== 'RunStatement') {
204
- code += this.generateDeclaration(decl);
205
- }
208
+
209
+ const sorted = this._topologicalSortDeclarations(sortableDecls);
210
+ for (const decl of sorted) {
211
+ code += this.generateDeclaration(decl);
206
212
  }
213
+
207
214
  this.skipAgentRegistration = false;
208
215
 
209
216
  // Generate main async function that coordinates everything
@@ -265,7 +272,13 @@ export class KoiTranspiler {
265
272
  code += this.generateRunBody(runStmt);
266
273
  }
267
274
 
275
+ // Graceful MCP shutdown on signals
276
+ code += this.emit(`${this.getIndent()}const _gracefulShutdown = async () => { await mcpRegistry.disconnectAll(); process.exit(0); };\n`);
277
+ code += this.emit(`${this.getIndent()}process.on('SIGINT', _gracefulShutdown);\n`);
278
+ code += this.emit(`${this.getIndent()}process.on('SIGTERM', _gracefulShutdown);\n\n`);
279
+
268
280
  // Clean exit
281
+ code += this.emit(`${this.getIndent()}await mcpRegistry.disconnectAll();\n`);
269
282
  code += this.emit(`${this.getIndent()}process.exit(0);\n`);
270
283
 
271
284
  this.indent--;
@@ -296,6 +309,125 @@ export class KoiTranspiler {
296
309
  return code;
297
310
  }
298
311
 
312
+ // ============================================================
313
+ // Declaration ordering (topological sort)
314
+ // ============================================================
315
+
316
+ /**
317
+ * Extract the name of a declaration node.
318
+ */
319
+ _getDeclName(decl) {
320
+ if (decl.name && decl.name.name) return decl.name.name;
321
+ if (decl.name && typeof decl.name === 'string') return decl.name;
322
+ return null;
323
+ }
324
+
325
+ /**
326
+ * Get dependency names for a declaration.
327
+ * AgentDecl → depends on teams from "uses team X"
328
+ * TeamDecl → depends on its member agents
329
+ * Others → no deps
330
+ */
331
+ _getDeclDeps(decl) {
332
+ const deps = [];
333
+ if (decl.type === 'AgentDecl' && decl.body) {
334
+ for (const item of decl.body) {
335
+ if (item.type === 'UsesTeam') {
336
+ const teamName = item.team?.name || (typeof item.team === 'string' ? item.team : null);
337
+ if (teamName) deps.push(teamName);
338
+ }
339
+ }
340
+ } else if (decl.type === 'TeamDecl' && decl.members) {
341
+ for (const member of decl.members) {
342
+ let agentName = null;
343
+ if (member.value?.type === 'AgentReference') {
344
+ agentName = member.value.agent.name;
345
+ } else if (member.value?.type === 'Identifier') {
346
+ agentName = member.value.name;
347
+ }
348
+ if (agentName) deps.push(agentName);
349
+ }
350
+ }
351
+ return deps;
352
+ }
353
+
354
+ /**
355
+ * Topological sort of declarations to prevent forward reference errors.
356
+ * Roles, Skills, MCPs have no deps and go first.
357
+ * Agents and Teams are sorted by their dependency graph.
358
+ */
359
+ _topologicalSortDeclarations(decls) {
360
+ // Priority for types without dependencies (lower = earlier)
361
+ const typePriority = { RoleDecl: 0, MCPDecl: 1, SkillDecl: 2 };
362
+
363
+ // Split into no-dep types (always first) and graph-sorted types
364
+ const noDeps = [];
365
+ const graphDecls = [];
366
+ for (const decl of decls) {
367
+ if (typePriority[decl.type] !== undefined) {
368
+ noDeps.push(decl);
369
+ } else if (decl.type === 'AgentDecl' || decl.type === 'TeamDecl') {
370
+ graphDecls.push(decl);
371
+ } else {
372
+ noDeps.push(decl);
373
+ }
374
+ }
375
+
376
+ // Sort no-dep types by priority
377
+ noDeps.sort((a, b) => (typePriority[a.type] ?? 99) - (typePriority[b.type] ?? 99));
378
+
379
+ if (graphDecls.length === 0) return [...noDeps];
380
+
381
+ // Build adjacency: name → decl, name → deps
382
+ const nameToDecl = new Map();
383
+ const nameToIdx = new Map();
384
+ for (let i = 0; i < graphDecls.length; i++) {
385
+ const name = this._getDeclName(graphDecls[i]);
386
+ if (name) {
387
+ nameToDecl.set(name, graphDecls[i]);
388
+ nameToIdx.set(name, i);
389
+ }
390
+ }
391
+
392
+ // Kahn's algorithm for topological sort
393
+ const inDegree = new Array(graphDecls.length).fill(0);
394
+ const adj = new Array(graphDecls.length).fill(null).map(() => []);
395
+
396
+ for (let i = 0; i < graphDecls.length; i++) {
397
+ const deps = this._getDeclDeps(graphDecls[i]);
398
+ for (const dep of deps) {
399
+ const j = nameToIdx.get(dep);
400
+ if (j !== undefined && j !== i) {
401
+ adj[j].push(i); // j must come before i
402
+ inDegree[i]++;
403
+ }
404
+ }
405
+ }
406
+
407
+ const queue = [];
408
+ for (let i = 0; i < graphDecls.length; i++) {
409
+ if (inDegree[i] === 0) queue.push(i);
410
+ }
411
+
412
+ const sorted = [];
413
+ while (queue.length > 0) {
414
+ const i = queue.shift();
415
+ sorted.push(graphDecls[i]);
416
+ for (const next of adj[i]) {
417
+ inDegree[next]--;
418
+ if (inDegree[next] === 0) queue.push(next);
419
+ }
420
+ }
421
+
422
+ // If cycle detected, fall back to original order
423
+ if (sorted.length < graphDecls.length) {
424
+ console.warn('[Transpiler] Circular dependency detected in declarations, using original order');
425
+ return [...noDeps, ...graphDecls];
426
+ }
427
+
428
+ return [...noDeps, ...sorted];
429
+ }
430
+
299
431
  // ============================================================
300
432
  // Declarations
301
433
  // ============================================================
@@ -303,7 +435,7 @@ export class KoiTranspiler {
303
435
  generateDeclaration(node) {
304
436
  switch (node.type) {
305
437
  case 'PackageDecl':
306
- return this.emit(`// Package: ${node.name.value}\n\n`, node);
438
+ return ''; // Package declarations are ignored (kept for backwards compatibility)
307
439
  case 'ImportDecl':
308
440
  return this.generateImport(node);
309
441
  case 'RoleDecl':
@@ -314,6 +446,8 @@ export class KoiTranspiler {
314
446
  return this.generateAgent(node);
315
447
  case 'SkillDecl':
316
448
  return this.generateSkill(node);
449
+ case 'MCPDecl':
450
+ return this.generateMCP(node);
317
451
  case 'RunStatement':
318
452
  return this.generateRun(node);
319
453
  default:
@@ -369,6 +503,12 @@ export class KoiTranspiler {
369
503
  return code;
370
504
  }
371
505
 
506
+ generateMCP(node) {
507
+ let code = this.emit(`// MCP Server: ${node.name.name}\n`, node);
508
+ code += this.emit(`mcpRegistry.register('${node.name.name}', ${this.generateExpression(node.config)});\n\n`);
509
+ return code;
510
+ }
511
+
372
512
  generateAgent(node) {
373
513
  let code = this.emit(`const ${node.name.name} = new Agent({\n`, node);
374
514
  this.indent++;
@@ -378,11 +518,13 @@ export class KoiTranspiler {
378
518
  // Extract body items
379
519
  const skills = node.body.filter(b => b.type === 'UsesSkill');
380
520
  const usesTeams = node.body.filter(b => b.type === 'UsesTeam');
521
+ const usesMCP = node.body.filter(b => b.type === 'UsesMCP');
381
522
  const llmConfig = node.body.find(b => b.type === 'LLMConfig');
382
523
  const eventHandlers = node.body.filter(b => b.type === 'EventHandler');
383
524
  const state = node.body.find(b => b.type === 'StateDecl');
384
525
  const playbooks = node.body.filter(b => b.type === 'PlaybookDecl');
385
526
  const resilience = node.body.find(b => b.type === 'ResilienceDecl');
527
+ const amnesia = node.body.find(b => b.type === 'AmnesiaDecl');
386
528
  const peers = node.body.find(b => b.type === 'PeersDecl');
387
529
 
388
530
  // Track if agent has handlers for auto-registration
@@ -402,10 +544,18 @@ export class KoiTranspiler {
402
544
  }
403
545
  }
404
546
 
547
+ if (usesMCP.length > 0) {
548
+ code += this.emit(`${this.getIndent()}usesMCP: [${usesMCP.map(m => `'${m.mcp.name}'`).join(', ')}],\n`);
549
+ }
550
+
405
551
  if (llmConfig) {
406
552
  code += this.emit(`${this.getIndent()}llm: ${this.generateExpression(llmConfig.config)},\n`);
407
553
  }
408
554
 
555
+ if (amnesia) {
556
+ code += this.emit(`${this.getIndent()}amnesia: ${amnesia.value.value},\n`);
557
+ }
558
+
409
559
  if (state) {
410
560
  code += this.emit(`${this.getIndent()}state: {\n`);
411
561
  this.indent++;
@@ -454,7 +604,7 @@ export class KoiTranspiler {
454
604
  code += this.emit(`${this.getIndent()}handlers: {\n`);
455
605
  this.indent++;
456
606
  for (const handler of eventHandlers) {
457
- code += this.generateEventHandler(handler);
607
+ code += this.generateEventHandler(handler, node.name.name);
458
608
  }
459
609
  this.indent--;
460
610
  code += this.emit(`${this.getIndent()}}\n`);
@@ -475,7 +625,7 @@ export class KoiTranspiler {
475
625
 
476
626
  if (hasCachedAffordances) {
477
627
  // Use cached affordances (no embedding generation needed)
478
- code += this.emit(`${this.getIndent()}// Using pre-computed embeddings from build cache\n`);
628
+ code += this.emit(`${this.getIndent()}// Using pre-generated descriptions from build cache\n`);
479
629
  code += this.emit(`${this.getIndent()}await agentRouter.register(${agentName}, CACHED_AFFORDANCES['${agentName}']);\n`);
480
630
  } else {
481
631
  // No cache, generate at runtime
@@ -490,8 +640,12 @@ export class KoiTranspiler {
490
640
  return code;
491
641
  }
492
642
 
493
- generateEventHandler(node) {
643
+ generateEventHandler(node, agentName = null) {
494
644
  const params = node.params.map(p => p.name.name).join(', ');
645
+ const handlerName = node.event.name;
646
+
647
+ // Look up cached description for this handler
648
+ const cachedDesc = agentName && this.cacheData?.affordances?.[agentName]?.[handlerName]?.description;
495
649
 
496
650
  // Check if this is a playbook-only handler (only has PlaybookStatement, no other code)
497
651
  const hasOnlyPlaybook = node.body.length === 1 && node.body[0].type === 'PlaybookStatement';
@@ -501,7 +655,7 @@ export class KoiTranspiler {
501
655
  const playbook = node.body[0].content.value;
502
656
  const escapedPlaybook = JSON.stringify(playbook);
503
657
 
504
- let code = this.emit(`${this.getIndent()}${node.event.name}: (() => {\n`);
658
+ let code = this.emit(`${this.getIndent()}${handlerName}: (() => {\n`);
505
659
  this.indent++;
506
660
  code += this.emit(`${this.getIndent()}const handler = async function(${params}) {\n`);
507
661
  this.indent++;
@@ -511,6 +665,9 @@ export class KoiTranspiler {
511
665
  code += this.emit(`${this.getIndent()}};\n`);
512
666
  code += this.emit(`${this.getIndent()}handler.__playbookOnly__ = true;\n`);
513
667
  code += this.emit(`${this.getIndent()}handler.__playbook__ = ${escapedPlaybook};\n`);
668
+ if (cachedDesc) {
669
+ code += this.emit(`${this.getIndent()}handler.__description__ = ${JSON.stringify(cachedDesc)};\n`);
670
+ }
514
671
  code += this.emit(`${this.getIndent()}return handler;\n`);
515
672
  this.indent--;
516
673
  code += this.emit(`${this.getIndent()}})(),\n`);
@@ -518,7 +675,27 @@ export class KoiTranspiler {
518
675
  }
519
676
 
520
677
  // Regular handler with code
521
- let code = this.emit(`${this.getIndent()}${node.event.name}: async function(${params}) {\n`);
678
+ if (cachedDesc) {
679
+ // Wrap in IIFE to attach __description__
680
+ let code = this.emit(`${this.getIndent()}${handlerName}: (() => {\n`);
681
+ this.indent++;
682
+ code += this.emit(`${this.getIndent()}const handler = async function(${params}) {\n`);
683
+ this.indent++;
684
+ this.inEventHandler = true;
685
+ for (const stmt of node.body) {
686
+ code += this.generateStatement(stmt);
687
+ }
688
+ this.inEventHandler = false;
689
+ this.indent--;
690
+ code += this.emit(`${this.getIndent()}};\n`);
691
+ code += this.emit(`${this.getIndent()}handler.__description__ = ${JSON.stringify(cachedDesc)};\n`);
692
+ code += this.emit(`${this.getIndent()}return handler;\n`);
693
+ this.indent--;
694
+ code += this.emit(`${this.getIndent()}})(),\n`);
695
+ return code;
696
+ }
697
+
698
+ let code = this.emit(`${this.getIndent()}${handlerName}: async function(${params}) {\n`);
522
699
  this.indent++;
523
700
  this.inEventHandler = true;
524
701
  for (const stmt of node.body) {
@@ -542,6 +719,35 @@ export class KoiTranspiler {
542
719
  }
543
720
  code += this.emit(`// ============================================================\n\n`);
544
721
 
722
+ // Check if we need to create a closure for local scope
723
+ const hasLocalDeclarations = (node.constants && node.constants.length > 0) ||
724
+ (node.variables && node.variables.length > 0) ||
725
+ (node.functions && node.functions.some(f => !f.isExport));
726
+
727
+ const exportedFunctionNames = node.functions ? node.functions.filter(f => f.isExport).map(f => f.name.name) : [];
728
+
729
+ if (hasLocalDeclarations && exportedFunctionNames.length > 0) {
730
+ // Use IIFE to create local scope and export functions
731
+ code += this.emit(`const { ${exportedFunctionNames.join(', ')} } = (() => {\n`);
732
+ this.indent++;
733
+ }
734
+
735
+ // Generate constants
736
+ if (node.constants && node.constants.length > 0) {
737
+ for (const constDecl of node.constants) {
738
+ code += this.generateSkillConstant(constDecl);
739
+ }
740
+ code += this.emit(`\n`);
741
+ }
742
+
743
+ // Generate variables
744
+ if (node.variables && node.variables.length > 0) {
745
+ for (const varDecl of node.variables) {
746
+ code += this.generateSkillVariable(varDecl);
747
+ }
748
+ code += this.emit(`\n`);
749
+ }
750
+
545
751
  // Generate internal agents
546
752
  if (node.agents && node.agents.length > 0) {
547
753
  for (const agent of node.agents) {
@@ -556,21 +762,28 @@ export class KoiTranspiler {
556
762
  }
557
763
  }
558
764
 
559
- // Generate exported functions
560
- const functionNames = [];
765
+ // Generate all functions (exported and non-exported)
561
766
  if (node.functions && node.functions.length > 0) {
562
767
  for (const func of node.functions) {
563
- code += this.generateFunction(func);
564
- if (func.isExport) {
565
- functionNames.push(func.name.name);
566
- }
768
+ // Remove 'export' keyword if inside IIFE closure
769
+ const funcCopy = hasLocalDeclarations && exportedFunctionNames.length > 0
770
+ ? { ...func, isExport: false }
771
+ : func;
772
+ code += this.generateFunction(funcCopy);
567
773
  }
568
774
  }
569
775
 
776
+ // Return exported functions from IIFE
777
+ if (hasLocalDeclarations && exportedFunctionNames.length > 0) {
778
+ code += this.emit(`${this.getIndent()}return { ${exportedFunctionNames.join(', ')} };\n`);
779
+ this.indent--;
780
+ code += this.emit(`})();\n\n`);
781
+ }
782
+
570
783
  // Register exported functions in SkillRegistry
571
- if (functionNames.length > 0) {
784
+ if (exportedFunctionNames.length > 0) {
572
785
  code += this.emit(`// Register skill functions\n`);
573
- for (const funcName of functionNames) {
786
+ for (const funcName of exportedFunctionNames) {
574
787
  code += this.emit(`SkillRegistry.register('${skillName}', '${funcName}', ${funcName}, { affordance: ${JSON.stringify(node.affordance || '')} });\n`);
575
788
  }
576
789
  code += this.emit(`\n`);
@@ -586,7 +799,14 @@ export class KoiTranspiler {
586
799
  // Function signature (strip TypeScript type annotations for JavaScript output)
587
800
  const exportKeyword = node.isExport ? 'export ' : '';
588
801
  const asyncKeyword = node.isAsync ? 'async ' : '';
589
- const params = node.params ? node.params.map(p => p.name.name).join(', ') : '';
802
+ const params = node.params ? node.params.map(p => {
803
+ const paramName = p.name.name;
804
+ if (p.default) {
805
+ const defaultValue = this.generateExpression(p.default);
806
+ return `${paramName} = ${defaultValue}`;
807
+ }
808
+ return paramName;
809
+ }).join(', ') : '';
590
810
 
591
811
  code += this.emit(`${exportKeyword}${asyncKeyword}function ${node.name.name}(${params}) {\n`, node);
592
812
 
@@ -608,6 +828,44 @@ export class KoiTranspiler {
608
828
  return code;
609
829
  }
610
830
 
831
+ generateSkillConstant(node) {
832
+ const pattern = this.generateDestructuringPattern(node.pattern);
833
+ const value = this.generateExpression(node.value);
834
+ return this.emit(`${this.getIndent()}const ${pattern} = ${value};\n`);
835
+ }
836
+
837
+ generateSkillVariable(node) {
838
+ const pattern = this.generateDestructuringPattern(node.pattern);
839
+ const init = node.init ? ` = ${this.generateExpression(node.init)}` : '';
840
+ return this.emit(`${this.getIndent()}let ${pattern}${init};\n`);
841
+ }
842
+
843
+ generateDestructuringPattern(pattern) {
844
+ if (typeof pattern === 'string') {
845
+ return pattern;
846
+ }
847
+
848
+ if (pattern.type === 'Identifier') {
849
+ return pattern.name;
850
+ }
851
+
852
+ if (pattern.type === 'ObjectPattern') {
853
+ const props = pattern.properties.map(prop => {
854
+ if (prop.key === prop.value) {
855
+ return prop.key;
856
+ }
857
+ return `${prop.key}: ${prop.value}`;
858
+ }).join(', ');
859
+ return `{ ${props} }`;
860
+ }
861
+
862
+ if (pattern.type === 'ArrayPattern') {
863
+ return `[ ${pattern.elements.join(', ')} ]`;
864
+ }
865
+
866
+ return pattern;
867
+ }
868
+
611
869
  generateTypeExpression(typeNode) {
612
870
  if (!typeNode) return 'any';
613
871
 
@@ -702,6 +960,10 @@ export class KoiTranspiler {
702
960
  return this.generateWhile(node);
703
961
  case 'ReturnStatement':
704
962
  return this.generateReturn(node);
963
+ case 'ThrowStatement':
964
+ return this.generateThrow(node);
965
+ case 'TryStatement':
966
+ return this.generateTry(node);
705
967
  case 'SendStatement':
706
968
  return this.generateSend(node);
707
969
  case 'UsePlaybookStatement':
@@ -818,6 +1080,43 @@ export class KoiTranspiler {
818
1080
  return this.emit(`${this.getIndent()}return${value};\n`, node);
819
1081
  }
820
1082
 
1083
+ generateThrow(node) {
1084
+ return this.emit(`${this.getIndent()}throw ${this.generateExpression(node.argument)};\n`, node);
1085
+ }
1086
+
1087
+ generateTry(node) {
1088
+ let code = this.emit(`${this.getIndent()}try {\n`, node);
1089
+ this.indent++;
1090
+ for (const stmt of node.body) {
1091
+ code += this.generateStatement(stmt);
1092
+ }
1093
+ this.indent--;
1094
+ code += this.emit(`${this.getIndent()}}`);
1095
+
1096
+ if (node.handler) {
1097
+ code += this.emit(` catch (${node.handler.param.name}) {\n`);
1098
+ this.indent++;
1099
+ for (const stmt of node.handler.body) {
1100
+ code += this.generateStatement(stmt);
1101
+ }
1102
+ this.indent--;
1103
+ code += this.emit(`${this.getIndent()}}`);
1104
+ }
1105
+
1106
+ if (node.finalizer) {
1107
+ code += this.emit(` finally {\n`);
1108
+ this.indent++;
1109
+ for (const stmt of node.finalizer) {
1110
+ code += this.generateStatement(stmt);
1111
+ }
1112
+ this.indent--;
1113
+ code += this.emit(`${this.getIndent()}}`);
1114
+ }
1115
+
1116
+ code += this.emit('\n');
1117
+ return code;
1118
+ }
1119
+
821
1120
  generateSend(node) {
822
1121
  let code = `await Runtime.send({\n`;
823
1122
  this.indent++;
@@ -849,9 +1148,11 @@ export class KoiTranspiler {
849
1148
  }
850
1149
 
851
1150
  if (node.timeout) {
852
- code += `${this.getIndent()}timeout: ${node.timeout.value}${node.timeout.unit === 's' ? '000' : ''}\n`;
1151
+ code += `${this.getIndent()}timeout: ${node.timeout.value}${node.timeout.unit === 's' ? '000' : ''},\n`;
853
1152
  }
854
1153
 
1154
+ code += `${this.getIndent()}caller: this\n`;
1155
+
855
1156
  this.indent--;
856
1157
  code += `${this.getIndent()}})`;
857
1158
  return this.emit(`${this.getIndent()}${code};\n`, node);
@@ -885,6 +1186,8 @@ export class KoiTranspiler {
885
1186
  return node.name;
886
1187
  case 'StringLiteral':
887
1188
  return JSON.stringify(node.value);
1189
+ case 'RegexLiteral':
1190
+ return `/${node.pattern}/${node.flags}`;
888
1191
  case 'NumberLiteral':
889
1192
  return String(node.value);
890
1193
  case 'BooleanLiteral':
@@ -901,6 +1204,8 @@ export class KoiTranspiler {
901
1204
  return this.generateTemplateLiteral(node);
902
1205
  case 'AssignmentExpression':
903
1206
  return this.generateAssignment(node);
1207
+ case 'ConditionalExpression':
1208
+ return `(${this.generateExpression(node.test)} ? ${this.generateExpression(node.consequent)} : ${this.generateExpression(node.alternate)})`;
904
1209
  default:
905
1210
  return `/* Unknown expr: ${node.type} */`;
906
1211
  }
@@ -938,7 +1243,7 @@ export class KoiTranspiler {
938
1243
  result += `, timeout: ${timeout}`;
939
1244
  }
940
1245
 
941
- result += ' })';
1246
+ result += ', caller: this })';
942
1247
  return result;
943
1248
  });
944
1249
  }
@@ -1009,23 +1314,25 @@ export class KoiTranspiler {
1009
1314
 
1010
1315
  const callee = this.generateExpression(node.callee);
1011
1316
  const args = node.arguments.map(arg => this.generateExpression(arg)).join(', ');
1012
- return `${callee}(${args})`;
1317
+ const optional = node.optional ? '?.' : '';
1318
+ return `${callee}${optional}(${args})`;
1013
1319
  }
1014
1320
 
1015
1321
  generateMember(node) {
1016
1322
  const obj = this.generateExpression(node.object);
1323
+ const optional = node.optional ? '?' : '';
1017
1324
 
1018
1325
  // Check if property is computed (array access with brackets)
1019
1326
  if (node.computed || node.property.type === 'NumberLiteral') {
1020
1327
  const prop = this.generateExpression(node.property);
1021
- return `${obj}[${prop}]`;
1328
+ return `${obj}${optional}[${prop}]`;
1022
1329
  }
1023
1330
 
1024
1331
  // Regular property access with dot notation
1025
1332
  const prop = typeof node.property === 'string'
1026
1333
  ? node.property
1027
1334
  : node.property.name || this.generateExpression(node.property);
1028
- return `${obj}.${prop}`;
1335
+ return `${obj}${optional}.${prop}`;
1029
1336
  }
1030
1337
 
1031
1338
  generateObject(node) {
@@ -1053,6 +1360,7 @@ export class KoiTranspiler {
1053
1360
  generateArrowFunction(node) {
1054
1361
  // Generate parameters
1055
1362
  const params = node.params.map(p => p.name || p).join(', ');
1363
+ const asyncKeyword = node.isAsync ? 'async ' : '';
1056
1364
 
1057
1365
  // Generate body
1058
1366
  if (node.body.type === 'BlockStatement') {
@@ -1064,17 +1372,17 @@ export class KoiTranspiler {
1064
1372
  }
1065
1373
  this.indent--;
1066
1374
  body += `${this.getIndent()}}`;
1067
- return `(${params}) => ${body}`;
1375
+ return `${asyncKeyword}(${params}) => ${body}`;
1068
1376
  } else {
1069
1377
  // Expression body (implicit return)
1070
1378
  const body = this.generateExpression(node.body);
1071
1379
 
1072
1380
  // If body is an object literal, wrap it in parentheses to avoid ambiguity
1073
1381
  if (node.body.type === 'ObjectLiteral') {
1074
- return `(${params}) => (${body})`;
1382
+ return `${asyncKeyword}(${params}) => (${body})`;
1075
1383
  }
1076
1384
 
1077
- return `(${params}) => ${body}`;
1385
+ return `${asyncKeyword}(${params}) => ${body}`;
1078
1386
  }
1079
1387
  }
1080
1388