switchroom 0.12.13 → 0.12.15
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/README.md +107 -371
- package/dist/cli/switchroom.js +627 -386
- package/dist/vault/approvals/kernel-server.js +88 -1
- package/dist/vault/broker/server.js +132 -2
- package/package.json +1 -1
- package/telegram-plugin/dist/gateway/gateway.js +289 -81
- package/telegram-plugin/gateway/approval-callback.test.ts +49 -1
- package/telegram-plugin/gateway/approval-callback.ts +85 -56
- package/telegram-plugin/gateway/gateway.ts +168 -19
- package/telegram-plugin/gateway/pending-inbound-buffer.ts +39 -0
- package/telegram-plugin/gateway/pending-permission-decisions.ts +112 -0
- package/telegram-plugin/gateway/vault-grant-inbound-builders.ts +117 -0
- package/telegram-plugin/tests/pending-inbound-buffer.test.ts +71 -1
- package/telegram-plugin/tests/pending-permission-decisions.test.ts +73 -0
- package/telegram-plugin/tests/vault-save-inbound-builders.test.ts +96 -0
package/dist/cli/switchroom.js
CHANGED
|
@@ -20645,18 +20645,6 @@ function getGdriveMcpSettingsEntry(switchroomCliPath, options = {}) {
|
|
|
20645
20645
|
}
|
|
20646
20646
|
};
|
|
20647
20647
|
}
|
|
20648
|
-
function shouldEmitGdriveMcp(agentName, agentGoogleAccount, googleAccounts) {
|
|
20649
|
-
if (!agentGoogleAccount)
|
|
20650
|
-
return false;
|
|
20651
|
-
const account = agentGoogleAccount.trim().toLowerCase();
|
|
20652
|
-
if (account.length === 0)
|
|
20653
|
-
return false;
|
|
20654
|
-
const acctEntry = googleAccounts?.[account];
|
|
20655
|
-
if (!acctEntry)
|
|
20656
|
-
return false;
|
|
20657
|
-
const enabledFor = acctEntry.enabled_for ?? [];
|
|
20658
|
-
return enabledFor.includes(agentName);
|
|
20659
|
-
}
|
|
20660
20648
|
function getBuiltinDefaultMcpEntries() {
|
|
20661
20649
|
const playwright = getPlaywrightMcpSettingsEntry();
|
|
20662
20650
|
return [
|
|
@@ -20689,6 +20677,48 @@ var init_scaffold_integration = __esm(() => {
|
|
|
20689
20677
|
init_hindsight();
|
|
20690
20678
|
});
|
|
20691
20679
|
|
|
20680
|
+
// src/config/google-workspace-acl.ts
|
|
20681
|
+
function shouldEmitGdriveMcp(agentName, agentGoogleAccount, googleAccounts) {
|
|
20682
|
+
if (!agentGoogleAccount)
|
|
20683
|
+
return false;
|
|
20684
|
+
const account = agentGoogleAccount.trim().toLowerCase();
|
|
20685
|
+
if (account.length === 0)
|
|
20686
|
+
return false;
|
|
20687
|
+
const acctEntry = googleAccounts?.[account];
|
|
20688
|
+
if (!acctEntry)
|
|
20689
|
+
return false;
|
|
20690
|
+
const enabledFor = acctEntry.enabled_for ?? [];
|
|
20691
|
+
return enabledFor.includes(agentName);
|
|
20692
|
+
}
|
|
20693
|
+
function vaultRefKey(value) {
|
|
20694
|
+
if (typeof value !== "string" || !value.startsWith("vault:"))
|
|
20695
|
+
return null;
|
|
20696
|
+
const key = value.slice("vault:".length).split("#")[0];
|
|
20697
|
+
return key.length > 0 ? key : null;
|
|
20698
|
+
}
|
|
20699
|
+
function isGoogleClientCredentialKeyForAgent(config, agentName, key) {
|
|
20700
|
+
if (!agentName || !key)
|
|
20701
|
+
return false;
|
|
20702
|
+
const agentConfig = config.agents?.[agentName];
|
|
20703
|
+
if (!agentConfig)
|
|
20704
|
+
return false;
|
|
20705
|
+
if (agentConfig.mcp_servers?.["gdrive"] === false) {
|
|
20706
|
+
return false;
|
|
20707
|
+
}
|
|
20708
|
+
const account = agentConfig.google_workspace?.account;
|
|
20709
|
+
if (!shouldEmitGdriveMcp(agentName, account, config.google_accounts)) {
|
|
20710
|
+
return false;
|
|
20711
|
+
}
|
|
20712
|
+
const gw = config.google_workspace;
|
|
20713
|
+
if (!gw)
|
|
20714
|
+
return false;
|
|
20715
|
+
for (const ref of [gw.google_client_id, gw.google_client_secret]) {
|
|
20716
|
+
if (vaultRefKey(ref) === key)
|
|
20717
|
+
return true;
|
|
20718
|
+
}
|
|
20719
|
+
return false;
|
|
20720
|
+
}
|
|
20721
|
+
|
|
20692
20722
|
// src/agents/reconcile-default-skills.ts
|
|
20693
20723
|
import { existsSync as existsSync5, lstatSync, mkdirSync as mkdirSync2, readdirSync as readdirSync3, readlinkSync as readlinkSync2, rmSync, symlinkSync } from "node:fs";
|
|
20694
20724
|
import { homedir as homedir2 } from "node:os";
|
|
@@ -21379,7 +21409,7 @@ function stripWireFields(entry) {
|
|
|
21379
21409
|
files: entry.files
|
|
21380
21410
|
};
|
|
21381
21411
|
}
|
|
21382
|
-
var MAX_FRAME_BYTES, GetRequestSchema, PutRequestSchema, ListRequestSchema, MintGrantRequestSchema, ListGrantsRequestSchema, RevokeGrantRequestSchema, StatusRequestSchema, LockRequestSchema, 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;
|
|
21383
21413
|
var init_protocol = __esm(() => {
|
|
21384
21414
|
init_zod();
|
|
21385
21415
|
MAX_FRAME_BYTES = 64 * 1024;
|
|
@@ -21438,6 +21468,24 @@ var init_protocol = __esm(() => {
|
|
|
21438
21468
|
v: exports_external.literal(1),
|
|
21439
21469
|
op: exports_external.literal("lock")
|
|
21440
21470
|
});
|
|
21471
|
+
PreflightAccessRequestSchema = exports_external.object({
|
|
21472
|
+
v: exports_external.literal(1),
|
|
21473
|
+
op: exports_external.literal("preflight_access"),
|
|
21474
|
+
agent: exports_external.string().min(1),
|
|
21475
|
+
keys: exports_external.array(exports_external.string().min(1)).min(1).max(128)
|
|
21476
|
+
});
|
|
21477
|
+
OkPreflightAccessResponseSchema = exports_external.object({
|
|
21478
|
+
ok: exports_external.literal(true),
|
|
21479
|
+
op: exports_external.literal("preflight_access"),
|
|
21480
|
+
results: exports_external.array(exports_external.object({
|
|
21481
|
+
key: exports_external.string(),
|
|
21482
|
+
exists: exports_external.boolean(),
|
|
21483
|
+
acl_ok: exports_external.boolean(),
|
|
21484
|
+
acl_reason: exports_external.string().optional(),
|
|
21485
|
+
scope_ok: exports_external.boolean(),
|
|
21486
|
+
scope_reason: exports_external.string().optional()
|
|
21487
|
+
}))
|
|
21488
|
+
});
|
|
21441
21489
|
ApprovalRequestRequestSchema = exports_external.object({
|
|
21442
21490
|
v: exports_external.literal(1),
|
|
21443
21491
|
op: exports_external.literal("approval_request"),
|
|
@@ -21489,12 +21537,22 @@ var init_protocol = __esm(() => {
|
|
|
21489
21537
|
granted_by_user_id: exports_external.number().int(),
|
|
21490
21538
|
ttl_ms: exports_external.number().int().positive().nullable().optional()
|
|
21491
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
|
+
});
|
|
21492
21549
|
RequestSchema = exports_external.discriminatedUnion("op", [
|
|
21493
21550
|
GetRequestSchema,
|
|
21494
21551
|
PutRequestSchema,
|
|
21495
21552
|
ListRequestSchema,
|
|
21496
21553
|
StatusRequestSchema,
|
|
21497
21554
|
LockRequestSchema,
|
|
21555
|
+
PreflightAccessRequestSchema,
|
|
21498
21556
|
MintGrantRequestSchema,
|
|
21499
21557
|
ListGrantsRequestSchema,
|
|
21500
21558
|
RevokeGrantRequestSchema,
|
|
@@ -21503,7 +21561,8 @@ var init_protocol = __esm(() => {
|
|
|
21503
21561
|
ApprovalConsumeRequestSchema,
|
|
21504
21562
|
ApprovalRevokeRequestSchema,
|
|
21505
21563
|
ApprovalListRequestSchema,
|
|
21506
|
-
ApprovalRecordRequestSchema
|
|
21564
|
+
ApprovalRecordRequestSchema,
|
|
21565
|
+
ApprovalConsumeRecordRequestSchema
|
|
21507
21566
|
]);
|
|
21508
21567
|
VaultEntrySchema = exports_external.union([
|
|
21509
21568
|
exports_external.object({ kind: exports_external.literal("string"), value: exports_external.string() }),
|
|
@@ -21625,6 +21684,15 @@ var init_protocol = __esm(() => {
|
|
|
21625
21684
|
ok: exports_external.literal(true),
|
|
21626
21685
|
decision_id: exports_external.string()
|
|
21627
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
|
+
});
|
|
21628
21696
|
ErrorResponseSchema = exports_external.object({
|
|
21629
21697
|
ok: exports_external.literal(false),
|
|
21630
21698
|
code: ErrorCode,
|
|
@@ -21635,6 +21703,7 @@ var init_protocol = __esm(() => {
|
|
|
21635
21703
|
OkKeysResponseSchema,
|
|
21636
21704
|
OkStatusResponseSchema,
|
|
21637
21705
|
OkLockResponseSchema,
|
|
21706
|
+
OkPreflightAccessResponseSchema,
|
|
21638
21707
|
OkPutResponseSchema,
|
|
21639
21708
|
OkMintGrantResponseSchema,
|
|
21640
21709
|
OkListGrantsResponseSchema,
|
|
@@ -21645,6 +21714,7 @@ var init_protocol = __esm(() => {
|
|
|
21645
21714
|
OkApprovalRevokeResponseSchema,
|
|
21646
21715
|
OkApprovalListResponseSchema,
|
|
21647
21716
|
OkApprovalRecordResponseSchema,
|
|
21717
|
+
OkApprovalConsumeRecordResponseSchema,
|
|
21648
21718
|
ErrorResponseSchema
|
|
21649
21719
|
]);
|
|
21650
21720
|
});
|
|
@@ -27169,6 +27239,9 @@ function checkAclByAgent(config, agentName, key) {
|
|
|
27169
27239
|
if (googleSlot !== null) {
|
|
27170
27240
|
return checkGoogleAccountAcl(config, agentName, googleSlot.account, key);
|
|
27171
27241
|
}
|
|
27242
|
+
if (isGoogleClientCredentialKeyForAgent(config, agentName, key)) {
|
|
27243
|
+
return { allow: true };
|
|
27244
|
+
}
|
|
27172
27245
|
const agentBot = agentConfig.bot_token;
|
|
27173
27246
|
const botRef = agentBot && agentBot.length > 0 ? agentBot : config.telegram?.bot_token;
|
|
27174
27247
|
if (typeof botRef === "string" && botRef.startsWith("vault:")) {
|
|
@@ -27226,6 +27299,7 @@ function checkGoogleAccountAcl(config, agentName, account, key) {
|
|
|
27226
27299
|
}
|
|
27227
27300
|
return { allow: true };
|
|
27228
27301
|
}
|
|
27302
|
+
var init_acl = () => {};
|
|
27229
27303
|
|
|
27230
27304
|
// src/util/audit-hashchain.ts
|
|
27231
27305
|
import { createHash as createHash6 } from "node:crypto";
|
|
@@ -28228,12 +28302,240 @@ function runHostdChecks(config, deps = {}) {
|
|
|
28228
28302
|
var HOSTD_CONTAINER = "switchroom-hostd", HOSTD_DRIFT_HOURS = 2;
|
|
28229
28303
|
var init_doctor_hostd = () => {};
|
|
28230
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
|
+
|
|
28231
28532
|
// src/cli/doctor-drive.ts
|
|
28232
28533
|
import {
|
|
28233
28534
|
existsSync as realExistsSync,
|
|
28234
28535
|
readFileSync as realReadFileSync
|
|
28235
28536
|
} from "node:fs";
|
|
28236
|
-
import { join as
|
|
28537
|
+
import { join as join39, resolve as resolve28 } from "node:path";
|
|
28538
|
+
import { homedir as homedir21 } from "node:os";
|
|
28237
28539
|
function resolveDeps(config, deps) {
|
|
28238
28540
|
let agentsDir = deps.agentsDir;
|
|
28239
28541
|
if (agentsDir === undefined) {
|
|
@@ -28356,8 +28658,8 @@ function checkScaffoldWiring(config, driveAgents, d) {
|
|
|
28356
28658
|
});
|
|
28357
28659
|
continue;
|
|
28358
28660
|
}
|
|
28359
|
-
const mcpPath =
|
|
28360
|
-
const claudeJsonPath =
|
|
28661
|
+
const mcpPath = join39(agentDir, ".mcp.json");
|
|
28662
|
+
const claudeJsonPath = join39(agentDir, ".claude", ".claude.json");
|
|
28361
28663
|
const mcpRead = readJson(d, mcpPath);
|
|
28362
28664
|
const trustRead = readJson(d, claudeJsonPath);
|
|
28363
28665
|
if (mcpRead.kind === "unreadable" || trustRead.kind === "unreadable") {
|
|
@@ -28441,16 +28743,78 @@ function runDriveChecks(config, deps = {}) {
|
|
|
28441
28743
|
const results = [];
|
|
28442
28744
|
const matrix = checkConfigMatrix(config);
|
|
28443
28745
|
results.push(...matrix);
|
|
28444
|
-
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) => {
|
|
28445
28754
|
const acct = agentAccount(config, name);
|
|
28446
28755
|
return !!acct && !!accounts?.[acct] && (accounts[acct].enabled_for ?? []).includes(name);
|
|
28447
28756
|
});
|
|
28448
|
-
|
|
28449
|
-
|
|
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
|
+
}
|
|
28450
28813
|
return results;
|
|
28451
28814
|
}
|
|
28452
28815
|
var init_doctor_drive = __esm(() => {
|
|
28453
28816
|
init_loader();
|
|
28817
|
+
init_doctor_secret_access();
|
|
28454
28818
|
});
|
|
28455
28819
|
|
|
28456
28820
|
// src/cli/doctor-credentials-migration.ts
|
|
@@ -28459,11 +28823,11 @@ import {
|
|
|
28459
28823
|
readdirSync as realReaddirSync,
|
|
28460
28824
|
statSync as realStatSync
|
|
28461
28825
|
} from "node:fs";
|
|
28462
|
-
import { homedir as
|
|
28463
|
-
import { join as
|
|
28826
|
+
import { homedir as homedir22 } from "node:os";
|
|
28827
|
+
import { join as join40 } from "node:path";
|
|
28464
28828
|
function runCredentialsMigrationChecks(config, deps = {}) {
|
|
28465
|
-
const credDir = deps.credentialsDir ??
|
|
28466
|
-
const
|
|
28829
|
+
const credDir = deps.credentialsDir ?? join40(homedir22(), ".switchroom", "credentials");
|
|
28830
|
+
const existsSync47 = deps.existsSync ?? ((p) => realExistsSync2(p));
|
|
28467
28831
|
const readdirSync17 = deps.readdirSync ?? ((p) => realReaddirSync(p));
|
|
28468
28832
|
const isDirectory = deps.isDirectory ?? ((p) => {
|
|
28469
28833
|
try {
|
|
@@ -28472,7 +28836,7 @@ function runCredentialsMigrationChecks(config, deps = {}) {
|
|
|
28472
28836
|
return false;
|
|
28473
28837
|
}
|
|
28474
28838
|
});
|
|
28475
|
-
if (!
|
|
28839
|
+
if (!existsSync47(credDir))
|
|
28476
28840
|
return [];
|
|
28477
28841
|
const agentNames = new Set(Object.keys(config.agents ?? {}));
|
|
28478
28842
|
let entries;
|
|
@@ -28490,7 +28854,7 @@ function runCredentialsMigrationChecks(config, deps = {}) {
|
|
|
28490
28854
|
const flat = [];
|
|
28491
28855
|
const perAgentDirs = [];
|
|
28492
28856
|
for (const e of entries) {
|
|
28493
|
-
const full =
|
|
28857
|
+
const full = join40(credDir, e);
|
|
28494
28858
|
if (isDirectory(full) && agentNames.has(e)) {
|
|
28495
28859
|
perAgentDirs.push(e);
|
|
28496
28860
|
} else {
|
|
@@ -28517,181 +28881,6 @@ function runCredentialsMigrationChecks(config, deps = {}) {
|
|
|
28517
28881
|
}
|
|
28518
28882
|
var init_doctor_credentials_migration = () => {};
|
|
28519
28883
|
|
|
28520
|
-
// src/cli/doctor-secret-access.ts
|
|
28521
|
-
import {
|
|
28522
|
-
accessSync,
|
|
28523
|
-
constants as fsConstants4,
|
|
28524
|
-
existsSync as existsSync46,
|
|
28525
|
-
realpathSync as realpathSync4,
|
|
28526
|
-
statSync as statSync20
|
|
28527
|
-
} from "node:fs";
|
|
28528
|
-
import { userInfo } from "node:os";
|
|
28529
|
-
function resolveVaultPath2(config) {
|
|
28530
|
-
return config.vault?.path ? config.vault.path.replace(/^~/, process.env.HOME ?? "") : resolveStatePath("vault.enc");
|
|
28531
|
-
}
|
|
28532
|
-
function defaultStatVault(path4) {
|
|
28533
|
-
if (!existsSync46(path4)) {
|
|
28534
|
-
return { exists: false, readable: false, uid: -1, mode: 0, realPath: path4 };
|
|
28535
|
-
}
|
|
28536
|
-
let real = path4;
|
|
28537
|
-
try {
|
|
28538
|
-
real = realpathSync4(path4);
|
|
28539
|
-
} catch {}
|
|
28540
|
-
let uid = -1;
|
|
28541
|
-
let mode = 0;
|
|
28542
|
-
try {
|
|
28543
|
-
const s = statSync20(real);
|
|
28544
|
-
uid = s.uid;
|
|
28545
|
-
mode = s.mode & 511;
|
|
28546
|
-
} catch {
|
|
28547
|
-
return { exists: true, readable: false, uid, mode, realPath: real };
|
|
28548
|
-
}
|
|
28549
|
-
let readable = false;
|
|
28550
|
-
try {
|
|
28551
|
-
accessSync(real, fsConstants4.R_OK);
|
|
28552
|
-
readable = true;
|
|
28553
|
-
} catch {
|
|
28554
|
-
readable = false;
|
|
28555
|
-
}
|
|
28556
|
-
return { exists: true, readable, uid, mode, realPath: real };
|
|
28557
|
-
}
|
|
28558
|
-
function collectVaultRefs2(value, out) {
|
|
28559
|
-
if (typeof value === "string") {
|
|
28560
|
-
if (value.startsWith("vault:")) {
|
|
28561
|
-
const key = value.slice("vault:".length).split("#")[0].trim();
|
|
28562
|
-
if (key)
|
|
28563
|
-
out.add(key);
|
|
28564
|
-
}
|
|
28565
|
-
return;
|
|
28566
|
-
}
|
|
28567
|
-
if (Array.isArray(value)) {
|
|
28568
|
-
for (const v of value)
|
|
28569
|
-
collectVaultRefs2(v, out);
|
|
28570
|
-
return;
|
|
28571
|
-
}
|
|
28572
|
-
if (value && typeof value === "object") {
|
|
28573
|
-
for (const v of Object.values(value)) {
|
|
28574
|
-
collectVaultRefs2(v, out);
|
|
28575
|
-
}
|
|
28576
|
-
}
|
|
28577
|
-
}
|
|
28578
|
-
function runSecretAccessChecks(config, deps = {}) {
|
|
28579
|
-
const results = [];
|
|
28580
|
-
const vaultPath = deps.vaultPath ?? resolveVaultPath2(config);
|
|
28581
|
-
const statVault = deps.statVault ?? defaultStatVault;
|
|
28582
|
-
const selfUid = deps.selfUid ?? (typeof process.getuid === "function" ? process.getuid() : -1);
|
|
28583
|
-
let selfUser = deps.selfUser;
|
|
28584
|
-
if (selfUser === undefined) {
|
|
28585
|
-
try {
|
|
28586
|
-
selfUser = userInfo().username;
|
|
28587
|
-
} catch {
|
|
28588
|
-
selfUser = "<you>";
|
|
28589
|
-
}
|
|
28590
|
-
}
|
|
28591
|
-
const vf = statVault(vaultPath);
|
|
28592
|
-
if (!vf.exists) {
|
|
28593
|
-
results.push({
|
|
28594
|
-
name: "vault: operator readable",
|
|
28595
|
-
status: "ok",
|
|
28596
|
-
detail: `vault file not present at ${vaultPath} \u2014 see the Vault section`
|
|
28597
|
-
});
|
|
28598
|
-
} else if (!vf.readable) {
|
|
28599
|
-
results.push({
|
|
28600
|
-
name: "vault: operator readable",
|
|
28601
|
-
status: "fail",
|
|
28602
|
-
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.`,
|
|
28603
|
-
fix: `sudo chown ${selfUser}:${selfUser} ${vf.realPath}`
|
|
28604
|
-
});
|
|
28605
|
-
} else {
|
|
28606
|
-
results.push({
|
|
28607
|
-
name: "vault: operator readable",
|
|
28608
|
-
status: "ok",
|
|
28609
|
-
detail: `operator can read ${vf.realPath}`
|
|
28610
|
-
});
|
|
28611
|
-
}
|
|
28612
|
-
const passphrase = deps.passphrase ?? process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
28613
|
-
if (!passphrase) {
|
|
28614
|
-
results.push({
|
|
28615
|
-
name: "agent secret access",
|
|
28616
|
-
status: "skip",
|
|
28617
|
-
detail: "SWITCHROOM_VAULT_PASSPHRASE not set \u2014 cannot enumerate vault keys/ACLs " + "to verify per-agent secret access",
|
|
28618
|
-
fix: "Export SWITCHROOM_VAULT_PASSPHRASE and re-run `switchroom doctor`"
|
|
28619
|
-
});
|
|
28620
|
-
return results;
|
|
28621
|
-
}
|
|
28622
|
-
let entries;
|
|
28623
|
-
try {
|
|
28624
|
-
entries = (deps.openVault ?? openVault)(passphrase, vaultPath);
|
|
28625
|
-
} catch (err) {
|
|
28626
|
-
results.push({
|
|
28627
|
-
name: "agent secret access",
|
|
28628
|
-
status: vf.readable ? "fail" : "warn",
|
|
28629
|
-
detail: `cannot open the vault: ${err.message}`,
|
|
28630
|
-
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)"
|
|
28631
|
-
});
|
|
28632
|
-
return results;
|
|
28633
|
-
}
|
|
28634
|
-
const agents = Object.keys(config.agents ?? {});
|
|
28635
|
-
for (const name of agents) {
|
|
28636
|
-
const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[name]);
|
|
28637
|
-
const cronKeys = new Set;
|
|
28638
|
-
for (const entry of resolved.schedule ?? []) {
|
|
28639
|
-
for (const s of entry.secrets ?? [])
|
|
28640
|
-
cronKeys.add(s);
|
|
28641
|
-
}
|
|
28642
|
-
const refKeys = new Set;
|
|
28643
|
-
collectVaultRefs2(resolved, refKeys);
|
|
28644
|
-
const needed = new Set([...cronKeys, ...refKeys]);
|
|
28645
|
-
if (needed.size === 0) {
|
|
28646
|
-
results.push({
|
|
28647
|
-
name: `secret access: ${name}`,
|
|
28648
|
-
status: "ok",
|
|
28649
|
-
detail: "no declared vault secrets"
|
|
28650
|
-
});
|
|
28651
|
-
continue;
|
|
28652
|
-
}
|
|
28653
|
-
const gaps = [];
|
|
28654
|
-
for (const key of [...needed].sort()) {
|
|
28655
|
-
const isGoogleSlot = key.startsWith("google:");
|
|
28656
|
-
if (!isGoogleSlot && !(key in entries)) {
|
|
28657
|
-
gaps.push(`'${key}' missing from the vault`);
|
|
28658
|
-
continue;
|
|
28659
|
-
}
|
|
28660
|
-
const byScope = checkEntryScope(entries[key]?.scope, name);
|
|
28661
|
-
if (cronKeys.has(key)) {
|
|
28662
|
-
const byAgent = checkAclByAgent(config, name, key);
|
|
28663
|
-
if (!byAgent.allow) {
|
|
28664
|
-
gaps.push(`'${key}' (cron) \u2014 no static ACL grants read (${byAgent.reason})`);
|
|
28665
|
-
continue;
|
|
28666
|
-
}
|
|
28667
|
-
}
|
|
28668
|
-
if (!byScope.allow) {
|
|
28669
|
-
gaps.push(`'${key}' \u2014 per-key scope denies read (${byScope.reason})`);
|
|
28670
|
-
}
|
|
28671
|
-
}
|
|
28672
|
-
if (gaps.length === 0) {
|
|
28673
|
-
results.push({
|
|
28674
|
-
name: `secret access: ${name}`,
|
|
28675
|
-
status: "ok",
|
|
28676
|
-
detail: `${needed.size} secret(s): all present + ACL ok`
|
|
28677
|
-
});
|
|
28678
|
-
} else {
|
|
28679
|
-
results.push({
|
|
28680
|
-
name: `secret access: ${name}`,
|
|
28681
|
-
status: "fail",
|
|
28682
|
-
detail: `${gaps.length}/${needed.size} unreachable \u2014 ${gaps.join("; ")}`,
|
|
28683
|
-
fix: "`switchroom vault set <key>` for missing keys; " + "`switchroom vault set <key> --allow " + name + "` to grant this agent read access"
|
|
28684
|
-
});
|
|
28685
|
-
}
|
|
28686
|
-
}
|
|
28687
|
-
return results;
|
|
28688
|
-
}
|
|
28689
|
-
var init_doctor_secret_access = __esm(() => {
|
|
28690
|
-
init_paths();
|
|
28691
|
-
init_merge();
|
|
28692
|
-
init_vault();
|
|
28693
|
-
});
|
|
28694
|
-
|
|
28695
28884
|
// src/cli/doctor-inlined-secrets.ts
|
|
28696
28885
|
import { readFileSync as fsReadFileSync } from "node:fs";
|
|
28697
28886
|
function isSecretShapedKey(key) {
|
|
@@ -28788,19 +28977,19 @@ var init_doctor_inlined_secrets = __esm(() => {
|
|
|
28788
28977
|
|
|
28789
28978
|
// src/cli/doctor-audit-integrity.ts
|
|
28790
28979
|
import { readFileSync as fsReadFileSync2 } from "node:fs";
|
|
28791
|
-
import { homedir as
|
|
28792
|
-
import { join as
|
|
28980
|
+
import { homedir as homedir23 } from "node:os";
|
|
28981
|
+
import { join as join41 } from "node:path";
|
|
28793
28982
|
function rootWrittenLogs(home2) {
|
|
28794
28983
|
return [
|
|
28795
|
-
{ label: "vault-broker", path:
|
|
28984
|
+
{ label: "vault-broker", path: join41(home2, ".switchroom", "vault-audit.log") },
|
|
28796
28985
|
{
|
|
28797
28986
|
label: "hostd",
|
|
28798
|
-
path:
|
|
28987
|
+
path: join41(home2, ".switchroom", "host-control-audit.log")
|
|
28799
28988
|
}
|
|
28800
28989
|
];
|
|
28801
28990
|
}
|
|
28802
28991
|
function runAuditIntegrityChecks(deps = {}) {
|
|
28803
|
-
const home2 = deps.homeDir ??
|
|
28992
|
+
const home2 = deps.homeDir ?? homedir23();
|
|
28804
28993
|
const read = deps.readFileSync ?? ((p) => fsReadFileSync2(p, "utf8"));
|
|
28805
28994
|
const results = [];
|
|
28806
28995
|
for (const { label, path: path4 } of rootWrittenLogs(home2)) {
|
|
@@ -29077,14 +29266,14 @@ var init_client4 = __esm(() => {
|
|
|
29077
29266
|
|
|
29078
29267
|
// src/cli/doctor-agent-smoke.ts
|
|
29079
29268
|
import { existsSync as existsSync47 } from "node:fs";
|
|
29080
|
-
import { homedir as
|
|
29081
|
-
import { join as
|
|
29269
|
+
import { homedir as homedir24 } from "node:os";
|
|
29270
|
+
import { join as join42 } from "node:path";
|
|
29082
29271
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
29083
29272
|
async function runAgentSmokeChecks(config, deps = {}) {
|
|
29084
29273
|
if (deps.fast)
|
|
29085
29274
|
return [];
|
|
29086
|
-
const home2 = deps.homeDir ??
|
|
29087
|
-
const sock = deps.operatorSockPath ??
|
|
29275
|
+
const home2 = deps.homeDir ?? homedir24();
|
|
29276
|
+
const sock = deps.operatorSockPath ?? join42(home2, ".switchroom", "hostd", "operator", "sock");
|
|
29088
29277
|
if (!deps.hostdRequestImpl && !existsSync47(sock)) {
|
|
29089
29278
|
return [
|
|
29090
29279
|
{
|
|
@@ -29211,16 +29400,16 @@ import {
|
|
|
29211
29400
|
readdirSync as readdirSync17,
|
|
29212
29401
|
statSync as statSync21
|
|
29213
29402
|
} from "node:fs";
|
|
29214
|
-
import { dirname as dirname12, join as
|
|
29403
|
+
import { dirname as dirname12, join as join43, resolve as resolve29 } from "node:path";
|
|
29215
29404
|
import { createPublicKey, createPrivateKey } from "node:crypto";
|
|
29216
29405
|
function findInNvm(bin) {
|
|
29217
|
-
const nvmRoot =
|
|
29406
|
+
const nvmRoot = join43(process.env.HOME ?? "", ".nvm", "versions", "node");
|
|
29218
29407
|
if (!existsSync48(nvmRoot))
|
|
29219
29408
|
return null;
|
|
29220
29409
|
try {
|
|
29221
29410
|
const versions = readdirSync17(nvmRoot).sort().reverse();
|
|
29222
29411
|
for (const v of versions) {
|
|
29223
|
-
const candidate =
|
|
29412
|
+
const candidate = join43(nvmRoot, v, "bin", bin);
|
|
29224
29413
|
try {
|
|
29225
29414
|
const s = statSync21(candidate);
|
|
29226
29415
|
if (s.isFile() || s.isSymbolicLink()) {
|
|
@@ -29385,7 +29574,7 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
|
|
|
29385
29574
|
if (envBrowsersPath && envBrowsersPath.length > 0) {
|
|
29386
29575
|
cacheLocations.push(envBrowsersPath);
|
|
29387
29576
|
}
|
|
29388
|
-
cacheLocations.push(
|
|
29577
|
+
cacheLocations.push(join43(homeDir, ".cache", "ms-playwright"));
|
|
29389
29578
|
for (const cacheDir of cacheLocations) {
|
|
29390
29579
|
if (!existsSync48(cacheDir))
|
|
29391
29580
|
continue;
|
|
@@ -29393,10 +29582,10 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
|
|
|
29393
29582
|
const entries = readdirSync17(cacheDir).filter((e) => e.startsWith("chromium"));
|
|
29394
29583
|
for (const entry of entries) {
|
|
29395
29584
|
const candidates2 = [
|
|
29396
|
-
|
|
29397
|
-
|
|
29398
|
-
|
|
29399
|
-
|
|
29585
|
+
join43(cacheDir, entry, "chrome-linux64", "chrome"),
|
|
29586
|
+
join43(cacheDir, entry, "chrome-linux", "chrome"),
|
|
29587
|
+
join43(cacheDir, entry, "chrome-linux64", "headless_shell"),
|
|
29588
|
+
join43(cacheDir, entry, "chrome-linux", "headless_shell")
|
|
29400
29589
|
];
|
|
29401
29590
|
for (const path4 of candidates2) {
|
|
29402
29591
|
if (existsSync48(path4))
|
|
@@ -29479,7 +29668,7 @@ function checkConfig(config, configPath) {
|
|
|
29479
29668
|
function checkLegacyState() {
|
|
29480
29669
|
const results = [];
|
|
29481
29670
|
const h = process.env.HOME ?? "/root";
|
|
29482
|
-
const clerkDir =
|
|
29671
|
+
const clerkDir = join43(h, LEGACY_STATE_DIR);
|
|
29483
29672
|
const clerkPresent = existsSync48(clerkDir);
|
|
29484
29673
|
results.push({
|
|
29485
29674
|
name: "legacy ~/.clerk state",
|
|
@@ -29489,7 +29678,7 @@ function checkLegacyState() {
|
|
|
29489
29678
|
fix: "Legacy state detected. Run `mv ~/.clerk ~/.switchroom` and rename " + "any top-level `clerk:` key in switchroom.yaml to `switchroom:`. " + "This back-compat shim is REMOVED in v0.13.0 \u2014 no automatic " + "migration exists."
|
|
29490
29679
|
} : {}
|
|
29491
29680
|
});
|
|
29492
|
-
const legacySock =
|
|
29681
|
+
const legacySock = join43(h, ".switchroom", "vault-broker.sock");
|
|
29493
29682
|
let sockStat = null;
|
|
29494
29683
|
try {
|
|
29495
29684
|
sockStat = lstatSync5(legacySock);
|
|
@@ -29693,7 +29882,7 @@ async function checkHindsight(config) {
|
|
|
29693
29882
|
}
|
|
29694
29883
|
function checkPendingRetainsQueue(dir) {
|
|
29695
29884
|
const home2 = process.env.HOME ?? "";
|
|
29696
|
-
const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ??
|
|
29885
|
+
const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ?? join43(home2, ".hindsight", "pending-retains");
|
|
29697
29886
|
if (!existsSync48(pendingDir)) {
|
|
29698
29887
|
return {
|
|
29699
29888
|
name: "pending-retains queue",
|
|
@@ -29824,7 +30013,7 @@ async function checkTelegram(config) {
|
|
|
29824
30013
|
const plugin = agentConfig.channels?.telegram?.plugin ?? "switchroom";
|
|
29825
30014
|
if (plugin !== "switchroom")
|
|
29826
30015
|
continue;
|
|
29827
|
-
const envPath =
|
|
30016
|
+
const envPath = join43(agentsDir, name, "telegram", ".env");
|
|
29828
30017
|
const read = tryReadHostFile(envPath);
|
|
29829
30018
|
if (read.kind === "eacces") {
|
|
29830
30019
|
results.push({
|
|
@@ -29907,7 +30096,7 @@ function checkStartShStale(agentName, startShPath) {
|
|
|
29907
30096
|
}
|
|
29908
30097
|
function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
29909
30098
|
const label = `${agentName}: $HOME/.switchroom symlink (#910)`;
|
|
29910
|
-
const path4 =
|
|
30099
|
+
const path4 = join43(agentDir, "home", ".switchroom");
|
|
29911
30100
|
let stats;
|
|
29912
30101
|
try {
|
|
29913
30102
|
stats = lstatSync5(path4);
|
|
@@ -29944,7 +30133,7 @@ function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
|
29944
30133
|
}
|
|
29945
30134
|
function checkRepoHygiene(repoRoot) {
|
|
29946
30135
|
const results = [];
|
|
29947
|
-
const exportDir =
|
|
30136
|
+
const exportDir = join43(repoRoot, "clerk-export");
|
|
29948
30137
|
if (existsSync48(exportDir)) {
|
|
29949
30138
|
results.push({
|
|
29950
30139
|
name: "repo hygiene: clerk-export/ on disk (#1072)",
|
|
@@ -29953,7 +30142,7 @@ function checkRepoHygiene(repoRoot) {
|
|
|
29953
30142
|
fix: `Run scripts/migrate-clerk-export-to-vault.sh to move the bundle ` + `into the vault, then delete the on-disk copy.`
|
|
29954
30143
|
});
|
|
29955
30144
|
}
|
|
29956
|
-
const knownTarball =
|
|
30145
|
+
const knownTarball = join43(repoRoot, "clerk-export-with-secrets.tar.gz");
|
|
29957
30146
|
if (existsSync48(knownTarball)) {
|
|
29958
30147
|
results.push({
|
|
29959
30148
|
name: "repo hygiene: clerk-export-with-secrets.tar.gz on disk (#1072)",
|
|
@@ -29971,7 +30160,7 @@ function checkRepoHygiene(repoRoot) {
|
|
|
29971
30160
|
results.push({
|
|
29972
30161
|
name: `repo hygiene: ${name} on disk (#1072)`,
|
|
29973
30162
|
status: "warn",
|
|
29974
|
-
detail: `${
|
|
30163
|
+
detail: `${join43(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
|
|
29975
30164
|
fix: `Inspect, migrate any secrets into the vault, then delete the ` + `archive.`
|
|
29976
30165
|
});
|
|
29977
30166
|
}
|
|
@@ -29994,9 +30183,9 @@ function checkRepoHygiene(repoRoot) {
|
|
|
29994
30183
|
}
|
|
29995
30184
|
function isSwitchroomCheckout(dir) {
|
|
29996
30185
|
try {
|
|
29997
|
-
if (!existsSync48(
|
|
30186
|
+
if (!existsSync48(join43(dir, ".git")))
|
|
29998
30187
|
return false;
|
|
29999
|
-
const pkgPath =
|
|
30188
|
+
const pkgPath = join43(dir, "package.json");
|
|
30000
30189
|
if (!existsSync48(pkgPath))
|
|
30001
30190
|
return false;
|
|
30002
30191
|
const pkg = JSON.parse(readFileSync44(pkgPath, "utf-8"));
|
|
@@ -30033,7 +30222,7 @@ function checkAgents(config, configPath) {
|
|
|
30033
30222
|
fix: `Rotate the bot token (e.g. via \`switchroom vault\`), then run ` + `\`switchroom agent unquarantine ${name}\` and \`switchroom agent restart ${name}\``
|
|
30034
30223
|
});
|
|
30035
30224
|
}
|
|
30036
|
-
results.push(checkStartShStale(name,
|
|
30225
|
+
results.push(checkStartShStale(name, join43(agentDir, "start.sh")));
|
|
30037
30226
|
results.push(checkLeakedHomeSwitchroom(name, agentDir));
|
|
30038
30227
|
const status = statuses[name];
|
|
30039
30228
|
const active = status?.active ?? "unknown";
|
|
@@ -30110,7 +30299,7 @@ function checkAgents(config, configPath) {
|
|
|
30110
30299
|
}
|
|
30111
30300
|
}
|
|
30112
30301
|
if (agentConfig.channels?.telegram?.plugin === "switchroom") {
|
|
30113
|
-
const mcpJsonPath =
|
|
30302
|
+
const mcpJsonPath = join43(agentDir, ".mcp.json");
|
|
30114
30303
|
if (!existsSync48(mcpJsonPath)) {
|
|
30115
30304
|
results.push({
|
|
30116
30305
|
name: `${name}: .mcp.json`,
|
|
@@ -30411,7 +30600,7 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
|
|
|
30411
30600
|
};
|
|
30412
30601
|
}
|
|
30413
30602
|
const credDir = dirname12(envPath);
|
|
30414
|
-
const authScript =
|
|
30603
|
+
const authScript = join43(credDir, "claude-auth.py");
|
|
30415
30604
|
if (!existsSync48(authScript)) {
|
|
30416
30605
|
return {
|
|
30417
30606
|
name: "mff: auth flow",
|
|
@@ -30690,7 +30879,7 @@ function registerDoctorCommand(program3) {
|
|
|
30690
30879
|
},
|
|
30691
30880
|
{ title: "Legacy State", results: checkLegacyState() },
|
|
30692
30881
|
{ title: "Vault", results: checkVault(config) },
|
|
30693
|
-
{ title: "Vault access", results: runSecretAccessChecks(config) },
|
|
30882
|
+
{ title: "Vault access", results: await runSecretAccessChecks(config) },
|
|
30694
30883
|
{ title: "Memory (Hindsight)", results: await checkHindsight(config) },
|
|
30695
30884
|
{ title: "Telegram", results: await checkTelegram(config) },
|
|
30696
30885
|
{ title: "Agents", results: checkAgents(config, configPath) },
|
|
@@ -30703,7 +30892,13 @@ function registerDoctorCommand(program3) {
|
|
|
30703
30892
|
title: "Agent liveness (in-agent via hostd)",
|
|
30704
30893
|
results: await runAgentSmokeChecks(config, { fast: opts.fast })
|
|
30705
30894
|
},
|
|
30706
|
-
{
|
|
30895
|
+
{
|
|
30896
|
+
title: "Google Drive",
|
|
30897
|
+
results: [
|
|
30898
|
+
...runDriveChecks(config),
|
|
30899
|
+
...await runDriveBrokerReachabilityChecks(config)
|
|
30900
|
+
]
|
|
30901
|
+
},
|
|
30707
30902
|
{ title: "MFF Skill", results: await checkMff(passphrase, vaultPath, config) }
|
|
30708
30903
|
];
|
|
30709
30904
|
const cwd = process.cwd();
|
|
@@ -47047,8 +47242,8 @@ var {
|
|
|
47047
47242
|
} = import__.default;
|
|
47048
47243
|
|
|
47049
47244
|
// src/build-info.ts
|
|
47050
|
-
var VERSION = "0.12.
|
|
47051
|
-
var COMMIT_SHA = "
|
|
47245
|
+
var VERSION = "0.12.15";
|
|
47246
|
+
var COMMIT_SHA = "dc508a92";
|
|
47052
47247
|
|
|
47053
47248
|
// src/cli/agent.ts
|
|
47054
47249
|
init_source();
|
|
@@ -55869,6 +56064,7 @@ var DEFAULT_AUTO_UNLOCK_PATH = "~/.switchroom/vault-auto-unlock";
|
|
|
55869
56064
|
|
|
55870
56065
|
// src/vault/broker/server.ts
|
|
55871
56066
|
init_peercred();
|
|
56067
|
+
init_acl();
|
|
55872
56068
|
init_protocol();
|
|
55873
56069
|
|
|
55874
56070
|
// src/vault/broker/audit-log.ts
|
|
@@ -58199,7 +58395,9 @@ class VaultBroker {
|
|
|
58199
58395
|
this._writePidFile();
|
|
58200
58396
|
this._sdNotify(`READY=1
|
|
58201
58397
|
`);
|
|
58202
|
-
this.
|
|
58398
|
+
if (this.testOpts._testSecrets === undefined) {
|
|
58399
|
+
this._tryAutoUnlock();
|
|
58400
|
+
}
|
|
58203
58401
|
if (process.platform !== "linux") {
|
|
58204
58402
|
process.stderr.write(`[vault-broker] WARNING: running on ${process.platform} with ` + `SWITCHROOM_BROKER_ALLOW_NON_LINUX=1 \u2014 peercred ACL is disabled. ` + `Access control is socket file mode 0600 ONLY. Do not use this configuration for production secrets.
|
|
58205
58403
|
`);
|
|
@@ -58510,6 +58708,49 @@ class VaultBroker {
|
|
|
58510
58708
|
socket.write(encodeResponse({ ok: true, locked: true }));
|
|
58511
58709
|
return;
|
|
58512
58710
|
}
|
|
58711
|
+
if (req.op === "preflight_access") {
|
|
58712
|
+
if (!isOperator) {
|
|
58713
|
+
writeAudit({
|
|
58714
|
+
ts: new Date().toISOString(),
|
|
58715
|
+
op: "preflight_access",
|
|
58716
|
+
caller: auditCaller,
|
|
58717
|
+
pid: auditPid,
|
|
58718
|
+
cgroup: auditCgroup,
|
|
58719
|
+
result: "denied:operator-only"
|
|
58720
|
+
});
|
|
58721
|
+
socket.write(encodeResponse(errorResponse("DENIED", "preflight_access is operator-only")));
|
|
58722
|
+
return;
|
|
58723
|
+
}
|
|
58724
|
+
if (this.secrets === null) {
|
|
58725
|
+
socket.write(encodeResponse(errorResponse("LOCKED", "Vault is locked")));
|
|
58726
|
+
return;
|
|
58727
|
+
}
|
|
58728
|
+
const pfSecrets = this.secrets;
|
|
58729
|
+
const pfConfig = this.config;
|
|
58730
|
+
const results = req.keys.map((key) => {
|
|
58731
|
+
const exists = Object.prototype.hasOwnProperty.call(pfSecrets, key);
|
|
58732
|
+
const acl = pfConfig !== null ? checkAclByAgent(pfConfig, req.agent, key) : { allow: false, reason: "broker has no config loaded" };
|
|
58733
|
+
const scope = checkEntryScope(pfSecrets[key]?.scope, req.agent);
|
|
58734
|
+
return {
|
|
58735
|
+
key,
|
|
58736
|
+
exists,
|
|
58737
|
+
acl_ok: acl.allow,
|
|
58738
|
+
...acl.allow ? {} : { acl_reason: acl.reason },
|
|
58739
|
+
scope_ok: scope.allow,
|
|
58740
|
+
...scope.allow ? {} : { scope_reason: scope.reason }
|
|
58741
|
+
};
|
|
58742
|
+
});
|
|
58743
|
+
writeAudit({
|
|
58744
|
+
ts: new Date().toISOString(),
|
|
58745
|
+
op: "preflight_access",
|
|
58746
|
+
caller: auditCaller,
|
|
58747
|
+
pid: auditPid,
|
|
58748
|
+
cgroup: auditCgroup,
|
|
58749
|
+
result: `allowed:agent=${req.agent},keys=${req.keys.length}`
|
|
58750
|
+
});
|
|
58751
|
+
socket.write(encodeResponse({ ok: true, op: "preflight_access", results }));
|
|
58752
|
+
return;
|
|
58753
|
+
}
|
|
58513
58754
|
if (req.op === "list") {
|
|
58514
58755
|
if (this.secrets === null) {
|
|
58515
58756
|
socket.write(encodeResponse(errorResponse("LOCKED", "Vault is locked")));
|
|
@@ -68972,15 +69213,15 @@ init_loader();
|
|
|
68972
69213
|
init_lifecycle();
|
|
68973
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";
|
|
68974
69215
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
68975
|
-
import { join as
|
|
68976
|
-
import { homedir as
|
|
68977
|
-
var DEFAULT_COMPOSE_PATH =
|
|
69216
|
+
import { join as join44, dirname as dirname13, resolve as resolve30 } from "node:path";
|
|
69217
|
+
import { homedir as homedir25 } from "node:os";
|
|
69218
|
+
var DEFAULT_COMPOSE_PATH = join44(homedir25(), ".switchroom", "compose", "docker-compose.yml");
|
|
68978
69219
|
function runningFromSwitchroomCheckout(scriptPath) {
|
|
68979
69220
|
let dir = dirname13(scriptPath);
|
|
68980
69221
|
for (let i = 0;i < 12; i++) {
|
|
68981
|
-
if (existsSync49(
|
|
69222
|
+
if (existsSync49(join44(dir, ".git"))) {
|
|
68982
69223
|
try {
|
|
68983
|
-
const pkg = JSON.parse(readFileSync45(
|
|
69224
|
+
const pkg = JSON.parse(readFileSync45(join44(dir, "package.json"), "utf-8"));
|
|
68984
69225
|
if (pkg.name === "switchroom")
|
|
68985
69226
|
return true;
|
|
68986
69227
|
} catch {}
|
|
@@ -69111,7 +69352,7 @@ function planUpdate(opts) {
|
|
|
69111
69352
|
return;
|
|
69112
69353
|
}
|
|
69113
69354
|
const source = resolve30(import.meta.dirname, "../../skills");
|
|
69114
|
-
const dest =
|
|
69355
|
+
const dest = join44(homedir25(), ".switchroom", "skills", "_bundled");
|
|
69115
69356
|
if (!existsSync49(source)) {
|
|
69116
69357
|
process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
|
|
69117
69358
|
`);
|
|
@@ -69225,7 +69466,7 @@ function defaultStatusProbe(composePath) {
|
|
|
69225
69466
|
} catch {}
|
|
69226
69467
|
let dir = dirname13(scriptPath);
|
|
69227
69468
|
for (let i = 0;i < 8; i++) {
|
|
69228
|
-
const pkgPath =
|
|
69469
|
+
const pkgPath = join44(dir, "package.json");
|
|
69229
69470
|
if (existsSync49(pkgPath)) {
|
|
69230
69471
|
try {
|
|
69231
69472
|
const pkg = JSON.parse(readFileSync45(pkgPath, "utf-8"));
|
|
@@ -69445,7 +69686,7 @@ init_helpers();
|
|
|
69445
69686
|
init_lifecycle();
|
|
69446
69687
|
import { execSync as execSync4 } from "node:child_process";
|
|
69447
69688
|
import { existsSync as existsSync50, readFileSync as readFileSync46 } from "node:fs";
|
|
69448
|
-
import { dirname as dirname14, join as
|
|
69689
|
+
import { dirname as dirname14, join as join45 } from "node:path";
|
|
69449
69690
|
function getClaudeCodeVersion() {
|
|
69450
69691
|
try {
|
|
69451
69692
|
const out = execSync4("claude --version 2>/dev/null", {
|
|
@@ -69495,11 +69736,11 @@ function formatUptime3(timestamp) {
|
|
|
69495
69736
|
function locateSwitchroomInstallDir() {
|
|
69496
69737
|
let dir = import.meta.dirname;
|
|
69497
69738
|
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
69498
|
-
const pkgPath =
|
|
69739
|
+
const pkgPath = join45(dir, "package.json");
|
|
69499
69740
|
if (existsSync50(pkgPath)) {
|
|
69500
69741
|
try {
|
|
69501
69742
|
const pkg = JSON.parse(readFileSync46(pkgPath, "utf-8"));
|
|
69502
|
-
if (pkg.name === "switchroom" && existsSync50(
|
|
69743
|
+
if (pkg.name === "switchroom" && existsSync50(join45(dir, ".git"))) {
|
|
69503
69744
|
return dir;
|
|
69504
69745
|
}
|
|
69505
69746
|
} catch {}
|
|
@@ -69729,7 +69970,7 @@ import {
|
|
|
69729
69970
|
writeFileSync as writeFileSync25,
|
|
69730
69971
|
writeSync as writeSync6
|
|
69731
69972
|
} from "node:fs";
|
|
69732
|
-
import { join as
|
|
69973
|
+
import { join as join46 } from "node:path";
|
|
69733
69974
|
import { randomBytes as randomBytes10 } from "node:crypto";
|
|
69734
69975
|
import { execSync as execSync5 } from "node:child_process";
|
|
69735
69976
|
|
|
@@ -70049,7 +70290,7 @@ function redactedMarker(ruleId) {
|
|
|
70049
70290
|
var ISSUES_FILE = "issues.jsonl";
|
|
70050
70291
|
var ISSUES_LOCK = "issues.lock";
|
|
70051
70292
|
function readAll(stateDir) {
|
|
70052
|
-
const path4 =
|
|
70293
|
+
const path4 = join46(stateDir, ISSUES_FILE);
|
|
70053
70294
|
if (!existsSync51(path4))
|
|
70054
70295
|
return [];
|
|
70055
70296
|
let raw;
|
|
@@ -70127,7 +70368,7 @@ function record(stateDir, input, nowFn = Date.now) {
|
|
|
70127
70368
|
});
|
|
70128
70369
|
}
|
|
70129
70370
|
function resolve33(stateDir, fingerprint, nowFn = Date.now) {
|
|
70130
|
-
if (!existsSync51(
|
|
70371
|
+
if (!existsSync51(join46(stateDir, ISSUES_FILE)))
|
|
70131
70372
|
return 0;
|
|
70132
70373
|
return withLock(stateDir, () => {
|
|
70133
70374
|
const all = readAll(stateDir);
|
|
@@ -70145,7 +70386,7 @@ function resolve33(stateDir, fingerprint, nowFn = Date.now) {
|
|
|
70145
70386
|
});
|
|
70146
70387
|
}
|
|
70147
70388
|
function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
70148
|
-
if (!existsSync51(
|
|
70389
|
+
if (!existsSync51(join46(stateDir, ISSUES_FILE)))
|
|
70149
70390
|
return 0;
|
|
70150
70391
|
return withLock(stateDir, () => {
|
|
70151
70392
|
const all = readAll(stateDir);
|
|
@@ -70163,7 +70404,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
|
70163
70404
|
});
|
|
70164
70405
|
}
|
|
70165
70406
|
function prune(stateDir, opts = {}) {
|
|
70166
|
-
if (!existsSync51(
|
|
70407
|
+
if (!existsSync51(join46(stateDir, ISSUES_FILE)))
|
|
70167
70408
|
return 0;
|
|
70168
70409
|
return withLock(stateDir, () => {
|
|
70169
70410
|
const all = readAll(stateDir);
|
|
@@ -70196,7 +70437,7 @@ function ensureDir(stateDir) {
|
|
|
70196
70437
|
mkdirSync28(stateDir, { recursive: true });
|
|
70197
70438
|
}
|
|
70198
70439
|
function writeAll(stateDir, events) {
|
|
70199
|
-
const path4 =
|
|
70440
|
+
const path4 = join46(stateDir, ISSUES_FILE);
|
|
70200
70441
|
sweepOrphanTmpFiles(stateDir);
|
|
70201
70442
|
const tmp = `${path4}.tmp-${process.pid}-${randomBytes10(4).toString("hex")}`;
|
|
70202
70443
|
const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
|
|
@@ -70218,7 +70459,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
70218
70459
|
for (const entry of entries) {
|
|
70219
70460
|
if (!entry.startsWith(TMP_PREFIX))
|
|
70220
70461
|
continue;
|
|
70221
|
-
const tmpPath =
|
|
70462
|
+
const tmpPath = join46(stateDir, entry);
|
|
70222
70463
|
try {
|
|
70223
70464
|
const stat = statSync23(tmpPath);
|
|
70224
70465
|
if (stat.mtimeMs < cutoff) {
|
|
@@ -70230,7 +70471,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
70230
70471
|
var LOCK_RETRY_MS = 25;
|
|
70231
70472
|
var LOCK_TIMEOUT_MS = 1e4;
|
|
70232
70473
|
function withLock(stateDir, fn) {
|
|
70233
|
-
const lockPath =
|
|
70474
|
+
const lockPath = join46(stateDir, ISSUES_LOCK);
|
|
70234
70475
|
const startedAt = Date.now();
|
|
70235
70476
|
let fd = null;
|
|
70236
70477
|
while (fd === null) {
|
|
@@ -70514,8 +70755,8 @@ function relTime(deltaMs) {
|
|
|
70514
70755
|
// src/cli/deps.ts
|
|
70515
70756
|
init_source();
|
|
70516
70757
|
import { existsSync as existsSync54 } from "node:fs";
|
|
70517
|
-
import { homedir as
|
|
70518
|
-
import { join as
|
|
70758
|
+
import { homedir as homedir28 } from "node:os";
|
|
70759
|
+
import { join as join49, resolve as resolve34 } from "node:path";
|
|
70519
70760
|
|
|
70520
70761
|
// src/deps/python.ts
|
|
70521
70762
|
import { createHash as createHash9 } from "node:crypto";
|
|
@@ -70526,8 +70767,8 @@ import {
|
|
|
70526
70767
|
rmSync as rmSync13,
|
|
70527
70768
|
writeFileSync as writeFileSync26
|
|
70528
70769
|
} from "node:fs";
|
|
70529
|
-
import { dirname as dirname15, join as
|
|
70530
|
-
import { homedir as
|
|
70770
|
+
import { dirname as dirname15, join as join47 } from "node:path";
|
|
70771
|
+
import { homedir as homedir26 } from "node:os";
|
|
70531
70772
|
import { execFileSync as execFileSync14 } from "node:child_process";
|
|
70532
70773
|
|
|
70533
70774
|
class PythonEnvError extends Error {
|
|
@@ -70539,7 +70780,7 @@ class PythonEnvError extends Error {
|
|
|
70539
70780
|
}
|
|
70540
70781
|
}
|
|
70541
70782
|
function defaultPythonCacheRoot() {
|
|
70542
|
-
return
|
|
70783
|
+
return join47(homedir26(), ".switchroom", "deps", "python");
|
|
70543
70784
|
}
|
|
70544
70785
|
function hashFile(path4) {
|
|
70545
70786
|
return createHash9("sha256").update(readFileSync48(path4)).digest("hex");
|
|
@@ -70551,11 +70792,11 @@ function ensurePythonEnv(opts) {
|
|
|
70551
70792
|
if (!existsSync52(requirementsPath)) {
|
|
70552
70793
|
throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
|
|
70553
70794
|
}
|
|
70554
|
-
const venvDir =
|
|
70555
|
-
const stampPath =
|
|
70556
|
-
const binDir =
|
|
70557
|
-
const pythonBin =
|
|
70558
|
-
const pipBin =
|
|
70795
|
+
const venvDir = join47(cacheRoot, skillName);
|
|
70796
|
+
const stampPath = join47(venvDir, ".requirements.sha256");
|
|
70797
|
+
const binDir = join47(venvDir, "bin");
|
|
70798
|
+
const pythonBin = join47(binDir, "python");
|
|
70799
|
+
const pipBin = join47(binDir, "pip");
|
|
70559
70800
|
const targetHash = hashFile(requirementsPath);
|
|
70560
70801
|
if (!force && existsSync52(stampPath) && existsSync52(pythonBin)) {
|
|
70561
70802
|
const existingHash = readFileSync48(stampPath, "utf8").trim();
|
|
@@ -70614,8 +70855,8 @@ import {
|
|
|
70614
70855
|
rmSync as rmSync14,
|
|
70615
70856
|
writeFileSync as writeFileSync27
|
|
70616
70857
|
} from "node:fs";
|
|
70617
|
-
import { dirname as dirname16, join as
|
|
70618
|
-
import { homedir as
|
|
70858
|
+
import { dirname as dirname16, join as join48 } from "node:path";
|
|
70859
|
+
import { homedir as homedir27 } from "node:os";
|
|
70619
70860
|
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
70620
70861
|
|
|
70621
70862
|
class NodeEnvError extends Error {
|
|
@@ -70638,7 +70879,7 @@ var LOCKFILES_FOR = {
|
|
|
70638
70879
|
npm: ["package-lock.json"]
|
|
70639
70880
|
};
|
|
70640
70881
|
function defaultNodeCacheRoot() {
|
|
70641
|
-
return
|
|
70882
|
+
return join48(homedir27(), ".switchroom", "deps", "node");
|
|
70642
70883
|
}
|
|
70643
70884
|
function hashDepInputs(packageJsonPath) {
|
|
70644
70885
|
const sourceDir = dirname16(packageJsonPath);
|
|
@@ -70647,7 +70888,7 @@ function hashDepInputs(packageJsonPath) {
|
|
|
70647
70888
|
`);
|
|
70648
70889
|
hasher.update(readFileSync49(packageJsonPath));
|
|
70649
70890
|
for (const lockName of ALL_LOCKFILES) {
|
|
70650
|
-
const lockPath =
|
|
70891
|
+
const lockPath = join48(sourceDir, lockName);
|
|
70651
70892
|
if (existsSync53(lockPath)) {
|
|
70652
70893
|
hasher.update(`
|
|
70653
70894
|
`);
|
|
@@ -70667,10 +70908,10 @@ function ensureNodeEnv(opts) {
|
|
|
70667
70908
|
throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
|
|
70668
70909
|
}
|
|
70669
70910
|
const sourceDir = dirname16(packageJsonPath);
|
|
70670
|
-
const envDir =
|
|
70671
|
-
const stampPath =
|
|
70672
|
-
const nodeModulesDir =
|
|
70673
|
-
const binDir =
|
|
70911
|
+
const envDir = join48(cacheRoot, skillName);
|
|
70912
|
+
const stampPath = join48(envDir, ".package.sha256");
|
|
70913
|
+
const nodeModulesDir = join48(envDir, "node_modules");
|
|
70914
|
+
const binDir = join48(nodeModulesDir, ".bin");
|
|
70674
70915
|
const targetHash = hashDepInputs(packageJsonPath);
|
|
70675
70916
|
if (!force && existsSync53(stampPath) && existsSync53(nodeModulesDir)) {
|
|
70676
70917
|
const existingHash = readFileSync49(stampPath, "utf8").trim();
|
|
@@ -70688,12 +70929,12 @@ function ensureNodeEnv(opts) {
|
|
|
70688
70929
|
rmSync14(envDir, { recursive: true, force: true });
|
|
70689
70930
|
}
|
|
70690
70931
|
mkdirSync30(envDir, { recursive: true });
|
|
70691
|
-
copyFileSync9(packageJsonPath,
|
|
70932
|
+
copyFileSync9(packageJsonPath, join48(envDir, "package.json"));
|
|
70692
70933
|
let copiedLockfile = false;
|
|
70693
70934
|
for (const lockName of LOCKFILES_FOR[installer]) {
|
|
70694
|
-
const lockPath =
|
|
70935
|
+
const lockPath = join48(sourceDir, lockName);
|
|
70695
70936
|
if (existsSync53(lockPath)) {
|
|
70696
|
-
copyFileSync9(lockPath,
|
|
70937
|
+
copyFileSync9(lockPath, join48(envDir, lockName));
|
|
70697
70938
|
copiedLockfile = true;
|
|
70698
70939
|
}
|
|
70699
70940
|
}
|
|
@@ -70722,7 +70963,7 @@ function ensureNodeEnv(opts) {
|
|
|
70722
70963
|
|
|
70723
70964
|
// src/cli/deps.ts
|
|
70724
70965
|
function builtinSkillsRoot() {
|
|
70725
|
-
return resolve34(
|
|
70966
|
+
return resolve34(homedir28(), ".switchroom/skills/_bundled");
|
|
70726
70967
|
}
|
|
70727
70968
|
function registerDepsCommand(program3) {
|
|
70728
70969
|
const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
|
|
@@ -70732,13 +70973,13 @@ function registerDepsCommand(program3) {
|
|
|
70732
70973
|
console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
|
|
70733
70974
|
process.exit(1);
|
|
70734
70975
|
}
|
|
70735
|
-
const skillDir =
|
|
70976
|
+
const skillDir = join49(skillsRoot, skill);
|
|
70736
70977
|
if (!existsSync54(skillDir)) {
|
|
70737
70978
|
console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
|
|
70738
70979
|
process.exit(1);
|
|
70739
70980
|
}
|
|
70740
|
-
const requirementsPath =
|
|
70741
|
-
const packageJsonPath =
|
|
70981
|
+
const requirementsPath = join49(skillDir, "requirements.txt");
|
|
70982
|
+
const packageJsonPath = join49(skillDir, "package.json");
|
|
70742
70983
|
const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync54(requirementsPath));
|
|
70743
70984
|
const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync54(packageJsonPath));
|
|
70744
70985
|
let did = 0;
|
|
@@ -71693,7 +71934,7 @@ init_helpers();
|
|
|
71693
71934
|
init_loader();
|
|
71694
71935
|
init_merge();
|
|
71695
71936
|
import { copyFileSync as copyFileSync10, existsSync as existsSync56, readFileSync as readFileSync50, writeFileSync as writeFileSync28 } from "node:fs";
|
|
71696
|
-
import { join as
|
|
71937
|
+
import { join as join50, resolve as resolve36 } from "node:path";
|
|
71697
71938
|
init_schema();
|
|
71698
71939
|
function resolveSoulTargetOrExit(program3, agentName) {
|
|
71699
71940
|
const config = getConfig(program3);
|
|
@@ -71717,7 +71958,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
71717
71958
|
profileName,
|
|
71718
71959
|
profilePath,
|
|
71719
71960
|
workspaceDir,
|
|
71720
|
-
soulPath:
|
|
71961
|
+
soulPath: join50(workspaceDir, "SOUL.md"),
|
|
71721
71962
|
soul: merged.soul
|
|
71722
71963
|
};
|
|
71723
71964
|
}
|
|
@@ -71784,7 +72025,7 @@ function registerSoulCommand(program3) {
|
|
|
71784
72025
|
init_helpers();
|
|
71785
72026
|
init_loader();
|
|
71786
72027
|
import { existsSync as existsSync57, readFileSync as readFileSync51, readdirSync as readdirSync19, statSync as statSync24 } from "node:fs";
|
|
71787
|
-
import { resolve as resolve37, join as
|
|
72028
|
+
import { resolve as resolve37, join as join51 } from "node:path";
|
|
71788
72029
|
import { createHash as createHash11 } from "node:crypto";
|
|
71789
72030
|
init_merge();
|
|
71790
72031
|
init_hindsight();
|
|
@@ -71798,7 +72039,7 @@ function sha256(content) {
|
|
|
71798
72039
|
return createHash11("sha256").update(content).digest("hex").slice(0, 16);
|
|
71799
72040
|
}
|
|
71800
72041
|
function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
71801
|
-
const projectsDir =
|
|
72042
|
+
const projectsDir = join51(claudeConfigDir, "projects");
|
|
71802
72043
|
if (!existsSync57(projectsDir))
|
|
71803
72044
|
return;
|
|
71804
72045
|
try {
|
|
@@ -71807,8 +72048,8 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
71807
72048
|
for (const entry of entries) {
|
|
71808
72049
|
if (!entry.isDirectory())
|
|
71809
72050
|
continue;
|
|
71810
|
-
const projectPath =
|
|
71811
|
-
const transcriptPath =
|
|
72051
|
+
const projectPath = join51(projectsDir, entry.name);
|
|
72052
|
+
const transcriptPath = join51(projectPath, "transcript.jsonl");
|
|
71812
72053
|
if (!existsSync57(transcriptPath))
|
|
71813
72054
|
continue;
|
|
71814
72055
|
const stat3 = statSync24(transcriptPath);
|
|
@@ -71877,11 +72118,11 @@ function registerDebugCommand(program3) {
|
|
|
71877
72118
|
process.exit(1);
|
|
71878
72119
|
}
|
|
71879
72120
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
71880
|
-
const claudeConfigDir =
|
|
71881
|
-
const claudeMdPath =
|
|
71882
|
-
const soulMdPath =
|
|
71883
|
-
const workspaceSoulMdPath =
|
|
71884
|
-
const handoffPath =
|
|
72121
|
+
const claudeConfigDir = join51(agentDir, ".claude");
|
|
72122
|
+
const claudeMdPath = join51(agentDir, "CLAUDE.md");
|
|
72123
|
+
const soulMdPath = join51(agentDir, "SOUL.md");
|
|
72124
|
+
const workspaceSoulMdPath = join51(workspaceDir, "SOUL.md");
|
|
72125
|
+
const handoffPath = join51(agentDir, ".handoff.md");
|
|
71885
72126
|
const lastN = parseInt(opts.last, 10);
|
|
71886
72127
|
if (isNaN(lastN) || lastN < 1) {
|
|
71887
72128
|
console.error("--last must be a positive integer");
|
|
@@ -72031,8 +72272,8 @@ init_source();
|
|
|
72031
72272
|
// src/worktree/claim.ts
|
|
72032
72273
|
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
72033
72274
|
import { closeSync as closeSync11, mkdirSync as mkdirSync32, openSync as openSync11, existsSync as existsSync59, unlinkSync as unlinkSync12 } from "node:fs";
|
|
72034
|
-
import { join as
|
|
72035
|
-
import { homedir as
|
|
72275
|
+
import { join as join53, resolve as resolve39 } from "node:path";
|
|
72276
|
+
import { homedir as homedir30 } from "node:os";
|
|
72036
72277
|
import { randomBytes as randomBytes11 } from "node:crypto";
|
|
72037
72278
|
|
|
72038
72279
|
// src/worktree/registry.ts
|
|
@@ -72045,13 +72286,13 @@ import {
|
|
|
72045
72286
|
existsSync as existsSync58,
|
|
72046
72287
|
renameSync as renameSync11
|
|
72047
72288
|
} from "node:fs";
|
|
72048
|
-
import { join as
|
|
72049
|
-
import { homedir as
|
|
72289
|
+
import { join as join52, resolve as resolve38 } from "node:path";
|
|
72290
|
+
import { homedir as homedir29 } from "node:os";
|
|
72050
72291
|
function registryDir() {
|
|
72051
|
-
return resolve38(process.env.SWITCHROOM_WORKTREE_DIR ??
|
|
72292
|
+
return resolve38(process.env.SWITCHROOM_WORKTREE_DIR ?? join52(homedir29(), ".switchroom", "worktrees"));
|
|
72052
72293
|
}
|
|
72053
72294
|
function recordPath(id) {
|
|
72054
|
-
return
|
|
72295
|
+
return join52(registryDir(), `${id}.json`);
|
|
72055
72296
|
}
|
|
72056
72297
|
function ensureDir2() {
|
|
72057
72298
|
mkdirSync31(registryDir(), { recursive: true });
|
|
@@ -72102,7 +72343,7 @@ function acquireRepoLock(repoPath) {
|
|
|
72102
72343
|
const lockDir = registryDir();
|
|
72103
72344
|
mkdirSync32(lockDir, { recursive: true });
|
|
72104
72345
|
const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
|
|
72105
|
-
const lockPath =
|
|
72346
|
+
const lockPath = join53(lockDir, `.lock-${lockName}`);
|
|
72106
72347
|
const deadline = Date.now() + 5000;
|
|
72107
72348
|
let fd = null;
|
|
72108
72349
|
while (fd === null) {
|
|
@@ -72129,7 +72370,7 @@ function acquireRepoLock(repoPath) {
|
|
|
72129
72370
|
}
|
|
72130
72371
|
var DEFAULT_CONCURRENCY = 5;
|
|
72131
72372
|
function worktreesBaseDir() {
|
|
72132
|
-
return resolve39(process.env.SWITCHROOM_WORKTREE_BASE ??
|
|
72373
|
+
return resolve39(process.env.SWITCHROOM_WORKTREE_BASE ?? join53(homedir30(), ".switchroom", "worktree-checkouts"));
|
|
72133
72374
|
}
|
|
72134
72375
|
function shortId() {
|
|
72135
72376
|
return randomBytes11(4).toString("hex");
|
|
@@ -72151,7 +72392,7 @@ function resolveRepoPath(repo, codeRepos) {
|
|
|
72151
72392
|
}
|
|
72152
72393
|
function expandHome(p) {
|
|
72153
72394
|
if (p.startsWith("~/"))
|
|
72154
|
-
return
|
|
72395
|
+
return join53(homedir30(), p.slice(2));
|
|
72155
72396
|
return p;
|
|
72156
72397
|
}
|
|
72157
72398
|
async function claimWorktree(input, codeRepos) {
|
|
@@ -72179,7 +72420,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
72179
72420
|
branch = `task/${taskSuffix}-${id}`;
|
|
72180
72421
|
const baseDir = worktreesBaseDir();
|
|
72181
72422
|
mkdirSync32(baseDir, { recursive: true });
|
|
72182
|
-
worktreePath =
|
|
72423
|
+
worktreePath = join53(baseDir, `${id}-${taskSuffix}`);
|
|
72183
72424
|
const now = new Date().toISOString();
|
|
72184
72425
|
const record2 = {
|
|
72185
72426
|
id,
|
|
@@ -72434,7 +72675,7 @@ import {
|
|
|
72434
72675
|
rmSync as rmSync15,
|
|
72435
72676
|
writeFileSync as writeFileSync30
|
|
72436
72677
|
} from "node:fs";
|
|
72437
|
-
import { join as
|
|
72678
|
+
import { join as join54 } from "node:path";
|
|
72438
72679
|
function encodeCredentialsFilename(email) {
|
|
72439
72680
|
const SAFE = new Set([
|
|
72440
72681
|
..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
|
@@ -72598,16 +72839,16 @@ function resolveCredentialsDir(env2) {
|
|
|
72598
72839
|
if (explicit && explicit.length > 0)
|
|
72599
72840
|
return explicit;
|
|
72600
72841
|
const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
|
|
72601
|
-
return
|
|
72842
|
+
return join54(stateBase, "google-workspace-mcp", "credentials");
|
|
72602
72843
|
}
|
|
72603
72844
|
function writeSeedFile(dir, email, seed) {
|
|
72604
72845
|
mkdirSync33(dir, { recursive: true, mode: 448 });
|
|
72605
72846
|
chmodSync9(dir, 448);
|
|
72606
72847
|
for (const name of readdirSync21(dir)) {
|
|
72607
|
-
rmSync15(
|
|
72848
|
+
rmSync15(join54(dir, name), { force: true, recursive: true });
|
|
72608
72849
|
}
|
|
72609
72850
|
const filename = encodeCredentialsFilename(email);
|
|
72610
|
-
const filePath =
|
|
72851
|
+
const filePath = join54(dir, filename);
|
|
72611
72852
|
writeFileSync30(filePath, JSON.stringify(seed), { mode: 384 });
|
|
72612
72853
|
chmodSync9(filePath, 384);
|
|
72613
72854
|
return filePath;
|
|
@@ -73105,8 +73346,8 @@ agents:
|
|
|
73105
73346
|
|
|
73106
73347
|
// src/cli/apply.ts
|
|
73107
73348
|
init_resolver();
|
|
73108
|
-
import { dirname as dirname19, join as
|
|
73109
|
-
import { homedir as
|
|
73349
|
+
import { dirname as dirname19, join as join58, resolve as resolve41 } from "node:path";
|
|
73350
|
+
import { homedir as homedir32 } from "node:os";
|
|
73110
73351
|
import { execFileSync as execFileSync19 } from "node:child_process";
|
|
73111
73352
|
init_vault();
|
|
73112
73353
|
init_loader();
|
|
@@ -73114,7 +73355,7 @@ init_loader();
|
|
|
73114
73355
|
|
|
73115
73356
|
// src/cli/update-prompt-hook.ts
|
|
73116
73357
|
import { existsSync as existsSync62, readFileSync as readFileSync53, writeFileSync as writeFileSync31, chmodSync as chmodSync10, mkdirSync as mkdirSync34 } from "node:fs";
|
|
73117
|
-
import { join as
|
|
73358
|
+
import { join as join55 } from "node:path";
|
|
73118
73359
|
var HOOK_FILENAME = "update-card-on-prompt.sh";
|
|
73119
73360
|
function updatePromptHookScript() {
|
|
73120
73361
|
return `#!/bin/bash
|
|
@@ -73180,9 +73421,9 @@ exit 0
|
|
|
73180
73421
|
`;
|
|
73181
73422
|
}
|
|
73182
73423
|
function installUpdatePromptHook(agentDir) {
|
|
73183
|
-
const hooksDir =
|
|
73424
|
+
const hooksDir = join55(agentDir, ".claude", "hooks");
|
|
73184
73425
|
mkdirSync34(hooksDir, { recursive: true });
|
|
73185
|
-
const scriptPath =
|
|
73426
|
+
const scriptPath = join55(hooksDir, HOOK_FILENAME);
|
|
73186
73427
|
const desired = updatePromptHookScript();
|
|
73187
73428
|
let installed = false;
|
|
73188
73429
|
const existing = existsSync62(scriptPath) ? readFileSync53(scriptPath, "utf-8") : "";
|
|
@@ -73195,7 +73436,7 @@ function installUpdatePromptHook(agentDir) {
|
|
|
73195
73436
|
chmodSync10(scriptPath, 493);
|
|
73196
73437
|
} catch {}
|
|
73197
73438
|
}
|
|
73198
|
-
const settingsPath =
|
|
73439
|
+
const settingsPath = join55(agentDir, ".claude", "settings.json");
|
|
73199
73440
|
if (!existsSync62(settingsPath)) {
|
|
73200
73441
|
return { scriptPath, settingsPath, installed };
|
|
73201
73442
|
}
|
|
@@ -73321,7 +73562,7 @@ import {
|
|
|
73321
73562
|
realpathSync as realpathSync6,
|
|
73322
73563
|
statSync as statSync25
|
|
73323
73564
|
} from "node:fs";
|
|
73324
|
-
import { join as
|
|
73565
|
+
import { join as join57 } from "node:path";
|
|
73325
73566
|
function resolveOperatorUid() {
|
|
73326
73567
|
const sudoUid = process.env.SUDO_UID;
|
|
73327
73568
|
if (sudoUid !== undefined) {
|
|
@@ -73337,14 +73578,14 @@ function resolveOperatorUid() {
|
|
|
73337
73578
|
return;
|
|
73338
73579
|
}
|
|
73339
73580
|
function operatorOwnedPaths(home2) {
|
|
73340
|
-
const root =
|
|
73581
|
+
const root = join57(home2, ".switchroom");
|
|
73341
73582
|
return [
|
|
73342
|
-
|
|
73343
|
-
|
|
73344
|
-
|
|
73345
|
-
|
|
73346
|
-
|
|
73347
|
-
|
|
73583
|
+
join57(root, "vault"),
|
|
73584
|
+
join57(root, "vault-auto-unlock"),
|
|
73585
|
+
join57(root, "vault-audit.log"),
|
|
73586
|
+
join57(root, "host-control-audit.log"),
|
|
73587
|
+
join57(root, "accounts"),
|
|
73588
|
+
join57(root, "compose")
|
|
73348
73589
|
];
|
|
73349
73590
|
}
|
|
73350
73591
|
function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
|
|
@@ -73393,7 +73634,7 @@ function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
|
|
|
73393
73634
|
} catch {}
|
|
73394
73635
|
if (isDir(target)) {
|
|
73395
73636
|
for (const entry of readdir2(target)) {
|
|
73396
|
-
visit(
|
|
73637
|
+
visit(join57(target, entry));
|
|
73397
73638
|
}
|
|
73398
73639
|
}
|
|
73399
73640
|
};
|
|
@@ -73407,14 +73648,14 @@ var EMBEDDED_EXAMPLES = {
|
|
|
73407
73648
|
switchroom: switchroom_default,
|
|
73408
73649
|
minimal: minimal_default
|
|
73409
73650
|
};
|
|
73410
|
-
var DEFAULT_COMPOSE_PATH2 =
|
|
73651
|
+
var DEFAULT_COMPOSE_PATH2 = join58(homedir32(), ".switchroom", "compose", "docker-compose.yml");
|
|
73411
73652
|
var COMPOSE_PROJECT2 = "switchroom";
|
|
73412
73653
|
function resolveVaultBindMountDir(homeDir, ctx) {
|
|
73413
73654
|
const isCustomPath = ctx.migrationKind === "custom-path-skipped";
|
|
73414
73655
|
if (isCustomPath && ctx.customVaultPath) {
|
|
73415
73656
|
return dirname19(ctx.customVaultPath);
|
|
73416
73657
|
}
|
|
73417
|
-
return
|
|
73658
|
+
return join58(homeDir, ".switchroom", "vault");
|
|
73418
73659
|
}
|
|
73419
73660
|
function inspectVaultBindMountDir(vaultDir) {
|
|
73420
73661
|
if (!existsSync65(vaultDir))
|
|
@@ -73443,31 +73684,31 @@ function hasVaultRefs(value) {
|
|
|
73443
73684
|
return false;
|
|
73444
73685
|
}
|
|
73445
73686
|
async function ensureHostMountSources(config) {
|
|
73446
|
-
const home2 =
|
|
73687
|
+
const home2 = homedir32();
|
|
73447
73688
|
const dirs = [
|
|
73448
|
-
|
|
73449
|
-
|
|
73450
|
-
|
|
73451
|
-
|
|
73452
|
-
|
|
73689
|
+
join58(home2, ".switchroom", "approvals"),
|
|
73690
|
+
join58(home2, ".switchroom", "scheduler"),
|
|
73691
|
+
join58(home2, ".switchroom", "logs"),
|
|
73692
|
+
join58(home2, ".switchroom", "compose"),
|
|
73693
|
+
join58(home2, ".switchroom", "broker-operator")
|
|
73453
73694
|
];
|
|
73454
73695
|
for (const name of Object.keys(config.agents)) {
|
|
73455
|
-
dirs.push(
|
|
73456
|
-
dirs.push(
|
|
73457
|
-
dirs.push(
|
|
73696
|
+
dirs.push(join58(home2, ".switchroom", "agents", name));
|
|
73697
|
+
dirs.push(join58(home2, ".switchroom", "logs", name));
|
|
73698
|
+
dirs.push(join58(home2, ".claude", "projects", name));
|
|
73458
73699
|
}
|
|
73459
73700
|
for (const dir of dirs) {
|
|
73460
73701
|
await mkdir(dir, { recursive: true });
|
|
73461
73702
|
}
|
|
73462
|
-
const autoUnlockPath =
|
|
73703
|
+
const autoUnlockPath = join58(home2, ".switchroom", "vault-auto-unlock");
|
|
73463
73704
|
if (!existsSync65(autoUnlockPath)) {
|
|
73464
73705
|
writeFileSync32(autoUnlockPath, "", { mode: 384 });
|
|
73465
73706
|
}
|
|
73466
|
-
const auditLogPath =
|
|
73707
|
+
const auditLogPath = join58(home2, ".switchroom", "vault-audit.log");
|
|
73467
73708
|
if (!existsSync65(auditLogPath)) {
|
|
73468
73709
|
writeFileSync32(auditLogPath, "", { mode: 420 });
|
|
73469
73710
|
}
|
|
73470
|
-
const hostdAuditLogPath =
|
|
73711
|
+
const hostdAuditLogPath = join58(home2, ".switchroom", "host-control-audit.log");
|
|
73471
73712
|
if (!existsSync65(hostdAuditLogPath)) {
|
|
73472
73713
|
writeFileSync32(hostdAuditLogPath, "", { mode: 420 });
|
|
73473
73714
|
}
|
|
@@ -73539,10 +73780,10 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
|
73539
73780
|
`));
|
|
73540
73781
|
}
|
|
73541
73782
|
}
|
|
73542
|
-
function writeInstallTypeCache(homeDir =
|
|
73783
|
+
function writeInstallTypeCache(homeDir = homedir32()) {
|
|
73543
73784
|
const ctx = detectInstallType();
|
|
73544
|
-
const dir =
|
|
73545
|
-
const out =
|
|
73785
|
+
const dir = join58(homeDir, ".switchroom");
|
|
73786
|
+
const out = join58(dir, "install-type.json");
|
|
73546
73787
|
const tmp = `${out}.tmp`;
|
|
73547
73788
|
mkdirSync35(dir, { recursive: true });
|
|
73548
73789
|
const payload = {
|
|
@@ -73591,14 +73832,14 @@ Applying switchroom config...
|
|
|
73591
73832
|
writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
|
|
73592
73833
|
`));
|
|
73593
73834
|
try {
|
|
73594
|
-
installUpdatePromptHook(
|
|
73835
|
+
installUpdatePromptHook(join58(agentsDir, name));
|
|
73595
73836
|
} catch (hookErr) {
|
|
73596
73837
|
writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
|
|
73597
73838
|
`));
|
|
73598
73839
|
}
|
|
73599
73840
|
try {
|
|
73600
73841
|
const uid = allocateAgentUid(name);
|
|
73601
|
-
alignAgentUid(name,
|
|
73842
|
+
alignAgentUid(name, join58(agentsDir, name), uid, {
|
|
73602
73843
|
confirm: !options.nonInteractive,
|
|
73603
73844
|
writeOut
|
|
73604
73845
|
});
|
|
@@ -73635,7 +73876,7 @@ Applying switchroom config...
|
|
|
73635
73876
|
for (const name of agentNames) {
|
|
73636
73877
|
try {
|
|
73637
73878
|
const uid = allocateAgentUid(name);
|
|
73638
|
-
alignAgentUid(name,
|
|
73879
|
+
alignAgentUid(name, join58(agentsDir, name), uid, {
|
|
73639
73880
|
confirm: !options.nonInteractive,
|
|
73640
73881
|
writeOut
|
|
73641
73882
|
});
|
|
@@ -73649,7 +73890,7 @@ Applying switchroom config...
|
|
|
73649
73890
|
}
|
|
73650
73891
|
const vaultPathConfigured = config.vault?.path;
|
|
73651
73892
|
const customVaultPath = vaultPathConfigured ? resolvePath(vaultPathConfigured) : undefined;
|
|
73652
|
-
const migrationResult = migrateVaultLayout(
|
|
73893
|
+
const migrationResult = migrateVaultLayout(homedir32(), {
|
|
73653
73894
|
customVaultPath
|
|
73654
73895
|
});
|
|
73655
73896
|
switch (migrationResult.kind) {
|
|
@@ -73675,7 +73916,7 @@ Applying switchroom config...
|
|
|
73675
73916
|
writeErr(formatDivergentRecoveryMessage(migrationResult.details));
|
|
73676
73917
|
process.exit(4);
|
|
73677
73918
|
}
|
|
73678
|
-
const postMigrationInspect = inspectVaultLayout(
|
|
73919
|
+
const postMigrationInspect = inspectVaultLayout(homedir32());
|
|
73679
73920
|
const acceptable = [
|
|
73680
73921
|
"no-vault",
|
|
73681
73922
|
"already-migrated",
|
|
@@ -73690,7 +73931,7 @@ Applying switchroom config...
|
|
|
73690
73931
|
`));
|
|
73691
73932
|
process.exit(5);
|
|
73692
73933
|
}
|
|
73693
|
-
const vaultDir = resolveVaultBindMountDir(
|
|
73934
|
+
const vaultDir = resolveVaultBindMountDir(homedir32(), {
|
|
73694
73935
|
migrationKind: migrationResult.kind,
|
|
73695
73936
|
customVaultPath
|
|
73696
73937
|
});
|
|
@@ -73720,7 +73961,7 @@ Applying switchroom config...
|
|
|
73720
73961
|
imageTag: composeImageTag,
|
|
73721
73962
|
buildMode: options.buildLocal ? "local" : "pull",
|
|
73722
73963
|
buildContext: options.buildContext,
|
|
73723
|
-
homeDir:
|
|
73964
|
+
homeDir: homedir32(),
|
|
73724
73965
|
switchroomConfigPath,
|
|
73725
73966
|
operatorUid
|
|
73726
73967
|
});
|
|
@@ -73740,7 +73981,7 @@ Wrote `) + composePath + source_default.gray(` (${composeBytes} bytes)
|
|
|
73740
73981
|
writeOut(source_default.gray(` (If pull returns 401, login to ghcr.io first: see docs/operators/install.md#ghcr-auth)
|
|
73741
73982
|
`));
|
|
73742
73983
|
if (process.geteuid?.() === 0 && operatorUid !== undefined) {
|
|
73743
|
-
const restored = restoreOperatorOwnership(
|
|
73984
|
+
const restored = restoreOperatorOwnership(homedir32(), operatorUid);
|
|
73744
73985
|
if (restored.length > 0) {
|
|
73745
73986
|
writeOut(source_default.gray(` Restored operator ownership of ${restored.length} ~/.switchroom path(s)
|
|
73746
73987
|
`));
|
|
@@ -73810,7 +74051,7 @@ function findUnwritableAgentDirs(config, opts) {
|
|
|
73810
74051
|
const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
|
|
73811
74052
|
const unwritable = [];
|
|
73812
74053
|
for (const name of targets) {
|
|
73813
|
-
const startSh =
|
|
74054
|
+
const startSh = join58(agentsDir, name, "start.sh");
|
|
73814
74055
|
if (!existsSync65(startSh))
|
|
73815
74056
|
continue;
|
|
73816
74057
|
try {
|
|
@@ -73990,8 +74231,8 @@ function runRedactStdin() {
|
|
|
73990
74231
|
|
|
73991
74232
|
// src/cli/status-ask.ts
|
|
73992
74233
|
import { readFileSync as readFileSync54, existsSync as existsSync66, readdirSync as readdirSync24 } from "node:fs";
|
|
73993
|
-
import { join as
|
|
73994
|
-
import { homedir as
|
|
74234
|
+
import { join as join59 } from "node:path";
|
|
74235
|
+
import { homedir as homedir33 } from "node:os";
|
|
73995
74236
|
|
|
73996
74237
|
// src/status-ask/report.ts
|
|
73997
74238
|
function parseJsonl(content) {
|
|
@@ -74326,7 +74567,7 @@ function resolveSources(explicitPath) {
|
|
|
74326
74567
|
const config = loadConfig();
|
|
74327
74568
|
agentsDir = resolveAgentsDir(config);
|
|
74328
74569
|
} catch {
|
|
74329
|
-
agentsDir =
|
|
74570
|
+
agentsDir = join59(homedir33(), ".switchroom", "agents");
|
|
74330
74571
|
}
|
|
74331
74572
|
if (!existsSync66(agentsDir))
|
|
74332
74573
|
return [];
|
|
@@ -74338,7 +74579,7 @@ function resolveSources(explicitPath) {
|
|
|
74338
74579
|
return [];
|
|
74339
74580
|
}
|
|
74340
74581
|
for (const name of entries) {
|
|
74341
|
-
const path8 =
|
|
74582
|
+
const path8 = join59(agentsDir, name, "runtime-metrics.jsonl");
|
|
74342
74583
|
if (existsSync66(path8)) {
|
|
74343
74584
|
sources.push({ path: path8, agent: name });
|
|
74344
74585
|
}
|
|
@@ -74360,17 +74601,17 @@ function inferAgentFromPath(p) {
|
|
|
74360
74601
|
|
|
74361
74602
|
// src/cli/agent-config.ts
|
|
74362
74603
|
init_helpers();
|
|
74363
|
-
import { join as
|
|
74364
|
-
import { homedir as
|
|
74604
|
+
import { join as join60 } from "node:path";
|
|
74605
|
+
import { homedir as homedir34 } from "node:os";
|
|
74365
74606
|
import {
|
|
74366
74607
|
existsSync as existsSync67,
|
|
74367
74608
|
mkdirSync as mkdirSync36,
|
|
74368
74609
|
appendFileSync as appendFileSync3,
|
|
74369
74610
|
readFileSync as readFileSync55
|
|
74370
74611
|
} from "node:fs";
|
|
74371
|
-
var AUDIT_ROOT =
|
|
74612
|
+
var AUDIT_ROOT = join60(homedir34(), ".switchroom", "audit");
|
|
74372
74613
|
function auditPathFor(agent) {
|
|
74373
|
-
return
|
|
74614
|
+
return join60(AUDIT_ROOT, agent, "agent-config.jsonl");
|
|
74374
74615
|
}
|
|
74375
74616
|
function appendAudit(agent, cmd, args, exit, opts = {}) {
|
|
74376
74617
|
const row = {
|
|
@@ -74628,21 +74869,21 @@ import {
|
|
|
74628
74869
|
unlinkSync as unlinkSync13,
|
|
74629
74870
|
writeSync as writeSync7
|
|
74630
74871
|
} from "node:fs";
|
|
74631
|
-
import { join as
|
|
74872
|
+
import { join as join61, resolve as resolve42 } from "node:path";
|
|
74632
74873
|
var STAGING_SUBDIR = ".staging";
|
|
74633
74874
|
function overlayPathsFor(agent, opts = {}) {
|
|
74634
74875
|
const base = opts.root ? resolve42(opts.root, agent) : resolve42(resolveDualPath(`~/.switchroom/agents/${agent}`));
|
|
74635
|
-
const scheduleDir =
|
|
74636
|
-
const scheduleStagingDir =
|
|
74637
|
-
const skillsDir =
|
|
74638
|
-
const skillsStagingDir =
|
|
74876
|
+
const scheduleDir = join61(base, "schedule.d");
|
|
74877
|
+
const scheduleStagingDir = join61(scheduleDir, STAGING_SUBDIR);
|
|
74878
|
+
const skillsDir = join61(base, "skills.d");
|
|
74879
|
+
const skillsStagingDir = join61(skillsDir, STAGING_SUBDIR);
|
|
74639
74880
|
return {
|
|
74640
74881
|
agentRoot: base,
|
|
74641
74882
|
scheduleDir,
|
|
74642
74883
|
scheduleStagingDir,
|
|
74643
74884
|
skillsDir,
|
|
74644
74885
|
skillsStagingDir,
|
|
74645
|
-
lockPath:
|
|
74886
|
+
lockPath: join61(base, ".lock"),
|
|
74646
74887
|
stagingDir: scheduleStagingDir
|
|
74647
74888
|
};
|
|
74648
74889
|
}
|
|
@@ -74696,8 +74937,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
74696
74937
|
const paths = overlayPathsFor(agent, opts);
|
|
74697
74938
|
return withAgentLock(paths, () => {
|
|
74698
74939
|
ensureDirs(paths);
|
|
74699
|
-
const stagingPath =
|
|
74700
|
-
const finalPath =
|
|
74940
|
+
const stagingPath = join61(paths.scheduleStagingDir, `${slug}.yaml`);
|
|
74941
|
+
const finalPath = join61(paths.scheduleDir, `${slug}.yaml`);
|
|
74701
74942
|
const fd = openSync12(stagingPath, "w", 384);
|
|
74702
74943
|
try {
|
|
74703
74944
|
writeSync7(fd, yamlText);
|
|
@@ -74713,8 +74954,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
74713
74954
|
const paths = overlayPathsFor(agent, opts);
|
|
74714
74955
|
return withAgentLock(paths, () => {
|
|
74715
74956
|
ensureSkillsDirs(paths);
|
|
74716
|
-
const stagingPath =
|
|
74717
|
-
const finalPath =
|
|
74957
|
+
const stagingPath = join61(paths.skillsStagingDir, `${slug}.yaml`);
|
|
74958
|
+
const finalPath = join61(paths.skillsDir, `${slug}.yaml`);
|
|
74718
74959
|
const fd = openSync12(stagingPath, "w", 384);
|
|
74719
74960
|
try {
|
|
74720
74961
|
writeSync7(fd, yamlText);
|
|
@@ -74729,7 +74970,7 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
74729
74970
|
function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
74730
74971
|
const paths = overlayPathsFor(agent, opts);
|
|
74731
74972
|
return withAgentLock(paths, () => {
|
|
74732
|
-
const finalPath =
|
|
74973
|
+
const finalPath = join61(paths.skillsDir, `${slug}.yaml`);
|
|
74733
74974
|
if (!existsSync68(finalPath))
|
|
74734
74975
|
return false;
|
|
74735
74976
|
unlinkSync13(finalPath);
|
|
@@ -74744,7 +74985,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
74744
74985
|
for (const name of readdirSync25(paths.skillsDir)) {
|
|
74745
74986
|
if (!/\.ya?ml$/i.test(name))
|
|
74746
74987
|
continue;
|
|
74747
|
-
const full =
|
|
74988
|
+
const full = join61(paths.skillsDir, name);
|
|
74748
74989
|
try {
|
|
74749
74990
|
const raw = readFileSync56(full, "utf-8");
|
|
74750
74991
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
@@ -74756,7 +74997,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
74756
74997
|
function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
74757
74998
|
const paths = overlayPathsFor(agent, opts);
|
|
74758
74999
|
return withAgentLock(paths, () => {
|
|
74759
|
-
const finalPath =
|
|
75000
|
+
const finalPath = join61(paths.scheduleDir, `${slug}.yaml`);
|
|
74760
75001
|
if (!existsSync68(finalPath))
|
|
74761
75002
|
return false;
|
|
74762
75003
|
unlinkSync13(finalPath);
|
|
@@ -74771,7 +75012,7 @@ function listOverlayEntries(agent, opts = {}) {
|
|
|
74771
75012
|
for (const name of readdirSync25(paths.scheduleDir)) {
|
|
74772
75013
|
if (!/\.ya?ml$/i.test(name))
|
|
74773
75014
|
continue;
|
|
74774
|
-
const full =
|
|
75015
|
+
const full = join61(paths.scheduleDir, name);
|
|
74775
75016
|
try {
|
|
74776
75017
|
const raw = readFileSync56(full, "utf-8");
|
|
74777
75018
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
@@ -74927,12 +75168,12 @@ import {
|
|
|
74927
75168
|
writeFileSync as writeFileSync33,
|
|
74928
75169
|
writeSync as writeSync8
|
|
74929
75170
|
} from "node:fs";
|
|
74930
|
-
import { join as
|
|
75171
|
+
import { join as join62 } from "node:path";
|
|
74931
75172
|
import { randomBytes as randomBytes12 } from "node:crypto";
|
|
74932
75173
|
var STAGE_ID_PREFIX = "cap_";
|
|
74933
75174
|
function pendingDir(agent, opts = {}) {
|
|
74934
75175
|
const paths = overlayPathsFor(agent, opts);
|
|
74935
|
-
return
|
|
75176
|
+
return join62(paths.scheduleDir, ".pending");
|
|
74936
75177
|
}
|
|
74937
75178
|
function ensurePendingDir(agent, opts = {}) {
|
|
74938
75179
|
const dir = pendingDir(agent, opts);
|
|
@@ -74945,8 +75186,8 @@ function newStageId() {
|
|
|
74945
75186
|
function stagePendingScheduleEntry(opts) {
|
|
74946
75187
|
const dir = ensurePendingDir(opts.agent, { root: opts.root });
|
|
74947
75188
|
const stageId = opts.stageId ?? newStageId();
|
|
74948
|
-
const yamlPath =
|
|
74949
|
-
const metaPath =
|
|
75189
|
+
const yamlPath = join62(dir, `${stageId}.yaml`);
|
|
75190
|
+
const metaPath = join62(dir, `${stageId}.meta.json`);
|
|
74950
75191
|
const meta = {
|
|
74951
75192
|
v: 1,
|
|
74952
75193
|
stage_id: stageId,
|
|
@@ -74980,8 +75221,8 @@ function listPendingScheduleEntries(agent, opts = {}) {
|
|
|
74980
75221
|
if (!name.endsWith(".meta.json"))
|
|
74981
75222
|
continue;
|
|
74982
75223
|
const stageId = name.slice(0, -".meta.json".length);
|
|
74983
|
-
const metaPath =
|
|
74984
|
-
const yamlPath =
|
|
75224
|
+
const metaPath = join62(dir, name);
|
|
75225
|
+
const yamlPath = join62(dir, `${stageId}.yaml`);
|
|
74985
75226
|
if (!existsSync69(yamlPath))
|
|
74986
75227
|
continue;
|
|
74987
75228
|
try {
|
|
@@ -75000,7 +75241,7 @@ function commitPendingScheduleEntry(opts) {
|
|
|
75000
75241
|
return { committed: false, reason: "not_found" };
|
|
75001
75242
|
const slug = match.meta.entry.name ?? match.stageId;
|
|
75002
75243
|
const paths = overlayPathsFor(opts.agent, { root: opts.root });
|
|
75003
|
-
const finalPath =
|
|
75244
|
+
const finalPath = join62(paths.scheduleDir, `${slug}.yaml`);
|
|
75004
75245
|
if (existsSync69(finalPath)) {
|
|
75005
75246
|
return { committed: false, reason: "slug_collision" };
|
|
75006
75247
|
}
|
|
@@ -75453,7 +75694,7 @@ var import_yaml15 = __toESM(require_dist(), 1);
|
|
|
75453
75694
|
import { existsSync as existsSync71 } from "node:fs";
|
|
75454
75695
|
init_reconcile_default_skills();
|
|
75455
75696
|
var import_yaml16 = __toESM(require_dist(), 1);
|
|
75456
|
-
import { join as
|
|
75697
|
+
import { join as join63 } from "node:path";
|
|
75457
75698
|
var MAX_SKILLS_PER_AGENT = 20;
|
|
75458
75699
|
var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
|
|
75459
75700
|
function exitCodeFor2(code) {
|
|
@@ -75528,7 +75769,7 @@ function skillInstall(opts) {
|
|
|
75528
75769
|
return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
|
|
75529
75770
|
}
|
|
75530
75771
|
const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
|
|
75531
|
-
const skillPath =
|
|
75772
|
+
const skillPath = join63(poolDir, skillName);
|
|
75532
75773
|
if (!existsSync71(skillPath)) {
|
|
75533
75774
|
return err("E_SKILL_NOT_FOUND", `bundled skill not found at ${skillPath}. The operator needs to ` + `place the skill at this path before the agent can opt in.`);
|
|
75534
75775
|
}
|
|
@@ -75721,14 +75962,14 @@ import {
|
|
|
75721
75962
|
unlinkSync as unlinkSync15
|
|
75722
75963
|
} from "node:fs";
|
|
75723
75964
|
import { createHash as createHash12 } from "node:crypto";
|
|
75724
|
-
import { join as
|
|
75965
|
+
import { join as join64 } from "node:path";
|
|
75725
75966
|
function planCronUnitRenames(agentsDir, agents) {
|
|
75726
75967
|
const plans = [];
|
|
75727
75968
|
for (const [agentName, agentConfig] of Object.entries(agents)) {
|
|
75728
75969
|
const schedule = agentConfig.schedule ?? [];
|
|
75729
75970
|
if (schedule.length === 0)
|
|
75730
75971
|
continue;
|
|
75731
|
-
const telegramDir =
|
|
75972
|
+
const telegramDir = join64(agentsDir, agentName, "telegram");
|
|
75732
75973
|
if (!existsSync73(telegramDir))
|
|
75733
75974
|
continue;
|
|
75734
75975
|
let entries;
|
|
@@ -75750,8 +75991,8 @@ function planCronUnitRenames(agentsDir, agents) {
|
|
|
75750
75991
|
continue;
|
|
75751
75992
|
plans.push({
|
|
75752
75993
|
agent: agentName,
|
|
75753
|
-
from:
|
|
75754
|
-
to:
|
|
75994
|
+
from: join64(telegramDir, file),
|
|
75995
|
+
to: join64(telegramDir, canonical),
|
|
75755
75996
|
scheduleIdx: idx,
|
|
75756
75997
|
entry
|
|
75757
75998
|
});
|
|
@@ -75889,8 +76130,8 @@ function registerMigrateCommand(program3) {
|
|
|
75889
76130
|
init_source();
|
|
75890
76131
|
init_helpers();
|
|
75891
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";
|
|
75892
|
-
import { homedir as
|
|
75893
|
-
import { join as
|
|
76133
|
+
import { homedir as homedir35 } from "node:os";
|
|
76134
|
+
import { join as join65 } from "node:path";
|
|
75894
76135
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
75895
76136
|
init_audit_reader();
|
|
75896
76137
|
var DEFAULT_IMAGE_TAG = "latest";
|
|
@@ -75981,10 +76222,10 @@ networks:
|
|
|
75981
76222
|
`;
|
|
75982
76223
|
}
|
|
75983
76224
|
function hostdDir() {
|
|
75984
|
-
return
|
|
76225
|
+
return join65(homedir35(), ".switchroom", "hostd");
|
|
75985
76226
|
}
|
|
75986
76227
|
function hostdComposePath() {
|
|
75987
|
-
return
|
|
76228
|
+
return join65(hostdDir(), "docker-compose.yml");
|
|
75988
76229
|
}
|
|
75989
76230
|
function backupExistingCompose() {
|
|
75990
76231
|
const p = hostdComposePath();
|
|
@@ -76023,7 +76264,7 @@ async function doInstall(opts, program3) {
|
|
|
76023
76264
|
const composePath = hostdComposePath();
|
|
76024
76265
|
mkdirSync39(dir, { recursive: true });
|
|
76025
76266
|
const yaml = renderHostdComposeFile({
|
|
76026
|
-
hostHome:
|
|
76267
|
+
hostHome: homedir35(),
|
|
76027
76268
|
imageTag: opts.tag ?? DEFAULT_IMAGE_TAG,
|
|
76028
76269
|
operatorUid: resolveOperatorUid()
|
|
76029
76270
|
});
|
|
@@ -76092,7 +76333,7 @@ function doStatus() {
|
|
|
76092
76333
|
for (const name of readdirSync28(dir)) {
|
|
76093
76334
|
if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
|
|
76094
76335
|
continue;
|
|
76095
|
-
const sockPath =
|
|
76336
|
+
const sockPath = join65(dir, name, "sock");
|
|
76096
76337
|
if (existsSync74(sockPath)) {
|
|
76097
76338
|
const st = statSync28(sockPath);
|
|
76098
76339
|
if ((st.mode & 61440) === 49152) {
|