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:
2026-06-08 04:30:09 +00:00
parent 3724016b24
commit fc281f5b78
17 changed files with 268 additions and 710 deletions

View File

@@ -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>();

View File

@@ -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);
}
};

View File

@@ -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;
}

View File

@@ -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;
}
}