agents-dojo 0.1.0 → 0.1.2
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/dist/agent-loader.js +86 -1
- package/dist/claude-bridge.js +2 -0
- package/dist/cli.js +84 -0
- package/dist/event-translator.js +9 -0
- package/package.json +1 -1
package/dist/agent-loader.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/agent-loader.ts
|
|
2
|
-
import { readFileSync, readdirSync, existsSync, statSync } from 'fs';
|
|
2
|
+
import { readFileSync, readdirSync, existsSync, statSync, mkdirSync, writeFileSync } from 'fs';
|
|
3
3
|
import { join, resolve, isAbsolute } from 'path';
|
|
4
4
|
import stripJsonComments from 'strip-json-comments';
|
|
5
5
|
import { AgentManifestSchema } from './manifest-schema.js';
|
|
@@ -49,6 +49,8 @@ function resolveRef(agentDir, relPath) {
|
|
|
49
49
|
export function loadAgent(agentDir) {
|
|
50
50
|
const manifest = loadManifest(agentDir);
|
|
51
51
|
const fixedContextContent = loadContext(agentDir, manifest.fixedContext);
|
|
52
|
+
// Install built-in call-agent skill so this agent can communicate with peers
|
|
53
|
+
installBuiltinSkills(agentDir);
|
|
52
54
|
return {
|
|
53
55
|
manifest,
|
|
54
56
|
agentDir,
|
|
@@ -60,6 +62,89 @@ export function loadAgent(agentDir) {
|
|
|
60
62
|
sandboxPath: resolveRef(agentDir, manifest.sandbox),
|
|
61
63
|
};
|
|
62
64
|
}
|
|
65
|
+
const CALL_AGENT_SKILL_MD = `---
|
|
66
|
+
name: call-agent
|
|
67
|
+
description: "Call another agent in the same AgentsDojo server via A2A protocol. Use when you need to delegate a task, ask for help, or collaborate with a peer agent."
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
# Calling Other Agents
|
|
71
|
+
|
|
72
|
+
You can communicate with other agents running in the same AgentsDojo server.
|
|
73
|
+
|
|
74
|
+
## List Available Agents
|
|
75
|
+
|
|
76
|
+
\`\`\`bash
|
|
77
|
+
curl -s http://localhost:41241/agents
|
|
78
|
+
\`\`\`
|
|
79
|
+
|
|
80
|
+
## Send a Message and Get Response
|
|
81
|
+
|
|
82
|
+
Use this helper script. It sends the message, then polls for the result (avoids blocking timeouts).
|
|
83
|
+
|
|
84
|
+
\`\`\`bash
|
|
85
|
+
python3 -c "
|
|
86
|
+
import urllib.request, json, uuid, time, sys
|
|
87
|
+
|
|
88
|
+
agent_id = 'AGENT_ID'
|
|
89
|
+
message = '''YOUR MESSAGE'''
|
|
90
|
+
url = f'http://localhost:41241/a2a/{agent_id}'
|
|
91
|
+
|
|
92
|
+
# Step 1: Send message
|
|
93
|
+
req_body = json.dumps({'jsonrpc':'2.0','id':1,'method':'message/send','params':{'message':{
|
|
94
|
+
'messageId':str(uuid.uuid4()),'kind':'message','role':'user',
|
|
95
|
+
'parts':[{'kind':'text','text':message}]
|
|
96
|
+
}}}).encode()
|
|
97
|
+
try:
|
|
98
|
+
req = urllib.request.Request(url, data=req_body, headers={'Content-Type':'application/json'})
|
|
99
|
+
resp = json.loads(urllib.request.urlopen(req, timeout=120).read())
|
|
100
|
+
result = resp.get('result',{})
|
|
101
|
+
except Exception as e:
|
|
102
|
+
print(f'Error sending: {e}', file=sys.stderr)
|
|
103
|
+
sys.exit(1)
|
|
104
|
+
|
|
105
|
+
# Step 2: If task not completed yet, poll with tasks/get
|
|
106
|
+
task_id = result.get('id','')
|
|
107
|
+
state = result.get('status',{}).get('state','')
|
|
108
|
+
if state not in ('completed','failed') and task_id:
|
|
109
|
+
for _ in range(60):
|
|
110
|
+
time.sleep(2)
|
|
111
|
+
poll_body = json.dumps({'jsonrpc':'2.0','id':2,'method':'tasks/get','params':{'id':task_id}}).encode()
|
|
112
|
+
try:
|
|
113
|
+
poll_req = urllib.request.Request(url, data=poll_body, headers={'Content-Type':'application/json'})
|
|
114
|
+
poll_resp = json.loads(urllib.request.urlopen(poll_req, timeout=10).read())
|
|
115
|
+
result = poll_resp.get('result', result)
|
|
116
|
+
state = result.get('status',{}).get('state','')
|
|
117
|
+
if state in ('completed','failed'): break
|
|
118
|
+
except: pass
|
|
119
|
+
|
|
120
|
+
# Step 3: Extract response text
|
|
121
|
+
for a in result.get('artifacts',[]):
|
|
122
|
+
for p in a.get('parts',[]):
|
|
123
|
+
if p.get('kind')=='text': print(p['text'])
|
|
124
|
+
msg = result.get('status',{}).get('message',{})
|
|
125
|
+
if isinstance(msg, dict):
|
|
126
|
+
for p in msg.get('parts',[]):
|
|
127
|
+
if p.get('kind')=='text': print(p['text'])
|
|
128
|
+
"
|
|
129
|
+
\`\`\`
|
|
130
|
+
|
|
131
|
+
Replace AGENT_ID and YOUR MESSAGE with the target agent and your message.
|
|
132
|
+
|
|
133
|
+
## Rules
|
|
134
|
+
|
|
135
|
+
- Be specific about what you need from the other agent
|
|
136
|
+
- Include relevant context — the other agent has no shared memory with you
|
|
137
|
+
- Don't call agents in a loop — if one can't help, handle it yourself
|
|
138
|
+
`;
|
|
139
|
+
function installBuiltinSkills(agentDir) {
|
|
140
|
+
const skillDir = join(agentDir, '.claude', 'skills', 'call-agent');
|
|
141
|
+
const skillFile = join(skillDir, 'SKILL.md');
|
|
142
|
+
// Only write if missing or outdated (check by size as a simple heuristic)
|
|
143
|
+
if (!existsSync(skillFile)) {
|
|
144
|
+
mkdirSync(skillDir, { recursive: true });
|
|
145
|
+
writeFileSync(skillFile, CALL_AGENT_SKILL_MD);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
63
148
|
export function loadAgents(agentsDir) {
|
|
64
149
|
const result = new Map();
|
|
65
150
|
const absDir = resolve(agentsDir);
|
package/dist/claude-bridge.js
CHANGED
|
@@ -8,6 +8,8 @@ export async function* runClaude(params) {
|
|
|
8
8
|
? `${agent.fixedContextContent}\n\n${m.systemPromptAppend}`
|
|
9
9
|
: agent.fixedContextContent;
|
|
10
10
|
const env = {
|
|
11
|
+
// Preserve system PATH so agents can use curl, python3, node etc.
|
|
12
|
+
PATH: process.env.PATH,
|
|
11
13
|
...(m.env ?? {}),
|
|
12
14
|
};
|
|
13
15
|
// Only override CLAUDE_CONFIG_DIR if the user explicitly specified configDir.
|
package/dist/cli.js
CHANGED
|
@@ -42,6 +42,8 @@ function runInit(targetDir) {
|
|
|
42
42
|
mkdirSync(echoDir, { recursive: true });
|
|
43
43
|
writeFileSync(join(echoDir, 'manifest.jsonc'), ECHO_MANIFEST);
|
|
44
44
|
writeFileSync(join(echoDir, 'context.md'), ECHO_CONTEXT);
|
|
45
|
+
// Install built-in call-agent skill
|
|
46
|
+
installCallAgentSkill(echoDir);
|
|
45
47
|
console.log(`Created agents/echo/ with manifest.jsonc and context.md.
|
|
46
48
|
|
|
47
49
|
Next steps:
|
|
@@ -53,6 +55,88 @@ To add your own agent:
|
|
|
53
55
|
# create manifest.jsonc and context.md inside it
|
|
54
56
|
`);
|
|
55
57
|
}
|
|
58
|
+
// ── Built-in skill: call-agent ────────────────────────────
|
|
59
|
+
const CALL_AGENT_SKILL = `---
|
|
60
|
+
name: call-agent
|
|
61
|
+
description: "Call another agent in the same AgentsDojo server via A2A protocol. Use this when you need to delegate a task, ask for help, or collaborate with another agent. TRIGGER when: you need expertise from another agent, the task requires skills outside your role, or you want to discuss/validate something with a peer."
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
# Calling Other Agents
|
|
65
|
+
|
|
66
|
+
You can communicate with other agents running in the same AgentsDojo server.
|
|
67
|
+
|
|
68
|
+
## Discover Available Agents
|
|
69
|
+
|
|
70
|
+
\`\`\`bash
|
|
71
|
+
curl -s http://localhost:41241/a2a/*/card 2>/dev/null || \\
|
|
72
|
+
for id in $(curl -s http://localhost:41241/agents 2>/dev/null | python3 -c "import json,sys;[print(a) for a in json.load(sys.stdin)]" 2>/dev/null); do
|
|
73
|
+
echo "--- $id ---"
|
|
74
|
+
curl -s "http://localhost:41241/a2a/$id/card" 2>/dev/null | python3 -c "import json,sys;d=json.load(sys.stdin);print(f\\"{d['name']}: {d['description']}\\");" 2>/dev/null
|
|
75
|
+
done
|
|
76
|
+
\`\`\`
|
|
77
|
+
|
|
78
|
+
## Send a Message to Another Agent
|
|
79
|
+
|
|
80
|
+
To ask another agent a question or delegate a task, use this curl command:
|
|
81
|
+
|
|
82
|
+
\`\`\`bash
|
|
83
|
+
curl -s -X POST http://localhost:41241/a2a/<AGENT_ID> \\
|
|
84
|
+
-H "Content-Type: application/json" \\
|
|
85
|
+
-d '{
|
|
86
|
+
"jsonrpc": "2.0",
|
|
87
|
+
"id": 1,
|
|
88
|
+
"method": "message/send",
|
|
89
|
+
"params": {
|
|
90
|
+
"message": {
|
|
91
|
+
"messageId": "'$(python3 -c "import uuid;print(uuid.uuid4())")'",
|
|
92
|
+
"kind": "message",
|
|
93
|
+
"role": "user",
|
|
94
|
+
"parts": [{"kind": "text", "text": "YOUR MESSAGE HERE"}]
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}'
|
|
98
|
+
\`\`\`
|
|
99
|
+
|
|
100
|
+
Replace \`<AGENT_ID>\` with the target agent's ID (e.g., \`alice\`, \`bob\`, \`reviewer\`).
|
|
101
|
+
|
|
102
|
+
## Parse the Response
|
|
103
|
+
|
|
104
|
+
The response is a JSON-RPC result. Extract the agent's reply text:
|
|
105
|
+
|
|
106
|
+
\`\`\`bash
|
|
107
|
+
curl -s -X POST http://localhost:41241/a2a/<AGENT_ID> \\
|
|
108
|
+
-H "Content-Type: application/json" \\
|
|
109
|
+
-d '{ ... }' | python3 -c "
|
|
110
|
+
import json, sys
|
|
111
|
+
r = json.load(sys.stdin)
|
|
112
|
+
result = r.get('result', {})
|
|
113
|
+
# Check for artifacts
|
|
114
|
+
artifacts = result.get('artifacts', [])
|
|
115
|
+
for a in artifacts:
|
|
116
|
+
for p in a.get('parts', []):
|
|
117
|
+
if p.get('kind') == 'text':
|
|
118
|
+
print(p['text'])
|
|
119
|
+
# Check for status message
|
|
120
|
+
status = result.get('status', {})
|
|
121
|
+
msg = status.get('message', {})
|
|
122
|
+
for p in msg.get('parts', []):
|
|
123
|
+
if p.get('kind') == 'text':
|
|
124
|
+
print(p['text'])
|
|
125
|
+
"
|
|
126
|
+
\`\`\`
|
|
127
|
+
|
|
128
|
+
## Guidelines
|
|
129
|
+
|
|
130
|
+
- **Be specific** about what you need from the other agent
|
|
131
|
+
- **Include context** — the other agent doesn't share your conversation history
|
|
132
|
+
- **Don't loop** — if an agent can't help, handle it yourself instead of retrying
|
|
133
|
+
- You can call multiple agents in sequence to build on each other's output
|
|
134
|
+
`;
|
|
135
|
+
function installCallAgentSkill(agentDir) {
|
|
136
|
+
const skillDir = join(agentDir, '.claude', 'skills', 'call-agent');
|
|
137
|
+
mkdirSync(skillDir, { recursive: true });
|
|
138
|
+
writeFileSync(join(skillDir, 'SKILL.md'), CALL_AGENT_SKILL);
|
|
139
|
+
}
|
|
56
140
|
export const DEFAULT_ARGS = {
|
|
57
141
|
agentsDir: './agents',
|
|
58
142
|
port: 41241,
|
package/dist/event-translator.js
CHANGED
|
@@ -25,6 +25,13 @@ export function createTranslator(ctx) {
|
|
|
25
25
|
},
|
|
26
26
|
final: false,
|
|
27
27
|
});
|
|
28
|
+
// Also emit to monitor bus so the GUI can show speech bubbles
|
|
29
|
+
ctx.monitorBus?.emit({
|
|
30
|
+
type: 'task_status',
|
|
31
|
+
taskId: ctx.taskId,
|
|
32
|
+
state: 'working',
|
|
33
|
+
message: text.length > 80 ? text.slice(0, 77) + '...' : text,
|
|
34
|
+
});
|
|
28
35
|
}
|
|
29
36
|
function publishCompleted() {
|
|
30
37
|
ctx.bus.publish({
|
|
@@ -38,6 +45,7 @@ export function createTranslator(ctx) {
|
|
|
38
45
|
},
|
|
39
46
|
final: true,
|
|
40
47
|
});
|
|
48
|
+
ctx.monitorBus?.emit({ type: 'task_status', taskId: ctx.taskId, state: 'completed' });
|
|
41
49
|
ctx.bus.finished();
|
|
42
50
|
}
|
|
43
51
|
function publishFailed(reason) {
|
|
@@ -60,6 +68,7 @@ export function createTranslator(ctx) {
|
|
|
60
68
|
},
|
|
61
69
|
final: true,
|
|
62
70
|
});
|
|
71
|
+
ctx.monitorBus?.emit({ type: 'task_status', taskId: ctx.taskId, state: 'failed', message: reason });
|
|
63
72
|
ctx.bus.finished();
|
|
64
73
|
}
|
|
65
74
|
function summarizeInput(input) {
|