feat(code-mode): add ACP client engine (Layer 2 core)

Own the Agent Client Protocol client instead of shelling out to `acpx`, so code
mode can stream structured events (tool calls, diffs, plan) and surface live
permission requests. Headless acpx can't do live approvals (it only supports
--approve-all), which is why we drive the agent adapters ourselves.

- code-mode/acp/{agents,client,permission-broker,session-store,manager,types}.ts:
  headless engine driving the Claude/Codex ACP adapters; one warm session per chat
  with create-or-resume via session/load; approval policy (ask | auto-approve-reads
  | yolo) in the broker.
- claude-exec.ts: cross-platform claude resolver (Windows .cmd EINVAL fix + macOS/Linux
  GUI-PATH safety net) shared with the legacy acpx path in builtin-tools.ts.
- add @agentclientprotocol/sdk + claude/codex adapters to core.
This commit is contained in:
Gagancreates 2026-06-01 13:33:02 +05:30
parent caea83aecf
commit 99ef643c8e
11 changed files with 973 additions and 57 deletions

View file

@ -11,6 +11,9 @@
"test:watch": "vitest"
},
"dependencies": {
"@agentclientprotocol/claude-agent-acp": "^0.39.0",
"@agentclientprotocol/codex-acp": "^0.0.44",
"@agentclientprotocol/sdk": "^0.22.1",
"@ai-sdk/anthropic": "^2.0.63",
"@ai-sdk/google": "^2.0.53",
"@ai-sdk/openai": "^2.0.91",

View file

@ -1,7 +1,6 @@
import { z, ZodType } from "zod";
import * as path from "path";
import * as fs from "fs/promises";
import { existsSync, readFileSync } from "fs";
import { executeCommand, executeCommandAbortable } from "./command-executor.js";
import { resolveSkill, availableSkills } from "../assistant/skills/index.js";
import { executeTool, listServers, listTools } from "../../mcp/mcp.js";
@ -16,6 +15,7 @@ import { executeAction as executeComposioAction, isConfigured as isComposioConfi
import { CURATED_TOOLKITS, CURATED_TOOLKIT_SLUGS } from "@x/shared/dist/composio.js";
import { BrowserControlInputSchema, type BrowserControlInput } from "@x/shared/dist/browser-control.js";
import { BackgroundTaskSchema, TriggersSchema } from "@x/shared/dist/background-task.js";
import { resolveClaudeExeOnWindows } from "../../code-mode/acp/claude-exec.js";
// Inputs for the bg-task builtin tools. Reuse the canonical schema field
// descriptions; only `triggers` gets a tighter contextual override (the
@ -90,61 +90,6 @@ const LLMPARSE_MIME_TYPES: Record<string, string> = {
'.tiff': 'image/tiff',
};
// Windows-only workaround: the Claude ACP bridge spawns CLAUDE_CODE_EXECUTABLE
// without `shell: true`, and Node refuses to spawn .cmd files that way (EINVAL).
// When the LLM invokes acpx via executeCommand, pre-resolve claude's real .exe
// from the npm-shim layout and inject it via env so the bridge can spawn it.
function resolveClaudeExeOnWindows(): string | undefined {
// Candidate dirs = everything on PATH, plus well-known npm/pnpm/volta global
// bin dirs. Electron's runtime PATH can omit these even when the user's shell
// includes them, which would otherwise leave us unable to find claude.exe and
// force a fallback to claude.cmd (which Node refuses to spawn — EINVAL).
const home = process.env.USERPROFILE ?? '';
const appData = process.env.APPDATA || (home && path.join(home, 'AppData', 'Roaming'));
const localAppData = process.env.LOCALAPPDATA || (home && path.join(home, 'AppData', 'Local'));
const programFiles = process.env.ProgramFiles || 'C:\\Program Files';
const knownDirs = [
appData && path.join(appData, 'npm'),
localAppData && path.join(localAppData, 'npm'),
appData && path.join(appData, 'pnpm'),
localAppData && path.join(localAppData, 'pnpm'),
home && path.join(home, '.volta', 'bin'),
path.join(programFiles, 'nodejs'),
].filter(Boolean) as string[];
const pathDirs = (process.env.PATH ?? '').split(';').map((d) => d.trim()).filter(Boolean);
const seen = new Set<string>();
const candidates = [...pathDirs, ...knownDirs].filter((d) => {
const key = d.toLowerCase();
if (seen.has(key)) return false;
seen.add(key);
return true;
});
for (const dir of candidates) {
// Direct npm-shim layout: <dir>\node_modules\@anthropic-ai\claude-code\bin\claude.exe
const exeFromLayout = path.join(dir, 'node_modules', '@anthropic-ai', 'claude-code', 'bin', 'claude.exe');
if (existsSync(exeFromLayout)) return exeFromLayout;
// Otherwise parse the claude.cmd shim for the real exe path.
const cmdPath = path.join(dir, 'claude.cmd');
if (!existsSync(cmdPath)) continue;
try {
const content = readFileSync(cmdPath, 'utf-8');
const absMatch = content.match(/[A-Z]:[\\/][^\s"]*claude\.exe/i);
if (absMatch && existsSync(absMatch[0])) return absMatch[0];
const relMatch = content.match(/%~dp0[\\/]?([^\s"%]+claude\.exe)/i);
if (relMatch) {
const resolved = path.join(dir, relMatch[1]);
if (existsSync(resolved)) return resolved;
}
} catch {
// ignore shim parse failures
}
}
return undefined;
}
function envForCommand(command: string): NodeJS.ProcessEnv | undefined {
if (process.platform !== 'win32') return undefined;
if (!/\bacpx\b/.test(command)) return undefined;

View file

@ -0,0 +1,53 @@
import { createRequire } from 'module';
import * as path from 'path';
import type { CodingAgent } from './types.js';
import { resolveClaudeExecutable } from './claude-exec.js';
const require = createRequire(import.meta.url);
// The ACP adapter npm package that exposes each coding agent as an ACP server.
const ADAPTER_PACKAGE: Record<CodingAgent, string> = {
claude: '@agentclientprotocol/claude-agent-acp',
codex: '@agentclientprotocol/codex-acp',
};
export interface AgentLaunchSpec {
/** Executable to spawn — always `node` so we never hit the Windows .cmd EINVAL. */
command: string;
/** Args = [adapter entry script]. */
args: string[];
/** Extra env merged over process.env (e.g. CLAUDE_CODE_EXECUTABLE on Windows). */
env: NodeJS.ProcessEnv;
}
// Resolve the adapter's executable ENTRY (its `bin`, not its library `main`) to an
// absolute path so we can spawn it directly with `node <entry>`. createRequire lets
// us resolve workspace/pnpm-installed packages from this module's location.
function resolveAdapterEntry(pkg: string): string {
const pkgJsonPath = require.resolve(`${pkg}/package.json`);
const pkgDir = path.dirname(pkgJsonPath);
const pkgJson = require(`${pkg}/package.json`) as { bin?: string | Record<string, string> };
const bin = pkgJson.bin;
const rel = typeof bin === 'string' ? bin : bin ? Object.values(bin)[0] : undefined;
if (!rel) {
throw new Error(`ACP adapter ${pkg} has no bin entry to spawn`);
}
return path.join(pkgDir, rel);
}
export function getAgentLaunchSpec(agent: CodingAgent): AgentLaunchSpec {
const entry = resolveAdapterEntry(ADAPTER_PACKAGE[agent]);
const env: NodeJS.ProcessEnv = { ...process.env };
// Point the Claude adapter at the real claude executable. On Windows this is
// mandatory (Node can't spawn the .cmd shim — EINVAL); on macOS/Linux it's a
// PATH safety net for GUI launches. Resolver is a no-op when claude isn't found,
// leaving the adapter to do its own lookup. (Codex relies on PATH for now — wire
// an equivalent when we add Codex support.)
if (agent === 'claude' && !env.CLAUDE_CODE_EXECUTABLE) {
const exe = resolveClaudeExecutable();
if (exe) env.CLAUDE_CODE_EXECUTABLE = exe;
}
return { command: process.execPath, args: [entry], env };
}

View file

@ -0,0 +1,91 @@
import { execSync } from 'child_process';
import * as path from 'path';
import { existsSync, readFileSync } from 'fs';
import { commonInstallPaths } from '../status.js';
// Windows-only: Node refuses to spawn `.cmd` files without `shell: true` (EINVAL),
// and the Claude ACP adapter spawns its executable directly. So we pre-resolve
// claude's real `.exe` from the npm-shim layout. Also used by the legacy acpx path.
export function resolveClaudeExeOnWindows(): string | undefined {
// Candidate dirs = everything on PATH, plus well-known npm/pnpm/volta global
// bin dirs. Electron's runtime PATH can omit these even when the user's shell
// includes them, which would otherwise leave us unable to find claude.exe and
// force a fallback to claude.cmd (which Node refuses to spawn — EINVAL).
const home = process.env.USERPROFILE ?? '';
const appData = process.env.APPDATA || (home && path.join(home, 'AppData', 'Roaming'));
const localAppData = process.env.LOCALAPPDATA || (home && path.join(home, 'AppData', 'Local'));
const programFiles = process.env.ProgramFiles || 'C:\\Program Files';
const knownDirs = [
appData && path.join(appData, 'npm'),
localAppData && path.join(localAppData, 'npm'),
appData && path.join(appData, 'pnpm'),
localAppData && path.join(localAppData, 'pnpm'),
home && path.join(home, '.volta', 'bin'),
path.join(programFiles, 'nodejs'),
].filter(Boolean) as string[];
const pathDirs = (process.env.PATH ?? '').split(';').map((d) => d.trim()).filter(Boolean);
const seen = new Set<string>();
const candidates = [...pathDirs, ...knownDirs].filter((d) => {
const key = d.toLowerCase();
if (seen.has(key)) return false;
seen.add(key);
return true;
});
for (const dir of candidates) {
// Direct npm-shim layout: <dir>\node_modules\@anthropic-ai\claude-code\bin\claude.exe
const exeFromLayout = path.join(dir, 'node_modules', '@anthropic-ai', 'claude-code', 'bin', 'claude.exe');
if (existsSync(exeFromLayout)) return exeFromLayout;
// Otherwise parse the claude.cmd shim for the real exe path.
const cmdPath = path.join(dir, 'claude.cmd');
if (!existsSync(cmdPath)) continue;
try {
const content = readFileSync(cmdPath, 'utf-8');
const absMatch = content.match(/[A-Z]:[\\/][^\s"]*claude\.exe/i);
if (absMatch && existsSync(absMatch[0])) return absMatch[0];
const relMatch = content.match(/%~dp0[\\/]?([^\s"%]+claude\.exe)/i);
if (relMatch) {
const resolved = path.join(dir, relMatch[1]);
if (existsSync(resolved)) return resolved;
}
} catch {
// ignore shim parse failures
}
}
return undefined;
}
// macOS/Linux: find the real `claude` binary. Unlike Windows this isn't a spawn
// requirement (no .cmd problem) — it's a PATH safety net. Electron apps launched
// from the GUI (Dock/Finder) often don't inherit the login shell's PATH, so the
// spawned adapter may fail to find `claude`. We resolve the path here so the adapter
// can be pointed straight at it.
function resolveClaudeBinaryUnix(): string | undefined {
// Primary: a login shell sees the user's full PATH (~/.zprofile, nvm, homebrew, …).
try {
const out = execSync("/bin/sh -lc 'command -v claude'", { timeout: 5000, encoding: 'utf-8' }).trim();
if (out && existsSync(out)) return out;
} catch {
// not found on the login-shell PATH
}
// Fallback: scan well-known install locations directly.
for (const candidate of commonInstallPaths('claude')) {
if (existsSync(candidate)) return candidate;
}
return undefined;
}
let cached: string | undefined;
// Cross-platform: the real `claude` executable to hand the ACP adapter via
// CLAUDE_CODE_EXECUTABLE (the adapter prefers this env var on every OS). Returns
// undefined if it can't be found — callers then fall back to the adapter's own lookup.
// Cached on first success so we don't re-probe the shell on every cold start.
export function resolveClaudeExecutable(): string | undefined {
if (cached) return cached;
const resolved = process.platform === 'win32' ? resolveClaudeExeOnWindows() : resolveClaudeBinaryUnix();
if (resolved) cached = resolved;
return resolved;
}

View file

@ -0,0 +1,178 @@
import { spawn, type ChildProcess } from 'child_process';
import { Writable, Readable } from 'node:stream';
import fs from 'fs/promises';
import {
ClientSideConnection,
ndJsonStream,
PROTOCOL_VERSION,
type Client,
type RequestPermissionRequest,
type RequestPermissionResponse,
type SessionNotification,
type SessionUpdate,
type PromptResponse,
type ReadTextFileRequest,
type ReadTextFileResponse,
type WriteTextFileRequest,
type WriteTextFileResponse,
} from '@agentclientprotocol/sdk';
import type { CodingAgent, CodeRunEvent } from './types.js';
import type { PermissionBroker } from './permission-broker.js';
import { getAgentLaunchSpec } from './agents.js';
export interface AcpClientOptions {
agent: CodingAgent;
cwd: string;
broker: PermissionBroker;
onEvent: (event: CodeRunEvent) => void;
}
// Map a raw ACP session/update notification onto our small CodeRunEvent union.
function toEvent(update: SessionUpdate): CodeRunEvent {
switch (update.sessionUpdate) {
case 'agent_message_chunk':
case 'user_message_chunk': {
const c = update.content;
const role = update.sessionUpdate === 'user_message_chunk' ? 'user' : 'agent';
return { type: 'message', role, text: c.type === 'text' ? c.text : `[${c.type}]` };
}
case 'agent_thought_chunk':
return { type: 'thought' };
case 'tool_call':
return {
type: 'tool_call',
id: update.toolCallId,
title: update.title,
kind: update.kind ?? undefined,
status: update.status ?? undefined,
};
case 'tool_call_update': {
const diffs = (update.content ?? [])
.filter((c): c is Extract<typeof c, { type: 'diff' }> => c.type === 'diff')
.map((c) => c.path);
return { type: 'tool_call_update', id: update.toolCallId, status: update.status ?? undefined, diffs };
}
case 'plan':
return {
type: 'plan',
entries: (update.entries ?? []).map((e) => ({
content: e.content,
status: e.status ?? undefined,
priority: e.priority ?? undefined,
})),
};
default:
return { type: 'other', sessionUpdate: update.sessionUpdate };
}
}
// Owns one spawned adapter process + ACP connection. Stateless about sessions —
// the manager decides whether to newSession or loadSession.
//
// The connection is long-lived and reused across follow-up prompts, but each prompt
// may stream to a different message's UI, so broker + onEvent are swappable via
// setHandlers() rather than fixed at construction.
export class AcpClient {
readonly agent: CodingAgent;
readonly cwd: string;
private broker: PermissionBroker;
private onEvent: (event: CodeRunEvent) => void;
private child?: ChildProcess;
private connection?: ClientSideConnection;
private loadSession_ = false;
constructor(opts: AcpClientOptions) {
this.agent = opts.agent;
this.cwd = opts.cwd;
this.broker = opts.broker;
this.onEvent = opts.onEvent;
}
get loadSupported(): boolean {
return this.loadSession_;
}
// Re-point the live connection at a new prompt's broker / event sink.
setHandlers(broker: PermissionBroker, onEvent: (event: CodeRunEvent) => void): void {
this.broker = broker;
this.onEvent = onEvent;
}
// Spawn the adapter and negotiate the protocol. Returns once initialized.
async start(): Promise<void> {
const spec = getAgentLaunchSpec(this.agent);
const child = spawn(spec.command, spec.args, {
cwd: this.cwd,
env: spec.env,
stdio: ['pipe', 'pipe', 'inherit'],
});
this.child = child;
const stream = ndJsonStream(
Writable.toWeb(child.stdin!) as WritableStream<Uint8Array>,
Readable.toWeb(child.stdout!) as ReadableStream<Uint8Array>,
);
const client = this.buildClient();
this.connection = new ClientSideConnection(() => client, stream);
const init = await this.connection.initialize({
protocolVersion: PROTOCOL_VERSION,
clientCapabilities: { fs: { readTextFile: true, writeTextFile: true } },
});
this.loadSession_ = init.agentCapabilities?.loadSession === true;
}
async newSession(): Promise<string> {
const res = await this.conn().newSession({ cwd: this.cwd, mcpServers: [] });
return res.sessionId;
}
async loadSession(sessionId: string): Promise<void> {
await this.conn().loadSession({ sessionId, cwd: this.cwd, mcpServers: [] });
}
async prompt(sessionId: string, text: string): Promise<PromptResponse> {
return this.conn().prompt({ sessionId, prompt: [{ type: 'text', text }] });
}
async cancel(sessionId: string): Promise<void> {
await this.conn().cancel({ sessionId });
}
dispose(): void {
try {
this.child?.kill();
} catch {
// already gone
}
this.child = undefined;
this.connection = undefined;
}
private conn(): ClientSideConnection {
if (!this.connection) throw new Error('AcpClient not started');
return this.connection;
}
// The client side of ACP: the agent calls these on us. These read the CURRENT
// handlers off `self` so follow-up prompts can swap them via setHandlers().
private buildClient(): Client {
const self = this;
return {
async requestPermission(params: RequestPermissionRequest): Promise<RequestPermissionResponse> {
return self.broker.resolve(params);
},
async sessionUpdate(params: SessionNotification): Promise<void> {
self.onEvent(toEvent(params.update));
},
async readTextFile(params: ReadTextFileRequest): Promise<ReadTextFileResponse> {
const content = await fs.readFile(params.path, 'utf8');
return { content };
},
async writeTextFile(params: WriteTextFileRequest): Promise<WriteTextFileResponse> {
await fs.writeFile(params.path, params.content);
return {};
},
};
}
}

View file

@ -0,0 +1,103 @@
import type { ApprovalPolicy, CodeRunEvent, CodingAgent, PermissionAsk, PermissionDecision, RunPromptResult } from './types.js';
import { AcpClient } from './client.js';
import { PermissionBroker } from './permission-broker.js';
import { readStoredSession, writeStoredSession, clearStoredSession } from './session-store.js';
export interface RunPromptArgs {
runId: string;
agent: CodingAgent;
cwd: string;
prompt: string;
policy: ApprovalPolicy;
/** Called when the policy needs the user to decide (the "ask" path). */
ask: (ask: PermissionAsk) => Promise<PermissionDecision>;
/** Stream sink for this prompt's run. */
onEvent: (event: CodeRunEvent) => void;
}
interface ActiveRun {
client: AcpClient;
sessionId: string;
agent: CodingAgent;
cwd: string;
}
// Drives ACP coding sessions, one live connection per chat run. Reuses a warm
// connection for follow-up prompts in the same chat; resumes a persisted session
// (via session/load) on the first prompt after an app restart.
export class CodeModeManager {
private readonly runs = new Map<string, ActiveRun>();
async runPrompt(args: RunPromptArgs): Promise<RunPromptResult> {
const { runId, agent, cwd, prompt, policy, ask, onEvent } = args;
const broker = new PermissionBroker({
policy,
ask,
onResolved: (a, decision, auto) => onEvent({ type: 'permission', ask: a, decision, auto }),
});
const run = await this.ensureRun(runId, agent, cwd, broker, onEvent);
const res = await run.client.prompt(run.sessionId, prompt);
return { stopReason: res.stopReason, sessionId: run.sessionId };
}
async cancel(runId: string): Promise<void> {
const run = this.runs.get(runId);
if (run) await run.client.cancel(run.sessionId);
}
dispose(runId: string): void {
const run = this.runs.get(runId);
if (!run) return;
run.client.dispose();
this.runs.delete(runId);
}
disposeAll(): void {
for (const runId of [...this.runs.keys()]) this.dispose(runId);
}
// Reuse the warm connection if it matches; otherwise (cold start, or the user
// switched agent/cwd for this chat) build a fresh one and create-or-resume its session.
private async ensureRun(
runId: string,
agent: CodingAgent,
cwd: string,
broker: PermissionBroker,
onEvent: (event: CodeRunEvent) => void,
): Promise<ActiveRun> {
const existing = this.runs.get(runId);
if (existing && existing.agent === agent && existing.cwd === cwd) {
existing.client.setHandlers(broker, onEvent);
return existing;
}
if (existing) this.dispose(runId); // agent/cwd changed — start over
const client = new AcpClient({ agent, cwd, broker, onEvent });
await client.start();
const sessionId = await this.openSession(runId, agent, cwd, client);
const run: ActiveRun = { client, sessionId, agent, cwd };
this.runs.set(runId, run);
return run;
}
// Resume the persisted session for this chat when possible; else start a new one
// and persist its id so a later restart can resume it.
private async openSession(runId: string, agent: CodingAgent, cwd: string, client: AcpClient): Promise<string> {
const stored = await readStoredSession(runId);
if (stored && stored.agent === agent && stored.cwd === cwd && client.loadSupported) {
try {
await client.loadSession(stored.sessionId);
return stored.sessionId;
} catch {
// Stored session is stale/unloadable — fall through to a fresh one.
await clearStoredSession(runId);
}
}
const sessionId = await client.newSession();
await writeStoredSession({ runId, agent, cwd, sessionId });
return sessionId;
}
}

View file

@ -0,0 +1,91 @@
import type {
RequestPermissionRequest,
RequestPermissionResponse,
PermissionOption,
PermissionOptionKind,
} from '@agentclientprotocol/sdk';
import type { ApprovalPolicy, PermissionDecision, PermissionAsk } from './types.js';
// Tool kinds that don't mutate anything — eligible for `auto-approve-reads`.
const READ_KINDS = new Set(['read', 'search', 'fetch', 'think']);
function toAsk(request: RequestPermissionRequest): PermissionAsk {
const tc = request.toolCall;
const kind = tc.kind ?? undefined;
const title = tc.title ?? kind ?? 'Tool call';
return {
toolCallId: tc.toolCallId ?? undefined,
title,
kind,
isRead: kind ? READ_KINDS.has(kind) : false,
};
}
// Map a desired decision to one of the options the agent actually offered.
// Agents may offer only a subset (e.g. allow_once + reject_once, no allow_always),
// so we fall back within the same allow/reject family before giving up.
function pickOption(options: PermissionOption[], decision: PermissionDecision): PermissionOption | undefined {
const order: Record<PermissionDecision, PermissionOptionKind[]> = {
allow_always: ['allow_always', 'allow_once'],
allow_once: ['allow_once', 'allow_always'],
reject: ['reject_once', 'reject_always'],
};
for (const kind of order[decision]) {
const found = options.find((o) => o.kind === kind);
if (found) return found;
}
return undefined;
}
function selected(optionId: string): RequestPermissionResponse {
return { outcome: { outcome: 'selected', optionId } };
}
// A request's identity for "always allow" memory: prefer tool kind, else title.
function memoryKey(ask: PermissionAsk): string {
return ask.kind ? `kind:${ask.kind}` : `title:${ask.title}`;
}
export interface PermissionBrokerOptions {
policy: ApprovalPolicy;
// Called only when the policy can't decide on its own (the "ask" path).
ask: (ask: PermissionAsk) => Promise<PermissionDecision>;
// Notified of every resolved request so the engine can emit a stream event.
onResolved?: (ask: PermissionAsk, decision: PermissionDecision, auto: boolean) => void;
}
// Decides how to answer the agent's requestPermission calls. Holds per-session
// "always allow" memory so a one-time approval sticks for the rest of the run.
export class PermissionBroker {
private readonly opts: PermissionBrokerOptions;
private readonly alwaysAllow = new Set<string>();
constructor(opts: PermissionBrokerOptions) {
this.opts = opts;
}
async resolve(request: RequestPermissionRequest): Promise<RequestPermissionResponse> {
const ask = toAsk(request);
const key = memoryKey(ask);
const finish = (decision: PermissionDecision, auto: boolean): RequestPermissionResponse => {
if (decision === 'allow_always') this.alwaysAllow.add(key);
this.opts.onResolved?.(ask, decision, auto);
const opt = pickOption(request.options, decision);
// If the agent offered no matching option we fall back to its first one
// (don't deadlock the turn); decision precedence above keeps this rare.
return selected(opt?.optionId ?? request.options[0]?.optionId ?? '');
};
// 1. Sticky "always allow" from earlier this session.
if (this.alwaysAllow.has(key)) return finish('allow_always', true);
// 2. Policy-level auto decisions.
if (this.opts.policy === 'yolo') return finish('allow_always', true);
if (this.opts.policy === 'auto-approve-reads' && ask.isRead) return finish('allow_once', true);
// 3. Ask the user.
const decision = await this.opts.ask(ask);
return finish(decision, false);
}
}

View file

@ -0,0 +1,43 @@
import fs from 'fs/promises';
import path from 'path';
import { WorkDir } from '../../config/config.js';
import type { CodingAgent } from './types.js';
// One ACP session is pinned per chat run. We persist its sessionId (plus the agent
// and cwd it belongs to) so reopening the chat after an app restart can resume the
// same agent context via session/load instead of starting over.
export interface StoredSession {
runId: string;
agent: CodingAgent;
cwd: string;
sessionId: string;
}
function sessionFile(runId: string): string {
return path.join(WorkDir, 'config', `codesession-${runId}.json`);
}
export async function readStoredSession(runId: string): Promise<StoredSession | null> {
try {
const raw = await fs.readFile(sessionFile(runId), 'utf8');
const parsed = JSON.parse(raw) as StoredSession;
if (parsed && parsed.sessionId && parsed.agent && parsed.cwd) return parsed;
return null;
} catch {
return null;
}
}
export async function writeStoredSession(session: StoredSession): Promise<void> {
const file = sessionFile(session.runId);
await fs.mkdir(path.dirname(file), { recursive: true });
await fs.writeFile(file, JSON.stringify(session, null, 2));
}
export async function clearStoredSession(runId: string): Promise<void> {
try {
await fs.rm(sessionFile(runId), { force: true });
} catch {
// best effort
}
}

View file

@ -0,0 +1,43 @@
// Rowboat-facing types for the ACP code-mode engine. These are intentionally
// decoupled from the raw @agentclientprotocol/sdk schema so the IPC layer (Phase 2)
// and renderer (Phase 3) consume a small, stable surface instead of the full protocol.
export type CodingAgent = 'claude' | 'codex';
// How the permission broker answers an agent's requestPermission, before any
// per-tool "allow for this session" memory is applied.
// ask -> surface every gated action to the user
// auto-approve-reads -> silently allow read-only tool calls, ask for the rest
// yolo -> auto-approve everything (the safe, scoped equivalent of
// `claude --dangerously-skip-permissions` — our toggle, not a flag)
export type ApprovalPolicy = 'ask' | 'auto-approve-reads' | 'yolo';
// A user's decision for a single permission request.
export type PermissionDecision = 'allow_once' | 'allow_always' | 'reject';
// What we hand to the UI (Phase 3) when the agent asks for permission.
export interface PermissionAsk {
toolCallId?: string;
title: string;
kind?: string; // tool kind, e.g. "edit" | "execute" | "read"
/** Whether this looks like a read-only action (used by auto-approve-reads). */
isRead: boolean;
}
// Normalized stream events emitted for a coding run. The renderer renders these;
// the engine maps raw ACP session/update notifications onto this union.
export type CodeRunEvent =
// role distinguishes the agent's own output from replayed user turns
// (loadSession streams the whole prior conversation back on resume).
| { type: 'message'; role: 'agent' | 'user'; text: string }
| { type: 'thought' }
| { type: 'tool_call'; id?: string; title?: string; kind?: string; status?: string }
| { type: 'tool_call_update'; id?: string; status?: string; diffs: string[] }
| { type: 'plan'; entries: { content: string; status?: string; priority?: string }[] }
| { type: 'permission'; ask: PermissionAsk; decision: PermissionDecision | 'cancelled'; auto: boolean }
| { type: 'other'; sessionUpdate: string };
export interface RunPromptResult {
stopReason: string;
sessionId: string;
}

View file

@ -12,7 +12,7 @@ const execAsync = promisify(exec);
// We scan these directly because Electron's spawned shell sometimes doesn't
// inherit the user's full PATH (especially on macOS GUI launches, and even on
// Windows when global npm prefix isn't propagated to system PATH).
function commonInstallPaths(binary: string): string[] {
export function commonInstallPaths(binary: string): string[] {
const home = os.homedir();
if (process.platform === 'win32') {
const appData = process.env.APPDATA || path.join(home, 'AppData', 'Roaming');

366
apps/x/pnpm-lock.yaml generated
View file

@ -362,6 +362,15 @@ importers:
packages/core:
dependencies:
'@agentclientprotocol/claude-agent-acp':
specifier: ^0.39.0
version: 0.39.0(@anthropic-ai/sdk@0.100.1(zod@4.2.1))(@modelcontextprotocol/sdk@1.25.1(hono@4.11.3)(zod@4.2.1))
'@agentclientprotocol/codex-acp':
specifier: ^0.0.44
version: 0.0.44(zod@4.2.1)
'@agentclientprotocol/sdk':
specifier: ^0.22.1
version: 0.22.1(zod@4.2.1)
'@ai-sdk/anthropic':
specifier: ^2.0.63
version: 2.0.70(zod@4.2.1)
@ -489,6 +498,24 @@ importers:
packages:
'@agentclientprotocol/claude-agent-acp@0.39.0':
resolution: {integrity: sha512-+tCm5v32L0R3zE4qjZQowfO1L/zqvQ5FapmsMSIf4gawXfTf26CG5hgz99wARdo0zn20/1eP80gzx7PbZlSX9A==}
hasBin: true
'@agentclientprotocol/codex-acp@0.0.44':
resolution: {integrity: sha512-iHzFWKzJ0Z8I6yJCkuLZ+nb9mF2WYmfTcHFFvc7sU/awBsQmVBmpSOXOpZ+IK2Dy9cR3iRoML/B2/Wq2/zKBCA==}
hasBin: true
'@agentclientprotocol/sdk@0.21.1':
resolution: {integrity: sha512-ZTLH+o9QxcZDLX/9ww+W7C2iExnXFM+vD/uGFVSlR61Kzj9FaxUqBC6Rv/kwgA7qVWYUEI9c5ZNqCuO9PM4rKg==}
peerDependencies:
zod: ^3.25.0 || ^4.0.0
'@agentclientprotocol/sdk@0.22.1':
resolution: {integrity: sha512-DfqXtl/8gO9NImq094MTaCXEU2vkhh6v7q/kT+9UjZxUqj8hYaya2OjLVIqn16MzNHcXEpShTR2RIauLSYeDQQ==}
peerDependencies:
zod: ^3.25.0 || ^4.0.0
'@ai-sdk/anthropic@2.0.70':
resolution: {integrity: sha512-W3WjQlb0Ho+CVAQUvb8Rtk3hGS3Jlgy79ihY2H0yj2k4yU8XuxpQw0Oz+7JQsB47j+jlHhk7nUXtxhAeRg3S3Q==}
engines: {node: '>=18'}
@ -544,6 +571,67 @@ packages:
'@antfu/install-pkg@1.1.0':
resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
'@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.156':
resolution: {integrity: sha512-IkjcS9dqAUlD4Nb62L9AZtmAXCa+FV4ul8lIlyXXUprh3nlecbKsWOXVd/GORrzAhMmynJaX4+iV1JiutFKXUA==}
cpu: [arm64]
os: [darwin]
'@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.156':
resolution: {integrity: sha512-6PKi5fPmGRuzXu+Em/iwLmPG3mqg0hl92wcTU8fmChqyNtxhxsjCw7LTbdFqp/05o5NeZVVV4k3p7YUv5IFD6g==}
cpu: [x64]
os: [darwin]
'@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.156':
resolution: {integrity: sha512-R7KEVjxkR4rYgIQoHGBzwPdUJYxRTO8I4vHjRbMLH1eW4FS7BJvVs7ogfKR/NnHFBvMVqtC+l6jHLQv8bobUiw==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.156':
resolution: {integrity: sha512-H0Nfd41iw5isto9uQI1FlVSZ0eaDttr8rBpJMR25oK/mj3egMO5EmZ6aAxeeUYSLn2mSU50HA5VNxlGUE118TQ==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.156':
resolution: {integrity: sha512-/Q6WUizI6a+hqZZ6ElwRU0PEuFhOoN4v6CuU35HHbiZ/7uaocGht4A8ZIgK1Fw6wOGtZzGLbc00CA1OU1Zg8EA==}
cpu: [x64]
os: [linux]
libc: [musl]
'@anthropic-ai/claude-agent-sdk-linux-x64@0.3.156':
resolution: {integrity: sha512-ymhrdlbWoYvTACUdaGdhrEv+ZMfwXLsf0BRLkr/IvY5aqybP7URzWmmZGOtDQpqkT/8xu/UCGqUYH3woJwUxfg==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.156':
resolution: {integrity: sha512-5sAeNObQQrMy4NF9HwxewrMnU7mVxZDHh+/MfJVQSz0GSTvXQ6gOuRH8helMlfspoU6VOdekPxVLRooX/3foEw==}
cpu: [arm64]
os: [win32]
'@anthropic-ai/claude-agent-sdk-win32-x64@0.3.156':
resolution: {integrity: sha512-/PofeTWoiKgnWNSNk0wG4SsRn22GGLmnLhg2R94WcNhCRFOyOTmiZcYH2DBlWZBIRVTZDsSfa/Pl1DyPvYCGKw==}
cpu: [x64]
os: [win32]
'@anthropic-ai/claude-agent-sdk@0.3.156':
resolution: {integrity: sha512-6nM/Dj+VMds52UXJ2YaV4IKhYamlUqN0HtdDrFzYz5lvPMpDS935qD8YZDAUpy+ltdoD6PJMd1V/CKFY3/oWCQ==}
engines: {node: '>=18.0.0'}
peerDependencies:
'@anthropic-ai/sdk': '>=0.93.0'
'@modelcontextprotocol/sdk': ^1.29.0
zod: ^4.0.0
'@anthropic-ai/sdk@0.100.1':
resolution: {integrity: sha512-RANcEe7LpiLczkKGOwoXOTuFdPhuubS0i4xaAKOMpcqc55YO0mukgxppV7eygx3DXNjxWT6RYOLPyOy0aIAmwg==}
hasBin: true
peerDependencies:
zod: ^3.25.0 || ^4.0.0
peerDependenciesMeta:
zod:
optional: true
'@aws-crypto/crc32@5.2.0':
resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==}
engines: {node: '>=16.0.0'}
@ -1743,6 +1831,47 @@ packages:
resolution: {integrity: sha512-6gH/bLQJSJEg7OEpkH4wGQdA8KXHRbzL1YkGyUO12YNAgV3jxKy4K9kvfXj4+9T0OLug5k58cnPCKSSIKzp7pg==}
engines: {node: '>=8.0'}
'@openai/codex@0.128.0':
resolution: {integrity: sha512-+xp6ODmFfBNnexIWRHApEaPXot2j6gyM8A5we/5IS/uY4eYHj4arETct4hQ5M4eO+MK7JY3ZU4xhuobhlysr0A==}
engines: {node: '>=16'}
hasBin: true
'@openai/codex@0.128.0-darwin-arm64':
resolution: {integrity: sha512-w+6zohfHx/kHBdles/CyFKaY57u9I3nK8QI9+NrdwMliKA0b7xn13yblRNkMpe09j6vL1oAWoxYsMOQ/vjBGug==}
engines: {node: '>=16'}
cpu: [arm64]
os: [darwin]
'@openai/codex@0.128.0-darwin-x64':
resolution: {integrity: sha512-SDbn6fO22Puy8xmMIbZi4f2znMrUEPwABApke4mo+4ihaauwuVjeqzXvW5SPJz5ty/bG11/mSupQgReT7T8BBw==}
engines: {node: '>=16'}
cpu: [x64]
os: [darwin]
'@openai/codex@0.128.0-linux-arm64':
resolution: {integrity: sha512-+SvH73H60qvCXFuQGP/EsmR//s1hHMBR22PvJkXvM/hdnTIGucx+JqRUjAWdmmQ1IU6j3kgwVvdLW/6ICB+M6w==}
engines: {node: '>=16'}
cpu: [arm64]
os: [linux]
'@openai/codex@0.128.0-linux-x64':
resolution: {integrity: sha512-2lnSPA05CRRuKAzFW8BCmmNCSieDcToLwfC2ALLbBYilGLgzhRibjlDglK9F1BkEzfohSSWJu4PBbRu/aG60lQ==}
engines: {node: '>=16'}
cpu: [x64]
os: [linux]
'@openai/codex@0.128.0-win32-arm64':
resolution: {integrity: sha512-ECJvsqmYFdA9pn42xxK3Odp/G16AjmBW0BglX8L0PwPjqbstbmlew9bfHf7xvL+SNfNl4NmyotW0+RNo1phgaA==}
engines: {node: '>=16'}
cpu: [arm64]
os: [win32]
'@openai/codex@0.128.0-win32-x64':
resolution: {integrity: sha512-k3jmUAFrzkUtvjGTXvSKjQqJLLlzjxp/VoHJDYedgmXUn6j70HxK38IwapzmnYfiBiTuzETvGwjXHzZgzKjhoQ==}
engines: {node: '>=16'}
cpu: [x64]
os: [win32]
'@openrouter/ai-sdk-provider@1.5.4':
resolution: {integrity: sha512-xrSQPUIH8n9zuyYZR0XK7Ba0h2KsjJcMkxnwaYfmv13pKs3sDkjPzVPPhlhzqBGddHb5cFEwJ9VFuFeDcxCDSw==}
engines: {node: '>=18'}
@ -3057,6 +3186,9 @@ packages:
resolution: {integrity: sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==}
engines: {node: '>=18.0.0'}
'@stablelib/base64@1.0.1':
resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==}
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
@ -4060,6 +4192,10 @@ packages:
buffer@6.0.3:
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
bundle-name@4.1.0:
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
engines: {node: '>=18'}
bytes@3.1.2:
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
engines: {node: '>= 0.8'}
@ -4540,6 +4676,14 @@ packages:
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
default-browser-id@5.0.1:
resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==}
engines: {node: '>=18'}
default-browser@5.5.0:
resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==}
engines: {node: '>=18'}
defaults@1.0.4:
resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==}
@ -4551,6 +4695,10 @@ packages:
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
engines: {node: '>= 0.4'}
define-lazy-prop@3.0.0:
resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
engines: {node: '>=12'}
define-properties@1.2.1:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'}
@ -4592,6 +4740,10 @@ packages:
diff3@0.0.3:
resolution: {integrity: sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g==}
diff@8.0.4:
resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==}
engines: {node: '>=0.3.1'}
dingbat-to-unicode@1.0.1:
resolution: {integrity: sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==}
@ -4942,6 +5094,9 @@ packages:
fast-levenshtein@2.0.6:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
fast-sha256@1.3.0:
resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==}
fast-uri@3.1.0:
resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
@ -5541,6 +5696,11 @@ packages:
engines: {node: '>=8'}
hasBin: true
is-docker@3.0.0:
resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
hasBin: true
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
@ -5560,6 +5720,15 @@ packages:
is-hexadecimal@2.0.1:
resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==}
is-in-ssh@1.0.0:
resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==}
engines: {node: '>=20'}
is-inside-container@1.0.0:
resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
engines: {node: '>=14.16'}
hasBin: true
is-interactive@1.0.0:
resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==}
engines: {node: '>=8'}
@ -5617,6 +5786,10 @@ packages:
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
engines: {node: '>=8'}
is-wsl@3.1.1:
resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==}
engines: {node: '>=16'}
isarray@1.0.0:
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
@ -5677,6 +5850,10 @@ packages:
json-parse-even-better-errors@2.3.1:
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
json-schema-to-ts@3.1.1:
resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==}
engines: {node: '>=16'}
json-schema-traverse@0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
@ -6431,6 +6608,10 @@ packages:
oniguruma-to-es@4.3.4:
resolution: {integrity: sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==}
open@11.0.0:
resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==}
engines: {node: '>=20'}
open@7.4.2:
resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==}
engines: {node: '>=8'}
@ -6679,6 +6860,10 @@ packages:
engines: {node: '>=14.0.0'}
hasBin: true
powershell-utils@0.1.0:
resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==}
engines: {node: '>=20'}
preact@10.28.2:
resolution: {integrity: sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==}
@ -7097,6 +7282,10 @@ packages:
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
engines: {node: '>= 18'}
run-applescript@7.1.0:
resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==}
engines: {node: '>=18'}
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@ -7305,6 +7494,9 @@ packages:
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
standardwebhooks@1.0.0:
resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==}
statuses@2.0.2:
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
engines: {node: '>= 0.8'}
@ -7523,6 +7715,9 @@ packages:
trough@2.2.0:
resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}
ts-algebra@2.0.0:
resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==}
ts-api-utils@2.1.0:
resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
engines: {node: '>=18.12'}
@ -7827,6 +8022,10 @@ packages:
resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==}
engines: {node: '>=14.0.0'}
vscode-jsonrpc@8.2.1:
resolution: {integrity: sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==}
engines: {node: '>=14.0.0'}
vscode-languageserver-protocol@3.17.5:
resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==}
@ -7933,6 +8132,10 @@ packages:
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
wsl-utils@0.3.1:
resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==}
engines: {node: '>=20'}
x-is-array@0.1.0:
resolution: {integrity: sha512-goHPif61oNrr0jJgsXRfc8oqtYzvfiMJpTqwE7Z4y9uH+T3UozkGqQ4d2nX9mB9khvA8U2o/UbPOFjgC7hLWIA==}
@ -8028,6 +8231,33 @@ packages:
snapshots:
'@agentclientprotocol/claude-agent-acp@0.39.0(@anthropic-ai/sdk@0.100.1(zod@4.2.1))(@modelcontextprotocol/sdk@1.25.1(hono@4.11.3)(zod@4.2.1))':
dependencies:
'@agentclientprotocol/sdk': 0.22.1(zod@4.2.1)
'@anthropic-ai/claude-agent-sdk': 0.3.156(@anthropic-ai/sdk@0.100.1(zod@4.2.1))(@modelcontextprotocol/sdk@1.25.1(hono@4.11.3)(zod@4.2.1))(zod@4.2.1)
zod: 4.2.1
transitivePeerDependencies:
- '@anthropic-ai/sdk'
- '@modelcontextprotocol/sdk'
'@agentclientprotocol/codex-acp@0.0.44(zod@4.2.1)':
dependencies:
'@agentclientprotocol/sdk': 0.21.1(zod@4.2.1)
'@openai/codex': 0.128.0
diff: 8.0.4
open: 11.0.0
vscode-jsonrpc: 8.2.1
transitivePeerDependencies:
- zod
'@agentclientprotocol/sdk@0.21.1(zod@4.2.1)':
dependencies:
zod: 4.2.1
'@agentclientprotocol/sdk@0.22.1(zod@4.2.1)':
dependencies:
zod: 4.2.1
'@ai-sdk/anthropic@2.0.70(zod@4.2.1)':
dependencies:
'@ai-sdk/provider': 2.0.1
@ -8089,6 +8319,52 @@ snapshots:
package-manager-detector: 1.6.0
tinyexec: 1.0.2
'@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.156':
optional: true
'@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.156':
optional: true
'@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.156':
optional: true
'@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.156':
optional: true
'@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.156':
optional: true
'@anthropic-ai/claude-agent-sdk-linux-x64@0.3.156':
optional: true
'@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.156':
optional: true
'@anthropic-ai/claude-agent-sdk-win32-x64@0.3.156':
optional: true
'@anthropic-ai/claude-agent-sdk@0.3.156(@anthropic-ai/sdk@0.100.1(zod@4.2.1))(@modelcontextprotocol/sdk@1.25.1(hono@4.11.3)(zod@4.2.1))(zod@4.2.1)':
dependencies:
'@anthropic-ai/sdk': 0.100.1(zod@4.2.1)
'@modelcontextprotocol/sdk': 1.25.1(hono@4.11.3)(zod@4.2.1)
zod: 4.2.1
optionalDependencies:
'@anthropic-ai/claude-agent-sdk-darwin-arm64': 0.3.156
'@anthropic-ai/claude-agent-sdk-darwin-x64': 0.3.156
'@anthropic-ai/claude-agent-sdk-linux-arm64': 0.3.156
'@anthropic-ai/claude-agent-sdk-linux-arm64-musl': 0.3.156
'@anthropic-ai/claude-agent-sdk-linux-x64': 0.3.156
'@anthropic-ai/claude-agent-sdk-linux-x64-musl': 0.3.156
'@anthropic-ai/claude-agent-sdk-win32-arm64': 0.3.156
'@anthropic-ai/claude-agent-sdk-win32-x64': 0.3.156
'@anthropic-ai/sdk@0.100.1(zod@4.2.1)':
dependencies:
json-schema-to-ts: 3.1.1
standardwebhooks: 1.0.0
optionalDependencies:
zod: 4.2.1
'@aws-crypto/crc32@5.2.0':
dependencies:
'@aws-crypto/util': 5.2.0
@ -9819,6 +10095,33 @@ snapshots:
'@oozcitak/util@8.3.4': {}
'@openai/codex@0.128.0':
optionalDependencies:
'@openai/codex-darwin-arm64': '@openai/codex@0.128.0-darwin-arm64'
'@openai/codex-darwin-x64': '@openai/codex@0.128.0-darwin-x64'
'@openai/codex-linux-arm64': '@openai/codex@0.128.0-linux-arm64'
'@openai/codex-linux-x64': '@openai/codex@0.128.0-linux-x64'
'@openai/codex-win32-arm64': '@openai/codex@0.128.0-win32-arm64'
'@openai/codex-win32-x64': '@openai/codex@0.128.0-win32-x64'
'@openai/codex@0.128.0-darwin-arm64':
optional: true
'@openai/codex@0.128.0-darwin-x64':
optional: true
'@openai/codex@0.128.0-linux-arm64':
optional: true
'@openai/codex@0.128.0-linux-x64':
optional: true
'@openai/codex@0.128.0-win32-arm64':
optional: true
'@openai/codex@0.128.0-win32-x64':
optional: true
'@openrouter/ai-sdk-provider@1.5.4(ai@5.0.151(zod@4.2.1))(zod@4.2.1)':
dependencies:
'@openrouter/sdk': 0.1.27
@ -11301,6 +11604,8 @@ snapshots:
dependencies:
tslib: 2.8.1
'@stablelib/base64@1.0.1': {}
'@standard-schema/spec@1.1.0': {}
'@standard-schema/utils@0.3.0': {}
@ -12431,6 +12736,10 @@ snapshots:
base64-js: 1.5.1
ieee754: 1.2.1
bundle-name@4.1.0:
dependencies:
run-applescript: 7.1.0
bytes@3.1.2: {}
cacache@16.1.3:
@ -12925,6 +13234,13 @@ snapshots:
deep-is@0.1.4: {}
default-browser-id@5.0.1: {}
default-browser@5.5.0:
dependencies:
bundle-name: 4.1.0
default-browser-id: 5.0.1
defaults@1.0.4:
dependencies:
clone: 1.0.4
@ -12937,6 +13253,8 @@ snapshots:
es-errors: 1.3.0
gopd: 1.2.0
define-lazy-prop@3.0.0: {}
define-properties@1.2.1:
dependencies:
define-data-property: 1.1.4
@ -12971,6 +13289,8 @@ snapshots:
diff3@0.0.3: {}
diff@8.0.4: {}
dingbat-to-unicode@1.0.1: {}
dir-compare@4.2.0:
@ -13473,6 +13793,8 @@ snapshots:
fast-levenshtein@2.0.6: {}
fast-sha256@1.3.0: {}
fast-uri@3.1.0: {}
fast-xml-parser@5.2.5:
@ -14248,6 +14570,8 @@ snapshots:
is-docker@2.2.1: {}
is-docker@3.0.0: {}
is-extglob@2.1.1: {}
is-fullwidth-code-point@3.0.0: {}
@ -14260,6 +14584,12 @@ snapshots:
is-hexadecimal@2.0.1: {}
is-in-ssh@1.0.0: {}
is-inside-container@1.0.0:
dependencies:
is-docker: 3.0.0
is-interactive@1.0.0: {}
is-lambda@1.0.1: {}
@ -14310,6 +14640,10 @@ snapshots:
dependencies:
is-docker: 2.2.1
is-wsl@3.1.1:
dependencies:
is-inside-container: 1.0.0
isarray@1.0.0: {}
isarray@2.0.5: {}
@ -14378,6 +14712,11 @@ snapshots:
json-parse-even-better-errors@2.3.1: {}
json-schema-to-ts@3.1.1:
dependencies:
'@babel/runtime': 7.28.6
ts-algebra: 2.0.0
json-schema-traverse@0.4.1: {}
json-schema-traverse@1.0.0: {}
@ -15367,6 +15706,15 @@ snapshots:
regex: 6.1.0
regex-recursion: 6.0.2
open@11.0.0:
dependencies:
default-browser: 5.5.0
define-lazy-prop: 3.0.0
is-in-ssh: 1.0.0
is-inside-container: 1.0.0
powershell-utils: 0.1.0
wsl-utils: 0.3.1
open@7.4.2:
dependencies:
is-docker: 2.2.1
@ -15606,6 +15954,8 @@ snapshots:
dependencies:
commander: 9.5.0
powershell-utils@0.1.0: {}
preact@10.28.2: {}
prelude-ls@1.2.1: {}
@ -16189,6 +16539,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
run-applescript@7.1.0: {}
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
@ -16424,6 +16776,11 @@ snapshots:
stackback@0.0.2: {}
standardwebhooks@1.0.0:
dependencies:
'@stablelib/base64': 1.0.1
fast-sha256: 1.3.0
statuses@2.0.2: {}
std-env@4.1.0: {}
@ -16664,6 +17021,8 @@ snapshots:
trough@2.2.0: {}
ts-algebra@2.0.0: {}
ts-api-utils@2.1.0(typescript@5.9.3):
dependencies:
typescript: 5.9.3
@ -16965,6 +17324,8 @@ snapshots:
vscode-jsonrpc@8.2.0: {}
vscode-jsonrpc@8.2.1: {}
vscode-languageserver-protocol@3.17.5:
dependencies:
vscode-jsonrpc: 8.2.0
@ -17097,6 +17458,11 @@ snapshots:
wrappy@1.0.2: {}
wsl-utils@0.3.1:
dependencies:
is-wsl: 3.1.1
powershell-utils: 0.1.0
x-is-array@0.1.0: {}
x-is-string@0.1.0: {}