zidane 9.0.2 → 9.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/{auto-update-restart-B1ukz5-_.js → auto-update-restart-Z2v5vltT.js} +2 -2
  2. package/dist/{auto-update-restart-B1ukz5-_.js.map → auto-update-restart-Z2v5vltT.js.map} +1 -1
  3. package/dist/chat.js +3 -3
  4. package/dist/daemon-cli.js +2 -2
  5. package/dist/{engine-C_v7ggaM.js → engine-BmHe3jKd.js} +2 -2
  6. package/dist/{engine-C_v7ggaM.js.map → engine-BmHe3jKd.js.map} +1 -1
  7. package/dist/{executor-Bopn2Uvo.js → executor-CZFiV2LN.js} +2 -2
  8. package/dist/{executor-Bopn2Uvo.js.map → executor-CZFiV2LN.js.map} +1 -1
  9. package/dist/{extensions-4D_tk7i-.js → extensions-Bb_-1vbS.js} +2 -2
  10. package/dist/{extensions-4D_tk7i-.js.map → extensions-Bb_-1vbS.js.map} +1 -1
  11. package/dist/extensions.js +1 -1
  12. package/dist/{host-signals-DN6nZ4zd.js → host-signals-DnZhcbMy.js} +5 -5
  13. package/dist/{host-signals-DN6nZ4zd.js.map → host-signals-DnZhcbMy.js.map} +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/ipc/remote.js +3 -3
  16. package/dist/{ipc-BdazyU1h.js → ipc-DTeKfnHc.js} +4 -4
  17. package/dist/{ipc-BdazyU1h.js.map → ipc-DTeKfnHc.js.map} +1 -1
  18. package/dist/ipc.js +4 -4
  19. package/dist/{links-Mt4r43nD.js → links-DUeZy_2e.js} +6 -6
  20. package/dist/{links-Mt4r43nD.js.map → links-DUeZy_2e.js.map} +1 -1
  21. package/dist/{memory-Dj9Eeybi.js → memory-T-sU3COB.js} +7 -7
  22. package/dist/{memory-Dj9Eeybi.js.map → memory-T-sU3COB.js.map} +1 -1
  23. package/dist/{path-display-BKAU3XtR.js → path-display-vzEF03NO.js} +3 -3
  24. package/dist/{path-display-BKAU3XtR.js.map → path-display-vzEF03NO.js.map} +1 -1
  25. package/dist/{remote-hosts-Ep4bgs3F.js → remote-hosts-RbuohEaP.js} +4 -4
  26. package/dist/{remote-hosts-Ep4bgs3F.js.map → remote-hosts-RbuohEaP.js.map} +1 -1
  27. package/dist/{settings-keys-CEmlwy2H.js → settings-keys-BP0tJDVS.js} +2 -2
  28. package/dist/{settings-keys-CEmlwy2H.js.map → settings-keys-BP0tJDVS.js.map} +1 -1
  29. package/dist/tui.js +8 -8
  30. package/dist/{version-DzI0HMrv.js → version-_Yunc9lZ.js} +2 -2
  31. package/dist/{version-DzI0HMrv.js.map → version-_Yunc9lZ.js.map} +1 -1
  32. package/dist/{ws-client-BWiyy8Op.js → ws-client-CsAtVY0b.js} +2 -2
  33. package/dist/{ws-client-BWiyy8Op.js.map → ws-client-CsAtVY0b.js.map} +1 -1
  34. package/dist/{ws-Bv6QGsEr.js → ws-ks0slKdL.js} +3 -3
  35. package/dist/{ws-Bv6QGsEr.js.map → ws-ks0slKdL.js.map} +1 -1
  36. package/docs/SERVER.md +12 -0
  37. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"links-Mt4r43nD.js","names":[],"sources":["../src/ipc/machine/agent.ts","../src/ipc/machine/reverse.ts","../src/ipc/machine/share-lock.ts","../src/ipc/machine/links.ts"],"sourcesContent":["/**\n * The machine agent (`docs/MACHINES.md` § Machine side): dials its host with\n * a machine-scoped token, registers, and serves the host's execution\n * requests through the confined executor (`./executor.ts`) until stopped.\n * Reconnects with the hosts' connection loop (backoff; a refused token, a\n * different host at the URL, or a refused registration stop it for good).\n *\n * The CLI (`./cli.ts`, `zidane machine share`) runs it in the foreground;\n * `./links.ts` starts it from the local daemon / in-process host.\n */\nimport type { HostConnectionStatus } from '../../chat/hosts/connection'\nimport type { MachineRegisterParams, MachineRegisterResult, MachineRoot } from '../../chat/machine-protocol'\nimport type { IpcClient } from '../transports/client-core'\nimport type { MachineExecutor, MachineExecutorOptions } from './executor'\nimport { mkdirSync, readFileSync, realpathSync, statSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { JsonRpcRemoteError } from '../../acp/json-rpc'\nimport { writeJsonFileAtomic } from '../../atomic-write'\nimport { HostIdentityError } from '../../chat/hosts/connection'\nimport { displayHostUrl } from '../../chat/hosts/url'\nimport { isValidMachineName, MACHINE_PROTOCOL_VERSION, MachineErrorCode } from '../../chat/machine-protocol'\nimport { ZIDANE_VERSION } from '../../version'\nimport { keepHostConnected } from '../hosts/connection-loop'\nimport { IpcVersionMismatchError } from '../transports/client-core'\nimport { connectWsClient, HostAuthError } from '../transports/ws-client'\nimport { createMachineExecutor, shareableRoot } from './executor'\nimport { machineIdentity } from './identity'\n\nexport { machineIdentity } from './identity'\n\nexport interface MachineAgentOptions {\n /** `ws://` / `wss://` URL of the host. */\n hostUrl: string\n /** A `machine`-scoped token of that host. */\n token: string\n /** Display name, and the suffix of its environment kinds (`local@<name>`). */\n name: string\n /** Folders the host may use; absolute, existing. */\n roots: ReadonlyArray<string | MachineRoot>\n /** This machine's data dir: its id, the host it pinned, task logs. Only its `worktrees/` and task logs are reachable by the host. */\n dataDir: string\n appVersion?: string\n /** Every status change (`connected`, `reconnecting`, `auth-refused`, …) and why it failed. */\n onStatus?: (status: HostConnectionStatus, detail?: string) => void\n /** Backoff bounds and heartbeat (tests shrink them). */\n minDelayMs?: number\n maxDelayMs?: number\n heartbeatMs?: number\n terminalGraceMs?: number\n /** This machine user's `sandbox*` settings: every command the host sends runs under them (`docs/MACHINES.md` § Sandbox). */\n sandbox?: MachineExecutorOptions['sandbox']\n /** This machine user's zidane data dir: never shared (a root can't be in it, file ops never reach it), and its credential files are hidden from sandboxed commands. */\n credentialsDataDir?: string\n /** This computer's GPU names (`capabilities.gpu`); asked once, before the first registration. */\n gpu?: () => Promise<string[]>\n /** After each registration: the host's version (`ipc.hello`'s `appVersion`). */\n onHostVersion?: (version: string | undefined) => void\n}\n\nexport interface MachineAgent {\n machineId: string\n status: () => HostConnectionStatus\n /** Reconnect now (after a fix on the host side). */\n retry: () => void\n stop: () => Promise<void>\n /** The host's version, once it answered. */\n hostVersion: () => string | undefined\n /** What runs on this machine now (see {@link MachineExecutor.activity}). */\n activity: MachineExecutor['activity']\n}\n\n/** A registration the host refused for good (its name taken, another protocol). */\nclass MachineRegisterError extends Error {\n constructor(message: string, readonly forGood: boolean) {\n super(message)\n this.name = 'MachineRegisterError'\n }\n}\n\nfunction normalizeRoots(roots: MachineAgentOptions['roots'], zidaneDataDir: string | undefined): MachineRoot[] {\n if (roots.length === 0)\n throw new Error('Share at least one folder (--root).')\n return roots.map((root) => {\n const entry = typeof root === 'string' ? { path: root } : root\n const real = shareableRoot(entry.path, zidaneDataDir)\n if (!statSync(real).isDirectory())\n throw new Error(`Root ${entry.path} is not a folder.`)\n return { path: real, ...(entry.label ? { label: entry.label } : {}) }\n })\n}\n\n/** The host id this machine pinned for `url` (the first host that answered there). */\nfunction pinnedHosts(dataDir: string): { get: (url: string) => string | undefined, set: (url: string, hostId: string) => void } {\n const file = join(dataDir, 'machine-hosts.json')\n const read = (): Record<string, string> => {\n try {\n return JSON.parse(readFileSync(file, 'utf8')) as Record<string, string>\n }\n catch {\n return {}\n }\n }\n return {\n get: url => read()[url],\n set: (url, hostId) => writeJsonFileAtomic(file, { ...read(), [url]: hostId }, { ensureDir: true, mode: 0o600 }),\n }\n}\n\nexport function startMachineAgent(options: MachineAgentOptions): MachineAgent {\n if (!isValidMachineName(options.name))\n throw new Error(`\"${options.name}\" is not a valid machine name: 1 to 64 letters, digits, \".\", \"-\" or \"_\".`)\n mkdirSync(options.dataDir, { recursive: true, mode: 0o700 })\n const dataDir = realpathSync.native(options.dataDir)\n const roots = normalizeRoots(options.roots, options.credentialsDataDir)\n const machineId = machineIdentity(dataDir)\n const pins = pinnedHosts(dataDir)\n const url = options.hostUrl\n let client: IpcClient | null = null\n let status: HostConnectionStatus = 'connecting'\n let hostVersion: string | undefined\n let gpu: Promise<string[]> | undefined\n\n const executor = createMachineExecutor({\n roots,\n dataDir,\n ...(options.terminalGraceMs !== undefined ? { terminalGraceMs: options.terminalGraceMs } : {}),\n ...(options.sandbox ? { sandbox: options.sandbox } : {}),\n ...(options.credentialsDataDir ? { credentialsDataDir: options.credentialsDataDir } : {}),\n notify: (method, params) => {\n if (!client)\n return false\n try {\n (client.notify as unknown as (m: string, p: unknown) => void)(method, params)\n return true\n }\n catch {\n return false\n }\n },\n })\n\n const register: MachineRegisterParams = {\n machineId,\n name: options.name,\n roots,\n capabilities: { shell: true, terminals: typeof (globalThis as { Bun?: unknown }).Bun !== 'undefined', background: true, worktrees: true, stream: true, entries: true, sandboxed: executor.sandbox.on },\n version: options.appVersion ?? ZIDANE_VERSION,\n protocolVersion: MACHINE_PROTOCOL_VERSION,\n }\n\n const loop = keepHostConnected({\n minDelayMs: options.minDelayMs ?? 1000,\n maxDelayMs: options.maxDelayMs ?? 30_000,\n async connect({ signal, onClose }) {\n const expectHostId = pins.get(url)\n const connected = await connectWsClient({\n url,\n token: options.token,\n signal,\n onClose,\n appVersion: register.version,\n ...(options.heartbeatMs !== undefined ? { heartbeatMs: options.heartbeatMs } : {}),\n ...(expectHostId ? { expectHostId } : {}),\n serve: {\n request: (method, params) => executor.handle(method, params),\n notification: (method, params) => void executor.handle(method, params).catch(() => undefined),\n },\n })\n gpu ??= options.gpu ? options.gpu().catch(() => []) : Promise.resolve([])\n const gpuNames = await gpu\n const params: MachineRegisterParams = gpuNames.length > 0 ? { ...register, capabilities: { ...register.capabilities, gpu: gpuNames } } : register\n try {\n await (connected.request as unknown as (m: string, p: unknown) => Promise<MachineRegisterResult>)('machine.register', params)\n }\n catch (err) {\n connected.close()\n const code = err instanceof JsonRpcRemoteError ? err.code : undefined\n throw new MachineRegisterError(`${displayHostUrl(url)} refused this machine: ${(err as Error).message}`, code === MachineErrorCode.NameTaken || code === MachineErrorCode.VersionSkew || code === MachineErrorCode.IdBound)\n }\n if (!expectHostId && connected.hello.hostId)\n pins.set(url, connected.hello.hostId)\n hostVersion = connected.hello.appVersion\n options.onHostVersion?.(hostVersion)\n return connected\n },\n classify: (err) => {\n if (err instanceof HostIdentityError)\n return 'identity'\n if (err instanceof HostAuthError || (err instanceof MachineRegisterError && err.forGood))\n return 'auth'\n return err instanceof IpcVersionMismatchError ? 'protocol' : 'retry'\n },\n update: (patch) => {\n if (patch.client !== undefined)\n client = patch.client\n if (patch.status) {\n status = patch.status\n options.onStatus?.(patch.status, patch.error)\n }\n },\n onConnected: () => executor.linkUp(),\n onDisconnected: () => executor.linkLost(),\n })\n\n return {\n machineId,\n status: () => status,\n retry: () => loop.retry(),\n hostVersion: () => hostVersion,\n activity: () => executor.activity(),\n async stop() {\n loop.stop()\n await executor.close()\n },\n }\n}\n","/**\n * A saved server used as a machine of this local host (`local@<server>`,\n * `docs/MACHINES.md` § Reverse direction). A local host has no listener, so\n * it dials the server as a client (its saved client token), asks it to lend\n * itself (`machine.attach`), and registers it in its own machine registry\n * with a connection that sends each host→machine request as `machine.call`.\n * The server's notifications come back as `machine:notification` signals.\n * Reconnects with the hosts' connection loop; a refused token, another host\n * at the URL, or a server that does not lend itself stop it for good.\n */\nimport type { AttachedMachine, MachineConnection, MachineRegistry } from '../../chat/host/machines'\nimport type { MachineLinkState } from '../../chat/machine-protocol'\nimport type { JsonValue } from '../../types'\nimport type { IpcClient } from '../transports/client-core'\nimport { JsonRpcConnectionClosedError, JsonRpcRemoteError } from '../../acp/json-rpc'\nimport { MachineLinkLostError } from '../../chat/host/machines'\nimport { HostIdentityError } from '../../chat/hosts/connection'\nimport { displayHostUrl } from '../../chat/hosts/url'\nimport { MachineErrorCode } from '../../chat/machine-protocol'\nimport { keepHostConnected } from '../hosts/connection-loop'\nimport { connectWsClient, HostAuthError } from '../transports/ws-client'\n\nexport interface ServerMachineOptions {\n machines: MachineRegistry\n url: string\n /** The saved server's client token. */\n token: string\n /** The saved server's host id: another host at the URL is refused. */\n expectHostId: string\n /** This host's id (the server keys its executor by it, so tasks survive a reconnect). */\n localHostId: string\n appVersion?: string\n onState: (state: MachineLinkState, detail?: string, machineName?: string) => void\n minDelayMs?: number\n maxDelayMs?: number\n heartbeatMs?: number\n}\n\nexport interface ServerMachine {\n retry: () => void\n stop: () => void\n}\n\n/** The server refused to lend itself (`allowAsMachine` off, a name taken here): for good. A busy server is retried. */\nclass AttachRefusedError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'AttachRefusedError'\n }\n}\n\nfunction connectionOver(client: IpcClient): MachineConnection {\n return {\n async request(method, params) {\n try {\n return await client.request('machine.call', { method, params: (params ?? {}) as JsonValue })\n }\n catch (err) {\n if (err instanceof JsonRpcConnectionClosedError)\n throw new MachineLinkLostError()\n throw err instanceof JsonRpcRemoteError ? new Error(err.message) : err\n }\n },\n notify: (method, params) => client.notify('machine.call', { method, params: (params ?? {}) as JsonValue }),\n close: () => client.close(),\n }\n}\n\nexport function startServerMachine(options: ServerMachineOptions): ServerMachine {\n let attached: AttachedMachine | undefined\n const where = displayHostUrl(options.url)\n\n const detach = (): void => {\n attached?.closed()\n attached = undefined\n }\n\n const loop = keepHostConnected({\n ...(options.minDelayMs !== undefined ? { minDelayMs: options.minDelayMs } : {}),\n ...(options.maxDelayMs !== undefined ? { maxDelayMs: options.maxDelayMs } : {}),\n async connect({ signal, onClose }) {\n const client = await connectWsClient({\n url: options.url,\n token: options.token,\n expectHostId: options.expectHostId,\n signal,\n onClose,\n ...(options.appVersion ? { appVersion: options.appVersion } : {}),\n ...(options.heartbeatMs !== undefined ? { heartbeatMs: options.heartbeatMs } : {}),\n })\n // Subscribed before attaching: the server re-sends queued task exits while it answers `machine.attach`.\n const early: Array<{ method: string, params: unknown }> = []\n let current: AttachedMachine | undefined\n client.onSignal((s) => {\n if (s.kind !== 'machine:notification')\n return\n if (current)\n current.notification(s.method, s.params)\n else if (early.length < 1024)\n early.push({ method: s.method, params: s.params })\n })\n try {\n const params = await client.request('machine.attach', { hostId: options.localHostId })\n detach()\n current = options.machines.attach(params, connectionOver(client))\n attached = current\n for (const { method, params: p } of early.splice(0))\n current.notification(method, p)\n options.onState('connected', undefined, params.name)\n }\n catch (err) {\n client.close()\n // No room there now (a capacity limit) or the link dropped: the loop retries with backoff.\n if (err instanceof JsonRpcConnectionClosedError || (err instanceof JsonRpcRemoteError && err.code === MachineErrorCode.Busy))\n throw new Error(`${where} can't lend itself right now: ${(err as Error).message}`)\n throw new AttachRefusedError(`${where} refused to be a machine: ${(err as Error).message}`)\n }\n return client\n },\n classify: err => (err instanceof HostIdentityError ? 'identity' : err instanceof HostAuthError || err instanceof AttachRefusedError ? 'auth' : 'retry'),\n update: (patch) => {\n if (!patch.status || patch.status === 'connected')\n return\n options.onState(patch.status === 'auth-refused' && patch.error?.includes('refused to be a machine') ? 'refused' : patch.status, patch.error)\n },\n onDisconnected: detach,\n })\n\n return {\n retry: () => loop.retry(),\n stop() {\n loop.stop()\n detach()\n },\n }\n}\n","/**\n * `machine-links.lock`: which process runs this computer's shares (one\n * machine id, one connection; two processes sharing it would keep replacing\n * each other's). Exclusive by construction, not by a read-then-write:\n *\n * - taking a free lock is an exclusive create (`wx`): one contender wins;\n * - the record names its holder (`pid`) and a per-process `nonce`; the\n * holder touches the file on every check, so a holder is stale when its\n * pid is gone OR the file went untouched for {@link STALE_MS} (a reused\n * pid never touches it);\n * - a stale lock is moved aside (an atomic rename to a name of our own),\n * checked to be the very record judged stale, then the exclusive create\n * runs again; a live lock moved aside by mistake is put back without\n * clobbering (`link`, which fails if the name is taken);\n * - a holder re-reads the record on every check: another nonce there means\n * it lost the lock, and the caller stops its shares;\n * - release removes the file only when it still carries this nonce.\n */\nimport { randomBytes } from 'node:crypto'\nimport { closeSync, linkSync, openSync, readFileSync, renameSync, rmSync, statSync, utimesSync, writeSync } from 'node:fs'\nimport process from 'node:process'\n\n/** A holder that has not touched its lock for this long is gone (its pid may be reused). */\nexport const SHARE_LOCK_STALE_MS = 30_000\n\ninterface LockRecord {\n pid: number\n nonce: string\n}\n\nexport interface ShareLock {\n /** Hold the lock (taking it when free or stale); `false` when another live process holds it. */\n acquire: () => boolean\n /** Whether this process held it at the last {@link acquire}. */\n held: () => boolean\n /** Remove the lock when this process still holds it; whether it did. */\n release: () => boolean\n}\n\nexport interface ShareLockOptions {\n /** Test seams. */\n pid?: number\n alive?: (pid: number) => boolean\n now?: () => number\n staleMs?: number\n}\n\nfunction processAlive(pid: number): boolean {\n try {\n process.kill(pid, 0)\n return true\n }\n catch (err) {\n return (err as NodeJS.ErrnoException).code === 'EPERM'\n }\n}\n\nfunction readRecord(file: string): { record?: LockRecord, mtimeMs: number } | undefined {\n try {\n const mtimeMs = statSync(file).mtimeMs\n const raw = JSON.parse(readFileSync(file, 'utf8')) as Partial<LockRecord>\n return { mtimeMs, ...(typeof raw.pid === 'number' && typeof raw.nonce === 'string' ? { record: { pid: raw.pid, nonce: raw.nonce } } : {}) }\n }\n catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT')\n return undefined\n // Unreadable or half-written: judged by its age alone.\n try {\n return { mtimeMs: statSync(file).mtimeMs }\n }\n catch {\n return undefined\n }\n }\n}\n\nexport function createShareLock(file: string, options: ShareLockOptions = {}): ShareLock {\n const pid = options.pid ?? process.pid\n const alive = options.alive ?? processAlive\n const now = options.now ?? Date.now\n const staleMs = options.staleMs ?? SHARE_LOCK_STALE_MS\n const nonce = randomBytes(12).toString('hex')\n let holding = false\n\n const create = (): boolean => {\n let fd: number\n try {\n fd = openSync(file, 'wx', 0o600)\n }\n catch {\n return false\n }\n try {\n writeSync(fd, JSON.stringify({ pid, nonce }))\n }\n finally {\n closeSync(fd)\n }\n return true\n }\n\n const ours = (): boolean => readRecord(file)?.record?.nonce === nonce\n\n const stale = (found: { record?: LockRecord, mtimeMs: number }): boolean =>\n now() - found.mtimeMs > staleMs || (found.record !== undefined && !alive(found.record.pid))\n\n /** Move a stale lock aside; `true` when the record moved is the one judged stale. */\n const evict = (judged: { record?: LockRecord, mtimeMs: number }): boolean => {\n const aside = `${file}.${nonce}.stale`\n try {\n renameSync(file, aside)\n }\n catch {\n return false\n }\n const moved = readRecord(aside)\n const same = moved?.record?.nonce === judged.record?.nonce && moved?.mtimeMs === judged.mtimeMs\n if (!same) {\n // A live lock taken meanwhile: put it back, never over a newer one.\n try {\n linkSync(aside, file)\n }\n catch {\n // Someone holds the name again; that holder's own check settles it.\n }\n }\n rmSync(aside, { force: true })\n return same\n }\n\n return {\n acquire() {\n if (holding) {\n if (ours()) {\n try {\n const at = new Date(now())\n utimesSync(file, at, at)\n }\n catch {\n // The touch is best effort; the record still names this process.\n }\n return true\n }\n holding = false\n }\n if (create()) {\n holding = true\n return true\n }\n const found = readRecord(file)\n if (found && !stale(found))\n return false\n if (found && !evict(found))\n return false\n holding = create()\n return holding\n },\n held: () => holding,\n release() {\n const mine = holding && ours()\n if (mine)\n rmSync(file, { force: true })\n holding = false\n return mine\n },\n }\n}\n","/**\n * A local host's machine links (`docs/MACHINES.md` § Client UI), loaded only\n * behind remote hosts:\n *\n * - **Shares**: this computer lent to a saved server (a machine agent in\n * this process, with the machine token stored under `machine:<hostId>`).\n * One process runs them for the whole computer: the holder of\n * `machine-links.lock` (the daemon, or the first window's in-process\n * host; exclusive, `./share-lock.ts`). Two processes sharing one computer\n * under one machine id would keep replacing each other's connection, so a\n * process that finds it lost the lock stops its shares at once.\n * - **Uses**: a saved server lent to THIS host (`local@<server>`), over its\n * own client connection. Per host process: each host has its own machine\n * registry, so each attaches under its own instance id.\n *\n * The wanted links live in `<dataDir>/machine-links.json` (any process writes\n * it); every process re-reads it every {@link POLL_MS}, the holder applies the\n * shares and publishes their state in `machine-links-status.json`. Tokens\n * are never in either file.\n */\nimport type { MachineRegistry } from '../../chat/host/machines'\nimport type { HostEntry } from '../../chat/hosts/types'\nimport type { MachineLinksStatus, MachineLinkState, MachineShareStatus, MachineUseStatus } from '../../chat/machine-protocol'\nimport type { LooseSandboxSettings } from '../../chat/sandbox-settings'\nimport type { MachineAgent } from './agent'\nimport type { ServerMachine } from './reverse'\nimport { randomBytes } from 'node:crypto'\nimport { existsSync, readFileSync, rmSync } from 'node:fs'\nimport { isAbsolute, join } from 'node:path'\nimport { OWNER_ONLY, writeJsonFileAtomic } from '../../atomic-write'\nimport { readCredentialEntry, writeCredentialEntry } from '../../chat/credentials'\nimport { createHostRegistry } from '../../chat/hosts'\nimport { isValidMachineName, machineCredentialKey } from '../../chat/machine-protocol'\nimport { startMachineAgent } from './agent'\nimport { shareableRoot } from './executor'\nimport { startServerMachine } from './reverse'\nimport { createShareLock } from './share-lock'\n\nconst POLL_MS = 2000\nconst CONFIG_FILE = 'machine-links.json'\nconst STATUS_FILE = 'machine-links-status.json'\nconst LOCK_FILE = 'machine-links.lock'\nconst MAX_ROOTS = 32\n\ninterface ShareConfig {\n hostId: string\n name: string\n roots: string[]\n}\n\ninterface LinksConfig {\n version: 1\n shares: ShareConfig[]\n uses: string[]\n}\n\nexport interface MachineLinksOptions {\n /** The host's data dir: saved hosts, credentials, these files. */\n dataDir: string\n machines: MachineRegistry\n /** This host's id (`ipc.hello`). */\n hostId: string | undefined\n /** This user's settings: a share applies their `sandbox*` to what it runs (`docs/MACHINES.md` § Sandbox). */\n settings?: () => LooseSandboxSettings\n /** Every status change, for `machine:links`. */\n onChange: (status: MachineLinksStatus) => void\n appVersion?: string\n /** Connection pacing (tests shrink them). */\n minDelayMs?: number\n maxDelayMs?: number\n heartbeatMs?: number\n pollMs?: number\n}\n\nexport interface MachineLinks {\n status: () => MachineLinksStatus\n startShare: (input: { hostId: string, name: string, roots: string[], token?: string }) => MachineLinksStatus\n stopShare: (hostId: string) => MachineLinksStatus\n setUse: (hostId: string, enabled: boolean) => MachineLinksStatus\n /** Stop everything (remote hosts turned off, or the host closes); the wanted links stay saved. */\n close: () => Promise<void>\n}\n\nfunction readJson<T>(file: string, fallback: T): T {\n try {\n return existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) as T : fallback\n }\n catch {\n return fallback\n }\n}\n\nfunction readConfig(file: string): LinksConfig {\n const raw = readJson<Partial<LinksConfig>>(file, {})\n const shares = Array.isArray(raw.shares)\n ? raw.shares.filter((s): s is ShareConfig => !!s && typeof s.hostId === 'string' && typeof s.name === 'string' && Array.isArray(s.roots) && s.roots.every(r => typeof r === 'string'))\n : []\n const uses = Array.isArray(raw.uses) ? raw.uses.filter((u): u is string => typeof u === 'string') : []\n return { version: 1, shares, uses }\n}\n\n/**\n * A share's name and roots, as a client sent them: every root absolute,\n * existing, and not (by its real path) zidane's data dir, inside it, or a\n * credentials folder (`./executor`'s `shareableRoot`).\n */\nexport function checkShareInput(input: { name: string, roots: string[] }, zidaneDataDir: string): void {\n if (!isValidMachineName(input.name))\n throw new Error(`\"${input.name}\" is not a valid machine name: 1 to 64 letters, digits, \".\", \"-\" or \"_\".`)\n if (!Array.isArray(input.roots) || input.roots.length === 0)\n throw new Error('Share at least one folder.')\n if (input.roots.length > MAX_ROOTS)\n throw new Error(`Share at most ${MAX_ROOTS} folders.`)\n for (const root of input.roots) {\n if (typeof root !== 'string' || !isAbsolute(root))\n throw new Error(`${String(root)} is not an absolute folder path.`)\n shareableRoot(root, zidaneDataDir)\n }\n}\n\nexport function startMachineLinks(options: MachineLinksOptions): MachineLinks {\n const { dataDir, machines } = options\n const configFile = join(dataDir, CONFIG_FILE)\n const statusFile = join(dataDir, STATUS_FILE)\n const lock = createShareLock(join(dataDir, LOCK_FILE))\n // Reading entries and tokens needs no probe: nothing is dialed here but the links themselves.\n const hosts = createHostRegistry({ dataDir, probe: async () => ({ ok: false, error: 'unreachable', message: 'not probed here' }) })\n const instance = `${(options.hostId ?? 'local').slice(0, 40)}-${randomBytes(4).toString('hex')}`\n const pacing = {\n ...(options.minDelayMs !== undefined ? { minDelayMs: options.minDelayMs } : {}),\n ...(options.maxDelayMs !== undefined ? { maxDelayMs: options.maxDelayMs } : {}),\n ...(options.heartbeatMs !== undefined ? { heartbeatMs: options.heartbeatMs } : {}),\n }\n\n const shares = new Map<string, { config: ShareConfig, agent?: MachineAgent, status: MachineShareStatus }>()\n const uses = new Map<string, { link?: ServerMachine, status: MachineUseStatus }>()\n let holder = false\n let closed = false\n let lastPublished = ''\n\n const ownStatus = (): MachineLinksStatus => ({ shares: [...shares.values()].map(s => s.status), uses: [...uses.values()].map(u => u.status) })\n\n /** Shares as the holder reports them (another process may run them). */\n const status = (): MachineLinksStatus => {\n const own = ownStatus()\n if (holder)\n return own\n const config = readConfig(configFile)\n const published = readJson<Partial<MachineLinksStatus>>(statusFile, {})\n const known = new Map((published.shares ?? []).map(s => [s.hostId, s]))\n return {\n shares: config.shares.map(s => known.get(s.hostId) ?? { hostId: s.hostId, name: s.name, roots: s.roots, state: 'connecting' as const, detail: 'waiting for the zidane process that shares this computer' }),\n uses: own.uses,\n }\n }\n\n const publish = (): void => {\n if (closed)\n return\n const snapshot = status()\n const text = JSON.stringify(snapshot)\n if (holder) {\n try {\n writeJsonFileAtomic(statusFile, { shares: snapshot.shares }, OWNER_ONLY)\n }\n catch {\n // Best effort: other processes show \"waiting\" until it lands.\n }\n }\n if (text === lastPublished)\n return\n lastPublished = text\n options.onChange(snapshot)\n }\n\n const entry = (hostId: string): HostEntry => {\n const found = hosts.get(hostId)\n if (!found)\n throw new Error(`No saved server \"${hostId}\": add it on the Hosts page first.`)\n return found\n }\n\n function runShare(config: ShareConfig): void {\n const status: MachineShareStatus = { hostId: config.hostId, name: config.name, roots: config.roots, state: 'connecting' }\n const share: { config: ShareConfig, agent?: MachineAgent, status: MachineShareStatus } = { config, status }\n shares.set(config.hostId, share)\n const set = (state: MachineLinkState, detail?: string): void => {\n share.status = { ...status, state, ...(detail && state !== 'connected' ? { detail } : {}) }\n publish()\n }\n try {\n const server = entry(config.hostId)\n const cred = readCredentialEntry(dataDir, machineCredentialKey(config.hostId))\n const token = cred?.kind === 'apikey' ? cred.value : undefined\n if (!token)\n throw new Error(`No machine token saved for ${server.name}.`)\n share.agent = startMachineAgent({ hostUrl: server.url, token, name: config.name, roots: config.roots, dataDir: join(dataDir, 'machine'), credentialsDataDir: dataDir, ...(options.settings ? { sandbox: options.settings() } : {}), ...pacing, ...(options.appVersion ? { appVersion: options.appVersion } : {}), onStatus: (state, detail) => set(state, detail) })\n }\n catch (err) {\n set('refused', (err as Error).message)\n }\n }\n\n function runUse(hostId: string): void {\n const use: { link?: ServerMachine, status: MachineUseStatus } = { status: { hostId, state: 'connecting' } }\n uses.set(hostId, use)\n const set = (state: MachineLinkState, detail?: string, machineName?: string): void => {\n use.status = { hostId, state, ...(detail && state !== 'connected' ? { detail } : {}), ...(machineName ?? use.status.machineName ? { machineName: machineName ?? use.status.machineName } : {}) }\n publish()\n }\n try {\n const server = entry(hostId)\n const token = hosts.token(hostId)\n if (!token)\n throw new Error(`No token saved for ${server.name}: add it again.`)\n use.link = startServerMachine({ machines, url: server.url, token, expectHostId: hostId, localHostId: instance, ...pacing, ...(options.appVersion ? { appVersion: options.appVersion } : {}), onState: set })\n }\n catch (err) {\n set('refused', (err as Error).message)\n }\n }\n\n const stopShare = async (hostId: string): Promise<void> => {\n const share = shares.get(hostId)\n shares.delete(hostId)\n await share?.agent?.stop()\n }\n\n /** Bring the running links in line with the saved config. */\n function reconcile(): void {\n if (closed)\n return\n const config = readConfig(configFile)\n holder = lock.acquire()\n if (!holder) {\n // Lost the lock (or never had it): another process runs this computer's shares, never two.\n for (const hostId of [...shares.keys()])\n void stopShare(hostId)\n }\n else {\n const wanted = new Map(config.shares.map(s => [s.hostId, s]))\n for (const [hostId, share] of shares) {\n const want = wanted.get(hostId)\n if (!want || JSON.stringify(want) !== JSON.stringify(share.config))\n void stopShare(hostId)\n }\n for (const share of config.shares) {\n if (!shares.has(share.hostId))\n runShare(share)\n }\n }\n const wantedUses = new Set(config.uses)\n for (const [hostId, use] of uses) {\n if (!wantedUses.has(hostId)) {\n use.link?.stop()\n uses.delete(hostId)\n }\n }\n for (const hostId of config.uses) {\n if (!uses.has(hostId))\n runUse(hostId)\n }\n publish()\n }\n\n const writeConfig = (mutate: (config: LinksConfig) => void): MachineLinksStatus => {\n const config = readConfig(configFile)\n mutate(config)\n writeJsonFileAtomic(configFile, config, OWNER_ONLY)\n reconcile()\n return status()\n }\n\n reconcile()\n const timer = setInterval(reconcile, options.pollMs ?? POLL_MS)\n timer.unref?.()\n\n return {\n status,\n startShare(input) {\n checkShareInput(input, dataDir)\n const server = entry(input.hostId)\n const token = input.token?.trim()\n if (token)\n writeCredentialEntry(dataDir, machineCredentialKey(input.hostId), { kind: 'apikey', value: token })\n else if (readCredentialEntry(dataDir, machineCredentialKey(input.hostId))?.kind !== 'apikey')\n throw new Error(`No machine token for ${server.name}: create one there (zidane server token create --scope machine) or let this window create it.`)\n return writeConfig((config) => {\n config.shares = [...config.shares.filter(s => s.hostId !== input.hostId), { hostId: input.hostId, name: input.name, roots: [...input.roots] }]\n })\n },\n stopShare(hostId) {\n return writeConfig((config) => {\n config.shares = config.shares.filter(s => s.hostId !== hostId)\n })\n },\n setUse(hostId, enabled) {\n if (enabled) {\n const server = entry(hostId)\n if (!hosts.token(hostId))\n throw new Error(`No token saved for ${server.name}: add it again.`)\n }\n return writeConfig((config) => {\n config.uses = enabled ? [...new Set([...config.uses, hostId])] : config.uses.filter(u => u !== hostId)\n })\n },\n async close() {\n if (closed)\n return\n closed = true\n clearInterval(timer)\n for (const use of uses.values())\n use.link?.stop()\n uses.clear()\n await Promise.all([...shares.keys()].map(stopShare))\n // Only the holder's own files: a process that lost the lock leaves the new holder's alone.\n if (lock.release())\n rmSync(statusFile, { force: true })\n },\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"links-DUeZy_2e.js","names":[],"sources":["../src/ipc/machine/agent.ts","../src/ipc/machine/reverse.ts","../src/ipc/machine/share-lock.ts","../src/ipc/machine/links.ts"],"sourcesContent":["/**\n * The machine agent (`docs/MACHINES.md` § Machine side): dials its host with\n * a machine-scoped token, registers, and serves the host's execution\n * requests through the confined executor (`./executor.ts`) until stopped.\n * Reconnects with the hosts' connection loop (backoff; a refused token, a\n * different host at the URL, or a refused registration stop it for good).\n *\n * The CLI (`./cli.ts`, `zidane machine share`) runs it in the foreground;\n * `./links.ts` starts it from the local daemon / in-process host.\n */\nimport type { HostConnectionStatus } from '../../chat/hosts/connection'\nimport type { MachineRegisterParams, MachineRegisterResult, MachineRoot } from '../../chat/machine-protocol'\nimport type { IpcClient } from '../transports/client-core'\nimport type { MachineExecutor, MachineExecutorOptions } from './executor'\nimport { mkdirSync, readFileSync, realpathSync, statSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { JsonRpcRemoteError } from '../../acp/json-rpc'\nimport { writeJsonFileAtomic } from '../../atomic-write'\nimport { HostIdentityError } from '../../chat/hosts/connection'\nimport { displayHostUrl } from '../../chat/hosts/url'\nimport { isValidMachineName, MACHINE_PROTOCOL_VERSION, MachineErrorCode } from '../../chat/machine-protocol'\nimport { ZIDANE_VERSION } from '../../version'\nimport { keepHostConnected } from '../hosts/connection-loop'\nimport { IpcVersionMismatchError } from '../transports/client-core'\nimport { connectWsClient, HostAuthError } from '../transports/ws-client'\nimport { createMachineExecutor, shareableRoot } from './executor'\nimport { machineIdentity } from './identity'\n\nexport { machineIdentity } from './identity'\n\nexport interface MachineAgentOptions {\n /** `ws://` / `wss://` URL of the host. */\n hostUrl: string\n /** A `machine`-scoped token of that host. */\n token: string\n /** Display name, and the suffix of its environment kinds (`local@<name>`). */\n name: string\n /** Folders the host may use; absolute, existing. */\n roots: ReadonlyArray<string | MachineRoot>\n /** This machine's data dir: its id, the host it pinned, task logs. Only its `worktrees/` and task logs are reachable by the host. */\n dataDir: string\n appVersion?: string\n /** Every status change (`connected`, `reconnecting`, `auth-refused`, …) and why it failed. */\n onStatus?: (status: HostConnectionStatus, detail?: string) => void\n /** Backoff bounds and heartbeat (tests shrink them). */\n minDelayMs?: number\n maxDelayMs?: number\n heartbeatMs?: number\n terminalGraceMs?: number\n /** This machine user's `sandbox*` settings: every command the host sends runs under them (`docs/MACHINES.md` § Sandbox). */\n sandbox?: MachineExecutorOptions['sandbox']\n /** This machine user's zidane data dir: never shared (a root can't be in it, file ops never reach it), and its credential files are hidden from sandboxed commands. */\n credentialsDataDir?: string\n /** This computer's GPU names (`capabilities.gpu`); asked once, before the first registration. */\n gpu?: () => Promise<string[]>\n /** After each registration: the host's version (`ipc.hello`'s `appVersion`). */\n onHostVersion?: (version: string | undefined) => void\n}\n\nexport interface MachineAgent {\n machineId: string\n status: () => HostConnectionStatus\n /** Reconnect now (after a fix on the host side). */\n retry: () => void\n stop: () => Promise<void>\n /** The host's version, once it answered. */\n hostVersion: () => string | undefined\n /** What runs on this machine now (see {@link MachineExecutor.activity}). */\n activity: MachineExecutor['activity']\n}\n\n/** A registration the host refused for good (its name taken, another protocol). */\nclass MachineRegisterError extends Error {\n constructor(message: string, readonly forGood: boolean) {\n super(message)\n this.name = 'MachineRegisterError'\n }\n}\n\nfunction normalizeRoots(roots: MachineAgentOptions['roots'], zidaneDataDir: string | undefined): MachineRoot[] {\n if (roots.length === 0)\n throw new Error('Share at least one folder (--root).')\n return roots.map((root) => {\n const entry = typeof root === 'string' ? { path: root } : root\n const real = shareableRoot(entry.path, zidaneDataDir)\n if (!statSync(real).isDirectory())\n throw new Error(`Root ${entry.path} is not a folder.`)\n return { path: real, ...(entry.label ? { label: entry.label } : {}) }\n })\n}\n\n/** The host id this machine pinned for `url` (the first host that answered there). */\nfunction pinnedHosts(dataDir: string): { get: (url: string) => string | undefined, set: (url: string, hostId: string) => void } {\n const file = join(dataDir, 'machine-hosts.json')\n const read = (): Record<string, string> => {\n try {\n return JSON.parse(readFileSync(file, 'utf8')) as Record<string, string>\n }\n catch {\n return {}\n }\n }\n return {\n get: url => read()[url],\n set: (url, hostId) => writeJsonFileAtomic(file, { ...read(), [url]: hostId }, { ensureDir: true, mode: 0o600 }),\n }\n}\n\nexport function startMachineAgent(options: MachineAgentOptions): MachineAgent {\n if (!isValidMachineName(options.name))\n throw new Error(`\"${options.name}\" is not a valid machine name: 1 to 64 letters, digits, \".\", \"-\" or \"_\".`)\n mkdirSync(options.dataDir, { recursive: true, mode: 0o700 })\n const dataDir = realpathSync.native(options.dataDir)\n const roots = normalizeRoots(options.roots, options.credentialsDataDir)\n const machineId = machineIdentity(dataDir)\n const pins = pinnedHosts(dataDir)\n const url = options.hostUrl\n let client: IpcClient | null = null\n let status: HostConnectionStatus = 'connecting'\n let hostVersion: string | undefined\n let gpu: Promise<string[]> | undefined\n\n const executor = createMachineExecutor({\n roots,\n dataDir,\n ...(options.terminalGraceMs !== undefined ? { terminalGraceMs: options.terminalGraceMs } : {}),\n ...(options.sandbox ? { sandbox: options.sandbox } : {}),\n ...(options.credentialsDataDir ? { credentialsDataDir: options.credentialsDataDir } : {}),\n notify: (method, params) => {\n if (!client)\n return false\n try {\n (client.notify as unknown as (m: string, p: unknown) => void)(method, params)\n return true\n }\n catch {\n return false\n }\n },\n })\n\n const register: MachineRegisterParams = {\n machineId,\n name: options.name,\n roots,\n capabilities: { shell: true, terminals: typeof (globalThis as { Bun?: unknown }).Bun !== 'undefined', background: true, worktrees: true, stream: true, entries: true, sandboxed: executor.sandbox.on },\n version: options.appVersion ?? ZIDANE_VERSION,\n protocolVersion: MACHINE_PROTOCOL_VERSION,\n }\n\n const loop = keepHostConnected({\n minDelayMs: options.minDelayMs ?? 1000,\n maxDelayMs: options.maxDelayMs ?? 30_000,\n async connect({ signal, onClose }) {\n const expectHostId = pins.get(url)\n const connected = await connectWsClient({\n url,\n token: options.token,\n signal,\n onClose,\n appVersion: register.version,\n ...(options.heartbeatMs !== undefined ? { heartbeatMs: options.heartbeatMs } : {}),\n ...(expectHostId ? { expectHostId } : {}),\n serve: {\n request: (method, params) => executor.handle(method, params),\n notification: (method, params) => void executor.handle(method, params).catch(() => undefined),\n },\n })\n gpu ??= options.gpu ? options.gpu().catch(() => []) : Promise.resolve([])\n const gpuNames = await gpu\n const params: MachineRegisterParams = gpuNames.length > 0 ? { ...register, capabilities: { ...register.capabilities, gpu: gpuNames } } : register\n try {\n await (connected.request as unknown as (m: string, p: unknown) => Promise<MachineRegisterResult>)('machine.register', params)\n }\n catch (err) {\n connected.close()\n const code = err instanceof JsonRpcRemoteError ? err.code : undefined\n throw new MachineRegisterError(`${displayHostUrl(url)} refused this machine: ${(err as Error).message}`, code === MachineErrorCode.NameTaken || code === MachineErrorCode.VersionSkew || code === MachineErrorCode.IdBound)\n }\n if (!expectHostId && connected.hello.hostId)\n pins.set(url, connected.hello.hostId)\n hostVersion = connected.hello.appVersion\n options.onHostVersion?.(hostVersion)\n return connected\n },\n classify: (err) => {\n if (err instanceof HostIdentityError)\n return 'identity'\n if (err instanceof HostAuthError || (err instanceof MachineRegisterError && err.forGood))\n return 'auth'\n return err instanceof IpcVersionMismatchError ? 'protocol' : 'retry'\n },\n update: (patch) => {\n if (patch.client !== undefined)\n client = patch.client\n if (patch.status) {\n status = patch.status\n options.onStatus?.(patch.status, patch.error)\n }\n },\n onConnected: () => executor.linkUp(),\n onDisconnected: () => executor.linkLost(),\n })\n\n return {\n machineId,\n status: () => status,\n retry: () => loop.retry(),\n hostVersion: () => hostVersion,\n activity: () => executor.activity(),\n async stop() {\n loop.stop()\n await executor.close()\n },\n }\n}\n","/**\n * A saved server used as a machine of this local host (`local@<server>`,\n * `docs/MACHINES.md` § Reverse direction). A local host has no listener, so\n * it dials the server as a client (its saved client token), asks it to lend\n * itself (`machine.attach`), and registers it in its own machine registry\n * with a connection that sends each host→machine request as `machine.call`.\n * The server's notifications come back as `machine:notification` signals.\n * Reconnects with the hosts' connection loop; a refused token, another host\n * at the URL, or a server that does not lend itself stop it for good.\n */\nimport type { AttachedMachine, MachineConnection, MachineRegistry } from '../../chat/host/machines'\nimport type { MachineLinkState } from '../../chat/machine-protocol'\nimport type { JsonValue } from '../../types'\nimport type { IpcClient } from '../transports/client-core'\nimport { JsonRpcConnectionClosedError, JsonRpcRemoteError } from '../../acp/json-rpc'\nimport { MachineLinkLostError } from '../../chat/host/machines'\nimport { HostIdentityError } from '../../chat/hosts/connection'\nimport { displayHostUrl } from '../../chat/hosts/url'\nimport { MachineErrorCode } from '../../chat/machine-protocol'\nimport { keepHostConnected } from '../hosts/connection-loop'\nimport { connectWsClient, HostAuthError } from '../transports/ws-client'\n\nexport interface ServerMachineOptions {\n machines: MachineRegistry\n url: string\n /** The saved server's client token. */\n token: string\n /** The saved server's host id: another host at the URL is refused. */\n expectHostId: string\n /** This host's id (the server keys its executor by it, so tasks survive a reconnect). */\n localHostId: string\n appVersion?: string\n onState: (state: MachineLinkState, detail?: string, machineName?: string) => void\n minDelayMs?: number\n maxDelayMs?: number\n heartbeatMs?: number\n}\n\nexport interface ServerMachine {\n retry: () => void\n stop: () => void\n}\n\n/** The server refused to lend itself (`allowAsMachine` off, a name taken here): for good. A busy server is retried. */\nclass AttachRefusedError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'AttachRefusedError'\n }\n}\n\nfunction connectionOver(client: IpcClient): MachineConnection {\n return {\n async request(method, params) {\n try {\n return await client.request('machine.call', { method, params: (params ?? {}) as JsonValue })\n }\n catch (err) {\n if (err instanceof JsonRpcConnectionClosedError)\n throw new MachineLinkLostError()\n throw err instanceof JsonRpcRemoteError ? new Error(err.message) : err\n }\n },\n notify: (method, params) => client.notify('machine.call', { method, params: (params ?? {}) as JsonValue }),\n close: () => client.close(),\n }\n}\n\nexport function startServerMachine(options: ServerMachineOptions): ServerMachine {\n let attached: AttachedMachine | undefined\n const where = displayHostUrl(options.url)\n\n const detach = (): void => {\n attached?.closed()\n attached = undefined\n }\n\n const loop = keepHostConnected({\n ...(options.minDelayMs !== undefined ? { minDelayMs: options.minDelayMs } : {}),\n ...(options.maxDelayMs !== undefined ? { maxDelayMs: options.maxDelayMs } : {}),\n async connect({ signal, onClose }) {\n const client = await connectWsClient({\n url: options.url,\n token: options.token,\n expectHostId: options.expectHostId,\n signal,\n onClose,\n ...(options.appVersion ? { appVersion: options.appVersion } : {}),\n ...(options.heartbeatMs !== undefined ? { heartbeatMs: options.heartbeatMs } : {}),\n })\n // Subscribed before attaching: the server re-sends queued task exits while it answers `machine.attach`.\n const early: Array<{ method: string, params: unknown }> = []\n let current: AttachedMachine | undefined\n client.onSignal((s) => {\n if (s.kind !== 'machine:notification')\n return\n if (current)\n current.notification(s.method, s.params)\n else if (early.length < 1024)\n early.push({ method: s.method, params: s.params })\n })\n try {\n const params = await client.request('machine.attach', { hostId: options.localHostId })\n detach()\n current = options.machines.attach(params, connectionOver(client))\n attached = current\n for (const { method, params: p } of early.splice(0))\n current.notification(method, p)\n options.onState('connected', undefined, params.name)\n }\n catch (err) {\n client.close()\n // No room there now (a capacity limit) or the link dropped: the loop retries with backoff.\n if (err instanceof JsonRpcConnectionClosedError || (err instanceof JsonRpcRemoteError && err.code === MachineErrorCode.Busy))\n throw new Error(`${where} can't lend itself right now: ${(err as Error).message}`)\n throw new AttachRefusedError(`${where} refused to be a machine: ${(err as Error).message}`)\n }\n return client\n },\n classify: err => (err instanceof HostIdentityError ? 'identity' : err instanceof HostAuthError || err instanceof AttachRefusedError ? 'auth' : 'retry'),\n update: (patch) => {\n if (!patch.status || patch.status === 'connected')\n return\n options.onState(patch.status === 'auth-refused' && patch.error?.includes('refused to be a machine') ? 'refused' : patch.status, patch.error)\n },\n onDisconnected: detach,\n })\n\n return {\n retry: () => loop.retry(),\n stop() {\n loop.stop()\n detach()\n },\n }\n}\n","/**\n * `machine-links.lock`: which process runs this computer's shares (one\n * machine id, one connection; two processes sharing it would keep replacing\n * each other's). Exclusive by construction, not by a read-then-write:\n *\n * - taking a free lock is an exclusive create (`wx`): one contender wins;\n * - the record names its holder (`pid`) and a per-process `nonce`; the\n * holder touches the file on every check, so a holder is stale when its\n * pid is gone OR the file went untouched for {@link STALE_MS} (a reused\n * pid never touches it);\n * - a stale lock is moved aside (an atomic rename to a name of our own),\n * checked to be the very record judged stale, then the exclusive create\n * runs again; a live lock moved aside by mistake is put back without\n * clobbering (`link`, which fails if the name is taken);\n * - a holder re-reads the record on every check: another nonce there means\n * it lost the lock, and the caller stops its shares;\n * - release removes the file only when it still carries this nonce.\n */\nimport { randomBytes } from 'node:crypto'\nimport { closeSync, linkSync, openSync, readFileSync, renameSync, rmSync, statSync, utimesSync, writeSync } from 'node:fs'\nimport process from 'node:process'\n\n/** A holder that has not touched its lock for this long is gone (its pid may be reused). */\nexport const SHARE_LOCK_STALE_MS = 30_000\n\ninterface LockRecord {\n pid: number\n nonce: string\n}\n\nexport interface ShareLock {\n /** Hold the lock (taking it when free or stale); `false` when another live process holds it. */\n acquire: () => boolean\n /** Whether this process held it at the last {@link acquire}. */\n held: () => boolean\n /** Remove the lock when this process still holds it; whether it did. */\n release: () => boolean\n}\n\nexport interface ShareLockOptions {\n /** Test seams. */\n pid?: number\n alive?: (pid: number) => boolean\n now?: () => number\n staleMs?: number\n}\n\nfunction processAlive(pid: number): boolean {\n try {\n process.kill(pid, 0)\n return true\n }\n catch (err) {\n return (err as NodeJS.ErrnoException).code === 'EPERM'\n }\n}\n\nfunction readRecord(file: string): { record?: LockRecord, mtimeMs: number } | undefined {\n try {\n const mtimeMs = statSync(file).mtimeMs\n const raw = JSON.parse(readFileSync(file, 'utf8')) as Partial<LockRecord>\n return { mtimeMs, ...(typeof raw.pid === 'number' && typeof raw.nonce === 'string' ? { record: { pid: raw.pid, nonce: raw.nonce } } : {}) }\n }\n catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT')\n return undefined\n // Unreadable or half-written: judged by its age alone.\n try {\n return { mtimeMs: statSync(file).mtimeMs }\n }\n catch {\n return undefined\n }\n }\n}\n\nexport function createShareLock(file: string, options: ShareLockOptions = {}): ShareLock {\n const pid = options.pid ?? process.pid\n const alive = options.alive ?? processAlive\n const now = options.now ?? Date.now\n const staleMs = options.staleMs ?? SHARE_LOCK_STALE_MS\n const nonce = randomBytes(12).toString('hex')\n let holding = false\n\n const create = (): boolean => {\n let fd: number\n try {\n fd = openSync(file, 'wx', 0o600)\n }\n catch {\n return false\n }\n try {\n writeSync(fd, JSON.stringify({ pid, nonce }))\n }\n finally {\n closeSync(fd)\n }\n return true\n }\n\n const ours = (): boolean => readRecord(file)?.record?.nonce === nonce\n\n const stale = (found: { record?: LockRecord, mtimeMs: number }): boolean =>\n now() - found.mtimeMs > staleMs || (found.record !== undefined && !alive(found.record.pid))\n\n /** Move a stale lock aside; `true` when the record moved is the one judged stale. */\n const evict = (judged: { record?: LockRecord, mtimeMs: number }): boolean => {\n const aside = `${file}.${nonce}.stale`\n try {\n renameSync(file, aside)\n }\n catch {\n return false\n }\n const moved = readRecord(aside)\n const same = moved?.record?.nonce === judged.record?.nonce && moved?.mtimeMs === judged.mtimeMs\n if (!same) {\n // A live lock taken meanwhile: put it back, never over a newer one.\n try {\n linkSync(aside, file)\n }\n catch {\n // Someone holds the name again; that holder's own check settles it.\n }\n }\n rmSync(aside, { force: true })\n return same\n }\n\n return {\n acquire() {\n if (holding) {\n if (ours()) {\n try {\n const at = new Date(now())\n utimesSync(file, at, at)\n }\n catch {\n // The touch is best effort; the record still names this process.\n }\n return true\n }\n holding = false\n }\n if (create()) {\n holding = true\n return true\n }\n const found = readRecord(file)\n if (found && !stale(found))\n return false\n if (found && !evict(found))\n return false\n holding = create()\n return holding\n },\n held: () => holding,\n release() {\n const mine = holding && ours()\n if (mine)\n rmSync(file, { force: true })\n holding = false\n return mine\n },\n }\n}\n","/**\n * A local host's machine links (`docs/MACHINES.md` § Client UI), loaded only\n * behind remote hosts:\n *\n * - **Shares**: this computer lent to a saved server (a machine agent in\n * this process, with the machine token stored under `machine:<hostId>`).\n * One process runs them for the whole computer: the holder of\n * `machine-links.lock` (the daemon, or the first window's in-process\n * host; exclusive, `./share-lock.ts`). Two processes sharing one computer\n * under one machine id would keep replacing each other's connection, so a\n * process that finds it lost the lock stops its shares at once.\n * - **Uses**: a saved server lent to THIS host (`local@<server>`), over its\n * own client connection. Per host process: each host has its own machine\n * registry, so each attaches under its own instance id.\n *\n * The wanted links live in `<dataDir>/machine-links.json` (any process writes\n * it); every process re-reads it every {@link POLL_MS}, the holder applies the\n * shares and publishes their state in `machine-links-status.json`. Tokens\n * are never in either file.\n */\nimport type { MachineRegistry } from '../../chat/host/machines'\nimport type { HostEntry } from '../../chat/hosts/types'\nimport type { MachineLinksStatus, MachineLinkState, MachineShareStatus, MachineUseStatus } from '../../chat/machine-protocol'\nimport type { LooseSandboxSettings } from '../../chat/sandbox-settings'\nimport type { MachineAgent } from './agent'\nimport type { ServerMachine } from './reverse'\nimport { randomBytes } from 'node:crypto'\nimport { existsSync, readFileSync, rmSync } from 'node:fs'\nimport { isAbsolute, join } from 'node:path'\nimport { OWNER_ONLY, writeJsonFileAtomic } from '../../atomic-write'\nimport { readCredentialEntry, writeCredentialEntry } from '../../chat/credentials'\nimport { createHostRegistry } from '../../chat/hosts'\nimport { isValidMachineName, machineCredentialKey } from '../../chat/machine-protocol'\nimport { startMachineAgent } from './agent'\nimport { shareableRoot } from './executor'\nimport { startServerMachine } from './reverse'\nimport { createShareLock } from './share-lock'\n\nconst POLL_MS = 2000\nconst CONFIG_FILE = 'machine-links.json'\nconst STATUS_FILE = 'machine-links-status.json'\nconst LOCK_FILE = 'machine-links.lock'\nconst MAX_ROOTS = 32\n\ninterface ShareConfig {\n hostId: string\n name: string\n roots: string[]\n}\n\ninterface LinksConfig {\n version: 1\n shares: ShareConfig[]\n uses: string[]\n}\n\nexport interface MachineLinksOptions {\n /** The host's data dir: saved hosts, credentials, these files. */\n dataDir: string\n machines: MachineRegistry\n /** This host's id (`ipc.hello`). */\n hostId: string | undefined\n /** This user's settings: a share applies their `sandbox*` to what it runs (`docs/MACHINES.md` § Sandbox). */\n settings?: () => LooseSandboxSettings\n /** Every status change, for `machine:links`. */\n onChange: (status: MachineLinksStatus) => void\n appVersion?: string\n /** Connection pacing (tests shrink them). */\n minDelayMs?: number\n maxDelayMs?: number\n heartbeatMs?: number\n pollMs?: number\n}\n\nexport interface MachineLinks {\n status: () => MachineLinksStatus\n startShare: (input: { hostId: string, name: string, roots: string[], token?: string }) => MachineLinksStatus\n stopShare: (hostId: string) => MachineLinksStatus\n setUse: (hostId: string, enabled: boolean) => MachineLinksStatus\n /** Stop everything (remote hosts turned off, or the host closes); the wanted links stay saved. */\n close: () => Promise<void>\n}\n\nfunction readJson<T>(file: string, fallback: T): T {\n try {\n return existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) as T : fallback\n }\n catch {\n return fallback\n }\n}\n\nfunction readConfig(file: string): LinksConfig {\n const raw = readJson<Partial<LinksConfig>>(file, {})\n const shares = Array.isArray(raw.shares)\n ? raw.shares.filter((s): s is ShareConfig => !!s && typeof s.hostId === 'string' && typeof s.name === 'string' && Array.isArray(s.roots) && s.roots.every(r => typeof r === 'string'))\n : []\n const uses = Array.isArray(raw.uses) ? raw.uses.filter((u): u is string => typeof u === 'string') : []\n return { version: 1, shares, uses }\n}\n\n/**\n * A share's name and roots, as a client sent them: every root absolute,\n * existing, and not (by its real path) zidane's data dir, inside it, or a\n * credentials folder (`./executor`'s `shareableRoot`).\n */\nexport function checkShareInput(input: { name: string, roots: string[] }, zidaneDataDir: string): void {\n if (!isValidMachineName(input.name))\n throw new Error(`\"${input.name}\" is not a valid machine name: 1 to 64 letters, digits, \".\", \"-\" or \"_\".`)\n if (!Array.isArray(input.roots) || input.roots.length === 0)\n throw new Error('Share at least one folder.')\n if (input.roots.length > MAX_ROOTS)\n throw new Error(`Share at most ${MAX_ROOTS} folders.`)\n for (const root of input.roots) {\n if (typeof root !== 'string' || !isAbsolute(root))\n throw new Error(`${String(root)} is not an absolute folder path.`)\n shareableRoot(root, zidaneDataDir)\n }\n}\n\nexport function startMachineLinks(options: MachineLinksOptions): MachineLinks {\n const { dataDir, machines } = options\n const configFile = join(dataDir, CONFIG_FILE)\n const statusFile = join(dataDir, STATUS_FILE)\n const lock = createShareLock(join(dataDir, LOCK_FILE))\n // Reading entries and tokens needs no probe: nothing is dialed here but the links themselves.\n const hosts = createHostRegistry({ dataDir, probe: async () => ({ ok: false, error: 'unreachable', message: 'not probed here' }) })\n const instance = `${(options.hostId ?? 'local').slice(0, 40)}-${randomBytes(4).toString('hex')}`\n const pacing = {\n ...(options.minDelayMs !== undefined ? { minDelayMs: options.minDelayMs } : {}),\n ...(options.maxDelayMs !== undefined ? { maxDelayMs: options.maxDelayMs } : {}),\n ...(options.heartbeatMs !== undefined ? { heartbeatMs: options.heartbeatMs } : {}),\n }\n\n const shares = new Map<string, { config: ShareConfig, agent?: MachineAgent, status: MachineShareStatus }>()\n const uses = new Map<string, { link?: ServerMachine, status: MachineUseStatus }>()\n let holder = false\n let closed = false\n let lastPublished = ''\n\n const ownStatus = (): MachineLinksStatus => ({ shares: [...shares.values()].map(s => s.status), uses: [...uses.values()].map(u => u.status) })\n\n /** Shares as the holder reports them (another process may run them). */\n const status = (): MachineLinksStatus => {\n const own = ownStatus()\n if (holder)\n return own\n const config = readConfig(configFile)\n const published = readJson<Partial<MachineLinksStatus>>(statusFile, {})\n const known = new Map((published.shares ?? []).map(s => [s.hostId, s]))\n return {\n shares: config.shares.map(s => known.get(s.hostId) ?? { hostId: s.hostId, name: s.name, roots: s.roots, state: 'connecting' as const, detail: 'waiting for the zidane process that shares this computer' }),\n uses: own.uses,\n }\n }\n\n const publish = (): void => {\n if (closed)\n return\n const snapshot = status()\n const text = JSON.stringify(snapshot)\n if (holder) {\n try {\n writeJsonFileAtomic(statusFile, { shares: snapshot.shares }, OWNER_ONLY)\n }\n catch {\n // Best effort: other processes show \"waiting\" until it lands.\n }\n }\n if (text === lastPublished)\n return\n lastPublished = text\n options.onChange(snapshot)\n }\n\n const entry = (hostId: string): HostEntry => {\n const found = hosts.get(hostId)\n if (!found)\n throw new Error(`No saved server \"${hostId}\": add it on the Hosts page first.`)\n return found\n }\n\n function runShare(config: ShareConfig): void {\n const status: MachineShareStatus = { hostId: config.hostId, name: config.name, roots: config.roots, state: 'connecting' }\n const share: { config: ShareConfig, agent?: MachineAgent, status: MachineShareStatus } = { config, status }\n shares.set(config.hostId, share)\n const set = (state: MachineLinkState, detail?: string): void => {\n share.status = { ...status, state, ...(detail && state !== 'connected' ? { detail } : {}) }\n publish()\n }\n try {\n const server = entry(config.hostId)\n const cred = readCredentialEntry(dataDir, machineCredentialKey(config.hostId))\n const token = cred?.kind === 'apikey' ? cred.value : undefined\n if (!token)\n throw new Error(`No machine token saved for ${server.name}.`)\n share.agent = startMachineAgent({ hostUrl: server.url, token, name: config.name, roots: config.roots, dataDir: join(dataDir, 'machine'), credentialsDataDir: dataDir, ...(options.settings ? { sandbox: options.settings() } : {}), ...pacing, ...(options.appVersion ? { appVersion: options.appVersion } : {}), onStatus: (state, detail) => set(state, detail) })\n }\n catch (err) {\n set('refused', (err as Error).message)\n }\n }\n\n function runUse(hostId: string): void {\n const use: { link?: ServerMachine, status: MachineUseStatus } = { status: { hostId, state: 'connecting' } }\n uses.set(hostId, use)\n const set = (state: MachineLinkState, detail?: string, machineName?: string): void => {\n use.status = { hostId, state, ...(detail && state !== 'connected' ? { detail } : {}), ...(machineName ?? use.status.machineName ? { machineName: machineName ?? use.status.machineName } : {}) }\n publish()\n }\n try {\n const server = entry(hostId)\n const token = hosts.token(hostId)\n if (!token)\n throw new Error(`No token saved for ${server.name}: add it again.`)\n use.link = startServerMachine({ machines, url: server.url, token, expectHostId: hostId, localHostId: instance, ...pacing, ...(options.appVersion ? { appVersion: options.appVersion } : {}), onState: set })\n }\n catch (err) {\n set('refused', (err as Error).message)\n }\n }\n\n const stopShare = async (hostId: string): Promise<void> => {\n const share = shares.get(hostId)\n shares.delete(hostId)\n await share?.agent?.stop()\n }\n\n /** Bring the running links in line with the saved config. */\n function reconcile(): void {\n if (closed)\n return\n const config = readConfig(configFile)\n holder = lock.acquire()\n if (!holder) {\n // Lost the lock (or never had it): another process runs this computer's shares, never two.\n for (const hostId of [...shares.keys()])\n void stopShare(hostId)\n }\n else {\n const wanted = new Map(config.shares.map(s => [s.hostId, s]))\n for (const [hostId, share] of shares) {\n const want = wanted.get(hostId)\n if (!want || JSON.stringify(want) !== JSON.stringify(share.config))\n void stopShare(hostId)\n }\n for (const share of config.shares) {\n if (!shares.has(share.hostId))\n runShare(share)\n }\n }\n const wantedUses = new Set(config.uses)\n for (const [hostId, use] of uses) {\n if (!wantedUses.has(hostId)) {\n use.link?.stop()\n uses.delete(hostId)\n }\n }\n for (const hostId of config.uses) {\n if (!uses.has(hostId))\n runUse(hostId)\n }\n publish()\n }\n\n const writeConfig = (mutate: (config: LinksConfig) => void): MachineLinksStatus => {\n const config = readConfig(configFile)\n mutate(config)\n writeJsonFileAtomic(configFile, config, OWNER_ONLY)\n reconcile()\n return status()\n }\n\n reconcile()\n const timer = setInterval(reconcile, options.pollMs ?? POLL_MS)\n timer.unref?.()\n\n return {\n status,\n startShare(input) {\n checkShareInput(input, dataDir)\n const server = entry(input.hostId)\n const token = input.token?.trim()\n if (token)\n writeCredentialEntry(dataDir, machineCredentialKey(input.hostId), { kind: 'apikey', value: token })\n else if (readCredentialEntry(dataDir, machineCredentialKey(input.hostId))?.kind !== 'apikey')\n throw new Error(`No machine token for ${server.name}: create one there (zidane server token create --scope machine) or let this window create it.`)\n return writeConfig((config) => {\n config.shares = [...config.shares.filter(s => s.hostId !== input.hostId), { hostId: input.hostId, name: input.name, roots: [...input.roots] }]\n })\n },\n stopShare(hostId) {\n return writeConfig((config) => {\n config.shares = config.shares.filter(s => s.hostId !== hostId)\n })\n },\n setUse(hostId, enabled) {\n if (enabled) {\n const server = entry(hostId)\n if (!hosts.token(hostId))\n throw new Error(`No token saved for ${server.name}: add it again.`)\n }\n return writeConfig((config) => {\n config.uses = enabled ? [...new Set([...config.uses, hostId])] : config.uses.filter(u => u !== hostId)\n })\n },\n async close() {\n if (closed)\n return\n closed = true\n clearInterval(timer)\n for (const use of uses.values())\n use.link?.stop()\n uses.clear()\n await Promise.all([...shares.keys()].map(stopShare))\n // Only the holder's own files: a process that lost the lock leaves the new holder's alone.\n if (lock.release())\n rmSync(statusFile, { force: true })\n },\n }\n}\n"],"mappings":"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"}
@@ -12,9 +12,9 @@ import "./row-fields-DvRvz1DD.js";
12
12
  import { f as abortableInstallIo, r as browseSkillSource, s as installSkillFromSpec } from "./skills-Cx8i9kNC.js";
13
13
  import { m as createExtension } from "./system-freeze-BbYXyZuF.js";
14
14
  import { C as referenceProviderWires, L as normalizeBadgeTarget, P as extensionBadgeActions, R as runBadgeAction, c as isPaletteContext, x as createReferenceResolver } from "./palette-CGiUyXqF.js";
15
- import { A as createExtensionHostRuntimeBridge, C as filterRegistryPlugins, O as createExtensionHud, _ as removeInstalledExtension, bt as createExtensionBus, c as runExtensionPanelAction, d as ExtensionInstallError, dt as toWireConfigSchema, ht as createCompletionRunner, i as loadExtensionPanel, l as subscribeExtensionPalette, mt as completionProviderSummaries, nt as probeActionStatuses, o as panelSummaries, p as installExtensionFromSpec, r as listExtensionPaletteItems, rt as runExtensionAction, s as runExtensionPaletteItem, t as createSharedWatches, tt as listActionSummaries, u as subscribeExtensionPanel, v as updateInstalledExtension, xt as serializedPayloadBytes, y as discoverAndBuildExtensionRegistry } from "./extensions-4D_tk7i-.js";
15
+ import { A as createExtensionHostRuntimeBridge, C as filterRegistryPlugins, O as createExtensionHud, _ as removeInstalledExtension, bt as createExtensionBus, c as runExtensionPanelAction, d as ExtensionInstallError, dt as toWireConfigSchema, ht as createCompletionRunner, i as loadExtensionPanel, l as subscribeExtensionPalette, mt as completionProviderSummaries, nt as probeActionStatuses, o as panelSummaries, p as installExtensionFromSpec, r as listExtensionPaletteItems, rt as runExtensionAction, s as runExtensionPaletteItem, t as createSharedWatches, tt as listActionSummaries, u as subscribeExtensionPanel, v as updateInstalledExtension, xt as serializedPayloadBytes, y as discoverAndBuildExtensionRegistry } from "./extensions-Bb_-1vbS.js";
16
16
  import { o as fanoutSink, r as createLogger, t as consoleSink } from "./logger-BQj8QCJp.js";
17
- import { n as compareVersions, t as ZIDANE_VERSION } from "./version-DzI0HMrv.js";
17
+ import { n as compareVersions, t as ZIDANE_VERSION } from "./version-_Yunc9lZ.js";
18
18
  import { n as canSignInRemotely } from "./server-uWgMffBf.js";
19
19
  import { a as sqliteStoreFile } from "./sqlite-store-pX_UwJSm.js";
20
20
  import { t as runUsageAggregates } from "./sqlite-usage-DuEEkueU.js";
@@ -23,7 +23,7 @@ import { B as selectActiveTodos, V as setTodosForRun, _ as availableAgents, o as
23
23
  import { d as sessionModelFallbacks, u as readSessionAccount } from "./turn-operations-CWoCTC_L.js";
24
24
  import { M as isMainSession, N as isSideSession, U as sideSessionParent, f as readSessionSpaceId, r as SPACE_METADATA_KEY, t as DEFAULT_SPACE_ID } from "./spaces-Bgq_mIe_.js";
25
25
  import { A as readProjects, D as listSafelists, M as safelistProjectKey, P as writeProjects, d as discoverProjectMcps, j as removeFromSafelist, p as parseMcpsFile, s as userSystemMdPath } from "./environment-credentials-DEETiFGr.js";
26
- import { $t as readPinnedSkills, An as scopeCwd, C as fetchAllProviderUsage, Dt as listDiagnosticsTools, Et as invalidateDiagnostics, F as connectorGithubToken, Ft as removeMcpServer, G as hasClaudeCodeSetup, Gt as createFileMcpCredentialStore, Hn as listImageModels, I as detectOnboardingEnvironment, It as upsertMcpServer, K as importFromClaudeCode, Kn as testDecisionModel, Lt as createMcpStatusTracker, M as renderSession, Mn as discoverProjectPlugins, Mt as listProjectFiles, Nt as isMcpServerName, Ot as runDiagnostics, Pt as mcpsWritePath, Qt as pinnedSkillsValue, Rt as createExtensionMcpHost, Tt as resolvePaths, Un as listDecisionModels, V as onboardingProviders, Vt as createChatHost, W as countClaudeSessionFiles, X as runOAuthLogin, Xt as isInside, Yn as createMemoryApprovalsStore, Zt as PINNED_SKILLS_META_KEY, an as discoverProjectSkills, at as gitCreateBranch, bt as gitStatus, ct as gitPrList, et as createGitWatches, ft as createCapabilityProbe, hn as insideDeclaredRoots, it as gitCommit, j as exportFilename, jn as buildPluginContributions, k as fetchGithubUsage, lt as gitSwitch, mn as machineDiscoveryCwd, nt as forgetGitCaches, o as watchInstalledVersion, ot as gitInfo, pn as createProviderBuilder, pt as searchWorkspace, q as previewClaudeImport, rt as gitBranches, st as gitPr, tn as isStaleRunMarker, tt as gitSync, ut as cloneRepository, vt as gitDiff, zt as resolveConfig } from "./auto-update-restart-B1ukz5-_.js";
26
+ import { $t as readPinnedSkills, An as scopeCwd, C as fetchAllProviderUsage, Dt as listDiagnosticsTools, Et as invalidateDiagnostics, F as connectorGithubToken, Ft as removeMcpServer, G as hasClaudeCodeSetup, Gt as createFileMcpCredentialStore, Hn as listImageModels, I as detectOnboardingEnvironment, It as upsertMcpServer, K as importFromClaudeCode, Kn as testDecisionModel, Lt as createMcpStatusTracker, M as renderSession, Mn as discoverProjectPlugins, Mt as listProjectFiles, Nt as isMcpServerName, Ot as runDiagnostics, Pt as mcpsWritePath, Qt as pinnedSkillsValue, Rt as createExtensionMcpHost, Tt as resolvePaths, Un as listDecisionModels, V as onboardingProviders, Vt as createChatHost, W as countClaudeSessionFiles, X as runOAuthLogin, Xt as isInside, Yn as createMemoryApprovalsStore, Zt as PINNED_SKILLS_META_KEY, an as discoverProjectSkills, at as gitCreateBranch, bt as gitStatus, ct as gitPrList, et as createGitWatches, ft as createCapabilityProbe, hn as insideDeclaredRoots, it as gitCommit, j as exportFilename, jn as buildPluginContributions, k as fetchGithubUsage, lt as gitSwitch, mn as machineDiscoveryCwd, nt as forgetGitCaches, o as watchInstalledVersion, ot as gitInfo, pn as createProviderBuilder, pt as searchWorkspace, q as previewClaudeImport, rt as gitBranches, st as gitPr, tn as isStaleRunMarker, tt as gitSync, ut as cloneRepository, vt as gitDiff, zt as resolveConfig } from "./auto-update-restart-Z2v5vltT.js";
27
27
  import { S as sumRunCosts, T as toolResultText, _ as runProgress, a as eventsFromTurns, b as sessionEnvironmentSummary, c as headerFromData, g as runInProgress, i as deriveSessionTitle, l as lastContextSizeFromTurns, o as getSessionHeader, s as getSessionMeta, u as listSessionMeta } from "./store-dJ1jDLZW.js";
28
28
  import { c as searchSessionHistoryIndexAsync, n as buildSessionSearchIndexAsync, u as searchSessionIndexAsync } from "./history-search-CbDaXIfU.js";
29
29
  import { X as MachineErrorCode, Z as isValidMachineName, t as parseRepoInput, tt as toMachineName } from "./repo-url--ttQWSay.js";
@@ -35,7 +35,7 @@ import { C as BUILTIN_PROVIDERS, _ as setPreferredCredential, c as providerField
35
35
  import { r as detectAuth } from "./cli-shared-BN-JCQaP.js";
36
36
  import { a as settingsFromWire, c as applyEnvironmentSettingsChange, f as readEnvironmentSettings, o as settingsToWire, r as isWindowOnlySetting } from "./host-scoped-settings-CLPvpPwd.js";
37
37
  import { i as METHOD_NOT_FOUND } from "./connection-BaHd3T-D.js";
38
- import { a as createSignalListeners, d as legacySocketPath, f as probeSocket, i as IpcVersionMismatchError, n as hashValue, o as openIpcClient, r as isRecord$1, s as IPC_BUILD_ID, t as isSyncableSetting, u as ensureDaemonRunning } from "./settings-keys-CEmlwy2H.js";
38
+ import { a as createSignalListeners, d as legacySocketPath, f as probeSocket, i as IpcVersionMismatchError, n as hashValue, o as openIpcClient, r as isRecord$1, s as IPC_BUILD_ID, t as isSyncableSetting, u as ensureDaemonRunning } from "./settings-keys-BP0tJDVS.js";
39
39
  import { c as SESSION_TURNS_MAX_PAGE, r as IpcErrorCode, s as REDACTED, u as SIGNAL_SEQ_FIELD } from "./protocol-DxbYJdHT.js";
40
40
  import { d as checkParams, f as nonNegativeInt, h as sessionIdOf, l as loadOrThrow, m as requiredString, n as changedSessionOf, o as isPagedLoad, p as optionalString, r as createTranscriptReplayCache, t as openReadonlySqlite, u as InvalidParamsError } from "./readonly-db-D9IAT4I8.js";
41
41
  import { n as SYNCABLE_ENVIRONMENT_KINDS, r as SYNC_CATEGORIES, s as SYSTEM_MD_FIELD, u as settingField } from "./types-Skz6Z48Z.js";
@@ -589,7 +589,7 @@ function createMachineLinkHandlers(options) {
589
589
  });
590
590
  };
591
591
  const load = () => {
592
- links ??= import("./links-Mt4r43nD.js").then(({ startMachineLinks }) => startMachineLinks({
592
+ links ??= import("./links-DUeZy_2e.js").then(({ startMachineLinks }) => startMachineLinks({
593
593
  dataDir: env.userDataDir(),
594
594
  machines: host.machines,
595
595
  hostId: options.hostId,
@@ -3466,7 +3466,7 @@ function createMachineServeHandlers(input) {
3466
3466
  return existing.executor;
3467
3467
  }
3468
3468
  makeRoom(principal);
3469
- const { createMachineExecutor } = await import("./executor-Bopn2Uvo.js");
3469
+ const { createMachineExecutor } = await import("./executor-CZFiV2LN.js");
3470
3470
  const entry = {
3471
3471
  peer,
3472
3472
  principal,
@@ -7905,4 +7905,4 @@ async function startInProcessHost(options) {
7905
7905
  //#endregion
7906
7906
  export { createDaemonEnvironment as _, shellPath as a, connectIpcClient as b, HISTORY_WORKER_KILL_SWITCH as c, historyWorkerUrl as d, WorkerRequestError as f, createMachineLinkHandlers as g, createMachineHandlers as h, mergePath as i, createHostTranscriptPager as l, createWorkerRpcClient as m, startInProcessHost as n, shellPathAsync as o, WorkerUnavailableError as p, fixPath as r, startIpcDaemon as s, connectInProcess as t, createTranscriptHistoryWorker as u, createFileApprovalsStore as v, connectOrSpawnDaemon as x, createHostEnvironment as y };
7907
7907
 
7908
- //# sourceMappingURL=memory-Dj9Eeybi.js.map
7908
+ //# sourceMappingURL=memory-T-sU3COB.js.map