mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
fix(agent-resume): resume Claude sessions at their own effort level (#33765)
* fix(agent-resume): resume Claude sessions at their own effort level * fix(agent-resume): keep effort through persistence and /clear; simplify ensure shaping
This commit is contained in:
@@ -1,13 +1,11 @@
|
||||
import { parseTerminalAttachmentLaunchResult } from '../../../../shared/terminal-attachment-launch-result'
|
||||
import {
|
||||
terminalLaunchFacts,
|
||||
agentProviderSessionForEnsure
|
||||
} from '../../../../shared/agent-session-resume'
|
||||
import { terminalLaunchFacts } from '../../../../shared/agent-session-resume'
|
||||
import { isRuntimeCompatBlockError } from '@/runtime/runtime-protocol-compat'
|
||||
import { runtimeEnvironmentSupportsCapability } from '@/runtime/runtime-rpc-client'
|
||||
import { readTerminalSpawnDeferral } from '../../../../shared/host-spawn-admission'
|
||||
import { remoteRestoreSpawnIntentParams } from '../../runtime/remote-restore-spawn-intent'
|
||||
import { createAgentSessionKeyboardOptions } from '@/runtime/agent-session-keyboard-capability'
|
||||
import { agentProviderSessionForHostEnsure } from '@/runtime/agent-session-ensure-provider-session'
|
||||
import { createRemoteRuntimeDisconnectedInputGrace } from './remote-runtime-disconnected-input-grace'
|
||||
import {
|
||||
createRemoteRuntimeRecoveryInputHold,
|
||||
@@ -3518,10 +3516,16 @@ export function createRemoteRuntimePtyTransport(
|
||||
)
|
||||
const hostAuthorityCreate = async (epoch = connectLifecycleEpoch) => {
|
||||
const keyboardOptions = await agentKeyboardOptions(createEnvironmentId)
|
||||
const ensureProviderSession = resumeProviderSessionToSend
|
||||
? await agentProviderSessionForHostEnsure(
|
||||
createEnvironmentId,
|
||||
resumeProviderSessionToSend
|
||||
)
|
||||
: undefined
|
||||
return createWithUnknownOutcomeRecovery(
|
||||
'agent-session',
|
||||
(timeoutMs) =>
|
||||
resumeProviderSessionToSend
|
||||
ensureProviderSession
|
||||
? callRuntimeForEnvironment<RuntimeEnsureAgentSessionResult>(
|
||||
createEnvironmentId,
|
||||
'terminal.ensureAgentSession',
|
||||
@@ -3531,7 +3535,7 @@ export function createRemoteRuntimePtyTransport(
|
||||
...originOptions,
|
||||
worktree: toRuntimeTerminalWorktreeSelector(worktreeId),
|
||||
agent: launchAgentToSend!,
|
||||
providerSession: agentProviderSessionForEnsure(resumeProviderSessionToSend),
|
||||
providerSession: ensureProviderSession,
|
||||
...(launchConfigToSend?.ompResumeFilePath
|
||||
? { ompResumeFilePath: launchConfigToSend.ompResumeFilePath }
|
||||
: {}),
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { AgentProviderSessionMetadata } from '../../../shared/agent-session-resume'
|
||||
import { AGENT_SESSION_RESUME_EFFORT_RUNTIME_CAPABILITY } from '../../../shared/agent-session-create-capabilities'
|
||||
import { runtimeEnvironmentSupportsCapability } from './runtime-rpc-client'
|
||||
import { isRuntimeCompatBlockError } from './runtime-protocol-compat'
|
||||
|
||||
/** The provider session an ensureAgentSession call may send to this host; older hosts' strict schema rejects `cwd` and `effort`. */
|
||||
export async function agentProviderSessionForHostEnsure(
|
||||
environmentId: string,
|
||||
providerSession: AgentProviderSessionMetadata
|
||||
): Promise<AgentProviderSessionMetadata> {
|
||||
const { cwd: _cwd, effort, ...identity } = providerSession
|
||||
return effort && (await hostAcceptsResumeEffort(environmentId))
|
||||
? { ...identity, effort }
|
||||
: identity
|
||||
}
|
||||
|
||||
async function hostAcceptsResumeEffort(environmentId: string): Promise<boolean> {
|
||||
try {
|
||||
return await runtimeEnvironmentSupportsCapability(
|
||||
environmentId,
|
||||
AGENT_SESSION_RESUME_EFFORT_RUNTIME_CAPABILITY
|
||||
)
|
||||
} catch (error) {
|
||||
if (isRuntimeCompatBlockError(error)) {
|
||||
throw error
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { createAgentSessionKeyboardOptions } from './agent-session-keyboard-capa
|
||||
import type { RuntimeRpcResponse } from '../../../shared/runtime-rpc-envelope'
|
||||
import type { RuntimeMobileSessionCreateTerminalResult } from '../../../shared/runtime-types'
|
||||
import { toRuntimeExecutionHostId } from '../../../shared/execution-host'
|
||||
import { agentProviderSessionForEnsure } from '../../../shared/agent-session-resume'
|
||||
import { agentProviderSessionForHostEnsure } from './agent-session-ensure-provider-session'
|
||||
import { translate } from '../i18n/i18n'
|
||||
import { useAppStore } from '../store'
|
||||
import { agentResumeHostAuthorityCapability } from './agent-resume-host-authority-capability'
|
||||
@@ -110,7 +110,10 @@ export async function createWebRuntimeSessionTerminalResult(
|
||||
kind: 'explicit',
|
||||
worktree: toRuntimeWorktreeSelector(args.worktreeId),
|
||||
agent,
|
||||
providerSession: agentProviderSessionForEnsure(args.providerSession!),
|
||||
providerSession: await agentProviderSessionForHostEnsure(
|
||||
environmentId,
|
||||
args.providerSession!
|
||||
),
|
||||
...(args.launchConfig?.ompResumeFilePath
|
||||
? { ompResumeFilePath: args.launchConfig.ompResumeFilePath }
|
||||
: {}),
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// Why a shape, not Claude's level list: a level a newer Claude adds must still round-trip into argv.
|
||||
export const AGENT_EFFORT_LEVEL = /^[a-z][a-z0-9-]{0,31}$/
|
||||
|
||||
export function readAgentEffortLevel(value: unknown): string | undefined {
|
||||
return typeof value === 'string' && AGENT_EFFORT_LEVEL.test(value) ? value : undefined
|
||||
}
|
||||
@@ -131,6 +131,51 @@ describe('shared agent-hook-listener', () => {
|
||||
expect(resumedElsewhere?.providerSession?.cwd).toBe('/elsewhere')
|
||||
})
|
||||
|
||||
it("records a Claude session's live effort and keeps it across hooks that omit it", () => {
|
||||
const sessionId = '88888888-8888-4888-8888-888888888888'
|
||||
const prompt = normalizeAndAccept(state, 'claude', {
|
||||
hook_event_name: 'UserPromptSubmit',
|
||||
session_id: sessionId,
|
||||
prompt: 'go'
|
||||
})
|
||||
normalizeAndAccept(state, 'claude', {
|
||||
hook_event_name: 'PreToolUse',
|
||||
session_id: sessionId,
|
||||
tool_name: 'Bash',
|
||||
effort: { level: 'max' }
|
||||
})
|
||||
// Subagent hooks name the parent session but report the subagent's own effort.
|
||||
const subagent = normalizeAndAccept(state, 'claude', {
|
||||
hook_event_name: 'PostToolUse',
|
||||
session_id: sessionId,
|
||||
agent_id: 'agent-1',
|
||||
tool_name: 'Read',
|
||||
effort: { level: 'low' }
|
||||
})
|
||||
const nextPrompt = normalizeAndAccept(state, 'claude', {
|
||||
hook_event_name: 'UserPromptSubmit',
|
||||
session_id: sessionId,
|
||||
prompt: 'again'
|
||||
})
|
||||
// /clear starts a new session id in the same process, which keeps its effort.
|
||||
const cleared = normalizeAndAccept(state, 'claude', {
|
||||
hook_event_name: 'SessionStart',
|
||||
source: 'clear',
|
||||
session_id: '99999999-9999-4999-8999-999999999999'
|
||||
})
|
||||
const otherSession = normalizeAndAccept(state, 'claude', {
|
||||
hook_event_name: 'SessionStart',
|
||||
source: 'startup',
|
||||
session_id: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa'
|
||||
})
|
||||
|
||||
expect(prompt?.providerSession?.effort).toBeUndefined()
|
||||
expect(subagent?.providerSession?.effort).toBe('max')
|
||||
expect(nextPrompt?.providerSession?.effort).toBe('max')
|
||||
expect(cleared?.providerSession?.effort).toBe('max')
|
||||
expect(otherSession?.providerSession?.effort).toBeUndefined()
|
||||
})
|
||||
|
||||
it('resets stale Claude turn state when SessionStart announces a new session on the pane', () => {
|
||||
normalizeAndAccept(state, 'claude', { hook_event_name: 'UserPromptSubmit', prompt: 'fix bug' })
|
||||
normalizeAndAccept(state, 'claude', {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { AgentHookSource } from '../agent-hook-relay'
|
||||
import { readAgentEffortLevel } from '../agent-effort-level'
|
||||
import {
|
||||
extractAgentProviderSession,
|
||||
readPathFromKeys,
|
||||
@@ -25,28 +26,69 @@ export function readPaneProviderSession(
|
||||
if (!providerSession) {
|
||||
return null
|
||||
}
|
||||
const cwd = readAgentCwd(state, source, paneKey, providerSession.id, hookPayload)
|
||||
return cwd ? { ...providerSession, cwd } : providerSession
|
||||
const continued = continuedProviderSession(
|
||||
state,
|
||||
source,
|
||||
paneKey,
|
||||
providerSession.id,
|
||||
hookPayload
|
||||
)
|
||||
const cwd = readAgentCwd(source, hookPayload, continued)
|
||||
const effort = readClaudeEffort(source, hookPayload, continued)
|
||||
return {
|
||||
...providerSession,
|
||||
...(cwd ? { cwd } : {}),
|
||||
...(effort ? { effort } : {})
|
||||
}
|
||||
}
|
||||
|
||||
/** The folder the pane's agent started in. Only Claude/Codex hooks are known to report it (Cursor's cwd is a shell command's). */
|
||||
function readAgentCwd(
|
||||
/** The pane's previous session when this hook continues it; /clear and compact restart Claude in the same process. */
|
||||
function continuedProviderSession(
|
||||
state: HookListenerState,
|
||||
source: AgentHookSource,
|
||||
paneKey: string,
|
||||
sessionId: string,
|
||||
hookPayload: Record<string, unknown>
|
||||
): string | undefined {
|
||||
if (source !== 'claude' && source !== 'codex') {
|
||||
return undefined
|
||||
}
|
||||
): AgentProviderSessionMetadata | undefined {
|
||||
const previous = state.lastStatusByPaneKey.get(paneKey)
|
||||
const previousCwd = previous?.source === source ? previous.providerSession?.cwd : undefined
|
||||
// Claude's hook cwd follows its Bash tool's `cd`; /clear and compact restart in the same process.
|
||||
const keepsPreviousFolder =
|
||||
const continues =
|
||||
previous?.providerSession?.id === sessionId ||
|
||||
(source === 'claude' &&
|
||||
hookPayload.hook_event_name === 'SessionStart' &&
|
||||
(hookPayload.source === 'clear' || hookPayload.source === 'compact'))
|
||||
return previousCwd && keepsPreviousFolder ? previousCwd : readPathFromKeys(hookPayload, ['cwd'])
|
||||
return previous?.source === source && continues ? previous.providerSession : undefined
|
||||
}
|
||||
|
||||
/** The folder the pane's agent started in. Only Claude/Codex hooks are known to report it (Cursor's cwd is a shell command's). */
|
||||
function readAgentCwd(
|
||||
source: AgentHookSource,
|
||||
hookPayload: Record<string, unknown>,
|
||||
continued: AgentProviderSessionMetadata | undefined
|
||||
): string | undefined {
|
||||
if (source !== 'claude' && source !== 'codex') {
|
||||
return undefined
|
||||
}
|
||||
// Claude's hook cwd follows its Bash tool's `cd`.
|
||||
return continued?.cwd ?? readPathFromKeys(hookPayload, ['cwd'])
|
||||
}
|
||||
|
||||
/** Claude 2.1.133+ reports `effort.level` on tool and Stop hooks only, so other events keep the last one. */
|
||||
function readClaudeEffort(
|
||||
source: AgentHookSource,
|
||||
hookPayload: Record<string, unknown>,
|
||||
continued: AgentProviderSessionMetadata | undefined
|
||||
): string | undefined {
|
||||
if (source !== 'claude') {
|
||||
return undefined
|
||||
}
|
||||
const effort = hookPayload.effort
|
||||
// Why skip subagents: their hooks name the parent session but report the subagent's own effort.
|
||||
const reported =
|
||||
typeof effort === 'object' &&
|
||||
effort !== null &&
|
||||
'level' in effort &&
|
||||
!readString(hookPayload, 'agent_id')
|
||||
? readAgentEffortLevel(effort.level)
|
||||
: undefined
|
||||
return reported ?? continued?.effort
|
||||
}
|
||||
|
||||
@@ -11,7 +11,14 @@ export function getAgentResumeArgv(
|
||||
case 'codebuddy':
|
||||
return providerSession.key === 'session_id' ? ['codebuddy', '--resume', id] : null
|
||||
case 'claude':
|
||||
return providerSession.key === 'session_id' ? ['claude', '--resume', id] : null
|
||||
return providerSession.key === 'session_id'
|
||||
? [
|
||||
'claude',
|
||||
...(providerSession.effort ? ['--effort', providerSession.effort] : []),
|
||||
'--resume',
|
||||
id
|
||||
]
|
||||
: null
|
||||
case 'cursor':
|
||||
return providerSession.key === 'conversation_id' ? ['cursor-agent', '--resume', id] : null
|
||||
case 'codex':
|
||||
|
||||
@@ -565,3 +565,32 @@ describe('buildAgentResumeStartupPlan claude selector guard', () => {
|
||||
expect(restored?.launchConfig.agentCommand).toBe("claude '--resume'")
|
||||
})
|
||||
})
|
||||
|
||||
describe('buildAgentResumeStartupPlan claude effort', () => {
|
||||
it.each(SHELLS)(
|
||||
"resumes at the session's effort after a stale launch flag ($platform/$shell)",
|
||||
({ platform, shell }) => {
|
||||
const session = { ...providerSession, effort: 'max' }
|
||||
const restored = buildAgentResumeStartupPlan({
|
||||
agent: 'claude',
|
||||
providerSession: session,
|
||||
cmdOverrides: {},
|
||||
agentArgs: '--effort low',
|
||||
platform,
|
||||
shell
|
||||
})
|
||||
const command = restored?.launchCommand ?? ''
|
||||
const tokens = tokenizeStartupCommand(command, shell)
|
||||
// Claude keeps the last --effort it parses.
|
||||
expect(tokens.ok && tokens.tokens.slice(-6)).toEqual([
|
||||
'--effort',
|
||||
'low',
|
||||
'--effort',
|
||||
'max',
|
||||
...RESUME
|
||||
])
|
||||
// The saved launch recipe stays the user's own.
|
||||
expect(restored?.launchConfig.agentArgs).toBe('--effort low')
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -61,6 +61,8 @@ export function recoveryRecordMatches(
|
||||
existing.state === next.state &&
|
||||
agentMainAgentVerdict(existing) === agentMainAgentVerdict(next) &&
|
||||
agentProviderSessionsEqual(existing.agent, existing.providerSession, next.providerSession) &&
|
||||
// Why: effort is not session identity, but a stale level would resume at the wrong effort.
|
||||
existing.providerSession.effort === next.providerSession.effort &&
|
||||
launchConfigsEqual(existing.launchConfig, next.launchConfig)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,10 @@ export const AGENT_SESSION_ACCOUNT_ENV_RUNTIME_CAPABILITY = 'agent-session.accou
|
||||
// Send is strict: advertise skills before sending selected paths; name-based agents still use the $name text.
|
||||
export const AGENT_SESSION_SEND_SKILLS_RUNTIME_CAPABILITY = 'agent-session.send-skills.v1' as const
|
||||
|
||||
// Ensure is strict: send a resumed Claude session's effort only to hosts advertising support.
|
||||
export const AGENT_SESSION_RESUME_EFFORT_RUNTIME_CAPABILITY =
|
||||
'agent-session.resume-effort.v1' as const
|
||||
|
||||
export const AGENT_SESSION_SEND_USAGE_LIMIT_CONTINUATION_RUNTIME_CAPABILITY =
|
||||
'agent-session.send-usage-limit-continuation.v1' as const
|
||||
|
||||
@@ -28,5 +32,6 @@ export const AGENT_SESSION_CREATE_RUNTIME_CAPABILITIES = [
|
||||
AGENT_SESSION_CREATE_TAB_PLACEMENT_RUNTIME_CAPABILITY,
|
||||
AGENT_SESSION_SEND_OPTIONS_RUNTIME_CAPABILITY,
|
||||
AGENT_SESSION_ACCOUNT_ENV_RUNTIME_CAPABILITY,
|
||||
AGENT_SESSION_SEND_SKILLS_RUNTIME_CAPABILITY
|
||||
AGENT_SESSION_SEND_SKILLS_RUNTIME_CAPABILITY,
|
||||
AGENT_SESSION_RESUME_EFFORT_RUNTIME_CAPABILITY
|
||||
] as const
|
||||
|
||||
@@ -65,6 +65,11 @@ describe('agent session resume metadata', () => {
|
||||
|
||||
it.each([
|
||||
['claude', { key: 'session_id', id: 's1' }, ['claude', '--resume', 's1']],
|
||||
[
|
||||
'claude',
|
||||
{ key: 'session_id', id: 's1', effort: 'max' },
|
||||
['claude', '--effort', 'max', '--resume', 's1']
|
||||
],
|
||||
['codebuddy', { key: 'session_id', id: 's1' }, ['codebuddy', '--resume', 's1']],
|
||||
['codex', { key: 'session_id', id: 's1' }, ['codex', 'resume', 's1']],
|
||||
['gemini', { key: 'session_id', id: 's1' }, ['gemini', '--resume', 's1']],
|
||||
@@ -168,6 +173,18 @@ describe('agent session resume metadata', () => {
|
||||
})
|
||||
).toEqual({ key: 'session_id', id: 'ok' })
|
||||
})
|
||||
|
||||
it('round-trips a Claude effort level and drops values unsafe for argv', () => {
|
||||
expect(normalizeAgentProviderSession({ key: 'session_id', id: 'ok', effort: 'xhigh' })).toEqual(
|
||||
{ key: 'session_id', id: 'ok', effort: 'xhigh' }
|
||||
)
|
||||
for (const effort of ['--model', 'high; rm -rf /', 'HIGH', '', 3]) {
|
||||
expect(normalizeAgentProviderSession({ key: 'session_id', id: 'ok', effort })).toEqual({
|
||||
key: 'session_id',
|
||||
id: 'ok'
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('OMP recorded resume locators', () => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { readAgentEffortLevel } from './agent-effort-level'
|
||||
import type { AgentHookSource } from './agent-hook-relay'
|
||||
import type { AgentStatusCacheIdentity, RefusedPaneResume } from './agent-status-ipc-payload'
|
||||
import type { AgentStatusState } from './agent-status-types'
|
||||
@@ -44,6 +45,8 @@ export type AgentProviderSessionMetadata = {
|
||||
transcriptPath?: string
|
||||
/** Folder the agent ran in; resume runs there, not the pane's start folder (#16960). */
|
||||
cwd?: string
|
||||
/** Claude's live effort level; resume replays it because Claude restores only its saved per-model default. */
|
||||
effort?: string
|
||||
}
|
||||
|
||||
export type SleepingAgentLaunchConfig = {
|
||||
@@ -181,22 +184,16 @@ export function normalizeAgentProviderSession(raw: unknown): AgentProviderSessio
|
||||
// Persisted/relay paths cross trust boundaries too; reject controls as for hook paths.
|
||||
const transcriptPath = readPathFromKeys(record, ['transcriptPath'])
|
||||
const cwd = readPathFromKeys(record, ['cwd'])
|
||||
const effort = readAgentEffortLevel(record.effort)
|
||||
return {
|
||||
key,
|
||||
id,
|
||||
...(transcriptPath ? { transcriptPath } : {}),
|
||||
...(cwd ? { cwd } : {})
|
||||
...(cwd ? { cwd } : {}),
|
||||
...(effort ? { effort } : {})
|
||||
}
|
||||
}
|
||||
|
||||
/** Older hosts' strict ensureAgentSession schema rejects the newer `cwd` field. */
|
||||
export function agentProviderSessionForEnsure(
|
||||
providerSession: AgentProviderSessionMetadata
|
||||
): AgentProviderSessionMetadata {
|
||||
const { cwd: _cwd, ...identity } = providerSession
|
||||
return identity
|
||||
}
|
||||
|
||||
/** Compare the provider-owned values that identify the CLI resume target.
|
||||
* Pi-family transcript resumes use file identity; other agents use provider ids. */
|
||||
export function agentProviderSessionsEqual(
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
getAgentResumeArgv,
|
||||
hasUnsafeProviderSessionIdChars
|
||||
} from '../agent-session-resume'
|
||||
import { AGENT_EFFORT_LEVEL } from '../agent-effort-level'
|
||||
import { parseAgentSessionOperationTimestamp } from '../agent-session-host-authority'
|
||||
import type {
|
||||
RuntimeCreateAgentSessionRequest,
|
||||
@@ -110,7 +111,8 @@ export const ProviderSession = z
|
||||
Buffer.byteLength(value, 'utf8') <= MAX_TRANSCRIPT_PATH_BYTES,
|
||||
'Invalid transcript path'
|
||||
)
|
||||
.optional()
|
||||
.optional(),
|
||||
effort: z.string().regex(AGENT_EFFORT_LEVEL, 'Invalid effort level').optional()
|
||||
})
|
||||
.strict()
|
||||
|
||||
|
||||
@@ -148,6 +148,35 @@ describe('parseWorkspaceSession sleeping agents', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it("preserves a Claude session's effort level through hydration", () => {
|
||||
const providerSession = { key: 'session_id', id: 'claude-session', effort: 'max' }
|
||||
const result = parseWorkspaceSession({
|
||||
activeRepoId: null,
|
||||
activeWorktreeId: null,
|
||||
activeTabId: null,
|
||||
tabsByWorktree: {},
|
||||
terminalLayoutsByTabId: {},
|
||||
sleepingAgentSessionsByPaneKey: {
|
||||
'tab1:pane-1': {
|
||||
paneKey: 'tab1:pane-1',
|
||||
tabId: 'tab1',
|
||||
worktreeId: 'wt',
|
||||
agent: 'claude',
|
||||
providerSession,
|
||||
prompt: '',
|
||||
state: 'done',
|
||||
capturedAt: 10,
|
||||
updatedAt: 10,
|
||||
origin: 'live'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
expect(
|
||||
result.ok && result.value.sleepingAgentSessionsByPaneKey?.['tab1:pane-1']?.providerSession
|
||||
).toEqual(providerSession)
|
||||
})
|
||||
|
||||
it('preserves the AI Vault OMP resume file through hydration', () => {
|
||||
const result = parseWorkspaceSession({
|
||||
activeRepoId: null,
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
normalizeAgentProviderSession,
|
||||
RESUMABLE_TUI_AGENTS
|
||||
} from './agent-session-resume'
|
||||
import { AGENT_EFFORT_LEVEL } from './agent-effort-level'
|
||||
import { isValidTerminalTabId } from './terminal-tab-id'
|
||||
import { normalizeMainAgentStatusField } from './agent-status-types'
|
||||
import { salvagingRecord } from './zod-salvage'
|
||||
@@ -20,7 +21,8 @@ const agentProviderSessionSchema = z.preprocess(
|
||||
id: z.string().min(1).max(512),
|
||||
// Pi resume requires its persisted authoritative transcript path.
|
||||
transcriptPath: z.string().min(1).optional(),
|
||||
cwd: z.string().min(1).optional()
|
||||
cwd: z.string().min(1).optional(),
|
||||
effort: z.string().regex(AGENT_EFFORT_LEVEL).optional()
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user