a2acalling 0.6.48 → 0.6.50

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.
@@ -0,0 +1,368 @@
1
+ # E2E Test Agent Prompt Sequence
2
+
3
+ Run a full end-to-end validation of `a2acalling` in an isolated environment. Execute each step sequentially. If a step fails, log the failure and continue to the next step unless the failure is blocking (steps 1-3 block all subsequent steps).
4
+
5
+ ## Prerequisites
6
+
7
+ Before starting, verify:
8
+
9
+ - **Node.js** >= 18.0.0 (`node --version`)
10
+ - **npm** available (`npm --version`)
11
+ - **Ports** 3100-3199 range available (used by test servers)
12
+ - **Disk** at least 200MB free in system temp directory
13
+ - **Network** localhost connections allowed (no firewall blocking loopback)
14
+
15
+ If any prerequisite is missing, report it as a step-0 failure and abort.
16
+
17
+ ## Output Format
18
+
19
+ Report each step as a JSON object:
20
+
21
+ ```json
22
+ {
23
+ "step": 1,
24
+ "name": "install",
25
+ "status": "pass",
26
+ "duration_ms": 4230,
27
+ "error": null
28
+ }
29
+ ```
30
+
31
+ On completion, return a JSON array of all step results:
32
+
33
+ ```json
34
+ {
35
+ "run_id": "e2e_<timestamp>",
36
+ "started_at": "<ISO-8601>",
37
+ "finished_at": "<ISO-8601>",
38
+ "total_steps": 9,
39
+ "passed": 8,
40
+ "failed": 1,
41
+ "results": [ ... ]
42
+ }
43
+ ```
44
+
45
+ ---
46
+
47
+ ## Step 1: Install from npm
48
+
49
+ **What to do:**
50
+
51
+ ```bash
52
+ WORKDIR=$(mktemp -d -t a2a-e2e-XXXXXX)
53
+ cd "$WORKDIR"
54
+ npm init -y --silent
55
+ npm install a2acalling
56
+ ```
57
+
58
+ **Expected outcome:**
59
+ - Exit code 0 from `npm install`
60
+ - `node_modules/a2acalling/bin/cli.js` exists
61
+ - `node_modules/a2acalling/src/server.js` exists
62
+
63
+ **Failure:**
64
+ - If npm install fails, record the stderr and abort all remaining steps (blocking).
65
+
66
+ **Variables to carry forward:**
67
+ - `WORKDIR` -- root temp directory
68
+ - `CLI` = `$WORKDIR/node_modules/.bin/a2a`
69
+ - `A2A_CONFIG_DIR` = `$WORKDIR/config` (create this directory)
70
+
71
+ ---
72
+
73
+ ## Step 2: Run Quickstart Onboarding
74
+
75
+ **What to do:**
76
+
77
+ ```bash
78
+ export A2A_CONFIG_DIR="$WORKDIR/config"
79
+ export CI=true
80
+ mkdir -p "$A2A_CONFIG_DIR"
81
+ ```
82
+
83
+ First, write a minimal config to simulate port detection completing:
84
+
85
+ ```bash
86
+ cat > "$A2A_CONFIG_DIR/a2a-config.json" << 'CONF'
87
+ {
88
+ "onboarding": { "version": 2, "step": "awaiting_disclosure", "server_port": 3100 },
89
+ "agent": { "hostname": "localhost:3100", "name": "e2e-test-agent" },
90
+ "tiers": {}
91
+ }
92
+ CONF
93
+ ```
94
+
95
+ Then submit the disclosure manifest:
96
+
97
+ ```bash
98
+ node "$CLI" onboard --submit '{
99
+ "tiers": {
100
+ "public": {
101
+ "topics": [{"topic": "General", "description": "Open discussion"}],
102
+ "objectives": [],
103
+ "do_not_discuss": []
104
+ },
105
+ "friends": {
106
+ "topics": [{"topic": "Projects", "description": "Current work"}],
107
+ "objectives": [],
108
+ "do_not_discuss": []
109
+ },
110
+ "family": {
111
+ "topics": [{"topic": "Everything", "description": "Full access"}],
112
+ "objectives": [],
113
+ "do_not_discuss": []
114
+ }
115
+ },
116
+ "never_disclose": ["passwords", "api-keys"],
117
+ "personality_notes": "E2E test agent. Direct and minimal responses."
118
+ }'
119
+ ```
120
+
121
+ **Expected outcome:**
122
+ - Exit code 0
123
+ - stdout contains "Onboarding complete"
124
+ - `$A2A_CONFIG_DIR/a2a-config.json` has `onboarding.step` set to `"complete"`
125
+
126
+ **Failure:**
127
+ - If onboarding fails, record stdout/stderr and abort (blocking).
128
+
129
+ ---
130
+
131
+ ## Step 3: Verify Server Health
132
+
133
+ **What to do:**
134
+
135
+ Start the server, then check health endpoints:
136
+
137
+ ```bash
138
+ node "$WORKDIR/node_modules/a2acalling/src/server.js" &
139
+ SERVER_PID=$!
140
+ sleep 2
141
+ ```
142
+
143
+ Check ping:
144
+
145
+ ```bash
146
+ curl -s http://localhost:3100/api/a2a/ping
147
+ ```
148
+
149
+ Check status:
150
+
151
+ ```bash
152
+ curl -s http://localhost:3100/api/a2a/status
153
+ ```
154
+
155
+ **Expected outcome:**
156
+ - Ping returns `{"pong": true, "timestamp": "..."}` with HTTP 200
157
+ - Status returns JSON with `"a2a": true` and a `"version"` field
158
+ - Server process is running (PID exists)
159
+
160
+ **Failure:**
161
+ - If server does not start or endpoints do not respond within 10 seconds, abort (blocking).
162
+
163
+ **Variables to carry forward:**
164
+ - `SERVER_PID`
165
+ - `BASE_URL` = `http://localhost:3100`
166
+
167
+ ---
168
+
169
+ ## Step 4: Create Invite Token
170
+
171
+ **What to do:**
172
+
173
+ ```bash
174
+ node "$CLI" create --name "E2E-Caller" --tier public --expires 1h
175
+ ```
176
+
177
+ Parse the output to extract the token and invite URL.
178
+
179
+ **Expected outcome:**
180
+ - Exit code 0
181
+ - Output contains an invite URL matching `a2a://localhost:3100/fed_...`
182
+ - Output contains a token matching `fed_[A-Za-z0-9_-]+`
183
+
184
+ **Variables to carry forward:**
185
+ - `TOKEN` -- the `fed_...` string
186
+ - `INVITE_URL` -- the full `a2a://...` URL
187
+
188
+ ---
189
+
190
+ ## Step 5: Test Inbound Call
191
+
192
+ **What to do:**
193
+
194
+ ```bash
195
+ curl -s -X POST "$BASE_URL/api/a2a/invoke" \
196
+ -H "Authorization: Bearer $TOKEN" \
197
+ -H "Content-Type: application/json" \
198
+ -d '{
199
+ "message": "Hello from E2E test",
200
+ "caller": {
201
+ "name": "E2E Test Runner",
202
+ "instance": "localhost",
203
+ "context": "Automated E2E validation"
204
+ }
205
+ }'
206
+ ```
207
+
208
+ **Expected outcome:**
209
+ - HTTP 200
210
+ - Response JSON has `"success": true`
211
+ - Response JSON has a `"conversation_id"` starting with `conv_`
212
+ - Response JSON has a non-empty `"response"` field
213
+ - Response JSON has `"can_continue": true`
214
+
215
+ ---
216
+
217
+ ## Step 6: Test Multi-turn Conversation
218
+
219
+ **What to do:**
220
+
221
+ Use the `conversation_id` from step 5. Make two follow-up calls:
222
+
223
+ Call 1:
224
+ ```bash
225
+ curl -s -X POST "$BASE_URL/api/a2a/invoke" \
226
+ -H "Authorization: Bearer $TOKEN" \
227
+ -H "Content-Type: application/json" \
228
+ -d "{
229
+ \"message\": \"Follow-up question 1\",
230
+ \"conversation_id\": \"$CONVERSATION_ID\"
231
+ }"
232
+ ```
233
+
234
+ Call 2:
235
+ ```bash
236
+ curl -s -X POST "$BASE_URL/api/a2a/invoke" \
237
+ -H "Authorization: Bearer $TOKEN" \
238
+ -H "Content-Type: application/json" \
239
+ -d "{
240
+ \"message\": \"Follow-up question 2\",
241
+ \"conversation_id\": \"$CONVERSATION_ID\"
242
+ }"
243
+ ```
244
+
245
+ **Expected outcome:**
246
+ - Both calls return HTTP 200 with `"success": true`
247
+ - Both return the same `conversation_id` as the original
248
+ - Both have non-empty `"response"` fields
249
+
250
+ ---
251
+
252
+ ## Step 7: Test Error Cases
253
+
254
+ Run three negative tests:
255
+
256
+ ### 7a: No auth header
257
+
258
+ ```bash
259
+ curl -s -o /dev/null -w "%{http_code}" -X POST "$BASE_URL/api/a2a/invoke" \
260
+ -H "Content-Type: application/json" \
261
+ -d '{"message": "no auth"}'
262
+ ```
263
+
264
+ **Expected:** HTTP 401, response body has `"error": "missing_token"`
265
+
266
+ ### 7b: Bad token
267
+
268
+ ```bash
269
+ curl -s -X POST "$BASE_URL/api/a2a/invoke" \
270
+ -H "Authorization: Bearer fed_invalid_token_value" \
271
+ -H "Content-Type: application/json" \
272
+ -d '{"message": "bad token"}'
273
+ ```
274
+
275
+ **Expected:** HTTP 401 or 403, response body has `"success": false`
276
+
277
+ ### 7c: Missing message body
278
+
279
+ ```bash
280
+ curl -s -X POST "$BASE_URL/api/a2a/invoke" \
281
+ -H "Authorization: Bearer $TOKEN" \
282
+ -H "Content-Type: application/json" \
283
+ -d '{}'
284
+ ```
285
+
286
+ **Expected:** HTTP 400, response body has `"error": "missing_message"`
287
+
288
+ **Step passes only if all three sub-tests pass.** Report sub-test details in the error field if any fail.
289
+
290
+ ---
291
+
292
+ ## Step 8: Test Token Revocation
293
+
294
+ **What to do:**
295
+
296
+ First, find the token ID:
297
+
298
+ ```bash
299
+ node "$CLI" list
300
+ ```
301
+
302
+ Parse the token ID (`tok_...`) from the output, then revoke it:
303
+
304
+ ```bash
305
+ node "$CLI" revoke "$TOKEN_ID"
306
+ ```
307
+
308
+ Then attempt a call with the revoked token:
309
+
310
+ ```bash
311
+ curl -s -X POST "$BASE_URL/api/a2a/invoke" \
312
+ -H "Authorization: Bearer $TOKEN" \
313
+ -H "Content-Type: application/json" \
314
+ -d '{"message": "should fail"}'
315
+ ```
316
+
317
+ **Expected outcome:**
318
+ - Revoke command exits 0
319
+ - Post-revocation call returns `"success": false`
320
+ - Error field is `"token_revoked"` or similar auth error
321
+
322
+ ---
323
+
324
+ ## Step 9: Cleanup
325
+
326
+ **What to do:**
327
+
328
+ ```bash
329
+ kill $SERVER_PID 2>/dev/null
330
+ wait $SERVER_PID 2>/dev/null
331
+
332
+ rm -rf "$WORKDIR"
333
+ ```
334
+
335
+ Verify:
336
+ ```bash
337
+ ! kill -0 $SERVER_PID 2>/dev/null # process is gone
338
+ [ ! -d "$WORKDIR" ] # directory is gone
339
+ ```
340
+
341
+ **Expected outcome:**
342
+ - Server process is terminated
343
+ - Temp directory is removed
344
+ - No orphaned processes on port 3100
345
+
346
+ **This step always passes unless cleanup throws an unexpected error.**
347
+
348
+ ---
349
+
350
+ ## Failure Reporting
351
+
352
+ When a step fails, generate a Linear bug report payload:
353
+
354
+ ```json
355
+ {
356
+ "title": "E2E: Step <N> (<name>) failed",
357
+ "description": "## Failure\n\n<error message>\n\n## Reproduction\n\nRun `node test/e2e/orchestrate.js --verbose`\n\n## Environment\n\n- Node: <version>\n- npm: <version>\n- OS: <platform>\n- a2acalling: <version>",
358
+ "priority": 2,
359
+ "labels": ["bug", "e2e"],
360
+ "team": "ENG"
361
+ }
362
+ ```
363
+
364
+ Priority mapping:
365
+ - Steps 1-3 (blocking): priority 1 (Urgent)
366
+ - Steps 4-6 (core flow): priority 2 (High)
367
+ - Steps 7-8 (error handling): priority 3 (Normal)
368
+ - Step 9 (cleanup): priority 4 (Low)
package/docs/protocol.md CHANGED
@@ -309,6 +309,127 @@ a2a_call({
309
309
  }
310
310
  ```
311
311
 
312
+ ## E2E Testing
313
+
314
+ ### Architecture
315
+
316
+ E2E tests run in fully isolated environments. Each test gets its own temp directory, config directory (`A2A_CONFIG_DIR`), and ephemeral port. No shared state between tests; no pollution of the host system.
317
+
318
+ Core components:
319
+
320
+ | Component | File | Purpose |
321
+ |-----------|------|---------|
322
+ | Environment | `test/e2e/env.js` | Creates isolated temp dir, config dir, port finder, cleanup |
323
+ | Two-server harness | `test/e2e/two-server.js` | Spins up two independent A2A servers for cross-agent tests |
324
+ | CLI runner | `test/e2e/cli-runner.js` | Wraps `bin/cli.js` as child process with structured output |
325
+ | Agent prompt | `docs/prompts/e2e-test-agent.md` | 9-step prompt sequence for Claude subagent validation |
326
+
327
+ ### Test Categories
328
+
329
+ - **Infrastructure** (`env.test.js`) -- Isolated environments, port allocation, cleanup
330
+ - **CLI integration** (`cli-runner.test.js`) -- Command execution, onboarding, exit codes, timeouts
331
+ - **Cross-agent flow** (`two-server.test.js`) -- Two servers, token isolation, ping/invoke across instances
332
+ - **Error handling** -- Bad tokens, missing auth, revoked tokens, malformed requests
333
+ - **Summary validation** -- Prompt correctness across `server.js`, `openclaw-integration.js`, `claude-subagent.js` paths
334
+
335
+ ### Entry Points
336
+
337
+ ```bash
338
+ # Run all tests (unit + integration + E2E)
339
+ node test/run.js
340
+
341
+ # Run E2E tests only
342
+ node test/run.js --e2e
343
+
344
+ # Standalone orchestrator with verbose or JSON output
345
+ node test/e2e/orchestrate.js [--verbose] [--json]
346
+
347
+ # Default: excludes E2E for faster feedback
348
+ node test/run.js
349
+ ```
350
+
351
+ The `--e2e` flag filters to files under `test/e2e/`. The standalone orchestrator runs the full 9-step sequence from `docs/prompts/e2e-test-agent.md` and outputs structured JSON results.
352
+
353
+ ### File Structure
354
+
355
+ ```
356
+ test/e2e/
357
+ env.js # createE2EEnv() — isolated temp + config + port
358
+ env.test.js # Tests for env isolation and port allocation
359
+ cli-runner.js # CLIRunner class — child-process CLI wrapper
360
+ cli-runner.test.js # Tests for CLI execution, onboarding, timeouts
361
+ two-server.js # TwoServerHarness — two independent A2A instances
362
+ two-server.test.js # Tests for cross-agent token isolation and ping
363
+ orchestrate.js # Standalone E2E orchestrator (planned)
364
+ ```
365
+
366
+ ### Adding New E2E Tests
367
+
368
+ 1. Create `test/e2e/<name>.test.js` exporting `function(test, assert, helpers)`.
369
+ 2. Use `createE2EEnv()` for isolation. Always call `env.cleanup()` in a finally block.
370
+ 3. Use `TwoServerHarness` if you need two agents. Call `harness.teardown()` after.
371
+ 4. Use `CLIRunner` for CLI interactions. It handles `A2A_CONFIG_DIR` automatically.
372
+ 5. The test runner discovers all `*.test.js` files recursively -- no registration needed.
373
+
374
+ Example skeleton:
375
+
376
+ ```js
377
+ module.exports = function (test, assert, helpers) {
378
+ const { createE2EEnv } = require('./env');
379
+
380
+ test('my new E2E scenario', async () => {
381
+ const env = createE2EEnv('my-test');
382
+ try {
383
+ // ... test logic using env.configDir, env.findAvailablePort()
384
+ } finally {
385
+ env.cleanup();
386
+ }
387
+ });
388
+ };
389
+ ```
390
+
391
+ ## CLI Skills (Claude Code & Codex)
392
+
393
+ A2A ships with slash commands for Claude Code and agent instructions for Codex CLI.
394
+
395
+ ### Installation
396
+
397
+ ```bash
398
+ a2a skills # Install into current project
399
+ a2a skills --check # See what would be installed
400
+ a2a skills --force # Overwrite existing files
401
+ ```
402
+
403
+ Skills are also installed automatically on `npm install -g a2acalling`.
404
+
405
+ ### Claude Code Commands
406
+
407
+ | Command | Description |
408
+ |---------|-------------|
409
+ | `/a2a-call <contact> <msg>` | Call another agent (multi-turn) |
410
+ | `/a2a-invite [name] [--tier]` | Create invite token |
411
+ | `/a2a-contacts [add\|show\|ping\|rm]` | Manage contacts |
412
+ | `/a2a-status` | Server and agent health dashboard |
413
+ | `/a2a-setup` | First-time setup and onboarding |
414
+
415
+ Files installed to: `.claude/commands/a2a-*.md`
416
+
417
+ ### Codex CLI
418
+
419
+ A2A agent instructions are installed to `.codex/AGENTS.md`. Codex reads this file automatically to understand available A2A commands, permission tiers, and workflows.
420
+
421
+ ### Manual Installation
422
+
423
+ If the automatic install didn't work, copy the files manually:
424
+
425
+ ```bash
426
+ # Claude Code commands
427
+ cp node_modules/a2acalling/.claude/commands/a2a-*.md .claude/commands/
428
+
429
+ # Codex instructions
430
+ cp node_modules/a2acalling/.codex/AGENTS.md .codex/AGENTS.md
431
+ ```
432
+
312
433
  ## Future Protocol Extensions (v1+)
313
434
 
314
435
  - **Capability advertisement**: Agents declare what they can help with
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "a2acalling",
3
- "version": "0.6.48",
3
+ "version": "0.6.50",
4
4
  "description": "Agent-to-agent calling for OpenClaw - A2A agent communication",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -0,0 +1,80 @@
1
+ /**
2
+ * A2A Skill Installer
3
+ *
4
+ * Copies Claude Code commands and Codex AGENTS.md into a target project directory.
5
+ * Idempotent: skips files that already exist with identical content.
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ const PACKAGE_ROOT = path.join(__dirname, '..');
12
+
13
+ const SKILL_FILES = [
14
+ { src: '.claude/commands/a2a-call.md', dest: '.claude/commands/a2a-call.md' },
15
+ { src: '.claude/commands/a2a-invite.md', dest: '.claude/commands/a2a-invite.md' },
16
+ { src: '.claude/commands/a2a-contacts.md', dest: '.claude/commands/a2a-contacts.md' },
17
+ { src: '.claude/commands/a2a-status.md', dest: '.claude/commands/a2a-status.md' },
18
+ { src: '.claude/commands/a2a-setup.md', dest: '.claude/commands/a2a-setup.md' },
19
+ { src: '.codex/AGENTS.md', dest: '.codex/AGENTS.md' }
20
+ ];
21
+
22
+ function installSkills(targetDir, options = {}) {
23
+ const result = { installed: [], skipped: [], errors: [] };
24
+
25
+ for (const file of SKILL_FILES) {
26
+ const srcPath = path.join(PACKAGE_ROOT, file.src);
27
+ const destPath = path.join(targetDir, file.dest);
28
+
29
+ try {
30
+ if (!fs.existsSync(srcPath)) {
31
+ result.errors.push({ file: file.src, error: 'Source file not found' });
32
+ continue;
33
+ }
34
+
35
+ const srcContent = fs.readFileSync(srcPath, 'utf8');
36
+
37
+ // Check if identical file already exists
38
+ if (!options.force && fs.existsSync(destPath)) {
39
+ const existing = fs.readFileSync(destPath, 'utf8');
40
+ if (existing === srcContent) {
41
+ result.skipped.push(file.dest);
42
+ continue;
43
+ }
44
+ }
45
+
46
+ // Create directory and write file
47
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
48
+ fs.writeFileSync(destPath, srcContent);
49
+ result.installed.push(file.dest);
50
+ } catch (err) {
51
+ result.errors.push({ file: file.dest, error: err.message });
52
+ }
53
+ }
54
+
55
+ return result;
56
+ }
57
+
58
+ // CLI mode: node scripts/install-skills.js [targetDir] [--force]
59
+ if (require.main === module) {
60
+ const args = process.argv.slice(2);
61
+ const force = args.includes('--force');
62
+ const targetDir = args.find(a => !a.startsWith('-')) || process.cwd();
63
+
64
+ const result = installSkills(targetDir, { force });
65
+
66
+ if (result.installed.length) {
67
+ console.log(`Installed ${result.installed.length} A2A skill file(s):`);
68
+ result.installed.forEach(f => console.log(` + ${f}`));
69
+ }
70
+ if (result.skipped.length) {
71
+ console.log(`Skipped ${result.skipped.length} unchanged file(s)`);
72
+ }
73
+ if (result.errors.length) {
74
+ console.error(`Errors: ${result.errors.length}`);
75
+ result.errors.forEach(e => console.error(` ! ${e.file}: ${e.error}`));
76
+ process.exit(1);
77
+ }
78
+ }
79
+
80
+ module.exports = { installSkills, SKILL_FILES };
@@ -43,13 +43,25 @@ const result = spawnSync(process.execPath, [cliPath, 'quickstart'], {
43
43
 
44
44
  if (result.error) {
45
45
  // Don't fail the install — the agent will get onboarding when it runs `a2a`.
46
+ installSkillFiles();
46
47
  installMacOSApp();
47
48
  process.exit(0);
48
49
  }
49
50
 
51
+ installSkillFiles();
50
52
  installMacOSApp();
51
53
  process.exit(result.status || 0);
52
54
 
55
+ // Best-effort: install Claude Code + Codex skills into the workspace
56
+ function installSkillFiles() {
57
+ try {
58
+ const { installSkills } = require('./install-skills');
59
+ installSkills(initCwd);
60
+ } catch (e) {
61
+ // Silent — skills can be installed later with `a2a skills`
62
+ }
63
+ }
64
+
53
65
  // Download and install the native macOS app from GitHub Releases
54
66
  function installMacOSApp() {
55
67
  const os = require('os');