a2acalling 0.6.57 → 0.6.59

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.
@@ -4,6 +4,10 @@
4
4
  --ink: #13233a;
5
5
  --line: #d7dee6;
6
6
  --accent: #1466c1;
7
+
8
+ /* Map app vars to Shoelace design tokens */
9
+ --sl-color-primary-600: var(--accent);
10
+ --sl-font-sans: "IBM Plex Sans", "Segoe UI", sans-serif;
7
11
  }
8
12
 
9
13
  * {
@@ -12,7 +16,7 @@
12
16
 
13
17
  body {
14
18
  margin: 0;
15
- font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
19
+ font-family: var(--sl-font-sans);
16
20
  color: var(--ink);
17
21
  background: linear-gradient(180deg, #eef3f8 0%, var(--bg) 100%);
18
22
  }
@@ -34,44 +38,10 @@ header p {
34
38
  font-size: 0.9rem;
35
39
  }
36
40
 
37
- nav {
38
- display: flex;
39
- gap: 0.5rem;
40
- padding: 0.75rem 1rem;
41
- border-bottom: 1px solid var(--line);
42
- background: #f8fafc;
43
- }
44
-
45
- .tab {
46
- border: 1px solid var(--line);
47
- background: #fff;
48
- color: var(--ink);
49
- padding: 0.45rem 0.7rem;
50
- border-radius: 8px;
51
- cursor: pointer;
52
- }
53
-
54
- .tab.is-active {
55
- border-color: var(--accent);
56
- color: var(--accent);
57
- }
58
-
59
41
  main {
60
42
  padding: 1rem;
61
43
  }
62
44
 
63
- .panel {
64
- display: none;
65
- background: var(--panel);
66
- border: 1px solid var(--line);
67
- border-radius: 10px;
68
- padding: 1rem;
69
- }
70
-
71
- .panel.is-active {
72
- display: block;
73
- }
74
-
75
45
  h2,
76
46
  h3 {
77
47
  margin-top: 0;
@@ -92,66 +62,38 @@ h3 {
92
62
  margin-bottom: 0.9rem;
93
63
  }
94
64
 
95
- .card {
96
- border: 1px solid var(--line);
97
- border-radius: 10px;
98
- padding: 0.75rem 0.8rem;
99
- background: #fbfdff;
65
+ /* Shoelace sl-card spacing */
66
+ sl-card {
100
67
  margin-bottom: 0.9rem;
68
+ --padding: 0.75rem 0.8rem;
101
69
  }
102
70
 
103
- label {
104
- display: block;
105
- margin-bottom: 0.6rem;
106
- font-size: 0.9rem;
107
- }
108
-
109
- input,
110
- textarea,
111
- select,
112
- button {
113
- font: inherit;
114
- }
115
-
116
- input,
117
- textarea,
118
- select {
119
- width: 100%;
120
- border: 1px solid var(--line);
121
- border-radius: 8px;
122
- padding: 0.45rem 0.55rem;
123
- background: #fff;
124
- }
125
-
126
- textarea {
127
- resize: vertical;
128
- }
129
-
130
- button {
131
- border: 1px solid var(--line);
132
- border-radius: 8px;
133
- background: #fff;
134
- padding: 0.42rem 0.65rem;
135
- cursor: pointer;
71
+ sl-card::part(base) {
72
+ background: #fbfdff;
73
+ border-color: var(--line);
136
74
  }
137
75
 
138
- button:hover {
139
- border-color: var(--accent);
140
- color: var(--accent);
76
+ /* Shoelace sl-details spacing */
77
+ sl-details {
78
+ margin-bottom: 0.9rem;
141
79
  }
142
80
 
143
- .btn-link {
144
- border: none;
145
- background: none;
146
- padding: 0;
147
- color: var(--accent);
148
- cursor: pointer;
81
+ /* Shoelace sl-input, sl-textarea, sl-select spacing */
82
+ sl-input,
83
+ sl-textarea,
84
+ sl-select,
85
+ sl-checkbox {
86
+ margin-bottom: 0.6rem;
149
87
  }
150
88
 
151
- .btn-link:hover {
152
- text-decoration: underline;
89
+ /* Labels for inline wrapping around sl-select */
90
+ label {
91
+ display: block;
92
+ margin-bottom: 0.6rem;
93
+ font-size: 0.9rem;
153
94
  }
154
95
 
96
+ /* Tables */
155
97
  table {
156
98
  width: 100%;
157
99
  border-collapse: collapse;
@@ -175,11 +117,34 @@ tr[data-selected="1"] td {
175
117
  background: #f0f7ff;
176
118
  }
177
119
 
120
+ /* Summary & transcript */
178
121
  .summary {
179
122
  max-width: 500px;
180
123
  white-space: pre-wrap;
181
124
  }
182
125
 
126
+ .summary-pending {
127
+ color: #6b7280;
128
+ margin: 0.25em 0 0.75em;
129
+ }
130
+
131
+ .transcript-details {
132
+ margin-top: 1em;
133
+ }
134
+
135
+ .transcript {
136
+ max-width: 600px;
137
+ white-space: pre-wrap;
138
+ background: #f9fafb;
139
+ border: 1px solid #e5e7eb;
140
+ border-radius: 4px;
141
+ padding: 0.75em;
142
+ margin-top: 0.5em;
143
+ font-size: 0.85em;
144
+ max-height: 400px;
145
+ overflow-y: auto;
146
+ }
147
+
183
148
  .mono {
184
149
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
185
150
  }
@@ -202,35 +167,261 @@ tr[data-selected="1"] td {
202
167
  display: none;
203
168
  }
204
169
 
205
- .status-pill {
206
- display: inline-block;
207
- padding: 0.15rem 0.45rem;
208
- border-radius: 999px;
170
+ /* Pin button for last-called contacts (sl-icon-button) */
171
+ .pin-btn {
172
+ font-size: 1rem;
173
+ color: #b0bec5;
174
+ }
175
+
176
+ .pin-btn:hover {
177
+ color: var(--accent);
178
+ }
179
+
180
+ .pin-btn.pinned {
181
+ color: var(--accent);
182
+ }
183
+
184
+ /* Shoelace tab group customization */
185
+ sl-tab-group {
186
+ --indicator-color: var(--accent);
187
+ }
188
+
189
+ sl-tab-group::part(base) {
190
+ background: var(--panel);
209
191
  border: 1px solid var(--line);
210
- font-size: 0.78rem;
211
- font-weight: 600;
192
+ border-radius: 10px;
212
193
  }
213
194
 
214
- .status-pill.ok {
215
- background: #ecfdf3;
216
- border-color: #9bd8b8;
217
- color: #125934;
195
+ sl-tab-group::part(nav) {
196
+ border-bottom: 1px solid var(--line);
197
+ background: #f8fafc;
218
198
  }
219
199
 
220
- .status-pill.warn {
221
- background: #fff7e8;
222
- border-color: #f1d08e;
223
- color: #8a5a00;
200
+ sl-tab-panel::part(base) {
201
+ padding: 1rem;
224
202
  }
225
203
 
226
- .status-pill.err {
227
- background: #fff0f1;
228
- border-color: #efb1b6;
229
- color: #8c1d26;
204
+ /* ── A2A-41: Topic/Goal list rows ──────────────────────────── */
205
+ .topic-row {
206
+ display: flex;
207
+ align-items: flex-start;
208
+ gap: 0.5rem;
209
+ padding: 0.5rem 0.6rem;
210
+ border: 1px solid var(--line);
211
+ border-radius: 6px;
212
+ margin-bottom: 0.4rem;
213
+ background: var(--panel);
214
+ cursor: grab;
215
+ transition: box-shadow 0.15s ease, border-color 0.15s ease;
216
+ }
217
+
218
+ .topic-row:hover {
219
+ border-color: var(--accent);
220
+ box-shadow: 0 1px 4px rgba(20, 102, 193, 0.1);
221
+ }
222
+
223
+ .topic-row.dragging {
224
+ opacity: 0.5;
225
+ border-style: dashed;
226
+ }
227
+
228
+ .topic-row.inherited {
229
+ opacity: 0.55;
230
+ background: #f8fafc;
231
+ cursor: default;
232
+ border-style: dashed;
233
+ border-color: #c5cfd9;
234
+ }
235
+
236
+ .topic-row.inherited .drag-handle,
237
+ .topic-row.inherited .topic-delete-btn {
238
+ display: none;
239
+ }
240
+
241
+ .drag-handle {
242
+ color: #9ca8b5;
243
+ cursor: grab;
244
+ user-select: none;
245
+ font-size: 1.1rem;
246
+ line-height: 1;
247
+ padding-top: 2px;
248
+ }
249
+
250
+ .topic-content {
251
+ flex: 1;
252
+ min-width: 0;
253
+ }
254
+
255
+ .topic-header {
256
+ display: flex;
257
+ align-items: center;
258
+ gap: 0.4rem;
259
+ }
260
+
261
+ .topic-label {
262
+ flex: 1;
263
+ font-size: 0.9rem;
264
+ }
265
+
266
+ .topic-description {
267
+ margin-top: 0.35rem;
268
+ padding-left: 0.1rem;
269
+ }
270
+
271
+ .topic-desc-text {
272
+ font-size: 0.82rem;
273
+ color: #4b5d73;
274
+ margin: 0 0 0.3rem;
275
+ line-height: 1.4;
276
+ }
277
+
278
+ .inherited-badge {
279
+ font-size: 0.72rem;
280
+ color: #7a8da0;
281
+ font-style: italic;
282
+ margin-left: auto;
283
+ }
284
+
285
+ .add-item-btn {
286
+ width: 100%;
287
+ margin-top: 0.3rem;
288
+ border: 1px dashed var(--line);
289
+ border-radius: 6px;
290
+ padding: 0.4rem;
291
+ background: transparent;
292
+ color: #7a8da0;
293
+ cursor: pointer;
294
+ font-size: 0.85rem;
295
+ transition: border-color 0.15s, color 0.15s;
296
+ }
297
+
298
+ .add-item-btn:hover {
299
+ border-color: var(--accent);
300
+ color: var(--accent);
301
+ }
302
+
303
+ /* ── A2A-41: Tools checklist ───────────────────────────────── */
304
+ .tools-checklist {
305
+ display: flex;
306
+ flex-direction: column;
307
+ gap: 0.35rem;
308
+ }
309
+
310
+ .tools-checklist sl-checkbox {
311
+ margin-bottom: 0;
312
+ }
313
+
314
+ .tools-checklist sl-checkbox::part(label) {
315
+ font-size: 0.88rem;
316
+ }
317
+
318
+ .tool-desc {
319
+ color: #4b5d73;
320
+ font-weight: normal;
321
+ }
322
+
323
+ /* ── A2A-41: Three-column tier layout ──────────────────────── */
324
+ .tier-columns {
325
+ display: grid;
326
+ grid-template-columns: 1fr 1fr 1fr;
327
+ gap: 0.8rem;
328
+ margin-bottom: 1.2rem;
329
+ }
330
+
331
+ .tier-column {
332
+ border: 1px solid var(--line);
333
+ border-radius: 8px;
334
+ padding: 0.6rem;
335
+ background: #fbfdff;
336
+ min-height: 120px;
337
+ }
338
+
339
+ .tier-column h4 {
340
+ margin: 0 0 0.5rem;
341
+ font-size: 0.9rem;
342
+ color: var(--ink);
343
+ padding-bottom: 0.4rem;
344
+ border-bottom: 1px solid var(--line);
345
+ }
346
+
347
+ .tier-drop-zone {
348
+ min-height: 60px;
349
+ transition: background 0.15s ease;
350
+ border-radius: 4px;
351
+ padding: 0.2rem;
352
+ }
353
+
354
+ .tier-drop-zone.drag-over {
355
+ background: rgba(20, 102, 193, 0.06);
356
+ outline: 2px dashed var(--accent);
357
+ outline-offset: -2px;
358
+ }
359
+
360
+ /* ── A2A-41: Validation warnings ───────────────────────────── */
361
+ .tier-warnings {
362
+ margin-bottom: 0.8rem;
363
+ }
364
+
365
+ .tier-warning {
366
+ display: flex;
367
+ align-items: center;
368
+ gap: 0.5rem;
369
+ padding: 0.45rem 0.7rem;
370
+ border-radius: 6px;
371
+ font-size: 0.82rem;
372
+ margin-bottom: 0.3rem;
373
+ }
374
+
375
+ .tier-warning.warn {
376
+ background: #fef3c7;
377
+ border: 1px solid #f59e0b;
378
+ color: #92400e;
379
+ }
380
+
381
+ .tier-warning.danger {
382
+ background: #fee2e2;
383
+ border: 1px solid #ef4444;
384
+ color: #991b1b;
385
+ }
386
+
387
+ .tier-warning.info {
388
+ background: #dbeafe;
389
+ border: 1px solid #3b82f6;
390
+ color: #1e40af;
391
+ }
392
+
393
+ /* ── A2A-41: Preview dialog ────────────────────────────────── */
394
+ #preview-content h4 {
395
+ font-size: 0.88rem;
396
+ margin: 0.8rem 0 0.3rem;
397
+ color: var(--ink);
398
+ }
399
+
400
+ #preview-content h4:first-child {
401
+ margin-top: 0;
402
+ }
403
+
404
+ #preview-content ul {
405
+ margin: 0;
406
+ padding-left: 1.2rem;
407
+ }
408
+
409
+ #preview-content li {
410
+ font-size: 0.85rem;
411
+ margin-bottom: 0.25rem;
412
+ line-height: 1.4;
413
+ }
414
+
415
+ #preview-content li strong {
416
+ color: var(--ink);
230
417
  }
231
418
 
232
419
  @media (max-width: 720px) {
233
- nav {
420
+ sl-tab-group::part(nav) {
234
421
  overflow-x: auto;
235
422
  }
423
+ /* A2A-41: collapse three-column layout to single column on narrow screens */
424
+ .tier-columns {
425
+ grid-template-columns: 1fr;
426
+ }
236
427
  }
package/src/lib/config.js CHANGED
@@ -106,17 +106,8 @@ function validateTierPatch(tierName, tierConfig) {
106
106
  out.description = sanitizeString(tierConfig.description, 300);
107
107
  }
108
108
 
109
- if (tierConfig.disclosure !== undefined) {
110
- if (typeof tierConfig.disclosure !== 'string') {
111
- throw configValidationError(
112
- 'A2A_CONFIG_INVALID_TIER_DISCLOSURE',
113
- `Invalid tier disclosure for "${tierName}": expected string`,
114
- null,
115
- { tier: tierName, received_type: typeof tierConfig.disclosure }
116
- );
117
- }
118
- out.disclosure = sanitizeString(tierConfig.disclosure, 40) || 'minimal';
119
- }
109
+ // A2A-41: disclosure field intentionally removed. If present in input,
110
+ // it is silently ignored for backward compatibility with older clients.
120
111
 
121
112
  if (tierConfig.capabilities !== undefined) {
122
113
  out.capabilities = validateStringArray(tierConfig.capabilities, `${tierName}.capabilities`, {
@@ -183,6 +174,9 @@ const DEFAULT_CONFIG = {
183
174
  },
184
175
 
185
176
  // Permission tiers
177
+ // A2A-41: disclosure field removed from tiers. It was intended to control
178
+ // HOW info is shared (freely/minimally/none) but was never consumed by
179
+ // prompt templates. The disclosure SYSTEM (disclosure.js, manifest) remains.
186
180
  tiers: {
187
181
  public: {
188
182
  name: 'Public',
@@ -191,7 +185,6 @@ const DEFAULT_CONFIG = {
191
185
  allowed_tools: ['Read', 'Grep', 'Glob'],
192
186
  topics: ['chat'],
193
187
  goals: [],
194
- disclosure: 'minimal',
195
188
  examples: ['calendar availability', 'public social posts', 'general questions']
196
189
  },
197
190
  friends: {
@@ -201,7 +194,6 @@ const DEFAULT_CONFIG = {
201
194
  allowed_tools: ['Bash(readonly)', 'Read', 'Grep', 'Glob', 'WebSearch', 'WebFetch'],
202
195
  topics: ['chat', 'search', 'openclaw', 'a2a'],
203
196
  goals: [],
204
- disclosure: 'public',
205
197
  examples: ['email summaries', 'schedule meetings', 'project discussions']
206
198
  },
207
199
  family: {
@@ -211,7 +203,6 @@ const DEFAULT_CONFIG = {
211
203
  allowed_tools: ['Bash', 'Read', 'Grep', 'Glob', 'WebSearch', 'WebFetch'],
212
204
  topics: ['chat', 'search', 'openclaw', 'a2a', 'tools', 'memory'],
213
205
  goals: [],
214
- disclosure: 'public',
215
206
  examples: ['deep collaboration', 'private project context', 'personal notes']
216
207
  },
217
208
  custom: {
@@ -221,7 +212,6 @@ const DEFAULT_CONFIG = {
221
212
  allowed_tools: ['Read', 'Grep', 'Glob'],
222
213
  topics: [],
223
214
  goals: [],
224
- disclosure: 'minimal',
225
215
  examples: []
226
216
  }
227
217
  },
package/src/lib/tokens.js CHANGED
@@ -189,12 +189,14 @@ class TokenStore {
189
189
  * - Timeout: 5-300 seconds (enforced server-side)
190
190
  */
191
191
  create(options = {}) {
192
+ // A2A-41: disclosure field removed from tokens. Was never used by
193
+ // prompt generation. Existing tokens with disclosure field are harmless
194
+ // — the field is simply not read.
192
195
  const {
193
196
  name = 'unnamed',
194
197
  owner = null,
195
198
  expires = '1d',
196
199
  permissions = 'public',
197
- disclosure = 'minimal',
198
200
  notify = 'all',
199
201
  maxCalls = 100, // Default limit, not unlimited
200
202
  capabilities = null, // Array of capability strings, snapshotted at creation
@@ -273,7 +275,6 @@ class TokenStore {
273
275
  allowed_tools: allowedTools || defaultTools[tier] || TokenStore.DEFAULT_ALLOWED_TOOLS.public,
274
276
  timeout_ms: parsePositiveTimeoutMs(timeoutMs),
275
277
  tier_settings: tierSettings || {}, // Snapshot of settings at creation
276
- disclosure,
277
278
  notify,
278
279
  max_calls: maxCalls,
279
280
  calls_made: 0,
@@ -359,7 +360,6 @@ class TokenStore {
359
360
  allowed_tools: record.allowed_tools || TokenStore.DEFAULT_ALLOWED_TOOLS[tier] || TokenStore.DEFAULT_ALLOWED_TOOLS.public,
360
361
  timeout_ms: timeoutMs,
361
362
  tier_settings: record.tier_settings || {},
362
- disclosure: record.disclosure,
363
363
  notify: record.notify,
364
364
  calls_remaining: record.max_calls ? record.max_calls - record.calls_made : null
365
365
  };
package/src/routes/a2a.js CHANGED
@@ -350,7 +350,6 @@ function createRoutes(options = {}) {
350
350
  allowed_goals: validation.allowed_goals,
351
351
  allowed_tools: validation.allowed_tools,
352
352
  timeout_ms: validation.timeout_ms,
353
- disclosure: validation.disclosure,
354
353
  caller: sanitizedCaller,
355
354
  conversation_id: conversation_id || `conv_${Date.now()}_${crypto.randomBytes(6).toString('hex')}`,
356
355
  trace_id: traceId,
@@ -1276,7 +1276,6 @@ function createDashboardApiRouter(options = {}) {
1276
1276
  allowed_tools: sanitizeStringArray(configTier.allowed_tools || [], 30, 80),
1277
1277
  topics: sanitizeStringArray(configTier.topics || []),
1278
1278
  goals: sanitizeStringArray(configTier.goals || []),
1279
- disclosure: configTier.disclosure || 'minimal',
1280
1279
  examples: sanitizeStringArray(configTier.examples || [], 20, 120),
1281
1280
  manifest: {
1282
1281
  topics: manifestTier.topics || [],
@@ -1310,7 +1309,6 @@ function createDashboardApiRouter(options = {}) {
1310
1309
  const update = {};
1311
1310
  if (body.name !== undefined) update.name = sanitizeString(body.name, 120);
1312
1311
  if (body.description !== undefined) update.description = sanitizeString(body.description, 300);
1313
- if (body.disclosure !== undefined) update.disclosure = sanitizeString(body.disclosure, 40) || 'minimal';
1314
1312
  if (body.capabilities !== undefined) update.capabilities = sanitizeStringArray(body.capabilities, 100, 120);
1315
1313
  if (body.allowed_tools !== undefined) update.allowed_tools = sanitizeStringArray(body.allowed_tools, 30, 80);
1316
1314
  if (body.examples !== undefined) update.examples = sanitizeStringArray(body.examples, 20, 120);
@@ -1375,7 +1373,6 @@ function createDashboardApiRouter(options = {}) {
1375
1373
  allowed_tools: sanitizeStringArray(body.allowed_tools || [], 30, 80),
1376
1374
  topics: sanitizeStringArray(body.topics || []),
1377
1375
  goals: sanitizeStringArray(body.goals || []),
1378
- disclosure: sanitizeString(body.disclosure || 'minimal', 40),
1379
1376
  examples: sanitizeStringArray(body.examples || [], 20, 120)
1380
1377
  });
1381
1378
  } catch (err) {
@@ -1,26 +0,0 @@
1
- ---
2
- description: Call another A2A agent — starts a multi-turn conversation
3
- allowed-tools: [Bash, Read]
4
- argument-hint: <contact-or-url> <message>
5
- ---
6
-
7
- Call an A2A agent. This starts a multi-turn agent-to-agent conversation.
8
-
9
- ## Usage
10
-
11
- ```
12
- /a2a-call Alice "Hello! My owner wants to discuss the project."
13
- /a2a-call a2a://host.com/fed_abc123 "Reaching out about collaboration"
14
- ```
15
-
16
- ## Instructions
17
-
18
- Run the following command with the user's arguments:
19
-
20
- ```bash
21
- a2a call $ARGUMENTS
22
- ```
23
-
24
- If the call succeeds, summarize the conversation outcome for the user.
25
- If it fails with "not onboarded", tell the user to run `/a2a-setup` first.
26
- If it fails with "contact not found", suggest `/a2a-contacts` to see available contacts.
@@ -1,31 +0,0 @@
1
- ---
2
- description: List A2A contacts — agents you can call or who can call you
3
- allowed-tools: [Bash]
4
- argument-hint: [add|show|ping|rm] [args...]
5
- ---
6
-
7
- Manage your A2A contact list — see who you can call and who has access to you.
8
-
9
- ## Usage
10
-
11
- ```
12
- /a2a-contacts # list all contacts
13
- /a2a-contacts add a2a://host/fed_xxx Alice # add contact from invite URL
14
- /a2a-contacts show Alice # show contact details
15
- /a2a-contacts ping Alice # check if contact is online
16
- /a2a-contacts rm Alice # remove a contact
17
- ```
18
-
19
- ## Instructions
20
-
21
- Run the appropriate command based on user input:
22
-
23
- - No arguments: `a2a contacts`
24
- - `add`: `a2a contacts add $ARGUMENTS`
25
- - `show`: `a2a contacts show $ARGUMENTS`
26
- - `ping`: `a2a contacts ping $ARGUMENTS`
27
- - `rm`: `a2a contacts rm $ARGUMENTS`
28
-
29
- If the user just wants to see their contacts, also run `a2a list` to show active tokens (outbound invites).
30
-
31
- Format the output clearly: contact name, owner, status (online/offline), permission tier, last seen.
@@ -1,33 +0,0 @@
1
- ---
2
- description: Create an A2A invite token to share with another agent
3
- allowed-tools: [Bash]
4
- argument-hint: [name] [--tier public|friends|family] [--expires 7d]
5
- ---
6
-
7
- Create an A2A federation token and display the invite URL for sharing.
8
-
9
- ## Usage
10
-
11
- ```
12
- /a2a-invite Alice --tier friends --expires 7d
13
- /a2a-invite "Bob's Agent" --tier public
14
- /a2a-invite # interactive — uses defaults
15
- ```
16
-
17
- ## Instructions
18
-
19
- Parse the user's arguments and run:
20
-
21
- ```bash
22
- a2a create $ARGUMENTS
23
- ```
24
-
25
- If no arguments provided, run `a2a create` with no flags (interactive mode).
26
-
27
- After success, display the invite URL prominently and explain:
28
- 1. The URL format: `a2a://<hostname>/<token>`
29
- 2. Share this URL with the other agent's owner
30
- 3. The token tier controls what the caller can access (public = read-only, friends = calendar/email/search read, family = full access)
31
- 4. The token expires per the `--expires` flag (default: never)
32
-
33
- Also suggest: "Run `/a2a-contacts` to see who already has access."