Make chat work directory per-run instead of global

The work directory was stored in a single shared config file, so setting
it in one chat changed it for every chat and new chats inherited a stale
value. Store it as run-scoped metadata: captured on the start event at
creation and updatable mid-chat via an appended workdir-changed event.
The runtime reads the per-run value from AgentState, and the chat input
reads/writes it against the run (pending chats pass it into runs:create).
This commit is contained in:
Gagancreates 2026-05-26 18:23:32 +00:00
parent b9c4099c3b
commit ce1913d4e0
No known key found for this signature in database
9 changed files with 113 additions and 40 deletions

View file

@ -524,6 +524,10 @@ export function setupIpcHandlers() {
'runs:fetch': async (_event, args) => { 'runs:fetch': async (_event, args) => {
return runsCore.fetchRun(args.runId); return runsCore.fetchRun(args.runId);
}, },
'runs:setWorkdir': async (_event, args) => {
await runsCore.setWorkdir(args.runId, args.workingDirectory);
return { success: true as const };
},
'runs:list': async (_event, args) => { 'runs:list': async (_event, args) => {
return runsCore.listRuns(args.cursor); return runsCore.listRuns(args.cursor);
}, },

View file

@ -970,6 +970,7 @@ function App() {
const newChatTabId = () => `chat-tab-${++chatTabIdCounterRef.current}` const newChatTabId = () => `chat-tab-${++chatTabIdCounterRef.current}`
const chatDraftsRef = useRef(new Map<string, string>()) const chatDraftsRef = useRef(new Map<string, string>())
const selectedModelByTabRef = useRef(new Map<string, { provider: string; model: string }>()) const selectedModelByTabRef = useRef(new Map<string, { provider: string; model: string }>())
const pendingWorkDirByTabRef = useRef(new Map<string, string>())
const chatScrollTopByTabRef = useRef(new Map<string, number>()) const chatScrollTopByTabRef = useRef(new Map<string, number>())
const [toolOpenByTab, setToolOpenByTab] = useState<Record<string, Record<string, boolean>>>({}) const [toolOpenByTab, setToolOpenByTab] = useState<Record<string, Record<string, boolean>>>({})
const [chatViewportAnchorByTab, setChatViewportAnchorByTab] = useState<Record<string, ChatViewportAnchorState>>({}) const [chatViewportAnchorByTab, setChatViewportAnchorByTab] = useState<Record<string, ChatViewportAnchorState>>({})
@ -2354,10 +2355,13 @@ function App() {
let newRunCreatedAt: string | null = null let newRunCreatedAt: string | null = null
if (!currentRunId) { if (!currentRunId) {
const selected = selectedModelByTabRef.current.get(submitTabId) const selected = selectedModelByTabRef.current.get(submitTabId)
const pendingWorkDir = pendingWorkDirByTabRef.current.get(submitTabId)
const run = await window.ipc.invoke('runs:create', { const run = await window.ipc.invoke('runs:create', {
agentId, agentId,
...(selected ? { model: selected.model, provider: selected.provider } : {}), ...(selected ? { model: selected.model, provider: selected.provider } : {}),
...(pendingWorkDir ? { workingDirectory: pendingWorkDir } : {}),
}) })
pendingWorkDirByTabRef.current.delete(submitTabId)
currentRunId = run.id currentRunId = run.id
newRunCreatedAt = run.createdAt newRunCreatedAt = run.createdAt
setRunId(currentRunId) setRunId(currentRunId)
@ -2666,6 +2670,7 @@ function App() {
}) })
chatDraftsRef.current.delete(tabId) chatDraftsRef.current.delete(tabId)
selectedModelByTabRef.current.delete(tabId) selectedModelByTabRef.current.delete(tabId)
pendingWorkDirByTabRef.current.delete(tabId)
chatScrollTopByTabRef.current.delete(tabId) chatScrollTopByTabRef.current.delete(tabId)
setToolOpenByTab((prev) => { setToolOpenByTab((prev) => {
if (!(tabId in prev)) return prev if (!(tabId in prev)) return prev
@ -5300,6 +5305,13 @@ function App() {
selectedModelByTabRef.current.delete(tab.id) selectedModelByTabRef.current.delete(tab.id)
} }
}} }}
onWorkDirChange={(dir) => {
if (dir) {
pendingWorkDirByTabRef.current.set(tab.id, dir)
} else {
pendingWorkDirByTabRef.current.delete(tab.id)
}
}}
isRecording={isActive && isRecording} isRecording={isActive && isRecording}
recordingText={isActive ? voice.interimText : undefined} recordingText={isActive ? voice.interimText : undefined}
recordingState={isActive ? (voice.state === 'connecting' ? 'connecting' : 'listening') : undefined} recordingState={isActive ? (voice.state === 'connecting' ? 'connecting' : 'listening') : undefined}
@ -5360,6 +5372,13 @@ function App() {
selectedModelByTabRef.current.delete(tabId) selectedModelByTabRef.current.delete(tabId)
} }
}} }}
onWorkDirChangeForTab={(tabId, dir) => {
if (dir) {
pendingWorkDirByTabRef.current.set(tabId, dir)
} else {
pendingWorkDirByTabRef.current.delete(tabId)
}
}}
pendingAskHumanRequests={pendingAskHumanRequests} pendingAskHumanRequests={pendingAskHumanRequests}
allPermissionRequests={allPermissionRequests} allPermissionRequests={allPermissionRequests}
permissionResponses={permissionResponses} permissionResponses={permissionResponses}

View file

@ -133,6 +133,9 @@ interface ChatInputInnerProps {
onTtsModeChange?: (mode: 'summary' | 'full') => void onTtsModeChange?: (mode: 'summary' | 'full') => void
/** Fired when the user picks a different model in the dropdown (only when no run exists yet). */ /** Fired when the user picks a different model in the dropdown (only when no run exists yet). */
onSelectedModelChange?: (model: SelectedModel | null) => void onSelectedModelChange?: (model: SelectedModel | null) => void
/** Fired when the user sets/clears the work directory before a run exists, so the
* parent can pass it into runs:create. Once a run exists, changes go straight to the run. */
onWorkDirChange?: (dir: string | null) => void
} }
function ChatInputInner({ function ChatInputInner({
@ -159,6 +162,7 @@ function ChatInputInner({
onToggleTts, onToggleTts,
onTtsModeChange, onTtsModeChange,
onSelectedModelChange, onSelectedModelChange,
onWorkDirChange,
}: ChatInputInnerProps) { }: ChatInputInnerProps) {
const controller = usePromptInputController() const controller = usePromptInputController()
const message = controller.textInput.value const message = controller.textInput.value
@ -256,21 +260,22 @@ function ChatInputInner({
return () => window.removeEventListener('models-config-changed', handler) return () => window.removeEventListener('models-config-changed', handler)
}, [loadModelConfig]) }, [loadModelConfig])
// Load currently configured work directory // The work directory is run-scoped. For an existing run, load it from the run.
const loadWorkDir = useCallback(async () => { // For a new chat (no run yet) the chosen directory lives in local state until
try { // the run is created, and is reported up via onWorkDirChange so it can be
const result = await window.ipc.invoke('workspace:readFile', { path: 'config/workdir.json' }) // passed into runs:create.
const parsed = JSON.parse(result.data)
const value = typeof parsed?.path === 'string' ? parsed.path.trim() : ''
setWorkDir(value || null)
} catch {
setWorkDir(null)
}
}, [])
useEffect(() => { useEffect(() => {
loadWorkDir() if (!runId) {
}, [isActive, loadWorkDir]) setWorkDir(null)
return
}
let cancelled = false
window.ipc.invoke('runs:fetch', { runId }).then((run) => {
if (cancelled) return
setWorkDir(run.workingDirectory ?? null)
}).catch(() => { /* legacy run or fetch failure — leave as-is */ })
return () => { cancelled = true }
}, [runId])
const handleSetWorkDir = useCallback(async () => { const handleSetWorkDir = useCallback(async () => {
try { try {
@ -279,31 +284,33 @@ function ChatInputInner({
defaultPath: workDir ?? undefined, defaultPath: workDir ?? undefined,
}) })
if (!chosen) return if (!chosen) return
await window.ipc.invoke('workspace:writeFile', { if (runId) {
path: 'config/workdir.json', await window.ipc.invoke('runs:setWorkdir', { runId, workingDirectory: chosen })
data: JSON.stringify({ path: chosen }, null, 2), } else {
}) onWorkDirChange?.(chosen)
}
setWorkDir(chosen) setWorkDir(chosen)
toast.success(`Work directory set: ${chosen}`) toast.success(`Work directory set: ${chosen}`)
} catch (err) { } catch (err) {
console.error('Failed to set work directory', err) console.error('Failed to set work directory', err)
toast.error('Failed to set work directory') toast.error('Failed to set work directory')
} }
}, [workDir]) }, [workDir, runId, onWorkDirChange])
const handleClearWorkDir = useCallback(async () => { const handleClearWorkDir = useCallback(async () => {
try { try {
await window.ipc.invoke('workspace:writeFile', { if (runId) {
path: 'config/workdir.json', await window.ipc.invoke('runs:setWorkdir', { runId, workingDirectory: null })
data: JSON.stringify({}, null, 2), } else {
}) onWorkDirChange?.(null)
}
setWorkDir(null) setWorkDir(null)
toast.success('Work directory cleared') toast.success('Work directory cleared')
} catch (err) { } catch (err) {
console.error('Failed to clear work directory', err) console.error('Failed to clear work directory', err)
toast.error('Failed to clear work directory') toast.error('Failed to clear work directory')
} }
}, []) }, [runId, onWorkDirChange])
// Check search tool availability (exa or signed-in via gateway) // Check search tool availability (exa or signed-in via gateway)
useEffect(() => { useEffect(() => {
@ -790,6 +797,7 @@ export interface ChatInputWithMentionsProps {
onToggleTts?: () => void onToggleTts?: () => void
onTtsModeChange?: (mode: 'summary' | 'full') => void onTtsModeChange?: (mode: 'summary' | 'full') => void
onSelectedModelChange?: (model: SelectedModel | null) => void onSelectedModelChange?: (model: SelectedModel | null) => void
onWorkDirChange?: (dir: string | null) => void
} }
export function ChatInputWithMentions({ export function ChatInputWithMentions({
@ -819,6 +827,7 @@ export function ChatInputWithMentions({
onToggleTts, onToggleTts,
onTtsModeChange, onTtsModeChange,
onSelectedModelChange, onSelectedModelChange,
onWorkDirChange,
}: ChatInputWithMentionsProps) { }: ChatInputWithMentionsProps) {
return ( return (
<PromptInputProvider knowledgeFiles={knowledgeFiles} recentFiles={recentFiles} visibleFiles={visibleFiles}> <PromptInputProvider knowledgeFiles={knowledgeFiles} recentFiles={recentFiles} visibleFiles={visibleFiles}>
@ -846,6 +855,7 @@ export function ChatInputWithMentions({
onToggleTts={onToggleTts} onToggleTts={onToggleTts}
onTtsModeChange={onTtsModeChange} onTtsModeChange={onTtsModeChange}
onSelectedModelChange={onSelectedModelChange} onSelectedModelChange={onSelectedModelChange}
onWorkDirChange={onWorkDirChange}
/> />
</PromptInputProvider> </PromptInputProvider>
) )

View file

@ -195,6 +195,7 @@ interface ChatSidebarProps {
getInitialDraft?: (tabId: string) => string | undefined getInitialDraft?: (tabId: string) => string | undefined
onDraftChangeForTab?: (tabId: string, text: string) => void onDraftChangeForTab?: (tabId: string, text: string) => void
onSelectedModelChangeForTab?: (tabId: string, model: SelectedModel | null) => void onSelectedModelChangeForTab?: (tabId: string, model: SelectedModel | null) => void
onWorkDirChangeForTab?: (tabId: string, dir: string | null) => void
pendingAskHumanRequests?: ChatTabViewState['pendingAskHumanRequests'] pendingAskHumanRequests?: ChatTabViewState['pendingAskHumanRequests']
allPermissionRequests?: ChatTabViewState['allPermissionRequests'] allPermissionRequests?: ChatTabViewState['allPermissionRequests']
permissionResponses?: ChatTabViewState['permissionResponses'] permissionResponses?: ChatTabViewState['permissionResponses']
@ -250,6 +251,7 @@ export function ChatSidebar({
getInitialDraft, getInitialDraft,
onDraftChangeForTab, onDraftChangeForTab,
onSelectedModelChangeForTab, onSelectedModelChangeForTab,
onWorkDirChangeForTab,
pendingAskHumanRequests = new Map(), pendingAskHumanRequests = new Map(),
allPermissionRequests = new Map(), allPermissionRequests = new Map(),
permissionResponses = new Map(), permissionResponses = new Map(),
@ -742,6 +744,7 @@ export function ChatSidebar({
initialDraft={getInitialDraft?.(tab.id)} initialDraft={getInitialDraft?.(tab.id)}
onDraftChange={onDraftChangeForTab ? (text) => onDraftChangeForTab(tab.id, text) : undefined} onDraftChange={onDraftChangeForTab ? (text) => onDraftChangeForTab(tab.id, text) : undefined}
onSelectedModelChange={onSelectedModelChangeForTab ? (m) => onSelectedModelChangeForTab(tab.id, m) : undefined} onSelectedModelChange={onSelectedModelChangeForTab ? (m) => onSelectedModelChangeForTab(tab.id, m) : undefined}
onWorkDirChange={onWorkDirChangeForTab ? (dir) => onWorkDirChangeForTab(tab.id, dir) : undefined}
isRecording={isActive && isRecording} isRecording={isActive && isRecording}
recordingText={isActive ? recordingText : undefined} recordingText={isActive ? recordingText : undefined}
recordingState={isActive ? recordingState : undefined} recordingState={isActive ? recordingState : undefined}

View file

@ -36,19 +36,6 @@ import { getRaw as getInlineTaskAgentRaw } from "../knowledge/inline_task_agent.
import { getRaw as getAgentNotesAgentRaw } from "../knowledge/agent_notes_agent.js"; import { getRaw as getAgentNotesAgentRaw } from "../knowledge/agent_notes_agent.js";
const AGENT_NOTES_DIR = path.join(WorkDir, 'knowledge', 'Agent Notes'); const AGENT_NOTES_DIR = path.join(WorkDir, 'knowledge', 'Agent Notes');
const WORKDIR_CONFIG_FILE = path.join(WorkDir, 'config', 'workdir.json');
function loadUserWorkDir(): string | null {
try {
if (!fs.existsSync(WORKDIR_CONFIG_FILE)) return null;
const raw = fs.readFileSync(WORKDIR_CONFIG_FILE, 'utf-8');
const parsed = JSON.parse(raw) as { path?: unknown };
const value = typeof parsed.path === 'string' ? parsed.path.trim() : '';
return value || null;
} catch {
return null;
}
}
function loadAgentNotesContext(): string | null { function loadAgentNotesContext(): string | null {
const sections: string[] = []; const sections: string[] = [];
@ -673,6 +660,7 @@ export class AgentState {
runProvider: string | null = null; runProvider: string | null = null;
runUseCase: UseCase | null = null; runUseCase: UseCase | null = null;
runSubUseCase: string | null = null; runSubUseCase: string | null = null;
workingDirectory: string | null = null;
messages: z.infer<typeof MessageList> = []; messages: z.infer<typeof MessageList> = [];
lastAssistantMsg: z.infer<typeof AssistantMessage> | null = null; lastAssistantMsg: z.infer<typeof AssistantMessage> | null = null;
subflowStates: Record<string, AgentState> = {}; subflowStates: Record<string, AgentState> = {};
@ -790,6 +778,10 @@ export class AgentState {
this.runProvider = event.provider; this.runProvider = event.provider;
this.runUseCase = event.useCase ?? null; this.runUseCase = event.useCase ?? null;
this.runSubUseCase = event.subUseCase ?? null; this.runSubUseCase = event.subUseCase ?? null;
this.workingDirectory = event.workingDirectory ?? null;
break;
case "workdir-changed":
this.workingDirectory = event.workingDirectory ?? null;
break; break;
case "spawn-subflow": case "spawn-subflow":
// Seed the subflow state with its agent so downstream loadAgent works. // Seed the subflow state with its agent so downstream loadAgent works.
@ -1122,7 +1114,7 @@ export async function* streamAgent({
if (agentNotesContext) { if (agentNotesContext) {
instructionsWithDateTime += `\n\n${agentNotesContext}`; instructionsWithDateTime += `\n\n${agentNotesContext}`;
} }
const userWorkDir = loadUserWorkDir(); const userWorkDir = state.workingDirectory;
if (userWorkDir) { if (userWorkDir) {
loopLogger.log('injecting user work directory', userWorkDir); loopLogger.log('injecting user work directory', userWorkDir);
instructionsWithDateTime += `\n\n# User Work Directory instructionsWithDateTime += `\n\n# User Work Directory

View file

@ -5,7 +5,7 @@ import path from "path";
import fsp from "fs/promises"; import fsp from "fs/promises";
import fs from "fs"; import fs from "fs";
import readline from "readline"; import readline from "readline";
import { Run, RunEvent, StartEvent, ListRunsResponse, MessageEvent, UseCase } from "@x/shared/dist/runs.js"; import { Run, RunEvent, StartEvent, ListRunsResponse, MessageEvent, UseCase, WorkdirChangedEvent } from "@x/shared/dist/runs.js";
import { getDefaultModelAndProvider } from "../models/defaults.js"; import { getDefaultModelAndProvider } from "../models/defaults.js";
/** /**
@ -37,6 +37,7 @@ export type CreateRunRepoOptions = {
provider: string; provider: string;
useCase: z.infer<typeof UseCase>; useCase: z.infer<typeof UseCase>;
subUseCase?: string; subUseCase?: string;
workingDirectory?: string;
}; };
function runLogPath(runId: string): string { function runLogPath(runId: string): string {
@ -206,6 +207,7 @@ export class FSRunsRepo implements IRunsRepo {
provider: options.provider, provider: options.provider,
useCase: options.useCase, useCase: options.useCase,
...(options.subUseCase ? { subUseCase: options.subUseCase } : {}), ...(options.subUseCase ? { subUseCase: options.subUseCase } : {}),
...(options.workingDirectory ? { workingDirectory: options.workingDirectory } : {}),
subflow: [], subflow: [],
ts, ts,
}; };
@ -218,6 +220,7 @@ export class FSRunsRepo implements IRunsRepo {
provider: options.provider, provider: options.provider,
useCase: options.useCase, useCase: options.useCase,
...(options.subUseCase ? { subUseCase: options.subUseCase } : {}), ...(options.subUseCase ? { subUseCase: options.subUseCase } : {}),
...(options.workingDirectory ? { workingDirectory: options.workingDirectory } : {}),
log: [start], log: [start],
}; };
} }
@ -244,6 +247,14 @@ export class FSRunsRepo implements IRunsRepo {
}; };
const events: z.infer<typeof RunEvent>[] = [start, ...rawEvents.slice(1) as z.infer<typeof RunEvent>[]]; const events: z.infer<typeof RunEvent>[] = [start, ...rawEvents.slice(1) as z.infer<typeof RunEvent>[]];
const title = this.extractTitle(events); const title = this.extractTitle(events);
// The current work directory is the start event's value, overridden by the
// most recent workdir-changed event (append-only log — last write wins).
let workingDirectory: string | undefined = start.workingDirectory || undefined;
for (const event of events) {
if (event.type === 'workdir-changed') {
workingDirectory = (event as z.infer<typeof WorkdirChangedEvent>).workingDirectory || undefined;
}
}
return { return {
id, id,
title, title,
@ -253,6 +264,7 @@ export class FSRunsRepo implements IRunsRepo {
provider: start.provider, provider: start.provider,
...(start.useCase ? { useCase: start.useCase } : {}), ...(start.useCase ? { useCase: start.useCase } : {}),
...(start.subUseCase ? { subUseCase: start.subUseCase } : {}), ...(start.subUseCase ? { subUseCase: start.subUseCase } : {}),
...(workingDirectory ? { workingDirectory } : {}),
log: events, log: events,
}; };
} }

View file

@ -1,7 +1,7 @@
import z from "zod"; import z from "zod";
import container from "../di/container.js"; import container from "../di/container.js";
import { IMessageQueue, UserMessageContentType, VoiceOutputMode, MiddlePaneContext } from "../application/lib/message-queue.js"; import { IMessageQueue, UserMessageContentType, VoiceOutputMode, MiddlePaneContext } from "../application/lib/message-queue.js";
import { AskHumanResponseEvent, ToolPermissionRequestEvent, ToolPermissionResponseEvent, CreateRunOptions, Run, ListRunsResponse, ToolPermissionAuthorizePayload, AskHumanResponsePayload } from "@x/shared/dist/runs.js"; import { AskHumanResponseEvent, ToolPermissionRequestEvent, ToolPermissionResponseEvent, CreateRunOptions, Run, ListRunsResponse, ToolPermissionAuthorizePayload, AskHumanResponsePayload, WorkdirChangedEvent } from "@x/shared/dist/runs.js";
import { IRunsRepo } from "./repo.js"; import { IRunsRepo } from "./repo.js";
import { IAgentRuntime } from "../agents/runtime.js"; import { IAgentRuntime } from "../agents/runtime.js";
import { IBus } from "../application/lib/bus.js"; import { IBus } from "../application/lib/bus.js";
@ -34,11 +34,23 @@ export async function createRun(opts: z.infer<typeof CreateRunOptions>): Promise
provider, provider,
useCase, useCase,
...(opts.subUseCase ? { subUseCase: opts.subUseCase } : {}), ...(opts.subUseCase ? { subUseCase: opts.subUseCase } : {}),
...(opts.workingDirectory ? { workingDirectory: opts.workingDirectory } : {}),
}); });
await bus.publish(run.log[0]); await bus.publish(run.log[0]);
return run; return run;
} }
export async function setWorkdir(runId: string, workingDirectory: string | null): Promise<void> {
const repo = container.resolve<IRunsRepo>('runsRepo');
const event: z.infer<typeof WorkdirChangedEvent> = {
runId,
type: "workdir-changed",
subflow: [],
...(workingDirectory ? { workingDirectory } : {}),
};
await repo.appendEvents(runId, [event]);
}
export async function createMessage(runId: string, message: UserMessageContentType, voiceInput?: boolean, voiceOutput?: VoiceOutputMode, searchEnabled?: boolean, middlePaneContext?: MiddlePaneContext): Promise<string> { export async function createMessage(runId: string, message: UserMessageContentType, voiceInput?: boolean, voiceOutput?: VoiceOutputMode, searchEnabled?: boolean, middlePaneContext?: MiddlePaneContext): Promise<string> {
const queue = container.resolve<IMessageQueue>('messageQueue'); const queue = container.resolve<IMessageQueue>('messageQueue');
const id = await queue.enqueue(runId, message, voiceInput, voiceOutput, searchEnabled, middlePaneContext); const id = await queue.enqueue(runId, message, voiceInput, voiceOutput, searchEnabled, middlePaneContext);

View file

@ -234,6 +234,15 @@ const ipcSchemas = {
}), }),
res: Run, res: Run,
}, },
'runs:setWorkdir': {
req: z.object({
runId: z.string(),
workingDirectory: z.string().nullable(),
}),
res: z.object({
success: z.literal(true),
}),
},
'runs:list': { 'runs:list': {
req: z.object({ req: z.object({
cursor: z.string().optional(), cursor: z.string().optional(),

View file

@ -31,6 +31,9 @@ export const StartEvent = BaseRunEvent.extend({
"knowledge_sync", "knowledge_sync",
]).optional(), ]).optional(),
subUseCase: z.string().optional(), subUseCase: z.string().optional(),
// Per-run work directory chosen at creation. Optional: a run may have none,
// and it can be changed later via WorkdirChangedEvent.
workingDirectory: z.string().optional(),
}); });
export const SpawnSubFlowEvent = BaseRunEvent.extend({ export const SpawnSubFlowEvent = BaseRunEvent.extend({
@ -105,6 +108,12 @@ export const RunStoppedEvent = BaseRunEvent.extend({
reason: z.enum(["user-requested", "force-stopped"]).optional(), reason: z.enum(["user-requested", "force-stopped"]).optional(),
}); });
export const WorkdirChangedEvent = BaseRunEvent.extend({
type: z.literal("workdir-changed"),
// Absent/empty means the work directory was cleared.
workingDirectory: z.string().optional(),
});
export const RunEvent = z.union([ export const RunEvent = z.union([
RunProcessingStartEvent, RunProcessingStartEvent,
RunProcessingEndEvent, RunProcessingEndEvent,
@ -121,6 +130,7 @@ export const RunEvent = z.union([
ToolPermissionResponseEvent, ToolPermissionResponseEvent,
RunErrorEvent, RunErrorEvent,
RunStoppedEvent, RunStoppedEvent,
WorkdirChangedEvent,
]); ]);
export const ToolPermissionAuthorizePayload = ToolPermissionResponseEvent.pick({ export const ToolPermissionAuthorizePayload = ToolPermissionResponseEvent.pick({
@ -153,6 +163,7 @@ export const Run = z.object({
provider: z.string(), provider: z.string(),
useCase: UseCase.optional(), useCase: UseCase.optional(),
subUseCase: z.string().optional(), subUseCase: z.string().optional(),
workingDirectory: z.string().optional(),
log: z.array(RunEvent), log: z.array(RunEvent),
}); });
@ -172,4 +183,5 @@ export const CreateRunOptions = z.object({
provider: z.string().optional(), provider: z.string().optional(),
useCase: UseCase.optional(), useCase: UseCase.optional(),
subUseCase: z.string().optional(), subUseCase: z.string().optional(),
workingDirectory: z.string().optional(),
}); });