switchroom 0.19.30 → 0.19.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/switchroom.js +1121 -584
- package/dist/host-control/main.js +151 -73
- package/package.json +1 -1
- package/profiles/_base/cron-session.sh.hbs +5 -1
- package/profiles/_base/start.sh.hbs +15 -1
- package/telegram-plugin/dist/gateway/gateway.js +1435 -551
- package/telegram-plugin/edit-flood-fuse.ts +70 -20
- package/telegram-plugin/gateway/boot-beacon.ts +364 -0
- package/telegram-plugin/gateway/boot-sweep-gate.ts +20 -15
- package/telegram-plugin/gateway/gateway.ts +87 -88
- package/telegram-plugin/gateway/inbound-spool.ts +39 -0
- package/telegram-plugin/gateway/narrative-lane.ts +12 -0
- package/telegram-plugin/gateway/obligation-store.ts +28 -0
- package/telegram-plugin/gateway/stale-pin-sweep-store.ts +221 -0
- package/telegram-plugin/gateway/stale-pin-sweep-wiring.ts +211 -0
- package/telegram-plugin/gateway/stale-pin-sweep.test.ts +804 -0
- package/telegram-plugin/gateway/stale-pin-sweep.ts +1146 -0
- package/telegram-plugin/gateway/status-pin-retarget.ts +15 -2
- package/telegram-plugin/gateway/status-pin-store.ts +33 -11
- package/telegram-plugin/registry/turns-schema.ts +21 -1
- package/telegram-plugin/retry-api-call.ts +46 -21
- package/telegram-plugin/shared/bot-runtime.ts +61 -17
- package/telegram-plugin/shared/gw-trace-gate.ts +18 -2
- package/telegram-plugin/tests/activity-card-wiring.test.ts +7 -7
- package/telegram-plugin/tests/activity-drain-fuse-drop-not-failure.test.ts +324 -0
- package/telegram-plugin/tests/agent-card-result-footer.test.ts +193 -0
- package/telegram-plugin/tests/boot-beacon.test.ts +462 -0
- package/telegram-plugin/tests/boot-pin-sweep-wiring.test.ts +6 -6
- package/telegram-plugin/tests/boot-sweep-gate.test.ts +42 -31
- package/telegram-plugin/tests/inbound-delivery-machine-dispatch.test.ts +2 -0
- package/telegram-plugin/tests/inbound-spool-progress.test.ts +2 -0
- package/telegram-plugin/tests/inbound-spool.test.ts +134 -5
- package/telegram-plugin/tests/narrative-lane-golden.test.ts +28 -0
- package/telegram-plugin/tests/obligation-determinism.test.ts +2 -0
- package/telegram-plugin/tests/obligation-store.test.ts +67 -1
- package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +3 -3
- package/telegram-plugin/tests/status-pin-store.test.ts +26 -5
- package/telegram-plugin/tests/tg-post-logger-error-shape.test.ts +161 -0
- package/telegram-plugin/tests/worker-feed-pin-persistence.test.ts +30 -0
- package/telegram-plugin/tool-activity-summary.ts +104 -38
- package/telegram-plugin/worker-activity-feed.ts +33 -16
- package/telegram-plugin/gateway/dm-pin-sweep.test.ts +0 -251
- package/telegram-plugin/gateway/dm-pin-sweep.ts +0 -178
package/dist/cli/switchroom.js
CHANGED
|
@@ -2120,7 +2120,7 @@ var init_esm = __esm(() => {
|
|
|
2120
2120
|
});
|
|
2121
2121
|
|
|
2122
2122
|
// src/build-info.ts
|
|
2123
|
-
var VERSION = "0.19.
|
|
2123
|
+
var VERSION = "0.19.31", COMMIT_SHA = "d24ee3cc";
|
|
2124
2124
|
|
|
2125
2125
|
// src/cli/resolve-version.ts
|
|
2126
2126
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -16154,6 +16154,7 @@ var init_hindsight_context_budget = __esm(() => {
|
|
|
16154
16154
|
var exports_atomic = {};
|
|
16155
16155
|
__export(exports_atomic, {
|
|
16156
16156
|
writeConfigFileSync: () => writeConfigFileSync,
|
|
16157
|
+
fsyncPathSync: () => fsyncPathSync,
|
|
16157
16158
|
atomicWriteJsonSync: () => atomicWriteJsonSync,
|
|
16158
16159
|
atomicWriteFileSync: () => atomicWriteFileSync
|
|
16159
16160
|
});
|
|
@@ -16195,6 +16196,14 @@ function atomicWriteFileSync(destPath, contents, modeOrOpts = 384) {
|
|
|
16195
16196
|
throw err;
|
|
16196
16197
|
}
|
|
16197
16198
|
}
|
|
16199
|
+
function fsyncPathSync(path) {
|
|
16200
|
+
const fd = openSync2(path, constants.O_RDONLY);
|
|
16201
|
+
try {
|
|
16202
|
+
fsyncSync(fd);
|
|
16203
|
+
} finally {
|
|
16204
|
+
closeSync2(fd);
|
|
16205
|
+
}
|
|
16206
|
+
}
|
|
16198
16207
|
function atomicWriteJsonSync(destPath, value, mode = 384) {
|
|
16199
16208
|
atomicWriteFileSync(destPath, JSON.stringify(value, null, 2) + `
|
|
16200
16209
|
`, mode);
|
|
@@ -32499,8 +32508,41 @@ import {
|
|
|
32499
32508
|
realpathSync as realpathSync3,
|
|
32500
32509
|
statSync as statSync9
|
|
32501
32510
|
} from "node:fs";
|
|
32511
|
+
import { homedir as homedir7 } from "node:os";
|
|
32502
32512
|
import { join as join15 } from "node:path";
|
|
32503
|
-
function
|
|
32513
|
+
function operatorHomeCandidates(env2, home2) {
|
|
32514
|
+
const roots = [env2.SWITCHROOM_HOST_HOME?.trim(), home2, CONTAINER_OPERATOR_HOME];
|
|
32515
|
+
const out = [];
|
|
32516
|
+
for (const r of roots) {
|
|
32517
|
+
if (!r || r.length === 0)
|
|
32518
|
+
continue;
|
|
32519
|
+
const dir = join15(r, ".switchroom");
|
|
32520
|
+
if (!out.includes(dir))
|
|
32521
|
+
out.push(dir);
|
|
32522
|
+
}
|
|
32523
|
+
return out;
|
|
32524
|
+
}
|
|
32525
|
+
function resolveOperatorUidFromOwnership(deps = {}) {
|
|
32526
|
+
const env2 = deps.env ?? process.env;
|
|
32527
|
+
const home2 = deps.home ?? homedir7();
|
|
32528
|
+
const exists = deps.exists ?? ((p) => existsSync20(p));
|
|
32529
|
+
const statUid = deps.statUid ?? ((p) => {
|
|
32530
|
+
try {
|
|
32531
|
+
return statSync9(p).uid;
|
|
32532
|
+
} catch {
|
|
32533
|
+
return;
|
|
32534
|
+
}
|
|
32535
|
+
});
|
|
32536
|
+
for (const dir of operatorHomeCandidates(env2, home2)) {
|
|
32537
|
+
if (!exists(join15(dir, SWITCHROOM_HOME_MARKER)))
|
|
32538
|
+
continue;
|
|
32539
|
+
const uid = statUid(dir);
|
|
32540
|
+
if (uid !== undefined && Number.isFinite(uid) && uid > 0)
|
|
32541
|
+
return uid;
|
|
32542
|
+
}
|
|
32543
|
+
return;
|
|
32544
|
+
}
|
|
32545
|
+
function resolveOperatorUid(ownershipDeps = {}) {
|
|
32504
32546
|
const sudoUid = process.env.SUDO_UID;
|
|
32505
32547
|
if (sudoUid !== undefined) {
|
|
32506
32548
|
const parsed = parseInt(sudoUid, 10);
|
|
@@ -32518,7 +32560,7 @@ function resolveOperatorUid() {
|
|
|
32518
32560
|
if (Number.isFinite(parsed) && parsed > 0)
|
|
32519
32561
|
return parsed;
|
|
32520
32562
|
}
|
|
32521
|
-
return;
|
|
32563
|
+
return resolveOperatorUidFromOwnership(ownershipDeps);
|
|
32522
32564
|
}
|
|
32523
32565
|
function operatorOwnedPaths(home2) {
|
|
32524
32566
|
const root = join15(home2, ".switchroom");
|
|
@@ -32597,12 +32639,13 @@ function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
|
|
|
32597
32639
|
}
|
|
32598
32640
|
return chowned;
|
|
32599
32641
|
}
|
|
32642
|
+
var CONTAINER_OPERATOR_HOME = "/host-home", SWITCHROOM_HOME_MARKER = "switchroom.yaml";
|
|
32600
32643
|
var init_operator_uid = () => {};
|
|
32601
32644
|
|
|
32602
32645
|
// src/cli/write-compose.ts
|
|
32603
32646
|
import { chownSync as chownSync2 } from "node:fs";
|
|
32604
32647
|
import { mkdir, readFile, writeFile, rename, copyFile } from "node:fs/promises";
|
|
32605
|
-
import { homedir as
|
|
32648
|
+
import { homedir as homedir8 } from "node:os";
|
|
32606
32649
|
import { basename as basename4, dirname as dirname9, join as join16 } from "node:path";
|
|
32607
32650
|
function agentHadLiteLLMRouting(composeContent, agentName) {
|
|
32608
32651
|
const lines = composeContent.split(`
|
|
@@ -32713,7 +32756,7 @@ function resolveHostHomeForCompose() {
|
|
|
32713
32756
|
|
|
32714
32757
|
` + `Recovery: run \`switchroom apply\`/\`update\` from the HOST shell, or via hostd (both set SWITCHROOM_HOST_HOME). Do NOT deploy via an ad-hoc ` + `\`docker run <agent-image> switchroom \u2026\` that mounts the operator home.`);
|
|
32715
32758
|
}
|
|
32716
|
-
return
|
|
32759
|
+
return homedir8();
|
|
32717
32760
|
}
|
|
32718
32761
|
async function computeComposeContent(opts) {
|
|
32719
32762
|
const release = resolveRelease({ override: opts.releaseOverride, root: opts.config.release });
|
|
@@ -32734,7 +32777,7 @@ async function computeComposeContent(opts) {
|
|
|
32734
32777
|
buildMode: opts.buildMode ?? "pull",
|
|
32735
32778
|
buildContext: opts.buildContext,
|
|
32736
32779
|
homeDir: resolveHostHomeForCompose(),
|
|
32737
|
-
probeHomeDir:
|
|
32780
|
+
probeHomeDir: homedir8(),
|
|
32738
32781
|
switchroomConfigPath: resolvedConfigPath,
|
|
32739
32782
|
operatorUid
|
|
32740
32783
|
});
|
|
@@ -32852,7 +32895,7 @@ var init_compose_env = () => {};
|
|
|
32852
32895
|
// src/agents/docker-fleet.ts
|
|
32853
32896
|
import { resolve as resolve14 } from "node:path";
|
|
32854
32897
|
import { mkdirSync as mkdirSync14, writeFileSync as writeFileSync7 } from "node:fs";
|
|
32855
|
-
import { homedir as
|
|
32898
|
+
import { homedir as homedir9 } from "node:os";
|
|
32856
32899
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
32857
32900
|
function resolveSwitchroomHome(explicit) {
|
|
32858
32901
|
if (explicit && explicit.length > 0)
|
|
@@ -32876,7 +32919,7 @@ function bringUpAgentService(opts) {
|
|
|
32876
32919
|
}
|
|
32877
32920
|
const compose = opts.generateComposeContent?.() ?? generateCompose({
|
|
32878
32921
|
config: opts.config,
|
|
32879
|
-
homeDir:
|
|
32922
|
+
homeDir: homedir9(),
|
|
32880
32923
|
switchroomConfigPath
|
|
32881
32924
|
});
|
|
32882
32925
|
const composePath = resolve14(composeDir, "docker-compose.yml");
|
|
@@ -35074,7 +35117,7 @@ __export(exports_client2, {
|
|
|
35074
35117
|
AuthBrokerClient: () => AuthBrokerClient
|
|
35075
35118
|
});
|
|
35076
35119
|
import * as net2 from "node:net";
|
|
35077
|
-
import { homedir as
|
|
35120
|
+
import { homedir as homedir11 } from "node:os";
|
|
35078
35121
|
import { randomUUID } from "node:crypto";
|
|
35079
35122
|
import { join as join25 } from "node:path";
|
|
35080
35123
|
function reviveDate(v) {
|
|
@@ -35085,7 +35128,7 @@ function reviveDate(v) {
|
|
|
35085
35128
|
const d = new Date(v);
|
|
35086
35129
|
return Number.isNaN(d.getTime()) ? null : d;
|
|
35087
35130
|
}
|
|
35088
|
-
function operatorSocketPath(home2 =
|
|
35131
|
+
function operatorSocketPath(home2 = homedir11()) {
|
|
35089
35132
|
return join25(home2, ".switchroom", "state", "auth-broker-operator", "sock");
|
|
35090
35133
|
}
|
|
35091
35134
|
function resolveAuthBrokerSocketPath(opts) {
|
|
@@ -35466,7 +35509,7 @@ import {
|
|
|
35466
35509
|
rmSync as rmSync11,
|
|
35467
35510
|
statSync as statSync19
|
|
35468
35511
|
} from "node:fs";
|
|
35469
|
-
import { homedir as
|
|
35512
|
+
import { homedir as homedir13 } from "node:os";
|
|
35470
35513
|
import { join as join27, resolve as resolve24 } from "node:path";
|
|
35471
35514
|
function accountsRootOverride() {
|
|
35472
35515
|
const v = process.env.SWITCHROOM_ACCOUNTS_DIR;
|
|
@@ -35474,19 +35517,19 @@ function accountsRootOverride() {
|
|
|
35474
35517
|
return v;
|
|
35475
35518
|
return;
|
|
35476
35519
|
}
|
|
35477
|
-
function accountsRoot(home2 =
|
|
35520
|
+
function accountsRoot(home2 = homedir13()) {
|
|
35478
35521
|
return accountsRootOverride() ?? resolve24(home2, ".switchroom", "accounts");
|
|
35479
35522
|
}
|
|
35480
|
-
function accountDir(label, home2 =
|
|
35523
|
+
function accountDir(label, home2 = homedir13()) {
|
|
35481
35524
|
return join27(accountsRoot(home2), label);
|
|
35482
35525
|
}
|
|
35483
|
-
function accountCredentialsPath(label, home2 =
|
|
35526
|
+
function accountCredentialsPath(label, home2 = homedir13()) {
|
|
35484
35527
|
return join27(accountDir(label, home2), "credentials.json");
|
|
35485
35528
|
}
|
|
35486
|
-
function accountMetaPath(label, home2 =
|
|
35529
|
+
function accountMetaPath(label, home2 = homedir13()) {
|
|
35487
35530
|
return join27(accountDir(label, home2), "meta.json");
|
|
35488
35531
|
}
|
|
35489
|
-
function listAccounts(home2 =
|
|
35532
|
+
function listAccounts(home2 = homedir13()) {
|
|
35490
35533
|
const root = accountsRoot(home2);
|
|
35491
35534
|
if (!existsSync34(root))
|
|
35492
35535
|
return [];
|
|
@@ -35502,7 +35545,7 @@ function listAccounts(home2 = homedir12()) {
|
|
|
35502
35545
|
return [];
|
|
35503
35546
|
}
|
|
35504
35547
|
}
|
|
35505
|
-
function readAccountCredentials(label, home2 =
|
|
35548
|
+
function readAccountCredentials(label, home2 = homedir13()) {
|
|
35506
35549
|
const p = accountCredentialsPath(label, home2);
|
|
35507
35550
|
if (!existsSync34(p))
|
|
35508
35551
|
return null;
|
|
@@ -35512,7 +35555,7 @@ function readAccountCredentials(label, home2 = homedir12()) {
|
|
|
35512
35555
|
return null;
|
|
35513
35556
|
}
|
|
35514
35557
|
}
|
|
35515
|
-
function readAccountMeta(label, home2 =
|
|
35558
|
+
function readAccountMeta(label, home2 = homedir13()) {
|
|
35516
35559
|
const p = accountMetaPath(label, home2);
|
|
35517
35560
|
if (!existsSync34(p))
|
|
35518
35561
|
return null;
|
|
@@ -35522,7 +35565,7 @@ function readAccountMeta(label, home2 = homedir12()) {
|
|
|
35522
35565
|
return null;
|
|
35523
35566
|
}
|
|
35524
35567
|
}
|
|
35525
|
-
function accountHealth(label, now = Date.now(), home2 =
|
|
35568
|
+
function accountHealth(label, now = Date.now(), home2 = homedir13()) {
|
|
35526
35569
|
const creds = readAccountCredentials(label, home2);
|
|
35527
35570
|
if (!creds?.claudeAiOauth?.accessToken)
|
|
35528
35571
|
return "missing-credentials";
|
|
@@ -35538,7 +35581,7 @@ function accountHealth(label, now = Date.now(), home2 = homedir12()) {
|
|
|
35538
35581
|
}
|
|
35539
35582
|
return "healthy";
|
|
35540
35583
|
}
|
|
35541
|
-
function getAccountInfos(now = Date.now(), home2 =
|
|
35584
|
+
function getAccountInfos(now = Date.now(), home2 = homedir13()) {
|
|
35542
35585
|
return listAccounts(home2).map((label) => {
|
|
35543
35586
|
const creds = readAccountCredentials(label, home2);
|
|
35544
35587
|
const meta = readAccountMeta(label, home2);
|
|
@@ -36182,12 +36225,12 @@ var init_disconnect = __esm(() => {
|
|
|
36182
36225
|
|
|
36183
36226
|
// src/vault/approvals/client.ts
|
|
36184
36227
|
import { existsSync as existsSync35 } from "node:fs";
|
|
36185
|
-
import { homedir as
|
|
36228
|
+
import { homedir as homedir14 } from "node:os";
|
|
36186
36229
|
import { join as join28 } from "node:path";
|
|
36187
|
-
function kernelOperatorSocketPath(home2 =
|
|
36230
|
+
function kernelOperatorSocketPath(home2 = homedir14()) {
|
|
36188
36231
|
return join28(home2, ".switchroom", "state", "kernel-operator", "sock");
|
|
36189
36232
|
}
|
|
36190
|
-
function resolveKernelOperatorSocket(home2 =
|
|
36233
|
+
function resolveKernelOperatorSocket(home2 = homedir14()) {
|
|
36191
36234
|
const p = kernelOperatorSocketPath(home2);
|
|
36192
36235
|
return existsSync35(p) ? p : null;
|
|
36193
36236
|
}
|
|
@@ -37999,7 +38042,7 @@ var init_audit_hashchain = () => {};
|
|
|
37999
38042
|
|
|
38000
38043
|
// src/vault/broker/test-isolation-guard.ts
|
|
38001
38044
|
import { mkdtempSync as mkdtempSync4 } from "node:fs";
|
|
38002
|
-
import { homedir as
|
|
38045
|
+
import { homedir as homedir16, tmpdir as tmpdir3 } from "node:os";
|
|
38003
38046
|
import { join as join34, resolve as resolve28, sep } from "node:path";
|
|
38004
38047
|
function isTestRuntime() {
|
|
38005
38048
|
return process.env.VITEST !== undefined;
|
|
@@ -38008,7 +38051,7 @@ function escapeHatchSet() {
|
|
|
38008
38051
|
return process.env.SWITCHROOM_ALLOW_PROD_VAULT_IN_TEST === "1";
|
|
38009
38052
|
}
|
|
38010
38053
|
function realSwitchroomHome() {
|
|
38011
|
-
return resolve28(
|
|
38054
|
+
return resolve28(homedir16(), ".switchroom");
|
|
38012
38055
|
}
|
|
38013
38056
|
function isUnderRealSwitchroomHome(p) {
|
|
38014
38057
|
const home2 = realSwitchroomHome();
|
|
@@ -39190,9 +39233,9 @@ var init_audit_rotation_config = __esm(() => {
|
|
|
39190
39233
|
|
|
39191
39234
|
// src/host-control/audit-reader.ts
|
|
39192
39235
|
import { closeSync as closeSync12, existsSync as existsSync51, openSync as openSync12, readSync as readSync3, statSync as statSync33 } from "node:fs";
|
|
39193
|
-
import { homedir as
|
|
39236
|
+
import { homedir as homedir23 } from "node:os";
|
|
39194
39237
|
import { join as join47 } from "node:path";
|
|
39195
|
-
function defaultAuditLogPath2(home2 =
|
|
39238
|
+
function defaultAuditLogPath2(home2 = homedir23()) {
|
|
39196
39239
|
return join47(home2, ".switchroom", "host-control-audit.log");
|
|
39197
39240
|
}
|
|
39198
39241
|
function auditRotation() {
|
|
@@ -39351,6 +39394,9 @@ function parseAuditLine2(line) {
|
|
|
39351
39394
|
entry.failed_step = o.failed_step;
|
|
39352
39395
|
if (typeof o.failed_agent === "string")
|
|
39353
39396
|
entry.failed_agent = o.failed_agent;
|
|
39397
|
+
if (Array.isArray(o.drifted) && o.drifted.every((x) => typeof x === "string")) {
|
|
39398
|
+
entry.drifted = o.drifted;
|
|
39399
|
+
}
|
|
39354
39400
|
if (typeof o.agent === "string")
|
|
39355
39401
|
entry.agent = o.agent;
|
|
39356
39402
|
if (typeof o.n === "number")
|
|
@@ -44237,8 +44283,8 @@ var init_client5 = __esm(() => {
|
|
|
44237
44283
|
// src/web/fleet-health-read.ts
|
|
44238
44284
|
import { readFileSync as readFileSync51 } from "node:fs";
|
|
44239
44285
|
import { resolve as resolve32 } from "node:path";
|
|
44240
|
-
import { homedir as
|
|
44241
|
-
function fleetHealthLedgerPath(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ??
|
|
44286
|
+
import { homedir as homedir26 } from "node:os";
|
|
44287
|
+
function fleetHealthLedgerPath(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir26()) {
|
|
44242
44288
|
return resolve32(home2, ".switchroom", "fleet-health", "ledger.json");
|
|
44243
44289
|
}
|
|
44244
44290
|
function readFleetHealth(path6) {
|
|
@@ -45484,6 +45530,241 @@ var init_doctor_hnsw_index = __esm(() => {
|
|
|
45484
45530
|
];
|
|
45485
45531
|
});
|
|
45486
45532
|
|
|
45533
|
+
// src/cli/doctor-observation-scopes.ts
|
|
45534
|
+
function computeScopeSaturation(scopes, cap, warnRatio = OBSERVATION_SCOPE_WARN_RATIO) {
|
|
45535
|
+
if (cap < 0)
|
|
45536
|
+
return [];
|
|
45537
|
+
const sets = scopes.map((s) => ({ tags: s.tags, set: new Set(s.tags), count: s.count }));
|
|
45538
|
+
const postings = new Map;
|
|
45539
|
+
sets.forEach((entry, i) => {
|
|
45540
|
+
for (const tag of entry.set) {
|
|
45541
|
+
const list = postings.get(tag);
|
|
45542
|
+
if (list)
|
|
45543
|
+
list.push(i);
|
|
45544
|
+
else
|
|
45545
|
+
postings.set(tag, [i]);
|
|
45546
|
+
}
|
|
45547
|
+
});
|
|
45548
|
+
const out = [];
|
|
45549
|
+
for (const entry of sets) {
|
|
45550
|
+
if (entry.set.size === 0)
|
|
45551
|
+
continue;
|
|
45552
|
+
let candidates;
|
|
45553
|
+
for (const tag of entry.set) {
|
|
45554
|
+
const list = postings.get(tag);
|
|
45555
|
+
if (list && (candidates === undefined || list.length < candidates.length)) {
|
|
45556
|
+
candidates = list;
|
|
45557
|
+
}
|
|
45558
|
+
}
|
|
45559
|
+
let effective = 0;
|
|
45560
|
+
for (const idx of candidates ?? []) {
|
|
45561
|
+
const other = sets[idx];
|
|
45562
|
+
if (other === undefined)
|
|
45563
|
+
continue;
|
|
45564
|
+
let contains = true;
|
|
45565
|
+
for (const tag of entry.set) {
|
|
45566
|
+
if (!other.set.has(tag)) {
|
|
45567
|
+
contains = false;
|
|
45568
|
+
break;
|
|
45569
|
+
}
|
|
45570
|
+
}
|
|
45571
|
+
if (contains)
|
|
45572
|
+
effective += other.count;
|
|
45573
|
+
}
|
|
45574
|
+
const status = cap > 0 && effective >= cap ? "fail" : cap > 0 && effective >= cap * warnRatio ? "warn" : "ok";
|
|
45575
|
+
out.push({ tags: [...entry.tags], exact: entry.count, effective, cap, status });
|
|
45576
|
+
}
|
|
45577
|
+
out.sort((a, b) => b.effective - a.effective);
|
|
45578
|
+
return out;
|
|
45579
|
+
}
|
|
45580
|
+
function describeScope(bankId, s) {
|
|
45581
|
+
return `${bankId}/${s.tags.join(",")} (${s.effective}/${s.cap})`;
|
|
45582
|
+
}
|
|
45583
|
+
function classifyObservationScopeSaturation(reports) {
|
|
45584
|
+
const name = OBSERVATION_SCOPE_CHECK_NAME;
|
|
45585
|
+
if (reports.length === 0) {
|
|
45586
|
+
return { name, status: "skip", detail: "no banks to inspect" };
|
|
45587
|
+
}
|
|
45588
|
+
const unreadable = reports.filter((r) => !r.ok);
|
|
45589
|
+
const readable = reports.filter((r) => r.ok);
|
|
45590
|
+
if (readable.length === 0) {
|
|
45591
|
+
return {
|
|
45592
|
+
name,
|
|
45593
|
+
status: "skip",
|
|
45594
|
+
detail: `could not read observation scopes for ${unreadable.length} bank(s): ` + unreadable.map((r) => `${r.bankId} (${r.reason ?? "unknown"})`).join(", ")
|
|
45595
|
+
};
|
|
45596
|
+
}
|
|
45597
|
+
const unjudged = readable.filter((r) => r.hasScopeLimitRules || !r.observationsEnabled);
|
|
45598
|
+
const judged = readable.filter((r) => !r.hasScopeLimitRules && r.observationsEnabled);
|
|
45599
|
+
const notes = [];
|
|
45600
|
+
if (unreadable.length > 0) {
|
|
45601
|
+
notes.push(`${unreadable.length} bank(s) unreadable: ` + unreadable.map((r) => `${r.bankId} (${r.reason ?? "unknown"})`).join(", "));
|
|
45602
|
+
}
|
|
45603
|
+
const withRules = unjudged.filter((r) => r.hasScopeLimitRules);
|
|
45604
|
+
if (withRules.length > 0) {
|
|
45605
|
+
notes.push(`${withRules.length} bank(s) not judged \u2014 observation_scope_limits sets a ` + `per-scope cap this check does not resolve: ${withRules.map((r) => r.bankId).join(", ")}`);
|
|
45606
|
+
}
|
|
45607
|
+
const disabled = unjudged.filter((r) => !r.hasScopeLimitRules && !r.observationsEnabled);
|
|
45608
|
+
if (disabled.length > 0) {
|
|
45609
|
+
notes.push(`${disabled.length} bank(s) have observations disabled: ` + disabled.map((r) => r.bankId).join(", "));
|
|
45610
|
+
}
|
|
45611
|
+
const suffix = notes.length > 0 ? ` \u00b7 ${notes.join(" \u00b7 ")}` : "";
|
|
45612
|
+
if (judged.length === 0) {
|
|
45613
|
+
return { name, status: "skip", detail: `no bank could be judged${suffix}` };
|
|
45614
|
+
}
|
|
45615
|
+
const closed = judged.filter((r) => r.cap === 0);
|
|
45616
|
+
const unlimited = judged.filter((r) => r.cap < 0);
|
|
45617
|
+
const capped = judged.filter((r) => r.cap > 0);
|
|
45618
|
+
const failing = capped.flatMap((r) => r.saturated.filter((s) => s.status === "fail").map((s) => ({ report: r, scope: s })));
|
|
45619
|
+
const warning = capped.flatMap((r) => r.saturated.filter((s) => s.status === "warn").map((s) => ({ report: r, scope: s })));
|
|
45620
|
+
failing.sort((a, b) => b.scope.effective - a.scope.effective);
|
|
45621
|
+
warning.sort((a, b) => b.scope.effective - a.scope.effective);
|
|
45622
|
+
const backlog = (rows) => {
|
|
45623
|
+
const seen = new Map;
|
|
45624
|
+
for (const { report } of rows) {
|
|
45625
|
+
if (report.pendingConsolidation != null && !seen.has(report.bankId)) {
|
|
45626
|
+
seen.set(report.bankId, report.pendingConsolidation);
|
|
45627
|
+
}
|
|
45628
|
+
}
|
|
45629
|
+
if (seen.size === 0)
|
|
45630
|
+
return "";
|
|
45631
|
+
const parts = [...seen].sort((a, b) => b[1] - a[1]).map(([bank, pending]) => `${bank} ${pending.toLocaleString()}`);
|
|
45632
|
+
return ` \u00b7 memories pending consolidation: ${parts.join(", ")}`;
|
|
45633
|
+
};
|
|
45634
|
+
const list = (rows) => {
|
|
45635
|
+
const shown = rows.slice(0, OBSERVATION_SCOPE_MAX_LISTED).map(({ report, scope }) => describeScope(report.bankId, scope)).join("; ");
|
|
45636
|
+
const rest = rows.length - Math.min(rows.length, OBSERVATION_SCOPE_MAX_LISTED);
|
|
45637
|
+
return rest > 0 ? `${shown}; +${rest} more` : shown;
|
|
45638
|
+
};
|
|
45639
|
+
const fix = "Consolidation keeps running against a full scope \u2014 it recalls, spends an " + "extraction-model call, and can only apply updates/deletes " + "(engine/consolidation/consolidator.py:1604-1613), so every pass on that " + "scope is paid for and its creates are discarded. Remedy: raise the cap, " + "via `hindsight.env.HINDSIGHT_API_MAX_OBSERVATIONS_PER_SCOPE` in " + "switchroom.yaml (fleet-wide) or `max_observations_per_scope` on the bank " + "(PATCH /v1/default/banks/<bank>/config). Confirm the counts first with " + "GET /v1/default/banks/<bank>/observations/scopes \u2014 but note they are EXACT " + "tag sets, while the engine counts by CONTAINMENT (`tags @> scope`), so a " + "scope's child scopes count toward it and its true number is higher than " + "that endpoint shows. Retiring a scope instead is NOT a single call: " + "DELETE /v1/default/banks/<bank>/observations clears the WHOLE bank's " + "observations, so enumerate the scope first " + "(GET /graph?type=observation&tags=<scope>&tags_match=exact) and delete " + "per memory.";
|
|
45640
|
+
if (failing.length > 0) {
|
|
45641
|
+
return {
|
|
45642
|
+
name,
|
|
45643
|
+
status: "fail",
|
|
45644
|
+
detail: `${failing.length} observation scope(s) at or over the cap \u2014 consolidation ` + `output is being discarded on them right now: ${list(failing)}` + (warning.length > 0 ? ` \u00b7 ${warning.length} more above ${Math.round(OBSERVATION_SCOPE_WARN_RATIO * 100)}%` : "") + backlog([...failing, ...warning]) + suffix,
|
|
45645
|
+
fix
|
|
45646
|
+
};
|
|
45647
|
+
}
|
|
45648
|
+
if (warning.length > 0) {
|
|
45649
|
+
return {
|
|
45650
|
+
name,
|
|
45651
|
+
status: "warn",
|
|
45652
|
+
detail: `${warning.length} observation scope(s) above ` + `${Math.round(OBSERVATION_SCOPE_WARN_RATIO * 100)}% of the cap and still writable: ` + `${list(warning)}` + backlog(warning) + suffix,
|
|
45653
|
+
fix
|
|
45654
|
+
};
|
|
45655
|
+
}
|
|
45656
|
+
const totalScopes = capped.reduce((n, r) => n + r.scopeCount, 0);
|
|
45657
|
+
const detailBase = capped.length === 0 ? `no bank has a positive observation cap` : `${totalScopes} scope(s) across ${capped.length} bank(s) below ` + `${Math.round(OBSERVATION_SCOPE_WARN_RATIO * 100)}% of the cap`;
|
|
45658
|
+
const extra = [];
|
|
45659
|
+
if (unlimited.length > 0) {
|
|
45660
|
+
extra.push(`${unlimited.length} bank(s) uncapped (-1)`);
|
|
45661
|
+
}
|
|
45662
|
+
if (closed.length > 0) {
|
|
45663
|
+
extra.push(`${closed.length} bank(s) deliberately closed to new observations (cap 0): ` + closed.map((r) => r.bankId).join(", "));
|
|
45664
|
+
}
|
|
45665
|
+
return {
|
|
45666
|
+
name,
|
|
45667
|
+
status: "ok",
|
|
45668
|
+
detail: detailBase + (extra.length > 0 ? ` \u00b7 ${extra.join(" \u00b7 ")}` : "") + suffix
|
|
45669
|
+
};
|
|
45670
|
+
}
|
|
45671
|
+
async function getJson2(url, opts) {
|
|
45672
|
+
const fetchImpl = opts?.fetchImpl ?? fetch;
|
|
45673
|
+
const timeoutMs = opts?.timeoutMs ?? 15000;
|
|
45674
|
+
const controller = new AbortController;
|
|
45675
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
45676
|
+
try {
|
|
45677
|
+
const resp = await fetchImpl(url, { signal: controller.signal });
|
|
45678
|
+
clearTimeout(timeout);
|
|
45679
|
+
if (!resp.ok)
|
|
45680
|
+
return { ok: false, reason: `HTTP ${resp.status}` };
|
|
45681
|
+
return { ok: true, data: await resp.json() };
|
|
45682
|
+
} catch (err) {
|
|
45683
|
+
clearTimeout(timeout);
|
|
45684
|
+
if (err.name === "AbortError")
|
|
45685
|
+
return { ok: false, reason: "Timeout" };
|
|
45686
|
+
return { ok: false, reason: String(err.message ?? err) };
|
|
45687
|
+
}
|
|
45688
|
+
}
|
|
45689
|
+
async function inspectBankScopes(apiUrl, bankId, opts) {
|
|
45690
|
+
const base = hindsightRestBase(apiUrl);
|
|
45691
|
+
const bank = encodeURIComponent(bankId);
|
|
45692
|
+
const fail4 = (reason) => ({
|
|
45693
|
+
bankId,
|
|
45694
|
+
ok: false,
|
|
45695
|
+
reason,
|
|
45696
|
+
cap: -1,
|
|
45697
|
+
observationsEnabled: false,
|
|
45698
|
+
hasScopeLimitRules: false,
|
|
45699
|
+
scopeCount: 0,
|
|
45700
|
+
saturated: [],
|
|
45701
|
+
pendingConsolidation: null
|
|
45702
|
+
});
|
|
45703
|
+
const cfg = await getJson2(`${base}/v1/default/banks/${bank}/config`, opts);
|
|
45704
|
+
if (!cfg.ok)
|
|
45705
|
+
return fail4(cfg.reason);
|
|
45706
|
+
const config = cfg.data?.config;
|
|
45707
|
+
if (config == null || typeof config !== "object")
|
|
45708
|
+
return fail4("Unexpected shape");
|
|
45709
|
+
const rawCap = config["max_observations_per_scope"];
|
|
45710
|
+
if (typeof rawCap !== "number" || !Number.isFinite(rawCap)) {
|
|
45711
|
+
return fail4("Unexpected shape");
|
|
45712
|
+
}
|
|
45713
|
+
const rules = config["observation_scope_limits"];
|
|
45714
|
+
const hasScopeLimitRules = Array.isArray(rules) && rules.length > 0;
|
|
45715
|
+
const observationsEnabled = config["enable_observations"] !== false;
|
|
45716
|
+
const scopesResp = await getJson2(`${base}/v1/default/banks/${bank}/observations/scopes`, opts);
|
|
45717
|
+
if (!scopesResp.ok)
|
|
45718
|
+
return fail4(scopesResp.reason);
|
|
45719
|
+
const rawScopes = scopesResp.data?.scopes;
|
|
45720
|
+
if (!Array.isArray(rawScopes))
|
|
45721
|
+
return fail4("Unexpected shape");
|
|
45722
|
+
const scopes = [];
|
|
45723
|
+
for (const entry of rawScopes) {
|
|
45724
|
+
const tags = entry?.tags;
|
|
45725
|
+
const count = entry?.count;
|
|
45726
|
+
if (!Array.isArray(tags) || typeof count !== "number")
|
|
45727
|
+
continue;
|
|
45728
|
+
if (!tags.every((t) => typeof t === "string"))
|
|
45729
|
+
continue;
|
|
45730
|
+
scopes.push({ tags, count });
|
|
45731
|
+
}
|
|
45732
|
+
const saturated = computeScopeSaturation(scopes, rawCap).filter((s) => s.status !== "ok");
|
|
45733
|
+
let pendingConsolidation = null;
|
|
45734
|
+
if (saturated.length > 0 && !hasScopeLimitRules && observationsEnabled) {
|
|
45735
|
+
const stats = await getJson2(`${base}/v1/default/banks/${bank}/stats`, opts);
|
|
45736
|
+
if (stats.ok && typeof stats.data?.pending_consolidation === "number") {
|
|
45737
|
+
pendingConsolidation = stats.data.pending_consolidation;
|
|
45738
|
+
}
|
|
45739
|
+
}
|
|
45740
|
+
return {
|
|
45741
|
+
bankId,
|
|
45742
|
+
ok: true,
|
|
45743
|
+
cap: rawCap,
|
|
45744
|
+
observationsEnabled,
|
|
45745
|
+
hasScopeLimitRules,
|
|
45746
|
+
scopeCount: scopes.length,
|
|
45747
|
+
saturated,
|
|
45748
|
+
pendingConsolidation
|
|
45749
|
+
};
|
|
45750
|
+
}
|
|
45751
|
+
async function checkObservationScopeSaturation(config, apiUrl, opts) {
|
|
45752
|
+
const banks = new Set;
|
|
45753
|
+
for (const [agentName, agentConfig] of Object.entries(config.agents ?? {})) {
|
|
45754
|
+
const resolved = resolveAgentConfig(config.defaults, config.profiles, agentConfig);
|
|
45755
|
+
banks.add(resolved.memory?.collection ?? agentName);
|
|
45756
|
+
}
|
|
45757
|
+
for (const bank of collectProfileBanks(config))
|
|
45758
|
+
banks.add(bank);
|
|
45759
|
+
const reports = await Promise.all([...banks].map((bankId) => inspectBankScopes(apiUrl, bankId, opts)));
|
|
45760
|
+
return classifyObservationScopeSaturation(reports);
|
|
45761
|
+
}
|
|
45762
|
+
var OBSERVATION_SCOPE_CHECK_NAME = "hindsight observation scopes", OBSERVATION_SCOPE_WARN_RATIO = 0.8, OBSERVATION_SCOPE_MAX_LISTED = 6;
|
|
45763
|
+
var init_doctor_observation_scopes = __esm(() => {
|
|
45764
|
+
init_merge();
|
|
45765
|
+
init_hindsight2();
|
|
45766
|
+
});
|
|
45767
|
+
|
|
45487
45768
|
// src/hindsight-watch/install-cron.ts
|
|
45488
45769
|
import { existsSync as existsSync63, mkdirSync as mkdirSync35, readFileSync as readFileSync60, renameSync as renameSync15, writeFileSync as writeFileSync22 } from "node:fs";
|
|
45489
45770
|
import { dirname as dirname22 } from "node:path";
|
|
@@ -45538,9 +45819,9 @@ var init_types5 = () => {};
|
|
|
45538
45819
|
|
|
45539
45820
|
// src/hindsight-watch/state.ts
|
|
45540
45821
|
import { mkdirSync as mkdirSync36, readFileSync as readFileSync61, renameSync as renameSync16, writeFileSync as writeFileSync23 } from "node:fs";
|
|
45541
|
-
import { homedir as
|
|
45822
|
+
import { homedir as homedir31 } from "node:os";
|
|
45542
45823
|
import { dirname as dirname23, resolve as resolve36 } from "node:path";
|
|
45543
|
-
function defaultStatePath2(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ??
|
|
45824
|
+
function defaultStatePath2(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir31()) {
|
|
45544
45825
|
return resolve36(home2, ".switchroom", "hindsight-watch", "state.json");
|
|
45545
45826
|
}
|
|
45546
45827
|
function loadState(path6) {
|
|
@@ -46434,12 +46715,121 @@ var init_key_allowlist_check = __esm(() => {
|
|
|
46434
46715
|
init_key_allowlist();
|
|
46435
46716
|
});
|
|
46436
46717
|
|
|
46718
|
+
// src/cli/doctor-routing-mode.ts
|
|
46719
|
+
import { readFileSync as readFileSync62 } from "node:fs";
|
|
46720
|
+
import { join as join59 } from "node:path";
|
|
46721
|
+
function routingModePath(agentDir) {
|
|
46722
|
+
return join59(agentDir, ROUTING_MODE_FILENAME);
|
|
46723
|
+
}
|
|
46724
|
+
function parseRoutingModeRecord(text) {
|
|
46725
|
+
const line = text.split(`
|
|
46726
|
+
`).find((l) => l.trim().length > 0);
|
|
46727
|
+
if (!line)
|
|
46728
|
+
return null;
|
|
46729
|
+
const fields = {};
|
|
46730
|
+
for (const tok of line.trim().split(/\s+/)) {
|
|
46731
|
+
const eq = tok.indexOf("=");
|
|
46732
|
+
if (eq <= 0)
|
|
46733
|
+
continue;
|
|
46734
|
+
fields[tok.slice(0, eq)] = tok.slice(eq + 1);
|
|
46735
|
+
}
|
|
46736
|
+
if (!fields.mode || !fields.declared)
|
|
46737
|
+
return null;
|
|
46738
|
+
return {
|
|
46739
|
+
mode: fields.mode,
|
|
46740
|
+
base: fields.base ?? "",
|
|
46741
|
+
model: fields.model ?? "",
|
|
46742
|
+
litellmOk: fields.litellm_ok ?? "",
|
|
46743
|
+
declared: fields.declared,
|
|
46744
|
+
ts: fields.ts ?? ""
|
|
46745
|
+
};
|
|
46746
|
+
}
|
|
46747
|
+
function classifyRoutingMode(rec) {
|
|
46748
|
+
if (!LITELLM_DECLARED_MODES.has(rec.declared))
|
|
46749
|
+
return null;
|
|
46750
|
+
if (rec.mode !== UNTRACKED_MODE)
|
|
46751
|
+
return null;
|
|
46752
|
+
const when = rec.ts ? ` at its last boot (${rec.ts})` : "";
|
|
46753
|
+
return {
|
|
46754
|
+
status: "warn",
|
|
46755
|
+
detail: `landed on direct-oauth${when} but its declared routing is ` + `\`${rec.declared}\` \u2014 start.sh's missing-key fail-open stripped the ` + `LiteLLM routing env, so this agent runs UNTRACKED on direct Anthropic ` + `OAuth: no spend attribution, no guardrails` + (rec.model ? ` (model \`${rec.model}\`)` : "") + `.`
|
|
46756
|
+
};
|
|
46757
|
+
}
|
|
46758
|
+
function runRoutingModeChecks(config, deps = {}) {
|
|
46759
|
+
const results = [];
|
|
46760
|
+
const agents = Object.keys(config.agents ?? {});
|
|
46761
|
+
if (agents.length === 0)
|
|
46762
|
+
return results;
|
|
46763
|
+
const readFile2 = deps.readFile ?? ((p) => readFileSync62(p, "utf-8"));
|
|
46764
|
+
let agentsDir = deps.agentsDir;
|
|
46765
|
+
if (agentsDir === undefined) {
|
|
46766
|
+
try {
|
|
46767
|
+
agentsDir = resolveAgentsDir(config);
|
|
46768
|
+
} catch {
|
|
46769
|
+
agentsDir = undefined;
|
|
46770
|
+
}
|
|
46771
|
+
}
|
|
46772
|
+
if (agentsDir === undefined) {
|
|
46773
|
+
return [
|
|
46774
|
+
{
|
|
46775
|
+
name: "litellm routing",
|
|
46776
|
+
status: "warn",
|
|
46777
|
+
detail: "could not resolve the agents directory (no switchroom block) \u2014 " + "skipped the boot routing-mode check"
|
|
46778
|
+
}
|
|
46779
|
+
];
|
|
46780
|
+
}
|
|
46781
|
+
for (const name of agents) {
|
|
46782
|
+
const path6 = routingModePath(join59(agentsDir, name));
|
|
46783
|
+
let text;
|
|
46784
|
+
try {
|
|
46785
|
+
text = readFile2(path6);
|
|
46786
|
+
} catch (err) {
|
|
46787
|
+
const code = err.code;
|
|
46788
|
+
if (code === "ENOENT")
|
|
46789
|
+
continue;
|
|
46790
|
+
results.push({
|
|
46791
|
+
name: `litellm routing: ${name}`,
|
|
46792
|
+
status: "warn",
|
|
46793
|
+
detail: `could not read the boot routing record at ${path6} ` + `(${code ?? err.message}) \u2014 an untracked boot would go ` + `unreported here.`,
|
|
46794
|
+
fix: `Check the file's ownership and mode (start.sh writes it 0644 before \`exec claude\`).`
|
|
46795
|
+
});
|
|
46796
|
+
continue;
|
|
46797
|
+
}
|
|
46798
|
+
const rec = parseRoutingModeRecord(text);
|
|
46799
|
+
if (!rec) {
|
|
46800
|
+
results.push({
|
|
46801
|
+
name: `litellm routing: ${name}`,
|
|
46802
|
+
status: "warn",
|
|
46803
|
+
detail: `the boot routing record at ${path6} is malformed (no \`mode=\`/` + `\`declared=\` fields) \u2014 an untracked boot would go unreported here.`,
|
|
46804
|
+
fix: `Delete it and restart the agent to have start.sh rewrite it: rm ${path6}`
|
|
46805
|
+
});
|
|
46806
|
+
continue;
|
|
46807
|
+
}
|
|
46808
|
+
const verdict = classifyRoutingMode(rec);
|
|
46809
|
+
if (!verdict)
|
|
46810
|
+
continue;
|
|
46811
|
+
results.push({
|
|
46812
|
+
name: `litellm routing: ${name}`,
|
|
46813
|
+
status: verdict.status,
|
|
46814
|
+
detail: verdict.detail,
|
|
46815
|
+
fix: FIX2
|
|
46816
|
+
});
|
|
46817
|
+
}
|
|
46818
|
+
return results;
|
|
46819
|
+
}
|
|
46820
|
+
var LITELLM_DECLARED_MODES, UNTRACKED_MODE = "direct-oauth", ROUTING_MODE_FILENAME = ".routing-mode", FIX2;
|
|
46821
|
+
var init_doctor_routing_mode = __esm(() => {
|
|
46822
|
+
init_loader();
|
|
46823
|
+
LITELLM_DECLARED_MODES = new Set(["passthrough", "router-root"]);
|
|
46824
|
+
FIX2 = "start.sh could not fetch this agent's `litellm/<agent>/api-key` from the " + "vault-broker. Re-run `switchroom apply` (it provisions the per-agent " + "virtual key idempotently and re-grants the agent's `secrets:` ACL), then " + "RESTART the agent \u2014 the strip is decided once at boot and is never " + "re-evaluated for the life of the session, so a fixed key does not take " + "effect until the next boot. Confirm from the agent's `.routing-mode` line " + "afterwards.";
|
|
46825
|
+
});
|
|
46826
|
+
|
|
46437
46827
|
// src/cli/doctor-auth-broker.ts
|
|
46438
|
-
import { existsSync as existsSync65, readFileSync as
|
|
46828
|
+
import { existsSync as existsSync65, readFileSync as readFileSync63 } from "node:fs";
|
|
46439
46829
|
import { createHash as createHash14 } from "node:crypto";
|
|
46440
46830
|
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
46441
|
-
import { homedir as
|
|
46442
|
-
import { join as
|
|
46831
|
+
import { homedir as homedir32 } from "node:os";
|
|
46832
|
+
import { join as join60 } from "node:path";
|
|
46443
46833
|
function defaultDockerInspect(container, format) {
|
|
46444
46834
|
try {
|
|
46445
46835
|
const r = spawnSync10("docker", ["inspect", "-f", format, container], { encoding: "utf-8", timeout: 5000 });
|
|
@@ -46539,7 +46929,7 @@ function checkAuthBrokerPerAgentSockets(config, deps = {}) {
|
|
|
46539
46929
|
}
|
|
46540
46930
|
function checkAuthBrokerDrift(deps = {}) {
|
|
46541
46931
|
const stateDir = resolveStateDir(deps);
|
|
46542
|
-
const indexPath =
|
|
46932
|
+
const indexPath = join60(stateDir, "sha-index.json");
|
|
46543
46933
|
if (!existsSync65(indexPath)) {
|
|
46544
46934
|
return {
|
|
46545
46935
|
name: "auth-broker: drift",
|
|
@@ -46549,7 +46939,7 @@ function checkAuthBrokerDrift(deps = {}) {
|
|
|
46549
46939
|
}
|
|
46550
46940
|
let index;
|
|
46551
46941
|
try {
|
|
46552
|
-
index = JSON.parse(
|
|
46942
|
+
index = JSON.parse(readFileSync63(indexPath, "utf-8"));
|
|
46553
46943
|
} catch (err) {
|
|
46554
46944
|
return {
|
|
46555
46945
|
name: "auth-broker: drift",
|
|
@@ -46558,7 +46948,7 @@ function checkAuthBrokerDrift(deps = {}) {
|
|
|
46558
46948
|
fix: "Inspect `~/.switchroom/state/auth-broker/sha-index.json` for corruption."
|
|
46559
46949
|
};
|
|
46560
46950
|
}
|
|
46561
|
-
const home2 = deps.home ??
|
|
46951
|
+
const home2 = deps.home ?? homedir32();
|
|
46562
46952
|
const divergent = [];
|
|
46563
46953
|
const missingOnDisk = [];
|
|
46564
46954
|
for (const [label, expected] of Object.entries(index)) {
|
|
@@ -46569,7 +46959,7 @@ function checkAuthBrokerDrift(deps = {}) {
|
|
|
46569
46959
|
}
|
|
46570
46960
|
let got;
|
|
46571
46961
|
try {
|
|
46572
|
-
got = sha256Hex(
|
|
46962
|
+
got = sha256Hex(readFileSync63(credsPath, "utf-8"));
|
|
46573
46963
|
} catch (err) {
|
|
46574
46964
|
divergent.push(`${label} (read failed: ${err.message})`);
|
|
46575
46965
|
continue;
|
|
@@ -46600,7 +46990,7 @@ function checkAuthBrokerDrift(deps = {}) {
|
|
|
46600
46990
|
}
|
|
46601
46991
|
function checkAuthBrokerThresholdViolations(deps = {}) {
|
|
46602
46992
|
const stateDir = resolveStateDir(deps);
|
|
46603
|
-
const path6 =
|
|
46993
|
+
const path6 = join60(stateDir, "threshold-violations.json");
|
|
46604
46994
|
if (!existsSync65(path6)) {
|
|
46605
46995
|
return {
|
|
46606
46996
|
name: "auth-broker: threshold violations",
|
|
@@ -46610,7 +47000,7 @@ function checkAuthBrokerThresholdViolations(deps = {}) {
|
|
|
46610
47000
|
}
|
|
46611
47001
|
let violations;
|
|
46612
47002
|
try {
|
|
46613
|
-
violations = JSON.parse(
|
|
47003
|
+
violations = JSON.parse(readFileSync63(path6, "utf-8"));
|
|
46614
47004
|
} catch (err) {
|
|
46615
47005
|
return {
|
|
46616
47006
|
name: "auth-broker: threshold violations",
|
|
@@ -46644,7 +47034,7 @@ function checkAuthBrokerActiveAccount(config, deps = {}) {
|
|
|
46644
47034
|
fix: "Run `switchroom auth use <label>` to pin a fleet-wide account, then `switchroom apply`. Without an active account every agent boot fails."
|
|
46645
47035
|
};
|
|
46646
47036
|
}
|
|
46647
|
-
const home2 = deps.home ??
|
|
47037
|
+
const home2 = deps.home ?? homedir32();
|
|
46648
47038
|
const dir = accountDir(active, home2);
|
|
46649
47039
|
if (!existsSync65(dir)) {
|
|
46650
47040
|
return {
|
|
@@ -46796,8 +47186,8 @@ import {
|
|
|
46796
47186
|
realpathSync as realpathSync5,
|
|
46797
47187
|
statSync as statSync39
|
|
46798
47188
|
} from "node:fs";
|
|
46799
|
-
import { userInfo, homedir as
|
|
46800
|
-
import { join as
|
|
47189
|
+
import { userInfo, homedir as homedir33 } from "node:os";
|
|
47190
|
+
import { join as join61 } from "node:path";
|
|
46801
47191
|
function resolveVaultPath2(config) {
|
|
46802
47192
|
return config.vault?.path ? config.vault.path.replace(/^~/, process.env.HOME ?? "") : resolveStatePath("vault.enc");
|
|
46803
47193
|
}
|
|
@@ -46946,7 +47336,7 @@ async function runSecretAccessChecks(config, deps = {}) {
|
|
|
46946
47336
|
}
|
|
46947
47337
|
}
|
|
46948
47338
|
const statPath = deps.statPath ?? statVault;
|
|
46949
|
-
const home2 = deps.operatorHome ??
|
|
47339
|
+
const home2 = deps.operatorHome ?? homedir33();
|
|
46950
47340
|
const ownedRoots = deps.ownedRoots ?? operatorOwnedPaths(home2);
|
|
46951
47341
|
const walkOwned = deps.walkOwned ?? ((root) => walkOperatorOwnedTargets(home2, {}, [root]));
|
|
46952
47342
|
for (const root of ownedRoots) {
|
|
@@ -46969,7 +47359,7 @@ async function runSecretAccessChecks(config, deps = {}) {
|
|
|
46969
47359
|
};
|
|
46970
47360
|
const passphrase = deps.passphrase ?? process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
46971
47361
|
if (!passphrase) {
|
|
46972
|
-
const sock = deps.brokerOperatorSocket ??
|
|
47362
|
+
const sock = deps.brokerOperatorSocket ?? join61(homedir33(), ".switchroom", "broker-operator", "sock");
|
|
46973
47363
|
const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
|
|
46974
47364
|
for (const name of Object.keys(config.agents ?? {})) {
|
|
46975
47365
|
const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[name]);
|
|
@@ -47061,8 +47451,8 @@ import {
|
|
|
47061
47451
|
existsSync as realExistsSync,
|
|
47062
47452
|
readFileSync as realReadFileSync
|
|
47063
47453
|
} from "node:fs";
|
|
47064
|
-
import { join as
|
|
47065
|
-
import { homedir as
|
|
47454
|
+
import { join as join62, resolve as resolve37 } from "node:path";
|
|
47455
|
+
import { homedir as homedir34 } from "node:os";
|
|
47066
47456
|
function resolveDeps(config, deps) {
|
|
47067
47457
|
let agentsDir = deps.agentsDir;
|
|
47068
47458
|
if (agentsDir === undefined) {
|
|
@@ -47185,8 +47575,8 @@ function checkScaffoldWiring(config, driveAgents, d) {
|
|
|
47185
47575
|
});
|
|
47186
47576
|
continue;
|
|
47187
47577
|
}
|
|
47188
|
-
const mcpPath =
|
|
47189
|
-
const claudeJsonPath =
|
|
47578
|
+
const mcpPath = join62(agentDir, ".mcp.json");
|
|
47579
|
+
const claudeJsonPath = join62(agentDir, ".claude", ".claude.json");
|
|
47190
47580
|
const mcpRead = readJson(d, mcpPath);
|
|
47191
47581
|
const trustRead = readJson(d, claudeJsonPath);
|
|
47192
47582
|
if (mcpRead.kind === "unreadable" || trustRead.kind === "unreadable") {
|
|
@@ -47299,7 +47689,7 @@ async function runDriveBrokerReachabilityChecks(config, deps = {}) {
|
|
|
47299
47689
|
}
|
|
47300
47690
|
];
|
|
47301
47691
|
}
|
|
47302
|
-
const sock = deps.brokerOperatorSocket ??
|
|
47692
|
+
const sock = deps.brokerOperatorSocket ?? join62(homedir34(), ".switchroom", "broker-operator", "sock");
|
|
47303
47693
|
const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
|
|
47304
47694
|
const results = [];
|
|
47305
47695
|
for (const agent of driveAgents) {
|
|
@@ -47353,8 +47743,8 @@ import {
|
|
|
47353
47743
|
readSync as realReadSync,
|
|
47354
47744
|
closeSync as realCloseSync
|
|
47355
47745
|
} from "node:fs";
|
|
47356
|
-
import { join as
|
|
47357
|
-
import { homedir as
|
|
47746
|
+
import { join as join63 } from "node:path";
|
|
47747
|
+
import { homedir as homedir35 } from "node:os";
|
|
47358
47748
|
function defaultReadHead(p, n) {
|
|
47359
47749
|
let fd;
|
|
47360
47750
|
try {
|
|
@@ -47382,7 +47772,7 @@ function resolveDeps2(config, deps) {
|
|
|
47382
47772
|
}
|
|
47383
47773
|
}
|
|
47384
47774
|
return {
|
|
47385
|
-
homeDir: deps.homeDir ??
|
|
47775
|
+
homeDir: deps.homeDir ?? homedir35(),
|
|
47386
47776
|
agentsDir,
|
|
47387
47777
|
existsSync: deps.existsSync ?? ((p) => realExistsSync2(p)),
|
|
47388
47778
|
readFileSync: deps.readFileSync ?? ((p) => realReadFileSync2(p, "utf-8")),
|
|
@@ -47411,7 +47801,7 @@ function runWebkiteChecks(config, deps = {}) {
|
|
|
47411
47801
|
return [];
|
|
47412
47802
|
const d = resolveDeps2(config, deps);
|
|
47413
47803
|
const results = [];
|
|
47414
|
-
const binPath =
|
|
47804
|
+
const binPath = join63(d.homeDir, ".switchroom", "bin", "webkite");
|
|
47415
47805
|
if (!d.existsSync(binPath)) {
|
|
47416
47806
|
results.push({
|
|
47417
47807
|
name: "webkite: binary",
|
|
@@ -47441,14 +47831,14 @@ function runWebkiteChecks(config, deps = {}) {
|
|
|
47441
47831
|
});
|
|
47442
47832
|
}
|
|
47443
47833
|
}
|
|
47444
|
-
const sharedCloakDir =
|
|
47445
|
-
const legacyCloakDir =
|
|
47834
|
+
const sharedCloakDir = join63(d.homeDir, ".switchroom", "cloakbrowser");
|
|
47835
|
+
const legacyCloakDir = join63(d.homeDir, ".cloakbrowser");
|
|
47446
47836
|
const hasChromium = (dir) => {
|
|
47447
47837
|
if (!d.existsSync(dir))
|
|
47448
47838
|
return false;
|
|
47449
47839
|
try {
|
|
47450
47840
|
for (const entry of d.readdirSync(dir)) {
|
|
47451
|
-
if (entry.startsWith("chromium-") && d.existsSync(
|
|
47841
|
+
if (entry.startsWith("chromium-") && d.existsSync(join63(dir, entry, "chrome"))) {
|
|
47452
47842
|
return true;
|
|
47453
47843
|
}
|
|
47454
47844
|
}
|
|
@@ -47485,9 +47875,9 @@ function runWebkiteChecks(config, deps = {}) {
|
|
|
47485
47875
|
return results;
|
|
47486
47876
|
}
|
|
47487
47877
|
for (const agent of enabledAgents) {
|
|
47488
|
-
const agentDir =
|
|
47489
|
-
const settingsPath =
|
|
47490
|
-
const mcpPath =
|
|
47878
|
+
const agentDir = join63(d.agentsDir, agent);
|
|
47879
|
+
const settingsPath = join63(agentDir, ".claude", "settings.json");
|
|
47880
|
+
const mcpPath = join63(agentDir, ".mcp.json");
|
|
47491
47881
|
if (!d.existsSync(settingsPath) && !d.existsSync(mcpPath)) {
|
|
47492
47882
|
continue;
|
|
47493
47883
|
}
|
|
@@ -47787,10 +48177,10 @@ var init_doctor_cron_session = __esm(() => {
|
|
|
47787
48177
|
});
|
|
47788
48178
|
|
|
47789
48179
|
// telegram-plugin/flood-429-ledger.ts
|
|
47790
|
-
import { readFileSync as
|
|
47791
|
-
import { dirname as dirname24, join as
|
|
48180
|
+
import { readFileSync as readFileSync64, writeFileSync as writeFileSync24, mkdirSync as mkdirSync37, chmodSync as chmodSync11, unlinkSync as unlinkSync14 } from "node:fs";
|
|
48181
|
+
import { dirname as dirname24, join as join64 } from "node:path";
|
|
47792
48182
|
function flood429LedgerPath(stateDir) {
|
|
47793
|
-
return
|
|
48183
|
+
return join64(stateDir, FLOOD_429_LEDGER_FILE);
|
|
47794
48184
|
}
|
|
47795
48185
|
function parseEpisode(raw) {
|
|
47796
48186
|
if (typeof raw !== "object" || raw === null)
|
|
@@ -47810,7 +48200,7 @@ function parseEpisode(raw) {
|
|
|
47810
48200
|
count: num2(r.count) ?? 1
|
|
47811
48201
|
};
|
|
47812
48202
|
}
|
|
47813
|
-
function readFlood429LedgerResult(path6, readFile2 = (p) =>
|
|
48203
|
+
function readFlood429LedgerResult(path6, readFile2 = (p) => readFileSync64(p, "utf-8")) {
|
|
47814
48204
|
let text;
|
|
47815
48205
|
try {
|
|
47816
48206
|
text = readFile2(path6);
|
|
@@ -47920,14 +48310,14 @@ var init_flood_429_ledger = __esm(() => {
|
|
|
47920
48310
|
});
|
|
47921
48311
|
|
|
47922
48312
|
// src/cli/doctor-flood-pressure.ts
|
|
47923
|
-
import { readFileSync as
|
|
47924
|
-
import { join as
|
|
48313
|
+
import { readFileSync as readFileSync65 } from "node:fs";
|
|
48314
|
+
import { join as join65 } from "node:path";
|
|
47925
48315
|
function runFloodPressureChecks(config, deps = {}) {
|
|
47926
48316
|
const results = [];
|
|
47927
48317
|
const agents = Object.keys(config.agents ?? {});
|
|
47928
48318
|
if (agents.length === 0)
|
|
47929
48319
|
return results;
|
|
47930
|
-
const readFile2 = deps.readFile ?? ((p) =>
|
|
48320
|
+
const readFile2 = deps.readFile ?? ((p) => readFileSync65(p, "utf-8"));
|
|
47931
48321
|
const now = deps.now ?? Date.now();
|
|
47932
48322
|
let agentsDir = deps.agentsDir;
|
|
47933
48323
|
if (agentsDir === undefined) {
|
|
@@ -47947,7 +48337,7 @@ function runFloodPressureChecks(config, deps = {}) {
|
|
|
47947
48337
|
];
|
|
47948
48338
|
}
|
|
47949
48339
|
for (const name of agents) {
|
|
47950
|
-
const path6 = flood429LedgerPath(
|
|
48340
|
+
const path6 = flood429LedgerPath(join65(agentsDir, name, "telegram"));
|
|
47951
48341
|
const read = readFlood429LedgerResult(path6, readFile2);
|
|
47952
48342
|
if (read.status === "unreadable" || read.status === "corrupt") {
|
|
47953
48343
|
results.push({
|
|
@@ -47967,16 +48357,16 @@ function runFloodPressureChecks(config, deps = {}) {
|
|
|
47967
48357
|
name: `429 flood pressure: ${name}`,
|
|
47968
48358
|
status: verdict.status,
|
|
47969
48359
|
detail: verdict.detail,
|
|
47970
|
-
fix:
|
|
48360
|
+
fix: FIX3
|
|
47971
48361
|
});
|
|
47972
48362
|
}
|
|
47973
48363
|
return results;
|
|
47974
48364
|
}
|
|
47975
|
-
var
|
|
48365
|
+
var FIX3;
|
|
47976
48366
|
var init_doctor_flood_pressure = __esm(() => {
|
|
47977
48367
|
init_loader();
|
|
47978
48368
|
init_flood_429_ledger();
|
|
47979
|
-
|
|
48369
|
+
FIX3 = "A flood ban is server-side and cannot be cleared early \u2014 cut the outbound " + "rate that earns it. Check the gateway's `send-gate stats` and " + "`edit-flood-fuse` lines for the class doing the sending (progress cards and " + "the worker feed are the usual culprits), and see " + "`telegram-plugin/edit-flood-fuse.ts` for the per-class ceilings.";
|
|
47980
48370
|
});
|
|
47981
48371
|
|
|
47982
48372
|
// src/litellm/budget.ts
|
|
@@ -48680,7 +49070,7 @@ async function runMcpSecretChecks(config, deps = {}) {
|
|
|
48680
49070
|
|
|
48681
49071
|
// src/agents/connection-health.ts
|
|
48682
49072
|
import { mkdirSync as mkdirSync38, writeFileSync as writeFileSync25 } from "node:fs";
|
|
48683
|
-
import { join as
|
|
49073
|
+
import { join as join66 } from "node:path";
|
|
48684
49074
|
async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
|
|
48685
49075
|
const reqs = computeMcpSecretRequirements(config).filter((r) => r.agent === agentName);
|
|
48686
49076
|
if (reqs.length === 0)
|
|
@@ -48737,8 +49127,8 @@ async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
|
|
|
48737
49127
|
return issues;
|
|
48738
49128
|
}
|
|
48739
49129
|
function writeConnectionHealthFile(agentDir, health, deps) {
|
|
48740
|
-
const dir =
|
|
48741
|
-
const path6 =
|
|
49130
|
+
const dir = join66(agentDir, ".claude");
|
|
49131
|
+
const path6 = join66(dir, CONNECTION_HEALTH_FILENAME);
|
|
48742
49132
|
(deps?.mkdir ?? ((p, o) => mkdirSync38(p, o)))(dir, { recursive: true });
|
|
48743
49133
|
(deps?.writeFile ?? ((p, d) => writeFileSync25(p, d)))(path6, JSON.stringify(health, null, 2) + `
|
|
48744
49134
|
`);
|
|
@@ -48761,10 +49151,10 @@ var CONNECTION_HEALTH_FILENAME = "connection-health.json";
|
|
|
48761
49151
|
var init_connection_health = () => {};
|
|
48762
49152
|
|
|
48763
49153
|
// src/cli/update-prompt-hook.ts
|
|
48764
|
-
import { existsSync as existsSync67, readFileSync as
|
|
48765
|
-
import { join as
|
|
49154
|
+
import { existsSync as existsSync67, readFileSync as readFileSync66, writeFileSync as writeFileSync26, chmodSync as chmodSync12, mkdirSync as mkdirSync39 } from "node:fs";
|
|
49155
|
+
import { join as join67 } from "node:path";
|
|
48766
49156
|
function containerHookCommand() {
|
|
48767
|
-
return
|
|
49157
|
+
return join67(CONTAINER_AGENT_DIR, ".claude", "hooks", HOOK_FILENAME);
|
|
48768
49158
|
}
|
|
48769
49159
|
function updatePromptHookScript() {
|
|
48770
49160
|
return `#!/bin/bash
|
|
@@ -48830,12 +49220,12 @@ exit 0
|
|
|
48830
49220
|
`;
|
|
48831
49221
|
}
|
|
48832
49222
|
function installUpdatePromptHook(agentDir) {
|
|
48833
|
-
const hooksDir =
|
|
49223
|
+
const hooksDir = join67(agentDir, ".claude", "hooks");
|
|
48834
49224
|
mkdirSync39(hooksDir, { recursive: true });
|
|
48835
|
-
const scriptPath =
|
|
49225
|
+
const scriptPath = join67(hooksDir, HOOK_FILENAME);
|
|
48836
49226
|
const desired = updatePromptHookScript();
|
|
48837
49227
|
let installed = false;
|
|
48838
|
-
const existing = existsSync67(scriptPath) ?
|
|
49228
|
+
const existing = existsSync67(scriptPath) ? readFileSync66(scriptPath, "utf-8") : "";
|
|
48839
49229
|
if (existing !== desired) {
|
|
48840
49230
|
writeFileSync26(scriptPath, desired, { mode: 493 });
|
|
48841
49231
|
chmodSync12(scriptPath, 493);
|
|
@@ -48845,11 +49235,11 @@ function installUpdatePromptHook(agentDir) {
|
|
|
48845
49235
|
chmodSync12(scriptPath, 493);
|
|
48846
49236
|
} catch {}
|
|
48847
49237
|
}
|
|
48848
|
-
const settingsPath =
|
|
49238
|
+
const settingsPath = join67(agentDir, ".claude", "settings.json");
|
|
48849
49239
|
if (!existsSync67(settingsPath)) {
|
|
48850
49240
|
return { scriptPath, settingsPath, installed };
|
|
48851
49241
|
}
|
|
48852
|
-
const raw =
|
|
49242
|
+
const raw = readFileSync66(settingsPath, "utf-8");
|
|
48853
49243
|
let parsed;
|
|
48854
49244
|
try {
|
|
48855
49245
|
parsed = JSON.parse(raw);
|
|
@@ -48975,7 +49365,7 @@ __export(exports_voice_sidecar_token, {
|
|
|
48975
49365
|
VOICE_SIDECAR_TOKEN_ENV: () => VOICE_SIDECAR_TOKEN_ENV
|
|
48976
49366
|
});
|
|
48977
49367
|
import { randomBytes as randomBytes12 } from "node:crypto";
|
|
48978
|
-
import { chmodSync as chmodSync13, chownSync as chownSync6, existsSync as existsSync69, mkdirSync as mkdirSync40, readFileSync as
|
|
49368
|
+
import { chmodSync as chmodSync13, chownSync as chownSync6, existsSync as existsSync69, mkdirSync as mkdirSync40, readFileSync as readFileSync67, rmSync as rmSync12, writeFileSync as writeFileSync27 } from "node:fs";
|
|
48979
49369
|
import { dirname as dirname27 } from "node:path";
|
|
48980
49370
|
async function defaultResolveOrSeedToken(home2, writeErr) {
|
|
48981
49371
|
const [{ getViaBrokerStructured: getViaBrokerStructured2, putViaBroker: putViaBroker2 }, { resolveOperatorVaultPassphrase }] = await Promise.all([
|
|
@@ -49011,7 +49401,7 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
|
|
|
49011
49401
|
if (engine !== "local") {
|
|
49012
49402
|
try {
|
|
49013
49403
|
if (existsSync69(envPath)) {
|
|
49014
|
-
const body =
|
|
49404
|
+
const body = readFileSync67(envPath, "utf-8");
|
|
49015
49405
|
if (body.includes(`${VOICE_SIDECAR_TOKEN_ENV}=`))
|
|
49016
49406
|
rmSync12(envPath);
|
|
49017
49407
|
}
|
|
@@ -49034,7 +49424,7 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
|
|
|
49034
49424
|
let body = "";
|
|
49035
49425
|
try {
|
|
49036
49426
|
if (existsSync69(envPath))
|
|
49037
|
-
body =
|
|
49427
|
+
body = readFileSync67(envPath, "utf-8");
|
|
49038
49428
|
} catch {}
|
|
49039
49429
|
const line = `${VOICE_SIDECAR_TOKEN_ENV}=${token}`;
|
|
49040
49430
|
const keyRe = new RegExp(`^${VOICE_SIDECAR_TOKEN_ENV}=.*$`, "m");
|
|
@@ -49096,12 +49486,12 @@ __export(exports_apply, {
|
|
|
49096
49486
|
DEFAULT_COMPOSE_PATH: () => DEFAULT_COMPOSE_PATH,
|
|
49097
49487
|
COMPOSE_PROJECT: () => COMPOSE_PROJECT2
|
|
49098
49488
|
});
|
|
49099
|
-
import { accessSync as accessSync2, chmodSync as chmodSync14, chownSync as chownSync7, constants as fsConstants5, copyFileSync as copyFileSync10, existsSync as existsSync70, mkdirSync as mkdirSync41, readFileSync as
|
|
49489
|
+
import { accessSync as accessSync2, chmodSync as chmodSync14, chownSync as chownSync7, constants as fsConstants5, copyFileSync as copyFileSync10, existsSync as existsSync70, mkdirSync as mkdirSync41, readFileSync as readFileSync68, readdirSync as readdirSync24, renameSync as renameSync17, statSync as statSync40, writeFileSync as writeFileSync28 } from "node:fs";
|
|
49100
49490
|
import { mkdir as mkdir2 } from "node:fs/promises";
|
|
49101
49491
|
import { spawnSync as childSpawnSync } from "node:child_process";
|
|
49102
49492
|
import readline from "node:readline";
|
|
49103
|
-
import { dirname as dirname28, join as
|
|
49104
|
-
import { homedir as
|
|
49493
|
+
import { dirname as dirname28, join as join69, resolve as resolve40 } from "node:path";
|
|
49494
|
+
import { homedir as homedir37 } from "node:os";
|
|
49105
49495
|
import { execFileSync as execFileSync20 } from "node:child_process";
|
|
49106
49496
|
function effectiveLiteLLMEnabled(config, agentResolvedLitellm) {
|
|
49107
49497
|
return agentResolvedLitellm?.enabled ?? config.litellm?.enabled ?? false;
|
|
@@ -49112,7 +49502,7 @@ async function resolveOperatorVaultPassphrase(home2) {
|
|
|
49112
49502
|
return envPass;
|
|
49113
49503
|
try {
|
|
49114
49504
|
const { readAutoUnlockFile: readAutoUnlockFile2 } = await Promise.resolve().then(() => (init_auto_unlock(), exports_auto_unlock));
|
|
49115
|
-
const blobPath =
|
|
49505
|
+
const blobPath = join69(home2, ".switchroom", "vault-auto-unlock");
|
|
49116
49506
|
const pass = readAutoUnlockFile2(blobPath);
|
|
49117
49507
|
return pass && pass.length > 0 ? pass : null;
|
|
49118
49508
|
} catch {
|
|
@@ -49121,9 +49511,9 @@ async function resolveOperatorVaultPassphrase(home2) {
|
|
|
49121
49511
|
}
|
|
49122
49512
|
function materializeLitellmMasterKeyForBroker(masterKey, home2 = process.env.HOME ?? "/root") {
|
|
49123
49513
|
try {
|
|
49124
|
-
const stateDir =
|
|
49514
|
+
const stateDir = join69(home2, ".switchroom", "state", "auth-broker");
|
|
49125
49515
|
mkdirSync41(stateDir, { recursive: true, mode: 448 });
|
|
49126
|
-
const path7 =
|
|
49516
|
+
const path7 = join69(stateDir, LITELLM_MASTER_KEY_STATE_BASENAME);
|
|
49127
49517
|
writeFileSync28(path7, masterKey.trim() + `
|
|
49128
49518
|
`, { mode: 384 });
|
|
49129
49519
|
try {
|
|
@@ -49159,7 +49549,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
49159
49549
|
Promise.resolve().then(() => (init_provision(), exports_provision)),
|
|
49160
49550
|
Promise.resolve().then(() => (init_telegram_yaml(), exports_telegram_yaml))
|
|
49161
49551
|
]);
|
|
49162
|
-
const passphrase = await resolveOperatorVaultPassphrase(ctx.home ??
|
|
49552
|
+
const passphrase = await resolveOperatorVaultPassphrase(ctx.home ?? homedir37());
|
|
49163
49553
|
if (passphrase === null) {
|
|
49164
49554
|
const { getViaBrokerStructured: getViaBrokerStructured3 } = await Promise.resolve().then(() => (init_client(), exports_client));
|
|
49165
49555
|
const alreadyProvisioned = [];
|
|
@@ -49222,7 +49612,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
49222
49612
|
let configText = null;
|
|
49223
49613
|
if (switchroomConfigPath && existsSync70(switchroomConfigPath)) {
|
|
49224
49614
|
try {
|
|
49225
|
-
configText =
|
|
49615
|
+
configText = readFileSync68(switchroomConfigPath, "utf-8");
|
|
49226
49616
|
} catch (err) {
|
|
49227
49617
|
ctx.writeErr(source_default.yellow(` ! litellm: could not read config for ACL grants (${err.message}); keys will be provisioned but agents may lack read-ACL.
|
|
49228
49618
|
`));
|
|
@@ -49269,7 +49659,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
49269
49659
|
}
|
|
49270
49660
|
}
|
|
49271
49661
|
if (masterKey && !brokerKeyMaterialized) {
|
|
49272
|
-
const mat = materializeLitellmMasterKeyForBroker(masterKey, ctx.home ??
|
|
49662
|
+
const mat = materializeLitellmMasterKeyForBroker(masterKey, ctx.home ?? homedir37());
|
|
49273
49663
|
brokerKeyMaterialized = true;
|
|
49274
49664
|
if (!mat.ok) {
|
|
49275
49665
|
ctx.writeErr(source_default.yellow(` ! litellm: could not materialize master key for auth-broker external-spend (${mat.error})
|
|
@@ -49552,7 +49942,7 @@ function resolveVaultBindMountDir(homeDir, ctx) {
|
|
|
49552
49942
|
if (isCustomPath && ctx.customVaultPath) {
|
|
49553
49943
|
return dirname28(ctx.customVaultPath);
|
|
49554
49944
|
}
|
|
49555
|
-
return
|
|
49945
|
+
return join69(homeDir, ".switchroom", "vault");
|
|
49556
49946
|
}
|
|
49557
49947
|
function inspectVaultBindMountDir(vaultDir) {
|
|
49558
49948
|
if (!existsSync70(vaultDir))
|
|
@@ -49583,41 +49973,41 @@ function hasVaultRefs(value) {
|
|
|
49583
49973
|
async function ensureHostMountSources(config) {
|
|
49584
49974
|
const home2 = resolveHostHomeForCompose();
|
|
49585
49975
|
const dirs = [
|
|
49586
|
-
|
|
49587
|
-
|
|
49588
|
-
|
|
49589
|
-
|
|
49590
|
-
|
|
49976
|
+
join69(home2, ".switchroom", "approvals"),
|
|
49977
|
+
join69(home2, ".switchroom", "scheduler"),
|
|
49978
|
+
join69(home2, ".switchroom", "logs"),
|
|
49979
|
+
join69(home2, ".switchroom", "compose"),
|
|
49980
|
+
join69(home2, ".switchroom", "broker-operator")
|
|
49591
49981
|
];
|
|
49592
49982
|
for (const name of Object.keys(config.agents)) {
|
|
49593
|
-
dirs.push(
|
|
49594
|
-
dirs.push(
|
|
49595
|
-
dirs.push(
|
|
49596
|
-
dirs.push(
|
|
49597
|
-
if (existsSync70(
|
|
49598
|
-
dirs.push(
|
|
49983
|
+
dirs.push(join69(home2, ".switchroom", "agents", name));
|
|
49984
|
+
dirs.push(join69(home2, ".switchroom", "logs", name));
|
|
49985
|
+
dirs.push(join69(home2, ".claude", "projects", name));
|
|
49986
|
+
dirs.push(join69(home2, ".switchroom", "audit", name));
|
|
49987
|
+
if (existsSync70(join69(home2, ".switchroom-config"))) {
|
|
49988
|
+
dirs.push(join69(home2, ".switchroom-config", "agents", name, "personal-skills"));
|
|
49599
49989
|
}
|
|
49600
49990
|
}
|
|
49601
49991
|
for (const dir of dirs) {
|
|
49602
49992
|
await mkdir2(dir, { recursive: true });
|
|
49603
49993
|
}
|
|
49604
|
-
const autoUnlockPath =
|
|
49994
|
+
const autoUnlockPath = join69(home2, ".switchroom", "vault-auto-unlock");
|
|
49605
49995
|
if (!existsSync70(autoUnlockPath)) {
|
|
49606
49996
|
writeFileSync28(autoUnlockPath, "", { mode: 384 });
|
|
49607
49997
|
}
|
|
49608
|
-
const auditLogPath =
|
|
49998
|
+
const auditLogPath = join69(home2, ".switchroom", "vault-audit.log");
|
|
49609
49999
|
if (!existsSync70(auditLogPath)) {
|
|
49610
50000
|
writeFileSync28(auditLogPath, "", { mode: 420 });
|
|
49611
50001
|
}
|
|
49612
50002
|
const grantsDbDir = getGrantsDbDir(home2);
|
|
49613
50003
|
mkdirSync41(grantsDbDir, { recursive: true, mode: 448 });
|
|
49614
50004
|
migrateLegacyGrantsDbLocation(getGrantsDbPath(home2));
|
|
49615
|
-
const hostdAuditLogPath =
|
|
50005
|
+
const hostdAuditLogPath = join69(home2, ".switchroom", "host-control-audit.log");
|
|
49616
50006
|
if (!existsSync70(hostdAuditLogPath)) {
|
|
49617
50007
|
writeFileSync28(hostdAuditLogPath, "", { mode: 420 });
|
|
49618
50008
|
}
|
|
49619
50009
|
for (const name of Object.keys(config.agents)) {
|
|
49620
|
-
const tokenPath =
|
|
50010
|
+
const tokenPath = join69(home2, ".switchroom", "agents", name, ".vault-token");
|
|
49621
50011
|
if (!existsSync70(tokenPath)) {
|
|
49622
50012
|
writeFileSync28(tokenPath, "", { mode: 384 });
|
|
49623
50013
|
}
|
|
@@ -49626,15 +50016,15 @@ async function ensureHostMountSources(config) {
|
|
|
49626
50016
|
chownSync7(tokenPath, uid, uid);
|
|
49627
50017
|
} catch {}
|
|
49628
50018
|
}
|
|
49629
|
-
const fleetDir =
|
|
50019
|
+
const fleetDir = join69(home2, ".switchroom", "fleet");
|
|
49630
50020
|
await mkdir2(fleetDir, { recursive: true });
|
|
49631
|
-
const invariantsPath =
|
|
50021
|
+
const invariantsPath = join69(fleetDir, "switchroom-invariants.md");
|
|
49632
50022
|
const invariantsCanonical = renderFleetInvariants();
|
|
49633
|
-
const invariantsCurrent = existsSync70(invariantsPath) ?
|
|
50023
|
+
const invariantsCurrent = existsSync70(invariantsPath) ? readFileSync68(invariantsPath, "utf-8") : null;
|
|
49634
50024
|
if (invariantsCurrent !== invariantsCanonical) {
|
|
49635
50025
|
writeFileSync28(invariantsPath, invariantsCanonical, { mode: 420 });
|
|
49636
50026
|
}
|
|
49637
|
-
const fleetClaudePath =
|
|
50027
|
+
const fleetClaudePath = join69(fleetDir, "CLAUDE.md");
|
|
49638
50028
|
if (!existsSync70(fleetClaudePath)) {
|
|
49639
50029
|
writeFileSync28(fleetClaudePath, renderFleetDefaultsClaudeMd(), {
|
|
49640
50030
|
mode: 420
|
|
@@ -49718,10 +50108,10 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
|
49718
50108
|
`));
|
|
49719
50109
|
}
|
|
49720
50110
|
}
|
|
49721
|
-
function writeInstallTypeCache(homeDir =
|
|
50111
|
+
function writeInstallTypeCache(homeDir = homedir37()) {
|
|
49722
50112
|
const ctx = detectInstallType();
|
|
49723
|
-
const dir =
|
|
49724
|
-
const out =
|
|
50113
|
+
const dir = join69(homeDir, ".switchroom");
|
|
50114
|
+
const out = join69(dir, "install-type.json");
|
|
49725
50115
|
const tmp = `${out}.tmp`;
|
|
49726
50116
|
mkdirSync41(dir, { recursive: true });
|
|
49727
50117
|
const payload = {
|
|
@@ -49788,17 +50178,17 @@ Applying switchroom config...
|
|
|
49788
50178
|
writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
|
|
49789
50179
|
`));
|
|
49790
50180
|
try {
|
|
49791
|
-
installUpdatePromptHook(
|
|
50181
|
+
installUpdatePromptHook(join69(agentsDir, name));
|
|
49792
50182
|
} catch (hookErr) {
|
|
49793
50183
|
writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
|
|
49794
50184
|
`));
|
|
49795
50185
|
}
|
|
49796
|
-
await refreshAgentConnectionHealth(config, name,
|
|
50186
|
+
await refreshAgentConnectionHealth(config, name, join69(agentsDir, name), {
|
|
49797
50187
|
vaultAclReader: connHealthVaultAclReader
|
|
49798
50188
|
});
|
|
49799
50189
|
try {
|
|
49800
50190
|
const uid = allocateAgentUid(name);
|
|
49801
|
-
alignAgentUid(name,
|
|
50191
|
+
alignAgentUid(name, join69(agentsDir, name), uid, {
|
|
49802
50192
|
confirm: !options.nonInteractive,
|
|
49803
50193
|
writeOut
|
|
49804
50194
|
});
|
|
@@ -49842,7 +50232,7 @@ Applying switchroom config...
|
|
|
49842
50232
|
writeOut,
|
|
49843
50233
|
writeErr,
|
|
49844
50234
|
failures,
|
|
49845
|
-
home:
|
|
50235
|
+
home: homedir37()
|
|
49846
50236
|
});
|
|
49847
50237
|
}
|
|
49848
50238
|
await ensureHostMountSources(config);
|
|
@@ -49850,7 +50240,7 @@ Applying switchroom config...
|
|
|
49850
50240
|
for (const name of agentNames) {
|
|
49851
50241
|
try {
|
|
49852
50242
|
const uid = allocateAgentUid(name);
|
|
49853
|
-
alignAgentUid(name,
|
|
50243
|
+
alignAgentUid(name, join69(agentsDir, name), uid, {
|
|
49854
50244
|
confirm: !options.nonInteractive,
|
|
49855
50245
|
writeOut
|
|
49856
50246
|
});
|
|
@@ -49864,7 +50254,7 @@ Applying switchroom config...
|
|
|
49864
50254
|
}
|
|
49865
50255
|
const vaultPathConfigured = config.vault?.path;
|
|
49866
50256
|
const customVaultPath = vaultPathConfigured ? resolvePath(vaultPathConfigured) : undefined;
|
|
49867
|
-
const migrationResult = migrateVaultLayout(
|
|
50257
|
+
const migrationResult = migrateVaultLayout(homedir37(), {
|
|
49868
50258
|
customVaultPath
|
|
49869
50259
|
});
|
|
49870
50260
|
switch (migrationResult.kind) {
|
|
@@ -49890,7 +50280,7 @@ Applying switchroom config...
|
|
|
49890
50280
|
writeErr(formatDivergentRecoveryMessage(migrationResult.details));
|
|
49891
50281
|
process.exit(4);
|
|
49892
50282
|
}
|
|
49893
|
-
const postMigrationInspect = inspectVaultLayout(
|
|
50283
|
+
const postMigrationInspect = inspectVaultLayout(homedir37());
|
|
49894
50284
|
const acceptable = [
|
|
49895
50285
|
"no-vault",
|
|
49896
50286
|
"already-migrated",
|
|
@@ -49905,7 +50295,7 @@ Expected one of: ${acceptable.join(", ")}
|
|
|
49905
50295
|
`));
|
|
49906
50296
|
process.exit(5);
|
|
49907
50297
|
}
|
|
49908
|
-
const vaultDir = resolveVaultBindMountDir(
|
|
50298
|
+
const vaultDir = resolveVaultBindMountDir(homedir37(), {
|
|
49909
50299
|
migrationKind: migrationResult.kind,
|
|
49910
50300
|
customVaultPath
|
|
49911
50301
|
});
|
|
@@ -49936,7 +50326,7 @@ vault.enc.lock (PID-file flock from saveVault), and
|
|
|
49936
50326
|
});
|
|
49937
50327
|
if (!skipScaffold) {
|
|
49938
50328
|
const { provisionVoiceSidecarToken: provisionVoiceSidecarToken2 } = await Promise.resolve().then(() => (init_voice_sidecar_token(), exports_voice_sidecar_token));
|
|
49939
|
-
await provisionVoiceSidecarToken2(composePath,
|
|
50329
|
+
await provisionVoiceSidecarToken2(composePath, homedir37(), {
|
|
49940
50330
|
writeOut,
|
|
49941
50331
|
writeErr,
|
|
49942
50332
|
operatorUid
|
|
@@ -49952,7 +50342,7 @@ Wrote `) + displayComposePath + source_default.gray(` (${composeBytes} bytes)
|
|
|
49952
50342
|
writeOut(source_default.gray(` (If pull returns 401, login to ghcr.io first: see docs/operators/install.md#ghcr-auth)
|
|
49953
50343
|
`));
|
|
49954
50344
|
if (process.geteuid?.() === 0 && operatorUid !== undefined) {
|
|
49955
|
-
const restored = restoreOperatorOwnership(
|
|
50345
|
+
const restored = restoreOperatorOwnership(homedir37(), operatorUid);
|
|
49956
50346
|
if (restored.length > 0) {
|
|
49957
50347
|
writeOut(source_default.gray(` Restored operator ownership of ${restored.length} ~/.switchroom path(s)
|
|
49958
50348
|
`));
|
|
@@ -50057,7 +50447,7 @@ Dry-run: validating switchroom config (no changes will be written)...
|
|
|
50057
50447
|
}
|
|
50058
50448
|
}
|
|
50059
50449
|
try {
|
|
50060
|
-
const probe2 = await probeVaultProvisioning(config, agentNames,
|
|
50450
|
+
const probe2 = await probeVaultProvisioning(config, agentNames, homedir37());
|
|
50061
50451
|
if (probe2.alreadyProvisioned.length > 0) {
|
|
50062
50452
|
info.push(`litellm: ${probe2.alreadyProvisioned.length} agent(s) already provisioned (${probe2.alreadyProvisioned.join(", ")}).`);
|
|
50063
50453
|
}
|
|
@@ -50210,7 +50600,7 @@ function findUnwritableAgentDirs(config, opts) {
|
|
|
50210
50600
|
const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
|
|
50211
50601
|
const unwritable = [];
|
|
50212
50602
|
for (const name of targets) {
|
|
50213
|
-
const startSh =
|
|
50603
|
+
const startSh = join69(agentsDir, name, "start.sh");
|
|
50214
50604
|
if (!existsSync70(startSh))
|
|
50215
50605
|
continue;
|
|
50216
50606
|
try {
|
|
@@ -50416,7 +50806,7 @@ var init_apply = __esm(() => {
|
|
|
50416
50806
|
switchroom: switchroom_default,
|
|
50417
50807
|
minimal: minimal_default
|
|
50418
50808
|
};
|
|
50419
|
-
DEFAULT_COMPOSE_PATH =
|
|
50809
|
+
DEFAULT_COMPOSE_PATH = join69(homedir37(), ".switchroom", "compose", "docker-compose.yml");
|
|
50420
50810
|
IN_AGENT_CONTAINER_APPLY_MSG = "`switchroom apply`'s full per-agent scaffold cannot run from inside an " + "agent container \u2014 this is a host/hostd operation by construction " + "(no vault at the container HOME, and no `docker compose` v2 plugin here).\nTo roll the fleet to a new version, drive the hostd rollout (`mcp__hostd__rollout`): it runs a `--compose-only` apply plus a per-agent restart-reconcile, and each agent refreshes its own templates " + `on restart \u2014 the roll completes without any agent running a full apply.
|
|
50421
50811
|
` + "A full host-side `sudo switchroom apply` is only needed for structural changes (compose regeneration / new-agent scaffolding), and is run by the operator on the host, never from inside an agent.";
|
|
50422
50812
|
SELF_ELEVATE_PRESERVED_ENV = [
|
|
@@ -50434,12 +50824,12 @@ import { createHash as createHash15 } from "node:crypto";
|
|
|
50434
50824
|
import {
|
|
50435
50825
|
existsSync as existsSync71,
|
|
50436
50826
|
lstatSync as lstatSync9,
|
|
50437
|
-
readFileSync as
|
|
50827
|
+
readFileSync as readFileSync69,
|
|
50438
50828
|
readdirSync as readdirSync25,
|
|
50439
50829
|
readlinkSync as readlinkSync7,
|
|
50440
50830
|
writeFileSync as writeFileSync29
|
|
50441
50831
|
} from "node:fs";
|
|
50442
|
-
import { dirname as dirname29, isAbsolute as isAbsolute5, join as
|
|
50832
|
+
import { dirname as dirname29, isAbsolute as isAbsolute5, join as join70, resolve as resolve41 } from "node:path";
|
|
50443
50833
|
async function detectComposeDrift(opts) {
|
|
50444
50834
|
let result;
|
|
50445
50835
|
try {
|
|
@@ -50472,7 +50862,7 @@ function detectHooksSurfaceDrift(name, agentConfig, agentDir, config, configPath
|
|
|
50472
50862
|
useSwitchroomPlugin: usesSwitchroomTelegramPlugin(agentConfig),
|
|
50473
50863
|
configPath
|
|
50474
50864
|
});
|
|
50475
|
-
const settingsPath =
|
|
50865
|
+
const settingsPath = join70(agentDir, ".claude", "settings.json");
|
|
50476
50866
|
if (!existsSync71(settingsPath)) {
|
|
50477
50867
|
return [
|
|
50478
50868
|
{
|
|
@@ -50485,7 +50875,7 @@ function detectHooksSurfaceDrift(name, agentConfig, agentDir, config, configPath
|
|
|
50485
50875
|
}
|
|
50486
50876
|
let actual;
|
|
50487
50877
|
try {
|
|
50488
|
-
const raw = JSON.parse(
|
|
50878
|
+
const raw = JSON.parse(readFileSync69(settingsPath, "utf-8"));
|
|
50489
50879
|
actual = raw.hooks ?? {};
|
|
50490
50880
|
} catch {
|
|
50491
50881
|
return [
|
|
@@ -50525,7 +50915,7 @@ function detectTemplateStampDrift(name, agentConfig, agentDir, opts = {}) {
|
|
|
50525
50915
|
}
|
|
50526
50916
|
function detectSkillsDrift(name, agentDir) {
|
|
50527
50917
|
const findings = [];
|
|
50528
|
-
const skillsDir =
|
|
50918
|
+
const skillsDir = join70(agentDir, ".claude", "skills");
|
|
50529
50919
|
const liveEntries = new Set;
|
|
50530
50920
|
if (existsSync71(skillsDir)) {
|
|
50531
50921
|
let entries = [];
|
|
@@ -50535,7 +50925,7 @@ function detectSkillsDrift(name, agentDir) {
|
|
|
50535
50925
|
entries = [];
|
|
50536
50926
|
}
|
|
50537
50927
|
for (const entry of entries) {
|
|
50538
|
-
const p =
|
|
50928
|
+
const p = join70(skillsDir, entry);
|
|
50539
50929
|
let st;
|
|
50540
50930
|
try {
|
|
50541
50931
|
st = lstatSync9(p);
|
|
@@ -50562,7 +50952,7 @@ function detectSkillsDrift(name, agentDir) {
|
|
|
50562
50952
|
liveEntries.add(entry);
|
|
50563
50953
|
}
|
|
50564
50954
|
}
|
|
50565
|
-
const overlayDir =
|
|
50955
|
+
const overlayDir = join70(agentDir, "skills.d");
|
|
50566
50956
|
if (existsSync71(overlayDir)) {
|
|
50567
50957
|
let overlayEntries = [];
|
|
50568
50958
|
try {
|
|
@@ -50589,8 +50979,8 @@ function detectSkillsDrift(name, agentDir) {
|
|
|
50589
50979
|
function locateInstallBinDir() {
|
|
50590
50980
|
let dir = import.meta.dirname;
|
|
50591
50981
|
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
50592
|
-
if (existsSync71(
|
|
50593
|
-
return
|
|
50982
|
+
if (existsSync71(join70(dir, "dependencies.json")) && existsSync71(join70(dir, "bin"))) {
|
|
50983
|
+
return join70(dir, "bin");
|
|
50594
50984
|
}
|
|
50595
50985
|
dir = dirname29(dir);
|
|
50596
50986
|
}
|
|
@@ -50610,7 +51000,7 @@ function detectHookScriptDrift(name, opts = {}) {
|
|
|
50610
51000
|
}
|
|
50611
51001
|
for (const f of names) {
|
|
50612
51002
|
try {
|
|
50613
|
-
expected.set(f, createHash15("sha256").update(
|
|
51003
|
+
expected.set(f, createHash15("sha256").update(readFileSync69(join70(binDir, f))).digest("hex"));
|
|
50614
51004
|
} catch {}
|
|
50615
51005
|
}
|
|
50616
51006
|
if (expected.size === 0)
|
|
@@ -50662,17 +51052,17 @@ function detectHindsightRecallTunableDrift(name, agentConfig, agentDir, config)
|
|
|
50662
51052
|
const resolved = resolveAgentConfig(config.defaults, config.profiles, agentConfig);
|
|
50663
51053
|
if (resolved.memory?.auto_recall === false)
|
|
50664
51054
|
return [];
|
|
50665
|
-
const pluginDir =
|
|
51055
|
+
const pluginDir = join70(agentDir, ".claude", "plugins", "hindsight-memory");
|
|
50666
51056
|
if (!existsSync71(pluginDir))
|
|
50667
51057
|
return [];
|
|
50668
51058
|
const expected = resolveHindsightRecallTunables(resolved.memory?.recall);
|
|
50669
51059
|
const findings = [];
|
|
50670
51060
|
const mismatches = [];
|
|
50671
|
-
const hooksPath =
|
|
51061
|
+
const hooksPath = join70(pluginDir, "hooks", "hooks.json");
|
|
50672
51062
|
if (existsSync71(hooksPath)) {
|
|
50673
51063
|
let installedHookTimeout = null;
|
|
50674
51064
|
try {
|
|
50675
|
-
installedHookTimeout = readHooksRecallTimeout(
|
|
51065
|
+
installedHookTimeout = readHooksRecallTimeout(readFileSync69(hooksPath, "utf-8"));
|
|
50676
51066
|
} catch {
|
|
50677
51067
|
installedHookTimeout = null;
|
|
50678
51068
|
}
|
|
@@ -50682,11 +51072,11 @@ function detectHindsightRecallTunableDrift(name, agentConfig, agentDir, config)
|
|
|
50682
51072
|
mismatches.push(`hook ceiling is ${installedHookTimeout}s, expected ` + `${expected.hookTimeoutSeconds}s (memory.recall.hook_timeout_seconds)`);
|
|
50683
51073
|
}
|
|
50684
51074
|
}
|
|
50685
|
-
const settingsPath =
|
|
51075
|
+
const settingsPath = join70(pluginDir, "settings.json");
|
|
50686
51076
|
if (existsSync71(settingsPath)) {
|
|
50687
51077
|
let settings = null;
|
|
50688
51078
|
try {
|
|
50689
|
-
settings = JSON.parse(
|
|
51079
|
+
settings = JSON.parse(readFileSync69(settingsPath, "utf-8"));
|
|
50690
51080
|
} catch {
|
|
50691
51081
|
settings = null;
|
|
50692
51082
|
}
|
|
@@ -50740,7 +51130,7 @@ function writeDriftReport(agentDir, findings) {
|
|
|
50740
51130
|
generatedAt: new Date().toISOString(),
|
|
50741
51131
|
findings: findings.map(({ surface, detail }) => ({ surface, detail }))
|
|
50742
51132
|
};
|
|
50743
|
-
writeFileSync29(
|
|
51133
|
+
writeFileSync29(join70(agentDir, DRIFT_REPORT_FILE), JSON.stringify(report, null, 2) + `
|
|
50744
51134
|
`, "utf-8");
|
|
50745
51135
|
} catch {}
|
|
50746
51136
|
}
|
|
@@ -50994,19 +51384,79 @@ var init_component_versions = __esm(() => {
|
|
|
50994
51384
|
SEMVER_TAG_RE = /^v\d+\.\d+\.\d+$/;
|
|
50995
51385
|
});
|
|
50996
51386
|
|
|
51387
|
+
// src/cli/component-scope.ts
|
|
51388
|
+
function imageRepoName(imageRef) {
|
|
51389
|
+
if (!imageRef)
|
|
51390
|
+
return null;
|
|
51391
|
+
const at = imageRef.indexOf("@");
|
|
51392
|
+
const withoutDigest = at >= 0 ? imageRef.slice(0, at) : imageRef;
|
|
51393
|
+
const slash = withoutDigest.lastIndexOf("/");
|
|
51394
|
+
if (slash < 0)
|
|
51395
|
+
return null;
|
|
51396
|
+
const afterSlash = withoutDigest.slice(slash + 1);
|
|
51397
|
+
const colon = afterSlash.indexOf(":");
|
|
51398
|
+
const repo = colon >= 0 ? afterSlash.slice(0, colon) : afterSlash;
|
|
51399
|
+
return /^switchroom-[a-z0-9-]+$/.test(repo) ? repo : null;
|
|
51400
|
+
}
|
|
51401
|
+
function classifyComponent(c) {
|
|
51402
|
+
if (c.kind === "cli")
|
|
51403
|
+
return { kind: "cli" };
|
|
51404
|
+
const repo = imageRepoName(c.imageRef);
|
|
51405
|
+
switch (repo) {
|
|
51406
|
+
case "switchroom-web":
|
|
51407
|
+
return { kind: "web" };
|
|
51408
|
+
case "switchroom-hostd":
|
|
51409
|
+
return { kind: "hostd" };
|
|
51410
|
+
case "switchroom-hindsight":
|
|
51411
|
+
return { kind: "hindsight" };
|
|
51412
|
+
case "switchroom-agent": {
|
|
51413
|
+
const agent = c.name.startsWith("switchroom-") ? c.name.slice("switchroom-".length) : c.name;
|
|
51414
|
+
return agent ? { kind: "fleet", agent } : { kind: "fleet" };
|
|
51415
|
+
}
|
|
51416
|
+
default:
|
|
51417
|
+
return { kind: "fleet" };
|
|
51418
|
+
}
|
|
51419
|
+
}
|
|
51420
|
+
function remediationFor(owner, target) {
|
|
51421
|
+
switch (owner.kind) {
|
|
51422
|
+
case "cli":
|
|
51423
|
+
return `switchroom update (self-updates the host CLI to ${target} first, then everything else)`;
|
|
51424
|
+
case "web":
|
|
51425
|
+
return `switchroom webd install --tag ${target} (separate compose project \u2014 regenerates + recreates switchroom-web)`;
|
|
51426
|
+
case "hostd":
|
|
51427
|
+
return `switchroom hostd install --tag ${target} (regenerates the hostd compose project \u2014 both hostd and the autoheal sidecar)`;
|
|
51428
|
+
case "hindsight":
|
|
51429
|
+
return `switchroom memory setup --recreate --tag ${target} (standalone container \u2014 not a compose service)`;
|
|
51430
|
+
case "fleet":
|
|
51431
|
+
return owner.agent ? `switchroom rollout --pin ${target} --agents ${owner.agent} (agent-fleet compose project)` : `switchroom rollout --pin ${target} (agent-fleet compose project \u2014 the singletons self-heal on the first agent restart)`;
|
|
51432
|
+
}
|
|
51433
|
+
}
|
|
51434
|
+
function gatedOwners(stepKinds, hostdContext) {
|
|
51435
|
+
const kinds = new Set(stepKinds);
|
|
51436
|
+
const owners = new Set(["fleet"]);
|
|
51437
|
+
if (kinds.has("refresh-web"))
|
|
51438
|
+
owners.add("web");
|
|
51439
|
+
if (kinds.has("refresh-hindsight"))
|
|
51440
|
+
owners.add("hindsight");
|
|
51441
|
+
if (kinds.has("refresh-hostd") || hostdContext)
|
|
51442
|
+
owners.add("hostd");
|
|
51443
|
+
return owners;
|
|
51444
|
+
}
|
|
51445
|
+
function partitionDrift(behind, opts) {
|
|
51446
|
+
const gated = [];
|
|
51447
|
+
const exempt = [];
|
|
51448
|
+
for (const c of behind) {
|
|
51449
|
+
const owner = classifyComponent(c);
|
|
51450
|
+
const inScope = owner.kind === "fleet" && owner.agent !== undefined ? opts.rolledAgents.has(owner.agent) : opts.owners.has(owner.kind);
|
|
51451
|
+
(inScope ? gated : exempt).push(c);
|
|
51452
|
+
}
|
|
51453
|
+
return { gated, exempt };
|
|
51454
|
+
}
|
|
51455
|
+
|
|
50997
51456
|
// src/cli/doctor-component-versions.ts
|
|
50998
51457
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
50999
51458
|
function fixFor(c, target) {
|
|
51000
|
-
|
|
51001
|
-
return `switchroom update (self-updates the host CLI to ${target} first, then everything else)`;
|
|
51002
|
-
}
|
|
51003
|
-
if (c.name === "switchroom-web") {
|
|
51004
|
-
return `switchroom webd install --tag ${target} (run host-side: webd install fails inside hostd when the hostd compose predates SWITCHROOM_HOSTD_OPERATOR_UID)`;
|
|
51005
|
-
}
|
|
51006
|
-
if (c.name === "switchroom-hindsight-autoheal" || c.name === "switchroom-hostd") {
|
|
51007
|
-
return `switchroom hostd install --tag ${target} (regenerates the hostd compose project \u2014 both hostd and the autoheal sidecar)`;
|
|
51008
|
-
}
|
|
51009
|
-
return `switchroom update`;
|
|
51459
|
+
return remediationFor(classifyComponent(c), target);
|
|
51010
51460
|
}
|
|
51011
51461
|
function runComponentVersionChecks(config, opts = {}) {
|
|
51012
51462
|
const exec = opts.exec ?? defaultExec2;
|
|
@@ -51064,7 +51514,7 @@ var init_doctor_component_versions = __esm(() => {
|
|
|
51064
51514
|
});
|
|
51065
51515
|
|
|
51066
51516
|
// src/cli/doctor-scaffold-wiring.ts
|
|
51067
|
-
import { join as
|
|
51517
|
+
import { join as join71, resolve as resolve43 } from "node:path";
|
|
51068
51518
|
function readJson2(d, path7) {
|
|
51069
51519
|
if (!d.existsSync(path7))
|
|
51070
51520
|
return { kind: "absent" };
|
|
@@ -51107,8 +51557,8 @@ function checkIntegrationScaffoldWiring(args) {
|
|
|
51107
51557
|
});
|
|
51108
51558
|
continue;
|
|
51109
51559
|
}
|
|
51110
|
-
const mcpPath =
|
|
51111
|
-
const claudeJsonPath =
|
|
51560
|
+
const mcpPath = join71(agentDir, ".mcp.json");
|
|
51561
|
+
const claudeJsonPath = join71(agentDir, ".claude", ".claude.json");
|
|
51112
51562
|
const mcpRead = readJson2(deps, mcpPath);
|
|
51113
51563
|
const trustRead = readJson2(deps, claudeJsonPath);
|
|
51114
51564
|
if (mcpRead.kind === "unreadable" || trustRead.kind === "unreadable") {
|
|
@@ -51172,14 +51622,14 @@ import {
|
|
|
51172
51622
|
existsSync as realExistsSync3,
|
|
51173
51623
|
readFileSync as realReadFileSync3
|
|
51174
51624
|
} from "node:fs";
|
|
51175
|
-
import { join as
|
|
51176
|
-
import { homedir as
|
|
51625
|
+
import { join as join72 } from "node:path";
|
|
51626
|
+
import { homedir as homedir38 } from "node:os";
|
|
51177
51627
|
function resolveDeps3(deps) {
|
|
51178
|
-
const home2 = deps.homeDir?.() ??
|
|
51628
|
+
const home2 = deps.homeDir?.() ?? homedir38();
|
|
51179
51629
|
return {
|
|
51180
51630
|
existsSync: deps.existsSync ?? realExistsSync3,
|
|
51181
51631
|
readFileSync: deps.readFileSync ?? realReadFileSync3,
|
|
51182
|
-
agentsDir:
|
|
51632
|
+
agentsDir: join72(home2, ".switchroom", "agents"),
|
|
51183
51633
|
now: deps.now ?? Date.now
|
|
51184
51634
|
};
|
|
51185
51635
|
}
|
|
@@ -51269,7 +51719,7 @@ function heartbeatFileName(config, agentName, account) {
|
|
|
51269
51719
|
return "m365-launcher.heartbeat.json";
|
|
51270
51720
|
}
|
|
51271
51721
|
function readHeartbeatFile(d, agentName, fileName) {
|
|
51272
|
-
const path7 =
|
|
51722
|
+
const path7 = join72(d.agentsDir, agentName, fileName);
|
|
51273
51723
|
if (!d.existsSync(path7)) {
|
|
51274
51724
|
return { error: "heartbeat file missing \u2014 launcher has not yet started" };
|
|
51275
51725
|
}
|
|
@@ -51375,15 +51825,15 @@ import {
|
|
|
51375
51825
|
readFileSync as realReadFileSync4,
|
|
51376
51826
|
statSync as realStatSync2
|
|
51377
51827
|
} from "node:fs";
|
|
51378
|
-
import { join as
|
|
51379
|
-
import { homedir as
|
|
51828
|
+
import { join as join73 } from "node:path";
|
|
51829
|
+
import { homedir as homedir39 } from "node:os";
|
|
51380
51830
|
function resolveDeps4(deps) {
|
|
51381
|
-
const home2 = deps.homeDir?.() ??
|
|
51831
|
+
const home2 = deps.homeDir?.() ?? homedir39();
|
|
51382
51832
|
return {
|
|
51383
51833
|
existsSync: deps.existsSync ?? realExistsSync4,
|
|
51384
51834
|
readFileSync: deps.readFileSync ?? realReadFileSync4,
|
|
51385
51835
|
statSync: deps.statSync ?? realStatSync2,
|
|
51386
|
-
agentsDir:
|
|
51836
|
+
agentsDir: join73(home2, ".switchroom", "agents"),
|
|
51387
51837
|
now: deps.now ?? Date.now,
|
|
51388
51838
|
vaultAclReader: deps.vaultAclReader ?? (async () => ({ kind: "unreachable", msg: "no default reader wired" }))
|
|
51389
51839
|
};
|
|
@@ -51508,7 +51958,7 @@ function checkLauncherHeartbeat2(notionAgents, d) {
|
|
|
51508
51958
|
return [];
|
|
51509
51959
|
const results = [];
|
|
51510
51960
|
for (const name of notionAgents) {
|
|
51511
|
-
const heartbeatPath =
|
|
51961
|
+
const heartbeatPath = join73(d.agentsDir, name, "notion-launcher.heartbeat.json");
|
|
51512
51962
|
if (!d.existsSync(heartbeatPath)) {
|
|
51513
51963
|
results.push({
|
|
51514
51964
|
name: `notion:launcher-heartbeat:${name}`,
|
|
@@ -51623,10 +52073,10 @@ import {
|
|
|
51623
52073
|
readdirSync as realReaddirSync2,
|
|
51624
52074
|
statSync as realStatSync3
|
|
51625
52075
|
} from "node:fs";
|
|
51626
|
-
import { homedir as
|
|
51627
|
-
import { join as
|
|
52076
|
+
import { homedir as homedir40 } from "node:os";
|
|
52077
|
+
import { join as join74 } from "node:path";
|
|
51628
52078
|
function runCredentialsMigrationChecks(config, deps = {}) {
|
|
51629
|
-
const credDir = deps.credentialsDir ??
|
|
52079
|
+
const credDir = deps.credentialsDir ?? join74(homedir40(), ".switchroom", "credentials");
|
|
51630
52080
|
const existsSync73 = deps.existsSync ?? ((p) => realExistsSync5(p));
|
|
51631
52081
|
const readdirSync26 = deps.readdirSync ?? ((p) => realReaddirSync2(p));
|
|
51632
52082
|
const isDirectory = deps.isDirectory ?? ((p) => {
|
|
@@ -51654,7 +52104,7 @@ function runCredentialsMigrationChecks(config, deps = {}) {
|
|
|
51654
52104
|
const flat = [];
|
|
51655
52105
|
const perAgentDirs = [];
|
|
51656
52106
|
for (const e of entries) {
|
|
51657
|
-
const full =
|
|
52107
|
+
const full = join74(credDir, e);
|
|
51658
52108
|
if (isDirectory(full) && agentNames.has(e)) {
|
|
51659
52109
|
perAgentDirs.push(e);
|
|
51660
52110
|
} else {
|
|
@@ -51683,14 +52133,14 @@ var init_doctor_credentials_migration = () => {};
|
|
|
51683
52133
|
|
|
51684
52134
|
// src/cli/doctor-agent-dotfile-ownership.ts
|
|
51685
52135
|
import { existsSync as existsSync73, lstatSync as lstatSync10, statSync as statSync41 } from "node:fs";
|
|
51686
|
-
import { join as
|
|
52136
|
+
import { join as join75 } from "node:path";
|
|
51687
52137
|
function agentDotfileCandidates(agentDir) {
|
|
51688
52138
|
return [
|
|
51689
|
-
|
|
51690
|
-
|
|
51691
|
-
|
|
51692
|
-
|
|
51693
|
-
|
|
52139
|
+
join75(agentDir, ".claude", "settings.json"),
|
|
52140
|
+
join75(agentDir, ".claude.json"),
|
|
52141
|
+
join75(agentDir, ".claude", ".claude.json"),
|
|
52142
|
+
join75(agentDir, ".mcp.json"),
|
|
52143
|
+
join75(agentDir, ".claude-cron", ".mcp.json")
|
|
51694
52144
|
];
|
|
51695
52145
|
}
|
|
51696
52146
|
function runAgentDotfileOwnershipChecks(config, deps = {}) {
|
|
@@ -51718,7 +52168,7 @@ function runAgentDotfileOwnershipChecks(config, deps = {}) {
|
|
|
51718
52168
|
];
|
|
51719
52169
|
}
|
|
51720
52170
|
for (const name of agents) {
|
|
51721
|
-
const agentDir =
|
|
52171
|
+
const agentDir = join75(agentsDir, name);
|
|
51722
52172
|
if (!exists(agentDir)) {
|
|
51723
52173
|
continue;
|
|
51724
52174
|
}
|
|
@@ -51846,7 +52296,7 @@ var init_doctor_inlined_secrets = __esm(() => {
|
|
|
51846
52296
|
// src/cli/doctor-approval-attribution.ts
|
|
51847
52297
|
import { execFileSync as execFileSync22 } from "node:child_process";
|
|
51848
52298
|
import { readFileSync as fsReadFileSync2 } from "node:fs";
|
|
51849
|
-
import { join as
|
|
52299
|
+
import { join as join76 } from "node:path";
|
|
51850
52300
|
function parseApproverSet(canonical) {
|
|
51851
52301
|
try {
|
|
51852
52302
|
const arr = JSON.parse(canonical);
|
|
@@ -51965,7 +52415,7 @@ function loadLiveAllowFromAccessFiles(agentNames, agentsDir, readFile2 = (p) =>
|
|
|
51965
52415
|
let readAny = false;
|
|
51966
52416
|
for (const name of agentNames) {
|
|
51967
52417
|
try {
|
|
51968
|
-
const raw = readFile2(
|
|
52418
|
+
const raw = readFile2(join76(agentsDir, name, "telegram", "access.json"));
|
|
51969
52419
|
const parsed = JSON.parse(raw);
|
|
51970
52420
|
readAny = true;
|
|
51971
52421
|
if (Array.isArray(parsed.allowFrom)) {
|
|
@@ -51998,13 +52448,13 @@ var init_doctor_approval_attribution = __esm(() => {
|
|
|
51998
52448
|
|
|
51999
52449
|
// src/cli/doctor-audit-integrity.ts
|
|
52000
52450
|
import { readFileSync as fsReadFileSync3 } from "node:fs";
|
|
52001
|
-
import { homedir as
|
|
52002
|
-
import { join as
|
|
52451
|
+
import { homedir as homedir41 } from "node:os";
|
|
52452
|
+
import { join as join77 } from "node:path";
|
|
52003
52453
|
function rootWrittenLogs(home2) {
|
|
52004
52454
|
return [
|
|
52005
52455
|
{
|
|
52006
52456
|
label: "vault-broker",
|
|
52007
|
-
path:
|
|
52457
|
+
path: join77(home2, ".switchroom", "vault-audit.log"),
|
|
52008
52458
|
maxFiles: resolveRotationConfig({
|
|
52009
52459
|
envBytesVar: "SWITCHROOM_VAULT_AUDIT_MAX_BYTES",
|
|
52010
52460
|
envFilesVar: "SWITCHROOM_VAULT_AUDIT_MAX_FILES",
|
|
@@ -52014,7 +52464,7 @@ function rootWrittenLogs(home2) {
|
|
|
52014
52464
|
},
|
|
52015
52465
|
{
|
|
52016
52466
|
label: "hostd",
|
|
52017
|
-
path:
|
|
52467
|
+
path: join77(home2, ".switchroom", "host-control-audit.log"),
|
|
52018
52468
|
maxFiles: resolveRotationConfig({
|
|
52019
52469
|
envBytesVar: "SWITCHROOM_HOSTD_AUDIT_MAX_BYTES",
|
|
52020
52470
|
envFilesVar: "SWITCHROOM_HOSTD_AUDIT_MAX_FILES",
|
|
@@ -52046,11 +52496,11 @@ function readRetainedHistory(read, path7, maxFiles) {
|
|
|
52046
52496
|
return { text, sources: parts.map((p) => p.path), unreadable };
|
|
52047
52497
|
}
|
|
52048
52498
|
function runAuditIntegrityChecks(deps = {}) {
|
|
52049
|
-
const home2 = deps.homeDir ??
|
|
52499
|
+
const home2 = deps.homeDir ?? homedir41();
|
|
52050
52500
|
const read = deps.readFileSync ?? ((p) => fsReadFileSync3(p, "utf8"));
|
|
52051
52501
|
const readFailOpen = deps.readFailOpenState ?? readFailOpenState;
|
|
52052
52502
|
const results = [];
|
|
52053
|
-
const vaultAuditLog =
|
|
52503
|
+
const vaultAuditLog = join77(home2, ".switchroom", "vault-audit.log");
|
|
52054
52504
|
const failOpen = readFailOpen(failOpenStatePath(vaultAuditLog));
|
|
52055
52505
|
if (failOpen.failOpenCount > 0) {
|
|
52056
52506
|
const when = failOpen.lastFailureTs ? ` (last at ${failOpen.lastFailureTs})` : ``;
|
|
@@ -52133,14 +52583,14 @@ var init_doctor_audit_integrity = __esm(() => {
|
|
|
52133
52583
|
|
|
52134
52584
|
// src/cli/doctor-agent-smoke.ts
|
|
52135
52585
|
import { existsSync as existsSync74 } from "node:fs";
|
|
52136
|
-
import { homedir as
|
|
52137
|
-
import { join as
|
|
52586
|
+
import { homedir as homedir42 } from "node:os";
|
|
52587
|
+
import { join as join78 } from "node:path";
|
|
52138
52588
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
52139
52589
|
async function runAgentSmokeChecks(config, deps = {}) {
|
|
52140
52590
|
if (deps.fast)
|
|
52141
52591
|
return [];
|
|
52142
|
-
const home2 = deps.homeDir ??
|
|
52143
|
-
const sock = deps.operatorSockPath ??
|
|
52592
|
+
const home2 = deps.homeDir ?? homedir42();
|
|
52593
|
+
const sock = deps.operatorSockPath ?? join78(home2, ".switchroom", "hostd", "operator", "sock");
|
|
52144
52594
|
if (!deps.hostdRequestImpl && !existsSync74(sock)) {
|
|
52145
52595
|
return [
|
|
52146
52596
|
{
|
|
@@ -52219,9 +52669,9 @@ var init_doctor_agent_smoke = __esm(() => {
|
|
|
52219
52669
|
|
|
52220
52670
|
// src/cli/doctor-vault-broker-durability.ts
|
|
52221
52671
|
import { execFileSync as execFileSync23 } from "node:child_process";
|
|
52222
|
-
import { existsSync as existsSync75, statSync as statSync42, readFileSync as
|
|
52223
|
-
import { homedir as
|
|
52224
|
-
import { join as
|
|
52672
|
+
import { existsSync as existsSync75, statSync as statSync42, readFileSync as readFileSync70 } from "node:fs";
|
|
52673
|
+
import { homedir as homedir43 } from "node:os";
|
|
52674
|
+
import { join as join79 } from "node:path";
|
|
52225
52675
|
import { createRequire as createRequire2 } from "node:module";
|
|
52226
52676
|
function probeBindMountInode(hostPath, brokerContainerPath, opts) {
|
|
52227
52677
|
const statHost = opts?.statHost ?? defaultStatHost;
|
|
@@ -52365,13 +52815,13 @@ function defaultBrokerStatusProbe() {
|
|
|
52365
52815
|
}
|
|
52366
52816
|
}
|
|
52367
52817
|
function runVaultBrokerDurabilityChecks(_config, opts) {
|
|
52368
|
-
const home2 =
|
|
52818
|
+
const home2 = homedir43();
|
|
52369
52819
|
const probe2 = opts?.inodeProbe ?? probeBindMountInode;
|
|
52370
52820
|
return [
|
|
52371
52821
|
probeBrokerUnlocked(opts?.statusProbe),
|
|
52372
52822
|
probeAutoUnlockBlob(home2),
|
|
52373
52823
|
probeMachineIdMount(),
|
|
52374
|
-
formatBindMountResult("vault-broker: vault.enc bind mount",
|
|
52824
|
+
formatBindMountResult("vault-broker: vault.enc bind mount", join79(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc", probe2(join79(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc")),
|
|
52375
52825
|
formatBindMountResult("vault-broker: vault-grants dir bind mount (#1737 / #3289)", getGrantsDbDir(home2), GRANTS_DB_CONTAINER_DIR, probe2(getGrantsDbDir(home2), GRANTS_DB_CONTAINER_DIR)),
|
|
52376
52826
|
probeGrantsWalDivergence(opts?.walStatBroker),
|
|
52377
52827
|
probeOrphanVaultTokens(_config, {
|
|
@@ -52380,14 +52830,14 @@ function runVaultBrokerDurabilityChecks(_config, opts) {
|
|
|
52380
52830
|
readTokenId: opts?.readTokenId,
|
|
52381
52831
|
tokenMtimeMs: opts?.tokenMtimeMs
|
|
52382
52832
|
}),
|
|
52383
|
-
formatBindMountResult("vault-broker: vault-audit.log bind mount (#1025)",
|
|
52833
|
+
formatBindMountResult("vault-broker: vault-audit.log bind mount (#1025)", join79(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log", probe2(join79(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log")),
|
|
52384
52834
|
probeKernelDbDurability(home2, {
|
|
52385
52835
|
statBroker: opts?.kernelStatBroker
|
|
52386
52836
|
})
|
|
52387
52837
|
];
|
|
52388
52838
|
}
|
|
52389
52839
|
function probeKernelDbDurability(home2, opts) {
|
|
52390
|
-
const hostDir =
|
|
52840
|
+
const hostDir = join79(home2, ".switchroom", "approvals");
|
|
52391
52841
|
const containerDir = "/state/approvals";
|
|
52392
52842
|
const name = "approval-kernel: approvals bind mount (allow_always durability)";
|
|
52393
52843
|
const kernelStat = opts?.statBroker ?? defaultKernelStatBroker;
|
|
@@ -52520,7 +52970,7 @@ function probeGrantsWalDivergence(statBroker = defaultWalStatBroker, opts) {
|
|
|
52520
52970
|
}
|
|
52521
52971
|
function probeOrphanVaultTokens(config, opts) {
|
|
52522
52972
|
const name = "vault-broker: no orphan .vault-token grants (#1737 / #3289)";
|
|
52523
|
-
const home2 = opts?.home ??
|
|
52973
|
+
const home2 = opts?.home ?? homedir43();
|
|
52524
52974
|
const agents = Object.keys(config.agents ?? {});
|
|
52525
52975
|
const readTokenId = opts?.readTokenId ?? defaultReadTokenId;
|
|
52526
52976
|
const grantIdExists = opts?.grantIdExists ?? defaultGrantIdExists;
|
|
@@ -52585,15 +53035,15 @@ function probeOrphanVaultTokens(config, opts) {
|
|
|
52585
53035
|
}
|
|
52586
53036
|
function defaultTokenMtimeMs(agent, home2) {
|
|
52587
53037
|
try {
|
|
52588
|
-
return statSync42(
|
|
53038
|
+
return statSync42(join79(home2, ".switchroom", "agents", agent, ".vault-token")).mtimeMs;
|
|
52589
53039
|
} catch {
|
|
52590
53040
|
return null;
|
|
52591
53041
|
}
|
|
52592
53042
|
}
|
|
52593
53043
|
function defaultReadTokenId(agent, home2) {
|
|
52594
|
-
const tokenPath =
|
|
53044
|
+
const tokenPath = join79(home2, ".switchroom", "agents", agent, ".vault-token");
|
|
52595
53045
|
try {
|
|
52596
|
-
const raw =
|
|
53046
|
+
const raw = readFileSync70(tokenPath, "utf8").trim();
|
|
52597
53047
|
if (!raw)
|
|
52598
53048
|
return null;
|
|
52599
53049
|
const dot = raw.indexOf(".");
|
|
@@ -52624,7 +53074,7 @@ function defaultGrantIdExists(id) {
|
|
|
52624
53074
|
}
|
|
52625
53075
|
}
|
|
52626
53076
|
function probeAutoUnlockBlob(home2) {
|
|
52627
|
-
const blobPath =
|
|
53077
|
+
const blobPath = join79(home2, ".switchroom", "vault-auto-unlock");
|
|
52628
53078
|
if (!existsSync75(blobPath)) {
|
|
52629
53079
|
return {
|
|
52630
53080
|
name: "vault-broker: auto-unlock blob",
|
|
@@ -52754,7 +53204,7 @@ var init_doctor_timezone = __esm(() => {
|
|
|
52754
53204
|
});
|
|
52755
53205
|
|
|
52756
53206
|
// src/cli/doctor-fix-session-model.ts
|
|
52757
|
-
import { join as
|
|
53207
|
+
import { join as join80 } from "node:path";
|
|
52758
53208
|
function describeReconcileChanges(changes) {
|
|
52759
53209
|
if (changes.length === 0)
|
|
52760
53210
|
return "Reconcile rewrote no other files.";
|
|
@@ -52767,7 +53217,7 @@ function fixSessionModelCarrierDrift(config, deps) {
|
|
|
52767
53217
|
const agentsDir = resolveAgentsDir(config);
|
|
52768
53218
|
const results = [];
|
|
52769
53219
|
for (const name of Object.keys(config.agents ?? {})) {
|
|
52770
|
-
const startShPath =
|
|
53220
|
+
const startShPath = join80(agentsDir, name, "start.sh");
|
|
52771
53221
|
const label = `${name}: start.sh /model session carrier (--fix)`;
|
|
52772
53222
|
const pre = deps.check(name, startShPath);
|
|
52773
53223
|
if (pre.status === "ok") {
|
|
@@ -52818,8 +53268,8 @@ var init_doctor_fix_session_model = __esm(() => {
|
|
|
52818
53268
|
|
|
52819
53269
|
// src/cli/doctor-claude-cli.ts
|
|
52820
53270
|
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
52821
|
-
import { existsSync as existsSync76, readFileSync as
|
|
52822
|
-
import { dirname as dirname30, join as
|
|
53271
|
+
import { existsSync as existsSync76, readFileSync as readFileSync71 } from "node:fs";
|
|
53272
|
+
import { dirname as dirname30, join as join81 } from "node:path";
|
|
52823
53273
|
function parseClaudeCliVersion(raw) {
|
|
52824
53274
|
const m = raw.trim().match(/^v?(\d+)\.(\d+)\.(\d+)/);
|
|
52825
53275
|
if (!m)
|
|
@@ -52842,7 +53292,7 @@ function claudeCliMeetsFloor(raw, floor) {
|
|
|
52842
53292
|
function locateRepoFile(relative4) {
|
|
52843
53293
|
let dir = import.meta.dirname;
|
|
52844
53294
|
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
52845
|
-
const candidate =
|
|
53295
|
+
const candidate = join81(dir, relative4);
|
|
52846
53296
|
if (existsSync76(candidate))
|
|
52847
53297
|
return candidate;
|
|
52848
53298
|
dir = dirname30(dir);
|
|
@@ -52850,12 +53300,12 @@ function locateRepoFile(relative4) {
|
|
|
52850
53300
|
return null;
|
|
52851
53301
|
}
|
|
52852
53302
|
function readPinnedClaudeCliVersion(dockerfilePath) {
|
|
52853
|
-
const path7 = dockerfilePath === undefined ? locateRepoFile(
|
|
53303
|
+
const path7 = dockerfilePath === undefined ? locateRepoFile(join81("docker", "Dockerfile.base")) : dockerfilePath;
|
|
52854
53304
|
if (!path7)
|
|
52855
53305
|
return null;
|
|
52856
53306
|
let text;
|
|
52857
53307
|
try {
|
|
52858
|
-
text =
|
|
53308
|
+
text = readFileSync71(path7, "utf-8");
|
|
52859
53309
|
} catch {
|
|
52860
53310
|
return null;
|
|
52861
53311
|
}
|
|
@@ -53013,34 +53463,34 @@ import {
|
|
|
53013
53463
|
existsSync as existsSync77,
|
|
53014
53464
|
lstatSync as lstatSync11,
|
|
53015
53465
|
mkdirSync as mkdirSync42,
|
|
53016
|
-
readFileSync as
|
|
53466
|
+
readFileSync as readFileSync72,
|
|
53017
53467
|
readdirSync as readdirSync26,
|
|
53018
53468
|
statSync as statSync43
|
|
53019
53469
|
} from "node:fs";
|
|
53020
|
-
import { dirname as dirname31, join as
|
|
53470
|
+
import { dirname as dirname31, join as join82, resolve as resolve44 } from "node:path";
|
|
53021
53471
|
import { createPublicKey, createPrivateKey } from "node:crypto";
|
|
53022
53472
|
function readLitellmConfigText() {
|
|
53023
53473
|
const candidates = [
|
|
53024
53474
|
process.env.LITELLM_CONFIG_PATH,
|
|
53025
53475
|
"/data/coolify/services/litellm/litellm-config.yaml",
|
|
53026
|
-
|
|
53476
|
+
join82(process.cwd(), "docker", "litellm-proxy", "litellm-config.yaml")
|
|
53027
53477
|
].filter((p) => typeof p === "string" && p.length > 0);
|
|
53028
53478
|
for (const p of candidates) {
|
|
53029
53479
|
try {
|
|
53030
53480
|
if (existsSync77(p))
|
|
53031
|
-
return
|
|
53481
|
+
return readFileSync72(p, "utf-8");
|
|
53032
53482
|
} catch {}
|
|
53033
53483
|
}
|
|
53034
53484
|
return null;
|
|
53035
53485
|
}
|
|
53036
53486
|
function findInNvm(bin) {
|
|
53037
|
-
const nvmRoot =
|
|
53487
|
+
const nvmRoot = join82(process.env.HOME ?? "", ".nvm", "versions", "node");
|
|
53038
53488
|
if (!existsSync77(nvmRoot))
|
|
53039
53489
|
return null;
|
|
53040
53490
|
try {
|
|
53041
53491
|
const versions = readdirSync26(nvmRoot).sort().reverse();
|
|
53042
53492
|
for (const v of versions) {
|
|
53043
|
-
const candidate =
|
|
53493
|
+
const candidate = join82(nvmRoot, v, "bin", bin);
|
|
53044
53494
|
try {
|
|
53045
53495
|
const s = statSync43(candidate);
|
|
53046
53496
|
if (s.isFile() || s.isSymbolicLink()) {
|
|
@@ -53067,7 +53517,7 @@ function whichOnPath(bin) {
|
|
|
53067
53517
|
for (const dir of pathEnv.split(":")) {
|
|
53068
53518
|
if (!dir)
|
|
53069
53519
|
continue;
|
|
53070
|
-
const candidate =
|
|
53520
|
+
const candidate = join82(dir, bin);
|
|
53071
53521
|
if (isX(candidate))
|
|
53072
53522
|
return candidate;
|
|
53073
53523
|
}
|
|
@@ -53233,7 +53683,7 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
|
|
|
53233
53683
|
if (envBrowsersPath && envBrowsersPath.length > 0) {
|
|
53234
53684
|
cacheLocations.push(envBrowsersPath);
|
|
53235
53685
|
}
|
|
53236
|
-
cacheLocations.push(
|
|
53686
|
+
cacheLocations.push(join82(homeDir, ".cache", "ms-playwright"));
|
|
53237
53687
|
for (const cacheDir of cacheLocations) {
|
|
53238
53688
|
if (!existsSync77(cacheDir))
|
|
53239
53689
|
continue;
|
|
@@ -53241,10 +53691,10 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
|
|
|
53241
53691
|
const entries = readdirSync26(cacheDir).filter((e) => e.startsWith("chromium"));
|
|
53242
53692
|
for (const entry of entries) {
|
|
53243
53693
|
const candidates2 = [
|
|
53244
|
-
|
|
53245
|
-
|
|
53246
|
-
|
|
53247
|
-
|
|
53694
|
+
join82(cacheDir, entry, "chrome-linux64", "chrome"),
|
|
53695
|
+
join82(cacheDir, entry, "chrome-linux", "chrome"),
|
|
53696
|
+
join82(cacheDir, entry, "chrome-linux64", "headless_shell"),
|
|
53697
|
+
join82(cacheDir, entry, "chrome-linux", "headless_shell")
|
|
53248
53698
|
];
|
|
53249
53699
|
for (const path7 of candidates2) {
|
|
53250
53700
|
if (existsSync77(path7))
|
|
@@ -53376,7 +53826,7 @@ function checkDeployMounts(opts) {
|
|
|
53376
53826
|
const home2 = opts?.home ?? process.env.HOME ?? "/root";
|
|
53377
53827
|
const { pathKind } = opts?.deps ?? DEFAULT_DEPLOY_MOUNTS_DEPS;
|
|
53378
53828
|
const results = [];
|
|
53379
|
-
const dockerComposePlugin =
|
|
53829
|
+
const dockerComposePlugin = join82(home2, ".docker", "cli-plugins", "docker-compose");
|
|
53380
53830
|
const pluginKind = pathKind(dockerComposePlugin);
|
|
53381
53831
|
if (pluginKind === "dir") {
|
|
53382
53832
|
results.push({
|
|
@@ -53414,7 +53864,7 @@ function checkDeployMounts(opts) {
|
|
|
53414
53864
|
function checkLegacyState() {
|
|
53415
53865
|
const results = [];
|
|
53416
53866
|
const h = process.env.HOME ?? "/root";
|
|
53417
|
-
const clerkDir =
|
|
53867
|
+
const clerkDir = join82(h, LEGACY_STATE_DIR);
|
|
53418
53868
|
const clerkPresent = existsSync77(clerkDir);
|
|
53419
53869
|
results.push({
|
|
53420
53870
|
name: "legacy ~/.clerk state",
|
|
@@ -53424,7 +53874,7 @@ function checkLegacyState() {
|
|
|
53424
53874
|
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."
|
|
53425
53875
|
} : {}
|
|
53426
53876
|
});
|
|
53427
|
-
const legacySock =
|
|
53877
|
+
const legacySock = join82(h, ".switchroom", "vault-broker.sock");
|
|
53428
53878
|
let sockStat = null;
|
|
53429
53879
|
try {
|
|
53430
53880
|
sockStat = lstatSync11(legacySock);
|
|
@@ -53914,6 +54364,7 @@ async function checkHindsight(config) {
|
|
|
53914
54364
|
results.push(await checkHindsightHealthEndpoint(url));
|
|
53915
54365
|
results.push(...await checkBankIngestHealth(config, url, { includeConsolidationBacklog: true }));
|
|
53916
54366
|
results.push(...await checkBankObservationsMissions(config, url));
|
|
54367
|
+
results.push(await checkObservationScopeSaturation(config, url));
|
|
53917
54368
|
const memoryAgentsDir = resolveAgentsDir(config);
|
|
53918
54369
|
for (const agentName of Object.keys(config.agents)) {
|
|
53919
54370
|
results.push(checkAgentRecallHealth(agentName, resolve44(memoryAgentsDir, agentName)));
|
|
@@ -54092,7 +54543,7 @@ function classifyReadError(err) {
|
|
|
54092
54543
|
}
|
|
54093
54544
|
function tryReadHostFile(path7) {
|
|
54094
54545
|
try {
|
|
54095
|
-
return { kind: "ok", content:
|
|
54546
|
+
return { kind: "ok", content: readFileSync72(path7, "utf-8") };
|
|
54096
54547
|
} catch (err) {
|
|
54097
54548
|
const kind = classifyReadError(err);
|
|
54098
54549
|
const error = err?.message ?? String(err);
|
|
@@ -54108,7 +54559,7 @@ function parseEnvFile(path7) {
|
|
|
54108
54559
|
return {};
|
|
54109
54560
|
let content;
|
|
54110
54561
|
try {
|
|
54111
|
-
content =
|
|
54562
|
+
content = readFileSync72(path7, "utf-8");
|
|
54112
54563
|
} catch {
|
|
54113
54564
|
return {};
|
|
54114
54565
|
}
|
|
@@ -54163,7 +54614,7 @@ async function checkTelegram(config) {
|
|
|
54163
54614
|
const plugin = agentConfig.channels?.telegram?.plugin ?? "switchroom";
|
|
54164
54615
|
if (plugin !== "switchroom")
|
|
54165
54616
|
continue;
|
|
54166
|
-
const envPath =
|
|
54617
|
+
const envPath = join82(agentsDir, name, "telegram", ".env");
|
|
54167
54618
|
const read = tryReadHostFile(envPath);
|
|
54168
54619
|
if (read.kind === "eacces") {
|
|
54169
54620
|
results.push({
|
|
@@ -54225,7 +54676,7 @@ function checkStartShStale(agentName, startShPath) {
|
|
|
54225
54676
|
}
|
|
54226
54677
|
let content;
|
|
54227
54678
|
try {
|
|
54228
|
-
content =
|
|
54679
|
+
content = readFileSync72(startShPath, "utf-8");
|
|
54229
54680
|
} catch (err) {
|
|
54230
54681
|
return {
|
|
54231
54682
|
name: label,
|
|
@@ -54256,7 +54707,7 @@ function checkStartShSessionModelCarrier(agentName, startShPath) {
|
|
|
54256
54707
|
}
|
|
54257
54708
|
let content;
|
|
54258
54709
|
try {
|
|
54259
|
-
content =
|
|
54710
|
+
content = readFileSync72(startShPath, "utf-8");
|
|
54260
54711
|
} catch (err) {
|
|
54261
54712
|
return {
|
|
54262
54713
|
name: label,
|
|
@@ -54293,7 +54744,7 @@ function checkStartShSessionModelCarrier(agentName, startShPath) {
|
|
|
54293
54744
|
}
|
|
54294
54745
|
function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
54295
54746
|
const label = `${agentName}: $HOME/.switchroom symlink (#910)`;
|
|
54296
|
-
const path7 =
|
|
54747
|
+
const path7 = join82(agentDir, "home", ".switchroom");
|
|
54297
54748
|
let stats;
|
|
54298
54749
|
try {
|
|
54299
54750
|
stats = lstatSync11(path7);
|
|
@@ -54330,7 +54781,7 @@ function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
|
54330
54781
|
}
|
|
54331
54782
|
function checkRepoHygiene(repoRoot) {
|
|
54332
54783
|
const results = [];
|
|
54333
|
-
const exportDir =
|
|
54784
|
+
const exportDir = join82(repoRoot, "clerk-export");
|
|
54334
54785
|
if (existsSync77(exportDir)) {
|
|
54335
54786
|
results.push({
|
|
54336
54787
|
name: "repo hygiene: clerk-export/ on disk (#1072)",
|
|
@@ -54339,7 +54790,7 @@ function checkRepoHygiene(repoRoot) {
|
|
|
54339
54790
|
fix: `Run scripts/migrate-clerk-export-to-vault.sh to move the bundle ` + `into the vault, then delete the on-disk copy.`
|
|
54340
54791
|
});
|
|
54341
54792
|
}
|
|
54342
|
-
const knownTarball =
|
|
54793
|
+
const knownTarball = join82(repoRoot, "clerk-export-with-secrets.tar.gz");
|
|
54343
54794
|
if (existsSync77(knownTarball)) {
|
|
54344
54795
|
results.push({
|
|
54345
54796
|
name: "repo hygiene: clerk-export-with-secrets.tar.gz on disk (#1072)",
|
|
@@ -54357,7 +54808,7 @@ function checkRepoHygiene(repoRoot) {
|
|
|
54357
54808
|
results.push({
|
|
54358
54809
|
name: `repo hygiene: ${name} on disk (#1072)`,
|
|
54359
54810
|
status: "warn",
|
|
54360
|
-
detail: `${
|
|
54811
|
+
detail: `${join82(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
|
|
54361
54812
|
fix: `Inspect, migrate any secrets into the vault, then delete the ` + `archive.`
|
|
54362
54813
|
});
|
|
54363
54814
|
}
|
|
@@ -54380,12 +54831,12 @@ function checkRepoHygiene(repoRoot) {
|
|
|
54380
54831
|
}
|
|
54381
54832
|
function isSwitchroomCheckout(dir) {
|
|
54382
54833
|
try {
|
|
54383
|
-
if (!existsSync77(
|
|
54834
|
+
if (!existsSync77(join82(dir, ".git")))
|
|
54384
54835
|
return false;
|
|
54385
|
-
const pkgPath =
|
|
54836
|
+
const pkgPath = join82(dir, "package.json");
|
|
54386
54837
|
if (!existsSync77(pkgPath))
|
|
54387
54838
|
return false;
|
|
54388
|
-
const pkg = JSON.parse(
|
|
54839
|
+
const pkg = JSON.parse(readFileSync72(pkgPath, "utf-8"));
|
|
54389
54840
|
return pkg.name === "switchroom";
|
|
54390
54841
|
} catch {
|
|
54391
54842
|
return false;
|
|
@@ -54419,8 +54870,8 @@ function checkAgents(config, configPath) {
|
|
|
54419
54870
|
fix: `Rotate the bot token (e.g. via \`switchroom vault\`), then run ` + `\`switchroom agent unquarantine ${name}\` and \`switchroom agent restart ${name}\``
|
|
54420
54871
|
});
|
|
54421
54872
|
}
|
|
54422
|
-
results.push(checkStartShStale(name,
|
|
54423
|
-
results.push(checkStartShSessionModelCarrier(name,
|
|
54873
|
+
results.push(checkStartShStale(name, join82(agentDir, "start.sh")));
|
|
54874
|
+
results.push(checkStartShSessionModelCarrier(name, join82(agentDir, "start.sh")));
|
|
54424
54875
|
results.push(checkLeakedHomeSwitchroom(name, agentDir));
|
|
54425
54876
|
const status = statuses[name];
|
|
54426
54877
|
const active = status?.active ?? "unknown";
|
|
@@ -54497,7 +54948,7 @@ function checkAgents(config, configPath) {
|
|
|
54497
54948
|
}
|
|
54498
54949
|
}
|
|
54499
54950
|
if (agentConfig.channels?.telegram?.plugin === "switchroom") {
|
|
54500
|
-
const mcpJsonPath =
|
|
54951
|
+
const mcpJsonPath = join82(agentDir, ".mcp.json");
|
|
54501
54952
|
if (!existsSync77(mcpJsonPath)) {
|
|
54502
54953
|
results.push({
|
|
54503
54954
|
name: `${name}: .mcp.json`,
|
|
@@ -54507,7 +54958,7 @@ function checkAgents(config, configPath) {
|
|
|
54507
54958
|
});
|
|
54508
54959
|
} else {
|
|
54509
54960
|
try {
|
|
54510
|
-
const mcp = JSON.parse(
|
|
54961
|
+
const mcp = JSON.parse(readFileSync72(mcpJsonPath, "utf-8"));
|
|
54511
54962
|
const hasSwitchroomTelegram = !!mcp.mcpServers?.["switchroom-telegram"];
|
|
54512
54963
|
const memoryEnabled = isHindsightEnabled(config);
|
|
54513
54964
|
const hasHindsight = !!mcp.mcpServers?.hindsight;
|
|
@@ -54799,7 +55250,7 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
|
|
|
54799
55250
|
};
|
|
54800
55251
|
}
|
|
54801
55252
|
const credDir = dirname31(envPath);
|
|
54802
|
-
const authScript =
|
|
55253
|
+
const authScript = join82(credDir, "claude-auth.py");
|
|
54803
55254
|
if (!existsSync77(authScript)) {
|
|
54804
55255
|
return {
|
|
54805
55256
|
name: "mff: auth flow",
|
|
@@ -55002,11 +55453,11 @@ function runDockerSection(config) {
|
|
|
55002
55453
|
let composeYaml;
|
|
55003
55454
|
let dockerfileAgent;
|
|
55004
55455
|
try {
|
|
55005
|
-
composeYaml =
|
|
55456
|
+
composeYaml = readFileSync72(composePath, "utf8");
|
|
55006
55457
|
} catch {}
|
|
55007
55458
|
const dockerfilePath = resolve44(process.env.HOME ?? "", ".switchroom", "docker", "Dockerfile.agent");
|
|
55008
55459
|
try {
|
|
55009
|
-
dockerfileAgent =
|
|
55460
|
+
dockerfileAgent = readFileSync72(dockerfilePath, "utf8");
|
|
55010
55461
|
} catch {}
|
|
55011
55462
|
return runDockerChecks({
|
|
55012
55463
|
config,
|
|
@@ -55152,6 +55603,10 @@ function registerDoctorCommand(program3) {
|
|
|
55152
55603
|
}
|
|
55153
55604
|
})
|
|
55154
55605
|
},
|
|
55606
|
+
{
|
|
55607
|
+
title: "LiteLLM boot routing",
|
|
55608
|
+
results: runRoutingModeChecks(config)
|
|
55609
|
+
},
|
|
55155
55610
|
{ title: "Telegram", results: await checkTelegram(config) },
|
|
55156
55611
|
{
|
|
55157
55612
|
title: "Telegram flood pressure (429)",
|
|
@@ -55331,8 +55786,10 @@ var init_doctor = __esm(() => {
|
|
|
55331
55786
|
init_doctor_memory();
|
|
55332
55787
|
init_doctor_recall_health();
|
|
55333
55788
|
init_doctor_hnsw_index();
|
|
55789
|
+
init_doctor_observation_scopes();
|
|
55334
55790
|
init_doctor_hindsight_watch();
|
|
55335
55791
|
init_key_allowlist_check();
|
|
55792
|
+
init_doctor_routing_mode();
|
|
55336
55793
|
init_resolver();
|
|
55337
55794
|
init_doctor_docker();
|
|
55338
55795
|
init_doctor_auth_broker();
|
|
@@ -69302,7 +69759,7 @@ var init_server4 = __esm(() => {
|
|
|
69302
69759
|
},
|
|
69303
69760
|
{
|
|
69304
69761
|
name: "rollout",
|
|
69305
|
-
description: "SAFELY roll the fleet to a pinned SEMVER version, staggered and " + "canary-gated (#2487). Unlike update_apply (a blunt all-at-once " + "recreate), this restarts agents one at a time canary-first, asserts " + "each agent's in-container `switchroom --version` matches the target, " + "and STOPS on the first mismatch \u2014 so a bad build fails on the canary " + "before touching the rest of the fleet. The durable release.pin is " + "persisted only AFTER the canary confirms (a failed canary never " + "strands a bad pin). `pin` MUST be a tagged semver (vX.Y.Z) \u2014 sha " + "pins are rejected because the version assert needs a semver to " + "compare against. When the target pin is NEWER than hostd's own CLI " + "(every freshly tagged release), hostd SELF-BUMPS first (#2645): its " + "container restarts on the target image (~30-60s blip on this MCP " + "socket), then the roll resumes AUTOMATICALLY under the SAME " + "request_id \u2014 do NOT re-issue the rollout during the blip; poll " + "get_status (pass the returned request_id as its `request_id` " + "argument) once the socket is back. " + "The web + hindsight singletons are refreshed in-plan on this path " + "(only the hostd template regen + host operator CLI stay host-side; " + "the terminal warnings name exactly what is still on the prior " + "version and the commands to finish). By default downgrade pins
|
|
69762
|
+
description: "SAFELY roll the fleet to a pinned SEMVER version, staggered and " + "canary-gated (#2487). Unlike update_apply (a blunt all-at-once " + "recreate), this restarts agents one at a time canary-first, asserts " + "each agent's in-container `switchroom --version` matches the target, " + "and STOPS on the first mismatch \u2014 so a bad build fails on the canary " + "before touching the rest of the fleet. The durable release.pin is " + "persisted only AFTER the canary confirms (a failed canary never " + "strands a bad pin). `pin` MUST be a tagged semver (vX.Y.Z) \u2014 sha " + "pins are rejected because the version assert needs a semver to " + "compare against. When the target pin is NEWER than hostd's own CLI " + "(every freshly tagged release), hostd SELF-BUMPS first (#2645): its " + "container restarts on the target image (~30-60s blip on this MCP " + "socket), then the roll resumes AUTOMATICALLY under the SAME " + "request_id \u2014 do NOT re-issue the rollout during the blip; poll " + "get_status (pass the returned request_id as its `request_id` " + "argument) once the socket is back. " + "The web + hindsight singletons are refreshed in-plan on this path " + "(only the hostd template regen + host operator CLI stay host-side; " + "the terminal warnings name exactly what is still on the prior " + "version and the commands to finish). The roll ENDS by re-reading " + "the same component inventory `update --check` uses and FAILS if " + "any in-scope component is still behind the target (#3928), so a " + "`completed` result means the host actually converged \u2014 never just " + "the agents. On that failure get_status returns " + '`failedStep: "verify-components"` plus `drifted[]`. By default downgrade pins ' + "are rejected \u2014 pass `allow_downgrade: true` for the operator-approved " + "rollback path to a known-good earlier tag; all other safety rails " + "(canary order, version-assert, stop-on-mismatch) apply unchanged. " + "Admin-only at the wire layer AND deliberately NOT pre-approved \u2014 every " + "call surfaces a Telegram approval card for the operator to tap. " + "ALWAYS pass a one-line `reason` explaining why you're rolling the " + "fleet to this pin; it renders on the card's `why:` line so the " + "operator can decide in context. " + "Returns `started`; poll get_status with `request_id` for the " + "structured outcome (which agents rolled / where it stopped).",
|
|
69306
69763
|
inputSchema: {
|
|
69307
69764
|
type: "object",
|
|
69308
69765
|
required: ["pin"],
|
|
@@ -69362,7 +69819,7 @@ var init_server4 = __esm(() => {
|
|
|
69362
69819
|
},
|
|
69363
69820
|
{
|
|
69364
69821
|
name: "get_status",
|
|
69365
|
-
description: "Look up the status of a prior async fleet mutation. With " + "`request_id` (the id echoed by rollout / update_apply / " + "agent_restart etc.), asks the host-control daemon for that " + "specific request's live status \u2014 for an in-flight `rollout` " + "this is the ONLY way to see progress: the response payload " + "carries the current phase, n/m counters, rolled[] agents, " + "failedStep/failedAgent and pin, and it keeps working across a " + "hostd self-bump/restart via the durable audit-log fallback. " + "This is the tool the `rollout` description tells you to poll " + "with the returned request_id. Without `request_id`, falls back " + "to reading the most recent terminal `update_apply` audit row " + "(channel, pin, resolved_sha, install_context, result, " + "exit_code, stderr_tail) \u2014 use that no-arg form only to report " + "the last blunt update on demand; it CANNOT see rollouts or " + "in-flight work. Read-only; returns JSON.",
|
|
69822
|
+
description: "Look up the status of a prior async fleet mutation. With " + "`request_id` (the id echoed by rollout / update_apply / " + "agent_restart etc.), asks the host-control daemon for that " + "specific request's live status \u2014 for an in-flight `rollout` " + "this is the ONLY way to see progress: the response payload " + "carries the current phase, n/m counters, rolled[] agents, " + "failedStep/failedAgent and pin, and it keeps working across a " + "hostd self-bump/restart via the durable audit-log fallback. " + "When `failedStep` is `verify-components` the payload also " + "carries `drifted[]` \u2014 the components that are STILL behind the " + "target after the roll. Report those names verbatim: re-running " + "the roll will not fix them, the named component's own install " + "must be run. " + "This is the tool the `rollout` description tells you to poll " + "with the returned request_id. Without `request_id`, falls back " + "to reading the most recent terminal `update_apply` audit row " + "(channel, pin, resolved_sha, install_context, result, " + "exit_code, stderr_tail) \u2014 use that no-arg form only to report " + "the last blunt update on demand; it CANNOT see rollouts or " + "in-flight work. Read-only; returns JSON.",
|
|
69366
69823
|
inputSchema: {
|
|
69367
69824
|
type: "object",
|
|
69368
69825
|
properties: {
|
|
@@ -69900,7 +70357,7 @@ var init_header_passthrough_guard = __esm(() => {
|
|
|
69900
70357
|
});
|
|
69901
70358
|
|
|
69902
70359
|
// src/fleet-health/litellm-config-sensor.ts
|
|
69903
|
-
import { readFileSync as
|
|
70360
|
+
import { readFileSync as readFileSync97, existsSync as existsSync109 } from "node:fs";
|
|
69904
70361
|
function resolveLitellmConfigPath(explicit, discoverFn = () => discoverLiveLitellmConfigPath()) {
|
|
69905
70362
|
if (explicit)
|
|
69906
70363
|
return explicit;
|
|
@@ -69912,7 +70369,7 @@ function resolveLitellmConfigPath(explicit, discoverFn = () => discoverLiveLitel
|
|
|
69912
70369
|
function scanLitellmConfig(opts = {}) {
|
|
69913
70370
|
const path10 = resolveLitellmConfigPath(opts.path, opts.discoverFn);
|
|
69914
70371
|
const exists = opts.existsFn ?? existsSync109;
|
|
69915
|
-
const read = opts.readFn ?? ((p) =>
|
|
70372
|
+
const read = opts.readFn ?? ((p) => readFileSync97(p, "utf-8"));
|
|
69916
70373
|
const log = opts.log ?? (() => {});
|
|
69917
70374
|
const nowIso = opts.nowIso ?? new Date().toISOString();
|
|
69918
70375
|
if (path10 === null) {
|
|
@@ -69987,15 +70444,15 @@ __export(exports_scan, {
|
|
|
69987
70444
|
ledgerPathForBase: () => ledgerPathForBase
|
|
69988
70445
|
});
|
|
69989
70446
|
import {
|
|
69990
|
-
readFileSync as
|
|
70447
|
+
readFileSync as readFileSync98,
|
|
69991
70448
|
readdirSync as readdirSync42,
|
|
69992
70449
|
existsSync as existsSync110,
|
|
69993
70450
|
mkdirSync as mkdirSync63,
|
|
69994
70451
|
writeFileSync as writeFileSync46
|
|
69995
70452
|
} from "node:fs";
|
|
69996
70453
|
import { resolve as resolve61, dirname as dirname42 } from "node:path";
|
|
69997
|
-
import { homedir as
|
|
69998
|
-
function resolveSwitchroomBase(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ??
|
|
70454
|
+
import { homedir as homedir61 } from "node:os";
|
|
70455
|
+
function resolveSwitchroomBase(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir61()) {
|
|
69999
70456
|
return resolve61(home2, ".switchroom");
|
|
70000
70457
|
}
|
|
70001
70458
|
function listAgents(base) {
|
|
@@ -70030,7 +70487,7 @@ function runScan(opts = {}) {
|
|
|
70030
70487
|
let sawArtifact = false;
|
|
70031
70488
|
try {
|
|
70032
70489
|
if (existsSync110(turnsPath)) {
|
|
70033
|
-
turnsText =
|
|
70490
|
+
turnsText = readFileSync98(turnsPath, "utf-8");
|
|
70034
70491
|
sawArtifact = true;
|
|
70035
70492
|
}
|
|
70036
70493
|
} catch (e) {
|
|
@@ -70038,7 +70495,7 @@ function runScan(opts = {}) {
|
|
|
70038
70495
|
}
|
|
70039
70496
|
try {
|
|
70040
70497
|
if (existsSync110(gwPath)) {
|
|
70041
|
-
gwText =
|
|
70498
|
+
gwText = readFileSync98(gwPath, "utf-8");
|
|
70042
70499
|
sawArtifact = true;
|
|
70043
70500
|
}
|
|
70044
70501
|
} catch (e) {
|
|
@@ -70082,7 +70539,7 @@ function readLedgerIfPresent(base) {
|
|
|
70082
70539
|
try {
|
|
70083
70540
|
if (!existsSync110(path10))
|
|
70084
70541
|
return null;
|
|
70085
|
-
return JSON.parse(
|
|
70542
|
+
return JSON.parse(readFileSync98(path10, "utf-8"));
|
|
70086
70543
|
} catch {
|
|
70087
70544
|
return null;
|
|
70088
70545
|
}
|
|
@@ -70114,7 +70571,7 @@ init_esm();
|
|
|
70114
70571
|
init_source();
|
|
70115
70572
|
import { join as join24, resolve as resolve23 } from "node:path";
|
|
70116
70573
|
import { rmSync as rmSync10, existsSync as existsSync33, readFileSync as readFileSync26, statSync as statSync18 } from "node:fs";
|
|
70117
|
-
import { homedir as
|
|
70574
|
+
import { homedir as homedir10 } from "node:os";
|
|
70118
70575
|
|
|
70119
70576
|
// src/agents/scheduler-state.ts
|
|
70120
70577
|
import { closeSync, openSync, readSync, statSync } from "node:fs";
|
|
@@ -70459,6 +70916,7 @@ import { join as join19 } from "node:path";
|
|
|
70459
70916
|
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
70460
70917
|
|
|
70461
70918
|
// src/agents/handoff-summarizer.ts
|
|
70919
|
+
init_atomic();
|
|
70462
70920
|
init_observation_scopes();
|
|
70463
70921
|
import { readFileSync as readFileSync19, writeFileSync as writeFileSync9, renameSync as renameSync7, mkdirSync as mkdirSync16, existsSync as existsSync23, statSync as statSync11, readdirSync as readdirSync10 } from "node:fs";
|
|
70464
70922
|
import { join as join18 } from "node:path";
|
|
@@ -70603,8 +71061,13 @@ function writeSidecarsAtomic(agentDir, briefing, topic) {
|
|
|
70603
71061
|
const topicTmp = topicPath + ".tmp";
|
|
70604
71062
|
writeFileSync9(handoffTmp, briefing, "utf-8");
|
|
70605
71063
|
writeFileSync9(topicTmp, topic, "utf-8");
|
|
71064
|
+
fsyncPathSync(handoffTmp);
|
|
71065
|
+
fsyncPathSync(topicTmp);
|
|
70606
71066
|
renameSync7(handoffTmp, handoffPath);
|
|
70607
71067
|
renameSync7(topicTmp, topicPath);
|
|
71068
|
+
try {
|
|
71069
|
+
fsyncPathSync(agentDir);
|
|
71070
|
+
} catch {}
|
|
70608
71071
|
}
|
|
70609
71072
|
async function buildHandoff(opts) {
|
|
70610
71073
|
const maxTurns = opts.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
@@ -72840,7 +73303,7 @@ function registerAgentCommand(program3) {
|
|
|
72840
73303
|
}
|
|
72841
73304
|
return;
|
|
72842
73305
|
}
|
|
72843
|
-
const logsDir = join24(
|
|
73306
|
+
const logsDir = join24(homedir10(), ".switchroom", "logs");
|
|
72844
73307
|
const schedulerStates = Object.fromEntries(agentNames.map((n) => [n, getSchedulerState(n, logsDir)]));
|
|
72845
73308
|
if (opts.json) {
|
|
72846
73309
|
const data = agentNames.map((name) => {
|
|
@@ -73909,7 +74372,7 @@ init_source();
|
|
|
73909
74372
|
init_lifecycle();
|
|
73910
74373
|
import { execFile } from "node:child_process";
|
|
73911
74374
|
import { promisify } from "node:util";
|
|
73912
|
-
import { homedir as
|
|
74375
|
+
import { homedir as homedir12 } from "node:os";
|
|
73913
74376
|
import { join as join26 } from "node:path";
|
|
73914
74377
|
init_helpers();
|
|
73915
74378
|
init_broker_call();
|
|
@@ -73982,7 +74445,7 @@ function registerStatusCommand(program3) {
|
|
|
73982
74445
|
const config = getConfig(program3);
|
|
73983
74446
|
const agentNames = Object.keys(config.agents ?? {}).sort();
|
|
73984
74447
|
const statuses = getAllAgentStatuses(config);
|
|
73985
|
-
const logsDir = join26(
|
|
74448
|
+
const logsDir = join26(homedir12(), ".switchroom", "logs");
|
|
73986
74449
|
const schedulerStates = Object.fromEntries(agentNames.map((n) => [n, getSchedulerState(n, logsDir)]));
|
|
73987
74450
|
const accountsP = fetchAccounts();
|
|
73988
74451
|
const mcpEnabled = opts.mcp !== false;
|
|
@@ -76423,12 +76886,12 @@ function loadCredentialsFromGlobalAccount(label) {
|
|
|
76423
76886
|
};
|
|
76424
76887
|
}
|
|
76425
76888
|
async function loadCredentialsViaClaude(label) {
|
|
76426
|
-
const [{ runViaClaude: runViaClaude2 }, { homedir:
|
|
76889
|
+
const [{ runViaClaude: runViaClaude2 }, { homedir: homedir15 }, { default: readline }] = await Promise.all([
|
|
76427
76890
|
Promise.resolve().then(() => (init_via_claude(), exports_via_claude)),
|
|
76428
76891
|
import("node:os"),
|
|
76429
76892
|
import("node:readline")
|
|
76430
76893
|
]);
|
|
76431
|
-
const configDir = join30(
|
|
76894
|
+
const configDir = join30(homedir15(), ".switchroom", "accounts", label);
|
|
76432
76895
|
const result = await runViaClaude2({
|
|
76433
76896
|
configDir,
|
|
76434
76897
|
promptForCode: async (_url) => {
|
|
@@ -76695,7 +77158,7 @@ init_hindsight2();
|
|
|
76695
77158
|
import { readFileSync as readFileSync32, writeFileSync as writeFileSync13, copyFileSync as copyFileSync6, existsSync as existsSync38, readdirSync as readdirSync17, statSync as statSync23 } from "node:fs";
|
|
76696
77159
|
import { execFileSync as execFileSync17 } from "node:child_process";
|
|
76697
77160
|
import { join as join31 } from "node:path";
|
|
76698
|
-
import { homedir as
|
|
77161
|
+
import { homedir as homedir15 } from "node:os";
|
|
76699
77162
|
function getVaultPath2(configPath) {
|
|
76700
77163
|
try {
|
|
76701
77164
|
const config = loadConfig(configPath);
|
|
@@ -76710,7 +77173,7 @@ function maskToken(s) {
|
|
|
76710
77173
|
return "***";
|
|
76711
77174
|
}
|
|
76712
77175
|
function findClaudeTranscripts() {
|
|
76713
|
-
const root = join31(
|
|
77176
|
+
const root = join31(homedir15(), ".claude", "projects");
|
|
76714
77177
|
if (!existsSync38(root))
|
|
76715
77178
|
return [];
|
|
76716
77179
|
const out = [];
|
|
@@ -76742,7 +77205,7 @@ function findClaudeTranscripts() {
|
|
|
76742
77205
|
function findTelegramHistoryDb() {
|
|
76743
77206
|
const candidates = [
|
|
76744
77207
|
process.env.TELEGRAM_STATE_DIR ? join31(process.env.TELEGRAM_STATE_DIR, "history.db") : null,
|
|
76745
|
-
join31(
|
|
77208
|
+
join31(homedir15(), ".claude", "channels", "telegram", "history.db")
|
|
76746
77209
|
].filter(Boolean);
|
|
76747
77210
|
for (const c of candidates) {
|
|
76748
77211
|
if (existsSync38(c))
|
|
@@ -81361,7 +81824,7 @@ init_auto_unlock();
|
|
|
81361
81824
|
var import_yaml11 = __toESM(require_dist(), 1);
|
|
81362
81825
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
81363
81826
|
import { readFileSync as readFileSync39, statSync as statSync28 } from "node:fs";
|
|
81364
|
-
import { homedir as
|
|
81827
|
+
import { homedir as homedir19 } from "node:os";
|
|
81365
81828
|
import { join as join40 } from "node:path";
|
|
81366
81829
|
|
|
81367
81830
|
class EncryptFailedError extends Error {
|
|
@@ -81400,7 +81863,7 @@ function setVaultBrokerAutoUnlock(configPath, value) {
|
|
|
81400
81863
|
} catch {}
|
|
81401
81864
|
writeConfigFileSync(configPath, doc.toString(), mode);
|
|
81402
81865
|
}
|
|
81403
|
-
var DEFAULT_COMPOSE_FILE = join40(
|
|
81866
|
+
var DEFAULT_COMPOSE_FILE = join40(homedir19(), ".switchroom", "compose", "docker-compose.yml");
|
|
81404
81867
|
async function applyAutoUnlock(opts = {}) {
|
|
81405
81868
|
const log = opts.log ?? ((s) => console.log(s));
|
|
81406
81869
|
const err = opts.err ?? ((s) => console.error(s));
|
|
@@ -82121,7 +82584,7 @@ init_loader();
|
|
|
82121
82584
|
init_loader();
|
|
82122
82585
|
init_client();
|
|
82123
82586
|
import { join as join41 } from "node:path";
|
|
82124
|
-
import { homedir as
|
|
82587
|
+
import { homedir as homedir20 } from "node:os";
|
|
82125
82588
|
function parseDuration(raw) {
|
|
82126
82589
|
const lower = raw.toLowerCase().trim();
|
|
82127
82590
|
if (lower === "0" || lower === "never" || lower === "none")
|
|
@@ -82213,7 +82676,7 @@ function registerVaultGrantCommands(vault, program3) {
|
|
|
82213
82676
|
console.error(source_default.red(`Failed to mint grant: ${result.msg}`));
|
|
82214
82677
|
process.exit(1);
|
|
82215
82678
|
}
|
|
82216
|
-
const tokenPath = join41(
|
|
82679
|
+
const tokenPath = join41(homedir20(), ".switchroom", "agents", agent, ".vault-token");
|
|
82217
82680
|
console.log(source_default.green(`\u2713 Grant minted`));
|
|
82218
82681
|
if (process.stdout.isTTY) {
|
|
82219
82682
|
console.log(source_default.bold("Token: ") + result.token);
|
|
@@ -82287,7 +82750,7 @@ import {
|
|
|
82287
82750
|
unlinkSync as unlinkSync12,
|
|
82288
82751
|
writeSync as writeSync7
|
|
82289
82752
|
} from "node:fs";
|
|
82290
|
-
import { homedir as
|
|
82753
|
+
import { homedir as homedir21 } from "node:os";
|
|
82291
82754
|
import { join as join42, resolve as resolvePath2 } from "node:path";
|
|
82292
82755
|
var LATEST_SYMLINK = "vault.enc.latest.bak";
|
|
82293
82756
|
var MANIFEST_FILE = "manifest.jsonl";
|
|
@@ -82486,7 +82949,7 @@ function backupVault(opts) {
|
|
|
82486
82949
|
};
|
|
82487
82950
|
}
|
|
82488
82951
|
function defaultHome() {
|
|
82489
|
-
return process.env.HOME ??
|
|
82952
|
+
return process.env.HOME ?? homedir21();
|
|
82490
82953
|
}
|
|
82491
82954
|
|
|
82492
82955
|
// src/cli/vault-backup.ts
|
|
@@ -84957,7 +85420,7 @@ Demoting memory ${source_default.cyan(memoryId)}`));
|
|
|
84957
85420
|
// src/cli/web.ts
|
|
84958
85421
|
init_source();
|
|
84959
85422
|
import { join as join56 } from "node:path";
|
|
84960
|
-
import { homedir as
|
|
85423
|
+
import { homedir as homedir30 } from "node:os";
|
|
84961
85424
|
|
|
84962
85425
|
// src/web/server.ts
|
|
84963
85426
|
init_merge();
|
|
@@ -84974,7 +85437,7 @@ import {
|
|
|
84974
85437
|
constants as fsConstants3
|
|
84975
85438
|
} from "node:fs";
|
|
84976
85439
|
import { resolve as resolve34, extname, join as join55, relative as relative3, dirname as dirname18 } from "node:path";
|
|
84977
|
-
import { homedir as
|
|
85440
|
+
import { homedir as homedir29 } from "node:os";
|
|
84978
85441
|
import { timingSafeEqual as timingSafeEqual3, randomBytes as randomBytes11 } from "node:crypto";
|
|
84979
85442
|
|
|
84980
85443
|
// src/web/api.ts
|
|
@@ -85053,8 +85516,8 @@ init_hindsight();
|
|
|
85053
85516
|
// src/web/blocked-approvals-read.ts
|
|
85054
85517
|
import { readFileSync as readFileSync47, readdirSync as readdirSync21 } from "node:fs";
|
|
85055
85518
|
import { resolve as resolve30, join as join46 } from "node:path";
|
|
85056
|
-
import { homedir as
|
|
85057
|
-
function blockedApprovalsDir(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ??
|
|
85519
|
+
import { homedir as homedir22 } from "node:os";
|
|
85520
|
+
function blockedApprovalsDir(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir22()) {
|
|
85058
85521
|
return resolve30(home2, ".switchroom", "blocked-approvals");
|
|
85059
85522
|
}
|
|
85060
85523
|
var FALLBACK_RECORD_NAME = "blocked-approval.json";
|
|
@@ -85256,7 +85719,7 @@ init_client();
|
|
|
85256
85719
|
init_posthog();
|
|
85257
85720
|
init_loader();
|
|
85258
85721
|
init_merge();
|
|
85259
|
-
import { homedir as
|
|
85722
|
+
import { homedir as homedir25 } from "node:os";
|
|
85260
85723
|
|
|
85261
85724
|
// src/web/config-edit-plan.ts
|
|
85262
85725
|
init_schema();
|
|
@@ -85351,7 +85814,7 @@ function generateUnifiedDiff(before, after, name = "switchroom.yaml", gitBin = "
|
|
|
85351
85814
|
// src/web/hostd-config-propose.ts
|
|
85352
85815
|
init_client5();
|
|
85353
85816
|
import { existsSync as existsSync53 } from "node:fs";
|
|
85354
|
-
import { homedir as
|
|
85817
|
+
import { homedir as homedir24 } from "node:os";
|
|
85355
85818
|
import { join as join49 } from "node:path";
|
|
85356
85819
|
var PROPOSE_TIMEOUT_MS = 61 * 60 * 1000;
|
|
85357
85820
|
function resolveHostdOperatorSocket(env2 = process.env) {
|
|
@@ -85360,7 +85823,7 @@ function resolveHostdOperatorSocket(env2 = process.env) {
|
|
|
85360
85823
|
return override;
|
|
85361
85824
|
const candidates = [
|
|
85362
85825
|
"/host-home/.switchroom/hostd/operator/sock",
|
|
85363
|
-
join49(
|
|
85826
|
+
join49(homedir24(), ".switchroom", "hostd", "operator", "sock")
|
|
85364
85827
|
];
|
|
85365
85828
|
for (const c of candidates) {
|
|
85366
85829
|
if (existsSync53(c))
|
|
@@ -85628,7 +86091,7 @@ var PHASE4_MIGRATIONS = [
|
|
|
85628
86091
|
];
|
|
85629
86092
|
function applySchema(db) {
|
|
85630
86093
|
db.exec("PRAGMA journal_mode = WAL");
|
|
85631
|
-
db.exec("PRAGMA synchronous =
|
|
86094
|
+
db.exec("PRAGMA synchronous = FULL");
|
|
85632
86095
|
db.exec("PRAGMA busy_timeout = 5000");
|
|
85633
86096
|
db.exec(SCHEMA_SQL);
|
|
85634
86097
|
for (const sql of [...PHASE1_MIGRATIONS, ...PHASE2_MIGRATIONS, ...PHASE3_MIGRATIONS, ...PHASE4_MIGRATIONS]) {
|
|
@@ -86613,10 +87076,10 @@ async function handleGetApprovals() {
|
|
|
86613
87076
|
const sorted = [...decisions].sort((a, b) => b.granted_at - a.granted_at);
|
|
86614
87077
|
return { reachable: true, decisions: sorted };
|
|
86615
87078
|
}
|
|
86616
|
-
function brokerOperatorSocketPath(home2 = process.env.HOME ||
|
|
87079
|
+
function brokerOperatorSocketPath(home2 = process.env.HOME || homedir25()) {
|
|
86617
87080
|
return join51(home2, ".switchroom", "broker-operator", "sock");
|
|
86618
87081
|
}
|
|
86619
|
-
function resolveBrokerOperatorSocket(home2 = process.env.HOME ||
|
|
87082
|
+
function resolveBrokerOperatorSocket(home2 = process.env.HOME || homedir25()) {
|
|
86620
87083
|
const p = brokerOperatorSocketPath(home2);
|
|
86621
87084
|
return existsSync54(p) ? p : null;
|
|
86622
87085
|
}
|
|
@@ -87011,7 +87474,7 @@ init_fleet_health_read();
|
|
|
87011
87474
|
// src/web/webhook-handler.ts
|
|
87012
87475
|
import { appendFileSync as appendFileSync4, existsSync as existsSync56, mkdirSync as mkdirSync31, readFileSync as readFileSync53, writeFileSync as writeFileSync20 } from "fs";
|
|
87013
87476
|
import { join as join53 } from "path";
|
|
87014
|
-
import { homedir as
|
|
87477
|
+
import { homedir as homedir28 } from "os";
|
|
87015
87478
|
|
|
87016
87479
|
// src/web/webhook-verify.ts
|
|
87017
87480
|
import { createHmac as createHmac2, timingSafeEqual } from "crypto";
|
|
@@ -87221,11 +87684,11 @@ function forwardToGateway(socketPath, req, opts = {}) {
|
|
|
87221
87684
|
// src/web/webhook-edge.ts
|
|
87222
87685
|
import { existsSync as existsSync55, readFileSync as readFileSync52 } from "fs";
|
|
87223
87686
|
import { join as join52 } from "path";
|
|
87224
|
-
import { homedir as
|
|
87687
|
+
import { homedir as homedir27 } from "os";
|
|
87225
87688
|
import { timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
87226
87689
|
var EDGE_HEADER = "x-switchroom-edge";
|
|
87227
87690
|
function edgeSecretPath() {
|
|
87228
|
-
return join52(
|
|
87691
|
+
return join52(homedir27(), ".switchroom", "webhook-edge-secret");
|
|
87229
87692
|
}
|
|
87230
87693
|
function loadEdgeSecret(path6) {
|
|
87231
87694
|
const p = path6 ?? edgeSecretPath();
|
|
@@ -87385,7 +87848,7 @@ function writeThrottleIssue(agent, source, now, telegramDir, log) {
|
|
|
87385
87848
|
async function handleWebhookIngest(args, deps = {}) {
|
|
87386
87849
|
const log = deps.log ?? ((s) => process.stderr.write(s));
|
|
87387
87850
|
const now = (deps.now ?? Date.now)();
|
|
87388
|
-
const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join53(
|
|
87851
|
+
const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join53(homedir28(), ".switchroom", "agents", a));
|
|
87389
87852
|
const rateLimiter = deps.rateLimiter ?? defaultRateLimiter;
|
|
87390
87853
|
const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir);
|
|
87391
87854
|
if (!args.agentExists) {
|
|
@@ -88450,7 +88913,7 @@ function resolveWebToken() {
|
|
|
88450
88913
|
const fromEnv = process.env.SWITCHROOM_WEB_TOKEN;
|
|
88451
88914
|
if (fromEnv && fromEnv.length > 0)
|
|
88452
88915
|
return fromEnv;
|
|
88453
|
-
const home2 = process.env.HOME ??
|
|
88916
|
+
const home2 = process.env.HOME ?? homedir29();
|
|
88454
88917
|
const tokenPath = join55(home2, ".switchroom", "web-token");
|
|
88455
88918
|
if (existsSync58(tokenPath)) {
|
|
88456
88919
|
const existing = readFileSync55(tokenPath, "utf8").trim();
|
|
@@ -88543,7 +89006,7 @@ function checkWsAuth(req, token, server) {
|
|
|
88543
89006
|
return presented !== null && constantTimeEqual(presented, token);
|
|
88544
89007
|
}
|
|
88545
89008
|
function loadWebhookSecrets() {
|
|
88546
|
-
const path6 = join55(
|
|
89009
|
+
const path6 = join55(homedir29(), ".switchroom", "webhook-secrets.json");
|
|
88547
89010
|
if (!existsSync58(path6))
|
|
88548
89011
|
return {};
|
|
88549
89012
|
try {
|
|
@@ -89251,7 +89714,7 @@ async function handleWebStartupFailure(err, configPath, stateFile) {
|
|
|
89251
89714
|
throw err;
|
|
89252
89715
|
}
|
|
89253
89716
|
function webCrashStatePath() {
|
|
89254
|
-
return join56(
|
|
89717
|
+
return join56(homedir30(), ".switchroom", "web", ".crashloop-state.json");
|
|
89255
89718
|
}
|
|
89256
89719
|
function registerWebCommand(program3) {
|
|
89257
89720
|
program3.command("web").description("Start the web dashboard for monitoring agents").option("-p, --port <port>", "Port to listen on", "8080").option("-b, --bind <host>", "Host/IP to bind to (default: 127.0.0.1, localhost-only)", "127.0.0.1").action(withConfigError(async (opts) => {
|
|
@@ -90803,10 +91266,10 @@ init_source();
|
|
|
90803
91266
|
init_loader();
|
|
90804
91267
|
init_lifecycle();
|
|
90805
91268
|
init_compose_env();
|
|
90806
|
-
import { existsSync as existsSync80, mkdirSync as mkdirSync45, readFileSync as
|
|
91269
|
+
import { existsSync as existsSync80, mkdirSync as mkdirSync45, readFileSync as readFileSync74, realpathSync as realpathSync6, statSync as statSync45, chownSync as chownSync8 } from "node:fs";
|
|
90807
91270
|
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
90808
|
-
import { join as
|
|
90809
|
-
import { homedir as
|
|
91271
|
+
import { join as join84, dirname as dirname33, resolve as resolve45 } from "node:path";
|
|
91272
|
+
import { homedir as homedir44 } from "node:os";
|
|
90810
91273
|
|
|
90811
91274
|
// src/cli/release-yaml.ts
|
|
90812
91275
|
var import_yaml19 = __toESM(require_dist(), 1);
|
|
@@ -90938,13 +91401,13 @@ import {
|
|
|
90938
91401
|
cpSync as cpSync2,
|
|
90939
91402
|
existsSync as existsSync78,
|
|
90940
91403
|
mkdirSync as mkdirSync43,
|
|
90941
|
-
readFileSync as
|
|
91404
|
+
readFileSync as readFileSync73,
|
|
90942
91405
|
readdirSync as readdirSync27,
|
|
90943
91406
|
renameSync as renameSync18,
|
|
90944
91407
|
rmSync as rmSync13,
|
|
90945
91408
|
writeFileSync as writeFileSync30
|
|
90946
91409
|
} from "node:fs";
|
|
90947
|
-
import { join as
|
|
91410
|
+
import { join as join83 } from "node:path";
|
|
90948
91411
|
var BUNDLED_SKILL_MANIFEST_NAME = ".switchroom-manifest.json";
|
|
90949
91412
|
function listSkillDirs(dir) {
|
|
90950
91413
|
if (!existsSync78(dir))
|
|
@@ -90952,11 +91415,11 @@ function listSkillDirs(dir) {
|
|
|
90952
91415
|
return readdirSync27(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
90953
91416
|
}
|
|
90954
91417
|
function readBundledSkillManifest(poolDir) {
|
|
90955
|
-
const path7 =
|
|
91418
|
+
const path7 = join83(poolDir, BUNDLED_SKILL_MANIFEST_NAME);
|
|
90956
91419
|
if (!existsSync78(path7))
|
|
90957
91420
|
return { firstRun: true };
|
|
90958
91421
|
try {
|
|
90959
|
-
const parsed = JSON.parse(
|
|
91422
|
+
const parsed = JSON.parse(readFileSync73(path7, "utf8"));
|
|
90960
91423
|
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.skills) || !parsed.skills.every((s) => typeof s === "string")) {
|
|
90961
91424
|
return { corrupt: true };
|
|
90962
91425
|
}
|
|
@@ -90967,7 +91430,7 @@ function readBundledSkillManifest(poolDir) {
|
|
|
90967
91430
|
}
|
|
90968
91431
|
}
|
|
90969
91432
|
function stageAndSwap(srcSkill, destSkill, poolDir, name) {
|
|
90970
|
-
const staging =
|
|
91433
|
+
const staging = join83(poolDir, `.tmp-${name}-${process.pid}-${Date.now()}`);
|
|
90971
91434
|
try {
|
|
90972
91435
|
rmSync13(staging, { recursive: true, force: true });
|
|
90973
91436
|
cpSync2(srcSkill, staging, { recursive: true, dereference: false });
|
|
@@ -90996,11 +91459,11 @@ function syncBundledSkills(opts) {
|
|
|
90996
91459
|
const shipped = listSkillDirs(source).sort();
|
|
90997
91460
|
const shippedSet = new Set(shipped);
|
|
90998
91461
|
for (const name of shipped) {
|
|
90999
|
-
const destSkill =
|
|
91462
|
+
const destSkill = join83(dest, name);
|
|
91000
91463
|
const existed = existsSync78(destSkill);
|
|
91001
91464
|
let transferred = false;
|
|
91002
91465
|
if (existed && !priorSkills.has(name) && !result.firstRun) {
|
|
91003
|
-
const backup =
|
|
91466
|
+
const backup = join83(dest, `${name}.operator-backup-${process.pid}-${Date.now()}`);
|
|
91004
91467
|
try {
|
|
91005
91468
|
renameSync18(destSkill, backup);
|
|
91006
91469
|
transferred = true;
|
|
@@ -91014,7 +91477,7 @@ function syncBundledSkills(opts) {
|
|
|
91014
91477
|
continue;
|
|
91015
91478
|
}
|
|
91016
91479
|
}
|
|
91017
|
-
stageAndSwap(
|
|
91480
|
+
stageAndSwap(join83(source, name), destSkill, dest, name);
|
|
91018
91481
|
if (!transferred && (priorSkills.has(name) || existed))
|
|
91019
91482
|
result.updated.push(name);
|
|
91020
91483
|
else
|
|
@@ -91024,7 +91487,7 @@ function syncBundledSkills(opts) {
|
|
|
91024
91487
|
for (const name of priorSkills) {
|
|
91025
91488
|
if (shippedSet.has(name))
|
|
91026
91489
|
continue;
|
|
91027
|
-
const target =
|
|
91490
|
+
const target = join83(dest, name);
|
|
91028
91491
|
if (existsSync78(target)) {
|
|
91029
91492
|
rmSync13(target, { recursive: true, force: true });
|
|
91030
91493
|
result.removed.push(name);
|
|
@@ -91043,8 +91506,8 @@ function syncBundledSkills(opts) {
|
|
|
91043
91506
|
skills: shipped,
|
|
91044
91507
|
updatedAt: new Date().toISOString()
|
|
91045
91508
|
};
|
|
91046
|
-
const manifestPath =
|
|
91047
|
-
const manifestTmp =
|
|
91509
|
+
const manifestPath = join83(dest, BUNDLED_SKILL_MANIFEST_NAME);
|
|
91510
|
+
const manifestTmp = join83(dest, `${BUNDLED_SKILL_MANIFEST_NAME}.tmp-${process.pid}-${Date.now()}`);
|
|
91048
91511
|
writeFileSync30(manifestTmp, JSON.stringify(manifest, null, 2) + `
|
|
91049
91512
|
`, "utf8");
|
|
91050
91513
|
renameSync18(manifestTmp, manifestPath);
|
|
@@ -91336,7 +91799,7 @@ init_component_versions();
|
|
|
91336
91799
|
function defaultPersistPin(configPath) {
|
|
91337
91800
|
return (pin) => {
|
|
91338
91801
|
const path7 = configPath ?? findConfigFile();
|
|
91339
|
-
const before =
|
|
91802
|
+
const before = readFileSync74(path7, "utf8");
|
|
91340
91803
|
const after = setReleasePinInConfig(before, pin);
|
|
91341
91804
|
if (after === before)
|
|
91342
91805
|
return;
|
|
@@ -91350,13 +91813,13 @@ function defaultPersistPin(configPath) {
|
|
|
91350
91813
|
} catch {}
|
|
91351
91814
|
};
|
|
91352
91815
|
}
|
|
91353
|
-
var DEFAULT_COMPOSE_PATH2 =
|
|
91816
|
+
var DEFAULT_COMPOSE_PATH2 = join84(homedir44(), ".switchroom", "compose", "docker-compose.yml");
|
|
91354
91817
|
function runningFromSwitchroomCheckout(scriptPath) {
|
|
91355
91818
|
let dir = dirname33(scriptPath);
|
|
91356
91819
|
for (let i = 0;i < 12; i++) {
|
|
91357
|
-
if (existsSync80(
|
|
91820
|
+
if (existsSync80(join84(dir, ".git"))) {
|
|
91358
91821
|
try {
|
|
91359
|
-
const pkg = JSON.parse(
|
|
91822
|
+
const pkg = JSON.parse(readFileSync74(join84(dir, "package.json"), "utf-8"));
|
|
91360
91823
|
if (pkg.name === "switchroom")
|
|
91361
91824
|
return true;
|
|
91362
91825
|
} catch {}
|
|
@@ -91622,7 +92085,7 @@ function planUpdate(opts) {
|
|
|
91622
92085
|
return;
|
|
91623
92086
|
}
|
|
91624
92087
|
const source = resolve45(import.meta.dirname, "../../skills");
|
|
91625
|
-
const dest =
|
|
92088
|
+
const dest = join84(homedir44(), ".switchroom", "skills", "_bundled");
|
|
91626
92089
|
if (!existsSync80(source)) {
|
|
91627
92090
|
process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
|
|
91628
92091
|
`);
|
|
@@ -91654,11 +92117,11 @@ function planUpdate(opts) {
|
|
|
91654
92117
|
run: () => {
|
|
91655
92118
|
if (opts.syncBundledSkillsFn)
|
|
91656
92119
|
return;
|
|
91657
|
-
const dest =
|
|
92120
|
+
const dest = join84(homedir44(), ".switchroom", "skills", "_bundled");
|
|
91658
92121
|
if (!existsSync80(dest)) {
|
|
91659
92122
|
return;
|
|
91660
92123
|
}
|
|
91661
|
-
const missing = getBuiltinDefaultSkillEntries().map((e) => e.key).filter((key) => !existsSync80(
|
|
92124
|
+
const missing = getBuiltinDefaultSkillEntries().map((e) => e.key).filter((key) => !existsSync80(join84(dest, key)));
|
|
91662
92125
|
if (missing.length > 0) {
|
|
91663
92126
|
throw new Error(`verify-bundled-skills: builtin default skill(s) missing from the pool after sync: ` + `${missing.join(", ")}. These ship in the CLI package and must exist in ${dest}. ` + `This is a broken sync or a packaging regression \u2014 the pool is not converged.`);
|
|
91664
92127
|
}
|
|
@@ -91693,7 +92156,7 @@ function planUpdate(opts) {
|
|
|
91693
92156
|
description: "docker compose up -d --remove-orphans (recreates services with new images / compose)",
|
|
91694
92157
|
run: () => {
|
|
91695
92158
|
try {
|
|
91696
|
-
const composeText =
|
|
92159
|
+
const composeText = readFileSync74(composePath, "utf8");
|
|
91697
92160
|
const pf = validateBindSources(composeText);
|
|
91698
92161
|
if (!pf.ok)
|
|
91699
92162
|
throw new Error(formatPreflightError(pf));
|
|
@@ -91772,10 +92235,10 @@ function defaultStatusProbe(composePath) {
|
|
|
91772
92235
|
} catch {}
|
|
91773
92236
|
let dir = dirname33(scriptPath);
|
|
91774
92237
|
for (let i = 0;i < 8; i++) {
|
|
91775
|
-
const pkgPath =
|
|
92238
|
+
const pkgPath = join84(dir, "package.json");
|
|
91776
92239
|
if (existsSync80(pkgPath)) {
|
|
91777
92240
|
try {
|
|
91778
|
-
const pkg = JSON.parse(
|
|
92241
|
+
const pkg = JSON.parse(readFileSync74(pkgPath, "utf-8"));
|
|
91779
92242
|
if (typeof pkg.version === "string")
|
|
91780
92243
|
cliVersion = pkg.version;
|
|
91781
92244
|
} catch (err) {
|
|
@@ -92044,22 +92507,22 @@ function registerUpdateCommand(program3) {
|
|
|
92044
92507
|
// src/cli/rollout.ts
|
|
92045
92508
|
init_helpers();
|
|
92046
92509
|
import { spawnSync as spawnSync18 } from "node:child_process";
|
|
92047
|
-
import { readFileSync as
|
|
92048
|
-
import { homedir as
|
|
92510
|
+
import { readFileSync as readFileSync76, chownSync as chownSync9, statSync as statSync47 } from "node:fs";
|
|
92511
|
+
import { homedir as homedir46 } from "node:os";
|
|
92049
92512
|
|
|
92050
92513
|
// src/cli/rollout-pin-journal.ts
|
|
92051
92514
|
import {
|
|
92052
92515
|
existsSync as existsSync81,
|
|
92053
|
-
readFileSync as
|
|
92516
|
+
readFileSync as readFileSync75,
|
|
92054
92517
|
writeFileSync as writeFileSync31,
|
|
92055
92518
|
renameSync as renameSync20,
|
|
92056
92519
|
unlinkSync as unlinkSync15,
|
|
92057
92520
|
statSync as statSync46,
|
|
92058
92521
|
mkdirSync as mkdirSync46
|
|
92059
92522
|
} from "node:fs";
|
|
92060
|
-
import { homedir as
|
|
92523
|
+
import { homedir as homedir45 } from "node:os";
|
|
92061
92524
|
import { createHash as createHash17 } from "node:crypto";
|
|
92062
|
-
import { join as
|
|
92525
|
+
import { join as join85, basename as basename9, resolve as resolve46, dirname as dirname34 } from "node:path";
|
|
92063
92526
|
init_flock();
|
|
92064
92527
|
var PIN_JOURNAL_MAX_AGE_MS = 15 * 60 * 1000;
|
|
92065
92528
|
var STATE_DIR_NAME = ".switchroom";
|
|
@@ -92072,12 +92535,12 @@ function pinJournalDir(configPath) {
|
|
|
92072
92535
|
if (basename9(dir) === STATE_DIR_NAME)
|
|
92073
92536
|
return dir;
|
|
92074
92537
|
}
|
|
92075
|
-
return
|
|
92538
|
+
return join85(homedir45(), STATE_DIR_NAME);
|
|
92076
92539
|
}
|
|
92077
92540
|
function pinJournalPath(configPath) {
|
|
92078
92541
|
const abs = resolve46(configPath);
|
|
92079
92542
|
const key = createHash17("sha256").update(abs).digest("hex").slice(0, 12);
|
|
92080
|
-
return
|
|
92543
|
+
return join85(pinJournalDir(abs), `.rollout-pin-journal.${basename9(abs)}.${key}.json`);
|
|
92081
92544
|
}
|
|
92082
92545
|
function isPidAlive(pid) {
|
|
92083
92546
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
@@ -92111,7 +92574,7 @@ function readPinJournal(configPath, warn = (m) => process.stderr.write(m)) {
|
|
|
92111
92574
|
const p = pinJournalPath(configPath);
|
|
92112
92575
|
let raw;
|
|
92113
92576
|
try {
|
|
92114
|
-
raw =
|
|
92577
|
+
raw = readFileSync75(p, "utf8");
|
|
92115
92578
|
} catch (e) {
|
|
92116
92579
|
if (existsSync81(p)) {
|
|
92117
92580
|
warn(`\u26a0\ufe0f rollout pin journal: ${p} exists but could not be read ` + `(${e.message}). A provisional \`release.pin\` may be ` + `uncommitted \u2014 verify it host-side before the next reconcile.
|
|
@@ -92164,7 +92627,7 @@ function beginPinPersist(configPath, pin, opts = {}) {
|
|
|
92164
92627
|
warn(`\u26a0\ufe0f rollout pin journal: overwriting an ABANDONED journal at ${p} ` + `(pid ${existing.pid} recorded ${existing.at}, provisional pin ` + `${existing.pin}). Its roll never committed or reverted; \`release.pin\` ` + `in ${configPath} may still name that unproven build \u2014 verify it.
|
|
92165
92628
|
`);
|
|
92166
92629
|
}
|
|
92167
|
-
const priorPin = getReleasePinFromConfig(
|
|
92630
|
+
const priorPin = getReleasePinFromConfig(readFileSync75(configPath, "utf8"));
|
|
92168
92631
|
const journal = {
|
|
92169
92632
|
v: 1,
|
|
92170
92633
|
configPath,
|
|
@@ -92207,7 +92670,7 @@ function rollbackPinPersist(configPath, opts = {}) {
|
|
|
92207
92670
|
}
|
|
92208
92671
|
}
|
|
92209
92672
|
try {
|
|
92210
|
-
const current =
|
|
92673
|
+
const current = readFileSync75(configPath, "utf8");
|
|
92211
92674
|
const next = journal.priorPin ? setReleasePinInConfig(current, journal.priorPin) : deleteReleasePinInConfig(current);
|
|
92212
92675
|
let mode = 384;
|
|
92213
92676
|
try {
|
|
@@ -92306,6 +92769,8 @@ function checkDowngrade(opts) {
|
|
|
92306
92769
|
|
|
92307
92770
|
// src/cli/rollout.ts
|
|
92308
92771
|
init_agent_owned_tree();
|
|
92772
|
+
init_component_versions();
|
|
92773
|
+
var VERIFY_COMPONENTS_STEP = "verify-components";
|
|
92309
92774
|
function normalizeVersion(v) {
|
|
92310
92775
|
return v.trim().replace(/^v/, "");
|
|
92311
92776
|
}
|
|
@@ -92339,6 +92804,7 @@ function planRollout(agents, opts = {}) {
|
|
|
92339
92804
|
}
|
|
92340
92805
|
steps.push({ kind: "refresh-hindsight" });
|
|
92341
92806
|
steps.push({ kind: "sweep" });
|
|
92807
|
+
steps.push({ kind: "verify-components" });
|
|
92342
92808
|
return steps;
|
|
92343
92809
|
}
|
|
92344
92810
|
if (opts.pinToPersist)
|
|
@@ -92353,6 +92819,7 @@ function planRollout(agents, opts = {}) {
|
|
|
92353
92819
|
steps.push({ kind: "refresh-hindsight" });
|
|
92354
92820
|
}
|
|
92355
92821
|
steps.push({ kind: "sweep" });
|
|
92822
|
+
steps.push({ kind: "verify-components" });
|
|
92356
92823
|
return steps;
|
|
92357
92824
|
}
|
|
92358
92825
|
function formatRolloutPlan(steps, target) {
|
|
@@ -92382,6 +92849,9 @@ function formatRolloutPlan(steps, target) {
|
|
|
92382
92849
|
case "sweep":
|
|
92383
92850
|
lines.push(` ${n}. sweep \u2014 print per-agent version table`);
|
|
92384
92851
|
break;
|
|
92852
|
+
case "verify-components":
|
|
92853
|
+
lines.push(` ${n}. verify-components \u2014 assert EVERY in-scope component is on ${target} ` + `(same inventory \`switchroom update --check\` uses); non-zero exit if any is behind`);
|
|
92854
|
+
break;
|
|
92385
92855
|
}
|
|
92386
92856
|
}
|
|
92387
92857
|
lines.push("");
|
|
@@ -92429,9 +92899,17 @@ function encodeRolloutResultLine(result) {
|
|
|
92429
92899
|
...result.got !== undefined ? { got: result.got } : {},
|
|
92430
92900
|
...result.timedOut ? { timedOut: true } : {},
|
|
92431
92901
|
...result.pinReverted ? { pinReverted: true } : {},
|
|
92902
|
+
...result.drifted && result.drifted.length > 0 ? { drifted: result.drifted } : {},
|
|
92432
92903
|
warnings: result.warnings
|
|
92433
92904
|
});
|
|
92434
92905
|
}
|
|
92906
|
+
function evaluateRolloutDrift(components, target, steps, hostdContext) {
|
|
92907
|
+
const report = detectComponentDrift(components, `v${normalizeVersion(target)}`);
|
|
92908
|
+
const owners = gatedOwners(steps.map((s) => s.kind), hostdContext);
|
|
92909
|
+
const rolledAgents = new Set(steps.flatMap((s) => s.kind === "restart-agent" ? [s.agent] : []));
|
|
92910
|
+
const { gated, exempt } = partitionDrift(report.behind, { owners, rolledAgents });
|
|
92911
|
+
return { gated, exempt, target: report.target };
|
|
92912
|
+
}
|
|
92435
92913
|
function executeRollout(steps, target, deps, execOpts = {}) {
|
|
92436
92914
|
const targetNorm = normalizeVersion(target);
|
|
92437
92915
|
const rolled = [];
|
|
@@ -92440,6 +92918,7 @@ function executeRollout(steps, target, deps, execOpts = {}) {
|
|
|
92440
92918
|
const totalAgents = steps.filter((s) => s.kind === "restart-agent").length;
|
|
92441
92919
|
let agentIndex = 0;
|
|
92442
92920
|
let pinPersisted = false;
|
|
92921
|
+
let driftVerdict = null;
|
|
92443
92922
|
const fail4 = (partial) => {
|
|
92444
92923
|
let pinReverted = false;
|
|
92445
92924
|
if (pinPersisted && deps.revertPin) {
|
|
@@ -92580,6 +93059,25 @@ function executeRollout(steps, target, deps, execOpts = {}) {
|
|
|
92580
93059
|
}
|
|
92581
93060
|
break;
|
|
92582
93061
|
}
|
|
93062
|
+
case "verify-components": {
|
|
93063
|
+
deps.log(`ROLL_STEP verify-components \u2014 asserting every in-scope component is on ${target}`);
|
|
93064
|
+
if (!deps.collectComponents) {
|
|
93065
|
+
warnings.push(`verify-components ran with NO component collector wired, so this ` + `roll did not prove the host converged. Confirm with ` + `\`switchroom update --check\` \u2014 anything still BEHIND ${target} ` + `was NOT caught here.`);
|
|
93066
|
+
break;
|
|
93067
|
+
}
|
|
93068
|
+
let inventory;
|
|
93069
|
+
try {
|
|
93070
|
+
inventory = deps.collectComponents();
|
|
93071
|
+
} catch (e) {
|
|
93072
|
+
warnings.push(`verify-components could not read the component inventory ` + `(${e.message}), so this roll did not prove the host ` + `converged. Confirm with \`switchroom update --check\`.`);
|
|
93073
|
+
break;
|
|
93074
|
+
}
|
|
93075
|
+
driftVerdict = evaluateRolloutDrift(inventory, target, steps, execOpts.hostdContext === true);
|
|
93076
|
+
for (const c of driftVerdict.exempt) {
|
|
93077
|
+
warnings.push(`${c.name} is on ${c.version ?? "an unreadable version"}, behind ` + `${target}, but was OUTSIDE this roll's scope (no step in this ` + `plan owns it). Finish it: ` + `${remediationFor(classifyComponent(c), `v${normalizeVersion(target)}`)}`);
|
|
93078
|
+
}
|
|
93079
|
+
break;
|
|
93080
|
+
}
|
|
92583
93081
|
}
|
|
92584
93082
|
}
|
|
92585
93083
|
} catch (e) {
|
|
@@ -92599,10 +93097,25 @@ function executeRollout(steps, target, deps, execOpts = {}) {
|
|
|
92599
93097
|
warnings.push(`roll succeeded but committing the durable release.pin threw: ` + `${e.message}. The pin IS written; a stale rollout ` + `journal may cause a later boot to revert it \u2014 verify host-side.`);
|
|
92600
93098
|
}
|
|
92601
93099
|
}
|
|
93100
|
+
if (driftVerdict && driftVerdict.gated.length > 0) {
|
|
93101
|
+
const normalizedTarget = `v${normalizeVersion(target)}`;
|
|
93102
|
+
for (const c of driftVerdict.gated) {
|
|
93103
|
+
deps.log(` \u2717 ${c.name} \u2192 ${c.version ?? "<unreadable>"} (expected ${target}) \u2014 STILL BEHIND`);
|
|
93104
|
+
warnings.push(`${c.name} is STILL on ${c.version ?? "an unreadable version"} after ` + `the roll to ${target} \u2014 it was in this roll's scope and did not ` + `converge. Finish it: ` + `${remediationFor(classifyComponent(c), normalizedTarget)}`);
|
|
93105
|
+
}
|
|
93106
|
+
deps.log(` \u2717 verify-components FAILED \u2014 ${driftVerdict.gated.length} component(s) ` + `still behind ${target}: ${driftVerdict.gated.map((c) => c.name).join(", ")}`);
|
|
93107
|
+
return {
|
|
93108
|
+
ok: false,
|
|
93109
|
+
rolled,
|
|
93110
|
+
warnings,
|
|
93111
|
+
failedStep: VERIFY_COMPONENTS_STEP,
|
|
93112
|
+
drifted: driftVerdict.gated.map((c) => c.name)
|
|
93113
|
+
};
|
|
93114
|
+
}
|
|
92602
93115
|
return { ok: true, rolled, warnings };
|
|
92603
93116
|
}
|
|
92604
93117
|
function resolveRollbackTarget(auditLogPath) {
|
|
92605
|
-
const logPath = auditLogPath ?? defaultAuditLogPath2(
|
|
93118
|
+
const logPath = auditLogPath ?? defaultAuditLogPath2(homedir46());
|
|
92606
93119
|
let raw;
|
|
92607
93120
|
try {
|
|
92608
93121
|
raw = readAuditRaw(logPath, {
|
|
@@ -92714,8 +93227,14 @@ function createRolloutDeps(params) {
|
|
|
92714
93227
|
const r = dockerRun(args);
|
|
92715
93228
|
return r.ok ? r.stdout : null;
|
|
92716
93229
|
}),
|
|
93230
|
+
collectComponents: () => collectComponents(SWITCHROOM_VERSION, (cmd, args) => {
|
|
93231
|
+
if (cmd !== "docker")
|
|
93232
|
+
return { status: 1, stdout: "" };
|
|
93233
|
+
const r = dockerRun(args);
|
|
93234
|
+
return { status: r.ok ? 0 : 1, stdout: r.stdout };
|
|
93235
|
+
}),
|
|
92717
93236
|
persistPin: (pin) => {
|
|
92718
|
-
const before =
|
|
93237
|
+
const before = readFileSync76(configPath, "utf8");
|
|
92719
93238
|
const after = setReleasePinInConfig(before, pin);
|
|
92720
93239
|
if (after === before)
|
|
92721
93240
|
return false;
|
|
@@ -92852,6 +93371,15 @@ function registerRolloutCommand(program3) {
|
|
|
92852
93371
|
process.stdout.write(encodeRolloutResultLine(result) + `
|
|
92853
93372
|
`);
|
|
92854
93373
|
}
|
|
93374
|
+
if (!result.ok && result.failedStep === VERIFY_COMPONENTS_STEP) {
|
|
93375
|
+
process.stderr.write(`
|
|
93376
|
+
\u2717 Rollout INCOMPLETE \u2014 ${(result.drifted ?? []).length} component(s) ` + `still behind ${target}: ${(result.drifted ?? []).join(", ") || "unknown"}.
|
|
93377
|
+
` + ` ${result.rolled.length} agent(s) DID reach ${target} and the pin is ` + `committed \u2014 re-running the agent roll will not fix this.
|
|
93378
|
+
` + ` Run the per-component command named in the warning(s) above, then ` + `\`switchroom update --check\` to confirm the host is converged.
|
|
93379
|
+
`);
|
|
93380
|
+
process.exitCode = 1;
|
|
93381
|
+
return;
|
|
93382
|
+
}
|
|
92855
93383
|
if (!result.ok) {
|
|
92856
93384
|
process.stderr.write(`
|
|
92857
93385
|
\u2717 Rollout STOPPED at ${result.failedStep}` + (result.failedAgent ? ` (${result.failedAgent} \u2192 ${result.got ?? "unreachable"})` : "") + `.
|
|
@@ -92894,8 +93422,8 @@ init_helpers();
|
|
|
92894
93422
|
init_lifecycle();
|
|
92895
93423
|
init_resolve_version();
|
|
92896
93424
|
import { execSync as execSync3 } from "node:child_process";
|
|
92897
|
-
import { existsSync as existsSync82, readFileSync as
|
|
92898
|
-
import { dirname as dirname35, join as
|
|
93425
|
+
import { existsSync as existsSync82, readFileSync as readFileSync77 } from "node:fs";
|
|
93426
|
+
import { dirname as dirname35, join as join86 } from "node:path";
|
|
92899
93427
|
function getClaudeCodeVersion() {
|
|
92900
93428
|
try {
|
|
92901
93429
|
const out = execSync3("claude --version 2>/dev/null", {
|
|
@@ -92945,11 +93473,11 @@ function formatUptime3(timestamp) {
|
|
|
92945
93473
|
function locateSwitchroomInstallDir() {
|
|
92946
93474
|
let dir = import.meta.dirname;
|
|
92947
93475
|
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
92948
|
-
const pkgPath =
|
|
93476
|
+
const pkgPath = join86(dir, "package.json");
|
|
92949
93477
|
if (existsSync82(pkgPath)) {
|
|
92950
93478
|
try {
|
|
92951
|
-
const pkg = JSON.parse(
|
|
92952
|
-
if (pkg.name === "switchroom" && existsSync82(
|
|
93479
|
+
const pkg = JSON.parse(readFileSync77(pkgPath, "utf-8"));
|
|
93480
|
+
if (pkg.name === "switchroom" && existsSync82(join86(dir, ".git"))) {
|
|
92953
93481
|
return dir;
|
|
92954
93482
|
}
|
|
92955
93483
|
} catch {}
|
|
@@ -93133,12 +93661,12 @@ import {
|
|
|
93133
93661
|
statSync as statSync48,
|
|
93134
93662
|
unlinkSync as unlinkSync16
|
|
93135
93663
|
} from "node:fs";
|
|
93136
|
-
import { join as
|
|
93664
|
+
import { join as join87 } from "node:path";
|
|
93137
93665
|
var DEFAULT_SESSION_RETENTION_MAX_COUNT = 20;
|
|
93138
93666
|
var DEFAULT_SESSION_RETENTION_MAX_AGE_DAYS = 30;
|
|
93139
93667
|
var MIN_KEEP = 2;
|
|
93140
93668
|
function collectSessionJsonl(claudeConfigDir) {
|
|
93141
|
-
const projects =
|
|
93669
|
+
const projects = join87(claudeConfigDir, "projects");
|
|
93142
93670
|
if (!existsSync83(projects))
|
|
93143
93671
|
return [];
|
|
93144
93672
|
const found = [];
|
|
@@ -93150,7 +93678,7 @@ function collectSessionJsonl(claudeConfigDir) {
|
|
|
93150
93678
|
return;
|
|
93151
93679
|
}
|
|
93152
93680
|
for (const name of entries) {
|
|
93153
|
-
const full =
|
|
93681
|
+
const full = join87(dir, name);
|
|
93154
93682
|
let st;
|
|
93155
93683
|
try {
|
|
93156
93684
|
st = statSync48(full);
|
|
@@ -93279,14 +93807,14 @@ import {
|
|
|
93279
93807
|
mkdirSync as mkdirSync47,
|
|
93280
93808
|
openSync as openSync16,
|
|
93281
93809
|
readdirSync as readdirSync29,
|
|
93282
|
-
readFileSync as
|
|
93810
|
+
readFileSync as readFileSync78,
|
|
93283
93811
|
renameSync as renameSync21,
|
|
93284
93812
|
statSync as statSync49,
|
|
93285
93813
|
unlinkSync as unlinkSync17,
|
|
93286
93814
|
writeFileSync as writeFileSync32,
|
|
93287
93815
|
writeSync as writeSync9
|
|
93288
93816
|
} from "node:fs";
|
|
93289
|
-
import { join as
|
|
93817
|
+
import { join as join88 } from "node:path";
|
|
93290
93818
|
import { randomBytes as randomBytes13 } from "node:crypto";
|
|
93291
93819
|
import { execSync as execSync4 } from "node:child_process";
|
|
93292
93820
|
|
|
@@ -93717,12 +94245,12 @@ function redactedMarker(ruleId) {
|
|
|
93717
94245
|
var ISSUES_FILE = "issues.jsonl";
|
|
93718
94246
|
var ISSUES_LOCK = "issues.lock";
|
|
93719
94247
|
function readAll(stateDir) {
|
|
93720
|
-
const path7 =
|
|
94248
|
+
const path7 = join88(stateDir, ISSUES_FILE);
|
|
93721
94249
|
if (!existsSync84(path7))
|
|
93722
94250
|
return [];
|
|
93723
94251
|
let raw;
|
|
93724
94252
|
try {
|
|
93725
|
-
raw =
|
|
94253
|
+
raw = readFileSync78(path7, "utf-8");
|
|
93726
94254
|
} catch {
|
|
93727
94255
|
return [];
|
|
93728
94256
|
}
|
|
@@ -93795,7 +94323,7 @@ function record(stateDir, input, nowFn = Date.now) {
|
|
|
93795
94323
|
});
|
|
93796
94324
|
}
|
|
93797
94325
|
function resolve49(stateDir, fingerprint, nowFn = Date.now) {
|
|
93798
|
-
if (!existsSync84(
|
|
94326
|
+
if (!existsSync84(join88(stateDir, ISSUES_FILE)))
|
|
93799
94327
|
return 0;
|
|
93800
94328
|
return withLock(stateDir, () => {
|
|
93801
94329
|
const all = readAll(stateDir);
|
|
@@ -93813,7 +94341,7 @@ function resolve49(stateDir, fingerprint, nowFn = Date.now) {
|
|
|
93813
94341
|
});
|
|
93814
94342
|
}
|
|
93815
94343
|
function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
93816
|
-
if (!existsSync84(
|
|
94344
|
+
if (!existsSync84(join88(stateDir, ISSUES_FILE)))
|
|
93817
94345
|
return 0;
|
|
93818
94346
|
return withLock(stateDir, () => {
|
|
93819
94347
|
const all = readAll(stateDir);
|
|
@@ -93831,7 +94359,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
|
93831
94359
|
});
|
|
93832
94360
|
}
|
|
93833
94361
|
function prune(stateDir, opts = {}) {
|
|
93834
|
-
if (!existsSync84(
|
|
94362
|
+
if (!existsSync84(join88(stateDir, ISSUES_FILE)))
|
|
93835
94363
|
return 0;
|
|
93836
94364
|
return withLock(stateDir, () => {
|
|
93837
94365
|
const all = readAll(stateDir);
|
|
@@ -93864,7 +94392,7 @@ function ensureDir(stateDir) {
|
|
|
93864
94392
|
mkdirSync47(stateDir, { recursive: true });
|
|
93865
94393
|
}
|
|
93866
94394
|
function writeAll(stateDir, events) {
|
|
93867
|
-
const path7 =
|
|
94395
|
+
const path7 = join88(stateDir, ISSUES_FILE);
|
|
93868
94396
|
sweepOrphanTmpFiles(stateDir);
|
|
93869
94397
|
const tmp = `${path7}.tmp-${process.pid}-${randomBytes13(4).toString("hex")}`;
|
|
93870
94398
|
const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
|
|
@@ -93886,7 +94414,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
93886
94414
|
for (const entry of entries) {
|
|
93887
94415
|
if (!entry.startsWith(TMP_PREFIX))
|
|
93888
94416
|
continue;
|
|
93889
|
-
const tmpPath =
|
|
94417
|
+
const tmpPath = join88(stateDir, entry);
|
|
93890
94418
|
try {
|
|
93891
94419
|
const stat = statSync49(tmpPath);
|
|
93892
94420
|
if (stat.mtimeMs < cutoff) {
|
|
@@ -93898,7 +94426,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
93898
94426
|
var LOCK_RETRY_MS = 25;
|
|
93899
94427
|
var LOCK_TIMEOUT_MS = 1e4;
|
|
93900
94428
|
function withLock(stateDir, fn) {
|
|
93901
|
-
const lockPath =
|
|
94429
|
+
const lockPath = join88(stateDir, ISSUES_LOCK);
|
|
93902
94430
|
const startedAt = Date.now();
|
|
93903
94431
|
let fd = null;
|
|
93904
94432
|
while (fd === null) {
|
|
@@ -93933,7 +94461,7 @@ function withLock(stateDir, fn) {
|
|
|
93933
94461
|
function tryStealStaleLock(lockPath) {
|
|
93934
94462
|
let pidStr;
|
|
93935
94463
|
try {
|
|
93936
|
-
pidStr =
|
|
94464
|
+
pidStr = readFileSync78(lockPath, "utf-8").trim();
|
|
93937
94465
|
} catch {
|
|
93938
94466
|
return true;
|
|
93939
94467
|
}
|
|
@@ -94182,20 +94710,20 @@ function relTime(deltaMs) {
|
|
|
94182
94710
|
// src/cli/deps.ts
|
|
94183
94711
|
init_source();
|
|
94184
94712
|
import { existsSync as existsSync87 } from "node:fs";
|
|
94185
|
-
import { homedir as
|
|
94186
|
-
import { join as
|
|
94713
|
+
import { homedir as homedir49 } from "node:os";
|
|
94714
|
+
import { join as join91, resolve as resolve50 } from "node:path";
|
|
94187
94715
|
|
|
94188
94716
|
// src/deps/python.ts
|
|
94189
94717
|
import { createHash as createHash18 } from "node:crypto";
|
|
94190
94718
|
import {
|
|
94191
94719
|
existsSync as existsSync85,
|
|
94192
94720
|
mkdirSync as mkdirSync48,
|
|
94193
|
-
readFileSync as
|
|
94721
|
+
readFileSync as readFileSync79,
|
|
94194
94722
|
rmSync as rmSync15,
|
|
94195
94723
|
writeFileSync as writeFileSync33
|
|
94196
94724
|
} from "node:fs";
|
|
94197
|
-
import { dirname as dirname36, join as
|
|
94198
|
-
import { homedir as
|
|
94725
|
+
import { dirname as dirname36, join as join89 } from "node:path";
|
|
94726
|
+
import { homedir as homedir47 } from "node:os";
|
|
94199
94727
|
import { execFileSync as execFileSync24 } from "node:child_process";
|
|
94200
94728
|
|
|
94201
94729
|
class PythonEnvError extends Error {
|
|
@@ -94207,10 +94735,10 @@ class PythonEnvError extends Error {
|
|
|
94207
94735
|
}
|
|
94208
94736
|
}
|
|
94209
94737
|
function defaultPythonCacheRoot() {
|
|
94210
|
-
return
|
|
94738
|
+
return join89(homedir47(), ".switchroom", "deps", "python");
|
|
94211
94739
|
}
|
|
94212
94740
|
function hashFile(path7) {
|
|
94213
|
-
return createHash18("sha256").update(
|
|
94741
|
+
return createHash18("sha256").update(readFileSync79(path7)).digest("hex");
|
|
94214
94742
|
}
|
|
94215
94743
|
function ensurePythonEnv(opts) {
|
|
94216
94744
|
const { skillName, requirementsPath, force = false } = opts;
|
|
@@ -94219,14 +94747,14 @@ function ensurePythonEnv(opts) {
|
|
|
94219
94747
|
if (!existsSync85(requirementsPath)) {
|
|
94220
94748
|
throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
|
|
94221
94749
|
}
|
|
94222
|
-
const venvDir =
|
|
94223
|
-
const stampPath =
|
|
94224
|
-
const binDir =
|
|
94225
|
-
const pythonBin =
|
|
94226
|
-
const pipBin =
|
|
94750
|
+
const venvDir = join89(cacheRoot, skillName);
|
|
94751
|
+
const stampPath = join89(venvDir, ".requirements.sha256");
|
|
94752
|
+
const binDir = join89(venvDir, "bin");
|
|
94753
|
+
const pythonBin = join89(binDir, "python");
|
|
94754
|
+
const pipBin = join89(binDir, "pip");
|
|
94227
94755
|
const targetHash = hashFile(requirementsPath);
|
|
94228
94756
|
if (!force && existsSync85(stampPath) && existsSync85(pythonBin)) {
|
|
94229
|
-
const existingHash =
|
|
94757
|
+
const existingHash = readFileSync79(stampPath, "utf8").trim();
|
|
94230
94758
|
if (existingHash === targetHash) {
|
|
94231
94759
|
return {
|
|
94232
94760
|
skillName,
|
|
@@ -94278,12 +94806,12 @@ import {
|
|
|
94278
94806
|
copyFileSync as copyFileSync12,
|
|
94279
94807
|
existsSync as existsSync86,
|
|
94280
94808
|
mkdirSync as mkdirSync49,
|
|
94281
|
-
readFileSync as
|
|
94809
|
+
readFileSync as readFileSync80,
|
|
94282
94810
|
rmSync as rmSync16,
|
|
94283
94811
|
writeFileSync as writeFileSync34
|
|
94284
94812
|
} from "node:fs";
|
|
94285
|
-
import { dirname as dirname37, join as
|
|
94286
|
-
import { homedir as
|
|
94813
|
+
import { dirname as dirname37, join as join90 } from "node:path";
|
|
94814
|
+
import { homedir as homedir48 } from "node:os";
|
|
94287
94815
|
import { execFileSync as execFileSync25 } from "node:child_process";
|
|
94288
94816
|
|
|
94289
94817
|
class NodeEnvError extends Error {
|
|
@@ -94306,23 +94834,23 @@ var LOCKFILES_FOR = {
|
|
|
94306
94834
|
npm: ["package-lock.json"]
|
|
94307
94835
|
};
|
|
94308
94836
|
function defaultNodeCacheRoot() {
|
|
94309
|
-
return
|
|
94837
|
+
return join90(homedir48(), ".switchroom", "deps", "node");
|
|
94310
94838
|
}
|
|
94311
94839
|
function hashDepInputs(packageJsonPath) {
|
|
94312
94840
|
const sourceDir = dirname37(packageJsonPath);
|
|
94313
94841
|
const hasher = createHash19("sha256");
|
|
94314
94842
|
hasher.update(`package.json
|
|
94315
94843
|
`);
|
|
94316
|
-
hasher.update(
|
|
94844
|
+
hasher.update(readFileSync80(packageJsonPath));
|
|
94317
94845
|
for (const lockName of ALL_LOCKFILES) {
|
|
94318
|
-
const lockPath =
|
|
94846
|
+
const lockPath = join90(sourceDir, lockName);
|
|
94319
94847
|
if (existsSync86(lockPath)) {
|
|
94320
94848
|
hasher.update(`
|
|
94321
94849
|
`);
|
|
94322
94850
|
hasher.update(lockName);
|
|
94323
94851
|
hasher.update(`
|
|
94324
94852
|
`);
|
|
94325
|
-
hasher.update(
|
|
94853
|
+
hasher.update(readFileSync80(lockPath));
|
|
94326
94854
|
}
|
|
94327
94855
|
}
|
|
94328
94856
|
return hasher.digest("hex");
|
|
@@ -94335,13 +94863,13 @@ function ensureNodeEnv(opts) {
|
|
|
94335
94863
|
throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
|
|
94336
94864
|
}
|
|
94337
94865
|
const sourceDir = dirname37(packageJsonPath);
|
|
94338
|
-
const envDir =
|
|
94339
|
-
const stampPath =
|
|
94340
|
-
const nodeModulesDir =
|
|
94341
|
-
const binDir =
|
|
94866
|
+
const envDir = join90(cacheRoot, skillName);
|
|
94867
|
+
const stampPath = join90(envDir, ".package.sha256");
|
|
94868
|
+
const nodeModulesDir = join90(envDir, "node_modules");
|
|
94869
|
+
const binDir = join90(nodeModulesDir, ".bin");
|
|
94342
94870
|
const targetHash = hashDepInputs(packageJsonPath);
|
|
94343
94871
|
if (!force && existsSync86(stampPath) && existsSync86(nodeModulesDir)) {
|
|
94344
|
-
const existingHash =
|
|
94872
|
+
const existingHash = readFileSync80(stampPath, "utf8").trim();
|
|
94345
94873
|
if (existingHash === targetHash) {
|
|
94346
94874
|
return {
|
|
94347
94875
|
skillName,
|
|
@@ -94356,12 +94884,12 @@ function ensureNodeEnv(opts) {
|
|
|
94356
94884
|
rmSync16(envDir, { recursive: true, force: true });
|
|
94357
94885
|
}
|
|
94358
94886
|
mkdirSync49(envDir, { recursive: true });
|
|
94359
|
-
copyFileSync12(packageJsonPath,
|
|
94887
|
+
copyFileSync12(packageJsonPath, join90(envDir, "package.json"));
|
|
94360
94888
|
let copiedLockfile = false;
|
|
94361
94889
|
for (const lockName of LOCKFILES_FOR[installer]) {
|
|
94362
|
-
const lockPath =
|
|
94890
|
+
const lockPath = join90(sourceDir, lockName);
|
|
94363
94891
|
if (existsSync86(lockPath)) {
|
|
94364
|
-
copyFileSync12(lockPath,
|
|
94892
|
+
copyFileSync12(lockPath, join90(envDir, lockName));
|
|
94365
94893
|
copiedLockfile = true;
|
|
94366
94894
|
}
|
|
94367
94895
|
}
|
|
@@ -94390,7 +94918,7 @@ function ensureNodeEnv(opts) {
|
|
|
94390
94918
|
|
|
94391
94919
|
// src/cli/deps.ts
|
|
94392
94920
|
function builtinSkillsRoot() {
|
|
94393
|
-
return resolve50(
|
|
94921
|
+
return resolve50(homedir49(), ".switchroom/skills/_bundled");
|
|
94394
94922
|
}
|
|
94395
94923
|
function registerDepsCommand(program3) {
|
|
94396
94924
|
const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
|
|
@@ -94400,13 +94928,13 @@ function registerDepsCommand(program3) {
|
|
|
94400
94928
|
console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
|
|
94401
94929
|
process.exit(1);
|
|
94402
94930
|
}
|
|
94403
|
-
const skillDir =
|
|
94931
|
+
const skillDir = join91(skillsRoot, skill);
|
|
94404
94932
|
if (!existsSync87(skillDir)) {
|
|
94405
94933
|
console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
|
|
94406
94934
|
process.exit(1);
|
|
94407
94935
|
}
|
|
94408
|
-
const requirementsPath =
|
|
94409
|
-
const packageJsonPath =
|
|
94936
|
+
const requirementsPath = join91(skillDir, "requirements.txt");
|
|
94937
|
+
const packageJsonPath = join91(skillDir, "package.json");
|
|
94410
94938
|
const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync87(requirementsPath));
|
|
94411
94939
|
const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync87(packageJsonPath));
|
|
94412
94940
|
let did = 0;
|
|
@@ -95360,8 +95888,8 @@ function safeParseInt(value, fallback) {
|
|
|
95360
95888
|
init_helpers();
|
|
95361
95889
|
init_loader();
|
|
95362
95890
|
init_merge();
|
|
95363
|
-
import { copyFileSync as copyFileSync13, existsSync as existsSync89, readFileSync as
|
|
95364
|
-
import { join as
|
|
95891
|
+
import { copyFileSync as copyFileSync13, existsSync as existsSync89, readFileSync as readFileSync81, writeFileSync as writeFileSync35 } from "node:fs";
|
|
95892
|
+
import { join as join92, resolve as resolve52 } from "node:path";
|
|
95365
95893
|
init_scaffold();
|
|
95366
95894
|
init_profiles();
|
|
95367
95895
|
init_schema();
|
|
@@ -95387,7 +95915,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
95387
95915
|
profileName,
|
|
95388
95916
|
profilePath,
|
|
95389
95917
|
workspaceDir,
|
|
95390
|
-
soulPath:
|
|
95918
|
+
soulPath: join92(workspaceDir, "SOUL.md"),
|
|
95391
95919
|
soul: merged.soul
|
|
95392
95920
|
};
|
|
95393
95921
|
}
|
|
@@ -95408,7 +95936,7 @@ function registerSoulCommand(program3) {
|
|
|
95408
95936
|
console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
|
|
95409
95937
|
process.exit(1);
|
|
95410
95938
|
}
|
|
95411
|
-
process.stdout.write(
|
|
95939
|
+
process.stdout.write(readFileSync81(t.soulPath, "utf-8"));
|
|
95412
95940
|
}));
|
|
95413
95941
|
cmd.command("reset <agent>").description("Re-seed SOUL.md from the agent's current profile " + "(backs the existing file up to SOUL.md.bak first)").option("-y, --yes", "Skip the confirmation prompt").action(withConfigError(async (agentName, opts) => {
|
|
95414
95942
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
@@ -95453,8 +95981,8 @@ function registerSoulCommand(program3) {
|
|
|
95453
95981
|
// src/cli/debug.ts
|
|
95454
95982
|
init_helpers();
|
|
95455
95983
|
init_loader();
|
|
95456
|
-
import { existsSync as existsSync90, readFileSync as
|
|
95457
|
-
import { resolve as resolve53, join as
|
|
95984
|
+
import { existsSync as existsSync90, readFileSync as readFileSync82, readdirSync as readdirSync30, statSync as statSync50 } from "node:fs";
|
|
95985
|
+
import { resolve as resolve53, join as join93 } from "node:path";
|
|
95458
95986
|
import { createHash as createHash20 } from "node:crypto";
|
|
95459
95987
|
init_merge();
|
|
95460
95988
|
init_hindsight2();
|
|
@@ -95465,11 +95993,11 @@ function estimateTokens(bytes) {
|
|
|
95465
95993
|
return Math.round(bytes / 3.7);
|
|
95466
95994
|
}
|
|
95467
95995
|
function readMcpServerNames(agentDir) {
|
|
95468
|
-
const mcpPath =
|
|
95996
|
+
const mcpPath = join93(agentDir, ".mcp.json");
|
|
95469
95997
|
if (!existsSync90(mcpPath))
|
|
95470
95998
|
return [];
|
|
95471
95999
|
try {
|
|
95472
|
-
const parsed = JSON.parse(
|
|
96000
|
+
const parsed = JSON.parse(readFileSync82(mcpPath, "utf-8"));
|
|
95473
96001
|
return Object.keys(parsed.mcpServers ?? {});
|
|
95474
96002
|
} catch {
|
|
95475
96003
|
return null;
|
|
@@ -95479,7 +96007,7 @@ function sha256(content) {
|
|
|
95479
96007
|
return createHash20("sha256").update(content).digest("hex").slice(0, 16);
|
|
95480
96008
|
}
|
|
95481
96009
|
function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
95482
|
-
const projectsDir =
|
|
96010
|
+
const projectsDir = join93(claudeConfigDir, "projects");
|
|
95483
96011
|
if (!existsSync90(projectsDir))
|
|
95484
96012
|
return;
|
|
95485
96013
|
try {
|
|
@@ -95488,8 +96016,8 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
95488
96016
|
for (const entry of entries) {
|
|
95489
96017
|
if (!entry.isDirectory())
|
|
95490
96018
|
continue;
|
|
95491
|
-
const projectPath =
|
|
95492
|
-
const transcriptPath =
|
|
96019
|
+
const projectPath = join93(projectsDir, entry.name);
|
|
96020
|
+
const transcriptPath = join93(projectPath, "transcript.jsonl");
|
|
95493
96021
|
if (!existsSync90(transcriptPath))
|
|
95494
96022
|
continue;
|
|
95495
96023
|
const stat3 = statSync50(transcriptPath);
|
|
@@ -95504,7 +96032,7 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
95504
96032
|
}
|
|
95505
96033
|
function extractLatestUserMessage(transcriptPath) {
|
|
95506
96034
|
try {
|
|
95507
|
-
const content =
|
|
96035
|
+
const content = readFileSync82(transcriptPath, "utf-8");
|
|
95508
96036
|
const lines = content.trim().split(`
|
|
95509
96037
|
`).filter(Boolean);
|
|
95510
96038
|
for (let i = lines.length - 1;i >= 0; i--) {
|
|
@@ -95558,11 +96086,11 @@ function registerDebugCommand(program3) {
|
|
|
95558
96086
|
process.exit(1);
|
|
95559
96087
|
}
|
|
95560
96088
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
95561
|
-
const claudeConfigDir =
|
|
95562
|
-
const claudeMdPath =
|
|
95563
|
-
const soulMdPath =
|
|
95564
|
-
const workspaceSoulMdPath =
|
|
95565
|
-
const handoffPath =
|
|
96089
|
+
const claudeConfigDir = join93(agentDir, ".claude");
|
|
96090
|
+
const claudeMdPath = join93(agentDir, "CLAUDE.md");
|
|
96091
|
+
const soulMdPath = join93(agentDir, "SOUL.md");
|
|
96092
|
+
const workspaceSoulMdPath = join93(workspaceDir, "SOUL.md");
|
|
96093
|
+
const handoffPath = join93(agentDir, ".handoff.md");
|
|
95566
96094
|
const lastN = parseInt(opts.last, 10);
|
|
95567
96095
|
if (isNaN(lastN) || lastN < 1) {
|
|
95568
96096
|
console.error("--last must be a positive integer");
|
|
@@ -95608,7 +96136,7 @@ function registerDebugCommand(program3) {
|
|
|
95608
96136
|
}
|
|
95609
96137
|
console.log(`=== Append System Prompt (per-session) ===
|
|
95610
96138
|
`);
|
|
95611
|
-
const handoffContent = existsSync90(handoffPath) ?
|
|
96139
|
+
const handoffContent = existsSync90(handoffPath) ? readFileSync82(handoffPath, "utf-8") : "";
|
|
95612
96140
|
if (handoffContent.trim().length > 0) {
|
|
95613
96141
|
console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
|
|
95614
96142
|
console.log(handoffContent);
|
|
@@ -95619,7 +96147,7 @@ function registerDebugCommand(program3) {
|
|
|
95619
96147
|
}
|
|
95620
96148
|
console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
|
|
95621
96149
|
`);
|
|
95622
|
-
const claudeMdContent = existsSync90(claudeMdPath) ?
|
|
96150
|
+
const claudeMdContent = existsSync90(claudeMdPath) ? readFileSync82(claudeMdPath, "utf-8") : "";
|
|
95623
96151
|
if (claudeMdContent.trim().length > 0) {
|
|
95624
96152
|
console.log(`(${formatBytes(claudeMdContent.length)})`);
|
|
95625
96153
|
console.log(claudeMdContent);
|
|
@@ -95630,7 +96158,7 @@ function registerDebugCommand(program3) {
|
|
|
95630
96158
|
}
|
|
95631
96159
|
console.log(`=== Persona (SOUL.md) ===
|
|
95632
96160
|
`);
|
|
95633
|
-
const soulMdContent = existsSync90(soulMdPath) ?
|
|
96161
|
+
const soulMdContent = existsSync90(soulMdPath) ? readFileSync82(soulMdPath, "utf-8") : existsSync90(workspaceSoulMdPath) ? readFileSync82(workspaceSoulMdPath, "utf-8") : "";
|
|
95634
96162
|
if (soulMdContent.trim().length > 0) {
|
|
95635
96163
|
console.log(`(${formatBytes(soulMdContent.length)})`);
|
|
95636
96164
|
console.log(soulMdContent);
|
|
@@ -95691,11 +96219,11 @@ function registerDebugCommand(program3) {
|
|
|
95691
96219
|
const soulMdBytes = soulMdContent.length;
|
|
95692
96220
|
const perTurnBytes = dynamicResult.concatenated.length;
|
|
95693
96221
|
const userBytes = userMessage?.text.length ?? 0;
|
|
95694
|
-
const fleetDir =
|
|
95695
|
-
const fleetInvPath =
|
|
95696
|
-
const fleetClaudePath =
|
|
95697
|
-
const fleetInvBytes = existsSync90(fleetInvPath) ?
|
|
95698
|
-
const fleetClaudeBytes = existsSync90(fleetClaudePath) ?
|
|
96222
|
+
const fleetDir = join93(agentsDir, "..", "fleet");
|
|
96223
|
+
const fleetInvPath = join93(fleetDir, "switchroom-invariants.md");
|
|
96224
|
+
const fleetClaudePath = join93(fleetDir, "CLAUDE.md");
|
|
96225
|
+
const fleetInvBytes = existsSync90(fleetInvPath) ? readFileSync82(fleetInvPath, "utf-8").length : 0;
|
|
96226
|
+
const fleetClaudeBytes = existsSync90(fleetClaudePath) ? readFileSync82(fleetClaudePath, "utf-8").length : 0;
|
|
95699
96227
|
const fleetBytes = fleetInvBytes + fleetClaudeBytes;
|
|
95700
96228
|
const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
|
|
95701
96229
|
console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
|
|
@@ -95729,27 +96257,27 @@ init_source();
|
|
|
95729
96257
|
// src/worktree/claim.ts
|
|
95730
96258
|
import { execFileSync as execFileSync26 } from "node:child_process";
|
|
95731
96259
|
import { closeSync as closeSync17, mkdirSync as mkdirSync51, openSync as openSync17, existsSync as existsSync92, unlinkSync as unlinkSync19 } from "node:fs";
|
|
95732
|
-
import { join as
|
|
95733
|
-
import { homedir as
|
|
96260
|
+
import { join as join95, resolve as resolve55 } from "node:path";
|
|
96261
|
+
import { homedir as homedir51 } from "node:os";
|
|
95734
96262
|
import { randomBytes as randomBytes14 } from "node:crypto";
|
|
95735
96263
|
|
|
95736
96264
|
// src/worktree/registry.ts
|
|
95737
96265
|
import {
|
|
95738
96266
|
mkdirSync as mkdirSync50,
|
|
95739
96267
|
writeFileSync as writeFileSync36,
|
|
95740
|
-
readFileSync as
|
|
96268
|
+
readFileSync as readFileSync83,
|
|
95741
96269
|
readdirSync as readdirSync31,
|
|
95742
96270
|
unlinkSync as unlinkSync18,
|
|
95743
96271
|
existsSync as existsSync91,
|
|
95744
96272
|
renameSync as renameSync22
|
|
95745
96273
|
} from "node:fs";
|
|
95746
|
-
import { join as
|
|
95747
|
-
import { homedir as
|
|
96274
|
+
import { join as join94, resolve as resolve54 } from "node:path";
|
|
96275
|
+
import { homedir as homedir50 } from "node:os";
|
|
95748
96276
|
function registryDir() {
|
|
95749
|
-
return resolve54(process.env.SWITCHROOM_WORKTREE_DIR ??
|
|
96277
|
+
return resolve54(process.env.SWITCHROOM_WORKTREE_DIR ?? join94(homedir50(), ".switchroom", "worktrees"));
|
|
95750
96278
|
}
|
|
95751
96279
|
function recordPath(id) {
|
|
95752
|
-
return
|
|
96280
|
+
return join94(registryDir(), `${id}.json`);
|
|
95753
96281
|
}
|
|
95754
96282
|
function ensureDir2() {
|
|
95755
96283
|
mkdirSync50(registryDir(), { recursive: true });
|
|
@@ -95765,7 +96293,7 @@ function writeRecord(record2) {
|
|
|
95765
96293
|
function readRecord(id) {
|
|
95766
96294
|
const path10 = recordPath(id);
|
|
95767
96295
|
try {
|
|
95768
|
-
const raw =
|
|
96296
|
+
const raw = readFileSync83(path10, "utf8");
|
|
95769
96297
|
return JSON.parse(raw);
|
|
95770
96298
|
} catch {
|
|
95771
96299
|
return null;
|
|
@@ -95800,7 +96328,7 @@ function acquireRepoLock(repoPath) {
|
|
|
95800
96328
|
const lockDir = registryDir();
|
|
95801
96329
|
mkdirSync51(lockDir, { recursive: true });
|
|
95802
96330
|
const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
|
|
95803
|
-
const lockPath =
|
|
96331
|
+
const lockPath = join95(lockDir, `.lock-${lockName}`);
|
|
95804
96332
|
const deadline = Date.now() + 5000;
|
|
95805
96333
|
let fd = null;
|
|
95806
96334
|
while (fd === null) {
|
|
@@ -95827,7 +96355,7 @@ function acquireRepoLock(repoPath) {
|
|
|
95827
96355
|
}
|
|
95828
96356
|
var DEFAULT_CONCURRENCY = 5;
|
|
95829
96357
|
function worktreesBaseDir() {
|
|
95830
|
-
return resolve55(process.env.SWITCHROOM_WORKTREE_BASE ??
|
|
96358
|
+
return resolve55(process.env.SWITCHROOM_WORKTREE_BASE ?? join95(homedir51(), ".switchroom", "worktree-checkouts"));
|
|
95831
96359
|
}
|
|
95832
96360
|
function shortId() {
|
|
95833
96361
|
return randomBytes14(4).toString("hex");
|
|
@@ -95849,7 +96377,7 @@ function resolveRepoPath(repo, codeRepos) {
|
|
|
95849
96377
|
}
|
|
95850
96378
|
function expandHome(p) {
|
|
95851
96379
|
if (p.startsWith("~/"))
|
|
95852
|
-
return
|
|
96380
|
+
return join95(homedir51(), p.slice(2));
|
|
95853
96381
|
return p;
|
|
95854
96382
|
}
|
|
95855
96383
|
async function claimWorktree(input, codeRepos) {
|
|
@@ -95877,7 +96405,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
95877
96405
|
branch = `task/${taskSuffix}-${id}`;
|
|
95878
96406
|
const baseDir = worktreesBaseDir();
|
|
95879
96407
|
mkdirSync51(baseDir, { recursive: true });
|
|
95880
|
-
worktreePath =
|
|
96408
|
+
worktreePath = join95(baseDir, `${id}-${taskSuffix}`);
|
|
95881
96409
|
const ambientOwner = process.env.SWITCHROOM_AGENT_NAME;
|
|
95882
96410
|
const ownerAgent = input.ownerAgent ?? (ambientOwner != null && ambientOwner !== "" ? ambientOwner : undefined);
|
|
95883
96411
|
const now = new Date().toISOString();
|
|
@@ -96100,15 +96628,15 @@ function runReaper(nowMs, deps = {}) {
|
|
|
96100
96628
|
import { execFileSync as execFileSync29 } from "node:child_process";
|
|
96101
96629
|
import {
|
|
96102
96630
|
existsSync as existsSync95,
|
|
96103
|
-
readFileSync as
|
|
96631
|
+
readFileSync as readFileSync84,
|
|
96104
96632
|
readdirSync as readdirSync32,
|
|
96105
96633
|
statSync as statSync51,
|
|
96106
96634
|
renameSync as renameSync23,
|
|
96107
96635
|
mkdirSync as mkdirSync52,
|
|
96108
96636
|
rmSync as rmSync17
|
|
96109
96637
|
} from "node:fs";
|
|
96110
|
-
import { homedir as
|
|
96111
|
-
import { join as
|
|
96638
|
+
import { homedir as homedir52 } from "node:os";
|
|
96639
|
+
import { join as join96, resolve as resolve56 } from "node:path";
|
|
96112
96640
|
function parseGitdirPointer(dotGitFileContents) {
|
|
96113
96641
|
const m = /^gitdir:\s*(.+?)\s*$/m.exec(dotGitFileContents);
|
|
96114
96642
|
return m ? m[1] : null;
|
|
@@ -96223,17 +96751,17 @@ function defaultPrSignal(repo, branch, exec) {
|
|
|
96223
96751
|
}
|
|
96224
96752
|
}
|
|
96225
96753
|
function trashRoot() {
|
|
96226
|
-
return resolve56(process.env.SWITCHROOM_WORKTREE_TRASH ??
|
|
96754
|
+
return resolve56(process.env.SWITCHROOM_WORKTREE_TRASH ?? join96(homedir52(), ".switchroom", "worktree-gc-trash"));
|
|
96227
96755
|
}
|
|
96228
96756
|
function planGc(roots, deps = {}) {
|
|
96229
96757
|
const exists = deps.existsSync ?? existsSync95;
|
|
96230
96758
|
const readDir = deps.readDir ?? ((p) => readdirSync32(p));
|
|
96231
|
-
const readFile4 = deps.readFile ?? ((p) =>
|
|
96759
|
+
const readFile4 = deps.readFile ?? ((p) => readFileSync84(p, "utf8"));
|
|
96232
96760
|
const stat3 = deps.stat ?? ((p) => statSync51(p));
|
|
96233
96761
|
const exec = deps.exec ?? defaultExec3;
|
|
96234
96762
|
const prSignal = deps.prSignal ?? ((repo, branch) => defaultPrSignal(repo, branch, exec));
|
|
96235
96763
|
const stamp = deps.dateStamp ?? "undated";
|
|
96236
|
-
const trash =
|
|
96764
|
+
const trash = join96(trashRoot(), stamp);
|
|
96237
96765
|
let claimed;
|
|
96238
96766
|
try {
|
|
96239
96767
|
claimed = new Set(listRecords().map((r) => resolve56(r.path)));
|
|
@@ -96269,10 +96797,10 @@ function planGc(roots, deps = {}) {
|
|
|
96269
96797
|
continue;
|
|
96270
96798
|
}
|
|
96271
96799
|
for (const name of entries) {
|
|
96272
|
-
const dir =
|
|
96800
|
+
const dir = join96(root, name);
|
|
96273
96801
|
if (isEphemeralPath(dir))
|
|
96274
96802
|
continue;
|
|
96275
|
-
const dotGit =
|
|
96803
|
+
const dotGit = join96(dir, ".git");
|
|
96276
96804
|
if (!exists(dotGit))
|
|
96277
96805
|
continue;
|
|
96278
96806
|
let st;
|
|
@@ -96301,7 +96829,7 @@ function planGc(roots, deps = {}) {
|
|
|
96301
96829
|
ownerRepos.add(repoRoot);
|
|
96302
96830
|
if (exists(ptr))
|
|
96303
96831
|
continue;
|
|
96304
|
-
orphans.push({ dir, owner: repoRoot, dest:
|
|
96832
|
+
orphans.push({ dir, owner: repoRoot, dest: join96(trash, name) });
|
|
96305
96833
|
}
|
|
96306
96834
|
}
|
|
96307
96835
|
const registered = [];
|
|
@@ -96412,7 +96940,7 @@ function listTrashEntries(nowMs, deps = {}) {
|
|
|
96412
96940
|
return [];
|
|
96413
96941
|
const out = [];
|
|
96414
96942
|
for (const stamp of readDir(root)) {
|
|
96415
|
-
const stampDir =
|
|
96943
|
+
const stampDir = join96(root, stamp);
|
|
96416
96944
|
let names;
|
|
96417
96945
|
try {
|
|
96418
96946
|
names = readDir(stampDir);
|
|
@@ -96420,7 +96948,7 @@ function listTrashEntries(nowMs, deps = {}) {
|
|
|
96420
96948
|
continue;
|
|
96421
96949
|
}
|
|
96422
96950
|
for (const name of names) {
|
|
96423
|
-
const p =
|
|
96951
|
+
const p = join96(stampDir, name);
|
|
96424
96952
|
let mtimeMs = nowMs;
|
|
96425
96953
|
try {
|
|
96426
96954
|
mtimeMs = statSync51(p).mtimeMs;
|
|
@@ -96444,7 +96972,7 @@ function purgeTrash(paths) {
|
|
|
96444
96972
|
return { deleted, errors: errors2 };
|
|
96445
96973
|
}
|
|
96446
96974
|
function defaultRoots() {
|
|
96447
|
-
return [
|
|
96975
|
+
return [join96(homedir52(), "code")];
|
|
96448
96976
|
}
|
|
96449
96977
|
|
|
96450
96978
|
// src/cli/worktree.ts
|
|
@@ -96672,7 +97200,7 @@ import {
|
|
|
96672
97200
|
rmSync as rmSync18,
|
|
96673
97201
|
writeFileSync as writeFileSync37
|
|
96674
97202
|
} from "node:fs";
|
|
96675
|
-
import { join as
|
|
97203
|
+
import { join as join97 } from "node:path";
|
|
96676
97204
|
function encodeCredentialsFilename(email) {
|
|
96677
97205
|
const SAFE = new Set([
|
|
96678
97206
|
..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
|
@@ -96862,16 +97390,16 @@ function resolveCredentialsDir(env2) {
|
|
|
96862
97390
|
if (explicit && explicit.length > 0)
|
|
96863
97391
|
return explicit;
|
|
96864
97392
|
const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
|
|
96865
|
-
return
|
|
97393
|
+
return join97(stateBase, "google-workspace-mcp", "credentials");
|
|
96866
97394
|
}
|
|
96867
97395
|
function writeSeedFile(dir, email, seed) {
|
|
96868
97396
|
mkdirSync53(dir, { recursive: true, mode: 448 });
|
|
96869
97397
|
chmodSync16(dir, 448);
|
|
96870
97398
|
for (const name of readdirSync33(dir)) {
|
|
96871
|
-
rmSync18(
|
|
97399
|
+
rmSync18(join97(dir, name), { force: true, recursive: true });
|
|
96872
97400
|
}
|
|
96873
97401
|
const filename = encodeCredentialsFilename(email);
|
|
96874
|
-
const filePath =
|
|
97402
|
+
const filePath = join97(dir, filename);
|
|
96875
97403
|
writeFileSync37(filePath, JSON.stringify(seed), { mode: 384 });
|
|
96876
97404
|
chmodSync16(filePath, 384);
|
|
96877
97405
|
return filePath;
|
|
@@ -97031,7 +97559,7 @@ function registerDriveMcpLauncherCommand(program3) {
|
|
|
97031
97559
|
init_scaffold_integration();
|
|
97032
97560
|
import { spawn as spawn6 } from "node:child_process";
|
|
97033
97561
|
import { writeFileSync as writeFileSync38, mkdirSync as mkdirSync54 } from "node:fs";
|
|
97034
|
-
import { dirname as dirname38, join as
|
|
97562
|
+
import { dirname as dirname38, join as join98 } from "node:path";
|
|
97035
97563
|
var SOFTERIA_TOKEN_ENV = "MS365_MCP_OAUTH_TOKEN";
|
|
97036
97564
|
var DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
|
|
97037
97565
|
var MAX_REFRESH_INTERVAL_MS = 60 * 60 * 1000;
|
|
@@ -97076,7 +97604,7 @@ function heartbeatPath(agentName, account) {
|
|
|
97076
97604
|
const override = process.env.SWITCHROOM_M365_HEARTBEAT_DIR;
|
|
97077
97605
|
if (override) {
|
|
97078
97606
|
const base = slug ? `m365-launcher-${agentName}-${slug}` : `m365-launcher-${agentName}`;
|
|
97079
|
-
return
|
|
97607
|
+
return join98(override, `${base}.heartbeat.json`);
|
|
97080
97608
|
}
|
|
97081
97609
|
return slug ? `/state/agent/m365-launcher-${slug}.heartbeat.json` : "/state/agent/m365-launcher.heartbeat.json";
|
|
97082
97610
|
}
|
|
@@ -97432,9 +97960,9 @@ function registerNotionMcpLauncherCommand(program3) {
|
|
|
97432
97960
|
|
|
97433
97961
|
// src/cli/hindsight-mcp-shim.ts
|
|
97434
97962
|
init_hindsight();
|
|
97435
|
-
import { mkdirSync as mkdirSync56, readFileSync as
|
|
97963
|
+
import { mkdirSync as mkdirSync56, readFileSync as readFileSync85, renameSync as renameSync24, writeFileSync as writeFileSync40 } from "node:fs";
|
|
97436
97964
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
97437
|
-
import { join as
|
|
97965
|
+
import { join as join99 } from "node:path";
|
|
97438
97966
|
import { createInterface as createInterface6 } from "node:readline";
|
|
97439
97967
|
var SHIM_SUPPORTED_PROTOCOL_VERSIONS = [
|
|
97440
97968
|
"2025-06-18",
|
|
@@ -97660,12 +98188,12 @@ class HindsightShim {
|
|
|
97660
98188
|
`));
|
|
97661
98189
|
}
|
|
97662
98190
|
get cachePath() {
|
|
97663
|
-
return
|
|
98191
|
+
return join99(this.opts.cacheDir, TOOLS_CACHE_FILENAME);
|
|
97664
98192
|
}
|
|
97665
98193
|
writeCache(result) {
|
|
97666
98194
|
try {
|
|
97667
98195
|
mkdirSync56(this.opts.cacheDir, { recursive: true });
|
|
97668
|
-
const tmp =
|
|
98196
|
+
const tmp = join99(this.opts.cacheDir, `.${TOOLS_CACHE_FILENAME}.${process.pid}.tmp`);
|
|
97669
98197
|
writeFileSync40(tmp, JSON.stringify(result, null, 2) + `
|
|
97670
98198
|
`);
|
|
97671
98199
|
renameSync24(tmp, this.cachePath);
|
|
@@ -97675,7 +98203,7 @@ class HindsightShim {
|
|
|
97675
98203
|
}
|
|
97676
98204
|
readCache() {
|
|
97677
98205
|
try {
|
|
97678
|
-
const parsed = JSON.parse(
|
|
98206
|
+
const parsed = JSON.parse(readFileSync85(this.cachePath, "utf-8"));
|
|
97679
98207
|
if (Array.isArray(parsed.tools))
|
|
97680
98208
|
return parsed;
|
|
97681
98209
|
return null;
|
|
@@ -97838,7 +98366,7 @@ function resolveShimOptionsFromEnv(env2) {
|
|
|
97838
98366
|
return {
|
|
97839
98367
|
url: env2.HINDSIGHT_MCP_URL || HINDSIGHT_DEFAULT_MCP_URL,
|
|
97840
98368
|
bankId: env2.HINDSIGHT_BANK_ID || "",
|
|
97841
|
-
cacheDir: env2.HINDSIGHT_SHIM_CACHE_DIR ||
|
|
98369
|
+
cacheDir: env2.HINDSIGHT_SHIM_CACHE_DIR || join99(home2, ".hindsight-shim")
|
|
97842
98370
|
};
|
|
97843
98371
|
}
|
|
97844
98372
|
function registerHindsightMcpShimCommand(program3) {
|
|
@@ -97851,7 +98379,7 @@ function registerHindsightMcpShimCommand(program3) {
|
|
|
97851
98379
|
|
|
97852
98380
|
// src/cli/deliver-file.ts
|
|
97853
98381
|
init_client2();
|
|
97854
|
-
import { readFileSync as
|
|
98382
|
+
import { readFileSync as readFileSync86, statSync as statSync52 } from "node:fs";
|
|
97855
98383
|
import { basename as basename13 } from "node:path";
|
|
97856
98384
|
|
|
97857
98385
|
// src/delivery/onedrive.ts
|
|
@@ -98191,7 +98719,7 @@ async function defaultResolveProvider() {
|
|
|
98191
98719
|
async function runDeliverFile(localPath, deps = {}) {
|
|
98192
98720
|
const agentName = safeAgentName(deps.agentName ?? process.env.SWITCHROOM_AGENT_NAME);
|
|
98193
98721
|
const sizeOf = deps.fileSize ?? ((p) => statSync52(p).size);
|
|
98194
|
-
const read = deps.readFile ?? ((p) => new Uint8Array(
|
|
98722
|
+
const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync86(p)));
|
|
98195
98723
|
const resolveProvider = deps.resolveProvider ?? defaultResolveProvider;
|
|
98196
98724
|
let size;
|
|
98197
98725
|
try {
|
|
@@ -98512,9 +99040,9 @@ function runRedactStdin() {
|
|
|
98512
99040
|
}
|
|
98513
99041
|
|
|
98514
99042
|
// src/cli/status-ask.ts
|
|
98515
|
-
import { readFileSync as
|
|
98516
|
-
import { join as
|
|
98517
|
-
import { homedir as
|
|
99043
|
+
import { readFileSync as readFileSync87, existsSync as existsSync97, readdirSync as readdirSync34 } from "node:fs";
|
|
99044
|
+
import { join as join100 } from "node:path";
|
|
99045
|
+
import { homedir as homedir53 } from "node:os";
|
|
98518
99046
|
|
|
98519
99047
|
// src/status-ask/report.ts
|
|
98520
99048
|
function parseJsonl(content) {
|
|
@@ -98788,7 +99316,7 @@ function runReport(opts) {
|
|
|
98788
99316
|
for (const src of sources) {
|
|
98789
99317
|
let content;
|
|
98790
99318
|
try {
|
|
98791
|
-
content =
|
|
99319
|
+
content = readFileSync87(src.path, "utf-8");
|
|
98792
99320
|
} catch (err) {
|
|
98793
99321
|
process.stderr.write(`status-ask report: cannot read ${src.path}: ${err instanceof Error ? err.message : String(err)}
|
|
98794
99322
|
`);
|
|
@@ -98849,7 +99377,7 @@ function resolveSources(explicitPath) {
|
|
|
98849
99377
|
const config = loadConfig();
|
|
98850
99378
|
agentsDir = resolveAgentsDir(config);
|
|
98851
99379
|
} catch {
|
|
98852
|
-
agentsDir =
|
|
99380
|
+
agentsDir = join100(homedir53(), ".switchroom", "agents");
|
|
98853
99381
|
}
|
|
98854
99382
|
if (!existsSync97(agentsDir))
|
|
98855
99383
|
return [];
|
|
@@ -98861,7 +99389,7 @@ function resolveSources(explicitPath) {
|
|
|
98861
99389
|
return [];
|
|
98862
99390
|
}
|
|
98863
99391
|
for (const name of entries) {
|
|
98864
|
-
const path10 =
|
|
99392
|
+
const path10 = join100(agentsDir, name, "runtime-metrics.jsonl");
|
|
98865
99393
|
if (existsSync97(path10)) {
|
|
98866
99394
|
sources.push({ path: path10, agent: name });
|
|
98867
99395
|
}
|
|
@@ -98897,27 +99425,27 @@ import {
|
|
|
98897
99425
|
mkdirSync as mkdirSync57,
|
|
98898
99426
|
openSync as openSync18,
|
|
98899
99427
|
readdirSync as readdirSync35,
|
|
98900
|
-
readFileSync as
|
|
99428
|
+
readFileSync as readFileSync88,
|
|
98901
99429
|
renameSync as renameSync25,
|
|
98902
99430
|
statSync as statSync53,
|
|
98903
99431
|
unlinkSync as unlinkSync20,
|
|
98904
99432
|
writeSync as writeSync10
|
|
98905
99433
|
} from "node:fs";
|
|
98906
|
-
import { join as
|
|
99434
|
+
import { join as join101, resolve as resolve57 } from "node:path";
|
|
98907
99435
|
var STAGING_SUBDIR = ".staging";
|
|
98908
99436
|
function overlayPathsFor(agent, opts = {}) {
|
|
98909
99437
|
const base = opts.root ? resolve57(opts.root, agent) : resolve57(resolveDualPath(`~/.switchroom/agents/${agent}`));
|
|
98910
|
-
const scheduleDir =
|
|
98911
|
-
const scheduleStagingDir =
|
|
98912
|
-
const skillsDir =
|
|
98913
|
-
const skillsStagingDir =
|
|
99438
|
+
const scheduleDir = join101(base, "schedule.d");
|
|
99439
|
+
const scheduleStagingDir = join101(scheduleDir, STAGING_SUBDIR);
|
|
99440
|
+
const skillsDir = join101(base, "skills.d");
|
|
99441
|
+
const skillsStagingDir = join101(skillsDir, STAGING_SUBDIR);
|
|
98914
99442
|
return {
|
|
98915
99443
|
agentRoot: base,
|
|
98916
99444
|
scheduleDir,
|
|
98917
99445
|
scheduleStagingDir,
|
|
98918
99446
|
skillsDir,
|
|
98919
99447
|
skillsStagingDir,
|
|
98920
|
-
lockPath:
|
|
99448
|
+
lockPath: join101(base, ".lock"),
|
|
98921
99449
|
stagingDir: scheduleStagingDir
|
|
98922
99450
|
};
|
|
98923
99451
|
}
|
|
@@ -98971,8 +99499,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
98971
99499
|
const paths = overlayPathsFor(agent, opts);
|
|
98972
99500
|
return withAgentLock(paths, () => {
|
|
98973
99501
|
ensureDirs(paths);
|
|
98974
|
-
const stagingPath =
|
|
98975
|
-
const finalPath =
|
|
99502
|
+
const stagingPath = join101(paths.scheduleStagingDir, `${slug}.yaml`);
|
|
99503
|
+
const finalPath = join101(paths.scheduleDir, `${slug}.yaml`);
|
|
98976
99504
|
const fd = openSync18(stagingPath, "w", 384);
|
|
98977
99505
|
try {
|
|
98978
99506
|
writeSync10(fd, yamlText);
|
|
@@ -98988,8 +99516,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
98988
99516
|
const paths = overlayPathsFor(agent, opts);
|
|
98989
99517
|
return withAgentLock(paths, () => {
|
|
98990
99518
|
ensureSkillsDirs(paths);
|
|
98991
|
-
const stagingPath =
|
|
98992
|
-
const finalPath =
|
|
99519
|
+
const stagingPath = join101(paths.skillsStagingDir, `${slug}.yaml`);
|
|
99520
|
+
const finalPath = join101(paths.skillsDir, `${slug}.yaml`);
|
|
98993
99521
|
const fd = openSync18(stagingPath, "w", 384);
|
|
98994
99522
|
try {
|
|
98995
99523
|
writeSync10(fd, yamlText);
|
|
@@ -99004,7 +99532,7 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
99004
99532
|
function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
99005
99533
|
const paths = overlayPathsFor(agent, opts);
|
|
99006
99534
|
return withAgentLock(paths, () => {
|
|
99007
|
-
const finalPath =
|
|
99535
|
+
const finalPath = join101(paths.skillsDir, `${slug}.yaml`);
|
|
99008
99536
|
if (!existsSync98(finalPath))
|
|
99009
99537
|
return false;
|
|
99010
99538
|
unlinkSync20(finalPath);
|
|
@@ -99019,9 +99547,9 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
99019
99547
|
for (const name of readdirSync35(paths.skillsDir)) {
|
|
99020
99548
|
if (!/\.ya?ml$/i.test(name))
|
|
99021
99549
|
continue;
|
|
99022
|
-
const full =
|
|
99550
|
+
const full = join101(paths.skillsDir, name);
|
|
99023
99551
|
try {
|
|
99024
|
-
const raw =
|
|
99552
|
+
const raw = readFileSync88(full, "utf-8");
|
|
99025
99553
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
99026
99554
|
out.push({ slug, path: full, raw });
|
|
99027
99555
|
} catch {}
|
|
@@ -99031,7 +99559,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
99031
99559
|
function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
99032
99560
|
const paths = overlayPathsFor(agent, opts);
|
|
99033
99561
|
return withAgentLock(paths, () => {
|
|
99034
|
-
const finalPath =
|
|
99562
|
+
const finalPath = join101(paths.scheduleDir, `${slug}.yaml`);
|
|
99035
99563
|
if (!existsSync98(finalPath))
|
|
99036
99564
|
return false;
|
|
99037
99565
|
unlinkSync20(finalPath);
|
|
@@ -99046,9 +99574,9 @@ function listOverlayEntries(agent, opts = {}) {
|
|
|
99046
99574
|
for (const name of readdirSync35(paths.scheduleDir)) {
|
|
99047
99575
|
if (!/\.ya?ml$/i.test(name))
|
|
99048
99576
|
continue;
|
|
99049
|
-
const full =
|
|
99577
|
+
const full = join101(paths.scheduleDir, name);
|
|
99050
99578
|
try {
|
|
99051
|
-
const raw =
|
|
99579
|
+
const raw = readFileSync88(full, "utf-8");
|
|
99052
99580
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
99053
99581
|
out.push({ slug, path: full, raw });
|
|
99054
99582
|
} catch {}
|
|
@@ -99273,18 +99801,18 @@ import {
|
|
|
99273
99801
|
mkdirSync as mkdirSync58,
|
|
99274
99802
|
openSync as openSync19,
|
|
99275
99803
|
readdirSync as readdirSync36,
|
|
99276
|
-
readFileSync as
|
|
99804
|
+
readFileSync as readFileSync89,
|
|
99277
99805
|
renameSync as renameSync26,
|
|
99278
99806
|
unlinkSync as unlinkSync21,
|
|
99279
99807
|
writeFileSync as writeFileSync41,
|
|
99280
99808
|
writeSync as writeSync11
|
|
99281
99809
|
} from "node:fs";
|
|
99282
|
-
import { join as
|
|
99810
|
+
import { join as join102 } from "node:path";
|
|
99283
99811
|
import { randomBytes as randomBytes15 } from "node:crypto";
|
|
99284
99812
|
var STAGE_ID_PREFIX = "cap_";
|
|
99285
99813
|
function pendingDir(agent, opts = {}) {
|
|
99286
99814
|
const paths = overlayPathsFor(agent, opts);
|
|
99287
|
-
return
|
|
99815
|
+
return join102(paths.scheduleDir, ".pending");
|
|
99288
99816
|
}
|
|
99289
99817
|
function ensurePendingDir(agent, opts = {}) {
|
|
99290
99818
|
const dir = pendingDir(agent, opts);
|
|
@@ -99297,8 +99825,8 @@ function newStageId() {
|
|
|
99297
99825
|
function stagePendingScheduleEntry(opts) {
|
|
99298
99826
|
const dir = ensurePendingDir(opts.agent, { root: opts.root });
|
|
99299
99827
|
const stageId = opts.stageId ?? newStageId();
|
|
99300
|
-
const yamlPath =
|
|
99301
|
-
const metaPath =
|
|
99828
|
+
const yamlPath = join102(dir, `${stageId}.yaml`);
|
|
99829
|
+
const metaPath = join102(dir, `${stageId}.meta.json`);
|
|
99302
99830
|
const meta = {
|
|
99303
99831
|
v: 1,
|
|
99304
99832
|
stage_id: stageId,
|
|
@@ -99332,12 +99860,12 @@ function listPendingScheduleEntries(agent, opts = {}) {
|
|
|
99332
99860
|
if (!name.endsWith(".meta.json"))
|
|
99333
99861
|
continue;
|
|
99334
99862
|
const stageId = name.slice(0, -".meta.json".length);
|
|
99335
|
-
const metaPath =
|
|
99336
|
-
const yamlPath =
|
|
99863
|
+
const metaPath = join102(dir, name);
|
|
99864
|
+
const yamlPath = join102(dir, `${stageId}.yaml`);
|
|
99337
99865
|
if (!existsSync99(yamlPath))
|
|
99338
99866
|
continue;
|
|
99339
99867
|
try {
|
|
99340
|
-
const meta = JSON.parse(
|
|
99868
|
+
const meta = JSON.parse(readFileSync89(metaPath, "utf-8"));
|
|
99341
99869
|
if (meta?.v !== 1 || typeof meta.stage_id !== "string")
|
|
99342
99870
|
continue;
|
|
99343
99871
|
out.push({ stageId: meta.stage_id, agent: meta.agent, yamlPath, metaPath, meta });
|
|
@@ -99352,7 +99880,7 @@ function commitPendingScheduleEntry(opts) {
|
|
|
99352
99880
|
return { committed: false, reason: "not_found" };
|
|
99353
99881
|
const slug = match.meta.entry.name ?? match.stageId;
|
|
99354
99882
|
const paths = overlayPathsFor(opts.agent, { root: opts.root });
|
|
99355
|
-
const finalPath =
|
|
99883
|
+
const finalPath = join102(paths.scheduleDir, `${slug}.yaml`);
|
|
99356
99884
|
if (existsSync99(finalPath)) {
|
|
99357
99885
|
return { committed: false, reason: "slug_collision" };
|
|
99358
99886
|
}
|
|
@@ -99375,7 +99903,7 @@ function denyPendingScheduleEntry(opts) {
|
|
|
99375
99903
|
}
|
|
99376
99904
|
|
|
99377
99905
|
// src/cli/agent-config-write.ts
|
|
99378
|
-
import { existsSync as existsSync100, readFileSync as
|
|
99906
|
+
import { existsSync as existsSync100, readFileSync as readFileSync90 } from "node:fs";
|
|
99379
99907
|
import { execFileSync as execFileSync30 } from "node:child_process";
|
|
99380
99908
|
|
|
99381
99909
|
// src/scheduler/schedule-report.ts
|
|
@@ -99766,7 +100294,7 @@ function scheduleRemove(opts) {
|
|
|
99766
100294
|
let priorContent = null;
|
|
99767
100295
|
try {
|
|
99768
100296
|
if (existsSync100(match.path))
|
|
99769
|
-
priorContent =
|
|
100297
|
+
priorContent = readFileSync90(match.path, "utf-8");
|
|
99770
100298
|
} catch {}
|
|
99771
100299
|
deleteOverlayEntry(agent, match.slug, { root: opts.root });
|
|
99772
100300
|
const reconcileFn = opts.reconcile === undefined ? opts.root ? null : reconcileAgentCronOnly : opts.reconcile;
|
|
@@ -99969,7 +100497,7 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
99969
100497
|
}
|
|
99970
100498
|
let blob;
|
|
99971
100499
|
if (opts.jsonl) {
|
|
99972
|
-
blob = existsSync100(opts.jsonl) ?
|
|
100500
|
+
blob = existsSync100(opts.jsonl) ? readFileSync90(opts.jsonl, "utf-8") : "";
|
|
99973
100501
|
} else {
|
|
99974
100502
|
try {
|
|
99975
100503
|
blob = execFileSync30("docker", ["exec", `switchroom-${agent}`, "cat", "/state/agent/scheduler.jsonl"], {
|
|
@@ -100005,7 +100533,7 @@ import { existsSync as existsSync101 } from "node:fs";
|
|
|
100005
100533
|
init_reconcile_default_skills();
|
|
100006
100534
|
init_agent_config();
|
|
100007
100535
|
var import_yaml23 = __toESM(require_dist(), 1);
|
|
100008
|
-
import { join as
|
|
100536
|
+
import { join as join103 } from "node:path";
|
|
100009
100537
|
var MAX_SKILLS_PER_AGENT = 20;
|
|
100010
100538
|
var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
|
|
100011
100539
|
function exitCodeFor2(code) {
|
|
@@ -100080,7 +100608,7 @@ function skillInstall(opts) {
|
|
|
100080
100608
|
return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
|
|
100081
100609
|
}
|
|
100082
100610
|
const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
|
|
100083
|
-
const skillPath =
|
|
100611
|
+
const skillPath = join103(poolDir, skillName);
|
|
100084
100612
|
if (!existsSync101(skillPath)) {
|
|
100085
100613
|
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.`);
|
|
100086
100614
|
}
|
|
@@ -100250,7 +100778,7 @@ import {
|
|
|
100250
100778
|
mkdirSync as mkdirSync59,
|
|
100251
100779
|
mkdtempSync as mkdtempSync5,
|
|
100252
100780
|
openSync as openSync20,
|
|
100253
|
-
readFileSync as
|
|
100781
|
+
readFileSync as readFileSync91,
|
|
100254
100782
|
readdirSync as readdirSync37,
|
|
100255
100783
|
realpathSync as realpathSync7,
|
|
100256
100784
|
renameSync as renameSync27,
|
|
@@ -100258,8 +100786,8 @@ import {
|
|
|
100258
100786
|
statSync as statSync54,
|
|
100259
100787
|
writeFileSync as writeFileSync42
|
|
100260
100788
|
} from "node:fs";
|
|
100261
|
-
import { tmpdir as tmpdir6, homedir as
|
|
100262
|
-
import { dirname as dirname40, join as
|
|
100789
|
+
import { tmpdir as tmpdir6, homedir as homedir54 } from "node:os";
|
|
100790
|
+
import { dirname as dirname40, join as join104, relative as relative4, resolve as resolve58 } from "node:path";
|
|
100263
100791
|
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
100264
100792
|
|
|
100265
100793
|
// src/cli/skill-common.ts
|
|
@@ -100493,10 +101021,10 @@ function scanForClaudeP2(content) {
|
|
|
100493
101021
|
function resolveSkillsPoolDir2(override) {
|
|
100494
101022
|
const raw = override ?? "~/.switchroom/skills";
|
|
100495
101023
|
if (raw.startsWith("~/")) {
|
|
100496
|
-
return
|
|
101024
|
+
return join104(homedir54(), raw.slice(2));
|
|
100497
101025
|
}
|
|
100498
101026
|
if (raw === "~")
|
|
100499
|
-
return
|
|
101027
|
+
return homedir54();
|
|
100500
101028
|
return resolve58(raw);
|
|
100501
101029
|
}
|
|
100502
101030
|
function readStdinSync() {
|
|
@@ -100532,7 +101060,7 @@ function loadFromDir(dir) {
|
|
|
100532
101060
|
const walk2 = (sub) => {
|
|
100533
101061
|
const entries = readdirSync37(sub, { withFileTypes: true });
|
|
100534
101062
|
for (const ent of entries) {
|
|
100535
|
-
const full =
|
|
101063
|
+
const full = join104(sub, ent.name);
|
|
100536
101064
|
const rel = relative4(abs, full);
|
|
100537
101065
|
if (ent.isSymbolicLink()) {
|
|
100538
101066
|
fail4(`refusing to read symlink inside --from dir: ${rel}`);
|
|
@@ -100542,7 +101070,7 @@ function loadFromDir(dir) {
|
|
|
100542
101070
|
continue;
|
|
100543
101071
|
}
|
|
100544
101072
|
if (ent.isFile()) {
|
|
100545
|
-
const buf =
|
|
101073
|
+
const buf = readFileSync91(full);
|
|
100546
101074
|
files[rel.replace(/\\/g, "/")] = buf.toString("utf-8");
|
|
100547
101075
|
}
|
|
100548
101076
|
}
|
|
@@ -100567,7 +101095,7 @@ function loadFromTarball(tarPath) {
|
|
|
100567
101095
|
fail4(`tarball contains disallowed path: ${JSON.stringify(entry)} \u2014 ` + `refusing to extract before any file is written`);
|
|
100568
101096
|
}
|
|
100569
101097
|
}
|
|
100570
|
-
const staging = mkdtempSync5(
|
|
101098
|
+
const staging = mkdtempSync5(join104(tmpdir6(), "skill-apply-extract-"));
|
|
100571
101099
|
try {
|
|
100572
101100
|
const flags = isGz ? ["-xzf"] : ["-xf"];
|
|
100573
101101
|
const r = spawnSync20("tar", [
|
|
@@ -100591,7 +101119,7 @@ function loadFromTarball(tarPath) {
|
|
|
100591
101119
|
}
|
|
100592
101120
|
}
|
|
100593
101121
|
function loadSingleFile(filePath) {
|
|
100594
|
-
const content =
|
|
101122
|
+
const content = readFileSync91(filePath, "utf-8");
|
|
100595
101123
|
return { "SKILL.md": content };
|
|
100596
101124
|
}
|
|
100597
101125
|
function loadFromStdin() {
|
|
@@ -100653,8 +101181,8 @@ function validatePayload(name, files) {
|
|
|
100653
101181
|
errors2.push(`${path10} fails \`bash -n\` syntax check: ${(r.stderr ?? "").trim()}`);
|
|
100654
101182
|
}
|
|
100655
101183
|
} else if (PY_SCRIPT_RE2.test(path10)) {
|
|
100656
|
-
const tmp = mkdtempSync5(
|
|
100657
|
-
const tmpPy =
|
|
101184
|
+
const tmp = mkdtempSync5(join104(tmpdir6(), "skill-apply-py-"));
|
|
101185
|
+
const tmpPy = join104(tmp, "check.py");
|
|
100658
101186
|
try {
|
|
100659
101187
|
writeFileSync42(tmpPy, content);
|
|
100660
101188
|
const r = spawnSync20("python3", ["-m", "py_compile", tmpPy], {
|
|
@@ -100677,12 +101205,12 @@ function diffSummary(currentDir, files) {
|
|
|
100677
101205
|
if (existsSync102(currentDir)) {
|
|
100678
101206
|
const walk2 = (sub) => {
|
|
100679
101207
|
for (const ent of readdirSync37(sub, { withFileTypes: true })) {
|
|
100680
|
-
const full =
|
|
101208
|
+
const full = join104(sub, ent.name);
|
|
100681
101209
|
const rel = relative4(currentDir, full);
|
|
100682
101210
|
if (ent.isDirectory()) {
|
|
100683
101211
|
walk2(full);
|
|
100684
101212
|
} else if (ent.isFile()) {
|
|
100685
|
-
currentFiles[rel.replace(/\\/g, "/")] =
|
|
101213
|
+
currentFiles[rel.replace(/\\/g, "/")] = readFileSync91(full, "utf-8");
|
|
100686
101214
|
}
|
|
100687
101215
|
}
|
|
100688
101216
|
};
|
|
@@ -100713,7 +101241,7 @@ function writePayload(poolDir, name, files) {
|
|
|
100713
101241
|
if (!existsSync102(poolDir)) {
|
|
100714
101242
|
mkdirSync59(poolDir, { recursive: true, mode: 493 });
|
|
100715
101243
|
}
|
|
100716
|
-
const target =
|
|
101244
|
+
const target = join104(poolDir, name);
|
|
100717
101245
|
let targetIsSymlink = false;
|
|
100718
101246
|
try {
|
|
100719
101247
|
const st = lstatSync12(target);
|
|
@@ -100724,11 +101252,11 @@ function writePayload(poolDir, name, files) {
|
|
|
100724
101252
|
if (targetIsSymlink) {
|
|
100725
101253
|
fail4(`refusing to overwrite symlink at ${target}; investigate manually`);
|
|
100726
101254
|
}
|
|
100727
|
-
const staging = mkdtempSync5(
|
|
101255
|
+
const staging = mkdtempSync5(join104(poolDir, `.skill-apply-stage-${name}-`));
|
|
100728
101256
|
let oldRename = null;
|
|
100729
101257
|
try {
|
|
100730
101258
|
for (const [path10, content] of Object.entries(files)) {
|
|
100731
|
-
const full =
|
|
101259
|
+
const full = join104(staging, path10);
|
|
100732
101260
|
mkdirSync59(dirname40(full), { recursive: true, mode: 493 });
|
|
100733
101261
|
const fd = openSync20(full, "wx");
|
|
100734
101262
|
try {
|
|
@@ -100806,7 +101334,7 @@ function registerSkillCommand(program3) {
|
|
|
100806
101334
|
}
|
|
100807
101335
|
const config = loadConfig();
|
|
100808
101336
|
const poolDir = resolveSkillsPoolDir2(config.switchroom?.skills_dir);
|
|
100809
|
-
const currentDir =
|
|
101337
|
+
const currentDir = join104(poolDir, name);
|
|
100810
101338
|
console.log(source_default.bold(`Skill: ${name}`) + source_default.gray(` (${Object.keys(files).length} files, ${sumBytes(files)} bytes)`));
|
|
100811
101339
|
console.log(source_default.bold("Diff vs current pool content:"));
|
|
100812
101340
|
console.log(diffSummary(currentDir, files));
|
|
@@ -100843,7 +101371,7 @@ import {
|
|
|
100843
101371
|
mkdirSync as mkdirSync60,
|
|
100844
101372
|
mkdtempSync as mkdtempSync6,
|
|
100845
101373
|
openSync as openSync21,
|
|
100846
|
-
readFileSync as
|
|
101374
|
+
readFileSync as readFileSync92,
|
|
100847
101375
|
readdirSync as readdirSync38,
|
|
100848
101376
|
renameSync as renameSync28,
|
|
100849
101377
|
rmSync as rmSync20,
|
|
@@ -100851,8 +101379,8 @@ import {
|
|
|
100851
101379
|
utimesSync,
|
|
100852
101380
|
writeFileSync as writeFileSync43
|
|
100853
101381
|
} from "node:fs";
|
|
100854
|
-
import { dirname as dirname41, join as
|
|
100855
|
-
import { homedir as
|
|
101382
|
+
import { dirname as dirname41, join as join105, relative as relative5, resolve as resolve59 } from "node:path";
|
|
101383
|
+
import { homedir as homedir55, tmpdir as tmpdir7 } from "node:os";
|
|
100856
101384
|
import { spawnSync as spawnSync21 } from "node:child_process";
|
|
100857
101385
|
init_helpers();
|
|
100858
101386
|
init_agent_config();
|
|
@@ -100863,10 +101391,10 @@ var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
|
|
|
100863
101391
|
var PERSONAL_SKILLS_SUBPATH = "personal-skills";
|
|
100864
101392
|
function resolveConfigSkillsDir(agent) {
|
|
100865
101393
|
const override = process.env.SWITCHROOM_CONFIG_DIR;
|
|
100866
|
-
const candidate = override ? resolve59(override) :
|
|
101394
|
+
const candidate = override ? resolve59(override) : join105(homedir55(), ".switchroom-config");
|
|
100867
101395
|
if (!existsSync103(candidate))
|
|
100868
101396
|
return null;
|
|
100869
|
-
return
|
|
101397
|
+
return join105(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
|
|
100870
101398
|
}
|
|
100871
101399
|
var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
|
|
100872
101400
|
function sweepMirrorPriors(configSkillsRoot) {
|
|
@@ -100884,7 +101412,7 @@ function sweepMirrorPriors(configSkillsRoot) {
|
|
|
100884
101412
|
if (now - ts < MIRROR_PRIOR_TTL_MS)
|
|
100885
101413
|
continue;
|
|
100886
101414
|
try {
|
|
100887
|
-
rmSync20(
|
|
101415
|
+
rmSync20(join105(configSkillsRoot, ent), { recursive: true, force: true });
|
|
100888
101416
|
} catch {}
|
|
100889
101417
|
}
|
|
100890
101418
|
} catch {}
|
|
@@ -100893,7 +101421,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
100893
101421
|
const configSkillsRoot = resolveConfigSkillsDir(agent);
|
|
100894
101422
|
if (!configSkillsRoot)
|
|
100895
101423
|
return;
|
|
100896
|
-
const dest =
|
|
101424
|
+
const dest = join105(configSkillsRoot, name);
|
|
100897
101425
|
try {
|
|
100898
101426
|
if (liveSkillDir !== null) {
|
|
100899
101427
|
try {
|
|
@@ -100908,31 +101436,31 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
100908
101436
|
if (liveSkillDir === null) {
|
|
100909
101437
|
sweepMirrorPriors(configSkillsRoot);
|
|
100910
101438
|
if (existsSync103(dest)) {
|
|
100911
|
-
const trash =
|
|
101439
|
+
const trash = join105(configSkillsRoot, `.${name}-trash-${Date.now()}`);
|
|
100912
101440
|
renameSync28(dest, trash);
|
|
100913
101441
|
}
|
|
100914
101442
|
return;
|
|
100915
101443
|
}
|
|
100916
101444
|
mkdirSync60(configSkillsRoot, { recursive: true, mode: 493 });
|
|
100917
101445
|
sweepMirrorPriors(configSkillsRoot);
|
|
100918
|
-
const staging = mkdtempSync6(
|
|
101446
|
+
const staging = mkdtempSync6(join105(configSkillsRoot, `.${name}-staging-`));
|
|
100919
101447
|
const walk2 = (src, dst) => {
|
|
100920
101448
|
mkdirSync60(dst, { recursive: true, mode: 493 });
|
|
100921
101449
|
for (const ent of readdirSync38(src, { withFileTypes: true })) {
|
|
100922
|
-
const s =
|
|
100923
|
-
const d =
|
|
101450
|
+
const s = join105(src, ent.name);
|
|
101451
|
+
const d = join105(dst, ent.name);
|
|
100924
101452
|
if (ent.isSymbolicLink())
|
|
100925
101453
|
continue;
|
|
100926
101454
|
if (ent.isDirectory())
|
|
100927
101455
|
walk2(s, d);
|
|
100928
101456
|
else if (ent.isFile()) {
|
|
100929
|
-
writeFileSync43(d,
|
|
101457
|
+
writeFileSync43(d, readFileSync92(s));
|
|
100930
101458
|
}
|
|
100931
101459
|
}
|
|
100932
101460
|
};
|
|
100933
101461
|
walk2(liveSkillDir, staging);
|
|
100934
101462
|
if (existsSync103(dest)) {
|
|
100935
|
-
const prior =
|
|
101463
|
+
const prior = join105(configSkillsRoot, `.${name}-prior-${Date.now()}`);
|
|
100936
101464
|
renameSync28(dest, prior);
|
|
100937
101465
|
}
|
|
100938
101466
|
renameSync28(staging, dest);
|
|
@@ -100962,16 +101490,16 @@ function resolveAgent(opts) {
|
|
|
100962
101490
|
function resolveAgentsRoot(opts) {
|
|
100963
101491
|
if (opts.root)
|
|
100964
101492
|
return resolve59(opts.root);
|
|
100965
|
-
return
|
|
101493
|
+
return join105(homedir55(), ".switchroom", "agents");
|
|
100966
101494
|
}
|
|
100967
101495
|
function personalSkillDir(agentsRoot, agent, name) {
|
|
100968
|
-
return
|
|
101496
|
+
return join105(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
|
|
100969
101497
|
}
|
|
100970
101498
|
function trashDir(agentsRoot, agent) {
|
|
100971
|
-
return
|
|
101499
|
+
return join105(agentsRoot, agent, ".claude", TRASH_DIRNAME);
|
|
100972
101500
|
}
|
|
100973
101501
|
function countPersonalSkills(agentsRoot, agent) {
|
|
100974
|
-
const skillsDir =
|
|
101502
|
+
const skillsDir = join105(agentsRoot, agent, ".claude", "skills");
|
|
100975
101503
|
if (!existsSync103(skillsDir))
|
|
100976
101504
|
return 0;
|
|
100977
101505
|
let n = 0;
|
|
@@ -101009,7 +101537,7 @@ function loadFromDir2(dir) {
|
|
|
101009
101537
|
const files = {};
|
|
101010
101538
|
const walk2 = (sub) => {
|
|
101011
101539
|
for (const ent of readdirSync38(sub, { withFileTypes: true })) {
|
|
101012
|
-
const full =
|
|
101540
|
+
const full = join105(sub, ent.name);
|
|
101013
101541
|
if (ent.isSymbolicLink()) {
|
|
101014
101542
|
fail5(`refusing to read symlink in --from dir: ${relative5(abs, full)}`);
|
|
101015
101543
|
}
|
|
@@ -101019,7 +101547,7 @@ function loadFromDir2(dir) {
|
|
|
101019
101547
|
}
|
|
101020
101548
|
if (ent.isFile()) {
|
|
101021
101549
|
const rel = relative5(abs, full).replace(/\\/g, "/");
|
|
101022
|
-
files[rel] =
|
|
101550
|
+
files[rel] = readFileSync92(full, "utf-8");
|
|
101023
101551
|
}
|
|
101024
101552
|
}
|
|
101025
101553
|
};
|
|
@@ -101062,8 +101590,8 @@ function behavioralValidate(files) {
|
|
|
101062
101590
|
errors2.push(`${path10} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
|
|
101063
101591
|
}
|
|
101064
101592
|
} else if (PY_SCRIPT_RE.test(path10)) {
|
|
101065
|
-
const tmp = mkdtempSync6(
|
|
101066
|
-
const tmpPy =
|
|
101593
|
+
const tmp = mkdtempSync6(join105(tmpdir7(), "skill-personal-py-"));
|
|
101594
|
+
const tmpPy = join105(tmp, "check.py");
|
|
101067
101595
|
try {
|
|
101068
101596
|
writeFileSync43(tmpPy, content);
|
|
101069
101597
|
const r = spawnSync21("python3", ["-m", "py_compile", tmpPy], {
|
|
@@ -101087,7 +101615,7 @@ function sweepTrash(agentsRoot, agent) {
|
|
|
101087
101615
|
for (const ent of readdirSync38(trash, { withFileTypes: true })) {
|
|
101088
101616
|
if (!ent.isDirectory())
|
|
101089
101617
|
continue;
|
|
101090
|
-
const entPath =
|
|
101618
|
+
const entPath = join105(trash, ent.name);
|
|
101091
101619
|
try {
|
|
101092
101620
|
const st = statSync55(entPath);
|
|
101093
101621
|
if (now - st.mtimeMs > TRASH_TTL_MS) {
|
|
@@ -101108,11 +101636,11 @@ function writePersonalSkill(targetDir, files) {
|
|
|
101108
101636
|
fail5(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
|
|
101109
101637
|
}
|
|
101110
101638
|
mkdirSync60(dirname41(targetDir), { recursive: true, mode: 493 });
|
|
101111
|
-
const staging = mkdtempSync6(
|
|
101639
|
+
const staging = mkdtempSync6(join105(dirname41(targetDir), `.skill-personal-stage-`));
|
|
101112
101640
|
let oldRename = null;
|
|
101113
101641
|
try {
|
|
101114
101642
|
for (const [path10, content] of Object.entries(files)) {
|
|
101115
|
-
const full =
|
|
101643
|
+
const full = join105(staging, path10);
|
|
101116
101644
|
mkdirSync60(dirname41(full), { recursive: true, mode: 493 });
|
|
101117
101645
|
const fd = openSync21(full, "wx");
|
|
101118
101646
|
try {
|
|
@@ -101219,7 +101747,7 @@ function loadFiles(opts) {
|
|
|
101219
101747
|
return loadFromDir2(p);
|
|
101220
101748
|
}
|
|
101221
101749
|
if (p.endsWith(".md")) {
|
|
101222
|
-
return { "SKILL.md":
|
|
101750
|
+
return { "SKILL.md": readFileSync92(p, "utf-8") };
|
|
101223
101751
|
}
|
|
101224
101752
|
fail5(`--from must be a directory or a .md file. Got: ${opts.from}`);
|
|
101225
101753
|
}
|
|
@@ -101259,10 +101787,10 @@ function editPersonalAction(name, opts) {
|
|
|
101259
101787
|
}
|
|
101260
101788
|
var CLONE_SOURCE_RE = /^(shared|bundled):([a-z0-9][a-z0-9_-]{0,62})$/;
|
|
101261
101789
|
function defaultSharedRoot() {
|
|
101262
|
-
return
|
|
101790
|
+
return join105(homedir55(), ".switchroom", "skills");
|
|
101263
101791
|
}
|
|
101264
101792
|
function defaultBundledRoot() {
|
|
101265
|
-
return
|
|
101793
|
+
return join105(homedir55(), ".switchroom", "skills", "_bundled");
|
|
101266
101794
|
}
|
|
101267
101795
|
function resolveCloneSource(source, opts) {
|
|
101268
101796
|
const m = CLONE_SOURCE_RE.exec(source);
|
|
@@ -101272,7 +101800,7 @@ function resolveCloneSource(source, opts) {
|
|
|
101272
101800
|
const tier = m[1];
|
|
101273
101801
|
const slug = m[2];
|
|
101274
101802
|
const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
|
|
101275
|
-
const dir =
|
|
101803
|
+
const dir = join105(root, slug);
|
|
101276
101804
|
if (!existsSync103(dir)) {
|
|
101277
101805
|
fail5(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
|
|
101278
101806
|
}
|
|
@@ -101288,7 +101816,7 @@ function readSourceFiles(dir) {
|
|
|
101288
101816
|
const skipped = [];
|
|
101289
101817
|
const walk2 = (sub) => {
|
|
101290
101818
|
for (const ent of readdirSync38(sub, { withFileTypes: true })) {
|
|
101291
|
-
const full =
|
|
101819
|
+
const full = join105(sub, ent.name);
|
|
101292
101820
|
if (ent.isSymbolicLink()) {
|
|
101293
101821
|
continue;
|
|
101294
101822
|
}
|
|
@@ -101308,7 +101836,7 @@ function readSourceFiles(dir) {
|
|
|
101308
101836
|
fail5(`clone source has oversized file ${rel} (${st.size} bytes > ${CLONE_MAX_FILE_BYTES}); ` + `refuse to read`, 3);
|
|
101309
101837
|
}
|
|
101310
101838
|
} catch {}
|
|
101311
|
-
files[rel] =
|
|
101839
|
+
files[rel] = readFileSync92(full, "utf-8");
|
|
101312
101840
|
}
|
|
101313
101841
|
}
|
|
101314
101842
|
};
|
|
@@ -101399,7 +101927,7 @@ function removePersonalAction(name, opts) {
|
|
|
101399
101927
|
const trashRoot2 = trashDir(agentsRoot, agent);
|
|
101400
101928
|
mkdirSync60(trashRoot2, { recursive: true, mode: 493 });
|
|
101401
101929
|
const ts = Date.now();
|
|
101402
|
-
const trashTarget =
|
|
101930
|
+
const trashTarget = join105(trashRoot2, `${name}-${ts}`);
|
|
101403
101931
|
renameSync28(target, trashTarget);
|
|
101404
101932
|
const now = new Date(ts);
|
|
101405
101933
|
utimesSync(trashTarget, now, now);
|
|
@@ -101418,7 +101946,7 @@ function listPersonalAction(opts) {
|
|
|
101418
101946
|
const agent = resolveAgent(opts);
|
|
101419
101947
|
const agentsRoot = resolveAgentsRoot(opts);
|
|
101420
101948
|
sweepTrash(agentsRoot, agent);
|
|
101421
|
-
const skillsDir =
|
|
101949
|
+
const skillsDir = join105(agentsRoot, agent, ".claude", "skills");
|
|
101422
101950
|
const personal = [];
|
|
101423
101951
|
if (existsSync103(skillsDir)) {
|
|
101424
101952
|
for (const ent of readdirSync38(skillsDir, { withFileTypes: true })) {
|
|
@@ -101427,7 +101955,7 @@ function listPersonalAction(opts) {
|
|
|
101427
101955
|
if (!ent.name.startsWith(PERSONAL_PREFIX))
|
|
101428
101956
|
continue;
|
|
101429
101957
|
const skillName = ent.name.slice(PERSONAL_PREFIX.length);
|
|
101430
|
-
const skillPath =
|
|
101958
|
+
const skillPath = join105(skillsDir, ent.name);
|
|
101431
101959
|
let fileCount = 0;
|
|
101432
101960
|
let totalBytes = 0;
|
|
101433
101961
|
const walk2 = (sub) => {
|
|
@@ -101435,10 +101963,10 @@ function listPersonalAction(opts) {
|
|
|
101435
101963
|
if (e.isFile()) {
|
|
101436
101964
|
fileCount += 1;
|
|
101437
101965
|
try {
|
|
101438
|
-
totalBytes += statSync55(
|
|
101966
|
+
totalBytes += statSync55(join105(sub, e.name)).size;
|
|
101439
101967
|
} catch {}
|
|
101440
101968
|
} else if (e.isDirectory()) {
|
|
101441
|
-
walk2(
|
|
101969
|
+
walk2(join105(sub, e.name));
|
|
101442
101970
|
}
|
|
101443
101971
|
}
|
|
101444
101972
|
};
|
|
@@ -101476,12 +102004,12 @@ function registerSkillPersonalCommands(program3) {
|
|
|
101476
102004
|
|
|
101477
102005
|
// src/cli/self-improve-propose-skill.ts
|
|
101478
102006
|
import { createConnection as createConnection4 } from "node:net";
|
|
101479
|
-
import { homedir as
|
|
101480
|
-
import { join as
|
|
101481
|
-
import { readFileSync as
|
|
102007
|
+
import { homedir as homedir56 } from "node:os";
|
|
102008
|
+
import { join as join106 } from "node:path";
|
|
102009
|
+
import { readFileSync as readFileSync93 } from "node:fs";
|
|
101482
102010
|
var IPC_CONNECT_TIMEOUT_MS = 5000;
|
|
101483
102011
|
function gatewaySocketPath() {
|
|
101484
|
-
return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ?
|
|
102012
|
+
return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ? join106(process.env.TELEGRAM_STATE_DIR, "gateway.sock") : join106(homedir56(), ".claude", "channels", "telegram", "gateway.sock"));
|
|
101485
102013
|
}
|
|
101486
102014
|
function fail6(msg, code = 1) {
|
|
101487
102015
|
console.error(msg);
|
|
@@ -101516,7 +102044,7 @@ function registerSelfImproveProposeSkillCommand(program3) {
|
|
|
101516
102044
|
fail6("agent name required (--agent or $SWITCHROOM_AGENT_NAME)");
|
|
101517
102045
|
let draft;
|
|
101518
102046
|
try {
|
|
101519
|
-
draft = JSON.parse(
|
|
102047
|
+
draft = JSON.parse(readFileSync93(opts.draft, "utf-8"));
|
|
101520
102048
|
} catch (e) {
|
|
101521
102049
|
fail6(`failed to read/parse --draft: ${e.message}`);
|
|
101522
102050
|
}
|
|
@@ -101553,28 +102081,28 @@ function registerSelfImproveProposeSkillCommand(program3) {
|
|
|
101553
102081
|
init_esm();
|
|
101554
102082
|
init_helpers();
|
|
101555
102083
|
var import_yaml25 = __toESM(require_dist(), 1);
|
|
101556
|
-
import { existsSync as existsSync104, readdirSync as readdirSync39, readFileSync as
|
|
101557
|
-
import { homedir as
|
|
101558
|
-
import { join as
|
|
102084
|
+
import { existsSync as existsSync104, readdirSync as readdirSync39, readFileSync as readFileSync94, statSync as statSync56 } from "node:fs";
|
|
102085
|
+
import { homedir as homedir57 } from "node:os";
|
|
102086
|
+
import { join as join107, resolve as resolve60 } from "node:path";
|
|
101559
102087
|
var PERSONAL_PREFIX2 = "personal-";
|
|
101560
102088
|
var BUNDLED_SUBDIR = "_bundled";
|
|
101561
102089
|
var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
|
|
101562
102090
|
function defaultAgentsRoot() {
|
|
101563
|
-
return resolve60(
|
|
102091
|
+
return resolve60(homedir57(), ".switchroom/agents");
|
|
101564
102092
|
}
|
|
101565
102093
|
function defaultSharedRoot2() {
|
|
101566
|
-
return resolve60(
|
|
102094
|
+
return resolve60(homedir57(), ".switchroom/skills");
|
|
101567
102095
|
}
|
|
101568
102096
|
function defaultBundledRoot2() {
|
|
101569
|
-
return resolve60(
|
|
102097
|
+
return resolve60(homedir57(), ".switchroom/skills/_bundled");
|
|
101570
102098
|
}
|
|
101571
102099
|
function readSkillFrontmatter(skillDir) {
|
|
101572
|
-
const mdPath =
|
|
102100
|
+
const mdPath = join107(skillDir, "SKILL.md");
|
|
101573
102101
|
if (!existsSync104(mdPath))
|
|
101574
102102
|
return null;
|
|
101575
102103
|
let content;
|
|
101576
102104
|
try {
|
|
101577
|
-
content =
|
|
102105
|
+
content = readFileSync94(mdPath, "utf-8");
|
|
101578
102106
|
} catch {
|
|
101579
102107
|
return null;
|
|
101580
102108
|
}
|
|
@@ -101602,7 +102130,7 @@ function readSkillFrontmatter(skillDir) {
|
|
|
101602
102130
|
return { fm: parsed };
|
|
101603
102131
|
}
|
|
101604
102132
|
function statSkillMd(skillDir) {
|
|
101605
|
-
const mdPath =
|
|
102133
|
+
const mdPath = join107(skillDir, "SKILL.md");
|
|
101606
102134
|
try {
|
|
101607
102135
|
const st = statSync56(mdPath);
|
|
101608
102136
|
return { size: st.size, mtime: st.mtime.toISOString() };
|
|
@@ -101613,7 +102141,7 @@ function statSkillMd(skillDir) {
|
|
|
101613
102141
|
function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
101614
102142
|
if (!AGENT_NAME_RE3.test(agent))
|
|
101615
102143
|
return [];
|
|
101616
|
-
const skillsDir =
|
|
102144
|
+
const skillsDir = join107(agentsRoot, agent, ".claude/skills");
|
|
101617
102145
|
if (!existsSync104(skillsDir))
|
|
101618
102146
|
return [];
|
|
101619
102147
|
const out = [];
|
|
@@ -101626,7 +102154,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
101626
102154
|
for (const ent of entries) {
|
|
101627
102155
|
if (!ent.startsWith(PERSONAL_PREFIX2))
|
|
101628
102156
|
continue;
|
|
101629
|
-
const dirPath =
|
|
102157
|
+
const dirPath = join107(skillsDir, ent);
|
|
101630
102158
|
try {
|
|
101631
102159
|
if (!statSync56(dirPath).isDirectory())
|
|
101632
102160
|
continue;
|
|
@@ -101666,7 +102194,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
|
101666
102194
|
continue;
|
|
101667
102195
|
if (ent.startsWith("."))
|
|
101668
102196
|
continue;
|
|
101669
|
-
const dirPath =
|
|
102197
|
+
const dirPath = join107(sharedRoot, ent);
|
|
101670
102198
|
try {
|
|
101671
102199
|
if (!statSync56(dirPath).isDirectory())
|
|
101672
102200
|
continue;
|
|
@@ -101702,7 +102230,7 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
|
101702
102230
|
for (const ent of entries) {
|
|
101703
102231
|
if (ent.startsWith("."))
|
|
101704
102232
|
continue;
|
|
101705
|
-
const dirPath =
|
|
102233
|
+
const dirPath = join107(bundledRoot, ent);
|
|
101706
102234
|
try {
|
|
101707
102235
|
if (!statSync56(dirPath).isDirectory())
|
|
101708
102236
|
continue;
|
|
@@ -101857,8 +102385,8 @@ import {
|
|
|
101857
102385
|
realpathSync as realpathSync8,
|
|
101858
102386
|
copyFileSync as copyFileSync14
|
|
101859
102387
|
} from "node:fs";
|
|
101860
|
-
import { homedir as
|
|
101861
|
-
import { join as
|
|
102388
|
+
import { homedir as homedir58 } from "node:os";
|
|
102389
|
+
import { join as join108 } from "node:path";
|
|
101862
102390
|
import { spawnSync as spawnSync24 } from "node:child_process";
|
|
101863
102391
|
|
|
101864
102392
|
// src/cli/singleton-stale-cleanup.ts
|
|
@@ -102196,7 +102724,7 @@ networks:
|
|
|
102196
102724
|
# operator surface; the daemon's stderr lands in \`docker logs switchroom-hostd\`.
|
|
102197
102725
|
`;
|
|
102198
102726
|
}
|
|
102199
|
-
function resolveHostdHostHome(env2 = process.env, home2 =
|
|
102727
|
+
function resolveHostdHostHome(env2 = process.env, home2 = homedir58()) {
|
|
102200
102728
|
const fromEnv = env2.SWITCHROOM_HOST_HOME?.trim();
|
|
102201
102729
|
const resolved = fromEnv && fromEnv.length > 0 ? fromEnv : home2;
|
|
102202
102730
|
if (resolved === "/host-home" || resolved.startsWith("/host-home/")) {
|
|
@@ -102207,7 +102735,7 @@ function resolveHostdHostHome(env2 = process.env, home2 = homedir57()) {
|
|
|
102207
102735
|
return resolved;
|
|
102208
102736
|
}
|
|
102209
102737
|
function resolveHostdSkillsTarget(hostHome) {
|
|
102210
|
-
const skillsPath =
|
|
102738
|
+
const skillsPath = join108(hostHome, ".switchroom", "skills");
|
|
102211
102739
|
let st;
|
|
102212
102740
|
try {
|
|
102213
102741
|
st = lstatSync14(skillsPath);
|
|
@@ -102231,10 +102759,10 @@ function resolveHostdSkillsTarget(hostHome) {
|
|
|
102231
102759
|
return target;
|
|
102232
102760
|
}
|
|
102233
102761
|
function hostdDir() {
|
|
102234
|
-
return
|
|
102762
|
+
return join108(homedir58(), ".switchroom", "hostd");
|
|
102235
102763
|
}
|
|
102236
102764
|
function hostdComposePath() {
|
|
102237
|
-
return
|
|
102765
|
+
return join108(hostdDir(), "docker-compose.yml");
|
|
102238
102766
|
}
|
|
102239
102767
|
function backupExistingCompose() {
|
|
102240
102768
|
const p = hostdComposePath();
|
|
@@ -102363,7 +102891,7 @@ function doStatus() {
|
|
|
102363
102891
|
for (const name of readdirSync40(dir)) {
|
|
102364
102892
|
if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
|
|
102365
102893
|
continue;
|
|
102366
|
-
const sockPath =
|
|
102894
|
+
const sockPath = join108(dir, name, "sock");
|
|
102367
102895
|
if (existsSync106(sockPath)) {
|
|
102368
102896
|
const st = statSync57(sockPath);
|
|
102369
102897
|
if ((st.mode & 61440) === 49152) {
|
|
@@ -102455,22 +102983,25 @@ The log is created when hostd handles its first privileged-verb request.`));
|
|
|
102455
102983
|
init_source();
|
|
102456
102984
|
init_helpers();
|
|
102457
102985
|
init_operator_uid();
|
|
102986
|
+
init_compose();
|
|
102458
102987
|
import { chownSync as chownSync10, existsSync as existsSync107, mkdirSync as mkdirSync62, writeFileSync as writeFileSync45, copyFileSync as copyFileSync15 } from "node:fs";
|
|
102459
|
-
import { homedir as
|
|
102460
|
-
import { join as
|
|
102988
|
+
import { homedir as homedir59 } from "node:os";
|
|
102989
|
+
import { join as join109 } from "node:path";
|
|
102461
102990
|
import { spawnSync as spawnSync25 } from "node:child_process";
|
|
102462
102991
|
function resolveWebImageTag(explicitTag, release) {
|
|
102463
102992
|
if (explicitTag)
|
|
102464
102993
|
return explicitTag;
|
|
102465
102994
|
return resolveImageTag(resolveRelease({ root: release }));
|
|
102466
102995
|
}
|
|
102467
|
-
function resolveWebHostHome(env2 = process.env, home2 =
|
|
102996
|
+
function resolveWebHostHome(env2 = process.env, home2 = homedir59()) {
|
|
102468
102997
|
const fromEnv = env2.SWITCHROOM_HOST_HOME?.trim();
|
|
102469
102998
|
const resolved = fromEnv && fromEnv.length > 0 ? fromEnv : home2;
|
|
102470
|
-
|
|
102471
|
-
|
|
102999
|
+
try {
|
|
103000
|
+
assertPlausibleHostHome(resolved);
|
|
103001
|
+
} catch {
|
|
103002
|
+
throw new Error(`switchroom webd install: refusing to generate \u2014 the host home resolved to ` + `"${resolved}", an in-container path (never a valid host bind source). ` + `Emitting it would make Docker create empty dirs on the host and break ` + `every webhook forward + secret read.
|
|
102472
103003
|
|
|
102473
|
-
` + `Recovery:
|
|
103004
|
+
` + `Recovery: set SWITCHROOM_HOST_HOME to the real host home, or drive the ` + `install from hostd / the host shell (both set it).`);
|
|
102474
103005
|
}
|
|
102475
103006
|
return resolved;
|
|
102476
103007
|
}
|
|
@@ -102557,10 +103088,10 @@ services:
|
|
|
102557
103088
|
`;
|
|
102558
103089
|
}
|
|
102559
103090
|
function webdDir() {
|
|
102560
|
-
return
|
|
103091
|
+
return join109(homedir59(), ".switchroom", "web");
|
|
102561
103092
|
}
|
|
102562
103093
|
function webdComposePath() {
|
|
102563
|
-
return
|
|
103094
|
+
return join109(webdDir(), "docker-compose.yml");
|
|
102564
103095
|
}
|
|
102565
103096
|
function backupExistingCompose2() {
|
|
102566
103097
|
const p = webdComposePath();
|
|
@@ -102582,9 +103113,15 @@ function runDocker2(args) {
|
|
|
102582
103113
|
async function doInstall2(opts, program3) {
|
|
102583
103114
|
const operatorUid = resolveOperatorUid();
|
|
102584
103115
|
if (operatorUid === undefined) {
|
|
102585
|
-
console.error(source_default.red(`Could not resolve the operator uid
|
|
103116
|
+
console.error(source_default.red(`Could not resolve the operator uid.
|
|
102586
103117
|
` + `The web container must run as the operator uid so its webhook forwards pass
|
|
102587
|
-
` +
|
|
103118
|
+
` + `each agent gateway's peercred ACL; running as root would be silently 503'd.
|
|
103119
|
+
` + `
|
|
103120
|
+
` + `Tried, in order: SUDO_UID, this process's own uid (non-root only),
|
|
103121
|
+
` + `SWITCHROOM_HOSTD_OPERATOR_UID, and the owner of ~/.switchroom.
|
|
103122
|
+
` + `
|
|
103123
|
+
` + "Recovery (no shell required): re-run `switchroom hostd install` so the\n" + `hostd container carries SWITCHROOM_HOSTD_OPERATOR_UID, or make
|
|
103124
|
+
` + "~/.switchroom owned by the operator account (`chown -R <operator>:<operator>\n" + "~/.switchroom`) so it can be derived. If neither is possible, set\n" + "SWITCHROOM_HOSTD_OPERATOR_UID to the operator's numeric uid."));
|
|
102588
103125
|
process.exit(1);
|
|
102589
103126
|
}
|
|
102590
103127
|
const dir = webdDir();
|
|
@@ -102719,10 +103256,10 @@ function registerWebdCommand(program3) {
|
|
|
102719
103256
|
|
|
102720
103257
|
// src/cli/host-repair.ts
|
|
102721
103258
|
init_source();
|
|
102722
|
-
import { homedir as
|
|
102723
|
-
import { join as
|
|
103259
|
+
import { homedir as homedir60 } from "node:os";
|
|
103260
|
+
import { join as join110 } from "node:path";
|
|
102724
103261
|
var ARTIFACT_ALLOWLIST = {
|
|
102725
|
-
dockerComposePluginDir: (home2) =>
|
|
103262
|
+
dockerComposePluginDir: (home2) => join110(home2, ".docker", "cli-plugins", "docker-compose"),
|
|
102726
103263
|
stateSentinel: "/state"
|
|
102727
103264
|
};
|
|
102728
103265
|
function isStateBogusAutoDir(probe2) {
|
|
@@ -102771,7 +103308,7 @@ function isStateBogusAutoDir(probe2) {
|
|
|
102771
103308
|
return true;
|
|
102772
103309
|
}
|
|
102773
103310
|
function planMountRepairs(probe2, opts = {}) {
|
|
102774
|
-
const home2 = opts.hostHome ??
|
|
103311
|
+
const home2 = opts.hostHome ?? homedir60();
|
|
102775
103312
|
const items = [];
|
|
102776
103313
|
const dockerComposePath = ARTIFACT_ALLOWLIST.dockerComposePluginDir(home2);
|
|
102777
103314
|
const dockerComposeSt = probe2.lstat(dockerComposePath);
|
|
@@ -103177,8 +103714,8 @@ function printDeepDiveBrief(targets) {
|
|
|
103177
103714
|
// src/cli/hindsight-watch.ts
|
|
103178
103715
|
init_source();
|
|
103179
103716
|
import { existsSync as existsSync112, readdirSync as readdirSync45 } from "node:fs";
|
|
103180
|
-
import { homedir as
|
|
103181
|
-
import { join as
|
|
103717
|
+
import { homedir as homedir63 } from "node:os";
|
|
103718
|
+
import { join as join112, resolve as resolve63 } from "node:path";
|
|
103182
103719
|
|
|
103183
103720
|
// src/host-control/config-degraded.ts
|
|
103184
103721
|
import { connect as connect3 } from "node:net";
|
|
@@ -103247,8 +103784,8 @@ init_install_cron();
|
|
|
103247
103784
|
|
|
103248
103785
|
// src/hindsight-watch/probe.ts
|
|
103249
103786
|
import { spawnSync as spawnSync27 } from "node:child_process";
|
|
103250
|
-
import { readFileSync as
|
|
103251
|
-
import { homedir as
|
|
103787
|
+
import { readFileSync as readFileSync99, readdirSync as readdirSync44, statSync as statSync59 } from "node:fs";
|
|
103788
|
+
import { homedir as homedir62 } from "node:os";
|
|
103252
103789
|
import { resolve as resolve62 } from "node:path";
|
|
103253
103790
|
|
|
103254
103791
|
// src/hindsight-watch/metrics.ts
|
|
@@ -103376,12 +103913,12 @@ function readLlmSignals(series) {
|
|
|
103376
103913
|
|
|
103377
103914
|
// src/hindsight-watch/recall-log.ts
|
|
103378
103915
|
import { closeSync as closeSync22, existsSync as existsSync111, openSync as openSync22, readdirSync as readdirSync43, readSync as readSync6, statSync as statSync58 } from "node:fs";
|
|
103379
|
-
import { join as
|
|
103916
|
+
import { join as join111 } from "node:path";
|
|
103380
103917
|
var RECALL_WINDOW_ROWS = 200;
|
|
103381
103918
|
var RECALL_MAX_TAIL_BYTES = 1024 * 1024;
|
|
103382
103919
|
var RECALL_MAX_ROW_AGE_MS = 24 * 60 * 60 * 1000;
|
|
103383
103920
|
function recallLogPath2(agentDir) {
|
|
103384
|
-
return
|
|
103921
|
+
return join111(agentDir, ".claude", "plugins", "data", "hindsight-memory-inline", "state", "recall_log.jsonl");
|
|
103385
103922
|
}
|
|
103386
103923
|
function readRecallLogTail2(path10, windowRows = RECALL_WINDOW_ROWS) {
|
|
103387
103924
|
if (!existsSync111(path10))
|
|
@@ -103529,7 +104066,7 @@ function probeRecallLogs(agentsDir, now, windowRows = RECALL_WINDOW_ROWS, maxAge
|
|
|
103529
104066
|
const all = [];
|
|
103530
104067
|
let agents = 0;
|
|
103531
104068
|
for (const name of names) {
|
|
103532
|
-
const rows = withinWindow(readRecallLogTail2(recallLogPath2(
|
|
104069
|
+
const rows = withinWindow(readRecallLogTail2(recallLogPath2(join111(agentsDir, name)), windowRows), now, maxAgeMs);
|
|
103533
104070
|
if (rows.length > 0)
|
|
103534
104071
|
agents++;
|
|
103535
104072
|
all.push(...rows);
|
|
@@ -103624,7 +104161,7 @@ function readDropCount(hindsightDir) {
|
|
|
103624
104161
|
try {
|
|
103625
104162
|
if (statSync59(path10).size > DROPS_LEDGER_MAX_BYTES)
|
|
103626
104163
|
return 0;
|
|
103627
|
-
parsed = JSON.parse(
|
|
104164
|
+
parsed = JSON.parse(readFileSync99(path10, "utf8"));
|
|
103628
104165
|
} catch {
|
|
103629
104166
|
return 0;
|
|
103630
104167
|
}
|
|
@@ -103634,7 +104171,7 @@ function readDropCount(hindsightDir) {
|
|
|
103634
104171
|
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
103635
104172
|
}
|
|
103636
104173
|
function resolveAgentsDir2(explicit) {
|
|
103637
|
-
return explicit ?? resolve62(process.env.SWITCHROOM_HOME ?? process.env.HOME ??
|
|
104174
|
+
return explicit ?? resolve62(process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir62(), ".switchroom", "agents");
|
|
103638
104175
|
}
|
|
103639
104176
|
function probeSpool(agentsDir = resolveAgentsDir2()) {
|
|
103640
104177
|
let names;
|
|
@@ -104324,7 +104861,7 @@ function registerHindsightWatchCommand(program3) {
|
|
|
104324
104861
|
process.exitCode = runInstallCron(opts.cronUser);
|
|
104325
104862
|
return;
|
|
104326
104863
|
}
|
|
104327
|
-
const agentsDir = opts.agentsDir ?? process.env.SWITCHROOM_AGENTS_DIR ??
|
|
104864
|
+
const agentsDir = opts.agentsDir ?? process.env.SWITCHROOM_AGENTS_DIR ?? join112(process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir63(), ".switchroom", "agents");
|
|
104328
104865
|
const statePath = opts.state ?? defaultStatePath2();
|
|
104329
104866
|
const log = (m) => {
|
|
104330
104867
|
process.stderr.write(`${m}
|
|
@@ -104433,11 +104970,11 @@ async function notifyOperator(agentsDir, text, log) {
|
|
|
104433
104970
|
// src/cli/openrouter-watch.ts
|
|
104434
104971
|
init_source();
|
|
104435
104972
|
import { existsSync as existsSync114, readdirSync as readdirSync46 } from "node:fs";
|
|
104436
|
-
import { homedir as
|
|
104437
|
-
import { join as
|
|
104973
|
+
import { homedir as homedir65 } from "node:os";
|
|
104974
|
+
import { join as join113, resolve as resolve65 } from "node:path";
|
|
104438
104975
|
|
|
104439
104976
|
// src/openrouter/install-cron.ts
|
|
104440
|
-
import { existsSync as existsSync113, mkdirSync as mkdirSync64, readFileSync as
|
|
104977
|
+
import { existsSync as existsSync113, mkdirSync as mkdirSync64, readFileSync as readFileSync100, renameSync as renameSync29, writeFileSync as writeFileSync47 } from "node:fs";
|
|
104441
104978
|
import { dirname as dirname43 } from "node:path";
|
|
104442
104979
|
var CRON_PATH2 = "/etc/cron.d/openrouter-watch";
|
|
104443
104980
|
var CRON_SCHEDULE2 = "17 * * * *";
|
|
@@ -104457,7 +104994,7 @@ function installCron2(opts) {
|
|
|
104457
104994
|
const content = renderCron2(opts);
|
|
104458
104995
|
if (existsSync113(path10)) {
|
|
104459
104996
|
try {
|
|
104460
|
-
if (
|
|
104997
|
+
if (readFileSync100(path10, "utf8") === content) {
|
|
104461
104998
|
return { status: "unchanged", path: path10, content };
|
|
104462
104999
|
}
|
|
104463
105000
|
} catch {}
|
|
@@ -104469,16 +105006,16 @@ function installCron2(opts) {
|
|
|
104469
105006
|
return { status: "installed", path: path10, content };
|
|
104470
105007
|
}
|
|
104471
105008
|
// src/openrouter/state.ts
|
|
104472
|
-
import { mkdirSync as mkdirSync65, readFileSync as
|
|
104473
|
-
import { homedir as
|
|
105009
|
+
import { mkdirSync as mkdirSync65, readFileSync as readFileSync101, renameSync as renameSync30, writeFileSync as writeFileSync48 } from "node:fs";
|
|
105010
|
+
import { homedir as homedir64 } from "node:os";
|
|
104474
105011
|
import { dirname as dirname44, resolve as resolve64 } from "node:path";
|
|
104475
|
-
function defaultStatePath3(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ??
|
|
105012
|
+
function defaultStatePath3(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir64()) {
|
|
104476
105013
|
return resolve64(home2, ".switchroom", "openrouter-watch", "state.json");
|
|
104477
105014
|
}
|
|
104478
105015
|
function loadState2(path10) {
|
|
104479
105016
|
let parsed;
|
|
104480
105017
|
try {
|
|
104481
|
-
parsed = JSON.parse(
|
|
105018
|
+
parsed = JSON.parse(readFileSync101(path10, "utf8"));
|
|
104482
105019
|
} catch {
|
|
104483
105020
|
return {};
|
|
104484
105021
|
}
|
|
@@ -104589,7 +105126,7 @@ function registerOpenRouterWatchCommand(program3) {
|
|
|
104589
105126
|
process.exitCode = runInstallCron2(opts.cronUser);
|
|
104590
105127
|
return;
|
|
104591
105128
|
}
|
|
104592
|
-
const agentsDir = opts.agentsDir ?? process.env.SWITCHROOM_AGENTS_DIR ??
|
|
105129
|
+
const agentsDir = opts.agentsDir ?? process.env.SWITCHROOM_AGENTS_DIR ?? join113(process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir65(), ".switchroom", "agents");
|
|
104593
105130
|
const statePath = opts.state ?? defaultStatePath3();
|
|
104594
105131
|
const log = (m) => {
|
|
104595
105132
|
process.stderr.write(`${m}
|