Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions apps/web/src/domains/chat/api/event-parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,149 @@ describe("parseAssistantEvent", () => {
});
});

// ---------------------------------------------------------------------
// compaction_circuit_open / compaction_circuit_closed (schema-validated)
// ---------------------------------------------------------------------

test("parses compaction_circuit_open with all required fields", () => {
const event = parseAssistantEvent({
type: "compaction_circuit_open",
conversationId: "conv-1",
reason: "3_consecutive_failures",
openUntil: 1_700_000_000_000,
});
expect(event).toEqual({
type: "compaction_circuit_open",
conversationId: "conv-1",
reason: "3_consecutive_failures",
openUntil: 1_700_000_000_000,
});
});

test("returns unknown compaction_circuit_open when reason is not the recognized literal", () => {
const data = {
type: "compaction_circuit_open",
conversationId: "conv-1",
reason: "unexplained",
openUntil: 1_700_000_000_000,
};
expect(parseAssistantEvent(data)).toEqual({
type: "unknown",
rawType: "compaction_circuit_open",
data,
conversationId: "conv-1",
});
});

test("returns unknown compaction_circuit_open when openUntil is missing", () => {
const data = {
type: "compaction_circuit_open",
conversationId: "conv-1",
reason: "3_consecutive_failures",
};
expect(parseAssistantEvent(data)).toEqual({
type: "unknown",
rawType: "compaction_circuit_open",
data,
conversationId: "conv-1",
});
});

test("parses compaction_circuit_closed with required fields", () => {
const event = parseAssistantEvent({
type: "compaction_circuit_closed",
conversationId: "conv-1",
});
expect(event).toEqual({
type: "compaction_circuit_closed",
conversationId: "conv-1",
});
});

test("returns unknown compaction_circuit_closed when conversationId is missing", () => {
const data = { type: "compaction_circuit_closed" };
expect(parseAssistantEvent(data)).toEqual({
type: "unknown",
rawType: "compaction_circuit_closed",
data,
});
});

// ---------------------------------------------------------------------
// home_feed_updated (schema-validated)
// ---------------------------------------------------------------------

test("parses home_feed_updated with all required fields", () => {
const event = parseAssistantEvent({
type: "home_feed_updated",
updatedAt: "2026-05-29T15:00:00.000Z",
newItemCount: 3,
});
expect(event).toEqual({
type: "home_feed_updated",
updatedAt: "2026-05-29T15:00:00.000Z",
newItemCount: 3,
});
});

test("returns unknown home_feed_updated when updatedAt is missing", () => {
const data = { type: "home_feed_updated", newItemCount: 3 };
expect(parseAssistantEvent(data)).toEqual({
type: "unknown",
rawType: "home_feed_updated",
data,
});
});

test("returns unknown home_feed_updated when newItemCount has wrong type", () => {
const data = {
type: "home_feed_updated",
updatedAt: "2026-05-29T15:00:00.000Z",
newItemCount: "many",
};
expect(parseAssistantEvent(data)).toEqual({
type: "unknown",
rawType: "home_feed_updated",
data,
});
});

// ---------------------------------------------------------------------
// interaction_resolved (schema-validated; legacy tests above still cover
// happy path + invalid state + missing requestId)
// ---------------------------------------------------------------------

test("returns unknown interaction_resolved when conversationId is missing", () => {
const data = {
type: "interaction_resolved",
requestId: "req-1",
state: "approved",
kind: "confirmation",
};
expect(parseAssistantEvent(data)).toEqual({
type: "unknown",
rawType: "interaction_resolved",
data,
});
});

test("returns unknown interaction_resolved when an unknown field is present", () => {
const data = {
type: "interaction_resolved",
requestId: "req-1",
conversationId: "conv-1",
state: "approved",
kind: "confirmation",
legacyField: "x",
};
expect(parseAssistantEvent(data)).toEqual({
type: "unknown",
rawType: "interaction_resolved",
data,
conversationId: "conv-1",
});
});

test("parses error with code and message", () => {
const event = parseAssistantEvent({
type: "error",
Expand Down
56 changes: 0 additions & 56 deletions apps/web/src/domains/chat/api/event-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import type {
AssistantEvent,
ConversationListInvalidatedReason,
DirectoryScopeOption,
InteractionKind,
QuestionEntry,
QuestionOption,
ScopeOption,
Expand Down Expand Up @@ -650,22 +649,6 @@ function parseLegacyEvent(data: Record<string, unknown>): AssistantEvent {
: undefined,
};

case "compaction_circuit_open":
return {
type: "compaction_circuit_open",
conversationId:
typeof data.conversationId === "string" ? data.conversationId : "",
reason: typeof data.reason === "string" ? data.reason : "",
openUntil: typeof data.openUntil === "number" ? data.openUntil : 0,
};

case "compaction_circuit_closed":
return {
type: "compaction_circuit_closed",
conversationId:
typeof data.conversationId === "string" ? data.conversationId : "",
};

case "disk_pressure_status_changed":
return {
type: "disk_pressure_status_changed",
Expand All @@ -680,14 +663,6 @@ function parseLegacyEvent(data: Record<string, unknown>): AssistantEvent {
: undefined,
};

case "home_feed_updated":
return {
type: "home_feed_updated",
updatedAt: typeof data.updatedAt === "string" ? data.updatedAt : "",
newItemCount:
typeof data.newItemCount === "number" ? data.newItemCount : 0,
};

case "subagent_spawned": {
const subagentId =
typeof data.subagentId === "string" ? data.subagentId : "";
Expand Down Expand Up @@ -776,37 +751,6 @@ function parseLegacyEvent(data: Record<string, unknown>): AssistantEvent {
};
}

case "interaction_resolved": {
const requestId =
typeof data.requestId === "string" ? data.requestId : "";
const stateRaw = typeof data.state === "string" ? data.state : "";
const validStates = new Set([
"approved",
"rejected",
"answered",
"cancelled",
"superseded",
]);
if (!requestId || !validStates.has(stateRaw)) {
return unknownEvent(rawType, data);
}
const conversationId =
typeof data.conversationId === "string" ? data.conversationId : "";
const kind = typeof data.kind === "string" ? data.kind : "";
return {
type: "interaction_resolved",
requestId,
conversationId,
state: stateRaw as
| "approved"
| "rejected"
| "answered"
| "cancelled"
| "superseded",
kind: kind as InteractionKind,
};
}

case "document_editor_update": {
const surfaceId =
typeof data.surfaceId === "string" ? data.surfaceId : "";
Expand Down
49 changes: 0 additions & 49 deletions apps/web/src/domains/chat/api/event-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,13 +379,6 @@ export interface NavigateSettingsEvent {
conversationId?: string;
}

export interface HomeFeedUpdatedEvent {
type: "home_feed_updated";
updatedAt: string;
newItemCount: number;
conversationId?: string;
}

// ---------------------------------------------------------------------------
// Subagent event types
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -536,18 +529,6 @@ export interface ConversationErrorEvent {
errorCategory?: string;
}

export interface CompactionCircuitOpenEvent {
type: "compaction_circuit_open";
conversationId: string;
reason: string;
openUntil: number;
}

export interface CompactionCircuitClosedEvent {
type: "compaction_circuit_closed";
conversationId: string;
}

export interface DiskPressureStatusChangedEvent {
type: "disk_pressure_status_changed";
status: DiskPressureStatus | null;
Expand All @@ -558,17 +539,6 @@ export interface AssistantSyncChangedEvent extends SyncChangedEvent {
conversationId?: string;
}

/**
* Lifecycle outcome reported alongside `interaction_resolved`. Mirrors the
* daemon-side `InteractionResolutionState` union.
*/
export type InteractionResolutionState =
| "approved"
| "rejected"
| "answered"
| "cancelled"
| "superseded";

/**
* Mirrors the daemon's `PendingInteraction["kind"]` union
* (`assistant/src/runtime/pending-interactions.ts`). Split into user-facing
Expand Down Expand Up @@ -611,21 +581,6 @@ export const USER_FACING_INTERACTION_KINDS: ReadonlySet<string> =
"acp_confirmation",
]);

/**
* Emitted when a daemon-side pending interaction (confirmation, secret,
* question, host-proxy request) transitions to a resolved state. Drives
* push-based attention reconciliation in the sidebar.
*/
export interface InteractionResolvedEvent {
type: "interaction_resolved";
requestId: string;
/** Conversation id the resolved interaction was registered against. */
conversationId: string;
state: InteractionResolutionState;
/** Kind of the resolved interaction (e.g. `"confirmation"`, `"secret"`). */
kind: InteractionKind;
}

/**
* Every event the chat SSE stream might emit. Schema-validated events
* are covered by `APIAssistantEvent` (the inferred union from
Expand Down Expand Up @@ -656,15 +611,11 @@ export type AssistantEvent =
| IdentityChangedEvent
| AvatarUpdatedEvent
| ConversationErrorEvent
| CompactionCircuitOpenEvent
| CompactionCircuitClosedEvent
| DiskPressureStatusChangedEvent
| AssistantSyncChangedEvent
| HomeFeedUpdatedEvent
| SubagentSpawnedEvent
| SubagentStatusChangedEvent
| SubagentEventWrapperEvent
| DocumentEditorUpdateEvent
| TurnProfileAutoRoutedEvent
| InteractionResolvedEvent
| UnknownEvent;
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { QueryClient } from "@tanstack/react-query";
import type { RelationshipStateUpdatedEvent } from "@vellumai/assistant-api";
import type { HomeFeedUpdatedEvent } from "@/domains/chat/api/event-types";
import type {
HomeFeedUpdatedEvent,
RelationshipStateUpdatedEvent,
} from "@vellumai/assistant-api";
import { HOME_FEED_QUERY_KEY_PREFIX } from "@/lib/sync/query-tags";

export function handleHomeFeedUpdated(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,7 @@ describe("handleUsageUpdate", () => {

it("sets fillRatio to null when maxTokens is missing", () => {
const ctx = makeCtx();
handleUsageUpdate(
{ type: "usage_update", contextWindowTokens: 5000 },
ctx,
);
handleUsageUpdate({ type: "usage_update", contextWindowTokens: 5000 }, ctx);
expect(
ctx.contextWindowUsageByConversationRef.current.get("conv-1"),
).toEqual({
Expand All @@ -69,8 +66,7 @@ describe("handleUsageUpdate", () => {
ctx,
);
expect(
ctx.contextWindowUsageByConversationRef.current.get("conv-1")
?.fillRatio,
ctx.contextWindowUsageByConversationRef.current.get("conv-1")?.fillRatio,
).toBe(1);
});
});
Expand All @@ -80,9 +76,7 @@ describe("handleConversationTitleUpdated", () => {
const ctx = makeCtx();
ctx.queryClient.setQueryData<Conversation[]>(
conversationsQueryKey(ctx.assistantIdRef.current),
[
{ conversationId: "conv-1", title: "Old Title" } as Conversation,
],
[{ conversationId: "conv-1", title: "Old Title" } as Conversation],
);

handleConversationTitleUpdated(
Expand All @@ -97,9 +91,7 @@ describe("handleConversationTitleUpdated", () => {
const cached = ctx.queryClient.getQueryData<Conversation[]>(
conversationsQueryKey(ctx.assistantIdRef.current),
);
const conv = cached?.find(
(c) => c.conversationId === "conv-1",
);
const conv = cached?.find((c) => c.conversationId === "conv-1");
expect(conv?.title).toBe("New Title");
});
});
Expand All @@ -111,7 +103,7 @@ describe("handleCompactionCircuitOpen", () => {
{
type: "compaction_circuit_open",
conversationId: "conv-1",
reason: "test",
reason: "3_consecutive_failures",
openUntil: Date.now() + 60000,
},
ctx,
Expand Down
Loading
Loading