a2acalling 0.2.1 โ†’ 0.3.0

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 CHANGED
@@ -99,7 +99,7 @@ const commands = {
99
99
  name: args.flags.name || args.flags.n || 'unnamed',
100
100
  owner: args.flags.owner || args.flags.o || null,
101
101
  expires: args.flags.expires || args.flags.e || 'never',
102
- permissions: args.flags.permissions || args.flags.p || 'chat-only',
102
+ permissions: args.flags.permissions || args.flags.p || 'public',
103
103
  disclosure: args.flags.disclosure || args.flags.d || 'minimal',
104
104
  notify: args.flags.notify || 'all',
105
105
  maxCalls,
@@ -141,25 +141,23 @@ const commands = {
141
141
  console.log(`${'โ”€'.repeat(50)}\n`);
142
142
 
143
143
  // Generate shareable invite block
144
- // Human-friendly above the fold, agent instructions below
144
+ // Special invitation above the fold, agent setup below
145
145
  const ownerText = record.owner || 'Someone';
146
146
  const agentName = record.name;
147
- const topicsList = record.allowed_topics.join(', ');
148
-
149
- const invite = `๐Ÿค **${ownerText}** is inviting you to connect agents!
147
+ const topicsList = record.allowed_topics.join(' ยท ');
148
+ const goalsList = (record.allowed_goals || []).join(' ยท ');
150
149
 
151
- Your agent can reach ${agentName} for: ${topicsList}
150
+ const invite = `๐Ÿ“ž๐Ÿ—ฃ๏ธ **Agent-to-Agent Call Invite**
152
151
 
153
- ${inviteUrl}
152
+ ๐Ÿ‘ค **${ownerText}** would like your agent to call **${agentName}** and explore where our owners might collaborate.
154
153
 
155
- ${expiresText === 'never' ? '' : `โฐ Expires: ${expiresText}\n`}
156
- โ”โ”โ” For your agent โ”โ”โ”
154
+ ๐Ÿ’ฌ ${topicsList}${goalsList ? `\n๐ŸŽฏ ${goalsList}` : ''}
157
155
 
158
- npm install -g a2acalling
159
- a2a add "${inviteUrl}" "${agentName}"
160
- a2a call "${agentName}" "Hello!"
156
+ ${inviteUrl}${expiresText === 'never' ? '' : `\nโฐ ${expiresText}`}
161
157
 
162
- ๐Ÿ“š https://github.com/onthegonow/a2a_calling`;
158
+ โ”€โ”€ setup โ”€โ”€
159
+ npm i -g a2acalling && a2a add "${inviteUrl}" "${agentName}" && a2a call "${agentName}" "Hello from my owner!"
160
+ https://github.com/onthegonow/a2a_calling`;
163
161
 
164
162
  console.log(invite);
165
163
  console.log(`\n${'โ”€'.repeat(50)}`);
@@ -258,7 +256,7 @@ a2a call "${agentName}" "Hello!"
258
256
  let permBadge = '';
259
257
  if (r.linked_token) {
260
258
  const tier = r.linked_token.tier || r.linked_token.permissions;
261
- permBadge = tier === 'tools-write' ? ' โšก' : tier === 'tools-read' ? ' ๐Ÿ”ง' : ' ๐ŸŒ';
259
+ permBadge = tier === 'family' ? ' โšก' : tier === 'friends' ? ' ๐Ÿ”ง' : ' ๐ŸŒ';
262
260
  }
263
261
 
264
262
  console.log(`${statusIcon} ${r.name}${ownerText}${permBadge}`);
@@ -272,7 +270,7 @@ a2a call "${agentName}" "Hello!"
272
270
  console.log();
273
271
  }
274
272
 
275
- console.log('Legend: ๐ŸŒ chat-only ๐Ÿ”ง tools-read โšก tools-write');
273
+ console.log('Legend: ๐ŸŒ public ๐Ÿ”ง friends โšก family');
276
274
  },
277
275
 
278
276
  'contacts:add': (args) => {
@@ -345,7 +343,7 @@ a2a call "${agentName}" "Hello!"
345
343
  const t = remote.linked_token;
346
344
  const tier = t.tier || t.permissions;
347
345
  const topics = t.allowed_topics || ['chat'];
348
- const tierIcon = tier === 'tools-write' ? 'โšก' : tier === 'tools-read' ? '๐Ÿ”ง' : '๐ŸŒ';
346
+ const tierIcon = tier === 'family' ? 'โšก' : tier === 'friends' ? '๐Ÿ”ง' : '๐ŸŒ';
349
347
  console.log(`๐Ÿ” Your token to them: ${t.id}`);
350
348
  console.log(` Tier: ${tierIcon} ${tier}`);
351
349
  console.log(` Topics: ${topics.join(', ')}`);
@@ -434,8 +432,8 @@ a2a call "${agentName}" "Hello!"
434
432
  process.exit(1);
435
433
  }
436
434
 
437
- const permLabel = result.token.permissions === 'tools-write' ? 'โšก tools-write' :
438
- result.token.permissions === 'tools-read' ? '๐Ÿ”ง tools-read' : '๐ŸŒ chat-only';
435
+ const permLabel = result.token.tier === 'family' ? 'โšก family' :
436
+ result.token.tier === 'friends' ? '๐Ÿ”ง friends' : '๐ŸŒ public';
439
437
 
440
438
  console.log(`โœ… Linked token to contact`);
441
439
  console.log(` Contact: ${result.remote.name}`);
@@ -772,7 +770,7 @@ a2a call "${agentName}" "Hello!"
772
770
  name,
773
771
  owner,
774
772
  expires: '7d',
775
- permissions: 'chat-only',
773
+ permissions: 'public',
776
774
  maxCalls: 100
777
775
  });
778
776
 
@@ -782,30 +780,24 @@ a2a call "${agentName}" "Hello!"
782
780
  });
783
781
 
784
782
  // Step 3: Show the invite
785
- const ownerText = owner ? `${owner}'s agent ${name}` : name;
783
+ const ownerText = owner ? `${owner}` : 'Someone';
784
+ const topicsList = record.allowed_topics.join(' ยท ');
785
+ const goalsList = (record.allowed_goals || []).join(' ยท ');
786
786
  console.log('3๏ธโƒฃ Share this invite:\n');
787
787
  console.log('โ”€'.repeat(50));
788
788
  console.log(`
789
- ๐Ÿค Agent-to-Agent Invite
790
-
791
- ${ownerText} is inviting your agent to connect!
792
-
793
- ๐Ÿ“ก Connection URL:
794
- ${inviteUrl}
795
-
796
- โฐ Expires: ${expiresText}
797
- ๐Ÿ” Permissions: chat-only
798
- ๐Ÿ“Š Limits: 100 calls, 10/min rate limit
789
+ ๐Ÿ“ž๐Ÿ—ฃ๏ธ **Agent-to-Agent Call Invite**
799
790
 
800
- โ”โ”โ” Quick Setup โ”โ”โ”
791
+ ๐Ÿ‘ค **${ownerText}** would like your agent to call **${name}** and explore where our owners might collaborate.
801
792
 
802
- 1. Install: npm install -g a2acalling
793
+ ๐Ÿ’ฌ ${topicsList}${goalsList ? `\n๐ŸŽฏ ${goalsList}` : ''}
803
794
 
804
- 2. Connect: a2a add "${inviteUrl}" "${name}"
805
-
806
- 3. Call: a2a call "${inviteUrl}" "Hello!"
795
+ ${inviteUrl}
796
+ โฐ ${expiresText}
807
797
 
808
- ๐Ÿ“š Docs: https://github.com/onthegonow/a2a_calling
798
+ โ”€โ”€ setup โ”€โ”€
799
+ npm i -g a2acalling && a2a add "${inviteUrl}" "${name}" && a2a call "${name}" "Hello from my owner!"
800
+ https://github.com/onthegonow/a2a_calling
809
801
  `);
810
802
  console.log('โ”€'.repeat(50));
811
803
  console.log(`\nโœ… Done! Share the invite above with other agents.\n`);
@@ -831,7 +823,7 @@ Commands:
831
823
  --name, -n Token/agent name
832
824
  --owner, -o Owner name (human behind the agent)
833
825
  --expires, -e Expiration (1h, 1d, 7d, 30d, never)
834
- --permissions, -p Tier (chat-only, tools-read, tools-write)
826
+ --permissions, -p Tier (public, friends, family)
835
827
  --topics Custom topics (comma-separated, overrides tier defaults)
836
828
  --disclosure, -d Disclosure level (public, minimal, none)
837
829
  --notify Owner notification (all, summary, none)
@@ -855,7 +847,7 @@ Contacts:
855
847
  contacts ping <n> Ping contact, update status
856
848
  contacts rm <n> Remove contact
857
849
 
858
- Permission badges: ๐ŸŒ chat-only ๐Ÿ”ง tools-read โšก tools-write
850
+ Permission badges: ๐ŸŒ public ๐Ÿ”ง friends โšก family
859
851
 
860
852
  Conversations:
861
853
  conversations List all conversations
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "a2acalling",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Agent-to-agent calling for OpenClaw - A2A agent communication",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -272,7 +272,7 @@ function renderInvites() {
272
272
  tr.innerHTML = `
273
273
  <td>${invite.id}</td>
274
274
  <td>${invite.name || '-'}</td>
275
- <td>${invite.tier_label || invite.tier || '-'}</td>
275
+ <td>${invite.tier || '-'}</td>
276
276
  <td>${invite.calls_made || 0}${invite.max_calls ? `/${invite.max_calls}` : ''}</td>
277
277
  <td>${fmtDate(invite.expires_at)}</td>
278
278
  <td>${invite.revoked ? 'revoked' : 'active'}</td>
package/src/lib/config.js CHANGED
@@ -22,7 +22,7 @@ const DEFAULT_CONFIG = {
22
22
  public: {
23
23
  name: 'Public',
24
24
  description: 'Basic networking - safe for anyone',
25
- capabilities: [],
25
+ capabilities: ['context-read'],
26
26
  topics: [],
27
27
  goals: [],
28
28
  disclosure: 'minimal',
@@ -31,7 +31,7 @@ const DEFAULT_CONFIG = {
31
31
  friends: {
32
32
  name: 'Friends',
33
33
  description: 'Most capabilities, no sensitive financial data',
34
- capabilities: [],
34
+ capabilities: ['context-read', 'calendar.read', 'email.read', 'search'],
35
35
  topics: [],
36
36
  goals: [],
37
37
  disclosure: 'public',
@@ -40,7 +40,7 @@ const DEFAULT_CONFIG = {
40
40
  private: {
41
41
  name: 'Private',
42
42
  description: 'Full access - only for you',
43
- capabilities: [],
43
+ capabilities: ['context-read', 'calendar', 'email', 'search', 'tools', 'memory'],
44
44
  topics: [],
45
45
  goals: [],
46
46
  disclosure: 'public',
@@ -49,7 +49,7 @@ const DEFAULT_CONFIG = {
49
49
  custom: {
50
50
  name: 'Custom',
51
51
  description: 'User-defined permissions',
52
- capabilities: [],
52
+ capabilities: ['context-read'],
53
53
  topics: [],
54
54
  goals: [],
55
55
  disclosure: 'minimal',
package/src/lib/tokens.js CHANGED
@@ -90,16 +90,20 @@ class TokenStore {
90
90
  name = 'unnamed',
91
91
  owner = null,
92
92
  expires = '1d',
93
- permissions = 'chat-only',
93
+ permissions = 'public',
94
94
  disclosure = 'minimal',
95
95
  notify = 'all',
96
96
  maxCalls = 100, // Default limit, not unlimited
97
+ capabilities = null, // Array of capability strings, snapshotted at creation
97
98
  // Snapshot of actual capabilities at creation time
98
99
  allowedTopics = null, // Array of topic strings, e.g. ['chat', 'calendar.read']
99
100
  allowedGoals = null, // Array of goal strings, e.g. ['grow-network', 'find-collaborators']
100
101
  tierSettings = null // Object with tier-specific settings
101
102
  } = options;
102
103
 
104
+ // Map legacy tier values to labels
105
+ const tier = TokenStore.LEGACY_TIER_MAP[permissions] || permissions;
106
+
103
107
  const token = TokenStore.generateToken();
104
108
  const tokenHash = TokenStore.hashToken(token);
105
109
  const durationMs = TokenStore.parseDuration(expires);
@@ -119,34 +123,25 @@ class TokenStore {
119
123
  // Config not available, use defaults
120
124
  }
121
125
 
122
- // Default topics based on permissions tier (snapshot at creation)
126
+ // Default topics based on tier label (snapshot at creation)
123
127
  // User config overrides these defaults
124
128
  const defaultTopics = {
125
- 'chat-only': ['chat'],
126
129
  'public': configTiers.public?.topics || ['chat'],
127
- 'tools-read': ['chat', 'calendar.read', 'email.read', 'search'],
128
130
  'friends': configTiers.friends?.topics || ['chat', 'calendar.read', 'email.read', 'search'],
129
- 'tools-write': ['chat', 'calendar', 'email', 'search', 'tools'],
130
131
  'family': configTiers.family?.topics || ['chat', 'calendar', 'email', 'search', 'tools']
131
132
  };
132
133
 
133
- // Default goals based on permissions tier (snapshot at creation)
134
+ // Default goals based on tier label (snapshot at creation)
134
135
  const defaultGoals = {
135
- 'chat-only': [],
136
136
  'public': configTiers.public?.goals || [],
137
- 'tools-read': [],
138
137
  'friends': configTiers.friends?.goals || [],
139
- 'tools-write': [],
140
138
  'family': configTiers.family?.goals || []
141
139
  };
142
140
 
143
- // Normalize tier name
144
- const tierAliases = {
145
- 'public': 'chat-only',
146
- 'friends': 'tools-read',
147
- 'family': 'tools-write'
148
- };
149
- const normalizedTier = tierAliases[permissions] || permissions;
141
+ // Resolve capabilities: explicit > config > defaults
142
+ const defaultCapabilities = (configTiers[tier]?.capabilities?.length)
143
+ ? configTiers[tier].capabilities
144
+ : (TokenStore.DEFAULT_CAPABILITIES[tier] || ['context-read']);
150
145
 
151
146
  // Use separate random ID (not derived from token) to prevent prefix attacks
152
147
  const record = {
@@ -154,10 +149,10 @@ class TokenStore {
154
149
  token_hash: tokenHash,
155
150
  name,
156
151
  owner,
157
- tier: normalizedTier, // Normalized tier (chat-only, tools-read, tools-write)
158
- tier_label: permissions, // Original label (public, friends, family)
159
- allowed_topics: allowedTopics || defaultTopics[permissions] || ['chat'],
160
- allowed_goals: allowedGoals || defaultGoals[permissions] || [],
152
+ tier,
153
+ capabilities: capabilities || defaultCapabilities,
154
+ allowed_topics: allowedTopics || defaultTopics[tier] || ['chat'],
155
+ allowed_goals: allowedGoals || defaultGoals[tier] || [],
161
156
  tier_settings: tierSettings || {}, // Snapshot of settings at creation
162
157
  disclosure,
163
158
  notify,
@@ -220,11 +215,20 @@ class TokenStore {
220
215
  record.last_used = new Date().toISOString();
221
216
  this._save(db);
222
217
 
218
+ // Map legacy tier values to labels for old records
219
+ const tier = TokenStore.LEGACY_TIER_MAP[record.tier] || record.tier || record.permissions || 'public';
220
+
221
+ // Resolve capabilities: stored > defaults
222
+ const capabilities = record.capabilities
223
+ || TokenStore.DEFAULT_CAPABILITIES[tier]
224
+ || ['context-read'];
225
+
223
226
  return {
224
227
  valid: true,
225
228
  id: record.id,
226
229
  name: record.name,
227
- tier: record.tier || record.permissions, // backward compat
230
+ tier,
231
+ capabilities,
228
232
  allowed_topics: record.allowed_topics || ['chat'],
229
233
  allowed_goals: record.allowed_goals || [],
230
234
  tier_settings: record.tier_settings || {},
@@ -458,4 +462,18 @@ class TokenStore {
458
462
  }
459
463
  }
460
464
 
465
+ // Legacy tier values from old records โ†’ label mapping
466
+ TokenStore.LEGACY_TIER_MAP = {
467
+ 'chat-only': 'public',
468
+ 'tools-read': 'friends',
469
+ 'tools-write': 'family'
470
+ };
471
+
472
+ // Default capabilities per tier label (used when config has none)
473
+ TokenStore.DEFAULT_CAPABILITIES = {
474
+ 'public': ['context-read'],
475
+ 'friends': ['context-read', 'calendar.read', 'email.read', 'search'],
476
+ 'family': ['context-read', 'calendar', 'email', 'search', 'tools', 'memory']
477
+ };
478
+
461
479
  module.exports = { TokenStore };
package/src/routes/a2a.js CHANGED
@@ -229,6 +229,7 @@ function createRoutes(options = {}) {
229
229
  token_id: validation.id,
230
230
  token_name: validation.name,
231
231
  tier: validation.tier,
232
+ capabilities: validation.capabilities,
232
233
  allowed_topics: validation.allowed_topics,
233
234
  disclosure: validation.disclosure,
234
235
  caller: sanitizedCaller,
@@ -84,16 +84,6 @@ function parseTopicObjects(values) {
84
84
  return cleaned;
85
85
  }
86
86
 
87
- function inferPermissionForTier(tierId) {
88
- const alias = {
89
- public: 'chat-only',
90
- friends: 'tools-read',
91
- family: 'tools-write',
92
- private: 'tools-write'
93
- };
94
- return alias[tierId] || tierId;
95
- }
96
-
97
87
  function formatInviteMessage({ owner, agentName, inviteUrl, topics, expiresText }) {
98
88
  const ownerText = owner || 'Someone';
99
89
  const topicsText = topics.length > 0 ? topics.join(', ') : 'chat';
@@ -485,13 +475,11 @@ function createDashboardApiRouter(options = {}) {
485
475
 
486
476
  const allowedTopics = sanitizeStringArray(body.topics || tier.topics || []);
487
477
  const allowedGoals = sanitizeStringArray(body.goals || tier.goals || []);
488
- const permission = inferPermissionForTier(tierId);
489
-
490
478
  const { token, record } = context.tokenStore.create({
491
479
  name,
492
480
  owner,
493
481
  expires,
494
- permissions: permission,
482
+ permissions: tierId,
495
483
  disclosure,
496
484
  notify,
497
485
  maxCalls,