@aramisfa/openclaw-a2a-outbound 1.0.0 → 2.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 +355 -33
- 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 +58 -15
- package/dist/result-shape.d.ts.map +1 -1
- package/dist/result-shape.js +324 -85
- package/dist/result-shape.js.map +1 -1
- package/dist/schemas.d.ts +28 -5
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +128 -21
- 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 +4 -2
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +333 -80
- 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 +7 -0
- 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 +74 -8
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`
|
|
@@ -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,74 @@ 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`. Use it either with `task_id` or by itself to start a new task inside an existing conversation.
|
|
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 `task_handle`, or `task_id` plus `target_alias`/`target_url` or a configured default target
|
|
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 `context_id` plus `target_alias`/`target_url` or a configured default target
|
|
111
|
+
|
|
112
|
+
When a delegated task pauses in `input-required` or an approval workflow, resume it with `send` again. Prefer `task_handle` first. If the handle is expired or unavailable, fall back to `target_alias` + `task_id`.
|
|
113
|
+
|
|
114
|
+
## Reading Output Continuations
|
|
115
|
+
|
|
116
|
+
This package returns continuation metadata under `summary.continuation`.
|
|
117
|
+
|
|
118
|
+
- `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`.
|
|
119
|
+
- `summary.continuation.conversation`: send-only conversation continuity. Read `context_id` from here and use it only for follow-up `send`.
|
|
120
|
+
- `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`.
|
|
121
|
+
- Do not poll from conversation continuity.
|
|
122
|
+
- Message-only follow-up uses `context_id`, not task actions.
|
|
123
|
+
- `task_handle` is returned only when the peer actually created a task.
|
|
124
|
+
- The old flat `summary.task_handle`, `summary.task_id`, `summary.context_id`, `summary.status`, and `summary.can_watch` fields are removed.
|
|
125
|
+
|
|
126
|
+
Branch on `summary.continuation.task` vs `summary.continuation.conversation` before choosing the next action:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
const task = result.summary.continuation?.task
|
|
130
|
+
const conversation = result.summary.continuation?.conversation
|
|
131
|
+
|
|
132
|
+
if (task) {
|
|
133
|
+
const followUp = task.task_handle
|
|
134
|
+
? { action: "status", task_handle: task.task_handle }
|
|
135
|
+
: { action: "status", target_alias: "support", task_id: task.task_id }
|
|
136
|
+
} else if (conversation) {
|
|
137
|
+
const followUp = {
|
|
138
|
+
action: "send",
|
|
139
|
+
target_alias: "support",
|
|
140
|
+
context_id: conversation.context_id,
|
|
141
|
+
parts: [{ kind: "text", text: "Start a related task in the same conversation." }],
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
90
145
|
|
|
91
146
|
## Examples
|
|
92
147
|
|
|
@@ -111,24 +166,89 @@ For follow-up actions, prefer `task_handle` first. If the handle is expired or u
|
|
|
111
166
|
"examples": ["Summarize this incident and propose next steps."],
|
|
112
167
|
"target_name": "Support Agent",
|
|
113
168
|
"description": "Primary support lane",
|
|
114
|
-
"
|
|
169
|
+
"peer_card": {
|
|
170
|
+
"preferred_transport": "JSONRPC",
|
|
171
|
+
"additional_interfaces": [
|
|
172
|
+
{
|
|
173
|
+
"transport": "JSONRPC",
|
|
174
|
+
"url": "https://support.example/a2a/jsonrpc"
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"transport": "HTTP+JSON",
|
|
178
|
+
"url": "https://support.example/a2a/rest"
|
|
179
|
+
}
|
|
180
|
+
],
|
|
181
|
+
"capabilities": {
|
|
182
|
+
"streaming": true,
|
|
183
|
+
"push_notifications": true,
|
|
184
|
+
"state_transition_history": true
|
|
185
|
+
},
|
|
186
|
+
"default_input_modes": ["text/plain"],
|
|
187
|
+
"default_output_modes": ["text/plain"],
|
|
188
|
+
"skills": [
|
|
189
|
+
{
|
|
190
|
+
"id": "triage",
|
|
191
|
+
"name": "Incident Triage",
|
|
192
|
+
"description": "Summarize incidents and propose next actions.",
|
|
193
|
+
"tags": ["support"],
|
|
194
|
+
"examples": ["Summarize this incident and propose next steps."],
|
|
195
|
+
"input_modes": ["application/json"],
|
|
196
|
+
"output_modes": ["application/pdf"]
|
|
197
|
+
}
|
|
198
|
+
]
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
},
|
|
203
|
+
"raw": [
|
|
204
|
+
{
|
|
205
|
+
"target": {
|
|
206
|
+
"baseUrl": "https://support.example/",
|
|
207
|
+
"cardPath": "/.well-known/agent-card.json",
|
|
208
|
+
"preferredTransports": ["JSONRPC", "HTTP+JSON"],
|
|
209
|
+
"alias": "support",
|
|
210
|
+
"displayName": "Support Agent",
|
|
211
|
+
"description": "Primary support lane",
|
|
212
|
+
"streamingSupported": true
|
|
213
|
+
},
|
|
214
|
+
"configuredDescription": "Primary support lane",
|
|
215
|
+
"default": true,
|
|
216
|
+
"tags": ["support"],
|
|
217
|
+
"examples": ["Summarize this incident and propose next steps."],
|
|
218
|
+
"card": {
|
|
219
|
+
"displayName": "Support Agent",
|
|
220
|
+
"description": "Summarize incidents and propose next actions.",
|
|
221
|
+
"preferredTransport": "JSONRPC",
|
|
222
|
+
"additionalInterfaces": [
|
|
223
|
+
{
|
|
224
|
+
"transport": "JSONRPC",
|
|
225
|
+
"url": "https://support.example/a2a/jsonrpc"
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
"transport": "HTTP+JSON",
|
|
229
|
+
"url": "https://support.example/a2a/rest"
|
|
230
|
+
}
|
|
231
|
+
],
|
|
232
|
+
"capabilities": {
|
|
233
|
+
"streaming": true,
|
|
234
|
+
"pushNotifications": true,
|
|
235
|
+
"stateTransitionHistory": true
|
|
236
|
+
},
|
|
237
|
+
"defaultInputModes": ["text/plain"],
|
|
238
|
+
"defaultOutputModes": ["text/plain"],
|
|
115
239
|
"skills": [
|
|
116
240
|
{
|
|
117
241
|
"id": "triage",
|
|
118
242
|
"name": "Incident Triage",
|
|
119
243
|
"description": "Summarize incidents and propose next actions.",
|
|
120
244
|
"tags": ["support"],
|
|
121
|
-
"examples": ["Summarize this incident and propose next steps."]
|
|
245
|
+
"examples": ["Summarize this incident and propose next steps."],
|
|
246
|
+
"inputModes": ["application/json"],
|
|
247
|
+
"outputModes": ["application/pdf"]
|
|
122
248
|
}
|
|
123
|
-
]
|
|
249
|
+
],
|
|
250
|
+
"lastRefreshedAt": "2026-03-12T10:00:00.000Z"
|
|
124
251
|
}
|
|
125
|
-
]
|
|
126
|
-
},
|
|
127
|
-
"raw": [
|
|
128
|
-
{
|
|
129
|
-
"default": true,
|
|
130
|
-
"tags": ["support"],
|
|
131
|
-
"examples": ["Summarize this incident and propose next steps."]
|
|
132
252
|
}
|
|
133
253
|
]
|
|
134
254
|
}
|
|
@@ -140,7 +260,12 @@ For follow-up actions, prefer `task_handle` first. If the handle is expired or u
|
|
|
140
260
|
{
|
|
141
261
|
"action": "send",
|
|
142
262
|
"target_alias": "support",
|
|
143
|
-
"
|
|
263
|
+
"parts": [
|
|
264
|
+
{
|
|
265
|
+
"kind": "text",
|
|
266
|
+
"text": "Summarize this bug report for triage."
|
|
267
|
+
}
|
|
268
|
+
],
|
|
144
269
|
"metadata": {
|
|
145
270
|
"ticket_id": "INC-42"
|
|
146
271
|
}
|
|
@@ -155,6 +280,7 @@ For follow-up actions, prefer `task_handle` first. If the handle is expired or u
|
|
|
155
280
|
"summary": {
|
|
156
281
|
"target_alias": "support",
|
|
157
282
|
"target_url": "https://support.example/",
|
|
283
|
+
"response_kind": "message",
|
|
158
284
|
"message_text": "Triage summary: reproduce, collect logs, and notify the on-call engineer."
|
|
159
285
|
},
|
|
160
286
|
"raw": {
|
|
@@ -163,6 +289,39 @@ For follow-up actions, prefer `task_handle` first. If the handle is expired or u
|
|
|
163
289
|
}
|
|
164
290
|
```
|
|
165
291
|
|
|
292
|
+
### Require A Durable Task
|
|
293
|
+
|
|
294
|
+
```json
|
|
295
|
+
{
|
|
296
|
+
"action": "send",
|
|
297
|
+
"target_alias": "support",
|
|
298
|
+
"task_requirement": "required",
|
|
299
|
+
"parts": [
|
|
300
|
+
{
|
|
301
|
+
"kind": "text",
|
|
302
|
+
"text": "Start a trackable task and return immediately."
|
|
303
|
+
}
|
|
304
|
+
]
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Start Related Work Without Continuing A Task
|
|
309
|
+
|
|
310
|
+
```json
|
|
311
|
+
{
|
|
312
|
+
"action": "send",
|
|
313
|
+
"target_alias": "support",
|
|
314
|
+
"context_id": "ctx-123",
|
|
315
|
+
"reference_task_ids": ["task-101", "task-102"],
|
|
316
|
+
"parts": [
|
|
317
|
+
{
|
|
318
|
+
"kind": "text",
|
|
319
|
+
"text": "Start a related task in the same conversation."
|
|
320
|
+
}
|
|
321
|
+
]
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
166
325
|
### Send Using The Configured Default Target
|
|
167
326
|
|
|
168
327
|
If one target is marked `"default": true`, `send` can omit `target_alias`:
|
|
@@ -170,7 +329,12 @@ If one target is marked `"default": true`, `send` can omit `target_alias`:
|
|
|
170
329
|
```json
|
|
171
330
|
{
|
|
172
331
|
"action": "send",
|
|
173
|
-
"
|
|
332
|
+
"parts": [
|
|
333
|
+
{
|
|
334
|
+
"kind": "text",
|
|
335
|
+
"text": "Draft a reply to the customer update."
|
|
336
|
+
}
|
|
337
|
+
],
|
|
174
338
|
"follow_updates": true
|
|
175
339
|
}
|
|
176
340
|
```
|
|
@@ -183,16 +347,30 @@ If one target is marked `"default": true`, `send` can omit `target_alias`:
|
|
|
183
347
|
"summary": {
|
|
184
348
|
"target_alias": "support",
|
|
185
349
|
"target_url": "https://support.example/",
|
|
186
|
-
"
|
|
187
|
-
"
|
|
188
|
-
|
|
189
|
-
|
|
350
|
+
"response_kind": "task",
|
|
351
|
+
"continuation": {
|
|
352
|
+
"task": {
|
|
353
|
+
"task_handle": "rah_0a3ff8c2-4a6d-48cb-a57d-4ae6f3c589d0",
|
|
354
|
+
"task_id": "task-456",
|
|
355
|
+
"status": "completed",
|
|
356
|
+
"can_resume_send": false,
|
|
357
|
+
"can_send": false,
|
|
358
|
+
"can_status": true,
|
|
359
|
+
"can_cancel": false,
|
|
360
|
+
"can_watch": false
|
|
361
|
+
},
|
|
362
|
+
"conversation": {
|
|
363
|
+
"context_id": "ctx-456",
|
|
364
|
+
"can_send": true
|
|
365
|
+
}
|
|
366
|
+
}
|
|
190
367
|
},
|
|
191
368
|
"raw": {
|
|
192
369
|
"events": [
|
|
193
370
|
{
|
|
194
371
|
"kind": "task",
|
|
195
372
|
"id": "task-456",
|
|
373
|
+
"contextId": "ctx-456",
|
|
196
374
|
"status": {
|
|
197
375
|
"state": "submitted"
|
|
198
376
|
}
|
|
@@ -200,6 +378,7 @@ If one target is marked `"default": true`, `send` can omit `target_alias`:
|
|
|
200
378
|
{
|
|
201
379
|
"kind": "status-update",
|
|
202
380
|
"taskId": "task-456",
|
|
381
|
+
"contextId": "ctx-456",
|
|
203
382
|
"status": {
|
|
204
383
|
"state": "completed"
|
|
205
384
|
},
|
|
@@ -209,6 +388,7 @@ If one target is marked `"default": true`, `send` can omit `target_alias`:
|
|
|
209
388
|
"finalEvent": {
|
|
210
389
|
"kind": "status-update",
|
|
211
390
|
"taskId": "task-456",
|
|
391
|
+
"contextId": "ctx-456",
|
|
212
392
|
"status": {
|
|
213
393
|
"state": "completed"
|
|
214
394
|
},
|
|
@@ -218,6 +398,118 @@ If one target is marked `"default": true`, `send` can omit `target_alias`:
|
|
|
218
398
|
}
|
|
219
399
|
```
|
|
220
400
|
|
|
401
|
+
```ts
|
|
402
|
+
const task = result.summary.continuation?.task
|
|
403
|
+
const conversation = result.summary.continuation?.conversation
|
|
404
|
+
|
|
405
|
+
if (!task) {
|
|
406
|
+
throw new Error("expected a trackable delegated task")
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (task.can_watch) {
|
|
410
|
+
// `watch` is valid here.
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const followUpContext = conversation?.context_id
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Continue An Existing Remote Task With `task_handle`
|
|
417
|
+
|
|
418
|
+
Use `summary.continuation.task.task_handle` from the prior result when a delegated task reaches `input-required` or asks for approval:
|
|
419
|
+
|
|
420
|
+
```json
|
|
421
|
+
{
|
|
422
|
+
"action": "send",
|
|
423
|
+
"task_handle": "rah_0a3ff8c2-4a6d-48cb-a57d-4ae6f3c589d0",
|
|
424
|
+
"parts": [
|
|
425
|
+
{
|
|
426
|
+
"kind": "text",
|
|
427
|
+
"text": "Approved. Continue with the task and finish the reply."
|
|
428
|
+
}
|
|
429
|
+
]
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Continue An Existing Remote Task With `task_id`
|
|
434
|
+
|
|
435
|
+
If `summary.continuation.task.task_handle` is unavailable, use `summary.continuation.task.task_id` plus target routing:
|
|
436
|
+
|
|
437
|
+
```json
|
|
438
|
+
{
|
|
439
|
+
"action": "send",
|
|
440
|
+
"target_alias": "support",
|
|
441
|
+
"task_id": "task-456",
|
|
442
|
+
"context_id": "ctx-456",
|
|
443
|
+
"parts": [
|
|
444
|
+
{
|
|
445
|
+
"kind": "text",
|
|
446
|
+
"text": "Continue the prior conversation and draft the final reply."
|
|
447
|
+
}
|
|
448
|
+
]
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Start A New Task In An Existing Conversation
|
|
453
|
+
|
|
454
|
+
Use `summary.continuation.conversation.context_id` when the prior result has conversation continuity without task continuity:
|
|
455
|
+
|
|
456
|
+
```json
|
|
457
|
+
{
|
|
458
|
+
"action": "send",
|
|
459
|
+
"target_alias": "support",
|
|
460
|
+
"context_id": "ctx-456",
|
|
461
|
+
"parts": [
|
|
462
|
+
{
|
|
463
|
+
"kind": "text",
|
|
464
|
+
"text": "Start a new side task, but keep it in the same conversation."
|
|
465
|
+
}
|
|
466
|
+
]
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
```json
|
|
471
|
+
{
|
|
472
|
+
"ok": true,
|
|
473
|
+
"operation": "remote_agent",
|
|
474
|
+
"action": "send",
|
|
475
|
+
"summary": {
|
|
476
|
+
"target_alias": "support",
|
|
477
|
+
"target_url": "https://support.example/",
|
|
478
|
+
"response_kind": "message",
|
|
479
|
+
"message_text": "Conversation continued. Start the next task when ready.",
|
|
480
|
+
"continuation": {
|
|
481
|
+
"conversation": {
|
|
482
|
+
"context_id": "ctx-456",
|
|
483
|
+
"can_send": true
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
},
|
|
487
|
+
"raw": {
|
|
488
|
+
"kind": "message"
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
```ts
|
|
494
|
+
const task = result.summary.continuation?.task
|
|
495
|
+
const conversation = result.summary.continuation?.conversation
|
|
496
|
+
|
|
497
|
+
if (task) {
|
|
498
|
+
throw new Error("expected a context-only continuation")
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (conversation) {
|
|
502
|
+
const followUp = {
|
|
503
|
+
action: "send",
|
|
504
|
+
target_alias: "support",
|
|
505
|
+
context_id: conversation.context_id,
|
|
506
|
+
parts: [{ kind: "text", text: "Start the next task in this conversation." }],
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
Do not poll from conversation continuity.
|
|
512
|
+
|
|
221
513
|
### Check Task Status With `task_handle`
|
|
222
514
|
|
|
223
515
|
```json
|
|
@@ -236,10 +528,23 @@ If one target is marked `"default": true`, `send` can omit `target_alias`:
|
|
|
236
528
|
"summary": {
|
|
237
529
|
"target_alias": "support",
|
|
238
530
|
"target_url": "https://support.example/",
|
|
239
|
-
"
|
|
240
|
-
"
|
|
241
|
-
|
|
242
|
-
|
|
531
|
+
"response_kind": "task",
|
|
532
|
+
"continuation": {
|
|
533
|
+
"task": {
|
|
534
|
+
"task_handle": "rah_0a3ff8c2-4a6d-48cb-a57d-4ae6f3c589d0",
|
|
535
|
+
"task_id": "task-456",
|
|
536
|
+
"status": "completed",
|
|
537
|
+
"can_resume_send": false,
|
|
538
|
+
"can_send": false,
|
|
539
|
+
"can_status": true,
|
|
540
|
+
"can_cancel": false,
|
|
541
|
+
"can_watch": false
|
|
542
|
+
},
|
|
543
|
+
"conversation": {
|
|
544
|
+
"context_id": "ctx-456",
|
|
545
|
+
"can_send": true
|
|
546
|
+
}
|
|
547
|
+
}
|
|
243
548
|
},
|
|
244
549
|
"raw": {
|
|
245
550
|
"kind": "task",
|
|
@@ -251,7 +556,24 @@ If one target is marked `"default": true`, `send` can omit `target_alias`:
|
|
|
251
556
|
}
|
|
252
557
|
```
|
|
253
558
|
|
|
254
|
-
|
|
559
|
+
```ts
|
|
560
|
+
const task = result.summary.continuation?.task
|
|
561
|
+
const conversation = result.summary.continuation?.conversation
|
|
562
|
+
|
|
563
|
+
if (!task) {
|
|
564
|
+
throw new Error("status requires summary.continuation.task")
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const nextStatus = task.task_handle
|
|
568
|
+
? { action: "status", task_handle: task.task_handle }
|
|
569
|
+
: { action: "status", target_alias: "support", task_id: task.task_id }
|
|
570
|
+
|
|
571
|
+
const nextSend = conversation
|
|
572
|
+
? { action: "send", target_alias: "support", context_id: conversation.context_id }
|
|
573
|
+
: undefined
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
When `summary.continuation.task.task_handle` is expired or unavailable, retry with `target_alias` + `summary.continuation.task.task_id`:
|
|
255
577
|
|
|
256
578
|
```json
|
|
257
579
|
{
|
|
@@ -261,7 +583,7 @@ When a handle is expired or unavailable, retry with `target_alias` + `task_id`:
|
|
|
261
583
|
}
|
|
262
584
|
```
|
|
263
585
|
|
|
264
|
-
`watch` and `cancel` use the same follow-up targeting rules:
|
|
586
|
+
`watch` and `cancel` use the same follow-up targeting rules, and both require `summary.continuation.task` from a prior result:
|
|
265
587
|
|
|
266
588
|
```json
|
|
267
589
|
{ "action": "watch", "task_handle": "rah_0a3ff8c2-4a6d-48cb-a57d-4ae6f3c589d0" }
|
|
@@ -290,7 +612,7 @@ Tool input validation uses Ajv in strict mode. Validation failures use `operatio
|
|
|
290
612
|
{
|
|
291
613
|
"keyword": "anyOf",
|
|
292
614
|
"instancePath": "",
|
|
293
|
-
"message": "send requires target_alias, target_url, or a configured default target"
|
|
615
|
+
"message": "send requires task_handle, target_alias, target_url, or a configured default target"
|
|
294
616
|
}
|
|
295
617
|
]
|
|
296
618
|
}
|
|
@@ -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"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { basename, extname } from "node:path";
|
|
2
|
+
const UNKNOWN_INPUT_MODE = "unknown";
|
|
3
|
+
const FILE_MIME_BY_EXTENSION = {
|
|
4
|
+
".csv": "text/csv",
|
|
5
|
+
".gif": "image/gif",
|
|
6
|
+
".jpeg": "image/jpeg",
|
|
7
|
+
".jpg": "image/jpeg",
|
|
8
|
+
".json": "application/json",
|
|
9
|
+
".md": "text/markdown",
|
|
10
|
+
".mp3": "audio/mpeg",
|
|
11
|
+
".mp4": "video/mp4",
|
|
12
|
+
".ogg": "audio/ogg",
|
|
13
|
+
".pdf": "application/pdf",
|
|
14
|
+
".png": "image/png",
|
|
15
|
+
".svg": "image/svg+xml",
|
|
16
|
+
".txt": "text/plain",
|
|
17
|
+
".wav": "audio/wav",
|
|
18
|
+
".webm": "video/webm",
|
|
19
|
+
".webp": "image/webp",
|
|
20
|
+
};
|
|
21
|
+
function isNonEmptyString(value) {
|
|
22
|
+
return typeof value === "string" && value.trim() !== "";
|
|
23
|
+
}
|
|
24
|
+
function normalizeMode(mode) {
|
|
25
|
+
return mode.trim().toLowerCase();
|
|
26
|
+
}
|
|
27
|
+
function normalizeModeList(modes) {
|
|
28
|
+
if (!modes) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
const normalized = new Set();
|
|
32
|
+
for (const mode of modes) {
|
|
33
|
+
if (!isNonEmptyString(mode)) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
normalized.add(normalizeMode(mode));
|
|
37
|
+
}
|
|
38
|
+
return [...normalized];
|
|
39
|
+
}
|
|
40
|
+
function readUriBasename(uri) {
|
|
41
|
+
try {
|
|
42
|
+
const parsed = new URL(uri);
|
|
43
|
+
const name = basename(parsed.pathname);
|
|
44
|
+
return name.length > 0 ? name : undefined;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function inferMimeTypeFromName(name) {
|
|
51
|
+
if (!isNonEmptyString(name)) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
const extension = extname(name.trim()).toLowerCase();
|
|
55
|
+
return extension.length > 0 ? FILE_MIME_BY_EXTENSION[extension] : undefined;
|
|
56
|
+
}
|
|
57
|
+
function inferMimeTypeFromUri(uri) {
|
|
58
|
+
if (!isNonEmptyString(uri)) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
return inferMimeTypeFromName(readUriBasename(uri));
|
|
62
|
+
}
|
|
63
|
+
function requestedInputModes(parts) {
|
|
64
|
+
const requested = new Set();
|
|
65
|
+
const unknownFileAttachments = [];
|
|
66
|
+
parts.forEach((part, index) => {
|
|
67
|
+
switch (part.kind) {
|
|
68
|
+
case "text":
|
|
69
|
+
requested.add("text/plain");
|
|
70
|
+
return;
|
|
71
|
+
case "data":
|
|
72
|
+
requested.add("application/json");
|
|
73
|
+
return;
|
|
74
|
+
case "file": {
|
|
75
|
+
const mode = isNonEmptyString(part.mime_type)
|
|
76
|
+
? normalizeMode(part.mime_type)
|
|
77
|
+
: inferMimeTypeFromName(part.name) ?? inferMimeTypeFromUri(part.uri);
|
|
78
|
+
if (mode) {
|
|
79
|
+
requested.add(mode);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
requested.add(UNKNOWN_INPUT_MODE);
|
|
83
|
+
unknownFileAttachments.push(index);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
return {
|
|
89
|
+
requested_input_modes: [...requested],
|
|
90
|
+
unknown_file_attachments: unknownFileAttachments,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function advertisedInputModes(card) {
|
|
94
|
+
if (!card) {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
return normalizeModeList([
|
|
98
|
+
...card.defaultInputModes,
|
|
99
|
+
...card.skills.flatMap((skill) => skill.inputModes ?? []),
|
|
100
|
+
]);
|
|
101
|
+
}
|
|
102
|
+
function advertisedOutputModes(card) {
|
|
103
|
+
if (!card) {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
return normalizeModeList([
|
|
107
|
+
...card.defaultOutputModes,
|
|
108
|
+
...card.skills.flatMap((skill) => skill.outputModes ?? []),
|
|
109
|
+
]);
|
|
110
|
+
}
|
|
111
|
+
function unsupportedModes(requestedModes, advertisedModes) {
|
|
112
|
+
const supported = new Set(advertisedModes);
|
|
113
|
+
return requestedModes.filter((mode, index) => mode !== UNKNOWN_INPUT_MODE &&
|
|
114
|
+
!supported.has(mode) &&
|
|
115
|
+
requestedModes.indexOf(mode) === index);
|
|
116
|
+
}
|
|
117
|
+
export function evaluateSendCompatibility(input, acceptedOutputModes, card) {
|
|
118
|
+
const inputModes = requestedInputModes(input.parts);
|
|
119
|
+
const requestedOutputModes = normalizeModeList(acceptedOutputModes);
|
|
120
|
+
const advertisedInput = advertisedInputModes(card);
|
|
121
|
+
const advertisedOutput = advertisedOutputModes(card);
|
|
122
|
+
return {
|
|
123
|
+
...inputModes,
|
|
124
|
+
advertised_input_modes: advertisedInput,
|
|
125
|
+
unsupported_input_modes: unsupportedModes(inputModes.requested_input_modes, advertisedInput),
|
|
126
|
+
requested_output_modes: requestedOutputModes,
|
|
127
|
+
advertised_output_modes: advertisedOutput,
|
|
128
|
+
unsupported_output_modes: unsupportedModes(requestedOutputModes, advertisedOutput),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=capability-diagnostics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability-diagnostics.js","sourceRoot":"","sources":["../src/capability-diagnostics.ts"],"names":[],"mappings":"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"}
|