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.
@@ -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);
@@ -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,
@@ -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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-dojo",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "A2A-compatible Agent framework built on Claude Code SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",