a2acalling 0.6.15 → 0.6.17
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/SKILL.md +12 -39
- package/bin/cli.js +30 -87
- package/package.json +1 -1
- package/scripts/postinstall.js +17 -88
package/SKILL.md
CHANGED
|
@@ -27,53 +27,26 @@ metadata:
|
|
|
27
27
|
|
|
28
28
|
Enable agent-to-agent communication across OpenClaw instances.
|
|
29
29
|
|
|
30
|
-
##
|
|
30
|
+
## Install & Onboarding
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
```bash
|
|
33
|
+
npm install -g a2acalling
|
|
34
|
+
```
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
- `/a2a gui` / `/a2a dashboard`
|
|
36
|
-
- `/a2a uninstall`
|
|
36
|
+
Onboarding is automatic. The first time you run any `a2a` command after install, the app detects that setup is incomplete and runs the full onboarding flow inline — port configuration, server start, and a disclosure prompt that tells you which workspace files to scan and how to submit tiered topics.
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
To manually trigger onboarding (or re-run it):
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const path = require('path');
|
|
44
|
-
|
|
45
|
-
const dir = process.env.A2A_CONFIG_DIR ||
|
|
46
|
-
process.env.OPENCLAW_CONFIG_DIR ||
|
|
47
|
-
path.join(process.env.HOME || '/tmp', '.config', 'openclaw');
|
|
48
|
-
const file = path.join(dir, 'a2a-config.json');
|
|
49
|
-
|
|
50
|
-
let ok = false;
|
|
51
|
-
try {
|
|
52
|
-
const cfg = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
53
|
-
ok = cfg.onboarding &&
|
|
54
|
-
cfg.onboarding.version === 2 &&
|
|
55
|
-
cfg.onboarding.step === 'complete';
|
|
56
|
-
} catch (e) {}
|
|
57
|
-
|
|
58
|
-
process.stdout.write(ok ? 'onboarded\n' : 'not_onboarded\n');
|
|
59
|
-
NODE
|
|
41
|
+
a2a quickstart # run onboarding
|
|
42
|
+
a2a quickstart --force # re-run from scratch
|
|
60
43
|
```
|
|
61
44
|
|
|
62
|
-
|
|
63
|
-
- DO NOT process the requested command yet
|
|
64
|
-
- Tell the user: "A2A needs initial setup. Let me configure your agent's disclosure topics first."
|
|
65
|
-
- Run the deterministic Quickstart flow below to completion (flags-based, step-by-step)
|
|
66
|
-
- Only THEN proceed with the user's original command
|
|
67
|
-
|
|
68
|
-
**If it prints `onboarded`:** proceed normally.
|
|
45
|
+
After the onboarding walkthrough prints, scan your workspace files (USER.md, SOUL.md, etc.), build the disclosure JSON, and submit:
|
|
69
46
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
- **Memory:** memory/*.md files
|
|
74
|
-
- **Project:** CLAUDE.md
|
|
75
|
-
|
|
76
|
-
Extract: professional context, interests, goals, skills, sensitive areas. Group them into Public/Friends/Family tiers based on sensitivity.
|
|
47
|
+
```bash
|
|
48
|
+
a2a quickstart --submit '<json>'
|
|
49
|
+
```
|
|
77
50
|
|
|
78
51
|
## Network Ingress (Internet-Facing Invites)
|
|
79
52
|
|
package/bin/cli.js
CHANGED
|
@@ -70,29 +70,28 @@ function getConvStore() {
|
|
|
70
70
|
|
|
71
71
|
const store = new TokenStore();
|
|
72
72
|
|
|
73
|
+
// ── enforceOnboarding ────────────────────────────────────────────────────
|
|
74
|
+
// If onboarding is incomplete or the config is missing/invalid, run the
|
|
75
|
+
// full quickstart flow inline — verbose, with direct stdio. The agent sees
|
|
76
|
+
// the banner, port selection, server start, and disclosure prompt right here.
|
|
77
|
+
//
|
|
78
|
+
// This is the primary onboarding entry point for agents. npm postinstall
|
|
79
|
+
// silently starts the server (npm captures its output), so the first time
|
|
80
|
+
// the agent runs ANY `a2a` command, this function fires and gives the agent
|
|
81
|
+
// the full verbose walkthrough it needs to complete setup.
|
|
82
|
+
//
|
|
83
|
+
// Returns a Promise if quickstart needs to run (caller must await), or
|
|
84
|
+
// undefined if onboarding is already complete.
|
|
73
85
|
function enforceOnboarding(command) {
|
|
74
86
|
if (ONBOARDING_EXEMPT.has(command)) {
|
|
75
87
|
return;
|
|
76
88
|
}
|
|
77
89
|
|
|
78
90
|
if (!isOnboarded()) {
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
console.log('\nA2A setup in progress. Disclosure topics not yet submitted.\n');
|
|
84
|
-
console.log("Next: run `a2a quickstart --submit '<json>'` (or `a2a onboard --submit`)\n");
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
87
|
-
} catch (e) {
|
|
88
|
-
if (e.code !== 'ENOENT' && e.name !== 'SyntaxError') {
|
|
89
|
-
console.error(`Warning: could not read config: ${e.message}`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
console.log('\nA2A not configured yet.\n');
|
|
94
|
-
console.log('Next: run `a2a quickstart`\n');
|
|
95
|
-
process.exit(1);
|
|
91
|
+
// Run the full quickstart flow inline — verbose output, direct stdio.
|
|
92
|
+
// This replaces the original command; after onboarding the agent can
|
|
93
|
+
// re-run their intended command.
|
|
94
|
+
return commands.quickstart({ flags: {}, positional: [] });
|
|
96
95
|
}
|
|
97
96
|
}
|
|
98
97
|
|
|
@@ -265,70 +264,6 @@ function printSection(title) {
|
|
|
265
264
|
console.log('\n━━━ ' + title + ' ━━━');
|
|
266
265
|
}
|
|
267
266
|
|
|
268
|
-
function readWorkspaceContext(baseDir = process.cwd()) {
|
|
269
|
-
const base = baseDir || process.cwd();
|
|
270
|
-
const workspaceFiles = {
|
|
271
|
-
USER: { filename: 'USER.md' },
|
|
272
|
-
SOUL: { filename: 'SOUL.md' },
|
|
273
|
-
HEARTBEAT: { filename: 'HEARTBEAT.md' },
|
|
274
|
-
SKILL: { filename: 'SKILL.md' },
|
|
275
|
-
CLAUDE: { filename: 'CLAUDE.md' }
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
const found = {};
|
|
279
|
-
for (const key of Object.keys(workspaceFiles)) {
|
|
280
|
-
found[key] = fs.existsSync(path.join(base, workspaceFiles[key].filename));
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const memoryDir = path.join(base, 'memory');
|
|
284
|
-
let memoryCount = 0;
|
|
285
|
-
if (fs.existsSync(memoryDir)) {
|
|
286
|
-
try {
|
|
287
|
-
memoryCount = fs.readdirSync(memoryDir).filter(item => item.endsWith('.md')).length;
|
|
288
|
-
} catch (err) {
|
|
289
|
-
memoryCount = 0;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
found.MEMORY = memoryCount;
|
|
293
|
-
|
|
294
|
-
return {
|
|
295
|
-
workspace: base,
|
|
296
|
-
found,
|
|
297
|
-
memoryCount
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
function printWorkspaceScan(context) {
|
|
302
|
-
const fileLabels = {
|
|
303
|
-
USER: 'USER.md — identity hints',
|
|
304
|
-
SOUL: 'SOUL.md — personality notes',
|
|
305
|
-
HEARTBEAT: 'HEARTBEAT.md — scheduled tasks',
|
|
306
|
-
SKILL: 'SKILL.md — capabilities',
|
|
307
|
-
CLAUDE: 'CLAUDE.md — agent instructions'
|
|
308
|
-
};
|
|
309
|
-
const found = Object.entries(fileLabels)
|
|
310
|
-
.filter(([key]) => context.found[key])
|
|
311
|
-
.map(([, label]) => label);
|
|
312
|
-
if (context.memoryCount > 0) found.push(`${context.memoryCount} memory file(s)`);
|
|
313
|
-
|
|
314
|
-
if (found.length) {
|
|
315
|
-
console.log(`Workspace context: ${found.join(', ')}`);
|
|
316
|
-
} else {
|
|
317
|
-
console.log('Workspace context: no context files found (disclosure topics will be minimal)');
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
function getDisclosurePromptFiles(context) {
|
|
322
|
-
return {
|
|
323
|
-
'USER.md': context.found.USER,
|
|
324
|
-
'SOUL.md': context.found.SOUL,
|
|
325
|
-
'HEARTBEAT.md': context.found.HEARTBEAT,
|
|
326
|
-
'SKILL.md': context.found.SKILL,
|
|
327
|
-
'CLAUDE.md': context.found.CLAUDE,
|
|
328
|
-
'memory/*.md': context.memoryCount > 0
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
|
|
332
267
|
async function inspectPorts(preferredPort = null) {
|
|
333
268
|
const candidates = [];
|
|
334
269
|
if (preferredPort) {
|
|
@@ -1854,13 +1789,21 @@ if (!commands[command]) {
|
|
|
1854
1789
|
process.exit(1);
|
|
1855
1790
|
}
|
|
1856
1791
|
|
|
1857
|
-
enforceOnboarding
|
|
1858
|
-
|
|
1859
|
-
//
|
|
1860
|
-
const
|
|
1861
|
-
if (
|
|
1862
|
-
|
|
1792
|
+
// If onboarding is incomplete, enforceOnboarding runs quickstart inline
|
|
1793
|
+
// (verbose, full output) and returns a Promise. Otherwise returns undefined
|
|
1794
|
+
// and we proceed to the requested command.
|
|
1795
|
+
const onboardResult = enforceOnboarding(command);
|
|
1796
|
+
if (onboardResult instanceof Promise) {
|
|
1797
|
+
onboardResult.catch(err => {
|
|
1863
1798
|
console.error(err.message);
|
|
1864
1799
|
process.exit(1);
|
|
1865
1800
|
});
|
|
1801
|
+
} else {
|
|
1802
|
+
const result = commands[command](args);
|
|
1803
|
+
if (result instanceof Promise) {
|
|
1804
|
+
result.catch(err => {
|
|
1805
|
+
console.error(err.message);
|
|
1806
|
+
process.exit(1);
|
|
1807
|
+
});
|
|
1808
|
+
}
|
|
1866
1809
|
}
|
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -1,56 +1,37 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// ============================================================================
|
|
4
|
-
// npm postinstall hook —
|
|
4
|
+
// npm postinstall hook — silent server setup
|
|
5
5
|
//
|
|
6
|
-
// OBJECTIVE: When
|
|
7
|
-
//
|
|
8
|
-
//
|
|
6
|
+
// OBJECTIVE: When `npm install -g a2acalling` runs, this script silently
|
|
7
|
+
// starts the A2A server and saves the initial config. No output is produced
|
|
8
|
+
// because npm v7+ captures all lifecycle script stdio.
|
|
9
9
|
//
|
|
10
|
-
//
|
|
10
|
+
// The VERBOSE onboarding (disclosure prompt, workspace scan instructions)
|
|
11
|
+
// happens when the agent runs any `a2a` command — enforceOnboarding detects
|
|
12
|
+
// incomplete setup and runs the full quickstart flow inline with direct stdio.
|
|
11
13
|
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// so the full onboarding completes unattended: port selection, hostname
|
|
16
|
-
// detection, server start, and disclosure prompt output.
|
|
14
|
+
// This split exists because:
|
|
15
|
+
// - npm postinstall: stdio is piped/captured, output is invisible to agents
|
|
16
|
+
// - `a2a <command>`: agent runs directly, stdio is clean, output is visible
|
|
17
17
|
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
// means normal console.log/console.error from postinstall is invisible.
|
|
21
|
-
// To make onboarding output visible:
|
|
22
|
-
//
|
|
23
|
-
// a) Run quickstart with piped stdio, capturing all its output.
|
|
24
|
-
// b) Save the output to a2a-onboarding.txt in the config dir (reliable
|
|
25
|
-
// fallback — agents can always read this file).
|
|
26
|
-
// c) Try /proc/$PPID/fd/2 FIRST (Linux — writes directly to npm's
|
|
27
|
-
// stderr fd, which IS the caller's output stream. Agents capture
|
|
28
|
-
// via pipes, so this is the primary delivery path).
|
|
29
|
-
// d) Try /dev/tty (macOS/non-Linux — interactive terminals).
|
|
30
|
-
// e) Last resort: process.stderr.write (npm may still buffer this).
|
|
31
|
-
//
|
|
32
|
-
// 3. Never fail the install. If quickstart can't launch (e.g. missing node
|
|
33
|
-
// binary edge case), we print a hint and exit 0. A broken postinstall
|
|
34
|
-
// would prevent the package from installing at all.
|
|
35
|
-
//
|
|
36
|
-
// 4. Skip non-global installs, CI, and Docker builds. Local `npm install`
|
|
37
|
-
// in a project shouldn't trigger onboarding. CI and Docker are build
|
|
38
|
-
// environments, not runtime hosts.
|
|
18
|
+
// So postinstall handles the parts that don't need output (server start),
|
|
19
|
+
// and the a2a binary handles the parts that DO need output (disclosure prompt).
|
|
39
20
|
// ============================================================================
|
|
40
21
|
|
|
41
22
|
if (process.env.CI || process.env.CONTINUOUS_INTEGRATION) process.exit(0);
|
|
42
23
|
if (process.env.DOCKER) process.exit(0);
|
|
43
24
|
if (process.env.npm_config_global !== 'true') process.exit(0);
|
|
44
25
|
|
|
45
|
-
const fs = require('fs');
|
|
46
26
|
const path = require('path');
|
|
47
27
|
const { spawnSync } = require('child_process');
|
|
48
28
|
|
|
49
29
|
const initCwd = process.env.INIT_CWD || process.env.HOME || process.cwd();
|
|
50
30
|
const cliPath = path.join(__dirname, '..', 'bin', 'cli.js');
|
|
51
31
|
|
|
52
|
-
// Run quickstart
|
|
53
|
-
//
|
|
32
|
+
// Run quickstart silently — starts server, saves config, detects hostname.
|
|
33
|
+
// All prompts auto-accept defaults (stdin is piped with no input).
|
|
34
|
+
// Output is captured (not shown) because npm would swallow it anyway.
|
|
54
35
|
const result = spawnSync(process.execPath, [cliPath, 'quickstart'], {
|
|
55
36
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
56
37
|
cwd: initCwd,
|
|
@@ -61,60 +42,8 @@ const result = spawnSync(process.execPath, [cliPath, 'quickstart'], {
|
|
|
61
42
|
});
|
|
62
43
|
|
|
63
44
|
if (result.error) {
|
|
64
|
-
|
|
65
|
-
process.
|
|
66
|
-
process.stderr.write('\nRun manually: a2a quickstart\n\n');
|
|
67
|
-
process.exit(0); // don't fail the install
|
|
45
|
+
// Don't fail the install — the agent will get onboarding when it runs `a2a`.
|
|
46
|
+
process.exit(0);
|
|
68
47
|
}
|
|
69
48
|
|
|
70
|
-
const output = (result.stdout || '').toString() + (result.stderr || '').toString();
|
|
71
|
-
|
|
72
|
-
// ── Always save to file (reliable fallback) ──────────────────────────────
|
|
73
|
-
// Agents can read this after install regardless of output visibility.
|
|
74
|
-
try {
|
|
75
|
-
const configDir = process.env.A2A_CONFIG_DIR
|
|
76
|
-
|| path.join(process.env.HOME || '/root', '.config', 'openclaw');
|
|
77
|
-
if (fs.existsSync(configDir)) {
|
|
78
|
-
fs.writeFileSync(path.join(configDir, 'a2a-onboarding.txt'), output);
|
|
79
|
-
}
|
|
80
|
-
} catch (_) {}
|
|
81
|
-
|
|
82
|
-
// ── Make output visible to the caller ────────────────────────────────────
|
|
83
|
-
// npm pipes BOTH stdout and stderr of lifecycle scripts (v7+), so writing
|
|
84
|
-
// to our own fds goes into npm's buffer. We need to bypass npm entirely.
|
|
85
|
-
|
|
86
|
-
function tryWrite(output) {
|
|
87
|
-
// Strategy 1: /proc/$PPID/fd/2 — Linux, write to npm's stderr directly.
|
|
88
|
-
// npm's stderr IS the caller's stderr (the agent's output stream).
|
|
89
|
-
// This bypasses npm's pipe buffer because we open the fd independently.
|
|
90
|
-
// This is tried FIRST because agents are the primary users — their output
|
|
91
|
-
// is captured via pipes, and /dev/tty would send output to a terminal
|
|
92
|
-
// device they can't read from.
|
|
93
|
-
try {
|
|
94
|
-
const fd = fs.openSync(`/proc/${process.ppid}/fd/2`, 'w');
|
|
95
|
-
fs.writeSync(fd, output);
|
|
96
|
-
fs.closeSync(fd);
|
|
97
|
-
return true;
|
|
98
|
-
} catch (_) {}
|
|
99
|
-
|
|
100
|
-
// Strategy 2: /dev/tty — interactive terminals (macOS, non-Linux).
|
|
101
|
-
// Talks directly to the user's terminal, bypassing npm's pipe.
|
|
102
|
-
try {
|
|
103
|
-
const fd = fs.openSync('/dev/tty', 'w');
|
|
104
|
-
fs.writeSync(fd, output);
|
|
105
|
-
fs.closeSync(fd);
|
|
106
|
-
return true;
|
|
107
|
-
} catch (_) {}
|
|
108
|
-
|
|
109
|
-
// Strategy 3: process.stderr — last resort, npm may still buffer this.
|
|
110
|
-
try {
|
|
111
|
-
process.stderr.write(output);
|
|
112
|
-
return true;
|
|
113
|
-
} catch (_) {}
|
|
114
|
-
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
tryWrite(output);
|
|
119
|
-
|
|
120
49
|
process.exit(result.status || 0);
|