@cicctencent/agent-server 0.2.81 → 0.2.83

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.
Files changed (125) hide show
  1. package/dist/cjs/agent/agent-server.d.ts.map +1 -1
  2. package/dist/cjs/agent/agent-server.js +175 -57
  3. package/dist/cjs/agent/agent-server.js.map +1 -1
  4. package/dist/cjs/connectors/calendar-adapter.d.ts +1 -1
  5. package/dist/cjs/connectors/calendar-adapter.d.ts.map +1 -1
  6. package/dist/cjs/connectors/calendar-adapter.js +2 -10
  7. package/dist/cjs/connectors/calendar-adapter.js.map +1 -1
  8. package/dist/cjs/connectors/db-adapter.d.ts +1 -1
  9. package/dist/cjs/connectors/db-adapter.d.ts.map +1 -1
  10. package/dist/cjs/connectors/db-adapter.js +2 -19
  11. package/dist/cjs/connectors/db-adapter.js.map +1 -1
  12. package/dist/cjs/connectors/email-adapter.d.ts +1 -1
  13. package/dist/cjs/connectors/email-adapter.d.ts.map +1 -1
  14. package/dist/cjs/connectors/email-adapter.js +2 -10
  15. package/dist/cjs/connectors/email-adapter.js.map +1 -1
  16. package/dist/cjs/connectors/fs-adapter.d.ts +1 -1
  17. package/dist/cjs/connectors/fs-adapter.d.ts.map +1 -1
  18. package/dist/cjs/connectors/fs-adapter.js +4 -12
  19. package/dist/cjs/connectors/fs-adapter.js.map +1 -1
  20. package/dist/cjs/connectors/http-adapter.js +2 -4
  21. package/dist/cjs/connectors/http-adapter.js.map +1 -1
  22. package/dist/cjs/connectors/mq-adapter.d.ts +1 -1
  23. package/dist/cjs/connectors/mq-adapter.d.ts.map +1 -1
  24. package/dist/cjs/connectors/mq-adapter.js +2 -10
  25. package/dist/cjs/connectors/mq-adapter.js.map +1 -1
  26. package/dist/cjs/mcp/api-bridge-client.d.ts +1 -0
  27. package/dist/cjs/mcp/api-bridge-client.d.ts.map +1 -1
  28. package/dist/cjs/mcp/api-bridge-client.js +60 -2
  29. package/dist/cjs/mcp/api-bridge-client.js.map +1 -1
  30. package/dist/cjs/prompt/prompt-builder.d.ts +6 -0
  31. package/dist/cjs/prompt/prompt-builder.d.ts.map +1 -1
  32. package/dist/cjs/prompt/prompt-builder.js +10 -1
  33. package/dist/cjs/prompt/prompt-builder.js.map +1 -1
  34. package/dist/cjs/service/artifact.service.d.ts.map +1 -1
  35. package/dist/cjs/service/artifact.service.js +0 -26
  36. package/dist/cjs/service/artifact.service.js.map +1 -1
  37. package/dist/cjs/service/cache.service.d.ts +2 -0
  38. package/dist/cjs/service/cache.service.d.ts.map +1 -1
  39. package/dist/cjs/service/cache.service.js +8 -1
  40. package/dist/cjs/service/cache.service.js.map +1 -1
  41. package/dist/cjs/service/chat.service.d.ts.map +1 -1
  42. package/dist/cjs/service/chat.service.js +1 -3
  43. package/dist/cjs/service/chat.service.js.map +1 -1
  44. package/dist/cjs/service/safety-audit.middleware.d.ts.map +1 -1
  45. package/dist/cjs/service/safety-audit.middleware.js +12 -1
  46. package/dist/cjs/service/safety-audit.middleware.js.map +1 -1
  47. package/dist/cjs/service/settings.service.d.ts.map +1 -1
  48. package/dist/cjs/service/settings.service.js +2 -2
  49. package/dist/cjs/service/settings.service.js.map +1 -1
  50. package/dist/cjs/sse/sse-handler.d.ts.map +1 -1
  51. package/dist/cjs/sse/sse-handler.js +3 -1
  52. package/dist/cjs/sse/sse-handler.js.map +1 -1
  53. package/dist/cjs/tool/local-executors.d.ts +1 -0
  54. package/dist/cjs/tool/local-executors.d.ts.map +1 -1
  55. package/dist/cjs/tool/local-executors.js +8 -3
  56. package/dist/cjs/tool/local-executors.js.map +1 -1
  57. package/dist/cjs/tool/tools/git.d.ts.map +1 -1
  58. package/dist/cjs/tool/tools/git.js +34 -7
  59. package/dist/cjs/tool/tools/git.js.map +1 -1
  60. package/dist/cjs/tool/tools/sandbox.js.map +1 -1
  61. package/dist/cjs/types.d.ts +17 -1
  62. package/dist/cjs/types.d.ts.map +1 -1
  63. package/dist/esm/agent/agent-server.d.ts.map +1 -1
  64. package/dist/esm/agent/agent-server.js +175 -57
  65. package/dist/esm/agent/agent-server.js.map +1 -1
  66. package/dist/esm/connectors/calendar-adapter.d.ts +1 -1
  67. package/dist/esm/connectors/calendar-adapter.d.ts.map +1 -1
  68. package/dist/esm/connectors/calendar-adapter.js +2 -10
  69. package/dist/esm/connectors/calendar-adapter.js.map +1 -1
  70. package/dist/esm/connectors/db-adapter.d.ts +1 -1
  71. package/dist/esm/connectors/db-adapter.d.ts.map +1 -1
  72. package/dist/esm/connectors/db-adapter.js +2 -19
  73. package/dist/esm/connectors/db-adapter.js.map +1 -1
  74. package/dist/esm/connectors/email-adapter.d.ts +1 -1
  75. package/dist/esm/connectors/email-adapter.d.ts.map +1 -1
  76. package/dist/esm/connectors/email-adapter.js +2 -10
  77. package/dist/esm/connectors/email-adapter.js.map +1 -1
  78. package/dist/esm/connectors/fs-adapter.d.ts +1 -1
  79. package/dist/esm/connectors/fs-adapter.d.ts.map +1 -1
  80. package/dist/esm/connectors/fs-adapter.js +4 -12
  81. package/dist/esm/connectors/fs-adapter.js.map +1 -1
  82. package/dist/esm/connectors/http-adapter.js +2 -4
  83. package/dist/esm/connectors/http-adapter.js.map +1 -1
  84. package/dist/esm/connectors/mq-adapter.d.ts +1 -1
  85. package/dist/esm/connectors/mq-adapter.d.ts.map +1 -1
  86. package/dist/esm/connectors/mq-adapter.js +2 -10
  87. package/dist/esm/connectors/mq-adapter.js.map +1 -1
  88. package/dist/esm/mcp/api-bridge-client.d.ts +1 -0
  89. package/dist/esm/mcp/api-bridge-client.d.ts.map +1 -1
  90. package/dist/esm/mcp/api-bridge-client.js +27 -2
  91. package/dist/esm/mcp/api-bridge-client.js.map +1 -1
  92. package/dist/esm/prompt/prompt-builder.d.ts +6 -0
  93. package/dist/esm/prompt/prompt-builder.d.ts.map +1 -1
  94. package/dist/esm/prompt/prompt-builder.js +10 -1
  95. package/dist/esm/prompt/prompt-builder.js.map +1 -1
  96. package/dist/esm/service/artifact.service.d.ts.map +1 -1
  97. package/dist/esm/service/artifact.service.js +0 -26
  98. package/dist/esm/service/artifact.service.js.map +1 -1
  99. package/dist/esm/service/cache.service.d.ts +2 -0
  100. package/dist/esm/service/cache.service.d.ts.map +1 -1
  101. package/dist/esm/service/cache.service.js +8 -1
  102. package/dist/esm/service/cache.service.js.map +1 -1
  103. package/dist/esm/service/chat.service.d.ts.map +1 -1
  104. package/dist/esm/service/chat.service.js +1 -3
  105. package/dist/esm/service/chat.service.js.map +1 -1
  106. package/dist/esm/service/safety-audit.middleware.d.ts.map +1 -1
  107. package/dist/esm/service/safety-audit.middleware.js +12 -1
  108. package/dist/esm/service/safety-audit.middleware.js.map +1 -1
  109. package/dist/esm/service/settings.service.d.ts.map +1 -1
  110. package/dist/esm/service/settings.service.js +2 -2
  111. package/dist/esm/service/settings.service.js.map +1 -1
  112. package/dist/esm/sse/sse-handler.d.ts.map +1 -1
  113. package/dist/esm/sse/sse-handler.js +3 -1
  114. package/dist/esm/sse/sse-handler.js.map +1 -1
  115. package/dist/esm/tool/local-executors.d.ts +1 -0
  116. package/dist/esm/tool/local-executors.d.ts.map +1 -1
  117. package/dist/esm/tool/local-executors.js +8 -3
  118. package/dist/esm/tool/local-executors.js.map +1 -1
  119. package/dist/esm/tool/tools/git.d.ts.map +1 -1
  120. package/dist/esm/tool/tools/git.js +34 -7
  121. package/dist/esm/tool/tools/git.js.map +1 -1
  122. package/dist/esm/tool/tools/sandbox.js.map +1 -1
  123. package/dist/esm/types.d.ts +17 -1
  124. package/dist/esm/types.d.ts.map +1 -1
  125. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"agent-server.d.ts","sourceRoot":"","sources":["../../../src/agent/agent-server.ts"],"names":[],"mappings":"AA4CA,OAAO,KAAK,EACV,WAAW,EACX,kBAAkB,EAMnB,MAAM,aAAa,CAAC;AA+BrB;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,WAAW,CAm4C1E"}
1
+ {"version":3,"file":"agent-server.d.ts","sourceRoot":"","sources":["../../../src/agent/agent-server.ts"],"names":[],"mappings":"AA4CA,OAAO,KAAK,EACV,WAAW,EACX,kBAAkB,EAMnB,MAAM,aAAa,CAAC;AA6ErB;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,WAAW,CAy9C1E"}
@@ -23,6 +23,53 @@ const cache_service_ts_1 = require("../service/cache.service.js");
23
23
  const data_dir_ts_1 = require("../utils/data-dir.js");
24
24
  const env_collector_ts_1 = require("../service/env-collector.js");
25
25
  const interaction_service_ts_1 = require("../service/interaction.service.js");
26
+ /**
27
+ * 子 Agent 事件全局去重包装器。
28
+ *
29
+ * 远程 A2A 服务(REST / JSON-RPC)有时会把同一事件重复下发——例如重复下发同一
30
+ * toolCallId 的 tool_call(导致前端出现「两张相同的工具卡片」),或重复下发
31
+ * start / done。这里按事件 key 在整条事件流生命周期内去重:
32
+ * - tool_call / tool_result:key = `${type}:${toolCallId}`(同一调用只转发一次)
33
+ * - start / done:key = `${type}:${parentToolCallId}`(每个父调用只一次)
34
+ * - content / thinking / progress:相邻去重(避免逐段重复下发,前端另有累计增量兜底)
35
+ *
36
+ * 既挡住顶层的重复 tool_call 卡片,也避免重复事件污染 fullContent / toolCallCount 等
37
+ * 聚合状态。
38
+ *
39
+ * @param emit 底层 SubAgentEventEmitter(通常是 ctx.emitSubEvent)
40
+ * @param getKeyContext 提供 parentToolCallId / agentName 的回调(用于 start/done 去重)
41
+ */
42
+ function dedupeSubEventEmitter(emit, getKeyContext) {
43
+ const seenGlobal = new Set();
44
+ let lastAdjKey = null;
45
+ return (evt) => {
46
+ const { eventType, data } = evt;
47
+ let dedupKey = null;
48
+ if (eventType === 'tool_call' || eventType === 'tool_result') {
49
+ const id = data?.toolCallId || data?.callId;
50
+ if (id)
51
+ dedupKey = `${eventType}:${id}`;
52
+ }
53
+ else if (eventType === 'start' || eventType === 'done') {
54
+ const ctx = getKeyContext();
55
+ dedupKey = `${eventType}:${ctx.parentToolCallId}`;
56
+ }
57
+ else if (eventType === 'content' || eventType === 'thinking' || eventType === 'progress') {
58
+ // 相邻去重:同一文本连续下发则跳过(避免逐段叠加噪声)
59
+ const text = eventType === 'progress' ? JSON.stringify(data ?? {}) : (data?.content || '');
60
+ const adjKey = `${eventType}:${text}`;
61
+ if (adjKey === lastAdjKey)
62
+ return;
63
+ lastAdjKey = adjKey;
64
+ }
65
+ if (dedupKey) {
66
+ if (seenGlobal.has(dedupKey))
67
+ return;
68
+ seenGlobal.add(dedupKey);
69
+ }
70
+ emit?.(evt);
71
+ };
72
+ }
26
73
  /**
27
74
  * 远程 Agent A2A 调试槽(per-request)。
28
75
  * 委派远程 Agent 的 runner 在引擎 build 期创建并缓存,运行时无法持有具体请求的
@@ -137,12 +184,21 @@ function createAgentServer(options) {
137
184
  toolRegistry.register(askUserTool);
138
185
  toolRegistry.registerAll([...builtinTools, ...extraTools]);
139
186
  skillManager = new agent_core_1.SkillManager();
140
- const skillsDir = options.skillsDir || process.env.SKILLS_DIR || './data/skills';
141
- try {
142
- skillManager.loadFromDir(skillsDir, 'system');
187
+ if (options.skills?.length) {
188
+ // DB 源:直接注入 Skills(以 name 为唯一标识,更扁平,不依赖文件路径)
189
+ for (const s of options.skills) {
190
+ skillManager.register(s);
191
+ }
192
+ console.log(`[AgentServer] 已从注入源加载 ${options.skills.length} 个 Skills`);
143
193
  }
144
- catch (e) {
145
- console.warn('[AgentServer] Failed to load skills from', skillsDir, e);
194
+ else {
195
+ const skillsDir = options.skillsDir || process.env.SKILLS_DIR || './data/skills';
196
+ try {
197
+ skillManager.loadFromDir(skillsDir, 'system');
198
+ }
199
+ catch (e) {
200
+ console.warn('[AgentServer] Failed to load skills from', skillsDir, e);
201
+ }
146
202
  }
147
203
  contextManager = new agent_core_1.ContextManager();
148
204
  const securityGuard = new agent_core_1.SecurityGuard();
@@ -227,8 +283,11 @@ function createAgentServer(options) {
227
283
  async function buildEngineForProfile(numericProfileId, profileOverride, skipCache, tenant, cacheKey, isSubAgent = false, allowedSpecialistIds, sessionId, canDelegateOverride) {
228
284
  const { toolRegistry: baseRegistry, skillManager: baseSkillManager, contextManager: ctxManager } = getAgentSystem();
229
285
  const profile = profileOverride || (numericProfileId ? await agentProfileProvider.getProfile(numericProfileId, tenant) : await agentProfileProvider.getDefaultProfile(tenant));
230
- if (!profile)
231
- return engine;
286
+ if (!profile) {
287
+ throw new Error(numericProfileId
288
+ ? `Agent profile not found: ${numericProfileId}`
289
+ : 'Default agent profile not found');
290
+ }
232
291
  // canDelegate: 非 remote Agent 均可委派子任务(BI 项目不依赖 isDefault 字段)
233
292
  const agentToolRegistry = new agent_core_1.ToolRegistry();
234
293
  baseRegistry.getDefinitions().forEach(def => {
@@ -316,12 +375,17 @@ function createAgentServer(options) {
316
375
  scope: sp.scope,
317
376
  keywords: sp.keywords,
318
377
  exclusiveScope: sp.exclusiveScope,
378
+ // 该子 Agent 自身的委派超时(来自 Fdelegate_timeout_ms),覆盖全局默认。
379
+ // 注:AgentProfileProvider.listProfiles 已在源头把未配置(0/null)归一化为
380
+ // undefined,此处 || undefined 为防御性兜底(防止其它未归一化的 provider 传入 0
381
+ // 被 `?? timeoutMs` 保留为 0 并当作 falsy → 完全不加超时)。
382
+ timeoutMs: sp.delegateTimeoutMs || undefined,
319
383
  run: async (task, _ctx, _runOptions) => {
320
384
  // 从 ToolContext 中提取父会话 ID(用于建立父子关系,
321
385
  // 使 RateLimitManager 能在父会话 abort 时终止子会话)
322
386
  const parentSessionId = _ctx?.sessionId;
323
387
  const parentCallId = (_ctx?.currentToolCallId ?? _ctx?.callId);
324
- const emit = _ctx?.emitSubEvent;
388
+ const emit = dedupeSubEventEmitter(_ctx?.emitSubEvent, () => ({ parentToolCallId: parentCallId ?? '', agentName: sp.name }));
325
389
  // 部分结果注入回调:子 Agent 流式输出时实时注入主 Agent 历史
326
390
  const injectPartialResult = _ctx?.injectPartialResult;
327
391
  const subSessionId = `sub_${sp.id}_${Date.now()}`;
@@ -673,38 +737,46 @@ function createAgentServer(options) {
673
737
  const waA2AClientPool = new Map();
674
738
  function waClientForKey(kind, profile) {
675
739
  const auth = profile.remoteAuth?.value ? { t: profile.remoteAuth.type, n: profile.remoteAuth.name } : null;
676
- return `${kind}|${JSON.stringify({ auth, h: buildExtraHeaders(profile.remoteAuth) })}`;
740
+ const t = a2aEffectiveTimeouts(profile);
741
+ return `${kind}|${JSON.stringify({ auth, h: buildExtraHeaders(profile.remoteAuth), t })}`;
742
+ }
743
+ /** 解析 Profile 上的 A2A 超时配置(带默认值),0/未设置回退到默认值 */
744
+ function a2aEffectiveTimeouts(profile) {
745
+ return {
746
+ timeout: profile.a2aTimeout || 300_000,
747
+ connectTimeout: profile.a2aConnectTimeout || 30_000,
748
+ chunkTimeout: profile.a2aChunkTimeout || 60_000,
749
+ };
750
+ }
751
+ /** 按 profile 构造 A2A/REST 客户端配置:profile 自身的 A2A 超时优先,缺省走 A2AClient 默认 */
752
+ function buildA2AClientOpts(profile, onDebugTrace) {
753
+ const t = a2aEffectiveTimeouts(profile);
754
+ const opts = {
755
+ timeout: t.timeout,
756
+ connectTimeout: t.connectTimeout,
757
+ chunkTimeout: t.chunkTimeout,
758
+ };
759
+ if (profile.remoteAuth?.value) {
760
+ opts.auth = { type: profile.remoteAuth.type, value: profile.remoteAuth.value, name: profile.remoteAuth.name };
761
+ opts.headers = buildExtraHeaders(profile.remoteAuth);
762
+ }
763
+ else if (profile.remoteAuth?.extraHeaders?.length) {
764
+ opts.headers = buildExtraHeaders(profile.remoteAuth);
765
+ }
766
+ if (onDebugTrace)
767
+ opts.onDebugTrace = onDebugTrace;
768
+ return opts;
677
769
  }
678
770
  function getWaRestClient(profile, onDebugTrace) {
679
771
  // debug trace handler 是 per-run 回调,不能池化,否则会复用旧 handler 导致调试 trace 串流
680
772
  if (onDebugTrace) {
681
- let opts;
682
- if (profile.remoteAuth?.value) {
683
- opts = { timeout: 300_000, auth: { type: profile.remoteAuth.type, value: profile.remoteAuth.value, name: profile.remoteAuth.name }, headers: buildExtraHeaders(profile.remoteAuth), onDebugTrace };
684
- }
685
- else if (profile.remoteAuth?.extraHeaders?.length) {
686
- opts = { timeout: 300_000, headers: buildExtraHeaders(profile.remoteAuth), onDebugTrace };
687
- }
688
- else {
689
- opts = { timeout: 300_000, onDebugTrace };
690
- }
691
- return new agent_core_1.RestA2AClient(opts);
773
+ return new agent_core_1.RestA2AClient(buildA2AClientOpts(profile, onDebugTrace));
692
774
  }
693
775
  const key = waClientForKey('rest', profile);
694
776
  const hit = waRestClientPool.get(key);
695
777
  if (hit && Date.now() - hit.ts < WA_CLIENT_POOL_TTL)
696
778
  return hit.client;
697
- let opts;
698
- if (profile.remoteAuth?.value) {
699
- opts = { timeout: 300_000, auth: { type: profile.remoteAuth.type, value: profile.remoteAuth.value, name: profile.remoteAuth.name }, headers: buildExtraHeaders(profile.remoteAuth) };
700
- }
701
- else if (profile.remoteAuth?.extraHeaders?.length) {
702
- opts = { timeout: 300_000, headers: buildExtraHeaders(profile.remoteAuth) };
703
- }
704
- else {
705
- opts = { timeout: 300_000 };
706
- }
707
- const client = new agent_core_1.RestA2AClient(opts);
779
+ const client = new agent_core_1.RestA2AClient(buildA2AClientOpts(profile));
708
780
  waRestClientPool.set(key, { client, ts: Date.now() });
709
781
  if (waRestClientPool.size > 100)
710
782
  for (const [k, v] of waRestClientPool)
@@ -715,33 +787,13 @@ function createAgentServer(options) {
715
787
  function getWaA2AClient(profile, onDebugTrace) {
716
788
  // debug trace handler 是 per-run 回调,不能池化,否则会复用旧 handler 导致调试 trace 串流
717
789
  if (onDebugTrace) {
718
- let opts;
719
- if (profile.remoteAuth?.value) {
720
- opts = { timeout: 300_000, auth: { type: profile.remoteAuth.type, value: profile.remoteAuth.value, name: profile.remoteAuth.name }, headers: buildExtraHeaders(profile.remoteAuth), onDebugTrace };
721
- }
722
- else if (profile.remoteAuth?.extraHeaders?.length) {
723
- opts = { timeout: 300_000, headers: buildExtraHeaders(profile.remoteAuth), onDebugTrace };
724
- }
725
- else {
726
- opts = { timeout: 300_000, onDebugTrace };
727
- }
728
- return new agent_core_1.A2AClient(opts);
790
+ return new agent_core_1.A2AClient(buildA2AClientOpts(profile, onDebugTrace));
729
791
  }
730
792
  const key = waClientForKey('a2a', profile);
731
793
  const hit = waA2AClientPool.get(key);
732
794
  if (hit && Date.now() - hit.ts < WA_CLIENT_POOL_TTL)
733
795
  return hit.client;
734
- let opts;
735
- if (profile.remoteAuth?.value) {
736
- opts = { timeout: 300_000, auth: { type: profile.remoteAuth.type, value: profile.remoteAuth.value, name: profile.remoteAuth.name }, headers: buildExtraHeaders(profile.remoteAuth) };
737
- }
738
- else if (profile.remoteAuth?.extraHeaders?.length) {
739
- opts = { timeout: 300_000, headers: buildExtraHeaders(profile.remoteAuth) };
740
- }
741
- else {
742
- opts = { timeout: 300_000 };
743
- }
744
- const client = new agent_core_1.A2AClient(opts);
796
+ const client = new agent_core_1.A2AClient(buildA2AClientOpts(profile));
745
797
  waA2AClientPool.set(key, { client, ts: Date.now() });
746
798
  if (waA2AClientPool.size > 100)
747
799
  for (const [k, v] of waA2AClientPool)
@@ -750,6 +802,10 @@ function createAgentServer(options) {
750
802
  return client;
751
803
  }
752
804
  function createRemoteRunnerFromProfile(profile, onDebugTrace) {
805
+ // 委派超时优先级:agent 自身配置(Fdelegate_timeout_ms)> 远程 agent 自身 A2A 超时(Fa2a_timeout)> 全局默认。
806
+ // 用 || 而非 ??:provider 会在源头把未配置(0/null)归一化为 undefined,但此处仍用 || 兜底,
807
+ // 把任何残留的 0 视为「未设置」逐级回退,最终 undefined 时由 delegate 工具回退到全局默认。
808
+ const runnerTimeoutMs = profile.delegateTimeoutMs || profile.a2aTimeout || undefined;
753
809
  // REST 风格 A2A
754
810
  if (profile.a2aProtocol === 'rest') {
755
811
  const restConfig = profile.restA2AConfig;
@@ -757,7 +813,7 @@ function createAgentServer(options) {
757
813
  throw new Error(`REST A2A profile '${profile.name}' missing capabilityId in restA2AConfig`);
758
814
  }
759
815
  const restClient = getWaRestClient(profile, onDebugTrace);
760
- return (0, agent_core_1.createRestA2ARemoteRunner)({
816
+ const runner = (0, agent_core_1.createRestA2ARemoteRunner)({
761
817
  baseUrl: profile.remoteUrl,
762
818
  capabilityId: restConfig.capabilityId,
763
819
  name: `[Remote] ${profile.name}`,
@@ -770,7 +826,11 @@ function createAgentServer(options) {
770
826
  fallbackToSync: restConfig.fallbackToSync,
771
827
  streamAction: restConfig.streamAction,
772
828
  invokeAction: restConfig.invokeAction,
829
+ // 透传输出模式:merge=完整内容回传主 Agent;panel=仅回传、完整内容保留在子面板
830
+ outputMode: profile.a2aOutputMode || 'merge',
773
831
  });
832
+ runner.timeoutMs = runnerTimeoutMs;
833
+ return runner;
774
834
  }
775
835
  // 标准 JSON-RPC A2A(默认)
776
836
  const agentUrl = profile.remoteCard?.url || profile.remoteUrl;
@@ -778,21 +838,27 @@ function createAgentServer(options) {
778
838
  throw new Error(`Remote profile '${profile.name}' missing remoteUrl`);
779
839
  }
780
840
  let agentClient;
841
+ const hasCustomA2ATimeout = !!(profile.a2aTimeout || profile.a2aConnectTimeout || profile.a2aChunkTimeout);
781
842
  // 调试模式(onDebugTrace 非空)下不能用共享 options.a2aClient--它未注入 handler,
782
843
  // 会导致 A2A 请求 trace 永不触发;改用 getWaA2AClient 创建带 handler 的客户端。
783
- if (options.a2aClient && !onDebugTrace) {
844
+ // profile 配置了自定义 A2A 超时时也必须走 getWaA2AClient(共享 client 写死 300_000,无法按 profile 覆盖)。
845
+ if (options.a2aClient && !onDebugTrace && !hasCustomA2ATimeout) {
784
846
  agentClient = options.a2aClient;
785
847
  }
786
848
  else {
787
849
  agentClient = getWaA2AClient(profile, onDebugTrace);
788
850
  }
789
- return (0, agent_core_1.createA2ARemoteRunner)({
851
+ const runner = (0, agent_core_1.createA2ARemoteRunner)({
790
852
  agentUrl,
791
853
  name: `[Remote] ${profile.name}`,
792
854
  description: profile.description || profile.domain || 'Remote specialist agent',
793
855
  streaming: true,
794
856
  client: agentClient,
857
+ // 透传输出模式:merge=完整内容回传主 Agent;panel=仅回传、完整内容保留在子面板
858
+ outputMode: profile.a2aOutputMode || 'merge',
795
859
  });
860
+ runner.timeoutMs = runnerTimeoutMs;
861
+ return runner;
796
862
  }
797
863
  /**
798
864
  * 运行远程 Agent(直接通过 A2A 协议与远程端点通信,不初始化本地引擎)
@@ -887,7 +953,10 @@ function createAgentServer(options) {
887
953
  };
888
954
  // 确保 abortSignal 始终有值(runner 依赖 ctx.abortSignal 进行中止控制)
889
955
  const effectiveAbortSignal = abortSignal ?? new AbortController().signal;
890
- const mockCtx = { currentToolCallId: parentCallId, abortSignal: effectiveAbortSignal, emitSubEvent };
956
+ // 包裹全局去重层:远程 A2A 服务重复下发的 tool_call / start / done 在源头被挡掉,
957
+ // 避免前端出现重复的工具卡片与进度噪声。
958
+ const dedupedEmitSubEvent = dedupeSubEventEmitter(emitSubEvent, () => ({ parentToolCallId: parentCallId, agentName: profile.name }));
959
+ const mockCtx = { currentToolCallId: parentCallId, abortSignal: effectiveAbortSignal, emitSubEvent: dedupedEmitSubEvent };
891
960
  console.log(`[AgentServer] Remote agent stream: profile=${profile.name}(${profile.id}), url=${profile.remoteUrl}, protocol=${profile.a2aProtocol || 'json-rpc'}`);
892
961
  try {
893
962
  const result = await runner.run(messageText, mockCtx, {});
@@ -1039,6 +1108,10 @@ function createAgentServer(options) {
1039
1108
  catch (err) {
1040
1109
  console.warn('[AgentServer] Memory recall failed:', err?.message);
1041
1110
  }
1111
+ // panel 模式追踪:记录哪些子 Agent 调用已进入摘要模式
1112
+ const subAgentSummaryActive = new Set();
1113
+ const subAgentContentMap = new Map();
1114
+ const subAgentMainRelayed = new Set();
1042
1115
  const emitSubEvent = (evt) => {
1043
1116
  // 子 Agent 的 interaction_request 事件需要特殊处理,转发为顶层 interaction-request
1044
1117
  if (evt.eventType === 'interaction_request') {
@@ -1058,7 +1131,52 @@ function createAgentServer(options) {
1058
1131
  });
1059
1132
  }
1060
1133
  else {
1134
+ // 始终转发到子 Agent 面板(sub-agent 事件供前端构建子面板卡片)
1061
1135
  registry.addEvent(threadIdNum, 'sub-agent', evt);
1136
+ // ── panel 模式逻辑 ──
1137
+ // 远程/子 Agent 在 start 事件声明 outputMode='panel' 时进入摘要模式:
1138
+ // 此后其原始 content 不再 relay 进主气泡(原文留在 sub-agent 面板),
1139
+ // 主气泡改由 kind=summary 的 content 或 summary(mode=final) 事件填充。
1140
+ if (evt.eventType === 'start' && evt.data?.outputMode === 'panel') {
1141
+ subAgentSummaryActive.add(evt.parentToolCallId);
1142
+ }
1143
+ // content 事件:panel 模式下抑制过程性内容,仅 kind=summary 进主气泡
1144
+ if (evt.eventType === 'content' && typeof evt.data?.content === 'string' && evt.data.content) {
1145
+ const current = evt.data.content;
1146
+ const isFirst = !subAgentContentMap.has(evt.parentToolCallId);
1147
+ const prev = subAgentContentMap.get(evt.parentToolCallId) ?? '';
1148
+ // 累积式内容(本地 specialist):current 以 prev 为前缀且更长 -> 取后缀
1149
+ // 增量式内容(远程 A2A):直接用作 delta
1150
+ const delta = (prev.length > 0 && current.length > prev.length && current.startsWith(prev))
1151
+ ? current.slice(prev.length)
1152
+ : current;
1153
+ subAgentContentMap.set(evt.parentToolCallId, prev + delta);
1154
+ if ((!subAgentSummaryActive.has(evt.parentToolCallId) || evt.data?.kind === 'summary') && delta) {
1155
+ const prefix = isFirst ? '\n\n' : '';
1156
+ registry.addEvent(threadIdNum, 'content', prefix + delta);
1157
+ subAgentMainRelayed.add(evt.parentToolCallId);
1158
+ }
1159
+ }
1160
+ // summary 事件:mode='final' 写入主气泡作为"分析结论"
1161
+ if (evt.eventType === 'summary' && typeof evt.data?.content === 'string' && evt.data.content) {
1162
+ subAgentSummaryActive.add(evt.parentToolCallId);
1163
+ const mode = evt.data.mode || 'replace';
1164
+ if (mode === 'final') {
1165
+ const isFirstMain = !subAgentMainRelayed.has(evt.parentToolCallId);
1166
+ const prefix = isFirstMain ? '\n\n' : '';
1167
+ registry.addEvent(threadIdNum, 'content', prefix + evt.data.content);
1168
+ subAgentMainRelayed.add(evt.parentToolCallId);
1169
+ }
1170
+ }
1171
+ // done/error:清理追踪 + 尾部分隔
1172
+ if (evt.eventType === 'done' || evt.eventType === 'error') {
1173
+ if (subAgentMainRelayed.has(evt.parentToolCallId)) {
1174
+ registry.addEvent(threadIdNum, 'content', '\n\n');
1175
+ subAgentMainRelayed.delete(evt.parentToolCallId);
1176
+ }
1177
+ subAgentContentMap.delete(evt.parentToolCallId);
1178
+ subAgentSummaryActive.delete(evt.parentToolCallId);
1179
+ }
1062
1180
  }
1063
1181
  };
1064
1182
  customContext.emitSubEvent = emitSubEvent;