import { spawn, spawnSync, type ChildProcessWithoutNullStreams } from 'node:child_process' import { createServer, type IncomingMessage } from 'node:http' import { createInterface } from 'node:readline' import { existsSync } from 'node:fs' import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' import { homedir, tmpdir } from 'node:os' import { join, parse, relative, resolve, sep } from 'node:path' import { randomUUID } from 'node:crypto' interface Frame { type?: string id?: string command?: string success?: boolean data?: unknown error?: { message?: string } [key: string]: unknown } interface ProbeRun { child: ChildProcessWithoutNullStreams frames: Frame[] rawLines: string[] stderrLines: string[] } interface MockRequest { model?: string stream: boolean roles: string[] toolNames: string[] toolProperties: Record containsImage: boolean messageShape: Array> } const STARTUP_WAIT_MS = 12_000 const COMMAND_WAIT_MS = 20_000 const SHUTDOWN_WAIT_MS = 1_500 const PROVIDER_ID = 'loopback-probe' const MODEL_ID = 'probe-model' const MODEL = `${PROVIDER_ID}/${MODEL_ID}` function delay(ms: number): Promise { return new Promise((resolveDelay) => setTimeout(resolveDelay, ms)) } function makeIsolatedEnvironment(home: string, tempRoot: string, profile: string): NodeJS.ProcessEnv { const env: NodeJS.ProcessEnv = {} for (const name of ['PATH', 'SystemRoot', 'SystemDrive', 'ComSpec', 'WINDIR']) { const value = process.env[name] if (value) env[name] = value } const root = parse(home).root env.USERPROFILE = home env.HOME = home env.HOMEDRIVE = root.replace(/[\\/]$/, '') env.HOMEPATH = relative(root, home).replaceAll('/', '\\') env.APPDATA = join(home, 'AppData', 'Roaming') env.LOCALAPPDATA = join(home, 'AppData', 'Local') env.TEMP = tempRoot env.TMP = tempRoot env.OMP_PROFILE = profile return env } function summarizeFrame(frame: Frame): Record { const data = frame.data const dataKeys = data && typeof data === 'object' ? Object.keys(data as object) : [] const event = frame.assistantMessageEvent const eventKeys = event && typeof event === 'object' ? Object.keys(event as object) : [] return { type: frame.type, frameKeys: Object.keys(frame), id: frame.id, command: frame.command, success: frame.success, status: frame.status, assistantEventType: event && typeof event === 'object' ? (event as Record).type : undefined, toolCallId: frame.toolCallId, toolName: frame.toolName, argumentKeys: frame.arguments && typeof frame.arguments === 'object' ? Object.keys(frame.arguments as object) : [], eventKeys, dataKeys, error: typeof frame.error === 'string' ? frame.error : frame.error?.message } } async function waitForFrame( run: ProbeRun, predicate: (frame: Frame) => boolean, timeoutMs = COMMAND_WAIT_MS, startIndex = 0 ): Promise { const deadline = Date.now() + timeoutMs while (Date.now() < deadline) { const frame = run.frames.slice(startIndex).find(predicate) if (frame) return frame if (run.child.exitCode !== null || run.child.signalCode !== null) break await delay(20) } return run.frames.slice(startIndex).find(predicate) ?? null } async function sendCommand(run: ProbeRun, id: string, command: Record): Promise { run.child.stdin.write(`${JSON.stringify({ id, ...command })}\n`) return waitForFrame(run, (frame) => frame.id === id) } async function sendLooseCommand(run: ProbeRun, id: string, command: Record): Promise { const startIndex = run.frames.length run.child.stdin.write(`${JSON.stringify({ id, ...command })}\n`) return waitForFrame(run, (frame) => frame.id === id || frame.type === 'response', 3_000, startIndex) } async function installProbeHostTool(run: ProbeRun, suffix: string): Promise { return sendCommand(run, `probe-host-tools-${suffix}`, { type: 'set_host_tools', tools: [{ name: 'read', label: 'Read local probe file', description: 'Read a file from the isolated protocol probe directory.', parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'], additionalProperties: false } }] }) } async function stopRun(run: ProbeRun): Promise { try { run.child.stdin.end() } catch { // The sidecar may already have exited. } await new Promise((resolveExit) => { if (run.child.exitCode !== null || run.child.signalCode !== null) { resolveExit() return } let settled = false const finish = (): void => { if (settled) return settled = true clearTimeout(timer) resolveExit() } const timer = setTimeout(finish, SHUTDOWN_WAIT_MS) run.child.once('exit', finish) }) if (run.child.exitCode === null && run.child.signalCode === null) { run.child.kill() await new Promise((resolveExit) => { if (run.child.exitCode !== null || run.child.signalCode !== null) resolveExit() else run.child.once('exit', () => resolveExit()) }) } } async function startRun( exePath: string, tempRoot: string, profile: string, home: string, sessionDir: string, sessionPath: string ): Promise { const args = [ '--mode', 'rpc', '--profile', profile, '--cwd', tempRoot, `--model=${MODEL}`, '--no-tools', `--session-dir=${sessionDir}`, `--resume=${sessionPath}`, '--no-ui', '--no-title', '--no-extensions', '--no-skills', '--no-rules', '--no-lsp' ] const child = spawn(exePath, args, { cwd: tempRoot, env: makeIsolatedEnvironment(home, tempRoot, profile), windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'] }) const run: ProbeRun = { child, frames: [], rawLines: [], stderrLines: [] } const stdout = createInterface({ input: child.stdout }) const stderr = createInterface({ input: child.stderr }) stdout.on('line', (line) => { run.rawLines.push(line) try { run.frames.push(JSON.parse(line) as Frame) } catch { // Keep non-JSON stdout separate from RPC frames. } }) stderr.on('line', (line) => run.stderrLines.push(line)) const ready = await waitForFrame(run, (frame) => frame.type === 'ready', STARTUP_WAIT_MS) if (!ready) { const stderrText = run.stderrLines.join('\n').slice(0, 1000) await stopRun(run) throw new Error(`OMP did not send ready; exit=${child.exitCode}; stderr=${stderrText}`) } return run } async function readRequest(request: IncomingMessage): Promise> { const chunks: Buffer[] = [] for await (const chunk of request) chunks.push(Buffer.from(chunk)) try { return JSON.parse(Buffer.concat(chunks).toString('utf8')) as Record } catch { return {} } } function toolNamesFrom(body: Record): string[] { if (!Array.isArray(body.tools)) return [] return body.tools.flatMap((tool) => { if (!tool || typeof tool !== 'object') return [] const item = tool as Record const fn = item.function && typeof item.function === 'object' ? item.function as Record : null const name = fn?.name ?? item.name return typeof name === 'string' ? [name] : [] }) } function requestHasImage(body: Record): boolean { if (!Array.isArray(body.messages)) return false return body.messages.some((message) => { if (!message || typeof message !== 'object') return false const content = (message as Record).content return Array.isArray(content) && content.some((part) => { if (!part || typeof part !== 'object') return false const item = part as Record return item.type === 'image_url' || item.type === 'image' }) }) } async function startMockProvider(probeFile: string): Promise<{ server: ReturnType url: string requests: MockRequest[] toolCallSent: boolean }> { const requests: MockRequest[] = [] let toolCallSent = false const server = createServer(async (request, response) => { const body = await readRequest(request) const names = toolNamesFrom(body) const messages = Array.isArray(body.messages) ? body.messages as Array> : [] const latestUser = [...messages].reverse().find((message) => message.role === 'user') const latestUserContent = Array.isArray(latestUser?.content) ? (latestUser.content as Array>).map((block) => block.text).filter((value) => typeof value === 'string').join('\n') : typeof latestUser?.content === 'string' ? latestUser.content : '' const toolProperties: Record = {} for (const tool of Array.isArray(body.tools) ? body.tools : []) { if (!tool || typeof tool !== 'object') continue const item = tool as Record const fn = item.function && typeof item.function === 'object' ? item.function as Record : null const name = fn?.name ?? item.name const parameters = fn?.parameters && typeof fn.parameters === 'object' ? fn.parameters as Record : null const properties = parameters?.properties && typeof parameters.properties === 'object' ? parameters.properties as Record : null if (typeof name === 'string') toolProperties[name] = Object.keys(properties ?? {}) } requests.push({ model: typeof body.model === 'string' ? body.model : undefined, stream: body.stream === true, roles: messages.map((message) => typeof message.role === 'string' ? message.role : '?'), toolNames: names, toolProperties, containsImage: requestHasImage(body), messageShape: messages.map((message) => { const content = message.content const blocks = Array.isArray(content) ? content as Array> : [] const calls = Array.isArray(message.tool_calls) ? message.tool_calls as Array> : [] return { role: message.role, contentType: typeof content === 'string' ? 'string' : Array.isArray(content) ? 'array' : typeof content, contentBlockTypes: blocks.map((block) => block.type), toolCallCount: calls.length, hasToolCallId: typeof message.tool_call_id === 'string' } }) }) const readToolName = names.find((name) => name === 'read' || name.endsWith('__read')) const sendReadTool = latestUserContent.includes('TOOL_PROBE') && readToolName && !toolCallSent const completion = sendReadTool ? { message: { role: 'assistant', content: '', tool_calls: [{ id: 'call_local_read_1', type: 'function', function: { name: readToolName, arguments: JSON.stringify({ path: probeFile }) } }] }, finish_reason: 'tool_calls' } : { message: { role: 'assistant', content: 'PROBE_OK' }, finish_reason: 'stop' } if (sendReadTool) toolCallSent = true if (body.stream === true) { response.writeHead(200, { 'content-type': 'text/event-stream', 'cache-control': 'no-cache', connection: 'keep-alive' }) const completionId = `chatcmpl-local-${requests.length}` const writeChunk = (delta: Record, finishReason: string | null = null): void => { response.write(`data: ${JSON.stringify({ id: completionId, object: 'chat.completion.chunk', created: Math.floor(Date.now() / 1000), model: MODEL_ID, choices: [{ index: 0, delta, finish_reason: finishReason }] })}\n\n`) } writeChunk({ role: 'assistant' }) if (sendReadTool) { writeChunk({ tool_calls: [{ index: 0, id: 'call_local_read_1', type: 'function', function: { name: readToolName, arguments: JSON.stringify({ path: probeFile }) } }] }) writeChunk({}, 'tool_calls') } else { writeChunk({ reasoning_content: 'LOCAL_THINKING_PROBE' }) writeChunk({ content: 'PROBE_OK' }) writeChunk({}, 'stop') } response.write(`data: ${JSON.stringify({ id: completionId, object: 'chat.completion.chunk', created: Math.floor(Date.now() / 1000), model: MODEL_ID, choices: [], usage: { prompt_tokens: 17, completion_tokens: 4, total_tokens: 21 } })}\n\n`) response.end('data: [DONE]\n\n') return } response.writeHead(200, { 'content-type': 'application/json' }) response.end(JSON.stringify({ id: `chatcmpl-local-${requests.length}`, object: 'chat.completion', created: Math.floor(Date.now() / 1000), model: MODEL_ID, choices: [{ index: 0, ...completion }], usage: { prompt_tokens: 17, completion_tokens: 4, total_tokens: 21 } })) }) await new Promise((resolveListen, reject) => { server.once('error', reject) server.listen(0, '127.0.0.1', () => { server.off('error', reject) resolveListen() }) }) const address = server.address() if (!address || typeof address === 'string') throw new Error('Loopback mock did not bind to a TCP port') return { server, url: `http://127.0.0.1:${address.port}/v1`, requests, get toolCallSent() { return toolCallSent } } } function summarizeMessages(data: unknown): Record { const root = data && typeof data === 'object' ? data as Record : {} const possibleArrays = [root.messages, root.entries, root.branchMessages] const records = possibleArrays.find(Array.isArray) as unknown[] | undefined const items = (records ?? (Array.isArray(data) ? data : [])).map((value) => { if (!value || typeof value !== 'object') return { valueType: typeof value } const item = value as Record const message = item.message && typeof item.message === 'object' ? item.message as Record : item const content = Array.isArray(message.content) ? message.content as Array> : [] const toolCalls = Array.isArray(message.toolCalls) ? message.toolCalls as Array> : Array.isArray(message.tool_calls) ? message.tool_calls as Array> : [] return { itemKeys: Object.keys(item), messageKeys: Object.keys(message), type: item.type, role: message.role, entryId: item.entryId ?? item.id, parentId: item.parentId, textBlocks: content.filter((block) => block.type === 'text').length, toolCalls: toolCalls.map((call) => call.name ?? (call.function as Record | undefined)?.name) } }) return { keys: Object.keys(root), count: items.length, messages: items } } async function inspectTranscript(path: string): Promise> { try { const text = await readFile(path, 'utf8') const records = text.split(/\r?\n/).filter(Boolean).map((record) => JSON.parse(record) as Record) return { exists: true, records: records.length, entries: records.map((record) => ({ type: record.type, id: record.id, parentId: record.parentId, messageRole: (record.message as Record | undefined)?.role, toolName: (record.toolName ?? (record.message as Record | undefined)?.toolName) ?? undefined })) } } catch { return { exists: false } } } function summarizeTree(value: unknown): unknown { if (Array.isArray(value)) return value.map(summarizeTree) if (!value || typeof value !== 'object') return value const node = value as Record const entry = node.entry && typeof node.entry === 'object' ? node.entry as Record : node const message = entry.message && typeof entry.message === 'object' ? entry.message as Record : null return { nodeKeys: Object.keys(node), entryKeys: Object.keys(entry), id: entry.id, parentId: entry.parentId, type: entry.type, role: message?.role, label: node.label, children: Array.isArray(node.children) ? summarizeTree(node.children) : [] } } function findUserNode(value: unknown): { id: string; parentId: string | null } | null { if (Array.isArray(value)) { let latest: { id: string; parentId: string | null } | null = null for (const child of value) { const found = findUserNode(child) if (found) latest = found } return latest } if (!value || typeof value !== 'object') return null const node = value as Record const entry = node.entry && typeof node.entry === 'object' ? node.entry as Record : node const message = entry.message && typeof entry.message === 'object' ? entry.message as Record : null let latest: { id: string; parentId: string | null } | null = null if (entry.type === 'message' && message?.role === 'user' && typeof entry.id === 'string') { latest = { id: entry.id, parentId: typeof entry.parentId === 'string' ? entry.parentId : null } } const child = Array.isArray(node.children) ? findUserNode(node.children) : null return child ?? latest } async function main(): Promise { const exePath = process.env.YCODE_OMP_PATH ?? 'C:\\Users\\Administrator\\AppData\\Local\\Ygent\\omp.exe' if (!existsSync(exePath)) throw new Error(`OMP executable not found: ${exePath}`) const tempRoot = await mkdtemp(join(tmpdir(), 'ycode-omp-protocol-')) const home = join(tempRoot, 'isolated-home') const sessionDir = join(tempRoot, 'sessions') const profile = `protocol-probe-${randomUUID().replaceAll('-', '')}` const sessionPath = join(sessionDir, 'probe.jsonl') const probeFile = join(tempRoot, 'probe.txt') const realProfilePath = resolve(homedir(), '.omp', 'profiles', profile) const realProfileRoot = resolve(homedir(), '.omp', 'profiles') const realProfileRelative = relative(realProfileRoot, realProfilePath) if (!realProfileRelative || realProfileRelative.startsWith(`..${sep}`) || resolve(realProfileRoot, realProfileRelative) !== realProfilePath) { throw new Error('Refusing an invalid isolated profile path') } if (existsSync(realProfilePath)) throw new Error('The unique probe profile unexpectedly exists') await mkdir(home, { recursive: true }) await mkdir(sessionDir, { recursive: true }) await writeFile(probeFile, 'LOCAL_OMP_TOOL_SENTINEL\n', 'utf8') const profileAgentDir = join(home, '.omp', 'profiles', profile, 'agent') await mkdir(profileAgentDir, { recursive: true }) const mock = await startMockProvider(probeFile) await writeFile(join(profileAgentDir, 'models.yml'), [ 'providers:', ` ${PROVIDER_ID}:`, ` baseUrl: ${mock.url}`, ' api: openai-completions', ' auth: none', ' models:', ` - id: ${MODEL_ID}`, ' name: Local protocol probe', ' reasoning: true', ' input: [text, image]', ' contextWindow: 64000', ' maxTokens: 4096', '' ].join('\n'), 'utf8') const runs: ProbeRun[] = [] try { const version = spawnSync(exePath, ['--version'], { encoding: 'utf8', windowsHide: true, env: makeIsolatedEnvironment(home, tempRoot, profile) }) if (version.error) throw version.error if (version.status !== 0) throw new Error(`OMP --version failed with exit ${version.status}`) console.log(`OMP_VERSION: ${version.stdout.trim()}`) console.log(`ISOLATED_PROFILE: ${profile}`) console.log('PROVIDER_NETWORK: loopback only') const first = await startRun(exePath, tempRoot, profile, home, sessionDir, sessionPath) runs.push(first) const hostToolsFirst = await installProbeHostTool(first, '1') const stateResponse = await sendCommand(first, 'probe-state-1', { type: 'get_state' }) console.log(`FIRST_READY: true`) console.log(`SET_HOST_TOOLS: ${JSON.stringify(hostToolsFirst ? summarizeFrame(hostToolsFirst) : null)}`) console.log(`GET_STATE: ${JSON.stringify(stateResponse ? summarizeFrame(stateResponse) : null)}`) const image = { type: 'image' as const, data: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/dXEAAAAASUVORK5CYII=', mimeType: 'image/png' } first.child.stdin.write(`${JSON.stringify({ id: 'probe-prompt-1', type: 'prompt', message: 'Reply exactly PROBE_OK.', images: [image] })}\n`) const promptAck = await waitForFrame(first, (frame) => frame.id === 'probe-prompt-1') const turnDone = await waitForFrame(first, (frame) => frame.type === 'agent_end' || (frame.type === 'prompt_result' && frame.agentInvoked === false), 60_000) const toolTurnStart = first.frames.length first.child.stdin.write(`${JSON.stringify({ id: 'probe-prompt-tool', type: 'prompt', message: 'TOOL_PROBE: call the read tool for probe.txt, then reply exactly PROBE_OK.' })}\n`) const toolPromptAck = await waitForFrame(first, (frame) => frame.id === 'probe-prompt-tool') const hostToolCall = await waitForFrame(first, (frame) => frame.type === 'host_tool_call', 20_000, toolTurnStart) let hostToolResponseSent = false if (hostToolCall && typeof hostToolCall.id === 'string') { const args = hostToolCall.arguments && typeof hostToolCall.arguments === 'object' ? hostToolCall.arguments as Record : {} const targetPath = typeof args.path === 'string' ? args.path : probeFile const content = await readFile(targetPath, 'utf8') first.child.stdin.write(`${JSON.stringify({ type: 'host_tool_result', id: hostToolCall.id, result: { content: [{ type: 'text', text: content }] } })}\n`) hostToolResponseSent = true } const toolTurnDone = await waitForFrame(first, (frame) => frame.type === 'agent_end' || (frame.type === 'prompt_result' && frame.agentInvoked === false), 60_000, toolTurnStart) const getMessages = await sendCommand(first, 'probe-messages-1', { type: 'get_messages' }) const tree = await sendCommand(first, 'probe-tree-1', { type: 'get_tree' }) const branchMessages = await sendCommand(first, 'probe-branch-messages-1', { type: 'get_branch_messages' }) const stateAfterTurn = await sendCommand(first, 'probe-state-after-turn', { type: 'get_state' }) const entries = getMessages ? summarizeMessages(getMessages.data) : null const treeData = tree?.data && typeof tree.data === 'object' ? tree.data as Record : {} const treeShape = summarizeTree(treeData.tree) const firstUserNode = findUserNode(treeData.tree) const fork = firstUserNode ? await sendCommand(first, 'probe-fork-before-branch', { type: 'fork', entryId: firstUserNode.id }) : null const stateAfterFork = await sendCommand(first, 'probe-state-after-fork', { type: 'get_state' }) const forkStateData = stateAfterFork?.data && typeof stateAfterFork.data === 'object' ? stateAfterFork.data as Record : {} const forkSessionPath = typeof forkStateData.sessionFile === 'string' ? forkStateData.sessionFile : '' const relativeForkSession = forkSessionPath ? relative(tempRoot, resolve(forkSessionPath)) : '' const forkSessionInProbe = !!relativeForkSession && relativeForkSession !== '..' && !relativeForkSession.startsWith(`..${sep}`) && !relativeForkSession.startsWith(sep) const messagesAfterFork = await sendCommand(first, 'probe-messages-after-fork', { type: 'get_messages' }) const treeAfterFork = await sendCommand(first, 'probe-tree-after-fork', { type: 'get_tree' }) const treeAfterForkData = treeAfterFork?.data && typeof treeAfterFork.data === 'object' ? treeAfterFork.data as Record : {} const forkData = fork?.data && typeof fork.data === 'object' ? fork.data as Record : {} const stateBeforeForkData = stateAfterTurn?.data && typeof stateAfterTurn.data === 'object' ? stateAfterTurn.data as Record : {} console.log(`PROMPT_ACK: ${JSON.stringify(promptAck ? summarizeFrame(promptAck) : null)}`) console.log(`FIRST_TURN_END_FRAME: ${JSON.stringify(turnDone ? summarizeFrame(turnDone) : null)}`) console.log(`TOOL_PROMPT_ACK: ${JSON.stringify(toolPromptAck ? summarizeFrame(toolPromptAck) : null)}`) console.log(`HOST_TOOL_CALL: ${JSON.stringify(hostToolCall ? summarizeFrame(hostToolCall) : null)}`) console.log(`HOST_TOOL_RESPONSE_SENT: ${hostToolResponseSent}`) console.log(`TOOL_TURN_END_FRAME: ${JSON.stringify(toolTurnDone ? summarizeFrame(toolTurnDone) : null)}`) console.log(`TURN_EVENTS: ${JSON.stringify(first.frames.filter((frame) => [ 'agent_start', 'message_start', 'message_update', 'message_end', 'tool_execution_start', 'tool_execution_update', 'tool_execution_end', 'host_tool_call', 'turn_start', 'turn_end', 'agent_end', 'prompt_result' ].includes(frame.type ?? '')).map(summarizeFrame))}`) console.log(`GET_MESSAGES_RESPONSE: ${JSON.stringify(getMessages ? { ...summarizeFrame(getMessages), dataSummary: entries } : null)}`) console.log(`GET_TREE_RESPONSE: ${JSON.stringify(tree ? { ...summarizeFrame(tree), leafId: treeData.leafId, tree: treeShape } : null)}`) console.log(`GET_BRANCH_MESSAGES_RESPONSE: ${JSON.stringify(branchMessages ? { ...summarizeFrame(branchMessages), dataSummary: summarizeMessages(branchMessages.data) } : null)}`) console.log(`FORK_ENTRY_ID: ${JSON.stringify(firstUserNode?.id ?? null)}`) console.log(`FORK_RESPONSE: ${JSON.stringify(fork ? summarizeFrame(fork) : null)}`) console.log(`FORK_CANCELLED: ${forkData.cancelled}`) console.log(`FORK_STATE: ${JSON.stringify(stateAfterFork ? summarizeFrame(stateAfterFork) : null)}`) console.log(`FORK_SESSION_ID_CHANGED: ${stateBeforeForkData.sessionId !== forkStateData.sessionId}`) console.log(`FORK_SESSION_FILE_IS_PROBE_PATH: ${forkSessionInProbe}`) console.log(`FORK_SESSION_FILE_EXISTS: ${forkSessionInProbe ? existsSync(forkSessionPath) : false}`) console.log(`FORK_MESSAGES: ${JSON.stringify(messagesAfterFork ? summarizeMessages(messagesAfterFork.data) : null)}`) console.log(`FORK_TREE_LEAF_ID: ${treeAfterForkData.leafId ?? null}`) console.log(`TRANSCRIPT_AFTER_FORK: ${JSON.stringify(await inspectTranscript(sessionPath))}`) console.log(`STATE_AFTER_TURN: ${JSON.stringify(stateAfterTurn ? summarizeFrame(stateAfterTurn) : null)}`) console.log(`LOOPBACK_REQUESTS: ${JSON.stringify(mock.requests)}`) console.log(`TOOL_CALL_SENT: ${mock.toolCallSent}`) console.log(`TRANSCRIPT_AFTER_TURN: ${JSON.stringify(await inspectTranscript(sessionPath))}`) await stopRun(first) const second = await startRun(exePath, tempRoot, profile, home, sessionDir, sessionPath) runs.push(second) const hostToolsSecond = await installProbeHostTool(second, '2') const resumedState = await sendCommand(second, 'probe-state-2', { type: 'get_state' }) const resumedMessages = await sendCommand(second, 'probe-messages-2', { type: 'get_messages' }) console.log(`RESUME_READY: true`) console.log(`RESUME_SET_HOST_TOOLS: ${JSON.stringify(hostToolsSecond ? summarizeFrame(hostToolsSecond) : null)}`) console.log(`RESUME_GET_STATE: ${JSON.stringify(resumedState ? summarizeFrame(resumedState) : null)}`) console.log(`RESUME_GET_MESSAGES: ${JSON.stringify(resumedMessages ? { ...summarizeFrame(resumedMessages), dataSummary: summarizeMessages(resumedMessages.data) } : null)}`) console.log(`RESUMED_TRANSCRIPT: ${JSON.stringify(await inspectTranscript(sessionPath))}`) let currentRun: ProbeRun = second const resumeTree = await sendCommand(second, 'probe-tree-2', { type: 'get_tree' }) const resumeTreeData = resumeTree?.data && typeof resumeTree.data === 'object' ? resumeTree.data as Record : {} const resumeUserNode = findUserNode(resumeTreeData.tree) console.log(`RESUME_TREE: ${JSON.stringify(resumeTree ? { ...summarizeFrame(resumeTree), leafId: resumeTreeData.leafId, tree: summarizeTree(resumeTreeData.tree) } : null)}`) if (resumeUserNode) { const branch = await sendCommand(second, 'probe-branch-1', { type: 'branch', entryId: resumeUserNode.id }) const branchState = await sendCommand(second, 'probe-state-after-branch', { type: 'get_state' }) const branchHistory = await sendCommand(second, 'probe-messages-after-branch', { type: 'get_messages' }) const branchTree = await sendCommand(second, 'probe-tree-after-branch', { type: 'get_tree' }) const branchTreeData = branchTree?.data && typeof branchTree.data === 'object' ? branchTree.data as Record : {} console.log(`BRANCH_ENTRY_ID: ${JSON.stringify(resumeUserNode.id)}`) console.log(`BRANCH_RESPONSE: ${JSON.stringify(branch ? summarizeFrame(branch) : null)}`) console.log(`BRANCH_STATE: ${JSON.stringify(branchState ? summarizeFrame(branchState) : null)}`) console.log(`BRANCH_MESSAGES: ${JSON.stringify(branchHistory ? { ...summarizeFrame(branchHistory), dataSummary: summarizeMessages(branchHistory.data) } : null)}`) console.log(`BRANCH_TREE: ${JSON.stringify(branchTree ? { ...summarizeFrame(branchTree), leafId: branchTreeData.leafId, tree: summarizeTree(branchTreeData.tree) } : null)}`) if (branch?.success === true) { await stopRun(second) const branchResume = await startRun(exePath, tempRoot, profile, home, sessionDir, sessionPath) runs.push(branchResume) currentRun = branchResume const resumeHostTools = await installProbeHostTool(branchResume, 'after-branch') const messagesAfterBranchResume = await sendCommand(branchResume, 'probe-messages-after-branch-resume', { type: 'get_messages' }) const treeAfterBranchResume = await sendCommand(branchResume, 'probe-tree-after-branch-resume', { type: 'get_tree' }) const treeAfterBranchResumeData = treeAfterBranchResume?.data && typeof treeAfterBranchResume.data === 'object' ? treeAfterBranchResume.data as Record : {} console.log(`BRANCH_RESUME_READY: true`) console.log(`BRANCH_RESUME_SET_HOST_TOOLS: ${JSON.stringify(resumeHostTools ? summarizeFrame(resumeHostTools) : null)}`) console.log(`BRANCH_RESUME_MESSAGES: ${JSON.stringify(messagesAfterBranchResume ? summarizeMessages(messagesAfterBranchResume.data) : null)}`) console.log(`BRANCH_RESUME_TREE_LEAF_ID: ${treeAfterBranchResumeData.leafId ?? null}`) console.log(`TRANSCRIPT_AFTER_BRANCH_WITHOUT_PROMPT: ${JSON.stringify(await inspectTranscript(sessionPath))}`) } const navigateTree = await sendLooseCommand(currentRun, 'probe-navigate-tree', { type: 'navigate_tree', entryId: resumeUserNode.id }) const navigateToEntry = await sendLooseCommand(currentRun, 'probe-navigate-to-entry', { type: 'navigate_to_entry', entryId: resumeUserNode.id }) const switchSession = await sendCommand(currentRun, 'probe-switch-session', { type: 'switch_session', sessionPath }) const clear = await sendLooseCommand(currentRun, 'probe-clear', { type: 'clear' }) console.log(`NAVIGATE_TREE_RESPONSE: ${JSON.stringify(navigateTree ? summarizeFrame(navigateTree) : null)}`) console.log(`NAVIGATE_TO_ENTRY_RESPONSE: ${JSON.stringify(navigateToEntry ? summarizeFrame(navigateToEntry) : null)}`) console.log(`SWITCH_SESSION_SAME_PATH: ${JSON.stringify(switchSession ? summarizeFrame(switchSession) : null)}`) console.log(`CLEAR_RESPONSE: ${JSON.stringify(clear ? summarizeFrame(clear) : null)}`) } else { console.log('BRANCH_ENTRY_ID: unavailable from get_tree parent relation') } await stopRun(currentRun) } finally { for (const run of runs) await stopRun(run) await new Promise((resolveClose) => mock.server.close(() => resolveClose())) await rm(tempRoot, { recursive: true, force: true }) if (existsSync(realProfilePath)) await rm(realProfilePath, { recursive: true, force: true }) } } main().catch((error: unknown) => { console.error('OMP protocol probe failed:', error) process.exitCode = 1 })