@harness-gw/sdk 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -63,7 +63,7 @@ You need a running harness. Hermes is the primary one; see
63
63
  docker run --rm \
64
64
  -v "$PWD/config.yaml:/run/harness-gw/config.yaml:ro" \
65
65
  -v "$PWD/hermes-token:/run/secrets/hermes-token:ro" \
66
- ghcr.io/almogbaku/harness-gw:0.5.1 \
66
+ ghcr.io/almogbaku/harness-gw:0.6.0 \
67
67
  config check --config /run/harness-gw/config.yaml
68
68
  ```
69
69
 
@@ -73,7 +73,7 @@ You need a running harness. Hermes is the primary one; see
73
73
  docker run --rm -p 127.0.0.1:4100:4100 \
74
74
  -v "$PWD/config.yaml:/run/harness-gw/config.yaml:ro" \
75
75
  -v "$PWD/hermes-token:/run/secrets/hermes-token:ro" \
76
- ghcr.io/almogbaku/harness-gw:0.5.1 \
76
+ ghcr.io/almogbaku/harness-gw:0.6.0 \
77
77
  serve --config /run/harness-gw/config.yaml
78
78
  ```
79
79
 
@@ -208,7 +208,7 @@ var SessionMessagePartSchema = z.discriminatedUnion("type", [
208
208
  }),
209
209
  z.strictObject({
210
210
  type: z.literal("image"),
211
- image: z.string().min(1).max(25000000),
211
+ image: z.string().min(1).max(35000000),
212
212
  filename: z.string().min(1).max(4096).optional()
213
213
  }),
214
214
  z.strictObject({
@@ -727,8 +727,10 @@ var HGW_JSONRPC_ERRORS2 = {
727
727
  revisionConflict: -31012,
728
728
  temporarilyUnavailable: -31013,
729
729
  uncertainMutation: -31014,
730
- unsupported: -31015
730
+ unsupported: -31015,
731
+ requestTooLarge: -31016
731
732
  };
733
+ var HGW_MAX_FRAME_BYTES2 = 1100000;
732
734
  var IdentifierSchema2 = z2.string().min(1).max(256).refine((value) => [...value].every((character) => {
733
735
  const code = character.charCodeAt(0);
734
736
  return code >= 32 && code !== 127;
@@ -1180,4 +1182,4 @@ var MCP_APP_SANDBOX_CSP2 = [
1180
1182
  "frame-ancestors 'self'"
1181
1183
  ].join("; ");
1182
1184
 
1183
- export { SESSION_CATALOG_MAX_WINDOW2, RuntimeAuthStateSchema2, OperationCapabilitySchema2, RuntimeInfoSchema2, AgentAvatarSchema2, AgentCatalogEntrySchema2, AgentCatalogResponseSchema2, AgentUpdateFields2, writesAgentField2, AgentUpdateRequestSchema2, AgentUpdateResponseSchema2, SessionStatusSchema2, SessionPlatformSchema2, SessionSchema2, newestSessionFirst2, SessionCatalogResponseSchema2, StopReason2, ToolKind2, ToolLocationSchema2, DiffOperation2, DiffChangeSchema2, ToolDiffSchema2, SessionMessageErrorStatusSchema2, SessionMessageSchema2, SessionPlanActivityMessageSchema2, SessionHistoryResponseSchema2, SessionCreateRequestSchema2, SessionCreateResponseSchema2, SessionPatchRequestSchema2, TurnStopResponseSchema2, TurnSteerRequestSchema2, TurnSteerResponseSchema2, SlashCommandSchema2, MAX_SLASH_COMMANDS2, INTERACTION_PROTOCOL2, SessionWorkspaceCapabilitiesResponseSchema2, GuestRuntimeCapabilitiesResponseSchema2, SessionModelsResponseSchema2, SessionModelUpdateRequestSchema2, SessionModelUpdateResponseSchema2, SessionContextResponseSchema2, ArtifactDescriptorSchema2, SessionTodosResponseSchema2, SessionActivityResponseSchema2, SessionAttachmentStageRequestSchema2, SessionAttachmentStageResponseSchema2, SessionAudioResponseSchema2, SessionTranscriptionRequestSchema2, SessionTranscriptionResponseSchema2, SessionSpeechRequestSchema2, ErrorResponseSchema2, ACP_PROTOCOL_VERSION2, HGW_API_PREFIX2, HGW_ACP_PATH2, HGW_ACP_AGENTS_PATH2, hgwAcpAgentPath2, HGW_META_KEY2, HGW_EXTENSION_VERSION2, HGW_AUTH_METHOD_INVITE2, HGW_ATTACHMENT_URI_SCHEME2, HGW_ARTIFACT_URI_SCHEME2, HGW_METHODS2, HGW_STOP_REASONS2, HGW_PERMISSION_KIND_SESSION2, HGW_PLAN_ID2, HGW_JSONRPC_ERRORS2, IdentifierSchema2, HgwExtensionsSchema2, HgwClientCapabilitiesMetaSchema2, HgwInitializeMetaSchema2, HgwLoginMetaSchema2, HgwSessionNewMetaSchema2, HgwSessionListMetaSchema2, HgwSessionSearchMetaSchema2, HgwSessionSearchResponseSchema2, HgwSessionInfoMetaSchema2, HgwSessionResumeMetaSchema2, HGW_REPLAY_BEFORE2, HgwReplayBeforeSchema2, HgwHistoryCursorSchema2, HgwHistoryPageResponseMetaSchema2, HgwHistoryPageTagSchema2, HgwSessionResumeResponseMetaSchema2, HgwPromptMetaSchema2, HgwSessionUpdateRequestSchema2, HgwSessionPartRequestSchema2, HgwSteerRequestSchema2, HgwSteerResponseSchema2, HgwFocusRequestSchema2, HgwAgentsListResponseSchema2, HgwAgentUpdateRequestSchema2, HgwAgentUpdateResponseSchema2, HgwTurnMetaSchema2, HgwNoticeMetaSchema2, HgwMessageMetaSchema2, HgwCostSchema2, HGW_SUBAGENT_STATUSES2, HgwSubagentSchema2, HgwStateMetaSchema2, HgwChunkMetaSchema2, HgwToolCallMetaSchema2, HgwPlanMetaSchema2, HgwUsageMetaSchema2, HgwAvailableCommandsMetaSchema2, HgwPermissionMetaSchema2, HgwQuestionOptionSchema2, HgwQuestionSchema2, HgwElicitationMetaSchema2, HgwArtifactDescriptorSchema2, formatArtifactUri2, parseArtifactUri2, HgwComposerPrefillNotificationSchema2, HgwErrorNotificationSchema2, HGW_REQUEST_TEXT_MAX2, HgwActivityNotificationSchema2, PushCategorySchema2, categoryOf2, PushLocaleSchema2, PRESENCE_IDLE_MS2, PRESENCE_HEARTBEAT_MS2, PRESENCE_GRACE_MS2, PRESENCE_CLOSED_GRACE_MS2, COALESCE_WINDOW_MS2, PUSH_TEXT_LIMITS2, PushMessageSchema2, PushCategoriesSchema2, PushSubscriptionJsonSchema2, PushInfoSchema2, PushRegistrationSchema2, PushUnregistrationSchema2, CallToolResultSchema2, ReadResourceResultSchema2, McpUiCspSchema2, isMcpAppCspDomain2, McpUiPermissionsSchema2, McpAppFilesSchema2, McpAppViewSchema2, viewerFileResult2, McpAppToolCallRequestSchema2, McpAppResourceReadRequestSchema2, MCP_APP_SANDBOX_PATH2, MCP_APP_SANDBOX_CSP2 };
1185
+ export { SESSION_CATALOG_MAX_WINDOW2, RuntimeAuthStateSchema2, OperationCapabilitySchema2, RuntimeInfoSchema2, AgentAvatarSchema2, AgentCatalogEntrySchema2, AgentCatalogResponseSchema2, AgentUpdateFields2, writesAgentField2, AgentUpdateRequestSchema2, AgentUpdateResponseSchema2, SessionStatusSchema2, SessionPlatformSchema2, SessionSchema2, newestSessionFirst2, SessionCatalogResponseSchema2, StopReason2, ToolKind2, ToolLocationSchema2, DiffOperation2, DiffChangeSchema2, ToolDiffSchema2, SessionMessageErrorStatusSchema2, SessionMessageSchema2, SessionPlanActivityMessageSchema2, SessionHistoryResponseSchema2, SessionCreateRequestSchema2, SessionCreateResponseSchema2, SessionPatchRequestSchema2, TurnStopResponseSchema2, TurnSteerRequestSchema2, TurnSteerResponseSchema2, SlashCommandSchema2, MAX_SLASH_COMMANDS2, INTERACTION_PROTOCOL2, SessionWorkspaceCapabilitiesResponseSchema2, GuestRuntimeCapabilitiesResponseSchema2, SessionModelsResponseSchema2, SessionModelUpdateRequestSchema2, SessionModelUpdateResponseSchema2, SessionContextResponseSchema2, ArtifactDescriptorSchema2, SessionTodosResponseSchema2, SessionActivityResponseSchema2, SessionAttachmentStageRequestSchema2, SessionAttachmentStageResponseSchema2, SessionAudioResponseSchema2, SessionTranscriptionRequestSchema2, SessionTranscriptionResponseSchema2, SessionSpeechRequestSchema2, ErrorResponseSchema2, ACP_PROTOCOL_VERSION2, HGW_API_PREFIX2, HGW_ACP_PATH2, HGW_ACP_AGENTS_PATH2, hgwAcpAgentPath2, HGW_META_KEY2, HGW_EXTENSION_VERSION2, HGW_AUTH_METHOD_INVITE2, HGW_ATTACHMENT_URI_SCHEME2, HGW_ARTIFACT_URI_SCHEME2, HGW_METHODS2, HGW_STOP_REASONS2, HGW_PERMISSION_KIND_SESSION2, HGW_PLAN_ID2, HGW_JSONRPC_ERRORS2, HGW_MAX_FRAME_BYTES2, IdentifierSchema2, HgwExtensionsSchema2, HgwClientCapabilitiesMetaSchema2, HgwInitializeMetaSchema2, HgwLoginMetaSchema2, HgwSessionNewMetaSchema2, HgwSessionListMetaSchema2, HgwSessionSearchMetaSchema2, HgwSessionSearchResponseSchema2, HgwSessionInfoMetaSchema2, HgwSessionResumeMetaSchema2, HGW_REPLAY_BEFORE2, HgwReplayBeforeSchema2, HgwHistoryCursorSchema2, HgwHistoryPageResponseMetaSchema2, HgwHistoryPageTagSchema2, HgwSessionResumeResponseMetaSchema2, HgwPromptMetaSchema2, HgwSessionUpdateRequestSchema2, HgwSessionPartRequestSchema2, HgwSteerRequestSchema2, HgwSteerResponseSchema2, HgwFocusRequestSchema2, HgwAgentsListResponseSchema2, HgwAgentUpdateRequestSchema2, HgwAgentUpdateResponseSchema2, HgwTurnMetaSchema2, HgwNoticeMetaSchema2, HgwMessageMetaSchema2, HgwCostSchema2, HGW_SUBAGENT_STATUSES2, HgwSubagentSchema2, HgwStateMetaSchema2, HgwChunkMetaSchema2, HgwToolCallMetaSchema2, HgwPlanMetaSchema2, HgwUsageMetaSchema2, HgwAvailableCommandsMetaSchema2, HgwPermissionMetaSchema2, HgwQuestionOptionSchema2, HgwQuestionSchema2, HgwElicitationMetaSchema2, HgwArtifactDescriptorSchema2, formatArtifactUri2, parseArtifactUri2, HgwComposerPrefillNotificationSchema2, HgwErrorNotificationSchema2, HGW_REQUEST_TEXT_MAX2, HgwActivityNotificationSchema2, PushCategorySchema2, categoryOf2, PushLocaleSchema2, PRESENCE_IDLE_MS2, PRESENCE_HEARTBEAT_MS2, PRESENCE_GRACE_MS2, PRESENCE_CLOSED_GRACE_MS2, COALESCE_WINDOW_MS2, PUSH_TEXT_LIMITS2, PushMessageSchema2, PushCategoriesSchema2, PushSubscriptionJsonSchema2, PushInfoSchema2, PushRegistrationSchema2, PushUnregistrationSchema2, CallToolResultSchema2, ReadResourceResultSchema2, McpUiCspSchema2, isMcpAppCspDomain2, McpUiPermissionsSchema2, McpAppFilesSchema2, McpAppViewSchema2, viewerFileResult2, McpAppToolCallRequestSchema2, McpAppResourceReadRequestSchema2, MCP_APP_SANDBOX_PATH2, MCP_APP_SANDBOX_CSP2 };
@@ -1,6 +1,7 @@
1
1
  import { type SessionInfo } from "@agentclientprotocol/sdk/experimental/v2";
2
2
  import { type HgwSessionInfoMeta } from "../protocol/acp.js";
3
3
  import type { SessionMetadata, SessionStatus, TodoItem, WorkspaceActivityEvent } from "./workspace.js";
4
+ import type { Logger } from "../lifecycle/index.js";
4
5
  import type { AcpConnection } from "./types.js";
5
6
  /**
6
7
  * Everything the workspace learns by subscribing to the connection: the Session row
@@ -13,8 +14,10 @@ export type AcpSessionStoreOptions = {
13
14
  now?: () => number;
14
15
  /** A live turn of a subscribed Session just stopped, however it ended. */
15
16
  onTurnFinished?: (sessionId: string) => void;
17
+ /** Where a listener that throws is told; the others still hear. */
18
+ logger?: Logger;
16
19
  };
17
- export declare function createAcpSessionStore({ connection, now, onTurnFinished, }: AcpSessionStoreOptions): {
20
+ export declare function createAcpSessionStore({ connection, now, onTurnFinished, logger, }: AcpSessionStoreOptions): {
18
21
  subscribe: (sessionId: string, agentId?: string) => () => void;
19
22
  dispose(): void;
20
23
  /**
@@ -36,6 +39,8 @@ export declare function createAcpSessionStore({ connection, now, onTurnFinished,
36
39
  createdAt?: string;
37
40
  platform?: import("./workspace.js").SessionPlatform;
38
41
  }[];
42
+ /** Marks Sessions a read has answered for, so a missing one gates no snapshot. */
43
+ answer(sessionIds: readonly string[]): void;
39
44
  /** The provider's title, as a list page or a subscribed Session reports it. */
40
45
  setTitle: (sessionId: string, title: string) => Map<string, string>;
41
46
  /** The proxy's read state, or the operator's own optimistic ack. */
@@ -14,6 +14,7 @@ import {
14
14
  HGW_STOP_REASONS2,
15
15
  HGW_PERMISSION_KIND_SESSION2,
16
16
  HGW_JSONRPC_ERRORS2,
17
+ HGW_MAX_FRAME_BYTES2,
17
18
  HgwInitializeMetaSchema2,
18
19
  HgwSessionSearchMetaSchema2,
19
20
  HgwSessionSearchResponseSchema2,
@@ -370,6 +371,21 @@ function loggedStream(stream, logger) {
370
371
  })
371
372
  };
372
373
  }
374
+ var LOGGED_ID_LENGTH = 256;
375
+ function loggedId(row, ...path) {
376
+ let value = row;
377
+ for (const key of path)
378
+ value = typeof value === "object" && value !== null ? value[key] : undefined;
379
+ return typeof value === "string" && value.length > 0 ? value.slice(0, LOGGED_ID_LENGTH) : undefined;
380
+ }
381
+ function callEach(listeners, logger, event, ...args) {
382
+ for (const listener of [...listeners])
383
+ try {
384
+ listener(...args);
385
+ } catch (err) {
386
+ logger.warn({ err }, event);
387
+ }
388
+ }
373
389
 
374
390
  // client/connection.ts
375
391
  var TRY_AGAIN_LATER = 1013;
@@ -637,6 +653,35 @@ function sessionMachine({
637
653
  }
638
654
  });
639
655
  }
656
+ function readEach(entries, schema, skipped) {
657
+ const kept = [];
658
+ let unreadable;
659
+ for (const entry of entries) {
660
+ const read = schema.safeParse(entry);
661
+ if (read.success)
662
+ kept.push(read.data);
663
+ else {
664
+ unreadable = read.error;
665
+ skipped(entry);
666
+ }
667
+ }
668
+ if (entries.length > 0 && kept.length === 0)
669
+ throw unreadable;
670
+ return kept;
671
+ }
672
+ var AgentsListEntriesSchema = HgwAgentsListResponseSchema2.extend({
673
+ agents: z.array(z.unknown()).max(1000)
674
+ });
675
+ var SessionSearchEntriesSchema = HgwSessionSearchResponseSchema2.extend({
676
+ sessions: z.array(z.unknown())
677
+ });
678
+ var FRAME_ENVELOPE_BYTES = 1024;
679
+ var frameEncoder = new TextEncoder;
680
+ function assertFrameFits(params) {
681
+ const bytes = frameEncoder.encode(JSON.stringify(params)).byteLength;
682
+ if (bytes > HGW_MAX_FRAME_BYTES2 - FRAME_ENVELOPE_BYTES)
683
+ throw new RequestError(HGW_JSONRPC_ERRORS2.requestTooLarge, "Request too large to send");
684
+ }
640
685
  function createAcpConnection(options) {
641
686
  const {
642
687
  clientInfo,
@@ -674,23 +719,13 @@ function createAcpConnection(options) {
674
719
  if (status === next)
675
720
  return;
676
721
  status = next;
677
- for (const listener of statusListeners)
678
- listener(next);
722
+ callEach(statusListeners, logger, "acp.listener.failed", next);
679
723
  }
680
724
  function setOutage(next) {
681
725
  if (outage === next)
682
726
  return;
683
727
  outage = next;
684
- for (const listener of outageListeners)
685
- listener(next);
686
- }
687
- function settleOutage() {
688
- if (status !== "ready")
689
- return;
690
- for (const open of sessions.values())
691
- if (open.state === "joining")
692
- return;
693
- setOutage(undefined);
728
+ callEach(outageListeners, logger, "acp.listener.failed", next);
694
729
  }
695
730
  function emitPending(request) {
696
731
  for (const listener of pendingListeners)
@@ -708,7 +743,9 @@ function createAcpConnection(options) {
708
743
  sendLivenessProbeAsync();
709
744
  }, LIVENESS_SILENCE_MS);
710
745
  }
746
+ let lastInboundAt = Number.NEGATIVE_INFINITY;
711
747
  function onInbound() {
748
+ lastInboundAt = clock.now();
712
749
  armSilenceTimer();
713
750
  }
714
751
  function sendLivenessProbeAsync() {
@@ -766,14 +803,13 @@ function createAcpConnection(options) {
766
803
  if (open.replaying)
767
804
  open.replaying.delivered = true;
768
805
  for (const { listener } of open.listeners)
769
- listener.update?.(params.update, meta);
806
+ callEach(listener.update ? [listener.update] : [], logger, "acp.listener.failed", params.update, meta);
770
807
  }).onRequest(methods2.client.session.requestPermission, ({ params, signal }) => permissionRequest(params, signal)).onRequest(methods2.client.elicitation.create, ({ params, signal }) => elicitationRequest(params, signal));
771
808
  for (const [method, parser] of Object.entries(NOTIFICATION_PARSERS))
772
809
  app.onNotification(method, parser, ({ params }) => {
773
810
  if (method === HGW_METHODS2.notify.error)
774
811
  noticeGone(params);
775
- for (const listener of notificationListeners.get(method) ?? [])
776
- listener(params);
812
+ callEach(notificationListeners.get(method) ?? [], logger, "acp.listener.failed", params);
777
813
  });
778
814
  function noticeGone(params) {
779
815
  const notice = HgwErrorNotificationSchema2.safeParse(params).data;
@@ -796,14 +832,15 @@ function createAcpConnection(options) {
796
832
  }
797
833
  async function requestOn({ connection }, tier, send, parent) {
798
834
  const deadline = new Deadline(REQUEST_DEADLINE_MS[tier], clock, parent);
835
+ const sentAt = clock.now();
799
836
  try {
800
837
  return await deadline.run((cancellationSignal) => send(connection.agent, { cancellationSignal }));
801
838
  } catch (error) {
802
839
  if (deadline.signal.aborted && !parent?.aborted) {
803
- if (tier === "probe")
804
- connection.close(error);
805
- else
840
+ if (tier !== "probe")
806
841
  sendLivenessProbeAsync();
842
+ else if (lastInboundAt <= sentAt)
843
+ connection.close(error);
807
844
  }
808
845
  throw error;
809
846
  }
@@ -921,7 +958,11 @@ function createAcpConnection(options) {
921
958
  }
922
959
  }
923
960
  async function listAgents() {
924
- return HgwAgentsListResponseSchema2.parse(await request("short", (agent, options) => agent.request(HGW_METHODS2.agents.list, undefined, options)));
961
+ const response = AgentsListEntriesSchema.parse(await request("short", (agent, options) => agent.request(HGW_METHODS2.agents.list, undefined, options)));
962
+ return {
963
+ ...response,
964
+ agents: readEach(response.agents, HgwAgentsListResponseSchema2.shape.agents.element, (entry) => logger.warn({ id: loggedId(entry, "summary", "id") }, "agents.entry_skipped"))
965
+ };
925
966
  }
926
967
  function settleOf({ listener }) {
927
968
  const settle = listener.replay?.();
@@ -976,8 +1017,7 @@ function createAcpConnection(options) {
976
1017
  if (next === "joined" || failure !== undefined)
977
1018
  open.waiters = undefined;
978
1019
  for (const { listener } of open.listeners)
979
- listener.state?.(next);
980
- settleOutage();
1020
+ callEach(listener.state ? [listener.state] : [], logger, "acp.listener.failed", next);
981
1021
  }
982
1022
  function subscribe(sessionId, listener) {
983
1023
  if (ended)
@@ -1028,7 +1068,6 @@ function createAcpConnection(options) {
1028
1068
  open.owner?.dispose();
1029
1069
  open.waiters?.reject(notOpen());
1030
1070
  open.waiters = undefined;
1031
- settleOutage();
1032
1071
  if (open.state === "gone")
1033
1072
  return;
1034
1073
  request("short", (agent, options) => agent.request(HGW_METHODS2.session.part, { sessionId }, options)).catch((err) => {
@@ -1063,6 +1102,7 @@ function createAcpConnection(options) {
1063
1102
  sendLivenessProbeAsync();
1064
1103
  }
1065
1104
  joinable = true;
1105
+ setOutage(undefined);
1066
1106
  const opened = [...sessions.keys()];
1067
1107
  for (const open of sessions.values())
1068
1108
  open.owner?.actor.send({ type: "ready" });
@@ -1102,7 +1142,6 @@ function createAcpConnection(options) {
1102
1142
  closeTransport: () => live?.connection.close(),
1103
1143
  markReady() {
1104
1144
  setStatus("ready");
1105
- settleOutage();
1106
1145
  },
1107
1146
  lose(next) {
1108
1147
  recovering = true;
@@ -1215,9 +1254,13 @@ function createAcpConnection(options) {
1215
1254
  };
1216
1255
  },
1217
1256
  async searchSessions(meta) {
1218
- return HgwSessionSearchResponseSchema2.parse(await request("short", (agent, options) => agent.request(HGW_METHODS2.sessions.search, {
1257
+ const response = SessionSearchEntriesSchema.parse(await request("short", (agent, options) => agent.request(HGW_METHODS2.sessions.search, {
1219
1258
  _meta: { [HGW_META_KEY2]: HgwSessionSearchMetaSchema2.parse(meta) }
1220
1259
  }, options)));
1260
+ return {
1261
+ ...response,
1262
+ sessions: readEach(response.sessions, HgwSessionSearchResponseSchema2.shape.sessions.element, (entry) => logger.warn({ id: loggedId(entry, "sessionId") }, "sessions.result_skipped"))
1263
+ };
1221
1264
  },
1222
1265
  subscribe,
1223
1266
  joined,
@@ -1226,7 +1269,13 @@ function createAcpConnection(options) {
1226
1269
  resumePage,
1227
1270
  history: (sessionId) => sessions.get(sessionId)?.history,
1228
1271
  async prompt(sessionId, blocks, meta) {
1229
- const response = await write(sessionId, "long", (agent, options) => agent.request(methods2.agent.session.prompt, { sessionId, prompt: blocks, _meta: { [HGW_META_KEY2]: meta } }, options));
1272
+ const params = {
1273
+ sessionId,
1274
+ prompt: blocks,
1275
+ _meta: { [HGW_META_KEY2]: meta }
1276
+ };
1277
+ assertFrameFits(params);
1278
+ const response = await write(sessionId, "long", (agent, options) => agent.request(methods2.agent.session.prompt, params, options));
1230
1279
  return { messageId: response.messageId };
1231
1280
  },
1232
1281
  cancel(sessionId) {
@@ -1243,6 +1292,7 @@ function createAcpConnection(options) {
1243
1292
  await request("short", (agent, options) => agent.request(HGW_METHODS2.session.update, update, options));
1244
1293
  },
1245
1294
  async steer(steer) {
1295
+ assertFrameFits(steer);
1246
1296
  return HgwSteerResponseSchema2.parse(await write(steer.sessionId, "medium", (agent, options) => agent.request(HGW_METHODS2.session.steer, steer, options)));
1247
1297
  },
1248
1298
  focus(sessionId, presence) {
@@ -1558,7 +1608,8 @@ function sameRow(left, right) {
1558
1608
  function createAcpSessionStore({
1559
1609
  connection,
1560
1610
  now = Date.now,
1561
- onTurnFinished
1611
+ onTurnFinished,
1612
+ logger = SILENT_LOGGER
1562
1613
  }) {
1563
1614
  const rows = new Map;
1564
1615
  const titles = new Map;
@@ -1577,11 +1628,12 @@ function createAcpSessionStore({
1577
1628
  return row ? [{ ...row }] : [];
1578
1629
  });
1579
1630
  }
1631
+ const answered = new Set;
1580
1632
  function notify(subscription) {
1581
1633
  for (const sessionId of subscription.sessionIds)
1582
- if (!rows.has(sessionId))
1634
+ if (!rows.has(sessionId) && !answered.has(sessionId))
1583
1635
  return;
1584
- subscription.listener(rowsFor(subscription.sessionIds));
1636
+ callEach([subscription.listener], logger, "sessions.listener.failed", rowsFor(subscription.sessionIds));
1585
1637
  }
1586
1638
  function publish(sessionId) {
1587
1639
  for (const subscription of subscriptions)
@@ -1623,15 +1675,13 @@ function createAcpSessionStore({
1623
1675
  function setTodos(sessionId, next) {
1624
1676
  todos.set(sessionId, next);
1625
1677
  for (const listener of todoListeners.get(sessionId) ?? [])
1626
- listener(next.map((todo) => ({ ...todo })));
1678
+ callEach([listener], logger, "sessions.listener.failed", next.map((todo) => ({ ...todo })));
1627
1679
  }
1628
1680
  function emitActivity(event) {
1629
- for (const listener of activityListeners)
1630
- listener(event);
1681
+ callEach(activityListeners, logger, "sessions.listener.failed", event);
1631
1682
  }
1632
1683
  function invalidate(sessionId) {
1633
- for (const listener of invalidationListeners.get(sessionId) ?? [])
1634
- listener();
1684
+ callEach(invalidationListeners.get(sessionId) ?? [], logger, "sessions.listener.failed");
1635
1685
  }
1636
1686
  function acceptUpdate(sessionId, update, meta) {
1637
1687
  if (SessionUpdate2.isStateUpdate(update)) {
@@ -1726,6 +1776,13 @@ function createAcpSessionStore({
1726
1776
  },
1727
1777
  put,
1728
1778
  rowsFor,
1779
+ answer(sessionIds) {
1780
+ for (const sessionId of sessionIds)
1781
+ answered.add(sessionId);
1782
+ for (const subscription of subscriptions)
1783
+ if (sessionIds.some((id) => subscription.sessionIds.has(id)))
1784
+ notify(subscription);
1785
+ },
1729
1786
  setTitle: (sessionId, title) => titles.set(sessionId, title),
1730
1787
  setUnread: (sessionId, unread) => patch(sessionId, { unread }),
1731
1788
  setPinned: (sessionId, pinned) => patch(sessionId, { pinned }),
@@ -1807,6 +1864,7 @@ function createAcpWorkspaceClient({
1807
1864
  }) {
1808
1865
  const store = createAcpSessionStore({
1809
1866
  connection,
1867
+ logger: clientLog,
1810
1868
  ...now ? { now } : {},
1811
1869
  onTurnFinished: (sessionId) => {
1812
1870
  reportCreatedAgents(sessionId).catch((err) => clientLog.warn({ err }, "agent.report_created_failed"));
@@ -1853,12 +1911,25 @@ function createAcpWorkspaceClient({
1853
1911
  return response;
1854
1912
  }
1855
1913
  function rememberRows(sessions) {
1914
+ const kept = [];
1915
+ let unreadable;
1856
1916
  for (const session of sessions) {
1857
- const row = rowOf(session);
1917
+ let row;
1918
+ try {
1919
+ row = rowOf(session);
1920
+ } catch (error) {
1921
+ unreadable = error;
1922
+ clientLog.warn({ id: loggedId(session, "sessionId") }, "sessions.row_skipped");
1923
+ continue;
1924
+ }
1858
1925
  remember(row.sessionId, row.info, row.updatedAt);
1859
1926
  if (row.title)
1860
1927
  store.setTitle(row.sessionId, row.title);
1928
+ kept.push(session);
1861
1929
  }
1930
+ if (sessions.length > 0 && kept.length === 0)
1931
+ throw unreadable;
1932
+ return kept;
1862
1933
  }
1863
1934
  const pageReads = new Map;
1864
1935
  let catalogScope;
@@ -1867,10 +1938,7 @@ function createAcpWorkspaceClient({
1867
1938
  const inFlight = pageReads.get(key);
1868
1939
  if (inFlight)
1869
1940
  return inFlight;
1870
- const read = connection.listSessions(meta, cursor).then((page) => {
1871
- rememberRows(page.sessions);
1872
- return page;
1873
- }).finally(() => pageReads.delete(key));
1941
+ const read = connection.listSessions(meta, cursor).then((page) => ({ ...page, sessions: rememberRows(page.sessions) })).finally(() => pageReads.delete(key));
1874
1942
  pageReads.set(key, read);
1875
1943
  return read;
1876
1944
  }
@@ -1938,16 +2006,16 @@ function createAcpWorkspaceClient({
1938
2006
  let page;
1939
2007
  try {
1940
2008
  page = await listSessions();
1941
- } catch {
2009
+ } catch (err) {
1942
2010
  if (stale(generation))
1943
2011
  return;
2012
+ clientLog.warn({ err, attempt }, "sessions.relist_retry_failed");
1944
2013
  attempt += 1;
1945
2014
  await backOff(backoffDelay(attempt, RELIST_BACKOFF));
1946
2015
  continue;
1947
2016
  }
1948
2017
  const sessionIds = page.sessions.map(({ sessionId }) => sessionId);
1949
- for (const listener of catalogListeners)
1950
- listener(sessionIds);
2018
+ callEach(catalogListeners, clientLog, "sessions.catalog_listener.failed", sessionIds);
1951
2019
  return;
1952
2020
  }
1953
2021
  }
@@ -2048,9 +2116,9 @@ function createAcpWorkspaceClient({
2048
2116
  query,
2049
2117
  ...agentId === undefined ? {} : { agentId }
2050
2118
  });
2051
- rememberRows(sessions);
2052
- const titles = new Map(sessions.map(({ sessionId, title }) => [sessionId, title ?? ""]));
2053
- return store.rowsFor(sessions.map(({ sessionId }) => sessionId)).map(({ sessionId, agentId, updatedAt }) => ({
2119
+ const kept = rememberRows(sessions);
2120
+ const titles = new Map(kept.map(({ sessionId, title }) => [sessionId, title ?? ""]));
2121
+ return store.rowsFor(kept.map(({ sessionId }) => sessionId)).map(({ sessionId, agentId, updatedAt }) => ({
2054
2122
  sessionId,
2055
2123
  agentId,
2056
2124
  title: titles.get(sessionId) ?? "",
@@ -2063,6 +2131,7 @@ function createAcpWorkspaceClient({
2063
2131
  },
2064
2132
  async getSessionMetadata(sessionIds) {
2065
2133
  await readRows(sessionIds);
2134
+ store.answer(sessionIds);
2066
2135
  return store.rowsFor(sessionIds);
2067
2136
  },
2068
2137
  subscribeSessionMetadata: store.subscribeMetadata,
@@ -28,4 +28,15 @@ export declare function frameFields(direction: "in" | "out", message: unknown):
28
28
  };
29
29
  /** `stream` with one `acp.frame` debug line per message, each way. */
30
30
  export declare function loggedStream(stream: WireStream, logger: Logger): WireStream;
31
+ /**
32
+ * The id at `path` in a row not yet checked, bounded for a warning. A warning
33
+ * about a rejected row names it by this alone, never by its content.
34
+ */
35
+ export declare function loggedId(row: unknown, ...path: string[]): string | undefined;
36
+ /**
37
+ * Calls every listener with `args`, each on its own: one that throws is
38
+ * logged as `event` and costs only itself, never the listeners after it nor
39
+ * the caller that fanned out to them.
40
+ */
41
+ export declare function callEach<Args extends readonly unknown[]>(listeners: Iterable<(...args: Args) => void>, logger: Logger, event: string, ...args: Args): void;
31
42
  export {};
@@ -75,7 +75,10 @@ export interface AcpConnection {
75
75
  /** Resolves with `InitializeResponse._meta.hgw` once the handshake settles. */
76
76
  readonly initialized: Promise<z.infer<typeof HgwInitializeMetaSchema>>;
77
77
  subscribeStatus(listener: (status: AcpConnectionStatus) => void): () => void;
78
- /** Set from a close until every resumed Session has rejoined. */
78
+ /**
79
+ * Set from a close until the transport is ready again. Each Session's
80
+ * rejoin is its own state (`sessionState`), not the connection's.
81
+ */
79
82
  readonly outage: AcpConnectionOutage | undefined;
80
83
  subscribeOutage(listener: (outage: AcpConnectionOutage | undefined) => void): () => void;
81
84
  /**
@@ -90,7 +90,17 @@ export declare const HGW_JSONRPC_ERRORS: {
90
90
  readonly temporarilyUnavailable: -31013;
91
91
  readonly uncertainMutation: -31014;
92
92
  readonly unsupported: -31015;
93
+ /**
94
+ * Params too large for one frame. The client raises it before sending and
95
+ * the gateway never sends it, so it has no machine name on the wire.
96
+ */
97
+ readonly requestTooLarge: -31016;
93
98
  };
99
+ /**
100
+ * The largest inbound WebSocket frame the gateway takes, in bytes. A larger
101
+ * one closes the socket, and with it every Session on the tab.
102
+ */
103
+ export declare const HGW_MAX_FRAME_BYTES = 1100000;
94
104
  export type HgwJsonRpcErrorCode = (typeof HGW_JSONRPC_ERRORS)[keyof typeof HGW_JSONRPC_ERRORS];
95
105
  export declare const IdentifierSchema: z.ZodString;
96
106
  export declare const HgwExtensionsSchema: z.ZodObject<{
@@ -65,6 +65,7 @@ import {
65
65
  HGW_PERMISSION_KIND_SESSION2,
66
66
  HGW_PLAN_ID2,
67
67
  HGW_JSONRPC_ERRORS2,
68
+ HGW_MAX_FRAME_BYTES2,
68
69
  IdentifierSchema2,
69
70
  HgwExtensionsSchema2,
70
71
  HgwClientCapabilitiesMetaSchema2,
@@ -215,6 +216,7 @@ export {
215
216
  HGW_AUTH_METHOD_INVITE2 as HGW_AUTH_METHOD_INVITE,
216
217
  HGW_EXTENSION_VERSION2 as HGW_EXTENSION_VERSION,
217
218
  HGW_JSONRPC_ERRORS2 as HGW_JSONRPC_ERRORS,
219
+ HGW_MAX_FRAME_BYTES2 as HGW_MAX_FRAME_BYTES,
218
220
  HGW_META_KEY2 as HGW_META_KEY,
219
221
  HGW_METHODS2 as HGW_METHODS,
220
222
  HGW_PERMISSION_KIND_SESSION2 as HGW_PERMISSION_KIND_SESSION,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harness-gw/sdk",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "A gateway that puts Hermes, OpenClaw, and OpenCode behind one ACP WebSocket, and the browser client and protocol SDK for it.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",