@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.
Files changed (40) hide show
  1. package/README.md +445 -41
  2. package/dist/capability-diagnostics.d.ts +13 -0
  3. package/dist/capability-diagnostics.d.ts.map +1 -0
  4. package/dist/capability-diagnostics.js +131 -0
  5. package/dist/capability-diagnostics.js.map +1 -0
  6. package/dist/errors.d.ts +1 -0
  7. package/dist/errors.d.ts.map +1 -1
  8. package/dist/errors.js +1 -0
  9. package/dist/errors.js.map +1 -1
  10. package/dist/request-normalization.d.ts +4 -10
  11. package/dist/request-normalization.d.ts.map +1 -1
  12. package/dist/request-normalization.js +83 -47
  13. package/dist/request-normalization.js.map +1 -1
  14. package/dist/result-shape.d.ts +66 -15
  15. package/dist/result-shape.d.ts.map +1 -1
  16. package/dist/result-shape.js +333 -85
  17. package/dist/result-shape.js.map +1 -1
  18. package/dist/schemas.d.ts +54 -5
  19. package/dist/schemas.d.ts.map +1 -1
  20. package/dist/schemas.js +275 -23
  21. package/dist/schemas.js.map +1 -1
  22. package/dist/sdk-client-pool.d.ts +0 -2
  23. package/dist/sdk-client-pool.d.ts.map +1 -1
  24. package/dist/sdk-client-pool.js +2 -22
  25. package/dist/sdk-client-pool.js.map +1 -1
  26. package/dist/service.d.ts +5 -2
  27. package/dist/service.d.ts.map +1 -1
  28. package/dist/service.js +411 -79
  29. package/dist/service.js.map +1 -1
  30. package/dist/target-catalog.d.ts +27 -5
  31. package/dist/target-catalog.d.ts.map +1 -1
  32. package/dist/target-catalog.js +164 -58
  33. package/dist/target-catalog.js.map +1 -1
  34. package/dist/task-handle-registry.d.ts +3 -2
  35. package/dist/task-handle-registry.d.ts.map +1 -1
  36. package/dist/task-handle-registry.js +37 -5
  37. package/dist/task-handle-registry.js.map +1 -1
  38. package/openclaw.plugin.json +42 -12
  39. package/package.json +10 -11
  40. 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
- The plugin installs through the OpenClaw CLI, but the tool stays disabled until you set `"enabled": true` for plugin id `openclaw-a2a-outbound` in your OpenClaw plugin config:
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 target-card metadata. Prefer `target_alias` over `target_url`; use `target_url` only when policy allows direct URL routing.
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
- - `input`: required for `send`; becomes the single user text part sent to the peer.
70
- - `attachments`: optional file or data attachments for `send`.
71
- - `task_handle`: opaque follow-up handle returned after delegated tasks are created.
72
- - `task_id`: fallback follow-up key when no live `task_handle` is available.
73
- - `follow_updates`: stream live updates during `send`.
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 user input to a remote agent selected by `target_alias`, `target_url`, or a configured default target.
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
- For follow-up actions, prefer `task_handle` first. If the handle is expired or unavailable, fall back to `target_alias` + `task_id`.
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
- "streaming_supported": true,
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
- "input": "Summarize this bug report for triage.",
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
- "input": "Draft a reply to the customer update.",
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
- "task_handle": "rah_0a3ff8c2-4a6d-48cb-a57d-4ae6f3c589d0",
187
- "task_id": "task-456",
188
- "status": "completed",
189
- "can_watch": true
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
- "task_handle": "rah_0a3ff8c2-4a6d-48cb-a57d-4ae6f3c589d0",
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
- "task_handle": "rah_0a3ff8c2-4a6d-48cb-a57d-4ae6f3c589d0",
240
- "task_id": "task-456",
241
- "status": "completed",
242
- "can_watch": true
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
- When a handle is expired or unavailable, retry with `target_alias` + `task_id`:
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
- "target_alias": "support",
260
- "task_id": "task-456"
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 explicit target plus taskId, or resend the original request after a restart to obtain a new handle.",
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, or send a new request."
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"}