@aramisfa/openclaw-a2a-outbound 1.0.0 → 3.0.0
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/README.md +445 -41
- package/dist/capability-diagnostics.d.ts +13 -0
- package/dist/capability-diagnostics.d.ts.map +1 -0
- package/dist/capability-diagnostics.js +131 -0
- package/dist/capability-diagnostics.js.map +1 -0
- package/dist/errors.d.ts +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +1 -0
- package/dist/errors.js.map +1 -1
- package/dist/request-normalization.d.ts +4 -10
- package/dist/request-normalization.d.ts.map +1 -1
- package/dist/request-normalization.js +83 -47
- package/dist/request-normalization.js.map +1 -1
- package/dist/result-shape.d.ts +66 -15
- package/dist/result-shape.d.ts.map +1 -1
- package/dist/result-shape.js +333 -85
- package/dist/result-shape.js.map +1 -1
- package/dist/schemas.d.ts +54 -5
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +275 -23
- package/dist/schemas.js.map +1 -1
- package/dist/sdk-client-pool.d.ts +0 -2
- package/dist/sdk-client-pool.d.ts.map +1 -1
- package/dist/sdk-client-pool.js +2 -22
- package/dist/sdk-client-pool.js.map +1 -1
- package/dist/service.d.ts +5 -2
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +411 -79
- package/dist/service.js.map +1 -1
- package/dist/target-catalog.d.ts +27 -5
- package/dist/target-catalog.d.ts.map +1 -1
- package/dist/target-catalog.js +164 -58
- package/dist/target-catalog.js.map +1 -1
- package/dist/task-handle-registry.d.ts +3 -2
- package/dist/task-handle-registry.d.ts.map +1 -1
- package/dist/task-handle-registry.js +37 -5
- package/dist/task-handle-registry.js.map +1 -1
- package/openclaw.plugin.json +42 -12
- package/package.json +10 -11
- package/skills/remote-agent/SKILL.md +85 -12
package/README.md
CHANGED
|
@@ -16,6 +16,14 @@ Pin the exact published version if you want reproducible installs:
|
|
|
16
16
|
openclaw plugins install @aramisfa/openclaw-a2a-outbound --pin
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
Optional guided setup helper:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
clawhub install a2a-delegation-setup
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The ClawHub skill is an optional guided setup helper for installing, enabling, configuring, verifying, updating, and troubleshooting `@aramisfa/openclaw-a2a-outbound`. The plugin itself still installs through `openclaw plugins install @aramisfa/openclaw-a2a-outbound`.
|
|
26
|
+
|
|
19
27
|
## Requirements
|
|
20
28
|
|
|
21
29
|
- Node.js `>=22.12.0`
|
|
@@ -23,7 +31,7 @@ openclaw plugins install @aramisfa/openclaw-a2a-outbound --pin
|
|
|
23
31
|
|
|
24
32
|
## OpenClaw Plugin Config
|
|
25
33
|
|
|
26
|
-
|
|
34
|
+
`openclaw plugins install` enables the plugin at the OpenClaw system level automatically — no separate `openclaw plugins enable` step is required. However, the `remote_agent` tool is gated by the plugin's own `"enabled"` flag inside its configuration object. Set `"enabled": true` under plugin id `openclaw-a2a-outbound` in your OpenClaw plugin config to activate the tool:
|
|
27
35
|
|
|
28
36
|
```json
|
|
29
37
|
{
|
|
@@ -57,7 +65,7 @@ The plugin installs through the OpenClaw CLI, but the tool stays disabled until
|
|
|
57
65
|
}
|
|
58
66
|
```
|
|
59
67
|
|
|
60
|
-
Call `list_targets` first to discover configured aliases and refreshed
|
|
68
|
+
Call `list_targets` first to discover configured aliases and refreshed peer-card metadata. Prefer `target_alias` over `target_url`; use `target_url` only when policy allows direct URL routing.
|
|
61
69
|
|
|
62
70
|
## Unified Tool Contract
|
|
63
71
|
|
|
@@ -66,27 +74,77 @@ Call `list_targets` first to discover configured aliases and refreshed target-ca
|
|
|
66
74
|
- `action`: required for every request.
|
|
67
75
|
- `target_alias`: preferred routing key for `send`, `watch`, `status`, and `cancel`.
|
|
68
76
|
- `target_url`: explicit remote base URL when policy allows it or when it matches a configured target.
|
|
69
|
-
- `
|
|
70
|
-
- `
|
|
71
|
-
- `task_handle`: opaque
|
|
72
|
-
- `task_id`: fallback follow-up key when no live `task_handle` is available.
|
|
73
|
-
- `
|
|
77
|
+
- `parts`: required non-empty array for `send`; each part is `text`, `file`, or `data`.
|
|
78
|
+
- `message_id`: optional client-supplied message id for `send`.
|
|
79
|
+
- `task_handle`: opaque delegated-task handle. `send`, `watch`, `status`, and `cancel` all accept it.
|
|
80
|
+
- `task_id`: for `send`, continue an existing remote task when no `task_handle` is available; for `watch`/`status`/`cancel`, fallback follow-up key when no live `task_handle` is available.
|
|
81
|
+
- `context_id`: optional remote conversation context id for `send` only. Use it either with `task_id` or by itself to start a new task inside an existing conversation. Flat `context_id` must not be used for `watch`, `status`, or `cancel`.
|
|
82
|
+
- `reference_task_ids`: optional related task ids for `send`. `task_id` continues an existing task; `reference_task_ids` references prior tasks without continuing them.
|
|
83
|
+
- `task_requirement`: optional `send` contract. Defaults to `"optional"`; set `task_requirement="required"` to require a real task.
|
|
84
|
+
- `follow_updates`: stream the initial `send`. `follow_updates=true` means “stream the initial send”; it does not guarantee task creation unless `task_requirement="required"`.
|
|
85
|
+
- `accepted_output_modes`: optional per-call output mode override for `send`.
|
|
86
|
+
- `blocking`: optional non-stream `send` knob. Rejected when `follow_updates=true`.
|
|
74
87
|
- `history_length`: optional history window for `send` and `status`.
|
|
88
|
+
- `push_notification_config`: optional push callback config for `send`.
|
|
75
89
|
- `timeout_ms`: per-request timeout override.
|
|
76
90
|
- `service_parameters`: optional outbound service parameters.
|
|
77
91
|
- `metadata`: optional metadata payload for `send`.
|
|
78
92
|
|
|
79
|
-
Action-specific validation rejects unsupported fields for each action, so keep requests flat and action-focused.
|
|
93
|
+
Snake_case tool fields are translated internally to the A2A SDK camelCase request payload. Action-specific validation rejects unsupported fields for each action, so keep requests flat and action-focused.
|
|
80
94
|
|
|
81
95
|
## Actions
|
|
82
96
|
|
|
83
|
-
- `list_targets`: discover configured targets, aliases, examples, and hydrated card metadata.
|
|
84
|
-
- `send`: send
|
|
97
|
+
- `list_targets`: discover configured targets, aliases, examples, and hydrated peer-card metadata.
|
|
98
|
+
- `send`: send one or more message parts to a remote agent, either as a new turn or as a follow-up turn on an existing task/conversation.
|
|
85
99
|
- `watch`: resubscribe to a running delegated task and stream updates.
|
|
86
100
|
- `status`: fetch the latest task snapshot.
|
|
87
101
|
- `cancel`: request cancellation for a delegated task.
|
|
88
102
|
|
|
89
|
-
|
|
103
|
+
Failed `send` and `sendStream` calls include `error.details.capability_diagnostics` so remote validation or content-type rejections can be compared against the stored peer card without blocking permissive runtime sends.
|
|
104
|
+
|
|
105
|
+
Supported `send` modes:
|
|
106
|
+
|
|
107
|
+
- new task: `send` with `target_alias`/`target_url` or a configured default target, and no continuation fields
|
|
108
|
+
- existing task continuation: `send` with a persisted `continuation`; flat `task_handle`, or `task_id` plus `target_alias`/`target_url`, remain manual compatibility inputs only
|
|
109
|
+
- related new task: `send` with `reference_task_ids`, optionally plus `context_id`, plus `target_alias`/`target_url` or a configured default target
|
|
110
|
+
- new task in an existing conversation: `send` with a persisted conversation-only `continuation`; flat `context_id` plus `target_alias`/`target_url` remains manual compatibility only
|
|
111
|
+
|
|
112
|
+
When a delegated task pauses in `input-required` or an approval workflow, resume it with `send` again. Persist `summary.continuation` verbatim and pass that subtree back directly. If the nested `task_handle` is expired or unavailable, the plugin automatically falls back within that nested contract to `continuation.target` + `continuation.task.task_id`; callers should not flatten persisted follow-up state back into `target_alias`/`task_id`.
|
|
113
|
+
|
|
114
|
+
## Reading Output Continuations
|
|
115
|
+
|
|
116
|
+
This package returns continuation metadata under `summary.continuation`.
|
|
117
|
+
|
|
118
|
+
- `summary.continuation.target`: the canonical persisted routing contract for machine follow-up. Persist `target_url`, `card_path`, and `preferred_transports` verbatim; `target_alias` is optional descriptive metadata.
|
|
119
|
+
- `summary.continuation.task`: the only machine-readable signal that a real remote task exists. Read `task_handle`, `task_id`, `status`, `can_resume_send`, `can_watch`, and the deprecated alias `can_send` from here, and use it for follow-up `send`, `watch`, `status`, and `cancel`.
|
|
120
|
+
- `summary.continuation.conversation`: send-only conversation continuity. Read `context_id` from here and use it only for follow-up `send`. Do not use it to infer task continuity.
|
|
121
|
+
- `response_kind`: descriptive wire-shape classification only. `response_kind="message"` means the peer returned a `Message`; `response_kind="task"` means a task-bearing response or event appeared. `response_kind` does not replace `summary.continuation`.
|
|
122
|
+
- Do not poll from conversation continuity.
|
|
123
|
+
- `watch`, `status`, and `cancel` require `summary.continuation.task`.
|
|
124
|
+
- Message-only follow-up uses `context_id`, not task actions.
|
|
125
|
+
- `task_handle` is returned only when the peer actually created a task.
|
|
126
|
+
- `summary.target_*` is descriptive only for humans and logs; it is no longer part of the machine follow-up recipe.
|
|
127
|
+
- Top-level compatibility aliases are descriptive only. Read task and conversation continuity from `summary.continuation`, and do not infer lifecycle continuity from flat `task_id`, flat `context_id`, or other top-level fields.
|
|
128
|
+
|
|
129
|
+
Branch on `summary.continuation.task` vs `summary.continuation.conversation` before choosing the next action:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
const continuation = result.summary.continuation
|
|
133
|
+
const task = continuation?.task
|
|
134
|
+
const conversation = continuation?.conversation
|
|
135
|
+
|
|
136
|
+
if (task) {
|
|
137
|
+
const followUp = { action: "status", continuation }
|
|
138
|
+
} else if (conversation) {
|
|
139
|
+
const followUp = {
|
|
140
|
+
action: "send",
|
|
141
|
+
continuation,
|
|
142
|
+
parts: [{ kind: "text", text: "Start a related task in the same conversation." }],
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Persist `summary.continuation` verbatim and branch on `summary.continuation.task` versus `summary.continuation.conversation`. `summary.continuation.task` is the only machine-readable authority for lifecycle follow-up. Conversation-only continuation remains send-only, and `watch`, `status`, and `cancel` require `summary.continuation.task`.
|
|
90
148
|
|
|
91
149
|
## Examples
|
|
92
150
|
|
|
@@ -111,24 +169,89 @@ For follow-up actions, prefer `task_handle` first. If the handle is expired or u
|
|
|
111
169
|
"examples": ["Summarize this incident and propose next steps."],
|
|
112
170
|
"target_name": "Support Agent",
|
|
113
171
|
"description": "Primary support lane",
|
|
114
|
-
"
|
|
172
|
+
"peer_card": {
|
|
173
|
+
"preferred_transport": "JSONRPC",
|
|
174
|
+
"additional_interfaces": [
|
|
175
|
+
{
|
|
176
|
+
"transport": "JSONRPC",
|
|
177
|
+
"url": "https://support.example/a2a/jsonrpc"
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"transport": "HTTP+JSON",
|
|
181
|
+
"url": "https://support.example/a2a/rest"
|
|
182
|
+
}
|
|
183
|
+
],
|
|
184
|
+
"capabilities": {
|
|
185
|
+
"streaming": true,
|
|
186
|
+
"push_notifications": true,
|
|
187
|
+
"state_transition_history": true
|
|
188
|
+
},
|
|
189
|
+
"default_input_modes": ["text/plain"],
|
|
190
|
+
"default_output_modes": ["text/plain"],
|
|
191
|
+
"skills": [
|
|
192
|
+
{
|
|
193
|
+
"id": "triage",
|
|
194
|
+
"name": "Incident Triage",
|
|
195
|
+
"description": "Summarize incidents and propose next actions.",
|
|
196
|
+
"tags": ["support"],
|
|
197
|
+
"examples": ["Summarize this incident and propose next steps."],
|
|
198
|
+
"input_modes": ["application/json"],
|
|
199
|
+
"output_modes": ["application/pdf"]
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
]
|
|
205
|
+
},
|
|
206
|
+
"raw": [
|
|
207
|
+
{
|
|
208
|
+
"target": {
|
|
209
|
+
"baseUrl": "https://support.example/",
|
|
210
|
+
"cardPath": "/.well-known/agent-card.json",
|
|
211
|
+
"preferredTransports": ["JSONRPC", "HTTP+JSON"],
|
|
212
|
+
"alias": "support",
|
|
213
|
+
"displayName": "Support Agent",
|
|
214
|
+
"description": "Primary support lane",
|
|
215
|
+
"streamingSupported": true
|
|
216
|
+
},
|
|
217
|
+
"configuredDescription": "Primary support lane",
|
|
218
|
+
"default": true,
|
|
219
|
+
"tags": ["support"],
|
|
220
|
+
"examples": ["Summarize this incident and propose next steps."],
|
|
221
|
+
"card": {
|
|
222
|
+
"displayName": "Support Agent",
|
|
223
|
+
"description": "Summarize incidents and propose next actions.",
|
|
224
|
+
"preferredTransport": "JSONRPC",
|
|
225
|
+
"additionalInterfaces": [
|
|
226
|
+
{
|
|
227
|
+
"transport": "JSONRPC",
|
|
228
|
+
"url": "https://support.example/a2a/jsonrpc"
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"transport": "HTTP+JSON",
|
|
232
|
+
"url": "https://support.example/a2a/rest"
|
|
233
|
+
}
|
|
234
|
+
],
|
|
235
|
+
"capabilities": {
|
|
236
|
+
"streaming": true,
|
|
237
|
+
"pushNotifications": true,
|
|
238
|
+
"stateTransitionHistory": true
|
|
239
|
+
},
|
|
240
|
+
"defaultInputModes": ["text/plain"],
|
|
241
|
+
"defaultOutputModes": ["text/plain"],
|
|
115
242
|
"skills": [
|
|
116
243
|
{
|
|
117
244
|
"id": "triage",
|
|
118
245
|
"name": "Incident Triage",
|
|
119
246
|
"description": "Summarize incidents and propose next actions.",
|
|
120
247
|
"tags": ["support"],
|
|
121
|
-
"examples": ["Summarize this incident and propose next steps."]
|
|
248
|
+
"examples": ["Summarize this incident and propose next steps."],
|
|
249
|
+
"inputModes": ["application/json"],
|
|
250
|
+
"outputModes": ["application/pdf"]
|
|
122
251
|
}
|
|
123
|
-
]
|
|
252
|
+
],
|
|
253
|
+
"lastRefreshedAt": "2026-03-12T10:00:00.000Z"
|
|
124
254
|
}
|
|
125
|
-
]
|
|
126
|
-
},
|
|
127
|
-
"raw": [
|
|
128
|
-
{
|
|
129
|
-
"default": true,
|
|
130
|
-
"tags": ["support"],
|
|
131
|
-
"examples": ["Summarize this incident and propose next steps."]
|
|
132
255
|
}
|
|
133
256
|
]
|
|
134
257
|
}
|
|
@@ -140,7 +263,12 @@ For follow-up actions, prefer `task_handle` first. If the handle is expired or u
|
|
|
140
263
|
{
|
|
141
264
|
"action": "send",
|
|
142
265
|
"target_alias": "support",
|
|
143
|
-
"
|
|
266
|
+
"parts": [
|
|
267
|
+
{
|
|
268
|
+
"kind": "text",
|
|
269
|
+
"text": "Summarize this bug report for triage."
|
|
270
|
+
}
|
|
271
|
+
],
|
|
144
272
|
"metadata": {
|
|
145
273
|
"ticket_id": "INC-42"
|
|
146
274
|
}
|
|
@@ -155,6 +283,7 @@ For follow-up actions, prefer `task_handle` first. If the handle is expired or u
|
|
|
155
283
|
"summary": {
|
|
156
284
|
"target_alias": "support",
|
|
157
285
|
"target_url": "https://support.example/",
|
|
286
|
+
"response_kind": "message",
|
|
158
287
|
"message_text": "Triage summary: reproduce, collect logs, and notify the on-call engineer."
|
|
159
288
|
},
|
|
160
289
|
"raw": {
|
|
@@ -163,6 +292,39 @@ For follow-up actions, prefer `task_handle` first. If the handle is expired or u
|
|
|
163
292
|
}
|
|
164
293
|
```
|
|
165
294
|
|
|
295
|
+
### Require A Durable Task
|
|
296
|
+
|
|
297
|
+
```json
|
|
298
|
+
{
|
|
299
|
+
"action": "send",
|
|
300
|
+
"target_alias": "support",
|
|
301
|
+
"task_requirement": "required",
|
|
302
|
+
"parts": [
|
|
303
|
+
{
|
|
304
|
+
"kind": "text",
|
|
305
|
+
"text": "Start a trackable task and return immediately."
|
|
306
|
+
}
|
|
307
|
+
]
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Start Related Work Without Continuing A Task
|
|
312
|
+
|
|
313
|
+
```json
|
|
314
|
+
{
|
|
315
|
+
"action": "send",
|
|
316
|
+
"target_alias": "support",
|
|
317
|
+
"context_id": "ctx-123",
|
|
318
|
+
"reference_task_ids": ["task-101", "task-102"],
|
|
319
|
+
"parts": [
|
|
320
|
+
{
|
|
321
|
+
"kind": "text",
|
|
322
|
+
"text": "Start a related task in the same conversation."
|
|
323
|
+
}
|
|
324
|
+
]
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
166
328
|
### Send Using The Configured Default Target
|
|
167
329
|
|
|
168
330
|
If one target is marked `"default": true`, `send` can omit `target_alias`:
|
|
@@ -170,7 +332,12 @@ If one target is marked `"default": true`, `send` can omit `target_alias`:
|
|
|
170
332
|
```json
|
|
171
333
|
{
|
|
172
334
|
"action": "send",
|
|
173
|
-
"
|
|
335
|
+
"parts": [
|
|
336
|
+
{
|
|
337
|
+
"kind": "text",
|
|
338
|
+
"text": "Draft a reply to the customer update."
|
|
339
|
+
}
|
|
340
|
+
],
|
|
174
341
|
"follow_updates": true
|
|
175
342
|
}
|
|
176
343
|
```
|
|
@@ -183,16 +350,36 @@ If one target is marked `"default": true`, `send` can omit `target_alias`:
|
|
|
183
350
|
"summary": {
|
|
184
351
|
"target_alias": "support",
|
|
185
352
|
"target_url": "https://support.example/",
|
|
186
|
-
"
|
|
187
|
-
"
|
|
188
|
-
|
|
189
|
-
|
|
353
|
+
"response_kind": "task",
|
|
354
|
+
"continuation": {
|
|
355
|
+
"target": {
|
|
356
|
+
"target_url": "https://support.example/",
|
|
357
|
+
"card_path": "/.well-known/agent-card.json",
|
|
358
|
+
"preferred_transports": ["JSONRPC", "HTTP+JSON"],
|
|
359
|
+
"target_alias": "support"
|
|
360
|
+
},
|
|
361
|
+
"task": {
|
|
362
|
+
"task_handle": "rah_0a3ff8c2-4a6d-48cb-a57d-4ae6f3c589d0",
|
|
363
|
+
"task_id": "task-456",
|
|
364
|
+
"status": "completed",
|
|
365
|
+
"can_resume_send": false,
|
|
366
|
+
"can_send": false,
|
|
367
|
+
"can_status": true,
|
|
368
|
+
"can_cancel": false,
|
|
369
|
+
"can_watch": false
|
|
370
|
+
},
|
|
371
|
+
"conversation": {
|
|
372
|
+
"context_id": "ctx-456",
|
|
373
|
+
"can_send": true
|
|
374
|
+
}
|
|
375
|
+
}
|
|
190
376
|
},
|
|
191
377
|
"raw": {
|
|
192
378
|
"events": [
|
|
193
379
|
{
|
|
194
380
|
"kind": "task",
|
|
195
381
|
"id": "task-456",
|
|
382
|
+
"contextId": "ctx-456",
|
|
196
383
|
"status": {
|
|
197
384
|
"state": "submitted"
|
|
198
385
|
}
|
|
@@ -200,6 +387,7 @@ If one target is marked `"default": true`, `send` can omit `target_alias`:
|
|
|
200
387
|
{
|
|
201
388
|
"kind": "status-update",
|
|
202
389
|
"taskId": "task-456",
|
|
390
|
+
"contextId": "ctx-456",
|
|
203
391
|
"status": {
|
|
204
392
|
"state": "completed"
|
|
205
393
|
},
|
|
@@ -209,6 +397,7 @@ If one target is marked `"default": true`, `send` can omit `target_alias`:
|
|
|
209
397
|
"finalEvent": {
|
|
210
398
|
"kind": "status-update",
|
|
211
399
|
"taskId": "task-456",
|
|
400
|
+
"contextId": "ctx-456",
|
|
212
401
|
"status": {
|
|
213
402
|
"state": "completed"
|
|
214
403
|
},
|
|
@@ -218,12 +407,181 @@ If one target is marked `"default": true`, `send` can omit `target_alias`:
|
|
|
218
407
|
}
|
|
219
408
|
```
|
|
220
409
|
|
|
410
|
+
```ts
|
|
411
|
+
const task = result.summary.continuation?.task
|
|
412
|
+
const conversation = result.summary.continuation?.conversation
|
|
413
|
+
|
|
414
|
+
if (!task) {
|
|
415
|
+
throw new Error("expected a trackable delegated task")
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (task.can_watch) {
|
|
419
|
+
// `watch` is valid here.
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const followUpContext = conversation?.context_id
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Continue An Existing Remote Task With `task_handle`
|
|
426
|
+
|
|
427
|
+
Prefer passing the persisted continuation subtree back directly when a delegated task reaches `input-required` or asks for approval:
|
|
428
|
+
|
|
429
|
+
```json
|
|
430
|
+
{
|
|
431
|
+
"action": "send",
|
|
432
|
+
"continuation": {
|
|
433
|
+
"target": {
|
|
434
|
+
"target_url": "https://support.example/",
|
|
435
|
+
"card_path": "/.well-known/agent-card.json",
|
|
436
|
+
"preferred_transports": ["JSONRPC", "HTTP+JSON"],
|
|
437
|
+
"target_alias": "support"
|
|
438
|
+
},
|
|
439
|
+
"task": {
|
|
440
|
+
"task_handle": "rah_0a3ff8c2-4a6d-48cb-a57d-4ae6f3c589d0",
|
|
441
|
+
"task_id": "task-456"
|
|
442
|
+
},
|
|
443
|
+
"conversation": {
|
|
444
|
+
"context_id": "ctx-456",
|
|
445
|
+
"can_send": true
|
|
446
|
+
}
|
|
447
|
+
},
|
|
448
|
+
"parts": [
|
|
449
|
+
{
|
|
450
|
+
"kind": "text",
|
|
451
|
+
"text": "Approved. Continue with the task and finish the reply."
|
|
452
|
+
}
|
|
453
|
+
]
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### Continue An Existing Remote Task With `task_id`
|
|
458
|
+
|
|
459
|
+
If `summary.continuation.task.task_handle` is unavailable, omit it and keep using the persisted nested target contract:
|
|
460
|
+
|
|
461
|
+
```json
|
|
462
|
+
{
|
|
463
|
+
"action": "send",
|
|
464
|
+
"continuation": {
|
|
465
|
+
"target": {
|
|
466
|
+
"target_url": "https://support.example/",
|
|
467
|
+
"card_path": "/.well-known/agent-card.json",
|
|
468
|
+
"preferred_transports": ["JSONRPC", "HTTP+JSON"],
|
|
469
|
+
"target_alias": "support"
|
|
470
|
+
},
|
|
471
|
+
"task": {
|
|
472
|
+
"task_id": "task-456"
|
|
473
|
+
},
|
|
474
|
+
"conversation": {
|
|
475
|
+
"context_id": "ctx-456",
|
|
476
|
+
"can_send": true
|
|
477
|
+
}
|
|
478
|
+
},
|
|
479
|
+
"parts": [
|
|
480
|
+
{
|
|
481
|
+
"kind": "text",
|
|
482
|
+
"text": "Continue the prior conversation and draft the final reply."
|
|
483
|
+
}
|
|
484
|
+
]
|
|
485
|
+
}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### Start A New Task In An Existing Conversation
|
|
489
|
+
|
|
490
|
+
Use `summary.continuation.conversation.context_id` when the prior result has conversation continuity without task continuity:
|
|
491
|
+
|
|
492
|
+
```json
|
|
493
|
+
{
|
|
494
|
+
"action": "send",
|
|
495
|
+
"continuation": {
|
|
496
|
+
"target": {
|
|
497
|
+
"target_url": "https://support.example/",
|
|
498
|
+
"card_path": "/.well-known/agent-card.json",
|
|
499
|
+
"preferred_transports": ["JSONRPC", "HTTP+JSON"],
|
|
500
|
+
"target_alias": "support"
|
|
501
|
+
},
|
|
502
|
+
"conversation": {
|
|
503
|
+
"context_id": "ctx-456",
|
|
504
|
+
"can_send": true
|
|
505
|
+
}
|
|
506
|
+
},
|
|
507
|
+
"parts": [
|
|
508
|
+
{
|
|
509
|
+
"kind": "text",
|
|
510
|
+
"text": "Start a new side task, but keep it in the same conversation."
|
|
511
|
+
}
|
|
512
|
+
]
|
|
513
|
+
}
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
```json
|
|
517
|
+
{
|
|
518
|
+
"ok": true,
|
|
519
|
+
"operation": "remote_agent",
|
|
520
|
+
"action": "send",
|
|
521
|
+
"summary": {
|
|
522
|
+
"target_alias": "support",
|
|
523
|
+
"target_url": "https://support.example/",
|
|
524
|
+
"response_kind": "message",
|
|
525
|
+
"message_text": "Conversation continued. Start the next task when ready.",
|
|
526
|
+
"continuation": {
|
|
527
|
+
"target": {
|
|
528
|
+
"target_url": "https://support.example/",
|
|
529
|
+
"card_path": "/.well-known/agent-card.json",
|
|
530
|
+
"preferred_transports": ["JSONRPC", "HTTP+JSON"],
|
|
531
|
+
"target_alias": "support"
|
|
532
|
+
},
|
|
533
|
+
"conversation": {
|
|
534
|
+
"context_id": "ctx-456",
|
|
535
|
+
"can_send": true
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
},
|
|
539
|
+
"raw": {
|
|
540
|
+
"kind": "message"
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
```ts
|
|
546
|
+
const task = result.summary.continuation?.task
|
|
547
|
+
const conversation = result.summary.continuation?.conversation
|
|
548
|
+
|
|
549
|
+
if (task) {
|
|
550
|
+
throw new Error("expected a context-only continuation")
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (conversation) {
|
|
554
|
+
const followUp = {
|
|
555
|
+
action: "send",
|
|
556
|
+
continuation: result.summary.continuation,
|
|
557
|
+
parts: [{ kind: "text", text: "Start the next task in this conversation." }],
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
Do not poll from conversation continuity. `watch`, `status`, and `cancel` require `summary.continuation.task`.
|
|
563
|
+
|
|
221
564
|
### Check Task Status With `task_handle`
|
|
222
565
|
|
|
223
566
|
```json
|
|
224
567
|
{
|
|
225
568
|
"action": "status",
|
|
226
|
-
"
|
|
569
|
+
"continuation": {
|
|
570
|
+
"target": {
|
|
571
|
+
"target_url": "https://support.example/",
|
|
572
|
+
"card_path": "/.well-known/agent-card.json",
|
|
573
|
+
"preferred_transports": ["JSONRPC", "HTTP+JSON"],
|
|
574
|
+
"target_alias": "support"
|
|
575
|
+
},
|
|
576
|
+
"task": {
|
|
577
|
+
"task_handle": "rah_0a3ff8c2-4a6d-48cb-a57d-4ae6f3c589d0",
|
|
578
|
+
"task_id": "task-456"
|
|
579
|
+
},
|
|
580
|
+
"conversation": {
|
|
581
|
+
"context_id": "ctx-456",
|
|
582
|
+
"can_send": true
|
|
583
|
+
}
|
|
584
|
+
},
|
|
227
585
|
"history_length": 2
|
|
228
586
|
}
|
|
229
587
|
```
|
|
@@ -236,10 +594,29 @@ If one target is marked `"default": true`, `send` can omit `target_alias`:
|
|
|
236
594
|
"summary": {
|
|
237
595
|
"target_alias": "support",
|
|
238
596
|
"target_url": "https://support.example/",
|
|
239
|
-
"
|
|
240
|
-
"
|
|
241
|
-
|
|
242
|
-
|
|
597
|
+
"response_kind": "task",
|
|
598
|
+
"continuation": {
|
|
599
|
+
"target": {
|
|
600
|
+
"target_url": "https://support.example/",
|
|
601
|
+
"card_path": "/.well-known/agent-card.json",
|
|
602
|
+
"preferred_transports": ["JSONRPC", "HTTP+JSON"],
|
|
603
|
+
"target_alias": "support"
|
|
604
|
+
},
|
|
605
|
+
"task": {
|
|
606
|
+
"task_handle": "rah_0a3ff8c2-4a6d-48cb-a57d-4ae6f3c589d0",
|
|
607
|
+
"task_id": "task-456",
|
|
608
|
+
"status": "completed",
|
|
609
|
+
"can_resume_send": false,
|
|
610
|
+
"can_send": false,
|
|
611
|
+
"can_status": true,
|
|
612
|
+
"can_cancel": false,
|
|
613
|
+
"can_watch": false
|
|
614
|
+
},
|
|
615
|
+
"conversation": {
|
|
616
|
+
"context_id": "ctx-456",
|
|
617
|
+
"can_send": true
|
|
618
|
+
}
|
|
619
|
+
}
|
|
243
620
|
},
|
|
244
621
|
"raw": {
|
|
245
622
|
"kind": "task",
|
|
@@ -251,24 +628,51 @@ If one target is marked `"default": true`, `send` can omit `target_alias`:
|
|
|
251
628
|
}
|
|
252
629
|
```
|
|
253
630
|
|
|
254
|
-
|
|
631
|
+
```ts
|
|
632
|
+
const task = result.summary.continuation?.task
|
|
633
|
+
const conversation = result.summary.continuation?.conversation
|
|
634
|
+
|
|
635
|
+
if (!task) {
|
|
636
|
+
throw new Error("status requires summary.continuation.task")
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const nextStatus = task.task_handle
|
|
640
|
+
? { action: "status", continuation: result.summary.continuation }
|
|
641
|
+
: { action: "status", continuation: result.summary.continuation }
|
|
642
|
+
|
|
643
|
+
const nextSend = conversation
|
|
644
|
+
? { action: "send", continuation: result.summary.continuation }
|
|
645
|
+
: undefined
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
When `summary.continuation.task.task_handle` is expired or unavailable, retry with the same persisted `continuation`; the plugin automatically falls back to `continuation.target` + `summary.continuation.task.task_id`:
|
|
255
649
|
|
|
256
650
|
```json
|
|
257
651
|
{
|
|
258
652
|
"action": "status",
|
|
259
|
-
"
|
|
260
|
-
|
|
653
|
+
"continuation": {
|
|
654
|
+
"target": {
|
|
655
|
+
"target_url": "https://support.example/",
|
|
656
|
+
"card_path": "/.well-known/agent-card.json",
|
|
657
|
+
"preferred_transports": ["JSONRPC", "HTTP+JSON"],
|
|
658
|
+
"target_alias": "support"
|
|
659
|
+
},
|
|
660
|
+
"task": {
|
|
661
|
+
"task_handle": "rah_0a3ff8c2-4a6d-48cb-a57d-4ae6f3c589d0",
|
|
662
|
+
"task_id": "task-456"
|
|
663
|
+
}
|
|
664
|
+
}
|
|
261
665
|
}
|
|
262
666
|
```
|
|
263
667
|
|
|
264
|
-
`watch` and `cancel` use the same follow-up targeting rules:
|
|
668
|
+
`watch` and `cancel` use the same follow-up targeting rules, and both require `summary.continuation.task` from a prior result. Conversation continuity by itself is never enough for lifecycle actions:
|
|
265
669
|
|
|
266
670
|
```json
|
|
267
|
-
{ "action": "watch", "task_handle": "rah_0a3ff8c2-4a6d-48cb-a57d-4ae6f3c589d0" }
|
|
671
|
+
{ "action": "watch", "continuation": { "target": { "target_url": "https://support.example/", "card_path": "/.well-known/agent-card.json", "preferred_transports": ["JSONRPC", "HTTP+JSON"], "target_alias": "support" }, "task": { "task_handle": "rah_0a3ff8c2-4a6d-48cb-a57d-4ae6f3c589d0", "task_id": "task-456" } } }
|
|
268
672
|
```
|
|
269
673
|
|
|
270
674
|
```json
|
|
271
|
-
{ "action": "cancel", "task_handle": "rah_0a3ff8c2-4a6d-48cb-a57d-4ae6f3c589d0" }
|
|
675
|
+
{ "action": "cancel", "continuation": { "target": { "target_url": "https://support.example/", "card_path": "/.well-known/agent-card.json", "preferred_transports": ["JSONRPC", "HTTP+JSON"], "target_alias": "support" }, "task": { "task_handle": "rah_0a3ff8c2-4a6d-48cb-a57d-4ae6f3c589d0", "task_id": "task-456" } } }
|
|
272
676
|
```
|
|
273
677
|
|
|
274
678
|
## Validation And Actionable Errors
|
|
@@ -290,7 +694,7 @@ Tool input validation uses Ajv in strict mode. Validation failures use `operatio
|
|
|
290
694
|
{
|
|
291
695
|
"keyword": "anyOf",
|
|
292
696
|
"instancePath": "",
|
|
293
|
-
"message": "send requires target_alias, target_url, or a configured default target"
|
|
697
|
+
"message": "send requires task_handle, target_alias, target_url, or a configured default target"
|
|
294
698
|
}
|
|
295
699
|
]
|
|
296
700
|
}
|
|
@@ -310,10 +714,10 @@ Expired handles return an actionable recovery envelope:
|
|
|
310
714
|
"message": "task handle \"rah_0a3ff8c2-4a6d-48cb-a57d-4ae6f3c589d0\" has expired",
|
|
311
715
|
"details": {
|
|
312
716
|
"taskHandle": "rah_0a3ff8c2-4a6d-48cb-a57d-4ae6f3c589d0",
|
|
313
|
-
"retryHint": "Retry with
|
|
717
|
+
"retryHint": "Retry with the same nested continuation so the plugin can fall back to the persisted target plus taskId, or resend the original request after a restart to obtain a new handle.",
|
|
314
718
|
"restartInvalidatesHandles": true,
|
|
315
719
|
"suggested_actions": ["status", "send"],
|
|
316
|
-
"hint": "Retry with target_alias + task_id
|
|
720
|
+
"hint": "Retry with the same nested continuation, or use flat target_alias + task_id only as manual compatibility."
|
|
317
721
|
}
|
|
318
722
|
}
|
|
319
723
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SendActionInput } from "./schemas.js";
|
|
2
|
+
import type { TargetCardSnapshot } from "./target-catalog.js";
|
|
3
|
+
export interface CapabilityDiagnostics {
|
|
4
|
+
requested_input_modes: string[];
|
|
5
|
+
advertised_input_modes: string[];
|
|
6
|
+
unsupported_input_modes: string[];
|
|
7
|
+
requested_output_modes: string[];
|
|
8
|
+
advertised_output_modes: string[];
|
|
9
|
+
unsupported_output_modes: string[];
|
|
10
|
+
unknown_file_attachments: number[];
|
|
11
|
+
}
|
|
12
|
+
export declare function evaluateSendCompatibility(input: SendActionInput, acceptedOutputModes: readonly string[], card: TargetCardSnapshot | undefined): CapabilityDiagnostics;
|
|
13
|
+
//# sourceMappingURL=capability-diagnostics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability-diagnostics.d.ts","sourceRoot":"","sources":["../src/capability-diagnostics.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAsB9D,MAAM,WAAW,qBAAqB;IACpC,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,sBAAsB,EAAE,MAAM,EAAE,CAAC;IACjC,uBAAuB,EAAE,MAAM,EAAE,CAAC;IAClC,sBAAsB,EAAE,MAAM,EAAE,CAAC;IACjC,uBAAuB,EAAE,MAAM,EAAE,CAAC;IAClC,wBAAwB,EAAE,MAAM,EAAE,CAAC;IACnC,wBAAwB,EAAE,MAAM,EAAE,CAAC;CACpC;AAgID,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,eAAe,EACtB,mBAAmB,EAAE,SAAS,MAAM,EAAE,EACtC,IAAI,EAAE,kBAAkB,GAAG,SAAS,GACnC,qBAAqB,CAoBvB"}
|