@aiwg/cli 2026.8.16 → 2026.8.18

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 (55) hide show
  1. package/bin/aiwg.mjs +24 -2
  2. package/dist/src/a2a/agent-card.js +4 -1
  3. package/dist/src/a2a/client.js +148 -68
  4. package/dist/src/a2a/codecs.js +480 -0
  5. package/dist/src/a2a/events.js +226 -0
  6. package/dist/src/a2a/hitl-driver.js +8 -6
  7. package/dist/src/a2a/hitl.js +2 -1
  8. package/dist/src/a2a/http.js +85 -5
  9. package/dist/src/a2a/protocol.js +136 -0
  10. package/dist/src/a2a/types.js +4 -14
  11. package/dist/src/a2a/webhook.js +101 -4
  12. package/dist/src/artifacts/browser-export.js +1 -0
  13. package/dist/src/artifacts/cli.js +1 -1
  14. package/dist/src/artifacts/fortemi-core-query-adapter.js +6 -0
  15. package/dist/src/artifacts/types.js +73 -1
  16. package/dist/src/audit/operator-decision.js +15 -1
  17. package/dist/src/channel/manager.mjs +89 -17
  18. package/dist/src/cli/agent-spawn.js +4 -2
  19. package/dist/src/cli/handlers/cockpit.js +41 -0
  20. package/dist/src/cli/handlers/help.js +1 -1
  21. package/dist/src/cli/handlers/index.js +3 -1
  22. package/dist/src/cli/handlers/installation.js +79 -0
  23. package/dist/src/cli/handlers/ralph.js +2 -1
  24. package/dist/src/cli/handlers/refresh.js +4 -2
  25. package/dist/src/cli/handlers/runtime-info.js +9 -1
  26. package/dist/src/cli/handlers/sdlc-accelerate.js +2 -1
  27. package/dist/src/cli/handlers/serve.js +107 -4
  28. package/dist/src/cli/handlers/session.js +12 -26
  29. package/dist/src/cli/handlers/use.js +117 -12
  30. package/dist/src/cli/handlers/utilities.js +4 -0
  31. package/dist/src/cli/handlers/version.js +4 -0
  32. package/dist/src/cli/handlers/workspace.js +24 -2
  33. package/dist/src/cli/services/deployment-verification.js +9 -2
  34. package/dist/src/cockpit/doctor.js +257 -0
  35. package/dist/src/config/aiwg-config.js +36 -3
  36. package/dist/src/config/user-config-dir.mjs +29 -0
  37. package/dist/src/config/user-config.js +4 -22
  38. package/dist/src/extensions/commands/definitions.js +20 -1
  39. package/dist/src/features/catalog.js +2 -1
  40. package/dist/src/flow/graph-metadata.js +56 -0
  41. package/dist/src/installation/manager.mjs +243 -0
  42. package/dist/src/providers/transformation-receipt-integration.js +130 -3
  43. package/dist/src/security/artifact-verifier.js +7 -1
  44. package/dist/src/serve/a2a-terminal-observer.js +28 -5
  45. package/dist/src/serve/dispatch-router.js +32 -4
  46. package/dist/src/serve/executor-registry.js +29 -0
  47. package/dist/src/serve/mission-conductor.js +15 -1
  48. package/dist/src/serve/pty-bridge.js +6 -11
  49. package/dist/src/serve/stack-adapters.js +2 -1
  50. package/dist/src/serve/telemetry.js +5 -1
  51. package/dist/src/skills/run.js +15 -6
  52. package/dist/src/update/checker.mjs +16 -15
  53. package/dist/src/update/notifier.mjs +8 -3
  54. package/dist/src/update/service.mjs +49 -5
  55. package/package.json +2 -1
@@ -0,0 +1,226 @@
1
+ import { decodeArtifact, decodeMessage, decodeStatus, decodeTask, A2AWireValidationError, } from './codecs.js';
2
+ import { isTerminalTaskState, } from './types.js';
3
+ /** Strict 1.0 StreamResponse decoder and separate 0.3 compatibility decoder. */
4
+ export function decodeStreamResponse(version, input, opts = {}) {
5
+ return version === '1.0'
6
+ ? decodeV1StreamResponse(input, opts)
7
+ : decodeLegacyStreamEvent(input, opts);
8
+ }
9
+ export function decodeV1StreamResponse(input, opts = {}) {
10
+ const obj = objectAt('1.0', '$', input);
11
+ if (typeof obj.kind === 'string') {
12
+ fail('1.0', '$.kind', 'legacy event discriminator is not valid in StreamResponse');
13
+ }
14
+ const members = ['task', 'message', 'statusUpdate', 'artifactUpdate']
15
+ .filter(key => Object.prototype.hasOwnProperty.call(obj, key));
16
+ if (members.length !== 1) {
17
+ fail('1.0', '$', 'StreamResponse must contain exactly one of task, message, statusUpdate, artifactUpdate');
18
+ }
19
+ const base = eventBase('1.0', opts);
20
+ switch (members[0]) {
21
+ case 'task':
22
+ return { ...base, type: 'task', task: decodeTask('1.0', obj.task, '$.task') };
23
+ case 'message':
24
+ return { ...base, type: 'message', message: decodeMessage('1.0', obj.message, '$.message') };
25
+ case 'statusUpdate': {
26
+ const update = objectAt('1.0', '$.statusUpdate', obj.statusUpdate);
27
+ return {
28
+ ...base,
29
+ type: 'status',
30
+ taskId: stringAt('1.0', '$.statusUpdate.taskId', update.taskId),
31
+ contextId: stringAt('1.0', '$.statusUpdate.contextId', update.contextId),
32
+ status: decodeStatus('1.0', update.status, '$.statusUpdate.status'),
33
+ ...(update.metadata !== undefined
34
+ ? { metadata: jsonObjectAt('1.0', '$.statusUpdate.metadata', update.metadata) }
35
+ : {}),
36
+ };
37
+ }
38
+ case 'artifactUpdate': {
39
+ const update = objectAt('1.0', '$.artifactUpdate', obj.artifactUpdate);
40
+ return {
41
+ ...base,
42
+ type: 'artifact',
43
+ taskId: stringAt('1.0', '$.artifactUpdate.taskId', update.taskId),
44
+ contextId: stringAt('1.0', '$.artifactUpdate.contextId', update.contextId),
45
+ artifact: decodeArtifact('1.0', update.artifact, '$.artifactUpdate.artifact'),
46
+ ...(update.append !== undefined ? { append: booleanAt('1.0', '$.artifactUpdate.append', update.append) } : {}),
47
+ ...(update.lastChunk !== undefined ? { lastChunk: booleanAt('1.0', '$.artifactUpdate.lastChunk', update.lastChunk) } : {}),
48
+ ...(update.metadata !== undefined
49
+ ? { metadata: jsonObjectAt('1.0', '$.artifactUpdate.metadata', update.metadata) }
50
+ : {}),
51
+ };
52
+ }
53
+ default:
54
+ fail('1.0', '$', 'unknown StreamResponse member');
55
+ }
56
+ }
57
+ export function decodeLegacyStreamEvent(input, opts = {}) {
58
+ const obj = objectAt('0.3', '$', input);
59
+ const base = eventBase('0.3', opts);
60
+ // Some 0.3 subscriptions use the SSE event name for a full initial Task.
61
+ if (typeof obj.id === 'string' && obj.status !== undefined) {
62
+ return { ...base, type: 'task', task: decodeTask('0.3', obj) };
63
+ }
64
+ const kind = typeof obj.kind === 'string' ? obj.kind : opts.eventName;
65
+ if (!kind)
66
+ fail('0.3', '$', 'legacy stream event requires kind or SSE event name');
67
+ switch (kind) {
68
+ case 'task-state':
69
+ return { ...base, type: 'task', task: decodeTask('0.3', obj.task, '$.task') };
70
+ case 'status-update':
71
+ return {
72
+ ...base,
73
+ type: 'status',
74
+ taskId: stringAt('0.3', '$.taskId', obj.taskId),
75
+ status: decodeStatus('0.3', obj.status, '$.status'),
76
+ };
77
+ case 'artifact-update':
78
+ return {
79
+ ...base,
80
+ type: 'artifact',
81
+ taskId: stringAt('0.3', '$.taskId', obj.taskId),
82
+ artifact: decodeArtifact('0.3', obj.artifact, '$.artifact'),
83
+ ...(obj.append !== undefined ? { append: booleanAt('0.3', '$.append', obj.append) } : {}),
84
+ };
85
+ default:
86
+ fail('0.3', '$.kind', `unsupported legacy event '${kind}'`);
87
+ }
88
+ }
89
+ /**
90
+ * Shared ordering/ownership/state-transition gate for SSE and push. It returns
91
+ * null for an exact duplicate and throws before state mutation for invalid
92
+ * ownership, out-of-order delivery, missing initial snapshots, or terminal
93
+ * regression.
94
+ */
95
+ export class A2AEventReconciler {
96
+ taskId;
97
+ contextId;
98
+ initialized;
99
+ terminal;
100
+ lastSequence;
101
+ eventIds = new Set();
102
+ constructor(opts) {
103
+ this.taskId = opts.taskId;
104
+ this.contextId = opts.contextId ?? opts.initialTask?.contextId;
105
+ this.initialized = opts.initialTask !== undefined || opts.requireInitialSnapshot !== true;
106
+ this.terminal = opts.initialTask ? isTerminalTaskState(opts.initialTask.status.state) : false;
107
+ }
108
+ accept(event) {
109
+ if (event.eventId && this.eventIds.has(event.eventId))
110
+ return null;
111
+ if (event.sequence !== undefined) {
112
+ if (this.lastSequence !== undefined && event.sequence <= this.lastSequence) {
113
+ if (event.sequence === this.lastSequence)
114
+ return null;
115
+ throw new Error(`A2A event sequence regressed from ${this.lastSequence} to ${event.sequence}`);
116
+ }
117
+ if (this.lastSequence !== undefined && event.sequence !== this.lastSequence + 1) {
118
+ throw new Error(`A2A event sequence gap after ${this.lastSequence}: received ${event.sequence}`);
119
+ }
120
+ }
121
+ const eventTaskId = taskIdOf(event);
122
+ if (eventTaskId && eventTaskId !== this.taskId) {
123
+ throw new Error(`A2A event belongs to task ${eventTaskId}, expected ${this.taskId}`);
124
+ }
125
+ const eventContextId = contextIdOf(event);
126
+ if (this.contextId && eventContextId && this.contextId !== eventContextId) {
127
+ throw new Error(`A2A event belongs to context ${eventContextId}, expected ${this.contextId}`);
128
+ }
129
+ if (!this.initialized) {
130
+ if (event.type !== 'task') {
131
+ throw new Error('A2A subscription must begin with a Task snapshot before deltas');
132
+ }
133
+ this.initialized = true;
134
+ }
135
+ if (this.terminal && event.type !== 'task') {
136
+ throw new Error(`A2A event '${event.type}' arrived after task ${this.taskId} became terminal`);
137
+ }
138
+ if (event.type === 'task') {
139
+ if (this.terminal && !isTerminalTaskState(event.task.status.state)) {
140
+ throw new Error(`A2A task ${this.taskId} cannot regress from terminal state`);
141
+ }
142
+ this.contextId ??= event.task.contextId;
143
+ this.terminal = isTerminalTaskState(event.task.status.state);
144
+ }
145
+ else if (event.type === 'status') {
146
+ this.contextId ??= event.contextId;
147
+ this.terminal = isTerminalTaskState(event.status.state);
148
+ }
149
+ else if (event.type === 'message') {
150
+ this.contextId ??= event.message.contextId;
151
+ }
152
+ else {
153
+ this.contextId ??= event.contextId;
154
+ }
155
+ if (event.eventId)
156
+ this.eventIds.add(event.eventId);
157
+ if (event.sequence !== undefined)
158
+ this.lastSequence = event.sequence;
159
+ return event;
160
+ }
161
+ isTerminal() {
162
+ return this.terminal;
163
+ }
164
+ }
165
+ function taskIdOf(event) {
166
+ if (event.type === 'task')
167
+ return event.task.id;
168
+ if (event.type === 'message')
169
+ return event.message.taskId;
170
+ return event.taskId;
171
+ }
172
+ function contextIdOf(event) {
173
+ if (event.type === 'task')
174
+ return event.task.contextId;
175
+ if (event.type === 'message')
176
+ return event.message.contextId;
177
+ return event.contextId;
178
+ }
179
+ function eventBase(version, opts) {
180
+ return {
181
+ protocolVersion: version,
182
+ ...(opts.sequence !== undefined ? { sequence: opts.sequence } : {}),
183
+ ...(opts.eventId !== undefined ? { eventId: opts.eventId } : {}),
184
+ };
185
+ }
186
+ function objectAt(version, path, value) {
187
+ if (!value || typeof value !== 'object' || Array.isArray(value))
188
+ fail(version, path, 'must be an object');
189
+ return value;
190
+ }
191
+ function stringAt(version, path, value) {
192
+ if (typeof value !== 'string' || !value)
193
+ fail(version, path, 'must be a non-empty string');
194
+ return value;
195
+ }
196
+ function booleanAt(version, path, value) {
197
+ if (typeof value !== 'boolean')
198
+ fail(version, path, 'must be a boolean');
199
+ return value;
200
+ }
201
+ function jsonObjectAt(version, path, value) {
202
+ const object = objectAt(version, path, value);
203
+ assertJsonValue(version, path, object);
204
+ return object;
205
+ }
206
+ function assertJsonValue(version, path, value) {
207
+ if (value === null || typeof value === 'string' || typeof value === 'boolean')
208
+ return;
209
+ if (typeof value === 'number' && Number.isFinite(value))
210
+ return;
211
+ if (Array.isArray(value)) {
212
+ value.forEach((entry, index) => assertJsonValue(version, `${path}[${index}]`, entry));
213
+ return;
214
+ }
215
+ if (value && typeof value === 'object') {
216
+ for (const [key, entry] of Object.entries(value)) {
217
+ assertJsonValue(version, `${path}.${key}`, entry);
218
+ }
219
+ return;
220
+ }
221
+ fail(version, path, 'must contain only JSON values');
222
+ }
223
+ function fail(version, path, detail) {
224
+ throw new A2AWireValidationError(version, path, detail);
225
+ }
226
+ //# sourceMappingURL=events.js.map
@@ -337,13 +337,13 @@ export async function driveOnePrompt(opts) {
337
337
  }
338
338
  // ── helpers ────────────────────────────────────────────────────────────
339
339
  function extractEnvelopeFromEvent(event) {
340
- if (event.kind === 'task-state') {
340
+ if (event.type === 'task') {
341
341
  const result = extractHitlEnvelope(event.task);
342
342
  if (result?.ok)
343
343
  return result.envelope;
344
344
  return null;
345
345
  }
346
- if (event.kind === 'status-update') {
346
+ if (event.type === 'status') {
347
347
  const result = extractHitlEnvelope(event.status);
348
348
  if (result?.ok)
349
349
  return result.envelope;
@@ -352,14 +352,16 @@ function extractEnvelopeFromEvent(event) {
352
352
  return null;
353
353
  }
354
354
  function inferEventContext(event) {
355
- if (event.kind === 'task-state') {
355
+ if (event.type === 'task') {
356
356
  return event.task.contextId !== undefined
357
357
  ? { contextId: event.task.contextId }
358
358
  : {};
359
359
  }
360
- // status-update / artifact-update don't carry a contextId; the caller
361
- // can pass one through `driveOnePrompt` directly if it has one cached.
362
- return {};
360
+ return event.type === 'status' || event.type === 'artifact'
361
+ ? event.contextId !== undefined ? { contextId: event.contextId } : {}
362
+ : event.type === 'message' && event.message.contextId !== undefined
363
+ ? { contextId: event.message.contextId }
364
+ : {};
363
365
  }
364
366
  function parseDeadline(deadline) {
365
367
  if (!deadline)
@@ -110,11 +110,12 @@ export function buildHitlResponseMessage(opts) {
110
110
  role: 'user',
111
111
  parts: [
112
112
  {
113
- kind: 'data',
113
+ type: 'data',
114
114
  data: opts.response,
115
115
  },
116
116
  ],
117
117
  metadata: {
118
+ ...(opts.metadata ?? {}),
118
119
  hitl_response_for: {
119
120
  prompt_id: opts.promptId,
120
121
  payload: opts.response,
@@ -15,12 +15,24 @@ export class A2AError extends Error {
15
15
  status;
16
16
  problem;
17
17
  path;
18
- constructor(status, path, problem) {
18
+ category;
19
+ versionNotSupported;
20
+ constructor(status, path, problem, category) {
19
21
  super(`${status} ${problem.code ?? problem.title}: ${problem.detail ?? problem.title}`);
20
22
  this.name = 'A2AError';
21
23
  this.status = status;
22
24
  this.problem = problem;
23
25
  this.path = path;
26
+ this.versionNotSupported = isVersionNotSupportedProblem(problem);
27
+ this.category = category ?? (this.versionNotSupported
28
+ ? 'negotiation'
29
+ : status === 401 || status === 403
30
+ ? 'authorization'
31
+ : status === 0
32
+ ? 'transport'
33
+ : status >= 400
34
+ ? 'application'
35
+ : 'transport');
24
36
  }
25
37
  }
26
38
  const MUTATING_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
@@ -34,6 +46,7 @@ export class A2AHttpClient {
34
46
  onExtensionEchoMissing;
35
47
  /** Per-process dedupe set: one log per (path, sunset_date). */
36
48
  seenDeprecations = new Set();
49
+ protocolVersion;
37
50
  constructor(opts) {
38
51
  this.baseUrl = opts.baseUrl.replace(/\/+$/, '');
39
52
  this.bearer = opts.bearer;
@@ -42,16 +55,21 @@ export class A2AHttpClient {
42
55
  this.fetchImpl = opts.fetch ?? fetch;
43
56
  this.onDeprecation = opts.onDeprecation;
44
57
  this.onExtensionEchoMissing = opts.onExtensionEchoMissing;
58
+ this.protocolVersion = opts.protocolVersion ?? '0.3';
45
59
  }
46
60
  async request(path, options = {}) {
47
61
  const method = (options.method ?? 'GET').toUpperCase();
48
62
  const url = path.startsWith('http') ? path : this.baseUrl + path;
63
+ const protocolVersion = options.protocolVersion ?? this.protocolVersion;
64
+ const mediaType = protocolVersion === '1.0' ? 'application/a2a+json' : 'application/json';
49
65
  const headers = {
50
66
  authorization: `Bearer ${options.bearer ?? this.bearer}`,
51
- accept: 'application/json',
67
+ accept: mediaType,
52
68
  };
69
+ if (protocolVersion === '1.0')
70
+ headers['a2a-version'] = '1.0';
53
71
  if (options.body !== undefined && options.bodyRaw === undefined) {
54
- headers['content-type'] = 'application/json';
72
+ headers['content-type'] = mediaType;
55
73
  }
56
74
  // Inject A2A-Extensions on mutating calls. Caller can override with
57
75
  // options.extensions (empty array clears injection).
@@ -74,7 +92,18 @@ export class A2AHttpClient {
74
92
  else if (options.body !== undefined) {
75
93
  init.body = JSON.stringify(options.body);
76
94
  }
77
- const resp = await this.fetchImpl(url, init);
95
+ let resp;
96
+ try {
97
+ resp = await this.fetchImpl(url, init);
98
+ }
99
+ catch (error) {
100
+ throw new A2AError(0, path, {
101
+ type: 'about:blank',
102
+ title: 'A2A transport failure',
103
+ detail: error instanceof Error ? error.message : String(error),
104
+ code: 'aiwg.transport_failure',
105
+ }, 'transport');
106
+ }
78
107
  // Capture deprecation headers regardless of status.
79
108
  const deprecation = captureDeprecation(path, resp.headers);
80
109
  if (deprecation) {
@@ -121,6 +150,18 @@ export class A2AHttpClient {
121
150
  // Parse body (JSON, problem+json, or empty).
122
151
  let body;
123
152
  const ct = resp.headers.get('content-type') ?? '';
153
+ if (protocolVersion === '1.0'
154
+ && resp.status < 400
155
+ && resp.status !== 204
156
+ && resp.status !== 205
157
+ && !ct.toLowerCase().includes('application/a2a+json')) {
158
+ throw new A2AError(502, path, {
159
+ type: 'about:blank',
160
+ title: 'Invalid A2A 1.0 content type',
161
+ detail: `Expected application/a2a+json, received ${ct || '(missing)'}`,
162
+ code: 'aiwg.invalid_content_type',
163
+ }, 'transport');
164
+ }
124
165
  if (resp.status !== 204 && resp.status !== 205) {
125
166
  const text = await resp.text();
126
167
  if (text.length > 0) {
@@ -143,7 +184,7 @@ export class A2AHttpClient {
143
184
  }
144
185
  }
145
186
  if (resp.status >= 400) {
146
- const problem = body ?? {
187
+ const problem = normalizeProblemDetails(resp.status, body) ?? {
147
188
  type: 'about:blank',
148
189
  title: `HTTP ${resp.status}`,
149
190
  };
@@ -159,6 +200,45 @@ export class A2AHttpClient {
159
200
  };
160
201
  }
161
202
  }
203
+ export function isVersionNotSupportedProblem(problem) {
204
+ const type = problem.type?.toLowerCase() ?? '';
205
+ const code = problem.code?.toLowerCase() ?? '';
206
+ return type.includes('version-not-supported')
207
+ || code === 'versionnotsupportederror'
208
+ || code === 'version_not_supported'
209
+ || code === 'a2a.version_not_supported'
210
+ || code === '-32009';
211
+ }
212
+ function normalizeProblemDetails(status, body) {
213
+ if (!body || typeof body !== 'object' || Array.isArray(body))
214
+ return undefined;
215
+ const obj = body;
216
+ // HTTP+JSON uses RFC 7807. JSON-RPC bindings may nest the standard code.
217
+ const nested = obj.error && typeof obj.error === 'object' && !Array.isArray(obj.error)
218
+ ? obj.error
219
+ : undefined;
220
+ const source = nested ?? obj;
221
+ const codeValue = source.code;
222
+ const code = typeof codeValue === 'string' || typeof codeValue === 'number'
223
+ ? String(codeValue)
224
+ : undefined;
225
+ const title = typeof obj.title === 'string'
226
+ ? obj.title
227
+ : typeof source.message === 'string'
228
+ ? source.message
229
+ : `HTTP ${status}`;
230
+ const problem = {
231
+ type: typeof obj.type === 'string' ? obj.type : 'about:blank',
232
+ title,
233
+ status,
234
+ ...(typeof obj.detail === 'string' ? { detail: obj.detail } : {}),
235
+ ...(code ? { code } : {}),
236
+ ...(Array.isArray(obj.supportedVersions) && obj.supportedVersions.every(v => typeof v === 'string')
237
+ ? { supportedVersions: obj.supportedVersions }
238
+ : {}),
239
+ };
240
+ return problem;
241
+ }
162
242
  // ---------- header helpers ----------
163
243
  function parseExtensionList(header) {
164
244
  if (!header)
@@ -0,0 +1,136 @@
1
+ export const A2A_HTTP_JSON_BINDING = 'HTTP+JSON';
2
+ export const A2A_LEGACY_REST_BINDING = 'REST';
3
+ export class A2ANegotiationError extends Error {
4
+ code;
5
+ constructor(code, message) {
6
+ super(message);
7
+ this.name = 'A2ANegotiationError';
8
+ this.code = code;
9
+ }
10
+ }
11
+ export function normalizeProtocolVersion(value) {
12
+ if (typeof value !== 'string')
13
+ return null;
14
+ const match = /^(0\.3|1\.0)(?:\.\d+)?$/.exec(value.trim());
15
+ return match?.[1] ?? null;
16
+ }
17
+ /**
18
+ * Parse 0.3 top-level cards and 1.0 per-interface cards into one discovery
19
+ * model. A card that claims top-level 1.0 while retaining only 0.3 interface
20
+ * fields is rejected instead of being treated as proof of 1.0 support.
21
+ */
22
+ export function normalizeAgentCard(card) {
23
+ if (!card || typeof card !== 'object') {
24
+ throw new A2ANegotiationError('agent_card.invalid', 'AgentCard must be an object');
25
+ }
26
+ if (typeof card.name !== 'string' || !card.name.trim()) {
27
+ throw new A2ANegotiationError('agent_card.name_missing', 'AgentCard.name is required');
28
+ }
29
+ if (typeof card.version !== 'string' || !card.version.trim()) {
30
+ throw new A2ANegotiationError('agent_card.version_missing', 'AgentCard.version is required');
31
+ }
32
+ const topVersion = normalizeProtocolVersion(card.protocolVersion);
33
+ if (card.protocolVersion !== undefined && !topVersion) {
34
+ throw new A2ANegotiationError('agent_card.protocol_version_invalid', `Unsupported AgentCard.protocolVersion '${String(card.protocolVersion)}'`);
35
+ }
36
+ const interfaces = [];
37
+ for (const [preference, entry] of (card.supportedInterfaces ?? []).entries()) {
38
+ if (!entry || typeof entry !== 'object' || typeof entry.url !== 'string') {
39
+ throw new A2ANegotiationError('agent_card.interface_invalid', `supportedInterfaces[${preference}] must contain an absolute URL`);
40
+ }
41
+ assertAbsoluteUrl(entry.url, `supportedInterfaces[${preference}].url`);
42
+ const interfaceVersion = normalizeProtocolVersion(entry.protocolVersion);
43
+ if (entry.protocolVersion !== undefined && !interfaceVersion) {
44
+ throw new A2ANegotiationError('agent_card.interface_version_invalid', `supportedInterfaces[${preference}].protocolVersion is unsupported`);
45
+ }
46
+ if (interfaceVersion) {
47
+ if (typeof entry.protocolBinding !== 'string' || !entry.protocolBinding.trim()) {
48
+ throw new A2ANegotiationError('agent_card.interface_binding_missing', `supportedInterfaces[${preference}] declares ${interfaceVersion} without protocolBinding`);
49
+ }
50
+ interfaces.push({
51
+ url: trimUrl(entry.url),
52
+ protocolBinding: entry.protocolBinding,
53
+ protocolVersion: interfaceVersion,
54
+ ...(entry.tenant ? { tenant: entry.tenant } : {}),
55
+ preference,
56
+ legacy: false,
57
+ });
58
+ continue;
59
+ }
60
+ // 0.3 cards put the version at top level and binding under `transport`.
61
+ if (topVersion !== '0.3') {
62
+ throw new A2ANegotiationError('agent_card.mixed_interface_shape', `supportedInterfaces[${preference}] uses legacy transport fields without a 0.3 top-level declaration`);
63
+ }
64
+ if (typeof entry.transport !== 'string' || !entry.transport.trim()) {
65
+ throw new A2ANegotiationError('agent_card.interface_transport_missing', `supportedInterfaces[${preference}] requires transport for the 0.3 card shape`);
66
+ }
67
+ interfaces.push({
68
+ url: trimUrl(entry.url),
69
+ protocolBinding: entry.transport,
70
+ protocolVersion: '0.3',
71
+ ...(entry.tenant ? { tenant: entry.tenant } : {}),
72
+ preference,
73
+ legacy: true,
74
+ });
75
+ }
76
+ // Legacy cards commonly omit supportedInterfaces entirely.
77
+ if (topVersion === '0.3' && typeof card.url === 'string') {
78
+ assertAbsoluteUrl(card.url, 'AgentCard.url');
79
+ if (!interfaces.some(entry => entry.protocolVersion === '0.3' && entry.url === trimUrl(card.url))) {
80
+ interfaces.push({
81
+ url: trimUrl(card.url),
82
+ protocolBinding: card.preferredTransport ?? A2A_LEGACY_REST_BINDING,
83
+ protocolVersion: '0.3',
84
+ preference: interfaces.length,
85
+ legacy: true,
86
+ });
87
+ }
88
+ }
89
+ if (topVersion === '1.0' && interfaces.length === 0) {
90
+ throw new A2ANegotiationError('agent_card.v1_interfaces_missing', 'A2A 1.0 cards must declare versioned supportedInterfaces; top-level protocolVersion/url are 0.3 fields');
91
+ }
92
+ if (interfaces.length === 0) {
93
+ throw new A2ANegotiationError('agent_card.interfaces_missing', 'AgentCard exposes no usable protocol interface');
94
+ }
95
+ return { card, interfaces };
96
+ }
97
+ export function selectAgentInterface(input, opts) {
98
+ const normalized = 'interfaces' in input ? input : normalizeAgentCard(input);
99
+ const bindings = opts.bindings ?? [A2A_HTTP_JSON_BINDING, A2A_LEGACY_REST_BINDING];
100
+ const versions = opts.policy === 'auto'
101
+ ? opts.versionPreference ?? ['1.0', '0.3']
102
+ : [opts.policy];
103
+ for (const version of versions) {
104
+ for (const binding of bindings) {
105
+ const selected = normalized.interfaces
106
+ .filter(entry => entry.protocolVersion === version && sameBinding(entry.protocolBinding, binding))
107
+ .sort((a, b) => a.preference - b.preference)[0];
108
+ if (selected)
109
+ return selected;
110
+ }
111
+ }
112
+ const available = normalized.interfaces
113
+ .map(entry => `${entry.protocolVersion}/${entry.protocolBinding}@${entry.url}`)
114
+ .join(', ');
115
+ throw new A2ANegotiationError('agent_card.no_compatible_interface', `No compatible A2A interface for policy=${opts.policy}; available: ${available || 'none'}`);
116
+ }
117
+ export function agentInterfaceCacheKey(host, instanceId, selected) {
118
+ return [host, instanceId, selected.protocolVersion, selected.protocolBinding, selected.url].join('|');
119
+ }
120
+ function sameBinding(actual, supported) {
121
+ return actual.trim().toUpperCase() === supported.trim().toUpperCase();
122
+ }
123
+ function trimUrl(value) {
124
+ return value.replace(/\/+$/, '');
125
+ }
126
+ function assertAbsoluteUrl(value, field) {
127
+ try {
128
+ const url = new URL(value);
129
+ if (!['http:', 'https:'].includes(url.protocol))
130
+ throw new Error('not HTTP(S)');
131
+ }
132
+ catch {
133
+ throw new A2ANegotiationError('agent_card.url_invalid', `${field} must be an absolute HTTP(S) URL`);
134
+ }
135
+ }
136
+ //# sourceMappingURL=protocol.js.map
@@ -1,17 +1,7 @@
1
- // A2A protocol types the subset AIWG needs to talk to an agentic-sandbox v2
2
- // executor. Modelled from the executor's wire surface
3
- // (`agentic-sandbox/management/agentic-sandbox-executor`):
4
- //
5
- // - send_message.rs — POST /agents/{id}/v1/messages:send
6
- // - get_task.rs — GET /agents/{id}/v1/tasks/{tid}
7
- // - cancel_task.rs — POST /agents/{id}/v1/tasks/{tid}/cancel
8
- // - subscribe_task — GET /agents/{id}/v1/tasks/{tid}/subscribe (SSE)
9
- // - push_delivery.rs — * /agents/{id}/v1/tasks/{tid}/pushNotificationConfigs/*
10
- // - agent_card.rs — GET /agents/{id}/.well-known/agent-card.json
11
- // GET /agents/{id}/v1/extendedAgentCard
12
- //
13
- // Types are intentionally permissive (extra fields allowed). We track only
14
- // what the AIWG orchestrator inspects; the rest is forwarded opaquely.
1
+ // Normalized A2A domain types. Protocol 0.3 and 1.0 wire values are decoded
2
+ // into these types at the boundary in codecs.ts. Application code must not
3
+ // depend on either version's enum spellings, kind fields, or oneof layout.
4
+ export { AIWG_GRAPH_METADATA_KEY } from '../flow/graph-metadata.js';
15
5
  export const TERMINAL_TASK_STATES = [
16
6
  'completed',
17
7
  'failed',