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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const state = {
|
|
2
2
|
settings: null,
|
|
3
3
|
dashboardStatus: null,
|
|
4
|
+
autoUpdate: null,
|
|
4
5
|
callbookDevices: [],
|
|
5
6
|
contacts: [],
|
|
6
7
|
selectedContactId: null,
|
|
@@ -63,6 +64,20 @@ function esc(text) {
|
|
|
63
64
|
.replaceAll("'", ''');
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
function formatUpdaterState(stateValue) {
|
|
68
|
+
const state = String(stateValue || '').trim() || 'unknown';
|
|
69
|
+
return state.replaceAll('_', ' ');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function updaterPillClass(stateValue) {
|
|
73
|
+
const state = String(stateValue || '').trim();
|
|
74
|
+
if (state === 'failed') return 'err';
|
|
75
|
+
if (state === 'waiting_for_safe_restart' || state === 'checking' || state === 'downloading' || state === 'applying' || state === 'restarting') {
|
|
76
|
+
return 'warn';
|
|
77
|
+
}
|
|
78
|
+
return 'ok';
|
|
79
|
+
}
|
|
80
|
+
|
|
66
81
|
async function copyText(value) {
|
|
67
82
|
const text = String(value || '');
|
|
68
83
|
if (!text) return false;
|
|
@@ -284,7 +299,7 @@ function bindContactsActions() {
|
|
|
284
299
|
if (!urlEl || !serverNameEl) return;
|
|
285
300
|
if (mineEl && !mineEl.checked) return;
|
|
286
301
|
if (serverNameEl.value.trim()) return;
|
|
287
|
-
const match = String(urlEl.value || '').trim().match(/^(?:a2a|oclaw)
|
|
302
|
+
const match = String(urlEl.value || '').trim().match(/^(?:a2a|oclaw):\/\/([^/]+)\//);
|
|
288
303
|
if (match && match[1]) {
|
|
289
304
|
serverNameEl.value = match[1];
|
|
290
305
|
}
|
|
@@ -1035,14 +1050,59 @@ function renderCallbookStatus() {
|
|
|
1035
1050
|
`;
|
|
1036
1051
|
}
|
|
1037
1052
|
|
|
1053
|
+
function renderAutoUpdateStatus() {
|
|
1054
|
+
const el = document.getElementById('auto-update-status');
|
|
1055
|
+
const toggleBtn = document.getElementById('auto-update-toggle');
|
|
1056
|
+
if (!el) return;
|
|
1057
|
+
|
|
1058
|
+
const au = state.autoUpdate;
|
|
1059
|
+
if (!au) {
|
|
1060
|
+
el.textContent = 'Loading…';
|
|
1061
|
+
if (toggleBtn) toggleBtn.disabled = true;
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
const stateText = formatUpdaterState(au.state);
|
|
1066
|
+
const pillClass = updaterPillClass(au.state);
|
|
1067
|
+
const enabled = Boolean(au.enabled);
|
|
1068
|
+
const intervalSec = Number.isFinite(au.interval_ms) ? Math.floor(au.interval_ms / 1000) : null;
|
|
1069
|
+
|
|
1070
|
+
el.innerHTML = `
|
|
1071
|
+
<div><strong>Status:</strong> <span class="status-pill ${pillClass}">${esc(stateText)}</span></div>
|
|
1072
|
+
<div><strong>Enabled:</strong> ${enabled ? 'yes' : 'no'}</div>
|
|
1073
|
+
<div><strong>Current version:</strong> <span class="mono">${esc(au.current_version || '-')}</span></div>
|
|
1074
|
+
<div><strong>Latest version:</strong> <span class="mono">${esc(au.latest_version || '-')}</span></div>
|
|
1075
|
+
<div><strong>Target version:</strong> <span class="mono">${esc(au.target_version || '-')}</span></div>
|
|
1076
|
+
<div><strong>Active calls:</strong> ${esc(String(au.active_calls || 0))}</div>
|
|
1077
|
+
<div><strong>Interval:</strong> ${intervalSec === null ? '-' : `${intervalSec}s`}</div>
|
|
1078
|
+
<div><strong>Last checked:</strong> ${esc(fmtDate(au.last_checked_at))}</div>
|
|
1079
|
+
<div><strong>Last success:</strong> ${esc(fmtDate(au.last_success_at))}</div>
|
|
1080
|
+
${au.defer_reason ? `<div><strong>Deferred:</strong> ${esc(au.defer_reason)}</div>` : ''}
|
|
1081
|
+
${au.last_error ? `<div><strong>Error:</strong> <span class="mono">${esc(au.last_error)}</span></div>` : ''}
|
|
1082
|
+
`;
|
|
1083
|
+
|
|
1084
|
+
if (toggleBtn) {
|
|
1085
|
+
toggleBtn.disabled = false;
|
|
1086
|
+
toggleBtn.textContent = enabled ? 'Disable auto-update' : 'Enable auto-update';
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1038
1090
|
async function loadDashboardStatus(refreshIp = false) {
|
|
1039
1091
|
const payload = await request(`/status${refreshIp ? '?refresh_ip=true' : ''}`);
|
|
1040
1092
|
state.dashboardStatus = payload;
|
|
1093
|
+
state.autoUpdate = payload.auto_update || state.autoUpdate;
|
|
1041
1094
|
renderCallbookStatus();
|
|
1095
|
+
renderAutoUpdateStatus();
|
|
1042
1096
|
renderContacts();
|
|
1043
1097
|
renderContactDetail();
|
|
1044
1098
|
}
|
|
1045
1099
|
|
|
1100
|
+
async function loadAutoUpdateStatus() {
|
|
1101
|
+
const payload = await request('/update/status');
|
|
1102
|
+
state.autoUpdate = payload.auto_update || null;
|
|
1103
|
+
renderAutoUpdateStatus();
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1046
1106
|
function renderCallbookDevices() {
|
|
1047
1107
|
const tbody = document.querySelector('#callbook-devices-table tbody');
|
|
1048
1108
|
if (!tbody) return;
|
|
@@ -1154,6 +1214,47 @@ function bindCallbookActions() {
|
|
|
1154
1214
|
});
|
|
1155
1215
|
}
|
|
1156
1216
|
|
|
1217
|
+
function bindAutoUpdateActions() {
|
|
1218
|
+
document.getElementById('auto-update-refresh')?.addEventListener('click', () => {
|
|
1219
|
+
loadAutoUpdateStatus().catch(err => showNotice(err.message));
|
|
1220
|
+
});
|
|
1221
|
+
|
|
1222
|
+
document.getElementById('auto-update-check')?.addEventListener('click', async () => {
|
|
1223
|
+
try {
|
|
1224
|
+
await request('/update/check', { method: 'POST', body: JSON.stringify({}) });
|
|
1225
|
+
await loadAutoUpdateStatus();
|
|
1226
|
+
showNotice('Update check complete');
|
|
1227
|
+
} catch (err) {
|
|
1228
|
+
showNotice(err.message);
|
|
1229
|
+
}
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
document.getElementById('auto-update-now')?.addEventListener('click', async () => {
|
|
1233
|
+
try {
|
|
1234
|
+
await request('/update/now', { method: 'POST', body: JSON.stringify({}) });
|
|
1235
|
+
await loadAutoUpdateStatus();
|
|
1236
|
+
showNotice('Update triggered');
|
|
1237
|
+
} catch (err) {
|
|
1238
|
+
showNotice(err.message);
|
|
1239
|
+
}
|
|
1240
|
+
});
|
|
1241
|
+
|
|
1242
|
+
document.getElementById('auto-update-toggle')?.addEventListener('click', async () => {
|
|
1243
|
+
const au = state.autoUpdate || {};
|
|
1244
|
+
const nextEnabled = !Boolean(au.enabled);
|
|
1245
|
+
try {
|
|
1246
|
+
await request('/update/config', {
|
|
1247
|
+
method: 'PUT',
|
|
1248
|
+
body: JSON.stringify({ enabled: nextEnabled })
|
|
1249
|
+
});
|
|
1250
|
+
await loadAutoUpdateStatus();
|
|
1251
|
+
showNotice(nextEnabled ? 'Auto-update enabled' : 'Auto-update disabled');
|
|
1252
|
+
} catch (err) {
|
|
1253
|
+
showNotice(err.message);
|
|
1254
|
+
}
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1157
1258
|
function renderInvites() {
|
|
1158
1259
|
const tbody = document.querySelector('#invites-table tbody');
|
|
1159
1260
|
tbody.innerHTML = '';
|
|
@@ -1247,6 +1348,7 @@ async function bootstrap() {
|
|
|
1247
1348
|
bindContactsActions();
|
|
1248
1349
|
bindSettingsActions();
|
|
1249
1350
|
bindCallbookActions();
|
|
1351
|
+
bindAutoUpdateActions();
|
|
1250
1352
|
bindInviteActions();
|
|
1251
1353
|
bindRefreshButtons();
|
|
1252
1354
|
|
|
@@ -1254,6 +1356,7 @@ async function bootstrap() {
|
|
|
1254
1356
|
await Promise.all([
|
|
1255
1357
|
loadSettings(),
|
|
1256
1358
|
loadDashboardStatus(),
|
|
1359
|
+
loadAutoUpdateStatus(),
|
|
1257
1360
|
loadCallbookDevices(),
|
|
1258
1361
|
loadContacts(),
|
|
1259
1362
|
loadCalls(),
|
|
@@ -1262,6 +1365,10 @@ async function bootstrap() {
|
|
|
1262
1365
|
loadLogs()
|
|
1263
1366
|
]);
|
|
1264
1367
|
showNotice('Dashboard loaded');
|
|
1368
|
+
|
|
1369
|
+
setInterval(() => {
|
|
1370
|
+
loadAutoUpdateStatus().catch(() => {});
|
|
1371
|
+
}, 10000);
|
|
1265
1372
|
} catch (err) {
|
|
1266
1373
|
showNotice(err.message);
|
|
1267
1374
|
}
|
|
@@ -171,6 +171,15 @@
|
|
|
171
171
|
<h3>Remote Callbook</h3>
|
|
172
172
|
<div id="callbook-status" class="card"></div>
|
|
173
173
|
|
|
174
|
+
<h3>Auto Update</h3>
|
|
175
|
+
<div id="auto-update-status" class="card">Loading…</div>
|
|
176
|
+
<div class="row">
|
|
177
|
+
<button id="auto-update-refresh" type="button">Refresh</button>
|
|
178
|
+
<button id="auto-update-check" type="button">Check now</button>
|
|
179
|
+
<button id="auto-update-now" type="button">Update now</button>
|
|
180
|
+
<button id="auto-update-toggle" type="button">Disable auto-update</button>
|
|
181
|
+
</div>
|
|
182
|
+
|
|
174
183
|
<form id="callbook-provision-form" class="card">
|
|
175
184
|
<div class="row">
|
|
176
185
|
<button type="submit">Create Install Link (24h)</button>
|
|
@@ -202,6 +202,33 @@ tr[data-selected="1"] td {
|
|
|
202
202
|
display: none;
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
.status-pill {
|
|
206
|
+
display: inline-block;
|
|
207
|
+
padding: 0.15rem 0.45rem;
|
|
208
|
+
border-radius: 999px;
|
|
209
|
+
border: 1px solid var(--line);
|
|
210
|
+
font-size: 0.78rem;
|
|
211
|
+
font-weight: 600;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.status-pill.ok {
|
|
215
|
+
background: #ecfdf3;
|
|
216
|
+
border-color: #9bd8b8;
|
|
217
|
+
color: #125934;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.status-pill.warn {
|
|
221
|
+
background: #fff7e8;
|
|
222
|
+
border-color: #f1d08e;
|
|
223
|
+
color: #8a5a00;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.status-pill.err {
|
|
227
|
+
background: #fff0f1;
|
|
228
|
+
border-color: #efb1b6;
|
|
229
|
+
color: #8c1d26;
|
|
230
|
+
}
|
|
231
|
+
|
|
205
232
|
@media (max-width: 720px) {
|
|
206
233
|
nav {
|
|
207
234
|
overflow-x: auto;
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
const { execSync, spawn } = require('child_process');
|
|
11
11
|
const { createLogger } = require('./logger');
|
|
12
|
+
const { HARD_FALLBACK_TURN_TIMEOUT_MS } = require('./turn-timeout');
|
|
12
13
|
|
|
13
14
|
const logger = createLogger({ component: 'a2a.claude-subagent' });
|
|
14
15
|
|
|
@@ -216,7 +217,7 @@ function parseSubagentResponse(resultText) {
|
|
|
216
217
|
* @param {number} timeoutMs - Timeout in milliseconds
|
|
217
218
|
* @returns {Promise<{ stdout: string, stderr: string }>}
|
|
218
219
|
*/
|
|
219
|
-
function spawnClaude(args, timeoutMs =
|
|
220
|
+
function spawnClaude(args, timeoutMs = HARD_FALLBACK_TURN_TIMEOUT_MS) {
|
|
220
221
|
return new Promise((resolve, reject) => {
|
|
221
222
|
const proc = spawn('claude', args, {
|
|
222
223
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
@@ -302,7 +303,7 @@ function extractResultFromJson(stdout) {
|
|
|
302
303
|
* @param {Array} options.activeThreads - Active conversation threads
|
|
303
304
|
* @param {Array} options.candidateCollaborations - Candidate collaboration ideas
|
|
304
305
|
* @param {boolean} options.closeSignal - Whether close has been signaled
|
|
305
|
-
* @param {number} [options.timeoutMs=
|
|
306
|
+
* @param {number} [options.timeoutMs=300000] - Timeout in milliseconds
|
|
306
307
|
* @returns {Promise<{ message: string, statePatch: object|null, flags: array, sessionId: string }>}
|
|
307
308
|
*/
|
|
308
309
|
async function runClaudeTurn(options) {
|
|
@@ -317,7 +318,7 @@ async function runClaudeTurn(options) {
|
|
|
317
318
|
activeThreads = [],
|
|
318
319
|
candidateCollaborations = [],
|
|
319
320
|
closeSignal = false,
|
|
320
|
-
timeoutMs =
|
|
321
|
+
timeoutMs = HARD_FALLBACK_TURN_TIMEOUT_MS
|
|
321
322
|
} = options;
|
|
322
323
|
|
|
323
324
|
const turnPrompt = buildTurnPrompt({
|
|
@@ -396,10 +397,10 @@ async function runClaudeTurn(options) {
|
|
|
396
397
|
*
|
|
397
398
|
* @param {string} sessionId - Session ID to resume
|
|
398
399
|
* @param {string} reason - Why the conversation is ending
|
|
399
|
-
* @param {number} [timeoutMs=
|
|
400
|
+
* @param {number} [timeoutMs=300000] - Timeout in milliseconds
|
|
400
401
|
* @returns {Promise<{ summary: string, ownerSummary: string, actionItems: array, flags: array }>}
|
|
401
402
|
*/
|
|
402
|
-
async function runClaudeSummary(sessionId, reason, timeoutMs =
|
|
403
|
+
async function runClaudeSummary(sessionId, reason, timeoutMs = HARD_FALLBACK_TURN_TIMEOUT_MS) {
|
|
403
404
|
if (!sessionId) {
|
|
404
405
|
throw new Error('Cannot summarize without a session ID');
|
|
405
406
|
}
|
package/src/lib/config.js
CHANGED
|
@@ -224,6 +224,7 @@ const DEFAULT_CONFIG = {
|
|
|
224
224
|
perHour: 100,
|
|
225
225
|
perDay: 1000
|
|
226
226
|
},
|
|
227
|
+
turnTimeoutMs: 300000, // default Claude turn timeout
|
|
227
228
|
maxPendingRequests: 5 // max connection requests per hour
|
|
228
229
|
},
|
|
229
230
|
|
|
@@ -233,6 +234,14 @@ const DEFAULT_CONFIG = {
|
|
|
233
234
|
description: '',
|
|
234
235
|
hostname: ''
|
|
235
236
|
},
|
|
237
|
+
|
|
238
|
+
// Auto-updater
|
|
239
|
+
auto_update: {
|
|
240
|
+
enabled: true,
|
|
241
|
+
intervalMs: 60 * 60 * 1000,
|
|
242
|
+
allowMajor: false,
|
|
243
|
+
lastGoodVersion: null
|
|
244
|
+
},
|
|
236
245
|
|
|
237
246
|
// Timestamps
|
|
238
247
|
createdAt: null,
|
|
@@ -381,6 +390,39 @@ class A2AConfig {
|
|
|
381
390
|
return this.config;
|
|
382
391
|
}
|
|
383
392
|
|
|
393
|
+
getAutoUpdate() {
|
|
394
|
+
const current = (this.config && typeof this.config.auto_update === 'object' && this.config.auto_update)
|
|
395
|
+
? this.config.auto_update
|
|
396
|
+
: {};
|
|
397
|
+
return {
|
|
398
|
+
enabled: current.enabled !== false,
|
|
399
|
+
intervalMs: Number.isFinite(current.intervalMs) && current.intervalMs > 0
|
|
400
|
+
? current.intervalMs
|
|
401
|
+
: DEFAULT_CONFIG.auto_update.intervalMs,
|
|
402
|
+
allowMajor: Boolean(current.allowMajor),
|
|
403
|
+
lastGoodVersion: current.lastGoodVersion || null
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
setAutoUpdate(patch = {}) {
|
|
408
|
+
const current = this.getAutoUpdate();
|
|
409
|
+
const next = { ...current };
|
|
410
|
+
if (patch.enabled !== undefined) next.enabled = Boolean(patch.enabled);
|
|
411
|
+
if (patch.intervalMs !== undefined) {
|
|
412
|
+
const parsed = Number.parseInt(String(patch.intervalMs), 10);
|
|
413
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
414
|
+
next.intervalMs = parsed;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (patch.allowMajor !== undefined) next.allowMajor = Boolean(patch.allowMajor);
|
|
418
|
+
if (patch.lastGoodVersion !== undefined) {
|
|
419
|
+
next.lastGoodVersion = patch.lastGoodVersion ? String(patch.lastGoodVersion).trim() : null;
|
|
420
|
+
}
|
|
421
|
+
this.config.auto_update = next;
|
|
422
|
+
this._save();
|
|
423
|
+
return next;
|
|
424
|
+
}
|
|
425
|
+
|
|
384
426
|
// Export for sharing
|
|
385
427
|
export() {
|
|
386
428
|
return {
|
|
@@ -21,6 +21,8 @@ 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');
|
|
25
|
+
const { resolveTokenTimeoutMs, resolveTurnTimeoutMs } = require('./turn-timeout');
|
|
24
26
|
|
|
25
27
|
const logger = createLogger({ component: 'a2a.conversation-driver' });
|
|
26
28
|
|
|
@@ -129,9 +131,16 @@ class ConversationDriver {
|
|
|
129
131
|
this.summarizer = options.summarizer || null;
|
|
130
132
|
this.ownerContext = options.ownerContext || {};
|
|
131
133
|
this.claudeMode = options.runtime?.mode === 'claude';
|
|
132
|
-
this.claudeTimeoutMs = options.claudeTimeoutMs || 180000;
|
|
133
134
|
|
|
134
|
-
const
|
|
135
|
+
const tokenTimeoutMs = options.tokenTimeoutMs
|
|
136
|
+
|| options.claudeTimeoutMs
|
|
137
|
+
|| resolveTokenTimeoutMs(options.token);
|
|
138
|
+
const configTimeoutMs = options.configTurnTimeoutMs;
|
|
139
|
+
this.claudeTimeoutMs = resolveTurnTimeoutMs({ tokenTimeoutMs, configTimeoutMs });
|
|
140
|
+
|
|
141
|
+
const clientTimeout = this.claudeMode
|
|
142
|
+
? Math.max(this.claudeTimeoutMs + 20000, 200000)
|
|
143
|
+
: 65000;
|
|
135
144
|
this.client = new A2AClient({ caller: this.caller, timeout: clientTimeout });
|
|
136
145
|
}
|
|
137
146
|
|
|
@@ -143,37 +152,75 @@ class ConversationDriver {
|
|
|
143
152
|
_buildSummarizer() {
|
|
144
153
|
const runtime = this.runtime;
|
|
145
154
|
const agentContext = this.agentContext;
|
|
155
|
+
const caller = this.caller;
|
|
156
|
+
const tier = this.tier;
|
|
146
157
|
|
|
147
158
|
return async (messages, ownerContext) => {
|
|
148
159
|
if (!messages || messages.length === 0) {
|
|
149
160
|
return { summary: null };
|
|
150
161
|
}
|
|
151
162
|
|
|
152
|
-
// Build
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
const prompt = `Summarize this A2A call for the owner. Write from the owner's perspective.
|
|
163
|
+
// Build transcript in unified format
|
|
164
|
+
const transcript = messages.map(m => ({
|
|
165
|
+
direction: m.direction,
|
|
166
|
+
content: m.content
|
|
167
|
+
}));
|
|
159
168
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
169
|
+
// Load disclosure manifest for the tier
|
|
170
|
+
let disclosure = null;
|
|
171
|
+
try {
|
|
172
|
+
const tierTopics = getTopicsForTier(tier);
|
|
173
|
+
if (tierTopics) {
|
|
174
|
+
disclosure = {
|
|
175
|
+
topics: tierTopics.topics || [],
|
|
176
|
+
objectives: tierTopics.objectives || [],
|
|
177
|
+
doNotDiscuss: tierTopics.do_not_discuss || [],
|
|
178
|
+
neverDisclose: tierTopics.never_disclose || []
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
} catch (e) {
|
|
182
|
+
// Disclosure is optional — continue without it
|
|
183
|
+
}
|
|
164
184
|
|
|
165
|
-
|
|
185
|
+
// Look up collaboration state from convStore if available
|
|
186
|
+
let collaborationState = null;
|
|
187
|
+
if (this.convStore) {
|
|
188
|
+
try {
|
|
189
|
+
const dbState = this.convStore.loadCollabState(this.lastConversationId || '');
|
|
190
|
+
if (dbState) {
|
|
191
|
+
collaborationState = {
|
|
192
|
+
phase: dbState.phase,
|
|
193
|
+
overlapScore: dbState.overlapScore,
|
|
194
|
+
turnCount: dbState.turnCount,
|
|
195
|
+
activeThreads: dbState.activeThreads,
|
|
196
|
+
candidateCollaborations: dbState.candidateCollaborations,
|
|
197
|
+
closeSignal: dbState.closeSignal
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
} catch (e) {
|
|
201
|
+
// Best effort
|
|
202
|
+
}
|
|
203
|
+
}
|
|
166
204
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
- [ ] Actionable item 2
|
|
174
|
-
**Assessment:** One-sentence strategic value judgment.
|
|
205
|
+
// Map ownerContext into unified format
|
|
206
|
+
const unifiedOwnerContext = {
|
|
207
|
+
agentName: agentContext.name,
|
|
208
|
+
ownerName: agentContext.owner,
|
|
209
|
+
goals: ownerContext?.goals || []
|
|
210
|
+
};
|
|
175
211
|
|
|
176
|
-
|
|
212
|
+
const prompt = buildUnifiedSummaryPrompt({
|
|
213
|
+
transcript,
|
|
214
|
+
callerInfo: {
|
|
215
|
+
name: caller.name || null,
|
|
216
|
+
owner: caller.owner || null,
|
|
217
|
+
context: caller.context || null
|
|
218
|
+
},
|
|
219
|
+
conversationObjective: 'You initiated this call.',
|
|
220
|
+
disclosure,
|
|
221
|
+
collaborationState,
|
|
222
|
+
ownerContext: unifiedOwnerContext
|
|
223
|
+
});
|
|
177
224
|
|
|
178
225
|
// Try runtime.summarize if available (OpenClaw path)
|
|
179
226
|
if (typeof runtime.summarize === 'function') {
|
|
@@ -182,7 +229,8 @@ Be concise but specific. No filler.`;
|
|
|
182
229
|
sessionId: `summary-${Date.now()}`,
|
|
183
230
|
prompt,
|
|
184
231
|
messages,
|
|
185
|
-
callerInfo: { name: agentContext.name, owner: agentContext.owner }
|
|
232
|
+
callerInfo: { name: agentContext.name, owner: agentContext.owner },
|
|
233
|
+
timeoutMs: this.claudeMode ? this.claudeTimeoutMs : 35000
|
|
186
234
|
});
|
|
187
235
|
} catch (err) {
|
|
188
236
|
logger.warn('Runtime summarizer failed, using default', {
|
|
@@ -505,6 +553,8 @@ Be concise but specific. No filler.`;
|
|
|
505
553
|
}
|
|
506
554
|
|
|
507
555
|
// Conclude locally with summarizer
|
|
556
|
+
// Store conversationId so _buildSummarizer can look up collab state
|
|
557
|
+
this.lastConversationId = conversationId;
|
|
508
558
|
let summary = null;
|
|
509
559
|
if (this.convStore) {
|
|
510
560
|
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
|
/**
|
|
@@ -14,6 +14,7 @@ const { execSync, spawnSync } = require('child_process');
|
|
|
14
14
|
const { createLogger } = require('./logger');
|
|
15
15
|
const { runClaudeTurn: invokeClaudeTurn, buildSubagentSystemPrompt, runClaudeSummary } = require('./claude-subagent');
|
|
16
16
|
const { getTopicsForTier, formatTopicsForPrompt, loadManifest } = require('./disclosure');
|
|
17
|
+
const { HARD_FALLBACK_TURN_TIMEOUT_MS } = require('./turn-timeout');
|
|
17
18
|
|
|
18
19
|
function commandExists(command) {
|
|
19
20
|
try {
|
|
@@ -208,7 +209,7 @@ function createRuntimeAdapter(options = {}) {
|
|
|
208
209
|
activeThreads: context?.activeThreads || [],
|
|
209
210
|
candidateCollaborations: context?.candidateCollaborations || [],
|
|
210
211
|
closeSignal: context?.closeSignal || false,
|
|
211
|
-
timeoutMs: timeoutMs ||
|
|
212
|
+
timeoutMs: timeoutMs || HARD_FALLBACK_TURN_TIMEOUT_MS
|
|
212
213
|
});
|
|
213
214
|
|
|
214
215
|
// Store session ID from first turn for subsequent --resume
|
|
@@ -379,7 +380,7 @@ function createRuntimeAdapter(options = {}) {
|
|
|
379
380
|
}
|
|
380
381
|
}
|
|
381
382
|
|
|
382
|
-
async function summarize({ sessionId, prompt, messages, callerInfo, traceId, conversationId }) {
|
|
383
|
+
async function summarize({ sessionId, prompt, messages, callerInfo, traceId, conversationId, timeoutMs }) {
|
|
383
384
|
const effectiveTraceId = traceId || callerInfo?.trace_id || callerInfo?.traceId;
|
|
384
385
|
const requestId = callerInfo?.request_id || callerInfo?.requestId;
|
|
385
386
|
const effectiveConversationId = conversationId || callerInfo?.conversation_id || callerInfo?.conversationId;
|
|
@@ -388,7 +389,11 @@ function createRuntimeAdapter(options = {}) {
|
|
|
388
389
|
if (modeInfo.mode === 'claude') {
|
|
389
390
|
const session = claudeSessions.get(sessionId);
|
|
390
391
|
if (session?.claudeSessionId) {
|
|
391
|
-
const result = await runClaudeSummary(
|
|
392
|
+
const result = await runClaudeSummary(
|
|
393
|
+
session.claudeSessionId,
|
|
394
|
+
'conversation ended',
|
|
395
|
+
timeoutMs || HARD_FALLBACK_TURN_TIMEOUT_MS
|
|
396
|
+
);
|
|
392
397
|
if (result && result.summary) {
|
|
393
398
|
return result;
|
|
394
399
|
}
|