@botbotgo/agent-harness 0.0.168 → 0.0.170

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 CHANGED
@@ -154,6 +154,8 @@ Boundary documents live in:
154
154
  - `docs/development/extensions.html`
155
155
  - `docs/development/runtime-operations.html`
156
156
  - `docs/development/testing-and-release.html`
157
+ - `docs/development/api-reference.html`
158
+ - `docs/development/cookbook.html`
157
159
  - `docs/upstream-feature-matrix.md`
158
160
  - `docs/product-boundary.md`
159
161
  - `docs/runtime-blueprint-assessment.md`
@@ -902,6 +904,7 @@ Primary exports:
902
904
  - `createAcpServer`
903
905
  - `serveAcpHttp`
904
906
  - `serveAcpStdio`
907
+ - `serveA2aHttp`
905
908
  - `serveAgUiHttp`
906
909
  - `createRuntimeMcpServer`
907
910
  - `serveRuntimeMcpOverStdio`
@@ -926,7 +929,9 @@ ACP transport notes:
926
929
 
927
930
  - `serveAcpStdio(runtime)` exposes newline-delimited JSON-RPC over stdio for local IDE, CLI, or subprocess clients.
928
931
  - `serveAcpHttp(runtime)` exposes JSON-RPC over HTTP plus SSE runtime events so remote operator surfaces can connect without importing the runtime in-process.
932
+ - `serveA2aHttp(runtime)` exposes a minimal A2A-compatible HTTP JSON-RPC bridge plus agent card discovery, mapping `message/send`, `tasks/get`, and `tasks/cancel` onto the existing session/request runtime surface.
929
933
  - `serveAgUiHttp(runtime)` exposes a minimal AG-UI-compatible HTTP SSE bridge that projects `run + output.delta + final result` onto `RUN_STARTED`, `TEXT_MESSAGE_*`, and `RUN_FINISHED` events for UI clients.
930
934
  - `createRuntimeMcpServer(runtime)` and `serveRuntimeMcpOverStdio(runtime)` expose the persisted runtime control surface itself as MCP tools, including sessions, requests, approvals, artifacts, events, and package export helpers.
931
935
  - `exportRunPackage(...)` and `exportSessionPackage(...)` package stable runtime records, transcript, approvals, events, and artifacts for operator tooling without reaching into persistence internals.
932
936
  - `runtime/default.governance.remoteMcp` can now deny or allow specific MCP servers, raise approval requirements by transport, and stamp transport-based risk tiers into runtime governance bundles.
937
+ - detailed A2A adapter guidance lives in [`docs/a2a-bridge.md`](docs/a2a-bridge.md)
package/README.zh.md CHANGED
@@ -154,6 +154,8 @@ AI 让 agent 逻辑、工具调用和工作流代码更容易生成,真正更
154
154
  - `docs/development/extensions.html`
155
155
  - `docs/development/runtime-operations.html`
156
156
  - `docs/development/testing-and-release.html`
157
+ - `docs/development/api-reference.html`
158
+ - `docs/development/cookbook.html`
157
159
  - `docs/upstream-feature-matrix.md`
158
160
  - `docs/product-boundary.md`
159
161
  - `docs/runtime-blueprint-assessment.md`
@@ -861,6 +863,7 @@ spec:
861
863
  - `createAcpServer`
862
864
  - `serveAcpHttp`
863
865
  - `serveAcpStdio`
866
+ - `serveA2aHttp`
864
867
  - `serveAgUiHttp`
865
868
  - `createRuntimeMcpServer`
866
869
  - `serveRuntimeMcpOverStdio`
@@ -885,7 +888,9 @@ ACP transport 说明:
885
888
 
886
889
  - `serveAcpStdio(runtime)` 提供基于 stdio 的 newline-delimited JSON-RPC,适合本地 IDE、CLI 或子进程客户端。
887
890
  - `serveAcpHttp(runtime)` 提供基于 HTTP 的 JSON-RPC 与 SSE runtime events,适合远程 operator surface 或独立控制面接入。
891
+ - `serveA2aHttp(runtime)` 提供最小可用的 A2A HTTP JSON-RPC bridge 与 agent card discovery,把 `message/send`、`tasks/get`、`tasks/cancel` 映射到现有 session/request runtime surface。
888
892
  - `serveAgUiHttp(runtime)` 提供最小可用的 AG-UI HTTP SSE bridge,把现有 `run + output.delta + final result` 投影成 `RUN_STARTED`、`TEXT_MESSAGE_*` 与 `RUN_FINISHED` 事件,便于 UI 客户端直接接入。
889
893
  - `createRuntimeMcpServer(runtime)` 与 `serveRuntimeMcpOverStdio(runtime)` 会把持久化 runtime 控制面本身暴露成 MCP tools,包括 sessions、requests、approvals、artifacts、events 与 package export helpers。
890
894
  - `exportRunPackage(...)` 与 `exportSessionPackage(...)` 可把稳定 runtime 记录、transcript、approvals、events 和 artifacts 打包给 operator tooling,而不必直接访问 persistence 内部实现。
891
895
  - `runtime/default.governance.remoteMcp` 现在可以按 MCP server 或 transport 做 allow/deny、审批升级,并把 transport 风险等级写进 runtime governance bundles。
896
+ - 更详细的 A2A 适配层开发说明见 [`docs/a2a-bridge.md`](docs/a2a-bridge.md)
package/dist/api.d.ts CHANGED
@@ -10,6 +10,7 @@ export { buildFlowGraph, exportFlowGraphToMermaid, exportFlowGraphToSequenceMerm
10
10
  export { createUpstreamTimelineReducer } from "./upstream-events.js";
11
11
  export type { ListMemoriesInput, ListMemoriesResult, MemoryDecision, MemoryKind, MemoryRecord, MemoryScope, MemorizeInput, MemorizeResult, RecallInput, RecallResult, RemoveMemoryInput, RuntimeEvaluationExport, RuntimeEvaluationExportInput, RuntimeEvaluationReplayInput, RuntimeEvaluationReplayResult, RuntimeRunPackage, RuntimeRunPackageInput, RuntimeSessionPackage, RuntimeSessionPackageInput, UpdateMemoryInput, } from "./contracts/types.js";
12
12
  export type { AcpHttpServer, AcpHttpServerOptions } from "./protocol/acp/http.js";
13
+ export type { A2aAgentCard, A2aHttpServer, A2aHttpServerOptions, A2aTask, A2aTaskState } from "./protocol/a2a/http.js";
13
14
  export type { AcpStdioServer, AcpStdioServerOptions } from "./protocol/acp/stdio.js";
14
15
  export type { AgUiEvent, AgUiHttpServer, AgUiHttpServerOptions, AgUiRunAgentInput } from "./protocol/ag-ui/http.js";
15
16
  type PublicApprovalRecord = {
@@ -99,6 +100,7 @@ export declare function replayEvaluationBundle(runtime: AgentHarnessRuntime, inp
99
100
  export declare function serveAcpStdio(runtime: AgentHarnessRuntime, options?: import("./protocol/acp/stdio.js").AcpStdioServerOptions): import("./protocol/acp/stdio.js").AcpStdioServer;
100
101
  export declare function serveAcpHttp(runtime: AgentHarnessRuntime, options?: import("./protocol/acp/http.js").AcpHttpServerOptions): Promise<import("./protocol/acp/http.js").AcpHttpServer>;
101
102
  export declare function serveAgUiHttp(runtime: AgentHarnessRuntime, options?: import("./protocol/ag-ui/http.js").AgUiHttpServerOptions): Promise<import("./protocol/ag-ui/http.js").AgUiHttpServer>;
103
+ export declare function serveA2aHttp(runtime: AgentHarnessRuntime, options?: import("./protocol/a2a/http.js").A2aHttpServerOptions): Promise<import("./protocol/a2a/http.js").A2aHttpServer>;
102
104
  export declare function listAgentSkills(runtime: AgentHarnessRuntime, agentId: string, options?: RequirementAssessmentOptions): InventorySkillRecord[];
103
105
  export declare function getAgent(runtime: AgentHarnessRuntime, agentId: string, options?: RequirementAssessmentOptions): InventoryAgentRecord | null;
104
106
  export declare function describeInventory(runtime: AgentHarnessRuntime, options?: RequirementAssessmentOptions): {
package/dist/api.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { AgentHarnessRuntime } from "./runtime/harness.js";
2
+ import { serveA2aOverHttp } from "./protocol/a2a/http.js";
2
3
  import { serveAgUiOverHttp } from "./protocol/ag-ui/http.js";
3
4
  import { serveAcpOverHttp } from "./protocol/acp/http.js";
4
5
  import { serveAcpOverStdio } from "./protocol/acp/stdio.js";
@@ -216,6 +217,9 @@ export async function serveAcpHttp(runtime, options) {
216
217
  export async function serveAgUiHttp(runtime, options) {
217
218
  return serveAgUiOverHttp(runtime, options);
218
219
  }
220
+ export async function serveA2aHttp(runtime, options) {
221
+ return serveA2aOverHttp(runtime, options);
222
+ }
219
223
  export function listAgentSkills(runtime, agentId, options) {
220
224
  return runtime.listAgentSkills(agentId, options);
221
225
  }
package/dist/cli.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createAgentHarness } from "./api.js";
3
+ import { serveA2aOverHttp } from "./protocol/a2a/http.js";
3
4
  import { serveAgUiOverHttp } from "./protocol/ag-ui/http.js";
4
5
  import { serveAcpOverHttp } from "./protocol/acp/http.js";
5
6
  import { serveAcpOverStdio } from "./protocol/acp/stdio.js";
@@ -11,6 +12,7 @@ type CliIo = {
11
12
  };
12
13
  type CliDeps = {
13
14
  createAgentHarness?: typeof createAgentHarness;
15
+ serveA2aOverHttp?: typeof serveA2aOverHttp;
14
16
  serveAgUiOverHttp?: typeof serveAgUiOverHttp;
15
17
  serveAcpOverHttp?: typeof serveAcpOverHttp;
16
18
  serveAcpOverStdio?: typeof serveAcpOverStdio;
package/dist/cli.js CHANGED
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
4
  import { createAgentHarness } from "./api.js";
5
5
  import { initProject } from "./init-project.js";
6
+ import { serveA2aOverHttp } from "./protocol/a2a/http.js";
6
7
  import { serveAgUiOverHttp } from "./protocol/ag-ui/http.js";
7
8
  import { serveAcpOverHttp } from "./protocol/acp/http.js";
8
9
  import { serveAcpOverStdio } from "./protocol/acp/stdio.js";
@@ -11,6 +12,7 @@ function renderUsage() {
11
12
  return `Usage:
12
13
  agent-harness init <project-name> [--template deep-research|single-agent] [--provider <provider>] [--model <model>] [--with-web-search|--no-web-search]
13
14
  agent-harness acp serve [--workspace <path>] [--transport stdio|http] [--host <hostname>] [--port <port>]
15
+ agent-harness a2a serve [--workspace <path>] [--host <hostname>] [--port <port>]
14
16
  agent-harness ag-ui serve [--workspace <path>] [--host <hostname>] [--port <port>]
15
17
  agent-harness runtime-mcp serve [--workspace <path>]
16
18
  `;
@@ -108,7 +110,7 @@ function parseAcpServeOptions(args) {
108
110
  }
109
111
  return { workspaceRoot, transport, hostname, port };
110
112
  }
111
- function parseHttpServeOptions(args) {
113
+ function parseHttpServeOptions(args, serviceLabel = "HTTP") {
112
114
  let workspaceRoot;
113
115
  let hostname;
114
116
  let port;
@@ -139,7 +141,7 @@ function parseHttpServeOptions(args) {
139
141
  }
140
142
  const parsedPort = Number.parseInt(value, 10);
141
143
  if (!Number.isFinite(parsedPort) || parsedPort < 0) {
142
- return { workspaceRoot, hostname, error: `Invalid AG-UI port: ${value}` };
144
+ return { workspaceRoot, hostname, error: `Invalid ${serviceLabel} port: ${value}` };
143
145
  }
144
146
  port = parsedPort;
145
147
  index += 1;
@@ -155,6 +157,7 @@ export async function runCli(argv, io = {}, deps = {}) {
155
157
  const stderr = io.stderr ?? ((message) => process.stderr.write(message));
156
158
  const [command, projectName, ...rest] = argv;
157
159
  const createHarness = deps.createAgentHarness ?? createAgentHarness;
160
+ const serveA2a = deps.serveA2aOverHttp ?? serveA2aOverHttp;
158
161
  const serveAgUi = deps.serveAgUiOverHttp ?? serveAgUiOverHttp;
159
162
  const serveAcpHttp = deps.serveAcpOverHttp ?? serveAcpOverHttp;
160
163
  const serveAcp = deps.serveAcpOverStdio ?? serveAcpOverStdio;
@@ -232,7 +235,7 @@ export async function runCli(argv, io = {}, deps = {}) {
232
235
  stderr(renderUsage());
233
236
  return 1;
234
237
  }
235
- const parsed = parseHttpServeOptions(subcommandArgs);
238
+ const parsed = parseHttpServeOptions(subcommandArgs, "AG-UI");
236
239
  if (parsed.error) {
237
240
  stderr(`${parsed.error}\n`);
238
241
  stderr(renderUsage());
@@ -256,13 +259,43 @@ export async function runCli(argv, io = {}, deps = {}) {
256
259
  return 1;
257
260
  }
258
261
  }
262
+ if (command === "a2a") {
263
+ const [subcommand, ...subcommandArgs] = [projectName, ...rest];
264
+ if (subcommand !== "serve") {
265
+ stderr(renderUsage());
266
+ return 1;
267
+ }
268
+ const parsed = parseHttpServeOptions(subcommandArgs, "A2A");
269
+ if (parsed.error) {
270
+ stderr(`${parsed.error}\n`);
271
+ stderr(renderUsage());
272
+ return 1;
273
+ }
274
+ try {
275
+ const runtime = await createHarness(path.resolve(cwd, parsed.workspaceRoot ?? "."));
276
+ const workspacePath = path.resolve(cwd, parsed.workspaceRoot ?? ".");
277
+ const server = await serveA2a(runtime, {
278
+ hostname: parsed.hostname,
279
+ port: parsed.port,
280
+ });
281
+ stderr(`Serving A2A over http from ${workspacePath} at ${server.rpcUrl} (card ${server.agentCardUrl})\n`);
282
+ await server.completed;
283
+ await runtime.stop();
284
+ return 0;
285
+ }
286
+ catch (error) {
287
+ const message = error instanceof Error ? error.message : String(error);
288
+ stderr(`${message}\n`);
289
+ return 1;
290
+ }
291
+ }
259
292
  if (command === "runtime-mcp") {
260
293
  const [subcommand, ...subcommandArgs] = [projectName, ...rest];
261
294
  if (subcommand !== "serve") {
262
295
  stderr(renderUsage());
263
296
  return 1;
264
297
  }
265
- const parsed = parseHttpServeOptions(subcommandArgs);
298
+ const parsed = parseHttpServeOptions(subcommandArgs, "runtime MCP");
266
299
  if (parsed.error) {
267
300
  stderr(`${parsed.error}\n`);
268
301
  stderr(renderUsage());
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- export { AgentHarnessAcpServer, AgentHarnessRuntime, buildFlowGraph, cancelRun, createAgentHarness, createAcpServer, createRuntimeMcpServer, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, exportEvaluationBundle, exportRunPackage, exportSessionPackage, replayEvaluationBundle, getArtifact, getAgent, getApproval, getRequest, getHealth, listMemories, listRunEvents, getSession, listAgentSkills, listArtifacts, listApprovals, listRequests, listSessions, memorize, normalizeUserChatInput, recall, removeMemory, resolveApproval, run, serveAcpHttp, serveAcpStdio, serveAgUiHttp, serveRuntimeMcpOverStdio, serveToolsOverStdio, subscribe, stop, updateMemory, exportFlowGraphToMermaid, exportFlowGraphToSequenceMermaid, } from "./api.js";
1
+ export { AgentHarnessAcpServer, AgentHarnessRuntime, buildFlowGraph, cancelRun, createAgentHarness, createAcpServer, createRuntimeMcpServer, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, exportEvaluationBundle, exportRunPackage, exportSessionPackage, replayEvaluationBundle, getArtifact, getAgent, getApproval, getRequest, getHealth, listMemories, listRunEvents, getSession, listAgentSkills, listArtifacts, listApprovals, listRequests, listSessions, memorize, normalizeUserChatInput, recall, removeMemory, resolveApproval, run, serveA2aHttp, serveAcpHttp, serveAcpStdio, serveAgUiHttp, serveRuntimeMcpOverStdio, serveToolsOverStdio, subscribe, stop, updateMemory, exportFlowGraphToMermaid, exportFlowGraphToSequenceMermaid, } from "./api.js";
2
2
  export type { AcpApproval, AcpArtifact, AcpEventNotification, AcpJsonRpcError, AcpJsonRpcRequest, AcpJsonRpcResponse, AcpJsonRpcSuccess, AcpRequestRecord, AcpRunRequestParams, AcpServerCapabilities, AcpSessionRecord, } from "./acp.js";
3
3
  export type { ListMemoriesInput, ListMemoriesResult, MemoryDecision, MemoryKind, MemoryRecord, MemoryScope, MemorizeInput, MemorizeResult, NormalizeUserChatInputOptions, RecallInput, RecallResult, RemoveMemoryInput, RuntimeEvaluationExport, RuntimeEvaluationExportInput, RuntimeEvaluationReplayInput, RuntimeEvaluationReplayResult, RuntimeRunPackage, RuntimeRunPackageInput, RuntimeSessionPackage, RuntimeSessionPackageInput, UpdateMemoryInput, UserChatInput, UserChatMessage, } from "./api.js";
4
4
  export type { BuildFlowGraphInput, FlowEdge, FlowEdgeKind, FlowGraph, FlowGraphMermaidOptions, FlowGraphSequenceMermaidOptions, FlowGroup, FlowGroupKind, FlowNode, FlowNodeKind, FlowNodeLayer, FlowNodeStatus, } from "./flow/index.js";
5
- export type { AcpHttpServer, AcpHttpServerOptions, AcpStdioServer, AcpStdioServerOptions, AgUiEvent, AgUiHttpServer, AgUiHttpServerOptions, AgUiRunAgentInput, } from "./api.js";
5
+ export type { A2aAgentCard, A2aHttpServer, A2aHttpServerOptions, A2aTask, A2aTaskState, AcpHttpServer, AcpHttpServerOptions, AcpStdioServer, AcpStdioServerOptions, AgUiEvent, AgUiHttpServer, AgUiHttpServerOptions, AgUiRunAgentInput, } from "./api.js";
6
6
  export type { RuntimeMcpServerOptions, ToolMcpServerOptions } from "./mcp.js";
7
7
  export { tool } from "./tools.js";
8
8
  export type { UpstreamTimelineProjection, UpstreamTimelineReducer } from "./upstream-events.js";
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { AgentHarnessAcpServer, AgentHarnessRuntime, buildFlowGraph, cancelRun, createAgentHarness, createAcpServer, createRuntimeMcpServer, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, exportEvaluationBundle, exportRunPackage, exportSessionPackage, replayEvaluationBundle, getArtifact, getAgent, getApproval, getRequest, getHealth, listMemories, listRunEvents, getSession, listAgentSkills, listArtifacts, listApprovals, listRequests, listSessions, memorize, normalizeUserChatInput, recall, removeMemory, resolveApproval, run, serveAcpHttp, serveAcpStdio, serveAgUiHttp, serveRuntimeMcpOverStdio, serveToolsOverStdio, subscribe, stop, updateMemory, exportFlowGraphToMermaid, exportFlowGraphToSequenceMermaid, } from "./api.js";
1
+ export { AgentHarnessAcpServer, AgentHarnessRuntime, buildFlowGraph, cancelRun, createAgentHarness, createAcpServer, createRuntimeMcpServer, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, exportEvaluationBundle, exportRunPackage, exportSessionPackage, replayEvaluationBundle, getArtifact, getAgent, getApproval, getRequest, getHealth, listMemories, listRunEvents, getSession, listAgentSkills, listArtifacts, listApprovals, listRequests, listSessions, memorize, normalizeUserChatInput, recall, removeMemory, resolveApproval, run, serveA2aHttp, serveAcpHttp, serveAcpStdio, serveAgUiHttp, serveRuntimeMcpOverStdio, serveToolsOverStdio, subscribe, stop, updateMemory, exportFlowGraphToMermaid, exportFlowGraphToSequenceMermaid, } from "./api.js";
2
2
  export { tool } from "./tools.js";
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.167";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.169";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.167";
1
+ export const AGENT_HARNESS_VERSION = "0.0.169";
@@ -0,0 +1,86 @@
1
+ import type { AgentHarnessRuntime } from "../../runtime/harness.js";
2
+ export type A2aHttpServerOptions = {
3
+ hostname?: string;
4
+ port?: number;
5
+ rpcPath?: string;
6
+ agentCardPath?: string;
7
+ agentName?: string;
8
+ agentDescription?: string;
9
+ documentationUrl?: string;
10
+ provider?: {
11
+ organization?: string;
12
+ url?: string;
13
+ };
14
+ defaultAgentId?: string;
15
+ };
16
+ export type A2aAgentCard = {
17
+ name: string;
18
+ description: string;
19
+ version: string;
20
+ url: string;
21
+ preferredTransport: "JSONRPC";
22
+ provider?: {
23
+ organization?: string;
24
+ url?: string;
25
+ };
26
+ documentationUrl?: string;
27
+ capabilities: {
28
+ streaming: boolean;
29
+ pushNotifications: boolean;
30
+ stateTransitionHistory: boolean;
31
+ };
32
+ defaultInputModes: string[];
33
+ defaultOutputModes: string[];
34
+ skills: Array<{
35
+ id: string;
36
+ name: string;
37
+ description: string;
38
+ tags: string[];
39
+ examples: string[];
40
+ }>;
41
+ };
42
+ export type A2aTaskState = "submitted" | "working" | "input-required" | "completed" | "failed" | "canceled";
43
+ type A2aTaskStatus = {
44
+ state: A2aTaskState;
45
+ timestamp: string;
46
+ message?: {
47
+ role: "agent";
48
+ parts: Array<{
49
+ type: "text";
50
+ text: string;
51
+ }>;
52
+ };
53
+ };
54
+ export type A2aTask = {
55
+ id: string;
56
+ contextId: string;
57
+ kind: "task";
58
+ status: A2aTaskStatus;
59
+ history: Array<{
60
+ role: "user" | "agent";
61
+ parts: Array<{
62
+ type: "text";
63
+ text: string;
64
+ }>;
65
+ }>;
66
+ artifacts: Array<{
67
+ name: string;
68
+ parts: Array<{
69
+ type: "text";
70
+ text: string;
71
+ }>;
72
+ }>;
73
+ metadata: Record<string, unknown>;
74
+ };
75
+ export type A2aHttpServer = {
76
+ hostname: string;
77
+ port: number;
78
+ rpcPath: string;
79
+ agentCardPath: string;
80
+ rpcUrl: string;
81
+ agentCardUrl: string;
82
+ completed: Promise<void>;
83
+ close: () => Promise<void>;
84
+ };
85
+ export declare function serveA2aOverHttp(runtime: AgentHarnessRuntime, options?: A2aHttpServerOptions): Promise<A2aHttpServer>;
86
+ export {};
@@ -0,0 +1,431 @@
1
+ import { createServer } from "node:http";
2
+ function normalizePath(value, fallback) {
3
+ const source = typeof value === "string" && value.trim().length > 0 ? value.trim() : fallback;
4
+ return source.startsWith("/") ? source : `/${source}`;
5
+ }
6
+ function writeJson(response, statusCode, payload) {
7
+ response.statusCode = statusCode;
8
+ response.setHeader("content-type", "application/json; charset=utf-8");
9
+ response.end(JSON.stringify(payload));
10
+ }
11
+ function readRequestBody(request) {
12
+ return new Promise((resolve, reject) => {
13
+ const chunks = [];
14
+ request.on("data", (chunk) => {
15
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
16
+ });
17
+ request.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
18
+ request.on("error", reject);
19
+ });
20
+ }
21
+ function toError(id, code, message, data) {
22
+ return {
23
+ jsonrpc: "2.0",
24
+ id,
25
+ error: {
26
+ code,
27
+ message,
28
+ ...(data === undefined ? {} : { data }),
29
+ },
30
+ };
31
+ }
32
+ function toSuccess(id, result) {
33
+ return {
34
+ jsonrpc: "2.0",
35
+ id,
36
+ result,
37
+ };
38
+ }
39
+ function parseTextPart(part) {
40
+ if (!part || typeof part !== "object" || Array.isArray(part)) {
41
+ return null;
42
+ }
43
+ const typed = part;
44
+ if (typed.type !== "text" || typeof typed.text !== "string" || typed.text.trim().length === 0) {
45
+ return null;
46
+ }
47
+ return typed.text;
48
+ }
49
+ function parseMessageSendParams(params) {
50
+ if (!params || typeof params !== "object" || Array.isArray(params)) {
51
+ throw new Error("A2A message/send requires params object.");
52
+ }
53
+ const typed = params;
54
+ const message = typed.message;
55
+ if (!message || typeof message !== "object" || Array.isArray(message)) {
56
+ throw new Error("A2A message/send requires `message`.");
57
+ }
58
+ const typedMessage = message;
59
+ const role = typeof typedMessage.role === "string" ? typedMessage.role : "";
60
+ if (role !== "user") {
61
+ throw new Error("A2A message/send currently supports only `user` role messages.");
62
+ }
63
+ const parts = Array.isArray(typedMessage.parts) ? typedMessage.parts : [];
64
+ const textParts = parts.map(parseTextPart).filter((item) => Boolean(item));
65
+ if (textParts.length === 0) {
66
+ throw new Error("A2A message/send requires at least one text part.");
67
+ }
68
+ const sessionId = typeof typed.contextId === "string" && typed.contextId.trim().length > 0
69
+ ? typed.contextId.trim()
70
+ : undefined;
71
+ const agentId = typeof typed.agentId === "string" && typed.agentId.trim().length > 0
72
+ ? typed.agentId.trim()
73
+ : undefined;
74
+ const metadata = typed.metadata;
75
+ const invocation = metadata && typeof metadata === "object" && !Array.isArray(metadata)
76
+ ? metadata
77
+ : undefined;
78
+ return {
79
+ input: textParts.join("\n\n"),
80
+ ...(agentId ? { agentId } : {}),
81
+ ...(sessionId ? { sessionId } : {}),
82
+ ...(invocation ? { invocation } : {}),
83
+ };
84
+ }
85
+ function parseTaskLocatorParams(params) {
86
+ if (!params || typeof params !== "object" || Array.isArray(params)) {
87
+ throw new Error("A2A task lookup requires params object.");
88
+ }
89
+ const typed = params;
90
+ const taskId = typeof typed.id === "string" && typed.id.trim().length > 0
91
+ ? typed.id.trim()
92
+ : typeof typed.taskId === "string" && typed.taskId.trim().length > 0
93
+ ? typed.taskId.trim()
94
+ : "";
95
+ if (!taskId) {
96
+ throw new Error("A2A task lookup requires `id`.");
97
+ }
98
+ return { taskId };
99
+ }
100
+ function mapRunState(state) {
101
+ switch (state) {
102
+ case "queued":
103
+ return "submitted";
104
+ case "running":
105
+ return "working";
106
+ case "waiting_for_approval":
107
+ return "input-required";
108
+ case "completed":
109
+ return "completed";
110
+ case "cancelled":
111
+ return "canceled";
112
+ case "failed":
113
+ default:
114
+ return "failed";
115
+ }
116
+ }
117
+ function toTextParts(text) {
118
+ if (typeof text !== "string" || text.length === 0) {
119
+ return [];
120
+ }
121
+ return [{ type: "text", text }];
122
+ }
123
+ function contentToText(content) {
124
+ if (typeof content === "string") {
125
+ return content;
126
+ }
127
+ if (!Array.isArray(content)) {
128
+ return undefined;
129
+ }
130
+ const text = content
131
+ .filter((part) => part.type === "text" && typeof part.text === "string")
132
+ .map((part) => part.text)
133
+ .join("\n\n")
134
+ .trim();
135
+ return text.length > 0 ? text : undefined;
136
+ }
137
+ function buildTaskFromSessionAndRequest(session, request, approvals, output, failureMessage) {
138
+ if (!session || !request) {
139
+ return null;
140
+ }
141
+ const latestUserMessage = [...session.messages].reverse().find((message) => message.role === "user");
142
+ const latestAgentMessage = [...session.messages].reverse().find((message) => message.role === "assistant");
143
+ const approval = approvals.find((item) => item.status === "pending");
144
+ const latestAgentText = contentToText(latestAgentMessage?.content);
145
+ const latestUserText = contentToText(latestUserMessage?.content);
146
+ const statusText = request.state === "waiting_for_approval"
147
+ ? approval
148
+ ? `Approval required for tool ${approval.toolName}.`
149
+ : "Approval required."
150
+ : output ?? latestAgentText ?? failureMessage;
151
+ return {
152
+ id: request.requestId,
153
+ contextId: session.sessionId,
154
+ kind: "task",
155
+ status: {
156
+ state: mapRunState(request.state),
157
+ timestamp: request.updatedAt,
158
+ ...(statusText ? {
159
+ message: {
160
+ role: "agent",
161
+ parts: toTextParts(statusText),
162
+ },
163
+ } : {}),
164
+ },
165
+ history: [
166
+ ...(latestUserText
167
+ ? [{
168
+ role: "user",
169
+ parts: toTextParts(latestUserText),
170
+ }]
171
+ : []),
172
+ ...(latestAgentText
173
+ ? [{
174
+ role: "agent",
175
+ parts: toTextParts(latestAgentText),
176
+ }]
177
+ : []),
178
+ ],
179
+ artifacts: [],
180
+ metadata: {
181
+ agentId: request.agentId,
182
+ sessionId: session.sessionId,
183
+ requestId: request.requestId,
184
+ state: request.state,
185
+ approvalIds: approvals.map((item) => item.approvalId),
186
+ },
187
+ };
188
+ }
189
+ async function buildTaskFromRuntime(runtime, requestId) {
190
+ const request = await runtime.getRun(requestId);
191
+ if (!request) {
192
+ return null;
193
+ }
194
+ const session = await runtime.getThread(request.threadId);
195
+ const approvals = await runtime.listApprovals({ threadId: request.threadId, runId: request.runId });
196
+ return buildTaskFromSessionAndRequest(session ? {
197
+ sessionId: session.threadId,
198
+ entryAgentId: session.entryAgentId,
199
+ currentAgentId: session.currentAgentId,
200
+ currentState: session.currentState,
201
+ latestRequestId: session.latestRunId,
202
+ createdAt: session.createdAt,
203
+ updatedAt: session.updatedAt,
204
+ messages: session.messages,
205
+ requests: session.runs.map((run) => ({
206
+ requestId: run.runId,
207
+ sessionId: run.threadId,
208
+ agentId: run.agentId,
209
+ executionMode: run.executionMode,
210
+ adapterKind: run.adapterKind,
211
+ createdAt: run.createdAt,
212
+ updatedAt: run.updatedAt,
213
+ state: run.state,
214
+ checkpointRef: run.checkpointRef,
215
+ resumable: run.resumable,
216
+ startedAt: run.startedAt,
217
+ endedAt: run.endedAt,
218
+ lastActivityAt: run.lastActivityAt,
219
+ currentAgentId: run.currentAgentId,
220
+ delegationChain: run.delegationChain,
221
+ runtimeSnapshot: run.runtimeSnapshot,
222
+ })),
223
+ pendingDecision: session.pendingDecision,
224
+ } : null, {
225
+ requestId: request.runId,
226
+ sessionId: request.threadId,
227
+ agentId: request.agentId,
228
+ executionMode: request.executionMode,
229
+ adapterKind: request.adapterKind,
230
+ createdAt: request.createdAt,
231
+ updatedAt: request.updatedAt,
232
+ state: request.state,
233
+ checkpointRef: request.checkpointRef,
234
+ resumable: request.resumable,
235
+ startedAt: request.startedAt,
236
+ endedAt: request.endedAt,
237
+ lastActivityAt: request.lastActivityAt,
238
+ currentAgentId: request.currentAgentId,
239
+ delegationChain: request.delegationChain,
240
+ runtimeSnapshot: request.runtimeSnapshot,
241
+ }, approvals);
242
+ }
243
+ function buildAgentCard(runtime, options) {
244
+ const inventory = runtime.describeWorkspaceInventory();
245
+ const skills = inventory.agents.map((agent) => ({
246
+ id: agent.id,
247
+ name: agent.id,
248
+ description: agent.description || `Agent ${agent.id}`,
249
+ tags: ["agent-harness", agent.parentAgentId ? "subagent" : "agent"],
250
+ examples: [`Send a task to ${agent.id}.`],
251
+ }));
252
+ return {
253
+ name: options.agentName,
254
+ description: options.agentDescription,
255
+ version: "0.1.0",
256
+ url: options.rpcUrl,
257
+ preferredTransport: "JSONRPC",
258
+ capabilities: {
259
+ streaming: false,
260
+ pushNotifications: false,
261
+ stateTransitionHistory: true,
262
+ },
263
+ defaultInputModes: ["text"],
264
+ defaultOutputModes: ["text"],
265
+ skills,
266
+ };
267
+ }
268
+ export async function serveA2aOverHttp(runtime, options = {}) {
269
+ const hostname = options.hostname?.trim() || "127.0.0.1";
270
+ const port = typeof options.port === "number" && Number.isFinite(options.port) ? options.port : 0;
271
+ const rpcPath = normalizePath(options.rpcPath, "/a2a");
272
+ const agentCardPath = normalizePath(options.agentCardPath, "/.well-known/agent-card.json");
273
+ const httpServer = createServer(async (request, response) => {
274
+ try {
275
+ const requestUrl = new URL(request.url ?? "/", `http://${hostname}`);
276
+ if (request.method === "GET" && (requestUrl.pathname === agentCardPath || requestUrl.pathname === "/.well-known/agent.json")) {
277
+ const card = buildAgentCard(runtime, {
278
+ agentName: options.agentName ?? "agent-harness-runtime",
279
+ agentDescription: options.agentDescription ?? "Agent-harness A2A bridge over the persisted runtime surface.",
280
+ cardUrl: `http://${hostname}:${resolvedPort}${agentCardPath}`,
281
+ rpcUrl: `http://${hostname}:${resolvedPort}${rpcPath}`,
282
+ });
283
+ writeJson(response, 200, {
284
+ ...card,
285
+ ...(options.documentationUrl ? { documentationUrl: options.documentationUrl } : {}),
286
+ ...(options.provider ? { provider: options.provider } : {}),
287
+ ...(options.defaultAgentId ? { defaultAgentId: options.defaultAgentId } : {}),
288
+ });
289
+ return;
290
+ }
291
+ if (request.method === "POST" && (requestUrl.pathname === rpcPath || requestUrl.pathname === agentCardPath)) {
292
+ let payload;
293
+ try {
294
+ payload = JSON.parse(await readRequestBody(request));
295
+ }
296
+ catch {
297
+ writeJson(response, 400, toError(null, -32700, "Invalid JSON payload."));
298
+ return;
299
+ }
300
+ if (payload.jsonrpc !== "2.0" || typeof payload.method !== "string") {
301
+ writeJson(response, 400, toError(payload.id ?? null, -32600, "Invalid JSON-RPC request."));
302
+ return;
303
+ }
304
+ try {
305
+ if (payload.method === "message/send" || payload.method === "tasks/send") {
306
+ const parsed = parseMessageSendParams(payload.params);
307
+ const result = await runtime.run({
308
+ agentId: parsed.agentId ?? options.defaultAgentId,
309
+ input: parsed.input,
310
+ ...(parsed.sessionId ? { threadId: parsed.sessionId } : {}),
311
+ ...(parsed.invocation ? { invocation: parsed.invocation } : {}),
312
+ });
313
+ const session = await runtime.getThread(result.threadId);
314
+ const requestRecord = await runtime.getRun(result.runId);
315
+ const approvals = await runtime.listApprovals({ threadId: result.threadId, runId: result.runId });
316
+ const task = buildTaskFromSessionAndRequest(session ? {
317
+ sessionId: session.threadId,
318
+ entryAgentId: session.entryAgentId,
319
+ currentAgentId: session.currentAgentId,
320
+ currentState: session.currentState,
321
+ latestRequestId: session.latestRunId,
322
+ createdAt: session.createdAt,
323
+ updatedAt: session.updatedAt,
324
+ messages: session.messages,
325
+ requests: session.runs.map((run) => ({
326
+ requestId: run.runId,
327
+ sessionId: run.threadId,
328
+ agentId: run.agentId,
329
+ executionMode: run.executionMode,
330
+ adapterKind: run.adapterKind,
331
+ createdAt: run.createdAt,
332
+ updatedAt: run.updatedAt,
333
+ state: run.state,
334
+ checkpointRef: run.checkpointRef,
335
+ resumable: run.resumable,
336
+ startedAt: run.startedAt,
337
+ endedAt: run.endedAt,
338
+ lastActivityAt: run.lastActivityAt,
339
+ currentAgentId: run.currentAgentId,
340
+ delegationChain: run.delegationChain,
341
+ runtimeSnapshot: run.runtimeSnapshot,
342
+ })),
343
+ pendingDecision: session.pendingDecision,
344
+ } : null, requestRecord ? {
345
+ requestId: requestRecord.runId,
346
+ sessionId: requestRecord.threadId,
347
+ agentId: requestRecord.agentId,
348
+ executionMode: requestRecord.executionMode,
349
+ adapterKind: requestRecord.adapterKind,
350
+ createdAt: requestRecord.createdAt,
351
+ updatedAt: requestRecord.updatedAt,
352
+ state: requestRecord.state,
353
+ checkpointRef: requestRecord.checkpointRef,
354
+ resumable: requestRecord.resumable,
355
+ startedAt: requestRecord.startedAt,
356
+ endedAt: requestRecord.endedAt,
357
+ lastActivityAt: requestRecord.lastActivityAt,
358
+ currentAgentId: requestRecord.currentAgentId,
359
+ delegationChain: requestRecord.delegationChain,
360
+ runtimeSnapshot: requestRecord.runtimeSnapshot,
361
+ } : null, approvals, result.output);
362
+ writeJson(response, 200, toSuccess(payload.id ?? null, task));
363
+ return;
364
+ }
365
+ if (payload.method === "tasks/get") {
366
+ const { taskId } = parseTaskLocatorParams(payload.params);
367
+ const task = await buildTaskFromRuntime(runtime, taskId);
368
+ if (!task) {
369
+ writeJson(response, 200, toError(payload.id ?? null, -32004, "Task not found."));
370
+ return;
371
+ }
372
+ writeJson(response, 200, toSuccess(payload.id ?? null, task));
373
+ return;
374
+ }
375
+ if (payload.method === "tasks/cancel") {
376
+ const { taskId } = parseTaskLocatorParams(payload.params);
377
+ const result = await runtime.cancelRun({ runId: taskId, reason: "Cancelled via A2A bridge." });
378
+ const task = await buildTaskFromRuntime(runtime, result.runId);
379
+ writeJson(response, 200, toSuccess(payload.id ?? null, task));
380
+ return;
381
+ }
382
+ writeJson(response, 200, toError(payload.id ?? null, -32601, `Unknown A2A method: ${payload.method}`));
383
+ }
384
+ catch (error) {
385
+ writeJson(response, 200, toError(payload.id ?? null, -32000, error instanceof Error ? error.message : String(error)));
386
+ }
387
+ return;
388
+ }
389
+ writeJson(response, 404, {
390
+ error: "Not Found",
391
+ rpcPath,
392
+ agentCardPath,
393
+ });
394
+ }
395
+ catch (error) {
396
+ writeJson(response, 500, {
397
+ error: error instanceof Error ? error.message : "A2A HTTP transport failed.",
398
+ });
399
+ }
400
+ });
401
+ const completed = new Promise((resolve, reject) => {
402
+ httpServer.once("close", resolve);
403
+ httpServer.once("error", reject);
404
+ });
405
+ await new Promise((resolve, reject) => {
406
+ httpServer.listen(port, hostname, () => resolve());
407
+ httpServer.once("error", reject);
408
+ });
409
+ const address = httpServer.address();
410
+ const resolvedPort = typeof address === "object" && address ? address.port : port;
411
+ return {
412
+ hostname,
413
+ port: resolvedPort,
414
+ rpcPath,
415
+ agentCardPath,
416
+ rpcUrl: `http://${hostname}:${resolvedPort}${rpcPath}`,
417
+ agentCardUrl: `http://${hostname}:${resolvedPort}${agentCardPath}`,
418
+ completed,
419
+ close: async () => {
420
+ await new Promise((resolve, reject) => {
421
+ httpServer.close((error) => {
422
+ if (error) {
423
+ reject(error);
424
+ return;
425
+ }
426
+ resolve();
427
+ });
428
+ });
429
+ },
430
+ };
431
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.168",
3
+ "version": "0.0.170",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",