@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,172 @@
1
+ /**
2
+ * Action Registry - Manages available actions for the LLM planner
3
+ *
4
+ * Actions are modules that define what the LLM can do in playbooks.
5
+ * Each action has a type, description, schema, and examples.
6
+ */
7
+
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import { fileURLToPath } from 'url';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+
15
+ class ActionRegistry {
16
+ constructor() {
17
+ this.actions = new Map(); // Map<type, actionDefinition>
18
+ this.actionsByIntent = new Map(); // NUEVO: Map<intent, actionDefinition>
19
+ }
20
+
21
+ /**
22
+ * Register an action
23
+ */
24
+ register(action) {
25
+ if (!action.type || !action.description) {
26
+ throw new Error('Action must have type and description');
27
+ }
28
+ this.actions.set(action.type, action);
29
+
30
+ // NUEVO: indexar también por intent
31
+ if (action.intent) {
32
+ this.actionsByIntent.set(action.intent, action);
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Get an action by type or intent
38
+ */
39
+ get(typeOrIntent) {
40
+ // Intentar por intent primero (nuevo)
41
+ const byIntent = this.actionsByIntent.get(typeOrIntent);
42
+ if (byIntent) return byIntent;
43
+
44
+ // Fallback a type (legacy)
45
+ return this.actions.get(typeOrIntent);
46
+ }
47
+
48
+ /**
49
+ * Get all registered actions
50
+ */
51
+ getAll() {
52
+ return Array.from(this.actions.values());
53
+ }
54
+
55
+ /**
56
+ * Load all actions from a directory
57
+ */
58
+ async loadFromDirectory(dirPath) {
59
+ const files = fs.readdirSync(dirPath);
60
+
61
+ for (const file of files) {
62
+ if (file.endsWith('.js')) {
63
+ const filePath = path.join(dirPath, file);
64
+ try {
65
+ const module = await import(`file://${filePath}`);
66
+ const action = module.default;
67
+
68
+ if (action && action.type) {
69
+ this.register(action);
70
+ }
71
+ } catch (error) {
72
+ console.warn(`[ActionRegistry] Failed to load action from ${file}: ${error.message}`);
73
+ }
74
+ }
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Generate LLM prompt documentation for actions
80
+ * @param {Agent} agent - Agent to filter actions by permissions (null = show all actions)
81
+ */
82
+ generatePromptDocumentation(agent = null) {
83
+ let actions = this.getAll();
84
+
85
+ // If agent is provided, filter by permissions
86
+ // If no agent, show all actions (for system-level operations like routing)
87
+ if (agent) {
88
+ actions = actions.filter(action => {
89
+ // Skip hidden actions (like call_skill which is handled via tool calling)
90
+ if (action.hidden) {
91
+ return false;
92
+ }
93
+
94
+ // If action has no permission requirement, it's always available
95
+ if (!action.permission) {
96
+ return true;
97
+ }
98
+
99
+ // Check if agent has the required permission
100
+ return agent.hasPermission(action.permission);
101
+ });
102
+ } else {
103
+ // Even without agent, filter out hidden actions
104
+ actions = actions.filter(action => !action.hidden);
105
+ }
106
+
107
+ if (actions.length === 0) {
108
+ return '';
109
+ }
110
+
111
+ let doc = '';
112
+
113
+ actions.forEach((action, index) => {
114
+ doc += `- { "actionType": "direct", "intent": "${action.intent || action.type}"`;
115
+
116
+ // Add required parameters
117
+ if (action.schema && action.schema.properties) {
118
+ const props = Object.keys(action.schema.properties);
119
+ if (props.length > 0) {
120
+ doc += `, ${props.map(p => `"${p}": ...`).join(', ')}`;
121
+ }
122
+ }
123
+
124
+ doc += ` } - ${action.description}\n`;
125
+ });
126
+
127
+ return doc;
128
+ }
129
+
130
+ /**
131
+ * Generate detailed examples for LLM prompt
132
+ */
133
+ generateExamples() {
134
+ const actions = this.getAll();
135
+ const actionsWithExamples = actions.filter(a => a.examples && a.examples.length > 0);
136
+
137
+ if (actionsWithExamples.length === 0) {
138
+ return '';
139
+ }
140
+
141
+ let examples = '\nAction Examples:\n';
142
+
143
+ actionsWithExamples.forEach(action => {
144
+ if (action.examples && action.examples.length > 0) {
145
+ examples += `\n${action.type}:\n`;
146
+ action.examples.forEach(example => {
147
+ examples += ` ${JSON.stringify(example)}\n`;
148
+ });
149
+ }
150
+ });
151
+
152
+ return examples;
153
+ }
154
+
155
+ /**
156
+ * Clear all registered actions
157
+ */
158
+ clear() {
159
+ this.actions.clear();
160
+ }
161
+ }
162
+
163
+ // Global singleton instance
164
+ export const actionRegistry = new ActionRegistry();
165
+
166
+ // Auto-load actions from the actions directory on module load (SYNCHRONOUSLY)
167
+ const actionsDir = path.join(__dirname, 'actions');
168
+ if (fs.existsSync(actionsDir)) {
169
+ await actionRegistry.loadFromDirectory(actionsDir).catch(err => {
170
+ console.warn('[ActionRegistry] Failed to auto-load actions:', err.message);
171
+ });
172
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Call Skill Action - Invoke a skill
3
+ */
4
+
5
+ export default {
6
+ type: 'call_skill', // Mantener temporalmente
7
+ intent: 'call_skill', // NUEVO: identificador semántico
8
+ description: 'Call a skill with parameters',
9
+ permission: 'execute', // Requires execute permission
10
+ hidden: true, // Don't show in LLM prompts - skills are handled via tool calling
11
+
12
+ schema: {
13
+ type: 'object',
14
+ properties: {
15
+ skill: {
16
+ type: 'string',
17
+ description: 'Name of the skill to call'
18
+ },
19
+ input: {
20
+ type: 'object',
21
+ description: 'Input parameters for the skill'
22
+ }
23
+ },
24
+ required: ['skill']
25
+ },
26
+
27
+ examples: [
28
+ { type: 'call_skill', skill: 'DataValidator', input: { data: 'user_input' } }
29
+ ],
30
+
31
+ // Executor function
32
+ async execute(action, agent) {
33
+ const skillName = action.skill || action.name;
34
+ const input = action.input || action.data || {};
35
+
36
+ if (!agent.skills.includes(skillName)) {
37
+ throw new Error(`Agent ${agent.name} does not have skill: ${skillName}`);
38
+ }
39
+
40
+ // Call the skill
41
+ const result = await agent.callSkill(skillName, input);
42
+
43
+ return result;
44
+ }
45
+ };
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Format Action - Use LLM to transform/format data dynamically
3
+ */
4
+
5
+ export default {
6
+ type: 'format',
7
+ intent: 'format',
8
+ description: 'Use LLM to format/transform data according to instructions → Returns: { formatted: "result text" }. Access with ${id.output.formatted}. IMPORTANT: format action MUST have an "id" to save the result!',
9
+ permission: 'execute',
10
+
11
+ schema: {
12
+ type: 'object',
13
+ properties: {
14
+ data: {
15
+ description: 'Data to format (any type: object, array, string, etc.)'
16
+ },
17
+ instruction: {
18
+ type: 'string',
19
+ description: 'Natural language instruction describing how to format the data'
20
+ }
21
+ },
22
+ required: ['data', 'instruction']
23
+ },
24
+
25
+ examples: [
26
+ {
27
+ type: 'format',
28
+ data: '${previousResult.users}',
29
+ instruction: 'Generate a markdown table with columns: Sr/Sra (deduce from name), Name, Age'
30
+ }
31
+ ],
32
+
33
+ async execute(action, agent) {
34
+ const { data, instruction } = action;
35
+
36
+ if (!instruction) {
37
+ throw new Error('Format action requires an instruction');
38
+ }
39
+
40
+ if (!agent.llmProvider) {
41
+ throw new Error('Agent does not have an LLM provider configured');
42
+ }
43
+
44
+ if (process.env.KOI_DEBUG_LLM) {
45
+ console.error(`[Agent] 🎨 Formatting data with LLM (instruction: "${instruction.substring(0, 50)}...")`);
46
+ console.error(`[Agent] 📊 Data received:`, JSON.stringify(data, null, 2));
47
+ }
48
+
49
+ try {
50
+ // Call OpenAI directly for a simple formatting task
51
+ const completion = await agent.llmProvider.openai.chat.completions.create({
52
+ model: 'gpt-4o-mini',
53
+ temperature: 0.3,
54
+ max_tokens: 2000,
55
+ messages: [
56
+ {
57
+ role: 'system',
58
+ content: `You are a data formatter. Your job is to transform data according to user instructions.
59
+
60
+ CRITICAL RULES:
61
+ 1. Return ONLY the formatted output - NO explanations, NO markdown wrapping, NO code blocks, NO JSON
62
+ 2. Follow the instruction exactly as specified
63
+ 3. NEVER generate template variables (\${...}) or placeholders ([name], {x}, [DD]) - use ACTUAL VALUES from data
64
+ 4. When calculations are needed (dates, time differences, derived values), perform them accurately
65
+ 5. Use the most authoritative data source available (e.g., birthdate over age field, timestamps over derived dates)
66
+ 6. Current date for any time-based calculations: ${new Date().toISOString().split('T')[0]}
67
+ 7. If instruction says "generate emails", "generate text", "format as", output formatted TEXT - NOT JSON or arrays
68
+ 8. Default output should be human-readable text unless instruction explicitly asks for JSON/table/specific format
69
+
70
+ CALCULATION REQUIREMENTS:
71
+ - Parse all date/time fields carefully, supporting multiple formats
72
+ - For derived values (age, days remaining, time elapsed), calculate accurately from source data
73
+ - When calculating age: current_year - birth_year, then subtract 1 if birthday hasn't occurred yet this year
74
+ - Use birthdate field as authoritative source, ignore any "age" field as it may be stale
75
+ - Verify results make logical sense (e.g., age should be positive and reasonable)
76
+
77
+ EMAIL/TEXT GENERATION:
78
+ - When generating emails or personalized text, create properly formatted text for each item
79
+ - Include salutations, body text, and sign-offs as appropriate
80
+ - Separate multiple emails/items with blank lines
81
+ - Use natural, human-friendly language
82
+ - Infer formatting details from context (e.g., "Estimado" vs "Estimada" based on names ending in 'a')`
83
+ },
84
+ {
85
+ role: 'user',
86
+ content: `IMPORTANT: Today's date is ${new Date().toISOString().split('T')[0]}. Use this for all date calculations.
87
+
88
+ Data:
89
+ ${JSON.stringify(data, null, 2)}
90
+
91
+ Instruction:
92
+ ${instruction}
93
+
94
+ Output (formatted result only):`
95
+ }
96
+ ]
97
+ });
98
+
99
+ let formattedText = completion.choices[0].message.content.trim();
100
+
101
+ // Clean up any markdown code blocks that might have leaked through
102
+ formattedText = formattedText.replace(/^```[\w]*\n/gm, '').replace(/\n```$/gm, '');
103
+ formattedText = formattedText.trim();
104
+
105
+ if (process.env.KOI_DEBUG_LLM) {
106
+ console.error(`[Agent] ✅ Formatted ${formattedText.length} characters`);
107
+ }
108
+
109
+ return { formatted: formattedText };
110
+ } catch (error) {
111
+ console.error(`[Agent] ❌ Format action failed: ${error.message}`);
112
+ throw error;
113
+ }
114
+ }
115
+ };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Print Action - Display text to console
3
+ */
4
+
5
+ import { cliLogger } from '../cli-logger.js';
6
+
7
+ export default {
8
+ type: 'print', // Mantener temporalmente
9
+ intent: 'print', // NUEVO: identificador semántico
10
+ description: 'Print directly to console (FAST - use this for all console output!)',
11
+ permission: 'execute', // Requires execute permission
12
+
13
+ schema: {
14
+ type: 'object',
15
+ properties: {
16
+ message: {
17
+ type: 'string',
18
+ description: 'Text to display on console'
19
+ }
20
+ },
21
+ required: ['message']
22
+ },
23
+
24
+ examples: [
25
+ { type: 'print', message: '╔══════════════════════════════════════╗' },
26
+ { type: 'print', message: '║ Processing... ║' },
27
+ { type: 'print', message: '╚══════════════════════════════════════╝' },
28
+ { type: 'print', message: '✅ Task completed successfully' },
29
+ { type: 'print', message: 'Found ${previousResult.count} items' }
30
+ ],
31
+
32
+ // Executor function - receives the action and agent context
33
+ async execute(action, agent) {
34
+ const message = action.message || action.text || action.data || '';
35
+
36
+ // Print directly to stdout (bypassing cliLogger interception)
37
+ cliLogger.clearProgress();
38
+ process.stdout.write(message + '\n');
39
+
40
+ return { printed: true, message };
41
+ }
42
+ };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Registry Delete Action - Delete data from registry
3
+ */
4
+
5
+ export default {
6
+ type: 'registry_delete', // Mantener temporalmente
7
+ intent: 'registry_delete', // NUEVO: identificador semántico
8
+ description: 'Delete data from the shared registry',
9
+ permission: 'registry:write', // Requires registry:write permission (or registry)
10
+
11
+ schema: {
12
+ type: 'object',
13
+ properties: {
14
+ key: {
15
+ type: 'string',
16
+ description: 'Registry key to delete (e.g., "user:123")'
17
+ }
18
+ },
19
+ required: ['key']
20
+ },
21
+
22
+ examples: [
23
+ { type: 'registry_delete', key: 'user:${args.id}' }
24
+ ],
25
+
26
+ // Executor function
27
+ async execute(action, agent) {
28
+ const key = action.key;
29
+
30
+ if (!key) {
31
+ throw new Error('registry_delete action requires "key" field');
32
+ }
33
+
34
+ const deleted = await globalThis.registry.delete(key);
35
+ return { success: true, key, deleted };
36
+ }
37
+ };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Registry Get Action - Load data from registry
3
+ */
4
+
5
+ export default {
6
+ type: 'registry_get', // Mantener temporalmente
7
+ intent: 'registry_get', // NUEVO: identificador semántico
8
+ description: 'Load data from the shared registry → Returns: { success, key, value, found }. Access the data with ${id.output.value} or ${id.output.value.field}',
9
+ permission: 'registry:read', // Requires registry:read permission (or registry)
10
+
11
+ schema: {
12
+ type: 'object',
13
+ properties: {
14
+ key: {
15
+ type: 'string',
16
+ description: 'Registry key to fetch (e.g., "user:123")'
17
+ }
18
+ },
19
+ required: ['key']
20
+ },
21
+
22
+ examples: [
23
+ { type: 'registry_get', key: 'user:${args.id}' }
24
+ ],
25
+
26
+ // Executor function
27
+ async execute(action, agent) {
28
+ const key = action.key;
29
+
30
+ if (!key) {
31
+ throw new Error('registry_get action requires "key" field');
32
+ }
33
+
34
+ const value = await globalThis.registry.get(key);
35
+ return { success: true, key, value, found: value !== null };
36
+ }
37
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Registry Keys Action - List keys with prefix
3
+ */
4
+
5
+ export default {
6
+ type: 'registry_keys', // Mantener temporalmente
7
+ intent: 'registry_keys', // NUEVO: identificador semántico
8
+ description: 'List all registry keys with optional prefix → Returns: { success, count, keys: [array of key strings] }. Access with ${id.output.keys[0]} or ${id.output.count}',
9
+ permission: 'registry:read', // Requires registry:read permission (or registry)
10
+
11
+ schema: {
12
+ type: 'object',
13
+ properties: {
14
+ prefix: {
15
+ type: 'string',
16
+ description: 'Optional prefix to filter keys (e.g., "user:")'
17
+ }
18
+ }
19
+ },
20
+
21
+ examples: [
22
+ { type: 'registry_keys', prefix: 'user:' },
23
+ { type: 'registry_keys', prefix: '' }
24
+ ],
25
+
26
+ // Executor function
27
+ async execute(action, agent) {
28
+ const prefix = action.prefix || '';
29
+
30
+ const keys = await globalThis.registry.keys(prefix);
31
+ return { success: true, count: keys.length, keys };
32
+ }
33
+ };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Registry Search Action - Search registry with query
3
+ */
4
+
5
+ export default {
6
+ type: 'registry_search', // Mantener temporalmente
7
+ intent: 'registry_search', // NUEVO: identificador semántico
8
+ description: 'Search registry with MongoDB-style query → Returns: { success, count, results: [array of {key, value} objects] }. Access with ${id.output.results[0].value} or ${id.output.count}',
9
+ permission: 'registry:read', // Requires registry:read permission (or registry)
10
+
11
+ schema: {
12
+ type: 'object',
13
+ properties: {
14
+ query: {
15
+ type: 'object',
16
+ description: 'MongoDB-style query object (e.g., { age: { $gte: 18 } })'
17
+ }
18
+ },
19
+ required: ['query']
20
+ },
21
+
22
+ examples: [
23
+ { type: 'registry_search', query: { age: { $gte: 18 } } },
24
+ { type: 'registry_search', query: { status: 'active' } }
25
+ ],
26
+
27
+ // Executor function
28
+ async execute(action, agent) {
29
+ const query = action.query || {};
30
+
31
+ const results = await globalThis.registry.search(query);
32
+ return { success: true, count: results.length, results };
33
+ }
34
+ };
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Registry Set Action - Save data to registry
3
+ */
4
+
5
+ export default {
6
+ type: 'registry_set', // Mantener temporalmente
7
+ intent: 'registry_set', // NUEVO: identificador semántico
8
+ description: 'Save data to the shared registry',
9
+ permission: 'registry:write', // Requires registry:write permission (or registry)
10
+
11
+ schema: {
12
+ type: 'object',
13
+ properties: {
14
+ key: {
15
+ type: 'string',
16
+ description: 'Registry key (e.g., "user:123")'
17
+ },
18
+ value: {
19
+ type: 'object',
20
+ description: 'Data to store'
21
+ }
22
+ },
23
+ required: ['key', 'value']
24
+ },
25
+
26
+ examples: [
27
+ {
28
+ type: 'registry_set',
29
+ key: 'user:${args.id}',
30
+ value: {
31
+ name: '${args.name}',
32
+ age: '${args.age}',
33
+ createdAt: '${Date.now()}'
34
+ }
35
+ }
36
+ ],
37
+
38
+ // Executor function
39
+ async execute(action, agent) {
40
+ const key = action.key;
41
+ const value = action.value;
42
+
43
+ if (!key) {
44
+ throw new Error('registry_set action requires "key" field');
45
+ }
46
+
47
+ await globalThis.registry.set(key, value);
48
+ return { success: true, key, saved: true };
49
+ }
50
+ };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Return Action - Return final result
3
+ */
4
+
5
+ export default {
6
+ type: 'return', // Mantener temporalmente
7
+ intent: 'return', // NUEVO: identificador semántico
8
+ description: 'Return final result from action sequence. CRITICAL: Return RAW data structures (objects, arrays) NOT formatted strings or markdown tables. If playbook says "Return: { count, users: [array] }", return actual JSON array not a formatted table string.',
9
+ permission: 'execute', // Requires execute permission
10
+
11
+ schema: {
12
+ type: 'object',
13
+ properties: {
14
+ data: {
15
+ type: 'object',
16
+ description: 'Data to return as final result'
17
+ }
18
+ },
19
+ required: ['data']
20
+ },
21
+
22
+ examples: [
23
+ { type: 'return', data: { success: true, message: 'Completed' } },
24
+ { type: 'return', data: { user: '${previousResult.value}' } }
25
+ ],
26
+
27
+ // Executor function
28
+ async execute(action, agent) {
29
+ return action.data || action.result || {};
30
+ }
31
+ };
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Send Message Action - Send message to team members
3
+ */
4
+
5
+ export default {
6
+ type: 'send_message', // Mantener temporalmente
7
+ intent: 'send_message', // NUEVO: identificador semántico
8
+ description: 'Send message to team members (explicit team communication)',
9
+ permission: 'delegate', // Requires delegate permission - only orchestrators can send messages
10
+
11
+ schema: {
12
+ type: 'object',
13
+ properties: {
14
+ event: {
15
+ type: 'string',
16
+ description: 'Event name to trigger'
17
+ },
18
+ role: {
19
+ type: 'string',
20
+ description: 'Optional role name to filter recipients'
21
+ },
22
+ data: {
23
+ type: 'object',
24
+ description: 'Data payload to send'
25
+ }
26
+ },
27
+ required: ['event', 'data']
28
+ },
29
+
30
+ examples: [
31
+ { type: 'send_message', event: 'processData', role: 'Worker', data: { id: 123 } }
32
+ ],
33
+
34
+ // Executor function
35
+ async execute(action, agent) {
36
+ if (process.env.KOI_DEBUG_LLM) {
37
+ const roleFilter = action.role ? ` (role: ${action.role})` : '';
38
+ console.error(`[Agent] 📨 ${agent.name} sending message: ${action.event}${roleFilter}`);
39
+ }
40
+
41
+ // Build query using peers API
42
+ let query = agent.peers.event(action.event);
43
+
44
+ // Add role filter if specified
45
+ if (action.role) {
46
+ query = query.role(action.role);
47
+ }
48
+
49
+ // Execute the query with any selector
50
+ const result = await query.any().execute(action.data);
51
+
52
+ if (process.env.KOI_DEBUG_LLM) {
53
+ console.error(`[Agent] ✅ Message sent, result:`, result);
54
+ }
55
+
56
+ return result;
57
+ }
58
+ };