@probelabs/visor 0.1.181-ee → 0.1.181

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 (57) hide show
  1. package/dist/index.js +23 -1840
  2. package/dist/output/traces/run-2026-03-17T13-58-29-402Z.ndjson +157 -0
  3. package/dist/output/traces/run-2026-03-17T13-59-10-403Z.ndjson +2333 -0
  4. package/dist/sdk/a2a-frontend-IWOUJOIZ.mjs +1676 -0
  5. package/dist/sdk/a2a-frontend-IWOUJOIZ.mjs.map +1 -0
  6. package/dist/sdk/{check-provider-registry-Z7EHECLC.mjs → check-provider-registry-4YFVBGYU.mjs} +2 -2
  7. package/dist/sdk/check-provider-registry-67ZLGDDQ.mjs +31 -0
  8. package/dist/sdk/{chunk-6HLDM4OB.mjs → chunk-4DVP6KVC.mjs} +9 -9
  9. package/dist/sdk/{chunk-6HLDM4OB.mjs.map → chunk-4DVP6KVC.mjs.map} +1 -1
  10. package/dist/sdk/chunk-DGIH6EX3.mjs +520 -0
  11. package/dist/sdk/chunk-DGIH6EX3.mjs.map +1 -0
  12. package/dist/sdk/chunk-J73GEFPT.mjs +739 -0
  13. package/dist/sdk/chunk-J73GEFPT.mjs.map +1 -0
  14. package/dist/sdk/chunk-QGBASDYP.mjs +46153 -0
  15. package/dist/sdk/chunk-QGBASDYP.mjs.map +1 -0
  16. package/dist/sdk/chunk-VXC2XNQJ.mjs +1502 -0
  17. package/dist/sdk/chunk-VXC2XNQJ.mjs.map +1 -0
  18. package/dist/sdk/failure-condition-evaluator-HTPB5FYW.mjs +18 -0
  19. package/dist/sdk/github-frontend-3SDFCCKI.mjs +1394 -0
  20. package/dist/sdk/github-frontend-3SDFCCKI.mjs.map +1 -0
  21. package/dist/sdk/{host-6QBBL3QW.mjs → host-QE4L7UXE.mjs} +1 -1
  22. package/dist/sdk/host-VBBSLUWG.mjs +87 -0
  23. package/dist/sdk/host-VBBSLUWG.mjs.map +1 -0
  24. package/dist/sdk/routing-YVMTKFDZ.mjs +26 -0
  25. package/dist/sdk/schedule-tool-Z5VG67JK.mjs +37 -0
  26. package/dist/sdk/schedule-tool-Z5VG67JK.mjs.map +1 -0
  27. package/dist/sdk/{schedule-tool-5XBASQVY.mjs → schedule-tool-ZMX3Y7LF.mjs} +2 -2
  28. package/dist/sdk/schedule-tool-ZMX3Y7LF.mjs.map +1 -0
  29. package/dist/sdk/{schedule-tool-handler-GBIJS376.mjs → schedule-tool-handler-PCERK6ZZ.mjs} +2 -2
  30. package/dist/sdk/schedule-tool-handler-PCERK6ZZ.mjs.map +1 -0
  31. package/dist/sdk/schedule-tool-handler-QOJVFRB4.mjs +41 -0
  32. package/dist/sdk/schedule-tool-handler-QOJVFRB4.mjs.map +1 -0
  33. package/dist/sdk/sdk.js +289 -1663
  34. package/dist/sdk/sdk.js.map +1 -1
  35. package/dist/sdk/sdk.mjs +4 -4
  36. package/dist/sdk/trace-helpers-KXDOJWBL.mjs +29 -0
  37. package/dist/sdk/trace-helpers-KXDOJWBL.mjs.map +1 -0
  38. package/dist/sdk/{workflow-check-provider-3IXIADSV.mjs → workflow-check-provider-CJXW2Z4F.mjs} +2 -2
  39. package/dist/sdk/workflow-check-provider-CJXW2Z4F.mjs.map +1 -0
  40. package/dist/sdk/workflow-check-provider-NTHC5ZBF.mjs +31 -0
  41. package/dist/sdk/workflow-check-provider-NTHC5ZBF.mjs.map +1 -0
  42. package/dist/traces/run-2026-03-17T13-58-29-402Z.ndjson +157 -0
  43. package/dist/traces/run-2026-03-17T13-59-10-403Z.ndjson +2333 -0
  44. package/package.json +1 -1
  45. package/dist/sdk/knex-store-QCEW4I4R.mjs +0 -527
  46. package/dist/sdk/knex-store-QCEW4I4R.mjs.map +0 -1
  47. package/dist/sdk/loader-ZNKKJEZ3.mjs +0 -89
  48. package/dist/sdk/loader-ZNKKJEZ3.mjs.map +0 -1
  49. package/dist/sdk/opa-policy-engine-QCSSIMUF.mjs +0 -655
  50. package/dist/sdk/opa-policy-engine-QCSSIMUF.mjs.map +0 -1
  51. package/dist/sdk/validator-XTZJZZJH.mjs +0 -134
  52. package/dist/sdk/validator-XTZJZZJH.mjs.map +0 -1
  53. /package/dist/sdk/{check-provider-registry-Z7EHECLC.mjs.map → check-provider-registry-4YFVBGYU.mjs.map} +0 -0
  54. /package/dist/sdk/{schedule-tool-5XBASQVY.mjs.map → check-provider-registry-67ZLGDDQ.mjs.map} +0 -0
  55. /package/dist/sdk/{schedule-tool-handler-GBIJS376.mjs.map → failure-condition-evaluator-HTPB5FYW.mjs.map} +0 -0
  56. /package/dist/sdk/{host-6QBBL3QW.mjs.map → host-QE4L7UXE.mjs.map} +0 -0
  57. /package/dist/sdk/{workflow-check-provider-3IXIADSV.mjs.map → routing-YVMTKFDZ.mjs.map} +0 -0
@@ -0,0 +1,1676 @@
1
+ import {
2
+ ContextMismatchError,
3
+ InvalidRequestError,
4
+ InvalidStateTransitionError,
5
+ ParseError,
6
+ TaskNotFoundError,
7
+ assertValidTransition,
8
+ init_state_transitions,
9
+ init_types,
10
+ isTerminalState
11
+ } from "./chunk-YSOIR46P.mjs";
12
+ import {
13
+ init_trace_helpers,
14
+ withActiveSpan
15
+ } from "./chunk-DGIH6EX3.mjs";
16
+ import "./chunk-6VVXKXTI.mjs";
17
+ import "./chunk-34QX63WK.mjs";
18
+ import {
19
+ init_logger,
20
+ logger
21
+ } from "./chunk-FT3I25QV.mjs";
22
+ import "./chunk-UCMJJ3IM.mjs";
23
+ import {
24
+ __esm,
25
+ __require
26
+ } from "./chunk-J7LXIPZS.mjs";
27
+
28
+ // src/agent-protocol/task-store.ts
29
+ import path from "path";
30
+ import fs from "fs";
31
+ import crypto from "crypto";
32
+ function safeJsonParse(value) {
33
+ if (!value) return void 0;
34
+ try {
35
+ return JSON.parse(value);
36
+ } catch {
37
+ return void 0;
38
+ }
39
+ }
40
+ function nowISO() {
41
+ return (/* @__PURE__ */ new Date()).toISOString();
42
+ }
43
+ function taskRowToAgentTask(row) {
44
+ return {
45
+ id: row.id,
46
+ context_id: row.context_id,
47
+ status: {
48
+ state: row.state,
49
+ message: safeJsonParse(row.status_message),
50
+ timestamp: row.updated_at
51
+ },
52
+ artifacts: safeJsonParse(row.artifacts) ?? [],
53
+ history: safeJsonParse(row.history) ?? [],
54
+ metadata: safeJsonParse(row.request_metadata),
55
+ workflow_id: row.workflow_id ?? void 0
56
+ };
57
+ }
58
+ var SqliteTaskStore;
59
+ var init_task_store = __esm({
60
+ "src/agent-protocol/task-store.ts"() {
61
+ "use strict";
62
+ init_logger();
63
+ init_types();
64
+ init_state_transitions();
65
+ SqliteTaskStore = class {
66
+ db = null;
67
+ dbPath;
68
+ constructor(filename) {
69
+ this.dbPath = filename || ".visor/agent-tasks.db";
70
+ }
71
+ async initialize() {
72
+ const resolvedPath = path.resolve(process.cwd(), this.dbPath);
73
+ const dir = path.dirname(resolvedPath);
74
+ fs.mkdirSync(dir, { recursive: true });
75
+ const { createRequire } = __require("module");
76
+ const runtimeRequire = createRequire(__filename);
77
+ let Database;
78
+ try {
79
+ Database = runtimeRequire("better-sqlite3");
80
+ } catch (err) {
81
+ const code = err?.code;
82
+ if (code === "MODULE_NOT_FOUND" || code === "ERR_MODULE_NOT_FOUND") {
83
+ throw new Error(
84
+ "better-sqlite3 is required for agent protocol task storage. Install it with: npm install better-sqlite3"
85
+ );
86
+ }
87
+ throw err;
88
+ }
89
+ this.db = new Database(resolvedPath);
90
+ this.db.pragma("journal_mode = WAL");
91
+ this.migrateSchema();
92
+ logger.info(`[AgentTaskStore] Initialized at ${this.dbPath}`);
93
+ }
94
+ async shutdown() {
95
+ if (this.db) {
96
+ this.db.close();
97
+ this.db = null;
98
+ }
99
+ }
100
+ getDb() {
101
+ if (!this.db) throw new Error("TaskStore not initialized");
102
+ return this.db;
103
+ }
104
+ /** Expose the underlying database for sibling managers (e.g. PushNotificationManager). */
105
+ getDatabase() {
106
+ return this.getDb();
107
+ }
108
+ migrateSchema() {
109
+ const db = this.getDb();
110
+ db.exec(`
111
+ CREATE TABLE IF NOT EXISTS agent_tasks (
112
+ id TEXT PRIMARY KEY,
113
+ context_id TEXT NOT NULL,
114
+ state TEXT NOT NULL DEFAULT 'submitted',
115
+ created_at TEXT NOT NULL,
116
+ updated_at TEXT NOT NULL,
117
+ request_message TEXT NOT NULL,
118
+ request_config TEXT,
119
+ request_metadata TEXT,
120
+ status_message TEXT,
121
+ artifacts TEXT DEFAULT '[]',
122
+ history TEXT DEFAULT '[]',
123
+ workflow_id TEXT,
124
+ run_id TEXT,
125
+ claimed_by TEXT,
126
+ claimed_at TEXT,
127
+ expires_at TEXT
128
+ );
129
+
130
+ CREATE INDEX IF NOT EXISTS idx_agent_tasks_context ON agent_tasks(context_id);
131
+ CREATE INDEX IF NOT EXISTS idx_agent_tasks_state ON agent_tasks(state);
132
+ CREATE INDEX IF NOT EXISTS idx_agent_tasks_updated ON agent_tasks(updated_at);
133
+ CREATE INDEX IF NOT EXISTS idx_agent_tasks_claim ON agent_tasks(state, claimed_by, claimed_at);
134
+ CREATE INDEX IF NOT EXISTS idx_agent_tasks_expires ON agent_tasks(expires_at);
135
+ `);
136
+ }
137
+ // -------------------------------------------------------------------------
138
+ // CRUD
139
+ // -------------------------------------------------------------------------
140
+ createTask(params) {
141
+ const db = this.getDb();
142
+ const id = crypto.randomUUID();
143
+ const now = nowISO();
144
+ const contextId = this.resolveContextId(params.requestMessage, params.contextId);
145
+ const expiresAt = params.expiresAt ?? null;
146
+ db.prepare(
147
+ `INSERT INTO agent_tasks
148
+ (id, context_id, state, created_at, updated_at,
149
+ request_message, request_config, request_metadata,
150
+ workflow_id, expires_at, artifacts, history)
151
+ VALUES (?, ?, 'submitted', ?, ?,
152
+ ?, ?, ?,
153
+ ?, ?, '[]', '[]')`
154
+ ).run(
155
+ id,
156
+ contextId,
157
+ now,
158
+ now,
159
+ JSON.stringify(params.requestMessage),
160
+ params.requestConfig ? JSON.stringify(params.requestConfig) : null,
161
+ params.requestMetadata ? JSON.stringify(params.requestMetadata) : null,
162
+ params.workflowId ?? null,
163
+ expiresAt
164
+ );
165
+ return {
166
+ id,
167
+ context_id: contextId,
168
+ status: { state: "submitted", timestamp: now },
169
+ artifacts: [],
170
+ history: [],
171
+ metadata: params.requestMetadata,
172
+ workflow_id: params.workflowId
173
+ };
174
+ }
175
+ getTask(taskId) {
176
+ const db = this.getDb();
177
+ const row = db.prepare("SELECT * FROM agent_tasks WHERE id = ?").get(taskId);
178
+ return row ? taskRowToAgentTask(row) : null;
179
+ }
180
+ buildFilterClause(filter) {
181
+ const conditions = [];
182
+ const params = [];
183
+ if (filter.contextId) {
184
+ conditions.push("context_id = ?");
185
+ params.push(filter.contextId);
186
+ }
187
+ if (filter.state && filter.state.length > 0) {
188
+ const placeholders = filter.state.map(() => "?").join(", ");
189
+ conditions.push(`state IN (${placeholders})`);
190
+ params.push(...filter.state);
191
+ }
192
+ if (filter.workflowId) {
193
+ conditions.push("workflow_id = ?");
194
+ params.push(filter.workflowId);
195
+ }
196
+ if (filter.search) {
197
+ const escaped = filter.search.replace(/[%_\\]/g, "\\$&");
198
+ conditions.push("request_message LIKE ? ESCAPE '\\'");
199
+ params.push(`%${escaped}%`);
200
+ }
201
+ if (filter.claimedBy) {
202
+ conditions.push("claimed_by = ?");
203
+ params.push(filter.claimedBy);
204
+ }
205
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
206
+ return { where, params };
207
+ }
208
+ listTasks(filter) {
209
+ const db = this.getDb();
210
+ const { where, params } = this.buildFilterClause(filter);
211
+ const countRow = db.prepare(`SELECT COUNT(*) as total FROM agent_tasks ${where}`).get(...params);
212
+ const total = countRow.total;
213
+ const limit = Math.min(filter.limit ?? 50, 200);
214
+ const offset = filter.offset ?? 0;
215
+ const rows = db.prepare(`SELECT * FROM agent_tasks ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...params, limit, offset);
216
+ return {
217
+ tasks: rows.map(taskRowToAgentTask),
218
+ total
219
+ };
220
+ }
221
+ listTasksRaw(filter) {
222
+ const db = this.getDb();
223
+ const { where, params } = this.buildFilterClause(filter);
224
+ const countRow = db.prepare(`SELECT COUNT(*) as total FROM agent_tasks ${where}`).get(...params);
225
+ const total = countRow.total;
226
+ const limit = Math.min(filter.limit ?? 50, 200);
227
+ const offset = filter.offset ?? 0;
228
+ const rawRows = db.prepare(`SELECT * FROM agent_tasks ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...params, limit, offset);
229
+ const rows = rawRows.map((r) => {
230
+ let messageText = "";
231
+ try {
232
+ const msg = JSON.parse(r.request_message);
233
+ const parts = msg.parts ?? msg.content?.parts ?? [];
234
+ const textPart = parts.find((p) => p.type === "text" || typeof p.text === "string");
235
+ messageText = textPart?.text ?? "";
236
+ } catch {
237
+ }
238
+ let source = "-";
239
+ let metadata = {};
240
+ try {
241
+ metadata = JSON.parse(r.request_metadata || "{}");
242
+ source = metadata.source || "-";
243
+ } catch {
244
+ }
245
+ return {
246
+ id: r.id,
247
+ context_id: r.context_id,
248
+ state: r.state,
249
+ created_at: r.created_at,
250
+ updated_at: r.updated_at,
251
+ claimed_by: r.claimed_by,
252
+ claimed_at: r.claimed_at,
253
+ workflow_id: r.workflow_id,
254
+ run_id: r.run_id,
255
+ request_message: messageText,
256
+ source,
257
+ metadata
258
+ };
259
+ });
260
+ return { rows, total };
261
+ }
262
+ // -------------------------------------------------------------------------
263
+ // State mutations
264
+ // -------------------------------------------------------------------------
265
+ updateTaskState(taskId, newState, statusMessage) {
266
+ const db = this.getDb();
267
+ const row = db.prepare("SELECT state FROM agent_tasks WHERE id = ?").get(taskId);
268
+ if (!row) throw new TaskNotFoundError(taskId);
269
+ assertValidTransition(row.state, newState);
270
+ const now = nowISO();
271
+ db.prepare(
272
+ `UPDATE agent_tasks
273
+ SET state = ?, updated_at = ?, status_message = ?
274
+ WHERE id = ?`
275
+ ).run(newState, now, statusMessage ? JSON.stringify(statusMessage) : null, taskId);
276
+ }
277
+ claimTask(taskId, workerId) {
278
+ const db = this.getDb();
279
+ const now = nowISO();
280
+ db.prepare(
281
+ `UPDATE agent_tasks SET claimed_by = ?, claimed_at = ?, updated_at = ? WHERE id = ?`
282
+ ).run(workerId, now, now, taskId);
283
+ }
284
+ addArtifact(taskId, artifact) {
285
+ const db = this.getDb();
286
+ const row = db.prepare("SELECT artifacts FROM agent_tasks WHERE id = ?").get(taskId);
287
+ if (!row) throw new TaskNotFoundError(taskId);
288
+ const artifacts = safeJsonParse(row.artifacts) ?? [];
289
+ artifacts.push(artifact);
290
+ db.prepare("UPDATE agent_tasks SET artifacts = ?, updated_at = ? WHERE id = ?").run(
291
+ JSON.stringify(artifacts),
292
+ nowISO(),
293
+ taskId
294
+ );
295
+ }
296
+ appendHistory(taskId, message) {
297
+ const db = this.getDb();
298
+ const row = db.prepare("SELECT history FROM agent_tasks WHERE id = ?").get(taskId);
299
+ if (!row) throw new TaskNotFoundError(taskId);
300
+ const history = safeJsonParse(row.history) ?? [];
301
+ history.push(message);
302
+ db.prepare("UPDATE agent_tasks SET history = ?, updated_at = ? WHERE id = ?").run(
303
+ JSON.stringify(history),
304
+ nowISO(),
305
+ taskId
306
+ );
307
+ }
308
+ setRunId(taskId, runId) {
309
+ const db = this.getDb();
310
+ const result = db.prepare("UPDATE agent_tasks SET run_id = ?, updated_at = ? WHERE id = ?").run(runId, nowISO(), taskId);
311
+ if (result.changes === 0) throw new TaskNotFoundError(taskId);
312
+ }
313
+ // -------------------------------------------------------------------------
314
+ // Queue operations
315
+ // -------------------------------------------------------------------------
316
+ claimNextSubmitted(workerId) {
317
+ const db = this.getDb();
318
+ const now = nowISO();
319
+ const row = db.prepare(
320
+ `UPDATE agent_tasks
321
+ SET state = 'working', claimed_by = ?, claimed_at = ?, updated_at = ?
322
+ WHERE id = (
323
+ SELECT id FROM agent_tasks
324
+ WHERE state = 'submitted'
325
+ AND claimed_by IS NULL
326
+ ORDER BY created_at ASC
327
+ LIMIT 1
328
+ )
329
+ RETURNING *`
330
+ ).get(workerId, now, now);
331
+ return row ? taskRowToAgentTask(row) : null;
332
+ }
333
+ reclaimStaleTasks(_workerId, staleTimeoutMs) {
334
+ const db = this.getDb();
335
+ const now = nowISO();
336
+ const staleTimeoutSec = Math.floor((staleTimeoutMs ?? 3e5) / 1e3);
337
+ const rows = db.prepare(
338
+ `UPDATE agent_tasks
339
+ SET state = 'submitted', claimed_by = NULL, claimed_at = NULL, updated_at = ?
340
+ WHERE state = 'working'
341
+ AND claimed_at IS NOT NULL
342
+ AND claimed_at < datetime('now', '-' || ? || ' seconds')
343
+ RETURNING *`
344
+ ).all(now, staleTimeoutSec);
345
+ return rows.map(taskRowToAgentTask);
346
+ }
347
+ releaseClaim(taskId) {
348
+ const db = this.getDb();
349
+ db.prepare(
350
+ `UPDATE agent_tasks
351
+ SET state = 'submitted', claimed_by = NULL, claimed_at = NULL, updated_at = ?
352
+ WHERE id = ? AND state = 'working'`
353
+ ).run(nowISO(), taskId);
354
+ }
355
+ // -------------------------------------------------------------------------
356
+ // Cleanup
357
+ // -------------------------------------------------------------------------
358
+ failStaleTasks(reason) {
359
+ const db = this.getDb();
360
+ const now = nowISO();
361
+ const msg = reason || "Process terminated while task was running";
362
+ const statusMessage = JSON.stringify({
363
+ message_id: crypto.randomUUID(),
364
+ role: "agent",
365
+ parts: [{ text: msg }]
366
+ });
367
+ const result = db.prepare(
368
+ `UPDATE agent_tasks
369
+ SET state = 'failed', updated_at = ?, status_message = ?
370
+ WHERE state = 'working'`
371
+ ).run(now, statusMessage);
372
+ return result.changes;
373
+ }
374
+ failStaleTasksByAge(olderThanMs, reason) {
375
+ const db = this.getDb();
376
+ const now = nowISO();
377
+ const cutoff = new Date(Date.now() - olderThanMs).toISOString();
378
+ const msg = reason || "Task exceeded maximum working duration";
379
+ const statusMessage = JSON.stringify({
380
+ message_id: crypto.randomUUID(),
381
+ role: "agent",
382
+ parts: [{ text: msg }]
383
+ });
384
+ const result = db.prepare(
385
+ `UPDATE agent_tasks
386
+ SET state = 'failed', updated_at = ?, status_message = ?
387
+ WHERE state = 'working'
388
+ AND updated_at <= ?`
389
+ ).run(now, statusMessage, cutoff);
390
+ return result.changes;
391
+ }
392
+ purgeOldTasks(olderThanMs) {
393
+ const db = this.getDb();
394
+ const cutoff = new Date(Date.now() - olderThanMs).toISOString();
395
+ const result = db.prepare(
396
+ `DELETE FROM agent_tasks
397
+ WHERE state IN ('completed', 'failed', 'canceled', 'rejected')
398
+ AND updated_at <= ?`
399
+ ).run(cutoff);
400
+ return result.changes;
401
+ }
402
+ deleteExpiredTasks() {
403
+ const db = this.getDb();
404
+ const now = nowISO();
405
+ const rows = db.prepare("SELECT id FROM agent_tasks WHERE expires_at IS NOT NULL AND expires_at < ?").all(now);
406
+ if (rows.length > 0) {
407
+ db.prepare("DELETE FROM agent_tasks WHERE expires_at IS NOT NULL AND expires_at < ?").run(
408
+ now
409
+ );
410
+ }
411
+ return rows.map((r) => r.id);
412
+ }
413
+ deleteTask(taskId) {
414
+ const db = this.getDb();
415
+ db.prepare("DELETE FROM agent_tasks WHERE id = ?").run(taskId);
416
+ }
417
+ // -------------------------------------------------------------------------
418
+ // Context ID resolution
419
+ // -------------------------------------------------------------------------
420
+ resolveContextId(message, providedContextId) {
421
+ if (message.task_id) {
422
+ const existingTask = this.getTask(message.task_id);
423
+ if (existingTask) {
424
+ if (message.context_id && message.context_id !== existingTask.context_id) {
425
+ throw new ContextMismatchError(message.context_id, existingTask.context_id);
426
+ }
427
+ return existingTask.context_id;
428
+ }
429
+ }
430
+ return message.context_id ?? providedContextId;
431
+ }
432
+ };
433
+ }
434
+ });
435
+
436
+ // src/agent-protocol/task-stream-manager.ts
437
+ var TaskStreamManager;
438
+ var init_task_stream_manager = __esm({
439
+ "src/agent-protocol/task-stream-manager.ts"() {
440
+ "use strict";
441
+ init_state_transitions();
442
+ TaskStreamManager = class {
443
+ subscribers = /* @__PURE__ */ new Map();
444
+ keepaliveTimers = /* @__PURE__ */ new Map();
445
+ keepaliveIntervalMs;
446
+ constructor(keepaliveIntervalMs = 3e4) {
447
+ this.keepaliveIntervalMs = keepaliveIntervalMs;
448
+ }
449
+ /**
450
+ * Subscribe an HTTP response to SSE events for a task.
451
+ * Sets SSE headers and registers cleanup on disconnect.
452
+ */
453
+ subscribe(taskId, res) {
454
+ res.writeHead(200, {
455
+ "Content-Type": "text/event-stream",
456
+ "Cache-Control": "no-cache",
457
+ Connection: "keep-alive"
458
+ });
459
+ if (!this.subscribers.has(taskId)) {
460
+ this.subscribers.set(taskId, /* @__PURE__ */ new Set());
461
+ }
462
+ this.subscribers.get(taskId).add(res);
463
+ res.on("close", () => {
464
+ this.removeSubscriber(taskId, res);
465
+ });
466
+ const timer = setInterval(() => {
467
+ if (res.writable) {
468
+ res.write(": keepalive\n\n");
469
+ } else {
470
+ this.removeSubscriber(taskId, res);
471
+ }
472
+ }, this.keepaliveIntervalMs);
473
+ this.keepaliveTimers.set(res, timer);
474
+ }
475
+ /**
476
+ * Emit an event to all subscribers of a task.
477
+ * If the event is a terminal status update, closes all connections.
478
+ */
479
+ emit(taskId, event) {
480
+ const subs = this.subscribers.get(taskId);
481
+ if (!subs || subs.size === 0) return;
482
+ const data = `data: ${JSON.stringify(event)}
483
+
484
+ `;
485
+ for (const res of subs) {
486
+ if (res.writable) {
487
+ res.write(data);
488
+ }
489
+ }
490
+ if (event.type === "TaskStatusUpdateEvent" && isTerminalState(event.status.state)) {
491
+ for (const res of subs) {
492
+ this.clearKeepalive(res);
493
+ if (res.writable) {
494
+ res.end();
495
+ }
496
+ }
497
+ this.subscribers.delete(taskId);
498
+ }
499
+ }
500
+ /**
501
+ * Check if a task has any active subscribers.
502
+ */
503
+ hasSubscribers(taskId) {
504
+ return (this.subscribers.get(taskId)?.size ?? 0) > 0;
505
+ }
506
+ /**
507
+ * Get count of subscribers for a task.
508
+ */
509
+ getSubscriberCount(taskId) {
510
+ return this.subscribers.get(taskId)?.size ?? 0;
511
+ }
512
+ /**
513
+ * Close all connections and clean up.
514
+ */
515
+ shutdown() {
516
+ for (const [, subs] of this.subscribers) {
517
+ for (const res of subs) {
518
+ this.clearKeepalive(res);
519
+ if (res.writable) {
520
+ res.end();
521
+ }
522
+ }
523
+ subs.clear();
524
+ }
525
+ this.subscribers.clear();
526
+ }
527
+ removeSubscriber(taskId, res) {
528
+ this.clearKeepalive(res);
529
+ const subs = this.subscribers.get(taskId);
530
+ if (subs) {
531
+ subs.delete(res);
532
+ if (subs.size === 0) {
533
+ this.subscribers.delete(taskId);
534
+ }
535
+ }
536
+ }
537
+ clearKeepalive(res) {
538
+ const timer = this.keepaliveTimers.get(res);
539
+ if (timer) {
540
+ clearInterval(timer);
541
+ this.keepaliveTimers.delete(res);
542
+ }
543
+ }
544
+ };
545
+ }
546
+ });
547
+
548
+ // src/agent-protocol/push-notification-manager.ts
549
+ import crypto2 from "crypto";
550
+ var PushNotificationManager;
551
+ var init_push_notification_manager = __esm({
552
+ "src/agent-protocol/push-notification-manager.ts"() {
553
+ "use strict";
554
+ init_logger();
555
+ PushNotificationManager = class {
556
+ db = null;
557
+ maxRetries;
558
+ baseDelayMs;
559
+ deliveryTimeoutMs;
560
+ constructor(options) {
561
+ this.maxRetries = options?.maxRetries ?? 3;
562
+ this.baseDelayMs = options?.baseDelayMs ?? 1e3;
563
+ this.deliveryTimeoutMs = options?.deliveryTimeoutMs ?? 1e4;
564
+ }
565
+ /**
566
+ * Initialize with a database connection. Must be called before any operations.
567
+ * Creates the push configs table if it doesn't exist.
568
+ */
569
+ initialize(db) {
570
+ this.db = db;
571
+ db.exec("PRAGMA foreign_keys = ON");
572
+ this.migrateSchema();
573
+ }
574
+ migrateSchema() {
575
+ this.getDb().exec(`
576
+ CREATE TABLE IF NOT EXISTS agent_push_configs (
577
+ id TEXT PRIMARY KEY,
578
+ task_id TEXT NOT NULL,
579
+ url TEXT NOT NULL,
580
+ token TEXT,
581
+ auth_scheme TEXT,
582
+ auth_credentials TEXT,
583
+ created_at TEXT NOT NULL,
584
+ FOREIGN KEY (task_id) REFERENCES agent_tasks(id) ON DELETE CASCADE
585
+ );
586
+ CREATE INDEX IF NOT EXISTS idx_agent_push_task ON agent_push_configs(task_id);
587
+ `);
588
+ }
589
+ getDb() {
590
+ if (!this.db) throw new Error("PushNotificationManager not initialized");
591
+ return this.db;
592
+ }
593
+ // -------------------------------------------------------------------------
594
+ // CRUD
595
+ // -------------------------------------------------------------------------
596
+ create(config) {
597
+ const db = this.getDb();
598
+ const id = config.id ?? crypto2.randomUUID();
599
+ const now = (/* @__PURE__ */ new Date()).toISOString();
600
+ db.prepare(
601
+ `INSERT INTO agent_push_configs (id, task_id, url, token, auth_scheme, auth_credentials, created_at)
602
+ VALUES (?, ?, ?, ?, ?, ?, ?)`
603
+ ).run(
604
+ id,
605
+ config.task_id,
606
+ config.url,
607
+ config.token ?? null,
608
+ config.auth_scheme ?? null,
609
+ config.auth_credentials ?? null,
610
+ now
611
+ );
612
+ return { ...config, id };
613
+ }
614
+ get(taskId, configId) {
615
+ const db = this.getDb();
616
+ const row = db.prepare("SELECT * FROM agent_push_configs WHERE id = ? AND task_id = ?").get(configId, taskId);
617
+ return row ? this.rowToConfig(row) : null;
618
+ }
619
+ list(taskId) {
620
+ const db = this.getDb();
621
+ const rows = db.prepare("SELECT * FROM agent_push_configs WHERE task_id = ? ORDER BY created_at ASC").all(taskId);
622
+ return rows.map((r) => this.rowToConfig(r));
623
+ }
624
+ delete(taskId, configId) {
625
+ const db = this.getDb();
626
+ const result = db.prepare("DELETE FROM agent_push_configs WHERE id = ? AND task_id = ?").run(configId, taskId);
627
+ return result.changes > 0;
628
+ }
629
+ deleteForTask(taskId) {
630
+ const db = this.getDb();
631
+ const result = db.prepare("DELETE FROM agent_push_configs WHERE task_id = ?").run(taskId);
632
+ return result.changes;
633
+ }
634
+ // -------------------------------------------------------------------------
635
+ // Delivery
636
+ // -------------------------------------------------------------------------
637
+ /**
638
+ * Deliver an event to all push configs registered for a task.
639
+ * Uses exponential backoff retry for server errors.
640
+ */
641
+ async notifyAll(taskId, event) {
642
+ const configs = this.list(taskId);
643
+ if (configs.length === 0) return;
644
+ const deliveries = configs.map((config) => this.deliver(config, event));
645
+ await Promise.allSettled(deliveries);
646
+ }
647
+ async deliver(config, event) {
648
+ const headers = {
649
+ "Content-Type": "application/json"
650
+ };
651
+ if (config.auth_scheme && config.auth_credentials) {
652
+ headers["Authorization"] = `${config.auth_scheme} ${config.auth_credentials}`;
653
+ }
654
+ const body = JSON.stringify(event);
655
+ for (let attempt = 0; attempt < this.maxRetries; attempt++) {
656
+ try {
657
+ const resp = await fetch(config.url, {
658
+ method: "POST",
659
+ headers,
660
+ body,
661
+ signal: AbortSignal.timeout(this.deliveryTimeoutMs)
662
+ });
663
+ if (resp.ok) return;
664
+ if (resp.status >= 400 && resp.status < 500) {
665
+ logger.warn(`Push notification to ${config.url} returned ${resp.status}, not retrying`);
666
+ return;
667
+ }
668
+ } catch (err) {
669
+ logger.warn(
670
+ `Push notification delivery attempt ${attempt + 1} failed for ${config.url}: ${err instanceof Error ? err.message : String(err)}`
671
+ );
672
+ }
673
+ if (attempt < this.maxRetries - 1) {
674
+ await new Promise((r) => setTimeout(r, this.baseDelayMs * Math.pow(2, attempt)));
675
+ }
676
+ }
677
+ logger.error(
678
+ `Push notification delivery failed after ${this.maxRetries} attempts for task ${config.task_id} to ${config.url}`
679
+ );
680
+ }
681
+ rowToConfig(row) {
682
+ return {
683
+ id: row.id,
684
+ task_id: row.task_id,
685
+ url: row.url,
686
+ token: row.token ?? void 0,
687
+ auth_scheme: row.auth_scheme ?? void 0,
688
+ auth_credentials: row.auth_credentials ?? void 0
689
+ };
690
+ }
691
+ };
692
+ }
693
+ });
694
+
695
+ // src/agent-protocol/task-queue.ts
696
+ import crypto3 from "crypto";
697
+ var DEFAULT_CONFIG, TaskQueue;
698
+ var init_task_queue = __esm({
699
+ "src/agent-protocol/task-queue.ts"() {
700
+ "use strict";
701
+ init_logger();
702
+ init_trace_helpers();
703
+ DEFAULT_CONFIG = {
704
+ pollInterval: 1e3,
705
+ maxConcurrent: 5,
706
+ staleClaimTimeout: 3e5
707
+ };
708
+ TaskQueue = class {
709
+ constructor(taskStore, executor, _eventBus, config, workerId) {
710
+ this.taskStore = taskStore;
711
+ this.executor = executor;
712
+ this.config = { ...DEFAULT_CONFIG, ...config };
713
+ this.workerId = workerId ?? crypto3.randomUUID();
714
+ }
715
+ running = false;
716
+ timer = null;
717
+ activeCount = 0;
718
+ config;
719
+ workerId;
720
+ start() {
721
+ this.running = true;
722
+ this.schedulePoll();
723
+ logger.info(
724
+ `Task queue started (worker=${this.workerId}, maxConcurrent=${this.config.maxConcurrent})`
725
+ );
726
+ }
727
+ async stop() {
728
+ this.running = false;
729
+ if (this.timer) {
730
+ clearTimeout(this.timer);
731
+ this.timer = null;
732
+ }
733
+ const shutdownTimeout = 3e4;
734
+ const start = Date.now();
735
+ while (this.activeCount > 0 && Date.now() - start < shutdownTimeout) {
736
+ await new Promise((r) => setTimeout(r, 100));
737
+ }
738
+ if (this.activeCount > 0) {
739
+ logger.warn(`Task queue stopped with ${this.activeCount} active tasks`);
740
+ }
741
+ }
742
+ getActiveCount() {
743
+ return this.activeCount;
744
+ }
745
+ isRunning() {
746
+ return this.running;
747
+ }
748
+ schedulePoll() {
749
+ if (!this.running) return;
750
+ this.timer = setTimeout(() => this.poll(), this.config.pollInterval);
751
+ }
752
+ async poll() {
753
+ if (!this.running) return;
754
+ try {
755
+ this.taskStore.reclaimStaleTasks(this.workerId, this.config.staleClaimTimeout);
756
+ while (this.activeCount < this.config.maxConcurrent) {
757
+ const task = this.taskStore.claimNextSubmitted(this.workerId);
758
+ if (!task) break;
759
+ this.activeCount++;
760
+ this.executeTask(task).finally(() => {
761
+ this.activeCount--;
762
+ });
763
+ }
764
+ } catch (err) {
765
+ logger.error(`Task queue poll error: ${err instanceof Error ? err.message : String(err)}`);
766
+ }
767
+ this.schedulePoll();
768
+ }
769
+ async executeTask(task) {
770
+ await withActiveSpan(
771
+ "agent.queue.execute",
772
+ {
773
+ "agent.task.id": task.id,
774
+ "agent.queue.worker_id": this.workerId
775
+ },
776
+ async () => this._executeTaskInner(task)
777
+ );
778
+ }
779
+ async _executeTaskInner(task) {
780
+ try {
781
+ const result = await this.executor(task);
782
+ if (result.stateAlreadySet) {
783
+ return;
784
+ }
785
+ if (result.success) {
786
+ const completedMsg = {
787
+ message_id: crypto3.randomUUID(),
788
+ role: "agent",
789
+ parts: [
790
+ {
791
+ text: result.summary ?? "Task completed successfully",
792
+ media_type: "text/markdown"
793
+ }
794
+ ]
795
+ };
796
+ this.taskStore.updateTaskState(task.id, "completed", completedMsg);
797
+ } else {
798
+ this.taskStore.updateTaskState(task.id, "failed", {
799
+ message_id: crypto3.randomUUID(),
800
+ role: "agent",
801
+ parts: [{ text: result.error ?? "Task execution failed" }]
802
+ });
803
+ }
804
+ } catch (err) {
805
+ logger.error(
806
+ `Task ${task.id} execution failed: ${err instanceof Error ? err.message : String(err)}`
807
+ );
808
+ try {
809
+ this.taskStore.updateTaskState(task.id, "failed", {
810
+ message_id: crypto3.randomUUID(),
811
+ role: "agent",
812
+ parts: [{ text: err instanceof Error ? err.message : "Unknown error" }]
813
+ });
814
+ } catch {
815
+ }
816
+ }
817
+ }
818
+ };
819
+ }
820
+ });
821
+
822
+ // src/agent-protocol/a2a-frontend.ts
823
+ import http from "http";
824
+ import https from "https";
825
+ import fs2 from "fs";
826
+ import crypto4 from "crypto";
827
+ function readJsonBody(req) {
828
+ return new Promise((resolve, reject) => {
829
+ const chunks = [];
830
+ req.on("data", (chunk) => chunks.push(chunk));
831
+ req.on("end", () => {
832
+ try {
833
+ const body = Buffer.concat(chunks).toString("utf8");
834
+ resolve(body ? JSON.parse(body) : {});
835
+ } catch {
836
+ reject(new ParseError("Malformed JSON body"));
837
+ }
838
+ });
839
+ req.on("error", reject);
840
+ });
841
+ }
842
+ function sendJson(res, status, data) {
843
+ const body = JSON.stringify(data);
844
+ res.writeHead(status, {
845
+ "Content-Type": "application/json",
846
+ "Content-Length": Buffer.byteLength(body)
847
+ });
848
+ res.end(body);
849
+ }
850
+ function sendError(res, httpStatus, message, code) {
851
+ sendJson(res, httpStatus, {
852
+ error: {
853
+ code: code ?? -httpStatus,
854
+ message
855
+ }
856
+ });
857
+ }
858
+ function timingSafeEqual(a, b) {
859
+ if (!a || !b) return false;
860
+ if (a.length !== b.length) return false;
861
+ try {
862
+ return crypto4.timingSafeEqual(Buffer.from(a), Buffer.from(b));
863
+ } catch {
864
+ return false;
865
+ }
866
+ }
867
+ function validateAuth(req, config) {
868
+ if (!config.auth || config.auth.type === "none") return true;
869
+ if (config.auth.type === "bearer") {
870
+ const header = req.headers["authorization"];
871
+ if (!header || !header.startsWith("Bearer ")) return false;
872
+ const token = header.slice(7);
873
+ const expected = config.auth.token_env ? process.env[config.auth.token_env] : void 0;
874
+ return timingSafeEqual(token, expected);
875
+ }
876
+ if (config.auth.type === "api_key") {
877
+ const headerName = config.auth.header_name ?? "x-api-key";
878
+ const key = req.headers[headerName.toLowerCase()];
879
+ const expected = config.auth.key_env ? process.env[config.auth.key_env] : void 0;
880
+ if (timingSafeEqual(key, expected)) return true;
881
+ const paramName = config.auth.param_name ?? "api_key";
882
+ const url = new URL(req.url || "", "http://localhost");
883
+ const queryKey = url.searchParams.get(paramName) ?? void 0;
884
+ return timingSafeEqual(queryKey, expected);
885
+ }
886
+ return false;
887
+ }
888
+ function resolveWorkflow(req, config) {
889
+ if (config.skill_routing && Object.keys(config.skill_routing).length > 0) {
890
+ const requestedSkill = req.metadata?.skill_id;
891
+ if (requestedSkill && config.skill_routing[requestedSkill]) {
892
+ return config.skill_routing[requestedSkill];
893
+ }
894
+ }
895
+ return config.default_workflow ?? "assistant";
896
+ }
897
+ function messageToWorkflowInput(message, task) {
898
+ const textContent = message.parts.filter((p) => p.text != null).map((p) => p.text).join("\n");
899
+ const dataParts = message.parts.filter((p) => p.data != null);
900
+ const structuredData = dataParts.length === 1 ? dataParts[0].data : dataParts.length > 1 ? dataParts.map((p) => p.data) : void 0;
901
+ const fileParts = message.parts.filter((p) => p.url != null || p.raw != null);
902
+ return {
903
+ question: textContent,
904
+ task: textContent,
905
+ data: structuredData,
906
+ files: fileParts.length > 0 ? fileParts : void 0,
907
+ _agent: {
908
+ task_id: task.id,
909
+ context_id: task.context_id,
910
+ message_id: message.message_id,
911
+ metadata: message.metadata
912
+ }
913
+ };
914
+ }
915
+ function resultToArtifacts(checkResults) {
916
+ const artifacts = [];
917
+ for (const [checkId, checkResult] of Object.entries(checkResults ?? {})) {
918
+ if (!checkResult || typeof checkResult !== "object") continue;
919
+ const cr = checkResult;
920
+ if (cr.status === "skipped") continue;
921
+ const parts = [];
922
+ if (typeof cr.output === "string") {
923
+ parts.push({ text: cr.output, media_type: "text/markdown" });
924
+ } else if (typeof cr.output === "object" && cr.output !== null) {
925
+ const output = cr.output;
926
+ if ("text" in output && typeof output.text === "string") {
927
+ parts.push({ text: output.text, media_type: "text/markdown" });
928
+ }
929
+ parts.push({ data: cr.output, media_type: "application/json" });
930
+ }
931
+ if (Array.isArray(cr.issues) && cr.issues.length > 0) {
932
+ parts.push({ data: cr.issues, media_type: "application/json" });
933
+ }
934
+ if (parts.length > 0) {
935
+ artifacts.push({
936
+ artifact_id: crypto4.randomUUID(),
937
+ name: checkId,
938
+ description: `Output from check: ${checkId}`,
939
+ parts
940
+ });
941
+ }
942
+ }
943
+ return artifacts;
944
+ }
945
+ var A2AFrontend;
946
+ var init_a2a_frontend = __esm({
947
+ "src/agent-protocol/a2a-frontend.ts"() {
948
+ init_logger();
949
+ init_task_store();
950
+ init_types();
951
+ init_state_transitions();
952
+ init_task_stream_manager();
953
+ init_push_notification_manager();
954
+ init_task_queue();
955
+ init_trace_helpers();
956
+ A2AFrontend = class {
957
+ name = "a2a";
958
+ server = null;
959
+ taskStore;
960
+ agentCard = null;
961
+ config;
962
+ cleanupTimer = null;
963
+ _ctx = null;
964
+ streamManager = new TaskStreamManager();
965
+ pushManager = new PushNotificationManager();
966
+ _engine = null;
967
+ // StateMachineExecutionEngine
968
+ _visorConfig = null;
969
+ taskQueue = null;
970
+ _boundPort = 0;
971
+ constructor(config, taskStore) {
972
+ this.config = config;
973
+ this.taskStore = taskStore ?? new SqliteTaskStore();
974
+ }
975
+ /** The actual port the server is listening on (useful when config.port is 0). */
976
+ get boundPort() {
977
+ return this._boundPort;
978
+ }
979
+ /** Set the execution engine for running workflows. */
980
+ setEngine(engine) {
981
+ this._engine = engine;
982
+ }
983
+ /** Set the full Visor config (needed for check definitions). */
984
+ setVisorConfig(config) {
985
+ this._visorConfig = config;
986
+ }
987
+ async start(ctx) {
988
+ this._ctx = ctx;
989
+ await this.taskStore.initialize();
990
+ const db = this.taskStore.getDatabase?.();
991
+ if (db) {
992
+ this.pushManager.initialize(db);
993
+ }
994
+ if (ctx.engine) this._engine = ctx.engine;
995
+ if (ctx.visorConfig) this._visorConfig = ctx.visorConfig;
996
+ if (this.config.agent_card) {
997
+ const cardPath = this.config.agent_card;
998
+ const raw = fs2.readFileSync(cardPath, "utf8");
999
+ this.agentCard = JSON.parse(raw);
1000
+ } else if (this.config.agent_card_inline) {
1001
+ this.agentCard = { ...this.config.agent_card_inline };
1002
+ }
1003
+ const handler = this.handleRequest.bind(this);
1004
+ if (this.config.tls) {
1005
+ const tlsOptions = {
1006
+ cert: fs2.readFileSync(this.config.tls.cert),
1007
+ key: fs2.readFileSync(this.config.tls.key)
1008
+ };
1009
+ this.server = https.createServer(tlsOptions, handler);
1010
+ } else {
1011
+ this.server = http.createServer(handler);
1012
+ }
1013
+ ctx.eventBus.on("CheckCompleted", (event) => {
1014
+ const envelope = event?.payload ? event : { payload: event };
1015
+ const taskId = envelope.metadata?.agentTaskId;
1016
+ if (!taskId) return;
1017
+ try {
1018
+ const artifact = this.checkResultToArtifact(envelope.payload);
1019
+ if (artifact) {
1020
+ this.taskStore.addArtifact(taskId, artifact);
1021
+ const task = this.taskStore.getTask(taskId);
1022
+ if (task) {
1023
+ this.emitArtifactEvent(taskId, task.context_id, artifact, false, false);
1024
+ }
1025
+ }
1026
+ } catch {
1027
+ }
1028
+ });
1029
+ ctx.eventBus.on("CheckErrored", (event) => {
1030
+ const envelope = event?.payload ? event : { payload: event };
1031
+ const taskId = envelope.metadata?.agentTaskId;
1032
+ if (!taskId) return;
1033
+ logger.warn(`Agent task ${taskId}: check errored`);
1034
+ });
1035
+ ctx.eventBus.on("HumanInputRequested", (event) => {
1036
+ const envelope = event?.payload ? event : { payload: event };
1037
+ const taskId = envelope.metadata?.agentTaskId;
1038
+ if (!taskId) return;
1039
+ try {
1040
+ const statusMessage = {
1041
+ message_id: crypto4.randomUUID(),
1042
+ role: "agent",
1043
+ parts: [{ text: envelope.payload?.prompt ?? "Agent requires input" }]
1044
+ };
1045
+ this.taskStore.updateTaskState(taskId, "input_required", statusMessage);
1046
+ const task = this.taskStore.getTask(taskId);
1047
+ if (task) {
1048
+ this.emitStatusEvent(taskId, task.context_id, task.status);
1049
+ }
1050
+ } catch {
1051
+ }
1052
+ });
1053
+ this.startCleanupSweep();
1054
+ if (this._engine && this._visorConfig) {
1055
+ const executor = this.createTaskExecutor();
1056
+ const queueCfg = this.config.queue;
1057
+ this.taskQueue = new TaskQueue(
1058
+ this.taskStore,
1059
+ executor,
1060
+ null,
1061
+ queueCfg ? {
1062
+ pollInterval: queueCfg.poll_interval,
1063
+ maxConcurrent: queueCfg.max_concurrent,
1064
+ staleClaimTimeout: queueCfg.stale_claim_timeout
1065
+ } : void 0
1066
+ );
1067
+ this.taskQueue.start();
1068
+ logger.info("[A2A] TaskQueue started for async task execution");
1069
+ }
1070
+ const port = this.config.port ?? 9e3;
1071
+ const host = this.config.host ?? "0.0.0.0";
1072
+ await new Promise((resolve) => {
1073
+ this.server.listen(port, host, () => {
1074
+ const addr = this.server.address();
1075
+ this._boundPort = typeof addr === "object" && addr ? addr.port : port;
1076
+ logger.info(`A2A server listening on ${host}:${this._boundPort}`);
1077
+ resolve();
1078
+ });
1079
+ });
1080
+ if (this.agentCard) {
1081
+ const publicUrl = this.config.public_url ?? `http://${host}:${this._boundPort}`;
1082
+ if (!this.agentCard.supported_interfaces?.length) {
1083
+ this.agentCard.supported_interfaces = [{ url: publicUrl, protocol_binding: "a2a/v1" }];
1084
+ } else {
1085
+ for (const iface of this.agentCard.supported_interfaces) {
1086
+ if (iface.protocol_binding === "a2a/v1" || !iface.url || !iface.url.startsWith("http") || iface.url.includes("localhost") || iface.url.includes("0.0.0.0")) {
1087
+ iface.url = publicUrl;
1088
+ }
1089
+ }
1090
+ }
1091
+ }
1092
+ }
1093
+ async stop() {
1094
+ this.stopCleanupSweep();
1095
+ if (this.taskQueue) {
1096
+ this.taskQueue.stop();
1097
+ this.taskQueue = null;
1098
+ }
1099
+ this.streamManager.shutdown();
1100
+ if (this.server) {
1101
+ await new Promise((resolve, reject) => {
1102
+ this.server.close((err) => err ? reject(err) : resolve());
1103
+ });
1104
+ this.server = null;
1105
+ }
1106
+ await this.taskStore.shutdown();
1107
+ }
1108
+ // -------------------------------------------------------------------------
1109
+ // HTTP request dispatcher
1110
+ // -------------------------------------------------------------------------
1111
+ async handleRequest(req, res) {
1112
+ const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
1113
+ if (url.pathname === "/.well-known/agent-card.json" && req.method === "GET") {
1114
+ return this.serveAgentCard(res);
1115
+ }
1116
+ if (!validateAuth(req, this.config)) {
1117
+ return sendError(res, 401, "Unauthorized");
1118
+ }
1119
+ try {
1120
+ if (url.pathname === "/message:send" && req.method === "POST") {
1121
+ return await this.handleSendMessage(req, res);
1122
+ }
1123
+ if (url.pathname === "/message:stream" && req.method === "POST") {
1124
+ if (!this.agentCard?.capabilities?.streaming) {
1125
+ return sendError(res, 400, "Streaming not supported", -32002);
1126
+ }
1127
+ return await this.handleSendStreamingMessage(req, res);
1128
+ }
1129
+ const taskMatch = url.pathname.match(/^\/tasks\/([^/:]+)$/);
1130
+ if (taskMatch && req.method === "GET") {
1131
+ return this.handleGetTask(taskMatch[1], res);
1132
+ }
1133
+ if (url.pathname === "/tasks" && req.method === "GET") {
1134
+ return this.handleListTasks(url.searchParams, res);
1135
+ }
1136
+ const cancelMatch = url.pathname.match(/^\/tasks\/([^/:]+):cancel$/);
1137
+ if (cancelMatch && req.method === "POST") {
1138
+ return this.handleCancelTask(cancelMatch[1], res);
1139
+ }
1140
+ const subscribeMatch = url.pathname.match(/^\/tasks\/([^/:]+):subscribe$/);
1141
+ if (subscribeMatch && req.method === "GET") {
1142
+ if (!this.agentCard?.capabilities?.streaming) {
1143
+ return sendError(res, 400, "Streaming not supported", -32002);
1144
+ }
1145
+ return this.handleSubscribeToTask(subscribeMatch[1], res);
1146
+ }
1147
+ const pushListMatch = url.pathname.match(/^\/tasks\/([^/:]+)\/pushNotificationConfigs$/);
1148
+ if (pushListMatch) {
1149
+ if (!this.agentCard?.capabilities?.push_notifications) {
1150
+ return sendError(res, 400, "Push notifications not supported", -32002);
1151
+ }
1152
+ if (req.method === "POST") {
1153
+ return await this.handleCreatePushConfig(pushListMatch[1], req, res);
1154
+ }
1155
+ if (req.method === "GET") {
1156
+ return this.handleListPushConfigs(pushListMatch[1], res);
1157
+ }
1158
+ }
1159
+ const pushDetailMatch = url.pathname.match(
1160
+ /^\/tasks\/([^/:]+)\/pushNotificationConfigs\/([^/:]+)$/
1161
+ );
1162
+ if (pushDetailMatch) {
1163
+ if (!this.agentCard?.capabilities?.push_notifications) {
1164
+ return sendError(res, 400, "Push notifications not supported", -32002);
1165
+ }
1166
+ if (req.method === "GET") {
1167
+ return this.handleGetPushConfig(pushDetailMatch[1], pushDetailMatch[2], res);
1168
+ }
1169
+ if (req.method === "DELETE") {
1170
+ return this.handleDeletePushConfig(pushDetailMatch[1], pushDetailMatch[2], res);
1171
+ }
1172
+ }
1173
+ sendError(res, 404, "MethodNotFound", -32601);
1174
+ } catch (err) {
1175
+ if (err instanceof ParseError) {
1176
+ return sendError(res, 400, err.message, -32700);
1177
+ }
1178
+ if (err instanceof TaskNotFoundError) {
1179
+ return sendError(res, 404, err.message, -32001);
1180
+ }
1181
+ if (err instanceof InvalidStateTransitionError) {
1182
+ return sendError(res, 409, err.message, -32003);
1183
+ }
1184
+ if (err instanceof InvalidRequestError) {
1185
+ return sendError(res, 400, err.message, -32600);
1186
+ }
1187
+ if (err instanceof ContextMismatchError) {
1188
+ return sendError(res, 400, err.message, -32600);
1189
+ }
1190
+ logger.error(`A2A request error: ${err instanceof Error ? err.message : String(err)}`);
1191
+ sendError(res, 500, "Internal error");
1192
+ }
1193
+ }
1194
+ // -------------------------------------------------------------------------
1195
+ // Endpoint handlers
1196
+ // -------------------------------------------------------------------------
1197
+ serveAgentCard(res) {
1198
+ if (!this.agentCard) {
1199
+ return sendError(res, 404, "Agent Card not configured");
1200
+ }
1201
+ sendJson(res, 200, this.agentCard);
1202
+ }
1203
+ async handleSendMessage(req, res) {
1204
+ const body = await readJsonBody(req);
1205
+ if (!body.message?.parts?.length) {
1206
+ throw new InvalidRequestError("Message must contain at least one part");
1207
+ }
1208
+ const existingTaskId = body.message.task_id;
1209
+ if (existingTaskId) {
1210
+ const response = await this.handleFollowUpMessage(existingTaskId, body);
1211
+ return sendJson(res, 200, response);
1212
+ }
1213
+ const contextId = body.message.context_id ?? crypto4.randomUUID();
1214
+ const workflowId = resolveWorkflow(body, this.config);
1215
+ const blocking = body.configuration?.blocking ?? false;
1216
+ await withActiveSpan(
1217
+ "agent.task",
1218
+ {
1219
+ "agent.task.context_id": contextId,
1220
+ "agent.task.workflow": workflowId,
1221
+ "agent.task.blocking": blocking,
1222
+ "agent.task.message_id": body.message.message_id ?? ""
1223
+ },
1224
+ async () => {
1225
+ const task = this.taskStore.createTask({
1226
+ contextId,
1227
+ requestMessage: body.message,
1228
+ requestConfig: body.configuration,
1229
+ requestMetadata: body.metadata,
1230
+ workflowId
1231
+ });
1232
+ this.taskStore.appendHistory(task.id, body.message);
1233
+ if (blocking) {
1234
+ await this.executeTaskDirectly(task, body.message);
1235
+ let finalTask = this.taskStore.getTask(task.id);
1236
+ const historyLength = body.configuration?.history_length;
1237
+ if (historyLength !== void 0) {
1238
+ finalTask = {
1239
+ ...finalTask,
1240
+ history: finalTask.history.slice(-historyLength)
1241
+ };
1242
+ }
1243
+ if (body.configuration?.accepted_output_modes?.length) {
1244
+ finalTask = this.filterOutputModes(finalTask, body.configuration.accepted_output_modes);
1245
+ }
1246
+ return sendJson(res, 200, { task: finalTask });
1247
+ }
1248
+ return sendJson(res, 200, { task: this.taskStore.getTask(task.id) });
1249
+ }
1250
+ );
1251
+ }
1252
+ async handleFollowUpMessage(taskId, req) {
1253
+ const task = this.taskStore.getTask(taskId);
1254
+ if (!task) throw new TaskNotFoundError(taskId);
1255
+ if (task.status.state !== "input_required" && task.status.state !== "auth_required") {
1256
+ throw new InvalidStateTransitionError(
1257
+ task.status.state,
1258
+ "working",
1259
+ "Task is not awaiting input"
1260
+ );
1261
+ }
1262
+ if (req.message.context_id && req.message.context_id !== task.context_id) {
1263
+ throw new ContextMismatchError(req.message.context_id, task.context_id);
1264
+ }
1265
+ this.taskStore.appendHistory(taskId, req.message);
1266
+ this.taskStore.updateTaskState(taskId, "working");
1267
+ this.emitStatusEvent(taskId, task.context_id, {
1268
+ state: "working",
1269
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1270
+ });
1271
+ const textContent = req.message.parts.filter((p) => p.text != null).map((p) => p.text).join("\n");
1272
+ if (this._ctx?.eventBus) {
1273
+ await this._ctx.eventBus.emit({
1274
+ type: "HumanInputReceived",
1275
+ taskId,
1276
+ message: textContent
1277
+ });
1278
+ }
1279
+ const blocking = req.configuration?.blocking ?? false;
1280
+ if (blocking) {
1281
+ await this.executeTaskDirectly(task, req.message);
1282
+ let finalTask = this.taskStore.getTask(taskId);
1283
+ const historyLength = req.configuration?.history_length;
1284
+ if (historyLength !== void 0) {
1285
+ finalTask = {
1286
+ ...finalTask,
1287
+ history: finalTask.history.slice(-historyLength)
1288
+ };
1289
+ }
1290
+ return { task: finalTask };
1291
+ }
1292
+ const updatedTask = this.taskStore.getTask(taskId);
1293
+ this.executeTaskDirectly(updatedTask, req.message).catch(() => {
1294
+ });
1295
+ return { task: this.taskStore.getTask(taskId) };
1296
+ }
1297
+ handleGetTask(taskId, res) {
1298
+ const task = this.taskStore.getTask(taskId);
1299
+ if (!task) throw new TaskNotFoundError(taskId);
1300
+ sendJson(res, 200, task);
1301
+ }
1302
+ handleListTasks(params, res) {
1303
+ const contextId = params.get("context_id") ?? void 0;
1304
+ const stateParam = params.get("state");
1305
+ const state = stateParam ? stateParam.split(",") : void 0;
1306
+ const limit = params.has("limit") ? parseInt(params.get("limit"), 10) : void 0;
1307
+ const offset = params.has("offset") ? parseInt(params.get("offset"), 10) : void 0;
1308
+ const result = this.taskStore.listTasks({ contextId, state, limit, offset });
1309
+ sendJson(res, 200, result);
1310
+ }
1311
+ handleCancelTask(taskId, res) {
1312
+ const task = this.taskStore.getTask(taskId);
1313
+ if (!task) throw new TaskNotFoundError(taskId);
1314
+ this.taskStore.updateTaskState(taskId, "canceled");
1315
+ const updated = this.taskStore.getTask(taskId);
1316
+ sendJson(res, 200, updated);
1317
+ }
1318
+ // -------------------------------------------------------------------------
1319
+ // Streaming handlers
1320
+ // -------------------------------------------------------------------------
1321
+ async handleSendStreamingMessage(req, res) {
1322
+ const body = await readJsonBody(req);
1323
+ if (!body.message?.parts?.length) {
1324
+ throw new InvalidRequestError("Message must contain at least one part");
1325
+ }
1326
+ const contextId = body.message.context_id ?? crypto4.randomUUID();
1327
+ const workflowId = resolveWorkflow(body, this.config);
1328
+ const task = this.taskStore.createTask({
1329
+ contextId,
1330
+ requestMessage: body.message,
1331
+ requestConfig: body.configuration,
1332
+ requestMetadata: body.metadata,
1333
+ workflowId
1334
+ });
1335
+ this.taskStore.appendHistory(task.id, body.message);
1336
+ this.streamManager.subscribe(task.id, res);
1337
+ this.emitStatusEvent(task.id, contextId, task.status);
1338
+ this.executeTaskDirectly(task, body.message).then(() => {
1339
+ const finalTask = this.taskStore.getTask(task.id);
1340
+ if (finalTask) {
1341
+ this.emitStatusEvent(task.id, contextId, finalTask.status);
1342
+ }
1343
+ }).catch(() => {
1344
+ });
1345
+ }
1346
+ handleSubscribeToTask(taskId, res) {
1347
+ const task = this.taskStore.getTask(taskId);
1348
+ if (!task) throw new TaskNotFoundError(taskId);
1349
+ if (isTerminalState(task.status.state)) {
1350
+ res.writeHead(200, {
1351
+ "Content-Type": "text/event-stream",
1352
+ "Cache-Control": "no-cache",
1353
+ Connection: "keep-alive"
1354
+ });
1355
+ const event = {
1356
+ type: "TaskStatusUpdateEvent",
1357
+ task_id: taskId,
1358
+ context_id: task.context_id,
1359
+ status: task.status
1360
+ };
1361
+ res.write(`data: ${JSON.stringify(event)}
1362
+
1363
+ `);
1364
+ res.end();
1365
+ return;
1366
+ }
1367
+ this.streamManager.subscribe(taskId, res);
1368
+ this.emitStatusEvent(taskId, task.context_id, task.status);
1369
+ }
1370
+ // -------------------------------------------------------------------------
1371
+ // Push notification handlers
1372
+ // -------------------------------------------------------------------------
1373
+ async handleCreatePushConfig(taskId, req, res) {
1374
+ const task = this.taskStore.getTask(taskId);
1375
+ if (!task) throw new TaskNotFoundError(taskId);
1376
+ const body = await readJsonBody(req);
1377
+ if (!body.url) {
1378
+ throw new InvalidRequestError("Push notification config must include url");
1379
+ }
1380
+ const config = this.pushManager.create({
1381
+ task_id: taskId,
1382
+ url: body.url,
1383
+ token: body.token,
1384
+ auth_scheme: body.auth_scheme,
1385
+ auth_credentials: body.auth_credentials
1386
+ });
1387
+ sendJson(res, 200, config);
1388
+ }
1389
+ handleListPushConfigs(taskId, res) {
1390
+ const task = this.taskStore.getTask(taskId);
1391
+ if (!task) throw new TaskNotFoundError(taskId);
1392
+ const configs = this.pushManager.list(taskId);
1393
+ sendJson(res, 200, { configs });
1394
+ }
1395
+ handleGetPushConfig(taskId, configId, res) {
1396
+ const config = this.pushManager.get(taskId, configId);
1397
+ if (!config) {
1398
+ return sendError(res, 404, "Push notification config not found", -32001);
1399
+ }
1400
+ sendJson(res, 200, config);
1401
+ }
1402
+ handleDeletePushConfig(taskId, configId, res) {
1403
+ const deleted = this.pushManager.delete(taskId, configId);
1404
+ if (!deleted) {
1405
+ return sendError(res, 404, "Push notification config not found", -32001);
1406
+ }
1407
+ sendJson(res, 200, { deleted: true });
1408
+ }
1409
+ // -------------------------------------------------------------------------
1410
+ // Event emission helpers
1411
+ // -------------------------------------------------------------------------
1412
+ /** Emit a task status update to SSE subscribers and push notification targets. */
1413
+ emitStatusEvent(taskId, contextId, status) {
1414
+ const event = {
1415
+ type: "TaskStatusUpdateEvent",
1416
+ task_id: taskId,
1417
+ context_id: contextId,
1418
+ status
1419
+ };
1420
+ this.streamManager.emit(taskId, event);
1421
+ this.pushManager.notifyAll(taskId, event).catch((err) => {
1422
+ logger.error(`Push notification error: ${err instanceof Error ? err.message : String(err)}`);
1423
+ });
1424
+ }
1425
+ /** Emit an artifact update to SSE subscribers and push notification targets. */
1426
+ emitArtifactEvent(taskId, contextId, artifact, append, lastChunk) {
1427
+ const event = {
1428
+ type: "TaskArtifactUpdateEvent",
1429
+ task_id: taskId,
1430
+ context_id: contextId,
1431
+ artifact,
1432
+ append,
1433
+ last_chunk: lastChunk
1434
+ };
1435
+ this.streamManager.emit(taskId, event);
1436
+ this.pushManager.notifyAll(taskId, event).catch((err) => {
1437
+ logger.error(`Push notification error: ${err instanceof Error ? err.message : String(err)}`);
1438
+ });
1439
+ }
1440
+ // -------------------------------------------------------------------------
1441
+ // Execution
1442
+ // -------------------------------------------------------------------------
1443
+ /**
1444
+ * Execute a task, either via the engine (if available) or with a stub response (for tests).
1445
+ */
1446
+ async executeTaskDirectly(task, message) {
1447
+ try {
1448
+ const currentTask = this.taskStore.getTask(task.id);
1449
+ if (currentTask && currentTask.status.state !== "working") {
1450
+ this.taskStore.updateTaskState(task.id, "working");
1451
+ this.emitStatusEvent(task.id, task.context_id, {
1452
+ state: "working",
1453
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1454
+ });
1455
+ }
1456
+ if (this._engine && this._visorConfig) {
1457
+ await this.executeTaskViaEngine(task, message);
1458
+ } else {
1459
+ const agentResponse = {
1460
+ message_id: crypto4.randomUUID(),
1461
+ role: "agent",
1462
+ parts: [{ text: `Task ${task.id} received and processed.`, media_type: "text/markdown" }]
1463
+ };
1464
+ this.taskStore.appendHistory(task.id, agentResponse);
1465
+ this.taskStore.updateTaskState(task.id, "completed", agentResponse);
1466
+ }
1467
+ } catch (err) {
1468
+ const errorMsg = err instanceof Error ? err.message : "Unknown error";
1469
+ logger.error(`[A2A] Task ${task.id} execution failed: ${errorMsg}`);
1470
+ try {
1471
+ const failMessage = {
1472
+ message_id: crypto4.randomUUID(),
1473
+ role: "agent",
1474
+ parts: [{ text: errorMsg }]
1475
+ };
1476
+ this.taskStore.updateTaskState(task.id, "failed", failMessage);
1477
+ this.emitStatusEvent(task.id, task.context_id, {
1478
+ state: "failed",
1479
+ message: failMessage,
1480
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1481
+ });
1482
+ } catch {
1483
+ }
1484
+ }
1485
+ }
1486
+ /**
1487
+ * Execute a task through the Visor engine.
1488
+ * Creates a fresh engine execution per task and converts results to artifacts.
1489
+ */
1490
+ async executeTaskViaEngine(task, message) {
1491
+ const workflowId = task.workflow_id ?? this.config.default_workflow;
1492
+ const checks = workflowId ? [workflowId] : ["all"];
1493
+ const workflowInputs = messageToWorkflowInput(message, task);
1494
+ const prevCtx = this._engine.getExecutionContext?.() || {};
1495
+ this._engine.setExecutionContext?.({ ...prevCtx, workflowInputs });
1496
+ const execOptions = {
1497
+ checks,
1498
+ config: this._visorConfig,
1499
+ timeout: 3e5
1500
+ };
1501
+ const result = await this._engine.executeChecks(execOptions);
1502
+ const groupedResults = result?.executionStatistics?.groupedResults ?? result?.results ?? {};
1503
+ const artifacts = [];
1504
+ if (result?.reviewSummary) {
1505
+ const summaryParts = [];
1506
+ if (result.reviewSummary.issues?.length) {
1507
+ const issueText = result.reviewSummary.issues.map((i) => `- **${i.severity}**: ${i.message} (${i.file}:${i.line})`).join("\n");
1508
+ summaryParts.push({ text: `## Issues Found
1509
+
1510
+ ${issueText}`, media_type: "text/markdown" });
1511
+ summaryParts.push({ data: result.reviewSummary.issues, media_type: "application/json" });
1512
+ }
1513
+ if (summaryParts.length > 0) {
1514
+ artifacts.push({
1515
+ artifact_id: crypto4.randomUUID(),
1516
+ name: "review-summary",
1517
+ description: "Review summary with issues found",
1518
+ parts: summaryParts
1519
+ });
1520
+ }
1521
+ }
1522
+ if (groupedResults && typeof groupedResults === "object") {
1523
+ for (const [groupName, groupChecks] of Object.entries(groupedResults)) {
1524
+ if (!Array.isArray(groupChecks)) continue;
1525
+ for (const cr of groupChecks) {
1526
+ const parts = [];
1527
+ if (typeof cr.content === "string" && cr.content.trim()) {
1528
+ parts.push({ text: cr.content, media_type: "text/markdown" });
1529
+ }
1530
+ if (cr.output != null) {
1531
+ parts.push({ data: cr.output, media_type: "application/json" });
1532
+ }
1533
+ if (parts.length > 0) {
1534
+ artifacts.push({
1535
+ artifact_id: crypto4.randomUUID(),
1536
+ name: cr.checkName ?? groupName,
1537
+ description: `Output from check: ${cr.checkName ?? groupName}`,
1538
+ parts
1539
+ });
1540
+ }
1541
+ }
1542
+ }
1543
+ }
1544
+ if (artifacts.length === 0) {
1545
+ artifacts.push({
1546
+ artifact_id: crypto4.randomUUID(),
1547
+ name: "result",
1548
+ description: "Execution result",
1549
+ parts: [
1550
+ {
1551
+ text: `Executed ${checks.join(", ")} checks. ${result?.checksExecuted?.length ?? 0} checks ran.`,
1552
+ media_type: "text/markdown"
1553
+ }
1554
+ ]
1555
+ });
1556
+ }
1557
+ for (let i = 0; i < artifacts.length; i++) {
1558
+ this.taskStore.addArtifact(task.id, artifacts[i]);
1559
+ this.emitArtifactEvent(
1560
+ task.id,
1561
+ task.context_id,
1562
+ artifacts[i],
1563
+ false,
1564
+ i === artifacts.length - 1
1565
+ );
1566
+ }
1567
+ const agentResponse = {
1568
+ message_id: crypto4.randomUUID(),
1569
+ role: "agent",
1570
+ parts: [
1571
+ {
1572
+ text: `Completed ${artifacts.length} artifact(s) from ${result?.checksExecuted?.length ?? 0} check(s).`,
1573
+ media_type: "text/markdown"
1574
+ }
1575
+ ],
1576
+ metadata: {
1577
+ executionTime: result?.executionTime,
1578
+ checksExecuted: result?.checksExecuted
1579
+ }
1580
+ };
1581
+ this.taskStore.appendHistory(task.id, agentResponse);
1582
+ this.taskStore.updateTaskState(task.id, "completed", agentResponse);
1583
+ this.emitStatusEvent(task.id, task.context_id, {
1584
+ state: "completed",
1585
+ message: agentResponse,
1586
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1587
+ });
1588
+ }
1589
+ /**
1590
+ * Create a TaskExecutor callback for the TaskQueue.
1591
+ * The queue handles state transitions (submitted → working), so the executor
1592
+ * focuses on running the engine and converting results.
1593
+ */
1594
+ createTaskExecutor() {
1595
+ return async (task) => {
1596
+ try {
1597
+ const userMessage = task.history.find((m) => m.role === "user");
1598
+ if (!userMessage) return { success: false, error: "No user message found" };
1599
+ await this.executeTaskViaEngine(task, userMessage);
1600
+ return { success: true, stateAlreadySet: true };
1601
+ } catch (err) {
1602
+ const errorMsg = err instanceof Error ? err.message : "Unknown error";
1603
+ return { success: false, error: errorMsg, stateAlreadySet: true };
1604
+ }
1605
+ };
1606
+ }
1607
+ // -------------------------------------------------------------------------
1608
+ // Helpers
1609
+ // -------------------------------------------------------------------------
1610
+ checkResultToArtifact(payload) {
1611
+ if (!payload || typeof payload !== "object") return null;
1612
+ const p = payload;
1613
+ const parts = [];
1614
+ if (typeof p.output === "string") {
1615
+ parts.push({ text: p.output, media_type: "text/markdown" });
1616
+ } else if (typeof p.output === "object" && p.output !== null) {
1617
+ parts.push({ data: p.output, media_type: "application/json" });
1618
+ }
1619
+ if (parts.length === 0) return null;
1620
+ return {
1621
+ artifact_id: crypto4.randomUUID(),
1622
+ name: p.checkId ?? "check-result",
1623
+ parts
1624
+ };
1625
+ }
1626
+ filterOutputModes(task, acceptedModes) {
1627
+ const filteredArtifacts = task.artifacts.map((a) => ({
1628
+ ...a,
1629
+ parts: a.parts.filter(
1630
+ (p) => acceptedModes.some((mode) => (p.media_type ?? "text/plain").startsWith(mode))
1631
+ )
1632
+ })).filter((a) => a.parts.length > 0);
1633
+ return { ...task, artifacts: filteredArtifacts };
1634
+ }
1635
+ startCleanupSweep() {
1636
+ this.cleanupTimer = setInterval(() => {
1637
+ try {
1638
+ const deletedTaskIds = this.taskStore.deleteExpiredTasks();
1639
+ if (deletedTaskIds.length > 0) {
1640
+ for (const taskId of deletedTaskIds) {
1641
+ this.pushManager.deleteForTask(taskId);
1642
+ }
1643
+ logger.info(`[A2A] Cleaned up ${deletedTaskIds.length} expired tasks`);
1644
+ }
1645
+ } catch (err) {
1646
+ logger.error(
1647
+ `[A2A] Cleanup sweep error: ${err instanceof Error ? err.message : String(err)}`
1648
+ );
1649
+ }
1650
+ }, 36e5);
1651
+ }
1652
+ stopCleanupSweep() {
1653
+ if (this.cleanupTimer) {
1654
+ clearInterval(this.cleanupTimer);
1655
+ this.cleanupTimer = null;
1656
+ }
1657
+ }
1658
+ // -------------------------------------------------------------------------
1659
+ // Public accessors (for testing)
1660
+ // -------------------------------------------------------------------------
1661
+ getTaskStore() {
1662
+ return this.taskStore;
1663
+ }
1664
+ getAgentCard() {
1665
+ return this.agentCard;
1666
+ }
1667
+ };
1668
+ }
1669
+ });
1670
+ init_a2a_frontend();
1671
+ export {
1672
+ A2AFrontend,
1673
+ messageToWorkflowInput,
1674
+ resultToArtifacts
1675
+ };
1676
+ //# sourceMappingURL=a2a-frontend-IWOUJOIZ.mjs.map