feat: single-source cross-app wire contracts in @boocode/contracts (v2.7.13)
Move all hand-synced cross-app wire contracts into one built workspace package, @boocode/contracts, consumed by server/web/coder/coder-web via workspace:* + a per-subpath exports map. The ws-frames and provider-config Zod schemas are schema-first (z.infer); MessageMetadata, ErrorReason, AgentSessionConfig, the provider snapshot types, and WorktreeRiskReport are each single-sourced. Deletes the byte-identical copies and their parity tests, fixes a live AgentSessionConfig drift (coder dead copy removed, unified to the web required/nullable shape), removes the dead pending_change WS arms in the fallback SPA, and inverts the build order (contracts builds first) across root build, Dockerfile, and the coder deploy docs. Reverses the shared-package decision declined in v2.5.12. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -34,18 +34,8 @@ export interface AvailableProject {
|
||||
|
||||
export type SessionStatus = 'open' | 'archived';
|
||||
|
||||
// Session-delete work-loss guard. Mirror of WorktreeRiskReport in
|
||||
// apps/server/src/types/api.ts — edit both copies together. Arrives as the
|
||||
// `reports` field of the 409 body when a delete is blocked.
|
||||
export interface WorktreeRiskReport {
|
||||
worktreePath: string;
|
||||
branch: string;
|
||||
dirty: boolean;
|
||||
unpushed: number; // commits ahead of upstream, or -1 if no upstream
|
||||
unmerged: number; // commits not in the project default branch
|
||||
atRisk: boolean;
|
||||
error?: string;
|
||||
}
|
||||
// WorktreeRiskReport single-sourced in @boocode/contracts — edit the package, not here.
|
||||
export type { WorktreeRiskReport } from '@boocode/contracts/worktree-risk';
|
||||
|
||||
export interface Session {
|
||||
id: string;
|
||||
@@ -143,49 +133,10 @@ export interface ToolResult {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// v1.8.2: structured reason codes that flow through error frames / metadata.
|
||||
// `error` text stays human; `reason` is the discriminator the UI matches on.
|
||||
export type ErrorReason =
|
||||
| 'llm_provider_error'
|
||||
| 'tool_execution_failed'
|
||||
| 'summary_after_cap_failed';
|
||||
|
||||
// v1.8.2 / v1.11.6: shapes stored in Message.metadata. Discriminated on `kind`.
|
||||
// cap_hit — sentinel emitted when the tool budget is hit; carries the
|
||||
// budget + agent name + whether Continue is still allowed.
|
||||
// doom_loop — sentinel emitted when the model called the same tool with
|
||||
// the same arguments threshold times in a row.
|
||||
// mistake_recovery — sentinel emitted when the model hit repeated *different*
|
||||
// errors; non-escalated means recovery guidance was injected and
|
||||
// the turn continues, escalated means the turn was stopped.
|
||||
// error — attached to a failed assistant message so the bubble can show
|
||||
// a specific reason on reload (WS error frame is one-shot).
|
||||
export type MessageMetadata =
|
||||
| {
|
||||
kind: 'cap_hit';
|
||||
used: number;
|
||||
limit: number;
|
||||
agent_name: string | null;
|
||||
can_continue: boolean;
|
||||
}
|
||||
| {
|
||||
kind: 'doom_loop';
|
||||
tool_name: string;
|
||||
args: Record<string, unknown>;
|
||||
threshold: number;
|
||||
}
|
||||
| {
|
||||
kind: 'mistake_recovery';
|
||||
failure_kinds: string[];
|
||||
count: number;
|
||||
escalated: boolean;
|
||||
can_continue?: boolean;
|
||||
}
|
||||
| {
|
||||
kind: 'error';
|
||||
error_reason: ErrorReason;
|
||||
error_text: string;
|
||||
};
|
||||
// v1.8.2 / v1.11.6: ErrorReason + MessageMetadata single-sourced in
|
||||
// @boocode/contracts — edit the package, not here.
|
||||
import type { ErrorReason, MessageMetadata } from '@boocode/contracts/message-metadata';
|
||||
export type { ErrorReason, MessageMetadata };
|
||||
|
||||
export interface Message {
|
||||
id: string;
|
||||
@@ -239,80 +190,23 @@ export interface ModelInfo {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface ProviderModel {
|
||||
id: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
isDefault?: boolean;
|
||||
thinkingOptions?: ThinkingOption[];
|
||||
defaultThinkingOptionId?: string;
|
||||
}
|
||||
export type {
|
||||
ProviderModel,
|
||||
ProviderMode,
|
||||
ThinkingOption,
|
||||
ProviderSnapshotStatus,
|
||||
AgentCommand,
|
||||
ProviderSnapshotEntry,
|
||||
} from '@boocode/contracts/provider-snapshot';
|
||||
|
||||
export interface ProviderMode {
|
||||
id: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
isUnattended?: boolean;
|
||||
}
|
||||
export type {
|
||||
ProviderOverride,
|
||||
CoderProvidersFile,
|
||||
ProviderConfigPatch,
|
||||
} from '@boocode/contracts/provider-config';
|
||||
|
||||
export interface ThinkingOption {
|
||||
id: string;
|
||||
label: string;
|
||||
isDefault?: boolean;
|
||||
}
|
||||
|
||||
// v2.3 phase 2: 'loading' + 'unavailable' restored alongside 'ready' | 'error'.
|
||||
export type ProviderSnapshotStatus = 'loading' | 'ready' | 'unavailable' | 'error';
|
||||
|
||||
// KEEP IN SYNC with apps/coder/src/services/provider-types.ts ProviderSnapshotEntry
|
||||
// — parity is enforced by coder __tests__/provider-types-parity.test.ts (field drift fails it).
|
||||
export interface ProviderSnapshotEntry {
|
||||
name: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
transport: string;
|
||||
status: ProviderSnapshotStatus;
|
||||
enabled: boolean;
|
||||
installed: boolean;
|
||||
models: ProviderModel[];
|
||||
modes: ProviderMode[];
|
||||
defaultModeId: string | null;
|
||||
commands: AgentCommand[];
|
||||
error?: string;
|
||||
fetchedAt?: string;
|
||||
}
|
||||
|
||||
// v2.3 Phase 4: provider config file wire types. Mirror of the Zod-inferred
|
||||
// ProviderOverride / CoderProvidersFile in apps/coder/src/services/provider-config.ts
|
||||
// (web can't cross-import the coder package — TS6307 on the composite project).
|
||||
export interface ProviderOverride {
|
||||
extends?: 'acp';
|
||||
label?: string;
|
||||
description?: string;
|
||||
command?: string[];
|
||||
env?: Record<string, string>;
|
||||
enabled?: boolean;
|
||||
order?: number;
|
||||
models?: Array<{ id: string; label: string }>;
|
||||
additionalModels?: Array<{ id: string; label: string }>;
|
||||
}
|
||||
|
||||
export interface CoderProvidersFile {
|
||||
providers: Record<string, ProviderOverride>;
|
||||
}
|
||||
|
||||
// PATCH body: a partial providers map. A `null` value deletes that id's
|
||||
// override (revert to built-in default); an object replaces it wholesale.
|
||||
export interface ProviderConfigPatch {
|
||||
providers: Record<string, ProviderOverride | null>;
|
||||
}
|
||||
|
||||
export interface AgentSessionConfig {
|
||||
provider: string;
|
||||
model: string;
|
||||
modeId: string | null;
|
||||
thinkingOptionId: string | null;
|
||||
}
|
||||
// AgentSessionConfig single-sourced in @boocode/contracts — edit the package, not here.
|
||||
export type { AgentSessionConfig } from '@boocode/contracts/message-metadata';
|
||||
|
||||
export type PermissionKind = 'tool' | 'question' | 'plan' | 'elicitation';
|
||||
|
||||
@@ -324,14 +218,6 @@ export interface PermissionPrompt {
|
||||
options: Array<{ optionId: string; label: string }>;
|
||||
}
|
||||
|
||||
export interface AgentCommand {
|
||||
name: string;
|
||||
description?: string;
|
||||
// v2.5.11: 'skill' (plugin skill) vs 'command' (native/CLI slash command).
|
||||
// Drives the icon split in the coder slash menu. Undefined → command.
|
||||
kind?: 'command' | 'skill';
|
||||
}
|
||||
|
||||
export interface CoderSendMessageBody {
|
||||
content: string;
|
||||
pane_id: string;
|
||||
|
||||
@@ -1,408 +0,0 @@
|
||||
// v1.13.11-a: Zod schemas for every WebSocket frame published by the server.
|
||||
// Validation runs both on send (broker.publishFrame / publishUserFrame) and
|
||||
// on receive (apps/web/src/hooks/useSessionStream + useUserEvents). Catches
|
||||
// silent protocol drift between publisher and consumer.
|
||||
//
|
||||
// IMPORTANT: This file is duplicated byte-identical at
|
||||
// apps/web/src/api/ws-frames.ts. The two apps have separate tsconfigs and
|
||||
// no path alias; the duplication is sync-by-hand. A test asserts the two
|
||||
// files match. If you change one, change the other.
|
||||
//
|
||||
// Per-kind payload schemas (tool_call args, message_parts payloads, etc.)
|
||||
// stay z.unknown() in v1.13.11. Frame-level drift detection is the goal;
|
||||
// deep payload validation is follow-up work.
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
// ---- shared primitives -----------------------------------------------------
|
||||
|
||||
const Uuid = z.string().uuid();
|
||||
// Tool call IDs are model-emitted (e.g. "call_abc123") — not UUIDs.
|
||||
const ToolCallId = z.string().min(1);
|
||||
// v1.13.12 fix: postgres returns timestamp columns as JS Date objects, not
|
||||
// strings. The publish sites pass them through unchanged, so the schema must
|
||||
// tolerate both. preprocess converts Date → ISO string before string-validation;
|
||||
// on the web side (where frames arrive via JSON.parse) it's a no-op. Before
|
||||
// this fix, every message_complete / session_updated / chat_updated frame
|
||||
// failed validation and got dropped — symptoms: token tracking blank in UI,
|
||||
// status stuck at 'streaming' tripping the 60s stale-stream banner.
|
||||
const IsoTimestamp = z.preprocess(
|
||||
(v) => (v instanceof Date ? v.toISOString() : v),
|
||||
z.string().min(1),
|
||||
);
|
||||
|
||||
const ChatStatusValue = z.enum([
|
||||
'streaming',
|
||||
'tool_running',
|
||||
'waiting_for_input',
|
||||
'idle',
|
||||
'error',
|
||||
]);
|
||||
|
||||
// agent-status-normalize (#10): normalized per-(chat,agent) lifecycle status for
|
||||
// external coding agents (warm-acp / opencode / claude-sdk / pty). Distinct from
|
||||
// ChatStatusValue (native-inference chat lifecycle) — published by BooCoder's
|
||||
// dispatcher + permission flow on the per-session channel.
|
||||
const AgentStatusValue = z.enum(['working', 'blocked', 'idle', 'error']);
|
||||
|
||||
const ErrorReasonValue = z.enum([
|
||||
'llm_provider_error',
|
||||
'doom_loop',
|
||||
'doom_loop_summary_failed',
|
||||
'cap_hit',
|
||||
'cap_hit_summary_failed',
|
||||
]);
|
||||
|
||||
const MessageRoleValue = z.enum(['user', 'assistant', 'system', 'tool']);
|
||||
|
||||
const ToolCallShape = z.object({
|
||||
id: ToolCallId,
|
||||
name: z.string().min(1),
|
||||
args: z.record(z.string(), z.unknown()),
|
||||
});
|
||||
|
||||
// Free-form bags: opaque to the frame schema; deep validation is out of
|
||||
// scope for v1.13.11 (frame-level drift detection is the goal; per-kind
|
||||
// payload narrowing is follow-up work). z.unknown() means the consumer
|
||||
// must narrow before reading — TypeScript-side this is fine because every
|
||||
// consumer already operates on the hand-maintained Project / Chat / Session
|
||||
// / WorkspacePane types (the brief's "Don't strip existing types yet"
|
||||
// rule), and the Zod-typed shape is only used at the publishFrame boundary.
|
||||
const OpaqueObject = z.unknown();
|
||||
|
||||
// ---- per-session channel frames --------------------------------------------
|
||||
|
||||
export const SnapshotFrame = z.object({
|
||||
type: z.literal('snapshot'),
|
||||
messages: z.array(OpaqueObject),
|
||||
});
|
||||
|
||||
export const MessageStartedFrame = z.object({
|
||||
type: z.literal('message_started'),
|
||||
message_id: Uuid,
|
||||
chat_id: Uuid.optional(),
|
||||
role: MessageRoleValue,
|
||||
});
|
||||
|
||||
export const DeltaFrame = z.object({
|
||||
type: z.literal('delta'),
|
||||
message_id: Uuid,
|
||||
chat_id: Uuid.optional(),
|
||||
content: z.string(),
|
||||
});
|
||||
|
||||
export const ReasoningDeltaFrame = z.object({
|
||||
type: z.literal('reasoning_delta'),
|
||||
message_id: Uuid,
|
||||
chat_id: Uuid.optional(),
|
||||
content: z.string(),
|
||||
});
|
||||
|
||||
export const ToolCallFrame = z.object({
|
||||
type: z.literal('tool_call'),
|
||||
message_id: Uuid,
|
||||
chat_id: Uuid.optional(),
|
||||
tool_call: ToolCallShape,
|
||||
});
|
||||
|
||||
export const ToolResultFrame = z.object({
|
||||
type: z.literal('tool_result'),
|
||||
tool_message_id: Uuid,
|
||||
chat_id: Uuid.optional(),
|
||||
tool_call_id: ToolCallId,
|
||||
output: z.unknown(),
|
||||
truncated: z.boolean(),
|
||||
error: z.string().optional(),
|
||||
});
|
||||
|
||||
export const MessageCompleteFrame = z.object({
|
||||
type: z.literal('message_complete'),
|
||||
message_id: Uuid,
|
||||
chat_id: Uuid.optional(),
|
||||
tokens_used: z.number().int().nonnegative().nullable().optional(),
|
||||
ctx_used: z.number().int().nonnegative().nullable().optional(),
|
||||
ctx_max: z.number().int().positive().nullable().optional(),
|
||||
started_at: IsoTimestamp.nullable().optional(),
|
||||
finished_at: IsoTimestamp.nullable().optional(),
|
||||
// nullable: external-coder turns carry task.model, which is null when no
|
||||
// model was selected. This frame is published through the same fail-closed
|
||||
// publishFrame, so null MUST validate or the entire frame (incl. the
|
||||
// status:'complete' transition) is dropped.
|
||||
model: z.string().nullable().optional(),
|
||||
metadata: OpaqueObject.nullable().optional(),
|
||||
});
|
||||
|
||||
export const UsageFrame = z.object({
|
||||
type: z.literal('usage'),
|
||||
message_id: Uuid,
|
||||
chat_id: Uuid.optional(),
|
||||
completion_tokens: z.number().int().nonnegative().nullable(),
|
||||
ctx_used: z.number().int().nonnegative().nullable(),
|
||||
ctx_max: z.number().int().positive().nullable(),
|
||||
});
|
||||
|
||||
export const MessagesDeletedFrame = z.object({
|
||||
type: z.literal('messages_deleted'),
|
||||
message_ids: z.array(Uuid),
|
||||
chat_id: Uuid.optional(),
|
||||
});
|
||||
|
||||
export const ChatRenamedFrame = z.object({
|
||||
type: z.literal('chat_renamed'),
|
||||
chat_id: Uuid,
|
||||
name: z.string(),
|
||||
});
|
||||
|
||||
export const CompactedFrame = z.object({
|
||||
type: z.literal('compacted'),
|
||||
session_id: Uuid,
|
||||
chat_id: Uuid,
|
||||
summary_message_id: Uuid,
|
||||
});
|
||||
|
||||
export const ErrorFrame = z.object({
|
||||
type: z.literal('error'),
|
||||
message_id: Uuid.optional(),
|
||||
chat_id: Uuid.optional(),
|
||||
error: z.string(),
|
||||
reason: ErrorReasonValue.optional(),
|
||||
});
|
||||
|
||||
// ---- per-user channel frames (sidebar refresh) -----------------------------
|
||||
|
||||
export const ChatStatusFrame = z.object({
|
||||
type: z.literal('chat_status'),
|
||||
chat_id: Uuid,
|
||||
status: ChatStatusValue,
|
||||
at: IsoTimestamp,
|
||||
reason: ErrorReasonValue.optional(),
|
||||
});
|
||||
|
||||
export const SessionUpdatedFrame = z.object({
|
||||
type: z.literal('session_updated'),
|
||||
session_id: Uuid,
|
||||
project_id: Uuid,
|
||||
name: z.string(),
|
||||
updated_at: IsoTimestamp,
|
||||
});
|
||||
|
||||
export const SessionRenamedFrame = z.object({
|
||||
type: z.literal('session_renamed'),
|
||||
session_id: Uuid,
|
||||
name: z.string(),
|
||||
});
|
||||
|
||||
export const SessionCreatedFrame = z.object({
|
||||
type: z.literal('session_created'),
|
||||
session: OpaqueObject,
|
||||
project_id: Uuid,
|
||||
});
|
||||
|
||||
export const SessionArchivedFrame = z.object({
|
||||
type: z.literal('session_archived'),
|
||||
session_id: Uuid,
|
||||
project_id: Uuid,
|
||||
});
|
||||
|
||||
export const SessionDeletedFrame = z.object({
|
||||
type: z.literal('session_deleted'),
|
||||
session_id: Uuid,
|
||||
project_id: Uuid,
|
||||
});
|
||||
|
||||
export const SessionWorkspaceUpdatedFrame = z.object({
|
||||
type: z.literal('session_workspace_updated'),
|
||||
session_id: Uuid,
|
||||
// v2.6.x: widened from z.array — the payload is now either the legacy bare
|
||||
// WorkspacePane[] OR the WorkspaceState envelope object (panes + tabNumbers +
|
||||
// nextTabNumber + closedPaneStack). z.array alone would fail-closed and drop
|
||||
// every envelope frame at validation. MUST be mirrored in the server's
|
||||
// byte-identical copy (parity test).
|
||||
workspace_panes: z.union([z.array(OpaqueObject), z.record(z.unknown())]),
|
||||
});
|
||||
|
||||
export const ChatCreatedFrame = z.object({
|
||||
type: z.literal('chat_created'),
|
||||
chat: OpaqueObject,
|
||||
session_id: Uuid,
|
||||
});
|
||||
|
||||
export const ChatUpdatedFrame = z.object({
|
||||
type: z.literal('chat_updated'),
|
||||
chat_id: Uuid,
|
||||
session_id: Uuid,
|
||||
name: z.string().nullable(),
|
||||
updated_at: IsoTimestamp,
|
||||
});
|
||||
|
||||
export const ChatArchivedFrame = z.object({
|
||||
type: z.literal('chat_archived'),
|
||||
chat_id: Uuid,
|
||||
session_id: Uuid,
|
||||
});
|
||||
|
||||
export const ChatUnarchivedFrame = z.object({
|
||||
type: z.literal('chat_unarchived'),
|
||||
chat: OpaqueObject,
|
||||
});
|
||||
|
||||
export const ChatDeletedFrame = z.object({
|
||||
type: z.literal('chat_deleted'),
|
||||
chat_id: Uuid,
|
||||
session_id: Uuid,
|
||||
});
|
||||
|
||||
export const ProjectCreatedFrame = z.object({
|
||||
type: z.literal('project_created'),
|
||||
project: OpaqueObject,
|
||||
});
|
||||
|
||||
export const ProjectArchivedFrame = z.object({
|
||||
type: z.literal('project_archived'),
|
||||
project_id: Uuid,
|
||||
});
|
||||
|
||||
export const ProjectUnarchivedFrame = z.object({
|
||||
type: z.literal('project_unarchived'),
|
||||
project: OpaqueObject,
|
||||
});
|
||||
|
||||
export const ProjectUpdatedFrame = z.object({
|
||||
type: z.literal('project_updated'),
|
||||
project_id: Uuid,
|
||||
name: z.string(),
|
||||
});
|
||||
|
||||
export const ProjectDeletedFrame = z.object({
|
||||
type: z.literal('project_deleted'),
|
||||
project_id: Uuid,
|
||||
});
|
||||
|
||||
const PermissionOptionShape = z.object({
|
||||
option_id: z.string(),
|
||||
label: z.string(),
|
||||
});
|
||||
|
||||
export const PermissionRequestedFrame = z.object({
|
||||
type: z.literal('permission_requested'),
|
||||
task_id: Uuid,
|
||||
session_id: Uuid,
|
||||
kind: z.enum(['tool', 'question', 'plan', 'elicitation']).optional(),
|
||||
tool_title: z.string().optional(),
|
||||
input: z.record(z.unknown()).optional(),
|
||||
options: z.array(PermissionOptionShape),
|
||||
});
|
||||
|
||||
export const PermissionResolvedFrame = z.object({
|
||||
type: z.literal('permission_resolved'),
|
||||
task_id: Uuid,
|
||||
session_id: Uuid,
|
||||
});
|
||||
|
||||
const AgentCommandShape = z.object({
|
||||
name: z.string(),
|
||||
description: z.string().optional(),
|
||||
});
|
||||
|
||||
export const AgentCommandsFrame = z.object({
|
||||
type: z.literal('agent_commands'),
|
||||
task_id: Uuid,
|
||||
session_id: Uuid,
|
||||
commands: z.array(AgentCommandShape),
|
||||
});
|
||||
|
||||
// agent-status-normalize (#10): published by BooCoder on the per-session channel
|
||||
// when an external agent's normalized status changes (turn start/end, permission
|
||||
// block/unblock). Keyed per (chat_id, agent); the frontend tracks the latest per
|
||||
// pair and resets on chat switch. `reason` is a free-form discriminator
|
||||
// (turn_start / turn_complete / failed / crashed / permission_request /
|
||||
// permission_resolved).
|
||||
export const AgentStatusUpdatedFrame = z.object({
|
||||
type: z.literal('agent_status_updated'),
|
||||
chat_id: Uuid,
|
||||
agent: z.string().min(1),
|
||||
status: AgentStatusValue,
|
||||
reason: z.string().optional(),
|
||||
at: IsoTimestamp,
|
||||
});
|
||||
|
||||
// ---- discriminated union ---------------------------------------------------
|
||||
|
||||
export const WsFrameSchema = z.discriminatedUnion('type', [
|
||||
// per-session
|
||||
SnapshotFrame,
|
||||
MessageStartedFrame,
|
||||
DeltaFrame,
|
||||
ReasoningDeltaFrame,
|
||||
ToolCallFrame,
|
||||
ToolResultFrame,
|
||||
MessageCompleteFrame,
|
||||
UsageFrame,
|
||||
MessagesDeletedFrame,
|
||||
ChatRenamedFrame,
|
||||
CompactedFrame,
|
||||
ErrorFrame,
|
||||
PermissionRequestedFrame,
|
||||
PermissionResolvedFrame,
|
||||
AgentCommandsFrame,
|
||||
AgentStatusUpdatedFrame,
|
||||
// per-user
|
||||
ChatStatusFrame,
|
||||
SessionUpdatedFrame,
|
||||
SessionRenamedFrame,
|
||||
SessionCreatedFrame,
|
||||
SessionArchivedFrame,
|
||||
SessionDeletedFrame,
|
||||
SessionWorkspaceUpdatedFrame,
|
||||
ChatCreatedFrame,
|
||||
ChatUpdatedFrame,
|
||||
ChatArchivedFrame,
|
||||
ChatUnarchivedFrame,
|
||||
ChatDeletedFrame,
|
||||
ProjectCreatedFrame,
|
||||
ProjectArchivedFrame,
|
||||
ProjectUnarchivedFrame,
|
||||
ProjectUpdatedFrame,
|
||||
ProjectDeletedFrame,
|
||||
]);
|
||||
|
||||
export type WsFrame = z.infer<typeof WsFrameSchema>;
|
||||
|
||||
// Convenience: the set of known frame types. Useful for the publishFrame
|
||||
// helper to log the offending type name when validation fails. Kept in sync
|
||||
// by hand with the discriminated union above.
|
||||
export const KNOWN_FRAME_TYPES: readonly WsFrame['type'][] = [
|
||||
'snapshot',
|
||||
'message_started',
|
||||
'delta',
|
||||
'reasoning_delta',
|
||||
'tool_call',
|
||||
'tool_result',
|
||||
'message_complete',
|
||||
'usage',
|
||||
'messages_deleted',
|
||||
'chat_renamed',
|
||||
'compacted',
|
||||
'error',
|
||||
'permission_requested',
|
||||
'permission_resolved',
|
||||
'agent_commands',
|
||||
'agent_status_updated',
|
||||
'chat_status',
|
||||
'session_updated',
|
||||
'session_renamed',
|
||||
'session_created',
|
||||
'session_archived',
|
||||
'session_deleted',
|
||||
'session_workspace_updated',
|
||||
'chat_created',
|
||||
'chat_updated',
|
||||
'chat_archived',
|
||||
'chat_unarchived',
|
||||
'chat_deleted',
|
||||
'project_created',
|
||||
'project_archived',
|
||||
'project_unarchived',
|
||||
'project_updated',
|
||||
'project_deleted',
|
||||
] as const;
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import type { Message, WsFrame } from '@/api/types';
|
||||
import { WsFrameSchema } from '@/api/ws-frames';
|
||||
import { WsFrameSchema } from '@boocode/contracts/ws-frames';
|
||||
import { api } from '@/api/client';
|
||||
import { sessionEvents } from './sessionEvents';
|
||||
import { recordUsage } from './useChatThroughput';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect } from 'react';
|
||||
import { WsFrameSchema } from '@/api/ws-frames';
|
||||
import { WsFrameSchema } from '@boocode/contracts/ws-frames';
|
||||
import { sessionEvents } from './sessionEvents';
|
||||
import { createWsReconnectToast } from './wsReconnectToast';
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
import './styles/globals.css';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
|
||||
Reference in New Issue
Block a user