a2acalling 0.6.50 → 0.6.52
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/bin/cli.js +172 -0
- package/native/macos/index.html +5 -10
- package/native/macos/src-tauri/Cargo.lock +5875 -0
- package/native/macos/src-tauri/Cargo.toml +3 -2
- package/native/macos/src-tauri/icons/128x128.png +0 -0
- package/native/macos/src-tauri/icons/128x128@2x.png +0 -0
- package/native/macos/src-tauri/icons/32x32.png +0 -0
- package/native/macos/src-tauri/icons/tray-connected.png +0 -0
- package/native/macos/src-tauri/icons/tray-disconnected.png +0 -0
- package/native/macos/src-tauri/src/health.rs +2 -3
- package/native/macos/src-tauri/src/lib.rs +2 -2
- package/native/macos/src-tauri/src/notifications.rs +148 -69
- package/native/macos/src-tauri/tauri.conf.json +2 -9
- package/package.json +1 -1
- package/scripts/postinstall.js +0 -49
- package/src/dashboard/public/app.js +147 -1
- package/src/lib/claude-subagent.js +222 -85
- package/src/lib/conversation-driver.js +8 -1
- package/src/lib/conversations.js +55 -1
- package/src/lib/dashboard-events.js +205 -0
- package/src/lib/runtime-adapter.js +46 -17
- package/src/routes/a2a.js +28 -4
- package/src/routes/dashboard.js +114 -1
- package/src/server.js +6 -0
|
@@ -4,7 +4,14 @@
|
|
|
4
4
|
* Spawns `claude` CLI processes for real LLM-powered A2A conversations
|
|
5
5
|
* as an alternative to OpenClaw for A2A conversations.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* Design decision (A2A-29):
|
|
8
|
+
* We intentionally run Claude turns in stateless one-shot mode instead of `--resume`.
|
|
9
|
+
* In production we observed intermittent hangs during nested Claude startup/restore.
|
|
10
|
+
* Stateless calls cost more tokens but are operationally safer under load.
|
|
11
|
+
*
|
|
12
|
+
* Permissioning is still enforced:
|
|
13
|
+
* `--allowedTools` is derived per request from token capabilities + allowed topics.
|
|
14
|
+
* We do not hardcode one universal allowlist anymore.
|
|
8
15
|
*/
|
|
9
16
|
|
|
10
17
|
const { execSync, spawn } = require('child_process');
|
|
@@ -14,6 +21,8 @@ const { HARD_FALLBACK_TURN_TIMEOUT_MS } = require('./turn-timeout');
|
|
|
14
21
|
const logger = createLogger({ component: 'a2a.claude-subagent' });
|
|
15
22
|
|
|
16
23
|
const A2A_RESPONSE_REGEX = /<a2a_response>\s*([\s\S]*?)\s*<\/a2a_response>/i;
|
|
24
|
+
const DEFAULT_CLAUDE_MODEL = 'claude-sonnet-4-5-20250929';
|
|
25
|
+
const LEGACY_DEFAULT_TOOLS = ['Bash(readonly)', 'Read', 'Grep', 'Glob', 'WebSearch', 'WebFetch'];
|
|
17
26
|
|
|
18
27
|
/**
|
|
19
28
|
* Check if `claude` CLI is available in PATH.
|
|
@@ -289,11 +298,144 @@ function extractResultFromJson(stdout) {
|
|
|
289
298
|
}
|
|
290
299
|
}
|
|
291
300
|
|
|
301
|
+
function normalizePermissionList(values) {
|
|
302
|
+
if (!Array.isArray(values)) return [];
|
|
303
|
+
return values
|
|
304
|
+
.map(v => String(v || '').trim().toLowerCase())
|
|
305
|
+
.filter(Boolean);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function hasPermissionMatch(values, key) {
|
|
309
|
+
if (!key) return false;
|
|
310
|
+
return values.some(value => value === key || value.startsWith(`${key}.`));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Resolve Claude tool allowlist from token-derived permissions.
|
|
315
|
+
*
|
|
316
|
+
* Notes:
|
|
317
|
+
* - We preserve legacy behavior when no permission context is provided, because
|
|
318
|
+
* outbound CLI flows may run without token metadata.
|
|
319
|
+
* - When permissions are present, we derive tools deterministically so runtime
|
|
320
|
+
* allowlists remain variable and auditable per token.
|
|
321
|
+
*/
|
|
322
|
+
function resolveClaudeAllowedTools({ capabilities = [], allowedTopics = [] } = {}) {
|
|
323
|
+
const normalizedCaps = normalizePermissionList(capabilities);
|
|
324
|
+
const normalizedTopics = normalizePermissionList(allowedTopics);
|
|
325
|
+
const hasPermissionContext = normalizedCaps.length > 0 || normalizedTopics.length > 0;
|
|
326
|
+
|
|
327
|
+
if (!hasPermissionContext) {
|
|
328
|
+
return [...LEGACY_DEFAULT_TOOLS];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const hasContextRead = hasPermissionMatch(normalizedCaps, 'context-read')
|
|
332
|
+
|| normalizedTopics.includes('chat');
|
|
333
|
+
const hasSearch = hasPermissionMatch(normalizedCaps, 'search')
|
|
334
|
+
|| normalizedTopics.includes('search');
|
|
335
|
+
const hasToolsRead = hasPermissionMatch(normalizedCaps, 'tools')
|
|
336
|
+
|| hasPermissionMatch(normalizedCaps, 'tools-read')
|
|
337
|
+
|| normalizedTopics.includes('tools');
|
|
338
|
+
const hasToolsWrite = hasPermissionMatch(normalizedCaps, 'tools-write')
|
|
339
|
+
|| hasPermissionMatch(normalizedCaps, 'tools.write')
|
|
340
|
+
|| normalizedTopics.includes('tools-write')
|
|
341
|
+
|| normalizedTopics.includes('tools.write');
|
|
342
|
+
|
|
343
|
+
const tools = [];
|
|
344
|
+
|
|
345
|
+
// Keep read-only introspection available for context-aware tiers.
|
|
346
|
+
if (hasContextRead || hasSearch || hasToolsRead || hasToolsWrite) {
|
|
347
|
+
tools.push('Read', 'Grep', 'Glob');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Web tools are explicitly tied to search-style permissions.
|
|
351
|
+
if (hasSearch) {
|
|
352
|
+
tools.push('WebSearch', 'WebFetch');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Shell access is gated behind tool permissions, with explicit writable opt-in.
|
|
356
|
+
if (hasToolsWrite) {
|
|
357
|
+
tools.unshift('Bash');
|
|
358
|
+
} else if (hasToolsRead) {
|
|
359
|
+
tools.unshift('Bash(readonly)');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Fail closed to read-only file inspection if metadata is custom/unknown.
|
|
363
|
+
if (tools.length === 0) {
|
|
364
|
+
return ['Read', 'Grep', 'Glob'];
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return tools;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function buildClaudeToolArg(allowedTools) {
|
|
371
|
+
return Array.isArray(allowedTools) ? allowedTools.join(' ').trim() : '';
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function parseSummaryPayload(resultText) {
|
|
375
|
+
const text = String(resultText || '').trim();
|
|
376
|
+
if (!text) return null;
|
|
377
|
+
|
|
378
|
+
// Backwards-compatible: older prompts wrapped JSON in <a2a_response>.
|
|
379
|
+
const tagged = text.match(A2A_RESPONSE_REGEX);
|
|
380
|
+
if (tagged && tagged[1]) {
|
|
381
|
+
try {
|
|
382
|
+
return JSON.parse(tagged[1].trim());
|
|
383
|
+
} catch (err) {
|
|
384
|
+
logger.warn('Failed to parse tagged summary JSON', {
|
|
385
|
+
event: 'subagent_summary_tag_parse_failed',
|
|
386
|
+
error: err
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Preferred path for unified summary prompt: direct JSON object.
|
|
392
|
+
try {
|
|
393
|
+
const parsed = JSON.parse(text);
|
|
394
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
395
|
+
return parsed;
|
|
396
|
+
}
|
|
397
|
+
} catch (err) {
|
|
398
|
+
logger.debug('Summary result is not direct JSON; falling back to plain text summary', {
|
|
399
|
+
event: 'subagent_summary_raw_fallback',
|
|
400
|
+
data: { output_length: text.length }
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function summarizeFromPayload(payload, fallbackText) {
|
|
408
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Native A2A summary payload shape.
|
|
413
|
+
if (typeof payload.summary === 'string' || typeof payload.ownerSummary === 'string') {
|
|
414
|
+
return {
|
|
415
|
+
summary: payload.summary || payload.message || fallbackText || '',
|
|
416
|
+
ownerSummary: payload.ownerSummary || payload.summary || payload.message || fallbackText || '',
|
|
417
|
+
actionItems: Array.isArray(payload.actionItems) ? payload.actionItems : [],
|
|
418
|
+
flags: Array.isArray(payload.flags) ? payload.flags : []
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Unified summary schema shape (headline/assessment/nextSteps).
|
|
423
|
+
if (typeof payload.headline === 'string') {
|
|
424
|
+
return {
|
|
425
|
+
summary: payload.headline,
|
|
426
|
+
ownerSummary: typeof payload.assessment === 'string' ? payload.assessment : payload.headline,
|
|
427
|
+
actionItems: Array.isArray(payload.nextSteps) ? payload.nextSteps : [],
|
|
428
|
+
flags: []
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
|
|
292
435
|
/**
|
|
293
436
|
* Run a single turn of the Claude subagent.
|
|
294
437
|
*
|
|
295
438
|
* @param {Object} options
|
|
296
|
-
* @param {string} options.sessionId - Conversation session ID (used for --resume on turn 2+)
|
|
297
439
|
* @param {string} options.systemPrompt - System prompt (used on turn 1 only)
|
|
298
440
|
* @param {string} options.turnMessage - The inbound message from the remote agent
|
|
299
441
|
* @param {number} options.turn - Current turn number (1-based)
|
|
@@ -303,12 +445,14 @@ function extractResultFromJson(stdout) {
|
|
|
303
445
|
* @param {Array} options.activeThreads - Active conversation threads
|
|
304
446
|
* @param {Array} options.candidateCollaborations - Candidate collaboration ideas
|
|
305
447
|
* @param {boolean} options.closeSignal - Whether close has been signaled
|
|
448
|
+
* @param {Array<string>} [options.capabilities] - Token capabilities (permission source of truth)
|
|
449
|
+
* @param {Array<string>} [options.allowedTopics] - Token allowed topics (permission source of truth)
|
|
450
|
+
* @param {function} [options.spawnFn] - Injectable process runner for tests
|
|
306
451
|
* @param {number} [options.timeoutMs=300000] - Timeout in milliseconds
|
|
307
|
-
* @returns {Promise<{ message: string, statePatch: object|null, flags: array
|
|
452
|
+
* @returns {Promise<{ message: string, statePatch: object|null, flags: array }>}
|
|
308
453
|
*/
|
|
309
454
|
async function runClaudeTurn(options) {
|
|
310
455
|
const {
|
|
311
|
-
sessionId,
|
|
312
456
|
systemPrompt,
|
|
313
457
|
turnMessage,
|
|
314
458
|
turn = 1,
|
|
@@ -318,6 +462,9 @@ async function runClaudeTurn(options) {
|
|
|
318
462
|
activeThreads = [],
|
|
319
463
|
candidateCollaborations = [],
|
|
320
464
|
closeSignal = false,
|
|
465
|
+
capabilities = [],
|
|
466
|
+
allowedTopics = [],
|
|
467
|
+
spawnFn = spawnClaude,
|
|
321
468
|
timeoutMs = HARD_FALLBACK_TURN_TIMEOUT_MS
|
|
322
469
|
} = options;
|
|
323
470
|
|
|
@@ -333,29 +480,21 @@ async function runClaudeTurn(options) {
|
|
|
333
480
|
});
|
|
334
481
|
|
|
335
482
|
const startAt = Date.now();
|
|
336
|
-
const allowedTools =
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
} else {
|
|
350
|
-
// Subsequent turns: resume existing session
|
|
351
|
-
args = [
|
|
352
|
-
'-p',
|
|
353
|
-
'--output-format', 'json',
|
|
354
|
-
'--resume', sessionId,
|
|
355
|
-
'--allowedTools', allowedTools,
|
|
356
|
-
turnPrompt
|
|
357
|
-
];
|
|
483
|
+
const allowedTools = resolveClaudeAllowedTools({ capabilities, allowedTopics });
|
|
484
|
+
const allowedToolsArg = buildClaudeToolArg(allowedTools);
|
|
485
|
+
const args = [
|
|
486
|
+
'-p',
|
|
487
|
+
'--output-format', 'json',
|
|
488
|
+
'--system-prompt', systemPrompt,
|
|
489
|
+
'--model', DEFAULT_CLAUDE_MODEL
|
|
490
|
+
];
|
|
491
|
+
|
|
492
|
+
// We always provide --allowedTools explicitly so token permissioning stays
|
|
493
|
+
// enforced in Claude mode even after moving to stateless turns.
|
|
494
|
+
if (allowedToolsArg) {
|
|
495
|
+
args.push('--allowedTools', allowedToolsArg);
|
|
358
496
|
}
|
|
497
|
+
args.push(turnPrompt);
|
|
359
498
|
|
|
360
499
|
logger.debug('Spawning Claude subagent turn', {
|
|
361
500
|
event: 'subagent_turn_start',
|
|
@@ -363,13 +502,14 @@ async function runClaudeTurn(options) {
|
|
|
363
502
|
turn,
|
|
364
503
|
max_turns: maxTurns,
|
|
365
504
|
phase,
|
|
366
|
-
|
|
505
|
+
is_stateless: true,
|
|
506
|
+
allowed_tools: allowedTools,
|
|
367
507
|
timeout_ms: timeoutMs
|
|
368
508
|
}
|
|
369
509
|
});
|
|
370
510
|
|
|
371
|
-
const { stdout } = await
|
|
372
|
-
const { result
|
|
511
|
+
const { stdout } = await spawnFn(args, timeoutMs);
|
|
512
|
+
const { result } = extractResultFromJson(stdout);
|
|
373
513
|
const parsed = parseSubagentResponse(result);
|
|
374
514
|
|
|
375
515
|
logger.debug('Claude subagent turn completed', {
|
|
@@ -379,91 +519,87 @@ async function runClaudeTurn(options) {
|
|
|
379
519
|
duration_ms: Date.now() - startAt,
|
|
380
520
|
message_length: parsed.message.length,
|
|
381
521
|
has_state_patch: Boolean(parsed.statePatch),
|
|
382
|
-
flag_count: parsed.flags.length
|
|
383
|
-
session_id: newSessionId || sessionId
|
|
522
|
+
flag_count: parsed.flags.length
|
|
384
523
|
}
|
|
385
524
|
});
|
|
386
525
|
|
|
387
526
|
return {
|
|
388
527
|
message: parsed.message,
|
|
389
528
|
statePatch: parsed.statePatch,
|
|
390
|
-
flags: parsed.flags
|
|
391
|
-
sessionId: newSessionId || sessionId
|
|
529
|
+
flags: parsed.flags
|
|
392
530
|
};
|
|
393
531
|
}
|
|
394
532
|
|
|
395
533
|
/**
|
|
396
|
-
* Run a summary turn
|
|
534
|
+
* Run a summary turn in stateless Claude mode.
|
|
397
535
|
*
|
|
398
|
-
* @param {
|
|
399
|
-
* @param {string}
|
|
536
|
+
* @param {Object} options
|
|
537
|
+
* @param {string} options.prompt - Unified summary prompt
|
|
538
|
+
* @param {string} [options.reason] - Why the conversation is ending
|
|
539
|
+
* @param {Array<string>} [options.capabilities] - Token capabilities for summary turn tooling
|
|
540
|
+
* @param {Array<string>} [options.allowedTopics] - Token allowed topics for summary turn tooling
|
|
541
|
+
* @param {function} [options.spawnFn] - Injectable process runner for tests
|
|
400
542
|
* @param {number} [timeoutMs=300000] - Timeout in milliseconds
|
|
401
543
|
* @returns {Promise<{ summary: string, ownerSummary: string, actionItems: array, flags: array }>}
|
|
402
544
|
*/
|
|
403
|
-
async function runClaudeSummary(
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
545
|
+
async function runClaudeSummary(options = {}) {
|
|
546
|
+
const {
|
|
547
|
+
prompt,
|
|
548
|
+
reason,
|
|
549
|
+
capabilities = [],
|
|
550
|
+
allowedTopics = [],
|
|
551
|
+
spawnFn = spawnClaude,
|
|
552
|
+
timeoutMs = HARD_FALLBACK_TURN_TIMEOUT_MS
|
|
553
|
+
} = options;
|
|
409
554
|
|
|
410
|
-
|
|
555
|
+
const summaryPrompt = String(prompt || '').trim();
|
|
556
|
+
if (!summaryPrompt) {
|
|
557
|
+
throw new Error('Cannot summarize without a prompt');
|
|
558
|
+
}
|
|
411
559
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
"message": "Brief 1-2 sentence summary of the conversation.",
|
|
415
|
-
"statePatch": {"phase": "close", "closeSignal": true},
|
|
416
|
-
"flags": [],
|
|
417
|
-
"summary": "Detailed summary for the conversation record.",
|
|
418
|
-
"ownerSummary": "Summary written for the owner highlighting key findings and opportunities.",
|
|
419
|
-
"actionItems": ["Specific follow-up item 1", "Specific follow-up item 2"]
|
|
420
|
-
}
|
|
421
|
-
</a2a_response>`;
|
|
560
|
+
const allowedTools = resolveClaudeAllowedTools({ capabilities, allowedTopics });
|
|
561
|
+
const allowedToolsArg = buildClaudeToolArg(allowedTools);
|
|
422
562
|
|
|
423
563
|
const args = [
|
|
424
564
|
'-p',
|
|
425
565
|
'--output-format', 'json',
|
|
426
|
-
'--
|
|
427
|
-
summaryPrompt
|
|
566
|
+
'--model', DEFAULT_CLAUDE_MODEL,
|
|
428
567
|
];
|
|
429
568
|
|
|
569
|
+
if (allowedToolsArg) {
|
|
570
|
+
args.push('--allowedTools', allowedToolsArg);
|
|
571
|
+
}
|
|
572
|
+
args.push(
|
|
573
|
+
'--append-system-prompt',
|
|
574
|
+
`Conversation summary mode. Reason: ${reason || 'conversation ended'}. Return only structured summary JSON.`,
|
|
575
|
+
summaryPrompt
|
|
576
|
+
);
|
|
577
|
+
|
|
430
578
|
const startAt = Date.now();
|
|
431
579
|
|
|
432
580
|
logger.debug('Spawning Claude summary', {
|
|
433
581
|
event: 'subagent_summary_start',
|
|
434
|
-
data: {
|
|
582
|
+
data: {
|
|
583
|
+
reason: reason || 'conversation ended',
|
|
584
|
+
allowed_tools: allowedTools
|
|
585
|
+
}
|
|
435
586
|
});
|
|
436
587
|
|
|
437
|
-
const { stdout } = await
|
|
588
|
+
const { stdout } = await spawnFn(args, timeoutMs);
|
|
438
589
|
const { result } = extractResultFromJson(stdout);
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
if (
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
}
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
return {
|
|
456
|
-
summary: parsed.summary || parsed.message || result.replace(A2A_RESPONSE_REGEX, '').trim(),
|
|
457
|
-
ownerSummary: parsed.ownerSummary || parsed.summary || parsed.message || '',
|
|
458
|
-
actionItems: Array.isArray(parsed.actionItems) ? parsed.actionItems : [],
|
|
459
|
-
flags: Array.isArray(parsed.flags) ? parsed.flags : []
|
|
460
|
-
};
|
|
461
|
-
} catch (err) {
|
|
462
|
-
logger.warn('Failed to parse summary JSON', {
|
|
463
|
-
event: 'subagent_summary_parse_failed',
|
|
464
|
-
error: err
|
|
465
|
-
});
|
|
466
|
-
}
|
|
590
|
+
const summaryPayload = parseSummaryPayload(result);
|
|
591
|
+
const parsedSummary = summarizeFromPayload(summaryPayload, result.trim());
|
|
592
|
+
|
|
593
|
+
if (parsedSummary) {
|
|
594
|
+
logger.debug('Claude summary completed', {
|
|
595
|
+
event: 'subagent_summary_complete',
|
|
596
|
+
data: {
|
|
597
|
+
duration_ms: Date.now() - startAt,
|
|
598
|
+
has_summary: Boolean(parsedSummary.summary),
|
|
599
|
+
action_item_count: parsedSummary.actionItems.length
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
return parsedSummary;
|
|
467
603
|
}
|
|
468
604
|
|
|
469
605
|
// Fallback: use raw text as summary
|
|
@@ -480,6 +616,7 @@ module.exports = {
|
|
|
480
616
|
isClaudeAvailable,
|
|
481
617
|
buildSubagentSystemPrompt,
|
|
482
618
|
buildTurnPrompt,
|
|
619
|
+
resolveClaudeAllowedTools,
|
|
483
620
|
runClaudeTurn,
|
|
484
621
|
runClaudeSummary,
|
|
485
622
|
parseSubagentResponse
|
|
@@ -128,6 +128,10 @@ class ConversationDriver {
|
|
|
128
128
|
this.maxTurns = options.maxTurns || 30;
|
|
129
129
|
this.onTurn = options.onTurn || null;
|
|
130
130
|
this.tier = options.tier || 'public';
|
|
131
|
+
// Optional permission envelope for runtimes (primarily Claude mode).
|
|
132
|
+
// If provided by caller, this keeps tool allowlists variable per token/profile.
|
|
133
|
+
this.capabilities = Array.isArray(options.capabilities) ? options.capabilities : [];
|
|
134
|
+
this.allowedTopics = Array.isArray(options.allowedTopics) ? options.allowedTopics : [];
|
|
131
135
|
this.summarizer = options.summarizer || null;
|
|
132
136
|
this.ownerContext = options.ownerContext || {};
|
|
133
137
|
this.claudeMode = options.runtime?.mode === 'claude';
|
|
@@ -407,7 +411,10 @@ class ConversationDriver {
|
|
|
407
411
|
tier: this.tier,
|
|
408
412
|
ownerName: this.agentContext.owner,
|
|
409
413
|
agentName: this.agentContext.name,
|
|
410
|
-
roleContext: 'You initiated this call.'
|
|
414
|
+
roleContext: 'You initiated this call.',
|
|
415
|
+
capabilities: this.capabilities,
|
|
416
|
+
allowedTopics: this.allowedTopics,
|
|
417
|
+
allowed_topics: this.allowedTopics
|
|
411
418
|
};
|
|
412
419
|
if (this.claudeMode) {
|
|
413
420
|
contextPayload.turnCount = turn + 1;
|
package/src/lib/conversations.js
CHANGED
|
@@ -18,9 +18,10 @@ const DB_FILENAME = 'a2a-conversations.db';
|
|
|
18
18
|
const logger = createLogger({ component: 'a2a.conversations' });
|
|
19
19
|
|
|
20
20
|
class ConversationStore {
|
|
21
|
-
constructor(configDir = DEFAULT_CONFIG_DIR) {
|
|
21
|
+
constructor(configDir = DEFAULT_CONFIG_DIR, options = {}) {
|
|
22
22
|
this.configDir = configDir;
|
|
23
23
|
this.dbPath = path.join(configDir, DB_FILENAME);
|
|
24
|
+
this.eventStore = options.eventStore || null;
|
|
24
25
|
this.db = null;
|
|
25
26
|
this._ensureDir();
|
|
26
27
|
}
|
|
@@ -253,6 +254,19 @@ class ConversationStore {
|
|
|
253
254
|
VALUES (?, ?, ?, ?, ?, ?, ?, 'active')
|
|
254
255
|
`).run(id, contactId, contactName, tokenId, direction, now, now);
|
|
255
256
|
|
|
257
|
+
if (this.eventStore && this.eventStore.isAvailable && this.eventStore.isAvailable()) {
|
|
258
|
+
this.eventStore.emitEvent('call.updated', {
|
|
259
|
+
conversation_id: id,
|
|
260
|
+
status: 'active',
|
|
261
|
+
direction,
|
|
262
|
+
contact_id: contactId || null,
|
|
263
|
+
contact_name: contactName || null
|
|
264
|
+
}, {
|
|
265
|
+
conversationId: id,
|
|
266
|
+
contactId: contactId || null
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
256
270
|
return { id, resumed: false };
|
|
257
271
|
}
|
|
258
272
|
|
|
@@ -284,6 +298,14 @@ class ConversationStore {
|
|
|
284
298
|
WHERE id = ?
|
|
285
299
|
`).run(now, conversationId);
|
|
286
300
|
|
|
301
|
+
if (this.eventStore && this.eventStore.isAvailable && this.eventStore.isAvailable()) {
|
|
302
|
+
this.eventStore.emitEvent('call.updated', {
|
|
303
|
+
conversation_id: conversationId,
|
|
304
|
+
status: 'active',
|
|
305
|
+
direction
|
|
306
|
+
}, { conversationId });
|
|
307
|
+
}
|
|
308
|
+
|
|
287
309
|
return { id, timestamp: now };
|
|
288
310
|
}
|
|
289
311
|
|
|
@@ -450,6 +472,31 @@ class ConversationStore {
|
|
|
450
472
|
`).run(now, conversationId);
|
|
451
473
|
}
|
|
452
474
|
|
|
475
|
+
if (this.eventStore && this.eventStore.isAvailable && this.eventStore.isAvailable()) {
|
|
476
|
+
this.eventStore.emitEvent('call.updated', {
|
|
477
|
+
conversation_id: conversationId,
|
|
478
|
+
status: 'concluded',
|
|
479
|
+
contact_id: conversation.contact_id || null,
|
|
480
|
+
contact_name: conversation.contact_name || null
|
|
481
|
+
}, {
|
|
482
|
+
conversationId,
|
|
483
|
+
contactId: conversation.contact_id || null
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
if (summary || ownerSummary) {
|
|
487
|
+
this.eventStore.emitEvent('summary.completed', {
|
|
488
|
+
conversation_id: conversationId,
|
|
489
|
+
contact_id: conversation.contact_id || null,
|
|
490
|
+
contact_name: conversation.contact_name || null,
|
|
491
|
+
has_summary: Boolean(summary),
|
|
492
|
+
has_owner_summary: Boolean(ownerSummary)
|
|
493
|
+
}, {
|
|
494
|
+
conversationId,
|
|
495
|
+
contactId: conversation.contact_id || null
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
453
500
|
return {
|
|
454
501
|
success: true,
|
|
455
502
|
conversationId,
|
|
@@ -472,6 +519,13 @@ class ConversationStore {
|
|
|
472
519
|
WHERE id = ?
|
|
473
520
|
`).run(now, conversationId);
|
|
474
521
|
|
|
522
|
+
if (this.eventStore && this.eventStore.isAvailable && this.eventStore.isAvailable()) {
|
|
523
|
+
this.eventStore.emitEvent('call.updated', {
|
|
524
|
+
conversation_id: conversationId,
|
|
525
|
+
status: 'timeout'
|
|
526
|
+
}, { conversationId });
|
|
527
|
+
}
|
|
528
|
+
|
|
475
529
|
return { success: true };
|
|
476
530
|
}
|
|
477
531
|
|