a2acalling 0.6.19 → 0.6.21

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 CHANGED
@@ -42,7 +42,20 @@ a2a quickstart # run onboarding
42
42
  a2a quickstart --force # re-run from scratch
43
43
  ```
44
44
 
45
- After the onboarding walkthrough prints, scan your workspace files (USER.md, SOUL.md, etc.), build the disclosure JSON, and submit:
45
+ After the onboarding walkthrough prints, scan ALL available context to build the disclosure JSON:
46
+
47
+ **Primary sources:**
48
+ - USER.md, SOUL.md — owner identity, interests, values
49
+ - memory/*.md — may contain relevant context
50
+
51
+ **If workspace files are missing/empty, also scan:**
52
+ - ~/.gitconfig — name, email hints
53
+ - Environment: whoami, hostname, $USER
54
+ - ~/.config/ — installed tools reveal work domain
55
+ - Installed CLIs — infer developer/devops/data roles
56
+ - System hostname — may indicate project or role
57
+
58
+ Build disclosure tiers from whatever context exists, then submit:
46
59
 
47
60
  ```bash
48
61
  a2a quickstart --submit '<json>'
@@ -101,13 +114,16 @@ a2a quickstart --port 3001 --interactive
101
114
 
102
115
  If reverse proxy/ingress is required, Quickstart will stop and ask for explicit confirmation (`--confirm-ingress`).
103
116
 
104
- Full disclosure onboarding (manifest editing) remains available below: it generates a disclosure manifest that controls what topics your agent leads with, discusses, or deflects during A2A calls — scoped by access tier (public, friends, family).
117
+ Full disclosure onboarding (manifest editing) remains available below: it generates a disclosure manifest that controls what topics your agent discusses or redirects during A2A calls — scoped by access tier (public, friends, family).
105
118
 
106
119
  This onboarding is required before the first `/a2a call`. The owner must approve permissions first.
107
120
 
108
121
  Flow:
109
122
 
110
- 1. Scan USER.md, HEARTBEAT.md, SOUL.md to generate a default manifest
123
+ 1. Scan ALL available context to generate a default manifest:
124
+ - Primary: USER.md, SOUL.md, memory/*.md
125
+ - Fallback: ~/.gitconfig, env vars, hostname, installed tools
126
+ - Infer owner's domain from system state if workspace is empty
111
127
  2. Present the manifest as a numbered text list grouped by tier:
112
128
 
113
129
  ```
package/bin/cli.js CHANGED
@@ -336,23 +336,10 @@ async function handleDisclosureSubmit(args, commandLabel = 'onboard') {
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
339
+ // Helper to extract topic names
340
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
347
- const out = [];
348
- for (const section of [tierData.lead_with, tierData.discuss_freely, tierData.deflect]) {
349
- if (!Array.isArray(section)) continue;
350
- for (const item of section) {
351
- const t = String(item && item.topic || '').trim();
352
- if (t && !out.includes(t)) out.push(t);
353
- }
354
- }
355
- return out;
341
+ if (!tierData || !Array.isArray(tierData.topics)) return [];
342
+ return tierData.topics.map(t => String(t && t.topic || '').trim()).filter(Boolean);
356
343
  }
357
344
 
358
345
  // Get tiers data (support both new 'tiers' key and legacy 'topics' key)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "a2acalling",
3
- "version": "0.6.19",
3
+ "version": "0.6.21",
4
4
  "description": "Agent-to-agent calling for OpenClaw - A2A agent communication",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -172,24 +172,9 @@ function getTopicsForTier(tier) {
172
172
 
173
173
  for (const t of tiersToMerge) {
174
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
- }
175
+ if (tierData.topics) merged.topics.push(...tierData.topics);
183
176
  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
- }
177
+ if (tierData.do_not_discuss) merged.do_not_discuss.push(...tierData.do_not_discuss);
193
178
  }
194
179
 
195
180
  // Remove do_not_discuss items that appear in topics (higher tiers promote them)
@@ -229,11 +214,7 @@ function formatTopicsForPrompt(tierTopics) {
229
214
  doNotDiscuss: formatDoNotDiscuss(tierTopics.do_not_discuss),
230
215
  neverDisclose: tierTopics.never_disclose?.length
231
216
  ? tierTopics.never_disclose.map(item => ` - ${item}`).join('\n')
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)
217
+ : ' (none specified)'
237
218
  };
238
219
  }
239
220
 
@@ -405,102 +386,72 @@ function validateDisclosureSubmission(data) {
405
386
  errors.push(`Unknown tiers: ${extraTiers.join(', ')} — only public, friends, family are allowed`);
406
387
  }
407
388
 
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
389
  const LIST_LIMITS = { topics: 15, objectives: 8, do_not_discuss: 10 };
416
390
 
417
391
  for (const tier of TIER_HIERARCHY) {
418
392
  const tierData = tiersData[tier];
419
393
 
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}`);
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}`);
401
+ }
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;
428
407
  }
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
- }
408
+ if (item.topic.trim().length === 0) {
409
+ errors.push(`tiers.${tier}.topics[${i}].topic must not be empty`);
445
410
  }
446
- }
447
- }
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}`);
411
+ if (item.topic.length > 160) {
412
+ errors.push(`tiers.${tier}.topics[${i}]: topic exceeds 160 chars`);
455
413
  }
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
- }
414
+ const desc = item.description || '';
415
+ if (desc.length > 500) {
416
+ errors.push(`tiers.${tier}.topics[${i}]: description exceeds 500 chars`);
465
417
  }
466
418
  }
467
419
  }
420
+ }
468
421
 
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}`);
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}`);
429
+ }
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;
475
435
  }
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
- }
436
+ if (item.objective.trim().length === 0) {
437
+ errors.push(`tiers.${tier}.objectives[${i}].objective must not be empty`);
481
438
  }
482
439
  }
483
440
  }
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`);
491
- continue;
492
- }
493
- if (tierData[cat].length > LEGACY_LIMITS[cat]) {
494
- errors.push(`topics.${tier}.${cat} has ${tierData[cat].length} items — max ${LEGACY_LIMITS[cat]}`);
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}`);
495
450
  }
496
- for (let i = 0; i < tierData[cat].length; i++) {
497
- const item = tierData[cat][i];
451
+ for (let i = 0; i < tierData.do_not_discuss.length; i++) {
452
+ const item = tierData.do_not_discuss[i];
498
453
  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`);
454
+ errors.push(`tiers.${tier}.do_not_discuss[${i}]: must have "topic" (string) and "reason" (string)`);
504
455
  }
505
456
  }
506
457
  }
@@ -538,63 +489,38 @@ function validateDisclosureSubmission(data) {
538
489
  return { valid: false, manifest: null, errors };
539
490
  }
540
491
 
541
- // Rebuild clean structure (isNewFormat already set above)
492
+ // Rebuild clean structure
542
493
  const now = new Date().toISOString();
543
494
 
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
- };
562
- }
563
-
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 || ''
495
+ // Build clean manifest with new format only
496
+ const cleanTiers = {};
497
+ for (const tier of TIER_HIERARCHY) {
498
+ cleanTiers[tier] = {
499
+ topics: (tiersData[tier].topics || []).map(item => ({
500
+ topic: item.topic,
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
+ }))
571
511
  };
512
+ }
572
513
 
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
- };
514
+ const manifest = {
515
+ version: 2,
516
+ generated_at: now,
517
+ updated_at: now,
518
+ tiers: cleanTiers,
519
+ never_disclose: data.never_disclose || ['API keys', 'Other users\' data', 'Financial figures'],
520
+ personality_notes: data.personality_notes || ''
521
+ };
595
522
 
596
- return { valid: true, manifest, errors: [] };
597
- }
523
+ return { valid: true, manifest, errors: [] };
598
524
  }
599
525
 
600
526
  /**
@@ -654,15 +580,31 @@ function buildExtractionPrompt(availableFiles) {
654
580
  .join('\n') || ' (none detected)';
655
581
  fileSection = `### Available workspace files\n${fileList}\n\nRead the available files above and extract disclosure topics.`;
656
582
  } else {
657
- fileSection = `### Workspace files to look for
583
+ fileSection = `### Context sources to scan
584
+
585
+ **Primary sources (workspace files):**
658
586
  - USER.md — owner identity, bio, interests
659
587
  - SOUL.md — values, personality, communication style
660
- - HEARTBEAT.md — skip this (contains agent tasks, not disclosure topics)
661
- - SKILL.md — skip this (contains agent instructions)
662
- - CLAUDE.md — skip this (contains agent instructions)
663
588
  - memory/*.md — may contain relevant context
664
-
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.`;
589
+ - Skip HEARTBEAT.md, SKILL.md, CLAUDE.md (agent instructions, not owner info)
590
+
591
+ **If workspace files are missing or empty, scan these additional sources:**
592
+ - ~/.gitconfig — name, email, identity hints
593
+ - Environment: whoami, hostname, $USER, $HOME
594
+ - ~/.config/ — installed tools hint at owner's work
595
+ - ~/.ssh/config — project/server names may reveal domains
596
+ - Any README.md files in common locations
597
+ - Shell history patterns (languages, tools used)
598
+ - Installed CLIs (what's in PATH)
599
+
600
+ **Inference from system state:**
601
+ - Programming languages installed → likely a developer
602
+ - Cloud CLIs (aws, gcloud, az) → infrastructure/devops
603
+ - Design tools → creative work
604
+ - Data tools (jupyter, pandas) → data science
605
+ - Server hostname → may indicate role or project
606
+
607
+ Use ALL available context to build a reasonable disclosure profile. If truly nothing exists, create a minimal placeholder with "New agent setup - owner details pending" and suggest what info the owner should provide.`;
666
608
  }
667
609
 
668
610
  const jsonBlock = `\`\`\`json
@@ -49,9 +49,9 @@ function buildConnectionPrompt(options) {
49
49
  } = options;
50
50
 
51
51
  const {
52
- leadWithTopics = ' (none specified)',
53
- discussFreelyTopics = ' (none specified)',
54
- deflectTopics = ' (none specified)',
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} is currently focused on:
66
- ${leadWithTopics}
65
+ ${ownerName}'s topics of interest:
66
+ ${topics}
67
67
 
68
- Other areas of active interest:
69
- ${discussFreelyTopics}
68
+ == OBJECTIVES FOR THIS CALL ==
70
69
 
71
- == STRATEGIC GOALS FOR THIS CALL ==
72
-
73
- ${tierGoals && tierGoals.length > 0
74
- ? `At the ${accessTier} access level, ${ownerName}'s objectives are:\n${formatList(tierGoals)}\n\nPursue these goals naturally during conversation. Surface opportunities that align with them.`
75
- : `No specific goals configured for this tier. Focus on general discovery and relationship building.`}
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
- LEAD WITH (proactively share):
127
- ${leadWithTopics}
128
-
129
- DISCUSS FREELY (share when relevant):
130
- ${discussFreelyTopics}
125
+ TOPICS (discuss openly):
126
+ ${topics}
131
127
 
132
- DEFLECT (acknowledge but redirect):
133
- ${deflectTopics}
128
+ DO NOT DISCUSS (redirect):
129
+ ${doNotDiscuss}
134
130
 
135
- When deflecting, don't say "I can't discuss that." Instead redirect naturally, acknowledge without detail, or suggest the owners connect directly.
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
- leadWithTopics = ' (none specified)',
182
- discussFreelyTopics = ' (none specified)',
183
- deflectTopics = ' (none specified)',
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} is currently focused on:
221
- ${leadWithTopics}
216
+ ${ownerName}'s topics of interest:
217
+ ${topics}
222
218
 
223
- Other areas of active interest:
224
- ${discussFreelyTopics}
219
+ == OBJECTIVES FOR THIS CALL ==
225
220
 
226
- == STRATEGIC GOALS FOR THIS CALL ==
227
-
228
- ${tierGoals && tierGoals.length > 0
229
- ? `At the ${accessTier} access level, ${ownerName}'s objectives are:\n${formatList(tierGoals)}\n\nPursue these goals naturally during conversation. Surface opportunities that align with them.`
230
- : `No specific goals configured for this tier. Focus on general discovery and relationship building.`}
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
- LEAD WITH (proactively share):
269
- ${leadWithTopics}
270
-
271
- DISCUSS FREELY (share when relevant):
272
- ${discussFreelyTopics}
263
+ TOPICS (discuss openly):
264
+ ${topics}
273
265
 
274
- DEFLECT (acknowledge but redirect):
275
- ${deflectTopics}
266
+ DO NOT DISCUSS (redirect):
267
+ ${doNotDiscuss}
276
268
 
277
- When deflecting, do not mention policy mechanics. Redirect naturally and suggest direct owner follow-up when needed.
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}
@@ -1032,14 +1032,9 @@ function createDashboardApiRouter(options = {}) {
1032
1032
  disclosure: configTier.disclosure || 'minimal',
1033
1033
  examples: sanitizeStringArray(configTier.examples || [], 20, 120),
1034
1034
  manifest: {
1035
- // Support both new format (topics/objectives/do_not_discuss) and legacy
1036
- topics: manifestTier.topics || manifestTier.lead_with || [],
1035
+ topics: manifestTier.topics || [],
1037
1036
  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 || []
1037
+ do_not_discuss: manifestTier.do_not_discuss || []
1043
1038
  }
1044
1039
  };
1045
1040
  });
@@ -1087,11 +1082,11 @@ function createDashboardApiRouter(options = {}) {
1087
1082
 
1088
1083
  if (body.manifest) {
1089
1084
  const manifest = loadManifest();
1090
- manifest.topics = manifest.topics || {};
1091
- manifest.topics[tierId] = {
1092
- lead_with: parseTopicObjects(body.manifest.lead_with),
1093
- discuss_freely: parseTopicObjects(body.manifest.discuss_freely),
1094
- deflect: parseTopicObjects(body.manifest.deflect)
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)
1095
1090
  };
1096
1091
  saveManifest(manifest);
1097
1092
  }
package/src/server.js CHANGED
@@ -185,11 +185,7 @@ function extractSignalPhrases(text, pattern, maxItems = 3) {
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
- ];
188
+ const topicsList = tierTopics?.topics || [];
193
189
  const objectivesList = tierTopics?.objectives || [];
194
190
 
195
191
  for (const item of topicsList) {