bus-agent 2.3.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.
@@ -0,0 +1,216 @@
1
+ """
2
+ CoCo Bus Client — Python SDK for Agent Bus
3
+
4
+ Direct file-based access to the CoCo Agent Bus. No MCP needed.
5
+ Any agent with filesystem access to the bus directory can use this.
6
+
7
+ Usage:
8
+ from coco_client import CoCoClient
9
+
10
+ bus = CoCoClient("/path/to/mcp-coco/.bus", agent_name="my-agent")
11
+ bus.register(description="My Python agent", capabilities=["data-analysis", "api-calls"])
12
+ bus.send("hermes", "Hello from Python!")
13
+ msgs = bus.fetch_messages()
14
+ for m in msgs:
15
+ print(f"[{m['from']}] {m['message']}")
16
+ bus.reply(m, "Got it!")
17
+ """
18
+
19
+ import json
20
+ import os
21
+ import time
22
+ import uuid
23
+
24
+
25
+ class CoCoClient:
26
+ def __init__(self, bus_dir, agent_name=None):
27
+ self.bus_dir = bus_dir
28
+ self.msgs_dir = os.path.join(bus_dir, "messages")
29
+ self.agents_file = os.path.join(bus_dir, "agents.json")
30
+ self.channels_dir = os.path.join(bus_dir, "channels")
31
+ self.events_dir = os.path.join(bus_dir, "events")
32
+ self.agent_name = agent_name or os.environ.get("COCO_AGENT") or os.environ.get("USER", "python-agent")
33
+ self._ensure_dirs()
34
+
35
+ def _ensure_dirs(self):
36
+ for d in [self.bus_dir, self.msgs_dir, self.channels_dir, self.events_dir]:
37
+ os.makedirs(d, exist_ok=True)
38
+
39
+ def _gen_id(self):
40
+ return f"{int(time.time() * 1000)}_{uuid.uuid4().hex[:8]}"
41
+
42
+ def _load_agents(self):
43
+ try:
44
+ if os.path.exists(self.agents_file):
45
+ with open(self.agents_file) as f:
46
+ return json.load(f)
47
+ except:
48
+ pass
49
+ return {}
50
+
51
+ def _save_agents(self, agents):
52
+ with open(self.agents_file, "w") as f:
53
+ json.dump(agents, f, indent=2)
54
+
55
+ # ── Registration ──
56
+
57
+ def register(self, description="", capabilities=None, model=None, tags=None, version="1.0.0", status="idle"):
58
+ """Register yourself on the CoCo Agent Bus."""
59
+ agents = self._load_agents()
60
+ existing = agents.get(self.agent_name, {})
61
+ agents[self.agent_name] = {
62
+ "name": self.agent_name,
63
+ "description": description,
64
+ "capabilities": capabilities or [],
65
+ "model": model or None,
66
+ "tags": tags or [],
67
+ "status": status,
68
+ "version": version,
69
+ "endpoints": {},
70
+ "tools": [],
71
+ "metadata": {},
72
+ "last_seen": datetime.utcnow().isoformat() + "Z",
73
+ "registered_at": existing.get("registered_at", datetime.utcnow().isoformat() + "Z"),
74
+ }
75
+ self._save_agents(agents)
76
+ is_new = not existing
77
+ return {"registered": True, "is_new": is_new}
78
+
79
+ def heartbeat(self):
80
+ """Mark yourself as online."""
81
+ agents = self._load_agents()
82
+ if self.agent_name in agents:
83
+ agents[self.agent_name]["last_seen"] = datetime.utcnow().isoformat() + "Z"
84
+ agents[self.agent_name]["status"] = "idle"
85
+ self._save_agents(agents)
86
+ return True
87
+ return False
88
+
89
+ def set_status(self, status):
90
+ """Set status: idle, busy, offline."""
91
+ agents = self._load_agents()
92
+ if self.agent_name in agents:
93
+ agents[self.agent_name]["status"] = status
94
+ agents[self.agent_name]["last_seen"] = datetime.utcnow().isoformat() + "Z"
95
+ self._save_agents(agents)
96
+ return True
97
+ return False
98
+
99
+ def list_agents(self, online_only=False):
100
+ """List agents on the bus."""
101
+ agents = self._load_agents()
102
+ if online_only:
103
+ cutoff = time.time() * 1000 - 300000
104
+ agents = {k: v for k, v in agents.items()
105
+ if datetime.fromisoformat(v["last_seen"].replace("Z", "+00:00")).timestamp() * 1000 > cutoff}
106
+ return agents
107
+
108
+ # ── Messaging ──
109
+
110
+ def send(self, to, message, metadata=None):
111
+ """Send a DM to another agent."""
112
+ msg = {
113
+ "id": self._gen_id(),
114
+ "from": self.agent_name,
115
+ "to": to,
116
+ "message": message,
117
+ "metadata": metadata or {},
118
+ "timestamp": datetime.utcnow().isoformat() + "Z",
119
+ }
120
+ inbox_dir = os.path.join(self.msgs_dir, to)
121
+ os.makedirs(inbox_dir, exist_ok=True)
122
+ with open(os.path.join(inbox_dir, f"{msg['id']}.json"), "w") as f:
123
+ json.dump(msg, f, indent=2)
124
+
125
+ # Outbox
126
+ outbox_dir = os.path.join(self.msgs_dir, f"{self.agent_name}_outbox")
127
+ os.makedirs(outbox_dir, exist_ok=True)
128
+ with open(os.path.join(outbox_dir, f"{msg['id']}.json"), "w") as f:
129
+ json.dump(msg, f, indent=2)
130
+
131
+ return msg["id"]
132
+
133
+ def broadcast(self, message):
134
+ """Send to all agents."""
135
+ agents = self._load_agents()
136
+ results = []
137
+ for name in agents:
138
+ if name != self.agent_name:
139
+ mid = self.send(name, message, {"broadcast": True})
140
+ results.append({"to": name, "message_id": mid})
141
+ return results
142
+
143
+ def fetch_messages(self, limit=50, from_agent=None):
144
+ """Read your inbox."""
145
+ inbox_dir = os.path.join(self.msgs_dir, self.agent_name)
146
+ if not os.path.exists(inbox_dir):
147
+ return []
148
+
149
+ files = sorted([f for f in os.listdir(inbox_dir) if f.endswith(".json")])
150
+ messages = []
151
+ for f in files[-limit:]:
152
+ try:
153
+ with open(os.path.join(inbox_dir, f)) as fp:
154
+ msg = json.load(fp)
155
+ if from_agent and msg.get("from") != from_agent:
156
+ continue
157
+ messages.append(msg)
158
+ except:
159
+ pass
160
+ return messages
161
+
162
+ def reply(self, original_msg, text):
163
+ """Reply to a message."""
164
+ return self.send(original_msg["from"], text, {
165
+ "in_reply_to": original_msg["id"],
166
+ "original_from": original_msg.get("from"),
167
+ })
168
+
169
+ # ── Channels ──
170
+
171
+ def channel_send(self, channel_id, message):
172
+ """Send a message to a channel."""
173
+ ch_path = os.path.join(self.channels_dir, f"{channel_id}.json")
174
+ if not os.path.exists(ch_path):
175
+ raise ValueError(f"Channel '{channel_id}' not found")
176
+
177
+ msg = {
178
+ "id": self._gen_id(),
179
+ "channel": channel_id,
180
+ "from": self.agent_name,
181
+ "message": message,
182
+ "timestamp": datetime.utcnow().isoformat() + "Z",
183
+ }
184
+
185
+ log_dir = os.path.join(self.channels_dir, channel_id, "log")
186
+ os.makedirs(log_dir, exist_ok=True)
187
+ with open(os.path.join(log_dir, f"{msg['id']}.json"), "w") as f:
188
+ json.dump(msg, f, indent=2)
189
+
190
+ # DM all members
191
+ with open(ch_path) as f:
192
+ channel = json.load(f)
193
+ for member in channel.get("members", []):
194
+ if member != self.agent_name:
195
+ self.send(member, f"[{channel_id}] {message}", {"channel": channel_id})
196
+
197
+ return msg["id"]
198
+
199
+ def channel_history(self, channel_id, limit=20):
200
+ """Read recent messages in a channel."""
201
+ log_dir = os.path.join(self.channels_dir, channel_id, "log")
202
+ if not os.path.exists(log_dir):
203
+ return []
204
+ files = sorted([f for f in os.listdir(log_dir) if f.endswith(".json")])
205
+ messages = []
206
+ for f in files[-limit:]:
207
+ try:
208
+ with open(os.path.join(log_dir, f)) as fp:
209
+ messages.append(json.load(fp))
210
+ except:
211
+ pass
212
+ return messages
213
+
214
+
215
+ # Make datetime available
216
+ from datetime import datetime
@@ -0,0 +1,10 @@
1
+
2
+ # ── CoCo Bus Aliases ──
3
+ alias coco-agents='node E:\_system\.openclaw\workspace\repos\mcp-coco\coco-cli.js agents'
4
+ alias coco-inbox='node E:\_system\.openclaw\workspace\repos\mcp-coco\coco-cli.js inbox'
5
+ alias coco-send='node E:\_system\.openclaw\workspace\repos\mcp-coco\coco-cli.js send'
6
+ alias coco-whoami='node E:\_system\.openclaw\workspace\repos\mcp-coco\coco-cli.js whoami'
7
+ alias coco-status='node E:\_system\.openclaw\workspace\repos\mcp-coco\coco-cli.js status'
8
+ alias coco-search='node E:\_system\.openclaw\workspace\repos\mcp-coco\coco-cli.js search'
9
+ alias coco-watch='node E:\_system\.openclaw\workspace\repos\mcp-coco\coco-cli.js watch'
10
+ alias coco-channel='node E:\_system\.openclaw\workspace\repos\mcp-coco\coco-cli.js channel'