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.
- package/README.md +29 -1
- package/SKILL.md +18 -0
- package/bin/cli.js +123 -0
- package/docs/protocol.md +19 -0
- package/package.json +1 -1
- package/scripts/install-openclaw.js +26 -4
- package/src/dashboard/public/app.js +195 -7
- package/src/dashboard/public/index.html +51 -0
- package/src/dashboard/public/style.css +27 -0
- package/src/index.js +5 -0
- package/src/lib/call-monitor.js +68 -6
- package/src/lib/config.js +11 -1
- package/src/lib/conversations.js +13 -1
- package/src/lib/disclosure.js +11 -1
- package/src/lib/invite-host.js +17 -7
- package/src/lib/logger.js +566 -0
- package/src/lib/openclaw-integration.js +53 -6
- package/src/lib/runtime-adapter.js +133 -39
- package/src/lib/summarizer.js +21 -2
- package/src/lib/tokens.js +12 -1
- package/src/routes/a2a.js +170 -11
- package/src/routes/dashboard.js +41 -0
- package/src/server.js +169 -32
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|