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.
- package/bin/cli.js +79 -2
- 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 +121 -0
- package/package.json +1 -1
- package/scripts/install-skills.js +80 -0
- package/scripts/postinstall.js +12 -0
- 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/claude-subagent.js +6 -5
- package/src/lib/config.js +42 -0
- package/src/lib/conversation-driver.js +74 -24
- package/src/lib/openclaw-integration.js +22 -66
- package/src/lib/runtime-adapter.js +8 -3
- package/src/lib/summary-formatter.js +168 -0
- package/src/lib/summary-prompt.js +203 -0
- package/src/lib/tokens.js +13 -1
- package/src/lib/turn-timeout.js +52 -0
- package/src/lib/update-checker.js +93 -0
- package/src/lib/update-manager.js +313 -0
- package/src/routes/a2a.js +9 -1
- package/src/routes/dashboard.js +103 -1
- package/src/server.js +130 -26
|
@@ -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
|
|
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
|
+
|