@harness-gw/sdk 0.7.0 → 0.8.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.7.0 \
66
+ ghcr.io/almogbaku/harness-gw:0.8.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.7.0 \
76
+ ghcr.io/almogbaku/harness-gw:0.8.0 \
77
77
  serve --config /run/harness-gw/config.yaml
78
78
  ```
79
79
 
@@ -21,7 +21,7 @@ export type AcpApprovalOption = {
21
21
  };
22
22
  export type AcpApproval = {
23
23
  readonly id: string;
24
- /** The tool call the request guards, when the proxy knows it. */
24
+ /** The tool call the request guards, when the gateway knows it. */
25
25
  readonly toolCallId?: string;
26
26
  /** The operation asked for, which is the request's title. */
27
27
  readonly action: string;
@@ -43,8 +43,11 @@ export declare function createAcpSessionStore({ connection, now, onTurnFinished,
43
43
  answer(sessionIds: readonly string[]): void;
44
44
  /** The provider's title, as a list page or a subscribed Session reports it. */
45
45
  setTitle: (sessionId: string, title: string) => Map<string, string>;
46
- /** The proxy's read state, or the operator's own optimistic ack. */
47
- setUnread: (sessionId: string, unread: boolean) => void;
46
+ /**
47
+ * The gateway's read state, or the operator's own optimistic write;
48
+ * `undefined` restores a refused one.
49
+ */
50
+ setUnread: (sessionId: string, unread: boolean | undefined) => void;
48
51
  /** The operator's own optimistic pin; `undefined` restores a refused one. */
49
52
  setPinned: (sessionId: string, pinned: boolean | undefined) => void;
50
53
  setStatus: (sessionId: string, status: SessionStatus) => void;
@@ -109,6 +109,8 @@ export declare function createAcpWorkspaceClient({ connection, rest, agentIdFor,
109
109
  markSessionRead(sessionId: string): Promise<void>;
110
110
  /** The row leads the write, so a refused pin has to be taken back. */
111
111
  setSessionPinned(sessionId: string, pinned: boolean): Promise<void>;
112
+ /** As a pin does, the row leads the write and a refusal takes it back. */
113
+ markSessionUnread(sessionId: string): Promise<void>;
112
114
  /**
113
115
  * The runtime's own report, read once. A failed read is not an answer, so
114
116
  * it is not remembered.
@@ -48,7 +48,7 @@ export type AcpConnectionOptions = {
48
48
  url?: never;
49
49
  socketConstructor?: never;
50
50
  });
51
- /** Whether the proxy refused an invitation it cannot redeem. */
51
+ /** Whether the gateway refused an invitation it cannot redeem. */
52
52
  export declare function isAuthenticationRequired(error: unknown): boolean;
53
53
  /** One of the gateway's ACP listener paths as a WebSocket URL from `base`. */
54
54
  export declare function acpSocketUrl(path: string, base: string): string;
@@ -6,7 +6,7 @@ import type { StagedAttachment } from "./workspace.js";
6
6
  export type HgwWorkspaceCapabilities = z.infer<typeof SessionWorkspaceCapabilitiesResponseSchema>;
7
7
  export type HgwModelChoices = z.infer<typeof SessionModelsResponseSchema>;
8
8
  export type HgwContext = z.infer<typeof SessionContextResponseSchema>;
9
- export type HgwClientFailure = "connection-interrupted" | "provider-unavailable" | "proxy-failure"
9
+ export type HgwClientFailure = "connection-interrupted" | "provider-unavailable" | "gateway-failure"
10
10
  /** The provider no longer holds the bytes a published receipt points at. */
11
11
  | "artifact-missing";
12
12
  /** What an MCP App view is opened for: a tool call, or a published Artifact. */
@@ -18,10 +18,10 @@ export type McpAppSubject = {
18
18
  export declare class HgwClientError extends Error {
19
19
  readonly kind: HgwClientFailure;
20
20
  readonly code?: string | undefined;
21
- /** The proxy's HTTP status, when it answered. */
21
+ /** The gateway's HTTP status, when it answered. */
22
22
  readonly status?: number | undefined;
23
23
  constructor(kind: HgwClientFailure, message?: string, code?: string | undefined,
24
- /** The proxy's HTTP status, when it answered. */
24
+ /** The gateway's HTTP status, when it answered. */
25
25
  status?: number | undefined);
26
26
  }
27
27
  /** The `fetch` the client sends every request with. */
@@ -302,7 +302,7 @@ export declare class HgwRemoteClient {
302
302
  addresses: Record<string, string>;
303
303
  expiresAt?: string | undefined;
304
304
  }>;
305
- /** A tool call the App view makes, answered by the proxy's MCP server. */
305
+ /** A tool call the App view makes, answered by the gateway's MCP server. */
306
306
  callMcpAppTool(sessionId: string, toolCallId: string, request: McpAppToolCallRequest): Promise<{
307
307
  [x: string]: unknown;
308
308
  _meta?: {
@@ -395,7 +395,7 @@ export declare class HgwRemoteClient {
395
395
  structuredContent?: unknown;
396
396
  isError?: boolean | undefined;
397
397
  }>;
398
- /** A resource read the App view makes, answered by the proxy's MCP server. */
398
+ /** A resource read the App view makes, answered by the gateway's MCP server. */
399
399
  readMcpAppResource(sessionId: string, subject: McpAppSubject, request: McpAppResourceReadRequest): Promise<{
400
400
  [x: string]: unknown;
401
401
  _meta?: {
@@ -1,4 +1,4 @@
1
1
  import type { z } from "zod";
2
2
  import type { AcpConnection } from "./types.js";
3
- /** Runs `handler` for every notification of `method` the proxy sends. */
3
+ /** Runs `handler` for every notification of `method` the gateway sends. */
4
4
  export declare function subscribeHgwNotification<Value>(connection: Pick<AcpConnection, "subscribeNotification">, method: string, schema: Pick<z.ZodType<Value>, "safeParse">, handler: (value: Value) => void): () => void;
@@ -2191,12 +2191,23 @@ function createAcpWorkspaceClient({
2191
2191
  throw reason;
2192
2192
  }
2193
2193
  },
2194
+ async markSessionUnread(sessionId) {
2195
+ const [previous] = store.rowsFor([sessionId]);
2196
+ store.setUnread(sessionId, true);
2197
+ try {
2198
+ await connection.updateSession({ sessionId, unread: true });
2199
+ } catch (reason) {
2200
+ store.setUnread(sessionId, previous?.unread);
2201
+ throw reason;
2202
+ }
2203
+ },
2194
2204
  sessionActionCapabilities() {
2195
2205
  sessionActions ??= rest.runtimeInfo().then(({ capabilities }) => ({
2196
2206
  rename: offers(capabilities.sessionTitle),
2197
2207
  archive: offers(capabilities.sessionArchival),
2198
2208
  delete: offers(capabilities.sessionDeletion),
2199
- pin: offers(capabilities.sessionPin)
2209
+ pin: offers(capabilities.sessionPin),
2210
+ unread: offers(capabilities.sessionReadState)
2200
2211
  })).catch((reason) => {
2201
2212
  sessionActions = undefined;
2202
2213
  throw reason;
@@ -2470,7 +2481,7 @@ class HgwClientError extends Error {
2470
2481
  kind;
2471
2482
  code;
2472
2483
  status;
2473
- constructor(kind, message = "AOS proxy request failed", code, status) {
2484
+ constructor(kind, message = "Gateway request failed", code, status) {
2474
2485
  super(message);
2475
2486
  this.kind = kind;
2476
2487
  this.code = code;
@@ -2504,7 +2515,7 @@ class HgwRemoteClient {
2504
2515
  this.#authorization = options.authorization;
2505
2516
  this.#origin = options.origin;
2506
2517
  }
2507
- async#read(path, schema, init, notFound = "proxy-failure") {
2518
+ async#read(path, schema, init, notFound = "gateway-failure") {
2508
2519
  let response;
2509
2520
  try {
2510
2521
  const headers = new Headers(init?.headers);
@@ -2521,17 +2532,17 @@ class HgwRemoteClient {
2521
2532
  }
2522
2533
  if (!response.ok) {
2523
2534
  const error = await normalizedError(response);
2524
- throw new HgwClientError(response.status === 503 ? "provider-unavailable" : response.status === 404 ? notFound : "proxy-failure", error?.description, error?.code, response.status);
2535
+ throw new HgwClientError(response.status === 503 ? "provider-unavailable" : response.status === 404 ? notFound : "gateway-failure", error?.description, error?.code, response.status);
2525
2536
  }
2526
2537
  let payload;
2527
2538
  try {
2528
2539
  payload = await response.json();
2529
2540
  } catch {
2530
- throw new HgwClientError("proxy-failure", "Invalid AOS proxy response");
2541
+ throw new HgwClientError("gateway-failure", "Invalid gateway response");
2531
2542
  }
2532
2543
  const parsed = schema.safeParse(payload);
2533
2544
  if (!parsed.success)
2534
- throw new HgwClientError("proxy-failure", "Invalid AOS proxy response");
2545
+ throw new HgwClientError("gateway-failure", "Invalid gateway response");
2535
2546
  return parsed.data;
2536
2547
  }
2537
2548
  runtimeInfo(signal) {
@@ -2546,7 +2557,7 @@ class HgwRemoteClient {
2546
2557
  async deletePushSubscription(endpoint) {
2547
2558
  const request = PushUnregistrationSchema2.safeParse({ endpoint });
2548
2559
  if (!request.success || !endpoint.trim())
2549
- throw new HgwClientError("proxy-failure", "Invalid push subscription");
2560
+ throw new HgwClientError("gateway-failure", "Invalid push subscription");
2550
2561
  await this.#write("/push/subscriptions", "DELETE", request.data);
2551
2562
  }
2552
2563
  adoptSessionOwnership(sessionId, agentId) {
@@ -2567,7 +2578,7 @@ class HgwRemoteClient {
2567
2578
  })
2568
2579
  });
2569
2580
  if (!request.success)
2570
- throw new HgwClientError("proxy-failure", "Invalid attachment staging request");
2581
+ throw new HgwClientError("gateway-failure", "Invalid attachment staging request");
2571
2582
  return this.#read(this.#sessionPath(sessionId, "/attachments/stage"), SessionAttachmentStageResponseSchema2, {
2572
2583
  method: "POST",
2573
2584
  headers: { "content-type": "application/json" },
@@ -2576,11 +2587,11 @@ class HgwRemoteClient {
2576
2587
  }
2577
2588
  async readArtifact(sessionId, artifactId, signal) {
2578
2589
  if (!artifactId.trim() || artifactId.length > 512)
2579
- throw new HgwClientError("proxy-failure", "Invalid artifact reference");
2590
+ throw new HgwClientError("gateway-failure", "Invalid artifact reference");
2580
2591
  return this.#readBlob(this.#sessionPath(sessionId, `/artifacts/${encodeURIComponent(artifactId)}`), { signal }, "artifact-missing");
2581
2592
  }
2582
2593
  async openMcpApp(sessionId, subject, signal) {
2583
- const view = await this.#read(this.#mcpAppPath(sessionId, subject), McpAppViewSchema2, { signal }, "artifactId" in subject ? "artifact-missing" : "proxy-failure");
2594
+ const view = await this.#read(this.#mcpAppPath(sessionId, subject), McpAppViewSchema2, { signal }, "artifactId" in subject ? "artifact-missing" : "gateway-failure");
2584
2595
  return view.files ? { ...view, files: this.#absoluteFiles(view.files) } : view;
2585
2596
  }
2586
2597
  async renewMcpAppFiles(sessionId, subject) {
@@ -2607,13 +2618,13 @@ class HgwRemoteClient {
2607
2618
  }
2608
2619
  async transcribeForAgent(agentId, audio, signal) {
2609
2620
  if (!audio.size || !audio.type)
2610
- throw new HgwClientError("proxy-failure", "Invalid audio recording");
2621
+ throw new HgwClientError("gateway-failure", "Invalid audio recording");
2611
2622
  const request = SessionTranscriptionRequestSchema2.safeParse({
2612
2623
  dataUrl: await dataUrl(audio),
2613
2624
  mimeType: audio.type
2614
2625
  });
2615
2626
  if (!request.success)
2616
- throw new HgwClientError("proxy-failure", "Invalid audio recording");
2627
+ throw new HgwClientError("gateway-failure", "Invalid audio recording");
2617
2628
  const path = `/agents/${encodeURIComponent(agentId)}/audio/transcribe`;
2618
2629
  const response = await this.#read(path, SessionTranscriptionResponseSchema2, {
2619
2630
  method: "POST",
@@ -2629,7 +2640,7 @@ class HgwRemoteClient {
2629
2640
  async speakForAgent(agentId, text, signal) {
2630
2641
  const request = SessionSpeechRequestSchema2.safeParse({ text });
2631
2642
  if (!request.success)
2632
- throw new HgwClientError("proxy-failure", "Invalid speech input");
2643
+ throw new HgwClientError("gateway-failure", "Invalid speech input");
2633
2644
  const path = `/agents/${encodeURIComponent(agentId)}/audio/speak`;
2634
2645
  return this.#readBlob(path, {
2635
2646
  method: "POST",
@@ -2655,18 +2666,18 @@ class HgwRemoteClient {
2655
2666
  }
2656
2667
  if (!response.ok) {
2657
2668
  const error = await normalizedError(response);
2658
- throw new HgwClientError(response.status === 503 ? "provider-unavailable" : "proxy-failure", error?.description, error?.code, response.status);
2669
+ throw new HgwClientError(response.status === 503 ? "provider-unavailable" : "gateway-failure", error?.description, error?.code, response.status);
2659
2670
  }
2660
2671
  }
2661
2672
  #mcpAppPath(sessionId, subject, suffix = "") {
2662
2673
  const [collection, id] = "toolCallId" in subject ? ["tool-calls", subject.toolCallId] : ["artifacts", subject.artifactId];
2663
2674
  if (!id.trim() || id.length > 512)
2664
- throw new HgwClientError("proxy-failure", "Invalid MCP App reference");
2675
+ throw new HgwClientError("gateway-failure", "Invalid MCP App reference");
2665
2676
  return this.#sessionPath(sessionId, `/${collection}/${encodeURIComponent(id)}/app${suffix}`);
2666
2677
  }
2667
2678
  async#postMcpApp(sessionId, subject, suffix, request, schema) {
2668
2679
  if (!request.success)
2669
- throw new HgwClientError("proxy-failure", "Invalid MCP App request");
2680
+ throw new HgwClientError("gateway-failure", "Invalid MCP App request");
2670
2681
  return this.#read(this.#mcpAppPath(sessionId, subject, suffix), schema, {
2671
2682
  method: "POST",
2672
2683
  headers: { "content-type": "application/json" },
@@ -2677,7 +2688,7 @@ class HgwRemoteClient {
2677
2688
  const agentId = this.#owner(sessionId);
2678
2689
  return `/agents/${encodeURIComponent(agentId)}/sessions/${encodeURIComponent(sessionId)}${suffix}`;
2679
2690
  }
2680
- async#readBlob(path, init, notFound = "proxy-failure") {
2691
+ async#readBlob(path, init, notFound = "gateway-failure") {
2681
2692
  let response;
2682
2693
  try {
2683
2694
  const headers = new Headers(init?.headers);
@@ -2694,15 +2705,15 @@ class HgwRemoteClient {
2694
2705
  }
2695
2706
  if (!response.ok) {
2696
2707
  const error = await normalizedError(response);
2697
- throw new HgwClientError(response.status === 503 ? "provider-unavailable" : response.status === 404 ? notFound : "proxy-failure", error?.description, error?.code, response.status);
2708
+ throw new HgwClientError(response.status === 503 ? "provider-unavailable" : response.status === 404 ? notFound : "gateway-failure", error?.description, error?.code, response.status);
2698
2709
  }
2699
2710
  const contentType = response.headers.get("content-type");
2700
2711
  if (!contentType || /[\r\n]/u.test(contentType))
2701
- throw new HgwClientError("proxy-failure", "Invalid AOS proxy response");
2712
+ throw new HgwClientError("gateway-failure", "Invalid gateway response");
2702
2713
  try {
2703
2714
  return await response.blob();
2704
2715
  } catch {
2705
- throw new HgwClientError("proxy-failure", "Invalid AOS proxy response");
2716
+ throw new HgwClientError("gateway-failure", "Invalid gateway response");
2706
2717
  }
2707
2718
  }
2708
2719
  #owner(sessionId) {
@@ -33,7 +33,7 @@ export declare const STABLE_AFTER_MS = 60000;
33
33
  * neither a close nor a resume.
34
34
  */
35
35
  export declare const PART_GRACE_MS = 2000;
36
- /** The window a reopen waits in after the proxy closes at capacity. */
36
+ /** The window a reopen waits in after the gateway closes at capacity. */
37
37
  export declare const CAPACITY_BACKOFF: {
38
38
  minMs: number;
39
39
  maxMs: number;
@@ -17,7 +17,7 @@ export type AcpPendingRequest = {
17
17
  sessionId: string;
18
18
  request: RequestPermissionRequest;
19
19
  respond(response: RequestPermissionResponse): void;
20
- /** Aborts when the proxy withdraws the request, which needs no answer. */
20
+ /** Aborts when the gateway withdraws the request, which needs no answer. */
21
21
  signal: AbortSignal;
22
22
  } | {
23
23
  kind: "elicitation";
@@ -82,6 +82,7 @@ export type SessionActionCapabilities = {
82
82
  archive: boolean;
83
83
  delete: boolean;
84
84
  pin: boolean;
85
+ unread: boolean;
85
86
  };
86
87
  export type TodoStatus = "pending" | "active" | "completed" | "failed";
87
88
  export type TodoItem = {
@@ -199,7 +199,7 @@ export declare const McpAppResourceReadRequestSchema: z.ZodObject<{
199
199
  }, z.core.$strict>;
200
200
  export type McpAppResourceReadRequest = z.infer<typeof McpAppResourceReadRequestSchema>;
201
201
  /**
202
- * The MCP App sandbox proxy: a static page, served beside the workspace on
202
+ * The MCP App sandbox gateway: a static page, served beside the workspace on
203
203
  * every listener, that holds only the relay creating the App's own frame. It
204
204
  * is a real document rather than `srcdoc` so it carries this policy instead of
205
205
  * inheriting the embedding page's, which on the guest surface forbids inline
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harness-gw/sdk",
3
- "version": "0.7.0",
3
+ "version": "0.8.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",