@koi-language/koi 1.0.6 → 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.
- package/README.md +4 -125
- package/examples/.build/agent-dialogue.ts +138 -0
- package/examples/.build/agent-dialogue.ts.map +1 -0
- package/examples/.build/chess.ts +77 -0
- package/examples/.build/chess.ts.map +1 -0
- package/examples/.build/delegation-test.ts +140 -0
- package/examples/.build/delegation-test.ts.map +1 -0
- package/examples/.build/dialog-demo.ts +77 -0
- package/examples/.build/dialog-demo.ts.map +1 -0
- package/examples/.build/hello-world.ts +77 -0
- package/examples/.build/hello-world.ts.map +1 -0
- package/examples/.build/lover-dialog-demo.ts +77 -0
- package/examples/.build/lover-dialog-demo.ts.map +1 -0
- package/examples/.build/package.json +3 -0
- package/examples/.build/registry-interactive-demo.ts +202 -0
- package/examples/.build/registry-interactive-demo.ts.map +1 -0
- package/examples/.build/registry-playbook-demo.ts +201 -0
- package/examples/.build/registry-playbook-demo.ts.map +1 -0
- package/examples/.build/tic-tac-toe.ts +77 -0
- package/examples/.build/tic-tac-toe.ts.map +1 -0
- package/examples/actions-demo.koi +8 -9
- package/examples/activists-dialogue.koi +75 -0
- package/examples/agent-dialogue.koi +66 -0
- package/examples/chess.koi +19 -0
- package/examples/counter.koi +20 -69
- package/examples/delegation-test.koi +16 -18
- package/examples/dialog-demo.koi +20 -0
- package/examples/hello-world.koi +7 -43
- package/examples/mcp-stdio-demo.koi +29 -0
- package/examples/memory-test.koi +49 -0
- package/examples/mobile-mcp-demo.koi +32 -0
- package/examples/multi-event-handler-test.koi +16 -18
- package/examples/pipeline.koi +15 -17
- package/examples/prompt-demo.koi +20 -0
- package/examples/{registry-playbook-email-compositor.koi → registry-interactive-demo.koi} +27 -27
- package/examples/registry-playbook-demo.koi +28 -28
- package/examples/skill-import-test.koi +7 -9
- package/examples/skills/.build/math-operations.ts +1656 -0
- package/examples/skills/.build/math-operations.ts.map +1 -0
- package/examples/skills/.build/package.json +3 -0
- package/examples/skills/.build/string-operations.ts +1643 -0
- package/examples/skills/.build/string-operations.ts.map +1 -0
- package/examples/skills/advanced/.build/index.ts +3223 -0
- package/examples/skills/advanced/.build/index.ts.map +1 -0
- package/examples/skills/advanced/.build/package.json +3 -0
- package/examples/skills/advanced/index.koi +3 -5
- package/examples/skills/math-operations.koi +1 -3
- package/examples/skills/string-operations.koi +1 -3
- package/examples/tic-tac-toe.koi +19 -0
- package/examples/utils/echo-mcp-server.js +141 -0
- package/examples/web-delegation-demo.koi +15 -17
- package/package.json +2 -1
- package/src/cli/koi.js +30 -41
- package/src/compiler/build-optimizer.js +204 -289
- package/src/compiler/cache-manager.js +1 -1
- package/src/compiler/import-resolver.js +5 -9
- package/src/compiler/parser.js +6072 -3476
- package/src/compiler/transpiler.js +346 -38
- package/src/grammar/koi.pegjs +302 -62
- package/src/runtime/actions/{format.js → call-llm.js} +37 -44
- package/src/runtime/actions/call-mcp.js +97 -0
- package/src/runtime/actions/if.js +179 -0
- package/src/runtime/actions/print.js +3 -1
- package/src/runtime/actions/prompt-user.js +75 -0
- package/src/runtime/actions/repeat.js +147 -0
- package/src/runtime/actions/shell.js +185 -0
- package/src/runtime/actions/while.js +205 -0
- package/src/runtime/agent.js +592 -178
- package/src/runtime/cli-display.js +26 -0
- package/src/runtime/cli-input.js +421 -0
- package/src/runtime/cli-logger.js +2 -5
- package/src/runtime/cli-markdown.js +61 -0
- package/src/runtime/cli-select.js +106 -0
- package/src/runtime/incremental-json-parser.js +27 -17
- package/src/runtime/index.js +1 -0
- package/src/runtime/llm-provider.js +1083 -572
- package/src/runtime/mcp-registry.js +141 -0
- package/src/runtime/mcp-stdio-client.js +334 -0
- package/src/runtime/planner.js +1 -1
- package/src/runtime/playbook-session.js +259 -0
- package/src/runtime/registry-backends/keyv-sqlite.js +1 -1
- package/src/runtime/registry-backends/local.js +1 -1
- package/src/runtime/router.js +22 -26
- package/src/runtime/runtime.js +7 -1
- package/examples/cache-test.koi +0 -29
- package/examples/calculator.koi +0 -61
- package/examples/clear-registry.js +0 -33
- package/examples/clear-registry.koi +0 -30
- package/examples/code-introspection-test.koi +0 -149
- package/examples/directory-import-test.koi +0 -84
- package/examples/hello-world-claude.koi +0 -52
- package/examples/hello.koi +0 -24
- package/examples/mcp-example.koi +0 -70
- package/examples/new-import-test.koi +0 -89
- package/examples/registry-demo.koi +0 -184
- package/examples/registry-playbook-email-compositor-2.koi +0 -140
- package/examples/sentiment.koi +0 -90
- package/examples/simple.koi +0 -48
- package/examples/task-chaining-demo.koi +0 -244
- package/examples/test-await.koi +0 -22
- package/examples/test-crypto-sha256.koi +0 -196
- package/examples/test-delegation.koi +0 -41
- package/examples/test-multi-team-routing.koi +0 -258
- package/examples/test-no-handler.koi +0 -35
- package/examples/test-npm-import.koi +0 -67
- package/examples/test-parse.koi +0 -10
- package/examples/test-peers-with-team.koi +0 -59
- package/examples/test-permissions-fail.koi +0 -20
- package/examples/test-permissions.koi +0 -36
- package/examples/test-simple-registry.koi +0 -31
- package/examples/test-typescript-import.koi +0 -64
- package/examples/test-uses-team-syntax.koi +0 -25
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
200
|
-
//
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
|
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-
|
|
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()}${
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
564
|
-
|
|
565
|
-
|
|
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 (
|
|
784
|
+
if (exportedFunctionNames.length > 0) {
|
|
572
785
|
code += this.emit(`// Register skill functions\n`);
|
|
573
|
-
for (const funcName of
|
|
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 =>
|
|
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' : ''}
|
|
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
|
-
|
|
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
|
|
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
|
|
1382
|
+
return `${asyncKeyword}(${params}) => (${body})`;
|
|
1075
1383
|
}
|
|
1076
1384
|
|
|
1077
|
-
return
|
|
1385
|
+
return `${asyncKeyword}(${params}) => ${body}`;
|
|
1078
1386
|
}
|
|
1079
1387
|
}
|
|
1080
1388
|
|