From 3c58aa460ddff9743aadfa7f7557502b5e40c602 Mon Sep 17 00:00:00 2001 From: Adam Woo Date: Tue, 25 Aug 2026 11:36:27 -0400 Subject: [PATCH] Fix merged source validation --- .../sessions/src/components/dashboard.tsx | 1 - .../dashboard/session-navigation.test.tsx | 1 - packages/infrastructure/protocol/src/index.ts | 16 +++++- .../session-client/src/index.test.ts | 55 ++++++++++++++++++- .../session-client/src/session-state.test.ts | 1 - 5 files changed, 67 insertions(+), 7 deletions(-) diff --git a/packages/features/sessions/src/components/dashboard.tsx b/packages/features/sessions/src/components/dashboard.tsx index e7504cd..62504b6 100644 --- a/packages/features/sessions/src/components/dashboard.tsx +++ b/packages/features/sessions/src/components/dashboard.tsx @@ -12,4 +12,3 @@ export function Dashboard(props: DashboardProps) { ); } - diff --git a/packages/features/sessions/src/components/dashboard/session-navigation.test.tsx b/packages/features/sessions/src/components/dashboard/session-navigation.test.tsx index 66998da..ec70a48 100644 --- a/packages/features/sessions/src/components/dashboard/session-navigation.test.tsx +++ b/packages/features/sessions/src/components/dashboard/session-navigation.test.tsx @@ -512,4 +512,3 @@ describe("dashboard launch selection", () => { ).toBe(true); }); }); - diff --git a/packages/infrastructure/protocol/src/index.ts b/packages/infrastructure/protocol/src/index.ts index 369914b..a5d65de 100644 --- a/packages/infrastructure/protocol/src/index.ts +++ b/packages/infrastructure/protocol/src/index.ts @@ -97,9 +97,19 @@ const TranscriptImageAvailableSchema = z .string() .min(1) .max(Math.ceil(TRANSCRIPT_IMAGE_MAX_BYTES / 3) * 4) - .refine(isTranscriptImageBase64Alphabet) - .refine((data) => data.length % 4 === 0) - .refine((data) => getTranscriptImageBase64ByteLength(data) <= TRANSCRIPT_IMAGE_MAX_BYTES), + .superRefine((data, context) => { + if (getTranscriptImageBase64ByteLength(data) > TRANSCRIPT_IMAGE_MAX_BYTES) { + context.addIssue({ code: "custom", message: "Image exceeds the per-image byte limit" }); + return; + } + if (data.length % 4 !== 0) { + context.addIssue({ code: "custom", message: "Expected padded base64" }); + return; + } + if (!isTranscriptImageBase64Alphabet(data)) { + context.addIssue({ code: "custom", message: "Expected valid base64" }); + } + }), }) .strict(); const TranscriptImageUnavailableSchema = z diff --git a/packages/infrastructure/session-client/src/index.test.ts b/packages/infrastructure/session-client/src/index.test.ts index b390819..1191957 100644 --- a/packages/infrastructure/session-client/src/index.test.ts +++ b/packages/infrastructure/session-client/src/index.test.ts @@ -1,14 +1,22 @@ -import type { NotificationEvent } from "@omp-remote/protocol"; +import type { NotificationEvent, Session, SessionTranscriptResponse } from "@omp-remote/protocol"; import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from "vitest"; import { + applyTranscriptToSessions, boundedServerError, commandResultValue, + cleanupSessionHistory, + createCatalogLoadCoordinator, dispatchNotificationEvent, + mergeSessions, + mergeTranscriptMessages, type QueuedUserMessage, rejectPendingCommands, resolvePendingCommand, sendBrowserCommand, SESSION_COMMAND_TIMEOUT_MS, + snapshotSessionsWithCurrentMessages, + type TranscriptProvenance, + upsertTranscriptMessage, useSessionClient, } from "./index.js"; @@ -31,6 +39,51 @@ vi.mock("react", () => ({ }, })); +const SESSION: Session = { + id: "session-1", + source: "rpc", + name: "Stream test", + cwd: "/tmp/stream-test", + branch: "feature/streaming", + status: "running", + connected: true, + model: "openai/gpt-5.6", + contextPercent: 12, + createdAt: "2026-07-28T21:00:00.000Z", + lastActivity: "2026-07-28T22:00:00.000Z", + capabilities: ["prompt", "steer", "follow_up", "abort", "resume"], + messages: [ + { + id: "message-1", + role: "assistant", + text: "Starting", + timestamp: "2026-07-28T22:01:00.000Z", + streaming: true, + presentation: "text", + }, + ], + sessionPath: "/tmp/session.jsonl", + activeSubagents: [], + skillCommands: [], +}; +const makeMsg = (id: string, text = id, streaming = false): Session["messages"][number] => ({ + id, + role: "user", + text, + timestamp: "2026-08-01T00:00:00.000Z", + streaming, + presentation: "text", +}); +const makePage = ( + sessionId: string, + messages: Session["messages"] = [], + status: "available" | "complete" | "unavailable" | "invalidated" = "complete", + olderCursor: string | null = null, +): SessionTranscriptResponse => + (status === "available" + ? { sessionId, messages, status: "available", olderCursor: olderCursor ?? "cursor" } + : { sessionId, messages, status, olderCursor: null }) as SessionTranscriptResponse; + class FakeWebSocket extends EventTarget { static readonly OPEN = 1; static readonly CLOSED = 3; diff --git a/packages/infrastructure/session-client/src/session-state.test.ts b/packages/infrastructure/session-client/src/session-state.test.ts index 5d44331..505954a 100644 --- a/packages/infrastructure/session-client/src/session-state.test.ts +++ b/packages/infrastructure/session-client/src/session-state.test.ts @@ -254,4 +254,3 @@ describe("application errors client support", () => { expect(result).toEqual({ ok: true, clearedCount: 1 }); }); }); -