a2acalling 0.6.42 → 0.6.44
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/README.md +1 -5
- package/bin/cli.js +146 -34
- package/package.json +1 -1
- package/src/lib/claude-subagent.js +485 -0
- package/src/lib/conversation-driver.js +109 -28
- package/src/lib/disclosure.js +5 -6
- package/src/lib/runtime-adapter.js +221 -437
- package/src/routes/dashboard.js +5 -5
- package/src/server.js +5 -10
|
@@ -3,20 +3,17 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Modes:
|
|
5
5
|
* - openclaw: uses `openclaw` CLI for turn handling, summaries, notifications
|
|
6
|
-
* -
|
|
6
|
+
* - claude: uses `claude` CLI as a real LLM subagent for conversations
|
|
7
7
|
*
|
|
8
8
|
* Selection:
|
|
9
|
-
* - A2A_RUNTIME=openclaw|
|
|
10
|
-
* - auto picks openclaw
|
|
11
|
-
*
|
|
12
|
-
* Generic bridge hooks:
|
|
13
|
-
* - A2A_AGENT_COMMAND command that receives JSON payload on stdin and returns text or JSON
|
|
14
|
-
* - A2A_SUMMARY_COMMAND command that receives JSON payload on stdin and returns summary text/JSON
|
|
15
|
-
* - A2A_NOTIFY_COMMAND command that receives JSON payload on stdin for owner notifications
|
|
9
|
+
* - A2A_RUNTIME=openclaw|claude|auto (default: auto)
|
|
10
|
+
* - auto picks openclaw → claude → error (no supported CLI)
|
|
16
11
|
*/
|
|
17
12
|
|
|
18
13
|
const { execSync, spawnSync } = require('child_process');
|
|
19
14
|
const { createLogger } = require('./logger');
|
|
15
|
+
const { runClaudeTurn: invokeClaudeTurn, buildSubagentSystemPrompt, runClaudeSummary } = require('./claude-subagent');
|
|
16
|
+
const { getTopicsForTier, formatTopicsForPrompt, loadManifest } = require('./disclosure');
|
|
20
17
|
|
|
21
18
|
function commandExists(command) {
|
|
22
19
|
try {
|
|
@@ -34,33 +31,40 @@ function cleanText(value, maxLength = 300) {
|
|
|
34
31
|
.slice(0, maxLength);
|
|
35
32
|
}
|
|
36
33
|
|
|
37
|
-
function normalizeCommandText(command) {
|
|
38
|
-
return String(command || '').trim().slice(0, 160);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function payloadAuditLength(payload) {
|
|
42
|
-
const raw = JSON.stringify(payload || {});
|
|
43
|
-
return Number.isFinite(raw?.length) ? raw.length : 0;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function toBool(value, fallback = true) {
|
|
47
|
-
if (value === undefined || value === null || value === '') {
|
|
48
|
-
return fallback;
|
|
49
|
-
}
|
|
50
|
-
const normalized = String(value).trim().toLowerCase();
|
|
51
|
-
return !(normalized === '0' || normalized === 'false' || normalized === 'no');
|
|
52
|
-
}
|
|
53
34
|
|
|
54
35
|
function resolveRuntimeMode() {
|
|
55
36
|
const requested = String(process.env.A2A_RUNTIME || 'auto').trim().toLowerCase();
|
|
56
37
|
const hasOpenClaw = commandExists('openclaw');
|
|
38
|
+
const hasClaude = commandExists('claude');
|
|
57
39
|
|
|
58
40
|
if (requested === 'generic') {
|
|
59
41
|
return {
|
|
60
|
-
mode: '
|
|
42
|
+
mode: 'none',
|
|
43
|
+
requested,
|
|
44
|
+
hasOpenClaw,
|
|
45
|
+
hasClaude,
|
|
46
|
+
warning: 'A2A_RUNTIME=generic is no longer supported. Use openclaw or claude runtime.',
|
|
47
|
+
reason: 'unsupported-generic-mode'
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (requested === 'claude') {
|
|
52
|
+
if (hasClaude) {
|
|
53
|
+
return {
|
|
54
|
+
mode: 'claude',
|
|
55
|
+
requested,
|
|
56
|
+
hasOpenClaw,
|
|
57
|
+
hasClaude,
|
|
58
|
+
reason: 'A2A_RUNTIME=claude'
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
mode: 'none',
|
|
61
63
|
requested,
|
|
62
64
|
hasOpenClaw,
|
|
63
|
-
|
|
65
|
+
hasClaude,
|
|
66
|
+
warning: 'A2A_RUNTIME=claude but claude CLI not found; install claude CLI or switch to openclaw',
|
|
67
|
+
reason: 'forced-claude-missing'
|
|
64
68
|
};
|
|
65
69
|
}
|
|
66
70
|
|
|
@@ -70,32 +74,48 @@ function resolveRuntimeMode() {
|
|
|
70
74
|
mode: 'openclaw',
|
|
71
75
|
requested,
|
|
72
76
|
hasOpenClaw,
|
|
77
|
+
hasClaude,
|
|
73
78
|
reason: 'A2A_RUNTIME=openclaw'
|
|
74
79
|
};
|
|
75
80
|
}
|
|
76
81
|
return {
|
|
77
|
-
mode: '
|
|
82
|
+
mode: 'none',
|
|
78
83
|
requested,
|
|
79
84
|
hasOpenClaw,
|
|
80
|
-
|
|
85
|
+
hasClaude,
|
|
86
|
+
warning: 'A2A_RUNTIME=openclaw but openclaw CLI not found; install openclaw CLI or switch to claude',
|
|
81
87
|
reason: 'forced-openclaw-missing'
|
|
82
88
|
};
|
|
83
89
|
}
|
|
84
90
|
|
|
91
|
+
// Auto detection chain: openclaw → claude → none
|
|
85
92
|
if (hasOpenClaw) {
|
|
86
93
|
return {
|
|
87
94
|
mode: 'openclaw',
|
|
88
95
|
requested: 'auto',
|
|
89
96
|
hasOpenClaw,
|
|
97
|
+
hasClaude,
|
|
90
98
|
reason: 'openclaw CLI detected'
|
|
91
99
|
};
|
|
92
100
|
}
|
|
93
101
|
|
|
102
|
+
if (hasClaude) {
|
|
103
|
+
return {
|
|
104
|
+
mode: 'claude',
|
|
105
|
+
requested: 'auto',
|
|
106
|
+
hasOpenClaw,
|
|
107
|
+
hasClaude,
|
|
108
|
+
reason: 'claude CLI detected'
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
94
112
|
return {
|
|
95
|
-
mode: '
|
|
113
|
+
mode: 'none',
|
|
96
114
|
requested: 'auto',
|
|
97
115
|
hasOpenClaw,
|
|
98
|
-
|
|
116
|
+
hasClaude,
|
|
117
|
+
warning: 'No supported runtime CLI found. Install openclaw or claude CLI.',
|
|
118
|
+
reason: 'no supported CLI detected'
|
|
99
119
|
};
|
|
100
120
|
}
|
|
101
121
|
|
|
@@ -111,152 +131,12 @@ function normalizeOpenClawOutput(raw) {
|
|
|
111
131
|
return lines.join('\n').trim();
|
|
112
132
|
}
|
|
113
133
|
|
|
114
|
-
function parseCommandTextOutput(rawOutput, keys = ['response', 'text', 'message']) {
|
|
115
|
-
const output = String(rawOutput || '').trim();
|
|
116
|
-
if (!output) {
|
|
117
|
-
return '';
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
const parsed = JSON.parse(output);
|
|
122
|
-
if (parsed && typeof parsed === 'object') {
|
|
123
|
-
for (const key of keys) {
|
|
124
|
-
if (typeof parsed[key] === 'string' && parsed[key].trim()) {
|
|
125
|
-
return parsed[key].trim();
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
} catch (err) {
|
|
130
|
-
// Plain text output is valid for bridge commands.
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return output;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function parseSummaryOutput(rawOutput) {
|
|
137
|
-
const output = String(rawOutput || '').trim();
|
|
138
|
-
if (!output) {
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
try {
|
|
143
|
-
const parsed = JSON.parse(output);
|
|
144
|
-
if (parsed && typeof parsed === 'object') {
|
|
145
|
-
const summary = cleanText(parsed.summary || parsed.text || parsed.message, 1500);
|
|
146
|
-
return {
|
|
147
|
-
...parsed,
|
|
148
|
-
summary: summary || null,
|
|
149
|
-
ownerSummary: cleanText(
|
|
150
|
-
parsed.ownerSummary || parsed.owner_summary || summary || '',
|
|
151
|
-
1500
|
|
152
|
-
) || null
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
} catch (err) {
|
|
156
|
-
// Plain text is also acceptable.
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const summary = cleanText(output, 1500);
|
|
160
|
-
return {
|
|
161
|
-
summary,
|
|
162
|
-
ownerSummary: summary
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function runCommand(command, payload, options = {}) {
|
|
167
|
-
const payloadJson = JSON.stringify(payload || {});
|
|
168
|
-
const timeoutMs = options.timeoutMs || 60000;
|
|
169
|
-
return execSync(command, {
|
|
170
|
-
encoding: 'utf8',
|
|
171
|
-
timeout: timeoutMs,
|
|
172
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
173
|
-
input: payloadJson,
|
|
174
|
-
cwd: options.cwd || process.cwd(),
|
|
175
|
-
env: {
|
|
176
|
-
...process.env,
|
|
177
|
-
A2A_PAYLOAD_JSON: payloadJson
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function escapeCliValue(value) {
|
|
183
|
-
return String(value || '')
|
|
184
|
-
.replace(/\\/g, '\\\\') // Backslashes first
|
|
185
|
-
.replace(/"/g, '\\"') // Double quotes
|
|
186
|
-
.replace(/\$/g, '\\$') // Dollar signs (variable expansion)
|
|
187
|
-
.replace(/`/g, '\\`') // Backticks (command substitution)
|
|
188
|
-
.replace(/!/g, '\\!') // History expansion in some shells
|
|
189
|
-
.replace(/\n/g, '\\n') // Newlines
|
|
190
|
-
.replace(/\r/g, ''); // Carriage returns
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function buildFallbackResponse(message, context = {}, reason = null) {
|
|
194
|
-
const callerName = cleanText(context.callerName || context.caller?.name || 'caller');
|
|
195
|
-
const ownerName = cleanText(context.ownerName || 'the owner');
|
|
196
|
-
const allowedTopics = Array.isArray(context.allowedTopics)
|
|
197
|
-
? context.allowedTopics.filter(Boolean).slice(0, 4)
|
|
198
|
-
: [];
|
|
199
|
-
const topicText = allowedTopics.length > 0
|
|
200
|
-
? allowedTopics.join(', ')
|
|
201
|
-
: 'permitted topics';
|
|
202
|
-
const excerpt = cleanText(message, 220) || 'No message content provided.';
|
|
203
|
-
|
|
204
|
-
let prefix = `I am running in generic A2A mode for ${ownerName}.`;
|
|
205
|
-
if (reason) {
|
|
206
|
-
prefix = `I switched to generic fallback mode (${cleanText(reason, 120)}).`;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return `${prefix} I received from ${callerName}: "${excerpt}". ` +
|
|
210
|
-
`We can still work through concrete overlap on ${topicText} and line up actionable next steps. ` +
|
|
211
|
-
`What outcome should we target first?`;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function buildFallbackSummary(messages = [], callerInfo = {}, reason = null) {
|
|
215
|
-
const inbound = messages.filter(m => m.direction === 'inbound');
|
|
216
|
-
const outbound = messages.filter(m => m.direction !== 'inbound');
|
|
217
|
-
const caller = cleanText(callerInfo?.name || 'Unknown caller');
|
|
218
|
-
const lastInbound = inbound.length > 0
|
|
219
|
-
? cleanText(inbound[inbound.length - 1].content, 220)
|
|
220
|
-
: '';
|
|
221
|
-
|
|
222
|
-
const summary = [
|
|
223
|
-
`Call concluded with ${caller}.`,
|
|
224
|
-
`Inbound turns: ${inbound.length}. Outbound turns: ${outbound.length}.`,
|
|
225
|
-
lastInbound ? `Latest caller request: "${lastInbound}".` : '',
|
|
226
|
-
reason ? `Summary mode: ${cleanText(reason, 140)}.` : 'Summary mode: generic fallback.'
|
|
227
|
-
].filter(Boolean).join(' ');
|
|
228
|
-
|
|
229
|
-
return {
|
|
230
|
-
summary,
|
|
231
|
-
ownerSummary: summary,
|
|
232
|
-
relevance: 'unknown',
|
|
233
|
-
goalsTouched: [],
|
|
234
|
-
ownerActionItems: [],
|
|
235
|
-
callerActionItems: [],
|
|
236
|
-
jointActionItems: [],
|
|
237
|
-
collaborationOpportunity: {
|
|
238
|
-
found: false,
|
|
239
|
-
rationale: 'Generic fallback summary (no platform-specific summarizer configured)'
|
|
240
|
-
},
|
|
241
|
-
followUp: lastInbound
|
|
242
|
-
? `Clarify the next concrete step for: ${lastInbound}`
|
|
243
|
-
: 'Ask both owners to confirm desired follow-up scope.',
|
|
244
|
-
notes: reason
|
|
245
|
-
? `Fallback summary generated after runtime issue: ${cleanText(reason, 180)}`
|
|
246
|
-
: 'Fallback summary generated by generic runtime.'
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
134
|
|
|
250
135
|
function createRuntimeAdapter(options = {}) {
|
|
251
136
|
const workspaceDir = options.workspaceDir || process.cwd();
|
|
252
137
|
const modeInfo = resolveRuntimeMode();
|
|
253
|
-
const failoverEnabled = toBool(process.env.A2A_RUNTIME_FAILOVER, true);
|
|
254
138
|
const logger = options.logger || createLogger({ component: 'a2a.runtime' });
|
|
255
139
|
|
|
256
|
-
const genericAgentCommand = process.env.A2A_AGENT_COMMAND || '';
|
|
257
|
-
const genericSummaryCommand = process.env.A2A_SUMMARY_COMMAND || '';
|
|
258
|
-
const genericNotifyCommand = process.env.A2A_NOTIFY_COMMAND || '';
|
|
259
|
-
|
|
260
140
|
logger.info('Runtime adapter initialized', {
|
|
261
141
|
event: 'runtime_initialized',
|
|
262
142
|
data: {
|
|
@@ -264,10 +144,107 @@ function createRuntimeAdapter(options = {}) {
|
|
|
264
144
|
requested_mode: modeInfo.requested,
|
|
265
145
|
reason: modeInfo.reason,
|
|
266
146
|
has_openclaw: modeInfo.hasOpenClaw,
|
|
267
|
-
|
|
147
|
+
has_claude: modeInfo.hasClaude
|
|
268
148
|
}
|
|
269
149
|
});
|
|
270
150
|
|
|
151
|
+
// Claude subagent session tracking
|
|
152
|
+
const claudeSessions = new Map();
|
|
153
|
+
|
|
154
|
+
async function runClaudeTurnAdapter({ sessionId, message, caller, context, timeoutMs }) {
|
|
155
|
+
const traceId = context?.traceId || context?.trace_id;
|
|
156
|
+
const requestId = context?.requestId || context?.request_id;
|
|
157
|
+
const conversationId = context?.conversationId || context?.conversation_id;
|
|
158
|
+
const startAt = Date.now();
|
|
159
|
+
|
|
160
|
+
// Get or create session state
|
|
161
|
+
let session = claudeSessions.get(sessionId);
|
|
162
|
+
if (!session) {
|
|
163
|
+
// First turn: build the system prompt from disclosure context
|
|
164
|
+
const manifest = loadManifest();
|
|
165
|
+
const tierTopics = getTopicsForTier(context?.tier || 'public');
|
|
166
|
+
const formatted = formatTopicsForPrompt(tierTopics);
|
|
167
|
+
|
|
168
|
+
const systemPrompt = buildSubagentSystemPrompt({
|
|
169
|
+
agentName: context?.agentName || 'Agent',
|
|
170
|
+
ownerName: context?.ownerName || 'the owner',
|
|
171
|
+
otherAgentName: caller?.name || 'Remote Agent',
|
|
172
|
+
otherOwnerName: caller?.owner || 'their owner',
|
|
173
|
+
accessTier: context?.tier || 'public',
|
|
174
|
+
tierTopics: formatted.topics,
|
|
175
|
+
tierObjectives: formatted.objectives,
|
|
176
|
+
doNotDiscuss: formatted.doNotDiscuss,
|
|
177
|
+
neverDisclose: formatted.neverDisclose,
|
|
178
|
+
personalityNotes: manifest.personality_notes || '',
|
|
179
|
+
roleContext: context?.roleContext || ''
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
session = { claudeSessionId: null, systemPrompt, turnCount: 0, lastMeta: null };
|
|
183
|
+
claudeSessions.set(sessionId, session);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
session.turnCount++;
|
|
187
|
+
|
|
188
|
+
logger.debug('Invoking Claude subagent turn', {
|
|
189
|
+
event: 'claude_turn_start',
|
|
190
|
+
traceId,
|
|
191
|
+
requestId,
|
|
192
|
+
conversationId,
|
|
193
|
+
data: {
|
|
194
|
+
session_id: sessionId,
|
|
195
|
+
turn: session.turnCount,
|
|
196
|
+
timeout_ms: timeoutMs
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const result = await invokeClaudeTurn({
|
|
201
|
+
sessionId: session.claudeSessionId,
|
|
202
|
+
systemPrompt: session.systemPrompt,
|
|
203
|
+
turnMessage: message,
|
|
204
|
+
turn: session.turnCount,
|
|
205
|
+
maxTurns: context?.maxTurns || 30,
|
|
206
|
+
phase: context?.phase || 'handshake',
|
|
207
|
+
overlapScore: context?.overlapScore || 0.15,
|
|
208
|
+
activeThreads: context?.activeThreads || [],
|
|
209
|
+
candidateCollaborations: context?.candidateCollaborations || [],
|
|
210
|
+
closeSignal: context?.closeSignal || false,
|
|
211
|
+
timeoutMs: timeoutMs || 180000
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Store session ID from first turn for subsequent --resume
|
|
215
|
+
if (result.sessionId) {
|
|
216
|
+
session.claudeSessionId = result.sessionId;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Store flags/state for retrieval via getLastTurnMeta
|
|
220
|
+
session.lastMeta = {
|
|
221
|
+
statePatch: result.statePatch,
|
|
222
|
+
flags: result.flags
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
logger.debug('Claude subagent turn completed', {
|
|
226
|
+
event: 'claude_turn_complete',
|
|
227
|
+
traceId,
|
|
228
|
+
requestId,
|
|
229
|
+
conversationId,
|
|
230
|
+
data: {
|
|
231
|
+
session_id: sessionId,
|
|
232
|
+
turn: session.turnCount,
|
|
233
|
+
duration_ms: Date.now() - startAt,
|
|
234
|
+
message_length: result.message.length,
|
|
235
|
+
has_state_patch: Boolean(result.statePatch),
|
|
236
|
+
flag_count: result.flags.length
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
return result.message;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function getLastTurnMeta(sessionId) {
|
|
244
|
+
const session = claudeSessions.get(sessionId);
|
|
245
|
+
return session?.lastMeta || null;
|
|
246
|
+
}
|
|
247
|
+
|
|
271
248
|
async function runOpenClawTurn({ sessionId, prompt, timeoutMs }) {
|
|
272
249
|
const timeoutSeconds = Math.max(5, Math.min(300, Math.round((timeoutMs || 65000) / 1000)));
|
|
273
250
|
// Use spawnSync with stdin to avoid shell escaping issues with complex prompts
|
|
@@ -328,185 +305,34 @@ function createRuntimeAdapter(options = {}) {
|
|
|
328
305
|
], { timeout: 10000, stdio: 'pipe' });
|
|
329
306
|
}
|
|
330
307
|
|
|
331
|
-
async function
|
|
332
|
-
const payload = {
|
|
333
|
-
mode: 'a2a-turn',
|
|
334
|
-
message,
|
|
335
|
-
caller: caller || {},
|
|
336
|
-
context: context || {}
|
|
337
|
-
};
|
|
308
|
+
async function runTurn({ sessionId, prompt, message, caller, context = {}, timeoutMs }) {
|
|
338
309
|
const traceId = context?.traceId || context?.trace_id;
|
|
339
310
|
const requestId = context?.requestId || context?.request_id;
|
|
340
311
|
const conversationId = context?.conversationId || context?.conversation_id;
|
|
341
|
-
const startAt = Date.now();
|
|
342
312
|
|
|
343
|
-
|
|
344
|
-
event: 'generic_agent_command_start',
|
|
345
|
-
traceId,
|
|
346
|
-
requestId,
|
|
347
|
-
conversationId,
|
|
348
|
-
data: {
|
|
349
|
-
command: normalizeCommandText(genericAgentCommand),
|
|
350
|
-
payload_bytes: payloadAuditLength(payload)
|
|
351
|
-
}
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
if (genericAgentCommand) {
|
|
313
|
+
if (modeInfo.mode === 'claude') {
|
|
355
314
|
try {
|
|
356
|
-
|
|
357
|
-
timeoutMs: context?.timeoutMs || 65000
|
|
358
|
-
});
|
|
359
|
-
const text = parseCommandTextOutput(output);
|
|
360
|
-
logger.debug('Generic agent command completed', {
|
|
361
|
-
event: 'generic_agent_command_complete',
|
|
362
|
-
traceId,
|
|
363
|
-
requestId,
|
|
364
|
-
conversationId,
|
|
365
|
-
data: {
|
|
366
|
-
command: normalizeCommandText(genericAgentCommand),
|
|
367
|
-
duration_ms: Date.now() - startAt,
|
|
368
|
-
output_length: String(output || '').length
|
|
369
|
-
}
|
|
370
|
-
});
|
|
371
|
-
if (text) {
|
|
372
|
-
return text;
|
|
373
|
-
}
|
|
315
|
+
return await runClaudeTurnAdapter({ sessionId, message, caller, context, timeoutMs });
|
|
374
316
|
} catch (err) {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
event: 'generic_agent_command_failed',
|
|
317
|
+
logger.error('Claude subagent turn failed', {
|
|
318
|
+
event: 'claude_turn_failed',
|
|
378
319
|
traceId,
|
|
379
320
|
requestId,
|
|
380
321
|
conversationId,
|
|
381
|
-
error_code: '
|
|
382
|
-
hint: '
|
|
322
|
+
error_code: 'CLAUDE_TURN_FAILED',
|
|
323
|
+
hint: 'Inspect Claude CLI availability, timeout settings, and CLAUDECODE env var.',
|
|
383
324
|
error: err,
|
|
384
|
-
data: {
|
|
385
|
-
command_present: Boolean(genericAgentCommand),
|
|
386
|
-
command: normalizeCommandText(genericAgentCommand),
|
|
387
|
-
payload_bytes: payloadAuditLength(payload),
|
|
388
|
-
duration_ms: Date.now() - startAt
|
|
389
|
-
}
|
|
325
|
+
data: { session_id: sessionId, timeout_ms: timeoutMs }
|
|
390
326
|
});
|
|
327
|
+
throw err;
|
|
391
328
|
}
|
|
392
329
|
}
|
|
393
330
|
|
|
394
|
-
return buildFallbackResponse(message, {
|
|
395
|
-
caller,
|
|
396
|
-
callerName: caller?.name,
|
|
397
|
-
ownerName: context?.ownerName,
|
|
398
|
-
allowedTopics: context?.allowedTopics
|
|
399
|
-
}, runtimeError);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
async function runGenericSummary({ messages, callerInfo, reason }) {
|
|
403
|
-
const payload = {
|
|
404
|
-
mode: 'a2a-summary',
|
|
405
|
-
messages,
|
|
406
|
-
caller: callerInfo || {}
|
|
407
|
-
};
|
|
408
|
-
const traceId = callerInfo?.trace_id || callerInfo?.traceId;
|
|
409
|
-
const requestId = callerInfo?.request_id || callerInfo?.requestId;
|
|
410
|
-
const conversationId = callerInfo?.conversation_id || callerInfo?.conversationId;
|
|
411
|
-
const startAt = Date.now();
|
|
412
|
-
|
|
413
|
-
if (genericSummaryCommand) {
|
|
414
|
-
try {
|
|
415
|
-
const output = runCommand(genericSummaryCommand, payload, { timeoutMs: 35000 });
|
|
416
|
-
const parsed = parseSummaryOutput(output);
|
|
417
|
-
logger.debug('Generic summary command completed', {
|
|
418
|
-
event: 'generic_summary_command_complete',
|
|
419
|
-
traceId,
|
|
420
|
-
requestId,
|
|
421
|
-
conversationId,
|
|
422
|
-
data: {
|
|
423
|
-
command: normalizeCommandText(genericSummaryCommand),
|
|
424
|
-
payload_bytes: payloadAuditLength(payload),
|
|
425
|
-
output_length: String(output || '').length
|
|
426
|
-
}
|
|
427
|
-
});
|
|
428
|
-
if (parsed && parsed.summary) {
|
|
429
|
-
return parsed;
|
|
430
|
-
}
|
|
431
|
-
} catch (err) {
|
|
432
|
-
reason = err.message;
|
|
433
|
-
logger.error('Generic summary command failed', {
|
|
434
|
-
event: 'generic_summary_command_failed',
|
|
435
|
-
traceId,
|
|
436
|
-
requestId,
|
|
437
|
-
conversationId,
|
|
438
|
-
error_code: 'GENERIC_SUMMARY_COMMAND_FAILED',
|
|
439
|
-
hint: 'Verify A2A_SUMMARY_COMMAND returns JSON with summary field or plain text.',
|
|
440
|
-
error: err,
|
|
441
|
-
data: {
|
|
442
|
-
command_present: Boolean(genericSummaryCommand),
|
|
443
|
-
command: normalizeCommandText(genericSummaryCommand),
|
|
444
|
-
payload_bytes: payloadAuditLength(payload),
|
|
445
|
-
duration_ms: Date.now() - startAt
|
|
446
|
-
}
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
return buildFallbackSummary(messages, callerInfo, reason);
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
async function runGenericNotify(payload) {
|
|
455
|
-
if (!genericNotifyCommand) {
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
const traceId = payload?.trace_id || payload?.traceId;
|
|
459
|
-
const requestId = payload?.request_id || payload?.requestId;
|
|
460
|
-
const conversationId = payload?.conversationId;
|
|
461
|
-
const startAt = Date.now();
|
|
462
|
-
logger.debug('Invoking generic notify command', {
|
|
463
|
-
event: 'generic_notify_command_start',
|
|
464
|
-
traceId,
|
|
465
|
-
requestId,
|
|
466
|
-
conversationId,
|
|
467
|
-
data: {
|
|
468
|
-
command: normalizeCommandText(genericNotifyCommand),
|
|
469
|
-
payload_bytes: payloadAuditLength(payload)
|
|
470
|
-
}
|
|
471
|
-
});
|
|
472
|
-
try {
|
|
473
|
-
runCommand(genericNotifyCommand, payload, { timeoutMs: 10000 });
|
|
474
|
-
logger.debug('Generic notify command completed', {
|
|
475
|
-
event: 'generic_notify_command_complete',
|
|
476
|
-
traceId,
|
|
477
|
-
requestId,
|
|
478
|
-
conversationId,
|
|
479
|
-
data: {
|
|
480
|
-
command: normalizeCommandText(genericNotifyCommand),
|
|
481
|
-
duration_ms: Date.now() - startAt
|
|
482
|
-
}
|
|
483
|
-
});
|
|
484
|
-
} catch (err) {
|
|
485
|
-
logger.error('Generic notify command failed', {
|
|
486
|
-
event: 'generic_notify_command_failed',
|
|
487
|
-
traceId,
|
|
488
|
-
requestId,
|
|
489
|
-
conversationId,
|
|
490
|
-
tokenId: payload?.token?.id,
|
|
491
|
-
error_code: 'GENERIC_NOTIFY_COMMAND_FAILED',
|
|
492
|
-
hint: 'Validate A2A_NOTIFY_COMMAND and downstream notifier transport availability.',
|
|
493
|
-
error: err,
|
|
494
|
-
data: {
|
|
495
|
-
command_present: Boolean(genericNotifyCommand),
|
|
496
|
-
command: normalizeCommandText(genericNotifyCommand),
|
|
497
|
-
payload_bytes: payloadAuditLength(payload),
|
|
498
|
-
duration_ms: Date.now() - startAt
|
|
499
|
-
}
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
async function runTurn({ sessionId, prompt, message, caller, context = {}, timeoutMs }) {
|
|
505
|
-
const traceId = context?.traceId || context?.trace_id;
|
|
506
|
-
const requestId = context?.requestId || context?.request_id;
|
|
507
|
-
const conversationId = context?.conversationId || context?.conversation_id;
|
|
508
331
|
if (modeInfo.mode !== 'openclaw') {
|
|
509
|
-
|
|
332
|
+
throw new Error(
|
|
333
|
+
`No supported A2A runtime available (mode=${modeInfo.mode}). ` +
|
|
334
|
+
'Install the openclaw or claude CLI and set A2A_RUNTIME accordingly.'
|
|
335
|
+
);
|
|
510
336
|
}
|
|
511
337
|
|
|
512
338
|
const startAt = Date.now();
|
|
@@ -535,42 +361,21 @@ function createRuntimeAdapter(options = {}) {
|
|
|
535
361
|
});
|
|
536
362
|
return response;
|
|
537
363
|
} catch (err) {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
event: 'openclaw_turn_failed',
|
|
541
|
-
traceId,
|
|
542
|
-
requestId,
|
|
543
|
-
conversationId,
|
|
544
|
-
error_code: 'OPENCLAW_TURN_FAILED',
|
|
545
|
-
hint: 'Inspect OpenClaw CLI output, timeout settings, and environment PATH.',
|
|
546
|
-
error: err,
|
|
547
|
-
data: {
|
|
548
|
-
session_id: sessionId,
|
|
549
|
-
timeout_ms: timeoutMs,
|
|
550
|
-
duration_ms: Date.now() - startAt
|
|
551
|
-
}
|
|
552
|
-
});
|
|
553
|
-
throw err;
|
|
554
|
-
}
|
|
555
|
-
logger.warn('OpenClaw runtime failed, switching to generic fallback', {
|
|
556
|
-
event: 'openclaw_turn_failed_fallback',
|
|
364
|
+
logger.error('OpenClaw turn failed', {
|
|
365
|
+
event: 'openclaw_turn_failed',
|
|
557
366
|
traceId,
|
|
558
367
|
requestId,
|
|
559
368
|
conversationId,
|
|
560
|
-
error_code: '
|
|
561
|
-
hint: 'Inspect OpenClaw CLI
|
|
369
|
+
error_code: 'OPENCLAW_TURN_FAILED',
|
|
370
|
+
hint: 'Inspect OpenClaw CLI output, timeout settings, and environment PATH.',
|
|
562
371
|
error: err,
|
|
563
372
|
data: {
|
|
564
|
-
|
|
565
|
-
|
|
373
|
+
session_id: sessionId,
|
|
374
|
+
timeout_ms: timeoutMs,
|
|
375
|
+
duration_ms: Date.now() - startAt
|
|
566
376
|
}
|
|
567
377
|
});
|
|
568
|
-
|
|
569
|
-
message,
|
|
570
|
-
caller,
|
|
571
|
-
context,
|
|
572
|
-
runtimeError: `openclaw runtime unavailable: ${err.message}`
|
|
573
|
-
});
|
|
378
|
+
throw err;
|
|
574
379
|
}
|
|
575
380
|
}
|
|
576
381
|
|
|
@@ -578,9 +383,26 @@ function createRuntimeAdapter(options = {}) {
|
|
|
578
383
|
const effectiveTraceId = traceId || callerInfo?.trace_id || callerInfo?.traceId;
|
|
579
384
|
const requestId = callerInfo?.request_id || callerInfo?.requestId;
|
|
580
385
|
const effectiveConversationId = conversationId || callerInfo?.conversation_id || callerInfo?.conversationId;
|
|
386
|
+
|
|
387
|
+
// Claude mode: use the subagent session for summarization
|
|
388
|
+
if (modeInfo.mode === 'claude') {
|
|
389
|
+
const session = claudeSessions.get(sessionId);
|
|
390
|
+
if (session?.claudeSessionId) {
|
|
391
|
+
const result = await runClaudeSummary(session.claudeSessionId, 'conversation ended');
|
|
392
|
+
if (result && result.summary) {
|
|
393
|
+
return result;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
throw new Error('Claude summary session not available or returned empty result');
|
|
397
|
+
}
|
|
398
|
+
|
|
581
399
|
if (modeInfo.mode !== 'openclaw') {
|
|
582
|
-
|
|
400
|
+
throw new Error(
|
|
401
|
+
`No supported A2A runtime available for summarization (mode=${modeInfo.mode}). ` +
|
|
402
|
+
'Install the openclaw or claude CLI and set A2A_RUNTIME accordingly.'
|
|
403
|
+
);
|
|
583
404
|
}
|
|
405
|
+
|
|
584
406
|
const startAt = Date.now();
|
|
585
407
|
logger.debug('Invoking openclaw summary', {
|
|
586
408
|
event: 'openclaw_summary_start',
|
|
@@ -612,72 +434,27 @@ function createRuntimeAdapter(options = {}) {
|
|
|
612
434
|
});
|
|
613
435
|
return result;
|
|
614
436
|
}
|
|
615
|
-
|
|
616
|
-
event: 'openclaw_summary_empty',
|
|
617
|
-
traceId: effectiveTraceId,
|
|
618
|
-
requestId,
|
|
619
|
-
conversationId: effectiveConversationId,
|
|
620
|
-
data: {
|
|
621
|
-
session_id: sessionId,
|
|
622
|
-
duration_ms: Date.now() - startAt
|
|
623
|
-
}
|
|
624
|
-
});
|
|
625
|
-
return runGenericSummary({
|
|
626
|
-
messages,
|
|
627
|
-
callerInfo,
|
|
628
|
-
reason: 'empty summary from openclaw runtime'
|
|
629
|
-
});
|
|
437
|
+
throw new Error('OpenClaw summary returned empty output');
|
|
630
438
|
} catch (err) {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
event: 'openclaw_summary_failed',
|
|
634
|
-
traceId: effectiveTraceId,
|
|
635
|
-
requestId,
|
|
636
|
-
conversationId: effectiveConversationId,
|
|
637
|
-
error_code: 'OPENCLAW_SUMMARY_FAILED',
|
|
638
|
-
hint: 'Inspect summary message length, timeout configuration, and CLI stderr output.',
|
|
639
|
-
error: err,
|
|
640
|
-
data: {
|
|
641
|
-
session_id: sessionId,
|
|
642
|
-
duration_ms: Date.now() - startAt
|
|
643
|
-
}
|
|
644
|
-
});
|
|
645
|
-
throw err;
|
|
646
|
-
}
|
|
647
|
-
logger.warn('OpenClaw summary failed, using generic fallback', {
|
|
648
|
-
event: 'openclaw_summary_failed_fallback',
|
|
439
|
+
logger.error('OpenClaw summary failed', {
|
|
440
|
+
event: 'openclaw_summary_failed',
|
|
649
441
|
traceId: effectiveTraceId,
|
|
650
442
|
requestId,
|
|
651
443
|
conversationId: effectiveConversationId,
|
|
652
|
-
error_code: '
|
|
653
|
-
hint: 'Inspect
|
|
444
|
+
error_code: 'OPENCLAW_SUMMARY_FAILED',
|
|
445
|
+
hint: 'Inspect summary message length, timeout configuration, and CLI stderr output.',
|
|
654
446
|
error: err,
|
|
655
447
|
data: {
|
|
656
448
|
session_id: sessionId,
|
|
657
|
-
duration_ms: Date.now() - startAt
|
|
658
|
-
failover_enabled: failoverEnabled
|
|
449
|
+
duration_ms: Date.now() - startAt
|
|
659
450
|
}
|
|
660
451
|
});
|
|
661
|
-
|
|
662
|
-
messages,
|
|
663
|
-
callerInfo,
|
|
664
|
-
reason: `openclaw summary unavailable: ${err.message}`
|
|
665
|
-
});
|
|
452
|
+
throw err;
|
|
666
453
|
}
|
|
667
454
|
}
|
|
668
455
|
|
|
669
456
|
async function notify({ level, token, caller, message, conversationId, traceId }) {
|
|
670
457
|
const requestId = token?.request_id || token?.requestId || null;
|
|
671
|
-
const payload = {
|
|
672
|
-
mode: 'a2a-notify',
|
|
673
|
-
level,
|
|
674
|
-
token: token || null,
|
|
675
|
-
caller: caller || null,
|
|
676
|
-
message,
|
|
677
|
-
conversationId,
|
|
678
|
-
traceId,
|
|
679
|
-
requestId
|
|
680
|
-
};
|
|
681
458
|
|
|
682
459
|
logger.debug('Owner notify requested', {
|
|
683
460
|
event: 'notify_requested',
|
|
@@ -688,8 +465,27 @@ function createRuntimeAdapter(options = {}) {
|
|
|
688
465
|
data: { level }
|
|
689
466
|
});
|
|
690
467
|
|
|
468
|
+
if (modeInfo.mode === 'claude') {
|
|
469
|
+
// Claude mode: notifications are a no-op (no notification transport available)
|
|
470
|
+
logger.debug('Notification skipped (claude mode has no notification transport)', {
|
|
471
|
+
event: 'notify_skipped_claude',
|
|
472
|
+
traceId,
|
|
473
|
+
requestId,
|
|
474
|
+
conversationId,
|
|
475
|
+
tokenId: token?.id
|
|
476
|
+
});
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
691
480
|
if (modeInfo.mode !== 'openclaw') {
|
|
692
|
-
|
|
481
|
+
logger.debug('Notification skipped (no supported runtime)', {
|
|
482
|
+
event: 'notify_skipped_no_runtime',
|
|
483
|
+
traceId,
|
|
484
|
+
requestId,
|
|
485
|
+
conversationId,
|
|
486
|
+
tokenId: token?.id
|
|
487
|
+
});
|
|
488
|
+
return;
|
|
693
489
|
}
|
|
694
490
|
|
|
695
491
|
if (level !== 'all') {
|
|
@@ -713,31 +509,20 @@ function createRuntimeAdapter(options = {}) {
|
|
|
713
509
|
}
|
|
714
510
|
});
|
|
715
511
|
} catch (err) {
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
logger.warn('OpenClaw notify failed, running generic notifier', {
|
|
720
|
-
event: 'openclaw_notify_failed_fallback',
|
|
512
|
+
// Notifications are best-effort; log and swallow
|
|
513
|
+
logger.warn('OpenClaw notify failed', {
|
|
514
|
+
event: 'openclaw_notify_failed',
|
|
721
515
|
traceId,
|
|
722
516
|
requestId,
|
|
723
517
|
conversationId,
|
|
724
518
|
tokenId: token?.id,
|
|
725
|
-
error_code: '
|
|
519
|
+
error_code: 'OPENCLAW_NOTIFY_FAILED',
|
|
726
520
|
hint: 'Check OpenClaw messaging channel config and notify permissions.',
|
|
727
521
|
error: err,
|
|
728
522
|
data: {
|
|
729
|
-
failover_enabled: failoverEnabled,
|
|
730
523
|
duration_ms: Date.now() - notifyStart
|
|
731
524
|
}
|
|
732
525
|
});
|
|
733
|
-
logger.debug('OpenClaw notify fallback to generic notifier', {
|
|
734
|
-
event: 'openclaw_notify_generic_fallback',
|
|
735
|
-
traceId,
|
|
736
|
-
requestId,
|
|
737
|
-
conversationId,
|
|
738
|
-
tokenId: token?.id
|
|
739
|
-
});
|
|
740
|
-
await runGenericNotify(payload);
|
|
741
526
|
}
|
|
742
527
|
}
|
|
743
528
|
|
|
@@ -745,18 +530,17 @@ function createRuntimeAdapter(options = {}) {
|
|
|
745
530
|
mode: modeInfo.mode,
|
|
746
531
|
requestedMode: modeInfo.requested,
|
|
747
532
|
hasOpenClaw: modeInfo.hasOpenClaw,
|
|
533
|
+
hasClaude: modeInfo.hasClaude,
|
|
748
534
|
reason: modeInfo.reason,
|
|
749
535
|
warning: modeInfo.warning || null,
|
|
750
|
-
failoverEnabled,
|
|
751
536
|
runTurn,
|
|
752
537
|
summarize,
|
|
753
538
|
notify,
|
|
754
|
-
|
|
539
|
+
getLastTurnMeta
|
|
755
540
|
};
|
|
756
541
|
}
|
|
757
542
|
|
|
758
543
|
module.exports = {
|
|
759
544
|
createRuntimeAdapter,
|
|
760
|
-
resolveRuntimeMode
|
|
761
|
-
buildFallbackResponse
|
|
545
|
+
resolveRuntimeMode
|
|
762
546
|
};
|