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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "a2acalling",
3
- "version": "0.6.5",
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');
@@ -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: [{ topic: 'What I do', detail: 'Brief professional description' }],
141
- discuss_freely: [{ topic: 'General interests', detail: 'Non-sensitive topics and hobbies' }],
142
- deflect: [{ topic: 'Personal details', detail: 'Redirect to direct owner contact' }]
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
- friends: { lead_with: [], discuss_freely: [], deflect: [] },
145
- family: { lead_with: [], discuss_freely: [], deflect: [] }
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 technical. Prefers depth over breadth.'
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
- const fileList = Object.entries(availableFiles)
341
- .filter(([, present]) => present)
342
- .map(([name]) => ` - ${name}`)
343
- .join('\n') || ' (no workspace files detected)';
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
- ### Available workspace files
352
- ${fileList}
529
+ ${fileSection}
353
530
 
354
- Read the available files above and extract disclosure topics. Focus on what the OWNER cares about, works on, and wants to discuss — NOT on agent instructions, code documentation, or operational tasks.
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