Files
openrig/packages/daemon/assets/pi-account-lock.cjs
T
Seán McNamara b1065870f6 feat(pi): switch accounts and preserve selection through the seat lifecycle (#2630)
rig provider switch --account-home and --clear-account-home select or clear an existing Pi seat's account home through a confirmed successor with a fresh conversation and handover; an unconfirmed or failed switch leaves the old account and state intact, and the selection survives restart, resume, restore and daemon restart (migration 151 keeps switch history and creates or copies no credentials). The chosen directory must already hold a readable auth.json, which the runner links into the seat's managed agent directory; env-only accounts and wrappers that create credentials later are outside this path, and an existing seat-local login or unmanaged auth link is preserved through the confirmed switch. Selected accounts load a Node preload that points only their auth-lock operations at the chosen account's auth.json.lock pathname, even when that auth.json is a symlink, and removes its own NODE_OPTIONS entry before Pi starts tools; outside the managed target it does nothing. A Node-hosted launch wrapper that execs Pi can consume the preload, so that Pi gets no shared-auth lock and keeps its native per-seat lock; compiled Pi and wrappers that discard NODE_OPTIONS are likewise not covered. Contributed by seanmcn; requested in #2629.
2026-11-06 23:52:50 -08:00

40 lines
1.8 KiB
JavaScript

// Pi uses proper-lockfile with realpath:false. Redirect only the managed auth
// link's lock operations, so seat aliases and the account owner use one lock.
const fs = require('node:fs');
const path = require('node:path');
const { syncBuiltinESMExports } = require('node:module');
// The runner injects this exact option. Consume only our preload before Pi can
// launch tools, preserving any other Node options supplied by its wrapper.
const preload = `--require=${JSON.stringify(__filename)}`;
if (process.env.NODE_OPTIONS) {
const remaining = process.env.NODE_OPTIONS.replace(preload, '').trim();
if (remaining) process.env.NODE_OPTIONS = remaining;
else delete process.env.NODE_OPTIONS;
}
let localLock, sharedLock;
try {
const agentDir = process.env.PI_CODING_AGENT_DIR;
if (!agentDir) return;
const auth = path.join(agentDir, 'auth.json');
const account = fs.readFileSync(path.join(agentDir, '.openrig-account'), 'utf8');
const source = path.resolve(path.dirname(auth), fs.readlinkSync(auth));
if (source !== path.resolve(account, 'auth.json')) return;
localLock = path.resolve(auth + '.lock');
// Match direct Pi's realpath:false pathname, even when the source is a link.
sharedLock = source + '.lock';
} catch {
// Preparation validates selected credentials. Unrelated processes are untouched.
return;
}
/** Map only the managed auth lock to the shared target; leave all other filesystem paths unchanged. */
function lockPath(value) {
return typeof value === 'string' && path.resolve(value) === localLock ? sharedLock : value;
}
for (const name of ['mkdir', 'rmdir', 'stat', 'utimes']) {
for (const method of [name, name + 'Sync']) {
const original = fs[method];
fs[method] = function (file, ...args) { return original.call(this, lockPath(file), ...args); };
}
}
syncBuiltinESMExports();