diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/FileViewContainer.tsx b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/FileViewContainer.tsx
index 9107fd07..ec965e77 100644
--- a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/FileViewContainer.tsx
+++ b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/FileViewContainer.tsx
@@ -1,17 +1,5 @@
import { useRef } from 'react';
-import {
- Loader2,
- Folder,
- ClipboardPaste,
- FolderPlus,
- FolderUp,
- LayoutGrid,
- Upload,
- ClipboardCopy,
- MessageSquare,
- Download,
- Mic,
-} from 'lucide-react';
+import { Loader2, Folder, ClipboardPaste, FolderPlus, FolderUp, LayoutGrid, Upload, ClipboardCopy, MessageSquare, Download, Mic } from 'lucide-react';
import { getIcon } from 'material-file-icons';
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from '@/components/ui/context-menu';
import type { UseFileBrowserAppType } from '../useFileBrowserApp';
@@ -104,7 +92,9 @@ export const FileViewContainer = ({ fileBrowserManager }: FileViewContainerProps
})}
) : searchResults ? (
-
No results found
+
+ No results found
+
) : null}
) : (
diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/Toolbar/SelectionActions.tsx b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/Toolbar/SelectionActions.tsx
index b6308148..ac6415c9 100644
--- a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/Toolbar/SelectionActions.tsx
+++ b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/Toolbar/SelectionActions.tsx
@@ -6,16 +6,8 @@ type SelectionActionsProps = {
};
export const SelectionActions = ({ fileBrowserManager }: SelectionActionsProps) => {
- const {
- selected,
- clipboard,
- handleCut,
- handleCopy,
- handlePaste,
- handleDownloadSelected,
- handleDeleteSelected,
- setSelected,
- } = fileBrowserManager;
+ const { selected, clipboard, handleCut, handleCopy, handlePaste, handleDownloadSelected, handleDeleteSelected, setSelected } =
+ fileBrowserManager;
return (
<>
diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/usePipelineRunner.ts b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/usePipelineRunner.ts
index f2552d17..63e4244d 100644
--- a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/usePipelineRunner.ts
+++ b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/usePipelineRunner.ts
@@ -33,61 +33,19 @@ type ParallelStep = {
type ServerMessage =
| { jobId: string; type: 'pipeline:init'; steps: StepDef[] }
- | {
- jobId: string;
- type: 'step:start';
- stepIndex: number;
- taskName: string;
- iteration?: { current: number; total: number; label: string };
- }
- | {
- jobId: string;
- type: 'step:complete';
- stepIndex: number;
- cost?: { inputTokens: number; outputTokens: number; totalUSD: number };
- }
+ | { jobId: string; type: 'step:start'; stepIndex: number; taskName: string; iteration?: { current: number; total: number; label: string } }
+ | { jobId: string; type: 'step:complete'; stepIndex: number; cost?: { inputTokens: number; outputTokens: number; totalUSD: number } }
| { jobId: string; type: 'step:skip'; stepIndex: number; label: string; reason: string }
- | {
- jobId: string;
- type: 'step:parallel';
- stepIndex: number;
- taskName: string;
- iterations: string[];
- concurrency: number;
- }
+ | { jobId: string; type: 'step:parallel'; stepIndex: number; taskName: string; iterations: string[]; concurrency: number }
| { jobId: string; type: 'step:waiting'; stepIndex: number; iterationLabel?: string; elapsed: number }
| { jobId: string; type: 'iteration:start'; stepIndex: number; label: string }
- | {
- jobId: string;
- type: 'iteration:complete';
- stepIndex: number;
- label: string;
- cost?: { inputTokens: number; outputTokens: number; totalUSD: number };
- }
+ | { jobId: string; type: 'iteration:complete'; stepIndex: number; label: string; cost?: { inputTokens: number; outputTokens: number; totalUSD: number } }
| { jobId: string; type: 'iteration:error'; stepIndex: number; label: string; error: string }
| { jobId: string; type: 'assistant:delta'; text: string; iterationLabel?: string }
| { jobId: string; type: 'assistant:text'; text: string; iterationLabel?: string }
- | {
- jobId: string;
- type: 'tool:start';
- toolCallId: string;
- toolName: string;
- toolInput: Record;
- iterationLabel?: string;
- }
- | {
- jobId: string;
- type: 'tool:result';
- toolCallId: string;
- output: string;
- isError: boolean;
- iterationLabel?: string;
- }
- | {
- jobId: string;
- type: 'pipeline:complete';
- totalCost: { inputTokens: number; outputTokens: number; totalUSD: number };
- }
+ | { jobId: string; type: 'tool:start'; toolCallId: string; toolName: string; toolInput: Record; iterationLabel?: string }
+ | { jobId: string; type: 'tool:result'; toolCallId: string; output: string; isError: boolean; iterationLabel?: string }
+ | { jobId: string; type: 'pipeline:complete'; totalCost: { inputTokens: number; outputTokens: number; totalUSD: number } }
| { jobId: string; type: 'error'; message: string }
| { jobId: string; type: 'stopped' }
| { type: 'job:created'; jobId: string }
@@ -103,18 +61,12 @@ export function usePipelineRunner() {
const [parallelStep, setParallelStep] = useState(null);
const [messages, setMessages] = useState([]);
const [streamingText, setStreamingText] = useState('');
- const [totalCost, setTotalCost] = useState<{ inputTokens: number; outputTokens: number; totalUSD: number } | null>(
- null,
- );
+ const [totalCost, setTotalCost] = useState<{ inputTokens: number; outputTokens: number; totalUSD: number } | null>(null);
const [runningCost, setRunningCost] = useState({ inputTokens: 0, outputTokens: 0, totalUSD: 0 });
const [hasError, setHasError] = useState(false);
const [skippedItems, setSkippedItems] = useState>([]);
const [elapsed, setElapsed] = useState(0);
- const [waitingStatus, setWaitingStatus] = useState<{
- stepIndex: number;
- elapsed: number;
- iterationLabel?: string;
- } | null>(null);
+ const [waitingStatus, setWaitingStatus] = useState<{ stepIndex: number; elapsed: number; iterationLabel?: string } | null>(null);
const wsRef = useRef(null);
const streamBufferRef = useRef('');
const startTimeRef = useRef(0);
@@ -132,10 +84,7 @@ export function usePipelineRunner() {
}, []);
const stopTimer = useCallback(() => {
- if (timerRef.current) {
- clearInterval(timerRef.current);
- timerRef.current = null;
- }
+ if (timerRef.current) { clearInterval(timerRef.current); timerRef.current = null; }
}, []);
const addCost = useCallback((cost: { inputTokens: number; outputTokens: number; totalUSD: number }) => {
@@ -146,184 +95,178 @@ export function usePipelineRunner() {
}));
}, []);
- const handleEvent = useCallback(
- (msg: ServerMessage) => {
- // Filter events by jobId (ignore events from other jobs)
- if ('jobId' in msg && msg.jobId && jobIdRef.current && msg.jobId !== jobIdRef.current) return;
+ const handleEvent = useCallback((msg: ServerMessage) => {
+ // Filter events by jobId (ignore events from other jobs)
+ if ('jobId' in msg && msg.jobId && jobIdRef.current && msg.jobId !== jobIdRef.current) return;
- switch (msg.type) {
- case 'job:created':
- jobIdRef.current = msg.jobId;
- setJobId(msg.jobId);
- break;
+ switch (msg.type) {
+ case 'job:created':
+ jobIdRef.current = msg.jobId;
+ setJobId(msg.jobId);
+ break;
- case 'job:state':
- // Reconnection to a completed/failed job
- if (
- msg.status === 'completed' ||
- msg.status === 'failed' ||
- msg.status === 'stopped' ||
- msg.status === 'interrupted'
- ) {
- setPhase('done');
- if (msg.cost) setTotalCost(msg.cost as { inputTokens: number; outputTokens: number; totalUSD: number });
- if (msg.status === 'failed' || msg.status === 'interrupted') setHasError(true);
- stopTimer();
- }
- break;
-
- case 'pipeline:init':
- setSteps(msg.steps);
- break;
-
- case 'step:start':
- flushStream();
- setMessages([]);
- setParallelStep(null);
- setWaitingStatus(null);
- inParallelRef.current = false;
- setCurrentStep({
- taskName: msg.taskName,
- iteration: msg.iteration,
- status: 'running',
- });
- break;
-
- case 'step:complete':
- flushStream();
- setWaitingStatus(null);
- setCurrentStep((prev) => (prev ? { ...prev, status: 'complete', cost: msg.cost } : null));
- if (msg.cost) addCost(msg.cost);
- break;
-
- case 'step:skip':
- setSkippedItems((prev) => [...prev, { label: msg.label, reason: msg.reason }]);
- break;
-
- case 'step:waiting':
- setWaitingStatus({ stepIndex: msg.stepIndex, elapsed: msg.elapsed, iterationLabel: msg.iterationLabel });
- break;
-
- case 'step:parallel':
- flushStream();
- setMessages([]);
- setCurrentStep(null);
- inParallelRef.current = true;
- setParallelStep({
- stepIndex: msg.stepIndex,
- taskName: msg.taskName,
- concurrency: msg.concurrency,
- iterations: msg.iterations.map((label) => ({ label, status: 'pending' })),
- });
- break;
-
- case 'iteration:start':
- setParallelStep((prev) => {
- if (!prev) return prev;
- return {
- ...prev,
- iterations: prev.iterations.map((it) => (it.label === msg.label ? { ...it, status: 'running' } : it)),
- };
- });
- break;
-
- case 'iteration:complete':
- setParallelStep((prev) => {
- if (!prev) return prev;
- return {
- ...prev,
- iterations: prev.iterations.map((it) =>
- it.label === msg.label ? { ...it, status: 'complete', cost: msg.cost } : it,
- ),
- };
- });
- if (msg.cost) addCost(msg.cost);
- break;
-
- case 'iteration:error':
- setParallelStep((prev) => {
- if (!prev) return prev;
- return {
- ...prev,
- iterations: prev.iterations.map((it) =>
- it.label === msg.label ? { ...it, status: 'error', error: msg.error } : it,
- ),
- };
- });
- break;
-
- case 'assistant:delta':
- // Skip messages from parallel sub-agents (shown in iteration grid instead)
- if (inParallelRef.current && msg.iterationLabel) break;
- setWaitingStatus(null);
- streamBufferRef.current += msg.text;
- setStreamingText(streamBufferRef.current);
- break;
-
- case 'assistant:text': {
- if (inParallelRef.current && msg.iterationLabel) break;
- const text = msg.text || streamBufferRef.current;
- if (text) {
- setMessages((prev) => [...prev, { role: 'assistant', id: randomId(), text }]);
- }
- streamBufferRef.current = '';
- setStreamingText('');
- break;
+ case 'job:state':
+ // Reconnection to a completed/failed job
+ if (msg.status === 'completed' || msg.status === 'failed' || msg.status === 'stopped' || msg.status === 'interrupted') {
+ setPhase('done');
+ if (msg.cost) setTotalCost(msg.cost as { inputTokens: number; outputTokens: number; totalUSD: number });
+ if (msg.status === 'failed' || msg.status === 'interrupted') setHasError(true);
+ stopTimer();
}
+ break;
- case 'tool:start':
- if (inParallelRef.current && msg.iterationLabel) break;
- setWaitingStatus(null);
- flushStream();
- setMessages((prev) => [
+ case 'pipeline:init':
+ setSteps(msg.steps);
+ break;
+
+ case 'step:start':
+ flushStream();
+ setMessages([]);
+ setParallelStep(null);
+ setWaitingStatus(null);
+ inParallelRef.current = false;
+ setCurrentStep({
+ taskName: msg.taskName,
+ iteration: msg.iteration,
+ status: 'running',
+ });
+ break;
+
+ case 'step:complete':
+ flushStream();
+ setWaitingStatus(null);
+ setCurrentStep((prev) => prev ? { ...prev, status: 'complete', cost: msg.cost } : null);
+ if (msg.cost) addCost(msg.cost);
+ break;
+
+ case 'step:skip':
+ setSkippedItems((prev) => [...prev, { label: msg.label, reason: msg.reason }]);
+ break;
+
+ case 'step:waiting':
+ setWaitingStatus({ stepIndex: msg.stepIndex, elapsed: msg.elapsed, iterationLabel: msg.iterationLabel });
+ break;
+
+ case 'step:parallel':
+ flushStream();
+ setMessages([]);
+ setCurrentStep(null);
+ inParallelRef.current = true;
+ setParallelStep({
+ stepIndex: msg.stepIndex,
+ taskName: msg.taskName,
+ concurrency: msg.concurrency,
+ iterations: msg.iterations.map((label) => ({ label, status: 'pending' })),
+ });
+ break;
+
+ case 'iteration:start':
+ setParallelStep((prev) => {
+ if (!prev) return prev;
+ return {
...prev,
- {
- role: 'tool' as const,
- id: randomId(),
- toolCallId: msg.toolCallId,
- toolName: msg.toolName,
- toolInput: msg.toolInput,
- output: undefined,
- isError: false,
- },
- ]);
- break;
-
- case 'tool:result':
- if (inParallelRef.current && msg.iterationLabel) break;
- setMessages((prev) =>
- prev.map((m) =>
- m.role === 'tool' && 'toolCallId' in m && m.toolCallId === msg.toolCallId
- ? { ...m, output: msg.output, isError: msg.isError }
- : m,
+ iterations: prev.iterations.map((it) =>
+ it.label === msg.label ? { ...it, status: 'running' } : it,
),
- );
- break;
+ };
+ });
+ break;
- case 'pipeline:complete':
- flushStream();
- setTotalCost(msg.totalCost);
- setPhase('done');
- stopTimer();
- break;
+ case 'iteration:complete':
+ setParallelStep((prev) => {
+ if (!prev) return prev;
+ return {
+ ...prev,
+ iterations: prev.iterations.map((it) =>
+ it.label === msg.label ? { ...it, status: 'complete', cost: msg.cost } : it,
+ ),
+ };
+ });
+ if (msg.cost) addCost(msg.cost);
+ break;
- case 'error':
- flushStream();
- setMessages((prev) => [...prev, { role: 'error' as const, id: randomId(), text: msg.message }]);
- setHasError(true);
- setPhase('done');
- stopTimer();
- break;
+ case 'iteration:error':
+ setParallelStep((prev) => {
+ if (!prev) return prev;
+ return {
+ ...prev,
+ iterations: prev.iterations.map((it) =>
+ it.label === msg.label ? { ...it, status: 'error', error: msg.error } : it,
+ ),
+ };
+ });
+ break;
- case 'stopped':
- flushStream();
- setPhase('done');
- stopTimer();
- break;
+ case 'assistant:delta':
+ // Skip messages from parallel sub-agents (shown in iteration grid instead)
+ if (inParallelRef.current && msg.iterationLabel) break;
+ setWaitingStatus(null);
+ streamBufferRef.current += msg.text;
+ setStreamingText(streamBufferRef.current);
+ break;
+
+ case 'assistant:text': {
+ if (inParallelRef.current && msg.iterationLabel) break;
+ const text = msg.text || streamBufferRef.current;
+ if (text) {
+ setMessages((prev) => [...prev, { role: 'assistant', id: randomId(), text }]);
+ }
+ streamBufferRef.current = '';
+ setStreamingText('');
+ break;
}
- },
- [flushStream, stopTimer, addCost],
- );
+
+ case 'tool:start':
+ if (inParallelRef.current && msg.iterationLabel) break;
+ setWaitingStatus(null);
+ flushStream();
+ setMessages((prev) => [
+ ...prev,
+ {
+ role: 'tool' as const,
+ id: randomId(),
+ toolCallId: msg.toolCallId,
+ toolName: msg.toolName,
+ toolInput: msg.toolInput,
+ output: undefined,
+ isError: false,
+ },
+ ]);
+ break;
+
+ case 'tool:result':
+ if (inParallelRef.current && msg.iterationLabel) break;
+ setMessages((prev) =>
+ prev.map((m) =>
+ m.role === 'tool' && 'toolCallId' in m && m.toolCallId === msg.toolCallId
+ ? { ...m, output: msg.output, isError: msg.isError }
+ : m,
+ ),
+ );
+ break;
+
+ case 'pipeline:complete':
+ flushStream();
+ setTotalCost(msg.totalCost);
+ setPhase('done');
+ stopTimer();
+ break;
+
+ case 'error':
+ flushStream();
+ setMessages((prev) => [...prev, { role: 'error' as const, id: randomId(), text: msg.message }]);
+ setHasError(true);
+ setPhase('done');
+ stopTimer();
+ break;
+
+ case 'stopped':
+ flushStream();
+ setPhase('done');
+ stopTimer();
+ break;
+ }
+ }, [flushStream, stopTimer, addCost]);
// The socket below is opened once and must stay open, so its listener is registered once too — and would
// hold the first render's `handleEvent` forever. That closure carries `flushStream`'s captured
@@ -365,35 +308,32 @@ export function usePipelineRunner() {
};
}, []);
- const run = useCallback(
- (taskDirName: string, inputs: Record, cwd?: string, model?: string, startAt?: number) => {
- if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) return;
+ const run = useCallback((taskDirName: string, inputs: Record, cwd?: string, model?: string, startAt?: number) => {
+ if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) return;
- setPhase('running');
- setMessages([]);
- setStreamingText('');
- setTotalCost(null);
- setRunningCost({ inputTokens: 0, outputTokens: 0, totalUSD: 0 });
- setHasError(false);
- setSkippedItems([]);
- setCurrentStep(null);
- setParallelStep(null);
- setWaitingStatus(null);
- setElapsed(0);
- setJobId(null);
- jobIdRef.current = null;
- streamBufferRef.current = '';
+ setPhase('running');
+ setMessages([]);
+ setStreamingText('');
+ setTotalCost(null);
+ setRunningCost({ inputTokens: 0, outputTokens: 0, totalUSD: 0 });
+ setHasError(false);
+ setSkippedItems([]);
+ setCurrentStep(null);
+ setParallelStep(null);
+ setWaitingStatus(null);
+ setElapsed(0);
+ setJobId(null);
+ jobIdRef.current = null;
+ streamBufferRef.current = '';
- startTimeRef.current = Date.now();
- if (timerRef.current) clearInterval(timerRef.current);
- timerRef.current = setInterval(() => {
- setElapsed(Math.floor((Date.now() - startTimeRef.current) / 1000));
- }, 1000);
+ startTimeRef.current = Date.now();
+ if (timerRef.current) clearInterval(timerRef.current);
+ timerRef.current = setInterval(() => {
+ setElapsed(Math.floor((Date.now() - startTimeRef.current) / 1000));
+ }, 1000);
- wsRef.current.send(JSON.stringify({ type: 'run', taskDirName, inputs, cwd, model, startAt }));
- },
- [],
- );
+ wsRef.current.send(JSON.stringify({ type: 'run', taskDirName, inputs, cwd, model, startAt }));
+ }, []);
const stop = useCallback(() => {
if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN || !jobIdRef.current) return;
@@ -401,21 +341,7 @@ export function usePipelineRunner() {
}, []);
return {
- phase,
- isConnected,
- jobId,
- steps,
- currentStep,
- parallelStep,
- messages,
- streamingText,
- totalCost,
- runningCost,
- hasError,
- skippedItems,
- elapsed,
- waitingStatus,
- run,
- stop,
+ phase, isConnected, jobId, steps, currentStep, parallelStep, messages, streamingText,
+ totalCost, runningCost, hasError, skippedItems, elapsed, waitingStatus, run, stop,
};
}
diff --git a/src/workspaces/officerdev/src/hooks/useFileViewerPanels/Providers.tsx b/src/workspaces/officerdev/src/hooks/useFileViewerPanels/Providers.tsx
index dc097f1d..3b652093 100644
--- a/src/workspaces/officerdev/src/hooks/useFileViewerPanels/Providers.tsx
+++ b/src/workspaces/officerdev/src/hooks/useFileViewerPanels/Providers.tsx
@@ -7,8 +7,7 @@ import { EmbeddableChat } from '../../apps/Chat/EmbeddableChat';
type SetSearchParams = ReturnType[1];
-const onReplaceView =
- (setSearchParams: SetSearchParams) =>
+const onReplaceView = (setSearchParams: SetSearchParams) =>
(viewPath: string, viewRoot: string, ephemeralPath: string, ephemeralRoot: string) => {
setSearchParams((prev) => {
const next = new URLSearchParams(prev);
@@ -37,12 +36,7 @@ export function ViewerProvider({ children }: { children: ReactNode }) {
};
return (
-
+
{children}
);
@@ -65,13 +59,7 @@ export function EphemeralProvider({ children }: { children: ReactNode }) {
};
return (
-
+
{children}
);
@@ -85,13 +73,7 @@ export function Ephemeral2Provider({ children }: { children: ReactNode }) {
const fileName = ephemeral2Path.split('/').pop() ?? '';
return (
-
+
{children}
);
@@ -103,14 +85,15 @@ export const ChatEphemeralBody = () => {
const chatContext = searchParams.get('chatContext') ?? '';
const chatType = searchParams.get('chatType') as 'file' | 'folder' | null;
- const cwdPath = chatType === 'file' ? chatContext.substring(0, chatContext.lastIndexOf('/')) || '/' : chatContext;
+ const cwdPath = chatType === 'file'
+ ? chatContext.substring(0, chatContext.lastIndexOf('/')) || '/'
+ : chatContext;
const tag = chatType === 'file' ? 'file' : 'folder';
const path = chatContext.replace(/^\//, '');
- const message =
- chatType === 'file'
- ? `[${tag}: ${path}] Let's talk about this file`
- : `[${tag}: ${path || '/'}] consider, for this session, this directory as your current working directory`;
+ const message = chatType === 'file'
+ ? `[${tag}: ${path}] Let's talk about this file`
+ : `[${tag}: ${path || '/'}] consider, for this session, this directory as your current working directory`;
const handleMessageComplete = useCallback(() => {
bumpFilesRefresh();