a2acalling 0.6.48 → 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/bin/cli.js +23 -0
- package/docs/plans/2026-02-16-auto-updater.md +1284 -0
- package/docs/plans/2026-02-16-e2e-test-prompt-sequence.md +3085 -0
- package/docs/plans/2026-02-17-claude-code-codex-skills.md +770 -0
- package/docs/prompts/e2e-test-agent.md +368 -0
- package/docs/protocol.md +79 -0
- package/package.json +1 -1
- package/src/dashboard/public/app.js +108 -1
- package/src/dashboard/public/index.html +9 -0
- package/src/dashboard/public/style.css +27 -0
- package/src/lib/config.js +41 -0
- package/src/lib/conversation-driver.js +62 -21
- package/src/lib/openclaw-integration.js +22 -66
- package/src/lib/summary-formatter.js +168 -0
- package/src/lib/summary-prompt.js +203 -0
- package/src/lib/update-checker.js +93 -0
- package/src/lib/update-manager.js +313 -0
- package/src/routes/a2a.js +8 -1
- package/src/routes/dashboard.js +103 -1
- package/src/server.js +115 -26
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
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
})
|
|
155
|
+
// Build transcript in unified format
|
|
156
|
+
const transcript = messages.map(m => ({
|
|
157
|
+
direction: m.direction,
|
|
158
|
+
content: m.content
|
|
159
|
+
}));
|
|
157
160
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
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 };
|