a2acalling 0.5.0 → 0.5.3

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.
@@ -16,6 +16,7 @@ const { ConversationStore } = require('../lib/conversations');
16
16
  const { A2AConfig } = require('../lib/config');
17
17
  const { loadManifest, saveManifest } = require('../lib/disclosure');
18
18
  const { resolveInviteHost } = require('../lib/invite-host');
19
+ const { createLogger } = require('../lib/logger');
19
20
 
20
21
  const DASHBOARD_STATIC_DIR = path.join(__dirname, '..', 'dashboard', 'public');
21
22
 
@@ -106,6 +107,7 @@ https://github.com/onthegonow/a2a_calling`;
106
107
  function buildContext(options = {}) {
107
108
  const tokenStore = options.tokenStore || new TokenStore();
108
109
  const config = options.config || new A2AConfig();
110
+ const logger = options.logger || createLogger({ component: 'a2a.dashboard' });
109
111
  let convStore = options.convStore || null;
110
112
  if (!convStore) {
111
113
  try {
@@ -122,6 +124,7 @@ function buildContext(options = {}) {
122
124
  tokenStore,
123
125
  config,
124
126
  convStore,
127
+ logger,
125
128
  staticDir: DASHBOARD_STATIC_DIR
126
129
  };
127
130
  }
@@ -189,6 +192,7 @@ function createDashboardApiRouter(options = {}) {
189
192
  router.use(ensureDashboardAccess);
190
193
 
191
194
  router.get('/status', (req, res) => {
195
+ context.logger.debug('Dashboard status requested', { event: 'dashboard_status' });
192
196
  res.json({
193
197
  success: true,
194
198
  dashboard: true,
@@ -198,6 +202,43 @@ function createDashboardApiRouter(options = {}) {
198
202
  });
199
203
  });
200
204
 
205
+ router.get('/logs', (req, res) => {
206
+ const limit = Math.min(1000, Math.max(1, Number.parseInt(req.query.limit || '200', 10) || 200));
207
+ const logs = context.logger.list({
208
+ limit,
209
+ level: req.query.level || null,
210
+ component: req.query.component || null,
211
+ event: req.query.event || null,
212
+ errorCode: req.query.error_code || req.query.errorCode || null,
213
+ statusCode: req.query.status_code || req.query.statusCode || null,
214
+ traceId: req.query.trace_id || req.query.traceId || null,
215
+ conversationId: req.query.conversation_id || req.query.conversationId || null,
216
+ tokenId: req.query.token_id || req.query.tokenId || null,
217
+ search: req.query.search || null,
218
+ from: req.query.from || null,
219
+ to: req.query.to || null
220
+ });
221
+ return res.json({ success: true, logs });
222
+ });
223
+
224
+ router.get('/logs/trace/:traceId', (req, res) => {
225
+ const traceId = sanitizeString(req.params.traceId, 120);
226
+ if (!traceId) {
227
+ return res.status(400).json({ success: false, error: 'trace_id_required' });
228
+ }
229
+ const limit = Math.min(1000, Math.max(1, Number.parseInt(req.query.limit || '500', 10) || 500));
230
+ const logs = context.logger.getTrace(traceId, { limit });
231
+ return res.json({ success: true, trace_id: traceId, logs });
232
+ });
233
+
234
+ router.get('/logs/stats', (req, res) => {
235
+ const stats = context.logger.stats({
236
+ from: req.query.from || null,
237
+ to: req.query.to || null
238
+ });
239
+ return res.json({ success: true, stats });
240
+ });
241
+
201
242
  router.get('/contacts', (req, res) => {
202
243
  const contacts = context.tokenStore.listRemotes();
203
244
  const contactIndex = buildContactIndex(contacts);
package/src/server.js CHANGED
@@ -20,12 +20,14 @@ const {
20
20
  extractCollaborationState
21
21
  } = require('./lib/prompt-template');
22
22
  const { findAvailablePort } = require('./lib/port-scanner');
23
+ const { createLogger } = require('./lib/logger');
23
24
 
24
25
  const DEFAULT_PORTS = [80, 3001, 8080, 8443, 9001];
25
26
  const requestedPort = process.env.PORT ? parseInt(process.env.PORT, 10)
26
27
  : process.argv[2] ? parseInt(process.argv[2], 10)
27
28
  : null;
28
29
  const workspaceDir = process.env.A2A_WORKSPACE || process.env.OPENCLAW_WORKSPACE || process.cwd();
30
+ const logger = createLogger({ component: 'a2a.server' });
29
31
 
30
32
  // Load workspace context for agent identity
31
33
  function loadAgentContext() {
@@ -59,17 +61,30 @@ const agentContext = loadAgentContext();
59
61
  const tokenStore = new TokenStore();
60
62
  const runtime = createRuntimeAdapter({
61
63
  workspaceDir,
62
- agentContext
64
+ agentContext,
65
+ logger: logger.child({ component: 'a2a.runtime' })
63
66
  });
64
67
  const VALID_PHASES = new Set(['handshake', 'explore', 'deep_dive', 'synthesize', 'close']);
65
68
  const collaborationSessions = new Map();
66
69
  const COLLAB_STATE_TTL_MS = readPositiveIntEnv('A2A_COLLAB_STATE_TTL_MS', 6 * 60 * 60 * 1000);
67
70
  const MAX_COLLAB_SESSIONS = readPositiveIntEnv('A2A_COLLAB_MAX_SESSIONS', 500);
68
71
 
69
- console.log(`[a2a] Agent: ${agentContext.name} (${agentContext.owner}'s agent)`);
70
- console.log(`[a2a] Runtime: ${runtime.mode} (${runtime.reason})`);
72
+ logger.info('A2A server bootstrapping', {
73
+ event: 'server_bootstrap',
74
+ data: {
75
+ agent_name: agentContext.name,
76
+ owner_name: agentContext.owner,
77
+ runtime_mode: runtime.mode,
78
+ runtime_reason: runtime.reason
79
+ }
80
+ });
71
81
  if (runtime.warning) {
72
- console.warn(`[a2a] Runtime warning: ${runtime.warning}`);
82
+ logger.warn('Runtime warning', {
83
+ event: 'runtime_warning',
84
+ data: {
85
+ warning: runtime.warning
86
+ }
87
+ });
73
88
  }
74
89
 
75
90
  function readPositiveIntEnv(name, fallback) {
@@ -405,11 +420,27 @@ function ensureContact(caller, tokenId) {
405
420
  try {
406
421
  const contact = tokenStore.ensureInboundContact(caller, tokenId);
407
422
  if (contact) {
408
- console.log(`[a2a] 📇 Contact ensured: ${caller.name}${caller.owner ? ` (${caller.owner})` : ''}`);
423
+ logger.info('Contact ensured from inbound call', {
424
+ event: 'contact_ensured',
425
+ tokenId,
426
+ data: {
427
+ caller_name: caller.name,
428
+ caller_owner: caller.owner || null
429
+ }
430
+ });
409
431
  }
410
432
  return contact;
411
433
  } catch (err) {
412
- console.error('[a2a] Failed to ensure contact:', err.message);
434
+ logger.error('Failed to ensure contact', {
435
+ event: 'contact_ensure_failed',
436
+ tokenId,
437
+ error_code: 'CONTACT_ENSURE_FAILED',
438
+ hint: 'Validate token/contact store files and write permissions.',
439
+ error: err,
440
+ data: {
441
+ caller_name: caller?.name || null
442
+ }
443
+ });
413
444
  return null;
414
445
  }
415
446
  }
@@ -422,6 +453,12 @@ async function callAgent(message, a2aContext) {
422
453
  const callerOwner = a2aContext.caller?.owner || '';
423
454
  const tierInfo = a2aContext.tier || 'public';
424
455
  const conversationId = a2aContext.conversation_id || `conv_${Date.now()}`;
456
+ const traceId = a2aContext.trace_id || null;
457
+ const callLogger = logger.child({
458
+ traceId,
459
+ conversationId,
460
+ tokenId: a2aContext.token_id
461
+ });
425
462
  const collabMode = resolveCollabMode();
426
463
  const collabState = getOrCreateCollaborationState(conversationId, {
427
464
  callerName,
@@ -481,6 +518,16 @@ async function callAgent(message, a2aContext) {
481
518
  const sessionId = `a2a-${conversationId}`;
482
519
 
483
520
  try {
521
+ callLogger.info('Handling inbound call turn', {
522
+ event: 'call_turn_start',
523
+ data: {
524
+ caller_name: callerName,
525
+ caller_owner: callerOwner || null,
526
+ tier: tierInfo,
527
+ collab_mode: collabMode
528
+ }
529
+ });
530
+
484
531
  const rawResponse = await runtime.runTurn({
485
532
  sessionId,
486
533
  prompt,
@@ -491,7 +538,8 @@ async function callAgent(message, a2aContext) {
491
538
  conversationId,
492
539
  tier: tierInfo,
493
540
  ownerName: agentContext.owner,
494
- allowedTopics: a2aContext.allowed_topics || []
541
+ allowedTopics: a2aContext.allowed_topics || [],
542
+ traceId
495
543
  }
496
544
  });
497
545
 
@@ -507,10 +555,21 @@ async function callAgent(message, a2aContext) {
507
555
  if (parsed.hasState && parsed.statePatch) {
508
556
  usedMetadata = applyCollaborationPatch(collabState, parsed.statePatch);
509
557
  if (!usedMetadata) {
510
- console.warn(`[a2a] Invalid collaboration patch for ${conversationId}; using fallback heuristics`);
558
+ callLogger.warn('Invalid collaboration patch; applying fallback heuristics', {
559
+ event: 'collaboration_patch_invalid',
560
+ error_code: 'COLLABORATION_PATCH_INVALID',
561
+ hint: 'Ensure assistant emits valid collaboration metadata JSON block.'
562
+ });
511
563
  }
512
564
  } else if (parsed.parseError) {
513
- console.warn(`[a2a] Could not parse collaboration metadata for ${conversationId}: ${parsed.parseError}`);
565
+ callLogger.warn('Could not parse collaboration metadata; applying fallback heuristics', {
566
+ event: 'collaboration_metadata_parse_failed',
567
+ error_code: 'COLLABORATION_METADATA_PARSE_FAILED',
568
+ hint: 'Inspect response format and ensure metadata wrapper markers are intact.',
569
+ data: {
570
+ parse_error: parsed.parseError
571
+ }
572
+ });
514
573
  }
515
574
 
516
575
  if (!usedMetadata) {
@@ -527,10 +586,27 @@ async function callAgent(message, a2aContext) {
527
586
  collabState.updatedAt = Date.now();
528
587
  collaborationSessions.set(conversationId, collabState);
529
588
 
589
+ callLogger.info('Call turn completed', {
590
+ event: 'call_turn_complete',
591
+ data: {
592
+ phase: collabState.phase,
593
+ overlap_score: collabState.overlapScore,
594
+ turn_count: collabState.turnCount
595
+ }
596
+ });
597
+
530
598
  return cleanResponse || '[Sub-agent returned empty response]';
531
599
 
532
600
  } catch (err) {
533
- console.error('[a2a] Runtime turn handling failed:', err.message);
601
+ callLogger.error('Runtime turn handling failed; using fallback response', {
602
+ event: 'call_turn_failed_fallback',
603
+ error_code: 'RUNTIME_TURN_FAILED',
604
+ hint: 'Inspect runtime adapter logs in this trace to identify CLI/bridge failure.',
605
+ error: err,
606
+ data: {
607
+ phase: 'runtime_turn'
608
+ }
609
+ });
534
610
  return runtime.buildFallbackResponse(message, {
535
611
  caller: a2aContext.caller,
536
612
  ownerName: agentContext.owner,
@@ -573,10 +649,22 @@ Be concise but specific. No filler.`;
573
649
  sessionId: `summary-${Date.now()}`,
574
650
  prompt,
575
651
  messages,
576
- callerInfo
652
+ callerInfo,
653
+ traceId: callerInfo?.trace_id || callerInfo?.traceId,
654
+ conversationId: callerInfo?.conversation_id || callerInfo?.conversationId
577
655
  });
578
656
  } catch (err) {
579
- console.error('[a2a] Summary generation failed:', err.message);
657
+ logger.error('Summary generation failed', {
658
+ event: 'summary_generation_failed',
659
+ traceId: callerInfo?.trace_id || callerInfo?.traceId,
660
+ conversationId: callerInfo?.conversation_id || callerInfo?.conversationId,
661
+ error_code: 'SUMMARY_GENERATION_FAILED',
662
+ hint: 'Check summarizer runtime and command configuration for summary stage.',
663
+ error: err,
664
+ data: {
665
+ phase: 'summary'
666
+ }
667
+ });
580
668
  return null;
581
669
  }
582
670
  }
@@ -584,23 +672,30 @@ Be concise but specific. No filler.`;
584
672
  /**
585
673
  * Notify owner via Telegram
586
674
  */
587
- async function notifyOwner({ level, token, caller, message, conversation_id }) {
675
+ async function notifyOwner({ level, token, caller, message, conversation_id, trace_id }) {
588
676
  const callerName = caller?.name || 'Unknown';
589
677
  const callerOwner = caller?.owner ? ` (${caller.owner})` : '';
590
678
  const messageText = String(message || '');
591
679
 
592
- console.log(`[a2a] 📞 Call from ${callerName}${callerOwner}`);
593
- console.log(`[a2a] Token: ${token?.name || 'unknown'}`);
594
- if (messageText) {
595
- console.log(`[a2a] Message: ${messageText.slice(0, 100)}...`);
596
- }
680
+ logger.info('Owner notification requested', {
681
+ event: 'owner_notify_requested',
682
+ conversationId: conversation_id,
683
+ tokenId: token?.id,
684
+ data: {
685
+ caller_name: callerName,
686
+ caller_owner: caller?.owner || null,
687
+ token_name: token?.name || 'unknown',
688
+ level
689
+ }
690
+ });
597
691
 
598
692
  await runtime.notify({
599
693
  level,
600
694
  token,
601
695
  caller,
602
696
  message: messageText,
603
- conversationId: conversation_id
697
+ conversationId: conversation_id,
698
+ traceId: trace_id || null
604
699
  });
605
700
  }
606
701
 
@@ -609,21 +704,40 @@ app.use(express.json());
609
704
 
610
705
  // Minimal owner dashboard (local by default unless A2A_ADMIN_TOKEN is provided)
611
706
  app.use('/api/a2a/dashboard', createDashboardApiRouter({
612
- tokenStore
707
+ tokenStore,
708
+ logger: logger.child({ component: 'a2a.dashboard' })
613
709
  }));
614
710
  app.use('/dashboard', createDashboardUiRouter({
615
- tokenStore
711
+ tokenStore,
712
+ logger: logger.child({ component: 'a2a.dashboard' })
616
713
  }));
617
714
 
618
715
  app.use('/api/a2a', createRoutes({
619
716
  tokenStore,
717
+ logger: logger.child({ component: 'a2a.routes' }),
620
718
 
621
719
  async handleMessage(message, context, options) {
622
- console.log(`[a2a] 📞 Incoming from ${context.caller?.name || 'unknown'}`);
720
+ const traceId = context.trace_id || null;
721
+ const requestLogger = logger.child({
722
+ traceId,
723
+ conversationId: context.conversation_id,
724
+ tokenId: context.token_id
725
+ });
726
+ requestLogger.info('Inbound message accepted for handling', {
727
+ event: 'handle_message_start',
728
+ data: {
729
+ caller_name: context.caller?.name || 'unknown'
730
+ }
731
+ });
623
732
 
624
733
  const response = await callAgent(message, context);
625
734
 
626
- console.log(`[a2a] 📤 Response: ${response.slice(0, 100)}...`);
735
+ requestLogger.info('Outbound response generated', {
736
+ event: 'handle_message_complete',
737
+ data: {
738
+ response_length: String(response || '').length
739
+ }
740
+ });
627
741
 
628
742
  return { text: response, canContinue: true };
629
743
  },
@@ -645,7 +759,12 @@ async function startServer() {
645
759
  if (await isPortAvailable(requestedPort)) {
646
760
  port = requestedPort;
647
761
  } else {
648
- console.warn(`[a2a] Requested port ${requestedPort} is in use, scanning for alternatives...`);
762
+ logger.warn('Requested port is in use, scanning for alternatives', {
763
+ event: 'requested_port_in_use',
764
+ data: {
765
+ requested_port: requestedPort
766
+ }
767
+ });
649
768
  port = await findAvailablePort(DEFAULT_PORTS);
650
769
  }
651
770
  } else {
@@ -653,22 +772,40 @@ async function startServer() {
653
772
  }
654
773
 
655
774
  if (!port) {
656
- console.error(`[a2a] No available port found. Tried: ${requestedPort ? requestedPort + ', ' : ''}${DEFAULT_PORTS.join(', ')}`);
657
- console.error('[a2a] Set PORT env or free one of the default ports.');
775
+ logger.error('No available port found', {
776
+ event: 'port_unavailable',
777
+ data: {
778
+ tried_ports: requestedPort ? [requestedPort, ...DEFAULT_PORTS] : DEFAULT_PORTS
779
+ }
780
+ });
781
+ logger.error('Set PORT env or free one of the default ports.', {
782
+ event: 'port_unavailable_hint'
783
+ });
658
784
  process.exit(1);
659
785
  }
660
786
 
661
787
  const server = app.listen(port, () => {
662
- console.log(`[a2a] A2A server listening on port ${port}`);
663
- console.log(`[a2a] Agent: ${agentContext.name} - LIVE`);
664
- console.log(`[a2a] Runtime mode: ${runtime.mode}${runtime.failoverEnabled ? ' (failover enabled)' : ''}`);
665
- console.log(`[a2a] Collaboration mode: ${resolveCollabMode()}`);
666
- console.log(`[a2a] Features: adaptive collaboration, auto-contacts, summaries, dashboard`);
788
+ logger.info('A2A server listening', {
789
+ event: 'server_started',
790
+ data: {
791
+ port,
792
+ agent_name: agentContext.name,
793
+ runtime_mode: runtime.mode,
794
+ failover_enabled: runtime.failoverEnabled,
795
+ collaboration_mode: resolveCollabMode(),
796
+ features: ['adaptive collaboration', 'auto-contacts', 'summaries', 'dashboard']
797
+ }
798
+ });
667
799
  });
668
800
 
669
801
  server.on('error', (err) => {
670
802
  if (err.code === 'EADDRINUSE') {
671
- console.error(`[a2a] Port ${port} became unavailable (EADDRINUSE). Exiting.`);
803
+ logger.error('Bound port became unavailable (EADDRINUSE)', {
804
+ event: 'server_port_lost',
805
+ data: {
806
+ port
807
+ }
808
+ });
672
809
  process.exit(1);
673
810
  }
674
811
  throw err;