@botbotgo/agent-harness 0.0.223 → 0.0.224

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
@@ -1024,7 +1024,7 @@ ACP transport notes:
1024
1024
  - `serveAcpHttp(runtime)` exposes JSON-RPC over HTTP plus SSE runtime events so remote operator surfaces can connect without importing the runtime in-process.
1025
1025
  - ACP transport validation now covers the reference-client core flow: capability discovery, request submit, session lookup, request lookup, invalid-JSON handling, notification calls without response ids, stdio JSON-RPC, and HTTP plus SSE runtime notifications.
1026
1026
  - For the thinnest editor or CLI starter, begin with `agent-harness acp serve --workspace . --transport stdio` and mirror the `examples/protocol-hello-world/app/acp-stdio-hello-world.mjs` wire shape. Applications that want an in-process reference client can use `createAcpStdioClient(...)` to issue JSON-RPC requests and route runtime notifications without hand-rolling line parsing.
1027
- - `serveA2aHttp(runtime)` exposes an A2A-compatible HTTP JSON-RPC bridge plus agent card discovery, mapping both existing methods such as `message/send` and A2A v1.0 PascalCase methods such as `SendMessage`, `SendStreamingMessage`, `GetTask`, `ListTasks`, `CancelTask`, `SubscribeToTask`, and `GetExtendedAgentCard` onto the existing session/request runtime surface. The bridge now advertises both `1.0` and `0.3` JSON-RPC interfaces, sets `capabilities.streaming = true`, validates `A2A-Version`, records `A2A-Extensions` into runtime invocation metadata, publishes `TASK_STATE_*` statuses plus the `{ task }` `SendMessage` wrapper, streams task snapshots over SSE for streaming methods, and still returns explicit unsupported errors for push notifications.
1027
+ - `serveA2aHttp(runtime)` exposes an A2A-compatible HTTP JSON-RPC bridge plus agent card discovery, mapping both existing methods such as `message/send` and A2A v1.0 PascalCase methods such as `SendMessage`, `SendStreamingMessage`, `GetTask`, `ListTasks`, `CancelTask`, `SubscribeToTask`, `GetExtendedAgentCard`, and task push-notification config methods onto the existing session/request runtime surface. The bridge now advertises both `1.0` and `0.3` JSON-RPC interfaces, sets `capabilities.streaming = true` plus `capabilities.pushNotifications = true`, validates `A2A-Version`, records `A2A-Extensions` into runtime invocation metadata, publishes `TASK_STATE_*` statuses plus the `{ task }` `SendMessage` wrapper, streams task snapshots over SSE for streaming methods, and can send best-effort webhook task snapshots for configured push notification receivers.
1028
1028
  - `serveAgUiHttp(runtime)` exposes an AG-UI-compatible HTTP SSE bridge that projects runtime lifecycle, text output, upstream thinking, step progress, and tool calls onto `RUN_*`, `TEXT_MESSAGE_*`, `THINKING_TEXT_MESSAGE_*`, `STEP_*`, and `TOOL_CALL_*` events for UI clients.
1029
1029
  - `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.
1030
1030
  - `listRequestEvents(...)` and `exportRequestPackage(...)` are the request-first inspection helpers.
package/README.zh.md CHANGED
@@ -982,7 +982,7 @@ ACP transport 说明:
982
982
  - `serveAcpHttp(runtime)` 提供基于 HTTP 的 JSON-RPC 与 SSE runtime events,适合远程界面或独立控制面接入。
983
983
  - ACP transport 现已覆盖核心参考客户端流程验证:capability discovery、request submit、session lookup、request lookup、invalid JSON 处理、无 id notification 不返回响应,以及 stdio JSON-RPC 与 HTTP + SSE runtime notifications。
984
984
  - 如果要从最薄的一层 editor / CLI starter 开始,优先用 `agent-harness acp serve --workspace . --transport stdio`,并直接参考 `examples/protocol-hello-world/app/acp-stdio-hello-world.mjs` 的 wire shape。需要在应用内使用 reference client 时,可直接用 `createAcpStdioClient(...)` 发起 JSON-RPC 请求并分流 runtime notifications,避免每个 sidecar 自己重写 line parsing。
985
- - `serveA2aHttp(runtime)` 提供 A2A HTTP JSON-RPC bridge 与 agent card discovery,同时兼容 `message/send` 这类旧方法,以及 `SendMessage`、`SendStreamingMessage`、`GetTask`、`ListTasks`、`CancelTask`、`SubscribeToTask`、`GetExtendedAgentCard` 这类 A2A v1.0 PascalCase 方法,并统一映射到现有 session/request 运行记录。bridge 现在会同时声明 `1.0` 与 `0.3` 两个 JSON-RPC interface、把 `capabilities.streaming` 设为 `true`、校验 `A2A-Version`、把 `A2A-Extensions` 记录进 runtime invocation metadata、发布 `TASK_STATE_*` 状态与 `SendMessage` 的 `{ task }` wrapper,并在 streaming 方法上通过 SSE 输出 task snapshots;push notification 仍返回明确 unsupported error
985
+ - `serveA2aHttp(runtime)` 提供 A2A HTTP JSON-RPC bridge 与 agent card discovery,同时兼容 `message/send` 这类旧方法,以及 `SendMessage`、`SendStreamingMessage`、`GetTask`、`ListTasks`、`CancelTask`、`SubscribeToTask`、`GetExtendedAgentCard` 与 task push-notification config 这类 A2A v1.0 方法,并统一映射到现有 session/request 运行记录。bridge 现在会同时声明 `1.0` 与 `0.3` 两个 JSON-RPC interface、把 `capabilities.streaming` `capabilities.pushNotifications` 都设为 `true`、校验 `A2A-Version`、把 `A2A-Extensions` 记录进 runtime invocation metadata、发布 `TASK_STATE_*` 状态与 `SendMessage` 的 `{ task }` wrapper、在 streaming 方法上通过 SSE 输出 task snapshots,并可向已配置的 webhook receiver 发送 best-effort task push notifications
986
986
  - `serveAgUiHttp(runtime)` 提供 AG-UI HTTP SSE bridge,把 runtime 生命周期、文本输出、upstream thinking、step 进度与 tool call 投影成 `RUN_*`、`TEXT_MESSAGE_*`、`THINKING_TEXT_MESSAGE_*`、`STEP_*` 与 `TOOL_CALL_*` 事件,便于 UI 客户端直接接入。
987
987
  - `createRuntimeMcpServer(runtime)` 与 `serveRuntimeMcpOverStdio(runtime)` 会把持久化 runtime 控制面本身暴露成 MCP tools,包括 sessions、requests、approvals、artifacts、events 与 package export helpers。
988
988
  - `listRequestEvents(...)` 与 `exportRequestPackage(...)` 是 request-first 的检查 helper。
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.222";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.223";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.222";
1
+ export const AGENT_HARNESS_VERSION = "0.0.223";
@@ -1,5 +1,6 @@
1
1
  import { createServer } from "node:http";
2
2
  import { AGENT_HARNESS_VERSION } from "../../package-version.js";
3
+ import { createPersistentId } from "../../utils/id.js";
3
4
  const SUPPORTED_A2A_VERSIONS = ["1.0", "0.3"];
4
5
  function normalizePath(value, fallback) {
5
6
  const source = typeof value === "string" && value.trim().length > 0 ? value.trim() : fallback;
@@ -134,11 +135,59 @@ function parseMessageSendParams(params) {
134
135
  const invocation = metadata && typeof metadata === "object" && !Array.isArray(metadata)
135
136
  ? metadata
136
137
  : undefined;
138
+ const configuration = typed.configuration;
139
+ const nestedPushNotificationConfig = configuration && typeof configuration === "object" && !Array.isArray(configuration)
140
+ ? configuration.pushNotificationConfig
141
+ ?? configuration.taskPushNotificationConfig
142
+ : undefined;
143
+ const pushNotificationConfig = parsePendingPushNotificationConfig(typed.pushNotificationConfig ?? nestedPushNotificationConfig);
137
144
  return {
138
145
  input: textParts.join("\n\n"),
139
146
  ...(agentId ? { agentId } : {}),
140
147
  ...(sessionId ? { sessionId } : {}),
141
148
  ...(invocation ? { invocation } : {}),
149
+ ...(pushNotificationConfig ? { pushNotificationConfig } : {}),
150
+ };
151
+ }
152
+ function parsePushNotificationAuthenticationInfo(value) {
153
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
154
+ return undefined;
155
+ }
156
+ const typed = value;
157
+ const schemes = Array.isArray(typed.schemes)
158
+ ? typed.schemes
159
+ .filter((entry) => typeof entry === "string" && entry.trim().length > 0)
160
+ .map((entry) => entry.trim())
161
+ : [];
162
+ if (schemes.length === 0) {
163
+ return undefined;
164
+ }
165
+ return {
166
+ schemes,
167
+ ...(typeof typed.credentials === "string" && typed.credentials.trim().length > 0
168
+ ? { credentials: typed.credentials.trim() }
169
+ : {}),
170
+ };
171
+ }
172
+ function parsePendingPushNotificationConfig(value) {
173
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
174
+ return undefined;
175
+ }
176
+ const typed = value;
177
+ const rawUrl = typeof typed.url === "string" ? typed.url.trim() : "";
178
+ if (rawUrl.length === 0) {
179
+ throw new Error("A2A push notification config requires `url`.");
180
+ }
181
+ const parsedUrl = new URL(rawUrl);
182
+ if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
183
+ throw new Error("A2A push notification config requires an http or https URL.");
184
+ }
185
+ const authentication = parsePushNotificationAuthenticationInfo(typed.authentication);
186
+ return {
187
+ url: parsedUrl.toString(),
188
+ ...(typeof typed.id === "string" && typed.id.trim().length > 0 ? { id: typed.id.trim() } : {}),
189
+ ...(typeof typed.token === "string" && typed.token.trim().length > 0 ? { token: typed.token.trim() } : {}),
190
+ ...(authentication ? { authentication } : {}),
142
191
  };
143
192
  }
144
193
  function attachServiceParameters(invocation, serviceParameters) {
@@ -199,6 +248,54 @@ function parseTaskListParams(params) {
199
248
  limit: limitValue,
200
249
  };
201
250
  }
251
+ function parsePushNotificationLocatorParams(params) {
252
+ if (!params || typeof params !== "object" || Array.isArray(params)) {
253
+ throw new Error("A2A push notification lookup requires params object.");
254
+ }
255
+ const typed = params;
256
+ const taskId = typeof typed.taskId === "string" && typed.taskId.trim().length > 0 ? typed.taskId.trim() : "";
257
+ const configId = typeof typed.id === "string" && typed.id.trim().length > 0 ? typed.id.trim() : "";
258
+ if (!taskId) {
259
+ throw new Error("A2A push notification lookup requires `taskId`.");
260
+ }
261
+ if (!configId) {
262
+ throw new Error("A2A push notification lookup requires `id`.");
263
+ }
264
+ return { taskId, configId };
265
+ }
266
+ function parsePushNotificationListParams(params) {
267
+ if (!params || typeof params !== "object" || Array.isArray(params)) {
268
+ throw new Error("A2A push notification list requires params object.");
269
+ }
270
+ const typed = params;
271
+ const taskId = typeof typed.taskId === "string" && typed.taskId.trim().length > 0 ? typed.taskId.trim() : "";
272
+ if (!taskId) {
273
+ throw new Error("A2A push notification list requires `taskId`.");
274
+ }
275
+ const pageSize = typeof typed.pageSize === "number" && Number.isFinite(typed.pageSize)
276
+ ? Math.max(1, Math.min(100, Math.trunc(typed.pageSize)))
277
+ : 20;
278
+ return {
279
+ taskId,
280
+ pageSize,
281
+ ...(typeof typed.pageToken === "string" && typed.pageToken.trim().length > 0 ? { pageToken: typed.pageToken.trim() } : {}),
282
+ };
283
+ }
284
+ function parseCreatePushNotificationConfigParams(params) {
285
+ if (!params || typeof params !== "object" || Array.isArray(params)) {
286
+ throw new Error("A2A push notification create requires params object.");
287
+ }
288
+ const typed = params;
289
+ const taskId = typeof typed.taskId === "string" && typed.taskId.trim().length > 0 ? typed.taskId.trim() : "";
290
+ if (!taskId) {
291
+ throw new Error("A2A push notification create requires `taskId`.");
292
+ }
293
+ const config = parsePendingPushNotificationConfig(typed.pushNotificationConfig ?? typed.config ?? typed);
294
+ if (!config) {
295
+ throw new Error("A2A push notification create requires a config object.");
296
+ }
297
+ return { taskId, config };
298
+ }
202
299
  function mapRunState(state) {
203
300
  switch (state) {
204
301
  case "queued":
@@ -449,7 +546,7 @@ function buildAgentCard(runtime, options) {
449
546
  ],
450
547
  capabilities: {
451
548
  streaming: true,
452
- pushNotifications: false,
549
+ pushNotifications: true,
453
550
  extendedAgentCard: true,
454
551
  },
455
552
  defaultInputModes: ["text/plain"],
@@ -541,6 +638,42 @@ function withRunResult(task, result, streamedText) {
541
638
  function isTerminalTaskState(state) {
542
639
  return state === "TASK_STATE_COMPLETED" || state === "TASK_STATE_FAILED" || state === "TASK_STATE_CANCELED";
543
640
  }
641
+ function clonePushNotificationConfig(config) {
642
+ return {
643
+ id: config.id,
644
+ taskId: config.taskId,
645
+ url: config.url,
646
+ ...(config.token ? { token: config.token } : {}),
647
+ ...(config.authentication ? { authentication: config.authentication } : {}),
648
+ };
649
+ }
650
+ function fingerprintTaskForPush(task) {
651
+ return JSON.stringify({
652
+ state: task.status.state,
653
+ timestamp: task.status.timestamp,
654
+ message: task.status.message,
655
+ });
656
+ }
657
+ async function sendPushNotification(config, task) {
658
+ const headers = {
659
+ "content-type": "application/json",
660
+ };
661
+ if (config.token) {
662
+ headers["x-a2a-push-token"] = config.token;
663
+ }
664
+ const scheme = config.authentication?.schemes[0]?.toLowerCase();
665
+ if (scheme === "bearer" && config.authentication?.credentials) {
666
+ headers.authorization = `Bearer ${config.authentication.credentials}`;
667
+ }
668
+ else if (scheme === "basic" && config.authentication?.credentials) {
669
+ headers.authorization = `Basic ${config.authentication.credentials}`;
670
+ }
671
+ await fetch(config.url, {
672
+ method: "POST",
673
+ headers,
674
+ body: JSON.stringify({ task }),
675
+ });
676
+ }
544
677
  async function streamTaskUpdates(response, id, method, iterator) {
545
678
  writeSseHeaders(response);
546
679
  for await (const item of iterator) {
@@ -677,6 +810,63 @@ export async function serveA2aOverHttp(runtime, options = {}) {
677
810
  ...(options.provider ? { provider: options.provider } : {}),
678
811
  ...(options.defaultAgentId ? { defaultAgentId: options.defaultAgentId } : {}),
679
812
  });
813
+ const pushConfigsByTask = new Map();
814
+ const registerPushNotificationConfig = (taskId, config) => {
815
+ const entry = {
816
+ id: config.id ?? `push-${createPersistentId()}`,
817
+ taskId,
818
+ url: config.url,
819
+ ...(config.token ? { token: config.token } : {}),
820
+ ...(config.authentication ? { authentication: config.authentication } : {}),
821
+ };
822
+ let taskConfigs = pushConfigsByTask.get(taskId);
823
+ if (!taskConfigs) {
824
+ taskConfigs = new Map();
825
+ pushConfigsByTask.set(taskId, taskConfigs);
826
+ }
827
+ taskConfigs.set(entry.id, entry);
828
+ return entry;
829
+ };
830
+ const listPushNotificationConfigs = (taskId) => [...(pushConfigsByTask.get(taskId)?.values() ?? [])].sort((left, right) => left.id.localeCompare(right.id));
831
+ const getPushNotificationConfig = (taskId, configId) => pushConfigsByTask.get(taskId)?.get(configId);
832
+ const deletePushNotificationConfig = (taskId, configId) => {
833
+ const taskConfigs = pushConfigsByTask.get(taskId);
834
+ if (!taskConfigs) {
835
+ return false;
836
+ }
837
+ const deleted = taskConfigs.delete(configId);
838
+ if (taskConfigs.size === 0) {
839
+ pushConfigsByTask.delete(taskId);
840
+ }
841
+ return deleted;
842
+ };
843
+ const dispatchPushNotificationsForTask = async (taskId) => {
844
+ const task = await hydrateTaskStateFromEvents(runtime, await buildTaskFromRuntime(runtime, taskId));
845
+ if (!task) {
846
+ return;
847
+ }
848
+ const fingerprint = fingerprintTaskForPush(task);
849
+ const configs = listPushNotificationConfigs(taskId);
850
+ await Promise.all(configs.map(async (config) => {
851
+ if (config.lastFingerprint === fingerprint) {
852
+ return;
853
+ }
854
+ try {
855
+ await sendPushNotification(config, task);
856
+ config.lastFingerprint = fingerprint;
857
+ }
858
+ catch {
859
+ // Push delivery is best-effort and should not fail the transport request.
860
+ }
861
+ }));
862
+ };
863
+ const pushRelevantEventTypes = new Set(["run.state.changed", "approval.requested", "approval.resolved"]);
864
+ const unsubscribePushNotifications = runtime.subscribe((event) => {
865
+ if (!pushRelevantEventTypes.has(event.eventType) || !pushConfigsByTask.has(event.runId)) {
866
+ return;
867
+ }
868
+ void dispatchPushNotificationsForTask(event.runId);
869
+ });
680
870
  const httpServer = createServer(async (request, response) => {
681
871
  try {
682
872
  const requestUrl = new URL(request.url ?? "/", `http://${hostname}`);
@@ -720,6 +910,10 @@ export async function serveA2aOverHttp(runtime, options = {}) {
720
910
  const requestRecord = await runtime.getRun(result.runId);
721
911
  const approvals = await runtime.listApprovals({ threadId: result.threadId, runId: result.runId });
722
912
  const task = buildTaskFromSessionAndRequest(toSessionRecord(session), toRequestRecord(requestRecord), approvals, result.output);
913
+ if (task && parsed.pushNotificationConfig) {
914
+ registerPushNotificationConfig(task.id, parsed.pushNotificationConfig);
915
+ await dispatchPushNotificationsForTask(task.id);
916
+ }
723
917
  writeJson(response, 200, toSuccess(payload.id ?? null, toSendMessageResult(payload.method, task)));
724
918
  return;
725
919
  }
@@ -729,12 +923,24 @@ export async function serveA2aOverHttp(runtime, options = {}) {
729
923
  return;
730
924
  }
731
925
  const parsed = parseMessageSendParams(payload.params);
732
- await streamTaskUpdates(response, payload.id ?? null, payload.method, streamSendMessageTaskUpdates(runtime, {
926
+ let registeredPushTaskId;
927
+ const sourceIterator = streamSendMessageTaskUpdates(runtime, {
733
928
  agentId: parsed.agentId ?? options.defaultAgentId,
734
929
  input: parsed.input,
735
930
  ...(parsed.sessionId ? { threadId: parsed.sessionId } : {}),
736
931
  invocation: attachServiceParameters(parsed.invocation, serviceParameters),
737
- }, payload.method));
932
+ }, payload.method);
933
+ const iterator = (async function* () {
934
+ for await (const item of sourceIterator) {
935
+ if (item.task && parsed.pushNotificationConfig && !registeredPushTaskId) {
936
+ registeredPushTaskId = item.task.id;
937
+ registerPushNotificationConfig(item.task.id, parsed.pushNotificationConfig);
938
+ await dispatchPushNotificationsForTask(item.task.id);
939
+ }
940
+ yield item;
941
+ }
942
+ }());
943
+ await streamTaskUpdates(response, payload.id ?? null, payload.method, iterator);
738
944
  return;
739
945
  }
740
946
  if (payload.method === "tasks/get" || payload.method === "GetTask") {
@@ -777,18 +983,55 @@ export async function serveA2aOverHttp(runtime, options = {}) {
777
983
  writeJson(response, 200, toSuccess(payload.id ?? null, {
778
984
  task,
779
985
  streamable: false,
780
- pushNotifications: false,
986
+ pushNotifications: true,
781
987
  nextPollAfterMs: task.status.state === "TASK_STATE_COMPLETED" || task.status.state === "TASK_STATE_FAILED" || task.status.state === "TASK_STATE_CANCELED"
782
988
  ? 0
783
989
  : 1_000,
784
990
  }));
785
991
  return;
786
992
  }
787
- if (payload.method === "CreateTaskPushNotificationConfig"
788
- || payload.method === "GetTaskPushNotificationConfig"
789
- || payload.method === "ListTaskPushNotificationConfigs"
790
- || payload.method === "DeleteTaskPushNotificationConfig") {
791
- writeJson(response, 200, toError(payload.id ?? null, -32003, "A2A push notifications are not supported by this bridge."));
993
+ if (payload.method === "CreateTaskPushNotificationConfig") {
994
+ const { taskId, config } = parseCreatePushNotificationConfigParams(payload.params);
995
+ if (!await buildTaskFromRuntime(runtime, taskId)) {
996
+ writeJson(response, 200, toError(payload.id ?? null, -32001, "Task not found."));
997
+ return;
998
+ }
999
+ const created = registerPushNotificationConfig(taskId, config);
1000
+ await dispatchPushNotificationsForTask(taskId);
1001
+ writeJson(response, 200, toSuccess(payload.id ?? null, clonePushNotificationConfig(created)));
1002
+ return;
1003
+ }
1004
+ if (payload.method === "GetTaskPushNotificationConfig") {
1005
+ const { taskId, configId } = parsePushNotificationLocatorParams(payload.params);
1006
+ const config = getPushNotificationConfig(taskId, configId);
1007
+ if (!config) {
1008
+ writeJson(response, 200, toError(payload.id ?? null, -32001, "Push notification config not found."));
1009
+ return;
1010
+ }
1011
+ writeJson(response, 200, toSuccess(payload.id ?? null, clonePushNotificationConfig(config)));
1012
+ return;
1013
+ }
1014
+ if (payload.method === "ListTaskPushNotificationConfigs") {
1015
+ const { taskId, pageSize, pageToken } = parsePushNotificationListParams(payload.params);
1016
+ const configs = listPushNotificationConfigs(taskId);
1017
+ const startIndex = pageToken ? Number.parseInt(Buffer.from(pageToken, "base64url").toString("utf8"), 10) || 0 : 0;
1018
+ const page = configs.slice(startIndex, startIndex + pageSize).map(clonePushNotificationConfig);
1019
+ const nextIndex = startIndex + page.length;
1020
+ const nextPageToken = nextIndex < configs.length ? Buffer.from(String(nextIndex), "utf8").toString("base64url") : "";
1021
+ const result = {
1022
+ configs: page,
1023
+ nextPageToken,
1024
+ };
1025
+ writeJson(response, 200, toSuccess(payload.id ?? null, result));
1026
+ return;
1027
+ }
1028
+ if (payload.method === "DeleteTaskPushNotificationConfig") {
1029
+ const { taskId, configId } = parsePushNotificationLocatorParams(payload.params);
1030
+ if (!deletePushNotificationConfig(taskId, configId)) {
1031
+ writeJson(response, 200, toError(payload.id ?? null, -32001, "Push notification config not found."));
1032
+ return;
1033
+ }
1034
+ writeJson(response, 200, toSuccess(payload.id ?? null, { deleted: true, id: configId, taskId }));
792
1035
  return;
793
1036
  }
794
1037
  if (payload.method === "GetExtendedAgentCard") {
@@ -833,6 +1076,7 @@ export async function serveA2aOverHttp(runtime, options = {}) {
833
1076
  agentCardUrl: `http://${hostname}:${resolvedPort}${agentCardPath}`,
834
1077
  completed,
835
1078
  close: async () => {
1079
+ unsubscribePushNotifications();
836
1080
  await new Promise((resolve, reject) => {
837
1081
  httpServer.close((error) => {
838
1082
  if (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.223",
3
+ "version": "0.0.224",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",