a2acalling 0.6.18 → 0.6.20
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 +12 -23
- package/package.json +1 -1
- package/src/lib/disclosure.js +243 -130
- package/src/lib/prompt-template.js +32 -40
- package/src/routes/dashboard.js +8 -8
- package/src/server.js +27 -17
package/bin/cli.js
CHANGED
|
@@ -335,35 +335,27 @@ async function handleDisclosureSubmit(args, commandLabel = 'onboard') {
|
|
|
335
335
|
|
|
336
336
|
// Sync tier config from manifest
|
|
337
337
|
const manifest = result.manifest;
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
if (t && !out.includes(t)) out.push(t);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
return out;
|
|
338
|
+
|
|
339
|
+
// Helper to extract topic names
|
|
340
|
+
function getTierTopics(tierData) {
|
|
341
|
+
if (!tierData || !Array.isArray(tierData.topics)) return [];
|
|
342
|
+
return tierData.topics.map(t => String(t && t.topic || '').trim()).filter(Boolean);
|
|
347
343
|
}
|
|
348
344
|
|
|
345
|
+
// Get tiers data (support both new 'tiers' key and legacy 'topics' key)
|
|
346
|
+
const tiersData = manifest.tiers || manifest.topics || {};
|
|
347
|
+
|
|
349
348
|
try {
|
|
350
349
|
config.setTier('public', {
|
|
351
|
-
topics:
|
|
350
|
+
topics: getTierTopics(tiersData.public),
|
|
352
351
|
disclosure: 'public'
|
|
353
352
|
});
|
|
354
353
|
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
|
-
]),
|
|
354
|
+
topics: [...getTierTopics(tiersData.public), ...getTierTopics(tiersData.friends)],
|
|
359
355
|
disclosure: 'minimal'
|
|
360
356
|
});
|
|
361
357
|
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
|
-
]),
|
|
358
|
+
topics: [...getTierTopics(tiersData.public), ...getTierTopics(tiersData.friends), ...getTierTopics(tiersData.family)],
|
|
367
359
|
disclosure: 'minimal'
|
|
368
360
|
});
|
|
369
361
|
} catch (err) {
|
|
@@ -382,10 +374,7 @@ async function handleDisclosureSubmit(args, commandLabel = 'onboard') {
|
|
|
382
374
|
const hostname = config.getAgent().hostname || process.env.A2A_HOSTNAME || 'localhost';
|
|
383
375
|
if (args.flags.name) config.setAgent({ name: agentName });
|
|
384
376
|
|
|
385
|
-
const publicTopics =
|
|
386
|
-
manifest.topics.public.lead_with,
|
|
387
|
-
manifest.topics.public.discuss_freely
|
|
388
|
-
]);
|
|
377
|
+
const publicTopics = getTierTopics(tiersData.public);
|
|
389
378
|
|
|
390
379
|
const { token } = store.create({
|
|
391
380
|
name: agentName,
|
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,27 @@ 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
|
-
if (
|
|
146
|
-
if (
|
|
147
|
-
if (
|
|
174
|
+
const tierData = tiers[t] || {};
|
|
175
|
+
if (tierData.topics) merged.topics.push(...tierData.topics);
|
|
176
|
+
if (tierData.objectives) merged.objectives.push(...tierData.objectives);
|
|
177
|
+
if (tierData.do_not_discuss) merged.do_not_discuss.push(...tierData.do_not_discuss);
|
|
148
178
|
}
|
|
149
179
|
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
merged.
|
|
180
|
+
// Remove do_not_discuss items that appear in topics (higher tiers promote them)
|
|
181
|
+
const promoted = new Set(merged.topics.map(t => (t.topic || '').toLowerCase()));
|
|
182
|
+
merged.do_not_discuss = merged.do_not_discuss.filter(t => !promoted.has((t.topic || '').toLowerCase()));
|
|
183
|
+
|
|
184
|
+
// Dedupe
|
|
185
|
+
merged.topics = dedupeByTopic(merged.topics);
|
|
186
|
+
merged.objectives = dedupeByObjective(merged.objectives);
|
|
187
|
+
merged.do_not_discuss = dedupeDoNotDiscuss(merged.do_not_discuss);
|
|
157
188
|
|
|
158
189
|
return merged;
|
|
159
190
|
}
|
|
@@ -162,15 +193,25 @@ function getTopicsForTier(tier) {
|
|
|
162
193
|
* Format topic lists into readable bullet points for prompt injection.
|
|
163
194
|
*/
|
|
164
195
|
function formatTopicsForPrompt(tierTopics) {
|
|
165
|
-
const
|
|
196
|
+
const formatTopicList = (items) => {
|
|
166
197
|
if (!items || items.length === 0) return ' (none specified)';
|
|
167
|
-
return items.map(item => ` - ${item.topic}: ${item.detail}`).join('\n');
|
|
198
|
+
return items.map(item => ` - ${item.topic}: ${item.description || item.detail || ''}`).join('\n');
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const formatObjectiveList = (items) => {
|
|
202
|
+
if (!items || items.length === 0) return ' (none specified)';
|
|
203
|
+
return items.map(item => ` - ${item.objective}: ${item.description || ''}`).join('\n');
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const formatDoNotDiscuss = (items) => {
|
|
207
|
+
if (!items || items.length === 0) return ' (none specified)';
|
|
208
|
+
return items.map(item => ` - ${item.topic}: ${item.reason || ''}`).join('\n');
|
|
168
209
|
};
|
|
169
210
|
|
|
170
211
|
return {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
212
|
+
topics: formatTopicList(tierTopics.topics),
|
|
213
|
+
objectives: formatObjectiveList(tierTopics.objectives),
|
|
214
|
+
doNotDiscuss: formatDoNotDiscuss(tierTopics.do_not_discuss),
|
|
174
215
|
neverDisclose: tierTopics.never_disclose?.length
|
|
175
216
|
? tierTopics.never_disclose.map(item => ` - ${item}`).join('\n')
|
|
176
217
|
: ' (none specified)'
|
|
@@ -226,86 +267,71 @@ function generateDefaultManifest(contextFiles = {}) {
|
|
|
226
267
|
|
|
227
268
|
if (candidateTopics.length === 0) {
|
|
228
269
|
return {
|
|
229
|
-
version:
|
|
270
|
+
version: 2,
|
|
230
271
|
generated_at: now,
|
|
231
272
|
updated_at: now,
|
|
232
|
-
|
|
273
|
+
tiers: {
|
|
233
274
|
public: {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
275
|
+
topics: [{ topic: 'What I do', description: 'Brief professional description' }],
|
|
276
|
+
objectives: [{ objective: 'Networking', description: 'Connect with others in the field' }],
|
|
277
|
+
do_not_discuss: [{ topic: 'Personal details', reason: 'Redirect to direct owner contact' }]
|
|
237
278
|
},
|
|
238
|
-
friends: {
|
|
239
|
-
family: {
|
|
279
|
+
friends: { topics: [], objectives: [], do_not_discuss: [] },
|
|
280
|
+
family: { topics: [], objectives: [], do_not_discuss: [] }
|
|
240
281
|
},
|
|
241
282
|
never_disclose: ['API keys', 'Other users\' data', 'Financial figures'],
|
|
242
283
|
personality_notes: 'Direct and technical. Prefers depth over breadth.'
|
|
243
284
|
};
|
|
244
285
|
}
|
|
245
286
|
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
const
|
|
249
|
-
const
|
|
250
|
-
const
|
|
251
|
-
const familyDiscuss = [];
|
|
287
|
+
const publicTopics = [];
|
|
288
|
+
const publicObjectives = [];
|
|
289
|
+
const friendsTopics = [];
|
|
290
|
+
const friendsObjectives = [];
|
|
291
|
+
const familyTopics = [];
|
|
252
292
|
|
|
253
293
|
candidateTopics.forEach((entry, index) => {
|
|
254
|
-
const topic = truncateAtWordBoundary(entry.topic || '',
|
|
255
|
-
const
|
|
294
|
+
const topic = truncateAtWordBoundary(entry.topic || '', 160);
|
|
295
|
+
const description = truncateAtWordBoundary(entry.description || entry.detail || 'Open discussion topic.', 500);
|
|
256
296
|
if (!topic) return;
|
|
257
297
|
|
|
258
|
-
const node = { topic,
|
|
259
|
-
if (index <
|
|
260
|
-
|
|
298
|
+
const node = { topic, description };
|
|
299
|
+
if (index < 5) {
|
|
300
|
+
publicTopics.push(node);
|
|
261
301
|
return;
|
|
262
302
|
}
|
|
263
|
-
if (index <
|
|
264
|
-
|
|
303
|
+
if (index < 10) {
|
|
304
|
+
friendsTopics.push(node);
|
|
265
305
|
return;
|
|
266
306
|
}
|
|
267
|
-
|
|
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
|
-
}
|
|
307
|
+
familyTopics.push(node);
|
|
278
308
|
});
|
|
279
309
|
|
|
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.' });
|
|
310
|
+
if (publicTopics.length === 0) {
|
|
311
|
+
publicTopics.push({ topic: 'Open source', description: 'General product and engineering topics.' });
|
|
288
312
|
}
|
|
289
313
|
|
|
290
314
|
return {
|
|
291
|
-
version:
|
|
315
|
+
version: 2,
|
|
292
316
|
generated_at: now,
|
|
293
317
|
updated_at: now,
|
|
294
|
-
|
|
318
|
+
tiers: {
|
|
295
319
|
public: {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
320
|
+
topics: publicTopics,
|
|
321
|
+
objectives: publicObjectives.length > 0 ? publicObjectives : [
|
|
322
|
+
{ objective: 'Grow network', description: 'Connect with others working on similar problems' }
|
|
323
|
+
],
|
|
324
|
+
do_not_discuss: [{ topic: 'Personal details', reason: 'Redirect to direct owner contact' }]
|
|
299
325
|
},
|
|
300
326
|
friends: {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
327
|
+
topics: friendsTopics,
|
|
328
|
+
objectives: friendsObjectives,
|
|
329
|
+
do_not_discuss: []
|
|
304
330
|
},
|
|
305
331
|
family: {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
332
|
+
topics: familyTopics,
|
|
333
|
+
objectives: [],
|
|
334
|
+
do_not_discuss: []
|
|
309
335
|
}
|
|
310
336
|
},
|
|
311
337
|
never_disclose: ['API keys', 'Other users\' data', 'Financial figures'],
|
|
@@ -337,16 +363,17 @@ function validateDisclosureSubmission(data) {
|
|
|
337
363
|
return { valid: false, manifest: null, errors: ['Submission must be a non-null object'] };
|
|
338
364
|
}
|
|
339
365
|
|
|
340
|
-
//
|
|
341
|
-
|
|
342
|
-
|
|
366
|
+
// Support both new format (tiers) and legacy format (topics)
|
|
367
|
+
const tiersData = data.tiers || data.topics;
|
|
368
|
+
if (!tiersData || typeof tiersData !== 'object' || Array.isArray(tiersData)) {
|
|
369
|
+
errors.push('Submission must include a "tiers" object (or legacy "topics" object)');
|
|
343
370
|
return { valid: false, manifest: null, errors };
|
|
344
371
|
}
|
|
345
372
|
|
|
346
373
|
// Require all three tiers
|
|
347
374
|
for (const tier of TIER_HIERARCHY) {
|
|
348
|
-
if (!
|
|
349
|
-
errors.push(`Missing required tier: "${tier}"
|
|
375
|
+
if (!tiersData[tier] || typeof tiersData[tier] !== 'object') {
|
|
376
|
+
errors.push(`Missing required tier: "${tier}"`);
|
|
350
377
|
}
|
|
351
378
|
}
|
|
352
379
|
if (errors.length > 0) {
|
|
@@ -354,42 +381,78 @@ function validateDisclosureSubmission(data) {
|
|
|
354
381
|
}
|
|
355
382
|
|
|
356
383
|
// Reject extra tiers beyond the known hierarchy
|
|
357
|
-
const extraTiers = Object.keys(
|
|
384
|
+
const extraTiers = Object.keys(tiersData).filter(t => !TIER_HIERARCHY.includes(t));
|
|
358
385
|
if (extraTiers.length > 0) {
|
|
359
386
|
errors.push(`Unknown tiers: ${extraTiers.join(', ')} — only public, friends, family are allowed`);
|
|
360
387
|
}
|
|
361
388
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
const LIST_LIMITS = { lead_with: 10, discuss_freely: 20, deflect: 10 };
|
|
389
|
+
const LIST_LIMITS = { topics: 15, objectives: 8, do_not_discuss: 10 };
|
|
390
|
+
|
|
365
391
|
for (const tier of TIER_HIERARCHY) {
|
|
366
|
-
const tierData =
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
for (let i = 0; i < tierData[cat].length; i++) {
|
|
376
|
-
const item = tierData[cat][i];
|
|
377
|
-
if (!item || typeof item !== 'object' || typeof item.topic !== 'string' || typeof item.detail !== 'string') {
|
|
378
|
-
errors.push(`topics.${tier}.${cat}[${i}]: each topic item must have "topic" (string) and "detail" (string)`);
|
|
379
|
-
continue;
|
|
392
|
+
const tierData = tiersData[tier];
|
|
393
|
+
|
|
394
|
+
// Validate topics array
|
|
395
|
+
if (tierData.topics !== undefined) {
|
|
396
|
+
if (!Array.isArray(tierData.topics)) {
|
|
397
|
+
errors.push(`tiers.${tier}.topics must be an array`);
|
|
398
|
+
} else {
|
|
399
|
+
if (tierData.topics.length > LIST_LIMITS.topics) {
|
|
400
|
+
errors.push(`tiers.${tier}.topics has ${tierData.topics.length} items — max ${LIST_LIMITS.topics}`);
|
|
380
401
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
402
|
+
for (let i = 0; i < tierData.topics.length; i++) {
|
|
403
|
+
const item = tierData.topics[i];
|
|
404
|
+
if (!item || typeof item !== 'object' || typeof item.topic !== 'string') {
|
|
405
|
+
errors.push(`tiers.${tier}.topics[${i}]: must have "topic" (string) and "description" (string)`);
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
if (item.topic.trim().length === 0) {
|
|
409
|
+
errors.push(`tiers.${tier}.topics[${i}].topic must not be empty`);
|
|
410
|
+
}
|
|
411
|
+
if (item.topic.length > 160) {
|
|
412
|
+
errors.push(`tiers.${tier}.topics[${i}]: topic exceeds 160 chars`);
|
|
413
|
+
}
|
|
414
|
+
const desc = item.description || '';
|
|
415
|
+
if (desc.length > 500) {
|
|
416
|
+
errors.push(`tiers.${tier}.topics[${i}]: description exceeds 500 chars`);
|
|
417
|
+
}
|
|
384
418
|
}
|
|
385
|
-
|
|
386
|
-
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Validate objectives array
|
|
423
|
+
if (tierData.objectives !== undefined) {
|
|
424
|
+
if (!Array.isArray(tierData.objectives)) {
|
|
425
|
+
errors.push(`tiers.${tier}.objectives must be an array`);
|
|
426
|
+
} else {
|
|
427
|
+
if (tierData.objectives.length > LIST_LIMITS.objectives) {
|
|
428
|
+
errors.push(`tiers.${tier}.objectives has ${tierData.objectives.length} items — max ${LIST_LIMITS.objectives}`);
|
|
387
429
|
}
|
|
388
|
-
|
|
389
|
-
|
|
430
|
+
for (let i = 0; i < tierData.objectives.length; i++) {
|
|
431
|
+
const item = tierData.objectives[i];
|
|
432
|
+
if (!item || typeof item !== 'object' || typeof item.objective !== 'string') {
|
|
433
|
+
errors.push(`tiers.${tier}.objectives[${i}]: must have "objective" (string) and "description" (string)`);
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
if (item.objective.trim().length === 0) {
|
|
437
|
+
errors.push(`tiers.${tier}.objectives[${i}].objective must not be empty`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Validate do_not_discuss array
|
|
444
|
+
if (tierData.do_not_discuss !== undefined) {
|
|
445
|
+
if (!Array.isArray(tierData.do_not_discuss)) {
|
|
446
|
+
errors.push(`tiers.${tier}.do_not_discuss must be an array`);
|
|
447
|
+
} else {
|
|
448
|
+
if (tierData.do_not_discuss.length > LIST_LIMITS.do_not_discuss) {
|
|
449
|
+
errors.push(`tiers.${tier}.do_not_discuss has ${tierData.do_not_discuss.length} items — max ${LIST_LIMITS.do_not_discuss}`);
|
|
390
450
|
}
|
|
391
|
-
|
|
392
|
-
|
|
451
|
+
for (let i = 0; i < tierData.do_not_discuss.length; i++) {
|
|
452
|
+
const item = tierData.do_not_discuss[i];
|
|
453
|
+
if (!item || typeof item !== 'object' || typeof item.topic !== 'string') {
|
|
454
|
+
errors.push(`tiers.${tier}.do_not_discuss[${i}]: must have "topic" (string) and "reason" (string)`);
|
|
455
|
+
}
|
|
393
456
|
}
|
|
394
457
|
}
|
|
395
458
|
}
|
|
@@ -426,25 +489,33 @@ function validateDisclosureSubmission(data) {
|
|
|
426
489
|
return { valid: false, manifest: null, errors };
|
|
427
490
|
}
|
|
428
491
|
|
|
429
|
-
// Rebuild
|
|
430
|
-
const
|
|
492
|
+
// Rebuild clean structure
|
|
493
|
+
const now = new Date().toISOString();
|
|
494
|
+
|
|
495
|
+
// Build clean manifest with new format only
|
|
496
|
+
const cleanTiers = {};
|
|
431
497
|
for (const tier of TIER_HIERARCHY) {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
cleanTopics[tier][cat] = (data.topics[tier][cat] || []).map(item => ({
|
|
498
|
+
cleanTiers[tier] = {
|
|
499
|
+
topics: (tiersData[tier].topics || []).map(item => ({
|
|
435
500
|
topic: item.topic,
|
|
436
|
-
|
|
437
|
-
}))
|
|
438
|
-
|
|
501
|
+
description: item.description || ''
|
|
502
|
+
})),
|
|
503
|
+
objectives: (tiersData[tier].objectives || []).map(item => ({
|
|
504
|
+
objective: item.objective,
|
|
505
|
+
description: item.description || ''
|
|
506
|
+
})),
|
|
507
|
+
do_not_discuss: (tiersData[tier].do_not_discuss || []).map(item => ({
|
|
508
|
+
topic: item.topic,
|
|
509
|
+
reason: item.reason || ''
|
|
510
|
+
}))
|
|
511
|
+
};
|
|
439
512
|
}
|
|
440
513
|
|
|
441
|
-
// Build valid manifest
|
|
442
|
-
const now = new Date().toISOString();
|
|
443
514
|
const manifest = {
|
|
444
|
-
version:
|
|
515
|
+
version: 2,
|
|
445
516
|
generated_at: now,
|
|
446
517
|
updated_at: now,
|
|
447
|
-
|
|
518
|
+
tiers: cleanTiers,
|
|
448
519
|
never_disclose: data.never_disclose || ['API keys', 'Other users\' data', 'Financial figures'],
|
|
449
520
|
personality_notes: data.personality_notes || ''
|
|
450
521
|
};
|
|
@@ -520,7 +591,35 @@ function buildExtractionPrompt(availableFiles) {
|
|
|
520
591
|
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
592
|
}
|
|
522
593
|
|
|
523
|
-
const jsonBlock =
|
|
594
|
+
const jsonBlock = `\`\`\`json
|
|
595
|
+
{
|
|
596
|
+
"tiers": {
|
|
597
|
+
"public": {
|
|
598
|
+
"topics": [
|
|
599
|
+
{ "topic": "Short label (max 160 chars)", "description": "Longer description of the topic" }
|
|
600
|
+
],
|
|
601
|
+
"objectives": [
|
|
602
|
+
{ "objective": "What you want to achieve", "description": "Longer description of this goal" }
|
|
603
|
+
],
|
|
604
|
+
"do_not_discuss": [
|
|
605
|
+
{ "topic": "Topic to avoid", "reason": "Why this should be redirected" }
|
|
606
|
+
]
|
|
607
|
+
},
|
|
608
|
+
"friends": {
|
|
609
|
+
"topics": [],
|
|
610
|
+
"objectives": [],
|
|
611
|
+
"do_not_discuss": []
|
|
612
|
+
},
|
|
613
|
+
"family": {
|
|
614
|
+
"topics": [],
|
|
615
|
+
"objectives": [],
|
|
616
|
+
"do_not_discuss": []
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
"never_disclose": ["API keys", "Credentials", "Financial figures"],
|
|
620
|
+
"personality_notes": "Brief description of communication style"
|
|
621
|
+
}
|
|
622
|
+
\`\`\``;
|
|
524
623
|
|
|
525
624
|
return `## A2A Disclosure Extraction
|
|
526
625
|
|
|
@@ -530,21 +629,35 @@ ${fileSection}
|
|
|
530
629
|
|
|
531
630
|
Focus on what the OWNER cares about, works on, and wants to discuss — NOT on agent instructions, code documentation, or operational tasks.
|
|
532
631
|
|
|
533
|
-
###
|
|
632
|
+
### Tier Inheritance
|
|
633
|
+
|
|
634
|
+
- **public** — base tier, anyone can see these
|
|
635
|
+
- **friends** — inherits all PUBLIC topics/objectives, plus additional friend-only items
|
|
636
|
+
- **family** — inherits all FRIENDS and PUBLIC items, plus additional family-only items
|
|
637
|
+
|
|
638
|
+
Family callers see everything. Friends see friends + public. Public callers see only public.
|
|
639
|
+
|
|
640
|
+
### What to extract for each tier
|
|
534
641
|
|
|
535
|
-
|
|
642
|
+
**topics** — Things the owner is interested in or working on:
|
|
643
|
+
- Professional role and expertise
|
|
644
|
+
- Current projects and interests
|
|
645
|
+
- Hobbies and activities
|
|
646
|
+
- Max 8 topics per tier
|
|
536
647
|
|
|
537
|
-
|
|
538
|
-
-
|
|
539
|
-
-
|
|
648
|
+
**objectives** — What the owner wants to achieve in conversations:
|
|
649
|
+
- Networking goals
|
|
650
|
+
- Collaboration interests
|
|
651
|
+
- Opportunities they're seeking
|
|
652
|
+
- Max 4 objectives per tier
|
|
540
653
|
|
|
541
|
-
|
|
542
|
-
-
|
|
543
|
-
-
|
|
544
|
-
-
|
|
654
|
+
**do_not_discuss** — Topics to redirect or decline (can be empty):
|
|
655
|
+
- Personal matters (for public tier)
|
|
656
|
+
- Sensitive subjects
|
|
657
|
+
- Max 3 per tier
|
|
545
658
|
|
|
546
659
|
Also identify:
|
|
547
|
-
- **never_disclose** — information that should
|
|
660
|
+
- **never_disclose** — information that should NEVER be shared regardless of tier (API keys, credentials, financial data, etc.)
|
|
548
661
|
- **personality_notes** — a 1-2 sentence description of the owner's communication style
|
|
549
662
|
|
|
550
663
|
### What NOT to extract
|
|
@@ -565,11 +678,11 @@ ${jsonBlock}
|
|
|
565
678
|
### Rules
|
|
566
679
|
|
|
567
680
|
1. Each "topic" string must be a short, human-readable label (max 160 chars)
|
|
568
|
-
2. Each "
|
|
681
|
+
2. Each "description" string explains the topic more fully (max 500 chars)
|
|
569
682
|
3. Topics should be things a person would discuss, not technical artifacts
|
|
570
|
-
4. Higher tiers
|
|
683
|
+
4. Higher tiers inherit lower-tier items automatically — only add NEW items at each tier
|
|
571
684
|
5. Present this to the owner for review before submitting
|
|
572
|
-
6. The owner may edit, remove, or add
|
|
685
|
+
6. The owner may edit, remove, or add items before final submission`;
|
|
573
686
|
}
|
|
574
687
|
|
|
575
688
|
module.exports = {
|
|
@@ -49,9 +49,9 @@ function buildConnectionPrompt(options) {
|
|
|
49
49
|
} = options;
|
|
50
50
|
|
|
51
51
|
const {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
topics = ' (none specified)',
|
|
53
|
+
objectives = ' (none specified)',
|
|
54
|
+
doNotDiscuss = ' (none specified)',
|
|
55
55
|
neverDisclose = ' (none specified)'
|
|
56
56
|
} = tierTopics || {};
|
|
57
57
|
|
|
@@ -62,17 +62,16 @@ Your job is NOT to answer questions and hang up. Your job is to have a real conv
|
|
|
62
62
|
|
|
63
63
|
== WHAT YOU BRING TO THE TABLE ==
|
|
64
64
|
|
|
65
|
-
${ownerName}
|
|
66
|
-
${
|
|
65
|
+
${ownerName}'s topics of interest:
|
|
66
|
+
${topics}
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
${discussFreelyTopics}
|
|
68
|
+
== OBJECTIVES FOR THIS CALL ==
|
|
70
69
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
${objectives !== ' (none specified)'
|
|
71
|
+
? `What ${ownerName} wants to achieve:\n${objectives}`
|
|
72
|
+
: (tierGoals && tierGoals.length > 0
|
|
73
|
+
? `At the ${accessTier} access level, ${ownerName}'s objectives are:\n${formatList(tierGoals)}`
|
|
74
|
+
: `No specific objectives configured. Focus on general discovery and relationship building.`)}
|
|
76
75
|
|
|
77
76
|
== WHAT THEY SHARED WITH YOU ==
|
|
78
77
|
|
|
@@ -123,16 +122,13 @@ PACING RULES:
|
|
|
123
122
|
|
|
124
123
|
Access level for this call: ${accessTier}
|
|
125
124
|
|
|
126
|
-
|
|
127
|
-
${
|
|
128
|
-
|
|
129
|
-
DISCUSS FREELY (share when relevant):
|
|
130
|
-
${discussFreelyTopics}
|
|
125
|
+
TOPICS (discuss openly):
|
|
126
|
+
${topics}
|
|
131
127
|
|
|
132
|
-
|
|
133
|
-
${
|
|
128
|
+
DO NOT DISCUSS (redirect):
|
|
129
|
+
${doNotDiscuss}
|
|
134
130
|
|
|
135
|
-
When
|
|
131
|
+
When redirecting, don't say "I can't discuss that." Instead redirect naturally, acknowledge without detail, or suggest the owners connect directly.
|
|
136
132
|
|
|
137
133
|
NEVER disclose:
|
|
138
134
|
${neverDisclose}
|
|
@@ -178,9 +174,9 @@ function buildAdaptiveConnectionPrompt(options) {
|
|
|
178
174
|
} = options;
|
|
179
175
|
|
|
180
176
|
const {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
177
|
+
topics = ' (none specified)',
|
|
178
|
+
objectives = ' (none specified)',
|
|
179
|
+
doNotDiscuss = ' (none specified)',
|
|
184
180
|
neverDisclose = ' (none specified)'
|
|
185
181
|
} = tierTopics || {};
|
|
186
182
|
|
|
@@ -217,17 +213,16 @@ ${openQuestions}
|
|
|
217
213
|
|
|
218
214
|
== WHAT YOU BRING TO THE TABLE ==
|
|
219
215
|
|
|
220
|
-
${ownerName}
|
|
221
|
-
${
|
|
216
|
+
${ownerName}'s topics of interest:
|
|
217
|
+
${topics}
|
|
222
218
|
|
|
223
|
-
|
|
224
|
-
${discussFreelyTopics}
|
|
219
|
+
== OBJECTIVES FOR THIS CALL ==
|
|
225
220
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
221
|
+
${objectives !== ' (none specified)'
|
|
222
|
+
? `What ${ownerName} wants to achieve:\n${objectives}`
|
|
223
|
+
: (tierGoals && tierGoals.length > 0
|
|
224
|
+
? `At the ${accessTier} access level, ${ownerName}'s objectives are:\n${formatList(tierGoals)}`
|
|
225
|
+
: `No specific objectives configured. Focus on general discovery and relationship building.`)}
|
|
231
226
|
|
|
232
227
|
== WHAT THEY SHARED WITH YOU ==
|
|
233
228
|
|
|
@@ -265,16 +260,13 @@ Pacing:
|
|
|
265
260
|
|
|
266
261
|
Access level for this call: ${accessTier}
|
|
267
262
|
|
|
268
|
-
|
|
269
|
-
${
|
|
270
|
-
|
|
271
|
-
DISCUSS FREELY (share when relevant):
|
|
272
|
-
${discussFreelyTopics}
|
|
263
|
+
TOPICS (discuss openly):
|
|
264
|
+
${topics}
|
|
273
265
|
|
|
274
|
-
|
|
275
|
-
${
|
|
266
|
+
DO NOT DISCUSS (redirect):
|
|
267
|
+
${doNotDiscuss}
|
|
276
268
|
|
|
277
|
-
When
|
|
269
|
+
When redirecting, do not mention policy mechanics. Redirect naturally and suggest direct owner follow-up when needed.
|
|
278
270
|
|
|
279
271
|
NEVER disclose:
|
|
280
272
|
${neverDisclose}
|
package/src/routes/dashboard.js
CHANGED
|
@@ -1032,9 +1032,9 @@ function createDashboardApiRouter(options = {}) {
|
|
|
1032
1032
|
disclosure: configTier.disclosure || 'minimal',
|
|
1033
1033
|
examples: sanitizeStringArray(configTier.examples || [], 20, 120),
|
|
1034
1034
|
manifest: {
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1035
|
+
topics: manifestTier.topics || [],
|
|
1036
|
+
objectives: manifestTier.objectives || [],
|
|
1037
|
+
do_not_discuss: manifestTier.do_not_discuss || []
|
|
1038
1038
|
}
|
|
1039
1039
|
};
|
|
1040
1040
|
});
|
|
@@ -1082,11 +1082,11 @@ function createDashboardApiRouter(options = {}) {
|
|
|
1082
1082
|
|
|
1083
1083
|
if (body.manifest) {
|
|
1084
1084
|
const manifest = loadManifest();
|
|
1085
|
-
manifest.
|
|
1086
|
-
manifest.
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1085
|
+
manifest.tiers = manifest.tiers || {};
|
|
1086
|
+
manifest.tiers[tierId] = {
|
|
1087
|
+
topics: parseTopicObjects(body.manifest.topics),
|
|
1088
|
+
objectives: parseTopicObjects(body.manifest.objectives),
|
|
1089
|
+
do_not_discuss: parseTopicObjects(body.manifest.do_not_discuss)
|
|
1090
1090
|
};
|
|
1091
1091
|
saveManifest(manifest);
|
|
1092
1092
|
}
|
package/src/server.js
CHANGED
|
@@ -184,24 +184,34 @@ function extractSignalPhrases(text, pattern, maxItems = 3) {
|
|
|
184
184
|
|
|
185
185
|
function collectTopicKeywords(tierTopics) {
|
|
186
186
|
const keywords = new Set();
|
|
187
|
-
|
|
187
|
+
|
|
188
|
+
const topicsList = tierTopics?.topics || [];
|
|
189
|
+
const objectivesList = tierTopics?.objectives || [];
|
|
190
|
+
|
|
191
|
+
for (const item of topicsList) {
|
|
192
|
+
for (const part of [item?.topic, item?.description, item?.detail]) {
|
|
193
|
+
if (!part) continue;
|
|
194
|
+
const terms = String(part)
|
|
195
|
+
.toLowerCase()
|
|
196
|
+
.split(/[^a-z0-9]+/)
|
|
197
|
+
.filter(term => term.length >= 4);
|
|
198
|
+
for (const term of terms.slice(0, 6)) {
|
|
199
|
+
keywords.add(term);
|
|
200
|
+
if (keywords.size >= 48) return Array.from(keywords);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
188
204
|
|
|
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
|
-
}
|
|
205
|
+
for (const item of objectivesList) {
|
|
206
|
+
for (const part of [item?.objective, item?.description]) {
|
|
207
|
+
if (!part) continue;
|
|
208
|
+
const terms = String(part)
|
|
209
|
+
.toLowerCase()
|
|
210
|
+
.split(/[^a-z0-9]+/)
|
|
211
|
+
.filter(term => term.length >= 4);
|
|
212
|
+
for (const term of terms.slice(0, 6)) {
|
|
213
|
+
keywords.add(term);
|
|
214
|
+
if (keywords.size >= 48) return Array.from(keywords);
|
|
205
215
|
}
|
|
206
216
|
}
|
|
207
217
|
}
|