@openclaw/gateway-protocol 2026.8.1-beta.1 → 2026.8.1-beta.3

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/CHANGELOG.md CHANGED
@@ -7,11 +7,14 @@ version and the additive schema surface. Dates are authoring dates (2026).
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ - Add bounded `sessions.patchMany` session mutation orchestration.
11
+ - Preserve required legacy agent-default fields while adding honest `ownership` and `selectionRequired` state to agent lists and initial snapshots.
10
12
  - Add semantic `agent` / `system` roster kinds negotiated through the `agent-kind` client capability.
11
13
  - Rename structured-question item `id` to `questionId` and flatten keyed answer arrays.
12
14
  - Slim worker and session-catalog payloads to the active wire contract.
13
15
  - Remove dead protocol surfaces and add since-vintage metadata to retained schemas and methods.
14
16
  - Add optional `step` on `SystemAgentChatResult` carrying the full awaited wizard step.
17
+ - Project plugin tab `placement` in `hello-ok` so active plugins can target native Control UI routes.
15
18
 
16
19
  ## Protocol v4 (current)
17
20
 
package/README.md CHANGED
@@ -128,7 +128,7 @@ Several identifier names coexist because they identify different things:
128
128
 
129
129
  Follow each method schema rather than converting fields based on their spelling.
130
130
  `sessions.resolve` is the explicit bridge when a caller has a key, raw session ID,
131
- label, or parent/agent scope.
131
+ label, Control UI short ID, or parent/agent scope.
132
132
 
133
133
  ### Intentionally open fields
134
134
 
@@ -59,7 +59,8 @@ type GatewayClientMode = (typeof GATEWAY_CLIENT_MODES)[keyof typeof GATEWAY_CLIE
59
59
  type GatewayClientInfo = {
60
60
  /** Stable product/client identifier from `GATEWAY_CLIENT_IDS`. */id: GatewayClientId; /** Human-readable label for diagnostics; not used for policy decisions. */
61
61
  displayName?: string; /** Client app or package version reported by the connecting process. */
62
- version: string; /** Runtime platform string, such as `darwin`, `ios`, `android`, or `web`. */
62
+ version: string; /** Exact immutable artifact identity when the client can report one. */
63
+ buildId?: string; /** Runtime platform string, such as `darwin`, `ios`, `android`, or `web`. */
63
64
  platform: string; /** Optional device family used by native clients for display and routing hints. */
64
65
  deviceFamily?: string; /** Native hardware/model identifier when available. */
65
66
  modelIdentifier?: string; /** Coarse category from `GATEWAY_CLIENT_MODES` for policy and diagnostics. */
@@ -1,3 +1,4 @@
1
+ import { n as normalizeOptionalProtocolString } from "./protocol-value-normalization-DcAoxLUs.mjs";
1
2
  //#region src/client-info.ts
2
3
  /**
3
4
  * Shared gateway client identity contract.
@@ -5,9 +6,8 @@
5
6
  * These values cross the WebSocket handshake boundary, so additions must stay
6
7
  * aligned with protocol schemas and server policy checks.
7
8
  */
8
- function normalizeOptionalLowercaseString(raw) {
9
- if (typeof raw !== "string") return;
10
- return raw.trim().toLowerCase() || void 0;
9
+ function normalizeOptionalProtocolLowercaseString(raw) {
10
+ return normalizeOptionalProtocolString(raw)?.toLowerCase();
11
11
  }
12
12
  /** Canonical client ids accepted in gateway hello/connect payloads. */
13
13
  const GATEWAY_CLIENT_IDS = {
@@ -59,7 +59,7 @@ const GATEWAY_CLIENT_ID_SET = new Set(Object.values(GATEWAY_CLIENT_IDS));
59
59
  const GATEWAY_CLIENT_MODE_SET = new Set(Object.values(GATEWAY_CLIENT_MODES));
60
60
  /** Normalizes untrusted client ids and rejects unknown values. */
61
61
  function normalizeGatewayClientId(raw) {
62
- const normalized = normalizeOptionalLowercaseString(raw);
62
+ const normalized = normalizeOptionalProtocolLowercaseString(raw);
63
63
  if (!normalized) return;
64
64
  return GATEWAY_CLIENT_ID_SET.has(normalized) ? normalized : void 0;
65
65
  }
@@ -69,7 +69,7 @@ function normalizeGatewayClientName(raw) {
69
69
  }
70
70
  /** Normalizes untrusted client modes and rejects unknown values. */
71
71
  function normalizeGatewayClientMode(raw) {
72
- const normalized = normalizeOptionalLowercaseString(raw);
72
+ const normalized = normalizeOptionalProtocolLowercaseString(raw);
73
73
  if (!normalized) return;
74
74
  return GATEWAY_CLIENT_MODE_SET.has(normalized) ? normalized : void 0;
75
75
  }
@@ -17,6 +17,8 @@ declare const ConnectErrorDetailCodes: {
17
17
  readonly AUTH_TAILSCALE_PROXY_MISSING: "AUTH_TAILSCALE_PROXY_MISSING";
18
18
  readonly AUTH_TAILSCALE_WHOIS_FAILED: "AUTH_TAILSCALE_WHOIS_FAILED";
19
19
  readonly AUTH_TAILSCALE_IDENTITY_MISMATCH: "AUTH_TAILSCALE_IDENTITY_MISMATCH";
20
+ readonly AUTH_IDENTITY_HEADER_REQUIRED: "AUTH_IDENTITY_HEADER_REQUIRED";
21
+ readonly CONTROL_UI_BUILD_MISMATCH: "CONTROL_UI_BUILD_MISMATCH";
20
22
  readonly CONTROL_UI_ORIGIN_NOT_ALLOWED: "CONTROL_UI_ORIGIN_NOT_ALLOWED";
21
23
  readonly PROTOCOL_MISMATCH: "PROTOCOL_MISMATCH";
22
24
  readonly CONTROL_UI_DEVICE_IDENTITY_REQUIRED: "CONTROL_UI_DEVICE_IDENTITY_REQUIRED";
@@ -70,6 +72,8 @@ declare function resolveAuthConnectErrorDetailCode(reason: string | undefined):
70
72
  declare function resolveDeviceAuthConnectErrorDetailCode(reason: string | undefined): ConnectErrorDetailCode;
71
73
  /** Reads a non-empty detail code from an untrusted error details payload. */
72
74
  declare function readConnectErrorDetailCode(details: unknown): string | null;
75
+ /** Read the exact target artifact from an untrusted reload-required rejection. */
76
+ declare function readControlUiBuildMismatchId(details: unknown): string | null;
73
77
  /** Extracts normalized retry advice from untrusted connect-error details. */
74
78
  declare function readConnectErrorRecoveryAdvice(details: unknown): ConnectErrorRecoveryAdvice;
75
79
  /** Normalizes pairing request ids before echoing them in close reasons or UI text. */
@@ -103,6 +107,24 @@ declare function buildPairingConnectCloseReason(params: {
103
107
  declare function readPairingConnectErrorDetails(details: unknown): PairingConnectErrorDetails | null;
104
108
  /** Parses legacy/string-only pairing-required messages into structured details. */
105
109
  declare function readConnectPairingRequiredMessage(message: string | null | undefined): ConnectPairingRequiredDetails | null;
110
+ /** Classifies Gateway connect failures from structured details, with one legacy text fallback. */
111
+ declare function classifyGatewayConnectFailure(input: {
112
+ details?: unknown;
113
+ reason?: string | null;
114
+ message?: string | null;
115
+ }): {
116
+ kind: "pairing-required";
117
+ userMessage: string;
118
+ remediation: string;
119
+ } | {
120
+ kind: "identity-proxy";
121
+ userMessage: string;
122
+ remediation: string;
123
+ } | {
124
+ remediation?: string | undefined;
125
+ kind: "device-identity-required" | "scope-mismatch" | "rate-limited" | "auth-rejected" | "gateway-rejected" | "unreachable";
126
+ userMessage: string;
127
+ };
106
128
  /** Formats pairing-required details into the canonical user-facing message. */
107
129
  declare function formatConnectPairingRequiredMessage(details: unknown): string;
108
130
  /** Formats connect errors using structured details before falling back to raw messages. */
@@ -111,4 +133,4 @@ declare function formatConnectErrorMessage(params: {
111
133
  details?: unknown;
112
134
  }): string;
113
135
  //#endregion
114
- export { ConnectErrorDetailCodes, ConnectPairingRequiredDetails, ConnectPairingRequiredReason, buildPairingConnectCloseReason, buildPairingConnectErrorDetails, buildPairingConnectErrorMessage, buildPairingConnectRecoveryTitle, describePairingConnectRequirement, formatConnectErrorMessage, formatConnectPairingRequiredMessage, normalizePairingConnectRequestId, readConnectErrorDetailCode, readConnectErrorRecoveryAdvice, readConnectPairingRequiredMessage, readPairingConnectErrorDetails, resolveAuthConnectErrorDetailCode, resolveDeviceAuthConnectErrorDetailCode };
136
+ export { ConnectErrorDetailCodes, ConnectPairingRequiredDetails, ConnectPairingRequiredReason, buildPairingConnectCloseReason, buildPairingConnectErrorDetails, buildPairingConnectErrorMessage, buildPairingConnectRecoveryTitle, classifyGatewayConnectFailure, describePairingConnectRequirement, formatConnectErrorMessage, formatConnectPairingRequiredMessage, normalizePairingConnectRequestId, readConnectErrorDetailCode, readConnectErrorRecoveryAdvice, readConnectPairingRequiredMessage, readControlUiBuildMismatchId, readPairingConnectErrorDetails, resolveAuthConnectErrorDetailCode, resolveDeviceAuthConnectErrorDetailCode };
@@ -1,3 +1,5 @@
1
+ import { n as isRecord } from "./record-coerce-BKHrZ46I.mjs";
2
+ import { n as normalizeOptionalProtocolString } from "./protocol-value-normalization-DcAoxLUs.mjs";
1
3
  //#region src/connect-error-details.ts
2
4
  /**
3
5
  * Shared gateway connect-error detail helpers.
@@ -5,13 +7,9 @@
5
7
  * These details cross client/server boundaries, so readers normalize untrusted
6
8
  * payloads before using them in reconnect decisions or user-facing messages.
7
9
  */
8
- function normalizeOptionalString(value) {
9
- if (typeof value !== "string") return;
10
- return value.trim() || void 0;
11
- }
12
- function normalizeArrayBackedTrimmedStringList(value) {
10
+ function normalizeOptionalConnectDetailStringList(value) {
13
11
  if (!Array.isArray(value)) return;
14
- const values = value.map((entry) => normalizeOptionalString(entry)).filter((entry) => Boolean(entry));
12
+ const values = value.map((entry) => normalizeOptionalProtocolString(entry)).filter((entry) => Boolean(entry));
15
13
  return values.length > 0 ? values : void 0;
16
14
  }
17
15
  /** Structured connect-error codes carried in gateway error `details.code`. */
@@ -32,6 +30,8 @@ const ConnectErrorDetailCodes = {
32
30
  AUTH_TAILSCALE_PROXY_MISSING: "AUTH_TAILSCALE_PROXY_MISSING",
33
31
  AUTH_TAILSCALE_WHOIS_FAILED: "AUTH_TAILSCALE_WHOIS_FAILED",
34
32
  AUTH_TAILSCALE_IDENTITY_MISMATCH: "AUTH_TAILSCALE_IDENTITY_MISMATCH",
33
+ AUTH_IDENTITY_HEADER_REQUIRED: "AUTH_IDENTITY_HEADER_REQUIRED",
34
+ CONTROL_UI_BUILD_MISMATCH: "CONTROL_UI_BUILD_MISMATCH",
35
35
  CONTROL_UI_ORIGIN_NOT_ALLOWED: "CONTROL_UI_ORIGIN_NOT_ALLOWED",
36
36
  PROTOCOL_MISMATCH: "PROTOCOL_MISMATCH",
37
37
  CONTROL_UI_DEVICE_IDENTITY_REQUIRED: "CONTROL_UI_DEVICE_IDENTITY_REQUIRED",
@@ -97,6 +97,7 @@ const CONNECT_PAIRING_REQUIRED_MESSAGE_BY_REASON = {
97
97
  };
98
98
  /** Maps internal auth failure reasons to public connect-error detail codes. */
99
99
  function resolveAuthConnectErrorDetailCode(reason) {
100
+ if (reason?.startsWith("trusted_proxy_missing_header_")) return ConnectErrorDetailCodes.AUTH_IDENTITY_HEADER_REQUIRED;
100
101
  switch (reason) {
101
102
  case "token_missing": return ConnectErrorDetailCodes.AUTH_TOKEN_MISSING;
102
103
  case "token_mismatch": return ConnectErrorDetailCodes.AUTH_TOKEN_MISMATCH;
@@ -130,32 +131,41 @@ function resolveDeviceAuthConnectErrorDetailCode(reason) {
130
131
  }
131
132
  /** Reads a non-empty detail code from an untrusted error details payload. */
132
133
  function readConnectErrorDetailCode(details) {
133
- if (!details || typeof details !== "object" || Array.isArray(details)) return null;
134
+ if (!isRecord(details)) return null;
134
135
  const code = details.code;
135
136
  return typeof code === "string" && code.trim().length > 0 ? code.trim() : null;
136
137
  }
138
+ /** Read the exact target artifact from an untrusted reload-required rejection. */
139
+ function readControlUiBuildMismatchId(details) {
140
+ const code = readConnectErrorDetailCode(details);
141
+ if (code !== ConnectErrorDetailCodes.PROTOCOL_MISMATCH && code !== ConnectErrorDetailCodes.CONTROL_UI_BUILD_MISMATCH) return null;
142
+ const raw = details;
143
+ const gatewayBuildId = normalizeOptionalProtocolString(raw.gatewayBuildId);
144
+ if (!gatewayBuildId || gatewayBuildId.length > 96 || raw.reloadRequired !== true) return null;
145
+ return gatewayBuildId;
146
+ }
137
147
  /** Extracts normalized retry advice from untrusted connect-error details. */
138
148
  function readConnectErrorRecoveryAdvice(details) {
139
- if (!details || typeof details !== "object" || Array.isArray(details)) return {};
149
+ if (!isRecord(details)) return {};
140
150
  const raw = details;
141
151
  const canRetryWithDeviceToken = typeof raw.canRetryWithDeviceToken === "boolean" ? raw.canRetryWithDeviceToken : void 0;
142
- const normalizedNextStep = normalizeOptionalString(raw.recommendedNextStep) ?? "";
152
+ const normalizedNextStep = normalizeOptionalProtocolString(raw.recommendedNextStep) ?? "";
143
153
  return {
144
154
  canRetryWithDeviceToken,
145
155
  recommendedNextStep: CONNECT_RECOVERY_NEXT_STEP_VALUES.has(normalizedNextStep) ? normalizedNextStep : void 0
146
156
  };
147
157
  }
148
158
  function normalizePairingConnectReason(value) {
149
- const normalized = normalizeOptionalString(value) ?? "";
159
+ const normalized = normalizeOptionalProtocolString(value) ?? "";
150
160
  return CONNECT_PAIRING_REQUIRED_REASON_VALUES.has(normalized) ? normalized : void 0;
151
161
  }
152
162
  /** Normalizes pairing request ids before echoing them in close reasons or UI text. */
153
163
  function normalizePairingConnectRequestId(value) {
154
- const normalized = normalizeOptionalString(value);
164
+ const normalized = normalizeOptionalProtocolString(value);
155
165
  return normalized && PAIRING_CONNECT_REQUEST_ID_PATTERN.test(normalized) ? normalized : void 0;
156
166
  }
157
167
  function normalizeStringArray(value) {
158
- return normalizeArrayBackedTrimmedStringList(value);
168
+ return normalizeOptionalConnectDetailStringList(value);
159
169
  }
160
170
  function createPairingConnectErrorDetails(params) {
161
171
  return {
@@ -191,9 +201,9 @@ function buildPairingConnectRecoveryTitle(reason) {
191
201
  /** Builds sanitized structured details for a pairing-required connect failure. */
192
202
  function buildPairingConnectErrorDetails(params) {
193
203
  const requestId = normalizePairingConnectRequestId(params.requestId);
194
- const remediationHint = normalizeOptionalString(params.remediationHint) ?? buildPairingConnectRemediationHint(params.reason);
195
- const deviceId = normalizeOptionalString(params.deviceId);
196
- const requestedRole = normalizeOptionalString(params.requestedRole);
204
+ const remediationHint = normalizeOptionalProtocolString(params.remediationHint) ?? buildPairingConnectRemediationHint(params.reason);
205
+ const deviceId = normalizeOptionalProtocolString(params.deviceId);
206
+ const requestedRole = normalizeOptionalProtocolString(params.requestedRole);
197
207
  const requestedScopes = normalizeStringArray(params.requestedScopes);
198
208
  const approvedRoles = normalizeStringArray(params.approvedRoles);
199
209
  const approvedScopes = normalizeStringArray(params.approvedScopes);
@@ -220,15 +230,15 @@ function buildPairingConnectCloseReason(params) {
220
230
  /** Reads and backfills pairing-required details from an untrusted details object. */
221
231
  function readPairingConnectErrorDetails(details) {
222
232
  if (readConnectErrorDetailCode(details) !== ConnectErrorDetailCodes.PAIRING_REQUIRED) return null;
223
- if (!details || typeof details !== "object" || Array.isArray(details)) return null;
233
+ if (!isRecord(details)) return null;
224
234
  const raw = details;
225
235
  const reason = normalizePairingConnectReason(raw.reason);
226
236
  const requestId = normalizePairingConnectRequestId(raw.requestId);
227
- const remediationHint = normalizeOptionalString(raw.remediationHint) ?? buildPairingConnectRemediationHint(reason);
228
- const normalizedNextStep = normalizeOptionalString(raw.recommendedNextStep) ?? "";
237
+ const remediationHint = normalizeOptionalProtocolString(raw.remediationHint) ?? buildPairingConnectRemediationHint(reason);
238
+ const normalizedNextStep = normalizeOptionalProtocolString(raw.recommendedNextStep) ?? "";
229
239
  const recommendedNextStep = CONNECT_RECOVERY_NEXT_STEP_VALUES.has(normalizedNextStep) ? normalizedNextStep : void 0;
230
- const deviceId = normalizeOptionalString(raw.deviceId);
231
- const requestedRole = normalizeOptionalString(raw.requestedRole);
240
+ const deviceId = normalizeOptionalProtocolString(raw.deviceId);
241
+ const requestedRole = normalizeOptionalProtocolString(raw.requestedRole);
232
242
  const requestedScopes = normalizeStringArray(raw.requestedScopes);
233
243
  const approvedRoles = normalizeStringArray(raw.approvedRoles);
234
244
  const approvedScopes = normalizeStringArray(raw.approvedScopes);
@@ -248,7 +258,7 @@ function readPairingConnectErrorDetails(details) {
248
258
  }
249
259
  /** Parses legacy/string-only pairing-required messages into structured details. */
250
260
  function readConnectPairingRequiredMessage(message) {
251
- const normalizedMessage = normalizeOptionalString(message);
261
+ const normalizedMessage = normalizeOptionalProtocolString(message);
252
262
  if (!normalizedMessage) return null;
253
263
  const normalized = normalizedMessage.trim().toLowerCase();
254
264
  let reason;
@@ -264,6 +274,68 @@ function readConnectPairingRequiredMessage(message) {
264
274
  reason
265
275
  };
266
276
  }
277
+ const PAIRING_APPROVAL_REMEDIATION = "Run `openclaw devices approve --latest` to preview the pending request, then rerun the printed `openclaw devices approve <requestId>` command and reconnect (pass the same --url and --token/--password flags if you connected with explicit credentials).";
278
+ const DEVICE_TOKEN_REMEDIATION = "Rotate the paired-device token with `openclaw devices rotate --device <deviceId> --role operator`, then reconnect.";
279
+ const SHARED_TOKEN_REMEDIATION = "Verify `gateway.remote.token` matches `gateway.auth.token`. If a paired-device token is stale, rotate it with `openclaw devices rotate --device <deviceId> --role operator`, then reconnect.";
280
+ const SCOPE_MISMATCH_REMEDIATION = "Review approved scopes with `openclaw devices list`; if an upgrade is pending, preview it with `openclaw devices approve --latest`, approve the printed request, then reconnect.";
281
+ const RATE_LIMITED_REMEDIATION = "Wait for the temporary authentication lockout to expire, then retry.";
282
+ const IDENTITY_PROXY_REMEDIATION = "An identity-aware proxy rejected the WebSocket upgrade. Configure gateway.remote.edgeAuth for the configured Gateway origin, then reconnect. See https://docs.openclaw.ai/gateway/remote#gateway-behind-an-identity-aware-proxy.";
283
+ const CLOUDFLARE_ACCESS_REMEDIATION = "Cloudflare Access detected: configure its token header or service-token headers in gateway.remote.edgeAuth.";
284
+ const IDENTITY_PROXY_HTTP_STATUSES = /* @__PURE__ */ new Set([
285
+ 301,
286
+ 302,
287
+ 303,
288
+ 307,
289
+ 308,
290
+ 401,
291
+ 403
292
+ ]);
293
+ const GATEWAY_CLOSED_MESSAGE_PATTERN = /\bgateway closed \(\d+\):/i;
294
+ function readIdentityProxyRejection(details) {
295
+ if (!isRecord(details)) return null;
296
+ if (details.reason !== "websocket-upgrade-rejected" || typeof details.httpStatus !== "number" || !IDENTITY_PROXY_HTTP_STATUSES.has(details.httpStatus)) return null;
297
+ const location = normalizeOptionalProtocolString(details.location);
298
+ if (!location) return { cloudflareAccess: false };
299
+ try {
300
+ return { cloudflareAccess: new URL(location).hostname.toLowerCase().replace(/\.+$/u, "").endsWith(".cloudflareaccess.com") };
301
+ } catch {
302
+ return { cloudflareAccess: false };
303
+ }
304
+ }
305
+ /** Classifies Gateway connect failures from structured details, with one legacy text fallback. */
306
+ function classifyGatewayConnectFailure(input) {
307
+ const code = readConnectErrorDetailCode(input.details);
308
+ const message = normalizeOptionalProtocolString(input.message);
309
+ const reason = normalizeOptionalProtocolString(input.reason);
310
+ const userMessage = message ?? reason;
311
+ const classificationText = [message, reason].filter((value) => Boolean(value)).join("\n");
312
+ const normalized = classificationText.toLowerCase();
313
+ const pairing = readPairingConnectErrorDetails(input.details) ?? readConnectPairingRequiredMessage(classificationText);
314
+ if (code === ConnectErrorDetailCodes.PAIRING_REQUIRED || pairing) return {
315
+ kind: "pairing-required",
316
+ userMessage: code === ConnectErrorDetailCodes.PAIRING_REQUIRED ? formatConnectPairingRequiredMessage(input.details) : userMessage ?? "device pairing required",
317
+ remediation: PAIRING_APPROVAL_REMEDIATION
318
+ };
319
+ const identityProxy = readIdentityProxyRejection(input.details);
320
+ if (identityProxy) return {
321
+ kind: "identity-proxy",
322
+ userMessage: userMessage ?? "identity-aware proxy rejected websocket upgrade",
323
+ remediation: identityProxy.cloudflareAccess ? `${IDENTITY_PROXY_REMEDIATION}\n${CLOUDFLARE_ACCESS_REMEDIATION}` : IDENTITY_PROXY_REMEDIATION
324
+ };
325
+ const deviceIdentityRequired = code === ConnectErrorDetailCodes.DEVICE_IDENTITY_REQUIRED || code === ConnectErrorDetailCodes.CONTROL_UI_DEVICE_IDENTITY_REQUIRED || normalized.includes("device identity required");
326
+ const scopeMismatch = code === ConnectErrorDetailCodes.AUTH_SCOPE_MISMATCH || normalized.includes("scope mismatch");
327
+ const rateLimited = code === ConnectErrorDetailCodes.AUTH_RATE_LIMITED || !code && normalized.includes("too many failed authentication attempts");
328
+ const deviceTokenMismatch = code === ConnectErrorDetailCodes.AUTH_DEVICE_TOKEN_MISMATCH || normalized.includes("device token mismatch");
329
+ const sharedTokenMismatch = code === ConnectErrorDetailCodes.AUTH_TOKEN_MISMATCH || normalized.includes("gateway token mismatch");
330
+ const authRejected = deviceTokenMismatch || sharedTokenMismatch || code?.startsWith("AUTH_") || code?.startsWith("DEVICE_AUTH_");
331
+ const kind = deviceIdentityRequired ? "device-identity-required" : scopeMismatch ? "scope-mismatch" : rateLimited ? "rate-limited" : authRejected ? "auth-rejected" : code || GATEWAY_CLOSED_MESSAGE_PATTERN.test(classificationText) ? "gateway-rejected" : "unreachable";
332
+ const remediation = rateLimited ? RATE_LIMITED_REMEDIATION : scopeMismatch ? SCOPE_MISMATCH_REMEDIATION : deviceTokenMismatch ? DEVICE_TOKEN_REMEDIATION : sharedTokenMismatch ? SHARED_TOKEN_REMEDIATION : void 0;
333
+ return {
334
+ kind,
335
+ userMessage: userMessage ?? (kind === "unreachable" ? "gateway unreachable" : "gateway rejected connection"),
336
+ ...remediation ? { remediation } : {}
337
+ };
338
+ }
267
339
  /** Formats pairing-required details into the canonical user-facing message. */
268
340
  function formatConnectPairingRequiredMessage(details) {
269
341
  const pairing = readPairingConnectErrorDetails(details);
@@ -274,7 +346,7 @@ function formatConnectPairingRequiredMessage(details) {
274
346
  function formatConnectErrorMessage(params) {
275
347
  if (readConnectErrorDetailCode(params.details) === ConnectErrorDetailCodes.PAIRING_REQUIRED) return formatConnectPairingRequiredMessage(params.details);
276
348
  if (readConnectErrorDetailCode(params.details) === ConnectErrorDetailCodes.PROTOCOL_MISMATCH) return formatProtocolMismatchMessage(params.message, params.details);
277
- return normalizeOptionalString(params.message) ?? "gateway request failed";
349
+ return normalizeOptionalProtocolString(params.message) ?? "gateway request failed";
278
350
  }
279
351
  function formatProtocolMismatchMessage(message, details) {
280
352
  const raw = details;
@@ -286,11 +358,11 @@ function formatProtocolMismatchMessage(message, details) {
286
358
  if (clientMin !== void 0 && clientMax !== void 0) parts.push(clientMin === clientMax ? `Control UI v${clientMin}` : `Control UI v${clientMin}-v${clientMax}`);
287
359
  if (expected !== void 0) parts.push(`Gateway v${expected}`);
288
360
  if (probeMin !== void 0) parts.push(`probe min v${probeMin}`);
289
- const normalized = normalizeOptionalString(message) ?? "protocol mismatch";
361
+ const normalized = normalizeOptionalProtocolString(message) ?? "protocol mismatch";
290
362
  return parts.length > 0 ? `${normalized}: ${parts.join(", ")}` : normalized;
291
363
  }
292
364
  function normalizeProtocolNumber(value) {
293
365
  return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
294
366
  }
295
367
  //#endregion
296
- export { ConnectErrorDetailCodes, buildPairingConnectCloseReason, buildPairingConnectErrorDetails, buildPairingConnectErrorMessage, buildPairingConnectRecoveryTitle, describePairingConnectRequirement, formatConnectErrorMessage, formatConnectPairingRequiredMessage, normalizePairingConnectRequestId, readConnectErrorDetailCode, readConnectErrorRecoveryAdvice, readConnectPairingRequiredMessage, readPairingConnectErrorDetails, resolveAuthConnectErrorDetailCode, resolveDeviceAuthConnectErrorDetailCode };
368
+ export { ConnectErrorDetailCodes, buildPairingConnectCloseReason, buildPairingConnectErrorDetails, buildPairingConnectErrorMessage, buildPairingConnectRecoveryTitle, classifyGatewayConnectFailure, describePairingConnectRequirement, formatConnectErrorMessage, formatConnectPairingRequiredMessage, normalizePairingConnectRequestId, readConnectErrorDetailCode, readConnectErrorRecoveryAdvice, readConnectPairingRequiredMessage, readControlUiBuildMismatchId, readPairingConnectErrorDetails, resolveAuthConnectErrorDetailCode, resolveDeviceAuthConnectErrorDetailCode };
@@ -1,4 +1,4 @@
1
- import { a as EventFrame, c as GatewayFrame, f as RequestFrame, m as ResponseFrame, r as ErrorShape, t as ConnectParams, u as HelloOk } from "./frames-C2oAsD4y.mjs";
1
+ import { a as EventFrame, c as GatewayFrame, f as RequestFrame, m as ResponseFrame, r as ErrorShape, t as ConnectParams, u as HelloOk } from "./frames-BKvowx_k.mjs";
2
2
 
3
3
  //#region src/frame-guards.d.ts
4
4
  declare function isGatewayEventFrame(value: unknown): value is EventFrame;
@@ -1,25 +1,21 @@
1
+ import { n as isRecord } from "./record-coerce-BKHrZ46I.mjs";
2
+ import { t as isNonEmptyProtocolString } from "./protocol-value-normalization-DcAoxLUs.mjs";
1
3
  //#region src/frame-guards.ts
2
- function isRecord(value) {
3
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
4
- }
5
- function isNonEmptyString(value) {
6
- return typeof value === "string" && value.length > 0;
7
- }
8
4
  function isNonNegativeInteger(value) {
9
5
  return typeof value === "number" && Number.isInteger(value) && value >= 0;
10
6
  }
11
7
  function isGatewayErrorShape(value) {
12
8
  if (!isRecord(value)) return false;
13
- if (!isNonEmptyString(value.code) || !isNonEmptyString(value.message)) return false;
9
+ if (!isNonEmptyProtocolString(value.code) || !isNonEmptyProtocolString(value.message)) return false;
14
10
  if (value.retryable !== void 0 && typeof value.retryable !== "boolean") return false;
15
11
  return value.retryAfterMs === void 0 || isNonNegativeInteger(value.retryAfterMs);
16
12
  }
17
13
  function isGatewayEventFrame(value) {
18
- if (!isRecord(value) || value.type !== "event" || !isNonEmptyString(value.event)) return false;
14
+ if (!isRecord(value) || value.type !== "event" || !isNonEmptyProtocolString(value.event)) return false;
19
15
  return value.seq === void 0 || isNonNegativeInteger(value.seq);
20
16
  }
21
17
  function isGatewayResponseFrame(value) {
22
- if (!isRecord(value) || value.type !== "res" || !isNonEmptyString(value.id) || typeof value.ok !== "boolean") return false;
18
+ if (!isRecord(value) || value.type !== "res" || !isNonEmptyProtocolString(value.id) || typeof value.ok !== "boolean") return false;
23
19
  return value.error === void 0 || isGatewayErrorShape(value.error);
24
20
  }
25
21
  //#endregion
@@ -4,7 +4,12 @@ import { Static, Type } from "typebox";
4
4
  declare const GATEWAY_SERVER_CAPS: {
5
5
  readonly BOARD_WIDGET_PUT_CANVAS_DOC: "board-widget-put-canvas-doc";
6
6
  readonly CHAT_SEND_ROUTING_CONTRACT: "chat-send-routing-contract";
7
+ readonly GATEWAY_RESTART_TARGET_SAFE: "gateway-restart-target-safe-v1";
8
+ readonly NODE_WORKER_BUNDLE_RETENTION: "node-worker-bundle-retention-v1";
9
+ readonly NODE_WORKER_BUNDLE_STATUS: "node-worker-bundle-status-v1";
10
+ readonly SYSTEM_AGENT_WIZARD_CANCEL: "openclaw-chat-wizard-cancel";
7
11
  readonly SYSTEM_AGENT_SETUP_MODEL_REF: "openclaw-setup-model-ref";
12
+ readonly TASK_SUGGESTIONS_ACCEPT_MODES: "taskSuggestions.acceptModes";
8
13
  };
9
14
  /**
10
15
  * Top-level gateway frame schemas.
@@ -29,6 +34,7 @@ declare const ConnectParamsSchema: Type.TObject<{
29
34
  id: Type.TEnum<["webchat-ui", "openclaw-control-ui", "openclaw-browser-copilot", "openclaw-tui", "webchat", "cli", "gateway-client", "openclaw-macos", "openclaw-linux", "openclaw-ios", "openclaw-watchos", "openclaw-android", "node-host", "openclaw-worker", "test", "fingerprint", "openclaw-probe"]>;
30
35
  displayName: Type.TOptional<Type.TString>;
31
36
  version: Type.TString;
37
+ buildId: Type.TOptional<Type.TString>;
32
38
  platform: Type.TString;
33
39
  deviceFamily: Type.TOptional<Type.TString>;
34
40
  modelIdentifier: Type.TOptional<Type.TString>;
@@ -36,7 +42,14 @@ declare const ConnectParamsSchema: Type.TObject<{
36
42
  instanceId: Type.TOptional<Type.TString>;
37
43
  }>;
38
44
  caps: Type.TOptional<Type.TArray<Type.TString>>;
39
- commands: Type.TOptional<Type.TArray<Type.TString>>;
45
+ commands: Type.TOptional<Type.TArray<Type.TString>>; /** Additive Computer Use declaration; the owning core contract validates its bounded shape. */
46
+ computerUse: Type.TOptional<Type.TUnknown>; /** @deprecated Accepted for the shipped v1 node-host envelope; current hosts use runner inventory. */
47
+ workerRuns: Type.TOptional<Type.TObject<{
48
+ bundleHash: Type.TString;
49
+ openclawVersion: Type.TString;
50
+ protocolFeatures: Type.TArray<Type.TString>;
51
+ bundlePrewarm: Type.TOptional<Type.TInteger>;
52
+ }>>;
40
53
  permissions: Type.TOptional<Type.TRecord<"^.*$", Type.TBoolean>>;
41
54
  pathEnv: Type.TOptional<Type.TString>;
42
55
  role: Type.TOptional<Type.TString>;
@@ -59,12 +72,14 @@ declare const ConnectParamsSchema: Type.TObject<{
59
72
  locale: Type.TOptional<Type.TString>;
60
73
  userAgent: Type.TOptional<Type.TString>;
61
74
  }>;
62
- /** Successful gateway hello response with negotiated protocol and initial state. */
75
+ /** Successful gateway hello response with the server protocol and initial state. */
63
76
  declare const HelloOkSchema: Type.TObject<{
64
77
  type: Type.TLiteral<"hello-ok">;
65
78
  protocol: Type.TInteger;
66
79
  server: Type.TObject<{
67
80
  version: Type.TString;
81
+ buildId: Type.TOptional<Type.TString>;
82
+ controlUiBuildSource: Type.TOptional<Type.TUnion<[Type.TLiteral<"bundled">, Type.TLiteral<"configured">]>>;
68
83
  connId: Type.TString;
69
84
  }>;
70
85
  features: Type.TObject<{
@@ -148,6 +163,21 @@ declare const HelloOkSchema: Type.TObject<{
148
163
  count: Type.TInteger;
149
164
  oldestFailedAt: Type.TOptional<Type.TInteger>;
150
165
  }>>;
166
+ ingressFailed: Type.TOptional<Type.TArray<Type.TObject<{
167
+ channelId: Type.TString;
168
+ accountId: Type.TString;
169
+ count: Type.TInteger;
170
+ oldestFailedAt: Type.TOptional<Type.TInteger>;
171
+ }>>>;
172
+ ingressPressure: Type.TOptional<Type.TArray<Type.TObject<{
173
+ channelId: Type.TString;
174
+ accountId: Type.TString;
175
+ laneCount: Type.TInteger;
176
+ pendingCount: Type.TInteger;
177
+ claimedCount: Type.TInteger;
178
+ blockedCount: Type.TInteger;
179
+ oldestReceivedAt: Type.TInteger;
180
+ }>>>;
151
181
  }>>;
152
182
  modelPricing: Type.TOptional<Type.TObject<{
153
183
  state: Type.TUnion<[Type.TLiteral<"ok">, Type.TLiteral<"degraded">, Type.TLiteral<"disabled">]>;
@@ -179,6 +209,7 @@ declare const HelloOkSchema: Type.TObject<{
179
209
  prompt: Type.TString;
180
210
  target: Type.TString;
181
211
  model: Type.TOptional<Type.TString>;
212
+ session: Type.TOptional<Type.TString>;
182
213
  ackMaxChars: Type.TInteger;
183
214
  }>;
184
215
  sessions: Type.TObject<{
@@ -211,6 +242,9 @@ declare const HelloOkSchema: Type.TObject<{
211
242
  stateDir: Type.TOptional<Type.TString>;
212
243
  sessionDefaults: Type.TOptional<Type.TObject<{
213
244
  defaultAgentId: Type.TString;
245
+ modelConfigured: Type.TOptional<Type.TBoolean>;
246
+ ownership: Type.TOptional<Type.TUnion<[Type.TLiteral<"sole">, Type.TLiteral<"legacy">, Type.TLiteral<"explicit">]>>;
247
+ selectionRequired: Type.TOptional<Type.TBoolean>;
214
248
  mainKey: Type.TString;
215
249
  mainSessionKey: Type.TString;
216
250
  scope: Type.TOptional<Type.TString>;
@@ -220,6 +254,70 @@ declare const HelloOkSchema: Type.TObject<{
220
254
  currentVersion: Type.TString;
221
255
  latestVersion: Type.TString;
222
256
  channel: Type.TString;
257
+ currentSha: Type.TOptional<Type.TString>;
258
+ upstreamRef: Type.TOptional<Type.TString>;
259
+ upstreamSha: Type.TOptional<Type.TString>;
260
+ commitsBehind: Type.TOptional<Type.TInteger>;
261
+ commits: Type.TOptional<Type.TArray<Type.TObject<{
262
+ sha: Type.TString;
263
+ subject: Type.TString;
264
+ }>>>;
265
+ }>>;
266
+ updateSchedule: Type.TOptional<Type.TObject<{
267
+ channel: Type.TString;
268
+ autoEnabled: Type.TBoolean;
269
+ install: Type.TOptional<Type.TObject<{
270
+ kind: Type.TUnion<[Type.TLiteral<"package">, Type.TLiteral<"git">, Type.TLiteral<"unknown">]>;
271
+ git: Type.TOptional<Type.TUnion<[Type.TObject<{
272
+ status: Type.TLiteral<"current">;
273
+ currentSha: Type.TOptional<Type.TString>;
274
+ commitAtMs: Type.TOptional<Type.TInteger>;
275
+ installedAtMs: Type.TOptional<Type.TInteger>;
276
+ }>, Type.TObject<{
277
+ status: Type.TLiteral<"behind">;
278
+ commitsBehind: Type.TInteger;
279
+ currentSha: Type.TOptional<Type.TString>;
280
+ commitAtMs: Type.TOptional<Type.TInteger>;
281
+ installedAtMs: Type.TOptional<Type.TInteger>;
282
+ }>, Type.TObject<{
283
+ status: Type.TLiteral<"ahead">;
284
+ commitsAhead: Type.TInteger;
285
+ currentSha: Type.TOptional<Type.TString>;
286
+ commitAtMs: Type.TOptional<Type.TInteger>;
287
+ installedAtMs: Type.TOptional<Type.TInteger>;
288
+ }>, Type.TObject<{
289
+ status: Type.TLiteral<"diverged">;
290
+ commitsAhead: Type.TInteger;
291
+ commitsBehind: Type.TInteger;
292
+ currentSha: Type.TOptional<Type.TString>;
293
+ commitAtMs: Type.TOptional<Type.TInteger>;
294
+ installedAtMs: Type.TOptional<Type.TInteger>;
295
+ }>, Type.TObject<{
296
+ status: Type.TLiteral<"unavailable">;
297
+ reason: Type.TUnion<[Type.TLiteral<"fetch-failed">, Type.TLiteral<"no-upstream">, Type.TLiteral<"no-upstream-sha">, Type.TLiteral<"comparison-failed">, Type.TLiteral<"git-unavailable">]>;
298
+ currentSha: Type.TOptional<Type.TString>;
299
+ commitAtMs: Type.TOptional<Type.TInteger>;
300
+ installedAtMs: Type.TOptional<Type.TInteger>;
301
+ }>]>>;
302
+ }>>;
303
+ target: Type.TOptional<Type.TUnion<[Type.TObject<{
304
+ kind: Type.TLiteral<"package">;
305
+ version: Type.TString;
306
+ }>, Type.TObject<{
307
+ kind: Type.TLiteral<"git">;
308
+ upstreamRef: Type.TString;
309
+ upstreamSha: Type.TString;
310
+ commitsBehind: Type.TInteger;
311
+ }>]>>;
312
+ campaign: Type.TOptional<Type.TObject<{
313
+ id: Type.TString;
314
+ state: Type.TUnion<[Type.TLiteral<"waiting-for-idle">, Type.TLiteral<"countdown">, Type.TLiteral<"applying">]>;
315
+ announcedAtMs: Type.TInteger;
316
+ applyAtMs: Type.TOptional<Type.TInteger>;
317
+ holdUntilMs: Type.TOptional<Type.TInteger>;
318
+ forceAtMs: Type.TInteger;
319
+ updatedAtMs: Type.TInteger;
320
+ }>>;
223
321
  }>>;
224
322
  }>;
225
323
  controlUiTabs: Type.TOptional<Type.TArray<Type.TObject<{
@@ -229,6 +327,7 @@ declare const HelloOkSchema: Type.TObject<{
229
327
  description: Type.TOptional<Type.TString>;
230
328
  icon: Type.TOptional<Type.TString>;
231
329
  path: Type.TOptional<Type.TString>;
330
+ placement: Type.TOptional<Type.TString>;
232
331
  requiresGatewayAuth: Type.TOptional<Type.TBoolean>;
233
332
  group: Type.TOptional<Type.TUnion<[Type.TLiteral<"control">, Type.TLiteral<"agent">]>>;
234
333
  order: Type.TOptional<Type.TNumber>;
@@ -239,11 +338,10 @@ declare const HelloOkSchema: Type.TObject<{
239
338
  label: Type.TString;
240
339
  }>>>;
241
340
  pluginSurfaceUrls: Type.TOptional<Type.TRecord<"^.*$", Type.TString>>;
242
- deviceAuthMigration: Type.TOptional<Type.TObject<{
243
- pending: Type.TLiteral<true>;
244
- }>>;
245
341
  auth: Type.TObject<{
246
342
  deviceToken: Type.TOptional<Type.TString>;
343
+ recoveryMigrationAllowed: Type.TOptional<Type.TLiteral<true>>;
344
+ recoveryScope: Type.TOptional<Type.TString>;
247
345
  role: Type.TString;
248
346
  scopes: Type.TArray<Type.TString>;
249
347
  issuedAtMs: Type.TOptional<Type.TInteger>;