switchroom 0.12.14 → 0.12.16
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.
- package/dist/cli/switchroom.js +367 -278
- package/dist/vault/approvals/kernel-server.js +68 -1
- package/dist/vault/broker/server.js +21 -1
- package/package.json +1 -1
- package/telegram-plugin/dist/gateway/gateway.js +119 -70
- package/telegram-plugin/gateway/approval-callback.test.ts +49 -1
- package/telegram-plugin/gateway/approval-callback.ts +85 -67
- package/telegram-plugin/gateway/gateway.ts +61 -2
- package/telegram-plugin/gateway/pending-inbound-buffer.ts +71 -0
- package/telegram-plugin/tests/pending-inbound-buffer.test.ts +118 -1
package/dist/cli/switchroom.js
CHANGED
|
@@ -21409,7 +21409,7 @@ function stripWireFields(entry) {
|
|
|
21409
21409
|
files: entry.files
|
|
21410
21410
|
};
|
|
21411
21411
|
}
|
|
21412
|
-
var MAX_FRAME_BYTES, GetRequestSchema, PutRequestSchema, ListRequestSchema, MintGrantRequestSchema, ListGrantsRequestSchema, RevokeGrantRequestSchema, StatusRequestSchema, LockRequestSchema, PreflightAccessRequestSchema, OkPreflightAccessResponseSchema, ApprovalRequestRequestSchema, ApprovalLookupRequestSchema, ApprovalConsumeRequestSchema, ApprovalRevokeRequestSchema, ApprovalListRequestSchema, ApprovalDecisionModeSchema, ApprovalRecordRequestSchema, RequestSchema, VaultEntrySchema, ErrorCode, OkEntryResponseSchema, OkKeysResponseSchema, BrokerStatus, OkStatusResponseSchema, OkLockResponseSchema, OkPutResponseSchema, OkMintGrantResponseSchema, GrantMetaSchema, OkListGrantsResponseSchema, OkRevokeGrantResponseSchema, OkApprovalRequestResponseSchema, ApprovalDecisionMetaSchema, OkApprovalLookupResponseSchema, OkApprovalConsumeResponseSchema, OkApprovalRevokeResponseSchema, OkApprovalListResponseSchema, OkApprovalRecordResponseSchema, ErrorResponseSchema, ResponseSchema;
|
|
21412
|
+
var MAX_FRAME_BYTES, GetRequestSchema, PutRequestSchema, ListRequestSchema, MintGrantRequestSchema, ListGrantsRequestSchema, RevokeGrantRequestSchema, StatusRequestSchema, LockRequestSchema, PreflightAccessRequestSchema, OkPreflightAccessResponseSchema, ApprovalRequestRequestSchema, ApprovalLookupRequestSchema, ApprovalConsumeRequestSchema, ApprovalRevokeRequestSchema, ApprovalListRequestSchema, ApprovalDecisionModeSchema, ApprovalRecordRequestSchema, ApprovalConsumeRecordRequestSchema, RequestSchema, VaultEntrySchema, ErrorCode, OkEntryResponseSchema, OkKeysResponseSchema, BrokerStatus, OkStatusResponseSchema, OkLockResponseSchema, OkPutResponseSchema, OkMintGrantResponseSchema, GrantMetaSchema, OkListGrantsResponseSchema, OkRevokeGrantResponseSchema, OkApprovalRequestResponseSchema, ApprovalDecisionMetaSchema, OkApprovalLookupResponseSchema, OkApprovalConsumeResponseSchema, OkApprovalRevokeResponseSchema, OkApprovalListResponseSchema, OkApprovalRecordResponseSchema, OkApprovalConsumeRecordResponseSchema, ErrorResponseSchema, ResponseSchema;
|
|
21413
21413
|
var init_protocol = __esm(() => {
|
|
21414
21414
|
init_zod();
|
|
21415
21415
|
MAX_FRAME_BYTES = 64 * 1024;
|
|
@@ -21537,6 +21537,15 @@ var init_protocol = __esm(() => {
|
|
|
21537
21537
|
granted_by_user_id: exports_external.number().int(),
|
|
21538
21538
|
ttl_ms: exports_external.number().int().positive().nullable().optional()
|
|
21539
21539
|
});
|
|
21540
|
+
ApprovalConsumeRecordRequestSchema = exports_external.object({
|
|
21541
|
+
v: exports_external.literal(1),
|
|
21542
|
+
op: exports_external.literal("approval_consume_record"),
|
|
21543
|
+
request_id: exports_external.string().regex(/^[0-9a-f]{32}$/),
|
|
21544
|
+
decision: ApprovalDecisionModeSchema,
|
|
21545
|
+
approver_set: exports_external.array(exports_external.string()),
|
|
21546
|
+
granted_by_user_id: exports_external.number().int(),
|
|
21547
|
+
ttl_ms: exports_external.number().int().positive().nullable().optional()
|
|
21548
|
+
});
|
|
21540
21549
|
RequestSchema = exports_external.discriminatedUnion("op", [
|
|
21541
21550
|
GetRequestSchema,
|
|
21542
21551
|
PutRequestSchema,
|
|
@@ -21552,7 +21561,8 @@ var init_protocol = __esm(() => {
|
|
|
21552
21561
|
ApprovalConsumeRequestSchema,
|
|
21553
21562
|
ApprovalRevokeRequestSchema,
|
|
21554
21563
|
ApprovalListRequestSchema,
|
|
21555
|
-
ApprovalRecordRequestSchema
|
|
21564
|
+
ApprovalRecordRequestSchema,
|
|
21565
|
+
ApprovalConsumeRecordRequestSchema
|
|
21556
21566
|
]);
|
|
21557
21567
|
VaultEntrySchema = exports_external.union([
|
|
21558
21568
|
exports_external.object({ kind: exports_external.literal("string"), value: exports_external.string() }),
|
|
@@ -21674,6 +21684,15 @@ var init_protocol = __esm(() => {
|
|
|
21674
21684
|
ok: exports_external.literal(true),
|
|
21675
21685
|
decision_id: exports_external.string()
|
|
21676
21686
|
});
|
|
21687
|
+
OkApprovalConsumeRecordResponseSchema = exports_external.object({
|
|
21688
|
+
ok: exports_external.literal(true),
|
|
21689
|
+
consumed: exports_external.boolean(),
|
|
21690
|
+
decision_id: exports_external.string().optional(),
|
|
21691
|
+
agent_unit: exports_external.string().optional(),
|
|
21692
|
+
scope: exports_external.string().optional(),
|
|
21693
|
+
action: exports_external.string().optional(),
|
|
21694
|
+
why: exports_external.string().nullable().optional()
|
|
21695
|
+
});
|
|
21677
21696
|
ErrorResponseSchema = exports_external.object({
|
|
21678
21697
|
ok: exports_external.literal(false),
|
|
21679
21698
|
code: ErrorCode,
|
|
@@ -21695,6 +21714,7 @@ var init_protocol = __esm(() => {
|
|
|
21695
21714
|
OkApprovalRevokeResponseSchema,
|
|
21696
21715
|
OkApprovalListResponseSchema,
|
|
21697
21716
|
OkApprovalRecordResponseSchema,
|
|
21717
|
+
OkApprovalConsumeRecordResponseSchema,
|
|
21698
21718
|
ErrorResponseSchema
|
|
21699
21719
|
]);
|
|
21700
21720
|
});
|
|
@@ -28282,12 +28302,240 @@ function runHostdChecks(config, deps = {}) {
|
|
|
28282
28302
|
var HOSTD_CONTAINER = "switchroom-hostd", HOSTD_DRIFT_HOURS = 2;
|
|
28283
28303
|
var init_doctor_hostd = () => {};
|
|
28284
28304
|
|
|
28305
|
+
// src/cli/doctor-secret-access.ts
|
|
28306
|
+
import {
|
|
28307
|
+
accessSync,
|
|
28308
|
+
constants as fsConstants4,
|
|
28309
|
+
existsSync as existsSync46,
|
|
28310
|
+
realpathSync as realpathSync4,
|
|
28311
|
+
statSync as statSync20
|
|
28312
|
+
} from "node:fs";
|
|
28313
|
+
import { userInfo, homedir as homedir20 } from "node:os";
|
|
28314
|
+
import { join as join38 } from "node:path";
|
|
28315
|
+
function resolveVaultPath2(config) {
|
|
28316
|
+
return config.vault?.path ? config.vault.path.replace(/^~/, process.env.HOME ?? "") : resolveStatePath("vault.enc");
|
|
28317
|
+
}
|
|
28318
|
+
function defaultStatVault(path4) {
|
|
28319
|
+
if (!existsSync46(path4)) {
|
|
28320
|
+
return { exists: false, readable: false, uid: -1, mode: 0, realPath: path4 };
|
|
28321
|
+
}
|
|
28322
|
+
let real = path4;
|
|
28323
|
+
try {
|
|
28324
|
+
real = realpathSync4(path4);
|
|
28325
|
+
} catch {}
|
|
28326
|
+
let uid = -1;
|
|
28327
|
+
let mode = 0;
|
|
28328
|
+
try {
|
|
28329
|
+
const s = statSync20(real);
|
|
28330
|
+
uid = s.uid;
|
|
28331
|
+
mode = s.mode & 511;
|
|
28332
|
+
} catch {
|
|
28333
|
+
return { exists: true, readable: false, uid, mode, realPath: real };
|
|
28334
|
+
}
|
|
28335
|
+
let readable = false;
|
|
28336
|
+
try {
|
|
28337
|
+
accessSync(real, fsConstants4.R_OK);
|
|
28338
|
+
readable = true;
|
|
28339
|
+
} catch {
|
|
28340
|
+
readable = false;
|
|
28341
|
+
}
|
|
28342
|
+
return { exists: true, readable, uid, mode, realPath: real };
|
|
28343
|
+
}
|
|
28344
|
+
function collectVaultRefs2(value, out) {
|
|
28345
|
+
if (typeof value === "string") {
|
|
28346
|
+
if (value.startsWith("vault:")) {
|
|
28347
|
+
const key = value.slice("vault:".length).split("#")[0].trim();
|
|
28348
|
+
if (key)
|
|
28349
|
+
out.add(key);
|
|
28350
|
+
}
|
|
28351
|
+
return;
|
|
28352
|
+
}
|
|
28353
|
+
if (Array.isArray(value)) {
|
|
28354
|
+
for (const v of value)
|
|
28355
|
+
collectVaultRefs2(v, out);
|
|
28356
|
+
return;
|
|
28357
|
+
}
|
|
28358
|
+
if (value && typeof value === "object") {
|
|
28359
|
+
for (const v of Object.values(value)) {
|
|
28360
|
+
collectVaultRefs2(v, out);
|
|
28361
|
+
}
|
|
28362
|
+
}
|
|
28363
|
+
}
|
|
28364
|
+
function collectNeeds(resolved) {
|
|
28365
|
+
const cronKeys = new Set;
|
|
28366
|
+
for (const entry of resolved.schedule ?? []) {
|
|
28367
|
+
for (const s of entry.secrets ?? [])
|
|
28368
|
+
cronKeys.add(s);
|
|
28369
|
+
}
|
|
28370
|
+
const refKeys = new Set;
|
|
28371
|
+
collectVaultRefs2(resolved, refKeys);
|
|
28372
|
+
return { needed: new Set([...cronKeys, ...refKeys]), cronKeys };
|
|
28373
|
+
}
|
|
28374
|
+
function keyGap(key, isCron, exists, acl, scope) {
|
|
28375
|
+
const isGoogleSlot = key.startsWith("google:");
|
|
28376
|
+
if (!isGoogleSlot && !exists)
|
|
28377
|
+
return `'${key}' missing from the vault`;
|
|
28378
|
+
if (isCron && !acl.allow) {
|
|
28379
|
+
return `'${key}' (cron) \u2014 no static ACL grants read (${acl.reason})`;
|
|
28380
|
+
}
|
|
28381
|
+
if (!scope.allow) {
|
|
28382
|
+
return `'${key}' \u2014 per-key scope denies read (${scope.reason})`;
|
|
28383
|
+
}
|
|
28384
|
+
return null;
|
|
28385
|
+
}
|
|
28386
|
+
async function defaultPreflight(socketPath, agent, keys) {
|
|
28387
|
+
const r = await rpcRaw({ v: 1, op: "preflight_access", agent, keys }, { socket: socketPath, timeoutMs: 5000 });
|
|
28388
|
+
if (r.kind === "unreachable")
|
|
28389
|
+
return { kind: "unreachable", msg: r.msg };
|
|
28390
|
+
const resp = r.resp;
|
|
28391
|
+
if (resp.ok === true && resp.op === "preflight_access") {
|
|
28392
|
+
return {
|
|
28393
|
+
kind: "ok",
|
|
28394
|
+
results: resp.results
|
|
28395
|
+
};
|
|
28396
|
+
}
|
|
28397
|
+
if (resp.ok === false && resp.code === "LOCKED")
|
|
28398
|
+
return { kind: "locked" };
|
|
28399
|
+
return {
|
|
28400
|
+
kind: "unreachable",
|
|
28401
|
+
msg: resp.ok === false ? `broker error ${resp.code}: ${resp.msg}` : "unexpected broker response"
|
|
28402
|
+
};
|
|
28403
|
+
}
|
|
28404
|
+
async function runSecretAccessChecks(config, deps = {}) {
|
|
28405
|
+
const results = [];
|
|
28406
|
+
const vaultPath = deps.vaultPath ?? resolveVaultPath2(config);
|
|
28407
|
+
const statVault = deps.statVault ?? defaultStatVault;
|
|
28408
|
+
const selfUid = deps.selfUid ?? (typeof process.getuid === "function" ? process.getuid() : -1);
|
|
28409
|
+
let selfUser = deps.selfUser;
|
|
28410
|
+
if (selfUser === undefined) {
|
|
28411
|
+
try {
|
|
28412
|
+
selfUser = userInfo().username;
|
|
28413
|
+
} catch {
|
|
28414
|
+
selfUser = "<you>";
|
|
28415
|
+
}
|
|
28416
|
+
}
|
|
28417
|
+
const vf = statVault(vaultPath);
|
|
28418
|
+
if (!vf.exists) {
|
|
28419
|
+
results.push({
|
|
28420
|
+
name: "vault: operator readable",
|
|
28421
|
+
status: "ok",
|
|
28422
|
+
detail: `vault file not present at ${vaultPath} \u2014 see the Vault section`
|
|
28423
|
+
});
|
|
28424
|
+
} else if (!vf.readable) {
|
|
28425
|
+
results.push({
|
|
28426
|
+
name: "vault: operator readable",
|
|
28427
|
+
status: "fail",
|
|
28428
|
+
detail: `${vf.realPath} is owned by uid ${vf.uid} (mode 0${vf.mode.toString(8)}) \u2014 ` + `the operator (uid ${selfUid} ${selfUser}) cannot read it, so every ` + `\`switchroom vault \u2026\` fails. The broker still works (CAP_DAC_READ_SEARCH), ` + `which masks this until you touch the vault directly.`,
|
|
28429
|
+
fix: `sudo chown ${selfUser}:${selfUser} ${vf.realPath}`
|
|
28430
|
+
});
|
|
28431
|
+
} else {
|
|
28432
|
+
results.push({
|
|
28433
|
+
name: "vault: operator readable",
|
|
28434
|
+
status: "ok",
|
|
28435
|
+
detail: `operator can read ${vf.realPath}`
|
|
28436
|
+
});
|
|
28437
|
+
}
|
|
28438
|
+
const pushAgentResult = (name, total, gaps) => {
|
|
28439
|
+
results.push(gaps.length === 0 ? {
|
|
28440
|
+
name: `secret access: ${name}`,
|
|
28441
|
+
status: "ok",
|
|
28442
|
+
detail: `${total} secret(s): all present + ACL ok`
|
|
28443
|
+
} : {
|
|
28444
|
+
name: `secret access: ${name}`,
|
|
28445
|
+
status: "fail",
|
|
28446
|
+
detail: `${gaps.length}/${total} unreachable \u2014 ${gaps.join("; ")}`,
|
|
28447
|
+
fix: "`switchroom vault set <key>` for missing keys; " + "`switchroom vault set <key> --allow " + name + "` to grant this agent read access"
|
|
28448
|
+
});
|
|
28449
|
+
};
|
|
28450
|
+
const passphrase = deps.passphrase ?? process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
28451
|
+
if (!passphrase) {
|
|
28452
|
+
const sock = deps.brokerOperatorSocket ?? join38(homedir20(), ".switchroom", "broker-operator", "sock");
|
|
28453
|
+
const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
|
|
28454
|
+
for (const name of Object.keys(config.agents ?? {})) {
|
|
28455
|
+
const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[name]);
|
|
28456
|
+
const { needed, cronKeys } = collectNeeds(resolved);
|
|
28457
|
+
if (needed.size === 0) {
|
|
28458
|
+
results.push({
|
|
28459
|
+
name: `secret access: ${name}`,
|
|
28460
|
+
status: "ok",
|
|
28461
|
+
detail: "no declared vault secrets"
|
|
28462
|
+
});
|
|
28463
|
+
continue;
|
|
28464
|
+
}
|
|
28465
|
+
const out = await preflight(name, [...needed].sort());
|
|
28466
|
+
if (out.kind === "unreachable" || out.kind === "locked") {
|
|
28467
|
+
results.push({
|
|
28468
|
+
name: "agent secret access",
|
|
28469
|
+
status: "skip",
|
|
28470
|
+
detail: out.kind === "locked" ? "vault-broker is locked \u2014 cron-secret existence/ACL unverified (re-run after it unlocks; it auto-unlocks on boot)" : `vault-broker operator socket unreachable (${out.msg}) and SWITCHROOM_VAULT_PASSPHRASE unset \u2014 cron-secret existence/ACL unverified`,
|
|
28471
|
+
fix: "Ensure the broker operator socket (~/.switchroom/broker-operator/sock) is bound, or export SWITCHROOM_VAULT_PASSPHRASE and re-run `switchroom doctor`"
|
|
28472
|
+
});
|
|
28473
|
+
return results;
|
|
28474
|
+
}
|
|
28475
|
+
const byKey = new Map(out.results.map((r) => [r.key, r]));
|
|
28476
|
+
const gaps = [];
|
|
28477
|
+
for (const key of [...needed].sort()) {
|
|
28478
|
+
const r = byKey.get(key);
|
|
28479
|
+
if (r === undefined) {
|
|
28480
|
+
gaps.push(`'${key}' \u2014 broker returned no result`);
|
|
28481
|
+
continue;
|
|
28482
|
+
}
|
|
28483
|
+
const g = keyGap(key, cronKeys.has(key), r.exists, { allow: r.acl_ok, reason: r.acl_reason }, { allow: r.scope_ok, reason: r.scope_reason });
|
|
28484
|
+
if (g)
|
|
28485
|
+
gaps.push(g);
|
|
28486
|
+
}
|
|
28487
|
+
pushAgentResult(name, needed.size, gaps);
|
|
28488
|
+
}
|
|
28489
|
+
return results;
|
|
28490
|
+
}
|
|
28491
|
+
let entries;
|
|
28492
|
+
try {
|
|
28493
|
+
entries = (deps.openVault ?? openVault)(passphrase, vaultPath);
|
|
28494
|
+
} catch (err) {
|
|
28495
|
+
results.push({
|
|
28496
|
+
name: "agent secret access",
|
|
28497
|
+
status: vf.readable ? "fail" : "warn",
|
|
28498
|
+
detail: `cannot open the vault: ${err.message}`,
|
|
28499
|
+
fix: vf.readable ? "SWITCHROOM_VAULT_PASSPHRASE may be wrong, or the vault is corrupt" : "fix the vault file ownership above first (operator cannot read it)"
|
|
28500
|
+
});
|
|
28501
|
+
return results;
|
|
28502
|
+
}
|
|
28503
|
+
for (const name of Object.keys(config.agents ?? {})) {
|
|
28504
|
+
const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[name]);
|
|
28505
|
+
const { needed, cronKeys } = collectNeeds(resolved);
|
|
28506
|
+
if (needed.size === 0) {
|
|
28507
|
+
results.push({
|
|
28508
|
+
name: `secret access: ${name}`,
|
|
28509
|
+
status: "ok",
|
|
28510
|
+
detail: "no declared vault secrets"
|
|
28511
|
+
});
|
|
28512
|
+
continue;
|
|
28513
|
+
}
|
|
28514
|
+
const gaps = [];
|
|
28515
|
+
for (const key of [...needed].sort()) {
|
|
28516
|
+
const g = keyGap(key, cronKeys.has(key), key in entries, checkAclByAgent(config, name, key), checkEntryScope(entries[key]?.scope, name));
|
|
28517
|
+
if (g)
|
|
28518
|
+
gaps.push(g);
|
|
28519
|
+
}
|
|
28520
|
+
pushAgentResult(name, needed.size, gaps);
|
|
28521
|
+
}
|
|
28522
|
+
return results;
|
|
28523
|
+
}
|
|
28524
|
+
var init_doctor_secret_access = __esm(() => {
|
|
28525
|
+
init_paths();
|
|
28526
|
+
init_merge();
|
|
28527
|
+
init_vault();
|
|
28528
|
+
init_acl();
|
|
28529
|
+
init_client();
|
|
28530
|
+
});
|
|
28531
|
+
|
|
28285
28532
|
// src/cli/doctor-drive.ts
|
|
28286
28533
|
import {
|
|
28287
28534
|
existsSync as realExistsSync,
|
|
28288
28535
|
readFileSync as realReadFileSync
|
|
28289
28536
|
} from "node:fs";
|
|
28290
|
-
import { join as
|
|
28537
|
+
import { join as join39, resolve as resolve28 } from "node:path";
|
|
28538
|
+
import { homedir as homedir21 } from "node:os";
|
|
28291
28539
|
function resolveDeps(config, deps) {
|
|
28292
28540
|
let agentsDir = deps.agentsDir;
|
|
28293
28541
|
if (agentsDir === undefined) {
|
|
@@ -28410,8 +28658,8 @@ function checkScaffoldWiring(config, driveAgents, d) {
|
|
|
28410
28658
|
});
|
|
28411
28659
|
continue;
|
|
28412
28660
|
}
|
|
28413
|
-
const mcpPath =
|
|
28414
|
-
const claudeJsonPath =
|
|
28661
|
+
const mcpPath = join39(agentDir, ".mcp.json");
|
|
28662
|
+
const claudeJsonPath = join39(agentDir, ".claude", ".claude.json");
|
|
28415
28663
|
const mcpRead = readJson(d, mcpPath);
|
|
28416
28664
|
const trustRead = readJson(d, claudeJsonPath);
|
|
28417
28665
|
if (mcpRead.kind === "unreadable" || trustRead.kind === "unreadable") {
|
|
@@ -28495,16 +28743,78 @@ function runDriveChecks(config, deps = {}) {
|
|
|
28495
28743
|
const results = [];
|
|
28496
28744
|
const matrix = checkConfigMatrix(config);
|
|
28497
28745
|
results.push(...matrix);
|
|
28498
|
-
const driveAgents =
|
|
28746
|
+
const driveAgents = computeDriveAgents(config);
|
|
28747
|
+
results.push(...checkOAuthClient(config, driveAgents.length > 0));
|
|
28748
|
+
results.push(...checkScaffoldWiring(config, driveAgents, d));
|
|
28749
|
+
return results;
|
|
28750
|
+
}
|
|
28751
|
+
function computeDriveAgents(config) {
|
|
28752
|
+
const accounts = config.google_accounts;
|
|
28753
|
+
return Object.keys(config.agents ?? {}).filter((name) => {
|
|
28499
28754
|
const acct = agentAccount(config, name);
|
|
28500
28755
|
return !!acct && !!accounts?.[acct] && (accounts[acct].enabled_for ?? []).includes(name);
|
|
28501
28756
|
});
|
|
28502
|
-
|
|
28503
|
-
|
|
28757
|
+
}
|
|
28758
|
+
async function runDriveBrokerReachabilityChecks(config, deps = {}) {
|
|
28759
|
+
const driveAgents = computeDriveAgents(config);
|
|
28760
|
+
if (driveAgents.length === 0)
|
|
28761
|
+
return [];
|
|
28762
|
+
const gw = config.google_workspace;
|
|
28763
|
+
const idKey = vaultRefKey(gw?.google_client_id);
|
|
28764
|
+
const secretKey = vaultRefKey(gw?.google_client_secret);
|
|
28765
|
+
const vaultKeys = [idKey, secretKey].filter((k) => k !== null);
|
|
28766
|
+
if (vaultKeys.length === 0) {
|
|
28767
|
+
return [
|
|
28768
|
+
{
|
|
28769
|
+
name: "drive: OAuth client broker-reachable",
|
|
28770
|
+
status: "ok",
|
|
28771
|
+
detail: "client credential is a literal (not a vault: ref) \u2014 the launcher reads it from config; no broker grant needed"
|
|
28772
|
+
}
|
|
28773
|
+
];
|
|
28774
|
+
}
|
|
28775
|
+
const sock = deps.brokerOperatorSocket ?? join39(homedir21(), ".switchroom", "broker-operator", "sock");
|
|
28776
|
+
const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
|
|
28777
|
+
const results = [];
|
|
28778
|
+
for (const agent of driveAgents) {
|
|
28779
|
+
const outcome = await preflight(agent, vaultKeys);
|
|
28780
|
+
if (outcome.kind === "locked") {
|
|
28781
|
+
results.push({
|
|
28782
|
+
name: `drive: ${agent} client-cred broker-reachable`,
|
|
28783
|
+
status: "skip",
|
|
28784
|
+
detail: "vault-broker is locked \u2014 cannot verify; the fleet's launchers/crons are dead while locked anyway (not a Drive-specific fault)"
|
|
28785
|
+
});
|
|
28786
|
+
continue;
|
|
28787
|
+
}
|
|
28788
|
+
if (outcome.kind === "unreachable") {
|
|
28789
|
+
results.push({
|
|
28790
|
+
name: `drive: ${agent} client-cred broker-reachable`,
|
|
28791
|
+
status: "skip",
|
|
28792
|
+
detail: `broker operator socket unreachable (${outcome.msg}) \u2014 cannot verify; not a false fail`
|
|
28793
|
+
});
|
|
28794
|
+
continue;
|
|
28795
|
+
}
|
|
28796
|
+
const denied = outcome.results.filter((r) => !r.acl_ok);
|
|
28797
|
+
if (denied.length === 0) {
|
|
28798
|
+
results.push({
|
|
28799
|
+
name: `drive: ${agent} client-cred broker-reachable`,
|
|
28800
|
+
status: "ok",
|
|
28801
|
+
detail: `vault-broker will serve the OAuth client credential (${vaultKeys.join(", ")}) to '${agent}'`
|
|
28802
|
+
});
|
|
28803
|
+
continue;
|
|
28804
|
+
}
|
|
28805
|
+
const d0 = denied[0];
|
|
28806
|
+
results.push({
|
|
28807
|
+
name: `drive: ${agent} client-cred broker-reachable`,
|
|
28808
|
+
status: "fail",
|
|
28809
|
+
detail: `vault-broker DENIES '${agent}' the OAuth client key '${d0.key}' (${d0.acl_reason ?? "no ACL grant"}) \u2014 the gdrive MCP launcher exits before spawning and Drive silently never works for this agent`,
|
|
28810
|
+
fix: `confirm '${agent}' is in google_accounts[<account>].enabled_for[] and has agents.${agent}.google_workspace.account set (v0.12.14+ broker grants the client credential off that gate automatically); then ensure the broker is running the v0.12.14+ image`
|
|
28811
|
+
});
|
|
28812
|
+
}
|
|
28504
28813
|
return results;
|
|
28505
28814
|
}
|
|
28506
28815
|
var init_doctor_drive = __esm(() => {
|
|
28507
28816
|
init_loader();
|
|
28817
|
+
init_doctor_secret_access();
|
|
28508
28818
|
});
|
|
28509
28819
|
|
|
28510
28820
|
// src/cli/doctor-credentials-migration.ts
|
|
@@ -28513,11 +28823,11 @@ import {
|
|
|
28513
28823
|
readdirSync as realReaddirSync,
|
|
28514
28824
|
statSync as realStatSync
|
|
28515
28825
|
} from "node:fs";
|
|
28516
|
-
import { homedir as
|
|
28517
|
-
import { join as
|
|
28826
|
+
import { homedir as homedir22 } from "node:os";
|
|
28827
|
+
import { join as join40 } from "node:path";
|
|
28518
28828
|
function runCredentialsMigrationChecks(config, deps = {}) {
|
|
28519
|
-
const credDir = deps.credentialsDir ??
|
|
28520
|
-
const
|
|
28829
|
+
const credDir = deps.credentialsDir ?? join40(homedir22(), ".switchroom", "credentials");
|
|
28830
|
+
const existsSync47 = deps.existsSync ?? ((p) => realExistsSync2(p));
|
|
28521
28831
|
const readdirSync17 = deps.readdirSync ?? ((p) => realReaddirSync(p));
|
|
28522
28832
|
const isDirectory = deps.isDirectory ?? ((p) => {
|
|
28523
28833
|
try {
|
|
@@ -28526,7 +28836,7 @@ function runCredentialsMigrationChecks(config, deps = {}) {
|
|
|
28526
28836
|
return false;
|
|
28527
28837
|
}
|
|
28528
28838
|
});
|
|
28529
|
-
if (!
|
|
28839
|
+
if (!existsSync47(credDir))
|
|
28530
28840
|
return [];
|
|
28531
28841
|
const agentNames = new Set(Object.keys(config.agents ?? {}));
|
|
28532
28842
|
let entries;
|
|
@@ -28544,7 +28854,7 @@ function runCredentialsMigrationChecks(config, deps = {}) {
|
|
|
28544
28854
|
const flat = [];
|
|
28545
28855
|
const perAgentDirs = [];
|
|
28546
28856
|
for (const e of entries) {
|
|
28547
|
-
const full =
|
|
28857
|
+
const full = join40(credDir, e);
|
|
28548
28858
|
if (isDirectory(full) && agentNames.has(e)) {
|
|
28549
28859
|
perAgentDirs.push(e);
|
|
28550
28860
|
} else {
|
|
@@ -28571,233 +28881,6 @@ function runCredentialsMigrationChecks(config, deps = {}) {
|
|
|
28571
28881
|
}
|
|
28572
28882
|
var init_doctor_credentials_migration = () => {};
|
|
28573
28883
|
|
|
28574
|
-
// src/cli/doctor-secret-access.ts
|
|
28575
|
-
import {
|
|
28576
|
-
accessSync,
|
|
28577
|
-
constants as fsConstants4,
|
|
28578
|
-
existsSync as existsSync46,
|
|
28579
|
-
realpathSync as realpathSync4,
|
|
28580
|
-
statSync as statSync20
|
|
28581
|
-
} from "node:fs";
|
|
28582
|
-
import { userInfo, homedir as homedir21 } from "node:os";
|
|
28583
|
-
import { join as join40 } from "node:path";
|
|
28584
|
-
function resolveVaultPath2(config) {
|
|
28585
|
-
return config.vault?.path ? config.vault.path.replace(/^~/, process.env.HOME ?? "") : resolveStatePath("vault.enc");
|
|
28586
|
-
}
|
|
28587
|
-
function defaultStatVault(path4) {
|
|
28588
|
-
if (!existsSync46(path4)) {
|
|
28589
|
-
return { exists: false, readable: false, uid: -1, mode: 0, realPath: path4 };
|
|
28590
|
-
}
|
|
28591
|
-
let real = path4;
|
|
28592
|
-
try {
|
|
28593
|
-
real = realpathSync4(path4);
|
|
28594
|
-
} catch {}
|
|
28595
|
-
let uid = -1;
|
|
28596
|
-
let mode = 0;
|
|
28597
|
-
try {
|
|
28598
|
-
const s = statSync20(real);
|
|
28599
|
-
uid = s.uid;
|
|
28600
|
-
mode = s.mode & 511;
|
|
28601
|
-
} catch {
|
|
28602
|
-
return { exists: true, readable: false, uid, mode, realPath: real };
|
|
28603
|
-
}
|
|
28604
|
-
let readable = false;
|
|
28605
|
-
try {
|
|
28606
|
-
accessSync(real, fsConstants4.R_OK);
|
|
28607
|
-
readable = true;
|
|
28608
|
-
} catch {
|
|
28609
|
-
readable = false;
|
|
28610
|
-
}
|
|
28611
|
-
return { exists: true, readable, uid, mode, realPath: real };
|
|
28612
|
-
}
|
|
28613
|
-
function collectVaultRefs2(value, out) {
|
|
28614
|
-
if (typeof value === "string") {
|
|
28615
|
-
if (value.startsWith("vault:")) {
|
|
28616
|
-
const key = value.slice("vault:".length).split("#")[0].trim();
|
|
28617
|
-
if (key)
|
|
28618
|
-
out.add(key);
|
|
28619
|
-
}
|
|
28620
|
-
return;
|
|
28621
|
-
}
|
|
28622
|
-
if (Array.isArray(value)) {
|
|
28623
|
-
for (const v of value)
|
|
28624
|
-
collectVaultRefs2(v, out);
|
|
28625
|
-
return;
|
|
28626
|
-
}
|
|
28627
|
-
if (value && typeof value === "object") {
|
|
28628
|
-
for (const v of Object.values(value)) {
|
|
28629
|
-
collectVaultRefs2(v, out);
|
|
28630
|
-
}
|
|
28631
|
-
}
|
|
28632
|
-
}
|
|
28633
|
-
function collectNeeds(resolved) {
|
|
28634
|
-
const cronKeys = new Set;
|
|
28635
|
-
for (const entry of resolved.schedule ?? []) {
|
|
28636
|
-
for (const s of entry.secrets ?? [])
|
|
28637
|
-
cronKeys.add(s);
|
|
28638
|
-
}
|
|
28639
|
-
const refKeys = new Set;
|
|
28640
|
-
collectVaultRefs2(resolved, refKeys);
|
|
28641
|
-
return { needed: new Set([...cronKeys, ...refKeys]), cronKeys };
|
|
28642
|
-
}
|
|
28643
|
-
function keyGap(key, isCron, exists, acl, scope) {
|
|
28644
|
-
const isGoogleSlot = key.startsWith("google:");
|
|
28645
|
-
if (!isGoogleSlot && !exists)
|
|
28646
|
-
return `'${key}' missing from the vault`;
|
|
28647
|
-
if (isCron && !acl.allow) {
|
|
28648
|
-
return `'${key}' (cron) \u2014 no static ACL grants read (${acl.reason})`;
|
|
28649
|
-
}
|
|
28650
|
-
if (!scope.allow) {
|
|
28651
|
-
return `'${key}' \u2014 per-key scope denies read (${scope.reason})`;
|
|
28652
|
-
}
|
|
28653
|
-
return null;
|
|
28654
|
-
}
|
|
28655
|
-
async function defaultPreflight(socketPath, agent, keys) {
|
|
28656
|
-
const r = await rpcRaw({ v: 1, op: "preflight_access", agent, keys }, { socket: socketPath, timeoutMs: 5000 });
|
|
28657
|
-
if (r.kind === "unreachable")
|
|
28658
|
-
return { kind: "unreachable", msg: r.msg };
|
|
28659
|
-
const resp = r.resp;
|
|
28660
|
-
if (resp.ok === true && resp.op === "preflight_access") {
|
|
28661
|
-
return {
|
|
28662
|
-
kind: "ok",
|
|
28663
|
-
results: resp.results
|
|
28664
|
-
};
|
|
28665
|
-
}
|
|
28666
|
-
if (resp.ok === false && resp.code === "LOCKED")
|
|
28667
|
-
return { kind: "locked" };
|
|
28668
|
-
return {
|
|
28669
|
-
kind: "unreachable",
|
|
28670
|
-
msg: resp.ok === false ? `broker error ${resp.code}: ${resp.msg}` : "unexpected broker response"
|
|
28671
|
-
};
|
|
28672
|
-
}
|
|
28673
|
-
async function runSecretAccessChecks(config, deps = {}) {
|
|
28674
|
-
const results = [];
|
|
28675
|
-
const vaultPath = deps.vaultPath ?? resolveVaultPath2(config);
|
|
28676
|
-
const statVault = deps.statVault ?? defaultStatVault;
|
|
28677
|
-
const selfUid = deps.selfUid ?? (typeof process.getuid === "function" ? process.getuid() : -1);
|
|
28678
|
-
let selfUser = deps.selfUser;
|
|
28679
|
-
if (selfUser === undefined) {
|
|
28680
|
-
try {
|
|
28681
|
-
selfUser = userInfo().username;
|
|
28682
|
-
} catch {
|
|
28683
|
-
selfUser = "<you>";
|
|
28684
|
-
}
|
|
28685
|
-
}
|
|
28686
|
-
const vf = statVault(vaultPath);
|
|
28687
|
-
if (!vf.exists) {
|
|
28688
|
-
results.push({
|
|
28689
|
-
name: "vault: operator readable",
|
|
28690
|
-
status: "ok",
|
|
28691
|
-
detail: `vault file not present at ${vaultPath} \u2014 see the Vault section`
|
|
28692
|
-
});
|
|
28693
|
-
} else if (!vf.readable) {
|
|
28694
|
-
results.push({
|
|
28695
|
-
name: "vault: operator readable",
|
|
28696
|
-
status: "fail",
|
|
28697
|
-
detail: `${vf.realPath} is owned by uid ${vf.uid} (mode 0${vf.mode.toString(8)}) \u2014 ` + `the operator (uid ${selfUid} ${selfUser}) cannot read it, so every ` + `\`switchroom vault \u2026\` fails. The broker still works (CAP_DAC_READ_SEARCH), ` + `which masks this until you touch the vault directly.`,
|
|
28698
|
-
fix: `sudo chown ${selfUser}:${selfUser} ${vf.realPath}`
|
|
28699
|
-
});
|
|
28700
|
-
} else {
|
|
28701
|
-
results.push({
|
|
28702
|
-
name: "vault: operator readable",
|
|
28703
|
-
status: "ok",
|
|
28704
|
-
detail: `operator can read ${vf.realPath}`
|
|
28705
|
-
});
|
|
28706
|
-
}
|
|
28707
|
-
const pushAgentResult = (name, total, gaps) => {
|
|
28708
|
-
results.push(gaps.length === 0 ? {
|
|
28709
|
-
name: `secret access: ${name}`,
|
|
28710
|
-
status: "ok",
|
|
28711
|
-
detail: `${total} secret(s): all present + ACL ok`
|
|
28712
|
-
} : {
|
|
28713
|
-
name: `secret access: ${name}`,
|
|
28714
|
-
status: "fail",
|
|
28715
|
-
detail: `${gaps.length}/${total} unreachable \u2014 ${gaps.join("; ")}`,
|
|
28716
|
-
fix: "`switchroom vault set <key>` for missing keys; " + "`switchroom vault set <key> --allow " + name + "` to grant this agent read access"
|
|
28717
|
-
});
|
|
28718
|
-
};
|
|
28719
|
-
const passphrase = deps.passphrase ?? process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
28720
|
-
if (!passphrase) {
|
|
28721
|
-
const sock = deps.brokerOperatorSocket ?? join40(homedir21(), ".switchroom", "broker-operator", "sock");
|
|
28722
|
-
const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
|
|
28723
|
-
for (const name of Object.keys(config.agents ?? {})) {
|
|
28724
|
-
const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[name]);
|
|
28725
|
-
const { needed, cronKeys } = collectNeeds(resolved);
|
|
28726
|
-
if (needed.size === 0) {
|
|
28727
|
-
results.push({
|
|
28728
|
-
name: `secret access: ${name}`,
|
|
28729
|
-
status: "ok",
|
|
28730
|
-
detail: "no declared vault secrets"
|
|
28731
|
-
});
|
|
28732
|
-
continue;
|
|
28733
|
-
}
|
|
28734
|
-
const out = await preflight(name, [...needed].sort());
|
|
28735
|
-
if (out.kind === "unreachable" || out.kind === "locked") {
|
|
28736
|
-
results.push({
|
|
28737
|
-
name: "agent secret access",
|
|
28738
|
-
status: "skip",
|
|
28739
|
-
detail: out.kind === "locked" ? "vault-broker is locked \u2014 cron-secret existence/ACL unverified (re-run after it unlocks; it auto-unlocks on boot)" : `vault-broker operator socket unreachable (${out.msg}) and SWITCHROOM_VAULT_PASSPHRASE unset \u2014 cron-secret existence/ACL unverified`,
|
|
28740
|
-
fix: "Ensure the broker operator socket (~/.switchroom/broker-operator/sock) is bound, or export SWITCHROOM_VAULT_PASSPHRASE and re-run `switchroom doctor`"
|
|
28741
|
-
});
|
|
28742
|
-
return results;
|
|
28743
|
-
}
|
|
28744
|
-
const byKey = new Map(out.results.map((r) => [r.key, r]));
|
|
28745
|
-
const gaps = [];
|
|
28746
|
-
for (const key of [...needed].sort()) {
|
|
28747
|
-
const r = byKey.get(key);
|
|
28748
|
-
if (r === undefined) {
|
|
28749
|
-
gaps.push(`'${key}' \u2014 broker returned no result`);
|
|
28750
|
-
continue;
|
|
28751
|
-
}
|
|
28752
|
-
const g = keyGap(key, cronKeys.has(key), r.exists, { allow: r.acl_ok, reason: r.acl_reason }, { allow: r.scope_ok, reason: r.scope_reason });
|
|
28753
|
-
if (g)
|
|
28754
|
-
gaps.push(g);
|
|
28755
|
-
}
|
|
28756
|
-
pushAgentResult(name, needed.size, gaps);
|
|
28757
|
-
}
|
|
28758
|
-
return results;
|
|
28759
|
-
}
|
|
28760
|
-
let entries;
|
|
28761
|
-
try {
|
|
28762
|
-
entries = (deps.openVault ?? openVault)(passphrase, vaultPath);
|
|
28763
|
-
} catch (err) {
|
|
28764
|
-
results.push({
|
|
28765
|
-
name: "agent secret access",
|
|
28766
|
-
status: vf.readable ? "fail" : "warn",
|
|
28767
|
-
detail: `cannot open the vault: ${err.message}`,
|
|
28768
|
-
fix: vf.readable ? "SWITCHROOM_VAULT_PASSPHRASE may be wrong, or the vault is corrupt" : "fix the vault file ownership above first (operator cannot read it)"
|
|
28769
|
-
});
|
|
28770
|
-
return results;
|
|
28771
|
-
}
|
|
28772
|
-
for (const name of Object.keys(config.agents ?? {})) {
|
|
28773
|
-
const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[name]);
|
|
28774
|
-
const { needed, cronKeys } = collectNeeds(resolved);
|
|
28775
|
-
if (needed.size === 0) {
|
|
28776
|
-
results.push({
|
|
28777
|
-
name: `secret access: ${name}`,
|
|
28778
|
-
status: "ok",
|
|
28779
|
-
detail: "no declared vault secrets"
|
|
28780
|
-
});
|
|
28781
|
-
continue;
|
|
28782
|
-
}
|
|
28783
|
-
const gaps = [];
|
|
28784
|
-
for (const key of [...needed].sort()) {
|
|
28785
|
-
const g = keyGap(key, cronKeys.has(key), key in entries, checkAclByAgent(config, name, key), checkEntryScope(entries[key]?.scope, name));
|
|
28786
|
-
if (g)
|
|
28787
|
-
gaps.push(g);
|
|
28788
|
-
}
|
|
28789
|
-
pushAgentResult(name, needed.size, gaps);
|
|
28790
|
-
}
|
|
28791
|
-
return results;
|
|
28792
|
-
}
|
|
28793
|
-
var init_doctor_secret_access = __esm(() => {
|
|
28794
|
-
init_paths();
|
|
28795
|
-
init_merge();
|
|
28796
|
-
init_vault();
|
|
28797
|
-
init_acl();
|
|
28798
|
-
init_client();
|
|
28799
|
-
});
|
|
28800
|
-
|
|
28801
28884
|
// src/cli/doctor-inlined-secrets.ts
|
|
28802
28885
|
import { readFileSync as fsReadFileSync } from "node:fs";
|
|
28803
28886
|
function isSecretShapedKey(key) {
|
|
@@ -28894,7 +28977,7 @@ var init_doctor_inlined_secrets = __esm(() => {
|
|
|
28894
28977
|
|
|
28895
28978
|
// src/cli/doctor-audit-integrity.ts
|
|
28896
28979
|
import { readFileSync as fsReadFileSync2 } from "node:fs";
|
|
28897
|
-
import { homedir as
|
|
28980
|
+
import { homedir as homedir23 } from "node:os";
|
|
28898
28981
|
import { join as join41 } from "node:path";
|
|
28899
28982
|
function rootWrittenLogs(home2) {
|
|
28900
28983
|
return [
|
|
@@ -28906,7 +28989,7 @@ function rootWrittenLogs(home2) {
|
|
|
28906
28989
|
];
|
|
28907
28990
|
}
|
|
28908
28991
|
function runAuditIntegrityChecks(deps = {}) {
|
|
28909
|
-
const home2 = deps.homeDir ??
|
|
28992
|
+
const home2 = deps.homeDir ?? homedir23();
|
|
28910
28993
|
const read = deps.readFileSync ?? ((p) => fsReadFileSync2(p, "utf8"));
|
|
28911
28994
|
const results = [];
|
|
28912
28995
|
for (const { label, path: path4 } of rootWrittenLogs(home2)) {
|
|
@@ -29183,13 +29266,13 @@ var init_client4 = __esm(() => {
|
|
|
29183
29266
|
|
|
29184
29267
|
// src/cli/doctor-agent-smoke.ts
|
|
29185
29268
|
import { existsSync as existsSync47 } from "node:fs";
|
|
29186
|
-
import { homedir as
|
|
29269
|
+
import { homedir as homedir24 } from "node:os";
|
|
29187
29270
|
import { join as join42 } from "node:path";
|
|
29188
29271
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
29189
29272
|
async function runAgentSmokeChecks(config, deps = {}) {
|
|
29190
29273
|
if (deps.fast)
|
|
29191
29274
|
return [];
|
|
29192
|
-
const home2 = deps.homeDir ??
|
|
29275
|
+
const home2 = deps.homeDir ?? homedir24();
|
|
29193
29276
|
const sock = deps.operatorSockPath ?? join42(home2, ".switchroom", "hostd", "operator", "sock");
|
|
29194
29277
|
if (!deps.hostdRequestImpl && !existsSync47(sock)) {
|
|
29195
29278
|
return [
|
|
@@ -30809,7 +30892,13 @@ function registerDoctorCommand(program3) {
|
|
|
30809
30892
|
title: "Agent liveness (in-agent via hostd)",
|
|
30810
30893
|
results: await runAgentSmokeChecks(config, { fast: opts.fast })
|
|
30811
30894
|
},
|
|
30812
|
-
{
|
|
30895
|
+
{
|
|
30896
|
+
title: "Google Drive",
|
|
30897
|
+
results: [
|
|
30898
|
+
...runDriveChecks(config),
|
|
30899
|
+
...await runDriveBrokerReachabilityChecks(config)
|
|
30900
|
+
]
|
|
30901
|
+
},
|
|
30813
30902
|
{ title: "MFF Skill", results: await checkMff(passphrase, vaultPath, config) }
|
|
30814
30903
|
];
|
|
30815
30904
|
const cwd = process.cwd();
|
|
@@ -47153,8 +47242,8 @@ var {
|
|
|
47153
47242
|
} = import__.default;
|
|
47154
47243
|
|
|
47155
47244
|
// src/build-info.ts
|
|
47156
|
-
var VERSION = "0.12.
|
|
47157
|
-
var COMMIT_SHA = "
|
|
47245
|
+
var VERSION = "0.12.16";
|
|
47246
|
+
var COMMIT_SHA = "b30ce83a";
|
|
47158
47247
|
|
|
47159
47248
|
// src/cli/agent.ts
|
|
47160
47249
|
init_source();
|
|
@@ -69125,8 +69214,8 @@ init_lifecycle();
|
|
|
69125
69214
|
import { cpSync as cpSync2, existsSync as existsSync49, mkdirSync as mkdirSync27, readFileSync as readFileSync45, realpathSync as realpathSync5, rmSync as rmSync12, statSync as statSync22 } from "node:fs";
|
|
69126
69215
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
69127
69216
|
import { join as join44, dirname as dirname13, resolve as resolve30 } from "node:path";
|
|
69128
|
-
import { homedir as
|
|
69129
|
-
var DEFAULT_COMPOSE_PATH = join44(
|
|
69217
|
+
import { homedir as homedir25 } from "node:os";
|
|
69218
|
+
var DEFAULT_COMPOSE_PATH = join44(homedir25(), ".switchroom", "compose", "docker-compose.yml");
|
|
69130
69219
|
function runningFromSwitchroomCheckout(scriptPath) {
|
|
69131
69220
|
let dir = dirname13(scriptPath);
|
|
69132
69221
|
for (let i = 0;i < 12; i++) {
|
|
@@ -69263,7 +69352,7 @@ function planUpdate(opts) {
|
|
|
69263
69352
|
return;
|
|
69264
69353
|
}
|
|
69265
69354
|
const source = resolve30(import.meta.dirname, "../../skills");
|
|
69266
|
-
const dest = join44(
|
|
69355
|
+
const dest = join44(homedir25(), ".switchroom", "skills", "_bundled");
|
|
69267
69356
|
if (!existsSync49(source)) {
|
|
69268
69357
|
process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
|
|
69269
69358
|
`);
|
|
@@ -70666,7 +70755,7 @@ function relTime(deltaMs) {
|
|
|
70666
70755
|
// src/cli/deps.ts
|
|
70667
70756
|
init_source();
|
|
70668
70757
|
import { existsSync as existsSync54 } from "node:fs";
|
|
70669
|
-
import { homedir as
|
|
70758
|
+
import { homedir as homedir28 } from "node:os";
|
|
70670
70759
|
import { join as join49, resolve as resolve34 } from "node:path";
|
|
70671
70760
|
|
|
70672
70761
|
// src/deps/python.ts
|
|
@@ -70679,7 +70768,7 @@ import {
|
|
|
70679
70768
|
writeFileSync as writeFileSync26
|
|
70680
70769
|
} from "node:fs";
|
|
70681
70770
|
import { dirname as dirname15, join as join47 } from "node:path";
|
|
70682
|
-
import { homedir as
|
|
70771
|
+
import { homedir as homedir26 } from "node:os";
|
|
70683
70772
|
import { execFileSync as execFileSync14 } from "node:child_process";
|
|
70684
70773
|
|
|
70685
70774
|
class PythonEnvError extends Error {
|
|
@@ -70691,7 +70780,7 @@ class PythonEnvError extends Error {
|
|
|
70691
70780
|
}
|
|
70692
70781
|
}
|
|
70693
70782
|
function defaultPythonCacheRoot() {
|
|
70694
|
-
return join47(
|
|
70783
|
+
return join47(homedir26(), ".switchroom", "deps", "python");
|
|
70695
70784
|
}
|
|
70696
70785
|
function hashFile(path4) {
|
|
70697
70786
|
return createHash9("sha256").update(readFileSync48(path4)).digest("hex");
|
|
@@ -70767,7 +70856,7 @@ import {
|
|
|
70767
70856
|
writeFileSync as writeFileSync27
|
|
70768
70857
|
} from "node:fs";
|
|
70769
70858
|
import { dirname as dirname16, join as join48 } from "node:path";
|
|
70770
|
-
import { homedir as
|
|
70859
|
+
import { homedir as homedir27 } from "node:os";
|
|
70771
70860
|
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
70772
70861
|
|
|
70773
70862
|
class NodeEnvError extends Error {
|
|
@@ -70790,7 +70879,7 @@ var LOCKFILES_FOR = {
|
|
|
70790
70879
|
npm: ["package-lock.json"]
|
|
70791
70880
|
};
|
|
70792
70881
|
function defaultNodeCacheRoot() {
|
|
70793
|
-
return join48(
|
|
70882
|
+
return join48(homedir27(), ".switchroom", "deps", "node");
|
|
70794
70883
|
}
|
|
70795
70884
|
function hashDepInputs(packageJsonPath) {
|
|
70796
70885
|
const sourceDir = dirname16(packageJsonPath);
|
|
@@ -70874,7 +70963,7 @@ function ensureNodeEnv(opts) {
|
|
|
70874
70963
|
|
|
70875
70964
|
// src/cli/deps.ts
|
|
70876
70965
|
function builtinSkillsRoot() {
|
|
70877
|
-
return resolve34(
|
|
70966
|
+
return resolve34(homedir28(), ".switchroom/skills/_bundled");
|
|
70878
70967
|
}
|
|
70879
70968
|
function registerDepsCommand(program3) {
|
|
70880
70969
|
const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
|
|
@@ -72184,7 +72273,7 @@ init_source();
|
|
|
72184
72273
|
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
72185
72274
|
import { closeSync as closeSync11, mkdirSync as mkdirSync32, openSync as openSync11, existsSync as existsSync59, unlinkSync as unlinkSync12 } from "node:fs";
|
|
72186
72275
|
import { join as join53, resolve as resolve39 } from "node:path";
|
|
72187
|
-
import { homedir as
|
|
72276
|
+
import { homedir as homedir30 } from "node:os";
|
|
72188
72277
|
import { randomBytes as randomBytes11 } from "node:crypto";
|
|
72189
72278
|
|
|
72190
72279
|
// src/worktree/registry.ts
|
|
@@ -72198,9 +72287,9 @@ import {
|
|
|
72198
72287
|
renameSync as renameSync11
|
|
72199
72288
|
} from "node:fs";
|
|
72200
72289
|
import { join as join52, resolve as resolve38 } from "node:path";
|
|
72201
|
-
import { homedir as
|
|
72290
|
+
import { homedir as homedir29 } from "node:os";
|
|
72202
72291
|
function registryDir() {
|
|
72203
|
-
return resolve38(process.env.SWITCHROOM_WORKTREE_DIR ?? join52(
|
|
72292
|
+
return resolve38(process.env.SWITCHROOM_WORKTREE_DIR ?? join52(homedir29(), ".switchroom", "worktrees"));
|
|
72204
72293
|
}
|
|
72205
72294
|
function recordPath(id) {
|
|
72206
72295
|
return join52(registryDir(), `${id}.json`);
|
|
@@ -72281,7 +72370,7 @@ function acquireRepoLock(repoPath) {
|
|
|
72281
72370
|
}
|
|
72282
72371
|
var DEFAULT_CONCURRENCY = 5;
|
|
72283
72372
|
function worktreesBaseDir() {
|
|
72284
|
-
return resolve39(process.env.SWITCHROOM_WORKTREE_BASE ?? join53(
|
|
72373
|
+
return resolve39(process.env.SWITCHROOM_WORKTREE_BASE ?? join53(homedir30(), ".switchroom", "worktree-checkouts"));
|
|
72285
72374
|
}
|
|
72286
72375
|
function shortId() {
|
|
72287
72376
|
return randomBytes11(4).toString("hex");
|
|
@@ -72303,7 +72392,7 @@ function resolveRepoPath(repo, codeRepos) {
|
|
|
72303
72392
|
}
|
|
72304
72393
|
function expandHome(p) {
|
|
72305
72394
|
if (p.startsWith("~/"))
|
|
72306
|
-
return join53(
|
|
72395
|
+
return join53(homedir30(), p.slice(2));
|
|
72307
72396
|
return p;
|
|
72308
72397
|
}
|
|
72309
72398
|
async function claimWorktree(input, codeRepos) {
|
|
@@ -73258,7 +73347,7 @@ agents:
|
|
|
73258
73347
|
// src/cli/apply.ts
|
|
73259
73348
|
init_resolver();
|
|
73260
73349
|
import { dirname as dirname19, join as join58, resolve as resolve41 } from "node:path";
|
|
73261
|
-
import { homedir as
|
|
73350
|
+
import { homedir as homedir32 } from "node:os";
|
|
73262
73351
|
import { execFileSync as execFileSync19 } from "node:child_process";
|
|
73263
73352
|
init_vault();
|
|
73264
73353
|
init_loader();
|
|
@@ -73559,7 +73648,7 @@ var EMBEDDED_EXAMPLES = {
|
|
|
73559
73648
|
switchroom: switchroom_default,
|
|
73560
73649
|
minimal: minimal_default
|
|
73561
73650
|
};
|
|
73562
|
-
var DEFAULT_COMPOSE_PATH2 = join58(
|
|
73651
|
+
var DEFAULT_COMPOSE_PATH2 = join58(homedir32(), ".switchroom", "compose", "docker-compose.yml");
|
|
73563
73652
|
var COMPOSE_PROJECT2 = "switchroom";
|
|
73564
73653
|
function resolveVaultBindMountDir(homeDir, ctx) {
|
|
73565
73654
|
const isCustomPath = ctx.migrationKind === "custom-path-skipped";
|
|
@@ -73595,7 +73684,7 @@ function hasVaultRefs(value) {
|
|
|
73595
73684
|
return false;
|
|
73596
73685
|
}
|
|
73597
73686
|
async function ensureHostMountSources(config) {
|
|
73598
|
-
const home2 =
|
|
73687
|
+
const home2 = homedir32();
|
|
73599
73688
|
const dirs = [
|
|
73600
73689
|
join58(home2, ".switchroom", "approvals"),
|
|
73601
73690
|
join58(home2, ".switchroom", "scheduler"),
|
|
@@ -73691,7 +73780,7 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
|
73691
73780
|
`));
|
|
73692
73781
|
}
|
|
73693
73782
|
}
|
|
73694
|
-
function writeInstallTypeCache(homeDir =
|
|
73783
|
+
function writeInstallTypeCache(homeDir = homedir32()) {
|
|
73695
73784
|
const ctx = detectInstallType();
|
|
73696
73785
|
const dir = join58(homeDir, ".switchroom");
|
|
73697
73786
|
const out = join58(dir, "install-type.json");
|
|
@@ -73801,7 +73890,7 @@ Applying switchroom config...
|
|
|
73801
73890
|
}
|
|
73802
73891
|
const vaultPathConfigured = config.vault?.path;
|
|
73803
73892
|
const customVaultPath = vaultPathConfigured ? resolvePath(vaultPathConfigured) : undefined;
|
|
73804
|
-
const migrationResult = migrateVaultLayout(
|
|
73893
|
+
const migrationResult = migrateVaultLayout(homedir32(), {
|
|
73805
73894
|
customVaultPath
|
|
73806
73895
|
});
|
|
73807
73896
|
switch (migrationResult.kind) {
|
|
@@ -73827,7 +73916,7 @@ Applying switchroom config...
|
|
|
73827
73916
|
writeErr(formatDivergentRecoveryMessage(migrationResult.details));
|
|
73828
73917
|
process.exit(4);
|
|
73829
73918
|
}
|
|
73830
|
-
const postMigrationInspect = inspectVaultLayout(
|
|
73919
|
+
const postMigrationInspect = inspectVaultLayout(homedir32());
|
|
73831
73920
|
const acceptable = [
|
|
73832
73921
|
"no-vault",
|
|
73833
73922
|
"already-migrated",
|
|
@@ -73842,7 +73931,7 @@ Applying switchroom config...
|
|
|
73842
73931
|
`));
|
|
73843
73932
|
process.exit(5);
|
|
73844
73933
|
}
|
|
73845
|
-
const vaultDir = resolveVaultBindMountDir(
|
|
73934
|
+
const vaultDir = resolveVaultBindMountDir(homedir32(), {
|
|
73846
73935
|
migrationKind: migrationResult.kind,
|
|
73847
73936
|
customVaultPath
|
|
73848
73937
|
});
|
|
@@ -73872,7 +73961,7 @@ Applying switchroom config...
|
|
|
73872
73961
|
imageTag: composeImageTag,
|
|
73873
73962
|
buildMode: options.buildLocal ? "local" : "pull",
|
|
73874
73963
|
buildContext: options.buildContext,
|
|
73875
|
-
homeDir:
|
|
73964
|
+
homeDir: homedir32(),
|
|
73876
73965
|
switchroomConfigPath,
|
|
73877
73966
|
operatorUid
|
|
73878
73967
|
});
|
|
@@ -73892,7 +73981,7 @@ Wrote `) + composePath + source_default.gray(` (${composeBytes} bytes)
|
|
|
73892
73981
|
writeOut(source_default.gray(` (If pull returns 401, login to ghcr.io first: see docs/operators/install.md#ghcr-auth)
|
|
73893
73982
|
`));
|
|
73894
73983
|
if (process.geteuid?.() === 0 && operatorUid !== undefined) {
|
|
73895
|
-
const restored = restoreOperatorOwnership(
|
|
73984
|
+
const restored = restoreOperatorOwnership(homedir32(), operatorUid);
|
|
73896
73985
|
if (restored.length > 0) {
|
|
73897
73986
|
writeOut(source_default.gray(` Restored operator ownership of ${restored.length} ~/.switchroom path(s)
|
|
73898
73987
|
`));
|
|
@@ -74143,7 +74232,7 @@ function runRedactStdin() {
|
|
|
74143
74232
|
// src/cli/status-ask.ts
|
|
74144
74233
|
import { readFileSync as readFileSync54, existsSync as existsSync66, readdirSync as readdirSync24 } from "node:fs";
|
|
74145
74234
|
import { join as join59 } from "node:path";
|
|
74146
|
-
import { homedir as
|
|
74235
|
+
import { homedir as homedir33 } from "node:os";
|
|
74147
74236
|
|
|
74148
74237
|
// src/status-ask/report.ts
|
|
74149
74238
|
function parseJsonl(content) {
|
|
@@ -74478,7 +74567,7 @@ function resolveSources(explicitPath) {
|
|
|
74478
74567
|
const config = loadConfig();
|
|
74479
74568
|
agentsDir = resolveAgentsDir(config);
|
|
74480
74569
|
} catch {
|
|
74481
|
-
agentsDir = join59(
|
|
74570
|
+
agentsDir = join59(homedir33(), ".switchroom", "agents");
|
|
74482
74571
|
}
|
|
74483
74572
|
if (!existsSync66(agentsDir))
|
|
74484
74573
|
return [];
|
|
@@ -74513,14 +74602,14 @@ function inferAgentFromPath(p) {
|
|
|
74513
74602
|
// src/cli/agent-config.ts
|
|
74514
74603
|
init_helpers();
|
|
74515
74604
|
import { join as join60 } from "node:path";
|
|
74516
|
-
import { homedir as
|
|
74605
|
+
import { homedir as homedir34 } from "node:os";
|
|
74517
74606
|
import {
|
|
74518
74607
|
existsSync as existsSync67,
|
|
74519
74608
|
mkdirSync as mkdirSync36,
|
|
74520
74609
|
appendFileSync as appendFileSync3,
|
|
74521
74610
|
readFileSync as readFileSync55
|
|
74522
74611
|
} from "node:fs";
|
|
74523
|
-
var AUDIT_ROOT = join60(
|
|
74612
|
+
var AUDIT_ROOT = join60(homedir34(), ".switchroom", "audit");
|
|
74524
74613
|
function auditPathFor(agent) {
|
|
74525
74614
|
return join60(AUDIT_ROOT, agent, "agent-config.jsonl");
|
|
74526
74615
|
}
|
|
@@ -76041,7 +76130,7 @@ function registerMigrateCommand(program3) {
|
|
|
76041
76130
|
init_source();
|
|
76042
76131
|
init_helpers();
|
|
76043
76132
|
import { existsSync as existsSync74, mkdirSync as mkdirSync39, readdirSync as readdirSync28, readFileSync as readFileSync61, writeFileSync as writeFileSync34, statSync as statSync28, copyFileSync as copyFileSync12 } from "node:fs";
|
|
76044
|
-
import { homedir as
|
|
76133
|
+
import { homedir as homedir35 } from "node:os";
|
|
76045
76134
|
import { join as join65 } from "node:path";
|
|
76046
76135
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
76047
76136
|
init_audit_reader();
|
|
@@ -76133,7 +76222,7 @@ networks:
|
|
|
76133
76222
|
`;
|
|
76134
76223
|
}
|
|
76135
76224
|
function hostdDir() {
|
|
76136
|
-
return join65(
|
|
76225
|
+
return join65(homedir35(), ".switchroom", "hostd");
|
|
76137
76226
|
}
|
|
76138
76227
|
function hostdComposePath() {
|
|
76139
76228
|
return join65(hostdDir(), "docker-compose.yml");
|
|
@@ -76175,7 +76264,7 @@ async function doInstall(opts, program3) {
|
|
|
76175
76264
|
const composePath = hostdComposePath();
|
|
76176
76265
|
mkdirSync39(dir, { recursive: true });
|
|
76177
76266
|
const yaml = renderHostdComposeFile({
|
|
76178
|
-
hostHome:
|
|
76267
|
+
hostHome: homedir35(),
|
|
76179
76268
|
imageTag: opts.tag ?? DEFAULT_IMAGE_TAG,
|
|
76180
76269
|
operatorUid: resolveOperatorUid()
|
|
76181
76270
|
});
|