a2acalling 0.6.72 → 0.6.73
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/.a2a-manifest.json +2 -2
- package/ARCHITECTURE.md +10 -0
- package/CONVENTIONS.md +13 -0
- package/package.json +1 -1
- package/src/lib/runtime-adapter.js +60 -6
package/.a2a-manifest.json
CHANGED
package/ARCHITECTURE.md
CHANGED
|
@@ -55,6 +55,16 @@ A2A Calling enables agent-to-agent communication across OpenClaw instances. Agen
|
|
|
55
55
|
- **Config**: JSON at `~/.config/openclaw/a2a-config.json`
|
|
56
56
|
- **Disclosure**: JSON at `~/.config/openclaw/a2a-disclosure.json`
|
|
57
57
|
|
|
58
|
+
## Database Lifecycle Management (A2A-55)
|
|
59
|
+
|
|
60
|
+
All three data stores have automatic retention cleanup that runs on server startup:
|
|
61
|
+
|
|
62
|
+
- **Conversations**: `ConversationStore.pruneOld()` compresses messages 7+ days old, deletes concluded/timeout conversations 90+ days old. Active conversations are never deleted.
|
|
63
|
+
- **Logs**: `LogStore.pruneOld()` deletes entries 30+ days old. Auto-prune triggers on every 1000th write (best effort). `pruneAllLoggerStores()` iterates all cached stores.
|
|
64
|
+
- **Tokens**: `TokenStore.cleanupExpired()` removes tokens expired >1 hour (grace for in-flight calls) and revoked tokens >30 days old.
|
|
65
|
+
|
|
66
|
+
All retention periods are configurable via `a2a-config.json` `retention` section. SQLite VACUUM runs only after >100 rows deleted. All cleanup is best-effort — failures are logged but never prevent server startup.
|
|
67
|
+
|
|
58
68
|
## Permission System
|
|
59
69
|
|
|
60
70
|
Three tiers with escalating capabilities:
|
package/CONVENTIONS.md
CHANGED
|
@@ -113,6 +113,19 @@ Tokens have a tier (`public`, `friends`, `family`) and a disclosure level (`publ
|
|
|
113
113
|
- Admin token comparison uses `timingSafeTokenEqual()` from `src/routes/a2a.js` — do NOT use `!==` for secret comparison
|
|
114
114
|
- Query parameter parsing follows the dashboard.js pattern: `Math.min(max, Math.max(min, Number.parseInt(String(value), 10) || defaultValue))`
|
|
115
115
|
|
|
116
|
+
## Retention & Cleanup (A2A-55)
|
|
117
|
+
|
|
118
|
+
All data stores implement retention cleanup following the `dashboard-events.js` auto-prune pattern:
|
|
119
|
+
|
|
120
|
+
- **Cleanup component**: Use `createLogger({ component: 'a2a.cleanup' })` for all retention logging
|
|
121
|
+
- **Best effort**: Prune failures are caught and logged as warnings — never crash the server
|
|
122
|
+
- **VACUUM threshold**: Only run SQLite VACUUM after >100 rows deleted (costly I/O)
|
|
123
|
+
- **Auto-prune**: Logger store prunes on every 1000th `write()` call (counter-based, like dashboard-events.js)
|
|
124
|
+
- **Recursion safety**: Logger `pruneOld()` uses `_pruning` flag to prevent auto-prune during explicit prune
|
|
125
|
+
- **Server startup**: `src/server.js` calls all three retention mechanisms after `writePidFile()`, before `updateManager.start()`
|
|
126
|
+
- **Config defaults**: `A2AConfig.getRetention()` merges partial config with defaults — never writes defaults to disk
|
|
127
|
+
- **Token grace period**: Expired tokens are kept for 1 hour after expiry (in-flight call protection)
|
|
128
|
+
|
|
116
129
|
## Anti-Patterns
|
|
117
130
|
|
|
118
131
|
- Do NOT use `console.log` — use the structured logger
|
package/package.json
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
* - openclaw: uses `openclaw` CLI for turn handling, summaries, notifications
|
|
6
6
|
* - claude: uses `claude` CLI as a real LLM subagent for conversations
|
|
7
7
|
*
|
|
8
|
+
* - test: minimal runtime for CI/headless — echoes messages or spawns A2A_AGENT_COMMAND
|
|
9
|
+
*
|
|
8
10
|
* Selection:
|
|
9
|
-
* - A2A_RUNTIME=openclaw|claude|auto (default: auto)
|
|
11
|
+
* - A2A_RUNTIME=openclaw|claude|test|auto (default: auto)
|
|
10
12
|
* - auto picks openclaw → claude → error (no supported CLI)
|
|
11
13
|
*/
|
|
12
14
|
|
|
@@ -43,6 +45,18 @@ function resolveRuntimeMode() {
|
|
|
43
45
|
const hasOpenClaw = commandExists('openclaw');
|
|
44
46
|
const hasClaude = commandExists('claude');
|
|
45
47
|
|
|
48
|
+
// A2A-66: test runtime for CI/headless environments — minimal runTurn with
|
|
49
|
+
// optional A2A_AGENT_COMMAND bridge support.
|
|
50
|
+
if (requested === 'test') {
|
|
51
|
+
return {
|
|
52
|
+
mode: 'test',
|
|
53
|
+
requested,
|
|
54
|
+
hasOpenClaw,
|
|
55
|
+
hasClaude,
|
|
56
|
+
reason: 'A2A_RUNTIME=test'
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
46
60
|
if (requested === 'generic') {
|
|
47
61
|
return {
|
|
48
62
|
mode: 'none',
|
|
@@ -372,6 +386,39 @@ function createRuntimeAdapter(options = {}) {
|
|
|
372
386
|
}
|
|
373
387
|
}
|
|
374
388
|
|
|
389
|
+
// A2A-66: test runtime — spawn A2A_AGENT_COMMAND if set, otherwise echo.
|
|
390
|
+
// Uses shell: true so the command string is parsed by the shell (supports
|
|
391
|
+
// quoted args, paths with spaces, pipes, etc.).
|
|
392
|
+
if (modeInfo.mode === 'test') {
|
|
393
|
+
const agentCommand = process.env.A2A_AGENT_COMMAND;
|
|
394
|
+
if (agentCommand) {
|
|
395
|
+
const payload = JSON.stringify({ message, caller, context });
|
|
396
|
+
const result = spawnSync(agentCommand, {
|
|
397
|
+
input: payload,
|
|
398
|
+
encoding: 'utf8',
|
|
399
|
+
shell: true,
|
|
400
|
+
timeout: (timeoutMs || 65000) + 5000,
|
|
401
|
+
maxBuffer: 1024 * 1024,
|
|
402
|
+
cwd: workspaceDir,
|
|
403
|
+
env: process.env
|
|
404
|
+
});
|
|
405
|
+
if (result.error) {
|
|
406
|
+
throw result.error;
|
|
407
|
+
}
|
|
408
|
+
// A2A-66: check exit code — non-zero means the bridge command failed.
|
|
409
|
+
if (result.status !== 0) {
|
|
410
|
+
const stderr = String(result.stderr || '').trim();
|
|
411
|
+
throw new Error(
|
|
412
|
+
`A2A_AGENT_COMMAND exited with code ${result.status}` +
|
|
413
|
+
(stderr ? `: ${stderr.slice(0, 200)}` : '')
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
return String(result.stdout || '').trim() || '[test-runtime] Empty command output';
|
|
417
|
+
}
|
|
418
|
+
const snippet = cleanText(message || prompt || '', 120);
|
|
419
|
+
return `[test-runtime] Echo: ${snippet}`;
|
|
420
|
+
}
|
|
421
|
+
|
|
375
422
|
if (modeInfo.mode !== 'openclaw') {
|
|
376
423
|
throw new Error(
|
|
377
424
|
`No supported A2A runtime available (mode=${modeInfo.mode}). ` +
|
|
@@ -457,6 +504,12 @@ function createRuntimeAdapter(options = {}) {
|
|
|
457
504
|
throw new Error('Claude summary returned empty result');
|
|
458
505
|
}
|
|
459
506
|
|
|
507
|
+
// A2A-66: test runtime — return canned summary.
|
|
508
|
+
if (modeInfo.mode === 'test') {
|
|
509
|
+
const text = 'Test conversation concluded.';
|
|
510
|
+
return { summary: text, ownerSummary: text };
|
|
511
|
+
}
|
|
512
|
+
|
|
460
513
|
if (modeInfo.mode !== 'openclaw') {
|
|
461
514
|
throw new Error(
|
|
462
515
|
`No supported A2A runtime available for summarization (mode=${modeInfo.mode}). ` +
|
|
@@ -526,14 +579,15 @@ function createRuntimeAdapter(options = {}) {
|
|
|
526
579
|
data: { level }
|
|
527
580
|
});
|
|
528
581
|
|
|
529
|
-
if (modeInfo.mode === 'claude') {
|
|
530
|
-
// Claude mode: notifications are a no-op (no notification transport available)
|
|
531
|
-
logger.debug('Notification skipped (
|
|
532
|
-
event: '
|
|
582
|
+
if (modeInfo.mode === 'claude' || modeInfo.mode === 'test') {
|
|
583
|
+
// Claude/test mode: notifications are a no-op (no notification transport available)
|
|
584
|
+
logger.debug('Notification skipped (no notification transport in this mode)', {
|
|
585
|
+
event: 'notify_skipped',
|
|
533
586
|
traceId,
|
|
534
587
|
requestId,
|
|
535
588
|
conversationId,
|
|
536
|
-
tokenId: token?.id
|
|
589
|
+
tokenId: token?.id,
|
|
590
|
+
data: { mode: modeInfo.mode }
|
|
537
591
|
});
|
|
538
592
|
return;
|
|
539
593
|
}
|