switchroom 0.12.19 → 0.12.21
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/agent-scheduler/index.js +5 -1
- package/dist/auth-broker/index.js +5 -1
- package/dist/cli/switchroom.js +927 -639
- package/dist/host-control/main.js +5 -1
- package/dist/vault/approvals/kernel-server.js +5 -1
- package/dist/vault/broker/server.js +11 -7
- package/package.json +3 -2
- package/telegram-plugin/dist/gateway/gateway.js +63 -23
- package/telegram-plugin/gateway/chat-key.ts +67 -0
- package/telegram-plugin/gateway/gateway.ts +79 -14
- package/telegram-plugin/gateway/turn-state-purge.ts +71 -0
- package/telegram-plugin/pty-partial-handler.ts +4 -1
- package/telegram-plugin/stream-reply-handler.ts +6 -1
- package/telegram-plugin/tests/e2e.test.ts +5 -1
- package/telegram-plugin/tests/races.test.ts +5 -1
- package/telegram-plugin/tests/turn-state-purge.test.ts +109 -0
package/dist/cli/switchroom.js
CHANGED
|
@@ -13857,7 +13857,11 @@ var init_schema = __esm(() => {
|
|
|
13857
13857
|
path: ["approvalAuth"]
|
|
13858
13858
|
});
|
|
13859
13859
|
}
|
|
13860
|
-
}).describe("Vault-broker daemon configuration. The broker holds the decrypted vault " + "in memory and serves secrets to cron scripts via a Unix socket, so the " + "vault passphrase is entered once at startup rather than per-cron invocation.")
|
|
13860
|
+
}).describe("Vault-broker daemon configuration. The broker holds the decrypted vault " + "in memory and serves secrets to cron scripts via a Unix socket, so the " + "vault passphrase is entered once at startup rather than per-cron invocation."),
|
|
13861
|
+
backup: exports_external.object({
|
|
13862
|
+
destination: exports_external.string().optional().describe("Destination directory for `switchroom vault backup`. " + "When unset, the CLI defaults to " + "`~/.switchroom-config/vault-backups/` if that operator config " + "repo exists, else `~/.switchroom/vault-backups/`. " + "Path is tilde-expanded at read time. " + "MUST NOT be `~/.switchroom/vault/` (the broker bind-mount dir, " + "validated by `switchroom apply` against an artifact allowlist)."),
|
|
13863
|
+
retain: exports_external.number().int().nonnegative().default(30).describe("How many of the most-recent backups to keep in the destination dir. " + "Older ones are pruned after each new backup is written. Default 30 " + "= roughly a month at daily cadence.")
|
|
13864
|
+
}).optional().describe("Configuration for `switchroom vault backup`. Optional \u2014 the CLI works " + "with built-in defaults if this block is absent. The backed-up file is " + "the AES-256-GCM-encrypted vault envelope; the operator passphrase " + "remains the gate, so committing backups to a private git repo extends " + "durability without weakening encryption (provided the auto-unlock " + "blob is NEVER co-located \u2014 `vault backup` refuses to write into a " + "directory that contains an auto-unlock-shaped sibling).")
|
|
13861
13865
|
});
|
|
13862
13866
|
QuotaConfigSchema = exports_external.object({
|
|
13863
13867
|
weekly_budget_usd: exports_external.number().positive().optional().describe("Weekly USD spend budget. If unset, the greeting shows raw usage only."),
|
|
@@ -27440,10 +27444,10 @@ var CHAIN_GENESIS = "GENESIS", SEP = "\x00";
|
|
|
27440
27444
|
var init_audit_hashchain = () => {};
|
|
27441
27445
|
|
|
27442
27446
|
// src/host-control/audit-reader.ts
|
|
27443
|
-
import { homedir as
|
|
27444
|
-
import { join as
|
|
27445
|
-
function defaultAuditLogPath2(home2 =
|
|
27446
|
-
return
|
|
27447
|
+
import { homedir as homedir17 } from "node:os";
|
|
27448
|
+
import { join as join34 } from "node:path";
|
|
27449
|
+
function defaultAuditLogPath2(home2 = homedir17()) {
|
|
27450
|
+
return join34(home2, ".switchroom", "host-control-audit.log");
|
|
27447
27451
|
}
|
|
27448
27452
|
function parseAuditLine2(line) {
|
|
27449
27453
|
const trimmed = line.trim();
|
|
@@ -27598,17 +27602,17 @@ var init_doctor_status = __esm(() => {
|
|
|
27598
27602
|
|
|
27599
27603
|
// src/manifest.ts
|
|
27600
27604
|
import {
|
|
27601
|
-
existsSync as
|
|
27602
|
-
readFileSync as
|
|
27603
|
-
readdirSync as
|
|
27605
|
+
existsSync as existsSync46,
|
|
27606
|
+
readFileSync as readFileSync42,
|
|
27607
|
+
readdirSync as readdirSync16
|
|
27604
27608
|
} from "node:fs";
|
|
27605
|
-
import { dirname as dirname11, join as
|
|
27609
|
+
import { dirname as dirname11, join as join38 } from "node:path";
|
|
27606
27610
|
import { execSync as execSync2 } from "node:child_process";
|
|
27607
27611
|
function locateManifestPath() {
|
|
27608
27612
|
let dir = import.meta.dirname;
|
|
27609
27613
|
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
27610
|
-
const candidate =
|
|
27611
|
-
if (
|
|
27614
|
+
const candidate = join38(dir, "dependencies.json");
|
|
27615
|
+
if (existsSync46(candidate))
|
|
27612
27616
|
return candidate;
|
|
27613
27617
|
dir = dirname11(dir);
|
|
27614
27618
|
}
|
|
@@ -27621,7 +27625,7 @@ function loadManifest(manifestPath) {
|
|
|
27621
27625
|
}
|
|
27622
27626
|
let raw;
|
|
27623
27627
|
try {
|
|
27624
|
-
raw =
|
|
27628
|
+
raw = readFileSync42(path4, "utf-8");
|
|
27625
27629
|
} catch (err) {
|
|
27626
27630
|
throw new Error(`Failed to read manifest at ${path4}: ${err.message}`);
|
|
27627
27631
|
}
|
|
@@ -27677,16 +27681,16 @@ function probeClaudeVersion() {
|
|
|
27677
27681
|
}
|
|
27678
27682
|
function probePlaywrightMcpVersion() {
|
|
27679
27683
|
const home2 = process.env.HOME ?? "";
|
|
27680
|
-
const npxCache =
|
|
27681
|
-
if (!
|
|
27684
|
+
const npxCache = join38(home2, ".npm/_npx");
|
|
27685
|
+
if (!existsSync46(npxCache))
|
|
27682
27686
|
return null;
|
|
27683
27687
|
try {
|
|
27684
|
-
const entries =
|
|
27688
|
+
const entries = readdirSync16(npxCache);
|
|
27685
27689
|
for (const entry of entries) {
|
|
27686
|
-
const pkgPath =
|
|
27687
|
-
if (
|
|
27690
|
+
const pkgPath = join38(npxCache, entry, "node_modules/@playwright/mcp/package.json");
|
|
27691
|
+
if (existsSync46(pkgPath)) {
|
|
27688
27692
|
try {
|
|
27689
|
-
const pkg = JSON.parse(
|
|
27693
|
+
const pkg = JSON.parse(readFileSync42(pkgPath, "utf-8"));
|
|
27690
27694
|
if (pkg.version)
|
|
27691
27695
|
return pkg.version;
|
|
27692
27696
|
} catch {}
|
|
@@ -27778,13 +27782,13 @@ var init_manifest = __esm(() => {
|
|
|
27778
27782
|
});
|
|
27779
27783
|
|
|
27780
27784
|
// src/cli/doctor-docker.ts
|
|
27781
|
-
import { readFileSync as
|
|
27785
|
+
import { readFileSync as readFileSync43 } from "node:fs";
|
|
27782
27786
|
function isDockerMode(opts) {
|
|
27783
27787
|
if (process.env.SWITCHROOM_RUNTIME === "docker")
|
|
27784
27788
|
return true;
|
|
27785
27789
|
if (opts?.composePath) {
|
|
27786
27790
|
try {
|
|
27787
|
-
|
|
27791
|
+
readFileSync43(opts.composePath, "utf8");
|
|
27788
27792
|
return true;
|
|
27789
27793
|
} catch {}
|
|
27790
27794
|
}
|
|
@@ -27950,11 +27954,11 @@ var init_doctor_docker = __esm(() => {
|
|
|
27950
27954
|
});
|
|
27951
27955
|
|
|
27952
27956
|
// src/cli/doctor-auth-broker.ts
|
|
27953
|
-
import { existsSync as
|
|
27954
|
-
import { createHash as
|
|
27957
|
+
import { existsSync as existsSync47, readFileSync as readFileSync44 } from "node:fs";
|
|
27958
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
27955
27959
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
27956
|
-
import { homedir as
|
|
27957
|
-
import { join as
|
|
27960
|
+
import { homedir as homedir20 } from "node:os";
|
|
27961
|
+
import { join as join39 } from "node:path";
|
|
27958
27962
|
function defaultDockerInspect(container, format) {
|
|
27959
27963
|
try {
|
|
27960
27964
|
const r = spawnSync5("docker", ["inspect", "-f", format, container], { encoding: "utf-8", timeout: 5000 });
|
|
@@ -27977,7 +27981,7 @@ function resolveStateDir(deps) {
|
|
|
27977
27981
|
return deps.stateDir ?? resolveStatePath("state/auth-broker");
|
|
27978
27982
|
}
|
|
27979
27983
|
function sha256Hex(content) {
|
|
27980
|
-
return
|
|
27984
|
+
return createHash9("sha256").update(content).digest("hex");
|
|
27981
27985
|
}
|
|
27982
27986
|
function checkAuthBrokerServiceHealth(deps = {}) {
|
|
27983
27987
|
const inspect = deps.dockerInspect ?? defaultDockerInspect;
|
|
@@ -28054,8 +28058,8 @@ function checkAuthBrokerPerAgentSockets(config, deps = {}) {
|
|
|
28054
28058
|
}
|
|
28055
28059
|
function checkAuthBrokerDrift(deps = {}) {
|
|
28056
28060
|
const stateDir = resolveStateDir(deps);
|
|
28057
|
-
const indexPath =
|
|
28058
|
-
if (!
|
|
28061
|
+
const indexPath = join39(stateDir, "sha-index.json");
|
|
28062
|
+
if (!existsSync47(indexPath)) {
|
|
28059
28063
|
return {
|
|
28060
28064
|
name: "auth-broker: drift",
|
|
28061
28065
|
status: "ok",
|
|
@@ -28064,7 +28068,7 @@ function checkAuthBrokerDrift(deps = {}) {
|
|
|
28064
28068
|
}
|
|
28065
28069
|
let index;
|
|
28066
28070
|
try {
|
|
28067
|
-
index = JSON.parse(
|
|
28071
|
+
index = JSON.parse(readFileSync44(indexPath, "utf-8"));
|
|
28068
28072
|
} catch (err) {
|
|
28069
28073
|
return {
|
|
28070
28074
|
name: "auth-broker: drift",
|
|
@@ -28073,18 +28077,18 @@ function checkAuthBrokerDrift(deps = {}) {
|
|
|
28073
28077
|
fix: "Inspect `~/.switchroom/state/auth-broker/sha-index.json` for corruption."
|
|
28074
28078
|
};
|
|
28075
28079
|
}
|
|
28076
|
-
const home2 = deps.home ??
|
|
28080
|
+
const home2 = deps.home ?? homedir20();
|
|
28077
28081
|
const divergent = [];
|
|
28078
28082
|
const missingOnDisk = [];
|
|
28079
28083
|
for (const [label, expected] of Object.entries(index)) {
|
|
28080
28084
|
const credsPath = accountCredentialsPath(label, home2);
|
|
28081
|
-
if (!
|
|
28085
|
+
if (!existsSync47(credsPath)) {
|
|
28082
28086
|
missingOnDisk.push(label);
|
|
28083
28087
|
continue;
|
|
28084
28088
|
}
|
|
28085
28089
|
let got;
|
|
28086
28090
|
try {
|
|
28087
|
-
got = sha256Hex(
|
|
28091
|
+
got = sha256Hex(readFileSync44(credsPath, "utf-8"));
|
|
28088
28092
|
} catch (err) {
|
|
28089
28093
|
divergent.push(`${label} (read failed: ${err.message})`);
|
|
28090
28094
|
continue;
|
|
@@ -28115,8 +28119,8 @@ function checkAuthBrokerDrift(deps = {}) {
|
|
|
28115
28119
|
}
|
|
28116
28120
|
function checkAuthBrokerThresholdViolations(deps = {}) {
|
|
28117
28121
|
const stateDir = resolveStateDir(deps);
|
|
28118
|
-
const path4 =
|
|
28119
|
-
if (!
|
|
28122
|
+
const path4 = join39(stateDir, "threshold-violations.json");
|
|
28123
|
+
if (!existsSync47(path4)) {
|
|
28120
28124
|
return {
|
|
28121
28125
|
name: "auth-broker: threshold violations",
|
|
28122
28126
|
status: "ok",
|
|
@@ -28125,7 +28129,7 @@ function checkAuthBrokerThresholdViolations(deps = {}) {
|
|
|
28125
28129
|
}
|
|
28126
28130
|
let violations;
|
|
28127
28131
|
try {
|
|
28128
|
-
violations = JSON.parse(
|
|
28132
|
+
violations = JSON.parse(readFileSync44(path4, "utf-8"));
|
|
28129
28133
|
} catch (err) {
|
|
28130
28134
|
return {
|
|
28131
28135
|
name: "auth-broker: threshold violations",
|
|
@@ -28159,9 +28163,9 @@ function checkAuthBrokerActiveAccount(config, deps = {}) {
|
|
|
28159
28163
|
fix: "Run `switchroom auth use <label>` to pin a fleet-wide account, then `switchroom apply`. Without an active account every agent boot fails."
|
|
28160
28164
|
};
|
|
28161
28165
|
}
|
|
28162
|
-
const home2 = deps.home ??
|
|
28166
|
+
const home2 = deps.home ?? homedir20();
|
|
28163
28167
|
const dir = accountDir(active, home2);
|
|
28164
|
-
if (!
|
|
28168
|
+
if (!existsSync47(dir)) {
|
|
28165
28169
|
return {
|
|
28166
28170
|
name: "auth-broker: fleet active account",
|
|
28167
28171
|
status: "fail",
|
|
@@ -28170,7 +28174,7 @@ function checkAuthBrokerActiveAccount(config, deps = {}) {
|
|
|
28170
28174
|
};
|
|
28171
28175
|
}
|
|
28172
28176
|
const creds = accountCredentialsPath(active, home2);
|
|
28173
|
-
if (!
|
|
28177
|
+
if (!existsSync47(creds)) {
|
|
28174
28178
|
return {
|
|
28175
28179
|
name: "auth-broker: fleet active account",
|
|
28176
28180
|
status: "fail",
|
|
@@ -28307,17 +28311,17 @@ var init_doctor_hostd = () => {};
|
|
|
28307
28311
|
import {
|
|
28308
28312
|
accessSync,
|
|
28309
28313
|
constants as fsConstants4,
|
|
28310
|
-
existsSync as
|
|
28314
|
+
existsSync as existsSync48,
|
|
28311
28315
|
realpathSync as realpathSync4,
|
|
28312
|
-
statSync as
|
|
28316
|
+
statSync as statSync21
|
|
28313
28317
|
} from "node:fs";
|
|
28314
|
-
import { userInfo, homedir as
|
|
28315
|
-
import { join as
|
|
28318
|
+
import { userInfo, homedir as homedir21 } from "node:os";
|
|
28319
|
+
import { join as join40 } from "node:path";
|
|
28316
28320
|
function resolveVaultPath2(config) {
|
|
28317
28321
|
return config.vault?.path ? config.vault.path.replace(/^~/, process.env.HOME ?? "") : resolveStatePath("vault.enc");
|
|
28318
28322
|
}
|
|
28319
28323
|
function defaultStatVault(path4) {
|
|
28320
|
-
if (!
|
|
28324
|
+
if (!existsSync48(path4)) {
|
|
28321
28325
|
return { exists: false, readable: false, uid: -1, mode: 0, realPath: path4 };
|
|
28322
28326
|
}
|
|
28323
28327
|
let real = path4;
|
|
@@ -28327,7 +28331,7 @@ function defaultStatVault(path4) {
|
|
|
28327
28331
|
let uid = -1;
|
|
28328
28332
|
let mode = 0;
|
|
28329
28333
|
try {
|
|
28330
|
-
const s =
|
|
28334
|
+
const s = statSync21(real);
|
|
28331
28335
|
uid = s.uid;
|
|
28332
28336
|
mode = s.mode & 511;
|
|
28333
28337
|
} catch {
|
|
@@ -28450,7 +28454,7 @@ async function runSecretAccessChecks(config, deps = {}) {
|
|
|
28450
28454
|
};
|
|
28451
28455
|
const passphrase = deps.passphrase ?? process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
28452
28456
|
if (!passphrase) {
|
|
28453
|
-
const sock = deps.brokerOperatorSocket ??
|
|
28457
|
+
const sock = deps.brokerOperatorSocket ?? join40(homedir21(), ".switchroom", "broker-operator", "sock");
|
|
28454
28458
|
const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
|
|
28455
28459
|
for (const name of Object.keys(config.agents ?? {})) {
|
|
28456
28460
|
const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[name]);
|
|
@@ -28535,8 +28539,8 @@ import {
|
|
|
28535
28539
|
existsSync as realExistsSync,
|
|
28536
28540
|
readFileSync as realReadFileSync
|
|
28537
28541
|
} from "node:fs";
|
|
28538
|
-
import { join as
|
|
28539
|
-
import { homedir as
|
|
28542
|
+
import { join as join41, resolve as resolve28 } from "node:path";
|
|
28543
|
+
import { homedir as homedir22 } from "node:os";
|
|
28540
28544
|
function resolveDeps(config, deps) {
|
|
28541
28545
|
let agentsDir = deps.agentsDir;
|
|
28542
28546
|
if (agentsDir === undefined) {
|
|
@@ -28659,8 +28663,8 @@ function checkScaffoldWiring(config, driveAgents, d) {
|
|
|
28659
28663
|
});
|
|
28660
28664
|
continue;
|
|
28661
28665
|
}
|
|
28662
|
-
const mcpPath =
|
|
28663
|
-
const claudeJsonPath =
|
|
28666
|
+
const mcpPath = join41(agentDir, ".mcp.json");
|
|
28667
|
+
const claudeJsonPath = join41(agentDir, ".claude", ".claude.json");
|
|
28664
28668
|
const mcpRead = readJson(d, mcpPath);
|
|
28665
28669
|
const trustRead = readJson(d, claudeJsonPath);
|
|
28666
28670
|
if (mcpRead.kind === "unreadable" || trustRead.kind === "unreadable") {
|
|
@@ -28773,7 +28777,7 @@ async function runDriveBrokerReachabilityChecks(config, deps = {}) {
|
|
|
28773
28777
|
}
|
|
28774
28778
|
];
|
|
28775
28779
|
}
|
|
28776
|
-
const sock = deps.brokerOperatorSocket ??
|
|
28780
|
+
const sock = deps.brokerOperatorSocket ?? join41(homedir22(), ".switchroom", "broker-operator", "sock");
|
|
28777
28781
|
const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
|
|
28778
28782
|
const results = [];
|
|
28779
28783
|
for (const agent of driveAgents) {
|
|
@@ -28824,12 +28828,12 @@ import {
|
|
|
28824
28828
|
readdirSync as realReaddirSync,
|
|
28825
28829
|
statSync as realStatSync
|
|
28826
28830
|
} from "node:fs";
|
|
28827
|
-
import { homedir as
|
|
28828
|
-
import { join as
|
|
28831
|
+
import { homedir as homedir23 } from "node:os";
|
|
28832
|
+
import { join as join42 } from "node:path";
|
|
28829
28833
|
function runCredentialsMigrationChecks(config, deps = {}) {
|
|
28830
|
-
const credDir = deps.credentialsDir ??
|
|
28831
|
-
const
|
|
28832
|
-
const
|
|
28834
|
+
const credDir = deps.credentialsDir ?? join42(homedir23(), ".switchroom", "credentials");
|
|
28835
|
+
const existsSync49 = deps.existsSync ?? ((p) => realExistsSync2(p));
|
|
28836
|
+
const readdirSync18 = deps.readdirSync ?? ((p) => realReaddirSync(p));
|
|
28833
28837
|
const isDirectory = deps.isDirectory ?? ((p) => {
|
|
28834
28838
|
try {
|
|
28835
28839
|
return realStatSync(p).isDirectory();
|
|
@@ -28837,12 +28841,12 @@ function runCredentialsMigrationChecks(config, deps = {}) {
|
|
|
28837
28841
|
return false;
|
|
28838
28842
|
}
|
|
28839
28843
|
});
|
|
28840
|
-
if (!
|
|
28844
|
+
if (!existsSync49(credDir))
|
|
28841
28845
|
return [];
|
|
28842
28846
|
const agentNames = new Set(Object.keys(config.agents ?? {}));
|
|
28843
28847
|
let entries;
|
|
28844
28848
|
try {
|
|
28845
|
-
entries =
|
|
28849
|
+
entries = readdirSync18(credDir);
|
|
28846
28850
|
} catch {
|
|
28847
28851
|
return [
|
|
28848
28852
|
{
|
|
@@ -28855,7 +28859,7 @@ function runCredentialsMigrationChecks(config, deps = {}) {
|
|
|
28855
28859
|
const flat = [];
|
|
28856
28860
|
const perAgentDirs = [];
|
|
28857
28861
|
for (const e of entries) {
|
|
28858
|
-
const full =
|
|
28862
|
+
const full = join42(credDir, e);
|
|
28859
28863
|
if (isDirectory(full) && agentNames.has(e)) {
|
|
28860
28864
|
perAgentDirs.push(e);
|
|
28861
28865
|
} else {
|
|
@@ -28978,19 +28982,19 @@ var init_doctor_inlined_secrets = __esm(() => {
|
|
|
28978
28982
|
|
|
28979
28983
|
// src/cli/doctor-audit-integrity.ts
|
|
28980
28984
|
import { readFileSync as fsReadFileSync2 } from "node:fs";
|
|
28981
|
-
import { homedir as
|
|
28982
|
-
import { join as
|
|
28985
|
+
import { homedir as homedir24 } from "node:os";
|
|
28986
|
+
import { join as join43 } from "node:path";
|
|
28983
28987
|
function rootWrittenLogs(home2) {
|
|
28984
28988
|
return [
|
|
28985
|
-
{ label: "vault-broker", path:
|
|
28989
|
+
{ label: "vault-broker", path: join43(home2, ".switchroom", "vault-audit.log") },
|
|
28986
28990
|
{
|
|
28987
28991
|
label: "hostd",
|
|
28988
|
-
path:
|
|
28992
|
+
path: join43(home2, ".switchroom", "host-control-audit.log")
|
|
28989
28993
|
}
|
|
28990
28994
|
];
|
|
28991
28995
|
}
|
|
28992
28996
|
function runAuditIntegrityChecks(deps = {}) {
|
|
28993
|
-
const home2 = deps.homeDir ??
|
|
28997
|
+
const home2 = deps.homeDir ?? homedir24();
|
|
28994
28998
|
const read = deps.readFileSync ?? ((p) => fsReadFileSync2(p, "utf8"));
|
|
28995
28999
|
const results = [];
|
|
28996
29000
|
for (const { label, path: path4 } of rootWrittenLogs(home2)) {
|
|
@@ -29266,16 +29270,16 @@ var init_client4 = __esm(() => {
|
|
|
29266
29270
|
});
|
|
29267
29271
|
|
|
29268
29272
|
// src/cli/doctor-agent-smoke.ts
|
|
29269
|
-
import { existsSync as
|
|
29270
|
-
import { homedir as
|
|
29271
|
-
import { join as
|
|
29273
|
+
import { existsSync as existsSync49 } from "node:fs";
|
|
29274
|
+
import { homedir as homedir25 } from "node:os";
|
|
29275
|
+
import { join as join44 } from "node:path";
|
|
29272
29276
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
29273
29277
|
async function runAgentSmokeChecks(config, deps = {}) {
|
|
29274
29278
|
if (deps.fast)
|
|
29275
29279
|
return [];
|
|
29276
|
-
const home2 = deps.homeDir ??
|
|
29277
|
-
const sock = deps.operatorSockPath ??
|
|
29278
|
-
if (!deps.hostdRequestImpl && !
|
|
29280
|
+
const home2 = deps.homeDir ?? homedir25();
|
|
29281
|
+
const sock = deps.operatorSockPath ?? join44(home2, ".switchroom", "hostd", "operator", "sock");
|
|
29282
|
+
if (!deps.hostdRequestImpl && !existsSync49(sock)) {
|
|
29279
29283
|
return [
|
|
29280
29284
|
{
|
|
29281
29285
|
name: "agent liveness",
|
|
@@ -29394,25 +29398,25 @@ import { execSync as execSync3, spawnSync as spawnSync7 } from "node:child_proce
|
|
|
29394
29398
|
import {
|
|
29395
29399
|
accessSync as accessSync2,
|
|
29396
29400
|
constants as fsConstants5,
|
|
29397
|
-
existsSync as
|
|
29401
|
+
existsSync as existsSync50,
|
|
29398
29402
|
lstatSync as lstatSync5,
|
|
29399
|
-
mkdirSync as
|
|
29400
|
-
readFileSync as
|
|
29401
|
-
readdirSync as
|
|
29402
|
-
statSync as
|
|
29403
|
+
mkdirSync as mkdirSync27,
|
|
29404
|
+
readFileSync as readFileSync45,
|
|
29405
|
+
readdirSync as readdirSync18,
|
|
29406
|
+
statSync as statSync22
|
|
29403
29407
|
} from "node:fs";
|
|
29404
|
-
import { dirname as dirname12, join as
|
|
29408
|
+
import { dirname as dirname12, join as join45, resolve as resolve29 } from "node:path";
|
|
29405
29409
|
import { createPublicKey, createPrivateKey } from "node:crypto";
|
|
29406
29410
|
function findInNvm(bin) {
|
|
29407
|
-
const nvmRoot =
|
|
29408
|
-
if (!
|
|
29411
|
+
const nvmRoot = join45(process.env.HOME ?? "", ".nvm", "versions", "node");
|
|
29412
|
+
if (!existsSync50(nvmRoot))
|
|
29409
29413
|
return null;
|
|
29410
29414
|
try {
|
|
29411
|
-
const versions =
|
|
29415
|
+
const versions = readdirSync18(nvmRoot).sort().reverse();
|
|
29412
29416
|
for (const v of versions) {
|
|
29413
|
-
const candidate =
|
|
29417
|
+
const candidate = join45(nvmRoot, v, "bin", bin);
|
|
29414
29418
|
try {
|
|
29415
|
-
const s =
|
|
29419
|
+
const s = statSync22(candidate);
|
|
29416
29420
|
if (s.isFile() || s.isSymbolicLink()) {
|
|
29417
29421
|
return candidate;
|
|
29418
29422
|
}
|
|
@@ -29575,21 +29579,21 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
|
|
|
29575
29579
|
if (envBrowsersPath && envBrowsersPath.length > 0) {
|
|
29576
29580
|
cacheLocations.push(envBrowsersPath);
|
|
29577
29581
|
}
|
|
29578
|
-
cacheLocations.push(
|
|
29582
|
+
cacheLocations.push(join45(homeDir, ".cache", "ms-playwright"));
|
|
29579
29583
|
for (const cacheDir of cacheLocations) {
|
|
29580
|
-
if (!
|
|
29584
|
+
if (!existsSync50(cacheDir))
|
|
29581
29585
|
continue;
|
|
29582
29586
|
try {
|
|
29583
|
-
const entries =
|
|
29587
|
+
const entries = readdirSync18(cacheDir).filter((e) => e.startsWith("chromium"));
|
|
29584
29588
|
for (const entry of entries) {
|
|
29585
29589
|
const candidates2 = [
|
|
29586
|
-
|
|
29587
|
-
|
|
29588
|
-
|
|
29589
|
-
|
|
29590
|
+
join45(cacheDir, entry, "chrome-linux64", "chrome"),
|
|
29591
|
+
join45(cacheDir, entry, "chrome-linux", "chrome"),
|
|
29592
|
+
join45(cacheDir, entry, "chrome-linux64", "headless_shell"),
|
|
29593
|
+
join45(cacheDir, entry, "chrome-linux", "headless_shell")
|
|
29590
29594
|
];
|
|
29591
29595
|
for (const path4 of candidates2) {
|
|
29592
|
-
if (
|
|
29596
|
+
if (existsSync50(path4))
|
|
29593
29597
|
return path4;
|
|
29594
29598
|
}
|
|
29595
29599
|
}
|
|
@@ -29611,7 +29615,7 @@ function checkChromium() {
|
|
|
29611
29615
|
}
|
|
29612
29616
|
function checkDepsCacheWritable(depsRoot = resolvePath("~/.switchroom/deps")) {
|
|
29613
29617
|
try {
|
|
29614
|
-
|
|
29618
|
+
mkdirSync27(depsRoot, { recursive: true });
|
|
29615
29619
|
accessSync2(depsRoot, fsConstants5.W_OK);
|
|
29616
29620
|
return {
|
|
29617
29621
|
name: "~/.switchroom/deps writable",
|
|
@@ -29669,8 +29673,8 @@ function checkConfig(config, configPath) {
|
|
|
29669
29673
|
function checkLegacyState() {
|
|
29670
29674
|
const results = [];
|
|
29671
29675
|
const h = process.env.HOME ?? "/root";
|
|
29672
|
-
const clerkDir =
|
|
29673
|
-
const clerkPresent =
|
|
29676
|
+
const clerkDir = join45(h, LEGACY_STATE_DIR);
|
|
29677
|
+
const clerkPresent = existsSync50(clerkDir);
|
|
29674
29678
|
results.push({
|
|
29675
29679
|
name: "legacy ~/.clerk state",
|
|
29676
29680
|
status: clerkPresent ? "warn" : "ok",
|
|
@@ -29679,7 +29683,7 @@ function checkLegacyState() {
|
|
|
29679
29683
|
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."
|
|
29680
29684
|
} : {}
|
|
29681
29685
|
});
|
|
29682
|
-
const legacySock =
|
|
29686
|
+
const legacySock = join45(h, ".switchroom", "vault-broker.sock");
|
|
29683
29687
|
let sockStat = null;
|
|
29684
29688
|
try {
|
|
29685
29689
|
sockStat = lstatSync5(legacySock);
|
|
@@ -29712,7 +29716,7 @@ function checkVault(config) {
|
|
|
29712
29716
|
status: "ok",
|
|
29713
29717
|
detail: "Approval auth: passphrase (two-factor)"
|
|
29714
29718
|
};
|
|
29715
|
-
if (!
|
|
29719
|
+
if (!existsSync50(vaultPath)) {
|
|
29716
29720
|
return [
|
|
29717
29721
|
postureResult,
|
|
29718
29722
|
{
|
|
@@ -29883,8 +29887,8 @@ async function checkHindsight(config) {
|
|
|
29883
29887
|
}
|
|
29884
29888
|
function checkPendingRetainsQueue(dir) {
|
|
29885
29889
|
const home2 = process.env.HOME ?? "";
|
|
29886
|
-
const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ??
|
|
29887
|
-
if (!
|
|
29890
|
+
const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ?? join45(home2, ".hindsight", "pending-retains");
|
|
29891
|
+
if (!existsSync50(pendingDir)) {
|
|
29888
29892
|
return {
|
|
29889
29893
|
name: "pending-retains queue",
|
|
29890
29894
|
status: "ok",
|
|
@@ -29893,7 +29897,7 @@ function checkPendingRetainsQueue(dir) {
|
|
|
29893
29897
|
}
|
|
29894
29898
|
let names;
|
|
29895
29899
|
try {
|
|
29896
|
-
names =
|
|
29900
|
+
names = readdirSync18(pendingDir);
|
|
29897
29901
|
} catch (err) {
|
|
29898
29902
|
return {
|
|
29899
29903
|
name: "pending-retains queue",
|
|
@@ -29943,7 +29947,7 @@ function classifyReadError(err) {
|
|
|
29943
29947
|
}
|
|
29944
29948
|
function tryReadHostFile(path4) {
|
|
29945
29949
|
try {
|
|
29946
|
-
return { kind: "ok", content:
|
|
29950
|
+
return { kind: "ok", content: readFileSync45(path4, "utf-8") };
|
|
29947
29951
|
} catch (err) {
|
|
29948
29952
|
const kind = classifyReadError(err);
|
|
29949
29953
|
const error = err?.message ?? String(err);
|
|
@@ -29955,11 +29959,11 @@ function tryReadHostFile(path4) {
|
|
|
29955
29959
|
}
|
|
29956
29960
|
}
|
|
29957
29961
|
function parseEnvFile(path4) {
|
|
29958
|
-
if (!
|
|
29962
|
+
if (!existsSync50(path4))
|
|
29959
29963
|
return {};
|
|
29960
29964
|
let content;
|
|
29961
29965
|
try {
|
|
29962
|
-
content =
|
|
29966
|
+
content = readFileSync45(path4, "utf-8");
|
|
29963
29967
|
} catch {
|
|
29964
29968
|
return {};
|
|
29965
29969
|
}
|
|
@@ -30014,7 +30018,7 @@ async function checkTelegram(config) {
|
|
|
30014
30018
|
const plugin = agentConfig.channels?.telegram?.plugin ?? "switchroom";
|
|
30015
30019
|
if (plugin !== "switchroom")
|
|
30016
30020
|
continue;
|
|
30017
|
-
const envPath =
|
|
30021
|
+
const envPath = join45(agentsDir, name, "telegram", ".env");
|
|
30018
30022
|
const read = tryReadHostFile(envPath);
|
|
30019
30023
|
if (read.kind === "eacces") {
|
|
30020
30024
|
results.push({
|
|
@@ -30066,7 +30070,7 @@ async function checkTelegram(config) {
|
|
|
30066
30070
|
}
|
|
30067
30071
|
function checkStartShStale(agentName, startShPath) {
|
|
30068
30072
|
const label = `${agentName}: start.sh scheduler block`;
|
|
30069
|
-
if (!
|
|
30073
|
+
if (!existsSync50(startShPath)) {
|
|
30070
30074
|
return {
|
|
30071
30075
|
name: label,
|
|
30072
30076
|
status: "warn",
|
|
@@ -30076,7 +30080,7 @@ function checkStartShStale(agentName, startShPath) {
|
|
|
30076
30080
|
}
|
|
30077
30081
|
let content;
|
|
30078
30082
|
try {
|
|
30079
|
-
content =
|
|
30083
|
+
content = readFileSync45(startShPath, "utf-8");
|
|
30080
30084
|
} catch (err) {
|
|
30081
30085
|
return {
|
|
30082
30086
|
name: label,
|
|
@@ -30097,7 +30101,7 @@ function checkStartShStale(agentName, startShPath) {
|
|
|
30097
30101
|
}
|
|
30098
30102
|
function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
30099
30103
|
const label = `${agentName}: $HOME/.switchroom symlink (#910)`;
|
|
30100
|
-
const path4 =
|
|
30104
|
+
const path4 = join45(agentDir, "home", ".switchroom");
|
|
30101
30105
|
let stats;
|
|
30102
30106
|
try {
|
|
30103
30107
|
stats = lstatSync5(path4);
|
|
@@ -30134,8 +30138,8 @@ function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
|
30134
30138
|
}
|
|
30135
30139
|
function checkRepoHygiene(repoRoot) {
|
|
30136
30140
|
const results = [];
|
|
30137
|
-
const exportDir =
|
|
30138
|
-
if (
|
|
30141
|
+
const exportDir = join45(repoRoot, "clerk-export");
|
|
30142
|
+
if (existsSync50(exportDir)) {
|
|
30139
30143
|
results.push({
|
|
30140
30144
|
name: "repo hygiene: clerk-export/ on disk (#1072)",
|
|
30141
30145
|
status: "warn",
|
|
@@ -30143,8 +30147,8 @@ function checkRepoHygiene(repoRoot) {
|
|
|
30143
30147
|
fix: `Run scripts/migrate-clerk-export-to-vault.sh to move the bundle ` + `into the vault, then delete the on-disk copy.`
|
|
30144
30148
|
});
|
|
30145
30149
|
}
|
|
30146
|
-
const knownTarball =
|
|
30147
|
-
if (
|
|
30150
|
+
const knownTarball = join45(repoRoot, "clerk-export-with-secrets.tar.gz");
|
|
30151
|
+
if (existsSync50(knownTarball)) {
|
|
30148
30152
|
results.push({
|
|
30149
30153
|
name: "repo hygiene: clerk-export-with-secrets.tar.gz on disk (#1072)",
|
|
30150
30154
|
status: "warn",
|
|
@@ -30153,7 +30157,7 @@ function checkRepoHygiene(repoRoot) {
|
|
|
30153
30157
|
});
|
|
30154
30158
|
}
|
|
30155
30159
|
try {
|
|
30156
|
-
const entries =
|
|
30160
|
+
const entries = readdirSync18(repoRoot);
|
|
30157
30161
|
for (const name of entries) {
|
|
30158
30162
|
if (name === "clerk-export-with-secrets.tar.gz")
|
|
30159
30163
|
continue;
|
|
@@ -30161,7 +30165,7 @@ function checkRepoHygiene(repoRoot) {
|
|
|
30161
30165
|
results.push({
|
|
30162
30166
|
name: `repo hygiene: ${name} on disk (#1072)`,
|
|
30163
30167
|
status: "warn",
|
|
30164
|
-
detail: `${
|
|
30168
|
+
detail: `${join45(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
|
|
30165
30169
|
fix: `Inspect, migrate any secrets into the vault, then delete the ` + `archive.`
|
|
30166
30170
|
});
|
|
30167
30171
|
}
|
|
@@ -30184,12 +30188,12 @@ function checkRepoHygiene(repoRoot) {
|
|
|
30184
30188
|
}
|
|
30185
30189
|
function isSwitchroomCheckout(dir) {
|
|
30186
30190
|
try {
|
|
30187
|
-
if (!
|
|
30191
|
+
if (!existsSync50(join45(dir, ".git")))
|
|
30188
30192
|
return false;
|
|
30189
|
-
const pkgPath =
|
|
30190
|
-
if (!
|
|
30193
|
+
const pkgPath = join45(dir, "package.json");
|
|
30194
|
+
if (!existsSync50(pkgPath))
|
|
30191
30195
|
return false;
|
|
30192
|
-
const pkg = JSON.parse(
|
|
30196
|
+
const pkg = JSON.parse(readFileSync45(pkgPath, "utf-8"));
|
|
30193
30197
|
return pkg.name === "switchroom";
|
|
30194
30198
|
} catch {
|
|
30195
30199
|
return false;
|
|
@@ -30202,7 +30206,7 @@ function checkAgents(config, configPath) {
|
|
|
30202
30206
|
const authStatuses = getAllAuthStatuses(config);
|
|
30203
30207
|
for (const [name, agentConfig] of Object.entries(config.agents)) {
|
|
30204
30208
|
const agentDir = resolve29(agentsDir, name);
|
|
30205
|
-
if (!
|
|
30209
|
+
if (!existsSync50(agentDir)) {
|
|
30206
30210
|
results.push({
|
|
30207
30211
|
name: `${name}: scaffold`,
|
|
30208
30212
|
status: "fail",
|
|
@@ -30223,7 +30227,7 @@ function checkAgents(config, configPath) {
|
|
|
30223
30227
|
fix: `Rotate the bot token (e.g. via \`switchroom vault\`), then run ` + `\`switchroom agent unquarantine ${name}\` and \`switchroom agent restart ${name}\``
|
|
30224
30228
|
});
|
|
30225
30229
|
}
|
|
30226
|
-
results.push(checkStartShStale(name,
|
|
30230
|
+
results.push(checkStartShStale(name, join45(agentDir, "start.sh")));
|
|
30227
30231
|
results.push(checkLeakedHomeSwitchroom(name, agentDir));
|
|
30228
30232
|
const status = statuses[name];
|
|
30229
30233
|
const active = status?.active ?? "unknown";
|
|
@@ -30300,8 +30304,8 @@ function checkAgents(config, configPath) {
|
|
|
30300
30304
|
}
|
|
30301
30305
|
}
|
|
30302
30306
|
if (agentConfig.channels?.telegram?.plugin === "switchroom") {
|
|
30303
|
-
const mcpJsonPath =
|
|
30304
|
-
if (!
|
|
30307
|
+
const mcpJsonPath = join45(agentDir, ".mcp.json");
|
|
30308
|
+
if (!existsSync50(mcpJsonPath)) {
|
|
30305
30309
|
results.push({
|
|
30306
30310
|
name: `${name}: .mcp.json`,
|
|
30307
30311
|
status: "fail",
|
|
@@ -30310,7 +30314,7 @@ function checkAgents(config, configPath) {
|
|
|
30310
30314
|
});
|
|
30311
30315
|
} else {
|
|
30312
30316
|
try {
|
|
30313
|
-
const mcp = JSON.parse(
|
|
30317
|
+
const mcp = JSON.parse(readFileSync45(mcpJsonPath, "utf-8"));
|
|
30314
30318
|
const hasSwitchroomTelegram = !!mcp.mcpServers?.["switchroom-telegram"];
|
|
30315
30319
|
const memoryEnabled = isHindsightEnabled(config);
|
|
30316
30320
|
const hasHindsight = !!mcp.mcpServers?.hindsight;
|
|
@@ -30386,7 +30390,7 @@ function mffEnvPath(config) {
|
|
|
30386
30390
|
return agent ? resolve29(home2, ".switchroom/credentials", agent, "my-family-finance/.env") : resolve29(home2, ".switchroom/credentials/my-family-finance/.env");
|
|
30387
30391
|
}
|
|
30388
30392
|
function mffEnvState(envPath) {
|
|
30389
|
-
if (!
|
|
30393
|
+
if (!existsSync50(envPath))
|
|
30390
30394
|
return "absent";
|
|
30391
30395
|
try {
|
|
30392
30396
|
accessSync2(envPath, fsConstants5.R_OK);
|
|
@@ -30404,7 +30408,7 @@ function checkMffVaultKeyPresent(passphrase, vaultPath) {
|
|
|
30404
30408
|
fix: "Export SWITCHROOM_VAULT_PASSPHRASE to enable MFF vault probes"
|
|
30405
30409
|
};
|
|
30406
30410
|
}
|
|
30407
|
-
if (!
|
|
30411
|
+
if (!existsSync50(vaultPath)) {
|
|
30408
30412
|
return {
|
|
30409
30413
|
name: "mff: vault key present",
|
|
30410
30414
|
status: "fail",
|
|
@@ -30457,7 +30461,7 @@ function deriveEd25519PublicKeyBytes(keyMaterial) {
|
|
|
30457
30461
|
}
|
|
30458
30462
|
}
|
|
30459
30463
|
function checkMffVaultKeyFormat(passphrase, vaultPath) {
|
|
30460
|
-
if (!passphrase || !
|
|
30464
|
+
if (!passphrase || !existsSync50(vaultPath)) {
|
|
30461
30465
|
return {
|
|
30462
30466
|
name: "mff: vault key format",
|
|
30463
30467
|
status: "warn",
|
|
@@ -30601,8 +30605,8 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
|
|
|
30601
30605
|
};
|
|
30602
30606
|
}
|
|
30603
30607
|
const credDir = dirname12(envPath);
|
|
30604
|
-
const authScript =
|
|
30605
|
-
if (!
|
|
30608
|
+
const authScript = join45(credDir, "claude-auth.py");
|
|
30609
|
+
if (!existsSync50(authScript)) {
|
|
30606
30610
|
return {
|
|
30607
30611
|
name: "mff: auth flow",
|
|
30608
30612
|
status: "warn",
|
|
@@ -30804,11 +30808,11 @@ function runDockerSection(config) {
|
|
|
30804
30808
|
let composeYaml;
|
|
30805
30809
|
let dockerfileAgent;
|
|
30806
30810
|
try {
|
|
30807
|
-
composeYaml =
|
|
30811
|
+
composeYaml = readFileSync45(composePath, "utf8");
|
|
30808
30812
|
} catch {}
|
|
30809
30813
|
const dockerfilePath = resolve29(process.env.HOME ?? "", ".switchroom", "docker", "Dockerfile.agent");
|
|
30810
30814
|
try {
|
|
30811
|
-
dockerfileAgent =
|
|
30815
|
+
dockerfileAgent = readFileSync45(dockerfilePath, "utf8");
|
|
30812
30816
|
} catch {}
|
|
30813
30817
|
return runDockerChecks({
|
|
30814
30818
|
config,
|
|
@@ -46890,13 +46894,13 @@ __export(exports_server2, {
|
|
|
46890
46894
|
dispatchTool: () => dispatchTool2,
|
|
46891
46895
|
TOOLS: () => TOOLS2
|
|
46892
46896
|
});
|
|
46893
|
-
import { randomBytes as
|
|
46894
|
-
import { existsSync as
|
|
46897
|
+
import { randomBytes as randomBytes14 } from "node:crypto";
|
|
46898
|
+
import { existsSync as existsSync74, readFileSync as readFileSync60 } from "node:fs";
|
|
46895
46899
|
function selfSocketPath() {
|
|
46896
46900
|
return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
|
|
46897
46901
|
}
|
|
46898
46902
|
function makeRequestId(prefix) {
|
|
46899
|
-
return `${prefix}-${Date.now()}-${
|
|
46903
|
+
return `${prefix}-${Date.now()}-${randomBytes14(4).toString("hex")}`;
|
|
46900
46904
|
}
|
|
46901
46905
|
async function dispatchTool2(name, args) {
|
|
46902
46906
|
if (name === "get_status") {
|
|
@@ -46906,7 +46910,7 @@ async function dispatchTool2(name, args) {
|
|
|
46906
46910
|
return errorText2("hostd MCP: SWITCHROOM_AGENT_NAME env var is not set \u2014 cannot " + "determine which per-agent socket to talk to.");
|
|
46907
46911
|
}
|
|
46908
46912
|
const sockPath = selfSocketPath();
|
|
46909
|
-
if (!
|
|
46913
|
+
if (!existsSync74(sockPath)) {
|
|
46910
46914
|
return errorText2(`hostd MCP: socket not bound at ${sockPath}. The host-control ` + `daemon is either not installed (run \`switchroom hostd install\`) ` + `or this agent isn't admin-flagged in switchroom.yaml. RFC C ` + `bind-mounts the per-agent socket only when host_control.enabled ` + `is true AND the agent has admin: true.`);
|
|
46911
46915
|
}
|
|
46912
46916
|
let req;
|
|
@@ -47025,18 +47029,18 @@ function resolveAuditLogPath() {
|
|
|
47025
47029
|
if (process.env.HOSTD_AUDIT_LOG_PATH)
|
|
47026
47030
|
return process.env.HOSTD_AUDIT_LOG_PATH;
|
|
47027
47031
|
const bindMounted = "/host-home/.switchroom/host-control-audit.log";
|
|
47028
|
-
if (
|
|
47032
|
+
if (existsSync74(bindMounted))
|
|
47029
47033
|
return bindMounted;
|
|
47030
47034
|
return defaultAuditLogPath2();
|
|
47031
47035
|
}
|
|
47032
47036
|
function getLastUpdateApplyStatus() {
|
|
47033
47037
|
const path8 = resolveAuditLogPath();
|
|
47034
|
-
if (!
|
|
47038
|
+
if (!existsSync74(path8)) {
|
|
47035
47039
|
return errorText2(`get_status: audit log not found at ${path8}. No update_apply has run yet?`);
|
|
47036
47040
|
}
|
|
47037
47041
|
let raw;
|
|
47038
47042
|
try {
|
|
47039
|
-
raw =
|
|
47043
|
+
raw = readFileSync60(path8, "utf-8");
|
|
47040
47044
|
} catch (err2) {
|
|
47041
47045
|
return errorText2(`get_status: failed to read audit log at ${path8}: ${err2.message}`);
|
|
47042
47046
|
}
|
|
@@ -47243,8 +47247,8 @@ var {
|
|
|
47243
47247
|
} = import__.default;
|
|
47244
47248
|
|
|
47245
47249
|
// src/build-info.ts
|
|
47246
|
-
var VERSION = "0.12.
|
|
47247
|
-
var COMMIT_SHA = "
|
|
47250
|
+
var VERSION = "0.12.21";
|
|
47251
|
+
var COMMIT_SHA = "e32c064";
|
|
47248
47252
|
|
|
47249
47253
|
// src/cli/agent.ts
|
|
47250
47254
|
init_source();
|
|
@@ -55349,7 +55353,7 @@ init_loader();
|
|
|
55349
55353
|
init_loader();
|
|
55350
55354
|
init_vault();
|
|
55351
55355
|
import { createInterface as createInterface3 } from "node:readline";
|
|
55352
|
-
import { readFileSync as
|
|
55356
|
+
import { readFileSync as readFileSync34 } from "node:fs";
|
|
55353
55357
|
|
|
55354
55358
|
// src/cli/vault-sweep.ts
|
|
55355
55359
|
init_source();
|
|
@@ -58598,7 +58602,7 @@ class VaultBroker {
|
|
|
58598
58602
|
chownSync(abs, operatorUid, operatorUid);
|
|
58599
58603
|
} catch {}
|
|
58600
58604
|
const unlockServer = net3.createServer((sock) => {
|
|
58601
|
-
this._handleUnlockConnection(sock);
|
|
58605
|
+
this._handleUnlockConnection(sock, true);
|
|
58602
58606
|
});
|
|
58603
58607
|
unlockServer.on("error", (err) => rejectP(err));
|
|
58604
58608
|
unlockServer.listen(unlockAbs, () => {
|
|
@@ -59598,9 +59602,9 @@ class VaultBroker {
|
|
|
59598
59602
|
socket.write(encodeResponse(errorResponse("INTERNAL", msg)));
|
|
59599
59603
|
}
|
|
59600
59604
|
}
|
|
59601
|
-
_handleUnlockConnection(socket) {
|
|
59605
|
+
_handleUnlockConnection(socket, isOperator = false) {
|
|
59602
59606
|
let unlockPeer = null;
|
|
59603
|
-
if (process.platform === "linux") {
|
|
59607
|
+
if (!isOperator && process.platform === "linux") {
|
|
59604
59608
|
unlockPeer = this.testOpts._testIdentify ? this.testOpts._testIdentify(this.unlockSocketPath, socket) : identify(this.unlockSocketPath, socket);
|
|
59605
59609
|
if (unlockPeer === null) {
|
|
59606
59610
|
this.auditLogger.write({
|
|
@@ -59615,9 +59619,9 @@ class VaultBroker {
|
|
|
59615
59619
|
return;
|
|
59616
59620
|
}
|
|
59617
59621
|
}
|
|
59618
|
-
const auditPid = unlockPeer?.pid ?? process.pid;
|
|
59619
|
-
const auditCaller = unlockPeer !== null ? callerFromPeer(unlockPeer) : `pid:${process.pid}`;
|
|
59620
|
-
const auditCgroup = unlockPeer?.systemdUnit ?? undefined;
|
|
59622
|
+
const auditPid = isOperator ? process.pid : unlockPeer?.pid ?? process.pid;
|
|
59623
|
+
const auditCaller = isOperator ? "operator" : unlockPeer !== null ? callerFromPeer(unlockPeer) : `pid:${process.pid}`;
|
|
59624
|
+
const auditCgroup = isOperator ? undefined : unlockPeer?.systemdUnit ?? undefined;
|
|
59621
59625
|
let buffer = "";
|
|
59622
59626
|
socket.on("data", (chunk) => {
|
|
59623
59627
|
buffer += chunk.toString("utf8");
|
|
@@ -60758,6 +60762,289 @@ The token file was written to the agent directory (mode 0600).`));
|
|
|
60758
60762
|
});
|
|
60759
60763
|
}
|
|
60760
60764
|
|
|
60765
|
+
// src/cli/vault-backup.ts
|
|
60766
|
+
init_source();
|
|
60767
|
+
init_loader();
|
|
60768
|
+
import { existsSync as existsSync38 } from "node:fs";
|
|
60769
|
+
import { join as join32 } from "node:path";
|
|
60770
|
+
|
|
60771
|
+
// src/vault/backup.ts
|
|
60772
|
+
import {
|
|
60773
|
+
createHash as createHash7,
|
|
60774
|
+
randomBytes as randomBytes9
|
|
60775
|
+
} from "node:crypto";
|
|
60776
|
+
import {
|
|
60777
|
+
appendFileSync as appendFileSync2,
|
|
60778
|
+
closeSync as closeSync9,
|
|
60779
|
+
existsSync as existsSync37,
|
|
60780
|
+
fsyncSync as fsyncSync5,
|
|
60781
|
+
mkdirSync as mkdirSync21,
|
|
60782
|
+
openSync as openSync9,
|
|
60783
|
+
readdirSync as readdirSync15,
|
|
60784
|
+
readFileSync as readFileSync33,
|
|
60785
|
+
renameSync as renameSync10,
|
|
60786
|
+
statSync as statSync20,
|
|
60787
|
+
symlinkSync as symlinkSync4,
|
|
60788
|
+
unlinkSync as unlinkSync10,
|
|
60789
|
+
writeSync as writeSync5
|
|
60790
|
+
} from "node:fs";
|
|
60791
|
+
import { homedir as homedir16 } from "node:os";
|
|
60792
|
+
import { join as join31, resolve as resolvePath2 } from "node:path";
|
|
60793
|
+
var LATEST_SYMLINK = "vault.enc.latest.bak";
|
|
60794
|
+
var MANIFEST_FILE = "manifest.jsonl";
|
|
60795
|
+
var DEFAULT_RETAIN = 30;
|
|
60796
|
+
var FILENAME_PREFIX = "vault.enc.";
|
|
60797
|
+
var FILENAME_SUFFIX = ".bak";
|
|
60798
|
+
var AUTO_UNLOCK_FILENAME_PATTERNS = [
|
|
60799
|
+
/auto-unlock/i,
|
|
60800
|
+
/^\.?vault-auto/i
|
|
60801
|
+
];
|
|
60802
|
+
var KNOWN_BACKUP_DIR_ARTIFACTS = new Set([
|
|
60803
|
+
LATEST_SYMLINK,
|
|
60804
|
+
MANIFEST_FILE,
|
|
60805
|
+
".git",
|
|
60806
|
+
".gitignore",
|
|
60807
|
+
"README.md"
|
|
60808
|
+
]);
|
|
60809
|
+
function computeBackupFilename(now) {
|
|
60810
|
+
const Y = now.getUTCFullYear().toString().padStart(4, "0");
|
|
60811
|
+
const M = (now.getUTCMonth() + 1).toString().padStart(2, "0");
|
|
60812
|
+
const D = now.getUTCDate().toString().padStart(2, "0");
|
|
60813
|
+
const h = now.getUTCHours().toString().padStart(2, "0");
|
|
60814
|
+
const m = now.getUTCMinutes().toString().padStart(2, "0");
|
|
60815
|
+
const s = now.getUTCSeconds().toString().padStart(2, "0");
|
|
60816
|
+
return `${FILENAME_PREFIX}${Y}${M}${D}-${h}${m}${s}${FILENAME_SUFFIX}`;
|
|
60817
|
+
}
|
|
60818
|
+
function parseBackupFilename(name) {
|
|
60819
|
+
if (!name.startsWith(FILENAME_PREFIX))
|
|
60820
|
+
return null;
|
|
60821
|
+
if (!name.endsWith(FILENAME_SUFFIX))
|
|
60822
|
+
return null;
|
|
60823
|
+
const inner = name.slice(FILENAME_PREFIX.length, -FILENAME_SUFFIX.length);
|
|
60824
|
+
if (!/^\d{8}-\d{6}$/.test(inner))
|
|
60825
|
+
return null;
|
|
60826
|
+
return inner;
|
|
60827
|
+
}
|
|
60828
|
+
function validateVaultEnvelopeFile(path4) {
|
|
60829
|
+
let buf;
|
|
60830
|
+
try {
|
|
60831
|
+
buf = readFileSync33(path4);
|
|
60832
|
+
} catch (err) {
|
|
60833
|
+
return `cannot read ${path4}: ${err.message}`;
|
|
60834
|
+
}
|
|
60835
|
+
if (buf.length === 0) {
|
|
60836
|
+
return `${path4} is empty`;
|
|
60837
|
+
}
|
|
60838
|
+
let parsed;
|
|
60839
|
+
try {
|
|
60840
|
+
parsed = JSON.parse(buf.toString("utf8"));
|
|
60841
|
+
} catch {
|
|
60842
|
+
return `${path4} is not valid JSON (vault envelope is JSON)`;
|
|
60843
|
+
}
|
|
60844
|
+
if (!parsed || typeof parsed !== "object") {
|
|
60845
|
+
return `${path4} is not a JSON object`;
|
|
60846
|
+
}
|
|
60847
|
+
const obj = parsed;
|
|
60848
|
+
for (const required of ["salt", "iv", "data", "tag"]) {
|
|
60849
|
+
if (typeof obj[required] !== "string" || obj[required].length === 0) {
|
|
60850
|
+
return `${path4} is missing or empty required field '${required}'`;
|
|
60851
|
+
}
|
|
60852
|
+
}
|
|
60853
|
+
if (obj.kdf !== undefined) {
|
|
60854
|
+
const k = obj.kdf;
|
|
60855
|
+
if (typeof k.N !== "number" || typeof k.r !== "number" || typeof k.p !== "number") {
|
|
60856
|
+
return `${path4} has malformed kdf field`;
|
|
60857
|
+
}
|
|
60858
|
+
}
|
|
60859
|
+
return null;
|
|
60860
|
+
}
|
|
60861
|
+
function resolveBackupDestination(input) {
|
|
60862
|
+
if (input.cliToFlag)
|
|
60863
|
+
return resolvePath2(input.cliToFlag);
|
|
60864
|
+
if (input.configDestination)
|
|
60865
|
+
return resolvePath2(input.configDestination);
|
|
60866
|
+
if (input.hasSwitchroomConfigRepo) {
|
|
60867
|
+
return join31(input.home, ".switchroom-config", "vault-backups");
|
|
60868
|
+
}
|
|
60869
|
+
return join31(input.home, ".switchroom", "vault-backups");
|
|
60870
|
+
}
|
|
60871
|
+
function findAutoUnlockSibling(entries) {
|
|
60872
|
+
for (const name of entries) {
|
|
60873
|
+
if (KNOWN_BACKUP_DIR_ARTIFACTS.has(name))
|
|
60874
|
+
continue;
|
|
60875
|
+
if (AUTO_UNLOCK_FILENAME_PATTERNS.some((re) => re.test(name))) {
|
|
60876
|
+
return name;
|
|
60877
|
+
}
|
|
60878
|
+
}
|
|
60879
|
+
return null;
|
|
60880
|
+
}
|
|
60881
|
+
function selectBackupsToPrune(sortedNewestFirst, retain) {
|
|
60882
|
+
if (retain < 0)
|
|
60883
|
+
throw new Error("retain must be >= 0");
|
|
60884
|
+
return sortedNewestFirst.slice(retain);
|
|
60885
|
+
}
|
|
60886
|
+
function listBackupFiles(dir) {
|
|
60887
|
+
if (!existsSync37(dir))
|
|
60888
|
+
return [];
|
|
60889
|
+
let entries;
|
|
60890
|
+
try {
|
|
60891
|
+
entries = readdirSync15(dir);
|
|
60892
|
+
} catch {
|
|
60893
|
+
return [];
|
|
60894
|
+
}
|
|
60895
|
+
return entries.filter((n) => parseBackupFilename(n) !== null).sort((a, b) => {
|
|
60896
|
+
const ka = parseBackupFilename(a);
|
|
60897
|
+
const kb = parseBackupFilename(b);
|
|
60898
|
+
return kb.localeCompare(ka);
|
|
60899
|
+
});
|
|
60900
|
+
}
|
|
60901
|
+
function sha256OfFile(path4) {
|
|
60902
|
+
const h = createHash7("sha256");
|
|
60903
|
+
h.update(readFileSync33(path4));
|
|
60904
|
+
return h.digest("hex");
|
|
60905
|
+
}
|
|
60906
|
+
function backupVault(opts) {
|
|
60907
|
+
const now = opts.now ?? new Date;
|
|
60908
|
+
const retain = opts.retain ?? DEFAULT_RETAIN;
|
|
60909
|
+
const validationError = validateVaultEnvelopeFile(opts.vaultPath);
|
|
60910
|
+
if (validationError) {
|
|
60911
|
+
throw new Error(`vault backup refused: source is not a valid vault envelope: ${validationError}`);
|
|
60912
|
+
}
|
|
60913
|
+
mkdirSync21(opts.destDir, { recursive: true, mode: 448 });
|
|
60914
|
+
const dirEntries = readdirSync15(opts.destDir);
|
|
60915
|
+
const offender = findAutoUnlockSibling(dirEntries);
|
|
60916
|
+
if (offender) {
|
|
60917
|
+
throw new Error(`vault backup refused: destination '${opts.destDir}' contains a file ` + `that looks like an auto-unlock credential ('${offender}'). The ` + `machine-bound auto-unlock blob MUST NOT be co-located with the ` + `encrypted vault \u2014 if they're together in version control, the ` + `passphrase gate is bypassed. Move/remove that file and retry.`);
|
|
60918
|
+
}
|
|
60919
|
+
const filename = computeBackupFilename(now);
|
|
60920
|
+
const fullPath = join31(opts.destDir, filename);
|
|
60921
|
+
const tmpName = `.tmp.${process.pid}.${randomBytes9(4).toString("hex")}`;
|
|
60922
|
+
const tmpPath = join31(opts.destDir, tmpName);
|
|
60923
|
+
const src = readFileSync33(opts.vaultPath);
|
|
60924
|
+
const fd = openSync9(tmpPath, "wx", 384);
|
|
60925
|
+
try {
|
|
60926
|
+
writeSync5(fd, src);
|
|
60927
|
+
fsyncSync5(fd);
|
|
60928
|
+
} finally {
|
|
60929
|
+
closeSync9(fd);
|
|
60930
|
+
}
|
|
60931
|
+
if (existsSync37(fullPath)) {
|
|
60932
|
+
try {
|
|
60933
|
+
unlinkSync10(tmpPath);
|
|
60934
|
+
} catch {}
|
|
60935
|
+
throw new Error(`vault backup refused: '${fullPath}' already exists ` + `(sub-second collision with another backup). Retry in 1 second, ` + `or check for a concurrent backup process.`);
|
|
60936
|
+
}
|
|
60937
|
+
renameSync10(tmpPath, fullPath);
|
|
60938
|
+
const stat = statSync20(fullPath);
|
|
60939
|
+
const sha256 = sha256OfFile(fullPath);
|
|
60940
|
+
const row = {
|
|
60941
|
+
ts: now.toISOString(),
|
|
60942
|
+
file: filename,
|
|
60943
|
+
size_bytes: stat.size,
|
|
60944
|
+
sha256
|
|
60945
|
+
};
|
|
60946
|
+
appendFileSync2(join31(opts.destDir, MANIFEST_FILE), JSON.stringify(row) + `
|
|
60947
|
+
`, { mode: 384 });
|
|
60948
|
+
const latestPath = join31(opts.destDir, LATEST_SYMLINK);
|
|
60949
|
+
try {
|
|
60950
|
+
unlinkSync10(latestPath);
|
|
60951
|
+
} catch {}
|
|
60952
|
+
try {
|
|
60953
|
+
symlinkSync4(filename, latestPath);
|
|
60954
|
+
} catch {}
|
|
60955
|
+
try {
|
|
60956
|
+
const dirFd = openSync9(opts.destDir, "r");
|
|
60957
|
+
try {
|
|
60958
|
+
fsyncSync5(dirFd);
|
|
60959
|
+
} finally {
|
|
60960
|
+
closeSync9(dirFd);
|
|
60961
|
+
}
|
|
60962
|
+
} catch {}
|
|
60963
|
+
const sorted = listBackupFiles(opts.destDir);
|
|
60964
|
+
const pruneNames = selectBackupsToPrune(sorted, retain);
|
|
60965
|
+
for (const old of pruneNames) {
|
|
60966
|
+
try {
|
|
60967
|
+
unlinkSync10(join31(opts.destDir, old));
|
|
60968
|
+
} catch {}
|
|
60969
|
+
}
|
|
60970
|
+
if (pruneNames.length > 0) {
|
|
60971
|
+
try {
|
|
60972
|
+
const dirFd = openSync9(opts.destDir, "r");
|
|
60973
|
+
try {
|
|
60974
|
+
fsyncSync5(dirFd);
|
|
60975
|
+
} finally {
|
|
60976
|
+
closeSync9(dirFd);
|
|
60977
|
+
}
|
|
60978
|
+
} catch {}
|
|
60979
|
+
}
|
|
60980
|
+
return {
|
|
60981
|
+
destDir: opts.destDir,
|
|
60982
|
+
filename,
|
|
60983
|
+
fullPath,
|
|
60984
|
+
bytes: stat.size,
|
|
60985
|
+
sha256,
|
|
60986
|
+
pruned: pruneNames
|
|
60987
|
+
};
|
|
60988
|
+
}
|
|
60989
|
+
function defaultHome() {
|
|
60990
|
+
return process.env.HOME ?? homedir16();
|
|
60991
|
+
}
|
|
60992
|
+
|
|
60993
|
+
// src/cli/vault-backup.ts
|
|
60994
|
+
function getLiveVaultPath(configPath) {
|
|
60995
|
+
try {
|
|
60996
|
+
const cfg = loadConfig(configPath);
|
|
60997
|
+
if (cfg.vault?.path)
|
|
60998
|
+
return resolvePath(cfg.vault.path);
|
|
60999
|
+
} catch {}
|
|
61000
|
+
return resolvePath("~/.switchroom/vault/vault.enc");
|
|
61001
|
+
}
|
|
61002
|
+
function registerVaultBackupCommand(vault, program3) {
|
|
61003
|
+
vault.command("backup").description("Write a dated, encrypted backup of the vault to a configured directory. " + "Default destination: ~/.switchroom-config/vault-backups/ if that operator " + "config repo exists, else ~/.switchroom/vault-backups/. Rotated to the last " + "30 backups by default.").option("--to <path>", "Destination directory for the backup. Overrides config and default. " + "MUST NOT be ~/.switchroom/vault/ (the broker's bind-mounted dir, " + "validated by `switchroom apply` against an allowlist). Created with mode 0700.").option("--retain <n>", `Keep only the N most recent backups in the destination dir (default ${DEFAULT_RETAIN}). ` + `Older ones are pruned after the new backup is written.`).action(async (opts) => {
|
|
61004
|
+
try {
|
|
61005
|
+
const parentOpts = program3.opts();
|
|
61006
|
+
const vaultPath = getLiveVaultPath(parentOpts.config);
|
|
61007
|
+
let configDestination;
|
|
61008
|
+
try {
|
|
61009
|
+
const cfg = loadConfig(parentOpts.config);
|
|
61010
|
+
const backupCfg = cfg.vault?.backup;
|
|
61011
|
+
if (backupCfg?.destination) {
|
|
61012
|
+
configDestination = resolvePath(backupCfg.destination);
|
|
61013
|
+
}
|
|
61014
|
+
} catch {}
|
|
61015
|
+
const home2 = defaultHome();
|
|
61016
|
+
const hasSwitchroomConfigRepo = existsSync38(join32(home2, ".switchroom-config"));
|
|
61017
|
+
const destDir = resolveBackupDestination({
|
|
61018
|
+
cliToFlag: opts.to ? resolvePath(opts.to) : undefined,
|
|
61019
|
+
configDestination,
|
|
61020
|
+
home: home2,
|
|
61021
|
+
hasSwitchroomConfigRepo
|
|
61022
|
+
});
|
|
61023
|
+
let retain = DEFAULT_RETAIN;
|
|
61024
|
+
if (opts.retain !== undefined) {
|
|
61025
|
+
const n = Number(opts.retain);
|
|
61026
|
+
if (!Number.isInteger(n) || n < 0) {
|
|
61027
|
+
console.error(source_default.red(`Error: --retain must be a non-negative integer (got ${JSON.stringify(opts.retain)})`));
|
|
61028
|
+
process.exit(1);
|
|
61029
|
+
}
|
|
61030
|
+
retain = n;
|
|
61031
|
+
}
|
|
61032
|
+
const result = backupVault({ vaultPath, destDir, retain });
|
|
61033
|
+
console.log(`${source_default.green("\u2713")} vault backup written`);
|
|
61034
|
+
console.log(` ${source_default.dim("path:")} ${result.fullPath}`);
|
|
61035
|
+
console.log(` ${source_default.dim("size:")} ${result.bytes} bytes`);
|
|
61036
|
+
console.log(` ${source_default.dim("sha256:")} ${result.sha256}`);
|
|
61037
|
+
if (result.pruned.length > 0) {
|
|
61038
|
+
console.log(` ${source_default.dim("pruned:")} ${result.pruned.length} older backup(s)`);
|
|
61039
|
+
}
|
|
61040
|
+
} catch (err) {
|
|
61041
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
61042
|
+
console.error(source_default.red(`Error: ${msg}`));
|
|
61043
|
+
process.exit(1);
|
|
61044
|
+
}
|
|
61045
|
+
});
|
|
61046
|
+
}
|
|
61047
|
+
|
|
60761
61048
|
// src/cli/vault.ts
|
|
60762
61049
|
function isSandboxContext() {
|
|
60763
61050
|
return process.env.SWITCHROOM_RUNTIME === "docker";
|
|
@@ -60992,7 +61279,7 @@ function registerVaultCommand(program3) {
|
|
|
60992
61279
|
let value;
|
|
60993
61280
|
if (opts.file) {
|
|
60994
61281
|
try {
|
|
60995
|
-
value =
|
|
61282
|
+
value = readFileSync34(resolvePath(opts.file), "utf8");
|
|
60996
61283
|
} catch (err) {
|
|
60997
61284
|
const msg = err instanceof Error ? err.message : String(err);
|
|
60998
61285
|
console.error(source_default.red(`Error reading file: ${msg}`));
|
|
@@ -61298,11 +61585,12 @@ Push passphrase to broker for future requests? [Y/n]: `);
|
|
|
61298
61585
|
registerVaultDoctorCommand(vault, program3);
|
|
61299
61586
|
registerVaultAuditCommand(vault, program3);
|
|
61300
61587
|
registerVaultGrantCommands(vault, program3);
|
|
61588
|
+
registerVaultBackupCommand(vault, program3);
|
|
61301
61589
|
}
|
|
61302
61590
|
|
|
61303
61591
|
// src/cli/telegram.ts
|
|
61304
61592
|
init_source();
|
|
61305
|
-
import { existsSync as
|
|
61593
|
+
import { existsSync as existsSync39, readFileSync as readFileSync35, writeFileSync as writeFileSync20 } from "node:fs";
|
|
61306
61594
|
|
|
61307
61595
|
// src/web/webhook-dispatch.ts
|
|
61308
61596
|
function renderTemplate2(template, ctx) {
|
|
@@ -61601,7 +61889,7 @@ function registerDisableVerb(tg, program3) {
|
|
|
61601
61889
|
}
|
|
61602
61890
|
async function applyYamlEdit(program3, agent, feature, value, dryRun) {
|
|
61603
61891
|
const path4 = getConfigPath(program3);
|
|
61604
|
-
const before =
|
|
61892
|
+
const before = readFileSync35(path4, "utf-8");
|
|
61605
61893
|
let after;
|
|
61606
61894
|
try {
|
|
61607
61895
|
after = setTelegramFeature(before, agent, feature, value);
|
|
@@ -61616,7 +61904,7 @@ async function applyYamlEdit(program3, agent, feature, value, dryRun) {
|
|
|
61616
61904
|
}
|
|
61617
61905
|
async function applyYamlRemove(program3, agent, feature, dryRun) {
|
|
61618
61906
|
const path4 = getConfigPath(program3);
|
|
61619
|
-
const before =
|
|
61907
|
+
const before = readFileSync35(path4, "utf-8");
|
|
61620
61908
|
const after = removeTelegramFeature(before, agent, feature);
|
|
61621
61909
|
if (before === after) {
|
|
61622
61910
|
console.log(source_default.yellow(`No change \u2014 ${feature.replace("_", "-")} is not set for agent '${agent}'.`));
|
|
@@ -61665,7 +61953,7 @@ function fail(msg) {
|
|
|
61665
61953
|
}
|
|
61666
61954
|
async function applyYamlAddWebhook(program3, agent, source, dryRun) {
|
|
61667
61955
|
const path4 = getConfigPath(program3);
|
|
61668
|
-
const before =
|
|
61956
|
+
const before = readFileSync35(path4, "utf-8");
|
|
61669
61957
|
let after;
|
|
61670
61958
|
try {
|
|
61671
61959
|
after = addWebhookSource(before, agent, source);
|
|
@@ -61685,7 +61973,7 @@ async function applyYamlAddWebhook(program3, agent, source, dryRun) {
|
|
|
61685
61973
|
}
|
|
61686
61974
|
async function applyYamlRemoveWebhook(program3, agent, source, dryRun) {
|
|
61687
61975
|
const path4 = getConfigPath(program3);
|
|
61688
|
-
const before =
|
|
61976
|
+
const before = readFileSync35(path4, "utf-8");
|
|
61689
61977
|
const after = removeWebhookSource(before, agent, source);
|
|
61690
61978
|
if (before === after) {
|
|
61691
61979
|
console.log(source_default.yellow(`No change \u2014 webhook source '${source}' is not currently enabled for agent '${agent}'.`));
|
|
@@ -61702,7 +61990,7 @@ async function vaultPut(program3, key, value) {
|
|
|
61702
61990
|
const configPath = program3.optsWithGlobals().config ?? undefined;
|
|
61703
61991
|
const vaultPath = resolveVaultPath(configPath);
|
|
61704
61992
|
const passphrase = await getVaultPassphrase();
|
|
61705
|
-
if (!
|
|
61993
|
+
if (!existsSync39(vaultPath)) {
|
|
61706
61994
|
createVault(passphrase, vaultPath);
|
|
61707
61995
|
console.log(source_default.gray(` Created new vault at ${vaultPath}`));
|
|
61708
61996
|
}
|
|
@@ -61742,7 +62030,7 @@ function registerDispatchVerb(tg, _program) {
|
|
|
61742
62030
|
}
|
|
61743
62031
|
let payload;
|
|
61744
62032
|
try {
|
|
61745
|
-
payload = JSON.parse(
|
|
62033
|
+
payload = JSON.parse(readFileSync35(opts.payload, "utf-8"));
|
|
61746
62034
|
} catch (err) {
|
|
61747
62035
|
fail(`Could not read payload file '${opts.payload}': ${err.message}`);
|
|
61748
62036
|
}
|
|
@@ -62071,15 +62359,15 @@ async function ensureHindsightConsumer(configPath, account, uid = HINDSIGHT_DEFA
|
|
|
62071
62359
|
// src/cli/memory.ts
|
|
62072
62360
|
init_loader();
|
|
62073
62361
|
var import_yaml10 = __toESM(require_dist(), 1);
|
|
62074
|
-
import { existsSync as
|
|
62075
|
-
import { join as
|
|
62362
|
+
import { existsSync as existsSync40, readFileSync as readFileSync36, writeFileSync as writeFileSync21 } from "node:fs";
|
|
62363
|
+
import { join as join33 } from "node:path";
|
|
62076
62364
|
function readRecallLog(agentDir, limit) {
|
|
62077
|
-
const path4 =
|
|
62078
|
-
if (!
|
|
62365
|
+
const path4 = join33(agentDir, ".claude", "plugins", "data", "hindsight-memory-inline", "state", "recall_log.jsonl");
|
|
62366
|
+
if (!existsSync40(path4))
|
|
62079
62367
|
return [];
|
|
62080
62368
|
let raw;
|
|
62081
62369
|
try {
|
|
62082
|
-
raw =
|
|
62370
|
+
raw = readFileSync36(path4, "utf-8");
|
|
62083
62371
|
} catch {
|
|
62084
62372
|
return [];
|
|
62085
62373
|
}
|
|
@@ -62273,8 +62561,8 @@ Cross-agent reflection plan
|
|
|
62273
62561
|
const url = `http://127.0.0.1:${ports.apiPort}/mcp/`;
|
|
62274
62562
|
const configPath = getConfigPath(program3);
|
|
62275
62563
|
try {
|
|
62276
|
-
if (
|
|
62277
|
-
const raw =
|
|
62564
|
+
if (existsSync40(configPath)) {
|
|
62565
|
+
const raw = readFileSync36(configPath, "utf-8");
|
|
62278
62566
|
const doc = import_yaml10.default.parseDocument(raw);
|
|
62279
62567
|
if (!doc.has("memory")) {
|
|
62280
62568
|
doc.set("memory", { backend: "hindsight", shared_collection: "shared", config: { provider, url } });
|
|
@@ -62315,7 +62603,7 @@ Cross-agent reflection plan
|
|
|
62315
62603
|
process.exit(1);
|
|
62316
62604
|
})() : Object.keys(config.agents);
|
|
62317
62605
|
for (const name of targets) {
|
|
62318
|
-
const agentDir =
|
|
62606
|
+
const agentDir = join33(agentsDir, name);
|
|
62319
62607
|
const entries = readRecallLog(agentDir, limit);
|
|
62320
62608
|
if (opts.json) {
|
|
62321
62609
|
for (const e of entries) {
|
|
@@ -62381,32 +62669,32 @@ init_source();
|
|
|
62381
62669
|
init_merge();
|
|
62382
62670
|
init_lifecycle();
|
|
62383
62671
|
import {
|
|
62384
|
-
readFileSync as
|
|
62385
|
-
existsSync as
|
|
62672
|
+
readFileSync as readFileSync40,
|
|
62673
|
+
existsSync as existsSync44,
|
|
62386
62674
|
realpathSync as realpathSync3,
|
|
62387
|
-
mkdirSync as
|
|
62388
|
-
openSync as
|
|
62389
|
-
closeSync as
|
|
62390
|
-
writeSync as
|
|
62675
|
+
mkdirSync as mkdirSync25,
|
|
62676
|
+
openSync as openSync10,
|
|
62677
|
+
closeSync as closeSync10,
|
|
62678
|
+
writeSync as writeSync6,
|
|
62391
62679
|
constants as fsConstants3
|
|
62392
62680
|
} from "node:fs";
|
|
62393
|
-
import { resolve as resolve26, extname, join as
|
|
62394
|
-
import { homedir as
|
|
62681
|
+
import { resolve as resolve26, extname, join as join37, relative, dirname as dirname9 } from "node:path";
|
|
62682
|
+
import { homedir as homedir19 } from "node:os";
|
|
62395
62683
|
import { spawn as spawn5 } from "node:child_process";
|
|
62396
|
-
import { timingSafeEqual as timingSafeEqual2, randomBytes as
|
|
62684
|
+
import { timingSafeEqual as timingSafeEqual2, randomBytes as randomBytes10 } from "node:crypto";
|
|
62397
62685
|
|
|
62398
62686
|
// src/web/api.ts
|
|
62399
62687
|
init_lifecycle();
|
|
62400
62688
|
init_manager();
|
|
62401
62689
|
init_hindsight();
|
|
62402
62690
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
62403
|
-
import { existsSync as
|
|
62691
|
+
import { existsSync as existsSync42, readFileSync as readFileSync38 } from "node:fs";
|
|
62404
62692
|
import { resolve as resolve25 } from "node:path";
|
|
62405
62693
|
init_audit_reader();
|
|
62406
62694
|
|
|
62407
62695
|
// src/scheduler/dispatch.ts
|
|
62408
62696
|
init_merge();
|
|
62409
|
-
import { createHash as
|
|
62697
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
62410
62698
|
function collectScheduleEntries(config) {
|
|
62411
62699
|
const out = [];
|
|
62412
62700
|
const agentNames = Object.keys(config.agents).sort();
|
|
@@ -62423,7 +62711,7 @@ function collectScheduleEntries(config) {
|
|
|
62423
62711
|
scheduleIndex: i,
|
|
62424
62712
|
cron: entry.cron,
|
|
62425
62713
|
prompt: entry.prompt,
|
|
62426
|
-
promptKey:
|
|
62714
|
+
promptKey: createHash8("sha256").update(entry.prompt).digest("hex").slice(0, 12)
|
|
62427
62715
|
});
|
|
62428
62716
|
}
|
|
62429
62717
|
}
|
|
@@ -66901,9 +67189,9 @@ class PostHog extends PostHogBackendClient {
|
|
|
66901
67189
|
// src/analytics/posthog.ts
|
|
66902
67190
|
init_paths();
|
|
66903
67191
|
import {
|
|
66904
|
-
existsSync as
|
|
66905
|
-
mkdirSync as
|
|
66906
|
-
readFileSync as
|
|
67192
|
+
existsSync as existsSync41,
|
|
67193
|
+
mkdirSync as mkdirSync22,
|
|
67194
|
+
readFileSync as readFileSync37,
|
|
66907
67195
|
writeFileSync as writeFileSync22
|
|
66908
67196
|
} from "node:fs";
|
|
66909
67197
|
import { dirname as dirname8 } from "node:path";
|
|
@@ -66923,8 +67211,8 @@ function getDistinctId() {
|
|
|
66923
67211
|
return cachedDistinctId;
|
|
66924
67212
|
const path4 = resolveStatePath("analytics-id");
|
|
66925
67213
|
try {
|
|
66926
|
-
if (
|
|
66927
|
-
const existing =
|
|
67214
|
+
if (existsSync41(path4)) {
|
|
67215
|
+
const existing = readFileSync37(path4, "utf-8").trim();
|
|
66928
67216
|
if (existing) {
|
|
66929
67217
|
cachedDistinctId = existing;
|
|
66930
67218
|
return existing;
|
|
@@ -66934,7 +67222,7 @@ function getDistinctId() {
|
|
|
66934
67222
|
const id = randomUUID3();
|
|
66935
67223
|
cachedDistinctId = id;
|
|
66936
67224
|
try {
|
|
66937
|
-
|
|
67225
|
+
mkdirSync22(dirname8(path4), { recursive: true });
|
|
66938
67226
|
writeFileSync22(path4, id, "utf-8");
|
|
66939
67227
|
} catch {}
|
|
66940
67228
|
return id;
|
|
@@ -67012,8 +67300,8 @@ init_account_store();
|
|
|
67012
67300
|
init_client2();
|
|
67013
67301
|
|
|
67014
67302
|
// telegram-plugin/registry/turns-schema.ts
|
|
67015
|
-
import { chmodSync as chmodSync8, mkdirSync as
|
|
67016
|
-
import { join as
|
|
67303
|
+
import { chmodSync as chmodSync8, mkdirSync as mkdirSync23 } from "fs";
|
|
67304
|
+
import { join as join35 } from "path";
|
|
67017
67305
|
var DatabaseClass = null;
|
|
67018
67306
|
function loadDatabaseClass() {
|
|
67019
67307
|
if (DatabaseClass != null)
|
|
@@ -67072,9 +67360,9 @@ function applySchema(db) {
|
|
|
67072
67360
|
}
|
|
67073
67361
|
function openTurnsDb(agentDir) {
|
|
67074
67362
|
const Database2 = loadDatabaseClass();
|
|
67075
|
-
const dir =
|
|
67076
|
-
|
|
67077
|
-
const path4 =
|
|
67363
|
+
const dir = join35(agentDir, "telegram");
|
|
67364
|
+
mkdirSync23(dir, { recursive: true, mode: 448 });
|
|
67365
|
+
const path4 = join35(dir, "registry.db");
|
|
67078
67366
|
const db = new Database2(path4, { create: true });
|
|
67079
67367
|
applySchema(db);
|
|
67080
67368
|
try {
|
|
@@ -67487,9 +67775,9 @@ async function handleGetSystemHealth(home2) {
|
|
|
67487
67775
|
};
|
|
67488
67776
|
try {
|
|
67489
67777
|
const logPath = defaultAuditLogPath2(home2);
|
|
67490
|
-
if (
|
|
67778
|
+
if (existsSync42(logPath)) {
|
|
67491
67779
|
hostd.auditLogPresent = true;
|
|
67492
|
-
const raw =
|
|
67780
|
+
const raw = readFileSync38(logPath, "utf-8");
|
|
67493
67781
|
hostd.recent = readAndFilter(raw, {}, 10);
|
|
67494
67782
|
}
|
|
67495
67783
|
} catch (err) {
|
|
@@ -67568,9 +67856,9 @@ async function handleGetApprovals() {
|
|
|
67568
67856
|
}
|
|
67569
67857
|
|
|
67570
67858
|
// src/web/webhook-handler.ts
|
|
67571
|
-
import { appendFileSync as
|
|
67572
|
-
import { join as
|
|
67573
|
-
import { homedir as
|
|
67859
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync43, mkdirSync as mkdirSync24, readFileSync as readFileSync39, writeFileSync as writeFileSync23 } from "fs";
|
|
67860
|
+
import { join as join36 } from "path";
|
|
67861
|
+
import { homedir as homedir18 } from "os";
|
|
67574
67862
|
|
|
67575
67863
|
// src/web/webhook-verify.ts
|
|
67576
67864
|
import { createHmac as createHmac2, timingSafeEqual } from "crypto";
|
|
@@ -67689,9 +67977,9 @@ var DEDUP_MAX = 1000;
|
|
|
67689
67977
|
var DEDUP_TTL_MS = 24 * 60 * 60 * 1000;
|
|
67690
67978
|
function loadDedupFile(path4) {
|
|
67691
67979
|
try {
|
|
67692
|
-
if (!
|
|
67980
|
+
if (!existsSync43(path4))
|
|
67693
67981
|
return {};
|
|
67694
|
-
const raw = JSON.parse(
|
|
67982
|
+
const raw = JSON.parse(readFileSync39(path4, "utf-8"));
|
|
67695
67983
|
return typeof raw.deliveries === "object" && raw.deliveries !== null ? raw.deliveries : {};
|
|
67696
67984
|
} catch {
|
|
67697
67985
|
return {};
|
|
@@ -67713,8 +68001,8 @@ var agentDedupCache = new Map;
|
|
|
67713
68001
|
function createFileDedupStore(resolveAgentDir) {
|
|
67714
68002
|
return {
|
|
67715
68003
|
check(agent, deliveryId, now) {
|
|
67716
|
-
const telegramDir =
|
|
67717
|
-
const filePath =
|
|
68004
|
+
const telegramDir = join36(resolveAgentDir(agent), "telegram");
|
|
68005
|
+
const filePath = join36(telegramDir, "webhook-dedup.json");
|
|
67718
68006
|
if (!agentDedupCache.has(agent)) {
|
|
67719
68007
|
agentDedupCache.set(agent, loadDedupFile(filePath));
|
|
67720
68008
|
}
|
|
@@ -67724,7 +68012,7 @@ function createFileDedupStore(resolveAgentDir) {
|
|
|
67724
68012
|
}
|
|
67725
68013
|
deliveries[deliveryId] = now;
|
|
67726
68014
|
try {
|
|
67727
|
-
|
|
68015
|
+
mkdirSync24(telegramDir, { recursive: true });
|
|
67728
68016
|
saveDedupFile(filePath, deliveries, now);
|
|
67729
68017
|
} catch {}
|
|
67730
68018
|
return;
|
|
@@ -67766,9 +68054,9 @@ function shouldWriteThrottleIssue(agent, source, now, windowMap) {
|
|
|
67766
68054
|
return true;
|
|
67767
68055
|
}
|
|
67768
68056
|
function writeThrottleIssue(agent, source, now, telegramDir, log) {
|
|
67769
|
-
const issuesPath =
|
|
68057
|
+
const issuesPath = join36(telegramDir, "issues.jsonl");
|
|
67770
68058
|
try {
|
|
67771
|
-
|
|
68059
|
+
mkdirSync24(telegramDir, { recursive: true });
|
|
67772
68060
|
const record = {
|
|
67773
68061
|
ts: now,
|
|
67774
68062
|
agent,
|
|
@@ -67781,7 +68069,7 @@ function writeThrottleIssue(agent, source, now, telegramDir, log) {
|
|
|
67781
68069
|
first_seen: now,
|
|
67782
68070
|
last_seen: now
|
|
67783
68071
|
};
|
|
67784
|
-
|
|
68072
|
+
appendFileSync3(issuesPath, JSON.stringify(record) + `
|
|
67785
68073
|
`, { mode: 384 });
|
|
67786
68074
|
} catch (err) {
|
|
67787
68075
|
log(`webhook-ingest: agent='${agent}' source='${source}' issues.jsonl write failed: ${err.message}
|
|
@@ -67791,7 +68079,7 @@ function writeThrottleIssue(agent, source, now, telegramDir, log) {
|
|
|
67791
68079
|
async function handleWebhookIngest(args, deps = {}) {
|
|
67792
68080
|
const log = deps.log ?? ((s) => process.stderr.write(s));
|
|
67793
68081
|
const now = (deps.now ?? Date.now)();
|
|
67794
|
-
const resolveAgentDir = deps.resolveAgentDir ?? ((a) =>
|
|
68082
|
+
const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join36(homedir18(), ".switchroom", "agents", a));
|
|
67795
68083
|
const rateLimiter = deps.rateLimiter ?? defaultRateLimiter;
|
|
67796
68084
|
const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir);
|
|
67797
68085
|
if (!args.agentExists) {
|
|
@@ -67845,7 +68133,7 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
67845
68133
|
const retryAfter = rpm !== undefined ? rateLimiter.check(args.agent, source, rpm, now) : null;
|
|
67846
68134
|
if (retryAfter !== null) {
|
|
67847
68135
|
const agentDir2 = resolveAgentDir(args.agent);
|
|
67848
|
-
const telegramDir2 =
|
|
68136
|
+
const telegramDir2 = join36(agentDir2, "telegram");
|
|
67849
68137
|
if (shouldWriteThrottleIssue(args.agent, source, now)) {
|
|
67850
68138
|
writeThrottleIssue(args.agent, source, now, telegramDir2, log);
|
|
67851
68139
|
}
|
|
@@ -67864,10 +68152,10 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
67864
68152
|
const eventType = source === "github" ? args.headers.get("x-github-event") ?? "unknown" : args.source;
|
|
67865
68153
|
const rendered = source === "github" ? renderGithubEvent(eventType, payload) : renderGenericEvent(args.source, payload);
|
|
67866
68154
|
const agentDir = resolveAgentDir(args.agent);
|
|
67867
|
-
const telegramDir =
|
|
67868
|
-
const logPath =
|
|
68155
|
+
const telegramDir = join36(agentDir, "telegram");
|
|
68156
|
+
const logPath = join36(telegramDir, "webhook-events.jsonl");
|
|
67869
68157
|
try {
|
|
67870
|
-
|
|
68158
|
+
mkdirSync24(telegramDir, { recursive: true });
|
|
67871
68159
|
const record = {
|
|
67872
68160
|
ts: now,
|
|
67873
68161
|
source,
|
|
@@ -67875,7 +68163,7 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
67875
68163
|
rendered_text: rendered.text,
|
|
67876
68164
|
payload
|
|
67877
68165
|
};
|
|
67878
|
-
|
|
68166
|
+
appendFileSync3(logPath, JSON.stringify(record) + `
|
|
67879
68167
|
`, { mode: 384 });
|
|
67880
68168
|
} catch (err) {
|
|
67881
68169
|
log(`webhook-ingest: agent='${args.agent}' source='${source}' write failed: ${err.message}
|
|
@@ -67918,27 +68206,27 @@ function resolveWebToken() {
|
|
|
67918
68206
|
const fromEnv = process.env.SWITCHROOM_WEB_TOKEN;
|
|
67919
68207
|
if (fromEnv && fromEnv.length > 0)
|
|
67920
68208
|
return fromEnv;
|
|
67921
|
-
const home2 = process.env.HOME ??
|
|
67922
|
-
const tokenPath =
|
|
67923
|
-
if (
|
|
67924
|
-
const existing =
|
|
68209
|
+
const home2 = process.env.HOME ?? homedir19();
|
|
68210
|
+
const tokenPath = join37(home2, ".switchroom", "web-token");
|
|
68211
|
+
if (existsSync44(tokenPath)) {
|
|
68212
|
+
const existing = readFileSync40(tokenPath, "utf8").trim();
|
|
67925
68213
|
if (existing.length > 0)
|
|
67926
68214
|
return existing;
|
|
67927
68215
|
}
|
|
67928
|
-
const token =
|
|
67929
|
-
|
|
68216
|
+
const token = randomBytes10(32).toString("hex");
|
|
68217
|
+
mkdirSync25(dirname9(tokenPath), { recursive: true, mode: 448 });
|
|
67930
68218
|
try {
|
|
67931
|
-
const fd =
|
|
68219
|
+
const fd = openSync10(tokenPath, fsConstants3.O_WRONLY | fsConstants3.O_CREAT | fsConstants3.O_EXCL, 384);
|
|
67932
68220
|
try {
|
|
67933
|
-
|
|
68221
|
+
writeSync6(fd, token + `
|
|
67934
68222
|
`);
|
|
67935
68223
|
} finally {
|
|
67936
|
-
|
|
68224
|
+
closeSync10(fd);
|
|
67937
68225
|
}
|
|
67938
68226
|
return token;
|
|
67939
68227
|
} catch (err) {
|
|
67940
68228
|
if (err.code === "EEXIST") {
|
|
67941
|
-
const existing =
|
|
68229
|
+
const existing = readFileSync40(tokenPath, "utf8").trim();
|
|
67942
68230
|
if (existing.length > 0)
|
|
67943
68231
|
return existing;
|
|
67944
68232
|
}
|
|
@@ -68004,11 +68292,11 @@ function checkWsAuth(req, token, server) {
|
|
|
68004
68292
|
return presented !== null && constantTimeEqual(presented, token);
|
|
68005
68293
|
}
|
|
68006
68294
|
function loadWebhookSecrets() {
|
|
68007
|
-
const path4 =
|
|
68008
|
-
if (!
|
|
68295
|
+
const path4 = join37(homedir19(), ".switchroom", "webhook-secrets.json");
|
|
68296
|
+
if (!existsSync44(path4))
|
|
68009
68297
|
return {};
|
|
68010
68298
|
try {
|
|
68011
|
-
const parsed = JSON.parse(
|
|
68299
|
+
const parsed = JSON.parse(readFileSync40(path4, "utf-8"));
|
|
68012
68300
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
68013
68301
|
} catch (err) {
|
|
68014
68302
|
process.stderr.write(`webhook-ingest: failed to parse ${path4}: ${err.message} \u2014 webhooks will return 401 until fixed
|
|
@@ -68107,7 +68395,7 @@ function parseRoute(pathname, method) {
|
|
|
68107
68395
|
}
|
|
68108
68396
|
function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
68109
68397
|
const uiDirRaw = resolve26(import.meta.dirname, "ui");
|
|
68110
|
-
const uiDir =
|
|
68398
|
+
const uiDir = existsSync44(uiDirRaw) ? realpathSync3(uiDirRaw) : uiDirRaw;
|
|
68111
68399
|
const token = resolveWebToken();
|
|
68112
68400
|
const localhostOnly = hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1";
|
|
68113
68401
|
const server = Bun.serve({
|
|
@@ -68262,8 +68550,8 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
68262
68550
|
}
|
|
68263
68551
|
}
|
|
68264
68552
|
let filePath = pathname === "/" ? "/index.html" : pathname;
|
|
68265
|
-
const fullPath =
|
|
68266
|
-
if (!
|
|
68553
|
+
const fullPath = join37(uiDir, filePath);
|
|
68554
|
+
if (!existsSync44(fullPath)) {
|
|
68267
68555
|
return new Response("Not Found", { status: 404 });
|
|
68268
68556
|
}
|
|
68269
68557
|
let realFullPath;
|
|
@@ -68278,7 +68566,7 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
68278
68566
|
}
|
|
68279
68567
|
const ext = extname(realFullPath);
|
|
68280
68568
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
68281
|
-
const content =
|
|
68569
|
+
const content = readFileSync40(realFullPath);
|
|
68282
68570
|
return new Response(content, {
|
|
68283
68571
|
headers: { "Content-Type": contentType }
|
|
68284
68572
|
});
|
|
@@ -68412,7 +68700,7 @@ Starting Switchroom dashboard...
|
|
|
68412
68700
|
// src/cli/setup.ts
|
|
68413
68701
|
init_source();
|
|
68414
68702
|
init_loader();
|
|
68415
|
-
import { existsSync as
|
|
68703
|
+
import { existsSync as existsSync45, copyFileSync as copyFileSync8, readFileSync as readFileSync41, writeFileSync as writeFileSync24, mkdirSync as mkdirSync26 } from "node:fs";
|
|
68416
68704
|
import { resolve as resolve27, dirname as dirname10 } from "node:path";
|
|
68417
68705
|
init_vault();
|
|
68418
68706
|
init_manager();
|
|
@@ -68552,7 +68840,7 @@ async function stepConfigFile(configPath, nonInteractive) {
|
|
|
68552
68840
|
existingConfig = null;
|
|
68553
68841
|
}
|
|
68554
68842
|
}
|
|
68555
|
-
if (existingConfig &&
|
|
68843
|
+
if (existingConfig && existsSync45(existingConfig)) {
|
|
68556
68844
|
if (!nonInteractive) {
|
|
68557
68845
|
const useExisting = await askYesNo(` Found ${source_default.cyan(existingConfig)}. Use it?`, true);
|
|
68558
68846
|
if (!useExisting) {
|
|
@@ -68580,10 +68868,10 @@ async function copyExampleConfig(nonInteractive) {
|
|
|
68580
68868
|
}
|
|
68581
68869
|
const srcFile = resolve27(examplesDir, `${choice}.yaml`);
|
|
68582
68870
|
const destFile = resolvePath("~/.switchroom/switchroom.yaml");
|
|
68583
|
-
if (!
|
|
68871
|
+
if (!existsSync45(srcFile)) {
|
|
68584
68872
|
throw new ConfigError(`Example config not found: ${choice}.yaml`);
|
|
68585
68873
|
}
|
|
68586
|
-
|
|
68874
|
+
mkdirSync26(dirname10(destFile), { recursive: true });
|
|
68587
68875
|
copyFileSync8(srcFile, destFile);
|
|
68588
68876
|
console.log(source_default.green(` Copied ${choice}.yaml -> ${destFile}`));
|
|
68589
68877
|
console.log(source_default.yellow(` Edit ${destFile} to customize, then re-run switchroom setup.`));
|
|
@@ -68664,7 +68952,7 @@ async function resolveOrPromptToken(rawToken, label, config, nonInteractive) {
|
|
|
68664
68952
|
try {
|
|
68665
68953
|
const { openVault: openVault2 } = await Promise.resolve().then(() => (init_vault(), exports_vault));
|
|
68666
68954
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
68667
|
-
if (
|
|
68955
|
+
if (existsSync45(vaultPath)) {
|
|
68668
68956
|
const secrets = openVault2(passphrase, vaultPath);
|
|
68669
68957
|
const key = rawToken.replace("vault:", "");
|
|
68670
68958
|
const entry = secrets[key];
|
|
@@ -68692,7 +68980,7 @@ async function resolveOrPromptToken(rawToken, label, config, nonInteractive) {
|
|
|
68692
68980
|
async function storeTokenInVault(config, vaultRef, token) {
|
|
68693
68981
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
68694
68982
|
const key = vaultRef.replace("vault:", "");
|
|
68695
|
-
if (!
|
|
68983
|
+
if (!existsSync45(vaultPath)) {
|
|
68696
68984
|
console.log(source_default.gray(" Creating encrypted vault..."));
|
|
68697
68985
|
let passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
68698
68986
|
if (!passphrase) {
|
|
@@ -68812,7 +69100,7 @@ async function stepMemoryBackend(config, nonInteractive, switchroomConfigPath) {
|
|
|
68812
69100
|
try {
|
|
68813
69101
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
68814
69102
|
const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
68815
|
-
if (passphrase &&
|
|
69103
|
+
if (passphrase && existsSync45(vaultPath)) {
|
|
68816
69104
|
const existing = getStringSecret(passphrase, vaultPath, "hindsight-api-key");
|
|
68817
69105
|
if (existing) {
|
|
68818
69106
|
console.log(source_default.gray(" Note: legacy 'hindsight-api-key' is in your vault but is no longer used. You can remove it with `switchroom vault rm hindsight-api-key`."));
|
|
@@ -68972,13 +69260,13 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
|
|
|
68972
69260
|
return;
|
|
68973
69261
|
}
|
|
68974
69262
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
68975
|
-
if (!
|
|
69263
|
+
if (!existsSync45(vaultPath)) {
|
|
68976
69264
|
console.log(source_default.gray(" Skipping (vault not created yet)."));
|
|
68977
69265
|
return;
|
|
68978
69266
|
}
|
|
68979
69267
|
const credPathRaw = config.vault?.broker?.autoUnlockCredentialPath ?? "~/.switchroom/vault-auto-unlock";
|
|
68980
69268
|
const credPath = resolvePath(credPathRaw);
|
|
68981
|
-
if (config.vault?.broker?.autoUnlock === true &&
|
|
69269
|
+
if (config.vault?.broker?.autoUnlock === true && existsSync45(credPath)) {
|
|
68982
69270
|
console.log(source_default.green(` ${STEP_DONE} Already configured (${credPath})`));
|
|
68983
69271
|
return;
|
|
68984
69272
|
}
|
|
@@ -69044,9 +69332,9 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
|
|
|
69044
69332
|
const choice = await askChoice(" Approval posture", [PASSPHRASE_CHOICE, TELEGRAM_ID_CHOICE]);
|
|
69045
69333
|
if (choice === TELEGRAM_ID_CHOICE) {
|
|
69046
69334
|
try {
|
|
69047
|
-
const yamlPath =
|
|
69048
|
-
if (
|
|
69049
|
-
const content =
|
|
69335
|
+
const yamlPath = existsSync45(resolve27(process.cwd(), "switchroom.yaml")) ? resolve27(process.cwd(), "switchroom.yaml") : resolve27(process.cwd(), "switchroom.yml");
|
|
69336
|
+
if (existsSync45(yamlPath)) {
|
|
69337
|
+
const content = readFileSync41(yamlPath, "utf-8");
|
|
69050
69338
|
const result = insertVaultBrokerApprovalAuth(content, "telegram-id");
|
|
69051
69339
|
if (result.kind === "rewritten") {
|
|
69052
69340
|
writeFileSync24(yamlPath, result.content, "utf-8");
|
|
@@ -69080,8 +69368,8 @@ async function stepDangerousMode(config, nonInteractive) {
|
|
|
69080
69368
|
resolve27(process.cwd(), "switchroom.yml")
|
|
69081
69369
|
];
|
|
69082
69370
|
for (const configPath of configPaths) {
|
|
69083
|
-
if (
|
|
69084
|
-
let content =
|
|
69371
|
+
if (existsSync45(configPath)) {
|
|
69372
|
+
let content = readFileSync41(configPath, "utf-8");
|
|
69085
69373
|
const agentNames = Object.keys(config.agents);
|
|
69086
69374
|
for (const name of agentNames) {
|
|
69087
69375
|
const agentPattern = new RegExp(`(^ ${name}:\\s*\\n)`, "m");
|
|
@@ -69210,17 +69498,17 @@ init_doctor();
|
|
|
69210
69498
|
init_source();
|
|
69211
69499
|
init_loader();
|
|
69212
69500
|
init_lifecycle();
|
|
69213
|
-
import { cpSync as cpSync2, existsSync as
|
|
69501
|
+
import { cpSync as cpSync2, existsSync as existsSync51, mkdirSync as mkdirSync28, readFileSync as readFileSync46, realpathSync as realpathSync5, rmSync as rmSync12, statSync as statSync23 } from "node:fs";
|
|
69214
69502
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
69215
|
-
import { join as
|
|
69216
|
-
import { homedir as
|
|
69217
|
-
var DEFAULT_COMPOSE_PATH =
|
|
69503
|
+
import { join as join46, dirname as dirname13, resolve as resolve30 } from "node:path";
|
|
69504
|
+
import { homedir as homedir26 } from "node:os";
|
|
69505
|
+
var DEFAULT_COMPOSE_PATH = join46(homedir26(), ".switchroom", "compose", "docker-compose.yml");
|
|
69218
69506
|
function runningFromSwitchroomCheckout(scriptPath) {
|
|
69219
69507
|
let dir = dirname13(scriptPath);
|
|
69220
69508
|
for (let i = 0;i < 12; i++) {
|
|
69221
|
-
if (
|
|
69509
|
+
if (existsSync51(join46(dir, ".git"))) {
|
|
69222
69510
|
try {
|
|
69223
|
-
const pkg = JSON.parse(
|
|
69511
|
+
const pkg = JSON.parse(readFileSync46(join46(dir, "package.json"), "utf-8"));
|
|
69224
69512
|
if (pkg.name === "switchroom")
|
|
69225
69513
|
return true;
|
|
69226
69514
|
} catch {}
|
|
@@ -69272,7 +69560,7 @@ function planUpdate(opts) {
|
|
|
69272
69560
|
steps.push({
|
|
69273
69561
|
name: "pull-images",
|
|
69274
69562
|
description: "Pull broker / kernel / agent images from GHCR",
|
|
69275
|
-
skipReason: opts.skipImages ? "--skip-images flag set" : !
|
|
69563
|
+
skipReason: opts.skipImages ? "--skip-images flag set" : !existsSync51(composePath) ? `compose file not found at ${composePath} (run \`switchroom apply --compose-only\` first)` : undefined,
|
|
69276
69564
|
run: () => {
|
|
69277
69565
|
const r = runner("docker", [
|
|
69278
69566
|
"compose",
|
|
@@ -69351,17 +69639,17 @@ function planUpdate(opts) {
|
|
|
69351
69639
|
return;
|
|
69352
69640
|
}
|
|
69353
69641
|
const source = resolve30(import.meta.dirname, "../../skills");
|
|
69354
|
-
const dest =
|
|
69355
|
-
if (!
|
|
69642
|
+
const dest = join46(homedir26(), ".switchroom", "skills", "_bundled");
|
|
69643
|
+
if (!existsSync51(source)) {
|
|
69356
69644
|
process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
|
|
69357
69645
|
`);
|
|
69358
69646
|
return;
|
|
69359
69647
|
}
|
|
69360
69648
|
try {
|
|
69361
|
-
if (
|
|
69649
|
+
if (existsSync51(dest)) {
|
|
69362
69650
|
rmSync12(dest, { recursive: true, force: true });
|
|
69363
69651
|
}
|
|
69364
|
-
|
|
69652
|
+
mkdirSync28(dirname13(dest), { recursive: true });
|
|
69365
69653
|
cpSync2(source, dest, { recursive: true, dereference: false });
|
|
69366
69654
|
} catch (err) {
|
|
69367
69655
|
throw new Error(`sync-bundled-skills failed: ${err.message}`);
|
|
@@ -69461,14 +69749,14 @@ function defaultStatusProbe(composePath) {
|
|
|
69461
69749
|
} catch {}
|
|
69462
69750
|
if (scriptPath) {
|
|
69463
69751
|
try {
|
|
69464
|
-
cliBuiltAt = new Date(
|
|
69752
|
+
cliBuiltAt = new Date(statSync23(scriptPath).mtimeMs).toISOString();
|
|
69465
69753
|
} catch {}
|
|
69466
69754
|
let dir = dirname13(scriptPath);
|
|
69467
69755
|
for (let i = 0;i < 8; i++) {
|
|
69468
|
-
const pkgPath =
|
|
69469
|
-
if (
|
|
69756
|
+
const pkgPath = join46(dir, "package.json");
|
|
69757
|
+
if (existsSync51(pkgPath)) {
|
|
69470
69758
|
try {
|
|
69471
|
-
const pkg = JSON.parse(
|
|
69759
|
+
const pkg = JSON.parse(readFileSync46(pkgPath, "utf-8"));
|
|
69472
69760
|
if (typeof pkg.version === "string")
|
|
69473
69761
|
cliVersion = pkg.version;
|
|
69474
69762
|
} catch (err) {
|
|
@@ -69489,7 +69777,7 @@ function defaultStatusProbe(composePath) {
|
|
|
69489
69777
|
warnings.push("could not resolve CLI version (no package.json found above the resolved script path)");
|
|
69490
69778
|
}
|
|
69491
69779
|
const services = [];
|
|
69492
|
-
if (!
|
|
69780
|
+
if (!existsSync51(composePath)) {
|
|
69493
69781
|
warnings.push(`compose file not found at ${composePath}; service status unknown`);
|
|
69494
69782
|
return { cliVersion, cliBuiltAt, services, warnings };
|
|
69495
69783
|
}
|
|
@@ -69684,8 +69972,8 @@ init_source();
|
|
|
69684
69972
|
init_helpers();
|
|
69685
69973
|
init_lifecycle();
|
|
69686
69974
|
import { execSync as execSync4 } from "node:child_process";
|
|
69687
|
-
import { existsSync as
|
|
69688
|
-
import { dirname as dirname14, join as
|
|
69975
|
+
import { existsSync as existsSync52, readFileSync as readFileSync47 } from "node:fs";
|
|
69976
|
+
import { dirname as dirname14, join as join47 } from "node:path";
|
|
69689
69977
|
function getClaudeCodeVersion() {
|
|
69690
69978
|
try {
|
|
69691
69979
|
const out = execSync4("claude --version 2>/dev/null", {
|
|
@@ -69735,11 +70023,11 @@ function formatUptime3(timestamp) {
|
|
|
69735
70023
|
function locateSwitchroomInstallDir() {
|
|
69736
70024
|
let dir = import.meta.dirname;
|
|
69737
70025
|
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
69738
|
-
const pkgPath =
|
|
69739
|
-
if (
|
|
70026
|
+
const pkgPath = join47(dir, "package.json");
|
|
70027
|
+
if (existsSync52(pkgPath)) {
|
|
69740
70028
|
try {
|
|
69741
|
-
const pkg = JSON.parse(
|
|
69742
|
-
if (pkg.name === "switchroom" &&
|
|
70029
|
+
const pkg = JSON.parse(readFileSync47(pkgPath, "utf-8"));
|
|
70030
|
+
if (pkg.name === "switchroom" && existsSync52(join47(dir, ".git"))) {
|
|
69743
70031
|
return dir;
|
|
69744
70032
|
}
|
|
69745
70033
|
} catch {}
|
|
@@ -69957,20 +70245,20 @@ function registerHandoffCommand(program3) {
|
|
|
69957
70245
|
|
|
69958
70246
|
// src/issues/store.ts
|
|
69959
70247
|
import {
|
|
69960
|
-
closeSync as
|
|
69961
|
-
existsSync as
|
|
69962
|
-
mkdirSync as
|
|
69963
|
-
openSync as
|
|
69964
|
-
readdirSync as
|
|
69965
|
-
readFileSync as
|
|
69966
|
-
renameSync as
|
|
69967
|
-
statSync as
|
|
69968
|
-
unlinkSync as
|
|
70248
|
+
closeSync as closeSync11,
|
|
70249
|
+
existsSync as existsSync53,
|
|
70250
|
+
mkdirSync as mkdirSync29,
|
|
70251
|
+
openSync as openSync11,
|
|
70252
|
+
readdirSync as readdirSync19,
|
|
70253
|
+
readFileSync as readFileSync48,
|
|
70254
|
+
renameSync as renameSync11,
|
|
70255
|
+
statSync as statSync24,
|
|
70256
|
+
unlinkSync as unlinkSync11,
|
|
69969
70257
|
writeFileSync as writeFileSync25,
|
|
69970
|
-
writeSync as
|
|
70258
|
+
writeSync as writeSync7
|
|
69971
70259
|
} from "node:fs";
|
|
69972
|
-
import { join as
|
|
69973
|
-
import { randomBytes as
|
|
70260
|
+
import { join as join48 } from "node:path";
|
|
70261
|
+
import { randomBytes as randomBytes11 } from "node:crypto";
|
|
69974
70262
|
import { execSync as execSync5 } from "node:child_process";
|
|
69975
70263
|
|
|
69976
70264
|
// src/issues/types.ts
|
|
@@ -70289,12 +70577,12 @@ function redactedMarker(ruleId) {
|
|
|
70289
70577
|
var ISSUES_FILE = "issues.jsonl";
|
|
70290
70578
|
var ISSUES_LOCK = "issues.lock";
|
|
70291
70579
|
function readAll(stateDir) {
|
|
70292
|
-
const path4 =
|
|
70293
|
-
if (!
|
|
70580
|
+
const path4 = join48(stateDir, ISSUES_FILE);
|
|
70581
|
+
if (!existsSync53(path4))
|
|
70294
70582
|
return [];
|
|
70295
70583
|
let raw;
|
|
70296
70584
|
try {
|
|
70297
|
-
raw =
|
|
70585
|
+
raw = readFileSync48(path4, "utf-8");
|
|
70298
70586
|
} catch {
|
|
70299
70587
|
return [];
|
|
70300
70588
|
}
|
|
@@ -70367,7 +70655,7 @@ function record(stateDir, input, nowFn = Date.now) {
|
|
|
70367
70655
|
});
|
|
70368
70656
|
}
|
|
70369
70657
|
function resolve33(stateDir, fingerprint, nowFn = Date.now) {
|
|
70370
|
-
if (!
|
|
70658
|
+
if (!existsSync53(join48(stateDir, ISSUES_FILE)))
|
|
70371
70659
|
return 0;
|
|
70372
70660
|
return withLock(stateDir, () => {
|
|
70373
70661
|
const all = readAll(stateDir);
|
|
@@ -70385,7 +70673,7 @@ function resolve33(stateDir, fingerprint, nowFn = Date.now) {
|
|
|
70385
70673
|
});
|
|
70386
70674
|
}
|
|
70387
70675
|
function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
70388
|
-
if (!
|
|
70676
|
+
if (!existsSync53(join48(stateDir, ISSUES_FILE)))
|
|
70389
70677
|
return 0;
|
|
70390
70678
|
return withLock(stateDir, () => {
|
|
70391
70679
|
const all = readAll(stateDir);
|
|
@@ -70403,7 +70691,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
|
70403
70691
|
});
|
|
70404
70692
|
}
|
|
70405
70693
|
function prune(stateDir, opts = {}) {
|
|
70406
|
-
if (!
|
|
70694
|
+
if (!existsSync53(join48(stateDir, ISSUES_FILE)))
|
|
70407
70695
|
return 0;
|
|
70408
70696
|
return withLock(stateDir, () => {
|
|
70409
70697
|
const all = readAll(stateDir);
|
|
@@ -70433,24 +70721,24 @@ function prune(stateDir, opts = {}) {
|
|
|
70433
70721
|
});
|
|
70434
70722
|
}
|
|
70435
70723
|
function ensureDir(stateDir) {
|
|
70436
|
-
|
|
70724
|
+
mkdirSync29(stateDir, { recursive: true });
|
|
70437
70725
|
}
|
|
70438
70726
|
function writeAll(stateDir, events) {
|
|
70439
|
-
const path4 =
|
|
70727
|
+
const path4 = join48(stateDir, ISSUES_FILE);
|
|
70440
70728
|
sweepOrphanTmpFiles(stateDir);
|
|
70441
|
-
const tmp = `${path4}.tmp-${process.pid}-${
|
|
70729
|
+
const tmp = `${path4}.tmp-${process.pid}-${randomBytes11(4).toString("hex")}`;
|
|
70442
70730
|
const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
|
|
70443
70731
|
`) + `
|
|
70444
70732
|
`;
|
|
70445
70733
|
writeFileSync25(tmp, body, "utf-8");
|
|
70446
|
-
|
|
70734
|
+
renameSync11(tmp, path4);
|
|
70447
70735
|
}
|
|
70448
70736
|
var ORPHAN_TMP_TTL_MS = 60000;
|
|
70449
70737
|
var TMP_PREFIX = `${ISSUES_FILE}.tmp-`;
|
|
70450
70738
|
function sweepOrphanTmpFiles(stateDir) {
|
|
70451
70739
|
let entries;
|
|
70452
70740
|
try {
|
|
70453
|
-
entries =
|
|
70741
|
+
entries = readdirSync19(stateDir);
|
|
70454
70742
|
} catch {
|
|
70455
70743
|
return;
|
|
70456
70744
|
}
|
|
@@ -70458,11 +70746,11 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
70458
70746
|
for (const entry of entries) {
|
|
70459
70747
|
if (!entry.startsWith(TMP_PREFIX))
|
|
70460
70748
|
continue;
|
|
70461
|
-
const tmpPath =
|
|
70749
|
+
const tmpPath = join48(stateDir, entry);
|
|
70462
70750
|
try {
|
|
70463
|
-
const stat =
|
|
70751
|
+
const stat = statSync24(tmpPath);
|
|
70464
70752
|
if (stat.mtimeMs < cutoff) {
|
|
70465
|
-
|
|
70753
|
+
unlinkSync11(tmpPath);
|
|
70466
70754
|
}
|
|
70467
70755
|
} catch {}
|
|
70468
70756
|
}
|
|
@@ -70470,14 +70758,14 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
70470
70758
|
var LOCK_RETRY_MS = 25;
|
|
70471
70759
|
var LOCK_TIMEOUT_MS = 1e4;
|
|
70472
70760
|
function withLock(stateDir, fn) {
|
|
70473
|
-
const lockPath =
|
|
70761
|
+
const lockPath = join48(stateDir, ISSUES_LOCK);
|
|
70474
70762
|
const startedAt = Date.now();
|
|
70475
70763
|
let fd = null;
|
|
70476
70764
|
while (fd === null) {
|
|
70477
70765
|
try {
|
|
70478
|
-
fd =
|
|
70766
|
+
fd = openSync11(lockPath, "wx");
|
|
70479
70767
|
try {
|
|
70480
|
-
|
|
70768
|
+
writeSync7(fd, String(process.pid));
|
|
70481
70769
|
} catch {}
|
|
70482
70770
|
} catch (err) {
|
|
70483
70771
|
const e = err;
|
|
@@ -70495,30 +70783,30 @@ function withLock(stateDir, fn) {
|
|
|
70495
70783
|
return fn();
|
|
70496
70784
|
} finally {
|
|
70497
70785
|
try {
|
|
70498
|
-
|
|
70786
|
+
closeSync11(fd);
|
|
70499
70787
|
} catch {}
|
|
70500
70788
|
try {
|
|
70501
|
-
|
|
70789
|
+
unlinkSync11(lockPath);
|
|
70502
70790
|
} catch {}
|
|
70503
70791
|
}
|
|
70504
70792
|
}
|
|
70505
70793
|
function tryStealStaleLock(lockPath) {
|
|
70506
70794
|
let pidStr;
|
|
70507
70795
|
try {
|
|
70508
|
-
pidStr =
|
|
70796
|
+
pidStr = readFileSync48(lockPath, "utf-8").trim();
|
|
70509
70797
|
} catch {
|
|
70510
70798
|
return true;
|
|
70511
70799
|
}
|
|
70512
70800
|
const pid = Number(pidStr);
|
|
70513
70801
|
if (!Number.isFinite(pid) || pid <= 0) {
|
|
70514
70802
|
try {
|
|
70515
|
-
|
|
70803
|
+
unlinkSync11(lockPath);
|
|
70516
70804
|
} catch {}
|
|
70517
70805
|
return true;
|
|
70518
70806
|
}
|
|
70519
70807
|
if (pid === process.pid) {
|
|
70520
70808
|
try {
|
|
70521
|
-
|
|
70809
|
+
unlinkSync11(lockPath);
|
|
70522
70810
|
} catch {}
|
|
70523
70811
|
return true;
|
|
70524
70812
|
}
|
|
@@ -70533,7 +70821,7 @@ function tryStealStaleLock(lockPath) {
|
|
|
70533
70821
|
return false;
|
|
70534
70822
|
}
|
|
70535
70823
|
try {
|
|
70536
|
-
|
|
70824
|
+
unlinkSync11(lockPath);
|
|
70537
70825
|
} catch {}
|
|
70538
70826
|
return true;
|
|
70539
70827
|
}
|
|
@@ -70753,21 +71041,21 @@ function relTime(deltaMs) {
|
|
|
70753
71041
|
|
|
70754
71042
|
// src/cli/deps.ts
|
|
70755
71043
|
init_source();
|
|
70756
|
-
import { existsSync as
|
|
70757
|
-
import { homedir as
|
|
70758
|
-
import { join as
|
|
71044
|
+
import { existsSync as existsSync56 } from "node:fs";
|
|
71045
|
+
import { homedir as homedir29 } from "node:os";
|
|
71046
|
+
import { join as join51, resolve as resolve34 } from "node:path";
|
|
70759
71047
|
|
|
70760
71048
|
// src/deps/python.ts
|
|
70761
|
-
import { createHash as
|
|
71049
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
70762
71050
|
import {
|
|
70763
|
-
existsSync as
|
|
70764
|
-
mkdirSync as
|
|
70765
|
-
readFileSync as
|
|
71051
|
+
existsSync as existsSync54,
|
|
71052
|
+
mkdirSync as mkdirSync30,
|
|
71053
|
+
readFileSync as readFileSync49,
|
|
70766
71054
|
rmSync as rmSync13,
|
|
70767
71055
|
writeFileSync as writeFileSync26
|
|
70768
71056
|
} from "node:fs";
|
|
70769
|
-
import { dirname as dirname15, join as
|
|
70770
|
-
import { homedir as
|
|
71057
|
+
import { dirname as dirname15, join as join49 } from "node:path";
|
|
71058
|
+
import { homedir as homedir27 } from "node:os";
|
|
70771
71059
|
import { execFileSync as execFileSync14 } from "node:child_process";
|
|
70772
71060
|
|
|
70773
71061
|
class PythonEnvError extends Error {
|
|
@@ -70779,26 +71067,26 @@ class PythonEnvError extends Error {
|
|
|
70779
71067
|
}
|
|
70780
71068
|
}
|
|
70781
71069
|
function defaultPythonCacheRoot() {
|
|
70782
|
-
return
|
|
71070
|
+
return join49(homedir27(), ".switchroom", "deps", "python");
|
|
70783
71071
|
}
|
|
70784
71072
|
function hashFile(path4) {
|
|
70785
|
-
return
|
|
71073
|
+
return createHash10("sha256").update(readFileSync49(path4)).digest("hex");
|
|
70786
71074
|
}
|
|
70787
71075
|
function ensurePythonEnv(opts) {
|
|
70788
71076
|
const { skillName, requirementsPath, force = false } = opts;
|
|
70789
71077
|
const cacheRoot = opts.cacheRoot ?? defaultPythonCacheRoot();
|
|
70790
71078
|
const hostPython = opts.pythonBin ?? "python3";
|
|
70791
|
-
if (!
|
|
71079
|
+
if (!existsSync54(requirementsPath)) {
|
|
70792
71080
|
throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
|
|
70793
71081
|
}
|
|
70794
|
-
const venvDir =
|
|
70795
|
-
const stampPath =
|
|
70796
|
-
const binDir =
|
|
70797
|
-
const pythonBin =
|
|
70798
|
-
const pipBin =
|
|
71082
|
+
const venvDir = join49(cacheRoot, skillName);
|
|
71083
|
+
const stampPath = join49(venvDir, ".requirements.sha256");
|
|
71084
|
+
const binDir = join49(venvDir, "bin");
|
|
71085
|
+
const pythonBin = join49(binDir, "python");
|
|
71086
|
+
const pipBin = join49(binDir, "pip");
|
|
70799
71087
|
const targetHash = hashFile(requirementsPath);
|
|
70800
|
-
if (!force &&
|
|
70801
|
-
const existingHash =
|
|
71088
|
+
if (!force && existsSync54(stampPath) && existsSync54(pythonBin)) {
|
|
71089
|
+
const existingHash = readFileSync49(stampPath, "utf8").trim();
|
|
70802
71090
|
if (existingHash === targetHash) {
|
|
70803
71091
|
return {
|
|
70804
71092
|
skillName,
|
|
@@ -70810,10 +71098,10 @@ function ensurePythonEnv(opts) {
|
|
|
70810
71098
|
};
|
|
70811
71099
|
}
|
|
70812
71100
|
}
|
|
70813
|
-
if (
|
|
71101
|
+
if (existsSync54(venvDir)) {
|
|
70814
71102
|
rmSync13(venvDir, { recursive: true, force: true });
|
|
70815
71103
|
}
|
|
70816
|
-
|
|
71104
|
+
mkdirSync30(dirname15(venvDir), { recursive: true });
|
|
70817
71105
|
try {
|
|
70818
71106
|
execFileSync14(hostPython, ["-m", "venv", venvDir], { stdio: "pipe" });
|
|
70819
71107
|
} catch (err) {
|
|
@@ -70845,17 +71133,17 @@ function ensurePythonEnv(opts) {
|
|
|
70845
71133
|
}
|
|
70846
71134
|
|
|
70847
71135
|
// src/deps/node.ts
|
|
70848
|
-
import { createHash as
|
|
71136
|
+
import { createHash as createHash11 } from "node:crypto";
|
|
70849
71137
|
import {
|
|
70850
71138
|
copyFileSync as copyFileSync9,
|
|
70851
|
-
existsSync as
|
|
70852
|
-
mkdirSync as
|
|
70853
|
-
readFileSync as
|
|
71139
|
+
existsSync as existsSync55,
|
|
71140
|
+
mkdirSync as mkdirSync31,
|
|
71141
|
+
readFileSync as readFileSync50,
|
|
70854
71142
|
rmSync as rmSync14,
|
|
70855
71143
|
writeFileSync as writeFileSync27
|
|
70856
71144
|
} from "node:fs";
|
|
70857
|
-
import { dirname as dirname16, join as
|
|
70858
|
-
import { homedir as
|
|
71145
|
+
import { dirname as dirname16, join as join50 } from "node:path";
|
|
71146
|
+
import { homedir as homedir28 } from "node:os";
|
|
70859
71147
|
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
70860
71148
|
|
|
70861
71149
|
class NodeEnvError extends Error {
|
|
@@ -70878,23 +71166,23 @@ var LOCKFILES_FOR = {
|
|
|
70878
71166
|
npm: ["package-lock.json"]
|
|
70879
71167
|
};
|
|
70880
71168
|
function defaultNodeCacheRoot() {
|
|
70881
|
-
return
|
|
71169
|
+
return join50(homedir28(), ".switchroom", "deps", "node");
|
|
70882
71170
|
}
|
|
70883
71171
|
function hashDepInputs(packageJsonPath) {
|
|
70884
71172
|
const sourceDir = dirname16(packageJsonPath);
|
|
70885
|
-
const hasher =
|
|
71173
|
+
const hasher = createHash11("sha256");
|
|
70886
71174
|
hasher.update(`package.json
|
|
70887
71175
|
`);
|
|
70888
|
-
hasher.update(
|
|
71176
|
+
hasher.update(readFileSync50(packageJsonPath));
|
|
70889
71177
|
for (const lockName of ALL_LOCKFILES) {
|
|
70890
|
-
const lockPath =
|
|
70891
|
-
if (
|
|
71178
|
+
const lockPath = join50(sourceDir, lockName);
|
|
71179
|
+
if (existsSync55(lockPath)) {
|
|
70892
71180
|
hasher.update(`
|
|
70893
71181
|
`);
|
|
70894
71182
|
hasher.update(lockName);
|
|
70895
71183
|
hasher.update(`
|
|
70896
71184
|
`);
|
|
70897
|
-
hasher.update(
|
|
71185
|
+
hasher.update(readFileSync50(lockPath));
|
|
70898
71186
|
}
|
|
70899
71187
|
}
|
|
70900
71188
|
return hasher.digest("hex");
|
|
@@ -70903,17 +71191,17 @@ function ensureNodeEnv(opts) {
|
|
|
70903
71191
|
const { skillName, packageJsonPath, force = false } = opts;
|
|
70904
71192
|
const cacheRoot = opts.cacheRoot ?? defaultNodeCacheRoot();
|
|
70905
71193
|
const installer = opts.installer ?? "bun";
|
|
70906
|
-
if (!
|
|
71194
|
+
if (!existsSync55(packageJsonPath)) {
|
|
70907
71195
|
throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
|
|
70908
71196
|
}
|
|
70909
71197
|
const sourceDir = dirname16(packageJsonPath);
|
|
70910
|
-
const envDir =
|
|
70911
|
-
const stampPath =
|
|
70912
|
-
const nodeModulesDir =
|
|
70913
|
-
const binDir =
|
|
71198
|
+
const envDir = join50(cacheRoot, skillName);
|
|
71199
|
+
const stampPath = join50(envDir, ".package.sha256");
|
|
71200
|
+
const nodeModulesDir = join50(envDir, "node_modules");
|
|
71201
|
+
const binDir = join50(nodeModulesDir, ".bin");
|
|
70914
71202
|
const targetHash = hashDepInputs(packageJsonPath);
|
|
70915
|
-
if (!force &&
|
|
70916
|
-
const existingHash =
|
|
71203
|
+
if (!force && existsSync55(stampPath) && existsSync55(nodeModulesDir)) {
|
|
71204
|
+
const existingHash = readFileSync50(stampPath, "utf8").trim();
|
|
70917
71205
|
if (existingHash === targetHash) {
|
|
70918
71206
|
return {
|
|
70919
71207
|
skillName,
|
|
@@ -70924,16 +71212,16 @@ function ensureNodeEnv(opts) {
|
|
|
70924
71212
|
};
|
|
70925
71213
|
}
|
|
70926
71214
|
}
|
|
70927
|
-
if (
|
|
71215
|
+
if (existsSync55(envDir)) {
|
|
70928
71216
|
rmSync14(envDir, { recursive: true, force: true });
|
|
70929
71217
|
}
|
|
70930
|
-
|
|
70931
|
-
copyFileSync9(packageJsonPath,
|
|
71218
|
+
mkdirSync31(envDir, { recursive: true });
|
|
71219
|
+
copyFileSync9(packageJsonPath, join50(envDir, "package.json"));
|
|
70932
71220
|
let copiedLockfile = false;
|
|
70933
71221
|
for (const lockName of LOCKFILES_FOR[installer]) {
|
|
70934
|
-
const lockPath =
|
|
70935
|
-
if (
|
|
70936
|
-
copyFileSync9(lockPath,
|
|
71222
|
+
const lockPath = join50(sourceDir, lockName);
|
|
71223
|
+
if (existsSync55(lockPath)) {
|
|
71224
|
+
copyFileSync9(lockPath, join50(envDir, lockName));
|
|
70937
71225
|
copiedLockfile = true;
|
|
70938
71226
|
}
|
|
70939
71227
|
}
|
|
@@ -70962,28 +71250,28 @@ function ensureNodeEnv(opts) {
|
|
|
70962
71250
|
|
|
70963
71251
|
// src/cli/deps.ts
|
|
70964
71252
|
function builtinSkillsRoot() {
|
|
70965
|
-
return resolve34(
|
|
71253
|
+
return resolve34(homedir29(), ".switchroom/skills/_bundled");
|
|
70966
71254
|
}
|
|
70967
71255
|
function registerDepsCommand(program3) {
|
|
70968
71256
|
const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
|
|
70969
71257
|
deps.command("rebuild <skill>").description("Rebuild the Python venv and/or Node node_modules cache for a skill").option("-p, --python", "Rebuild only the Python env").option("-n, --node", "Rebuild only the Node env").action(async (skill, opts) => {
|
|
70970
71258
|
const skillsRoot = builtinSkillsRoot();
|
|
70971
|
-
if (!
|
|
71259
|
+
if (!existsSync56(skillsRoot)) {
|
|
70972
71260
|
console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
|
|
70973
71261
|
process.exit(1);
|
|
70974
71262
|
}
|
|
70975
|
-
const skillDir =
|
|
70976
|
-
if (!
|
|
71263
|
+
const skillDir = join51(skillsRoot, skill);
|
|
71264
|
+
if (!existsSync56(skillDir)) {
|
|
70977
71265
|
console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
|
|
70978
71266
|
process.exit(1);
|
|
70979
71267
|
}
|
|
70980
|
-
const requirementsPath =
|
|
70981
|
-
const packageJsonPath =
|
|
70982
|
-
const wantPython = opts.python ?? (!opts.python && !opts.node &&
|
|
70983
|
-
const wantNode = opts.node ?? (!opts.python && !opts.node &&
|
|
71268
|
+
const requirementsPath = join51(skillDir, "requirements.txt");
|
|
71269
|
+
const packageJsonPath = join51(skillDir, "package.json");
|
|
71270
|
+
const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync56(requirementsPath));
|
|
71271
|
+
const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync56(packageJsonPath));
|
|
70984
71272
|
let did = 0;
|
|
70985
71273
|
if (wantPython) {
|
|
70986
|
-
if (!
|
|
71274
|
+
if (!existsSync56(requirementsPath)) {
|
|
70987
71275
|
console.error(source_default.red(`Skill "${skill}" has no requirements.txt at ${requirementsPath}`));
|
|
70988
71276
|
process.exit(1);
|
|
70989
71277
|
}
|
|
@@ -71007,7 +71295,7 @@ function registerDepsCommand(program3) {
|
|
|
71007
71295
|
}
|
|
71008
71296
|
}
|
|
71009
71297
|
if (wantNode) {
|
|
71010
|
-
if (!
|
|
71298
|
+
if (!existsSync56(packageJsonPath)) {
|
|
71011
71299
|
console.error(source_default.red(`Skill "${skill}" has no package.json at ${packageJsonPath}`));
|
|
71012
71300
|
process.exit(1);
|
|
71013
71301
|
}
|
|
@@ -71040,7 +71328,7 @@ function registerDepsCommand(program3) {
|
|
|
71040
71328
|
// src/cli/workspace.ts
|
|
71041
71329
|
init_helpers();
|
|
71042
71330
|
init_loader();
|
|
71043
|
-
import { existsSync as
|
|
71331
|
+
import { existsSync as existsSync57 } from "node:fs";
|
|
71044
71332
|
import { resolve as resolve35, sep as sep2 } from "node:path";
|
|
71045
71333
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
71046
71334
|
|
|
@@ -71817,7 +72105,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
71817
72105
|
if (!dir)
|
|
71818
72106
|
return;
|
|
71819
72107
|
const gitDir = resolve35(dir, ".git");
|
|
71820
|
-
if (!
|
|
72108
|
+
if (!existsSync57(gitDir)) {
|
|
71821
72109
|
process.stdout.write(`Workspace is not a git repository. Re-run \`switchroom agent create ${agentName}\` ` + `or manually \`git init\` in ${dir} to enable versioning.
|
|
71822
72110
|
`);
|
|
71823
72111
|
return;
|
|
@@ -71871,7 +72159,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
71871
72159
|
if (!dir)
|
|
71872
72160
|
return;
|
|
71873
72161
|
const gitDir = resolve35(dir, ".git");
|
|
71874
|
-
if (!
|
|
72162
|
+
if (!existsSync57(gitDir)) {
|
|
71875
72163
|
process.stdout.write(`Workspace is not a git repository.
|
|
71876
72164
|
`);
|
|
71877
72165
|
return;
|
|
@@ -71896,7 +72184,7 @@ function resolveAgentWorkspaceDirOrExit(program3, agentName) {
|
|
|
71896
72184
|
const agentsDir = resolveAgentsDir(config);
|
|
71897
72185
|
const agentDir = resolve35(agentsDir, agentName);
|
|
71898
72186
|
const dir = resolveAgentWorkspaceDir(agentDir);
|
|
71899
|
-
if (!
|
|
72187
|
+
if (!existsSync57(dir)) {
|
|
71900
72188
|
process.stderr.write(`workspace: ${dir} does not exist yet. Run \`switchroom setup\` or \`switchroom agent scaffold ${agentName}\` to seed it.
|
|
71901
72189
|
`);
|
|
71902
72190
|
return;
|
|
@@ -71932,8 +72220,8 @@ function safeParseInt(value, fallback) {
|
|
|
71932
72220
|
init_helpers();
|
|
71933
72221
|
init_loader();
|
|
71934
72222
|
init_merge();
|
|
71935
|
-
import { copyFileSync as copyFileSync10, existsSync as
|
|
71936
|
-
import { join as
|
|
72223
|
+
import { copyFileSync as copyFileSync10, existsSync as existsSync58, readFileSync as readFileSync51, writeFileSync as writeFileSync28 } from "node:fs";
|
|
72224
|
+
import { join as join52, resolve as resolve36 } from "node:path";
|
|
71937
72225
|
init_schema();
|
|
71938
72226
|
function resolveSoulTargetOrExit(program3, agentName) {
|
|
71939
72227
|
const config = getConfig(program3);
|
|
@@ -71948,7 +72236,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
71948
72236
|
const agentsDir = resolveAgentsDir(config);
|
|
71949
72237
|
const agentDir = resolve36(agentsDir, agentName);
|
|
71950
72238
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
71951
|
-
if (!
|
|
72239
|
+
if (!existsSync58(workspaceDir)) {
|
|
71952
72240
|
console.error(`soul: ${workspaceDir} does not exist yet. Run \`switchroom setup\` ` + `or \`switchroom agent scaffold ${agentName}\` to seed it.`);
|
|
71953
72241
|
process.exit(1);
|
|
71954
72242
|
}
|
|
@@ -71957,7 +72245,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
71957
72245
|
profileName,
|
|
71958
72246
|
profilePath,
|
|
71959
72247
|
workspaceDir,
|
|
71960
|
-
soulPath:
|
|
72248
|
+
soulPath: join52(workspaceDir, "SOUL.md"),
|
|
71961
72249
|
soul: merged.soul
|
|
71962
72250
|
};
|
|
71963
72251
|
}
|
|
@@ -71974,11 +72262,11 @@ function registerSoulCommand(program3) {
|
|
|
71974
72262
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
71975
72263
|
if (!t)
|
|
71976
72264
|
return;
|
|
71977
|
-
if (!
|
|
72265
|
+
if (!existsSync58(t.soulPath)) {
|
|
71978
72266
|
console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
|
|
71979
72267
|
process.exit(1);
|
|
71980
72268
|
}
|
|
71981
|
-
process.stdout.write(
|
|
72269
|
+
process.stdout.write(readFileSync51(t.soulPath, "utf-8"));
|
|
71982
72270
|
}));
|
|
71983
72271
|
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) => {
|
|
71984
72272
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
@@ -71989,7 +72277,7 @@ function registerSoulCommand(program3) {
|
|
|
71989
72277
|
console.error(`soul: profile "${t.profileName}" ships no SOUL.md.hbs \u2014 ` + `nothing to re-seed from.`);
|
|
71990
72278
|
process.exit(1);
|
|
71991
72279
|
}
|
|
71992
|
-
const exists =
|
|
72280
|
+
const exists = existsSync58(t.soulPath);
|
|
71993
72281
|
if (exists && !opts.yes) {
|
|
71994
72282
|
if (!isInteractive()) {
|
|
71995
72283
|
console.error(`soul: ${t.soulPath} already exists. Re-run with --yes to ` + `replace it (the current file is backed up to SOUL.md.bak).`);
|
|
@@ -72004,7 +72292,7 @@ function registerSoulCommand(program3) {
|
|
|
72004
72292
|
let backupPath;
|
|
72005
72293
|
if (exists) {
|
|
72006
72294
|
backupPath = `${t.soulPath}.bak`;
|
|
72007
|
-
if (
|
|
72295
|
+
if (existsSync58(backupPath)) {
|
|
72008
72296
|
backupPath = `${t.soulPath}.bak.${Date.now()}`;
|
|
72009
72297
|
}
|
|
72010
72298
|
copyFileSync10(t.soulPath, backupPath);
|
|
@@ -72023,9 +72311,9 @@ function registerSoulCommand(program3) {
|
|
|
72023
72311
|
// src/cli/debug.ts
|
|
72024
72312
|
init_helpers();
|
|
72025
72313
|
init_loader();
|
|
72026
|
-
import { existsSync as
|
|
72027
|
-
import { resolve as resolve37, join as
|
|
72028
|
-
import { createHash as
|
|
72314
|
+
import { existsSync as existsSync59, readFileSync as readFileSync52, readdirSync as readdirSync20, statSync as statSync25 } from "node:fs";
|
|
72315
|
+
import { resolve as resolve37, join as join53 } from "node:path";
|
|
72316
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
72029
72317
|
init_merge();
|
|
72030
72318
|
init_hindsight();
|
|
72031
72319
|
function formatBytes(bytes) {
|
|
@@ -72035,23 +72323,23 @@ function estimateTokens(bytes) {
|
|
|
72035
72323
|
return Math.round(bytes / 4);
|
|
72036
72324
|
}
|
|
72037
72325
|
function sha256(content) {
|
|
72038
|
-
return
|
|
72326
|
+
return createHash12("sha256").update(content).digest("hex").slice(0, 16);
|
|
72039
72327
|
}
|
|
72040
72328
|
function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
72041
|
-
const projectsDir =
|
|
72042
|
-
if (!
|
|
72329
|
+
const projectsDir = join53(claudeConfigDir, "projects");
|
|
72330
|
+
if (!existsSync59(projectsDir))
|
|
72043
72331
|
return;
|
|
72044
72332
|
try {
|
|
72045
|
-
const entries =
|
|
72333
|
+
const entries = readdirSync20(projectsDir, { withFileTypes: true });
|
|
72046
72334
|
let latest;
|
|
72047
72335
|
for (const entry of entries) {
|
|
72048
72336
|
if (!entry.isDirectory())
|
|
72049
72337
|
continue;
|
|
72050
|
-
const projectPath =
|
|
72051
|
-
const transcriptPath =
|
|
72052
|
-
if (!
|
|
72338
|
+
const projectPath = join53(projectsDir, entry.name);
|
|
72339
|
+
const transcriptPath = join53(projectPath, "transcript.jsonl");
|
|
72340
|
+
if (!existsSync59(transcriptPath))
|
|
72053
72341
|
continue;
|
|
72054
|
-
const stat3 =
|
|
72342
|
+
const stat3 = statSync25(transcriptPath);
|
|
72055
72343
|
if (!latest || stat3.mtimeMs > latest.mtime) {
|
|
72056
72344
|
latest = { path: transcriptPath, mtime: stat3.mtimeMs };
|
|
72057
72345
|
}
|
|
@@ -72063,7 +72351,7 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
72063
72351
|
}
|
|
72064
72352
|
function extractLatestUserMessage(transcriptPath) {
|
|
72065
72353
|
try {
|
|
72066
|
-
const content =
|
|
72354
|
+
const content = readFileSync52(transcriptPath, "utf-8");
|
|
72067
72355
|
const lines = content.trim().split(`
|
|
72068
72356
|
`).filter(Boolean);
|
|
72069
72357
|
for (let i = lines.length - 1;i >= 0; i--) {
|
|
@@ -72112,16 +72400,16 @@ function registerDebugCommand(program3) {
|
|
|
72112
72400
|
}
|
|
72113
72401
|
const agentsDir = resolveAgentsDir(config);
|
|
72114
72402
|
const agentDir = resolve37(agentsDir, agentName);
|
|
72115
|
-
if (!
|
|
72403
|
+
if (!existsSync59(agentDir)) {
|
|
72116
72404
|
console.error(`Agent directory not found: ${agentDir}`);
|
|
72117
72405
|
process.exit(1);
|
|
72118
72406
|
}
|
|
72119
72407
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
72120
|
-
const claudeConfigDir =
|
|
72121
|
-
const claudeMdPath =
|
|
72122
|
-
const soulMdPath =
|
|
72123
|
-
const workspaceSoulMdPath =
|
|
72124
|
-
const handoffPath =
|
|
72408
|
+
const claudeConfigDir = join53(agentDir, ".claude");
|
|
72409
|
+
const claudeMdPath = join53(agentDir, "CLAUDE.md");
|
|
72410
|
+
const soulMdPath = join53(agentDir, "SOUL.md");
|
|
72411
|
+
const workspaceSoulMdPath = join53(workspaceDir, "SOUL.md");
|
|
72412
|
+
const handoffPath = join53(agentDir, ".handoff.md");
|
|
72125
72413
|
const lastN = parseInt(opts.last, 10);
|
|
72126
72414
|
if (isNaN(lastN) || lastN < 1) {
|
|
72127
72415
|
console.error("--last must be a positive integer");
|
|
@@ -72167,7 +72455,7 @@ function registerDebugCommand(program3) {
|
|
|
72167
72455
|
}
|
|
72168
72456
|
console.log(`=== Append System Prompt (per-session) ===
|
|
72169
72457
|
`);
|
|
72170
|
-
const handoffContent =
|
|
72458
|
+
const handoffContent = existsSync59(handoffPath) ? readFileSync52(handoffPath, "utf-8") : "";
|
|
72171
72459
|
if (handoffContent.trim().length > 0) {
|
|
72172
72460
|
console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
|
|
72173
72461
|
console.log(handoffContent);
|
|
@@ -72178,7 +72466,7 @@ function registerDebugCommand(program3) {
|
|
|
72178
72466
|
}
|
|
72179
72467
|
console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
|
|
72180
72468
|
`);
|
|
72181
|
-
const claudeMdContent =
|
|
72469
|
+
const claudeMdContent = existsSync59(claudeMdPath) ? readFileSync52(claudeMdPath, "utf-8") : "";
|
|
72182
72470
|
if (claudeMdContent.trim().length > 0) {
|
|
72183
72471
|
console.log(`(${formatBytes(claudeMdContent.length)})`);
|
|
72184
72472
|
console.log(claudeMdContent);
|
|
@@ -72189,7 +72477,7 @@ function registerDebugCommand(program3) {
|
|
|
72189
72477
|
}
|
|
72190
72478
|
console.log(`=== Persona (SOUL.md) ===
|
|
72191
72479
|
`);
|
|
72192
|
-
const soulMdContent =
|
|
72480
|
+
const soulMdContent = existsSync59(soulMdPath) ? readFileSync52(soulMdPath, "utf-8") : existsSync59(workspaceSoulMdPath) ? readFileSync52(workspaceSoulMdPath, "utf-8") : "";
|
|
72193
72481
|
if (soulMdContent.trim().length > 0) {
|
|
72194
72482
|
console.log(`(${formatBytes(soulMdContent.length)})`);
|
|
72195
72483
|
console.log(soulMdContent);
|
|
@@ -72270,31 +72558,31 @@ init_source();
|
|
|
72270
72558
|
|
|
72271
72559
|
// src/worktree/claim.ts
|
|
72272
72560
|
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
72273
|
-
import { closeSync as
|
|
72274
|
-
import { join as
|
|
72275
|
-
import { homedir as
|
|
72276
|
-
import { randomBytes as
|
|
72561
|
+
import { closeSync as closeSync12, mkdirSync as mkdirSync33, openSync as openSync12, existsSync as existsSync61, unlinkSync as unlinkSync13 } from "node:fs";
|
|
72562
|
+
import { join as join55, resolve as resolve39 } from "node:path";
|
|
72563
|
+
import { homedir as homedir31 } from "node:os";
|
|
72564
|
+
import { randomBytes as randomBytes12 } from "node:crypto";
|
|
72277
72565
|
|
|
72278
72566
|
// src/worktree/registry.ts
|
|
72279
72567
|
import {
|
|
72280
|
-
mkdirSync as
|
|
72568
|
+
mkdirSync as mkdirSync32,
|
|
72281
72569
|
writeFileSync as writeFileSync29,
|
|
72282
|
-
readFileSync as
|
|
72283
|
-
readdirSync as
|
|
72284
|
-
unlinkSync as
|
|
72285
|
-
existsSync as
|
|
72286
|
-
renameSync as
|
|
72570
|
+
readFileSync as readFileSync53,
|
|
72571
|
+
readdirSync as readdirSync21,
|
|
72572
|
+
unlinkSync as unlinkSync12,
|
|
72573
|
+
existsSync as existsSync60,
|
|
72574
|
+
renameSync as renameSync12
|
|
72287
72575
|
} from "node:fs";
|
|
72288
|
-
import { join as
|
|
72289
|
-
import { homedir as
|
|
72576
|
+
import { join as join54, resolve as resolve38 } from "node:path";
|
|
72577
|
+
import { homedir as homedir30 } from "node:os";
|
|
72290
72578
|
function registryDir() {
|
|
72291
|
-
return resolve38(process.env.SWITCHROOM_WORKTREE_DIR ??
|
|
72579
|
+
return resolve38(process.env.SWITCHROOM_WORKTREE_DIR ?? join54(homedir30(), ".switchroom", "worktrees"));
|
|
72292
72580
|
}
|
|
72293
72581
|
function recordPath(id) {
|
|
72294
|
-
return
|
|
72582
|
+
return join54(registryDir(), `${id}.json`);
|
|
72295
72583
|
}
|
|
72296
72584
|
function ensureDir2() {
|
|
72297
|
-
|
|
72585
|
+
mkdirSync32(registryDir(), { recursive: true });
|
|
72298
72586
|
}
|
|
72299
72587
|
function writeRecord(record2) {
|
|
72300
72588
|
ensureDir2();
|
|
@@ -72302,12 +72590,12 @@ function writeRecord(record2) {
|
|
|
72302
72590
|
const tmp = `${target}.tmp${process.pid}`;
|
|
72303
72591
|
writeFileSync29(tmp, JSON.stringify(record2, null, 2) + `
|
|
72304
72592
|
`, { mode: 384 });
|
|
72305
|
-
|
|
72593
|
+
renameSync12(tmp, target);
|
|
72306
72594
|
}
|
|
72307
72595
|
function readRecord(id) {
|
|
72308
72596
|
const path7 = recordPath(id);
|
|
72309
72597
|
try {
|
|
72310
|
-
const raw =
|
|
72598
|
+
const raw = readFileSync53(path7, "utf8");
|
|
72311
72599
|
return JSON.parse(raw);
|
|
72312
72600
|
} catch {
|
|
72313
72601
|
return null;
|
|
@@ -72316,14 +72604,14 @@ function readRecord(id) {
|
|
|
72316
72604
|
function deleteRecord(id) {
|
|
72317
72605
|
const path7 = recordPath(id);
|
|
72318
72606
|
try {
|
|
72319
|
-
|
|
72607
|
+
unlinkSync12(path7);
|
|
72320
72608
|
} catch {}
|
|
72321
72609
|
}
|
|
72322
72610
|
function listRecords() {
|
|
72323
72611
|
ensureDir2();
|
|
72324
72612
|
const dir = registryDir();
|
|
72325
72613
|
const records = [];
|
|
72326
|
-
for (const entry of
|
|
72614
|
+
for (const entry of readdirSync21(dir)) {
|
|
72327
72615
|
if (!entry.endsWith(".json"))
|
|
72328
72616
|
continue;
|
|
72329
72617
|
const id = entry.slice(0, -5);
|
|
@@ -72340,14 +72628,14 @@ function countByRepo(repoPath) {
|
|
|
72340
72628
|
// src/worktree/claim.ts
|
|
72341
72629
|
function acquireRepoLock(repoPath) {
|
|
72342
72630
|
const lockDir = registryDir();
|
|
72343
|
-
|
|
72631
|
+
mkdirSync33(lockDir, { recursive: true });
|
|
72344
72632
|
const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
|
|
72345
|
-
const lockPath =
|
|
72633
|
+
const lockPath = join55(lockDir, `.lock-${lockName}`);
|
|
72346
72634
|
const deadline = Date.now() + 5000;
|
|
72347
72635
|
let fd = null;
|
|
72348
72636
|
while (fd === null) {
|
|
72349
72637
|
try {
|
|
72350
|
-
fd =
|
|
72638
|
+
fd = openSync12(lockPath, "wx");
|
|
72351
72639
|
} catch (err) {
|
|
72352
72640
|
if (err.code !== "EEXIST")
|
|
72353
72641
|
throw err;
|
|
@@ -72360,19 +72648,19 @@ function acquireRepoLock(repoPath) {
|
|
|
72360
72648
|
}
|
|
72361
72649
|
return () => {
|
|
72362
72650
|
try {
|
|
72363
|
-
|
|
72651
|
+
closeSync12(fd);
|
|
72364
72652
|
} catch {}
|
|
72365
72653
|
try {
|
|
72366
|
-
|
|
72654
|
+
unlinkSync13(lockPath);
|
|
72367
72655
|
} catch {}
|
|
72368
72656
|
};
|
|
72369
72657
|
}
|
|
72370
72658
|
var DEFAULT_CONCURRENCY = 5;
|
|
72371
72659
|
function worktreesBaseDir() {
|
|
72372
|
-
return resolve39(process.env.SWITCHROOM_WORKTREE_BASE ??
|
|
72660
|
+
return resolve39(process.env.SWITCHROOM_WORKTREE_BASE ?? join55(homedir31(), ".switchroom", "worktree-checkouts"));
|
|
72373
72661
|
}
|
|
72374
72662
|
function shortId() {
|
|
72375
|
-
return
|
|
72663
|
+
return randomBytes12(4).toString("hex");
|
|
72376
72664
|
}
|
|
72377
72665
|
function sanitizeTaskName(name) {
|
|
72378
72666
|
return name.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
@@ -72391,12 +72679,12 @@ function resolveRepoPath(repo, codeRepos) {
|
|
|
72391
72679
|
}
|
|
72392
72680
|
function expandHome(p) {
|
|
72393
72681
|
if (p.startsWith("~/"))
|
|
72394
|
-
return
|
|
72682
|
+
return join55(homedir31(), p.slice(2));
|
|
72395
72683
|
return p;
|
|
72396
72684
|
}
|
|
72397
72685
|
async function claimWorktree(input, codeRepos) {
|
|
72398
72686
|
const repoPath = resolveRepoPath(input.repo, codeRepos);
|
|
72399
|
-
if (!
|
|
72687
|
+
if (!existsSync61(repoPath)) {
|
|
72400
72688
|
throw new Error(`Repository path does not exist: ${repoPath}`);
|
|
72401
72689
|
}
|
|
72402
72690
|
let concurrencyCap = DEFAULT_CONCURRENCY;
|
|
@@ -72418,8 +72706,8 @@ async function claimWorktree(input, codeRepos) {
|
|
|
72418
72706
|
const taskSuffix = input.taskName ? sanitizeTaskName(input.taskName) : "task";
|
|
72419
72707
|
branch = `task/${taskSuffix}-${id}`;
|
|
72420
72708
|
const baseDir = worktreesBaseDir();
|
|
72421
|
-
|
|
72422
|
-
worktreePath =
|
|
72709
|
+
mkdirSync33(baseDir, { recursive: true });
|
|
72710
|
+
worktreePath = join55(baseDir, `${id}-${taskSuffix}`);
|
|
72423
72711
|
const now = new Date().toISOString();
|
|
72424
72712
|
const record2 = {
|
|
72425
72713
|
id,
|
|
@@ -72450,7 +72738,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
72450
72738
|
|
|
72451
72739
|
// src/worktree/release.ts
|
|
72452
72740
|
import { execFileSync as execFileSync17 } from "node:child_process";
|
|
72453
|
-
import { existsSync as
|
|
72741
|
+
import { existsSync as existsSync62 } from "node:fs";
|
|
72454
72742
|
function releaseWorktree(input) {
|
|
72455
72743
|
const { id } = input;
|
|
72456
72744
|
const record2 = readRecord(id);
|
|
@@ -72458,7 +72746,7 @@ function releaseWorktree(input) {
|
|
|
72458
72746
|
return { released: true };
|
|
72459
72747
|
}
|
|
72460
72748
|
let gitSuccess = true;
|
|
72461
|
-
if (
|
|
72749
|
+
if (existsSync62(record2.path)) {
|
|
72462
72750
|
try {
|
|
72463
72751
|
execFileSync17("git", ["worktree", "remove", "--force", record2.path], {
|
|
72464
72752
|
cwd: record2.repo,
|
|
@@ -72497,7 +72785,7 @@ function listWorktrees() {
|
|
|
72497
72785
|
|
|
72498
72786
|
// src/worktree/reaper.ts
|
|
72499
72787
|
import { execFileSync as execFileSync18 } from "node:child_process";
|
|
72500
|
-
import { existsSync as
|
|
72788
|
+
import { existsSync as existsSync63 } from "node:fs";
|
|
72501
72789
|
var STALE_THRESHOLD_MS = 10 * 60 * 1000;
|
|
72502
72790
|
function isPathInUse(path7) {
|
|
72503
72791
|
try {
|
|
@@ -72524,7 +72812,7 @@ function hasUncommittedChanges(repoPath, worktreePath) {
|
|
|
72524
72812
|
function reapRecord(record2) {
|
|
72525
72813
|
const { id, path: path7, repo, branch, ownerAgent } = record2;
|
|
72526
72814
|
let warning = null;
|
|
72527
|
-
if (
|
|
72815
|
+
if (existsSync63(path7)) {
|
|
72528
72816
|
if (hasUncommittedChanges(repo, path7)) {
|
|
72529
72817
|
warning = `[worktree-reaper] Reaped worktree with uncommitted changes: ` + `id=${id} branch=${branch} agent=${ownerAgent ?? "unknown"} path=${path7}`;
|
|
72530
72818
|
}
|
|
@@ -72545,7 +72833,7 @@ function runReaper(nowMs) {
|
|
|
72545
72833
|
const warnings = [];
|
|
72546
72834
|
for (const record2 of records) {
|
|
72547
72835
|
const heartbeatAge = now - new Date(record2.heartbeatAt).getTime();
|
|
72548
|
-
const worktreeExists =
|
|
72836
|
+
const worktreeExists = existsSync63(record2.path);
|
|
72549
72837
|
if (!worktreeExists) {
|
|
72550
72838
|
deleteRecord(record2.id);
|
|
72551
72839
|
reaped.push(record2.id);
|
|
@@ -72669,12 +72957,12 @@ init_drive();
|
|
|
72669
72957
|
init_scaffold_integration();
|
|
72670
72958
|
import {
|
|
72671
72959
|
chmodSync as chmodSync9,
|
|
72672
|
-
mkdirSync as
|
|
72673
|
-
readdirSync as
|
|
72960
|
+
mkdirSync as mkdirSync34,
|
|
72961
|
+
readdirSync as readdirSync22,
|
|
72674
72962
|
rmSync as rmSync15,
|
|
72675
72963
|
writeFileSync as writeFileSync30
|
|
72676
72964
|
} from "node:fs";
|
|
72677
|
-
import { join as
|
|
72965
|
+
import { join as join56 } from "node:path";
|
|
72678
72966
|
function encodeCredentialsFilename(email) {
|
|
72679
72967
|
const SAFE = new Set([
|
|
72680
72968
|
..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
|
@@ -72838,16 +73126,16 @@ function resolveCredentialsDir(env2) {
|
|
|
72838
73126
|
if (explicit && explicit.length > 0)
|
|
72839
73127
|
return explicit;
|
|
72840
73128
|
const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
|
|
72841
|
-
return
|
|
73129
|
+
return join56(stateBase, "google-workspace-mcp", "credentials");
|
|
72842
73130
|
}
|
|
72843
73131
|
function writeSeedFile(dir, email, seed) {
|
|
72844
|
-
|
|
73132
|
+
mkdirSync34(dir, { recursive: true, mode: 448 });
|
|
72845
73133
|
chmodSync9(dir, 448);
|
|
72846
|
-
for (const name of
|
|
72847
|
-
rmSync15(
|
|
73134
|
+
for (const name of readdirSync22(dir)) {
|
|
73135
|
+
rmSync15(join56(dir, name), { force: true, recursive: true });
|
|
72848
73136
|
}
|
|
72849
73137
|
const filename = encodeCredentialsFilename(email);
|
|
72850
|
-
const filePath =
|
|
73138
|
+
const filePath = join56(dir, filename);
|
|
72851
73139
|
writeFileSync30(filePath, JSON.stringify(seed), { mode: 384 });
|
|
72852
73140
|
chmodSync9(filePath, 384);
|
|
72853
73141
|
return filePath;
|
|
@@ -73000,7 +73288,7 @@ function registerDriveMcpLauncherCommand(program3) {
|
|
|
73000
73288
|
|
|
73001
73289
|
// src/cli/apply.ts
|
|
73002
73290
|
init_source();
|
|
73003
|
-
import { accessSync as accessSync3, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as
|
|
73291
|
+
import { accessSync as accessSync3, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as existsSync67, mkdirSync as mkdirSync36, readdirSync as readdirSync24, renameSync as renameSync13, writeFileSync as writeFileSync32 } from "node:fs";
|
|
73004
73292
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
73005
73293
|
import { spawnSync as childSpawnSync } from "node:child_process";
|
|
73006
73294
|
import readline from "node:readline";
|
|
@@ -73345,16 +73633,16 @@ agents:
|
|
|
73345
73633
|
|
|
73346
73634
|
// src/cli/apply.ts
|
|
73347
73635
|
init_resolver();
|
|
73348
|
-
import { dirname as dirname19, join as
|
|
73349
|
-
import { homedir as
|
|
73636
|
+
import { dirname as dirname19, join as join60, resolve as resolve41 } from "node:path";
|
|
73637
|
+
import { homedir as homedir33 } from "node:os";
|
|
73350
73638
|
import { execFileSync as execFileSync19 } from "node:child_process";
|
|
73351
73639
|
init_vault();
|
|
73352
73640
|
init_loader();
|
|
73353
73641
|
init_loader();
|
|
73354
73642
|
|
|
73355
73643
|
// src/cli/update-prompt-hook.ts
|
|
73356
|
-
import { existsSync as
|
|
73357
|
-
import { join as
|
|
73644
|
+
import { existsSync as existsSync64, readFileSync as readFileSync54, writeFileSync as writeFileSync31, chmodSync as chmodSync10, mkdirSync as mkdirSync35 } from "node:fs";
|
|
73645
|
+
import { join as join57 } from "node:path";
|
|
73358
73646
|
var HOOK_FILENAME = "update-card-on-prompt.sh";
|
|
73359
73647
|
function updatePromptHookScript() {
|
|
73360
73648
|
return `#!/bin/bash
|
|
@@ -73420,12 +73708,12 @@ exit 0
|
|
|
73420
73708
|
`;
|
|
73421
73709
|
}
|
|
73422
73710
|
function installUpdatePromptHook(agentDir) {
|
|
73423
|
-
const hooksDir =
|
|
73424
|
-
|
|
73425
|
-
const scriptPath =
|
|
73711
|
+
const hooksDir = join57(agentDir, ".claude", "hooks");
|
|
73712
|
+
mkdirSync35(hooksDir, { recursive: true });
|
|
73713
|
+
const scriptPath = join57(hooksDir, HOOK_FILENAME);
|
|
73426
73714
|
const desired = updatePromptHookScript();
|
|
73427
73715
|
let installed = false;
|
|
73428
|
-
const existing =
|
|
73716
|
+
const existing = existsSync64(scriptPath) ? readFileSync54(scriptPath, "utf-8") : "";
|
|
73429
73717
|
if (existing !== desired) {
|
|
73430
73718
|
writeFileSync31(scriptPath, desired, { mode: 493 });
|
|
73431
73719
|
chmodSync10(scriptPath, 493);
|
|
@@ -73435,11 +73723,11 @@ function installUpdatePromptHook(agentDir) {
|
|
|
73435
73723
|
chmodSync10(scriptPath, 493);
|
|
73436
73724
|
} catch {}
|
|
73437
73725
|
}
|
|
73438
|
-
const settingsPath =
|
|
73439
|
-
if (!
|
|
73726
|
+
const settingsPath = join57(agentDir, ".claude", "settings.json");
|
|
73727
|
+
if (!existsSync64(settingsPath)) {
|
|
73440
73728
|
return { scriptPath, settingsPath, installed };
|
|
73441
73729
|
}
|
|
73442
|
-
const raw =
|
|
73730
|
+
const raw = readFileSync54(settingsPath, "utf-8");
|
|
73443
73731
|
let parsed;
|
|
73444
73732
|
try {
|
|
73445
73733
|
parsed = JSON.parse(raw);
|
|
@@ -73555,13 +73843,13 @@ function detectInstallType() {
|
|
|
73555
73843
|
// src/cli/operator-uid.ts
|
|
73556
73844
|
import {
|
|
73557
73845
|
chownSync as chownSync2,
|
|
73558
|
-
existsSync as
|
|
73846
|
+
existsSync as existsSync66,
|
|
73559
73847
|
lstatSync as lstatSync7,
|
|
73560
|
-
readdirSync as
|
|
73848
|
+
readdirSync as readdirSync23,
|
|
73561
73849
|
realpathSync as realpathSync6,
|
|
73562
|
-
statSync as
|
|
73850
|
+
statSync as statSync26
|
|
73563
73851
|
} from "node:fs";
|
|
73564
|
-
import { join as
|
|
73852
|
+
import { join as join59 } from "node:path";
|
|
73565
73853
|
function resolveOperatorUid() {
|
|
73566
73854
|
const sudoUid = process.env.SUDO_UID;
|
|
73567
73855
|
if (sudoUid !== undefined) {
|
|
@@ -73577,19 +73865,19 @@ function resolveOperatorUid() {
|
|
|
73577
73865
|
return;
|
|
73578
73866
|
}
|
|
73579
73867
|
function operatorOwnedPaths(home2) {
|
|
73580
|
-
const root =
|
|
73868
|
+
const root = join59(home2, ".switchroom");
|
|
73581
73869
|
return [
|
|
73582
|
-
|
|
73583
|
-
|
|
73584
|
-
|
|
73585
|
-
|
|
73586
|
-
|
|
73587
|
-
|
|
73870
|
+
join59(root, "vault"),
|
|
73871
|
+
join59(root, "vault-auto-unlock"),
|
|
73872
|
+
join59(root, "vault-audit.log"),
|
|
73873
|
+
join59(root, "host-control-audit.log"),
|
|
73874
|
+
join59(root, "accounts"),
|
|
73875
|
+
join59(root, "compose")
|
|
73588
73876
|
];
|
|
73589
73877
|
}
|
|
73590
73878
|
function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
|
|
73591
73879
|
const chown = deps.chown ?? ((p, u, g) => chownSync2(p, u, g));
|
|
73592
|
-
const exists = deps.exists ?? ((p) =>
|
|
73880
|
+
const exists = deps.exists ?? ((p) => existsSync66(p));
|
|
73593
73881
|
const isSymlink = deps.isSymlink ?? ((p) => {
|
|
73594
73882
|
try {
|
|
73595
73883
|
return lstatSync7(p).isSymbolicLink();
|
|
@@ -73599,7 +73887,7 @@ function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
|
|
|
73599
73887
|
});
|
|
73600
73888
|
const isDir = deps.isDir ?? ((p) => {
|
|
73601
73889
|
try {
|
|
73602
|
-
return
|
|
73890
|
+
return statSync26(p).isDirectory();
|
|
73603
73891
|
} catch {
|
|
73604
73892
|
return false;
|
|
73605
73893
|
}
|
|
@@ -73613,7 +73901,7 @@ function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
|
|
|
73613
73901
|
});
|
|
73614
73902
|
const readdir2 = deps.readdir ?? ((p) => {
|
|
73615
73903
|
try {
|
|
73616
|
-
return
|
|
73904
|
+
return readdirSync23(p);
|
|
73617
73905
|
} catch {
|
|
73618
73906
|
return [];
|
|
73619
73907
|
}
|
|
@@ -73633,7 +73921,7 @@ function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
|
|
|
73633
73921
|
} catch {}
|
|
73634
73922
|
if (isDir(target)) {
|
|
73635
73923
|
for (const entry of readdir2(target)) {
|
|
73636
|
-
visit(
|
|
73924
|
+
visit(join59(target, entry));
|
|
73637
73925
|
}
|
|
73638
73926
|
}
|
|
73639
73927
|
};
|
|
@@ -73647,19 +73935,19 @@ var EMBEDDED_EXAMPLES = {
|
|
|
73647
73935
|
switchroom: switchroom_default,
|
|
73648
73936
|
minimal: minimal_default
|
|
73649
73937
|
};
|
|
73650
|
-
var DEFAULT_COMPOSE_PATH2 =
|
|
73938
|
+
var DEFAULT_COMPOSE_PATH2 = join60(homedir33(), ".switchroom", "compose", "docker-compose.yml");
|
|
73651
73939
|
var COMPOSE_PROJECT2 = "switchroom";
|
|
73652
73940
|
function resolveVaultBindMountDir(homeDir, ctx) {
|
|
73653
73941
|
const isCustomPath = ctx.migrationKind === "custom-path-skipped";
|
|
73654
73942
|
if (isCustomPath && ctx.customVaultPath) {
|
|
73655
73943
|
return dirname19(ctx.customVaultPath);
|
|
73656
73944
|
}
|
|
73657
|
-
return
|
|
73945
|
+
return join60(homeDir, ".switchroom", "vault");
|
|
73658
73946
|
}
|
|
73659
73947
|
function inspectVaultBindMountDir(vaultDir) {
|
|
73660
|
-
if (!
|
|
73948
|
+
if (!existsSync67(vaultDir))
|
|
73661
73949
|
return { kind: "missing" };
|
|
73662
|
-
const entries =
|
|
73950
|
+
const entries = readdirSync24(vaultDir);
|
|
73663
73951
|
const unknown = [];
|
|
73664
73952
|
for (const name of entries) {
|
|
73665
73953
|
if (KNOWN_VAULT_ARTIFACT_NAMES.has(name))
|
|
@@ -73683,32 +73971,32 @@ function hasVaultRefs(value) {
|
|
|
73683
73971
|
return false;
|
|
73684
73972
|
}
|
|
73685
73973
|
async function ensureHostMountSources(config) {
|
|
73686
|
-
const home2 =
|
|
73974
|
+
const home2 = homedir33();
|
|
73687
73975
|
const dirs = [
|
|
73688
|
-
|
|
73689
|
-
|
|
73690
|
-
|
|
73691
|
-
|
|
73692
|
-
|
|
73976
|
+
join60(home2, ".switchroom", "approvals"),
|
|
73977
|
+
join60(home2, ".switchroom", "scheduler"),
|
|
73978
|
+
join60(home2, ".switchroom", "logs"),
|
|
73979
|
+
join60(home2, ".switchroom", "compose"),
|
|
73980
|
+
join60(home2, ".switchroom", "broker-operator")
|
|
73693
73981
|
];
|
|
73694
73982
|
for (const name of Object.keys(config.agents)) {
|
|
73695
|
-
dirs.push(
|
|
73696
|
-
dirs.push(
|
|
73697
|
-
dirs.push(
|
|
73983
|
+
dirs.push(join60(home2, ".switchroom", "agents", name));
|
|
73984
|
+
dirs.push(join60(home2, ".switchroom", "logs", name));
|
|
73985
|
+
dirs.push(join60(home2, ".claude", "projects", name));
|
|
73698
73986
|
}
|
|
73699
73987
|
for (const dir of dirs) {
|
|
73700
73988
|
await mkdir(dir, { recursive: true });
|
|
73701
73989
|
}
|
|
73702
|
-
const autoUnlockPath =
|
|
73703
|
-
if (!
|
|
73990
|
+
const autoUnlockPath = join60(home2, ".switchroom", "vault-auto-unlock");
|
|
73991
|
+
if (!existsSync67(autoUnlockPath)) {
|
|
73704
73992
|
writeFileSync32(autoUnlockPath, "", { mode: 384 });
|
|
73705
73993
|
}
|
|
73706
|
-
const auditLogPath =
|
|
73707
|
-
if (!
|
|
73994
|
+
const auditLogPath = join60(home2, ".switchroom", "vault-audit.log");
|
|
73995
|
+
if (!existsSync67(auditLogPath)) {
|
|
73708
73996
|
writeFileSync32(auditLogPath, "", { mode: 420 });
|
|
73709
73997
|
}
|
|
73710
|
-
const hostdAuditLogPath =
|
|
73711
|
-
if (!
|
|
73998
|
+
const hostdAuditLogPath = join60(home2, ".switchroom", "host-control-audit.log");
|
|
73999
|
+
if (!existsSync67(hostdAuditLogPath)) {
|
|
73712
74000
|
writeFileSync32(hostdAuditLogPath, "", { mode: 420 });
|
|
73713
74001
|
}
|
|
73714
74002
|
}
|
|
@@ -73729,7 +74017,7 @@ ${out.trim()}`;
|
|
|
73729
74017
|
}
|
|
73730
74018
|
function runApplyPreflight(config, opts = {}) {
|
|
73731
74019
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
73732
|
-
if (hasVaultRefs(config) && !
|
|
74020
|
+
if (hasVaultRefs(config) && !existsSync67(vaultPath)) {
|
|
73733
74021
|
throw new Error(`Config references vault keys (vault:<name>) but ${vaultPath} is missing. ` + `Run \`switchroom setup\` first to initialise the vault.`);
|
|
73734
74022
|
}
|
|
73735
74023
|
const detect = opts.detectComposeV2 ?? detectComposeV2;
|
|
@@ -73740,7 +74028,7 @@ function runApplyPreflight(config, opts = {}) {
|
|
|
73740
74028
|
detectAndReportLegacyGdriveSlots(vaultPath);
|
|
73741
74029
|
}
|
|
73742
74030
|
function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
73743
|
-
if (!
|
|
74031
|
+
if (!existsSync67(vaultPath))
|
|
73744
74032
|
return;
|
|
73745
74033
|
const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
73746
74034
|
if (!passphrase)
|
|
@@ -73779,19 +74067,19 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
|
73779
74067
|
`));
|
|
73780
74068
|
}
|
|
73781
74069
|
}
|
|
73782
|
-
function writeInstallTypeCache(homeDir =
|
|
74070
|
+
function writeInstallTypeCache(homeDir = homedir33()) {
|
|
73783
74071
|
const ctx = detectInstallType();
|
|
73784
|
-
const dir =
|
|
73785
|
-
const out =
|
|
74072
|
+
const dir = join60(homeDir, ".switchroom");
|
|
74073
|
+
const out = join60(dir, "install-type.json");
|
|
73786
74074
|
const tmp = `${out}.tmp`;
|
|
73787
|
-
|
|
74075
|
+
mkdirSync36(dir, { recursive: true });
|
|
73788
74076
|
const payload = {
|
|
73789
74077
|
install_type: ctx.install_type,
|
|
73790
74078
|
detected_at: new Date().toISOString(),
|
|
73791
74079
|
source_paths: ctx.source_paths
|
|
73792
74080
|
};
|
|
73793
74081
|
writeFileSync32(tmp, JSON.stringify(payload, null, 2), { mode: 420 });
|
|
73794
|
-
|
|
74082
|
+
renameSync13(tmp, out);
|
|
73795
74083
|
return out;
|
|
73796
74084
|
}
|
|
73797
74085
|
async function runApply(config, options, deps = {}, switchroomConfigPath) {
|
|
@@ -73831,14 +74119,14 @@ Applying switchroom config...
|
|
|
73831
74119
|
writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
|
|
73832
74120
|
`));
|
|
73833
74121
|
try {
|
|
73834
|
-
installUpdatePromptHook(
|
|
74122
|
+
installUpdatePromptHook(join60(agentsDir, name));
|
|
73835
74123
|
} catch (hookErr) {
|
|
73836
74124
|
writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
|
|
73837
74125
|
`));
|
|
73838
74126
|
}
|
|
73839
74127
|
try {
|
|
73840
74128
|
const uid = allocateAgentUid(name);
|
|
73841
|
-
alignAgentUid(name,
|
|
74129
|
+
alignAgentUid(name, join60(agentsDir, name), uid, {
|
|
73842
74130
|
confirm: !options.nonInteractive,
|
|
73843
74131
|
writeOut
|
|
73844
74132
|
});
|
|
@@ -73875,7 +74163,7 @@ Applying switchroom config...
|
|
|
73875
74163
|
for (const name of agentNames) {
|
|
73876
74164
|
try {
|
|
73877
74165
|
const uid = allocateAgentUid(name);
|
|
73878
|
-
alignAgentUid(name,
|
|
74166
|
+
alignAgentUid(name, join60(agentsDir, name), uid, {
|
|
73879
74167
|
confirm: !options.nonInteractive,
|
|
73880
74168
|
writeOut
|
|
73881
74169
|
});
|
|
@@ -73889,7 +74177,7 @@ Applying switchroom config...
|
|
|
73889
74177
|
}
|
|
73890
74178
|
const vaultPathConfigured = config.vault?.path;
|
|
73891
74179
|
const customVaultPath = vaultPathConfigured ? resolvePath(vaultPathConfigured) : undefined;
|
|
73892
|
-
const migrationResult = migrateVaultLayout(
|
|
74180
|
+
const migrationResult = migrateVaultLayout(homedir33(), {
|
|
73893
74181
|
customVaultPath
|
|
73894
74182
|
});
|
|
73895
74183
|
switch (migrationResult.kind) {
|
|
@@ -73915,7 +74203,7 @@ Applying switchroom config...
|
|
|
73915
74203
|
writeErr(formatDivergentRecoveryMessage(migrationResult.details));
|
|
73916
74204
|
process.exit(4);
|
|
73917
74205
|
}
|
|
73918
|
-
const postMigrationInspect = inspectVaultLayout(
|
|
74206
|
+
const postMigrationInspect = inspectVaultLayout(homedir33());
|
|
73919
74207
|
const acceptable = [
|
|
73920
74208
|
"no-vault",
|
|
73921
74209
|
"already-migrated",
|
|
@@ -73930,7 +74218,7 @@ Applying switchroom config...
|
|
|
73930
74218
|
`));
|
|
73931
74219
|
process.exit(5);
|
|
73932
74220
|
}
|
|
73933
|
-
const vaultDir = resolveVaultBindMountDir(
|
|
74221
|
+
const vaultDir = resolveVaultBindMountDir(homedir33(), {
|
|
73934
74222
|
migrationKind: migrationResult.kind,
|
|
73935
74223
|
customVaultPath
|
|
73936
74224
|
});
|
|
@@ -73960,7 +74248,7 @@ Applying switchroom config...
|
|
|
73960
74248
|
imageTag: composeImageTag,
|
|
73961
74249
|
buildMode: options.buildLocal ? "local" : "pull",
|
|
73962
74250
|
buildContext: options.buildContext,
|
|
73963
|
-
homeDir:
|
|
74251
|
+
homeDir: homedir33(),
|
|
73964
74252
|
switchroomConfigPath,
|
|
73965
74253
|
operatorUid
|
|
73966
74254
|
});
|
|
@@ -73980,7 +74268,7 @@ Wrote `) + composePath + source_default.gray(` (${composeBytes} bytes)
|
|
|
73980
74268
|
writeOut(source_default.gray(` (If pull returns 401, login to ghcr.io first: see docs/operators/install.md#ghcr-auth)
|
|
73981
74269
|
`));
|
|
73982
74270
|
if (process.geteuid?.() === 0 && operatorUid !== undefined) {
|
|
73983
|
-
const restored = restoreOperatorOwnership(
|
|
74271
|
+
const restored = restoreOperatorOwnership(homedir33(), operatorUid);
|
|
73984
74272
|
if (restored.length > 0) {
|
|
73985
74273
|
writeOut(source_default.gray(` Restored operator ownership of ${restored.length} ~/.switchroom path(s)
|
|
73986
74274
|
`));
|
|
@@ -74028,7 +74316,7 @@ function copyExampleConfig2(name) {
|
|
|
74028
74316
|
throw new Error(`Invalid example name: ${name} (must match /^[a-z0-9_-]+$/)`);
|
|
74029
74317
|
}
|
|
74030
74318
|
const dest = resolve41(process.cwd(), "switchroom.yaml");
|
|
74031
|
-
if (
|
|
74319
|
+
if (existsSync67(dest)) {
|
|
74032
74320
|
console.error(source_default.yellow("switchroom.yaml already exists \u2014 skipping example copy"));
|
|
74033
74321
|
return;
|
|
74034
74322
|
}
|
|
@@ -74039,7 +74327,7 @@ function copyExampleConfig2(name) {
|
|
|
74039
74327
|
return;
|
|
74040
74328
|
}
|
|
74041
74329
|
const exampleFile = resolve41(import.meta.dirname, `../../examples/${name}.yaml`);
|
|
74042
|
-
if (!
|
|
74330
|
+
if (!existsSync67(exampleFile)) {
|
|
74043
74331
|
throw new Error(`Example config not found: ${name}.yaml (available: ${Object.keys(EMBEDDED_EXAMPLES).join(", ")})`);
|
|
74044
74332
|
}
|
|
74045
74333
|
copyFileSync11(exampleFile, dest);
|
|
@@ -74050,8 +74338,8 @@ function findUnwritableAgentDirs(config, opts) {
|
|
|
74050
74338
|
const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
|
|
74051
74339
|
const unwritable = [];
|
|
74052
74340
|
for (const name of targets) {
|
|
74053
|
-
const startSh =
|
|
74054
|
-
if (!
|
|
74341
|
+
const startSh = join60(agentsDir, name, "start.sh");
|
|
74342
|
+
if (!existsSync67(startSh))
|
|
74055
74343
|
continue;
|
|
74056
74344
|
try {
|
|
74057
74345
|
accessSync3(startSh, fsConstants6.W_OK);
|
|
@@ -74229,9 +74517,9 @@ function runRedactStdin() {
|
|
|
74229
74517
|
}
|
|
74230
74518
|
|
|
74231
74519
|
// src/cli/status-ask.ts
|
|
74232
|
-
import { readFileSync as
|
|
74233
|
-
import { join as
|
|
74234
|
-
import { homedir as
|
|
74520
|
+
import { readFileSync as readFileSync55, existsSync as existsSync68, readdirSync as readdirSync25 } from "node:fs";
|
|
74521
|
+
import { join as join61 } from "node:path";
|
|
74522
|
+
import { homedir as homedir34 } from "node:os";
|
|
74235
74523
|
|
|
74236
74524
|
// src/status-ask/report.ts
|
|
74237
74525
|
function parseJsonl(content) {
|
|
@@ -74505,7 +74793,7 @@ function runReport(opts) {
|
|
|
74505
74793
|
for (const src of sources) {
|
|
74506
74794
|
let content;
|
|
74507
74795
|
try {
|
|
74508
|
-
content =
|
|
74796
|
+
content = readFileSync55(src.path, "utf-8");
|
|
74509
74797
|
} catch (err) {
|
|
74510
74798
|
process.stderr.write(`status-ask report: cannot read ${src.path}: ${err instanceof Error ? err.message : String(err)}
|
|
74511
74799
|
`);
|
|
@@ -74552,7 +74840,7 @@ function runReport(opts) {
|
|
|
74552
74840
|
function resolveSources(explicitPath) {
|
|
74553
74841
|
if (explicitPath != null && explicitPath.trim() !== "") {
|
|
74554
74842
|
const trimmed = explicitPath.trim();
|
|
74555
|
-
if (!
|
|
74843
|
+
if (!existsSync68(trimmed)) {
|
|
74556
74844
|
process.stderr.write(`status-ask report: ${trimmed}: file not found
|
|
74557
74845
|
`);
|
|
74558
74846
|
process.exit(1);
|
|
@@ -74566,20 +74854,20 @@ function resolveSources(explicitPath) {
|
|
|
74566
74854
|
const config = loadConfig();
|
|
74567
74855
|
agentsDir = resolveAgentsDir(config);
|
|
74568
74856
|
} catch {
|
|
74569
|
-
agentsDir =
|
|
74857
|
+
agentsDir = join61(homedir34(), ".switchroom", "agents");
|
|
74570
74858
|
}
|
|
74571
|
-
if (!
|
|
74859
|
+
if (!existsSync68(agentsDir))
|
|
74572
74860
|
return [];
|
|
74573
74861
|
const sources = [];
|
|
74574
74862
|
let entries;
|
|
74575
74863
|
try {
|
|
74576
|
-
entries =
|
|
74864
|
+
entries = readdirSync25(agentsDir);
|
|
74577
74865
|
} catch {
|
|
74578
74866
|
return [];
|
|
74579
74867
|
}
|
|
74580
74868
|
for (const name of entries) {
|
|
74581
|
-
const path8 =
|
|
74582
|
-
if (
|
|
74869
|
+
const path8 = join61(agentsDir, name, "runtime-metrics.jsonl");
|
|
74870
|
+
if (existsSync68(path8)) {
|
|
74583
74871
|
sources.push({ path: path8, agent: name });
|
|
74584
74872
|
}
|
|
74585
74873
|
}
|
|
@@ -74600,17 +74888,17 @@ function inferAgentFromPath(p) {
|
|
|
74600
74888
|
|
|
74601
74889
|
// src/cli/agent-config.ts
|
|
74602
74890
|
init_helpers();
|
|
74603
|
-
import { join as
|
|
74604
|
-
import { homedir as
|
|
74891
|
+
import { join as join62 } from "node:path";
|
|
74892
|
+
import { homedir as homedir35 } from "node:os";
|
|
74605
74893
|
import {
|
|
74606
|
-
existsSync as
|
|
74607
|
-
mkdirSync as
|
|
74608
|
-
appendFileSync as
|
|
74609
|
-
readFileSync as
|
|
74894
|
+
existsSync as existsSync69,
|
|
74895
|
+
mkdirSync as mkdirSync37,
|
|
74896
|
+
appendFileSync as appendFileSync4,
|
|
74897
|
+
readFileSync as readFileSync56
|
|
74610
74898
|
} from "node:fs";
|
|
74611
|
-
var AUDIT_ROOT =
|
|
74899
|
+
var AUDIT_ROOT = join62(homedir35(), ".switchroom", "audit");
|
|
74612
74900
|
function auditPathFor(agent) {
|
|
74613
|
-
return
|
|
74901
|
+
return join62(AUDIT_ROOT, agent, "agent-config.jsonl");
|
|
74614
74902
|
}
|
|
74615
74903
|
function appendAudit(agent, cmd, args, exit, opts = {}) {
|
|
74616
74904
|
const row = {
|
|
@@ -74624,10 +74912,10 @@ function appendAudit(agent, cmd, args, exit, opts = {}) {
|
|
|
74624
74912
|
const path8 = opts.auditPath ?? auditPathFor(agent);
|
|
74625
74913
|
const dir = path8.slice(0, path8.lastIndexOf("/"));
|
|
74626
74914
|
try {
|
|
74627
|
-
if (!
|
|
74628
|
-
|
|
74915
|
+
if (!existsSync69(dir)) {
|
|
74916
|
+
mkdirSync37(dir, { recursive: true });
|
|
74629
74917
|
}
|
|
74630
|
-
|
|
74918
|
+
appendFileSync4(path8, JSON.stringify(row) + `
|
|
74631
74919
|
`, { flag: "a" });
|
|
74632
74920
|
} catch {}
|
|
74633
74921
|
}
|
|
@@ -74636,7 +74924,7 @@ function isContainerContext(env2 = process.env, opts = {}) {
|
|
|
74636
74924
|
return true;
|
|
74637
74925
|
const probe2 = opts.dockerEnvPath ?? "/.dockerenv";
|
|
74638
74926
|
try {
|
|
74639
|
-
if (
|
|
74927
|
+
if (existsSync69(probe2))
|
|
74640
74928
|
return true;
|
|
74641
74929
|
} catch {}
|
|
74642
74930
|
return false;
|
|
@@ -74697,11 +74985,11 @@ function getAgentSlice(config, agent) {
|
|
|
74697
74985
|
}
|
|
74698
74986
|
function readAuditTail(agent, limit, opts = {}) {
|
|
74699
74987
|
const path8 = opts.auditPath ?? auditPathFor(agent);
|
|
74700
|
-
if (!
|
|
74988
|
+
if (!existsSync69(path8))
|
|
74701
74989
|
return [];
|
|
74702
74990
|
let raw;
|
|
74703
74991
|
try {
|
|
74704
|
-
raw =
|
|
74992
|
+
raw = readFileSync56(path8, "utf-8");
|
|
74705
74993
|
} catch {
|
|
74706
74994
|
return [];
|
|
74707
74995
|
}
|
|
@@ -74856,61 +75144,61 @@ var import_yaml14 = __toESM(require_dist(), 1);
|
|
|
74856
75144
|
// src/config/overlay-writer.ts
|
|
74857
75145
|
init_paths();
|
|
74858
75146
|
import {
|
|
74859
|
-
closeSync as
|
|
74860
|
-
existsSync as
|
|
74861
|
-
fsyncSync as
|
|
74862
|
-
mkdirSync as
|
|
74863
|
-
openSync as
|
|
74864
|
-
readdirSync as
|
|
74865
|
-
readFileSync as
|
|
74866
|
-
renameSync as
|
|
74867
|
-
statSync as
|
|
74868
|
-
unlinkSync as
|
|
74869
|
-
writeSync as
|
|
75147
|
+
closeSync as closeSync13,
|
|
75148
|
+
existsSync as existsSync70,
|
|
75149
|
+
fsyncSync as fsyncSync6,
|
|
75150
|
+
mkdirSync as mkdirSync38,
|
|
75151
|
+
openSync as openSync13,
|
|
75152
|
+
readdirSync as readdirSync26,
|
|
75153
|
+
readFileSync as readFileSync57,
|
|
75154
|
+
renameSync as renameSync14,
|
|
75155
|
+
statSync as statSync27,
|
|
75156
|
+
unlinkSync as unlinkSync14,
|
|
75157
|
+
writeSync as writeSync8
|
|
74870
75158
|
} from "node:fs";
|
|
74871
|
-
import { join as
|
|
75159
|
+
import { join as join63, resolve as resolve42 } from "node:path";
|
|
74872
75160
|
var STAGING_SUBDIR = ".staging";
|
|
74873
75161
|
function overlayPathsFor(agent, opts = {}) {
|
|
74874
75162
|
const base = opts.root ? resolve42(opts.root, agent) : resolve42(resolveDualPath(`~/.switchroom/agents/${agent}`));
|
|
74875
|
-
const scheduleDir =
|
|
74876
|
-
const scheduleStagingDir =
|
|
74877
|
-
const skillsDir =
|
|
74878
|
-
const skillsStagingDir =
|
|
75163
|
+
const scheduleDir = join63(base, "schedule.d");
|
|
75164
|
+
const scheduleStagingDir = join63(scheduleDir, STAGING_SUBDIR);
|
|
75165
|
+
const skillsDir = join63(base, "skills.d");
|
|
75166
|
+
const skillsStagingDir = join63(skillsDir, STAGING_SUBDIR);
|
|
74879
75167
|
return {
|
|
74880
75168
|
agentRoot: base,
|
|
74881
75169
|
scheduleDir,
|
|
74882
75170
|
scheduleStagingDir,
|
|
74883
75171
|
skillsDir,
|
|
74884
75172
|
skillsStagingDir,
|
|
74885
|
-
lockPath:
|
|
75173
|
+
lockPath: join63(base, ".lock"),
|
|
74886
75174
|
stagingDir: scheduleStagingDir
|
|
74887
75175
|
};
|
|
74888
75176
|
}
|
|
74889
75177
|
function ensureDirs(paths) {
|
|
74890
|
-
|
|
74891
|
-
|
|
75178
|
+
mkdirSync38(paths.scheduleDir, { recursive: true });
|
|
75179
|
+
mkdirSync38(paths.scheduleStagingDir, { recursive: true });
|
|
74892
75180
|
}
|
|
74893
75181
|
function ensureSkillsDirs(paths) {
|
|
74894
|
-
|
|
74895
|
-
|
|
75182
|
+
mkdirSync38(paths.skillsDir, { recursive: true });
|
|
75183
|
+
mkdirSync38(paths.skillsStagingDir, { recursive: true });
|
|
74896
75184
|
}
|
|
74897
75185
|
function withAgentLock(paths, fn) {
|
|
74898
|
-
|
|
75186
|
+
mkdirSync38(paths.agentRoot, { recursive: true });
|
|
74899
75187
|
const start = Date.now();
|
|
74900
75188
|
const TIMEOUT_MS = 5000;
|
|
74901
75189
|
let fd = null;
|
|
74902
75190
|
while (Date.now() - start < TIMEOUT_MS) {
|
|
74903
75191
|
try {
|
|
74904
|
-
fd =
|
|
75192
|
+
fd = openSync13(paths.lockPath, "wx");
|
|
74905
75193
|
break;
|
|
74906
75194
|
} catch (err) {
|
|
74907
75195
|
const e = err;
|
|
74908
75196
|
if (e.code !== "EEXIST")
|
|
74909
75197
|
throw err;
|
|
74910
75198
|
try {
|
|
74911
|
-
const age = Date.now() -
|
|
75199
|
+
const age = Date.now() - statSync27(paths.lockPath).mtimeMs;
|
|
74912
75200
|
if (age > 30000) {
|
|
74913
|
-
|
|
75201
|
+
unlinkSync14(paths.lockPath);
|
|
74914
75202
|
continue;
|
|
74915
75203
|
}
|
|
74916
75204
|
} catch {}
|
|
@@ -74925,10 +75213,10 @@ function withAgentLock(paths, fn) {
|
|
|
74925
75213
|
return fn();
|
|
74926
75214
|
} finally {
|
|
74927
75215
|
try {
|
|
74928
|
-
|
|
75216
|
+
closeSync13(fd);
|
|
74929
75217
|
} catch {}
|
|
74930
75218
|
try {
|
|
74931
|
-
|
|
75219
|
+
unlinkSync14(paths.lockPath);
|
|
74932
75220
|
} catch {}
|
|
74933
75221
|
}
|
|
74934
75222
|
}
|
|
@@ -74936,16 +75224,16 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
74936
75224
|
const paths = overlayPathsFor(agent, opts);
|
|
74937
75225
|
return withAgentLock(paths, () => {
|
|
74938
75226
|
ensureDirs(paths);
|
|
74939
|
-
const stagingPath =
|
|
74940
|
-
const finalPath =
|
|
74941
|
-
const fd =
|
|
75227
|
+
const stagingPath = join63(paths.scheduleStagingDir, `${slug}.yaml`);
|
|
75228
|
+
const finalPath = join63(paths.scheduleDir, `${slug}.yaml`);
|
|
75229
|
+
const fd = openSync13(stagingPath, "w", 384);
|
|
74942
75230
|
try {
|
|
74943
|
-
|
|
74944
|
-
|
|
75231
|
+
writeSync8(fd, yamlText);
|
|
75232
|
+
fsyncSync6(fd);
|
|
74945
75233
|
} finally {
|
|
74946
|
-
|
|
75234
|
+
closeSync13(fd);
|
|
74947
75235
|
}
|
|
74948
|
-
|
|
75236
|
+
renameSync14(stagingPath, finalPath);
|
|
74949
75237
|
return finalPath;
|
|
74950
75238
|
});
|
|
74951
75239
|
}
|
|
@@ -74953,40 +75241,40 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
74953
75241
|
const paths = overlayPathsFor(agent, opts);
|
|
74954
75242
|
return withAgentLock(paths, () => {
|
|
74955
75243
|
ensureSkillsDirs(paths);
|
|
74956
|
-
const stagingPath =
|
|
74957
|
-
const finalPath =
|
|
74958
|
-
const fd =
|
|
75244
|
+
const stagingPath = join63(paths.skillsStagingDir, `${slug}.yaml`);
|
|
75245
|
+
const finalPath = join63(paths.skillsDir, `${slug}.yaml`);
|
|
75246
|
+
const fd = openSync13(stagingPath, "w", 384);
|
|
74959
75247
|
try {
|
|
74960
|
-
|
|
74961
|
-
|
|
75248
|
+
writeSync8(fd, yamlText);
|
|
75249
|
+
fsyncSync6(fd);
|
|
74962
75250
|
} finally {
|
|
74963
|
-
|
|
75251
|
+
closeSync13(fd);
|
|
74964
75252
|
}
|
|
74965
|
-
|
|
75253
|
+
renameSync14(stagingPath, finalPath);
|
|
74966
75254
|
return finalPath;
|
|
74967
75255
|
});
|
|
74968
75256
|
}
|
|
74969
75257
|
function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
74970
75258
|
const paths = overlayPathsFor(agent, opts);
|
|
74971
75259
|
return withAgentLock(paths, () => {
|
|
74972
|
-
const finalPath =
|
|
74973
|
-
if (!
|
|
75260
|
+
const finalPath = join63(paths.skillsDir, `${slug}.yaml`);
|
|
75261
|
+
if (!existsSync70(finalPath))
|
|
74974
75262
|
return false;
|
|
74975
|
-
|
|
75263
|
+
unlinkSync14(finalPath);
|
|
74976
75264
|
return true;
|
|
74977
75265
|
});
|
|
74978
75266
|
}
|
|
74979
75267
|
function listSkillsOverlayEntries(agent, opts = {}) {
|
|
74980
75268
|
const paths = overlayPathsFor(agent, opts);
|
|
74981
|
-
if (!
|
|
75269
|
+
if (!existsSync70(paths.skillsDir))
|
|
74982
75270
|
return [];
|
|
74983
75271
|
const out = [];
|
|
74984
|
-
for (const name of
|
|
75272
|
+
for (const name of readdirSync26(paths.skillsDir)) {
|
|
74985
75273
|
if (!/\.ya?ml$/i.test(name))
|
|
74986
75274
|
continue;
|
|
74987
|
-
const full =
|
|
75275
|
+
const full = join63(paths.skillsDir, name);
|
|
74988
75276
|
try {
|
|
74989
|
-
const raw =
|
|
75277
|
+
const raw = readFileSync57(full, "utf-8");
|
|
74990
75278
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
74991
75279
|
out.push({ slug, path: full, raw });
|
|
74992
75280
|
} catch {}
|
|
@@ -74996,24 +75284,24 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
74996
75284
|
function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
74997
75285
|
const paths = overlayPathsFor(agent, opts);
|
|
74998
75286
|
return withAgentLock(paths, () => {
|
|
74999
|
-
const finalPath =
|
|
75000
|
-
if (!
|
|
75287
|
+
const finalPath = join63(paths.scheduleDir, `${slug}.yaml`);
|
|
75288
|
+
if (!existsSync70(finalPath))
|
|
75001
75289
|
return false;
|
|
75002
|
-
|
|
75290
|
+
unlinkSync14(finalPath);
|
|
75003
75291
|
return true;
|
|
75004
75292
|
});
|
|
75005
75293
|
}
|
|
75006
75294
|
function listOverlayEntries(agent, opts = {}) {
|
|
75007
75295
|
const paths = overlayPathsFor(agent, opts);
|
|
75008
|
-
if (!
|
|
75296
|
+
if (!existsSync70(paths.scheduleDir))
|
|
75009
75297
|
return [];
|
|
75010
75298
|
const out = [];
|
|
75011
|
-
for (const name of
|
|
75299
|
+
for (const name of readdirSync26(paths.scheduleDir)) {
|
|
75012
75300
|
if (!/\.ya?ml$/i.test(name))
|
|
75013
75301
|
continue;
|
|
75014
|
-
const full =
|
|
75302
|
+
const full = join63(paths.scheduleDir, name);
|
|
75015
75303
|
try {
|
|
75016
|
-
const raw =
|
|
75304
|
+
const raw = readFileSync57(full, "utf-8");
|
|
75017
75305
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
75018
75306
|
out.push({ slug, path: full, raw });
|
|
75019
75307
|
} catch {}
|
|
@@ -75155,38 +75443,38 @@ function reconcileAgentCronOnly(agent) {
|
|
|
75155
75443
|
|
|
75156
75444
|
// src/cli/agent-config-pending.ts
|
|
75157
75445
|
import {
|
|
75158
|
-
closeSync as
|
|
75159
|
-
existsSync as
|
|
75160
|
-
fsyncSync as
|
|
75161
|
-
mkdirSync as
|
|
75162
|
-
openSync as
|
|
75163
|
-
readdirSync as
|
|
75164
|
-
readFileSync as
|
|
75165
|
-
renameSync as
|
|
75166
|
-
unlinkSync as
|
|
75446
|
+
closeSync as closeSync14,
|
|
75447
|
+
existsSync as existsSync71,
|
|
75448
|
+
fsyncSync as fsyncSync7,
|
|
75449
|
+
mkdirSync as mkdirSync39,
|
|
75450
|
+
openSync as openSync14,
|
|
75451
|
+
readdirSync as readdirSync27,
|
|
75452
|
+
readFileSync as readFileSync58,
|
|
75453
|
+
renameSync as renameSync15,
|
|
75454
|
+
unlinkSync as unlinkSync15,
|
|
75167
75455
|
writeFileSync as writeFileSync33,
|
|
75168
|
-
writeSync as
|
|
75456
|
+
writeSync as writeSync9
|
|
75169
75457
|
} from "node:fs";
|
|
75170
|
-
import { join as
|
|
75171
|
-
import { randomBytes as
|
|
75458
|
+
import { join as join64 } from "node:path";
|
|
75459
|
+
import { randomBytes as randomBytes13 } from "node:crypto";
|
|
75172
75460
|
var STAGE_ID_PREFIX = "cap_";
|
|
75173
75461
|
function pendingDir(agent, opts = {}) {
|
|
75174
75462
|
const paths = overlayPathsFor(agent, opts);
|
|
75175
|
-
return
|
|
75463
|
+
return join64(paths.scheduleDir, ".pending");
|
|
75176
75464
|
}
|
|
75177
75465
|
function ensurePendingDir(agent, opts = {}) {
|
|
75178
75466
|
const dir = pendingDir(agent, opts);
|
|
75179
|
-
|
|
75467
|
+
mkdirSync39(dir, { recursive: true });
|
|
75180
75468
|
return dir;
|
|
75181
75469
|
}
|
|
75182
75470
|
function newStageId() {
|
|
75183
|
-
return `${STAGE_ID_PREFIX}${
|
|
75471
|
+
return `${STAGE_ID_PREFIX}${randomBytes13(4).toString("hex")}`;
|
|
75184
75472
|
}
|
|
75185
75473
|
function stagePendingScheduleEntry(opts) {
|
|
75186
75474
|
const dir = ensurePendingDir(opts.agent, { root: opts.root });
|
|
75187
75475
|
const stageId = opts.stageId ?? newStageId();
|
|
75188
|
-
const yamlPath =
|
|
75189
|
-
const metaPath =
|
|
75476
|
+
const yamlPath = join64(dir, `${stageId}.yaml`);
|
|
75477
|
+
const metaPath = join64(dir, `${stageId}.meta.json`);
|
|
75190
75478
|
const meta = {
|
|
75191
75479
|
v: 1,
|
|
75192
75480
|
stage_id: stageId,
|
|
@@ -75198,14 +75486,14 @@ function stagePendingScheduleEntry(opts) {
|
|
|
75198
75486
|
};
|
|
75199
75487
|
const yamlTmp = `${yamlPath}.tmp-${process.pid}`;
|
|
75200
75488
|
{
|
|
75201
|
-
const fd =
|
|
75489
|
+
const fd = openSync14(yamlTmp, "w", 384);
|
|
75202
75490
|
try {
|
|
75203
|
-
|
|
75204
|
-
|
|
75491
|
+
writeSync9(fd, opts.yamlText);
|
|
75492
|
+
fsyncSync7(fd);
|
|
75205
75493
|
} finally {
|
|
75206
|
-
|
|
75494
|
+
closeSync14(fd);
|
|
75207
75495
|
}
|
|
75208
|
-
|
|
75496
|
+
renameSync15(yamlTmp, yamlPath);
|
|
75209
75497
|
}
|
|
75210
75498
|
writeFileSync33(metaPath, JSON.stringify(meta, null, 2) + `
|
|
75211
75499
|
`, { mode: 384 });
|
|
@@ -75213,19 +75501,19 @@ function stagePendingScheduleEntry(opts) {
|
|
|
75213
75501
|
}
|
|
75214
75502
|
function listPendingScheduleEntries(agent, opts = {}) {
|
|
75215
75503
|
const dir = pendingDir(agent, opts);
|
|
75216
|
-
if (!
|
|
75504
|
+
if (!existsSync71(dir))
|
|
75217
75505
|
return [];
|
|
75218
75506
|
const out = [];
|
|
75219
|
-
for (const name of
|
|
75507
|
+
for (const name of readdirSync27(dir).sort()) {
|
|
75220
75508
|
if (!name.endsWith(".meta.json"))
|
|
75221
75509
|
continue;
|
|
75222
75510
|
const stageId = name.slice(0, -".meta.json".length);
|
|
75223
|
-
const metaPath =
|
|
75224
|
-
const yamlPath =
|
|
75225
|
-
if (!
|
|
75511
|
+
const metaPath = join64(dir, name);
|
|
75512
|
+
const yamlPath = join64(dir, `${stageId}.yaml`);
|
|
75513
|
+
if (!existsSync71(yamlPath))
|
|
75226
75514
|
continue;
|
|
75227
75515
|
try {
|
|
75228
|
-
const meta = JSON.parse(
|
|
75516
|
+
const meta = JSON.parse(readFileSync58(metaPath, "utf-8"));
|
|
75229
75517
|
if (meta?.v !== 1 || typeof meta.stage_id !== "string")
|
|
75230
75518
|
continue;
|
|
75231
75519
|
out.push({ stageId: meta.stage_id, agent: meta.agent, yamlPath, metaPath, meta });
|
|
@@ -75240,12 +75528,12 @@ function commitPendingScheduleEntry(opts) {
|
|
|
75240
75528
|
return { committed: false, reason: "not_found" };
|
|
75241
75529
|
const slug = match.meta.entry.name ?? match.stageId;
|
|
75242
75530
|
const paths = overlayPathsFor(opts.agent, { root: opts.root });
|
|
75243
|
-
const finalPath =
|
|
75244
|
-
if (
|
|
75531
|
+
const finalPath = join64(paths.scheduleDir, `${slug}.yaml`);
|
|
75532
|
+
if (existsSync71(finalPath)) {
|
|
75245
75533
|
return { committed: false, reason: "slug_collision" };
|
|
75246
75534
|
}
|
|
75247
|
-
|
|
75248
|
-
|
|
75535
|
+
renameSync15(match.yamlPath, finalPath);
|
|
75536
|
+
unlinkSync15(match.metaPath);
|
|
75249
75537
|
return { committed: true, path: finalPath, slug };
|
|
75250
75538
|
}
|
|
75251
75539
|
function denyPendingScheduleEntry(opts) {
|
|
@@ -75254,16 +75542,16 @@ function denyPendingScheduleEntry(opts) {
|
|
|
75254
75542
|
if (!match)
|
|
75255
75543
|
return { denied: false, reason: "not_found" };
|
|
75256
75544
|
try {
|
|
75257
|
-
|
|
75545
|
+
unlinkSync15(match.yamlPath);
|
|
75258
75546
|
} catch {}
|
|
75259
75547
|
try {
|
|
75260
|
-
|
|
75548
|
+
unlinkSync15(match.metaPath);
|
|
75261
75549
|
} catch {}
|
|
75262
75550
|
return { denied: true };
|
|
75263
75551
|
}
|
|
75264
75552
|
|
|
75265
75553
|
// src/cli/agent-config-write.ts
|
|
75266
|
-
import { existsSync as
|
|
75554
|
+
import { existsSync as existsSync72, readFileSync as readFileSync59 } from "node:fs";
|
|
75267
75555
|
var MAX_ENTRIES_PER_AGENT = 20;
|
|
75268
75556
|
function checkOperatorContext(verb, env2 = process.env) {
|
|
75269
75557
|
if (env2.SWITCHROOM_OPERATOR === "1")
|
|
@@ -75497,8 +75785,8 @@ function scheduleRemove(opts) {
|
|
|
75497
75785
|
}
|
|
75498
75786
|
let priorContent = null;
|
|
75499
75787
|
try {
|
|
75500
|
-
if (
|
|
75501
|
-
priorContent =
|
|
75788
|
+
if (existsSync72(match.path))
|
|
75789
|
+
priorContent = readFileSync59(match.path, "utf-8");
|
|
75502
75790
|
} catch {}
|
|
75503
75791
|
deleteOverlayEntry(agent, match.slug, { root: opts.root });
|
|
75504
75792
|
const reconcileFn = opts.reconcile === undefined ? opts.root ? null : reconcileAgentCronOnly : opts.reconcile;
|
|
@@ -75690,10 +75978,10 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
75690
75978
|
|
|
75691
75979
|
// src/cli/agent-config-skill-write.ts
|
|
75692
75980
|
var import_yaml15 = __toESM(require_dist(), 1);
|
|
75693
|
-
import { existsSync as
|
|
75981
|
+
import { existsSync as existsSync73 } from "node:fs";
|
|
75694
75982
|
init_reconcile_default_skills();
|
|
75695
75983
|
var import_yaml16 = __toESM(require_dist(), 1);
|
|
75696
|
-
import { join as
|
|
75984
|
+
import { join as join65 } from "node:path";
|
|
75697
75985
|
var MAX_SKILLS_PER_AGENT = 20;
|
|
75698
75986
|
var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
|
|
75699
75987
|
function exitCodeFor2(code) {
|
|
@@ -75768,8 +76056,8 @@ function skillInstall(opts) {
|
|
|
75768
76056
|
return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
|
|
75769
76057
|
}
|
|
75770
76058
|
const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
|
|
75771
|
-
const skillPath =
|
|
75772
|
-
if (!
|
|
76059
|
+
const skillPath = join65(poolDir, skillName);
|
|
76060
|
+
if (!existsSync73(skillPath)) {
|
|
75773
76061
|
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.`);
|
|
75774
76062
|
}
|
|
75775
76063
|
const yamlText = import_yaml15.stringify({ skills: [skillName] });
|
|
@@ -75953,27 +76241,27 @@ init_source();
|
|
|
75953
76241
|
init_helpers();
|
|
75954
76242
|
init_loader();
|
|
75955
76243
|
import {
|
|
75956
|
-
existsSync as
|
|
75957
|
-
readdirSync as
|
|
75958
|
-
readFileSync as
|
|
75959
|
-
renameSync as
|
|
75960
|
-
statSync as
|
|
75961
|
-
unlinkSync as
|
|
76244
|
+
existsSync as existsSync75,
|
|
76245
|
+
readdirSync as readdirSync28,
|
|
76246
|
+
readFileSync as readFileSync61,
|
|
76247
|
+
renameSync as renameSync16,
|
|
76248
|
+
statSync as statSync28,
|
|
76249
|
+
unlinkSync as unlinkSync16
|
|
75962
76250
|
} from "node:fs";
|
|
75963
|
-
import { createHash as
|
|
75964
|
-
import { join as
|
|
76251
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
76252
|
+
import { join as join66 } from "node:path";
|
|
75965
76253
|
function planCronUnitRenames(agentsDir, agents) {
|
|
75966
76254
|
const plans = [];
|
|
75967
76255
|
for (const [agentName, agentConfig] of Object.entries(agents)) {
|
|
75968
76256
|
const schedule = agentConfig.schedule ?? [];
|
|
75969
76257
|
if (schedule.length === 0)
|
|
75970
76258
|
continue;
|
|
75971
|
-
const telegramDir =
|
|
75972
|
-
if (!
|
|
76259
|
+
const telegramDir = join66(agentsDir, agentName, "telegram");
|
|
76260
|
+
if (!existsSync75(telegramDir))
|
|
75973
76261
|
continue;
|
|
75974
76262
|
let entries;
|
|
75975
76263
|
try {
|
|
75976
|
-
entries =
|
|
76264
|
+
entries = readdirSync28(telegramDir);
|
|
75977
76265
|
} catch {
|
|
75978
76266
|
continue;
|
|
75979
76267
|
}
|
|
@@ -75990,8 +76278,8 @@ function planCronUnitRenames(agentsDir, agents) {
|
|
|
75990
76278
|
continue;
|
|
75991
76279
|
plans.push({
|
|
75992
76280
|
agent: agentName,
|
|
75993
|
-
from:
|
|
75994
|
-
to:
|
|
76281
|
+
from: join66(telegramDir, file),
|
|
76282
|
+
to: join66(telegramDir, canonical),
|
|
75995
76283
|
scheduleIdx: idx,
|
|
75996
76284
|
entry
|
|
75997
76285
|
});
|
|
@@ -76000,10 +76288,10 @@ function planCronUnitRenames(agentsDir, agents) {
|
|
|
76000
76288
|
return plans;
|
|
76001
76289
|
}
|
|
76002
76290
|
function sha256File2(path8) {
|
|
76003
|
-
return
|
|
76291
|
+
return createHash13("sha256").update(readFileSync61(path8)).digest("hex");
|
|
76004
76292
|
}
|
|
76005
76293
|
function renamePair(from, to, opts = {}) {
|
|
76006
|
-
if (
|
|
76294
|
+
if (existsSync75(to)) {
|
|
76007
76295
|
let identical = false;
|
|
76008
76296
|
try {
|
|
76009
76297
|
identical = sha256File2(from) === sha256File2(to);
|
|
@@ -76013,7 +76301,7 @@ function renamePair(from, to, opts = {}) {
|
|
|
76013
76301
|
if (identical) {
|
|
76014
76302
|
if (!opts.dryRun) {
|
|
76015
76303
|
try {
|
|
76016
|
-
|
|
76304
|
+
unlinkSync16(from);
|
|
76017
76305
|
} catch {}
|
|
76018
76306
|
}
|
|
76019
76307
|
return { kind: "deduped", legacy: from };
|
|
@@ -76021,13 +76309,13 @@ function renamePair(from, to, opts = {}) {
|
|
|
76021
76309
|
return { kind: "skipped", reason: "target exists, legacy preserved", legacy: from };
|
|
76022
76310
|
}
|
|
76023
76311
|
if (!opts.dryRun)
|
|
76024
|
-
|
|
76312
|
+
renameSync16(from, to);
|
|
76025
76313
|
return { kind: "renamed" };
|
|
76026
76314
|
}
|
|
76027
76315
|
function extractPromptFromLegacyScript(path8) {
|
|
76028
76316
|
let body;
|
|
76029
76317
|
try {
|
|
76030
|
-
body =
|
|
76318
|
+
body = readFileSync61(path8, "utf-8");
|
|
76031
76319
|
} catch {
|
|
76032
76320
|
return null;
|
|
76033
76321
|
}
|
|
@@ -76096,7 +76384,7 @@ function registerMigrateCommand(program3) {
|
|
|
76096
76384
|
const fromSidecar = p.from.replace(/\.sh$/, ".source");
|
|
76097
76385
|
const toSidecar = p.to.replace(/\.sh$/, ".source");
|
|
76098
76386
|
let sidecarStatus = null;
|
|
76099
|
-
if (
|
|
76387
|
+
if (existsSync75(fromSidecar) && statSync28(fromSidecar).isFile()) {
|
|
76100
76388
|
sidecarStatus = renamePair(fromSidecar, toSidecar);
|
|
76101
76389
|
}
|
|
76102
76390
|
switch (status.kind) {
|
|
@@ -76128,9 +76416,9 @@ function registerMigrateCommand(program3) {
|
|
|
76128
76416
|
// src/cli/hostd.ts
|
|
76129
76417
|
init_source();
|
|
76130
76418
|
init_helpers();
|
|
76131
|
-
import { existsSync as
|
|
76132
|
-
import { homedir as
|
|
76133
|
-
import { join as
|
|
76419
|
+
import { existsSync as existsSync76, mkdirSync as mkdirSync40, readdirSync as readdirSync29, readFileSync as readFileSync62, writeFileSync as writeFileSync34, statSync as statSync29, copyFileSync as copyFileSync12 } from "node:fs";
|
|
76420
|
+
import { homedir as homedir36 } from "node:os";
|
|
76421
|
+
import { join as join67 } from "node:path";
|
|
76134
76422
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
76135
76423
|
init_audit_reader();
|
|
76136
76424
|
var DEFAULT_IMAGE_TAG = "latest";
|
|
@@ -76221,14 +76509,14 @@ networks:
|
|
|
76221
76509
|
`;
|
|
76222
76510
|
}
|
|
76223
76511
|
function hostdDir() {
|
|
76224
|
-
return
|
|
76512
|
+
return join67(homedir36(), ".switchroom", "hostd");
|
|
76225
76513
|
}
|
|
76226
76514
|
function hostdComposePath() {
|
|
76227
|
-
return
|
|
76515
|
+
return join67(hostdDir(), "docker-compose.yml");
|
|
76228
76516
|
}
|
|
76229
76517
|
function backupExistingCompose() {
|
|
76230
76518
|
const p = hostdComposePath();
|
|
76231
|
-
if (!
|
|
76519
|
+
if (!existsSync76(p))
|
|
76232
76520
|
return null;
|
|
76233
76521
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
76234
76522
|
const bak = `${p}.bak-${ts}`;
|
|
@@ -76261,9 +76549,9 @@ async function doInstall(opts, program3) {
|
|
|
76261
76549
|
}
|
|
76262
76550
|
const dir = hostdDir();
|
|
76263
76551
|
const composePath = hostdComposePath();
|
|
76264
|
-
|
|
76552
|
+
mkdirSync40(dir, { recursive: true });
|
|
76265
76553
|
const yaml = renderHostdComposeFile({
|
|
76266
|
-
hostHome:
|
|
76554
|
+
hostHome: homedir36(),
|
|
76267
76555
|
imageTag: opts.tag ?? DEFAULT_IMAGE_TAG,
|
|
76268
76556
|
operatorUid: resolveOperatorUid()
|
|
76269
76557
|
});
|
|
@@ -76305,7 +76593,7 @@ function doStatus() {
|
|
|
76305
76593
|
const composeYml = hostdComposePath();
|
|
76306
76594
|
console.log(source_default.bold("switchroom-hostd"));
|
|
76307
76595
|
console.log("");
|
|
76308
|
-
if (!
|
|
76596
|
+
if (!existsSync76(composeYml)) {
|
|
76309
76597
|
console.log(source_default.yellow(" compose: not installed"));
|
|
76310
76598
|
console.log(source_default.dim(" run `switchroom hostd install` to set up."));
|
|
76311
76599
|
return;
|
|
@@ -76326,15 +76614,15 @@ function doStatus() {
|
|
|
76326
76614
|
} else {
|
|
76327
76615
|
console.log(source_default.green(` container: ${ps.stdout.trim()}`));
|
|
76328
76616
|
}
|
|
76329
|
-
if (
|
|
76617
|
+
if (existsSync76(dir)) {
|
|
76330
76618
|
const entries = [];
|
|
76331
76619
|
try {
|
|
76332
|
-
for (const name of
|
|
76620
|
+
for (const name of readdirSync29(dir)) {
|
|
76333
76621
|
if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
|
|
76334
76622
|
continue;
|
|
76335
|
-
const sockPath =
|
|
76336
|
-
if (
|
|
76337
|
-
const st =
|
|
76623
|
+
const sockPath = join67(dir, name, "sock");
|
|
76624
|
+
if (existsSync76(sockPath)) {
|
|
76625
|
+
const st = statSync29(sockPath);
|
|
76338
76626
|
if ((st.mode & 61440) === 49152) {
|
|
76339
76627
|
entries.push(`${name} \u2192 ${sockPath}`);
|
|
76340
76628
|
}
|
|
@@ -76352,7 +76640,7 @@ function doStatus() {
|
|
|
76352
76640
|
}
|
|
76353
76641
|
function doUninstall() {
|
|
76354
76642
|
const composeYml = hostdComposePath();
|
|
76355
|
-
if (!
|
|
76643
|
+
if (!existsSync76(composeYml)) {
|
|
76356
76644
|
console.log(source_default.yellow(" No hostd install detected (no compose file at this path)."));
|
|
76357
76645
|
return;
|
|
76358
76646
|
}
|
|
@@ -76376,12 +76664,12 @@ function registerHostdCommand(program3) {
|
|
|
76376
76664
|
hostd.command("uninstall").description("Stop the hostd container. Leaves the compose file in place for re-install.").action(() => doUninstall());
|
|
76377
76665
|
hostd.command("audit").description("Tail and filter the hostd audit log (privileged-verb call history)").option("--tail <n>", "Number of matching entries to show (default: 50)", "50").option("--agent <name>", "Filter to a specific caller agent").option("--op <verb>", "Filter to a specific hostd verb (e.g. update_apply, agent_restart)").option("--error", "Show only failed (error/denied) entries").option("--verbose", "Show the captured stderr / error tail under each failed row").option("--path <file>", "Override audit log path (for debugging)").action((opts) => {
|
|
76378
76666
|
const logPath = opts.path ?? defaultAuditLogPath2();
|
|
76379
|
-
if (!
|
|
76667
|
+
if (!existsSync76(logPath)) {
|
|
76380
76668
|
console.error(source_default.yellow(`Audit log not found at ${logPath}.`) + source_default.gray(`
|
|
76381
76669
|
The log is created when hostd handles its first privileged-verb request.`));
|
|
76382
76670
|
return;
|
|
76383
76671
|
}
|
|
76384
|
-
const raw =
|
|
76672
|
+
const raw = readFileSync62(logPath, "utf-8");
|
|
76385
76673
|
const limit = Math.max(1, parseInt(opts.tail ?? "50", 10) || 50);
|
|
76386
76674
|
const filters = {
|
|
76387
76675
|
agent: opts.agent,
|