feat: component wiring integration — orphan cleanup, Memory page, WS handlers
Memory page: Added REST endpoints (routes/memory.ts, 3 GETs: list/daily/dreams), React route in App.tsx, nav link in ProjectSidebar (Brain icon). Orphan components wired: KeyboardShortcutsDialog (? key in AppShell), McpResponseDisplay (MCP tool results in ToolCallLine), CacheShapeBadge (StatsLine in MessageBubble). MessageBoundary + MessageListErrorBoundary confirmed already wired in MarkdownRenderer/MessageList. Dead code cleanup: useDraftPersistence integrated into ChatInput (localStorage draft save/restore/clear on send). message-parts barrel made canonical — MessageBubble imports from it; StatsLine updated with CacheShapeBadge parity. api.settings.inference typed wrapper added; InferenceSettings raw fetch replaced. WS frame handlers: reasoning_delta (accumulates like delta), tool_trace_start, tool_trace_finish, collision_warning, agent_message acknowledged in useSessionStream. CollisionWarningEvent + AgentMessageEvent added to sessionEvents union. Forwarding in useCoderUserEvents. reasoning_delta + collision_warning added to web WsFrame type. useSidebar default case fixes pre-existing fallthrough error. Workflow engine: services/workflow/index.ts documented as experimental; coder flow-runner (apps/coder/src/services/flow-runner.ts) is canonical. Verification: web type-check clean, server build clean, 627 tests pass.
This commit is contained in:
@@ -279,6 +279,23 @@ export interface BattleUpdatedEvent {
|
||||
cross_exam_id?: string;
|
||||
}
|
||||
|
||||
// Collision warning: published when the BooCoder detects multiple agents
|
||||
// editing the same file concurrently. Advisory only — writes are not blocked.
|
||||
export interface CollisionWarningEvent {
|
||||
type: 'collision_warning';
|
||||
file_path: string;
|
||||
agents: string[];
|
||||
}
|
||||
|
||||
// Inter-agent message: one agent step sends a live message to another step
|
||||
// in the same flow run.
|
||||
export interface AgentMessageEvent {
|
||||
type: 'agent_message';
|
||||
from_agent: string;
|
||||
to_agent: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
// Re-export arena API shapes for consumers that need the full battle data.
|
||||
export type { BattleShape, ContestantShape, CrossExaminationShape };
|
||||
|
||||
@@ -318,7 +335,9 @@ export type SessionEvent =
|
||||
| OpenArenaPaneEvent
|
||||
| BattleStartedEvent
|
||||
| ContestantUpdatedEvent
|
||||
| BattleUpdatedEvent;
|
||||
| BattleUpdatedEvent
|
||||
| CollisionWarningEvent
|
||||
| AgentMessageEvent;
|
||||
type Listener = (event: SessionEvent) => void;
|
||||
|
||||
const listeners = new Set<Listener>();
|
||||
|
||||
@@ -9,8 +9,10 @@ import { useEffect } from 'react';
|
||||
import { WsFrameSchema } from '@boocode/contracts/ws-frames';
|
||||
import { sessionEvents } from './sessionEvents';
|
||||
import type {
|
||||
AgentMessageEvent,
|
||||
BattleStartedEvent,
|
||||
BattleUpdatedEvent,
|
||||
CollisionWarningEvent,
|
||||
ContestantUpdatedEvent,
|
||||
FlowRunStartedEvent,
|
||||
FlowRunStepUpdatedEvent,
|
||||
@@ -61,6 +63,19 @@ export function useCoderUserEvents(): void {
|
||||
sessionEvents.emit(frame as unknown as ContestantUpdatedEvent);
|
||||
} else if (frame.type === 'battle_updated') {
|
||||
sessionEvents.emit(frame as unknown as BattleUpdatedEvent);
|
||||
} else if (frame.type === 'agent_message') {
|
||||
sessionEvents.emit({
|
||||
type: 'agent_message',
|
||||
from_agent: frame.sender_step_id,
|
||||
to_agent: frame.channel ?? '',
|
||||
content: frame.content,
|
||||
} as AgentMessageEvent);
|
||||
} else if (frame.type === 'collision_warning') {
|
||||
sessionEvents.emit({
|
||||
type: 'collision_warning',
|
||||
file_path: frame.file_path,
|
||||
agents: frame.agents,
|
||||
} as CollisionWarningEvent);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -324,6 +324,23 @@ function applyFrame(state: State, frame: WsFrame): State {
|
||||
case 'channel_delta': {
|
||||
return state;
|
||||
}
|
||||
case 'reasoning_delta': {
|
||||
const next = state.messages.map((m) => {
|
||||
if (m.id !== frame.message_id) return m;
|
||||
const chunk = frame.content ?? '';
|
||||
return { ...m, reasoning_text: (m.reasoning_text ?? '') + chunk };
|
||||
});
|
||||
return { ...state, messages: next };
|
||||
}
|
||||
case 'tool_trace_start':
|
||||
case 'tool_trace_finish':
|
||||
case 'collision_warning':
|
||||
case 'agent_message': {
|
||||
if (typeof console !== 'undefined') {
|
||||
console.debug(`ws-frame (acknowledged): ${frame.type}`, frame);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -202,6 +202,10 @@ function applyEvent(prev: SidebarResponse, event: import('./sessionEvents').Sess
|
||||
case 'battle_updated':
|
||||
// Consumed by useWorkspacePanes / ArenaPane / ArenaLauncherDialog; sidebar has no stake.
|
||||
return prev;
|
||||
case 'collision_warning':
|
||||
case 'agent_message':
|
||||
// Published by BooCoder on the coder user channel; sidebar has no stake.
|
||||
return prev;
|
||||
case 'project_archived': {
|
||||
const next = prev.projects.filter((p) => p.id !== event.project_id);
|
||||
if (next.length === prev.projects.length) return prev;
|
||||
@@ -229,6 +233,8 @@ function applyEvent(prev: SidebarResponse, event: import('./sessionEvents').Sess
|
||||
});
|
||||
return changed ? { ...prev, projects } : prev;
|
||||
}
|
||||
default:
|
||||
return prev;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user