a2acalling 0.6.48 → 0.6.50

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.
@@ -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 };
package/src/lib/tokens.js CHANGED
@@ -56,6 +56,11 @@ function sanitizeCustomFields(fields, options = {}) {
56
56
  return cleaned;
57
57
  }
58
58
 
59
+ function parsePositiveTimeoutMs(value) {
60
+ const parsed = Number.parseInt(String(value ?? ''), 10);
61
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
62
+ }
63
+
59
64
  class TokenStore {
60
65
  constructor(configDir = DEFAULT_CONFIG_DIR) {
61
66
  this.configDir = configDir;
@@ -196,7 +201,8 @@ class TokenStore {
196
201
  // Snapshot of actual capabilities at creation time
197
202
  allowedTopics = null, // Array of topic strings, e.g. ['chat', 'calendar.read']
198
203
  allowedGoals = null, // Array of goal strings, e.g. ['grow-network', 'find-collaborators']
199
- tierSettings = null // Object with tier-specific settings
204
+ tierSettings = null, // Object with tier-specific settings
205
+ timeoutMs = null
200
206
  } = options;
201
207
 
202
208
  const tier = String(permissions || 'public').trim() || 'public';
@@ -255,6 +261,7 @@ class TokenStore {
255
261
  capabilities: capabilities || defaultCapabilities,
256
262
  allowed_topics: allowedTopics || defaultTopics[tier] || ['chat'],
257
263
  allowed_goals: allowedGoals || defaultGoals[tier] || [],
264
+ timeout_ms: parsePositiveTimeoutMs(timeoutMs),
258
265
  tier_settings: tierSettings || {}, // Snapshot of settings at creation
259
266
  disclosure,
260
267
  notify,
@@ -327,6 +334,10 @@ class TokenStore {
327
334
  || TokenStore.DEFAULT_CAPABILITIES[tier]
328
335
  || ['context-read'];
329
336
 
337
+ const timeoutMs = parsePositiveTimeoutMs(record.timeout_ms)
338
+ || parsePositiveTimeoutMs(record.tier_settings?.timeout_ms)
339
+ || parsePositiveTimeoutMs(record.tier_settings?.timeoutMs);
340
+
330
341
  return {
331
342
  valid: true,
332
343
  id: record.id,
@@ -335,6 +346,7 @@ class TokenStore {
335
346
  capabilities,
336
347
  allowed_topics: record.allowed_topics || ['chat'],
337
348
  allowed_goals: record.allowed_goals || [],
349
+ timeout_ms: timeoutMs,
338
350
  tier_settings: record.tier_settings || {},
339
351
  disclosure: record.disclosure,
340
352
  notify: record.notify,
@@ -0,0 +1,52 @@
1
+ const HARD_FALLBACK_TURN_TIMEOUT_MS = 300000;
2
+
3
+ function parsePositiveInt(value) {
4
+ const parsed = Number.parseInt(String(value ?? ''), 10);
5
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
6
+ }
7
+
8
+ function resolveTokenTimeoutMs(token) {
9
+ if (!token || typeof token !== 'object') {
10
+ return null;
11
+ }
12
+
13
+ const topLevel = parsePositiveInt(token.timeout_ms ?? token.timeoutMs);
14
+ if (topLevel) {
15
+ return topLevel;
16
+ }
17
+
18
+ const tierSettings = token.tier_settings || token.tierSettings;
19
+ if (!tierSettings || typeof tierSettings !== 'object') {
20
+ return null;
21
+ }
22
+ return parsePositiveInt(tierSettings.timeout_ms ?? tierSettings.timeoutMs);
23
+ }
24
+
25
+ function resolveTurnTimeoutMs(options = {}) {
26
+ const tokenTimeoutMs = parsePositiveInt(options.tokenTimeoutMs);
27
+ if (tokenTimeoutMs) {
28
+ return tokenTimeoutMs;
29
+ }
30
+
31
+ const envTimeoutMs = parsePositiveInt(
32
+ options.envTimeoutMs !== undefined ? options.envTimeoutMs : process.env.A2A_TURN_TIMEOUT
33
+ );
34
+ if (envTimeoutMs) {
35
+ return envTimeoutMs;
36
+ }
37
+
38
+ const configTimeoutMs = parsePositiveInt(options.configTimeoutMs);
39
+ if (configTimeoutMs) {
40
+ return configTimeoutMs;
41
+ }
42
+
43
+ const fallbackTimeoutMs = parsePositiveInt(options.hardFallbackMs);
44
+ return fallbackTimeoutMs || HARD_FALLBACK_TURN_TIMEOUT_MS;
45
+ }
46
+
47
+ module.exports = {
48
+ HARD_FALLBACK_TURN_TIMEOUT_MS,
49
+ parsePositiveInt,
50
+ resolveTokenTimeoutMs,
51
+ resolveTurnTimeoutMs
52
+ };
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Update Checker
3
+ *
4
+ * Zero-dependency npm version checks for a2acalling.
5
+ */
6
+
7
+ const REGISTRY_URL = 'https://registry.npmjs.org/a2acalling/latest';
8
+ const FETCH_TIMEOUT_MS = 15000;
9
+
10
+ function parseVersion(str) {
11
+ if (!str || typeof str !== 'string') return null;
12
+ const match = str.trim().match(/^(\d+)\.(\d+)\.(\d+)/);
13
+ if (!match) return null;
14
+ return {
15
+ major: Number.parseInt(match[1], 10),
16
+ minor: Number.parseInt(match[2], 10),
17
+ patch: Number.parseInt(match[3], 10)
18
+ };
19
+ }
20
+
21
+ function compareVersions(a, b) {
22
+ const va = parseVersion(a);
23
+ const vb = parseVersion(b);
24
+ if (!va || !vb) return 0;
25
+ if (va.major !== vb.major) return va.major < vb.major ? -1 : 1;
26
+ if (va.minor !== vb.minor) return va.minor < vb.minor ? -1 : 1;
27
+ if (va.patch !== vb.patch) return va.patch < vb.patch ? -1 : 1;
28
+ return 0;
29
+ }
30
+
31
+ function isSameMajor(a, b) {
32
+ const va = parseVersion(a);
33
+ const vb = parseVersion(b);
34
+ if (!va || !vb) return false;
35
+ return va.major === vb.major;
36
+ }
37
+
38
+ async function fetchLatestVersion() {
39
+ try {
40
+ const controller = new AbortController();
41
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
42
+ const res = await fetch(REGISTRY_URL, {
43
+ signal: controller.signal,
44
+ headers: { Accept: 'application/json' }
45
+ });
46
+ clearTimeout(timeout);
47
+ if (!res.ok) {
48
+ return { error: `Registry returned ${res.status}` };
49
+ }
50
+ const data = await res.json();
51
+ if (!data || typeof data.version !== 'string') {
52
+ return { error: 'No version field in registry response' };
53
+ }
54
+ return { version: data.version };
55
+ } catch (err) {
56
+ if (err && err.name === 'AbortError') {
57
+ return { error: 'Registry request timed out' };
58
+ }
59
+ return { error: err && err.message ? err.message : 'Unknown fetch error' };
60
+ }
61
+ }
62
+
63
+ async function checkForUpdate(currentVersion) {
64
+ const result = await fetchLatestVersion();
65
+ if (result.error) {
66
+ return {
67
+ available: false,
68
+ current: currentVersion,
69
+ latest: null,
70
+ sameMajor: false,
71
+ error: result.error
72
+ };
73
+ }
74
+
75
+ const latest = result.version;
76
+ return {
77
+ available: compareVersions(currentVersion, latest) < 0,
78
+ current: currentVersion,
79
+ latest,
80
+ sameMajor: isSameMajor(currentVersion, latest)
81
+ };
82
+ }
83
+
84
+ module.exports = {
85
+ REGISTRY_URL,
86
+ FETCH_TIMEOUT_MS,
87
+ parseVersion,
88
+ compareVersions,
89
+ isSameMajor,
90
+ fetchLatestVersion,
91
+ checkForUpdate
92
+ };
93
+