a2acalling 0.6.47 → 0.6.49

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/src/lib/config.js CHANGED
@@ -233,6 +233,14 @@ const DEFAULT_CONFIG = {
233
233
  description: '',
234
234
  hostname: ''
235
235
  },
236
+
237
+ // Auto-updater
238
+ auto_update: {
239
+ enabled: true,
240
+ intervalMs: 60 * 60 * 1000,
241
+ allowMajor: false,
242
+ lastGoodVersion: null
243
+ },
236
244
 
237
245
  // Timestamps
238
246
  createdAt: null,
@@ -381,6 +389,39 @@ class A2AConfig {
381
389
  return this.config;
382
390
  }
383
391
 
392
+ getAutoUpdate() {
393
+ const current = (this.config && typeof this.config.auto_update === 'object' && this.config.auto_update)
394
+ ? this.config.auto_update
395
+ : {};
396
+ return {
397
+ enabled: current.enabled !== false,
398
+ intervalMs: Number.isFinite(current.intervalMs) && current.intervalMs > 0
399
+ ? current.intervalMs
400
+ : DEFAULT_CONFIG.auto_update.intervalMs,
401
+ allowMajor: Boolean(current.allowMajor),
402
+ lastGoodVersion: current.lastGoodVersion || null
403
+ };
404
+ }
405
+
406
+ setAutoUpdate(patch = {}) {
407
+ const current = this.getAutoUpdate();
408
+ const next = { ...current };
409
+ if (patch.enabled !== undefined) next.enabled = Boolean(patch.enabled);
410
+ if (patch.intervalMs !== undefined) {
411
+ const parsed = Number.parseInt(String(patch.intervalMs), 10);
412
+ if (Number.isFinite(parsed) && parsed > 0) {
413
+ next.intervalMs = parsed;
414
+ }
415
+ }
416
+ if (patch.allowMajor !== undefined) next.allowMajor = Boolean(patch.allowMajor);
417
+ if (patch.lastGoodVersion !== undefined) {
418
+ next.lastGoodVersion = patch.lastGoodVersion ? String(patch.lastGoodVersion).trim() : null;
419
+ }
420
+ this.config.auto_update = next;
421
+ this._save();
422
+ return next;
423
+ }
424
+
384
425
  // Export for sharing
385
426
  export() {
386
427
  return {
@@ -21,6 +21,7 @@ const {
21
21
  } = require('./prompt-template');
22
22
  const { getTopicsForTier, formatTopicsForPrompt, loadManifest } = require('./disclosure');
23
23
  const { createLogger } = require('./logger');
24
+ const { buildUnifiedSummaryPrompt } = require('./summary-prompt');
24
25
 
25
26
  const logger = createLogger({ component: 'a2a.conversation-driver' });
26
27
 
@@ -143,37 +144,75 @@ class ConversationDriver {
143
144
  _buildSummarizer() {
144
145
  const runtime = this.runtime;
145
146
  const agentContext = this.agentContext;
147
+ const caller = this.caller;
148
+ const tier = this.tier;
146
149
 
147
150
  return async (messages, ownerContext) => {
148
151
  if (!messages || messages.length === 0) {
149
152
  return { summary: null };
150
153
  }
151
154
 
152
- // Build the summary prompt (same structure as server.js generateSummary)
153
- const messageText = messages.map(m => {
154
- const role = m.direction === 'inbound' ? '[Them]' : '[You]';
155
- return `${role}: ${m.content}`;
156
- }).join('\n');
155
+ // Build transcript in unified format
156
+ const transcript = messages.map(m => ({
157
+ direction: m.direction,
158
+ content: m.content
159
+ }));
157
160
 
158
- const prompt = `Summarize this A2A call for the owner. Write from the owner's perspective.
159
-
160
- You initiated this call.
161
-
162
- Conversation:
163
- ${messageText}
161
+ // Load disclosure manifest for the tier
162
+ let disclosure = null;
163
+ try {
164
+ const tierTopics = getTopicsForTier(tier);
165
+ if (tierTopics) {
166
+ disclosure = {
167
+ topics: tierTopics.topics || [],
168
+ objectives: tierTopics.objectives || [],
169
+ doNotDiscuss: tierTopics.do_not_discuss || [],
170
+ neverDisclose: tierTopics.never_disclose || []
171
+ };
172
+ }
173
+ } catch (e) {
174
+ // Disclosure is optional — continue without it
175
+ }
164
176
 
165
- Structure your summary with these sections:
177
+ // Look up collaboration state from convStore if available
178
+ let collaborationState = null;
179
+ if (this.convStore) {
180
+ try {
181
+ const dbState = this.convStore.loadCollabState(this.lastConversationId || '');
182
+ if (dbState) {
183
+ collaborationState = {
184
+ phase: dbState.phase,
185
+ overlapScore: dbState.overlapScore,
186
+ turnCount: dbState.turnCount,
187
+ activeThreads: dbState.activeThreads,
188
+ candidateCollaborations: dbState.candidateCollaborations,
189
+ closeSignal: dbState.closeSignal
190
+ };
191
+ }
192
+ } catch (e) {
193
+ // Best effort
194
+ }
195
+ }
166
196
 
167
- **Who:** Who you called, who they represent, key facts about them.
168
- **Key Discoveries:** What was learned about the other side — capabilities, interests, blind spots.
169
- **Collaboration Potential:** Rate HIGH/MEDIUM/LOW. List specific opportunities identified.
170
- **What We Learned vs Shared:** Brief information exchange audit — what did we get, what did we give.
171
- **Recommended Follow-Up:**
172
- - [ ] Actionable item 1
173
- - [ ] Actionable item 2
174
- **Assessment:** One-sentence strategic value judgment.
197
+ // Map ownerContext into unified format
198
+ const unifiedOwnerContext = {
199
+ agentName: agentContext.name,
200
+ ownerName: agentContext.owner,
201
+ goals: ownerContext?.goals || []
202
+ };
175
203
 
176
- Be concise but specific. No filler.`;
204
+ const prompt = buildUnifiedSummaryPrompt({
205
+ transcript,
206
+ callerInfo: {
207
+ name: caller.name || null,
208
+ owner: caller.owner || null,
209
+ context: caller.context || null
210
+ },
211
+ conversationObjective: 'You initiated this call.',
212
+ disclosure,
213
+ collaborationState,
214
+ ownerContext: unifiedOwnerContext
215
+ });
177
216
 
178
217
  // Try runtime.summarize if available (OpenClaw path)
179
218
  if (typeof runtime.summarize === 'function') {
@@ -505,6 +544,8 @@ Be concise but specific. No filler.`;
505
544
  }
506
545
 
507
546
  // Conclude locally with summarizer
547
+ // Store conversationId so _buildSummarizer can look up collab state
548
+ this.lastConversationId = conversationId;
508
549
  let summary = null;
509
550
  if (this.convStore) {
510
551
  try {
@@ -7,6 +7,7 @@
7
7
  const fs = require('fs');
8
8
  const path = require('path');
9
9
  const { createLogger } = require('./logger');
10
+ const { buildUnifiedSummaryPrompt } = require('./summary-prompt');
10
11
 
11
12
  const logger = createLogger({ component: 'a2a.openclaw-integration' });
12
13
 
@@ -89,73 +90,28 @@ function loadOwnerContext(workspaceDir = process.cwd(), options = {}) {
89
90
  * Track the exchange balance AND surface partnership opportunities.
90
91
  */
91
92
  function buildSummaryPrompt(messages, ownerContext, callerInfo = {}) {
92
- const messageText = messages.map(m => {
93
- const role = m.direction === 'inbound' ? `[Caller${callerInfo.name ? ` - ${callerInfo.name}` : ''}]` : '[You]';
94
- return `${role}: ${m.content}`;
95
- }).join('\n\n');
96
-
97
- const goalsSection = ownerContext.goals?.length ? `### Current Goals\n- ${ownerContext.goals.join('\n- ')}` : '';
98
- const interestsSection = ownerContext.interests?.length ? `### Interests\n- ${ownerContext.interests.join('\n- ')}` : '';
99
-
100
- return `You just finished an A2A agent-to-agent call. Analyze it strategically for your owner.
101
-
102
- ## Philosophy
103
- A2A is cooperative AND adversarial. Each agent maximizes value for their own owner — but the best outcomes are mutual wins. Your job:
104
-
105
- 1. **Track the exchange** — what did we get vs give?
106
- 2. **Find mutual value** — what can BOTH parties gain?
107
- 3. **Surface alignment** — does this connect to owner's goals?
108
- 4. **Advise strategically** — protect interests while building relationships
109
-
110
- ## Your Owner's Context
111
- ${ownerContext.user ? `### From USER.md\n${ownerContext.user.slice(0, 2000)}` : ''}
112
-
113
- ${goalsSection}
114
-
115
- ${interestsSection}
116
-
117
- ## The Conversation
118
- ${messageText}
119
-
120
- ## Caller Context
121
- ${callerInfo.name ? `Name: ${callerInfo.name}` : 'Unknown caller'}
122
- ${callerInfo.context ? `Context: ${callerInfo.context}` : ''}
123
-
124
- ## Your Task
125
- Analyze as a strategic advisor. Return JSON:
126
-
127
- {
128
- "who": "Who called, who they represent, key facts about them",
129
-
130
- "keyDiscoveries": ["What was learned about the other side — capabilities, interests, blind spots"],
131
-
132
- "collaborationPotential": {
133
- "rating": "HIGH | MEDIUM | LOW",
134
- "opportunities": ["specific opportunities identified"]
135
- },
136
-
137
- "exchange": {
138
- "weGot": ["info, commitments, or value we extracted"],
139
- "weGave": ["info, compute, or commitments we provided"],
140
- "balance": "favorable | even | unfavorable"
141
- },
142
-
143
- "recommendedFollowUp": ["actionable items with specifics"],
144
-
145
- "assessment": "One-sentence strategic value judgment",
146
-
147
- "trust": {
148
- "assessment": "appropriate | too_high | too_low",
149
- "recommendation": "maintain | increase | decrease | revoke",
150
- "pattern": "What's their angle? Genuine partner or extractive?"
151
- },
152
-
153
- "ownerBrief": "2-3 sentences: the strategic takeaway for your owner"
154
- }
155
-
156
- Think like a strategic advisor: protect your owner's interests AND find mutual wins.
93
+ // Build transcript in unified format
94
+ const transcript = messages.map(m => ({
95
+ direction: m.direction,
96
+ content: m.content
97
+ }));
98
+
99
+ // Map ownerContext (which has .user, .goals, .interests from USER.md) into unified format
100
+ const unifiedOwnerContext = {
101
+ agentName: null,
102
+ ownerName: null,
103
+ goals: ownerContext?.goals || []
104
+ };
157
105
 
158
- JSON:`;
106
+ return buildUnifiedSummaryPrompt({
107
+ transcript,
108
+ callerInfo: {
109
+ name: callerInfo.name || null,
110
+ owner: callerInfo.owner || null,
111
+ context: callerInfo.context || null
112
+ },
113
+ ownerContext: unifiedOwnerContext
114
+ });
159
115
  }
160
116
 
161
117
  /**
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Summary Formatter
3
+ *
4
+ * Renders the structured JSON summary into a human-readable markdown
5
+ * format. Designed to be scannable, upbeat, and genuinely useful.
6
+ *
7
+ * Layout: most important info at the top, details below.
8
+ *
9
+ * 1. Headline (one sentence — the takeaway)
10
+ * 2. Quick Take (3 bullets — what happened, what to do)
11
+ * 3. Collaboration score + rating
12
+ * 4. Next Steps (actionable checklist)
13
+ * 5. --- separator ---
14
+ * 6. Details: who, exchange, disclosure, objectives, trust
15
+ */
16
+
17
+ const VIBE_LABELS = {
18
+ productive: 'Productive call',
19
+ exploratory: 'Exploratory — still feeling things out',
20
+ mismatch: 'Friendly but not much overlap',
21
+ guarded: 'Guarded — worth reviewing',
22
+ breakthrough: 'Great connection — real momentum'
23
+ };
24
+
25
+ /**
26
+ * Render a structured summary JSON object into human-readable markdown.
27
+ *
28
+ * @param {object} summary - The JSON output from the summary prompt
29
+ * @returns {string} Formatted markdown
30
+ */
31
+ function formatSummary(summary) {
32
+ const lines = [];
33
+ const s = summary;
34
+
35
+ // ── Headline ──
36
+ lines.push(`# Call with ${s.who?.name || 'Unknown'}`);
37
+ lines.push('');
38
+ lines.push(`**${s.headline}**`);
39
+ lines.push('');
40
+
41
+ // ── Vibe + Score one-liner ──
42
+ const vibeLabel = VIBE_LABELS[s.vibe] || s.vibe;
43
+ const scoreStr = s.collaboration?.score != null
44
+ ? ` | Overlap: ${s.collaboration.score.toFixed(2)}/1.00`
45
+ : '';
46
+ lines.push(`*${vibeLabel}${scoreStr}* \`${s.vibe}\``);
47
+ lines.push('');
48
+
49
+ // ── Quick Take ──
50
+ if (s.quickTake?.length) {
51
+ lines.push('### Quick Take');
52
+ for (const item of s.quickTake) {
53
+ lines.push(`- ${item}`);
54
+ }
55
+ lines.push('');
56
+ }
57
+
58
+ // ── Collaboration ──
59
+ if (s.collaboration) {
60
+ const c = s.collaboration;
61
+ lines.push(`### Collaboration: ${c.rating || 'N/A'}`);
62
+ if (c.scoreJustification) {
63
+ lines.push(c.scoreJustification);
64
+ }
65
+ if (c.opportunities?.length) {
66
+ lines.push('');
67
+ for (const opp of c.opportunities) {
68
+ lines.push(`- ${opp}`);
69
+ }
70
+ }
71
+ lines.push('');
72
+ }
73
+
74
+ // ── Next Steps ──
75
+ if (s.nextSteps?.length) {
76
+ lines.push('### Next Steps');
77
+ for (const step of s.nextSteps) {
78
+ lines.push(`- [ ] ${step}`);
79
+ }
80
+ lines.push('');
81
+ }
82
+
83
+ // ── Separator ──
84
+ lines.push('---');
85
+ lines.push('');
86
+
87
+ // ── Details Section ──
88
+ lines.push('### Details');
89
+ lines.push('');
90
+
91
+ // Who
92
+ if (s.who) {
93
+ lines.push(`**Who:** ${s.who.name || 'Unknown'}${s.who.represents ? ` — ${s.who.represents}` : ''}`);
94
+ if (s.who.keyFacts?.length) {
95
+ for (const fact of s.who.keyFacts) {
96
+ lines.push(`- ${fact}`);
97
+ }
98
+ }
99
+ lines.push('');
100
+ }
101
+
102
+ // Exchange
103
+ if (s.exchange) {
104
+ lines.push('**What We Exchanged**');
105
+ if (s.exchange.weGot?.length) {
106
+ lines.push(`- Got: ${s.exchange.weGot.join('; ')}`);
107
+ }
108
+ if (s.exchange.weGave?.length) {
109
+ lines.push(`- Gave: ${s.exchange.weGave.join('; ')}`);
110
+ }
111
+ if (s.exchange.balance) {
112
+ lines.push(`- Balance: ${s.exchange.balance}`);
113
+ }
114
+ lines.push('');
115
+ }
116
+
117
+ // Disclosure
118
+ if (s.disclosure) {
119
+ const d = s.disclosure;
120
+ const complianceLabel = d.compliance === 'clean' ? 'Clean — no issues'
121
+ : d.compliance === 'minor_concern' ? 'Minor concern — review below'
122
+ : d.compliance === 'violation' ? 'VIOLATION — action required'
123
+ : d.compliance;
124
+
125
+ lines.push(`**Disclosure:** ${complianceLabel} \`${d.compliance}\``);
126
+ if (d.topicsCovered?.length) {
127
+ lines.push(`- Covered: ${d.topicsCovered.join(', ')}`);
128
+ }
129
+ if (d.topicsAvoided?.length) {
130
+ lines.push(`- Properly avoided: ${d.topicsAvoided.join(', ')}`);
131
+ }
132
+ if (d.concerns?.length) {
133
+ for (const concern of d.concerns) {
134
+ lines.push(`- **Concern:** ${concern}`);
135
+ }
136
+ }
137
+ lines.push('');
138
+ }
139
+
140
+ // Objectives
141
+ if (s.objectives) {
142
+ const o = s.objectives;
143
+ const parts = [];
144
+ if (o.achieved?.length) parts.push(`Achieved: ${o.achieved.join(', ')}`);
145
+ if (o.partiallyAchieved?.length) parts.push(`In progress: ${o.partiallyAchieved.join(', ')}`);
146
+ if (o.notAchieved?.length) parts.push(`Not addressed: ${o.notAchieved.join(', ')}`);
147
+ if (parts.length) {
148
+ lines.push('**Objectives**');
149
+ for (const p of parts) lines.push(`- ${p}`);
150
+ lines.push('');
151
+ }
152
+ }
153
+
154
+ // Trust
155
+ if (s.trust) {
156
+ lines.push(`**Trust:** ${s.trust.level}${s.trust.reasoning ? ` — ${s.trust.reasoning}` : ''}`);
157
+ lines.push('');
158
+ }
159
+
160
+ // Assessment
161
+ if (s.assessment) {
162
+ lines.push(`**Bottom line:** ${s.assessment}`);
163
+ }
164
+
165
+ return lines.join('\n');
166
+ }
167
+
168
+ module.exports = { formatSummary, VIBE_LABELS };
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Unified Summary Prompt Builder
3
+ *
4
+ * Builds a comprehensive summary prompt that includes all context
5
+ * needed for accurate, auditable conversation summaries:
6
+ *
7
+ * - Conversation objective (why the call happened)
8
+ * - Disclosure manifest (what's in scope for this tier)
9
+ * - Collaboration state (phase, overlap score, threads)
10
+ * - Full transcript
11
+ * - Owner context
12
+ *
13
+ * Used by both OpenClaw and spawned-agent summary paths.
14
+ */
15
+
16
+ /**
17
+ * Build a unified summary prompt with full context.
18
+ *
19
+ * @param {object} options
20
+ * @param {Array} options.transcript - [{direction, content}]
21
+ * @param {object} options.callerInfo - {name, owner, context}
22
+ * @param {string} [options.conversationObjective] - Why this call was made
23
+ * @param {object} [options.disclosure] - {topics, objectives, doNotDiscuss, neverDisclose}
24
+ * @param {object} [options.collaborationState] - {phase, overlapScore, activeThreads, ...}
25
+ * @param {object} [options.ownerContext] - {agentName, ownerName, goals}
26
+ * @returns {string} The complete prompt
27
+ */
28
+ function buildUnifiedSummaryPrompt(options = {}) {
29
+ const {
30
+ transcript = [],
31
+ callerInfo = {},
32
+ conversationObjective,
33
+ disclosure,
34
+ collaborationState,
35
+ ownerContext = {}
36
+ } = options;
37
+
38
+ const sections = [];
39
+
40
+ // ── Header ──
41
+ sections.push(`You just finished an A2A agent-to-agent call. Summarize it for your owner.
42
+
43
+ Your tone: friendly, clear, and genuinely helpful. Lead with what matters most.
44
+ Write like you're briefing a smart friend — not filing a report.`);
45
+
46
+ // ── Conversation Objective ──
47
+ if (conversationObjective) {
48
+ sections.push(`## Why This Call Happened
49
+ ${conversationObjective}`);
50
+ }
51
+
52
+ // ── Owner Context ──
53
+ if (ownerContext.agentName || ownerContext.ownerName || ownerContext.goals) {
54
+ const parts = [];
55
+ if (ownerContext.agentName) parts.push(`You are: ${ownerContext.agentName}`);
56
+ if (ownerContext.ownerName) parts.push(`Your owner: ${ownerContext.ownerName}`);
57
+ if (ownerContext.goals?.length) {
58
+ parts.push(`Owner's current goals:\n${ownerContext.goals.map(g => `- ${g}`).join('\n')}`);
59
+ }
60
+ sections.push(`## Your Owner\n${parts.join('\n')}`);
61
+ }
62
+
63
+ // ── Disclosure Manifest ──
64
+ if (disclosure) {
65
+ const discParts = [];
66
+
67
+ if (disclosure.topics?.length) {
68
+ discParts.push('### Topics In Scope');
69
+ for (const t of disclosure.topics) {
70
+ discParts.push(`- **${t.topic}**: ${t.description}`);
71
+ }
72
+ }
73
+
74
+ if (disclosure.objectives?.length) {
75
+ discParts.push('\n### Conversation Objectives');
76
+ for (const o of disclosure.objectives) {
77
+ const label = o.objective || o.topic;
78
+ discParts.push(`- **${label}**: ${o.description}`);
79
+ }
80
+ }
81
+
82
+ if (disclosure.doNotDiscuss?.length) {
83
+ discParts.push('\n### Do Not Discuss (Deflect These)');
84
+ for (const d of disclosure.doNotDiscuss) {
85
+ discParts.push(`- **${d.topic}**: ${d.reason}`);
86
+ }
87
+ }
88
+
89
+ if (disclosure.neverDisclose?.length) {
90
+ discParts.push('\n### Never Disclose (Hard Blocks)');
91
+ for (const n of disclosure.neverDisclose) {
92
+ discParts.push(`- ${n}`);
93
+ }
94
+ }
95
+
96
+ sections.push(`## Disclosure Boundaries\nThese are the rules your agent operated under. Check whether they were followed.\n\n${discParts.join('\n')}`);
97
+ }
98
+
99
+ // ── Collaboration State ──
100
+ if (collaborationState) {
101
+ const cs = collaborationState;
102
+ sections.push(`## Collaboration State at End of Call
103
+ - **Phase:** ${cs.phase || 'unknown'} (handshake -> exploring -> deepening -> converging -> close)
104
+ - **Overlap Score:** ${cs.overlapScore != null ? cs.overlapScore.toFixed(2) : 'unknown'}/1.00
105
+ - **Turn Count:** ${cs.turnCount || 'unknown'}
106
+ - **Active Threads:** ${cs.activeThreads?.length ? cs.activeThreads.join(', ') : 'none identified'}
107
+ - **Candidate Collaborations:** ${cs.candidateCollaborations?.length ? cs.candidateCollaborations.join(', ') : 'none yet'}
108
+ - **Close Signal:** ${cs.closeSignal ? 'yes' : 'no'}
109
+
110
+ ### What Overlap Score Means
111
+ - 0.00–0.30: Minimal alignment — different domains, graceful mismatch expected
112
+ - 0.30–0.60: Moderate — some shared interests, worth exploring
113
+ - 0.60–0.80: Strong — clear mutual value, specific opportunities emerging
114
+ - 0.80–1.00: Deep alignment — ready for concrete collaboration`);
115
+ }
116
+
117
+ // ── Transcript ──
118
+ const callerLabel = callerInfo.name || 'Caller';
119
+ const messageText = transcript.map(m => {
120
+ const role = m.direction === 'inbound' ? `[${callerLabel}]` : '[You]';
121
+ return `${role}: ${m.content}`;
122
+ }).join('\n\n');
123
+
124
+ sections.push(`## Caller
125
+ ${callerInfo.name ? `**Name:** ${callerInfo.name}` : 'Unknown caller'}
126
+ ${callerInfo.owner ? `**Represents:** ${callerInfo.owner}` : ''}
127
+ ${callerInfo.context ? `**Context:** ${callerInfo.context}` : ''}`);
128
+
129
+ sections.push(`## Full Transcript\n${messageText}`);
130
+
131
+ // ── Output Instructions ──
132
+ sections.push(`## Your Task
133
+
134
+ Summarize this call. Return valid JSON matching this exact schema:
135
+
136
+ {
137
+ "headline": "One sentence — the single most important takeaway for the owner",
138
+
139
+ "vibe": "productive | exploratory | mismatch | guarded | breakthrough",
140
+
141
+ "quickTake": [
142
+ "Most important discovery or outcome",
143
+ "Key opportunity or concern",
144
+ "Recommended immediate action"
145
+ ],
146
+
147
+ "who": {
148
+ "name": "Caller name",
149
+ "represents": "Who they work for or represent",
150
+ "keyFacts": ["Notable fact 1", "Notable fact 2"]
151
+ },
152
+
153
+ "collaboration": {
154
+ "score": 0.00,
155
+ "scoreJustification": "Why this score — what aligned, what didn't",
156
+ "rating": "HIGH | MEDIUM | LOW",
157
+ "opportunities": ["Specific opportunity with details"]
158
+ },
159
+
160
+ "exchange": {
161
+ "weGot": ["Info or value we received"],
162
+ "weGave": ["Info or value we shared"],
163
+ "balance": "favorable | even | unfavorable"
164
+ },
165
+
166
+ "disclosure": {
167
+ "compliance": "clean | minor_concern | violation",
168
+ "topicsCovered": ["In-scope topics that were discussed"],
169
+ "topicsAvoided": ["Topics that were properly deflected"],
170
+ "concerns": ["Any info shared that shouldn't have been, or empty array"]
171
+ },
172
+
173
+ "objectives": {
174
+ "achieved": ["Objectives that were met"],
175
+ "partiallyAchieved": ["Objectives with some progress"],
176
+ "notAchieved": ["Objectives not addressed"]
177
+ },
178
+
179
+ "nextSteps": [
180
+ "Specific actionable follow-up 1",
181
+ "Specific actionable follow-up 2"
182
+ ],
183
+
184
+ "trust": {
185
+ "level": "maintain | increase | decrease | revoke",
186
+ "reasoning": "One sentence — why this trust recommendation"
187
+ },
188
+
189
+ "assessment": "One sentence — strategic value judgment for the owner"
190
+ }
191
+
192
+ Important:
193
+ - Validate the collaboration score — does it match what actually happened in the conversation?
194
+ - Check disclosure compliance — was any never_disclose or do_not_discuss info leaked?
195
+ - Be honest about objectives — don't inflate partial progress into "achieved"
196
+ - quickTake should be genuinely useful, not generic platitudes
197
+
198
+ JSON:`);
199
+
200
+ return sections.join('\n\n');
201
+ }
202
+
203
+ module.exports = { buildUnifiedSummaryPrompt };