@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.
- package/QUICKSTART.md +89 -0
- package/README.md +545 -0
- package/examples/actions-demo.koi +177 -0
- package/examples/cache-test.koi +29 -0
- package/examples/calculator.koi +61 -0
- package/examples/clear-registry.js +33 -0
- package/examples/clear-registry.koi +30 -0
- package/examples/code-introspection-test.koi +149 -0
- package/examples/counter.koi +132 -0
- package/examples/delegation-test.koi +52 -0
- package/examples/directory-import-test.koi +84 -0
- package/examples/hello-world-claude.koi +52 -0
- package/examples/hello-world.koi +52 -0
- package/examples/hello.koi +24 -0
- package/examples/mcp-example.koi +70 -0
- package/examples/multi-event-handler-test.koi +144 -0
- package/examples/new-import-test.koi +89 -0
- package/examples/pipeline.koi +162 -0
- package/examples/registry-demo.koi +184 -0
- package/examples/registry-playbook-demo.koi +162 -0
- package/examples/registry-playbook-email-compositor-2.koi +140 -0
- package/examples/registry-playbook-email-compositor.koi +140 -0
- package/examples/sentiment.koi +90 -0
- package/examples/simple.koi +48 -0
- package/examples/skill-import-test.koi +76 -0
- package/examples/skills/advanced/index.koi +95 -0
- package/examples/skills/math-operations.koi +69 -0
- package/examples/skills/string-operations.koi +56 -0
- package/examples/task-chaining-demo.koi +244 -0
- package/examples/test-await.koi +22 -0
- package/examples/test-crypto-sha256.koi +196 -0
- package/examples/test-delegation.koi +41 -0
- package/examples/test-multi-team-routing.koi +258 -0
- package/examples/test-no-handler.koi +35 -0
- package/examples/test-npm-import.koi +67 -0
- package/examples/test-parse.koi +10 -0
- package/examples/test-peers-with-team.koi +59 -0
- package/examples/test-permissions-fail.koi +20 -0
- package/examples/test-permissions.koi +36 -0
- package/examples/test-simple-registry.koi +31 -0
- package/examples/test-typescript-import.koi +64 -0
- package/examples/test-uses-team-syntax.koi +25 -0
- package/examples/test-uses-team.koi +31 -0
- package/examples/utils/calculator.test.ts +144 -0
- package/examples/utils/calculator.ts +56 -0
- package/examples/utils/math-helpers.js +50 -0
- package/examples/utils/math-helpers.ts +55 -0
- package/examples/web-delegation-demo.koi +165 -0
- package/package.json +78 -0
- package/src/cli/koi.js +793 -0
- package/src/compiler/build-optimizer.js +447 -0
- package/src/compiler/cache-manager.js +274 -0
- package/src/compiler/import-resolver.js +369 -0
- package/src/compiler/parser.js +7542 -0
- package/src/compiler/transpiler.js +1105 -0
- package/src/compiler/typescript-transpiler.js +148 -0
- package/src/grammar/koi.pegjs +767 -0
- package/src/runtime/action-registry.js +172 -0
- package/src/runtime/actions/call-skill.js +45 -0
- package/src/runtime/actions/format.js +115 -0
- package/src/runtime/actions/print.js +42 -0
- package/src/runtime/actions/registry-delete.js +37 -0
- package/src/runtime/actions/registry-get.js +37 -0
- package/src/runtime/actions/registry-keys.js +33 -0
- package/src/runtime/actions/registry-search.js +34 -0
- package/src/runtime/actions/registry-set.js +50 -0
- package/src/runtime/actions/return.js +31 -0
- package/src/runtime/actions/send-message.js +58 -0
- package/src/runtime/actions/update-state.js +36 -0
- package/src/runtime/agent.js +1368 -0
- package/src/runtime/cli-logger.js +205 -0
- package/src/runtime/incremental-json-parser.js +201 -0
- package/src/runtime/index.js +33 -0
- package/src/runtime/llm-provider.js +1372 -0
- package/src/runtime/mcp-client.js +1171 -0
- package/src/runtime/planner.js +273 -0
- package/src/runtime/registry-backends/keyv-sqlite.js +215 -0
- package/src/runtime/registry-backends/local.js +260 -0
- package/src/runtime/registry.js +162 -0
- package/src/runtime/role.js +14 -0
- package/src/runtime/router.js +395 -0
- package/src/runtime/runtime.js +113 -0
- package/src/runtime/skill-selector.js +173 -0
- package/src/runtime/skill.js +25 -0
- 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
|
+
};
|