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.
@@ -0,0 +1,566 @@
1
+ /**
2
+ * Structured logger with SQLite persistence and stdout output.
3
+ *
4
+ * - Writes structured entries to a local SQLite DB
5
+ * - Prints concise lines to stdout/stderr for operator visibility
6
+ * - Supports filtering and trace retrieval for dashboard APIs
7
+ */
8
+
9
+ const path = require('path');
10
+ const fs = require('fs');
11
+ const crypto = require('crypto');
12
+
13
+ function resolveDefaultConfigDir() {
14
+ return process.env.A2A_CONFIG_DIR ||
15
+ process.env.OPENCLAW_CONFIG_DIR ||
16
+ path.join(process.env.HOME || '/tmp', '.config', 'openclaw');
17
+ }
18
+
19
+ const LOG_DB_FILENAME = 'a2a-logs.db';
20
+ const LOG_LEVEL_ORDER = {
21
+ trace: 10,
22
+ debug: 20,
23
+ info: 30,
24
+ warn: 40,
25
+ error: 50
26
+ };
27
+
28
+ function envBool(name, fallback = false) {
29
+ const raw = process.env[name];
30
+ if (raw === undefined || raw === null || raw === '') {
31
+ return fallback;
32
+ }
33
+ const normalized = String(raw).trim().toLowerCase();
34
+ return !(normalized === '0' || normalized === 'false' || normalized === 'no');
35
+ }
36
+
37
+ function normalizeLevel(level) {
38
+ const normalized = String(level || '').trim().toLowerCase();
39
+ return LOG_LEVEL_ORDER[normalized] ? normalized : 'info';
40
+ }
41
+
42
+ function shouldLog(level, minimumLevel) {
43
+ const current = LOG_LEVEL_ORDER[normalizeLevel(level)] || LOG_LEVEL_ORDER.info;
44
+ const threshold = LOG_LEVEL_ORDER[normalizeLevel(minimumLevel)] || LOG_LEVEL_ORDER.info;
45
+ return current >= threshold;
46
+ }
47
+
48
+ function sanitizeText(value, maxLength = 1000) {
49
+ return String(value || '')
50
+ .replace(/\s+/g, ' ')
51
+ .trim()
52
+ .slice(0, maxLength);
53
+ }
54
+
55
+ function serializeError(error, options = {}) {
56
+ if (!error) return null;
57
+ if (error instanceof Error) {
58
+ const payload = {
59
+ name: error.name || 'Error',
60
+ message: sanitizeText(error.message || 'Unknown error', 2000)
61
+ };
62
+ if (error.code) payload.code = String(error.code);
63
+ if (error.cause) payload.cause = sanitizeText(String(error.cause), 400);
64
+ if (options.includeStacks && error.stack) {
65
+ payload.stack = String(error.stack).slice(0, 8000);
66
+ }
67
+ return payload;
68
+ }
69
+ if (typeof error === 'object') {
70
+ try {
71
+ return JSON.parse(JSON.stringify(error));
72
+ } catch (err) {
73
+ return { message: sanitizeText(String(error), 2000) };
74
+ }
75
+ }
76
+ return { message: sanitizeText(String(error), 2000) };
77
+ }
78
+
79
+ function normalizeContext(context = {}, options = {}) {
80
+ const entry = context && typeof context === 'object' ? { ...context } : {};
81
+ const errorDetails = serializeError(entry.error, { includeStacks: options.includeStacks });
82
+ const baseData = entry.data && typeof entry.data === 'object' ? { ...entry.data } : {};
83
+ if (errorDetails) {
84
+ baseData.error = errorDetails;
85
+ }
86
+ const hasData = Object.keys(baseData).length > 0;
87
+ const statusCodeRaw = entry.status_code ?? entry.statusCode;
88
+ const statusCode = Number.isFinite(Number(statusCodeRaw)) ? Number(statusCodeRaw) : null;
89
+ const explicitErrorCode = entry.error_code || entry.errorCode || null;
90
+ const inferredErrorCode = explicitErrorCode || (errorDetails && errorDetails.code ? errorDetails.code : null);
91
+ return {
92
+ event: entry.event ? sanitizeText(entry.event, 120) : null,
93
+ trace_id: entry.trace_id || entry.traceId || null,
94
+ conversation_id: entry.conversation_id || entry.conversationId || null,
95
+ token_id: entry.token_id || entry.tokenId || null,
96
+ request_id: entry.request_id || entry.requestId || null,
97
+ error_code: inferredErrorCode ? sanitizeText(inferredErrorCode, 120) : null,
98
+ hint: entry.hint ? sanitizeText(entry.hint, 500) : null,
99
+ status_code: statusCode,
100
+ component: entry.component ? sanitizeText(entry.component, 120) : null,
101
+ data: hasData ? baseData : null
102
+ };
103
+ }
104
+
105
+ class LogStore {
106
+ constructor(configDir = resolveDefaultConfigDir()) {
107
+ this.configDir = configDir;
108
+ this.dbPath = path.join(configDir, LOG_DB_FILENAME);
109
+ this.db = null;
110
+ this._dbError = null;
111
+ this._ensureDir();
112
+ }
113
+
114
+ _ensureDir() {
115
+ if (!fs.existsSync(this.configDir)) {
116
+ fs.mkdirSync(this.configDir, { recursive: true });
117
+ }
118
+ }
119
+
120
+ _initDb() {
121
+ if (this.db) return this.db;
122
+ if (this._dbError) return null;
123
+
124
+ try {
125
+ const Database = require('better-sqlite3');
126
+ this.db = new Database(this.dbPath);
127
+ try {
128
+ fs.chmodSync(this.dbPath, 0o600);
129
+ } catch (err) {
130
+ // best effort
131
+ }
132
+ this._migrate();
133
+ this._prepareStatements();
134
+ return this.db;
135
+ } catch (err) {
136
+ this._dbError = err.message || 'failed_to_initialize_log_db';
137
+ return null;
138
+ }
139
+ }
140
+
141
+ _migrate() {
142
+ this.db.exec(`
143
+ CREATE TABLE IF NOT EXISTS logs (
144
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
145
+ timestamp TEXT NOT NULL,
146
+ level TEXT NOT NULL,
147
+ component TEXT NOT NULL,
148
+ event TEXT,
149
+ message TEXT NOT NULL,
150
+ trace_id TEXT,
151
+ conversation_id TEXT,
152
+ token_id TEXT,
153
+ request_id TEXT,
154
+ error_code TEXT,
155
+ status_code INTEGER,
156
+ hint TEXT,
157
+ data TEXT
158
+ );
159
+
160
+ CREATE INDEX IF NOT EXISTS idx_logs_timestamp ON logs(timestamp DESC);
161
+ CREATE INDEX IF NOT EXISTS idx_logs_level ON logs(level);
162
+ CREATE INDEX IF NOT EXISTS idx_logs_component ON logs(component);
163
+ CREATE INDEX IF NOT EXISTS idx_logs_trace ON logs(trace_id);
164
+ CREATE INDEX IF NOT EXISTS idx_logs_conversation ON logs(conversation_id);
165
+ CREATE INDEX IF NOT EXISTS idx_logs_token ON logs(token_id);
166
+ CREATE INDEX IF NOT EXISTS idx_logs_error_code ON logs(error_code);
167
+ `);
168
+
169
+ const columns = this.db.prepare(`PRAGMA table_info(logs)`).all();
170
+ const hasErrorCode = columns.some(c => c.name === 'error_code');
171
+ const hasStatusCode = columns.some(c => c.name === 'status_code');
172
+ const hasHint = columns.some(c => c.name === 'hint');
173
+ if (!hasErrorCode) {
174
+ this.db.exec(`ALTER TABLE logs ADD COLUMN error_code TEXT`);
175
+ }
176
+ if (!hasStatusCode) {
177
+ this.db.exec(`ALTER TABLE logs ADD COLUMN status_code INTEGER`);
178
+ }
179
+ if (!hasHint) {
180
+ this.db.exec(`ALTER TABLE logs ADD COLUMN hint TEXT`);
181
+ }
182
+ }
183
+
184
+ _prepareStatements() {
185
+ this.insertStmt = this.db.prepare(`
186
+ INSERT INTO logs (
187
+ timestamp, level, component, event, message,
188
+ trace_id, conversation_id, token_id, request_id, error_code, status_code, hint, data
189
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
190
+ `);
191
+ }
192
+
193
+ isAvailable() {
194
+ return this._initDb() !== null;
195
+ }
196
+
197
+ getError() {
198
+ this._initDb();
199
+ return this._dbError;
200
+ }
201
+
202
+ write(entry) {
203
+ const db = this._initDb();
204
+ if (!db) return false;
205
+ const dataText = entry.data ? JSON.stringify(entry.data) : null;
206
+ try {
207
+ this.insertStmt.run(
208
+ entry.timestamp,
209
+ entry.level,
210
+ entry.component,
211
+ entry.event,
212
+ entry.message,
213
+ entry.trace_id,
214
+ entry.conversation_id,
215
+ entry.token_id,
216
+ entry.request_id,
217
+ entry.error_code,
218
+ entry.status_code,
219
+ entry.hint,
220
+ dataText
221
+ );
222
+ return true;
223
+ } catch (err) {
224
+ this._dbError = err.message || 'failed_to_write_log_entry';
225
+ return false;
226
+ }
227
+ }
228
+
229
+ list(options = {}) {
230
+ const db = this._initDb();
231
+ if (!db) return [];
232
+
233
+ const limit = Math.min(1000, Math.max(1, Number.parseInt(options.limit || '200', 10) || 200));
234
+ const where = [];
235
+ const params = [];
236
+
237
+ if (options.level) {
238
+ where.push('level = ?');
239
+ params.push(normalizeLevel(options.level));
240
+ }
241
+ if (options.component) {
242
+ where.push('component = ?');
243
+ params.push(String(options.component));
244
+ }
245
+ if (options.event) {
246
+ where.push('event = ?');
247
+ params.push(String(options.event));
248
+ }
249
+ if (options.errorCode) {
250
+ where.push('error_code = ?');
251
+ params.push(String(options.errorCode));
252
+ }
253
+ if (options.statusCode !== undefined && options.statusCode !== null && options.statusCode !== '') {
254
+ where.push('status_code = ?');
255
+ params.push(Number(options.statusCode));
256
+ }
257
+ if (options.traceId) {
258
+ where.push('trace_id = ?');
259
+ params.push(String(options.traceId));
260
+ }
261
+ if (options.conversationId) {
262
+ where.push('conversation_id = ?');
263
+ params.push(String(options.conversationId));
264
+ }
265
+ if (options.tokenId) {
266
+ where.push('token_id = ?');
267
+ params.push(String(options.tokenId));
268
+ }
269
+ if (options.from) {
270
+ where.push('timestamp >= ?');
271
+ params.push(String(options.from));
272
+ }
273
+ if (options.to) {
274
+ where.push('timestamp <= ?');
275
+ params.push(String(options.to));
276
+ }
277
+ if (options.search) {
278
+ where.push('(message LIKE ? OR data LIKE ?)');
279
+ const like = `%${String(options.search).replace(/%/g, '')}%`;
280
+ params.push(like, like);
281
+ }
282
+
283
+ const query = `
284
+ SELECT id, timestamp, level, component, event, message,
285
+ trace_id, conversation_id, token_id, request_id, error_code, status_code, hint, data
286
+ FROM logs
287
+ ${where.length ? `WHERE ${where.join(' AND ')}` : ''}
288
+ ORDER BY id DESC
289
+ LIMIT ?
290
+ `;
291
+ params.push(limit);
292
+
293
+ return db.prepare(query).all(...params).map(row => ({
294
+ id: row.id,
295
+ timestamp: row.timestamp,
296
+ level: row.level,
297
+ component: row.component,
298
+ event: row.event,
299
+ message: row.message,
300
+ trace_id: row.trace_id,
301
+ conversation_id: row.conversation_id,
302
+ token_id: row.token_id,
303
+ request_id: row.request_id,
304
+ error_code: row.error_code,
305
+ status_code: row.status_code,
306
+ hint: row.hint,
307
+ data: row.data ? safeJsonParse(row.data) : null
308
+ }));
309
+ }
310
+
311
+ getTrace(traceId, options = {}) {
312
+ const db = this._initDb();
313
+ if (!db || !traceId) return [];
314
+ const limit = Math.min(1000, Math.max(1, Number.parseInt(options.limit || '500', 10) || 500));
315
+ return db.prepare(`
316
+ SELECT id, timestamp, level, component, event, message,
317
+ trace_id, conversation_id, token_id, request_id, error_code, status_code, hint, data
318
+ FROM logs
319
+ WHERE trace_id = ?
320
+ ORDER BY id ASC
321
+ LIMIT ?
322
+ `).all(String(traceId), limit).map(row => ({
323
+ id: row.id,
324
+ timestamp: row.timestamp,
325
+ level: row.level,
326
+ component: row.component,
327
+ event: row.event,
328
+ message: row.message,
329
+ trace_id: row.trace_id,
330
+ conversation_id: row.conversation_id,
331
+ token_id: row.token_id,
332
+ request_id: row.request_id,
333
+ error_code: row.error_code,
334
+ status_code: row.status_code,
335
+ hint: row.hint,
336
+ data: row.data ? safeJsonParse(row.data) : null
337
+ }));
338
+ }
339
+
340
+ stats(options = {}) {
341
+ const db = this._initDb();
342
+ if (!db) {
343
+ return {
344
+ total: 0,
345
+ by_level: {},
346
+ by_component: {}
347
+ };
348
+ }
349
+
350
+ const where = [];
351
+ const params = [];
352
+ if (options.from) {
353
+ where.push('timestamp >= ?');
354
+ params.push(String(options.from));
355
+ }
356
+ if (options.to) {
357
+ where.push('timestamp <= ?');
358
+ params.push(String(options.to));
359
+ }
360
+ const clause = where.length ? `WHERE ${where.join(' AND ')}` : '';
361
+
362
+ const totalRow = db.prepare(`SELECT COUNT(*) AS count FROM logs ${clause}`).get(...params);
363
+ const levelRows = db.prepare(`
364
+ SELECT level, COUNT(*) AS count
365
+ FROM logs ${clause}
366
+ GROUP BY level
367
+ `).all(...params);
368
+ const componentRows = db.prepare(`
369
+ SELECT component, COUNT(*) AS count
370
+ FROM logs ${clause}
371
+ GROUP BY component
372
+ `).all(...params);
373
+
374
+ const byLevel = {};
375
+ for (const row of levelRows) {
376
+ byLevel[row.level] = row.count;
377
+ }
378
+ const byComponent = {};
379
+ for (const row of componentRows) {
380
+ byComponent[row.component] = row.count;
381
+ }
382
+
383
+ return {
384
+ total: totalRow?.count || 0,
385
+ by_level: byLevel,
386
+ by_component: byComponent
387
+ };
388
+ }
389
+
390
+ close() {
391
+ if (this.db) {
392
+ try {
393
+ this.db.close();
394
+ } catch (err) {
395
+ // best effort
396
+ }
397
+ this.db = null;
398
+ }
399
+ }
400
+ }
401
+
402
+ class Logger {
403
+ constructor(store, options = {}) {
404
+ this.store = store;
405
+ this.component = options.component || 'a2a';
406
+ this.bindings = options.bindings || {};
407
+ this.stdout = options.stdout !== false;
408
+ this.minLevel = normalizeLevel(options.minLevel || process.env.A2A_LOG_LEVEL || 'info');
409
+ this.includeStacks = options.includeStacks !== undefined
410
+ ? Boolean(options.includeStacks)
411
+ : envBool('A2A_LOG_STACKS', process.env.NODE_ENV !== 'production');
412
+ }
413
+
414
+ child(bindings = {}) {
415
+ return new Logger(this.store, {
416
+ component: bindings.component || this.component,
417
+ bindings: { ...this.bindings, ...bindings },
418
+ stdout: this.stdout,
419
+ minLevel: this.minLevel,
420
+ includeStacks: this.includeStacks
421
+ });
422
+ }
423
+
424
+ trace(message, context = {}) {
425
+ return this.log('trace', message, context);
426
+ }
427
+
428
+ debug(message, context = {}) {
429
+ return this.log('debug', message, context);
430
+ }
431
+
432
+ info(message, context = {}) {
433
+ return this.log('info', message, context);
434
+ }
435
+
436
+ warn(message, context = {}) {
437
+ return this.log('warn', message, context);
438
+ }
439
+
440
+ error(message, context = {}) {
441
+ return this.log('error', message, context);
442
+ }
443
+
444
+ log(level, message, context = {}) {
445
+ const normalizedLevel = normalizeLevel(level);
446
+ if (!shouldLog(normalizedLevel, this.minLevel)) {
447
+ return null;
448
+ }
449
+
450
+ const now = new Date().toISOString();
451
+ const mergedContext = {
452
+ ...(this.bindings || {}),
453
+ ...(context || {})
454
+ };
455
+ const normalized = normalizeContext(mergedContext, {
456
+ includeStacks: this.includeStacks
457
+ });
458
+
459
+ const entry = {
460
+ timestamp: now,
461
+ level: normalizedLevel,
462
+ component: normalized.component || this.component || 'a2a',
463
+ event: normalized.event,
464
+ message: sanitizeText(message, 2000) || '(empty)',
465
+ trace_id: normalized.trace_id ? String(normalized.trace_id) : null,
466
+ conversation_id: normalized.conversation_id ? String(normalized.conversation_id) : null,
467
+ token_id: normalized.token_id ? String(normalized.token_id) : null,
468
+ request_id: normalized.request_id ? String(normalized.request_id) : null,
469
+ error_code: normalized.error_code,
470
+ status_code: normalized.status_code,
471
+ hint: normalized.hint,
472
+ data: normalized.data
473
+ };
474
+
475
+ this.store.write(entry);
476
+ if (this.stdout) {
477
+ this._print(entry);
478
+ }
479
+ return entry;
480
+ }
481
+
482
+ list(options = {}) {
483
+ return this.store.list(options);
484
+ }
485
+
486
+ getTrace(traceId, options = {}) {
487
+ return this.store.getTrace(traceId, options);
488
+ }
489
+
490
+ stats(options = {}) {
491
+ return this.store.stats(options);
492
+ }
493
+
494
+ _print(entry) {
495
+ const parts = [
496
+ `[a2a]`,
497
+ entry.level.toUpperCase(),
498
+ `${entry.component}${entry.event ? `:${entry.event}` : ''}`,
499
+ entry.message
500
+ ];
501
+ if (entry.trace_id) parts.push(`trace=${entry.trace_id}`);
502
+ if (entry.conversation_id) parts.push(`conv=${entry.conversation_id}`);
503
+ if (entry.token_id) parts.push(`tok=${entry.token_id}`);
504
+ if (entry.request_id) parts.push(`req=${entry.request_id}`);
505
+ if (entry.error_code) parts.push(`code=${entry.error_code}`);
506
+ if (entry.status_code !== null && entry.status_code !== undefined) parts.push(`status=${entry.status_code}`);
507
+ if (entry.data && Object.keys(entry.data).length > 0) {
508
+ parts.push(`data=${JSON.stringify(entry.data)}`);
509
+ }
510
+ if (entry.hint) parts.push(`hint=${entry.hint}`);
511
+ const line = parts.join(' ');
512
+ if (entry.level === 'error' || entry.level === 'warn') {
513
+ console.error(line);
514
+ } else {
515
+ console.log(line);
516
+ }
517
+ }
518
+ }
519
+
520
+ function safeJsonParse(value) {
521
+ try {
522
+ return JSON.parse(value);
523
+ } catch (err) {
524
+ return null;
525
+ }
526
+ }
527
+
528
+ const storeCache = new Map();
529
+
530
+ function getStore(configDir) {
531
+ const resolved = path.resolve(configDir);
532
+ if (!storeCache.has(resolved)) {
533
+ storeCache.set(resolved, new LogStore(resolved));
534
+ }
535
+ return storeCache.get(resolved);
536
+ }
537
+
538
+ function createLogger(options = {}) {
539
+ const configDir = options.configDir || resolveDefaultConfigDir();
540
+ const store = getStore(configDir);
541
+ return new Logger(store, {
542
+ component: options.component || 'a2a',
543
+ bindings: options.bindings || {},
544
+ stdout: options.stdout !== false,
545
+ minLevel: options.minLevel,
546
+ includeStacks: options.includeStacks
547
+ });
548
+ }
549
+
550
+ function createTraceId(prefix = 'trace') {
551
+ return `${prefix}_${crypto.randomBytes(8).toString('hex')}`;
552
+ }
553
+
554
+ function closeAllLoggerStores() {
555
+ for (const store of storeCache.values()) {
556
+ store.close();
557
+ }
558
+ storeCache.clear();
559
+ }
560
+
561
+ module.exports = {
562
+ LOG_DB_FILENAME,
563
+ createLogger,
564
+ createTraceId,
565
+ closeAllLoggerStores
566
+ };
@@ -6,6 +6,9 @@
6
6
 
7
7
  const fs = require('fs');
8
8
  const path = require('path');
9
+ const { createLogger } = require('./logger');
10
+
11
+ const logger = createLogger({ component: 'a2a.openclaw-integration' });
9
12
 
10
13
  /**
11
14
  * Load owner context from OpenClaw workspace files
@@ -194,7 +197,16 @@ function createExecSummarizer(workspaceDir = process.cwd()) {
194
197
  notes: null
195
198
  };
196
199
  } catch (err) {
197
- console.error('[a2a] Exec summarizer failed:', err.message);
200
+ logger.error('Exec summarizer failed', {
201
+ event: 'exec_summary_failed',
202
+ trace_id: callerInfo?.trace_id || callerInfo?.traceId || null,
203
+ error: err,
204
+ error_code: 'OPENCLAW_EXEC_SUMMARIZER_FAILED',
205
+ hint: 'Verify `openclaw prompt --json` is available and returns valid JSON.',
206
+ data: {
207
+ workspace_dir: workspaceDir
208
+ }
209
+ });
198
210
  return { summary: null };
199
211
  } finally {
200
212
  // Cleanup
@@ -239,7 +251,16 @@ function createHttpSummarizer(endpoint = 'http://localhost:3000/api/summarize')
239
251
  });
240
252
 
241
253
  req.on('error', (err) => {
242
- console.error('[a2a] HTTP summarizer failed:', err.message);
254
+ logger.error('HTTP summarizer failed', {
255
+ event: 'http_summary_failed',
256
+ trace_id: callerInfo?.trace_id || callerInfo?.traceId || null,
257
+ error: err,
258
+ error_code: 'OPENCLAW_HTTP_SUMMARIZER_FAILED',
259
+ hint: 'Check summarizer endpoint reachability and response format.',
260
+ data: {
261
+ endpoint
262
+ }
263
+ });
243
264
  resolve({ summary: null });
244
265
  });
245
266
 
@@ -290,7 +311,16 @@ function createSessionSummarizer(gatewayUrl, gatewayToken) {
290
311
  });
291
312
 
292
313
  req.on('error', (err) => {
293
- console.error('[a2a] Session summarizer failed:', err.message);
314
+ logger.error('Session summarizer failed', {
315
+ event: 'session_summary_failed',
316
+ trace_id: callerInfo?.trace_id || callerInfo?.traceId || null,
317
+ error: err,
318
+ error_code: 'OPENCLAW_SESSION_SUMMARIZER_FAILED',
319
+ hint: 'Verify OpenClaw gateway URL/token and /api/internal/summarize availability.',
320
+ data: {
321
+ gateway_url: gatewayUrl || 'http://localhost:3000'
322
+ }
323
+ });
294
324
  resolve({ summary: null });
295
325
  });
296
326
 
@@ -308,7 +338,12 @@ function createAutoSummarizer(options = {}) {
308
338
 
309
339
  // If gateway URL provided, use session summarizer
310
340
  if (options.gatewayUrl || process.env.OPENCLAW_GATEWAY_URL) {
311
- console.log('[a2a] Using session summarizer');
341
+ logger.info('Using session summarizer', {
342
+ event: 'summary_mode_selected',
343
+ data: {
344
+ mode: 'session'
345
+ }
346
+ });
312
347
  return createSessionSummarizer(
313
348
  options.gatewayUrl || process.env.OPENCLAW_GATEWAY_URL,
314
349
  options.gatewayToken || process.env.OPENCLAW_TOKEN
@@ -317,12 +352,24 @@ function createAutoSummarizer(options = {}) {
317
352
 
318
353
  // If HTTP endpoint provided, use that
319
354
  if (options.summaryEndpoint) {
320
- console.log('[a2a] Using HTTP summarizer');
355
+ logger.info('Using HTTP summarizer', {
356
+ event: 'summary_mode_selected',
357
+ data: {
358
+ mode: 'http',
359
+ endpoint: options.summaryEndpoint
360
+ }
361
+ });
321
362
  return createHttpSummarizer(options.summaryEndpoint);
322
363
  }
323
364
 
324
365
  // Fall back to exec summarizer
325
- console.log('[a2a] Using exec summarizer');
366
+ logger.info('Using exec summarizer', {
367
+ event: 'summary_mode_selected',
368
+ data: {
369
+ mode: 'exec',
370
+ workspace_dir: workspaceDir
371
+ }
372
+ });
326
373
  return createExecSummarizer(workspaceDir);
327
374
  }
328
375