a2acalling 0.6.38 → 0.6.40

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "a2acalling",
3
- "version": "0.6.38",
3
+ "version": "0.6.40",
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 };