a2acalling 0.6.39 → 0.6.41

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 CHANGED
@@ -1245,7 +1245,7 @@ a2a add "${inviteUrl}" "${ownerText || 'friend'}" && a2a call "${ownerText || 'f
1245
1245
 
1246
1246
  if (!target || !message) {
1247
1247
  console.error('Usage: a2a call <contact_or_url> <message>');
1248
- console.error(' --multi Enable multi-turn conversation');
1248
+ console.error(' --single Single-turn call (one message, one response)');
1249
1249
  console.error(' --min-turns N Minimum turns before close (default: 8)');
1250
1250
  console.error(' --max-turns N Maximum turns (default: 25)');
1251
1251
  process.exit(1);
@@ -1262,10 +1262,10 @@ a2a add "${inviteUrl}" "${ownerText || 'friend'}" && a2a call "${ownerText || 'f
1262
1262
  }
1263
1263
  }
1264
1264
 
1265
- const multi = Boolean(args.flags.multi);
1265
+ const single = Boolean(args.flags.single);
1266
1266
  const callerName = args.flags.name || 'CLI User';
1267
1267
 
1268
- if (multi) {
1268
+ if (!single) {
1269
1269
  // Multi-turn conversation via ConversationDriver
1270
1270
  const { ConversationDriver } = require('../src/lib/conversation-driver');
1271
1271
  const { createRuntimeAdapter } = require('../src/lib/runtime-adapter');
@@ -2292,8 +2292,8 @@ Conversations:
2292
2292
  conversations end <id> End and summarize conversation
2293
2293
 
2294
2294
  Calling:
2295
- call <contact|url> <msg> Call a contact (or invite URL)
2296
- --multi Enable multi-turn conversation
2295
+ call <contact|url> <msg> Call a contact (multi-turn by default)
2296
+ --single Single-turn call (one message, one response)
2297
2297
  --min-turns N Minimum turns before close (default: 8)
2298
2298
  --max-turns N Maximum turns (default: 25)
2299
2299
  ping <url> Check if agent is reachable
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "a2acalling",
3
- "version": "0.6.39",
3
+ "version": "0.6.41",
4
4
  "description": "Agent-to-agent calling for OpenClaw - A2A agent communication",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -24,6 +24,67 @@ const { createLogger } = require('./logger');
24
24
 
25
25
  const logger = createLogger({ component: 'a2a.conversation-driver' });
26
26
 
27
+ /**
28
+ * Detect remote termination intent from response text.
29
+ * Returns true if the remote agent is clearly trying to end the conversation.
30
+ */
31
+ const TERMINATION_PATTERNS = [
32
+ /\[TERMINAT/i,
33
+ /\[DISCONNECT/i,
34
+ /\[END.?CALL\]/i,
35
+ /\[CLOSING\]/i,
36
+ /\bREFUSING\s+(TO\s+)?(CONTINU|RESPOND|ENGAG)/i,
37
+ /\bcall\s+complet(ed|e)\b/i,
38
+ /\bconversation\s+(is\s+)?(over|ended|closed|complet)/i,
39
+ /\bclosing\s+this\s+(call|conversation|connection)\b/i,
40
+ /\bgoodbye\b.*\b(end|clos|terminat)/i,
41
+ /\bdisconnect(ing|ed)\b/i
42
+ ];
43
+
44
+ function detectRemoteTermination(text) {
45
+ if (!text || typeof text !== 'string') return false;
46
+ return TERMINATION_PATTERNS.some(pattern => pattern.test(text));
47
+ }
48
+
49
+ /**
50
+ * Infer collaboration state progression when the runtime doesn't emit
51
+ * <collab_state> tags (generic/fallback mode). Advances phase based on
52
+ * turn count and estimates overlap from remote text analysis.
53
+ */
54
+ function inferStateProgression(collabState, remoteText, turn) {
55
+ const patch = {};
56
+
57
+ // Phase progression based on turn count
58
+ if (collabState.phase === 'handshake' && turn >= 2) {
59
+ patch.phase = 'exploring';
60
+ } else if (collabState.phase === 'exploring' && turn >= 5) {
61
+ patch.phase = 'deepening';
62
+ } else if (collabState.phase === 'deepening' && turn >= 8) {
63
+ patch.phase = 'converging';
64
+ }
65
+
66
+ // Estimate overlap from remote text length and engagement signals
67
+ const text = (remoteText || '').toLowerCase();
68
+ const engagementSignals = [
69
+ /\binteresting\b/, /\bgreat\b/, /\bexciting\b/, /\bagree\b/,
70
+ /\bcollaborat/i, /\bpartner/i, /\btogether\b/, /\bshare\b/,
71
+ /\bopportunit/i, /\bsynerg/i, /\bcomplement/i, /\balign/i,
72
+ /\?/ // questions indicate engagement
73
+ ];
74
+ const matchCount = engagementSignals.filter(p => p.test(text)).length;
75
+ const textLengthFactor = Math.min(text.length / 500, 1); // longer = more engaged
76
+ const engagement = (matchCount / engagementSignals.length) * 0.5 + textLengthFactor * 0.3;
77
+
78
+ // Blend current overlap with new engagement signal
79
+ const newOverlap = collabState.overlapScore * 0.6 + engagement * 0.4;
80
+ patch.overlapScore = Math.max(0.1, Math.min(0.95, newOverlap));
81
+
82
+ // Confidence increases over turns
83
+ patch.confidence = Math.min(0.9, 0.25 + turn * 0.08);
84
+
85
+ return patch;
86
+ }
87
+
27
88
  class ConversationDriver {
28
89
  /**
29
90
  * @param {object} options
@@ -231,6 +292,15 @@ Be concise but specific. No filler.`;
231
292
  break;
232
293
  }
233
294
 
295
+ // 3b. Detect termination intent from remote text even if can_continue is true
296
+ if (detectRemoteTermination(remoteText)) {
297
+ logger.info('Remote text indicates termination intent', {
298
+ event: 'driver_remote_text_close',
299
+ data: { turn: turn + 1, conversationId, preview: remoteText.slice(0, 80) }
300
+ });
301
+ break;
302
+ }
303
+
234
304
  if (collabState.closeSignal && collabState.turnCount >= this.minTurns) {
235
305
  logger.info('Local close signal met minimum turns', {
236
306
  event: 'driver_local_close',
@@ -309,6 +379,12 @@ Be concise but specific. No filler.`;
309
379
  if (parsed.statePatch.confidence != null) {
310
380
  collabState.confidence = Math.max(0, Math.min(1, parsed.statePatch.confidence));
311
381
  }
382
+ } else {
383
+ // No <collab_state> in response (generic/fallback runtime) — infer progression
384
+ const inferred = inferStateProgression(collabState, remoteText, turn + 1);
385
+ if (inferred.phase) collabState.phase = inferred.phase;
386
+ if (inferred.overlapScore != null) collabState.overlapScore = inferred.overlapScore;
387
+ if (inferred.confidence != null) collabState.confidence = inferred.confidence;
312
388
  }
313
389
 
314
390
  // 7. Persist collab state to DB
@@ -376,4 +452,4 @@ Be concise but specific. No filler.`;
376
452
  }
377
453
  }
378
454
 
379
- module.exports = { ConversationDriver };
455
+ module.exports = { ConversationDriver, detectRemoteTermination, inferStateProgression };