@tencent-ai/codebuddy-code 2.139.0-dev.c47174a.202608271521 → 2.139.0-dev.d4a09ae.202608281607

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.
@@ -1,4 +1,5 @@
1
- import type { AbTestService } from '@genie/product';
1
+ import { type AbTestService } from '@genie/product';
2
+ import type { Logger } from '@genie/telemetry';
2
3
  /**
3
4
  * Env var that force-enables outbound A2A independent of cloud product
4
5
  * config — set it truthy (`1`/`true`/`yes`/`on`, see `BooleanUtils.isTruthy`)
@@ -13,12 +14,33 @@ import type { AbTestService } from '@genie/product';
13
14
  * authority stays with cloud config (see CODEBUDDY.md §10's kill-switch
14
15
  * semantics) so an operator can still remotely disable A2A even on hosts
15
16
  * that happen to have this env var lingering in their process environment.
17
+ *
18
+ * Independent of, and AND'd with, `ENV_REMOTE_AGENTS_URL` below — this env
19
+ * var only ever forces the *feature switch* on, never bypasses the
20
+ * "is there actually a discovery URL configured" requirement.
16
21
  */
17
22
  export declare const ENV_A2A_OUTBOUND_ENABLED = "CODEBUDDY_A2A_OUTBOUND_ENABLED";
23
+ /**
24
+ * Discovery source for outbound A2A agents (issue #99451 Part B). Read LIVE
25
+ * from `process.env` at the point of use — see `A2aAgentDiscoveryService`'s
26
+ * doc comment for why this must never be cached earlier (prewarm
27
+ * per-session env only lands at `activate`).
28
+ *
29
+ * ANDed into `isA2AOutboundEnabled()`: even if the feature switch is on, the
30
+ * gate stays off with no discovery source configured — there would be
31
+ * nothing to discover, and the 4 A2A tools would otherwise expose
32
+ * themselves to the model only to fail on first use.
33
+ */
34
+ export declare const ENV_REMOTE_AGENTS_URL = "CODEBUDDY_REMOTE_AGENTS_URL";
18
35
  /**
19
36
  * Resolve whether outbound A2A is enabled for the current process.
20
37
  *
21
- * Check order (first hit wins):
38
+ * `CODEBUDDY_REMOTE_AGENTS_URL` is checked FIRST — cheap, synchronous, and
39
+ * lets us skip the `AbTestService` round trip entirely (and log a specific
40
+ * "off because no URL" reason, distinct from "off because the feature flag
41
+ * is off") when there's no discovery source configured at all.
42
+ *
43
+ * Feature-switch check order (first hit wins):
22
44
  * 1. `CODEBUDDY_A2A_OUTBOUND_ENABLED` truthy → enabled, no `AbTestService`
23
45
  * round trip needed.
24
46
  * 2. `ProductFeature.A2AOutbound` via `AbTestService` (existing behavior).
@@ -27,5 +49,10 @@ export declare const ENV_A2A_OUTBOUND_ENABLED = "CODEBUDDY_A2A_OUTBOUND_ENABLED"
27
49
  * `abTestService` is `undefined` in unassembled-container unit tests;
28
50
  * those paths fall back to disabled, matching the same conservative
29
51
  * default.
52
+ *
53
+ * `logger` is optional (debug-level only — this is called from every A2A
54
+ * tool's `isEnabled`/`execute`, so it must stay quiet by default) and used
55
+ * solely for the Logging section's "why is the gate off" diagnostic — it
56
+ * does not affect the returned value.
30
57
  */
31
- export declare function isA2AOutboundEnabled(abTestService?: AbTestService): Promise<boolean>;
58
+ export declare function isA2AOutboundEnabled(abTestService?: AbTestService, logger?: Pick<Logger, 'debug'>): Promise<boolean>;
@@ -2,23 +2,23 @@
2
2
  * Outbound A2A (Agent2Agent) protocol surface — CloudAgent Phase 1.
3
3
  *
4
4
  * Scope (see issue #99451): agent-cli acts ONLY as an outbound A2A client /
5
- * orchestrator. It discovers Agent Cards from a host-supplied discovery URL
6
- * (see `RemoteAgentsConfig` in control-signal-protocol.ts) and invokes them
7
- * via the official `@a2a-js/sdk`. There is no agent-cli A2A server, no
5
+ * orchestrator. It discovers Agent Cards from a discovery URL supplied via
6
+ * the `CODEBUDDY_REMOTE_AGENTS_URL` env var (see `a2a-gate.ts`) and invokes
7
+ * them via the official `@a2a-js/sdk`. There is no agent-cli A2A server, no
8
8
  * remote-agent callback, and no A2A streaming (`sendMessageStream`/
9
- * `subscribeToTask`) in Phase 1 — `sendMessage` always blocks until a
10
- * terminal or interrupted state, and `getTask`/`cancelTask` are simple
11
- * point-in-time queries, not the start of a polling loop owned by agent-cli
12
- * (the model re-invokes `A2AGetTask` itself when it wants to check again —
13
- * see `A2AGetTaskTool`).
9
+ * `subscribeToTask`) in Phase 1 — `sendMessage` always returns immediately
10
+ * with whatever the initial response is, and `A2AGetTaskTool` owns a
11
+ * bounded poll loop for non-terminal tasks (see that tool's doc comment).
14
12
  *
15
- * Discovery is LAZY, not eager at `initialize`: the host only hands us a
16
- * URL + optional auth headers at
17
- * `initialize` time (`A2aAgentDiscoveryService.setConfig`); the actual
18
- * network fetch happens on first use, gated by `A2aDiscoveryReadyService`
19
- * (single shared readiness gate, mirrors `McpReadyService` — see that
20
- * file's doc comment for why a shared gate beats each tool independently
21
- * deciding whether to fetch).
13
+ * Discovery is LAZY: the actual network fetch happens on first A2A tool
14
+ * use, gated by `A2aDiscoveryReadyService` (single shared readiness gate,
15
+ * mirrors `McpReadyService` — see that file's doc comment for why a shared
16
+ * gate beats each tool independently deciding whether to fetch). The
17
+ * discovery URL itself is read live from `process.env` at `discover()`-call
18
+ * time (issue #99451 Part B) — never cached earlier, per this repo's
19
+ * prewarm convention (see `CODEBUDDY.md` §1.1's "env 消费同理" section):
20
+ * a prewarmed standby process only gets the real per-session env merged in
21
+ * at `activate`, and discovery's first use always happens after that point.
22
22
  *
23
23
  * State is process-global (like `SdkInitializeStateService`, not
24
24
  * per-ACP-session like `AgentScopedMcpManager`): CloudAgent's real
@@ -30,35 +30,25 @@
30
30
  * All Symbols/interfaces live here per the repo's `xxx-protocol.ts` convention
31
31
  * (Symbol + interface; implementations live in sibling files).
32
32
  */
33
- import type { RemoteAgentsConfig } from '../control-signal/control-signal-protocol';
34
33
  export declare const A2aAgentRegistry: unique symbol;
35
34
  export declare const A2aClientService: unique symbol;
36
35
  export declare const A2aAgentDiscoveryService: unique symbol;
37
36
  export declare const A2aDiscoveryReadyService: unique symbol;
38
- /**
39
- * Auth headers applied to an agent's own `sendMessage`/`getTask`/`cancelTask`
40
- * calls. Internal registry shape — NOT part of the wire protocol anymore
41
- * (see `RemoteAgentsConfig` in control-signal-protocol.ts for the actual
42
- * host-supplied contract). `A2aAgentDiscoveryService` populates this from
43
- * the single discovery-level `auth` config shared across every discovered
44
- * agent (raw A2A `AgentCard`s carry no credentials of their own).
45
- */
46
- export interface A2AAgentAuthConfig {
47
- headers?: Record<string, string>;
48
- }
49
37
  /**
50
38
  * Internal input shape for `A2aAgentRegistry.register()` — NOT part of the
51
39
  * wire protocol (see module doc comment: the host only supplies a discovery
52
40
  * URL now, not this shape directly). `A2aAgentDiscoveryService` maps each
53
41
  * fetched raw `AgentCard` into one of these, deriving `agentId` from the
54
- * card's own `name` field.
42
+ * card's own `name` field. No per-agent auth: the discovery URL (and every
43
+ * discovered agent's own calls, via the same proxy path) is routed through
44
+ * our own infrastructure, which handles auth uniformly (issue #99451 Part
45
+ * B) — see `A2aAgentDiscoveryService`'s doc comment.
55
46
  */
56
47
  export interface A2AAgentDefinition {
57
48
  agentId: string;
58
49
  agentCard: Record<string, unknown>;
59
50
  /** Additional natural-language aliases for discovery, beyond the card's own name/description/skills. */
60
51
  aliases?: string[];
61
- auth?: A2AAgentAuthConfig;
62
52
  }
63
53
  /**
64
54
  * URI of the private codebuddy.ai extension that lets a caller pin a
@@ -82,9 +72,6 @@ export interface A2aRegisteredAgent {
82
72
  agentId: string;
83
73
  agentCard: A2aAgentCard;
84
74
  aliases: string[];
85
- auth?: {
86
- headers?: Record<string, string>;
87
- };
88
75
  }
89
76
  /**
90
77
  * Minimal shape of an A2A v1.0 AgentCard this registry actually reads.
@@ -163,18 +150,11 @@ export interface A2aAgentRegistry {
163
150
  */
164
151
  export interface A2aAgentDiscoveryService {
165
152
  /**
166
- * Store the discovery config (URL + optional auth headers). No I/O —
167
- * safe to call from `InitializeHandler` without blocking the handshake.
168
- * `undefined` clears the config and any previously discovered agents
169
- * (mirrors the old `a2aAgents: []` "host says no agents this time"
170
- * semantics, now expressed by omitting `remoteAgentsConfig` entirely).
171
- */
172
- setConfig(config: RemoteAgentsConfig | undefined): void;
173
- /**
174
- * Fetch the configured URL, parse the response as a JSON array of A2A
175
- * v1.0 `AgentCard` objects, map each into an `A2AAgentDefinition`
176
- * (`agentId` derived from `agentCard.name`, `auth` from the shared
177
- * discovery-level config), and register the result via
153
+ * Fetch `CODEBUDDY_REMOTE_AGENTS_URL` (read live from `process.env` at
154
+ * call time — see module doc comment on why this must never be cached
155
+ * earlier), parse the response as a JSON array of A2A v1.0 `AgentCard`
156
+ * objects, map each into an `A2AAgentDefinition` (`agentId` derived
157
+ * from `agentCard.name`), and register the result via
178
158
  * `A2aAgentRegistry.register()`. Always performs a fresh fetch when
179
159
  * called — caching/dedup against repeated calls is `A2aDiscoveryReadyService`'s
180
160
  * job, not this service's.
@@ -184,7 +164,7 @@ export interface A2aAgentDiscoveryService {
184
164
  * `A2AAgentSearchTool`'s existing empty-registry response, not a thrown
185
165
  * error.
186
166
  *
187
- * No-op (resolves immediately) when no config has been set.
167
+ * No-op (resolves immediately) when the env var is unset/empty.
188
168
  */
189
169
  discover(options?: {
190
170
  signal?: AbortSignal;
@@ -192,7 +172,7 @@ export interface A2aAgentDiscoveryService {
192
172
  }
193
173
  /**
194
174
  * Single shared readiness gate ensuring the discovery fetch runs exactly
195
- * once (per config generation) no matter which of the 4 A2A tools is the
175
+ * once per process lifetime no matter which of the 4 A2A tools is the
196
176
  * first to need it. Mirrors `McpReadyService`'s "trigger once, let every
197
177
  * caller await the same in-flight promise" shape — deliberately NOT
198
178
  * duplicated per tool (an earlier draft of this refactor called
@@ -204,19 +184,18 @@ export interface A2aAgentDiscoveryService {
204
184
  export interface A2aDiscoveryReadyService {
205
185
  /**
206
186
  * Triggers `A2aAgentDiscoveryService.discover()` if not already
207
- * started for the current config, and awaits it. Idempotent and safe
208
- * to call from every A2A tool's guard — concurrent callers await the
209
- * same in-flight fetch rather than each starting their own.
187
+ * started, and awaits it. Idempotent and safe to call from every A2A
188
+ * tool's guard — concurrent callers await the same in-flight fetch
189
+ * rather than each starting their own.
210
190
  */
211
191
  waitForDiscovery(): Promise<void>;
212
192
  /**
213
193
  * Drop the cached in-flight/completed fetch so the NEXT `waitForDiscovery()`
214
- * call triggers a fresh `discover()`. Call this whenever
215
- * `A2aAgentDiscoveryService.setConfig()` changes the config (a new
216
- * `initialize` control_request with a different `remoteAgentsConfig`,
217
- * or one that clears it) — otherwise a process that got its discovery
218
- * URL rotated mid-session would keep serving the stale, already-fetched
219
- * agent list forever.
194
+ * call triggers a fresh `discover()`. Test/process-teardown use — in
195
+ * production there is exactly one discovery source per process
196
+ * lifetime (issue #99451 Part B: `CODEBUDDY_REMOTE_AGENTS_URL` is a
197
+ * process-env value, not something that changes mid-session), so
198
+ * nothing in the normal request path calls this anymore.
220
199
  */
221
200
  reset(): void;
222
201
  }
@@ -245,7 +224,17 @@ export interface A2aSendMessageResult {
245
224
  /** Raw SDK response, retained for `ToolResult.rawResponse` (diagnostics only, never shown raw to the model). */
246
225
  raw: unknown;
247
226
  }
248
- export type A2aTaskStatus = 'submitted' | 'working' | 'completed' | 'failed' | 'canceled' | 'rejected' | 'input-required' | 'auth-required';
227
+ export type A2aTaskStatus = 'submitted' | 'working' | 'completed' | 'failed' | 'canceled' | 'rejected' | 'input-required' | 'auth-required'
228
+ /**
229
+ * The wire's `TASK_STATE_UNSPECIFIED` (a legal, spec-defined "unknown or
230
+ * indeterminate state" per the A2A `TaskState` enum) or an enum value this
231
+ * SDK version doesn't recognize yet (`TaskState.UNRECOGNIZED`) — NOT an
232
+ * agent-cli-side error. Non-terminal: `A2AGetTaskTool` keeps polling
233
+ * through it exactly like `submitted`/`working`, on the theory that a
234
+ * spec-compliant remote agent reporting this will eventually resolve to a
235
+ * real state.
236
+ */
237
+ | 'unknown';
249
238
  export interface A2aArtifactSummary {
250
239
  name?: string;
251
240
  mediaType?: string;
@@ -273,16 +262,20 @@ export interface A2aTaskSnapshot {
273
262
  /**
274
263
  * Thin wrapper around `@a2a-js/sdk`'s `ClientFactory` + `Client`. Owns the
275
264
  * per-`agentId` `Client` cache so repeated calls to the same agent within the
276
- * process lifetime reuse the negotiated transport. Phase 1 scope: `sendMessage`
277
- * blocks until a terminal/interrupted state; `getTask`/`cancelTask` are single
278
- * point-in-time calls (no polling loop, no `sendMessageStream`/`subscribeToTask`).
265
+ * process lifetime reuse the negotiated transport. `sendMessage`/`getTask`/
266
+ * `cancelTask` are each a single RPC call — whatever status the remote agent
267
+ * returns (terminal or not) is returned as-is, unmodified; no polling loop
268
+ * and no `sendMessageStream`/`subscribeToTask` live here (issue #99451 Phase
269
+ * 2: the poll loop for a non-terminal task lives exclusively in
270
+ * `A2AGetTaskTool`, one layer up — see that tool's doc comment).
279
271
  */
280
272
  export interface A2aClientService {
281
273
  /**
282
- * Send a message to `agentId` (optionally targeting `skillId`) and block
283
- * until a terminal or interrupted (`input-required`/`auth-required`)
284
- * state is reached. `contextId`/`taskId` are supplied by the caller to
285
- * continue a prior turn; omit both to start a new task/context.
274
+ * Send a message to `agentId` (optionally targeting `skillId`) and
275
+ * return whatever the single `message/send` RPC call gets back —
276
+ * terminal, `working`, `input-required`, anything — unmodified.
277
+ * `contextId`/`taskId` are supplied by the caller to continue a prior
278
+ * turn; omit both to start a new task/context.
286
279
  */
287
280
  sendMessage(agentId: string, params: {
288
281
  text: string;
@@ -120,35 +120,6 @@ export interface SdkCapabilities {
120
120
  form?: boolean;
121
121
  };
122
122
  }
123
- /**
124
- * Host-managed authentication reference for the outbound A2A discovery
125
- * endpoint. Headers are forwarded on the discovery fetch itself AND reused
126
- * for every discovered agent's own `sendMessage`/`getTask`/`cancelTask`
127
- * calls (raw A2A `AgentCard`s carry no credentials of their own — see
128
- * `RemoteAgentsConfig`'s doc comment) — never exposed to the model (not in
129
- * tool schemas, prompts, or logs).
130
- */
131
- export interface RemoteAgentsAuthConfig {
132
- headers?: Record<string, string>;
133
- }
134
- /**
135
- * Outbound A2A agent discovery source, supplied by a trusted host (e.g.
136
- * CloudAgent) at session start via the SDK `initialize` control_request.
137
- * Replaces the old inline `a2aAgents` array (issue #99451 Phase 1): instead
138
- * of the host pushing a fully-resolved agent list, it hands the CLI ONE
139
- * discovery URL that returns a plain array of standard A2A v1.0 `AgentCard`
140
- * objects (no host-specific wrapper shape) — the CLI fetches it itself,
141
- * lazily, on first A2A tool use (see `A2aAgentDiscoveryService`).
142
- *
143
- * `agentId` (the stable key used in `@Agent`/`@Agent.Skill` routing) is
144
- * derived from each card's own `name` field, since a standard `AgentCard`
145
- * has no separate host-assigned identifier.
146
- */
147
- export interface RemoteAgentsConfig {
148
- /** Discovery endpoint. Expected to return a JSON array of A2A v1.0 AgentCard objects. */
149
- url: string;
150
- auth?: RemoteAgentsAuthConfig;
151
- }
152
123
  /**
153
124
  * 初始化请求
154
125
  */
@@ -168,13 +139,6 @@ export interface ControlInitializeRequest {
168
139
  capabilities?: SdkCapabilities;
169
140
  /** 是否有 prompt 将在初始化后发送(用于 resume 时决定是否回放历史) */
170
141
  hasPrompt?: boolean;
171
- /**
172
- * 出站 A2A agent 发现源(CloudAgent 一期:仅同步出站调用)。由 AbTestService 的
173
- * `ProductFeature.A2AOutbound` 端到端门控;关闭时完全忽略此字段,不发起任何
174
- * 网络请求。真正的 fetch 发生在模型首次调用任一 A2A 工具时(懒加载),initialize
175
- * 握手本身只存配置,不阻塞在网络 I/O 上。
176
- */
177
- remoteAgentsConfig?: RemoteAgentsConfig;
178
142
  }
179
143
  /**
180
144
  * 初始化响应
@@ -1,7 +1,9 @@
1
1
  import { AbTestService } from '@genie/product';
2
+ import { Logger } from '@genie/telemetry';
2
3
  import { RunContext } from '@openai/agents';
3
4
  import { z } from 'zod';
4
5
  import { A2aAgentRegistry, A2aClientService, A2aDiscoveryReadyService } from '../a2a/a2a-protocol';
6
+ import { FunctionCallItem } from '../agent/openai-agents-types';
5
7
  import { Session } from '../session/session-protocol';
6
8
  import { BaseTool } from './base-tool';
7
9
  import { Tool, ToolCallArgumentRenderer, ToolNames, ToolResult } from './tool-protocol';
@@ -15,13 +17,46 @@ declare const A2AGetTaskParameters: z.ZodObject<{
15
17
  agent_id: string;
16
18
  task_id: string;
17
19
  }>;
20
+ export declare const ENV_POLL_INTERVAL_MS = "CODEBUDDY_A2A_POLL_INTERVAL_MS";
21
+ export declare const ENV_POLL_BACKOFF_MULTIPLIER = "CODEBUDDY_A2A_POLL_BACKOFF_MULTIPLIER";
22
+ export declare const ENV_POLL_MAX_INTERVAL_MS = "CODEBUDDY_A2A_POLL_MAX_INTERVAL_MS";
23
+ export declare const ENV_POLL_MAX_ATTEMPTS = "CODEBUDDY_A2A_POLL_MAX_ATTEMPTS";
24
+ export declare const ENV_POLL_MAX_DURATION_MS = "CODEBUDDY_A2A_POLL_MAX_DURATION_MS";
25
+ export interface A2APollPolicy {
26
+ /** Initial poll interval, ms. Default 2s. */
27
+ initialIntervalMs: number;
28
+ /** Exponential backoff multiplier applied after each poll. Default 1.5x. */
29
+ backoffMultiplier: number;
30
+ /** Interval cap the backoff schedule never exceeds. Default 30s. */
31
+ maxIntervalMs: number;
32
+ /** Hard cap on poll attempts — a secondary, independent circuit breaker from `maxDurationMs`. Default 60. */
33
+ maxAttempts: number;
34
+ /** Overall wall-clock budget for the whole loop, ms. Default 15 minutes. */
35
+ maxDurationMs: number;
36
+ }
37
+ /**
38
+ * Resolve the effective poll policy, applying any env overrides on top of
39
+ * `DEFAULT_POLL_POLICY`. Read fresh on every `execute()` call — not cached
40
+ * — so a test/host can change the env between calls.
41
+ */
42
+ export declare function resolvePollPolicy(): A2APollPolicy;
18
43
  /**
19
- * A2AGetTask — retrieve the current state of a previously created A2A task
20
- * (issue #99451, CloudAgent Phase 1). Single point-in-time query, not a
21
- * polling loop: the model calls this again itself if it wants a fresher
22
- * snapshot later. Reconciles a task whose status the caller didn't act on
23
- * immediately, or checks on a task the remote agent is still working on
24
- * after an earlier `input-required`/`auth-required` response.
44
+ * A2AGetTask — wait for a previously created outbound A2A task to finish
45
+ * (issue #99451 CloudAgent Phase 1; Phase 2 revises this from a bare
46
+ * single-shot query into the tool that owns the ENTIRE bounded, streamed
47
+ * poll loop — see the design doc's "A.7 Tool ownership split" section).
48
+ *
49
+ * Every non-terminal status — including `input-required`/`auth-required` —
50
+ * is relayed (A.2's live-progress mechanism, reusing the foreground Bash
51
+ * tool's `session.inputSubject.next({status: 'in_progress', ...})` pattern)
52
+ * and polled straight through with exponential backoff (A.3), never treated
53
+ * as a stop condition (A.5): the remote agent's own resolution path for
54
+ * those states (e.g. a WeCom approval card) is out of band from agent-cli
55
+ * entirely, so there is nothing for this tool to pause and ask the model
56
+ * about. Only the 4 true terminal states (`completed`/`failed`/`canceled`/
57
+ * `rejected`) or one of the two independent circuit breakers
58
+ * (`maxAttempts`/`maxDurationMs`, both env-overridable — see
59
+ * `resolvePollPolicy()`) stop the loop.
25
60
  *
26
61
  * Restricted to the main session, same recursive-delegation guard as
27
62
  * `A2ASendMessageTool` (see that tool's doc comment).
@@ -31,6 +66,7 @@ export declare class A2AGetTaskTool extends BaseTool implements Tool {
31
66
  protected readonly client?: A2aClientService;
32
67
  protected readonly abTestService?: AbTestService;
33
68
  protected readonly discoveryReadyService?: A2aDiscoveryReadyService;
69
+ protected readonly logger: Logger;
34
70
  name: ToolNames;
35
71
  description: string;
36
72
  needsApproval: boolean;
@@ -48,6 +84,46 @@ export declare class A2AGetTaskTool extends BaseTool implements Tool {
48
84
  isEnabled: (args: {
49
85
  runContext: RunContext<Session>;
50
86
  }) => Promise<boolean>;
51
- execute(input: z.infer<typeof A2AGetTaskParameters>, context: RunContext<Session>): Promise<ToolResult>;
87
+ execute(input: z.infer<typeof A2AGetTaskParameters>, context: RunContext<Session>, details?: {
88
+ toolCall: FunctionCallItem;
89
+ }): Promise<ToolResult>;
90
+ private pollOnce;
91
+ /**
92
+ * A.2's live-progress relay — the SAME `session.inputSubject.next({...})`
93
+ * shape the foreground Bash tool uses for its own streaming updates
94
+ * (`bash-tool.ts`'s `updateToolResult()`), reusing the tool call's own
95
+ * `callId` and a single `streamingResultId` held for the whole loop.
96
+ * `providerData.toolResult.rawResponse.a2a` carries the FULL snapshot
97
+ * (including `snapshot.raw`'s structured `parts`/`metadata`), not just
98
+ * the derived text — required for a frontend that knows how to render a
99
+ * structured interrupt payload (e.g. a WeCom `wecom_part_type: 'approval'`
100
+ * card, see A.2) to have the data it needs.
101
+ *
102
+ * **Also pushes the SAME item to `session.combineSubject`** — traced
103
+ * empirically (issue #99451 Phase 2 e2e work) that `inputSubject.next()`
104
+ * alone, exactly as Bash's own `updateToolResult()` does it, never
105
+ * reaches either transport's actual subscription: both
106
+ * `stream-json-view.ts` and `acp-agent.ts` subscribe ONLY to
107
+ * `session.combineSubject` (`{type: 'input', data: item}`), and nothing
108
+ * bridges `inputSubject` into `combineSubject` except
109
+ * `SessionManager.addHistory()`'s explicit dual-push for REAL history
110
+ * items — which a live, ephemeral, non-terminal relay frame like this
111
+ * one deliberately is not (it's never persisted to `session.history`).
112
+ * Confirmed live: Bash's OWN in-progress streaming updates do NOT
113
+ * currently reach stream-json `-p` output either (only its terminal
114
+ * result does) — that's a pre-existing gap in `bash-tool.ts`, out of
115
+ * scope to fix here, but exactly why this method can't just copy Bash's
116
+ * `inputSubject`-only call and call it done: A.2's actual requirement is
117
+ * multiple live relay frames on BOTH transports, so this pushes to both
118
+ * subjects explicitly rather than reproducing that gap.
119
+ */
120
+ private relayProgress;
121
+ /**
122
+ * Fallback result when a circuit breaker (not a terminal status) stops
123
+ * the loop. Per A.5: surfaces the last observed status/clarification —
124
+ * useful context even though it wasn't treated as a stop condition, the
125
+ * loop stopped because of the budget, not because of an interrupt.
126
+ */
127
+ private buildBudgetExceededResult;
52
128
  }
53
129
  export {};
@@ -1,4 +1,5 @@
1
1
  import { AbTestService } from '@genie/product';
2
+ import { Logger } from '@genie/telemetry';
2
3
  import { RunContext } from '@openai/agents';
3
4
  import { z } from 'zod';
4
5
  import { A2aAgentRegistry, A2aClientService, A2aDiscoveryReadyService } from '../a2a/a2a-protocol';
@@ -20,9 +21,14 @@ declare const A2ASendMessageParameters: z.ZodObject<{
20
21
  }>;
21
22
  export declare function continuationKey(agentId: string, skillId: string | undefined): string;
22
23
  /**
23
- * A2ASendMessage — invoke one outbound A2A agent synchronously (issue #99451,
24
- * CloudAgent Phase 1). Foreground call: blocks until the remote agent
25
- * reaches a terminal or interrupted state, then returns.
24
+ * A2ASendMessage — invoke one outbound A2A agent (issue #99451 CloudAgent
25
+ * Phase 1; ownership split revised in Phase 2, see the design doc's "A.7
26
+ * Tool ownership split" section). Single, non-looping RPC call: makes
27
+ * exactly one `message/send` request and returns whatever it gets back to
28
+ * the model UNMODIFIED — terminal, `working`, `input-required`, anything.
29
+ * No polling, no streaming, no waiting for a terminal state — that's
30
+ * `A2AGetTaskTool`'s job (see its doc comment), which this tool's own
31
+ * description tells the model to call when the result isn't final.
26
32
  *
27
33
  * Restricted to the main session: subagent/team sessions never see this tool
28
34
  * enabled, which is the enforcement point that prevents A2A-triggered
@@ -36,6 +42,7 @@ export declare class A2ASendMessageTool extends BaseTool implements Tool {
36
42
  protected readonly client?: A2aClientService;
37
43
  protected readonly abTestService?: AbTestService;
38
44
  protected readonly discoveryReadyService?: A2aDiscoveryReadyService;
45
+ protected readonly logger: Logger;
39
46
  name: ToolNames;
40
47
  description: string;
41
48
  needsApproval: boolean;
@@ -1,4 +1,5 @@
1
1
  import { AbTestService } from '@genie/product';
2
+ import type { Logger } from '@genie/telemetry';
2
3
  import { A2aAgentRegistry, A2aDiscoveryReadyService, A2aRegisteredAgent } from '../a2a/a2a-protocol';
3
4
  import { Session } from '../session/session-protocol';
4
5
  import { ToolErrorCode } from './tool-error-codes';
@@ -28,7 +29,7 @@ export declare function isSubSession(session: Session | undefined): boolean;
28
29
  * assembled (e.g. a headless build without the A2A feature), matching this
29
30
  * function's existing tolerance for missing components via `hasRequiredComponents`.
30
31
  */
31
- export declare function assertA2AToolAllowed(hasRequiredComponents: boolean, session: Session | undefined, abTestService: AbTestService | undefined, discoveryReadyService?: A2aDiscoveryReadyService): Promise<void>;
32
+ export declare function assertA2AToolAllowed(hasRequiredComponents: boolean, session: Session | undefined, abTestService: AbTestService | undefined, discoveryReadyService?: A2aDiscoveryReadyService, logger?: Pick<Logger, 'debug'>): Promise<void>;
32
33
  /** Look up `agentId` in the registry, throwing a consistent "not registered" error (with the list of available agents) if missing. */
33
34
  export declare function requireA2AAgent(registry: A2aAgentRegistry, agentId: string): A2aRegisteredAgent;
34
35
  /** Classify a thrown A2aClientService error into the right ToolErrorCode by matching known message patterns (see a2a-client-service.ts's doc comment on why we match text rather than `instanceof` typed SDK errors — this SDK version's JSON-RPC transport doesn't reconstruct semantic error subclasses). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tencent-ai/codebuddy-code",
3
- "version": "2.139.0-dev.c47174a.202608271521",
3
+ "version": "2.139.0-dev.d4a09ae.202608281607",
4
4
  "description": "Use CodeBuddy, Tencent's AI assistant, right from your terminal. CodeBuddy can understand your codebase, edit files, run terminal commands, and handle entire workflows for you.",
5
5
  "main": "lib/node/index.js",
6
6
  "typings": "lib/node/index.d.ts",
@@ -824,6 +824,6 @@
824
824
  "SelectImage": true,
825
825
  "SkipToolCallSupportCheck": true
826
826
  },
827
- "commit": "c47174a63599a2d8644e6898d361d06ff662631e",
828
- "date": "2026-08-27T07:20:10.926Z"
827
+ "commit": "d4a09ae479e13e4ffdc70d77b5f1bd584aeb6128",
828
+ "date": "2026-08-28T08:06:20.634Z"
829
829
  }
@@ -831,6 +831,6 @@
831
831
  }
832
832
  }
833
833
  },
834
- "commit": "c47174a63599a2d8644e6898d361d06ff662631e",
835
- "date": "2026-08-27T07:20:10.988Z"
834
+ "commit": "d4a09ae479e13e4ffdc70d77b5f1bd584aeb6128",
835
+ "date": "2026-08-28T08:06:20.692Z"
836
836
  }
package/product.ioa.json CHANGED
@@ -1327,6 +1327,6 @@
1327
1327
  }
1328
1328
  }
1329
1329
  },
1330
- "commit": "c47174a63599a2d8644e6898d361d06ff662631e",
1331
- "date": "2026-08-27T07:20:10.911Z"
1330
+ "commit": "d4a09ae479e13e4ffdc70d77b5f1bd584aeb6128",
1331
+ "date": "2026-08-28T08:06:20.622Z"
1332
1332
  }
package/product.json CHANGED
@@ -1239,6 +1239,22 @@
1239
1239
  "name": "tool-deferexecutetool-description",
1240
1240
  "template": "Execute a deferred tool by name. Use this to invoke tools discovered via ToolSearch without needing them in the active tools list.\n\nUsage:\n- First use ToolSearch to discover a tool and learn its parameter schema\n- Then call this tool with the exact tool name and parameters\n- Parameters are validated against the tool's schema before execution\n- The target tool's permission checks and hooks are applied normally\n\nExample:\nDeferExecuteTool({ toolName: \"ImageGen\", params: { prompt: \"a sunset over mountains\" } })\n\nNotes:\n- If parameter validation fails, a detailed error with the expected schema is returned\n- You can skip ToolSearch if you already know the tool name and parameters from a previous turn\n- This tool follows standard permission checks (may require approval depending on permissions configuration)\n"
1241
1241
  },
1242
+ {
1243
+ "name": "tool-a2aagentsearch-description",
1244
+ "template": "Search for outbound A2A (Agent2Agent) agents authorized for this conversation.\n\nUse this to discover which agents are available before calling A2ASendMessage:\n- Pass `query` with a natural-language description of the task (\"translate a document\", \"look up an order status\") to search by name/description/skill.\n- Pass `agent_id` to look up one agent directly when you already know its exact id (e.g. from an explicit @Agent mention).\n- Omit both to list every agent currently authorized.\n\nResults include each agent's `agent_id`, name, description, and declared skills (with their own `id`/name/description). A `supports_skill_routing` flag tells you whether `A2ASendMessage`'s `skill_id` parameter will actually be honored by that agent — if false, send the task as a plain message and let the agent dispatch it itself.\n\nThis tool never contacts the network; it only reads the agents registered for this session.\n"
1245
+ },
1246
+ {
1247
+ "name": "tool-a2asendmessage-description",
1248
+ "template": "Send a message to one outbound A2A (Agent2Agent) agent authorized for this conversation.\n\nParameters:\n- `agent_id` (required): exact id from A2AAgentSearch or an explicit @Agent mention.\n- `message` (required): a concise, self-contained task or question. Do not paste the whole conversation — state the goal and only the context the agent actually needs (the agent has no visibility into this conversation otherwise).\n- `skill_id` (optional): route directly to one declared skill, for @Agent.Skill-style precise dispatch. Only effective when A2AAgentSearch reported `supports_skill_routing: true` for that agent; otherwise this is ignored.\n\nThis call is NOT synchronous: it returns immediately with whatever single reply the agent gave, and does not wait for the task to finish. Check the returned status: only completed/failed/canceled/rejected are final. For ANY other status — working, submitted, input-required, or auth-required — you MUST immediately call A2AGetTask with the returned task_id, in the same turn, before doing anything else with this task. Do not wait, and do not ask the user for permission first.\n\nauth-required and input-required are NOT a signal to stop and hand off to the user by themselves — calling A2AGetTask to check status is just reading state, not performing a login or filling credentials on the user's behalf, so it does not conflict with any policy against automating logins. A2AGetTask polls automatically with backoff, relays the agent's own request (e.g. \"please sign in\", a clarifying question) to the user exactly once, and keeps waiting for the remote task to advance — it will surface the eventual outcome once the user (or the agent) resolves whatever it was waiting on.\n\nBehavior notes:\n- Calling the same `agent_id` (and `skill_id`, if used) again in a later turn continues the same conversation with that agent automatically — you don't need to restate prior context.\n- To message multiple agents in one turn, call this tool once per agent as parallel tool calls — one agent failing does not affect the others.\n- Any files or links the agent produced are returned as part of the result text.\n- Not available inside a sub-agent or team-member session — only the main conversation can call outbound A2A agents.\n"
1249
+ },
1250
+ {
1251
+ "name": "tool-a2agettask-description",
1252
+ "template": "Wait for a previously created outbound A2A task to finish, polling automatically.\n\nParameters:\n- `agent_id` (required): the same agentId passed to the original A2ASendMessage call.\n- `task_id` (required): the taskId from a prior A2ASendMessage or A2AGetTask result.\n\nThis call polls the remote task's real, current state with backoff, showing live progress as it goes, up to a bounded time/attempt limit. It returns once the task reaches a final state (completed/failed/canceled/rejected), or a \"still running\" result if the bound is hit first — if you get \"still running\", immediately call A2AGetTask again with the same task_id to keep waiting; do not give up after one bounded call.\n\nIf the task is waiting on external input or auth (input-required/auth-required), that request is relayed to the user once and polling continues with backoff — the next call/round of polling reflects the task's real state at that later point in time, not a repeat of what you already saw. Checking status this way is just reading state, not performing a login or filling credentials on the user's behalf, so calling this while the task is waiting on the user to complete a login/approval step out of band does not conflict with any policy against automating logins.\n\nNot available inside a sub-agent or team-member session — only the main conversation can query outbound A2A tasks.\n"
1253
+ },
1254
+ {
1255
+ "name": "tool-a2acanceltask-description",
1256
+ "template": "Request cancellation of a previously created outbound A2A task.\n\nParameters:\n- `agent_id` (required): the same agentId passed to the original A2ASendMessage call.\n- `task_id` (required): the taskId from a prior A2ASendMessage or A2AGetTask result.\n\nCancellation is a best-effort request, not a guarantee. If the task has already reached a terminal state (completed/failed/canceled/rejected), the call fails and the tool result will say so — that is expected, not an error to retry. Use A2AGetTask if you need to check the task's status before deciding whether to cancel it.\n\nNot available inside a sub-agent or team-member session — only the main conversation can cancel outbound A2A tasks.\n"
1257
+ },
1242
1258
  {
1243
1259
  "name": "tool-describetool-description",
1244
1260
  "template": "Get detailed information about a specific tool by name.\n\nUse this when you already know the tool name and need its full definition (description and input schema). This is more efficient than searching when the tool name is known.\n\nUsage:\n- Provide the exact tool name (case-sensitive)\n- Returns the tool's full description and parameter schema\n- The tool is automatically activated for use in subsequent messages\n\nIf the tool is not found, consider using ToolSearch to discover available tools by description.\n"
@@ -2197,25 +2213,21 @@
2197
2213
  },
2198
2214
  {
2199
2215
  "name": "A2AAgentSearch",
2200
- "description": "tool-a2aagentsearch-description",
2201
- "deferLoading": true
2216
+ "description": "tool-a2aagentsearch-description"
2202
2217
  },
2203
2218
  {
2204
2219
  "name": "A2ASendMessage",
2205
- "description": "tool-a2asendmessage-description",
2206
- "deferLoading": true
2220
+ "description": "tool-a2asendmessage-description"
2207
2221
  },
2208
2222
  {
2209
2223
  "name": "A2AGetTask",
2210
- "description": "tool-a2agettask-description",
2211
- "deferLoading": true
2224
+ "description": "tool-a2agettask-description"
2212
2225
  },
2213
2226
  {
2214
2227
  "name": "A2ACancelTask",
2215
- "description": "tool-a2acanceltask-description",
2216
- "deferLoading": true
2228
+ "description": "tool-a2acanceltask-description"
2217
2229
  }
2218
2230
  ],
2219
- "commit": "c47174a63599a2d8644e6898d361d06ff662631e",
2220
- "date": "2026-08-27T07:20:10.911Z"
2231
+ "commit": "d4a09ae479e13e4ffdc70d77b5f1bd584aeb6128",
2232
+ "date": "2026-08-28T08:06:20.642Z"
2221
2233
  }
@@ -361,6 +361,6 @@
361
361
  "ScheduledTasks": true,
362
362
  "SkipToolCallSupportCheck": true
363
363
  },
364
- "commit": "c47174a63599a2d8644e6898d361d06ff662631e",
365
- "date": "2026-08-27T07:20:10.978Z"
364
+ "commit": "d4a09ae479e13e4ffdc70d77b5f1bd584aeb6128",
365
+ "date": "2026-08-28T08:06:20.646Z"
366
366
  }