a2acalling 0.6.5 → 0.6.7
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/README.md +13 -0
- package/SKILL.md +29 -1
- package/bin/cli.js +453 -521
- package/docs/plans/2026-02-14-agent-driven-disclosure-extraction.md +986 -0
- package/package.json +2 -1
- package/scripts/postinstall.js +9 -0
- package/src/lib/disclosure.js +192 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "a2acalling",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.7",
|
|
4
4
|
"description": "Agent-to-agent calling for OpenClaw - A2A agent communication",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"a2acalling": "scripts/install-openclaw.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
+
"postinstall": "node scripts/postinstall.js",
|
|
11
12
|
"start": "node src/server.js",
|
|
12
13
|
"test": "node test/run.js"
|
|
13
14
|
},
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Only run for global installs; skip in CI, dev, and Docker builds.
|
|
4
|
+
if (process.env.CI || process.env.CONTINUOUS_INTEGRATION) process.exit(0);
|
|
5
|
+
if (process.env.npm_config_global !== 'true') process.exit(0);
|
|
6
|
+
|
|
7
|
+
console.log('\n a2acalling installed successfully.\n');
|
|
8
|
+
console.log(' To get started, run:\n');
|
|
9
|
+
console.log(' a2a quickstart\n');
|
package/src/lib/disclosure.js
CHANGED
|
@@ -17,6 +17,62 @@ const MANIFEST_FILE = path.join(CONFIG_DIR, 'a2a-disclosure.json');
|
|
|
17
17
|
|
|
18
18
|
const TIER_HIERARCHY = ['public', 'friends', 'family'];
|
|
19
19
|
const logger = createLogger({ component: 'a2a.disclosure' });
|
|
20
|
+
const SKIP_FILES = new Set(['heartbeat', 'skill', 'claude']);
|
|
21
|
+
|
|
22
|
+
function normalizeTopic(raw) {
|
|
23
|
+
return String(raw || '').trim();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function dedupeByTopic(items) {
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
const out = [];
|
|
29
|
+
for (const item of items) {
|
|
30
|
+
const topic = normalizeTopic(item && item.topic);
|
|
31
|
+
if (!topic || seen.has(topic.toLowerCase())) continue;
|
|
32
|
+
seen.add(topic.toLowerCase());
|
|
33
|
+
out.push({
|
|
34
|
+
topic,
|
|
35
|
+
detail: normalizeTopic(item && item.detail)
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function parseTopicLine(rawLine) {
|
|
42
|
+
const line = normalizeTopic(rawLine);
|
|
43
|
+
if (!line) return null;
|
|
44
|
+
|
|
45
|
+
const splitPoint = line.search(/\s+[-–—:]\s+/);
|
|
46
|
+
if (splitPoint > 10) {
|
|
47
|
+
const topic = normalizeTopic(line.slice(0, splitPoint));
|
|
48
|
+
const detail = normalizeTopic(line.slice(splitPoint + 3));
|
|
49
|
+
return { topic, detail };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { topic: line, detail: '' };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isValidTopic(line) {
|
|
56
|
+
if (!line || line.length < 5) return false;
|
|
57
|
+
if (line.includes('`')) return false;
|
|
58
|
+
if (line.includes('http')) return false;
|
|
59
|
+
if (line.includes('**:')) return false;
|
|
60
|
+
if (line.startsWith('//')) return false;
|
|
61
|
+
if (line.includes('()')) return false;
|
|
62
|
+
if (/\d{4}-\d{2}-\d{2}/.test(line)) return false;
|
|
63
|
+
if (line.toLowerCase().includes('todo')) return false;
|
|
64
|
+
if (line.toLowerCase().includes('fixme')) return false;
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function truncateAtWordBoundary(text, max = 60) {
|
|
69
|
+
const normalized = normalizeTopic(text);
|
|
70
|
+
if (normalized.length <= max) return normalized;
|
|
71
|
+
|
|
72
|
+
const truncated = normalized.slice(0, max);
|
|
73
|
+
const lastSpace = truncated.lastIndexOf(' ');
|
|
74
|
+
return (lastSpace > 20 ? truncated.slice(0, lastSpace) : truncated) + '...';
|
|
75
|
+
}
|
|
20
76
|
|
|
21
77
|
/**
|
|
22
78
|
* Load manifest from disk. Returns {} if not found.
|
|
@@ -128,8 +184,108 @@ function formatTopicsForPrompt(tierTopics) {
|
|
|
128
184
|
* For proper topic extraction, use buildExtractionPrompt() to instruct
|
|
129
185
|
* an agent, then validate the result with validateDisclosureSubmission().
|
|
130
186
|
*/
|
|
131
|
-
function generateDefaultManifest() {
|
|
187
|
+
function generateDefaultManifest(contextFiles = {}) {
|
|
132
188
|
const now = new Date().toISOString();
|
|
189
|
+
const source = {};
|
|
190
|
+
const raw = contextFiles || {};
|
|
191
|
+
Object.keys(raw).forEach((key) => {
|
|
192
|
+
if (!SKIP_FILES.has(key.toLowerCase())) {
|
|
193
|
+
source[key] = raw[key];
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const userContent = String(source.user || '');
|
|
198
|
+
const soulContent = String(source.soul || '');
|
|
199
|
+
function extractFromSource(content, sectionNames) {
|
|
200
|
+
const sectionPattern = new RegExp(
|
|
201
|
+
`##\\s*(?:${sectionNames.map(name => name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|')})[^\\n]*\\n([\\s\\S]*?)(?=\\n##|$)`,
|
|
202
|
+
'i'
|
|
203
|
+
);
|
|
204
|
+
const match = String(content || '').match(sectionPattern);
|
|
205
|
+
if (!match) {
|
|
206
|
+
return [];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return String(match[1] || '')
|
|
210
|
+
.split('\n')
|
|
211
|
+
.map(line => normalizeTopic(line))
|
|
212
|
+
.filter(line => line.startsWith('-') || line.startsWith('*'))
|
|
213
|
+
.map(line => normalizeTopic(line.replace(/^[\s\-\*]+/, '')))
|
|
214
|
+
.map(parseTopicLine)
|
|
215
|
+
.filter(topic => topic && isValidTopic(topic.topic))
|
|
216
|
+
.map(topic => ({
|
|
217
|
+
topic: truncateAtWordBoundary(topic.topic, 60),
|
|
218
|
+
detail: truncateAtWordBoundary(topic.detail || '', 120)
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const candidateTopics = dedupeByTopic([
|
|
223
|
+
...extractFromSource(userContent, ['Goals', 'Interests', 'Projects', 'Current']),
|
|
224
|
+
...extractFromSource(soulContent, ['Goals', 'Interests', 'Projects', 'Current', 'Values', 'Personal'])
|
|
225
|
+
]);
|
|
226
|
+
|
|
227
|
+
if (candidateTopics.length === 0) {
|
|
228
|
+
return {
|
|
229
|
+
version: 1,
|
|
230
|
+
generated_at: now,
|
|
231
|
+
updated_at: now,
|
|
232
|
+
topics: {
|
|
233
|
+
public: {
|
|
234
|
+
lead_with: [{ topic: 'What I do', detail: 'Brief professional description' }],
|
|
235
|
+
discuss_freely: [{ topic: 'General interests', detail: 'Non-sensitive topics and hobbies' }],
|
|
236
|
+
deflect: [{ topic: 'Personal details', detail: 'Redirect to direct owner contact' }]
|
|
237
|
+
},
|
|
238
|
+
friends: { lead_with: [], discuss_freely: [], deflect: [] },
|
|
239
|
+
family: { lead_with: [], discuss_freely: [], deflect: [] }
|
|
240
|
+
},
|
|
241
|
+
never_disclose: ['API keys', 'Other users\' data', 'Financial figures'],
|
|
242
|
+
personality_notes: 'Direct and technical. Prefers depth over breadth.'
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const publicLead = [];
|
|
247
|
+
const publicDiscuss = [];
|
|
248
|
+
const publicDeflect = [];
|
|
249
|
+
const friendsLead = [];
|
|
250
|
+
const friendsDiscuss = [];
|
|
251
|
+
const familyDiscuss = [];
|
|
252
|
+
|
|
253
|
+
candidateTopics.forEach((entry, index) => {
|
|
254
|
+
const topic = truncateAtWordBoundary(entry.topic || '', 60);
|
|
255
|
+
const detail = truncateAtWordBoundary(entry.detail || 'Open discussion topic.', 120);
|
|
256
|
+
if (!topic) return;
|
|
257
|
+
|
|
258
|
+
const node = { topic, detail };
|
|
259
|
+
if (index < 2) {
|
|
260
|
+
publicLead.push(node);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (index < 6) {
|
|
264
|
+
publicDiscuss.push(node);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (index < 8) {
|
|
268
|
+
friendsLead.push(node);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (index < 12) {
|
|
272
|
+
friendsDiscuss.push(node);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (index < 14) {
|
|
276
|
+
familyDiscuss.push(node);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
if (publicLead.length === 0) {
|
|
281
|
+
publicLead.push({ topic: 'Open source', detail: 'General product and engineering topics.' });
|
|
282
|
+
}
|
|
283
|
+
if (publicDiscuss.length === 0) {
|
|
284
|
+
publicDiscuss.push({ topic: 'Collaboration', detail: 'Ways to collaborate and support each other.' });
|
|
285
|
+
}
|
|
286
|
+
if (publicDeflect.length === 0) {
|
|
287
|
+
publicDeflect.push({ topic: 'Personal details', detail: 'Redirect to direct owner contact.' });
|
|
288
|
+
}
|
|
133
289
|
|
|
134
290
|
return {
|
|
135
291
|
version: 1,
|
|
@@ -137,15 +293,23 @@ function generateDefaultManifest() {
|
|
|
137
293
|
updated_at: now,
|
|
138
294
|
topics: {
|
|
139
295
|
public: {
|
|
140
|
-
lead_with:
|
|
141
|
-
discuss_freely:
|
|
142
|
-
deflect:
|
|
296
|
+
lead_with: publicLead,
|
|
297
|
+
discuss_freely: publicDiscuss,
|
|
298
|
+
deflect: publicDeflect
|
|
299
|
+
},
|
|
300
|
+
friends: {
|
|
301
|
+
lead_with: friendsLead,
|
|
302
|
+
discuss_freely: friendsDiscuss,
|
|
303
|
+
deflect: []
|
|
143
304
|
},
|
|
144
|
-
|
|
145
|
-
|
|
305
|
+
family: {
|
|
306
|
+
lead_with: [],
|
|
307
|
+
discuss_freely: familyDiscuss,
|
|
308
|
+
deflect: []
|
|
309
|
+
}
|
|
146
310
|
},
|
|
147
311
|
never_disclose: ['API keys', 'Other users\' data', 'Financial figures'],
|
|
148
|
-
personality_notes: 'Direct and
|
|
312
|
+
personality_notes: 'Direct and practical. Open to collaboration with clear boundaries.'
|
|
149
313
|
};
|
|
150
314
|
}
|
|
151
315
|
|
|
@@ -336,11 +500,25 @@ function readContextFiles(workspaceDir) {
|
|
|
336
500
|
* @param {Object} [availableFiles] - Map of filename to truthy if present
|
|
337
501
|
* @returns {string} The instruction prompt for the agent
|
|
338
502
|
*/
|
|
339
|
-
function buildExtractionPrompt(availableFiles
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
503
|
+
function buildExtractionPrompt(availableFiles) {
|
|
504
|
+
let fileSection;
|
|
505
|
+
if (availableFiles && Object.keys(availableFiles).length > 0) {
|
|
506
|
+
const fileList = Object.entries(availableFiles)
|
|
507
|
+
.filter(([, present]) => present)
|
|
508
|
+
.map(([name]) => ` - ${name}`)
|
|
509
|
+
.join('\n') || ' (none detected)';
|
|
510
|
+
fileSection = `### Available workspace files\n${fileList}\n\nRead the available files above and extract disclosure topics.`;
|
|
511
|
+
} else {
|
|
512
|
+
fileSection = `### Workspace files to look for
|
|
513
|
+
- USER.md — owner identity, bio, interests
|
|
514
|
+
- SOUL.md — values, personality, communication style
|
|
515
|
+
- HEARTBEAT.md — skip this (contains agent tasks, not disclosure topics)
|
|
516
|
+
- SKILL.md — skip this (contains agent instructions)
|
|
517
|
+
- CLAUDE.md — skip this (contains agent instructions)
|
|
518
|
+
- memory/*.md — may contain relevant context
|
|
519
|
+
|
|
520
|
+
Look for these files in your workspace directory and read the ones that exist. Extract disclosure topics from USER.md and SOUL.md primarily.`;
|
|
521
|
+
}
|
|
344
522
|
|
|
345
523
|
const jsonBlock = '```json\n{\n "topics": {\n "public": {\n "lead_with": [\n { "topic": "Short label (max 160 chars)", "detail": "Longer description of the topic" }\n ],\n "discuss_freely": [],\n "deflect": []\n },\n "friends": {\n "lead_with": [],\n "discuss_freely": [],\n "deflect": []\n },\n "family": {\n "lead_with": [],\n "discuss_freely": [],\n "deflect": []\n }\n },\n "never_disclose": ["API keys", "Credentials", "Financial figures"],\n "personality_notes": "Brief description of communication style"\n}\n```';
|
|
346
524
|
|
|
@@ -348,10 +526,9 @@ function buildExtractionPrompt(availableFiles = {}) {
|
|
|
348
526
|
|
|
349
527
|
You are helping the owner set up their A2A disclosure profile — the topics and information their agent is willing to discuss with other agents at different trust levels.
|
|
350
528
|
|
|
351
|
-
|
|
352
|
-
${fileList}
|
|
529
|
+
${fileSection}
|
|
353
530
|
|
|
354
|
-
|
|
531
|
+
Focus on what the OWNER cares about, works on, and wants to discuss — NOT on agent instructions, code documentation, or operational tasks.
|
|
355
532
|
|
|
356
533
|
### What to extract
|
|
357
534
|
|