a2acalling 0.6.52 → 0.6.53

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.
@@ -12,7 +12,12 @@
12
12
 
13
13
  const { execSync, spawnSync } = require('child_process');
14
14
  const { createLogger } = require('./logger');
15
- const { runClaudeTurn: invokeClaudeTurn, buildSubagentSystemPrompt, runClaudeSummary } = require('./claude-subagent');
15
+ const {
16
+ runClaudeTurn: invokeClaudeTurn,
17
+ buildSubagentSystemPrompt,
18
+ runClaudeSummary,
19
+ resolveClaudeAllowedTools
20
+ } = require('./claude-subagent');
16
21
  const { getTopicsForTier, formatTopicsForPrompt, loadManifest } = require('./disclosure');
17
22
  const { HARD_FALLBACK_TURN_TIMEOUT_MS } = require('./turn-timeout');
18
23
 
@@ -168,7 +173,29 @@ function createRuntimeAdapter(options = {}) {
168
173
  const tierTopics = getTopicsForTier(context?.tier || 'public');
169
174
  const formatted = formatTopicsForPrompt(tierTopics);
170
175
 
171
- const systemPrompt = buildSubagentSystemPrompt({
176
+ session = {
177
+ systemPrompt: '',
178
+ turnCount: 0,
179
+ lastMeta: null,
180
+ // Keep a permission snapshot so summary runs with the same policy envelope.
181
+ permissionSnapshot: {
182
+ capabilities: Array.isArray(context?.capabilities) ? context.capabilities : [],
183
+ allowedTopics: Array.isArray(context?.allowedTopics || context?.allowed_topics)
184
+ ? (context?.allowedTopics || context?.allowed_topics)
185
+ : [],
186
+ allowedTools: Array.isArray(context?.allowedTools || context?.allowed_tools)
187
+ ? (context?.allowedTools || context?.allowed_tools)
188
+ : []
189
+ }
190
+ };
191
+
192
+ const sessionAllowedTools = resolveClaudeAllowedTools({
193
+ capabilities: session.permissionSnapshot.capabilities,
194
+ allowedTopics: session.permissionSnapshot.allowedTopics,
195
+ allowedTools: session.permissionSnapshot.allowedTools
196
+ });
197
+
198
+ session.systemPrompt = buildSubagentSystemPrompt({
172
199
  agentName: context?.agentName || 'Agent',
173
200
  ownerName: context?.ownerName || 'the owner',
174
201
  otherAgentName: caller?.name || 'Remote Agent',
@@ -179,21 +206,10 @@ function createRuntimeAdapter(options = {}) {
179
206
  doNotDiscuss: formatted.doNotDiscuss,
180
207
  neverDisclose: formatted.neverDisclose,
181
208
  personalityNotes: manifest.personality_notes || '',
182
- roleContext: context?.roleContext || ''
209
+ roleContext: context?.roleContext || '',
210
+ allowedTools: sessionAllowedTools
183
211
  });
184
212
 
185
- session = {
186
- systemPrompt,
187
- turnCount: 0,
188
- lastMeta: null,
189
- // Keep a permission snapshot so summary runs with the same policy envelope.
190
- permissionSnapshot: {
191
- capabilities: Array.isArray(context?.capabilities) ? context.capabilities : [],
192
- allowedTopics: Array.isArray(context?.allowedTopics || context?.allowed_topics)
193
- ? (context?.allowedTopics || context?.allowed_topics)
194
- : []
195
- }
196
- };
197
213
  claudeSessions.set(sessionId, session);
198
214
  }
199
215
 
@@ -227,6 +243,9 @@ function createRuntimeAdapter(options = {}) {
227
243
  allowedTopics: Array.isArray(context?.allowedTopics || context?.allowed_topics)
228
244
  ? (context?.allowedTopics || context?.allowed_topics)
229
245
  : (session.permissionSnapshot?.allowedTopics || []),
246
+ allowedTools: Array.isArray(context?.allowedTools || context?.allowed_tools)
247
+ ? (context?.allowedTools || context?.allowed_tools)
248
+ : (session.permissionSnapshot?.allowedTools || []),
230
249
  timeoutMs: timeoutMs || HARD_FALLBACK_TURN_TIMEOUT_MS
231
250
  });
232
251
 
@@ -237,6 +256,9 @@ function createRuntimeAdapter(options = {}) {
237
256
  if (Array.isArray(context?.allowedTopics || context?.allowed_topics)) {
238
257
  session.permissionSnapshot.allowedTopics = context?.allowedTopics || context?.allowed_topics;
239
258
  }
259
+ if (Array.isArray(context?.allowedTools || context?.allowed_tools)) {
260
+ session.permissionSnapshot.allowedTools = context?.allowedTools || context?.allowed_tools;
261
+ }
240
262
 
241
263
  // Store flags/state for retrieval via getLastTurnMeta
242
264
  session.lastMeta = {
@@ -416,12 +438,17 @@ function createRuntimeAdapter(options = {}) {
416
438
  || callerInfo?.allowedTopics
417
439
  || callerInfo?.allowed_topics
418
440
  || [];
441
+ const allowedTools = session?.permissionSnapshot?.allowedTools
442
+ || callerInfo?.allowedTools
443
+ || callerInfo?.allowed_tools
444
+ || [];
419
445
 
420
446
  const result = await runClaudeSummary({
421
447
  prompt,
422
448
  reason: 'conversation ended',
423
449
  capabilities,
424
450
  allowedTopics,
451
+ allowedTools,
425
452
  timeoutMs: timeoutMs || HARD_FALLBACK_TURN_TIMEOUT_MS
426
453
  });
427
454
  if (result && result.summary) {
package/src/lib/tokens.js CHANGED
@@ -201,6 +201,7 @@ class TokenStore {
201
201
  // Snapshot of actual capabilities at creation time
202
202
  allowedTopics = null, // Array of topic strings, e.g. ['chat', 'calendar.read']
203
203
  allowedGoals = null, // Array of goal strings, e.g. ['grow-network', 'find-collaborators']
204
+ allowedTools = null, // Array of tool names, e.g. ['Read', 'Grep', 'Glob']
204
205
  tierSettings = null, // Object with tier-specific settings
205
206
  timeoutMs = null
206
207
  } = options;
@@ -246,6 +247,14 @@ class TokenStore {
246
247
  'custom': configTiers.custom?.goals || []
247
248
  };
248
249
 
250
+ // Default tool allowlist based on tier label (snapshot at creation).
251
+ const defaultTools = {
252
+ 'public': configTiers.public?.allowed_tools || TokenStore.DEFAULT_ALLOWED_TOOLS.public,
253
+ 'friends': configTiers.friends?.allowed_tools || TokenStore.DEFAULT_ALLOWED_TOOLS.friends,
254
+ 'family': configTiers.family?.allowed_tools || TokenStore.DEFAULT_ALLOWED_TOOLS.family,
255
+ 'custom': configTiers.custom?.allowed_tools || TokenStore.DEFAULT_ALLOWED_TOOLS.custom
256
+ };
257
+
249
258
  // Resolve capabilities: explicit > config > defaults
250
259
  const defaultCapabilities = (configTiers[tier]?.capabilities?.length)
251
260
  ? configTiers[tier].capabilities
@@ -261,6 +270,7 @@ class TokenStore {
261
270
  capabilities: capabilities || defaultCapabilities,
262
271
  allowed_topics: allowedTopics || defaultTopics[tier] || ['chat'],
263
272
  allowed_goals: allowedGoals || defaultGoals[tier] || [],
273
+ allowed_tools: allowedTools || defaultTools[tier] || TokenStore.DEFAULT_ALLOWED_TOOLS.public,
264
274
  timeout_ms: parsePositiveTimeoutMs(timeoutMs),
265
275
  tier_settings: tierSettings || {}, // Snapshot of settings at creation
266
276
  disclosure,
@@ -346,6 +356,7 @@ class TokenStore {
346
356
  capabilities,
347
357
  allowed_topics: record.allowed_topics || ['chat'],
348
358
  allowed_goals: record.allowed_goals || [],
359
+ allowed_tools: record.allowed_tools || TokenStore.DEFAULT_ALLOWED_TOOLS[tier] || TokenStore.DEFAULT_ALLOWED_TOOLS.public,
349
360
  timeout_ms: timeoutMs,
350
361
  tier_settings: record.tier_settings || {},
351
362
  disclosure: record.disclosure,
@@ -777,4 +788,11 @@ TokenStore.DEFAULT_CAPABILITIES = {
777
788
  'custom': ['context-read']
778
789
  };
779
790
 
791
+ TokenStore.DEFAULT_ALLOWED_TOOLS = {
792
+ 'public': ['Read', 'Grep', 'Glob'],
793
+ 'friends': ['Bash(readonly)', 'Read', 'Grep', 'Glob', 'WebSearch', 'WebFetch'],
794
+ 'family': ['Bash', 'Read', 'Grep', 'Glob', 'WebSearch', 'WebFetch'],
795
+ 'custom': ['Read', 'Grep', 'Glob']
796
+ };
797
+
780
798
  module.exports = { TokenStore };
package/src/routes/a2a.js CHANGED
@@ -347,6 +347,8 @@ function createRoutes(options = {}) {
347
347
  tier: validation.tier,
348
348
  capabilities: validation.capabilities,
349
349
  allowed_topics: validation.allowed_topics,
350
+ allowed_goals: validation.allowed_goals,
351
+ allowed_tools: validation.allowed_tools,
350
352
  timeout_ms: validation.timeout_ms,
351
353
  disclosure: validation.disclosure,
352
354
  caller: sanitizedCaller,
@@ -393,6 +395,8 @@ function createRoutes(options = {}) {
393
395
  tier: validation.tier,
394
396
  capabilities: validation.capabilities,
395
397
  allowed_topics: validation.allowed_topics,
398
+ allowed_goals: validation.allowed_goals,
399
+ allowed_tools: validation.allowed_tools,
396
400
  trace_id: traceId,
397
401
  request_id: requestId
398
402
  });
@@ -170,16 +170,18 @@ function parseTopicObjects(values) {
170
170
  return cleaned;
171
171
  }
172
172
 
173
- function formatInviteMessage({ owner, agentName, inviteUrl, topics, goals, expiresText }) {
173
+ function formatInviteMessage({ owner, agentName, inviteUrl, topics, goals, tools, expiresText }) {
174
174
  const ownerText = owner || 'Someone';
175
175
  const topicsList = topics.length > 0 ? topics.join(' · ') : 'chat';
176
176
  const goalsList = (goals || []).join(' · ');
177
+ const toolsList = (tools || []).join(' · ');
177
178
  const expirationLine = expiresText === 'never' ? '' : `\n⏰ ${expiresText}`;
178
179
  return `📞🗣️ **Agent-to-Agent Call Invite**
179
180
 
180
181
  👤 **${ownerText}** would like your agent to call **${agentName}** and explore where our owners might collaborate.
181
182
 
182
183
  💬 ${topicsList}${goalsList ? `\n🎯 ${goalsList}` : ''}
184
+ ${toolsList ? `\n🧰 ${toolsList}` : ''}
183
185
 
184
186
  ${inviteUrl}${expirationLine}
185
187
 
@@ -1271,6 +1273,7 @@ function createDashboardApiRouter(options = {}) {
1271
1273
  name: configTier.name || tierId,
1272
1274
  description: configTier.description || '',
1273
1275
  capabilities: configTier.capabilities || [],
1276
+ allowed_tools: sanitizeStringArray(configTier.allowed_tools || [], 30, 80),
1274
1277
  topics: sanitizeStringArray(configTier.topics || []),
1275
1278
  goals: sanitizeStringArray(configTier.goals || []),
1276
1279
  disclosure: configTier.disclosure || 'minimal',
@@ -1309,6 +1312,7 @@ function createDashboardApiRouter(options = {}) {
1309
1312
  if (body.description !== undefined) update.description = sanitizeString(body.description, 300);
1310
1313
  if (body.disclosure !== undefined) update.disclosure = sanitizeString(body.disclosure, 40) || 'minimal';
1311
1314
  if (body.capabilities !== undefined) update.capabilities = sanitizeStringArray(body.capabilities, 100, 120);
1315
+ if (body.allowed_tools !== undefined) update.allowed_tools = sanitizeStringArray(body.allowed_tools, 30, 80);
1312
1316
  if (body.examples !== undefined) update.examples = sanitizeStringArray(body.examples, 20, 120);
1313
1317
  if (body.topics !== undefined) update.topics = sanitizeStringArray(body.topics, 200, 160);
1314
1318
  if (body.goals !== undefined) update.goals = sanitizeStringArray(body.goals, 200, 160);
@@ -1368,6 +1372,7 @@ function createDashboardApiRouter(options = {}) {
1368
1372
  name: sanitizeString(body.name || tierId, 120),
1369
1373
  description: sanitizeString(body.description || 'Custom tier', 300),
1370
1374
  capabilities: sanitizeStringArray(body.capabilities || []),
1375
+ allowed_tools: sanitizeStringArray(body.allowed_tools || [], 30, 80),
1371
1376
  topics: sanitizeStringArray(body.topics || []),
1372
1377
  goals: sanitizeStringArray(body.goals || []),
1373
1378
  disclosure: sanitizeString(body.disclosure || 'minimal', 40),
@@ -1464,6 +1469,7 @@ function createDashboardApiRouter(options = {}) {
1464
1469
 
1465
1470
  const allowedTopics = sanitizeStringArray(body.topics || tier.topics || []);
1466
1471
  const allowedGoals = sanitizeStringArray(body.goals || tier.goals || []);
1472
+ const allowedTools = sanitizeStringArray(body.tools || body.allowed_tools || tier.allowed_tools || [], 30, 80);
1467
1473
  const { token, record } = context.tokenStore.create({
1468
1474
  name,
1469
1475
  owner,
@@ -1474,6 +1480,7 @@ function createDashboardApiRouter(options = {}) {
1474
1480
  maxCalls,
1475
1481
  allowedTopics: allowedTopics.length ? allowedTopics : null,
1476
1482
  allowedGoals: allowedGoals.length ? allowedGoals : null,
1483
+ allowedTools: allowedTools.length ? allowedTools : null,
1477
1484
  tierSettings: {
1478
1485
  tierId,
1479
1486
  ...tier
@@ -1495,6 +1502,7 @@ function createDashboardApiRouter(options = {}) {
1495
1502
  inviteUrl,
1496
1503
  topics: record.allowed_topics || [],
1497
1504
  goals: record.allowed_goals || [],
1505
+ tools: record.allowed_tools || [],
1498
1506
  expiresText
1499
1507
  });
1500
1508
 
package/src/server.js CHANGED
@@ -624,12 +624,41 @@ async function callAgent(message, a2aContext) {
624
624
  capabilities: Array.isArray(a2aContext.capabilities) ? a2aContext.capabilities : [],
625
625
  allowedTopics: a2aContext.allowed_topics || [],
626
626
  allowed_topics: a2aContext.allowed_topics || [],
627
+ allowedGoals: a2aContext.allowed_goals || [],
628
+ allowed_goals: a2aContext.allowed_goals || [],
629
+ allowedTools: a2aContext.allowed_tools || [],
630
+ allowed_tools: a2aContext.allowed_tools || [],
627
631
  timeoutMs: runtime.mode === 'claude' ? claudeTurnTimeoutMs : 65000,
628
632
  traceId,
629
633
  requestId
630
634
  }
631
635
  });
632
636
 
637
+ // Claude mode returns structured metadata (statePatch + flags) via side channel.
638
+ // We persist owner-facing flags so permission questions (e.g. blocked tool requests)
639
+ // are visible in call history even though the remote only sees conversational text.
640
+ const turnMeta = runtime.mode === 'claude' && typeof runtime.getLastTurnMeta === 'function'
641
+ ? runtime.getLastTurnMeta(sessionId)
642
+ : null;
643
+ if (turnMeta?.flags?.length > 0) {
644
+ const convStoreForFlags = getServerConvStore();
645
+ if (convStoreForFlags) {
646
+ try {
647
+ convStoreForFlags.addMessage(conversationId, {
648
+ direction: 'outbound',
649
+ role: 'system',
650
+ content: `[flags] ${turnMeta.flags.map(f => f.content || f.type).join('; ')}`,
651
+ metadata: JSON.stringify({ flags: turnMeta.flags, phase: collabState.phase })
652
+ });
653
+ } catch (err) {
654
+ callLogger.warn('Failed to persist owner flags for turn', {
655
+ event: 'call_turn_flags_persist_failed',
656
+ error: err
657
+ });
658
+ }
659
+ }
660
+ }
661
+
633
662
  if (collabMode !== 'adaptive') {
634
663
  return rawResponse;
635
664
  }
@@ -639,7 +668,17 @@ async function callAgent(message, a2aContext) {
639
668
  const beforeTurn = collabState.turnCount;
640
669
  let usedMetadata = false;
641
670
 
642
- if (parsed.hasState && parsed.statePatch) {
671
+ // Prefer explicit Claude metadata side channel in claude mode.
672
+ if (turnMeta?.statePatch) {
673
+ usedMetadata = applyCollaborationPatch(collabState, turnMeta.statePatch);
674
+ if (!usedMetadata) {
675
+ callLogger.warn('Invalid collaboration patch; applying fallback heuristics', {
676
+ event: 'collaboration_patch_invalid',
677
+ error_code: 'COLLABORATION_PATCH_INVALID',
678
+ hint: 'Ensure assistant emits valid collaboration metadata JSON block.'
679
+ });
680
+ }
681
+ } else if (parsed.hasState && parsed.statePatch) {
643
682
  usedMetadata = applyCollaborationPatch(collabState, parsed.statePatch);
644
683
  if (!usedMetadata) {
645
684
  callLogger.warn('Invalid collaboration patch; applying fallback heuristics', {
@@ -772,8 +811,9 @@ async function generateSummary(messages, callerInfo) {
772
811
  });
773
812
 
774
813
  try {
814
+ const summarySessionId = conversationId ? `a2a-${conversationId}` : `summary-${Date.now()}`;
775
815
  return await runtime.summarize({
776
- sessionId: `summary-${Date.now()}`,
816
+ sessionId: summarySessionId,
777
817
  prompt,
778
818
  messages,
779
819
  callerInfo,