refactor(chat): route browser chat through one runtime

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01ESFAnh2t9HmLwng8oW95St
This commit is contained in:
2026-08-12 14:43:59 -05:00
co-authored by Claude Opus 4.8
parent 6a8ce66702
commit bd69eca555
29 changed files with 4417 additions and 662 deletions
+45
View File
@@ -6,6 +6,7 @@ import type {
SlashCommandResultPayload,
SystemReloadPayload,
} from '../commands/index.js';
import type { HarnessErrorCode, HarnessSelection, HarnessTurnState } from '../harness/index.js';
export interface MessageAckPayload {
conversationId: string;
@@ -107,6 +108,48 @@ export interface AbortPayload {
conversationId: string;
}
/**
* The frozen P3 `turn:send` wire contract (Task Five; reused unchanged by Tasks 15 and 16).
* Accepts no attachments or authority-bearing fields in Slice Zero. Gateway validation
* requires a UUID conversation id, non-empty bounded content, a nested selection with exactly
* `harnessId`/`providerId`/`modelId` (each 1..255 chars), and a UUID-v4 idempotency key; it
* rejects unknown fields, top-level `provider`/`modelId`, malformed nesting, and empty values
* before any runtime dispatch.
*/
export interface HarnessTurnSendPayload {
readonly conversationId: string; // UUID; required before send
readonly content: string; // trimmed, 1..10_000 characters
readonly selection: HarnessSelection; // nested; exactly three ids
readonly idempotencyKey: string; // browser-generated UUID v4
}
/**
* The frozen `turn:ack` wire contract. Success echoes the accepted idempotency key and the
* exact requested selection tuple; failure carries only fixed/safe text and never a
* substituted effective selection or raw exception text.
*/
export type HarnessTurnAckPayload =
| {
readonly ok: true;
readonly conversationId: string;
readonly idempotencyKey: string;
readonly turnId: string;
readonly correlationId: string;
readonly state: HarnessTurnState;
readonly selection: HarnessSelection;
}
| {
readonly ok: false;
readonly conversationId?: string;
readonly idempotencyKey?: string;
readonly code: HarnessErrorCode | 'request_invalid' | 'runtime_unsupported';
readonly message: string; // fixed/safe text only
readonly retryable: boolean;
readonly correlationId: string;
/** Present only when a complete tuple was validated; always the requested tuple. */
readonly selection?: HarnessSelection;
};
/** Socket.IO typed event map: server → client */
export interface ServerToClientEvents {
'message:ack': (payload: MessageAckPayload) => void;
@@ -121,12 +164,14 @@ export interface ServerToClientEvents {
'command:result': (payload: SlashCommandResultPayload) => void;
'command:approval': (payload: SlashCommandApprovalResultPayload) => void;
'system:reload': (payload: SystemReloadPayload) => void;
'turn:ack': (payload: HarnessTurnAckPayload) => void;
error: (payload: ErrorPayload) => void;
}
/** Socket.IO typed event map: client → server */
export interface ClientToServerEvents {
message: (data: ChatMessagePayload) => void;
'turn:send': (data: HarnessTurnSendPayload) => void;
'set:thinking': (data: SetThinkingPayload) => void;
'command:execute': (data: SlashCommandPayload) => void;
'command:approve': (data: SlashCommandPayload) => void;
+2
View File
@@ -14,6 +14,8 @@ export type {
AbortPayload,
ErrorPayload,
ChatMessagePayload,
HarnessTurnSendPayload,
HarnessTurnAckPayload,
ServerToClientEvents,
ClientToServerEvents,
} from './events.js';