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.
- package/README.md +1 -0
- package/bin/cli.js +83 -7
- package/docs/protocol.md +6 -5
- package/native/macos/index.html +24 -12
- package/native/macos/package-lock.json +232 -0
- package/native/macos/src-tauri/src/discovery.rs +100 -13
- package/native/macos/src-tauri/src/server.rs +4 -1
- package/package.json +1 -1
- package/src/dashboard/public/app.js +2 -0
- package/src/dashboard/public/index.html +1 -0
- package/src/lib/claude-subagent.js +100 -27
- package/src/lib/config.js +11 -0
- package/src/lib/conversation-driver.js +11 -2
- package/src/lib/disclosure.js +89 -13
- package/src/lib/runtime-adapter.js +42 -15
- package/src/lib/tokens.js +18 -0
- package/src/routes/a2a.js +4 -0
- package/src/routes/dashboard.js +9 -1
- package/src/server.js +42 -2
|
@@ -12,7 +12,12 @@
|
|
|
12
12
|
|
|
13
13
|
const { execSync, spawnSync } = require('child_process');
|
|
14
14
|
const { createLogger } = require('./logger');
|
|
15
|
-
const {
|
|
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
|
-
|
|
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
|
});
|
package/src/routes/dashboard.js
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
816
|
+
sessionId: summarySessionId,
|
|
777
817
|
prompt,
|
|
778
818
|
messages,
|
|
779
819
|
callerInfo,
|