Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
0610f76
fix(claude): open structured chat without a startup deadline, and mak…
brennanb2025 Sep 23, 2026
a0aa442
fix(native-chat): sending into a chat that failed to start restarts it
brennanb2025 Sep 23, 2026
732ae66
fix(native-chat): a send with no live owner restarts it once
brennanb2025 Sep 22, 2026
480b316
test(native-chat): pin the unrecoverable refusal as settled in the ou…
brennanb2025 Sep 22, 2026
34bd711
test(native-chat): pin the release clock after a send restarts an unh…
brennanb2025 Sep 22, 2026
b1bc4ec
test: read the sent operation id without a cast
brennanb2025 Sep 22, 2026
70b42b7
fix(native-chat): type the send-recovery record lookup as the store r…
brennanb2025 Sep 22, 2026
509c688
fix(native-chat): a send ensures its owner before admission, and an u…
brennanb2025 Sep 23, 2026
1df2457
fix(native-chat): a create that throws releases its event sink
brennanb2025 Sep 23, 2026
2b0d5cf
Merge remote-tracking branch 'origin/main' into brennanb2025/claude-i…
brennanb2025 Sep 23, 2026
46f4d76
Merge remote-tracking branch 'origin/brennanb2025/claude-init-deadlin…
brennanb2025 Sep 23, 2026
3156821
fix(native-chat): a resend the journal answers restarts nothing, and …
brennanb2025 Sep 23, 2026
d3e47d7
fix(native-chat): the host learns a Claude start positively, and pers…
brennanb2025 Sep 23, 2026
49c6c03
fix(native-chat): a hold and a send that find the owner gone share on…
brennanb2025 Sep 23, 2026
6c0393a
fix(native-chat): a failed create answers one refusal shape, stamped …
brennanb2025 Sep 23, 2026
39234e4
fix(native-chat): a send into a session whose child ended restarts it…
brennanb2025 Sep 23, 2026
3d1e474
chore: restore pnpm-lock.yaml to origin/main (local pnpm rewrote it)
brennanb2025 Sep 23, 2026
4fd2c30
test(native-chat): pin the pre-handover exit as a failed acquire; stu…
brennanb2025 Sep 23, 2026
f7178a3
test(native-chat): a same-ID re-hold over the wire joins the one resu…
brennanb2025 Sep 23, 2026
fe21434
test(native-chat): a re-hold that joins a failing resume proves one r…
brennanb2025 Sep 23, 2026
f4ef3db
Merge branch 'brennanb2025/claude-init-deadline' into structured-send…
brennanb2025 Sep 23, 2026
e52cce4
fix(native-chat): a create whose child was proven gone answers the re…
brennanb2025 Sep 23, 2026
136a39d
test(native-chat): pin the outbox re-driving a stale-refused send und…
brennanb2025 Sep 23, 2026
e52c4a6
fix(native-chat): a child restarted for a send nobody holds is still …
brennanb2025 Sep 23, 2026
b8feb16
test(native-chat): pin the picked Claude model across a resume whose …
brennanb2025 Sep 23, 2026
f411c21
Revert "fix(native-chat): a child restarted for a send nobody holds i…
brennanb2025 Sep 23, 2026
a696115
Revert "test(native-chat): pin the outbox re-driving a stale-refused …
brennanb2025 Sep 23, 2026
c690f24
Revert "fix(native-chat): a send into a session whose child ended res…
brennanb2025 Sep 23, 2026
0709d55
Merge remote-tracking branch 'origin/main' into brennanb2025/claude-i…
brennanb2025 Sep 23, 2026
0e58f82
refactor(native-chat): make ensure-owner a step of the serialized send
brennanb2025 Sep 23, 2026
6754dd4
fix(native-chat): a Claude start proving itself no longer waits on th…
brennanb2025 Sep 23, 2026
fc65d74
test(native-chat): a hold reads its lease only after this host has re…
brennanb2025 Sep 23, 2026
150cadf
fix(claude): a chat whose first start failed resumes as the same conv…
brennanb2025 Sep 23, 2026
a666d88
refactor(native-chat): a resume answers with a typed refusal the send…
brennanb2025 Sep 23, 2026
ab5c8cd
fix(native-chat): a child's event sink belongs to the attach attempt …
brennanb2025 Sep 23, 2026
b51f6dd
Merge branch 'brennanb2025/claude-init-deadline' into structured-send…
brennanb2025 Sep 23, 2026
1148d1a
Merge branch 'brennanb2025/claude-init-deadline' into structured-send…
brennanb2025 Sep 23, 2026
395774e
test(native-chat): pin that only the adopted child's event sink still…
brennanb2025 Sep 23, 2026
09faab5
perf(native-chat): the chat reads only the host's startup phase from …
brennanb2025 Sep 23, 2026
fefc481
Merge remote-tracking branch 'origin/main' into brennanb2025/claude-i…
brennanb2025 Sep 23, 2026
35924ed
fix(native-chat): the startup-phase hook answers a phase or null, nev…
brennanb2025 Sep 23, 2026
3862657
fix(native-chat): every restart is counted from the moment it is aske…
brennanb2025 Sep 23, 2026
2458b2a
fix(native-chat): a Claude start no longer waits behind another sessi…
brennanb2025 Sep 23, 2026
a9dd581
fix(native-chat): a Claude create that dies at spawn is refused with …
brennanb2025 Sep 23, 2026
b2ad2c3
test(native-chat): pin that a reopened Claude chat dying before initi…
brennanb2025 Sep 23, 2026
d878a15
fix(native-chat): Stop while a Claude start drains its held prompts w…
brennanb2025 Sep 23, 2026
0c39550
fix(native-chat): the release clock keeps a session that still owes a…
brennanb2025 Sep 23, 2026
9830341
Merge remote-tracking branch 'origin/brennanb2025/claude-init-deadlin…
brennanb2025 Sep 23, 2026
40ae2c0
fix(native-chat): only a start that holds a sent message keeps a rele…
brennanb2025 Sep 23, 2026
3f564ab
fix(native-chat): a send waits for the owner it met to prove its star…
brennanb2025 Sep 23, 2026
c9f9c2a
Merge remote-tracking branch 'origin/main' into brennanb2025/claude-i…
brennanb2025 Sep 23, 2026
174437e
fix(native-chat): restart the release grace when a start writes its h…
brennanb2025 Sep 23, 2026
c851fe2
Merge branch 'brennanb2025/claude-init-deadline' into structured-send…
brennanb2025 Sep 23, 2026
0ddf885
Merge remote-tracking branch 'origin/main' into brennanb2025/claude-i…
brennanb2025 Sep 23, 2026
87f12fc
fix(native-chat): a starting child owns the send; the adapter holds t…
brennanb2025 Sep 23, 2026
6bcd8cd
Merge branch 'brennanb2025/claude-init-deadline' into structured-send…
brennanb2025 Sep 23, 2026
87d21f4
fix(native-chat): a failed send restart says why, and offers a new ch…
brennanb2025 Sep 23, 2026
689009b
fix(native-chat): a Claude start skips an option write the CLI never …
brennanb2025 Sep 23, 2026
62dafdb
test(native-chat): wait for the recovery's reserved lease, not the re…
brennanb2025 Sep 24, 2026
3af990a
fix(native-chat): a send whose restarted child dies before starting i…
brennanb2025 Sep 24, 2026
c67badc
test(native-chat): expect a send held for a start that never proved i…
brennanb2025 Sep 24, 2026
238fa22
fix(claude): write a prompt held after startup already drained, inste…
brennanb2025 Sep 24, 2026
77e38e0
test(native-chat): pin that a send to a child that died before starti…
brennanb2025 Sep 24, 2026
501b901
test(native-chat): leave the cast exit-session fixtures as they were,…
brennanb2025 Sep 24, 2026
dcdee46
fix(claude): a saved option the CLI never answered stays saved instea…
brennanb2025 Sep 24, 2026
26453aa
fix(native-chat): a rejected send opens no turn, so the row naming wh…
brennanb2025 Sep 24, 2026
f8bee36
fix(native-chat): a send whose restart died starting leaves the same …
brennanb2025 Sep 24, 2026
6349aee
fix(native-chat): a send rejected because the agent never started nam…
brennanb2025 Sep 24, 2026
060b159
fix(claude): a saved option the CLI never answered survives a later c…
brennanb2025 Sep 24, 2026
015ff03
fix(claude): a send that meets a child whose exit already settled nam…
brennanb2025 Sep 24, 2026
acd929e
test(native-chat): pin the words an exit settlement rejects a never-s…
brennanb2025 Sep 24, 2026
f2d5c85
fix(claude): derive which saved options are still unanswered from wha…
brennanb2025 Sep 24, 2026
64f2484
fix(native-chat): a cleared chat starts from a saved choice the child…
brennanb2025 Sep 24, 2026
17b41b5
fix(claude): closing a chat forgets its exit's diagnostic even when t…
brennanb2025 Sep 24, 2026
a88d95b
refactor(claude): keep a saved option the CLI never answered as the w…
brennanb2025 Sep 24, 2026
ced1e0c
Merge origin/main into structured-send-recovery
brennanb2025 Sep 24, 2026
9ccb39f
Merge origin/main into structured-send-recovery
brennanb2025 Sep 24, 2026
ff1c838
test(claude): wait for the start to finish before changing the saved …
brennanb2025 Sep 24, 2026
e9e7707
fix(i18n): translate the still-starting chat notice
brennanb2025 Sep 24, 2026
1b67125
test(claude): pin the failed acquisition's own reading-control release
brennanb2025 Sep 24, 2026
e81f02c
Merge remote-tracking branch 'origin/main' into structured-send-recovery
brennanb2025 Sep 24, 2026
b1e91e4
Merge origin/main into brennanb2025/claude-init-deadline
brennanb2025 Sep 24, 2026
c2d0b33
Merge origin/main into brennanb2025/claude-init-deadline
brennanb2025 Sep 24, 2026
634f65b
refactor(claude): move unexpected-exit handling into the exit lifecyc…
brennanb2025 Sep 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 30 additions & 19 deletions src/main/claude/claude-agent-sdk-control-requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ export class ClaudeControlRequestError extends Error {

export const CLAUDE_DEFAULT_REQUEST_TIMEOUT_MS = 30_000

/** The deadline fired before the CLI answered. The request may still land, so this proves
* nothing about it either way; it is neither the CLI refusing nor the transport closing. */
export class ClaudeControlRequestTimeoutError extends Error {
constructor(readonly subtype: string) {
super(`claude ${subtype} request timed out`)
this.name = 'ClaudeControlRequestTimeoutError'
}
}

/** The SDK closes a query out from under an in-flight control request with this exact message. */
const QUERY_CLOSED_MESSAGE = 'Query closed before response received'

Expand Down Expand Up @@ -46,33 +55,34 @@ export type ClaudeControlOptions = { timeoutMs?: number }
/**
* Run one native Query control method under Orca's deadline and error classification.
*
* The SDK owns correlation but applies no deadline, so the timeout stays here — and its
* message is load-bearing: the init proof matches on `claude initialize request timed out`.
* The SDK owns correlation but applies no deadline, so the timeout stays here. `null` means
* none: the request then settles only on the CLI's answer or on the query closing under it.
* A closed query is a transport failure, not the CLI rejecting the request, so only the
* latter is re-thrown as a `ClaudeControlRequestError` a caller may surface as a rejection.
*/
export function runClaudeControl<T>(
subtype: string,
run: () => Promise<T>,
timeoutMs: number = CLAUDE_DEFAULT_REQUEST_TIMEOUT_MS
timeoutMs: number | null = CLAUDE_DEFAULT_REQUEST_TIMEOUT_MS
): Promise<T> {
let timer: ReturnType<typeof setTimeout> | null = null
const request = Promise.resolve()
.then(run)
.catch((error: unknown) => {
const message = error instanceof Error ? error.message : String(error)
if (error instanceof ClaudeControlRequestError || message === QUERY_CLOSED_MESSAGE) {
throw error
}
throw new ClaudeControlRequestError(subtype, message)
})
if (timeoutMs === null) {
return request
}
const deadline = new Promise<never>((_resolve, reject) => {
timer = setTimeout(() => reject(new Error(`claude ${subtype} request timed out`)), timeoutMs)
timer = setTimeout(() => reject(new ClaudeControlRequestTimeoutError(subtype)), timeoutMs)
timer.unref?.()
})
return Promise.race([
Promise.resolve()
.then(run)
.catch((error: unknown) => {
const message = error instanceof Error ? error.message : String(error)
if (error instanceof ClaudeControlRequestError || message === QUERY_CLOSED_MESSAGE) {
throw error
}
throw new ClaudeControlRequestError(subtype, message)
}),
deadline
]).finally(() => {
return Promise.race([request, deadline]).finally(() => {
if (timer) {
clearTimeout(timer)
}
Expand All @@ -93,7 +103,8 @@ export type ClaudeControlSurface = {
) => Promise<void>
stopTask: (taskId: string, options?: ClaudeControlOptions) => Promise<void>
supportedModels: (options?: ClaudeControlOptions) => Promise<unknown[]>
initializationResult: (options?: ClaudeControlOptions) => Promise<unknown>
/** Untimed: a slow start is still a start, and the child's exit closes the query under it. */
initializationResult: () => Promise<unknown>
getSettings: (options?: ClaudeControlOptions) => Promise<unknown>
/** The `/context` breakdown; older CLIs reject the request and the caller shows nothing. */
getContextUsage: (options?: ClaudeControlOptions) => Promise<unknown>
Expand Down Expand Up @@ -144,8 +155,8 @@ export function createClaudeControlSurface(query: Query): ClaudeControlSurface {
),
supportedModels: (options) =>
runClaudeControl('list_models', () => query.supportedModels(), options?.timeoutMs),
initializationResult: (options) =>
runClaudeControl('initialize', () => query.initializationResult(), options?.timeoutMs),
initializationResult: () =>
runClaudeControl('initialize', () => query.initializationResult(), null),
getContextUsage: (options) =>
runClaudeControl('get_context_usage', () => query.getContextUsage(), options?.timeoutMs),
getSettings: (options) => {
Expand Down
14 changes: 7 additions & 7 deletions src/main/claude/claude-stream-json-connection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -515,10 +515,12 @@ describe('Claude stream-json connection', () => {
}
})
const connection = await open(launchFor(scenario))
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the fixture supplies every session member the option paths under test read.
const session = {
connection,
options: new Map<string, string>(),
reportedOptions: {}
reportedOptions: {},
startup: { state: 'proven' }
} as unknown as ClaudeSession

const options = await readClaudeStructuredSessionOptions(session, 5_000)
Expand Down Expand Up @@ -567,17 +569,15 @@ describe('Claude stream-json connection', () => {
expect(JSON.stringify(diagnostic)).not.toContain('secret')
})

it('reports an unauthenticated start through the init deadline instead of hanging', async () => {
// The scripted CLI never answers, which is the shape of a silently unauthenticated CLI.
const scenario = scriptScenario([HOLD_OPEN])
it('settles an unanswered initialize when the child exits, with no timer of its own', async () => {
// The scripted CLI never answers, then leaves: only its exit can settle the request.
const scenario = scriptScenario([{ stderr: 'claude: not signed in\n' }, { exit: 1 }])
const connection = await open({
...launchFor(scenario),
env: { ...launchFor(scenario).env, ORCA_SDK_CONTRACT_IGNORE_CONTROL_REQUESTS: '1' }
})

await expect(connection.initializationResult({ timeoutMs: 200 })).rejects.toThrow(
'claude initialize request timed out'
)
await expect(connection.initializationResult()).rejects.toThrow()
})

it('reports a self-exit with its status, stderr, and observed tree verdict', async () => {
Expand Down
4 changes: 3 additions & 1 deletion src/main/claude/claude-structured-dispatch-test-support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { retireClaudeDispatchWaiters } from './claude-structured-dispatch'
import type { ClaudeSession } from './claude-structured-session-state'
import { ClaudeBackgroundTaskTracker } from './claude-background-task-tracker'
import { ClaudeSlashCommandCatalog } from './claude-slash-command-catalog'
import { createClaudeSessionStartupGate } from './claude-structured-session-startup-gate'

export function sessionFor(send: Mock = vi.fn().mockResolvedValue(undefined)): ClaudeSession {
return {
Expand All @@ -28,7 +29,8 @@ export function sessionFor(send: Mock = vi.fn().mockResolvedValue(undefined)): C
restoreSkippedOptions: new Set(),
capabilities: [],
events: undefined,
translator: null
translator: null,
startup: { ...createClaudeSessionStartupGate(), state: 'proven' }
}
}

Expand Down
36 changes: 28 additions & 8 deletions src/main/claude/claude-structured-dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ import {
claudeUserMessageWasProvablyUnwritten
} from './claude-agent-sdk-user-message-queue'
import { AgentSessionPreDispatchError } from '../native-chat/agent-session-wire/structured-agent-session-operation-settlement'
import {
claudeStartupFailureReason,
claudeStartupHoldsWrites,
failClaudeStartupGate,
holdClaudeStartupWrite
} from './claude-structured-session-startup-gate'

const MAX_ACTIVE_DISPATCH_WAITERS = 64

Expand Down Expand Up @@ -220,6 +226,7 @@ export function settleCancelledClaudeDispatchWaiters(
* Retired rather than dropped: their identities stay joinable, bounded by
* `MAX_RETIRED_DISPATCH_WAITERS`. */
export function retireClaudeDispatchWaiters(session: ClaudeSession): void {
failClaudeStartupGate(session, new Error('claude stream-json ended before startup completed'))
for (const waiter of session.dispatchWaiters.splice(0)) {
retireWaiter(session, waiter)
waiter.resolve(null)
Expand All @@ -229,7 +236,8 @@ export function retireClaudeDispatchWaiters(session: ClaudeSession): void {
export async function dispatchClaudeTurn(
session: ClaudeSession,
input: { clientMessageId?: string; body: AgentJournalMessageItem; requestedAt?: number },
beforeDispatch?: () => Promise<void>
beforeDispatch?: () => Promise<void>,
onSettledLate?: ClaudeLateDispatchSettlement
): Promise<AgentSessionDispatchOutcome> {
let content: unknown[]
try {
Expand All @@ -240,6 +248,10 @@ export async function dispatchClaudeTurn(
if (session.dispatchWaiters.length >= MAX_ACTIVE_DISPATCH_WAITERS) {
return { state: 'rejected', reason: DISPATCH_REJECTED_QUEUE_FULL }
}
const startupFailure = claudeStartupFailureReason(session)
if (startupFailure) {
return { state: 'rejected', reason: startupFailure }
}
// Read the sent content, not the journal blocks: only the mapped trailing prompt decides
// whether Claude runs a command, so the two cannot disagree about which frame settles this.
const acceptsResult = claudeDispatchInvokesSlashCommand(content)
Expand All @@ -257,6 +269,21 @@ export async function dispatchClaudeTurn(
input.requestedAt ?? null
)
}
const message = {
type: 'user',
uuid: sentUuid,
message: { role: 'user', content },
parent_tool_use_id: null,
session_id: session.providerSessionId
}
if (claudeStartupHoldsWrites(session)) {
return holdClaudeStartupWrite(session, {
message,
arm,
...(beforeDispatch ? { beforeDispatch } : {}),
...(onSettledLate ? { settleLate: onSettledLate } : {})
})
}
const pending = { replay: beforeDispatch ? undefined : arm() }
const authorize = beforeDispatch
? async () => {
Expand All @@ -268,13 +295,6 @@ export async function dispatchClaudeTurn(
}
: undefined
try {
const message = {
type: 'user',
uuid: sentUuid,
message: { role: 'user', content },
parent_tool_use_id: null,
session_id: session.providerSessionId
}
await (authorize
? session.connection.send(message, authorize)
: session.connection.send(message))
Expand Down
4 changes: 3 additions & 1 deletion src/main/claude/claude-structured-effort-reporting.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ function sessionWith(
listed?: { model: string; catalog: readonly Record<string, unknown>[] }
) {
return {
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the fixture supplies every session member the option paths under test read.
session: {
options: new Map<string, string>(listed ? [['model', listed.model]] : []),
reportedOptions: {} as { model?: string; effort?: string },
Expand All @@ -47,7 +48,8 @@ function sessionWith(
? { applied: {}, effective: {}, sources: {} }
: { applied: { effort: reported }, effective: { effortLevel: reported }, sources: {} }
}
}
},
startup: { state: 'proven' }
} as unknown as ClaudeSession,
calls
}
Expand Down
68 changes: 0 additions & 68 deletions src/main/claude/claude-structured-init-deadline.ts

This file was deleted.

43 changes: 39 additions & 4 deletions src/main/claude/claude-structured-launch-resolution.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { chmodSync, mkdtempSync, mkdirSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { delimiter, join } from 'node:path'
import { describe, expect, it } from 'vitest'
import { describe, expect, it, vi } from 'vitest'
import type { AgentSessionRecord } from '../../shared/agent-session-record'
import { LOCAL_EXECUTION_HOST_ID } from '../../shared/execution-host'
import type { AgentSessionRecordStore } from '../runtime/agent-session-record-store'
Expand Down Expand Up @@ -58,14 +58,16 @@ function resolverFor(
resolveEnv?: () => Record<string, string>,
stripAuthEnv = false,
// Manual by default so a test that is not about permissions is not silently about them.
agentDefaultArgs: Record<string, string> = { claude: '' }
agentDefaultArgs: Record<string, string> = { claude: '' },
hasTranscript: () => Promise<boolean> = async () => true
) {
return createClaudeStructuredLaunchResolver({
store: { getRecord: () => value } as unknown as AgentSessionRecordStore,
resolveWorkspacePath: async (id) => `/repos/${id}`,
resolveCommand: () => '/usr/local/bin/claude',
resolveAuthPolicy: () => ({ stripAuthEnv }),
resolvePermissionMode: () => claudeStructuredPermissionModeForSettings({ agentDefaultArgs }),
hasTranscript,
...(resolveEnv ? { resolveEnv } : {})
})
}
Expand Down Expand Up @@ -115,7 +117,8 @@ describe('claude structured launch resolution', () => {
cwd: '/repos/workspace-1',
claudeConfigDir: '/home/work/.claude',
resumeLeafUuid: null,
resumed: false
resumesTranscript: false,
continuesChain: false
})
expect(first.options).toEqual({
includePartialMessages: true,
Expand Down Expand Up @@ -149,7 +152,8 @@ describe('claude structured launch resolution', () => {
expect(launch).toMatchObject({
providerSessionId: 'provider-current',
resumeLeafUuid: 'leaf-current',
resumed: true
resumesTranscript: true,
continuesChain: true
})
expect(launch.options.resume).toBe('provider-current')
// Claude owns where the conversation continues; a stored leaf would cut or branch it.
Expand Down Expand Up @@ -202,6 +206,37 @@ describe('claude structured launch resolution', () => {
expect(launch.options).not.toHaveProperty('resumeSessionAt')
})

it('launches a leafless head fresh under its own id when Claude never wrote its transcript', async () => {
// A start that failed before its first turn: `--resume` would exit "No conversation found".
const hasTranscript = vi.fn(async () => false)
const launch = await resolverFor(
record({
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the resolver reads only each link's handle.
providerHandleChain: [
{ handle: { provider: 'claude', sessionId: 'provider-current', leafUuid: null } }
] as AgentSessionRecord['providerHandleChain']
}),
undefined,
false,
{ claude: '' },
hasTranscript
)({ identity: identityAt(null) })

expect(hasTranscript).toHaveBeenCalledWith({
providerSessionId: 'provider-current',
claudeConfigDir: expect.any(String)
})
expect(launch.options.resume).toBeUndefined()
expect(launch.options.sessionId).toBe('provider-current')
expect(launch).toMatchObject({
providerSessionId: 'provider-current',
resumeLeafUuid: null,
resumesTranscript: false,
// Launching the id fresh does not start a new conversation: the child continues the chain.
continuesChain: true
})
})

// Agent Permissions is stored as the bypass flag inside the launch arguments, so presence of
// that flag — not the whole string — is what Yolo means, exactly as a terminal launch reads it.
it.each([
Expand Down
Loading
Loading