Protocol: lint fixes for client/program
parent
d1217e84c7
commit
cf5769753a
|
|
@ -0,0 +1,37 @@
|
||||||
|
# TypeBox as Protocol Source of Truth
|
||||||
|
|
||||||
|
Last updated: 2025-12-09
|
||||||
|
|
||||||
|
We use TypeBox schemas in `src/gateway/protocol/schema.ts` as the single source of truth for the Gateway control plane (hello/req/res/event frames and payloads). All derived artifacts should be generated from these schemas, not edited by hand.
|
||||||
|
|
||||||
|
## Current pipeline
|
||||||
|
|
||||||
|
- **TypeBox → JSON Schema**: `pnpm protocol:gen` writes `dist/protocol.schema.json` (draft-07) and runs AJV in the server tests.
|
||||||
|
- **TypeBox → Swift (quicktype)**: `pnpm protocol:gen` currently also generates `apps/macos/Sources/ClawdisProtocol/Protocol.swift` via quicktype. This produces a single struct with many optionals and is not ideal for strong typing.
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
- Quicktype flattens `oneOf`/`discriminator` into an all-optional struct, so Swift loses exhaustiveness and safety for `GatewayFrame`.
|
||||||
|
|
||||||
|
## Preferred plan (next step)
|
||||||
|
|
||||||
|
- Add a small, custom Swift generator driven directly by the TypeBox schemas:
|
||||||
|
- Emit a sealed `enum GatewayFrame: Codable { case hello(Hello), helloOk(HelloOk), helloError(...), req(RequestFrame), res(ResponseFrame), event(EventFrame) }`.
|
||||||
|
- Emit strongly typed payload structs/enums (`Hello`, `HelloOk`, `HelloError`, `RequestFrame`, `ResponseFrame`, `EventFrame`, `PresenceEntry`, `Snapshot`, `StateVersion`, `ErrorShape`, `AgentEvent`, `TickEvent`, `ShutdownEvent`, `SendParams`, `AgentParams`, `ErrorCode`, `PROTOCOL_VERSION`).
|
||||||
|
- Custom `init(from:)` / `encode(to:)` enforces the `type` discriminator and can include an `unknown` case for forward compatibility.
|
||||||
|
- Wire a new script (e.g., `pnpm protocol:gen:swift`) into `protocol:check` so CI fails if the generated Swift is stale.
|
||||||
|
|
||||||
|
Why this path:
|
||||||
|
- Single source of truth stays TypeBox; no new IDL to maintain.
|
||||||
|
- Predictable, strongly typed Swift (no optional soup).
|
||||||
|
- Small deterministic codegen (~150–200 LOC script) we control.
|
||||||
|
|
||||||
|
## Alternative (if we want off-the-shelf codegen)
|
||||||
|
|
||||||
|
- Wrap the existing JSON Schema into an OpenAPI 3.1 doc (auto-generated) and use **swift-openapi-generator** or **openapi-generator swift5**. More moving parts, but also yields enums with discriminator support. Keep this as a fallback if we don’t want a custom emitter.
|
||||||
|
|
||||||
|
## Action items
|
||||||
|
|
||||||
|
- Implement `protocol:gen:swift` that reads the TypeBox schemas and emits the sealed Swift enum + payload structs.
|
||||||
|
- Update `protocol:check` to include the Swift generator output in the diff check.
|
||||||
|
- Remove quicktype output once the custom generator is in place (or keep it for docs only).
|
||||||
|
|
@ -5,7 +5,6 @@ import { healthCommand } from "../commands/health.js";
|
||||||
import { sendCommand } from "../commands/send.js";
|
import { sendCommand } from "../commands/send.js";
|
||||||
import { sessionsCommand } from "../commands/sessions.js";
|
import { sessionsCommand } from "../commands/sessions.js";
|
||||||
import { statusCommand } from "../commands/status.js";
|
import { statusCommand } from "../commands/status.js";
|
||||||
import { loadConfig } from "../config/config.js";
|
|
||||||
import { callGateway, randomIdempotencyKey } from "../gateway/call.js";
|
import { callGateway, randomIdempotencyKey } from "../gateway/call.js";
|
||||||
import { startGatewayServer } from "../gateway/server.js";
|
import { startGatewayServer } from "../gateway/server.js";
|
||||||
import { danger, info, setVerbose } from "../globals.js";
|
import { danger, info, setVerbose } from "../globals.js";
|
||||||
|
|
@ -13,11 +12,8 @@ import { loginWeb, logoutWeb } from "../provider-web.js";
|
||||||
import { runRpcLoop } from "../rpc/loop.js";
|
import { runRpcLoop } from "../rpc/loop.js";
|
||||||
import { defaultRuntime } from "../runtime.js";
|
import { defaultRuntime } from "../runtime.js";
|
||||||
import { VERSION } from "../version.js";
|
import { VERSION } from "../version.js";
|
||||||
import {
|
import { startWebChatServer } from "../webchat/server.js";
|
||||||
ensureWebChatServerFromConfig,
|
import { createDefaultDeps } from "./deps.js";
|
||||||
startWebChatServer,
|
|
||||||
} from "../webchat/server.js";
|
|
||||||
import { createDefaultDeps, logWebSelfId } from "./deps.js";
|
|
||||||
|
|
||||||
export function buildProgram() {
|
export function buildProgram() {
|
||||||
const program = new Command();
|
const program = new Command();
|
||||||
|
|
@ -66,14 +62,8 @@ export function buildProgram() {
|
||||||
'clawdis send --to +15555550123 --message "Hi" --json',
|
'clawdis send --to +15555550123 --message "Hi" --json',
|
||||||
"Send via your web session and print JSON result.",
|
"Send via your web session and print JSON result.",
|
||||||
],
|
],
|
||||||
[
|
["clawdis gateway --port 18789", "Run the WebSocket Gateway locally."],
|
||||||
"clawdis gateway --port 18789",
|
["clawdis gw:status", "Fetch Gateway status over WS."],
|
||||||
"Run the WebSocket Gateway locally.",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"clawdis gw:status",
|
|
||||||
"Fetch Gateway status over WS.",
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
'clawdis agent --to +15555550123 --message "Run summary" --deliver',
|
'clawdis agent --to +15555550123 --message "Run summary" --deliver',
|
||||||
"Talk directly to the agent using the Gateway; optionally send the WhatsApp reply.",
|
"Talk directly to the agent using the Gateway; optionally send the WhatsApp reply.",
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ import {
|
||||||
} from "./protocol/index.js";
|
} from "./protocol/index.js";
|
||||||
|
|
||||||
type Pending = {
|
type Pending = {
|
||||||
resolve: (value: any) => void;
|
resolve: (value: unknown) => void;
|
||||||
reject: (err: any) => void;
|
reject: (err: unknown) => void;
|
||||||
expectFinal: boolean;
|
expectFinal: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -167,7 +167,11 @@ export class GatewayClient {
|
||||||
}
|
}
|
||||||
const expectFinal = opts?.expectFinal === true;
|
const expectFinal = opts?.expectFinal === true;
|
||||||
const p = new Promise<T>((resolve, reject) => {
|
const p = new Promise<T>((resolve, reject) => {
|
||||||
this.pending.set(id, { resolve, reject, expectFinal });
|
this.pending.set(id, {
|
||||||
|
resolve: (value) => resolve(value as T),
|
||||||
|
reject,
|
||||||
|
expectFinal,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
this.ws.send(JSON.stringify(frame));
|
this.ws.send(JSON.stringify(frame));
|
||||||
return p;
|
return p;
|
||||||
|
|
|
||||||
|
|
@ -9,31 +9,31 @@ import {
|
||||||
type EventFrame,
|
type EventFrame,
|
||||||
EventFrameSchema,
|
EventFrameSchema,
|
||||||
errorShape,
|
errorShape,
|
||||||
|
type GatewayFrame,
|
||||||
|
GatewayFrameSchema,
|
||||||
type Hello,
|
type Hello,
|
||||||
type HelloError,
|
type HelloError,
|
||||||
HelloErrorSchema,
|
HelloErrorSchema,
|
||||||
type HelloOk,
|
type HelloOk,
|
||||||
HelloOkSchema,
|
HelloOkSchema,
|
||||||
HelloSchema,
|
HelloSchema,
|
||||||
|
PROTOCOL_VERSION,
|
||||||
type PresenceEntry,
|
type PresenceEntry,
|
||||||
PresenceEntrySchema,
|
PresenceEntrySchema,
|
||||||
ProtocolSchemas,
|
ProtocolSchemas,
|
||||||
PROTOCOL_VERSION,
|
|
||||||
type RequestFrame,
|
type RequestFrame,
|
||||||
RequestFrameSchema,
|
RequestFrameSchema,
|
||||||
type ResponseFrame,
|
type ResponseFrame,
|
||||||
ResponseFrameSchema,
|
ResponseFrameSchema,
|
||||||
SendParamsSchema,
|
SendParamsSchema,
|
||||||
|
type ShutdownEvent,
|
||||||
|
ShutdownEventSchema,
|
||||||
type Snapshot,
|
type Snapshot,
|
||||||
SnapshotSchema,
|
SnapshotSchema,
|
||||||
type StateVersion,
|
type StateVersion,
|
||||||
StateVersionSchema,
|
StateVersionSchema,
|
||||||
TickEventSchema,
|
|
||||||
type TickEvent,
|
type TickEvent,
|
||||||
GatewayFrameSchema,
|
TickEventSchema,
|
||||||
type GatewayFrame,
|
|
||||||
type ShutdownEvent,
|
|
||||||
ShutdownEventSchema,
|
|
||||||
} from "./schema.js";
|
} from "./schema.js";
|
||||||
|
|
||||||
const ajv = new (
|
const ajv = new (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue