a2acalling 0.6.17 → 0.6.19
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/bin/cli.js +48 -19
- package/package.json +1 -1
- package/src/lib/disclosure.js +325 -138
- package/src/routes/dashboard.js +8 -3
- package/src/server.js +31 -17
package/bin/cli.js
CHANGED
|
@@ -335,9 +335,18 @@ async function handleDisclosureSubmit(args, commandLabel = 'onboard') {
|
|
|
335
335
|
|
|
336
336
|
// Sync tier config from manifest
|
|
337
337
|
const manifest = result.manifest;
|
|
338
|
-
|
|
338
|
+
|
|
339
|
+
// Helper to extract topic names from both new and legacy formats
|
|
340
|
+
function getTierTopics(tierData) {
|
|
341
|
+
if (!tierData) return [];
|
|
342
|
+
// New format: topics array
|
|
343
|
+
if (Array.isArray(tierData.topics)) {
|
|
344
|
+
return tierData.topics.map(t => String(t && t.topic || '').trim()).filter(Boolean);
|
|
345
|
+
}
|
|
346
|
+
// Legacy format: lead_with + discuss_freely + deflect
|
|
339
347
|
const out = [];
|
|
340
|
-
for (const section of
|
|
348
|
+
for (const section of [tierData.lead_with, tierData.discuss_freely, tierData.deflect]) {
|
|
349
|
+
if (!Array.isArray(section)) continue;
|
|
341
350
|
for (const item of section) {
|
|
342
351
|
const t = String(item && item.topic || '').trim();
|
|
343
352
|
if (t && !out.includes(t)) out.push(t);
|
|
@@ -346,24 +355,20 @@ async function handleDisclosureSubmit(args, commandLabel = 'onboard') {
|
|
|
346
355
|
return out;
|
|
347
356
|
}
|
|
348
357
|
|
|
358
|
+
// Get tiers data (support both new 'tiers' key and legacy 'topics' key)
|
|
359
|
+
const tiersData = manifest.tiers || manifest.topics || {};
|
|
360
|
+
|
|
349
361
|
try {
|
|
350
362
|
config.setTier('public', {
|
|
351
|
-
topics:
|
|
363
|
+
topics: getTierTopics(tiersData.public),
|
|
352
364
|
disclosure: 'public'
|
|
353
365
|
});
|
|
354
366
|
config.setTier('friends', {
|
|
355
|
-
topics:
|
|
356
|
-
manifest.topics.public.lead_with, manifest.topics.public.discuss_freely, manifest.topics.public.deflect,
|
|
357
|
-
manifest.topics.friends.lead_with, manifest.topics.friends.discuss_freely, manifest.topics.friends.deflect
|
|
358
|
-
]),
|
|
367
|
+
topics: [...getTierTopics(tiersData.public), ...getTierTopics(tiersData.friends)],
|
|
359
368
|
disclosure: 'minimal'
|
|
360
369
|
});
|
|
361
370
|
config.setTier('family', {
|
|
362
|
-
topics:
|
|
363
|
-
manifest.topics.public.lead_with, manifest.topics.public.discuss_freely, manifest.topics.public.deflect,
|
|
364
|
-
manifest.topics.friends.lead_with, manifest.topics.friends.discuss_freely, manifest.topics.friends.deflect,
|
|
365
|
-
manifest.topics.family.lead_with, manifest.topics.family.discuss_freely, manifest.topics.family.deflect
|
|
366
|
-
]),
|
|
371
|
+
topics: [...getTierTopics(tiersData.public), ...getTierTopics(tiersData.friends), ...getTierTopics(tiersData.family)],
|
|
367
372
|
disclosure: 'minimal'
|
|
368
373
|
});
|
|
369
374
|
} catch (err) {
|
|
@@ -382,10 +387,7 @@ async function handleDisclosureSubmit(args, commandLabel = 'onboard') {
|
|
|
382
387
|
const hostname = config.getAgent().hostname || process.env.A2A_HOSTNAME || 'localhost';
|
|
383
388
|
if (args.flags.name) config.setAgent({ name: agentName });
|
|
384
389
|
|
|
385
|
-
const publicTopics =
|
|
386
|
-
manifest.topics.public.lead_with,
|
|
387
|
-
manifest.topics.public.discuss_freely
|
|
388
|
-
]);
|
|
390
|
+
const publicTopics = getTierTopics(tiersData.public);
|
|
389
391
|
|
|
390
392
|
const { token } = store.create({
|
|
391
393
|
name: agentName,
|
|
@@ -1402,10 +1404,37 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
1402
1404
|
if (externalIp) {
|
|
1403
1405
|
const verifyUrl = `http://${publicHost}/api/a2a/ping`;
|
|
1404
1406
|
if (serverPort !== 80) {
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
+
// Check what's using port 80
|
|
1408
|
+
const port80Status = await isPortListening(80, '127.0.0.1', { timeoutMs: 250 });
|
|
1409
|
+
|
|
1410
|
+
console.log(`\n ━━━ Reverse Proxy Setup ━━━`);
|
|
1411
|
+
console.log(` Server running on port ${serverPort}, but external callers expect port 80/443.`);
|
|
1412
|
+
if (port80Status.listening) {
|
|
1413
|
+
console.log(` Port 80: in use (likely nginx, caddy, or another web server)`);
|
|
1414
|
+
} else {
|
|
1415
|
+
console.log(` Port 80: available (but A2A is on ${serverPort})`);
|
|
1416
|
+
}
|
|
1417
|
+
console.log(`\n RECOMMENDED: Configure your web server to proxy A2A requests:\n`);
|
|
1418
|
+
console.log(` For nginx, add to your server block:`);
|
|
1419
|
+
console.log(` location /api/a2a/ {`);
|
|
1420
|
+
console.log(` proxy_pass http://127.0.0.1:${serverPort}/api/a2a/;`);
|
|
1421
|
+
console.log(` proxy_http_version 1.1;`);
|
|
1422
|
+
console.log(` proxy_set_header Host $host;`);
|
|
1423
|
+
console.log(` }`);
|
|
1424
|
+
console.log(`\n For Caddy:`);
|
|
1425
|
+
console.log(` handle /api/a2a/* {`);
|
|
1426
|
+
console.log(` reverse_proxy 127.0.0.1:${serverPort}`);
|
|
1427
|
+
console.log(` }`);
|
|
1428
|
+
console.log(`\n After configuring, update the invite hostname to use port 80:`);
|
|
1429
|
+
console.log(` Current: ${publicHost}`);
|
|
1430
|
+
console.log(` Should be: ${externalIp} (without port, defaults to 80)`);
|
|
1431
|
+
console.log(`\n Or open port ${serverPort} directly in your firewall if no proxy is used.`);
|
|
1432
|
+
console.log(` ━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
1433
|
+
console.log(`\n ACTION REQUIRED: Ask your owner if they want to configure a reverse proxy.`);
|
|
1434
|
+
console.log(` If they say yes, help them add the nginx/caddy config above.`);
|
|
1435
|
+
console.log(` If they say no or skip, the invite will use port ${serverPort} directly.`);
|
|
1407
1436
|
}
|
|
1408
|
-
console.log(
|
|
1437
|
+
console.log(`\n Verify: curl -s ${verifyUrl}`);
|
|
1409
1438
|
}
|
|
1410
1439
|
|
|
1411
1440
|
// Save server config and advance onboarding state to awaiting_disclosure.
|
package/package.json
CHANGED
package/src/lib/disclosure.js
CHANGED
|
@@ -32,7 +32,37 @@ function dedupeByTopic(items) {
|
|
|
32
32
|
seen.add(topic.toLowerCase());
|
|
33
33
|
out.push({
|
|
34
34
|
topic,
|
|
35
|
-
|
|
35
|
+
description: normalizeTopic(item && (item.description || item.detail))
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function dedupeByObjective(items) {
|
|
42
|
+
const seen = new Set();
|
|
43
|
+
const out = [];
|
|
44
|
+
for (const item of items) {
|
|
45
|
+
const objective = normalizeTopic(item && item.objective);
|
|
46
|
+
if (!objective || seen.has(objective.toLowerCase())) continue;
|
|
47
|
+
seen.add(objective.toLowerCase());
|
|
48
|
+
out.push({
|
|
49
|
+
objective,
|
|
50
|
+
description: normalizeTopic(item && item.description)
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function dedupeDoNotDiscuss(items) {
|
|
57
|
+
const seen = new Set();
|
|
58
|
+
const out = [];
|
|
59
|
+
for (const item of items) {
|
|
60
|
+
const topic = normalizeTopic(item && item.topic);
|
|
61
|
+
if (!topic || seen.has(topic.toLowerCase())) continue;
|
|
62
|
+
seen.add(topic.toLowerCase());
|
|
63
|
+
out.push({
|
|
64
|
+
topic,
|
|
65
|
+
reason: normalizeTopic(item && item.reason)
|
|
36
66
|
});
|
|
37
67
|
}
|
|
38
68
|
return out;
|
|
@@ -118,11 +148,11 @@ function saveManifest(manifest) {
|
|
|
118
148
|
* Get topics for a given tier, merged down the hierarchy.
|
|
119
149
|
* family gets everything, friends gets friends+public, public gets public only.
|
|
120
150
|
*
|
|
121
|
-
* Returns {
|
|
151
|
+
* Returns { topics, objectives, do_not_discuss, never_disclose }
|
|
122
152
|
*/
|
|
123
153
|
function getTopicsForTier(tier) {
|
|
124
154
|
const manifest = loadManifest();
|
|
125
|
-
const
|
|
155
|
+
const tiers = manifest.tiers || manifest.topics || {};
|
|
126
156
|
|
|
127
157
|
const tierIndex = TIER_HIERARCHY.indexOf(tier);
|
|
128
158
|
if (tierIndex === -1) {
|
|
@@ -134,26 +164,42 @@ function getTopicsForTier(tier) {
|
|
|
134
164
|
const tiersToMerge = TIER_HIERARCHY.slice(0, tierIndex + 1);
|
|
135
165
|
|
|
136
166
|
const merged = {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
167
|
+
topics: [],
|
|
168
|
+
objectives: [],
|
|
169
|
+
do_not_discuss: [],
|
|
140
170
|
never_disclose: manifest.never_disclose || []
|
|
141
171
|
};
|
|
142
172
|
|
|
143
173
|
for (const t of tiersToMerge) {
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
if (
|
|
147
|
-
|
|
174
|
+
const tierData = tiers[t] || {};
|
|
175
|
+
// Support both new format (topics/objectives/do_not_discuss) and legacy (lead_with/discuss_freely/deflect)
|
|
176
|
+
if (tierData.topics) {
|
|
177
|
+
merged.topics.push(...tierData.topics);
|
|
178
|
+
} else {
|
|
179
|
+
// Legacy format fallback
|
|
180
|
+
if (tierData.lead_with) merged.topics.push(...tierData.lead_with);
|
|
181
|
+
if (tierData.discuss_freely) merged.topics.push(...tierData.discuss_freely);
|
|
182
|
+
}
|
|
183
|
+
if (tierData.objectives) merged.objectives.push(...tierData.objectives);
|
|
184
|
+
if (tierData.do_not_discuss) {
|
|
185
|
+
merged.do_not_discuss.push(...tierData.do_not_discuss);
|
|
186
|
+
} else if (tierData.deflect) {
|
|
187
|
+
// Legacy format fallback
|
|
188
|
+
merged.do_not_discuss.push(...tierData.deflect.map(d => ({
|
|
189
|
+
topic: d.topic,
|
|
190
|
+
reason: d.detail || d.reason
|
|
191
|
+
})));
|
|
192
|
+
}
|
|
148
193
|
}
|
|
149
194
|
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
merged.
|
|
195
|
+
// Remove do_not_discuss items that appear in topics (higher tiers promote them)
|
|
196
|
+
const promoted = new Set(merged.topics.map(t => (t.topic || '').toLowerCase()));
|
|
197
|
+
merged.do_not_discuss = merged.do_not_discuss.filter(t => !promoted.has((t.topic || '').toLowerCase()));
|
|
198
|
+
|
|
199
|
+
// Dedupe
|
|
200
|
+
merged.topics = dedupeByTopic(merged.topics);
|
|
201
|
+
merged.objectives = dedupeByObjective(merged.objectives);
|
|
202
|
+
merged.do_not_discuss = dedupeDoNotDiscuss(merged.do_not_discuss);
|
|
157
203
|
|
|
158
204
|
return merged;
|
|
159
205
|
}
|
|
@@ -162,18 +208,32 @@ function getTopicsForTier(tier) {
|
|
|
162
208
|
* Format topic lists into readable bullet points for prompt injection.
|
|
163
209
|
*/
|
|
164
210
|
function formatTopicsForPrompt(tierTopics) {
|
|
165
|
-
const
|
|
211
|
+
const formatTopicList = (items) => {
|
|
212
|
+
if (!items || items.length === 0) return ' (none specified)';
|
|
213
|
+
return items.map(item => ` - ${item.topic}: ${item.description || item.detail || ''}`).join('\n');
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const formatObjectiveList = (items) => {
|
|
166
217
|
if (!items || items.length === 0) return ' (none specified)';
|
|
167
|
-
return items.map(item => ` - ${item.
|
|
218
|
+
return items.map(item => ` - ${item.objective}: ${item.description || ''}`).join('\n');
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const formatDoNotDiscuss = (items) => {
|
|
222
|
+
if (!items || items.length === 0) return ' (none specified)';
|
|
223
|
+
return items.map(item => ` - ${item.topic}: ${item.reason || ''}`).join('\n');
|
|
168
224
|
};
|
|
169
225
|
|
|
170
226
|
return {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
227
|
+
topics: formatTopicList(tierTopics.topics),
|
|
228
|
+
objectives: formatObjectiveList(tierTopics.objectives),
|
|
229
|
+
doNotDiscuss: formatDoNotDiscuss(tierTopics.do_not_discuss),
|
|
174
230
|
neverDisclose: tierTopics.never_disclose?.length
|
|
175
231
|
? tierTopics.never_disclose.map(item => ` - ${item}`).join('\n')
|
|
176
|
-
: ' (none specified)'
|
|
232
|
+
: ' (none specified)',
|
|
233
|
+
// Legacy compatibility
|
|
234
|
+
leadWithTopics: formatTopicList(tierTopics.topics?.slice(0, 3) || tierTopics.lead_with),
|
|
235
|
+
discussFreelyTopics: formatTopicList(tierTopics.topics?.slice(3) || tierTopics.discuss_freely),
|
|
236
|
+
deflectTopics: formatDoNotDiscuss(tierTopics.do_not_discuss || tierTopics.deflect)
|
|
177
237
|
};
|
|
178
238
|
}
|
|
179
239
|
|
|
@@ -226,86 +286,71 @@ function generateDefaultManifest(contextFiles = {}) {
|
|
|
226
286
|
|
|
227
287
|
if (candidateTopics.length === 0) {
|
|
228
288
|
return {
|
|
229
|
-
version:
|
|
289
|
+
version: 2,
|
|
230
290
|
generated_at: now,
|
|
231
291
|
updated_at: now,
|
|
232
|
-
|
|
292
|
+
tiers: {
|
|
233
293
|
public: {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
294
|
+
topics: [{ topic: 'What I do', description: 'Brief professional description' }],
|
|
295
|
+
objectives: [{ objective: 'Networking', description: 'Connect with others in the field' }],
|
|
296
|
+
do_not_discuss: [{ topic: 'Personal details', reason: 'Redirect to direct owner contact' }]
|
|
237
297
|
},
|
|
238
|
-
friends: {
|
|
239
|
-
family: {
|
|
298
|
+
friends: { topics: [], objectives: [], do_not_discuss: [] },
|
|
299
|
+
family: { topics: [], objectives: [], do_not_discuss: [] }
|
|
240
300
|
},
|
|
241
301
|
never_disclose: ['API keys', 'Other users\' data', 'Financial figures'],
|
|
242
302
|
personality_notes: 'Direct and technical. Prefers depth over breadth.'
|
|
243
303
|
};
|
|
244
304
|
}
|
|
245
305
|
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
const
|
|
249
|
-
const
|
|
250
|
-
const
|
|
251
|
-
const familyDiscuss = [];
|
|
306
|
+
const publicTopics = [];
|
|
307
|
+
const publicObjectives = [];
|
|
308
|
+
const friendsTopics = [];
|
|
309
|
+
const friendsObjectives = [];
|
|
310
|
+
const familyTopics = [];
|
|
252
311
|
|
|
253
312
|
candidateTopics.forEach((entry, index) => {
|
|
254
|
-
const topic = truncateAtWordBoundary(entry.topic || '',
|
|
255
|
-
const
|
|
313
|
+
const topic = truncateAtWordBoundary(entry.topic || '', 160);
|
|
314
|
+
const description = truncateAtWordBoundary(entry.description || entry.detail || 'Open discussion topic.', 500);
|
|
256
315
|
if (!topic) return;
|
|
257
316
|
|
|
258
|
-
const node = { topic,
|
|
259
|
-
if (index <
|
|
260
|
-
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
if (index < 6) {
|
|
264
|
-
publicDiscuss.push(node);
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
if (index < 8) {
|
|
268
|
-
friendsLead.push(node);
|
|
317
|
+
const node = { topic, description };
|
|
318
|
+
if (index < 5) {
|
|
319
|
+
publicTopics.push(node);
|
|
269
320
|
return;
|
|
270
321
|
}
|
|
271
|
-
if (index <
|
|
272
|
-
|
|
322
|
+
if (index < 10) {
|
|
323
|
+
friendsTopics.push(node);
|
|
273
324
|
return;
|
|
274
325
|
}
|
|
275
|
-
|
|
276
|
-
familyDiscuss.push(node);
|
|
277
|
-
}
|
|
326
|
+
familyTopics.push(node);
|
|
278
327
|
});
|
|
279
328
|
|
|
280
|
-
if (
|
|
281
|
-
|
|
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.' });
|
|
329
|
+
if (publicTopics.length === 0) {
|
|
330
|
+
publicTopics.push({ topic: 'Open source', description: 'General product and engineering topics.' });
|
|
288
331
|
}
|
|
289
332
|
|
|
290
333
|
return {
|
|
291
|
-
version:
|
|
334
|
+
version: 2,
|
|
292
335
|
generated_at: now,
|
|
293
336
|
updated_at: now,
|
|
294
|
-
|
|
337
|
+
tiers: {
|
|
295
338
|
public: {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
339
|
+
topics: publicTopics,
|
|
340
|
+
objectives: publicObjectives.length > 0 ? publicObjectives : [
|
|
341
|
+
{ objective: 'Grow network', description: 'Connect with others working on similar problems' }
|
|
342
|
+
],
|
|
343
|
+
do_not_discuss: [{ topic: 'Personal details', reason: 'Redirect to direct owner contact' }]
|
|
299
344
|
},
|
|
300
345
|
friends: {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
346
|
+
topics: friendsTopics,
|
|
347
|
+
objectives: friendsObjectives,
|
|
348
|
+
do_not_discuss: []
|
|
304
349
|
},
|
|
305
350
|
family: {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
351
|
+
topics: familyTopics,
|
|
352
|
+
objectives: [],
|
|
353
|
+
do_not_discuss: []
|
|
309
354
|
}
|
|
310
355
|
},
|
|
311
356
|
never_disclose: ['API keys', 'Other users\' data', 'Financial figures'],
|
|
@@ -337,16 +382,17 @@ function validateDisclosureSubmission(data) {
|
|
|
337
382
|
return { valid: false, manifest: null, errors: ['Submission must be a non-null object'] };
|
|
338
383
|
}
|
|
339
384
|
|
|
340
|
-
//
|
|
341
|
-
|
|
342
|
-
|
|
385
|
+
// Support both new format (tiers) and legacy format (topics)
|
|
386
|
+
const tiersData = data.tiers || data.topics;
|
|
387
|
+
if (!tiersData || typeof tiersData !== 'object' || Array.isArray(tiersData)) {
|
|
388
|
+
errors.push('Submission must include a "tiers" object (or legacy "topics" object)');
|
|
343
389
|
return { valid: false, manifest: null, errors };
|
|
344
390
|
}
|
|
345
391
|
|
|
346
392
|
// Require all three tiers
|
|
347
393
|
for (const tier of TIER_HIERARCHY) {
|
|
348
|
-
if (!
|
|
349
|
-
errors.push(`Missing required tier: "${tier}"
|
|
394
|
+
if (!tiersData[tier] || typeof tiersData[tier] !== 'object') {
|
|
395
|
+
errors.push(`Missing required tier: "${tier}"`);
|
|
350
396
|
}
|
|
351
397
|
}
|
|
352
398
|
if (errors.length > 0) {
|
|
@@ -354,42 +400,108 @@ function validateDisclosureSubmission(data) {
|
|
|
354
400
|
}
|
|
355
401
|
|
|
356
402
|
// Reject extra tiers beyond the known hierarchy
|
|
357
|
-
const extraTiers = Object.keys(
|
|
403
|
+
const extraTiers = Object.keys(tiersData).filter(t => !TIER_HIERARCHY.includes(t));
|
|
358
404
|
if (extraTiers.length > 0) {
|
|
359
405
|
errors.push(`Unknown tiers: ${extraTiers.join(', ')} — only public, friends, family are allowed`);
|
|
360
406
|
}
|
|
361
407
|
|
|
362
|
-
//
|
|
363
|
-
const
|
|
364
|
-
|
|
408
|
+
// Detect format: new (topics/objectives/do_not_discuss) or legacy (lead_with/discuss_freely/deflect)
|
|
409
|
+
const isNewFormat = tiersData.public && (
|
|
410
|
+
Array.isArray(tiersData.public.topics) ||
|
|
411
|
+
Array.isArray(tiersData.public.objectives) ||
|
|
412
|
+
Array.isArray(tiersData.public.do_not_discuss)
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
const LIST_LIMITS = { topics: 15, objectives: 8, do_not_discuss: 10 };
|
|
416
|
+
|
|
365
417
|
for (const tier of TIER_HIERARCHY) {
|
|
366
|
-
const tierData =
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
418
|
+
const tierData = tiersData[tier];
|
|
419
|
+
|
|
420
|
+
if (isNewFormat) {
|
|
421
|
+
// Validate new format: topics, objectives, do_not_discuss
|
|
422
|
+
if (tierData.topics !== undefined) {
|
|
423
|
+
if (!Array.isArray(tierData.topics)) {
|
|
424
|
+
errors.push(`tiers.${tier}.topics must be an array`);
|
|
425
|
+
} else {
|
|
426
|
+
if (tierData.topics.length > LIST_LIMITS.topics) {
|
|
427
|
+
errors.push(`tiers.${tier}.topics has ${tierData.topics.length} items — max ${LIST_LIMITS.topics}`);
|
|
428
|
+
}
|
|
429
|
+
for (let i = 0; i < tierData.topics.length; i++) {
|
|
430
|
+
const item = tierData.topics[i];
|
|
431
|
+
if (!item || typeof item !== 'object' || typeof item.topic !== 'string') {
|
|
432
|
+
errors.push(`tiers.${tier}.topics[${i}]: must have "topic" (string) and "description" (string)`);
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
if (item.topic.trim().length === 0) {
|
|
436
|
+
errors.push(`tiers.${tier}.topics[${i}].topic must not be empty`);
|
|
437
|
+
}
|
|
438
|
+
if (item.topic.length > 160) {
|
|
439
|
+
errors.push(`tiers.${tier}.topics[${i}]: topic exceeds 160 chars`);
|
|
440
|
+
}
|
|
441
|
+
const desc = item.description || '';
|
|
442
|
+
if (desc.length > 500) {
|
|
443
|
+
errors.push(`tiers.${tier}.topics[${i}]: description exceeds 500 chars`);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
371
447
|
}
|
|
372
|
-
|
|
373
|
-
|
|
448
|
+
|
|
449
|
+
if (tierData.objectives !== undefined) {
|
|
450
|
+
if (!Array.isArray(tierData.objectives)) {
|
|
451
|
+
errors.push(`tiers.${tier}.objectives must be an array`);
|
|
452
|
+
} else {
|
|
453
|
+
if (tierData.objectives.length > LIST_LIMITS.objectives) {
|
|
454
|
+
errors.push(`tiers.${tier}.objectives has ${tierData.objectives.length} items — max ${LIST_LIMITS.objectives}`);
|
|
455
|
+
}
|
|
456
|
+
for (let i = 0; i < tierData.objectives.length; i++) {
|
|
457
|
+
const item = tierData.objectives[i];
|
|
458
|
+
if (!item || typeof item !== 'object' || typeof item.objective !== 'string') {
|
|
459
|
+
errors.push(`tiers.${tier}.objectives[${i}]: must have "objective" (string) and "description" (string)`);
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
if (item.objective.trim().length === 0) {
|
|
463
|
+
errors.push(`tiers.${tier}.objectives[${i}].objective must not be empty`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
374
467
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
if (!
|
|
378
|
-
errors.push(`
|
|
379
|
-
|
|
468
|
+
|
|
469
|
+
if (tierData.do_not_discuss !== undefined) {
|
|
470
|
+
if (!Array.isArray(tierData.do_not_discuss)) {
|
|
471
|
+
errors.push(`tiers.${tier}.do_not_discuss must be an array`);
|
|
472
|
+
} else {
|
|
473
|
+
if (tierData.do_not_discuss.length > LIST_LIMITS.do_not_discuss) {
|
|
474
|
+
errors.push(`tiers.${tier}.do_not_discuss has ${tierData.do_not_discuss.length} items — max ${LIST_LIMITS.do_not_discuss}`);
|
|
475
|
+
}
|
|
476
|
+
for (let i = 0; i < tierData.do_not_discuss.length; i++) {
|
|
477
|
+
const item = tierData.do_not_discuss[i];
|
|
478
|
+
if (!item || typeof item !== 'object' || typeof item.topic !== 'string') {
|
|
479
|
+
errors.push(`tiers.${tier}.do_not_discuss[${i}]: must have "topic" (string) and "reason" (string)`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
380
482
|
}
|
|
381
|
-
|
|
382
|
-
|
|
483
|
+
}
|
|
484
|
+
} else {
|
|
485
|
+
// Validate legacy format: lead_with, discuss_freely, deflect
|
|
486
|
+
const requiredLists = ['lead_with', 'discuss_freely', 'deflect'];
|
|
487
|
+
const LEGACY_LIMITS = { lead_with: 10, discuss_freely: 20, deflect: 10 };
|
|
488
|
+
for (const cat of requiredLists) {
|
|
489
|
+
if (!Array.isArray(tierData[cat])) {
|
|
490
|
+
errors.push(`topics.${tier}.${cat} must be an array`);
|
|
383
491
|
continue;
|
|
384
492
|
}
|
|
385
|
-
if (
|
|
386
|
-
errors.push(`topics.${tier}.${cat}
|
|
493
|
+
if (tierData[cat].length > LEGACY_LIMITS[cat]) {
|
|
494
|
+
errors.push(`topics.${tier}.${cat} has ${tierData[cat].length} items — max ${LEGACY_LIMITS[cat]}`);
|
|
387
495
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
496
|
+
for (let i = 0; i < tierData[cat].length; i++) {
|
|
497
|
+
const item = tierData[cat][i];
|
|
498
|
+
if (!item || typeof item !== 'object' || typeof item.topic !== 'string') {
|
|
499
|
+
errors.push(`topics.${tier}.${cat}[${i}]: must have "topic" (string) and "detail" (string)`);
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
if (item.topic.trim().length === 0) {
|
|
503
|
+
errors.push(`topics.${tier}.${cat}[${i}].topic must not be empty`);
|
|
504
|
+
}
|
|
393
505
|
}
|
|
394
506
|
}
|
|
395
507
|
}
|
|
@@ -426,30 +538,63 @@ function validateDisclosureSubmission(data) {
|
|
|
426
538
|
return { valid: false, manifest: null, errors };
|
|
427
539
|
}
|
|
428
540
|
|
|
429
|
-
// Rebuild
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
541
|
+
// Rebuild clean structure (isNewFormat already set above)
|
|
542
|
+
const now = new Date().toISOString();
|
|
543
|
+
|
|
544
|
+
if (isNewFormat) {
|
|
545
|
+
// New format: tiers with topics/objectives/do_not_discuss
|
|
546
|
+
const cleanTiers = {};
|
|
547
|
+
for (const tier of TIER_HIERARCHY) {
|
|
548
|
+
cleanTiers[tier] = {
|
|
549
|
+
topics: (tiersData[tier].topics || []).map(item => ({
|
|
550
|
+
topic: item.topic,
|
|
551
|
+
description: item.description || ''
|
|
552
|
+
})),
|
|
553
|
+
objectives: (tiersData[tier].objectives || []).map(item => ({
|
|
554
|
+
objective: item.objective,
|
|
555
|
+
description: item.description || ''
|
|
556
|
+
})),
|
|
557
|
+
do_not_discuss: (tiersData[tier].do_not_discuss || []).map(item => ({
|
|
558
|
+
topic: item.topic,
|
|
559
|
+
reason: item.reason || ''
|
|
560
|
+
}))
|
|
561
|
+
};
|
|
438
562
|
}
|
|
439
|
-
}
|
|
440
563
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
564
|
+
const manifest = {
|
|
565
|
+
version: 2,
|
|
566
|
+
generated_at: now,
|
|
567
|
+
updated_at: now,
|
|
568
|
+
tiers: cleanTiers,
|
|
569
|
+
never_disclose: data.never_disclose || ['API keys', 'Other users\' data', 'Financial figures'],
|
|
570
|
+
personality_notes: data.personality_notes || ''
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
return { valid: true, manifest, errors: [] };
|
|
574
|
+
} else {
|
|
575
|
+
// Legacy format: topics with lead_with/discuss_freely/deflect
|
|
576
|
+
const cleanTopics = {};
|
|
577
|
+
for (const tier of TIER_HIERARCHY) {
|
|
578
|
+
cleanTopics[tier] = {};
|
|
579
|
+
for (const cat of ['lead_with', 'discuss_freely', 'deflect']) {
|
|
580
|
+
cleanTopics[tier][cat] = (tiersData[tier][cat] || []).map(item => ({
|
|
581
|
+
topic: item.topic,
|
|
582
|
+
detail: item.detail || ''
|
|
583
|
+
}));
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const manifest = {
|
|
588
|
+
version: 1,
|
|
589
|
+
generated_at: now,
|
|
590
|
+
updated_at: now,
|
|
591
|
+
topics: cleanTopics,
|
|
592
|
+
never_disclose: data.never_disclose || ['API keys', 'Other users\' data', 'Financial figures'],
|
|
593
|
+
personality_notes: data.personality_notes || ''
|
|
594
|
+
};
|
|
451
595
|
|
|
452
|
-
|
|
596
|
+
return { valid: true, manifest, errors: [] };
|
|
597
|
+
}
|
|
453
598
|
}
|
|
454
599
|
|
|
455
600
|
/**
|
|
@@ -520,7 +665,35 @@ function buildExtractionPrompt(availableFiles) {
|
|
|
520
665
|
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
666
|
}
|
|
522
667
|
|
|
523
|
-
const jsonBlock =
|
|
668
|
+
const jsonBlock = `\`\`\`json
|
|
669
|
+
{
|
|
670
|
+
"tiers": {
|
|
671
|
+
"public": {
|
|
672
|
+
"topics": [
|
|
673
|
+
{ "topic": "Short label (max 160 chars)", "description": "Longer description of the topic" }
|
|
674
|
+
],
|
|
675
|
+
"objectives": [
|
|
676
|
+
{ "objective": "What you want to achieve", "description": "Longer description of this goal" }
|
|
677
|
+
],
|
|
678
|
+
"do_not_discuss": [
|
|
679
|
+
{ "topic": "Topic to avoid", "reason": "Why this should be redirected" }
|
|
680
|
+
]
|
|
681
|
+
},
|
|
682
|
+
"friends": {
|
|
683
|
+
"topics": [],
|
|
684
|
+
"objectives": [],
|
|
685
|
+
"do_not_discuss": []
|
|
686
|
+
},
|
|
687
|
+
"family": {
|
|
688
|
+
"topics": [],
|
|
689
|
+
"objectives": [],
|
|
690
|
+
"do_not_discuss": []
|
|
691
|
+
}
|
|
692
|
+
},
|
|
693
|
+
"never_disclose": ["API keys", "Credentials", "Financial figures"],
|
|
694
|
+
"personality_notes": "Brief description of communication style"
|
|
695
|
+
}
|
|
696
|
+
\`\`\``;
|
|
524
697
|
|
|
525
698
|
return `## A2A Disclosure Extraction
|
|
526
699
|
|
|
@@ -530,21 +703,35 @@ ${fileSection}
|
|
|
530
703
|
|
|
531
704
|
Focus on what the OWNER cares about, works on, and wants to discuss — NOT on agent instructions, code documentation, or operational tasks.
|
|
532
705
|
|
|
533
|
-
###
|
|
706
|
+
### Tier Inheritance
|
|
707
|
+
|
|
708
|
+
- **public** — base tier, anyone can see these
|
|
709
|
+
- **friends** — inherits all PUBLIC topics/objectives, plus additional friend-only items
|
|
710
|
+
- **family** — inherits all FRIENDS and PUBLIC items, plus additional family-only items
|
|
711
|
+
|
|
712
|
+
Family callers see everything. Friends see friends + public. Public callers see only public.
|
|
713
|
+
|
|
714
|
+
### What to extract for each tier
|
|
534
715
|
|
|
535
|
-
|
|
716
|
+
**topics** — Things the owner is interested in or working on:
|
|
717
|
+
- Professional role and expertise
|
|
718
|
+
- Current projects and interests
|
|
719
|
+
- Hobbies and activities
|
|
720
|
+
- Max 8 topics per tier
|
|
536
721
|
|
|
537
|
-
|
|
538
|
-
-
|
|
539
|
-
-
|
|
722
|
+
**objectives** — What the owner wants to achieve in conversations:
|
|
723
|
+
- Networking goals
|
|
724
|
+
- Collaboration interests
|
|
725
|
+
- Opportunities they're seeking
|
|
726
|
+
- Max 4 objectives per tier
|
|
540
727
|
|
|
541
|
-
|
|
542
|
-
-
|
|
543
|
-
-
|
|
544
|
-
-
|
|
728
|
+
**do_not_discuss** — Topics to redirect or decline (can be empty):
|
|
729
|
+
- Personal matters (for public tier)
|
|
730
|
+
- Sensitive subjects
|
|
731
|
+
- Max 3 per tier
|
|
545
732
|
|
|
546
733
|
Also identify:
|
|
547
|
-
- **never_disclose** — information that should
|
|
734
|
+
- **never_disclose** — information that should NEVER be shared regardless of tier (API keys, credentials, financial data, etc.)
|
|
548
735
|
- **personality_notes** — a 1-2 sentence description of the owner's communication style
|
|
549
736
|
|
|
550
737
|
### What NOT to extract
|
|
@@ -565,11 +752,11 @@ ${jsonBlock}
|
|
|
565
752
|
### Rules
|
|
566
753
|
|
|
567
754
|
1. Each "topic" string must be a short, human-readable label (max 160 chars)
|
|
568
|
-
2. Each "
|
|
755
|
+
2. Each "description" string explains the topic more fully (max 500 chars)
|
|
569
756
|
3. Topics should be things a person would discuss, not technical artifacts
|
|
570
|
-
4. Higher tiers
|
|
757
|
+
4. Higher tiers inherit lower-tier items automatically — only add NEW items at each tier
|
|
571
758
|
5. Present this to the owner for review before submitting
|
|
572
|
-
6. The owner may edit, remove, or add
|
|
759
|
+
6. The owner may edit, remove, or add items before final submission`;
|
|
573
760
|
}
|
|
574
761
|
|
|
575
762
|
module.exports = {
|
package/src/routes/dashboard.js
CHANGED
|
@@ -1032,9 +1032,14 @@ function createDashboardApiRouter(options = {}) {
|
|
|
1032
1032
|
disclosure: configTier.disclosure || 'minimal',
|
|
1033
1033
|
examples: sanitizeStringArray(configTier.examples || [], 20, 120),
|
|
1034
1034
|
manifest: {
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1035
|
+
// Support both new format (topics/objectives/do_not_discuss) and legacy
|
|
1036
|
+
topics: manifestTier.topics || manifestTier.lead_with || [],
|
|
1037
|
+
objectives: manifestTier.objectives || [],
|
|
1038
|
+
do_not_discuss: manifestTier.do_not_discuss || manifestTier.deflect || [],
|
|
1039
|
+
// Legacy fields for backwards compatibility
|
|
1040
|
+
lead_with: manifestTier.lead_with || manifestTier.topics?.slice(0, 3) || [],
|
|
1041
|
+
discuss_freely: manifestTier.discuss_freely || manifestTier.topics?.slice(3) || [],
|
|
1042
|
+
deflect: manifestTier.deflect || manifestTier.do_not_discuss || []
|
|
1038
1043
|
}
|
|
1039
1044
|
};
|
|
1040
1045
|
});
|
package/src/server.js
CHANGED
|
@@ -184,24 +184,38 @@ function extractSignalPhrases(text, pattern, maxItems = 3) {
|
|
|
184
184
|
|
|
185
185
|
function collectTopicKeywords(tierTopics) {
|
|
186
186
|
const keywords = new Set();
|
|
187
|
-
|
|
187
|
+
|
|
188
|
+
// Support both new format (topics) and legacy format (lead_with/discuss_freely)
|
|
189
|
+
const topicsList = tierTopics?.topics || [
|
|
190
|
+
...(tierTopics?.lead_with || []),
|
|
191
|
+
...(tierTopics?.discuss_freely || [])
|
|
192
|
+
];
|
|
193
|
+
const objectivesList = tierTopics?.objectives || [];
|
|
194
|
+
|
|
195
|
+
for (const item of topicsList) {
|
|
196
|
+
for (const part of [item?.topic, item?.description, item?.detail]) {
|
|
197
|
+
if (!part) continue;
|
|
198
|
+
const terms = String(part)
|
|
199
|
+
.toLowerCase()
|
|
200
|
+
.split(/[^a-z0-9]+/)
|
|
201
|
+
.filter(term => term.length >= 4);
|
|
202
|
+
for (const term of terms.slice(0, 6)) {
|
|
203
|
+
keywords.add(term);
|
|
204
|
+
if (keywords.size >= 48) return Array.from(keywords);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
188
208
|
|
|
189
|
-
for (const
|
|
190
|
-
for (const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
for (const term of terms.slice(0, 6)) {
|
|
200
|
-
keywords.add(term);
|
|
201
|
-
if (keywords.size >= 48) {
|
|
202
|
-
return Array.from(keywords);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
209
|
+
for (const item of objectivesList) {
|
|
210
|
+
for (const part of [item?.objective, item?.description]) {
|
|
211
|
+
if (!part) continue;
|
|
212
|
+
const terms = String(part)
|
|
213
|
+
.toLowerCase()
|
|
214
|
+
.split(/[^a-z0-9]+/)
|
|
215
|
+
.filter(term => term.length >= 4);
|
|
216
|
+
for (const term of terms.slice(0, 6)) {
|
|
217
|
+
keywords.add(term);
|
|
218
|
+
if (keywords.size >= 48) return Array.from(keywords);
|
|
205
219
|
}
|
|
206
220
|
}
|
|
207
221
|
}
|