Files
openclaw/packages/gateway-protocol/src/frame-guards.ts
T
Peter Steinberger 1bcc4c5e70 feat(gateway): add cooperative host suspension (#103618)
* feat(gateway): add cooperative suspension preparation

* style: satisfy suspension lint checks

* test(gateway): reset work admission between shared suites

* fix(gateway): reject upgrades during suspension

* fix(gateway): preserve admitted work during suspension

* test(gateway): isolate suspension and restart state

* fix(gateway): close suspension false-ready gaps

* refactor(protocol): slim suspension declaration graph

* refactor(plugin-sdk): sever protocol registry edges

* fix(gateway): preserve admitted restart follow-ups

* fix(gateway): make suspension recovery fail closed

* fix(protocol): keep validation formatter re-export only

* test(gateway): simplify deferred fixture type

* style(gateway): clarify suspension entry name

* fix(gateway): retain detached work admission
2026-07-10 20:24:53 +01:00

57 lines
1.7 KiB
TypeScript

import type { EventFrame, ResponseFrame } from "./schema/frames.js";
export type {
ConnectParams,
ErrorShape,
EventFrame,
GatewayFrame,
HelloOk,
RequestFrame,
ResponseFrame,
} from "./schema/frames.js";
function isRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
function isNonEmptyString(value: unknown): value is string {
return typeof value === "string" && value.length > 0;
}
function isNonNegativeInteger(value: unknown): value is number {
return typeof value === "number" && Number.isInteger(value) && value >= 0;
}
function isGatewayErrorShape(value: unknown): boolean {
if (!isRecord(value)) {
return false;
}
if (!isNonEmptyString(value.code) || !isNonEmptyString(value.message)) {
return false;
}
if (value.retryable !== undefined && typeof value.retryable !== "boolean") {
return false;
}
return value.retryAfterMs === undefined || isNonNegativeInteger(value.retryAfterMs);
}
// These lightweight guards validate dispatch-critical envelope fields without
// compiling the full schemas or rejecting additive payload fields.
export function isGatewayEventFrame(value: unknown): value is EventFrame {
if (!isRecord(value) || value.type !== "event" || !isNonEmptyString(value.event)) {
return false;
}
return value.seq === undefined || isNonNegativeInteger(value.seq);
}
export function isGatewayResponseFrame(value: unknown): value is ResponseFrame {
if (
!isRecord(value) ||
value.type !== "res" ||
!isNonEmptyString(value.id) ||
typeof value.ok !== "boolean"
) {
return false;
}
return value.error === undefined || isGatewayErrorShape(value.error);
}