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/src/server.js CHANGED
@@ -23,6 +23,11 @@ const {
23
23
  const { findAvailablePort } = require('./lib/port-scanner');
24
24
  const { createLogger } = require('./lib/logger');
25
25
  const { writePidFile, removePidFile } = require('./lib/pid-file');
26
+ const { buildUnifiedSummaryPrompt } = require('./lib/summary-prompt');
27
+ const { A2AConfig } = require('./lib/config');
28
+ const { UpdateManager } = require('./lib/update-manager');
29
+ const { spawn } = require('child_process');
30
+ const { resolveTurnTimeoutMs } = require('./lib/turn-timeout');
26
31
 
27
32
  const DEFAULT_PORTS = [80, 3001, 8080, 8443, 9001];
28
33
  const requestedPort = process.env.PORT ? parseInt(process.env.PORT, 10)
@@ -61,6 +66,7 @@ function loadAgentContext() {
61
66
 
62
67
  const agentContext = loadAgentContext();
63
68
  const tokenStore = new TokenStore();
69
+ const config = new A2AConfig();
64
70
  const runtime = createRuntimeAdapter({
65
71
  workspaceDir,
66
72
  agentContext,
@@ -115,6 +121,15 @@ function readPositiveIntEnv(name, fallback) {
115
121
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
116
122
  }
117
123
 
124
+ function resolveConfiguredTurnTimeoutMs() {
125
+ try {
126
+ const defaults = config.getDefaults?.() || {};
127
+ return defaults.turnTimeoutMs ?? defaults.turn_timeout_ms ?? null;
128
+ } catch (err) {
129
+ return null;
130
+ }
131
+ }
132
+
118
133
  function resolveCollabMode() {
119
134
  const raw = String(process.env.A2A_COLLAB_MODE || 'adaptive').trim().toLowerCase();
120
135
  if (raw === 'deep_dive' || raw === 'deep-dive') {
@@ -578,6 +593,10 @@ async function callAgent(message, a2aContext) {
578
593
  : buildConnectionPrompt(promptOptions);
579
594
 
580
595
  const sessionId = `a2a-${conversationId}`;
596
+ const claudeTurnTimeoutMs = resolveTurnTimeoutMs({
597
+ tokenTimeoutMs: a2aContext.timeout_ms,
598
+ configTimeoutMs: resolveConfiguredTurnTimeoutMs()
599
+ });
581
600
 
582
601
  try {
583
602
  callLogger.info('Handling inbound call turn', {
@@ -595,12 +614,13 @@ async function callAgent(message, a2aContext) {
595
614
  prompt,
596
615
  message,
597
616
  caller: a2aContext.caller || {},
598
- timeoutMs: 65000,
617
+ timeoutMs: runtime.mode === 'claude' ? claudeTurnTimeoutMs : 65000,
599
618
  context: {
600
619
  conversationId,
601
620
  tier: tierInfo,
602
621
  ownerName: agentContext.owner,
603
622
  allowedTopics: a2aContext.allowed_topics || [],
623
+ timeoutMs: runtime.mode === 'claude' ? claudeTurnTimeoutMs : 65000,
604
624
  traceId,
605
625
  requestId
606
626
  }
@@ -691,30 +711,61 @@ async function callAgent(message, a2aContext) {
691
711
  * Generate strategic summary via sub-agent
692
712
  */
693
713
  async function generateSummary(messages, callerInfo) {
694
- const messageText = messages.map(m => {
695
- const role = m.direction === 'inbound' ? `[${callerInfo?.name || 'Caller'}]` : '[You]';
696
- return `${role}: ${m.content}`;
697
- }).join('\n');
698
-
699
- const callerDesc = `${callerInfo?.name || 'Unknown'}${callerInfo?.owner ? ` (${callerInfo.owner}'s agent)` : ''}`;
700
-
701
- const prompt = `Summarize this A2A call for the owner. Write from the owner's perspective.
702
-
703
- Conversation with ${callerDesc}:
704
- ${messageText}
705
-
706
- Structure your summary with these sections:
714
+ // Look up collaboration state if we have a conversation_id
715
+ const conversationId = callerInfo?.conversation_id || callerInfo?.conversationId;
716
+ let collaborationState = null;
717
+ if (conversationId) {
718
+ const collabSession = collaborationSessions.get(conversationId);
719
+ if (collabSession) {
720
+ collaborationState = {
721
+ phase: collabSession.phase,
722
+ overlapScore: collabSession.overlapScore,
723
+ turnCount: collabSession.turnCount,
724
+ activeThreads: collabSession.activeThreads,
725
+ candidateCollaborations: collabSession.candidateCollaborations,
726
+ closeSignal: collabSession.closeSignal
727
+ };
728
+ }
729
+ }
707
730
 
708
- **Who:** Who called, who they represent, key facts about them.
709
- **Key Discoveries:** What was learned about the other side — capabilities, interests, blind spots.
710
- **Collaboration Potential:** Rate HIGH/MEDIUM/LOW. List specific opportunities identified.
711
- **What We Learned vs Shared:** Brief information exchange audit — what did we get, what did we give.
712
- **Recommended Follow-Up:**
713
- - [ ] Actionable item 1
714
- - [ ] Actionable item 2
715
- **Assessment:** One-sentence strategic value judgment.
731
+ // Load disclosure manifest for the caller's tier
732
+ const tier = callerInfo?.tier || 'public';
733
+ let disclosure = null;
734
+ try {
735
+ const tierTopics = getTopicsForTier(tier);
736
+ if (tierTopics) {
737
+ disclosure = {
738
+ topics: tierTopics.topics || [],
739
+ objectives: tierTopics.objectives || [],
740
+ doNotDiscuss: tierTopics.do_not_discuss || [],
741
+ neverDisclose: tierTopics.never_disclose || []
742
+ };
743
+ }
744
+ } catch (e) {
745
+ // Disclosure is optional — continue without it
746
+ }
716
747
 
717
- Be concise but specific. No filler.`;
748
+ // Build transcript in unified format
749
+ const transcript = messages.map(m => ({
750
+ direction: m.direction,
751
+ content: m.content
752
+ }));
753
+
754
+ const prompt = buildUnifiedSummaryPrompt({
755
+ transcript,
756
+ callerInfo: {
757
+ name: callerInfo?.name || null,
758
+ owner: callerInfo?.owner || null,
759
+ context: callerInfo?.context || null
760
+ },
761
+ disclosure,
762
+ collaborationState,
763
+ ownerContext: {
764
+ agentName: agentContext.name,
765
+ ownerName: agentContext.owner,
766
+ goals: []
767
+ }
768
+ });
718
769
 
719
770
  try {
720
771
  return await runtime.summarize({
@@ -723,13 +774,13 @@ Be concise but specific. No filler.`;
723
774
  messages,
724
775
  callerInfo,
725
776
  traceId: callerInfo?.trace_id || callerInfo?.traceId,
726
- conversationId: callerInfo?.conversation_id || callerInfo?.conversationId
777
+ conversationId
727
778
  });
728
779
  } catch (err) {
729
780
  logger.error('Summary generation failed', {
730
781
  event: 'summary_generation_failed',
731
782
  traceId: callerInfo?.trace_id || callerInfo?.traceId,
732
- conversationId: callerInfo?.conversation_id || callerInfo?.conversationId,
783
+ conversationId,
733
784
  error_code: 'SUMMARY_GENERATION_FAILED',
734
785
  hint: 'Check summarizer runtime and command configuration for summary stage.',
735
786
  error: err,
@@ -773,17 +824,23 @@ async function notifyOwner({ level, token, caller, message, conversation_id, tra
773
824
 
774
825
  const app = express();
775
826
  app.use(express.json());
827
+ let activeCallMonitor = null;
828
+ let updateManager = null;
776
829
 
777
830
  // Minimal owner dashboard (local by default unless A2A_ADMIN_TOKEN is provided)
778
831
  // All routes under /api/a2a/* so reverse proxy config stays simple.
779
832
  app.use('/api/a2a/dashboard', createDashboardApiRouter({
780
833
  tokenStore,
781
834
  agentContext,
835
+ config,
836
+ getUpdateManager: () => updateManager,
782
837
  logger: logger.child({ component: 'a2a.dashboard' })
783
838
  }));
784
839
  app.use('/api/a2a/dashboard', createDashboardUiRouter({
785
840
  tokenStore,
786
841
  agentContext,
842
+ config,
843
+ getUpdateManager: () => updateManager,
787
844
  logger: logger.child({ component: 'a2a.dashboard' })
788
845
  }));
789
846
 
@@ -802,6 +859,9 @@ app.use('/callbook', createCallbookRouter());
802
859
  app.use('/api/a2a', createRoutes({
803
860
  tokenStore,
804
861
  logger: logger.child({ component: 'a2a.routes' }),
862
+ onCallMonitor: (monitor) => {
863
+ activeCallMonitor = monitor;
864
+ },
805
865
 
806
866
  async handleMessage(message, context, options) {
807
867
  const traceId = context.trace_id || null;
@@ -905,6 +965,50 @@ async function startServer() {
905
965
  }
906
966
  });
907
967
  writePidFile(process.pid);
968
+
969
+ if (!updateManager) {
970
+ const pkg = require('../package.json');
971
+ const restartFn = async () => {
972
+ const cliPath = path.join(__dirname, '..', 'bin', 'cli.js');
973
+ const helperScript = `
974
+ const { spawn } = require('child_process');
975
+ const startNext = () => {
976
+ const child = spawn(process.execPath, [${JSON.stringify(cliPath)}, 'server', '--port', ${JSON.stringify(String(port))}], {
977
+ detached: true,
978
+ stdio: 'ignore',
979
+ env: process.env
980
+ });
981
+ child.unref();
982
+ process.exit(0);
983
+ };
984
+ setTimeout(startNext, 1500);
985
+ `;
986
+ const helper = spawn(process.execPath, ['-e', helperScript], {
987
+ detached: true,
988
+ stdio: 'ignore',
989
+ env: process.env
990
+ });
991
+ helper.unref();
992
+ setTimeout(() => {
993
+ process.kill(process.pid, 'SIGTERM');
994
+ }, 150);
995
+ };
996
+
997
+ updateManager = new UpdateManager({
998
+ currentVersion: pkg.version,
999
+ config,
1000
+ logger: logger.child({ component: 'a2a.updater' }),
1001
+ getCallMonitor: () => activeCallMonitor,
1002
+ restartFn
1003
+ });
1004
+ updateManager.start();
1005
+ updateManager.triggerCheck({ reason: 'startup' }).catch((err) => {
1006
+ logger.warn('Initial auto-update check failed', {
1007
+ event: 'updater_startup_check_failed',
1008
+ error: err
1009
+ });
1010
+ });
1011
+ }
908
1012
  });
909
1013
 
910
1014
  server.on('error', (err) => {
@@ -920,8 +1024,8 @@ async function startServer() {
920
1024
  throw err;
921
1025
  });
922
1026
 
923
- // Graceful shutdown: clean up PID file
924
1027
  function shutdown() {
1028
+ if (updateManager) updateManager.stop();
925
1029
  removePidFile();
926
1030
  server.close(() => process.exit(0));
927
1031
  // Force exit after 5s if connections won't close