Claude Code Claude Opus 4.6 commited on
Commit
ee42c89
·
1 Parent(s): 86389f0

feat: Add resource exchange system for multi-agent coordination

Browse files

- Add SharedMemory class with resource tracking and transfer methods
- Add TRANSFER_RESOURCES, CHECK_RESOURCES, and GET_RESOURCE_HISTORY tools
- Add resource display UI with bars and transfer notifications
- Add API endpoints for resource management (/api/resources, /api/resources/transfer, /api/resources/history)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

.openclaw/agents/brain_minimal.py CHANGED
@@ -20,6 +20,7 @@ import signal
20
  import sys
21
  import traceback
22
  import asyncio
 
23
 
24
  logger = logging.getLogger(__name__)
25
 
@@ -141,6 +142,19 @@ except ImportError:
141
  print("[Brain] Warning: Conversation persistence not available")
142
 
143
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  # State Machine for Agent State Management
145
  class AgentState(Enum):
146
  """Agent operational states"""
@@ -690,6 +704,27 @@ class BrainMinimal:
690
  "Ping another agent"
691
  )
692
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
693
  def _is_infrastructure_agent(self) -> bool:
694
  """Check if this is an infrastructure agent (Adam)"""
695
  if self.legacy_mode:
@@ -1237,6 +1272,131 @@ class BrainMinimal:
1237
  "timestamp": datetime.utcnow().isoformat()
1238
  }
1239
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1240
  # ========== Decision Making ==========
1241
 
1242
  def think(self, context: Dict[str, Any]) -> str:
 
20
  import sys
21
  import traceback
22
  import asyncio
23
+ import re
24
 
25
  logger = logging.getLogger(__name__)
26
 
 
142
  print("[Brain] Warning: Conversation persistence not available")
143
 
144
 
145
+ # Import Shared Memory for resource exchange
146
+ try:
147
+ from ..core.shared_memory import get_shared_memory, SharedMemory
148
+ SHARED_MEMORY_AVAILABLE = True
149
+ except ImportError:
150
+ try:
151
+ from core.shared_memory import get_shared_memory, SharedMemory
152
+ SHARED_MEMORY_AVAILABLE = True
153
+ except ImportError:
154
+ SHARED_MEMORY_AVAILABLE = False
155
+ print("[Brain] Warning: Shared memory not available")
156
+
157
+
158
  # State Machine for Agent State Management
159
  class AgentState(Enum):
160
  """Agent operational states"""
 
704
  "Ping another agent"
705
  )
706
 
707
+ # Resource management tools
708
+ if SHARED_MEMORY_AVAILABLE:
709
+ self.register_tool(
710
+ "transfer_resources",
711
+ self._transfer_resources,
712
+ ToolCategory.SHARED,
713
+ "Transfer resources to another agent"
714
+ )
715
+ self.register_tool(
716
+ "check_resources",
717
+ self._check_resources,
718
+ ToolCategory.SHARED,
719
+ "Check current resource holdings"
720
+ )
721
+ self.register_tool(
722
+ "get_resource_history",
723
+ self._get_resource_history,
724
+ ToolCategory.SHARED,
725
+ "Get recent resource transfer history"
726
+ )
727
+
728
  def _is_infrastructure_agent(self) -> bool:
729
  """Check if this is an infrastructure agent (Adam)"""
730
  if self.legacy_mode:
 
1272
  "timestamp": datetime.utcnow().isoformat()
1273
  }
1274
 
1275
+ # ========== Resource Management ==========
1276
+
1277
+ def _transfer_resources(self, to_agent: str, amount: int) -> Dict[str, Any]:
1278
+ """
1279
+ Transfer resources to another agent.
1280
+
1281
+ Args:
1282
+ to_agent: Target agent to receive resources
1283
+ amount: Amount of resources to transfer
1284
+
1285
+ Returns:
1286
+ Result dict with transfer status
1287
+ """
1288
+ if not SHARED_MEMORY_AVAILABLE:
1289
+ return {
1290
+ "success": False,
1291
+ "error": "Shared memory not available",
1292
+ "agent": self.agent_name
1293
+ }
1294
+
1295
+ try:
1296
+ shared_mem = get_shared_memory()
1297
+ success = shared_mem.transfer_resources(
1298
+ from_agent=self.agent_name,
1299
+ to_agent=to_agent,
1300
+ amount=amount
1301
+ )
1302
+
1303
+ if success:
1304
+ logger.info(f"[{self.agent_name}] Transferred {amount} resources to {to_agent}")
1305
+ return {
1306
+ "success": True,
1307
+ "from": self.agent_name,
1308
+ "to": to_agent,
1309
+ "amount": amount,
1310
+ "timestamp": datetime.utcnow().isoformat()
1311
+ }
1312
+ else:
1313
+ return {
1314
+ "success": False,
1315
+ "error": "Insufficient resources",
1316
+ "from": self.agent_name,
1317
+ "to": to_agent,
1318
+ "amount": amount,
1319
+ "available": shared_mem.get_resources(self.agent_name).get(self.agent_name, 0)
1320
+ }
1321
+ except Exception as e:
1322
+ logger.error(f"[{self.agent_name}] Transfer failed: {e}")
1323
+ return {
1324
+ "success": False,
1325
+ "error": str(e),
1326
+ "agent": self.agent_name
1327
+ }
1328
+
1329
+ def _check_resources(self, agent: Optional[str] = None) -> Dict[str, Any]:
1330
+ """
1331
+ Check current resource holdings.
1332
+
1333
+ Args:
1334
+ agent: Optional agent name to check. If None, returns all agents.
1335
+
1336
+ Returns:
1337
+ Result dict with resource information
1338
+ """
1339
+ if not SHARED_MEMORY_AVAILABLE:
1340
+ return {
1341
+ "success": False,
1342
+ "error": "Shared memory not available",
1343
+ "agent": self.agent_name
1344
+ }
1345
+
1346
+ try:
1347
+ shared_mem = get_shared_memory()
1348
+ resources = shared_mem.get_resources(agent)
1349
+
1350
+ return {
1351
+ "success": True,
1352
+ "resources": resources,
1353
+ "agent": self.agent_name,
1354
+ "timestamp": datetime.utcnow().isoformat()
1355
+ }
1356
+ except Exception as e:
1357
+ logger.error(f"[{self.agent_name}] Check resources failed: {e}")
1358
+ return {
1359
+ "success": False,
1360
+ "error": str(e),
1361
+ "agent": self.agent_name
1362
+ }
1363
+
1364
+ def _get_resource_history(self, limit: int = 10) -> Dict[str, Any]:
1365
+ """
1366
+ Get recent resource transfer history.
1367
+
1368
+ Args:
1369
+ limit: Maximum number of transfers to return
1370
+
1371
+ Returns:
1372
+ Result dict with transfer history
1373
+ """
1374
+ if not SHARED_MEMORY_AVAILABLE:
1375
+ return {
1376
+ "success": False,
1377
+ "error": "Shared memory not available",
1378
+ "agent": self.agent_name
1379
+ }
1380
+
1381
+ try:
1382
+ shared_mem = get_shared_memory()
1383
+ history = shared_mem.get_transfer_log(limit)
1384
+
1385
+ return {
1386
+ "success": True,
1387
+ "history": history,
1388
+ "count": len(history),
1389
+ "agent": self.agent_name,
1390
+ "timestamp": datetime.utcnow().isoformat()
1391
+ }
1392
+ except Exception as e:
1393
+ logger.error(f"[{self.agent_name}] Get history failed: {e}")
1394
+ return {
1395
+ "success": False,
1396
+ "error": str(e),
1397
+ "agent": self.agent_name
1398
+ }
1399
+
1400
  # ========== Decision Making ==========
1401
 
1402
  def think(self, context: Dict[str, Any]) -> str:
.openclaw/core/shared_memory.py CHANGED
@@ -2,6 +2,7 @@
2
  Shared Memory Module for OpenClaw
3
  ==================================
4
  Provides persistent conversation storage with atomic write operations.
 
5
  """
6
  import json
7
  import os
@@ -208,3 +209,151 @@ class ConversationStore:
208
  if session_id:
209
  return sum(1 for msg in messages if msg.session_id == session_id)
210
  return len(messages)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  Shared Memory Module for OpenClaw
3
  ==================================
4
  Provides persistent conversation storage with atomic write operations.
5
+ Also provides SharedMemory class for agent resource exchange.
6
  """
7
  import json
8
  import os
 
209
  if session_id:
210
  return sum(1 for msg in messages if msg.session_id == session_id)
211
  return len(messages)
212
+
213
+
214
+ class SharedMemory:
215
+ """
216
+ Thread-safe shared memory for agent resource exchange and coordination.
217
+ Provides resource tracking, transfer capabilities, and inter-agent communication.
218
+ """
219
+
220
+ def __init__(self, storage_dir: Optional[Path] = None):
221
+ """
222
+ Initialize shared memory with resource tracking.
223
+
224
+ Args:
225
+ storage_dir: Directory to store shared memory data.
226
+ Defaults to ~/.openclaw/memory/
227
+ """
228
+ if storage_dir is None:
229
+ storage_dir = Path.home() / ".openclaw" / "memory"
230
+
231
+ self.storage_dir = Path(storage_dir)
232
+ self._lock = Lock()
233
+ self.storage_dir.mkdir(parents=True, exist_ok=True)
234
+
235
+ # Initialize resources
236
+ self.resources = {"adam": 100, "eve": 100}
237
+ self._transfer_log = []
238
+
239
+ # Try to load existing state
240
+ self._load_state()
241
+
242
+ def _load_state(self) -> None:
243
+ """Load shared memory state from disk."""
244
+ state_file = self.storage_dir / "shared_memory_state.json"
245
+ if state_file.exists():
246
+ try:
247
+ with open(state_file, 'r') as f:
248
+ state = json.load(f)
249
+ self.resources = state.get("resources", {"adam": 100, "eve": 100})
250
+ self._transfer_log = state.get("transfer_log", [])
251
+ except (json.JSONDecodeError, IOError):
252
+ pass
253
+
254
+ def _save_state(self) -> None:
255
+ """Save shared memory state to disk."""
256
+ state_file = self.storage_dir / "shared_memory_state.json"
257
+ try:
258
+ with open(state_file, 'w') as f:
259
+ json.dump({
260
+ "resources": self.resources,
261
+ "transfer_log": self._transfer_log
262
+ }, f)
263
+ except IOError:
264
+ pass
265
+
266
+ def transfer_resources(self, from_agent: str, to_agent: str, amount: int) -> bool:
267
+ """
268
+ Transfer resources between agents.
269
+
270
+ Args:
271
+ from_agent: Source agent name
272
+ to_agent: Target agent name
273
+ amount: Amount to transfer
274
+
275
+ Returns:
276
+ True if transfer succeeded, False otherwise
277
+ """
278
+ with self._lock:
279
+ from_agent = from_agent.lower()
280
+ to_agent = to_agent.lower()
281
+
282
+ # Check if sender has enough resources
283
+ if self.resources.get(from_agent, 0) >= amount:
284
+ self.resources[from_agent] -= amount
285
+ self.resources[to_agent] = self.resources.get(to_agent, 0) + amount
286
+
287
+ # Log the transfer
288
+ self._transfer_log.append({
289
+ "from": from_agent,
290
+ "to": to_agent,
291
+ "amount": amount,
292
+ "timestamp": datetime.utcnow().isoformat() + 'Z'
293
+ })
294
+
295
+ self._save_state()
296
+ return True
297
+ return False
298
+
299
+ def get_resources(self, agent: Optional[str] = None) -> Dict[str, int]:
300
+ """
301
+ Get current resource values.
302
+
303
+ Args:
304
+ agent: If provided, return only this agent's resources
305
+
306
+ Returns:
307
+ Dictionary of agent to resource amount
308
+ """
309
+ with self._lock:
310
+ if agent:
311
+ return {agent.lower(): self.resources.get(agent.lower(), 0)}
312
+ return self.resources.copy()
313
+
314
+ def get_transfer_log(self, limit: int = 10) -> List[Dict[str, Any]]:
315
+ """
316
+ Get recent transfer history.
317
+
318
+ Args:
319
+ limit: Maximum number of transfers to return
320
+
321
+ Returns:
322
+ List of transfer records
323
+ """
324
+ with self._lock:
325
+ return self._transfer_log[-limit:]
326
+
327
+ def reset_resources(self, initial_values: Optional[Dict[str, int]] = None) -> None:
328
+ """
329
+ Reset resources to initial values.
330
+
331
+ Args:
332
+ initial_values: Optional dict of agent to starting amount.
333
+ Defaults to {"adam": 100, "eve": 100}
334
+ """
335
+ with self._lock:
336
+ if initial_values is None:
337
+ initial_values = {"adam": 100, "eve": 100}
338
+ self.resources = {k.lower(): v for k, v in initial_values.items()}
339
+ self._transfer_log = []
340
+ self._save_state()
341
+
342
+
343
+ # Global shared memory instance
344
+ _shared_memory_instance: Optional[SharedMemory] = None
345
+ _shared_memory_lock = Lock()
346
+
347
+
348
+ def get_shared_memory() -> SharedMemory:
349
+ """
350
+ Get the global shared memory instance (singleton pattern).
351
+
352
+ Returns:
353
+ The global SharedMemory instance
354
+ """
355
+ global _shared_memory_instance
356
+ with _shared_memory_lock:
357
+ if _shared_memory_instance is None:
358
+ _shared_memory_instance = SharedMemory()
359
+ return _shared_memory_instance
app.py CHANGED
@@ -2935,6 +2935,99 @@ def create_agent_office_with_ws():
2935
  logger.error(f"[API_ERROR] /api/analytics/reset: {e}")
2936
  return {"error": str(e)}
2937
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2938
  # ========== Conversation Persistence API Endpoints ==========
2939
 
2940
  if CONVERSATION_STORAGE_AVAILABLE:
 
2935
  logger.error(f"[API_ERROR] /api/analytics/reset: {e}")
2936
  return {"error": str(e)}
2937
 
2938
+ # ========== Resource Exchange API Endpoints ==========
2939
+
2940
+ # Import SharedMemory for resource management
2941
+ try:
2942
+ from .openclaw.core.shared_memory import get_shared_memory
2943
+ SHARED_MEMORY_AVAILABLE = True
2944
+ except ImportError:
2945
+ try:
2946
+ from openclaw.core.shared_memory import get_shared_memory
2947
+ SHARED_MEMORY_AVAILABLE = True
2948
+ except ImportError:
2949
+ SHARED_MEMORY_AVAILABLE = False
2950
+
2951
+ if SHARED_MEMORY_AVAILABLE:
2952
+ @fastapi_app.get("/api/resources")
2953
+ async def api_get_resources(agent: str = None):
2954
+ """Get current resource holdings."""
2955
+ try:
2956
+ shared_mem = get_shared_memory()
2957
+ resources = shared_mem.get_resources(agent)
2958
+ return {
2959
+ "success": True,
2960
+ "resources": resources,
2961
+ "timestamp": datetime.utcnow().isoformat()
2962
+ }
2963
+ except asyncio.CancelledError:
2964
+ logger.debug("[API_CANCEL] /api/resources was cancelled")
2965
+ from fastapi.responses import Response
2966
+ return Response(status_code=204, content=None)
2967
+ except Exception as e:
2968
+ logger.error(f"[API_ERROR] /api/resources: {e}")
2969
+ return {"success": False, "error": str(e)}
2970
+
2971
+ @fastapi_app.post("/api/resources/transfer")
2972
+ async def api_transfer_resources(from_agent: str, to_agent: str, amount: int):
2973
+ """Transfer resources between agents."""
2974
+ try:
2975
+ shared_mem = get_shared_memory()
2976
+ success = shared_mem.transfer_resources(from_agent, to_agent, amount)
2977
+ return {
2978
+ "success": success,
2979
+ "from": from_agent,
2980
+ "to": to_agent,
2981
+ "amount": amount,
2982
+ "timestamp": datetime.utcnow().isoformat()
2983
+ }
2984
+ except asyncio.CancelledError:
2985
+ logger.debug("[API_CANCEL] /api/resources/transfer was cancelled")
2986
+ from fastapi.responses import Response
2987
+ return Response(status_code=204, content=None)
2988
+ except Exception as e:
2989
+ logger.error(f"[API_ERROR] /api/resources/transfer: {e}")
2990
+ return {"success": False, "error": str(e)}
2991
+
2992
+ @fastapi_app.get("/api/resources/history")
2993
+ async def api_resource_history(limit: int = 10):
2994
+ """Get recent resource transfer history."""
2995
+ try:
2996
+ shared_mem = get_shared_memory()
2997
+ history = shared_mem.get_transfer_log(limit)
2998
+ return {
2999
+ "success": True,
3000
+ "history": history,
3001
+ "count": len(history),
3002
+ "timestamp": datetime.utcnow().isoformat()
3003
+ }
3004
+ except asyncio.CancelledError:
3005
+ logger.debug("[API_CANCEL] /api/resources/history was cancelled")
3006
+ from fastapi.responses import Response
3007
+ return Response(status_code=204, content=None)
3008
+ except Exception as e:
3009
+ logger.error(f"[API_ERROR] /api/resources/history: {e}")
3010
+ return {"success": False, "error": str(e)}
3011
+
3012
+ @fastapi_app.post("/api/resources/reset")
3013
+ async def api_reset_resources():
3014
+ """Reset resources to initial values."""
3015
+ try:
3016
+ shared_mem = get_shared_memory()
3017
+ shared_mem.reset_resources()
3018
+ return {
3019
+ "success": True,
3020
+ "message": "Resources reset",
3021
+ "timestamp": datetime.utcnow().isoformat()
3022
+ }
3023
+ except asyncio.CancelledError:
3024
+ logger.debug("[API_CANCEL] /api/resources/reset was cancelled")
3025
+ from fastapi.responses import Response
3026
+ return Response(status_code=204, content=None)
3027
+ except Exception as e:
3028
+ logger.error(f"[API_ERROR] /api/resources/reset: {e}")
3029
+ return {"success": False, "error": str(e)}
3030
+
3031
  # ========== Conversation Persistence API Endpoints ==========
3032
 
3033
  if CONVERSATION_STORAGE_AVAILABLE:
frontend/agent-dashboard.html CHANGED
@@ -300,6 +300,115 @@
300
  margin-bottom: 16px;
301
  opacity: 0.5;
302
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  </style>
304
  </head>
305
  <body>
@@ -393,23 +502,62 @@
393
  </div>
394
  </div>
395
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
396
  </div>
397
 
 
 
 
398
  <script>
399
  // Configuration
400
  const API_BASE = window.location.origin;
401
  const POLL_INTERVAL = 2000; // 2 seconds
 
402
 
403
  // State
404
  let lastThoughtCount = 0;
405
  let startTime = Date.now();
406
  let eventCount = 0;
 
407
 
408
  // Initialize
409
  document.addEventListener('DOMContentLoaded', () => {
410
  connectToEventBus();
411
  setInterval(fetchThoughts, POLL_INTERVAL);
412
  setInterval(updateStats, 1000);
 
 
413
  });
414
 
415
  // Connect to event bus
@@ -588,6 +736,96 @@
588
 
589
  // Expose simulation function for testing
590
  window.simulateThought = simulateThought;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
591
  </script>
592
  </body>
593
  </html>
 
300
  margin-bottom: 16px;
301
  opacity: 0.5;
302
  }
303
+
304
+ /* Resource Display Styles */
305
+ .resource-panel {
306
+ background: white;
307
+ border-radius: 12px;
308
+ padding: 20px;
309
+ margin-top: 20px;
310
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
311
+ }
312
+
313
+ .resource-panel h2 {
314
+ color: #667eea;
315
+ font-size: 20px;
316
+ margin-bottom: 16px;
317
+ }
318
+
319
+ .resource-grid {
320
+ display: grid;
321
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
322
+ gap: 16px;
323
+ }
324
+
325
+ .resource-card {
326
+ background: #f8f9fa;
327
+ border-radius: 8px;
328
+ padding: 16px;
329
+ }
330
+
331
+ .resource-card h3 {
332
+ font-size: 16px;
333
+ margin-bottom: 8px;
334
+ color: #333;
335
+ }
336
+
337
+ .resource-bar-container {
338
+ background: #e0e0e0;
339
+ border-radius: 8px;
340
+ height: 24px;
341
+ overflow: hidden;
342
+ margin-bottom: 8px;
343
+ }
344
+
345
+ .resource-bar {
346
+ height: 100%;
347
+ background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
348
+ transition: width 0.5s ease;
349
+ border-radius: 8px;
350
+ }
351
+
352
+ .resource-bar.adam {
353
+ background: linear-gradient(90deg, #28a745 0%, #20c997 100%);
354
+ }
355
+
356
+ .resource-bar.eve {
357
+ background: linear-gradient(90deg, #fd7e14 0%, #ffc107 100%);
358
+ }
359
+
360
+ .resource-value {
361
+ font-size: 24px;
362
+ font-weight: bold;
363
+ color: #667eea;
364
+ }
365
+
366
+ .resource-label {
367
+ font-size: 12px;
368
+ color: #666;
369
+ }
370
+
371
+ .transfer-notification {
372
+ position: fixed;
373
+ bottom: 20px;
374
+ right: 20px;
375
+ background: white;
376
+ border-radius: 8px;
377
+ padding: 16px;
378
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
379
+ animation: slideInRight 0.3s ease-out;
380
+ max-width: 300px;
381
+ z-index: 1000;
382
+ }
383
+
384
+ @keyframes slideInRight {
385
+ from {
386
+ transform: translateX(100%);
387
+ opacity: 0;
388
+ }
389
+ to {
390
+ transform: translateX(0);
391
+ opacity: 1;
392
+ }
393
+ }
394
+
395
+ .transfer-notification.success {
396
+ border-left: 4px solid #28a745;
397
+ }
398
+
399
+ .transfer-notification.error {
400
+ border-left: 4px solid #dc3545;
401
+ }
402
+
403
+ .transfer-notification .title {
404
+ font-weight: bold;
405
+ margin-bottom: 4px;
406
+ }
407
+
408
+ .transfer-notification .message {
409
+ font-size: 14px;
410
+ color: #666;
411
+ }
412
  </style>
413
  </head>
414
  <body>
 
502
  </div>
503
  </div>
504
  </div>
505
+
506
+ <!-- Resource Exchange Panel -->
507
+ <div class="resource-panel">
508
+ <h2>
509
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align: middle; margin-right: 8px;">
510
+ <path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path>
511
+ </svg>
512
+ Resource Exchange
513
+ </h2>
514
+ <div class="resource-grid" id="resourceGrid">
515
+ <div class="resource-card">
516
+ <h3>Adam</h3>
517
+ <div class="resource-bar-container">
518
+ <div class="resource-bar adam" id="adamBar" style="width: 50%"></div>
519
+ </div>
520
+ <div class="resource-value" id="adamValue">100</div>
521
+ <div class="resource-label">Resources</div>
522
+ </div>
523
+ <div class="resource-card">
524
+ <h3>Eve</h3>
525
+ <div class="resource-bar-container">
526
+ <div class="resource-bar eve" id="eveBar" style="width: 50%"></div>
527
+ </div>
528
+ <div class="resource-value" id="eveValue">100</div>
529
+ <div class="resource-label">Resources</div>
530
+ </div>
531
+ </div>
532
+ <div class="controls" style="margin-top: 16px;">
533
+ <button class="btn btn-primary" onclick="fetchResources()">Refresh Resources</button>
534
+ <button class="btn btn-secondary" onclick="showTransferHistory()">Transfer History</button>
535
+ </div>
536
+ </div>
537
  </div>
538
 
539
+ <!-- Transfer notification container -->
540
+ <div id="notificationContainer"></div>
541
+
542
  <script>
543
  // Configuration
544
  const API_BASE = window.location.origin;
545
  const POLL_INTERVAL = 2000; // 2 seconds
546
+ const RESOURCE_INTERVAL = 5000; // 5 seconds
547
 
548
  // State
549
  let lastThoughtCount = 0;
550
  let startTime = Date.now();
551
  let eventCount = 0;
552
+ let lastResources = { adam: 100, eve: 100 };
553
 
554
  // Initialize
555
  document.addEventListener('DOMContentLoaded', () => {
556
  connectToEventBus();
557
  setInterval(fetchThoughts, POLL_INTERVAL);
558
  setInterval(updateStats, 1000);
559
+ setInterval(fetchResources, RESOURCE_INTERVAL);
560
+ fetchResources(); // Initial fetch
561
  });
562
 
563
  // Connect to event bus
 
736
 
737
  // Expose simulation function for testing
738
  window.simulateThought = simulateThought;
739
+
740
+ // ========== Resource Management ==========
741
+
742
+ // Fetch resources from API
743
+ async function fetchResources() {
744
+ try {
745
+ const response = await fetch(`${API_BASE}/api/resources`);
746
+ if (response.ok) {
747
+ const data = await response.json();
748
+ updateResourceDisplay(data.resources || {});
749
+ }
750
+ } catch (error) {
751
+ console.error('Error fetching resources:', error);
752
+ }
753
+ }
754
+
755
+ // Update resource display
756
+ function updateResourceDisplay(resources) {
757
+ const maxResources = 200; // For bar scaling
758
+
759
+ for (const [agent, amount] of Object.entries(resources)) {
760
+ const valueEl = document.getElementById(`${agent}Value`);
761
+ const barEl = document.getElementById(`${agent}Bar`);
762
+
763
+ if (valueEl && barEl) {
764
+ valueEl.textContent = amount;
765
+ const percentage = Math.min((amount / maxResources) * 100, 100);
766
+ barEl.style.width = `${percentage}%`;
767
+
768
+ // Check for changes and show notification
769
+ if (lastResources[agent] !== undefined && lastResources[agent] !== amount) {
770
+ const diff = amount - lastResources[agent];
771
+ if (diff !== 0) {
772
+ showTransferNotification(agent, diff);
773
+ }
774
+ }
775
+ }
776
+ }
777
+
778
+ lastResources = { ...resources };
779
+ }
780
+
781
+ // Show transfer notification
782
+ function showTransferNotification(agent, diff) {
783
+ const container = document.getElementById('notificationContainer');
784
+ const notification = document.createElement('div');
785
+
786
+ const isSuccess = diff > 0;
787
+ notification.className = `transfer-notification ${isSuccess ? 'success' : 'error'}`;
788
+
789
+ notification.innerHTML = `
790
+ <div class="title">${isSuccess ? 'Resources Received!' : 'Resources Transferred'}</div>
791
+ <div class="message">
792
+ ${agent.charAt(0).toUpperCase() + agent.slice(1)}: ${diff > 0 ? '+' : ''}${diff} resources
793
+ </div>
794
+ `;
795
+
796
+ container.appendChild(notification);
797
+
798
+ // Remove notification after 3 seconds
799
+ setTimeout(() => {
800
+ notification.style.animation = 'slideInRight 0.3s ease-out reverse';
801
+ setTimeout(() => notification.remove(), 300);
802
+ }, 3000);
803
+ }
804
+
805
+ // Show transfer history
806
+ async function showTransferHistory() {
807
+ try {
808
+ const response = await fetch(`${API_BASE}/api/resources/history?limit=10`);
809
+ if (response.ok) {
810
+ const data = await response.json();
811
+ displayTransferHistory(data.history || []);
812
+ }
813
+ } catch (error) {
814
+ console.error('Error fetching transfer history:', error);
815
+ }
816
+ }
817
+
818
+ // Display transfer history
819
+ function displayTransferHistory(history) {
820
+ if (history.length === 0) {
821
+ showTransferNotification('system', 0);
822
+ return;
823
+ }
824
+
825
+ // Show a summary notification
826
+ const latest = history[history.length - 1];
827
+ showTransferNotification(latest.to, latest.amount);
828
+ }
829
  </script>
830
  </body>
831
  </html>