a2acalling 0.6.45 → 0.6.46

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
@@ -29,6 +29,7 @@ const ONBOARDING_EXEMPT = new Set([
29
29
  'quickstart',
30
30
  'help',
31
31
  'version',
32
+ 'status',
32
33
  'update',
33
34
  'uninstall',
34
35
  'onboard',
@@ -75,7 +76,7 @@ const store = new TokenStore();
75
76
  // rather than falling through to the interactive quickstart flow.
76
77
  // These are outbound operations often invoked by agents/automation.
77
78
  const ONBOARDING_HARD_FAIL = new Set([
78
- 'call', 'ping', 'status'
79
+ 'call', 'ping'
79
80
  ]);
80
81
 
81
82
  // ── enforceOnboarding ────────────────────────────────────────────────────
@@ -489,6 +490,19 @@ function generateProxyConfig(backendPort) {
489
490
  return { hasNginx, hasCaddy, nginxConfig, caddyConfig };
490
491
  }
491
492
 
493
+ function extractNameFromPersonality(notes) {
494
+ if (!notes || typeof notes !== 'string') return null;
495
+ const patterns = [
496
+ /(?:I'm|I am|My name is|Name:|Owner:)\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)/,
497
+ /^([A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)\s+(?:is|here|speaking)/
498
+ ];
499
+ for (const p of patterns) {
500
+ const m = notes.match(p);
501
+ if (m && m[1]) return m[1].trim();
502
+ }
503
+ return null;
504
+ }
505
+
492
506
  async function handleDisclosureSubmit(args, commandLabel = 'onboard') {
493
507
  const submitRaw = args.flags.submit;
494
508
  if (!submitRaw) return false;
@@ -537,18 +551,29 @@ async function handleDisclosureSubmit(args, commandLabel = 'onboard') {
537
551
 
538
552
  const tiersData = manifest.tiers || {};
539
553
 
554
+ // Derive goals from disclosure objectives (used in tier config and token creation)
555
+ const disclosureObjectives = (tiersData.public?.objectives || [])
556
+ .map(o => typeof o === 'string' ? o : (o && o.objective || ''))
557
+ .map(s => s.trim().toLowerCase().replace(/\s+/g, '-').slice(0, 60))
558
+ .filter(Boolean);
559
+
560
+ const tokenGoals = disclosureObjectives.length > 0
561
+ ? [...new Set(disclosureObjectives)].slice(0, 5)
562
+ : ['grow-network', 'find-collaborators', 'build-in-public'];
563
+
540
564
  try {
541
565
  config.setTier('public', {
542
566
  topics: getTierTopics(tiersData.public),
543
- disclosure: 'public'
567
+ goals: tokenGoals,
568
+ disclosure: 'minimal'
544
569
  });
545
570
  config.setTier('friends', {
546
571
  topics: [...getTierTopics(tiersData.public), ...getTierTopics(tiersData.friends)],
547
- disclosure: 'minimal'
572
+ disclosure: 'standard'
548
573
  });
549
574
  config.setTier('family', {
550
575
  topics: [...getTierTopics(tiersData.public), ...getTierTopics(tiersData.friends), ...getTierTopics(tiersData.family)],
551
- disclosure: 'minimal'
576
+ disclosure: 'full'
552
577
  });
553
578
  } catch (err) {
554
579
  console.error(` Warning: could not sync tier config: ${err.message}`);
@@ -562,21 +587,34 @@ async function handleDisclosureSubmit(args, commandLabel = 'onboard') {
562
587
 
563
588
  console.log('\nStep 4 of 4: Generating your first invite...\n');
564
589
 
565
- const agentName = args.flags.name || config.getAgent().name || process.env.A2A_AGENT_NAME || 'my-agent';
590
+ // Extract identity from disclosure submission (parsed = raw JSON, result = validated)
591
+ const ownerName = parsed.owner_name
592
+ || extractNameFromPersonality(result.manifest?.personality_notes)
593
+ || process.env.USER
594
+ || 'Agent Owner';
595
+
596
+ const agentName = args.flags.name
597
+ || parsed.agent_name
598
+ || config.getAgent().name
599
+ || process.env.A2A_AGENT_NAME
600
+ || `${ownerName}'s Agent`;
601
+
602
+ // Save identity to config
603
+ config.setAgent({ name: agentName, owner_name: ownerName });
604
+
566
605
  const hostname = config.getAgent().hostname || process.env.A2A_HOSTNAME || 'localhost';
567
- if (args.flags.name) config.setAgent({ name: agentName });
568
606
 
569
607
  const publicTopics = getTierTopics(tiersData.public);
570
608
 
571
609
  const { token } = store.create({
572
610
  name: agentName,
573
- owner: agentName,
611
+ owner: ownerName,
574
612
  permissions: 'public',
575
613
  disclosure: 'minimal',
576
614
  expires: 'never',
577
615
  maxCalls: null,
578
616
  allowedTopics: publicTopics,
579
- allowedGoals: ['grow-network', 'find-collaborators', 'build-in-public'],
617
+ allowedGoals: tokenGoals,
580
618
  notify: 'all'
581
619
  });
582
620
 
@@ -1537,20 +1575,83 @@ a2a add "${inviteUrl}" "${ownerText || 'friend'}" && a2a call "${ownerText || 'f
1537
1575
 
1538
1576
  status: async (args) => {
1539
1577
  const url = args._[1];
1540
- if (!url) {
1541
- console.error('Usage: a2a status <invite_url>');
1542
- process.exit(1);
1578
+
1579
+ // If a URL is provided, check that remote agent's status
1580
+ if (url) {
1581
+ const client = new A2AClient();
1582
+ try {
1583
+ const status = await client.status(url);
1584
+ console.log(`A2A status for ${url}:\n`);
1585
+ console.log(JSON.stringify(status, null, 2));
1586
+ } catch (err) {
1587
+ console.error(`❌ Failed to get status: ${err.message}`);
1588
+ process.exit(1);
1589
+ }
1590
+ return;
1543
1591
  }
1544
1592
 
1545
- const client = new A2AClient();
1546
- try {
1547
- const status = await client.status(url);
1548
- console.log(`A2A status for ${url}:\n`);
1549
- console.log(JSON.stringify(status, null, 2));
1550
- } catch (err) {
1551
- console.error(`❌ Failed to get status: ${err.message}`);
1552
- process.exit(1);
1593
+ // No URL show local server status
1594
+ const { A2AConfig } = require('../src/lib/config');
1595
+ const config = new A2AConfig();
1596
+ const onboarding = config.getOnboarding();
1597
+ const agent = config.getAgent();
1598
+
1599
+ console.log('A2A Local Status\n');
1600
+
1601
+ // Onboarding state
1602
+ const onboarded = onboarding.version === 2 && onboarding.step === 'complete';
1603
+ console.log(` Onboarding: ${onboarded ? '✅ Complete' : `⚠️ ${onboarding.step || 'not started'} (run: a2a quickstart)`}`);
1604
+ console.log(` Agent name: ${agent.name || '(not set)'}`);
1605
+ console.log(` Hostname: ${agent.hostname || '(not set)'}`);
1606
+
1607
+ // Check if server is running
1608
+ const preferred = [];
1609
+ if (onboarding.server_port) preferred.push(onboarding.server_port);
1610
+ const port = await findLocalServerPort(preferred);
1611
+ if (port) {
1612
+ console.log(` Server: ✅ Running on port ${port}`);
1613
+
1614
+ // Fetch dashboard status for more detail
1615
+ const http = require('http');
1616
+ try {
1617
+ const statusData = await new Promise((resolve, reject) => {
1618
+ const req = http.request({
1619
+ hostname: '127.0.0.1', port,
1620
+ path: '/api/a2a/dashboard/status',
1621
+ method: 'GET', timeout: 2000
1622
+ }, (res) => {
1623
+ let body = '';
1624
+ res.on('data', c => body += c);
1625
+ res.on('end', () => {
1626
+ try { resolve(JSON.parse(body)); } catch (e) { reject(e); }
1627
+ });
1628
+ });
1629
+ req.on('error', reject);
1630
+ req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
1631
+ req.end();
1632
+ });
1633
+
1634
+ if (statusData.agent) {
1635
+ if (statusData.agent.owner_name) console.log(` Owner: ${statusData.agent.owner_name}`);
1636
+ }
1637
+ if (statusData.invite_host) {
1638
+ console.log(` Invite host: ${statusData.invite_host}`);
1639
+ }
1640
+ if (statusData.warnings && statusData.warnings.length) {
1641
+ console.log('');
1642
+ for (const w of statusData.warnings) {
1643
+ console.log(` ⚠️ ${w}`);
1644
+ }
1645
+ }
1646
+ } catch (_) {
1647
+ // Dashboard status unavailable — that's fine, we already showed port
1648
+ }
1649
+ } else {
1650
+ console.log(' Server: ❌ Not running');
1651
+ console.log(' Start with: a2a server --port 3001');
1553
1652
  }
1653
+
1654
+ console.log(`\n Tip: a2a status <invite_url> to check a remote agent`);
1554
1655
  },
1555
1656
 
1556
1657
  config: (args) => {
@@ -1945,6 +2046,9 @@ a2a add "${inviteUrl}" "${ownerText || 'friend'}" && a2a call "${ownerText || 'f
1945
2046
  // User chose 'continue' on non-standard port — brief reminder
1946
2047
  console.log(`\n ⚠ Running on port ${serverPort} (non-standard).`);
1947
2048
  console.log(` Invite hostname: ${publicHost}`);
2049
+ console.log('');
2050
+ console.log(' ⚠️ Remote agents using your invite URL will try port 80 by default.');
2051
+ console.log(' Without a reverse proxy, inbound calls on port 80 will fail silently.');
1948
2052
  console.log(`\n To set up a reverse proxy later:`);
1949
2053
  console.log(` a2a config --hostname ${externalIp}`);
1950
2054
  console.log(` Then configure nginx/caddy to proxy port 80 → ${serverPort}.`);
@@ -1952,6 +2056,45 @@ a2a add "${inviteUrl}" "${ownerText || 'friend'}" && a2a call "${ownerText || 'f
1952
2056
 
1953
2057
  const verifyUrl = `http://${publicHost}/api/a2a/ping`;
1954
2058
  console.log(`\n Verify: curl -s ${verifyUrl}`);
2059
+
2060
+ // Fix 6: Actually run the connectivity check
2061
+ const http = require('http');
2062
+ const verifyOk = await new Promise(resolve => {
2063
+ const req = http.request({
2064
+ hostname: '127.0.0.1',
2065
+ port: serverPort,
2066
+ path: '/api/a2a/ping',
2067
+ method: 'GET',
2068
+ timeout: 2000
2069
+ }, (res) => {
2070
+ res.resume();
2071
+ resolve(res.statusCode === 200);
2072
+ });
2073
+ req.on('error', () => resolve(false));
2074
+ req.on('timeout', () => { req.destroy(); resolve(false); });
2075
+ req.end();
2076
+ });
2077
+
2078
+ if (verifyOk) {
2079
+ console.log(' ✅ Local connectivity verified');
2080
+ } else {
2081
+ console.log(' ⚠️ Local server check failed — server may still be starting');
2082
+ }
2083
+
2084
+ // Fix 7: Surface invite-host warnings during quickstart
2085
+ try {
2086
+ const { resolveInviteHost } = require('../src/lib/invite-host');
2087
+ const resolved = await resolveInviteHost({
2088
+ hostname: publicHost,
2089
+ port: serverPort
2090
+ });
2091
+ if (resolved.warnings && resolved.warnings.length) {
2092
+ console.log('\n ━━━ Network Warnings ━━━');
2093
+ for (const w of resolved.warnings) {
2094
+ console.warn(` ⚠️ ${w}`);
2095
+ }
2096
+ }
2097
+ } catch (_) {}
1955
2098
  }
1956
2099
 
1957
2100
  // Save server config and advance onboarding state to awaiting_disclosure.
@@ -2513,6 +2656,13 @@ Examples:
2513
2656
 
2514
2657
  // Main
2515
2658
  const args = parseArgs(process.argv);
2659
+
2660
+ // Handle --version flag before command dispatch (standard CLI convention)
2661
+ if (args.flags.version || args.flags.v) {
2662
+ commands.version();
2663
+ process.exit(0);
2664
+ }
2665
+
2516
2666
  const command = args._[0] || 'help';
2517
2667
 
2518
2668
  if (!commands[command]) {
@@ -0,0 +1,246 @@
1
+ # Bugfix Plan: A2A-22 + A2A-24
2
+
3
+ > **For Claude:** Execute these fixes on branch `fix/bugs-22-23-24` in `/root/a2acalling`
4
+
5
+ **Goal:** Fix quickstart onboarding gaps (identity, disclosure levels, goals) and surface network warnings during quickstart.
6
+
7
+ **A2A-23 is already fixed** in commit d3fe14e. This plan covers A2A-22 and A2A-24 only.
8
+
9
+ ---
10
+
11
+ ## Bug A2A-22: Quickstart onboarding gaps
12
+
13
+ ### Fix 1: Disclosure levels inverted on friends/family tiers
14
+
15
+ **File:** `bin/cli.js` ~line 546-553 (inside `handleDisclosureSubmit`)
16
+
17
+ **Problem:** Friends and family tiers are set to `disclosure: 'minimal'` when they should be more open than public.
18
+
19
+ **Fix:** Change disclosure levels to escalate: public→'minimal', friends→'standard', family→'full'.
20
+
21
+ ```javascript
22
+ // BEFORE (broken):
23
+ config.setTier('public', { topics: ..., disclosure: 'public' });
24
+ config.setTier('friends', { topics: ..., disclosure: 'minimal' });
25
+ config.setTier('family', { topics: ..., disclosure: 'minimal' });
26
+
27
+ // AFTER (fixed):
28
+ config.setTier('public', { topics: ..., disclosure: 'minimal' });
29
+ config.setTier('friends', { topics: ..., disclosure: 'standard' });
30
+ config.setTier('family', { topics: ..., disclosure: 'full' });
31
+ ```
32
+
33
+ **Rationale:** Public tier should be most restrictive (minimal). Friends get more (standard). Family gets full disclosure. This matches the trust hierarchy.
34
+
35
+ ### Fix 2: Extract identity from disclosure submission
36
+
37
+ **File:** `bin/cli.js` ~line 564-568 (inside `handleDisclosureSubmit`)
38
+
39
+ **Problem:** Agent name defaults to 'my-agent', owner name is set to agent name. The disclosure manifest often contains the real owner name in `personality_notes` or the submission JSON, but it's never extracted.
40
+
41
+ **Fix:** After parsing the disclosure JSON, extract identity fields:
42
+ 1. Check if the submission JSON has `agent_name` or `owner_name` fields (the extraction prompt should request these)
43
+ 2. Fall back to extracting from `personality_notes` (often contains "I'm [Name]" or similar)
44
+ 3. Fall back to the OS username
45
+ 4. Use extracted values for config and token creation
46
+
47
+ In `handleDisclosureSubmit`, after line ~527 where `result` is validated, add:
48
+
49
+ ```javascript
50
+ // Extract identity from disclosure submission
51
+ const ownerName = result.owner_name
52
+ || result.manifest?.owner_name
53
+ || extractNameFromPersonality(result.manifest?.personality_notes)
54
+ || process.env.USER
55
+ || 'Agent Owner';
56
+
57
+ const agentName = args.flags.name
58
+ || result.agent_name
59
+ || config.getAgent().name
60
+ || process.env.A2A_AGENT_NAME
61
+ || `${ownerName}'s Agent`;
62
+
63
+ // Save identity to config
64
+ config.setAgent({ name: agentName, owner_name: ownerName });
65
+ ```
66
+
67
+ Add a helper function (before `handleDisclosureSubmit`):
68
+
69
+ ```javascript
70
+ function extractNameFromPersonality(notes) {
71
+ if (!notes || typeof notes !== 'string') return null;
72
+ // Look for patterns like "I'm Ben", "My name is Ben", "Owner: Ben"
73
+ const patterns = [
74
+ /(?:I'm|I am|My name is|Name:|Owner:)\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)/,
75
+ /^([A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)\s+(?:is|here|speaking)/
76
+ ];
77
+ for (const p of patterns) {
78
+ const m = notes.match(p);
79
+ if (m && m[1]) return m[1].trim();
80
+ }
81
+ return null;
82
+ }
83
+ ```
84
+
85
+ ### Fix 3: Use extracted identity in token creation
86
+
87
+ **File:** `bin/cli.js` ~line 572-582
88
+
89
+ **Problem:** Token uses `agentName` as both name and owner, with hardcoded goals.
90
+
91
+ **Fix:** Use the extracted `ownerName` for the token owner field:
92
+
93
+ ```javascript
94
+ // BEFORE:
95
+ const { token } = store.create({
96
+ name: agentName,
97
+ owner: agentName, // wrong — should be owner name
98
+ ...
99
+ });
100
+
101
+ // AFTER:
102
+ const { token } = store.create({
103
+ name: agentName,
104
+ owner: ownerName,
105
+ ...
106
+ });
107
+ ```
108
+
109
+ ### Fix 4: Sync disclosure objectives to token goals
110
+
111
+ **File:** `bin/cli.js` ~line 579
112
+
113
+ **Problem:** Token goals are hardcoded `['grow-network', 'find-collaborators', 'build-in-public']` instead of derived from disclosure.
114
+
115
+ **Fix:** Extract objectives from the disclosure manifest and use them as goals:
116
+
117
+ ```javascript
118
+ // Extract goals from disclosure objectives
119
+ const disclosureObjectives = (result.manifest?.objectives || [])
120
+ .map(o => typeof o === 'string' ? o : (o && o.objective || ''))
121
+ .map(s => s.trim().toLowerCase().replace(/\s+/g, '-').slice(0, 60))
122
+ .filter(Boolean);
123
+
124
+ const tokenGoals = disclosureObjectives.length > 0
125
+ ? disclosureObjectives.slice(0, 5)
126
+ : ['grow-network', 'find-collaborators', 'build-in-public'];
127
+ ```
128
+
129
+ Then use `tokenGoals` in the store.create call:
130
+ ```javascript
131
+ allowedGoals: tokenGoals,
132
+ ```
133
+
134
+ Also sync goals to tier config:
135
+ ```javascript
136
+ config.setTier('public', {
137
+ topics: getTierTopics(tiersData.public),
138
+ goals: tokenGoals,
139
+ disclosure: 'minimal'
140
+ });
141
+ ```
142
+
143
+ ### Fix 5: Update extraction prompt to request identity fields
144
+
145
+ **File:** `src/lib/disclosure.js` — the `buildExtractionPrompt` function
146
+
147
+ **Problem:** The extraction prompt tells the agent what to scan but doesn't ask for `owner_name` or `agent_name` fields in the JSON output.
148
+
149
+ **Fix:** Add `owner_name` and `agent_name` to the required JSON schema in the prompt. Look for the JSON structure section and add:
150
+
151
+ ```
152
+ "owner_name": "The human owner's real name (extracted from USER.md, git config, etc.)",
153
+ "agent_name": "The agent's display name (extracted from USER.md or workspace context)",
154
+ ```
155
+
156
+ ---
157
+
158
+ ## Bug A2A-24: Surface network warnings during quickstart
159
+
160
+ ### Fix 6: Add connectivity check after server start
161
+
162
+ **File:** `bin/cli.js` — quickstart command, after server start (~line 2017-2019)
163
+
164
+ **Problem:** The verify URL is printed but never executed. No connectivity feedback.
165
+
166
+ **Fix:** After the server starts and the verify URL is printed, actually run the connectivity check:
167
+
168
+ ```javascript
169
+ // After line: console.log(`\n Verify: curl -s ${verifyUrl}`);
170
+ // Add actual verification:
171
+ const http = require('http');
172
+ const verifyOk = await new Promise(resolve => {
173
+ const req = http.request({
174
+ hostname: '127.0.0.1',
175
+ port: serverPort,
176
+ path: '/api/a2a/ping',
177
+ method: 'GET',
178
+ timeout: 2000
179
+ }, (res) => {
180
+ res.resume();
181
+ resolve(res.statusCode === 200);
182
+ });
183
+ req.on('error', () => resolve(false));
184
+ req.on('timeout', () => { req.destroy(); resolve(false); });
185
+ req.end();
186
+ });
187
+
188
+ if (verifyOk) {
189
+ console.log(' ✅ Local connectivity verified');
190
+ } else {
191
+ console.log(' ⚠️ Local server check failed — server may still be starting');
192
+ }
193
+ ```
194
+
195
+ ### Fix 7: Surface invite-host warnings during quickstart
196
+
197
+ **File:** `bin/cli.js` — quickstart command, after server start
198
+
199
+ **Problem:** `resolveInviteHost()` returns warnings about NAT/firewall, but they're only shown during `a2a create`, not during quickstart.
200
+
201
+ **Fix:** After `publicHost` is set in quickstart, call `resolveInviteHost` and print its warnings:
202
+
203
+ ```javascript
204
+ // After publicHost is determined, resolve and show warnings
205
+ try {
206
+ const { resolveInviteHost } = require('../src/lib/invite-host');
207
+ const resolved = await resolveInviteHost({
208
+ hostname: publicHost,
209
+ port: serverPort
210
+ });
211
+ if (resolved.warnings && resolved.warnings.length) {
212
+ console.log('\n ━━━ Network Warnings ━━━');
213
+ for (const w of resolved.warnings) {
214
+ console.warn(` ⚠️ ${w}`);
215
+ }
216
+ }
217
+ } catch (_) {}
218
+ ```
219
+
220
+ ### Fix 8: Add non-standard port warning with reverse proxy guidance
221
+
222
+ **File:** `bin/cli.js` — quickstart, already partially handled
223
+
224
+ **Assessment:** Looking at the code at lines 2008-2014, this is already partially implemented — when the user chooses 'continue' on a non-standard port, it prints a brief reminder about reverse proxy setup. But it could be more prominent.
225
+
226
+ **Fix:** Enhance the existing warning. After the "Running on port X (non-standard)" message, add:
227
+
228
+ ```javascript
229
+ console.log('');
230
+ console.log(' ⚠️ Remote agents using your invite URL will try port 80 by default.');
231
+ console.log(' Without a reverse proxy, inbound calls on port 80 will fail silently.');
232
+ ```
233
+
234
+ ---
235
+
236
+ ## Commit Strategy
237
+
238
+ 1. First commit: A2A-22 fixes (disclosure levels, identity extraction, goals sync)
239
+ 2. Second commit: A2A-24 fixes (connectivity check, network warnings)
240
+ 3. Run `npm test` after each commit to verify no regressions
241
+
242
+ ## Testing
243
+
244
+ After all fixes:
245
+ - `npm test` — all 269+ tests must pass
246
+ - Manual verification of the logic changes (the reviewer should trace through the code paths)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "a2acalling",
3
- "version": "0.6.45",
3
+ "version": "0.6.46",
4
4
  "description": "Agent-to-agent calling for OpenClaw - A2A agent communication",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -608,6 +608,8 @@ Use ALL available context to build a reasonable disclosure profile. If truly not
608
608
 
609
609
  const jsonBlock = `\`\`\`json
610
610
  {
611
+ "owner_name": "The human owner's real name (extracted from USER.md, git config, etc.)",
612
+ "agent_name": "The agent's display name (extracted from USER.md or workspace context)",
611
613
  "tiers": {
612
614
  "public": {
613
615
  "topics": [