switchroom 0.18.7 → 0.18.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/switchroom.js +905 -758
- package/dist/host-control/main.js +1 -1
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +111 -34
- package/skills/switchroom-runtime/SKILL.md +2 -0
- package/telegram-plugin/dist/gateway/gateway.js +1403 -657
- package/telegram-plugin/flood-circuit-breaker.ts +123 -0
- package/telegram-plugin/gateway/activity-card-store.ts +63 -18
- package/telegram-plugin/gateway/boot-card.ts +27 -0
- package/telegram-plugin/gateway/busy-ack.ts +106 -0
- package/telegram-plugin/gateway/gateway.ts +564 -85
- package/telegram-plugin/gateway/mental-model-propose-diff.ts +61 -5
- package/telegram-plugin/gateway/model-command.ts +23 -11
- package/telegram-plugin/gateway/session-model-file.ts +198 -0
- package/telegram-plugin/gateway/status-pin-store.ts +82 -22
- package/telegram-plugin/gateway/worker-pin-reaper.ts +114 -0
- package/telegram-plugin/hooks/hooks.json +10 -10
- package/telegram-plugin/hooks/run-hook.sh +84 -0
- package/telegram-plugin/model-unavailable.ts +26 -0
- package/telegram-plugin/pty-partial-handler.ts +39 -0
- package/telegram-plugin/render/rich-render.ts +79 -1
- package/telegram-plugin/retry-api-call.ts +62 -0
- package/telegram-plugin/shared/bot-runtime.ts +8 -1
- package/telegram-plugin/silence-poke.ts +14 -0
- package/telegram-plugin/stream-controller.ts +156 -38
- package/telegram-plugin/tests/activity-card-store.test.ts +47 -2
- package/telegram-plugin/tests/approval-card-restart-outcome.test.ts +218 -0
- package/telegram-plugin/tests/boot-card-flood-suppress.test.ts +111 -0
- package/telegram-plugin/tests/busy-ack-wiring.test.ts +118 -0
- package/telegram-plugin/tests/busy-ack.test.ts +121 -0
- package/telegram-plugin/tests/flood-circuit-breaker.test.ts +74 -0
- package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +177 -25
- package/telegram-plugin/tests/mental-model-name-entity-corruption.test.ts +119 -0
- package/telegram-plugin/tests/model-command.test.ts +2 -2
- package/telegram-plugin/tests/model-unavailable.test.ts +41 -0
- package/telegram-plugin/tests/pty-partial-handler.test.ts +56 -0
- package/telegram-plugin/tests/render/render-outbound-chunks.test.ts +98 -0
- package/telegram-plugin/tests/retry-api-call.test.ts +59 -0
- package/telegram-plugin/tests/run-hook-wrapper.test.ts +132 -0
- package/telegram-plugin/tests/session-model-file.test.ts +132 -0
- package/telegram-plugin/tests/slot-banner-boot-recovery.test.ts +3 -3
- package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +3 -3
- package/telegram-plugin/tests/status-pin-store.test.ts +62 -6
- package/telegram-plugin/tests/stream-controller-chunk-cap.test.ts +122 -0
- package/telegram-plugin/tests/voice-send.test.ts +308 -0
- package/telegram-plugin/tests/worker-pin-reaper.test.ts +132 -0
- package/telegram-plugin/uat/scenarios/jtbd-deliberate-restart-resumes-dm.test.ts +118 -0
- package/telegram-plugin/uat/scenarios/jtbd-midflight-busy-ack-dm.test.ts +201 -0
- package/telegram-plugin/uat/scenarios/jtbd-worker-pin-lifecycle-dm.test.ts +208 -0
- package/telegram-plugin/uat/scenarios/vault-card-survives-gateway-restart-dm.test.ts +140 -0
- package/telegram-plugin/uat/scenarios/vault-deny-resumes-turn-dm.test.ts +84 -0
- package/telegram-plugin/uat/scenarios/vault-timeout-wakes-agent-dm.test.ts +91 -0
- package/telegram-plugin/voice-ondemand.ts +25 -1
- package/telegram-plugin/voice-send.ts +154 -0
package/dist/cli/switchroom.js
CHANGED
|
@@ -40469,19 +40469,19 @@ var init_thinking_effort_risk = __esm(() => {
|
|
|
40469
40469
|
|
|
40470
40470
|
// src/manifest.ts
|
|
40471
40471
|
import {
|
|
40472
|
-
existsSync as
|
|
40473
|
-
readFileSync as
|
|
40472
|
+
existsSync as existsSync58,
|
|
40473
|
+
readFileSync as readFileSync53,
|
|
40474
40474
|
readdirSync as readdirSync19
|
|
40475
40475
|
} from "node:fs";
|
|
40476
|
-
import { dirname as
|
|
40476
|
+
import { dirname as dirname18, join as join51 } from "node:path";
|
|
40477
40477
|
import { execSync as execSync2 } from "node:child_process";
|
|
40478
40478
|
function locateManifestPath() {
|
|
40479
40479
|
let dir = import.meta.dirname;
|
|
40480
40480
|
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
40481
|
-
const candidate =
|
|
40482
|
-
if (
|
|
40481
|
+
const candidate = join51(dir, "dependencies.json");
|
|
40482
|
+
if (existsSync58(candidate))
|
|
40483
40483
|
return candidate;
|
|
40484
|
-
dir =
|
|
40484
|
+
dir = dirname18(dir);
|
|
40485
40485
|
}
|
|
40486
40486
|
return null;
|
|
40487
40487
|
}
|
|
@@ -40492,7 +40492,7 @@ function loadManifest(manifestPath) {
|
|
|
40492
40492
|
}
|
|
40493
40493
|
let raw;
|
|
40494
40494
|
try {
|
|
40495
|
-
raw =
|
|
40495
|
+
raw = readFileSync53(path4, "utf-8");
|
|
40496
40496
|
} catch (err) {
|
|
40497
40497
|
throw new Error(`Failed to read manifest at ${path4}: ${err.message}`);
|
|
40498
40498
|
}
|
|
@@ -40548,16 +40548,16 @@ function probeClaudeVersion() {
|
|
|
40548
40548
|
}
|
|
40549
40549
|
function probePlaywrightMcpVersion() {
|
|
40550
40550
|
const home2 = process.env.HOME ?? "";
|
|
40551
|
-
const npxCache =
|
|
40552
|
-
if (!
|
|
40551
|
+
const npxCache = join51(home2, ".npm/_npx");
|
|
40552
|
+
if (!existsSync58(npxCache))
|
|
40553
40553
|
return null;
|
|
40554
40554
|
try {
|
|
40555
40555
|
const entries = readdirSync19(npxCache);
|
|
40556
40556
|
for (const entry of entries) {
|
|
40557
|
-
const pkgPath =
|
|
40558
|
-
if (
|
|
40557
|
+
const pkgPath = join51(npxCache, entry, "node_modules/@playwright/mcp/package.json");
|
|
40558
|
+
if (existsSync58(pkgPath)) {
|
|
40559
40559
|
try {
|
|
40560
|
-
const pkg = JSON.parse(
|
|
40560
|
+
const pkg = JSON.parse(readFileSync53(pkgPath, "utf-8"));
|
|
40561
40561
|
if (pkg.version)
|
|
40562
40562
|
return pkg.version;
|
|
40563
40563
|
} catch {}
|
|
@@ -40859,7 +40859,7 @@ var init_doctor_memory = __esm(() => {
|
|
|
40859
40859
|
});
|
|
40860
40860
|
|
|
40861
40861
|
// src/cli/doctor-docker.ts
|
|
40862
|
-
import { readFileSync as
|
|
40862
|
+
import { readFileSync as readFileSync54 } from "node:fs";
|
|
40863
40863
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
40864
40864
|
function imageTagOf(ref) {
|
|
40865
40865
|
if (!ref)
|
|
@@ -40875,7 +40875,7 @@ function isDockerMode(opts) {
|
|
|
40875
40875
|
return true;
|
|
40876
40876
|
if (opts?.composePath) {
|
|
40877
40877
|
try {
|
|
40878
|
-
|
|
40878
|
+
readFileSync54(opts.composePath, "utf8");
|
|
40879
40879
|
return true;
|
|
40880
40880
|
} catch {}
|
|
40881
40881
|
}
|
|
@@ -41157,11 +41157,11 @@ var init_doctor_docker = __esm(() => {
|
|
|
41157
41157
|
});
|
|
41158
41158
|
|
|
41159
41159
|
// src/cli/doctor-auth-broker.ts
|
|
41160
|
-
import { existsSync as
|
|
41160
|
+
import { existsSync as existsSync59, readFileSync as readFileSync55 } from "node:fs";
|
|
41161
41161
|
import { createHash as createHash11 } from "node:crypto";
|
|
41162
41162
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
41163
|
-
import { homedir as
|
|
41164
|
-
import { join as
|
|
41163
|
+
import { homedir as homedir29 } from "node:os";
|
|
41164
|
+
import { join as join52 } from "node:path";
|
|
41165
41165
|
function defaultDockerInspect(container, format) {
|
|
41166
41166
|
try {
|
|
41167
41167
|
const r = spawnSync9("docker", ["inspect", "-f", format, container], { encoding: "utf-8", timeout: 5000 });
|
|
@@ -41261,8 +41261,8 @@ function checkAuthBrokerPerAgentSockets(config, deps = {}) {
|
|
|
41261
41261
|
}
|
|
41262
41262
|
function checkAuthBrokerDrift(deps = {}) {
|
|
41263
41263
|
const stateDir = resolveStateDir(deps);
|
|
41264
|
-
const indexPath =
|
|
41265
|
-
if (!
|
|
41264
|
+
const indexPath = join52(stateDir, "sha-index.json");
|
|
41265
|
+
if (!existsSync59(indexPath)) {
|
|
41266
41266
|
return {
|
|
41267
41267
|
name: "auth-broker: drift",
|
|
41268
41268
|
status: "ok",
|
|
@@ -41271,7 +41271,7 @@ function checkAuthBrokerDrift(deps = {}) {
|
|
|
41271
41271
|
}
|
|
41272
41272
|
let index;
|
|
41273
41273
|
try {
|
|
41274
|
-
index = JSON.parse(
|
|
41274
|
+
index = JSON.parse(readFileSync55(indexPath, "utf-8"));
|
|
41275
41275
|
} catch (err) {
|
|
41276
41276
|
return {
|
|
41277
41277
|
name: "auth-broker: drift",
|
|
@@ -41280,18 +41280,18 @@ function checkAuthBrokerDrift(deps = {}) {
|
|
|
41280
41280
|
fix: "Inspect `~/.switchroom/state/auth-broker/sha-index.json` for corruption."
|
|
41281
41281
|
};
|
|
41282
41282
|
}
|
|
41283
|
-
const home2 = deps.home ??
|
|
41283
|
+
const home2 = deps.home ?? homedir29();
|
|
41284
41284
|
const divergent = [];
|
|
41285
41285
|
const missingOnDisk = [];
|
|
41286
41286
|
for (const [label, expected] of Object.entries(index)) {
|
|
41287
41287
|
const credsPath = accountCredentialsPath(label, home2);
|
|
41288
|
-
if (!
|
|
41288
|
+
if (!existsSync59(credsPath)) {
|
|
41289
41289
|
missingOnDisk.push(label);
|
|
41290
41290
|
continue;
|
|
41291
41291
|
}
|
|
41292
41292
|
let got;
|
|
41293
41293
|
try {
|
|
41294
|
-
got = sha256Hex(
|
|
41294
|
+
got = sha256Hex(readFileSync55(credsPath, "utf-8"));
|
|
41295
41295
|
} catch (err) {
|
|
41296
41296
|
divergent.push(`${label} (read failed: ${err.message})`);
|
|
41297
41297
|
continue;
|
|
@@ -41322,8 +41322,8 @@ function checkAuthBrokerDrift(deps = {}) {
|
|
|
41322
41322
|
}
|
|
41323
41323
|
function checkAuthBrokerThresholdViolations(deps = {}) {
|
|
41324
41324
|
const stateDir = resolveStateDir(deps);
|
|
41325
|
-
const path4 =
|
|
41326
|
-
if (!
|
|
41325
|
+
const path4 = join52(stateDir, "threshold-violations.json");
|
|
41326
|
+
if (!existsSync59(path4)) {
|
|
41327
41327
|
return {
|
|
41328
41328
|
name: "auth-broker: threshold violations",
|
|
41329
41329
|
status: "ok",
|
|
@@ -41332,7 +41332,7 @@ function checkAuthBrokerThresholdViolations(deps = {}) {
|
|
|
41332
41332
|
}
|
|
41333
41333
|
let violations;
|
|
41334
41334
|
try {
|
|
41335
|
-
violations = JSON.parse(
|
|
41335
|
+
violations = JSON.parse(readFileSync55(path4, "utf-8"));
|
|
41336
41336
|
} catch (err) {
|
|
41337
41337
|
return {
|
|
41338
41338
|
name: "auth-broker: threshold violations",
|
|
@@ -41366,9 +41366,9 @@ function checkAuthBrokerActiveAccount(config, deps = {}) {
|
|
|
41366
41366
|
fix: "Run `switchroom auth use <label>` to pin a fleet-wide account, then `switchroom apply`. Without an active account every agent boot fails."
|
|
41367
41367
|
};
|
|
41368
41368
|
}
|
|
41369
|
-
const home2 = deps.home ??
|
|
41369
|
+
const home2 = deps.home ?? homedir29();
|
|
41370
41370
|
const dir = accountDir(active, home2);
|
|
41371
|
-
if (!
|
|
41371
|
+
if (!existsSync59(dir)) {
|
|
41372
41372
|
return {
|
|
41373
41373
|
name: "auth-broker: fleet active account",
|
|
41374
41374
|
status: "fail",
|
|
@@ -41377,7 +41377,7 @@ function checkAuthBrokerActiveAccount(config, deps = {}) {
|
|
|
41377
41377
|
};
|
|
41378
41378
|
}
|
|
41379
41379
|
const creds = accountCredentialsPath(active, home2);
|
|
41380
|
-
if (!
|
|
41380
|
+
if (!existsSync59(creds)) {
|
|
41381
41381
|
return {
|
|
41382
41382
|
name: "auth-broker: fleet active account",
|
|
41383
41383
|
status: "fail",
|
|
@@ -41514,17 +41514,17 @@ var init_doctor_hostd = () => {};
|
|
|
41514
41514
|
import {
|
|
41515
41515
|
accessSync,
|
|
41516
41516
|
constants as fsConstants4,
|
|
41517
|
-
existsSync as
|
|
41517
|
+
existsSync as existsSync60,
|
|
41518
41518
|
realpathSync as realpathSync5,
|
|
41519
|
-
statSync as
|
|
41519
|
+
statSync as statSync25
|
|
41520
41520
|
} from "node:fs";
|
|
41521
|
-
import { userInfo, homedir as
|
|
41522
|
-
import { join as
|
|
41521
|
+
import { userInfo, homedir as homedir30 } from "node:os";
|
|
41522
|
+
import { join as join53 } from "node:path";
|
|
41523
41523
|
function resolveVaultPath2(config) {
|
|
41524
41524
|
return config.vault?.path ? config.vault.path.replace(/^~/, process.env.HOME ?? "") : resolveStatePath("vault.enc");
|
|
41525
41525
|
}
|
|
41526
41526
|
function defaultStatVault(path4) {
|
|
41527
|
-
if (!
|
|
41527
|
+
if (!existsSync60(path4)) {
|
|
41528
41528
|
return { exists: false, readable: false, uid: -1, mode: 0, realPath: path4 };
|
|
41529
41529
|
}
|
|
41530
41530
|
let real = path4;
|
|
@@ -41534,7 +41534,7 @@ function defaultStatVault(path4) {
|
|
|
41534
41534
|
let uid = -1;
|
|
41535
41535
|
let mode = 0;
|
|
41536
41536
|
try {
|
|
41537
|
-
const s =
|
|
41537
|
+
const s = statSync25(real);
|
|
41538
41538
|
uid = s.uid;
|
|
41539
41539
|
mode = s.mode & 511;
|
|
41540
41540
|
} catch {
|
|
@@ -41657,7 +41657,7 @@ async function runSecretAccessChecks(config, deps = {}) {
|
|
|
41657
41657
|
};
|
|
41658
41658
|
const passphrase = deps.passphrase ?? process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
41659
41659
|
if (!passphrase) {
|
|
41660
|
-
const sock = deps.brokerOperatorSocket ??
|
|
41660
|
+
const sock = deps.brokerOperatorSocket ?? join53(homedir30(), ".switchroom", "broker-operator", "sock");
|
|
41661
41661
|
const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
|
|
41662
41662
|
for (const name of Object.keys(config.agents ?? {})) {
|
|
41663
41663
|
const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[name]);
|
|
@@ -41742,8 +41742,8 @@ import {
|
|
|
41742
41742
|
existsSync as realExistsSync,
|
|
41743
41743
|
readFileSync as realReadFileSync
|
|
41744
41744
|
} from "node:fs";
|
|
41745
|
-
import { join as
|
|
41746
|
-
import { homedir as
|
|
41745
|
+
import { join as join54, resolve as resolve34 } from "node:path";
|
|
41746
|
+
import { homedir as homedir31 } from "node:os";
|
|
41747
41747
|
function resolveDeps(config, deps) {
|
|
41748
41748
|
let agentsDir = deps.agentsDir;
|
|
41749
41749
|
if (agentsDir === undefined) {
|
|
@@ -41866,8 +41866,8 @@ function checkScaffoldWiring(config, driveAgents, d) {
|
|
|
41866
41866
|
});
|
|
41867
41867
|
continue;
|
|
41868
41868
|
}
|
|
41869
|
-
const mcpPath =
|
|
41870
|
-
const claudeJsonPath =
|
|
41869
|
+
const mcpPath = join54(agentDir, ".mcp.json");
|
|
41870
|
+
const claudeJsonPath = join54(agentDir, ".claude", ".claude.json");
|
|
41871
41871
|
const mcpRead = readJson(d, mcpPath);
|
|
41872
41872
|
const trustRead = readJson(d, claudeJsonPath);
|
|
41873
41873
|
if (mcpRead.kind === "unreadable" || trustRead.kind === "unreadable") {
|
|
@@ -41980,7 +41980,7 @@ async function runDriveBrokerReachabilityChecks(config, deps = {}) {
|
|
|
41980
41980
|
}
|
|
41981
41981
|
];
|
|
41982
41982
|
}
|
|
41983
|
-
const sock = deps.brokerOperatorSocket ??
|
|
41983
|
+
const sock = deps.brokerOperatorSocket ?? join54(homedir31(), ".switchroom", "broker-operator", "sock");
|
|
41984
41984
|
const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
|
|
41985
41985
|
const results = [];
|
|
41986
41986
|
for (const agent of driveAgents) {
|
|
@@ -42034,8 +42034,8 @@ import {
|
|
|
42034
42034
|
readSync as realReadSync,
|
|
42035
42035
|
closeSync as realCloseSync
|
|
42036
42036
|
} from "node:fs";
|
|
42037
|
-
import { join as
|
|
42038
|
-
import { homedir as
|
|
42037
|
+
import { join as join55 } from "node:path";
|
|
42038
|
+
import { homedir as homedir32 } from "node:os";
|
|
42039
42039
|
function defaultReadHead(p, n) {
|
|
42040
42040
|
let fd;
|
|
42041
42041
|
try {
|
|
@@ -42063,7 +42063,7 @@ function resolveDeps2(config, deps) {
|
|
|
42063
42063
|
}
|
|
42064
42064
|
}
|
|
42065
42065
|
return {
|
|
42066
|
-
homeDir: deps.homeDir ??
|
|
42066
|
+
homeDir: deps.homeDir ?? homedir32(),
|
|
42067
42067
|
agentsDir,
|
|
42068
42068
|
existsSync: deps.existsSync ?? ((p) => realExistsSync2(p)),
|
|
42069
42069
|
readFileSync: deps.readFileSync ?? ((p) => realReadFileSync2(p, "utf-8")),
|
|
@@ -42092,7 +42092,7 @@ function runWebkiteChecks(config, deps = {}) {
|
|
|
42092
42092
|
return [];
|
|
42093
42093
|
const d = resolveDeps2(config, deps);
|
|
42094
42094
|
const results = [];
|
|
42095
|
-
const binPath =
|
|
42095
|
+
const binPath = join55(d.homeDir, ".switchroom", "bin", "webkite");
|
|
42096
42096
|
if (!d.existsSync(binPath)) {
|
|
42097
42097
|
results.push({
|
|
42098
42098
|
name: "webkite: binary",
|
|
@@ -42122,12 +42122,12 @@ function runWebkiteChecks(config, deps = {}) {
|
|
|
42122
42122
|
});
|
|
42123
42123
|
}
|
|
42124
42124
|
}
|
|
42125
|
-
const cloakDir =
|
|
42125
|
+
const cloakDir = join55(d.homeDir, ".cloakbrowser");
|
|
42126
42126
|
let chromeFound = false;
|
|
42127
42127
|
if (d.existsSync(cloakDir)) {
|
|
42128
42128
|
try {
|
|
42129
42129
|
for (const entry of d.readdirSync(cloakDir)) {
|
|
42130
|
-
if (entry.startsWith("chromium-") && d.existsSync(
|
|
42130
|
+
if (entry.startsWith("chromium-") && d.existsSync(join55(cloakDir, entry, "chrome"))) {
|
|
42131
42131
|
chromeFound = true;
|
|
42132
42132
|
break;
|
|
42133
42133
|
}
|
|
@@ -42153,9 +42153,9 @@ function runWebkiteChecks(config, deps = {}) {
|
|
|
42153
42153
|
return results;
|
|
42154
42154
|
}
|
|
42155
42155
|
for (const agent of enabledAgents) {
|
|
42156
|
-
const agentDir =
|
|
42157
|
-
const settingsPath =
|
|
42158
|
-
const mcpPath =
|
|
42156
|
+
const agentDir = join55(d.agentsDir, agent);
|
|
42157
|
+
const settingsPath = join55(agentDir, ".claude", "settings.json");
|
|
42158
|
+
const mcpPath = join55(agentDir, ".mcp.json");
|
|
42159
42159
|
if (!d.existsSync(settingsPath) && !d.existsSync(mcpPath)) {
|
|
42160
42160
|
continue;
|
|
42161
42161
|
}
|
|
@@ -42285,7 +42285,7 @@ var init_doctor_cron_session = __esm(() => {
|
|
|
42285
42285
|
});
|
|
42286
42286
|
|
|
42287
42287
|
// src/cli/doctor-scaffold-wiring.ts
|
|
42288
|
-
import { join as
|
|
42288
|
+
import { join as join56, resolve as resolve36 } from "node:path";
|
|
42289
42289
|
function readJson2(d, path4) {
|
|
42290
42290
|
if (!d.existsSync(path4))
|
|
42291
42291
|
return { kind: "absent" };
|
|
@@ -42328,8 +42328,8 @@ function checkIntegrationScaffoldWiring(args) {
|
|
|
42328
42328
|
});
|
|
42329
42329
|
continue;
|
|
42330
42330
|
}
|
|
42331
|
-
const mcpPath =
|
|
42332
|
-
const claudeJsonPath =
|
|
42331
|
+
const mcpPath = join56(agentDir, ".mcp.json");
|
|
42332
|
+
const claudeJsonPath = join56(agentDir, ".claude", ".claude.json");
|
|
42333
42333
|
const mcpRead = readJson2(deps, mcpPath);
|
|
42334
42334
|
const trustRead = readJson2(deps, claudeJsonPath);
|
|
42335
42335
|
if (mcpRead.kind === "unreadable" || trustRead.kind === "unreadable") {
|
|
@@ -42393,14 +42393,14 @@ import {
|
|
|
42393
42393
|
existsSync as realExistsSync3,
|
|
42394
42394
|
readFileSync as realReadFileSync3
|
|
42395
42395
|
} from "node:fs";
|
|
42396
|
-
import { join as
|
|
42397
|
-
import { homedir as
|
|
42396
|
+
import { join as join57 } from "node:path";
|
|
42397
|
+
import { homedir as homedir33 } from "node:os";
|
|
42398
42398
|
function resolveDeps3(deps) {
|
|
42399
|
-
const home2 = deps.homeDir?.() ??
|
|
42399
|
+
const home2 = deps.homeDir?.() ?? homedir33();
|
|
42400
42400
|
return {
|
|
42401
42401
|
existsSync: deps.existsSync ?? realExistsSync3,
|
|
42402
42402
|
readFileSync: deps.readFileSync ?? realReadFileSync3,
|
|
42403
|
-
agentsDir:
|
|
42403
|
+
agentsDir: join57(home2, ".switchroom", "agents"),
|
|
42404
42404
|
now: deps.now ?? Date.now
|
|
42405
42405
|
};
|
|
42406
42406
|
}
|
|
@@ -42483,7 +42483,7 @@ function checkOAuthClient2(config, anyAgentEnabled) {
|
|
|
42483
42483
|
];
|
|
42484
42484
|
}
|
|
42485
42485
|
function readHeartbeat(d, agentName) {
|
|
42486
|
-
const path4 =
|
|
42486
|
+
const path4 = join57(d.agentsDir, agentName, "m365-launcher.heartbeat.json");
|
|
42487
42487
|
if (!d.existsSync(path4)) {
|
|
42488
42488
|
return { error: "heartbeat file missing \u2014 launcher has not yet started" };
|
|
42489
42489
|
}
|
|
@@ -42580,15 +42580,15 @@ import {
|
|
|
42580
42580
|
readFileSync as realReadFileSync4,
|
|
42581
42581
|
statSync as realStatSync2
|
|
42582
42582
|
} from "node:fs";
|
|
42583
|
-
import { join as
|
|
42584
|
-
import { homedir as
|
|
42583
|
+
import { join as join58 } from "node:path";
|
|
42584
|
+
import { homedir as homedir34 } from "node:os";
|
|
42585
42585
|
function resolveDeps4(deps) {
|
|
42586
|
-
const home2 = deps.homeDir?.() ??
|
|
42586
|
+
const home2 = deps.homeDir?.() ?? homedir34();
|
|
42587
42587
|
return {
|
|
42588
42588
|
existsSync: deps.existsSync ?? realExistsSync4,
|
|
42589
42589
|
readFileSync: deps.readFileSync ?? realReadFileSync4,
|
|
42590
42590
|
statSync: deps.statSync ?? realStatSync2,
|
|
42591
|
-
agentsDir:
|
|
42591
|
+
agentsDir: join58(home2, ".switchroom", "agents"),
|
|
42592
42592
|
now: deps.now ?? Date.now,
|
|
42593
42593
|
vaultAclReader: deps.vaultAclReader ?? (async () => ({ kind: "unreachable", msg: "no default reader wired" }))
|
|
42594
42594
|
};
|
|
@@ -42713,7 +42713,7 @@ function checkLauncherHeartbeat2(notionAgents, d) {
|
|
|
42713
42713
|
return [];
|
|
42714
42714
|
const results = [];
|
|
42715
42715
|
for (const name of notionAgents) {
|
|
42716
|
-
const heartbeatPath =
|
|
42716
|
+
const heartbeatPath = join58(d.agentsDir, name, "notion-launcher.heartbeat.json");
|
|
42717
42717
|
if (!d.existsSync(heartbeatPath)) {
|
|
42718
42718
|
results.push({
|
|
42719
42719
|
name: `notion:launcher-heartbeat:${name}`,
|
|
@@ -42934,11 +42934,11 @@ import {
|
|
|
42934
42934
|
readdirSync as realReaddirSync2,
|
|
42935
42935
|
statSync as realStatSync3
|
|
42936
42936
|
} from "node:fs";
|
|
42937
|
-
import { homedir as
|
|
42938
|
-
import { join as
|
|
42937
|
+
import { homedir as homedir35 } from "node:os";
|
|
42938
|
+
import { join as join59 } from "node:path";
|
|
42939
42939
|
function runCredentialsMigrationChecks(config, deps = {}) {
|
|
42940
|
-
const credDir = deps.credentialsDir ??
|
|
42941
|
-
const
|
|
42940
|
+
const credDir = deps.credentialsDir ?? join59(homedir35(), ".switchroom", "credentials");
|
|
42941
|
+
const existsSync61 = deps.existsSync ?? ((p) => realExistsSync5(p));
|
|
42942
42942
|
const readdirSync21 = deps.readdirSync ?? ((p) => realReaddirSync2(p));
|
|
42943
42943
|
const isDirectory = deps.isDirectory ?? ((p) => {
|
|
42944
42944
|
try {
|
|
@@ -42947,7 +42947,7 @@ function runCredentialsMigrationChecks(config, deps = {}) {
|
|
|
42947
42947
|
return false;
|
|
42948
42948
|
}
|
|
42949
42949
|
});
|
|
42950
|
-
if (!
|
|
42950
|
+
if (!existsSync61(credDir))
|
|
42951
42951
|
return [];
|
|
42952
42952
|
const agentNames = new Set(Object.keys(config.agents ?? {}));
|
|
42953
42953
|
let entries;
|
|
@@ -42965,7 +42965,7 @@ function runCredentialsMigrationChecks(config, deps = {}) {
|
|
|
42965
42965
|
const flat = [];
|
|
42966
42966
|
const perAgentDirs = [];
|
|
42967
42967
|
for (const e of entries) {
|
|
42968
|
-
const full =
|
|
42968
|
+
const full = join59(credDir, e);
|
|
42969
42969
|
if (isDirectory(full) && agentNames.has(e)) {
|
|
42970
42970
|
perAgentDirs.push(e);
|
|
42971
42971
|
} else {
|
|
@@ -43088,19 +43088,19 @@ var init_doctor_inlined_secrets = __esm(() => {
|
|
|
43088
43088
|
|
|
43089
43089
|
// src/cli/doctor-audit-integrity.ts
|
|
43090
43090
|
import { readFileSync as fsReadFileSync2 } from "node:fs";
|
|
43091
|
-
import { homedir as
|
|
43092
|
-
import { join as
|
|
43091
|
+
import { homedir as homedir36 } from "node:os";
|
|
43092
|
+
import { join as join60 } from "node:path";
|
|
43093
43093
|
function rootWrittenLogs(home2) {
|
|
43094
43094
|
return [
|
|
43095
|
-
{ label: "vault-broker", path:
|
|
43095
|
+
{ label: "vault-broker", path: join60(home2, ".switchroom", "vault-audit.log") },
|
|
43096
43096
|
{
|
|
43097
43097
|
label: "hostd",
|
|
43098
|
-
path:
|
|
43098
|
+
path: join60(home2, ".switchroom", "host-control-audit.log")
|
|
43099
43099
|
}
|
|
43100
43100
|
];
|
|
43101
43101
|
}
|
|
43102
43102
|
function runAuditIntegrityChecks(deps = {}) {
|
|
43103
|
-
const home2 = deps.homeDir ??
|
|
43103
|
+
const home2 = deps.homeDir ?? homedir36();
|
|
43104
43104
|
const read = deps.readFileSync ?? ((p) => fsReadFileSync2(p, "utf8"));
|
|
43105
43105
|
const results = [];
|
|
43106
43106
|
for (const { label, path: path4 } of rootWrittenLogs(home2)) {
|
|
@@ -43157,16 +43157,16 @@ var init_doctor_audit_integrity = __esm(() => {
|
|
|
43157
43157
|
});
|
|
43158
43158
|
|
|
43159
43159
|
// src/cli/doctor-agent-smoke.ts
|
|
43160
|
-
import { existsSync as
|
|
43161
|
-
import { homedir as
|
|
43162
|
-
import { join as
|
|
43160
|
+
import { existsSync as existsSync61 } from "node:fs";
|
|
43161
|
+
import { homedir as homedir37 } from "node:os";
|
|
43162
|
+
import { join as join61 } from "node:path";
|
|
43163
43163
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
43164
43164
|
async function runAgentSmokeChecks(config, deps = {}) {
|
|
43165
43165
|
if (deps.fast)
|
|
43166
43166
|
return [];
|
|
43167
|
-
const home2 = deps.homeDir ??
|
|
43168
|
-
const sock = deps.operatorSockPath ??
|
|
43169
|
-
if (!deps.hostdRequestImpl && !
|
|
43167
|
+
const home2 = deps.homeDir ?? homedir37();
|
|
43168
|
+
const sock = deps.operatorSockPath ?? join61(home2, ".switchroom", "hostd", "operator", "sock");
|
|
43169
|
+
if (!deps.hostdRequestImpl && !existsSync61(sock)) {
|
|
43170
43170
|
return [
|
|
43171
43171
|
{
|
|
43172
43172
|
name: "agent liveness",
|
|
@@ -43244,9 +43244,9 @@ var init_doctor_agent_smoke = __esm(() => {
|
|
|
43244
43244
|
|
|
43245
43245
|
// src/cli/doctor-vault-broker-durability.ts
|
|
43246
43246
|
import { execFileSync as execFileSync18 } from "node:child_process";
|
|
43247
|
-
import { existsSync as
|
|
43248
|
-
import { homedir as
|
|
43249
|
-
import { join as
|
|
43247
|
+
import { existsSync as existsSync62, statSync as statSync26 } from "node:fs";
|
|
43248
|
+
import { homedir as homedir38 } from "node:os";
|
|
43249
|
+
import { join as join62 } from "node:path";
|
|
43250
43250
|
function probeBindMountInode(hostPath, brokerContainerPath, opts) {
|
|
43251
43251
|
const statHost = opts?.statHost ?? defaultStatHost;
|
|
43252
43252
|
const statBroker = opts?.statBroker ?? defaultStatBroker;
|
|
@@ -43268,10 +43268,10 @@ function probeBindMountInode(hostPath, brokerContainerPath, opts) {
|
|
|
43268
43268
|
};
|
|
43269
43269
|
}
|
|
43270
43270
|
function defaultStatHost(p) {
|
|
43271
|
-
if (!
|
|
43271
|
+
if (!existsSync62(p))
|
|
43272
43272
|
return null;
|
|
43273
43273
|
try {
|
|
43274
|
-
const s =
|
|
43274
|
+
const s = statSync26(p, { bigint: true });
|
|
43275
43275
|
return { ino: s.ino, size: Number(s.size) };
|
|
43276
43276
|
} catch {
|
|
43277
43277
|
return null;
|
|
@@ -43389,22 +43389,22 @@ function defaultBrokerStatusProbe() {
|
|
|
43389
43389
|
}
|
|
43390
43390
|
}
|
|
43391
43391
|
function runVaultBrokerDurabilityChecks(_config, opts) {
|
|
43392
|
-
const home2 =
|
|
43392
|
+
const home2 = homedir38();
|
|
43393
43393
|
const probe2 = opts?.inodeProbe ?? probeBindMountInode;
|
|
43394
43394
|
return [
|
|
43395
43395
|
probeBrokerUnlocked(opts?.statusProbe),
|
|
43396
43396
|
probeAutoUnlockBlob(home2),
|
|
43397
43397
|
probeMachineIdMount(),
|
|
43398
|
-
formatBindMountResult("vault-broker: vault.enc bind mount",
|
|
43399
|
-
formatBindMountResult("vault-broker: vault-grants.db bind mount (#1737)",
|
|
43400
|
-
formatBindMountResult("vault-broker: vault-audit.log bind mount (#1025)",
|
|
43398
|
+
formatBindMountResult("vault-broker: vault.enc bind mount", join62(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc", probe2(join62(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc")),
|
|
43399
|
+
formatBindMountResult("vault-broker: vault-grants.db bind mount (#1737)", join62(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db", probe2(join62(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db")),
|
|
43400
|
+
formatBindMountResult("vault-broker: vault-audit.log bind mount (#1025)", join62(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log", probe2(join62(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log")),
|
|
43401
43401
|
probeKernelDbDurability(home2, {
|
|
43402
43402
|
statBroker: opts?.kernelStatBroker
|
|
43403
43403
|
})
|
|
43404
43404
|
];
|
|
43405
43405
|
}
|
|
43406
43406
|
function probeKernelDbDurability(home2, opts) {
|
|
43407
|
-
const hostDir =
|
|
43407
|
+
const hostDir = join62(home2, ".switchroom", "approvals");
|
|
43408
43408
|
const containerDir = "/state/approvals";
|
|
43409
43409
|
const name = "approval-kernel: approvals bind mount (allow_always durability)";
|
|
43410
43410
|
const kernelStat = opts?.statBroker ?? defaultKernelStatBroker;
|
|
@@ -43472,8 +43472,8 @@ function defaultKernelStatBroker(p) {
|
|
|
43472
43472
|
return { kind: "ok-with-stat", ino: inoStr, size };
|
|
43473
43473
|
}
|
|
43474
43474
|
function probeAutoUnlockBlob(home2) {
|
|
43475
|
-
const blobPath =
|
|
43476
|
-
if (!
|
|
43475
|
+
const blobPath = join62(home2, ".switchroom", "vault-auto-unlock");
|
|
43476
|
+
if (!existsSync62(blobPath)) {
|
|
43477
43477
|
return {
|
|
43478
43478
|
name: "vault-broker: auto-unlock blob",
|
|
43479
43479
|
status: "warn",
|
|
@@ -43481,7 +43481,7 @@ function probeAutoUnlockBlob(home2) {
|
|
|
43481
43481
|
fix: "Run `switchroom vault broker enable-auto-unlock` to seal the blob with the current passphrase + machine-id"
|
|
43482
43482
|
};
|
|
43483
43483
|
}
|
|
43484
|
-
const sz =
|
|
43484
|
+
const sz = statSync26(blobPath).size;
|
|
43485
43485
|
if (sz === 0) {
|
|
43486
43486
|
return {
|
|
43487
43487
|
name: "vault-broker: auto-unlock blob",
|
|
@@ -43497,7 +43497,7 @@ function probeAutoUnlockBlob(home2) {
|
|
|
43497
43497
|
};
|
|
43498
43498
|
}
|
|
43499
43499
|
function probeMachineIdMount() {
|
|
43500
|
-
const hostExists =
|
|
43500
|
+
const hostExists = existsSync62("/etc/machine-id");
|
|
43501
43501
|
if (!hostExists) {
|
|
43502
43502
|
return {
|
|
43503
43503
|
name: "vault-broker: machine-id passthrough",
|
|
@@ -43645,25 +43645,25 @@ import { execSync as execSync3, spawnSync as spawnSync11 } from "node:child_proc
|
|
|
43645
43645
|
import {
|
|
43646
43646
|
accessSync as accessSync2,
|
|
43647
43647
|
constants as fsConstants5,
|
|
43648
|
-
existsSync as
|
|
43648
|
+
existsSync as existsSync63,
|
|
43649
43649
|
lstatSync as lstatSync7,
|
|
43650
|
-
mkdirSync as
|
|
43651
|
-
readFileSync as
|
|
43650
|
+
mkdirSync as mkdirSync33,
|
|
43651
|
+
readFileSync as readFileSync56,
|
|
43652
43652
|
readdirSync as readdirSync21,
|
|
43653
|
-
statSync as
|
|
43653
|
+
statSync as statSync27
|
|
43654
43654
|
} from "node:fs";
|
|
43655
|
-
import { dirname as
|
|
43655
|
+
import { dirname as dirname19, join as join63, resolve as resolve37 } from "node:path";
|
|
43656
43656
|
import { createPublicKey, createPrivateKey } from "node:crypto";
|
|
43657
43657
|
function findInNvm(bin) {
|
|
43658
|
-
const nvmRoot =
|
|
43659
|
-
if (!
|
|
43658
|
+
const nvmRoot = join63(process.env.HOME ?? "", ".nvm", "versions", "node");
|
|
43659
|
+
if (!existsSync63(nvmRoot))
|
|
43660
43660
|
return null;
|
|
43661
43661
|
try {
|
|
43662
43662
|
const versions = readdirSync21(nvmRoot).sort().reverse();
|
|
43663
43663
|
for (const v of versions) {
|
|
43664
|
-
const candidate =
|
|
43664
|
+
const candidate = join63(nvmRoot, v, "bin", bin);
|
|
43665
43665
|
try {
|
|
43666
|
-
const s =
|
|
43666
|
+
const s = statSync27(candidate);
|
|
43667
43667
|
if (s.isFile() || s.isSymbolicLink()) {
|
|
43668
43668
|
return candidate;
|
|
43669
43669
|
}
|
|
@@ -43826,21 +43826,21 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
|
|
|
43826
43826
|
if (envBrowsersPath && envBrowsersPath.length > 0) {
|
|
43827
43827
|
cacheLocations.push(envBrowsersPath);
|
|
43828
43828
|
}
|
|
43829
|
-
cacheLocations.push(
|
|
43829
|
+
cacheLocations.push(join63(homeDir, ".cache", "ms-playwright"));
|
|
43830
43830
|
for (const cacheDir of cacheLocations) {
|
|
43831
|
-
if (!
|
|
43831
|
+
if (!existsSync63(cacheDir))
|
|
43832
43832
|
continue;
|
|
43833
43833
|
try {
|
|
43834
43834
|
const entries = readdirSync21(cacheDir).filter((e) => e.startsWith("chromium"));
|
|
43835
43835
|
for (const entry of entries) {
|
|
43836
43836
|
const candidates2 = [
|
|
43837
|
-
|
|
43838
|
-
|
|
43839
|
-
|
|
43840
|
-
|
|
43837
|
+
join63(cacheDir, entry, "chrome-linux64", "chrome"),
|
|
43838
|
+
join63(cacheDir, entry, "chrome-linux", "chrome"),
|
|
43839
|
+
join63(cacheDir, entry, "chrome-linux64", "headless_shell"),
|
|
43840
|
+
join63(cacheDir, entry, "chrome-linux", "headless_shell")
|
|
43841
43841
|
];
|
|
43842
43842
|
for (const path4 of candidates2) {
|
|
43843
|
-
if (
|
|
43843
|
+
if (existsSync63(path4))
|
|
43844
43844
|
return path4;
|
|
43845
43845
|
}
|
|
43846
43846
|
}
|
|
@@ -43862,7 +43862,7 @@ function checkChromium() {
|
|
|
43862
43862
|
}
|
|
43863
43863
|
function checkDepsCacheWritable(depsRoot = resolvePath("~/.switchroom/deps")) {
|
|
43864
43864
|
try {
|
|
43865
|
-
|
|
43865
|
+
mkdirSync33(depsRoot, { recursive: true });
|
|
43866
43866
|
accessSync2(depsRoot, fsConstants5.W_OK);
|
|
43867
43867
|
return {
|
|
43868
43868
|
name: "~/.switchroom/deps writable",
|
|
@@ -43969,7 +43969,7 @@ function checkDeployMounts(opts) {
|
|
|
43969
43969
|
const home2 = opts?.home ?? process.env.HOME ?? "/root";
|
|
43970
43970
|
const { pathKind } = opts?.deps ?? DEFAULT_DEPLOY_MOUNTS_DEPS;
|
|
43971
43971
|
const results = [];
|
|
43972
|
-
const dockerComposePlugin =
|
|
43972
|
+
const dockerComposePlugin = join63(home2, ".docker", "cli-plugins", "docker-compose");
|
|
43973
43973
|
const pluginKind = pathKind(dockerComposePlugin);
|
|
43974
43974
|
if (pluginKind === "dir") {
|
|
43975
43975
|
results.push({
|
|
@@ -44007,8 +44007,8 @@ function checkDeployMounts(opts) {
|
|
|
44007
44007
|
function checkLegacyState() {
|
|
44008
44008
|
const results = [];
|
|
44009
44009
|
const h = process.env.HOME ?? "/root";
|
|
44010
|
-
const clerkDir =
|
|
44011
|
-
const clerkPresent =
|
|
44010
|
+
const clerkDir = join63(h, LEGACY_STATE_DIR);
|
|
44011
|
+
const clerkPresent = existsSync63(clerkDir);
|
|
44012
44012
|
results.push({
|
|
44013
44013
|
name: "legacy ~/.clerk state",
|
|
44014
44014
|
status: clerkPresent ? "warn" : "ok",
|
|
@@ -44017,7 +44017,7 @@ function checkLegacyState() {
|
|
|
44017
44017
|
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."
|
|
44018
44018
|
} : {}
|
|
44019
44019
|
});
|
|
44020
|
-
const legacySock =
|
|
44020
|
+
const legacySock = join63(h, ".switchroom", "vault-broker.sock");
|
|
44021
44021
|
let sockStat = null;
|
|
44022
44022
|
try {
|
|
44023
44023
|
sockStat = lstatSync7(legacySock);
|
|
@@ -44138,7 +44138,7 @@ function checkVault(config) {
|
|
|
44138
44138
|
detail: "Approval auth: passphrase (two-factor)"
|
|
44139
44139
|
};
|
|
44140
44140
|
const pairsResult = checkVaultBrokerSocketPairs(config);
|
|
44141
|
-
if (!
|
|
44141
|
+
if (!existsSync63(vaultPath)) {
|
|
44142
44142
|
return [
|
|
44143
44143
|
postureResult,
|
|
44144
44144
|
{
|
|
@@ -44416,8 +44416,8 @@ async function checkHindsight(config) {
|
|
|
44416
44416
|
}
|
|
44417
44417
|
function checkPendingRetainsQueue(dir) {
|
|
44418
44418
|
const home2 = process.env.HOME ?? "";
|
|
44419
|
-
const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ??
|
|
44420
|
-
if (!
|
|
44419
|
+
const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ?? join63(home2, ".hindsight", "pending-retains");
|
|
44420
|
+
if (!existsSync63(pendingDir)) {
|
|
44421
44421
|
return {
|
|
44422
44422
|
name: "pending-retains queue",
|
|
44423
44423
|
status: "ok",
|
|
@@ -44476,7 +44476,7 @@ function classifyReadError(err) {
|
|
|
44476
44476
|
}
|
|
44477
44477
|
function tryReadHostFile(path4) {
|
|
44478
44478
|
try {
|
|
44479
|
-
return { kind: "ok", content:
|
|
44479
|
+
return { kind: "ok", content: readFileSync56(path4, "utf-8") };
|
|
44480
44480
|
} catch (err) {
|
|
44481
44481
|
const kind = classifyReadError(err);
|
|
44482
44482
|
const error = err?.message ?? String(err);
|
|
@@ -44488,11 +44488,11 @@ function tryReadHostFile(path4) {
|
|
|
44488
44488
|
}
|
|
44489
44489
|
}
|
|
44490
44490
|
function parseEnvFile(path4) {
|
|
44491
|
-
if (!
|
|
44491
|
+
if (!existsSync63(path4))
|
|
44492
44492
|
return {};
|
|
44493
44493
|
let content;
|
|
44494
44494
|
try {
|
|
44495
|
-
content =
|
|
44495
|
+
content = readFileSync56(path4, "utf-8");
|
|
44496
44496
|
} catch {
|
|
44497
44497
|
return {};
|
|
44498
44498
|
}
|
|
@@ -44547,7 +44547,7 @@ async function checkTelegram(config) {
|
|
|
44547
44547
|
const plugin = agentConfig.channels?.telegram?.plugin ?? "switchroom";
|
|
44548
44548
|
if (plugin !== "switchroom")
|
|
44549
44549
|
continue;
|
|
44550
|
-
const envPath =
|
|
44550
|
+
const envPath = join63(agentsDir, name, "telegram", ".env");
|
|
44551
44551
|
const read = tryReadHostFile(envPath);
|
|
44552
44552
|
if (read.kind === "eacces") {
|
|
44553
44553
|
results.push({
|
|
@@ -44599,7 +44599,7 @@ async function checkTelegram(config) {
|
|
|
44599
44599
|
}
|
|
44600
44600
|
function checkStartShStale(agentName, startShPath) {
|
|
44601
44601
|
const label = `${agentName}: start.sh scheduler block`;
|
|
44602
|
-
if (!
|
|
44602
|
+
if (!existsSync63(startShPath)) {
|
|
44603
44603
|
return {
|
|
44604
44604
|
name: label,
|
|
44605
44605
|
status: "warn",
|
|
@@ -44609,7 +44609,7 @@ function checkStartShStale(agentName, startShPath) {
|
|
|
44609
44609
|
}
|
|
44610
44610
|
let content;
|
|
44611
44611
|
try {
|
|
44612
|
-
content =
|
|
44612
|
+
content = readFileSync56(startShPath, "utf-8");
|
|
44613
44613
|
} catch (err) {
|
|
44614
44614
|
return {
|
|
44615
44615
|
name: label,
|
|
@@ -44630,7 +44630,7 @@ function checkStartShStale(agentName, startShPath) {
|
|
|
44630
44630
|
}
|
|
44631
44631
|
function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
44632
44632
|
const label = `${agentName}: $HOME/.switchroom symlink (#910)`;
|
|
44633
|
-
const path4 =
|
|
44633
|
+
const path4 = join63(agentDir, "home", ".switchroom");
|
|
44634
44634
|
let stats;
|
|
44635
44635
|
try {
|
|
44636
44636
|
stats = lstatSync7(path4);
|
|
@@ -44667,8 +44667,8 @@ function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
|
44667
44667
|
}
|
|
44668
44668
|
function checkRepoHygiene(repoRoot) {
|
|
44669
44669
|
const results = [];
|
|
44670
|
-
const exportDir =
|
|
44671
|
-
if (
|
|
44670
|
+
const exportDir = join63(repoRoot, "clerk-export");
|
|
44671
|
+
if (existsSync63(exportDir)) {
|
|
44672
44672
|
results.push({
|
|
44673
44673
|
name: "repo hygiene: clerk-export/ on disk (#1072)",
|
|
44674
44674
|
status: "warn",
|
|
@@ -44676,8 +44676,8 @@ function checkRepoHygiene(repoRoot) {
|
|
|
44676
44676
|
fix: `Run scripts/migrate-clerk-export-to-vault.sh to move the bundle ` + `into the vault, then delete the on-disk copy.`
|
|
44677
44677
|
});
|
|
44678
44678
|
}
|
|
44679
|
-
const knownTarball =
|
|
44680
|
-
if (
|
|
44679
|
+
const knownTarball = join63(repoRoot, "clerk-export-with-secrets.tar.gz");
|
|
44680
|
+
if (existsSync63(knownTarball)) {
|
|
44681
44681
|
results.push({
|
|
44682
44682
|
name: "repo hygiene: clerk-export-with-secrets.tar.gz on disk (#1072)",
|
|
44683
44683
|
status: "warn",
|
|
@@ -44694,7 +44694,7 @@ function checkRepoHygiene(repoRoot) {
|
|
|
44694
44694
|
results.push({
|
|
44695
44695
|
name: `repo hygiene: ${name} on disk (#1072)`,
|
|
44696
44696
|
status: "warn",
|
|
44697
|
-
detail: `${
|
|
44697
|
+
detail: `${join63(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
|
|
44698
44698
|
fix: `Inspect, migrate any secrets into the vault, then delete the ` + `archive.`
|
|
44699
44699
|
});
|
|
44700
44700
|
}
|
|
@@ -44717,12 +44717,12 @@ function checkRepoHygiene(repoRoot) {
|
|
|
44717
44717
|
}
|
|
44718
44718
|
function isSwitchroomCheckout(dir) {
|
|
44719
44719
|
try {
|
|
44720
|
-
if (!
|
|
44720
|
+
if (!existsSync63(join63(dir, ".git")))
|
|
44721
44721
|
return false;
|
|
44722
|
-
const pkgPath =
|
|
44723
|
-
if (!
|
|
44722
|
+
const pkgPath = join63(dir, "package.json");
|
|
44723
|
+
if (!existsSync63(pkgPath))
|
|
44724
44724
|
return false;
|
|
44725
|
-
const pkg = JSON.parse(
|
|
44725
|
+
const pkg = JSON.parse(readFileSync56(pkgPath, "utf-8"));
|
|
44726
44726
|
return pkg.name === "switchroom";
|
|
44727
44727
|
} catch {
|
|
44728
44728
|
return false;
|
|
@@ -44735,7 +44735,7 @@ function checkAgents(config, configPath) {
|
|
|
44735
44735
|
const authStatuses = getAllAuthStatuses(config);
|
|
44736
44736
|
for (const [name, agentConfig] of Object.entries(config.agents)) {
|
|
44737
44737
|
const agentDir = resolve37(agentsDir, name);
|
|
44738
|
-
if (!
|
|
44738
|
+
if (!existsSync63(agentDir)) {
|
|
44739
44739
|
results.push({
|
|
44740
44740
|
name: `${name}: scaffold`,
|
|
44741
44741
|
status: "fail",
|
|
@@ -44756,7 +44756,7 @@ function checkAgents(config, configPath) {
|
|
|
44756
44756
|
fix: `Rotate the bot token (e.g. via \`switchroom vault\`), then run ` + `\`switchroom agent unquarantine ${name}\` and \`switchroom agent restart ${name}\``
|
|
44757
44757
|
});
|
|
44758
44758
|
}
|
|
44759
|
-
results.push(checkStartShStale(name,
|
|
44759
|
+
results.push(checkStartShStale(name, join63(agentDir, "start.sh")));
|
|
44760
44760
|
results.push(checkLeakedHomeSwitchroom(name, agentDir));
|
|
44761
44761
|
const status = statuses[name];
|
|
44762
44762
|
const active = status?.active ?? "unknown";
|
|
@@ -44833,8 +44833,8 @@ function checkAgents(config, configPath) {
|
|
|
44833
44833
|
}
|
|
44834
44834
|
}
|
|
44835
44835
|
if (agentConfig.channels?.telegram?.plugin === "switchroom") {
|
|
44836
|
-
const mcpJsonPath =
|
|
44837
|
-
if (!
|
|
44836
|
+
const mcpJsonPath = join63(agentDir, ".mcp.json");
|
|
44837
|
+
if (!existsSync63(mcpJsonPath)) {
|
|
44838
44838
|
results.push({
|
|
44839
44839
|
name: `${name}: .mcp.json`,
|
|
44840
44840
|
status: "fail",
|
|
@@ -44843,7 +44843,7 @@ function checkAgents(config, configPath) {
|
|
|
44843
44843
|
});
|
|
44844
44844
|
} else {
|
|
44845
44845
|
try {
|
|
44846
|
-
const mcp = JSON.parse(
|
|
44846
|
+
const mcp = JSON.parse(readFileSync56(mcpJsonPath, "utf-8"));
|
|
44847
44847
|
const hasSwitchroomTelegram = !!mcp.mcpServers?.["switchroom-telegram"];
|
|
44848
44848
|
const memoryEnabled = isHindsightEnabled(config);
|
|
44849
44849
|
const hasHindsight = !!mcp.mcpServers?.hindsight;
|
|
@@ -44920,7 +44920,7 @@ function mffEnvPath(config) {
|
|
|
44920
44920
|
return agent ? resolve37(home2, ".switchroom/credentials", agent, "my-family-finance/.env") : resolve37(home2, ".switchroom/credentials/my-family-finance/.env");
|
|
44921
44921
|
}
|
|
44922
44922
|
function mffEnvState(envPath) {
|
|
44923
|
-
if (!
|
|
44923
|
+
if (!existsSync63(envPath))
|
|
44924
44924
|
return "absent";
|
|
44925
44925
|
try {
|
|
44926
44926
|
accessSync2(envPath, fsConstants5.R_OK);
|
|
@@ -44938,7 +44938,7 @@ function checkMffVaultKeyPresent(passphrase, vaultPath) {
|
|
|
44938
44938
|
fix: "Export SWITCHROOM_VAULT_PASSPHRASE to enable MFF vault probes"
|
|
44939
44939
|
};
|
|
44940
44940
|
}
|
|
44941
|
-
if (!
|
|
44941
|
+
if (!existsSync63(vaultPath)) {
|
|
44942
44942
|
return {
|
|
44943
44943
|
name: "mff: vault key present",
|
|
44944
44944
|
status: "fail",
|
|
@@ -44991,7 +44991,7 @@ function deriveEd25519PublicKeyBytes(keyMaterial) {
|
|
|
44991
44991
|
}
|
|
44992
44992
|
}
|
|
44993
44993
|
function checkMffVaultKeyFormat(passphrase, vaultPath) {
|
|
44994
|
-
if (!passphrase || !
|
|
44994
|
+
if (!passphrase || !existsSync63(vaultPath)) {
|
|
44995
44995
|
return {
|
|
44996
44996
|
name: "mff: vault key format",
|
|
44997
44997
|
status: "warn",
|
|
@@ -45134,9 +45134,9 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
|
|
|
45134
45134
|
detail: "skipped (MFF_API_URL not set)"
|
|
45135
45135
|
};
|
|
45136
45136
|
}
|
|
45137
|
-
const credDir =
|
|
45138
|
-
const authScript =
|
|
45139
|
-
if (!
|
|
45137
|
+
const credDir = dirname19(envPath);
|
|
45138
|
+
const authScript = join63(credDir, "claude-auth.py");
|
|
45139
|
+
if (!existsSync63(authScript)) {
|
|
45140
45140
|
return {
|
|
45141
45141
|
name: "mff: auth flow",
|
|
45142
45142
|
status: "warn",
|
|
@@ -45338,11 +45338,11 @@ function runDockerSection(config) {
|
|
|
45338
45338
|
let composeYaml;
|
|
45339
45339
|
let dockerfileAgent;
|
|
45340
45340
|
try {
|
|
45341
|
-
composeYaml =
|
|
45341
|
+
composeYaml = readFileSync56(composePath, "utf8");
|
|
45342
45342
|
} catch {}
|
|
45343
45343
|
const dockerfilePath = resolve37(process.env.HOME ?? "", ".switchroom", "docker", "Dockerfile.agent");
|
|
45344
45344
|
try {
|
|
45345
|
-
dockerfileAgent =
|
|
45345
|
+
dockerfileAgent = readFileSync56(dockerfilePath, "utf8");
|
|
45346
45346
|
} catch {}
|
|
45347
45347
|
return runDockerChecks({
|
|
45348
45348
|
config,
|
|
@@ -48662,8 +48662,8 @@ agents:
|
|
|
48662
48662
|
var init_minimal = () => {};
|
|
48663
48663
|
|
|
48664
48664
|
// src/agents/connection-health.ts
|
|
48665
|
-
import { mkdirSync as
|
|
48666
|
-
import { join as
|
|
48665
|
+
import { mkdirSync as mkdirSync44, writeFileSync as writeFileSync36 } from "node:fs";
|
|
48666
|
+
import { join as join78 } from "node:path";
|
|
48667
48667
|
async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
|
|
48668
48668
|
const reqs = computeMcpSecretRequirements(config).filter((r) => r.agent === agentName);
|
|
48669
48669
|
if (reqs.length === 0)
|
|
@@ -48720,10 +48720,10 @@ async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
|
|
|
48720
48720
|
return issues;
|
|
48721
48721
|
}
|
|
48722
48722
|
function writeConnectionHealthFile(agentDir, health, deps) {
|
|
48723
|
-
const dir =
|
|
48724
|
-
const path7 =
|
|
48725
|
-
(deps?.mkdir ?? ((p, o) =>
|
|
48726
|
-
(deps?.writeFile ?? ((p, d) =>
|
|
48723
|
+
const dir = join78(agentDir, ".claude");
|
|
48724
|
+
const path7 = join78(dir, CONNECTION_HEALTH_FILENAME);
|
|
48725
|
+
(deps?.mkdir ?? ((p, o) => mkdirSync44(p, o)))(dir, { recursive: true });
|
|
48726
|
+
(deps?.writeFile ?? ((p, d) => writeFileSync36(p, d)))(path7, JSON.stringify(health, null, 2) + `
|
|
48727
48727
|
`);
|
|
48728
48728
|
}
|
|
48729
48729
|
async function refreshAgentConnectionHealth(config, agentName, agentDir, deps) {
|
|
@@ -48744,10 +48744,10 @@ var CONNECTION_HEALTH_FILENAME = "connection-health.json";
|
|
|
48744
48744
|
var init_connection_health = () => {};
|
|
48745
48745
|
|
|
48746
48746
|
// src/cli/update-prompt-hook.ts
|
|
48747
|
-
import { existsSync as
|
|
48748
|
-
import { join as
|
|
48747
|
+
import { existsSync as existsSync80, readFileSync as readFileSync68, writeFileSync as writeFileSync37, chmodSync as chmodSync10, mkdirSync as mkdirSync45 } from "node:fs";
|
|
48748
|
+
import { join as join79 } from "node:path";
|
|
48749
48749
|
function containerHookCommand() {
|
|
48750
|
-
return
|
|
48750
|
+
return join79(CONTAINER_AGENT_DIR, ".claude", "hooks", HOOK_FILENAME);
|
|
48751
48751
|
}
|
|
48752
48752
|
function updatePromptHookScript() {
|
|
48753
48753
|
return `#!/bin/bash
|
|
@@ -48813,14 +48813,14 @@ exit 0
|
|
|
48813
48813
|
`;
|
|
48814
48814
|
}
|
|
48815
48815
|
function installUpdatePromptHook(agentDir) {
|
|
48816
|
-
const hooksDir =
|
|
48817
|
-
|
|
48818
|
-
const scriptPath =
|
|
48816
|
+
const hooksDir = join79(agentDir, ".claude", "hooks");
|
|
48817
|
+
mkdirSync45(hooksDir, { recursive: true });
|
|
48818
|
+
const scriptPath = join79(hooksDir, HOOK_FILENAME);
|
|
48819
48819
|
const desired = updatePromptHookScript();
|
|
48820
48820
|
let installed = false;
|
|
48821
|
-
const existing =
|
|
48821
|
+
const existing = existsSync80(scriptPath) ? readFileSync68(scriptPath, "utf-8") : "";
|
|
48822
48822
|
if (existing !== desired) {
|
|
48823
|
-
|
|
48823
|
+
writeFileSync37(scriptPath, desired, { mode: 493 });
|
|
48824
48824
|
chmodSync10(scriptPath, 493);
|
|
48825
48825
|
installed = true;
|
|
48826
48826
|
} else {
|
|
@@ -48828,11 +48828,11 @@ function installUpdatePromptHook(agentDir) {
|
|
|
48828
48828
|
chmodSync10(scriptPath, 493);
|
|
48829
48829
|
} catch {}
|
|
48830
48830
|
}
|
|
48831
|
-
const settingsPath =
|
|
48832
|
-
if (!
|
|
48831
|
+
const settingsPath = join79(agentDir, ".claude", "settings.json");
|
|
48832
|
+
if (!existsSync80(settingsPath)) {
|
|
48833
48833
|
return { scriptPath, settingsPath, installed };
|
|
48834
48834
|
}
|
|
48835
|
-
const raw =
|
|
48835
|
+
const raw = readFileSync68(settingsPath, "utf-8");
|
|
48836
48836
|
let parsed;
|
|
48837
48837
|
try {
|
|
48838
48838
|
parsed = JSON.parse(raw);
|
|
@@ -48868,7 +48868,7 @@ function installUpdatePromptHook(agentDir) {
|
|
|
48868
48868
|
if (mutated) {
|
|
48869
48869
|
hooks.UserPromptSubmit = list2;
|
|
48870
48870
|
parsed.hooks = hooks;
|
|
48871
|
-
|
|
48871
|
+
writeFileSync37(settingsPath, JSON.stringify(parsed, null, 2) + `
|
|
48872
48872
|
`, { mode: 384 });
|
|
48873
48873
|
installed = true;
|
|
48874
48874
|
} else if (!alreadyCorrect) {
|
|
@@ -48877,7 +48877,7 @@ function installUpdatePromptHook(agentDir) {
|
|
|
48877
48877
|
});
|
|
48878
48878
|
hooks.UserPromptSubmit = list2;
|
|
48879
48879
|
parsed.hooks = hooks;
|
|
48880
|
-
|
|
48880
|
+
writeFileSync37(settingsPath, JSON.stringify(parsed, null, 2) + `
|
|
48881
48881
|
`, { mode: 384 });
|
|
48882
48882
|
installed = true;
|
|
48883
48883
|
}
|
|
@@ -49169,8 +49169,8 @@ __export(exports_voice_sidecar_token, {
|
|
|
49169
49169
|
VOICE_SIDECAR_TOKEN_ENV: () => VOICE_SIDECAR_TOKEN_ENV
|
|
49170
49170
|
});
|
|
49171
49171
|
import { randomBytes as randomBytes14 } from "node:crypto";
|
|
49172
|
-
import { chmodSync as chmodSync11, chownSync as chownSync7, existsSync as
|
|
49173
|
-
import { dirname as
|
|
49172
|
+
import { chmodSync as chmodSync11, chownSync as chownSync7, existsSync as existsSync82, mkdirSync as mkdirSync46, readFileSync as readFileSync69, rmSync as rmSync17, writeFileSync as writeFileSync38 } from "node:fs";
|
|
49173
|
+
import { dirname as dirname28 } from "node:path";
|
|
49174
49174
|
async function defaultResolveOrSeedToken(home2, writeErr) {
|
|
49175
49175
|
const [{ getViaBrokerStructured: getViaBrokerStructured2, putViaBroker: putViaBroker2 }, { resolveOperatorVaultPassphrase }] = await Promise.all([
|
|
49176
49176
|
Promise.resolve().then(() => (init_client(), exports_client)),
|
|
@@ -49204,8 +49204,8 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
|
|
|
49204
49204
|
const envPath = composeEnvPath(composePath);
|
|
49205
49205
|
if (engine !== "local") {
|
|
49206
49206
|
try {
|
|
49207
|
-
if (
|
|
49208
|
-
const body =
|
|
49207
|
+
if (existsSync82(envPath)) {
|
|
49208
|
+
const body = readFileSync69(envPath, "utf-8");
|
|
49209
49209
|
if (body.includes(`${VOICE_SIDECAR_TOKEN_ENV}=`))
|
|
49210
49210
|
rmSync17(envPath);
|
|
49211
49211
|
}
|
|
@@ -49224,11 +49224,11 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
|
|
|
49224
49224
|
if (!token)
|
|
49225
49225
|
return;
|
|
49226
49226
|
try {
|
|
49227
|
-
|
|
49227
|
+
mkdirSync46(dirname28(envPath), { recursive: true });
|
|
49228
49228
|
let body = "";
|
|
49229
49229
|
try {
|
|
49230
|
-
if (
|
|
49231
|
-
body =
|
|
49230
|
+
if (existsSync82(envPath))
|
|
49231
|
+
body = readFileSync69(envPath, "utf-8");
|
|
49232
49232
|
} catch {}
|
|
49233
49233
|
const line = `${VOICE_SIDECAR_TOKEN_ENV}=${token}`;
|
|
49234
49234
|
const keyRe = new RegExp(`^${VOICE_SIDECAR_TOKEN_ENV}=.*$`, "m");
|
|
@@ -49236,7 +49236,7 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
|
|
|
49236
49236
|
`) ? body + `
|
|
49237
49237
|
` : body) + line + `
|
|
49238
49238
|
`;
|
|
49239
|
-
|
|
49239
|
+
writeFileSync38(envPath, next, {
|
|
49240
49240
|
encoding: "utf-8",
|
|
49241
49241
|
mode: 384
|
|
49242
49242
|
});
|
|
@@ -49286,12 +49286,12 @@ __export(exports_apply, {
|
|
|
49286
49286
|
DEFAULT_COMPOSE_PATH: () => DEFAULT_COMPOSE_PATH2,
|
|
49287
49287
|
COMPOSE_PROJECT: () => COMPOSE_PROJECT2
|
|
49288
49288
|
});
|
|
49289
|
-
import { accessSync as accessSync3, chownSync as chownSync8, constants as fsConstants6, copyFileSync as copyFileSync12, existsSync as
|
|
49289
|
+
import { accessSync as accessSync3, chownSync as chownSync8, constants as fsConstants6, copyFileSync as copyFileSync12, existsSync as existsSync83, mkdirSync as mkdirSync47, readFileSync as readFileSync70, readdirSync as readdirSync28, renameSync as renameSync18, writeFileSync as writeFileSync39 } from "node:fs";
|
|
49290
49290
|
import { mkdir as mkdir2 } from "node:fs/promises";
|
|
49291
49291
|
import { spawnSync as childSpawnSync } from "node:child_process";
|
|
49292
49292
|
import readline from "node:readline";
|
|
49293
|
-
import { dirname as
|
|
49294
|
-
import { homedir as
|
|
49293
|
+
import { dirname as dirname29, join as join81, resolve as resolve50 } from "node:path";
|
|
49294
|
+
import { homedir as homedir48 } from "node:os";
|
|
49295
49295
|
import { execFileSync as execFileSync25 } from "node:child_process";
|
|
49296
49296
|
function effectiveLiteLLMEnabled(config, agentResolvedLitellm) {
|
|
49297
49297
|
return agentResolvedLitellm?.enabled ?? config.litellm?.enabled ?? false;
|
|
@@ -49302,7 +49302,7 @@ async function resolveOperatorVaultPassphrase(home2) {
|
|
|
49302
49302
|
return envPass;
|
|
49303
49303
|
try {
|
|
49304
49304
|
const { readAutoUnlockFile: readAutoUnlockFile2 } = await Promise.resolve().then(() => (init_auto_unlock(), exports_auto_unlock));
|
|
49305
|
-
const blobPath =
|
|
49305
|
+
const blobPath = join81(home2, ".switchroom", "vault-auto-unlock");
|
|
49306
49306
|
const pass = readAutoUnlockFile2(blobPath);
|
|
49307
49307
|
return pass && pass.length > 0 ? pass : null;
|
|
49308
49308
|
} catch {
|
|
@@ -49329,7 +49329,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
49329
49329
|
Promise.resolve().then(() => (init_provision(), exports_provision)),
|
|
49330
49330
|
Promise.resolve().then(() => (init_telegram_yaml(), exports_telegram_yaml))
|
|
49331
49331
|
]);
|
|
49332
|
-
const passphrase = await resolveOperatorVaultPassphrase(ctx.home ??
|
|
49332
|
+
const passphrase = await resolveOperatorVaultPassphrase(ctx.home ?? homedir48());
|
|
49333
49333
|
if (passphrase === null) {
|
|
49334
49334
|
const { getViaBrokerStructured: getViaBrokerStructured3 } = await Promise.resolve().then(() => (init_client(), exports_client));
|
|
49335
49335
|
const alreadyProvisioned = [];
|
|
@@ -49390,9 +49390,9 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
49390
49390
|
const oauthAccount = config.auth?.active;
|
|
49391
49391
|
let pendingConfigEdits = false;
|
|
49392
49392
|
let configText = null;
|
|
49393
|
-
if (switchroomConfigPath &&
|
|
49393
|
+
if (switchroomConfigPath && existsSync83(switchroomConfigPath)) {
|
|
49394
49394
|
try {
|
|
49395
|
-
configText =
|
|
49395
|
+
configText = readFileSync70(switchroomConfigPath, "utf-8");
|
|
49396
49396
|
} catch (err) {
|
|
49397
49397
|
ctx.writeErr(source_default.yellow(` ! litellm: could not read config for ACL grants (${err.message}); keys will be provisioned but agents may lack read-ACL.
|
|
49398
49398
|
`));
|
|
@@ -49515,7 +49515,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
49515
49515
|
}
|
|
49516
49516
|
if (pendingConfigEdits && configText !== null && switchroomConfigPath) {
|
|
49517
49517
|
try {
|
|
49518
|
-
|
|
49518
|
+
writeFileSync39(switchroomConfigPath, configText, "utf-8");
|
|
49519
49519
|
writeOut(source_default.gray(` ~ litellm: granted read-ACL on per-agent keys (updated ${switchroomConfigPath})
|
|
49520
49520
|
`));
|
|
49521
49521
|
} catch (err) {
|
|
@@ -49572,12 +49572,12 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
49572
49572
|
function resolveVaultBindMountDir(homeDir, ctx) {
|
|
49573
49573
|
const isCustomPath = ctx.migrationKind === "custom-path-skipped";
|
|
49574
49574
|
if (isCustomPath && ctx.customVaultPath) {
|
|
49575
|
-
return
|
|
49575
|
+
return dirname29(ctx.customVaultPath);
|
|
49576
49576
|
}
|
|
49577
|
-
return
|
|
49577
|
+
return join81(homeDir, ".switchroom", "vault");
|
|
49578
49578
|
}
|
|
49579
49579
|
function inspectVaultBindMountDir(vaultDir) {
|
|
49580
|
-
if (!
|
|
49580
|
+
if (!existsSync83(vaultDir))
|
|
49581
49581
|
return { kind: "missing" };
|
|
49582
49582
|
const entries = readdirSync28(vaultDir);
|
|
49583
49583
|
const unknown = [];
|
|
@@ -49605,61 +49605,61 @@ function hasVaultRefs(value) {
|
|
|
49605
49605
|
async function ensureHostMountSources(config) {
|
|
49606
49606
|
const home2 = resolveHostHomeForCompose();
|
|
49607
49607
|
const dirs = [
|
|
49608
|
-
|
|
49609
|
-
|
|
49610
|
-
|
|
49611
|
-
|
|
49612
|
-
|
|
49608
|
+
join81(home2, ".switchroom", "approvals"),
|
|
49609
|
+
join81(home2, ".switchroom", "scheduler"),
|
|
49610
|
+
join81(home2, ".switchroom", "logs"),
|
|
49611
|
+
join81(home2, ".switchroom", "compose"),
|
|
49612
|
+
join81(home2, ".switchroom", "broker-operator")
|
|
49613
49613
|
];
|
|
49614
49614
|
for (const name of Object.keys(config.agents)) {
|
|
49615
|
-
dirs.push(
|
|
49616
|
-
dirs.push(
|
|
49617
|
-
dirs.push(
|
|
49618
|
-
dirs.push(
|
|
49619
|
-
if (
|
|
49620
|
-
dirs.push(
|
|
49615
|
+
dirs.push(join81(home2, ".switchroom", "agents", name));
|
|
49616
|
+
dirs.push(join81(home2, ".switchroom", "logs", name));
|
|
49617
|
+
dirs.push(join81(home2, ".claude", "projects", name));
|
|
49618
|
+
dirs.push(join81(home2, ".switchroom", "audit", name));
|
|
49619
|
+
if (existsSync83(join81(home2, ".switchroom-config"))) {
|
|
49620
|
+
dirs.push(join81(home2, ".switchroom-config", "agents", name, "personal-skills"));
|
|
49621
49621
|
}
|
|
49622
49622
|
}
|
|
49623
49623
|
for (const dir of dirs) {
|
|
49624
49624
|
await mkdir2(dir, { recursive: true });
|
|
49625
49625
|
}
|
|
49626
|
-
const autoUnlockPath =
|
|
49627
|
-
if (!
|
|
49628
|
-
|
|
49626
|
+
const autoUnlockPath = join81(home2, ".switchroom", "vault-auto-unlock");
|
|
49627
|
+
if (!existsSync83(autoUnlockPath)) {
|
|
49628
|
+
writeFileSync39(autoUnlockPath, "", { mode: 384 });
|
|
49629
49629
|
}
|
|
49630
|
-
const auditLogPath =
|
|
49631
|
-
if (!
|
|
49632
|
-
|
|
49630
|
+
const auditLogPath = join81(home2, ".switchroom", "vault-audit.log");
|
|
49631
|
+
if (!existsSync83(auditLogPath)) {
|
|
49632
|
+
writeFileSync39(auditLogPath, "", { mode: 420 });
|
|
49633
49633
|
}
|
|
49634
|
-
const grantsDbPath =
|
|
49635
|
-
if (!
|
|
49636
|
-
|
|
49634
|
+
const grantsDbPath = join81(home2, ".switchroom", "vault-grants.db");
|
|
49635
|
+
if (!existsSync83(grantsDbPath)) {
|
|
49636
|
+
writeFileSync39(grantsDbPath, "", { mode: 384 });
|
|
49637
49637
|
}
|
|
49638
|
-
const hostdAuditLogPath =
|
|
49639
|
-
if (!
|
|
49640
|
-
|
|
49638
|
+
const hostdAuditLogPath = join81(home2, ".switchroom", "host-control-audit.log");
|
|
49639
|
+
if (!existsSync83(hostdAuditLogPath)) {
|
|
49640
|
+
writeFileSync39(hostdAuditLogPath, "", { mode: 420 });
|
|
49641
49641
|
}
|
|
49642
49642
|
for (const name of Object.keys(config.agents)) {
|
|
49643
|
-
const tokenPath =
|
|
49644
|
-
if (!
|
|
49645
|
-
|
|
49643
|
+
const tokenPath = join81(home2, ".switchroom", "agents", name, ".vault-token");
|
|
49644
|
+
if (!existsSync83(tokenPath)) {
|
|
49645
|
+
writeFileSync39(tokenPath, "", { mode: 384 });
|
|
49646
49646
|
}
|
|
49647
49647
|
try {
|
|
49648
49648
|
const uid = allocateAgentUid(name);
|
|
49649
49649
|
chownSync8(tokenPath, uid, uid);
|
|
49650
49650
|
} catch {}
|
|
49651
49651
|
}
|
|
49652
|
-
const fleetDir =
|
|
49652
|
+
const fleetDir = join81(home2, ".switchroom", "fleet");
|
|
49653
49653
|
await mkdir2(fleetDir, { recursive: true });
|
|
49654
|
-
const invariantsPath =
|
|
49654
|
+
const invariantsPath = join81(fleetDir, "switchroom-invariants.md");
|
|
49655
49655
|
const invariantsCanonical = renderFleetInvariants();
|
|
49656
|
-
const invariantsCurrent =
|
|
49656
|
+
const invariantsCurrent = existsSync83(invariantsPath) ? readFileSync70(invariantsPath, "utf-8") : null;
|
|
49657
49657
|
if (invariantsCurrent !== invariantsCanonical) {
|
|
49658
|
-
|
|
49658
|
+
writeFileSync39(invariantsPath, invariantsCanonical, { mode: 420 });
|
|
49659
49659
|
}
|
|
49660
|
-
const fleetClaudePath =
|
|
49661
|
-
if (!
|
|
49662
|
-
|
|
49660
|
+
const fleetClaudePath = join81(fleetDir, "CLAUDE.md");
|
|
49661
|
+
if (!existsSync83(fleetClaudePath)) {
|
|
49662
|
+
writeFileSync39(fleetClaudePath, [
|
|
49663
49663
|
"# Switchroom fleet defaults",
|
|
49664
49664
|
"",
|
|
49665
49665
|
"Operator-owned fleet brain. Every agent reads this via",
|
|
@@ -49697,7 +49697,7 @@ function isInAgentContainer(vaultPresent, composeV2Present, env2 = process.env)
|
|
|
49697
49697
|
function runApplyPreflight(config, opts = {}) {
|
|
49698
49698
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
49699
49699
|
const detect = opts.detectComposeV2 ?? detectComposeV2;
|
|
49700
|
-
const vaultMissing = hasVaultRefs(config) && !
|
|
49700
|
+
const vaultMissing = hasVaultRefs(config) && !existsSync83(vaultPath);
|
|
49701
49701
|
const composeErr = detect();
|
|
49702
49702
|
if ((vaultMissing || composeErr) && isInAgentContainer(!vaultMissing, composeErr === null)) {
|
|
49703
49703
|
throw new Error(IN_AGENT_CONTAINER_APPLY_MSG);
|
|
@@ -49711,7 +49711,7 @@ function runApplyPreflight(config, opts = {}) {
|
|
|
49711
49711
|
detectAndReportLegacyGdriveSlots(vaultPath);
|
|
49712
49712
|
}
|
|
49713
49713
|
function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
49714
|
-
if (!
|
|
49714
|
+
if (!existsSync83(vaultPath))
|
|
49715
49715
|
return;
|
|
49716
49716
|
const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
49717
49717
|
if (!passphrase)
|
|
@@ -49750,18 +49750,18 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
|
49750
49750
|
`));
|
|
49751
49751
|
}
|
|
49752
49752
|
}
|
|
49753
|
-
function writeInstallTypeCache(homeDir =
|
|
49753
|
+
function writeInstallTypeCache(homeDir = homedir48()) {
|
|
49754
49754
|
const ctx = detectInstallType();
|
|
49755
|
-
const dir =
|
|
49756
|
-
const out =
|
|
49755
|
+
const dir = join81(homeDir, ".switchroom");
|
|
49756
|
+
const out = join81(dir, "install-type.json");
|
|
49757
49757
|
const tmp = `${out}.tmp`;
|
|
49758
|
-
|
|
49758
|
+
mkdirSync47(dir, { recursive: true });
|
|
49759
49759
|
const payload = {
|
|
49760
49760
|
install_type: ctx.install_type,
|
|
49761
49761
|
detected_at: new Date().toISOString(),
|
|
49762
49762
|
source_paths: ctx.source_paths
|
|
49763
49763
|
};
|
|
49764
|
-
|
|
49764
|
+
writeFileSync39(tmp, JSON.stringify(payload, null, 2), { mode: 420 });
|
|
49765
49765
|
renameSync18(tmp, out);
|
|
49766
49766
|
return out;
|
|
49767
49767
|
}
|
|
@@ -49820,17 +49820,17 @@ Applying switchroom config...
|
|
|
49820
49820
|
writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
|
|
49821
49821
|
`));
|
|
49822
49822
|
try {
|
|
49823
|
-
installUpdatePromptHook(
|
|
49823
|
+
installUpdatePromptHook(join81(agentsDir, name));
|
|
49824
49824
|
} catch (hookErr) {
|
|
49825
49825
|
writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
|
|
49826
49826
|
`));
|
|
49827
49827
|
}
|
|
49828
|
-
await refreshAgentConnectionHealth(config, name,
|
|
49828
|
+
await refreshAgentConnectionHealth(config, name, join81(agentsDir, name), {
|
|
49829
49829
|
vaultAclReader: connHealthVaultAclReader
|
|
49830
49830
|
});
|
|
49831
49831
|
try {
|
|
49832
49832
|
const uid = allocateAgentUid(name);
|
|
49833
|
-
alignAgentUid(name,
|
|
49833
|
+
alignAgentUid(name, join81(agentsDir, name), uid, {
|
|
49834
49834
|
confirm: !options.nonInteractive,
|
|
49835
49835
|
writeOut
|
|
49836
49836
|
});
|
|
@@ -49867,7 +49867,7 @@ Applying switchroom config...
|
|
|
49867
49867
|
writeOut,
|
|
49868
49868
|
writeErr,
|
|
49869
49869
|
failures,
|
|
49870
|
-
home:
|
|
49870
|
+
home: homedir48()
|
|
49871
49871
|
});
|
|
49872
49872
|
}
|
|
49873
49873
|
await ensureHostMountSources(config);
|
|
@@ -49875,7 +49875,7 @@ Applying switchroom config...
|
|
|
49875
49875
|
for (const name of agentNames) {
|
|
49876
49876
|
try {
|
|
49877
49877
|
const uid = allocateAgentUid(name);
|
|
49878
|
-
alignAgentUid(name,
|
|
49878
|
+
alignAgentUid(name, join81(agentsDir, name), uid, {
|
|
49879
49879
|
confirm: !options.nonInteractive,
|
|
49880
49880
|
writeOut
|
|
49881
49881
|
});
|
|
@@ -49889,7 +49889,7 @@ Applying switchroom config...
|
|
|
49889
49889
|
}
|
|
49890
49890
|
const vaultPathConfigured = config.vault?.path;
|
|
49891
49891
|
const customVaultPath = vaultPathConfigured ? resolvePath(vaultPathConfigured) : undefined;
|
|
49892
|
-
const migrationResult = migrateVaultLayout(
|
|
49892
|
+
const migrationResult = migrateVaultLayout(homedir48(), {
|
|
49893
49893
|
customVaultPath
|
|
49894
49894
|
});
|
|
49895
49895
|
switch (migrationResult.kind) {
|
|
@@ -49915,7 +49915,7 @@ Applying switchroom config...
|
|
|
49915
49915
|
writeErr(formatDivergentRecoveryMessage(migrationResult.details));
|
|
49916
49916
|
process.exit(4);
|
|
49917
49917
|
}
|
|
49918
|
-
const postMigrationInspect = inspectVaultLayout(
|
|
49918
|
+
const postMigrationInspect = inspectVaultLayout(homedir48());
|
|
49919
49919
|
const acceptable = [
|
|
49920
49920
|
"no-vault",
|
|
49921
49921
|
"already-migrated",
|
|
@@ -49930,7 +49930,7 @@ Expected one of: ${acceptable.join(", ")}
|
|
|
49930
49930
|
`));
|
|
49931
49931
|
process.exit(5);
|
|
49932
49932
|
}
|
|
49933
|
-
const vaultDir = resolveVaultBindMountDir(
|
|
49933
|
+
const vaultDir = resolveVaultBindMountDir(homedir48(), {
|
|
49934
49934
|
migrationKind: migrationResult.kind,
|
|
49935
49935
|
customVaultPath
|
|
49936
49936
|
});
|
|
@@ -49961,7 +49961,7 @@ vault.enc.lock (PID-file flock from saveVault), and
|
|
|
49961
49961
|
});
|
|
49962
49962
|
if (!skipScaffold) {
|
|
49963
49963
|
const { provisionVoiceSidecarToken: provisionVoiceSidecarToken2 } = await Promise.resolve().then(() => (init_voice_sidecar_token(), exports_voice_sidecar_token));
|
|
49964
|
-
await provisionVoiceSidecarToken2(composePath,
|
|
49964
|
+
await provisionVoiceSidecarToken2(composePath, homedir48(), {
|
|
49965
49965
|
writeOut,
|
|
49966
49966
|
writeErr,
|
|
49967
49967
|
operatorUid
|
|
@@ -49977,7 +49977,7 @@ Wrote `) + displayComposePath + source_default.gray(` (${composeBytes} bytes)
|
|
|
49977
49977
|
writeOut(source_default.gray(` (If pull returns 401, login to ghcr.io first: see docs/operators/install.md#ghcr-auth)
|
|
49978
49978
|
`));
|
|
49979
49979
|
if (process.geteuid?.() === 0 && operatorUid !== undefined) {
|
|
49980
|
-
const restored = restoreOperatorOwnership(
|
|
49980
|
+
const restored = restoreOperatorOwnership(homedir48(), operatorUid);
|
|
49981
49981
|
if (restored.length > 0) {
|
|
49982
49982
|
writeOut(source_default.gray(` Restored operator ownership of ${restored.length} ~/.switchroom path(s)
|
|
49983
49983
|
`));
|
|
@@ -50082,7 +50082,7 @@ Dry-run: validating switchroom config (no changes will be written)...
|
|
|
50082
50082
|
}
|
|
50083
50083
|
}
|
|
50084
50084
|
try {
|
|
50085
|
-
const probe2 = await probeVaultProvisioning(config, agentNames,
|
|
50085
|
+
const probe2 = await probeVaultProvisioning(config, agentNames, homedir48());
|
|
50086
50086
|
if (probe2.alreadyProvisioned.length > 0) {
|
|
50087
50087
|
info.push(`litellm: ${probe2.alreadyProvisioned.length} agent(s) already provisioned (${probe2.alreadyProvisioned.join(", ")}).`);
|
|
50088
50088
|
}
|
|
@@ -50213,18 +50213,18 @@ function copyExampleConfig2(name) {
|
|
|
50213
50213
|
throw new Error(`Invalid example name: ${name} (must match /^[a-z0-9_-]+$/)`);
|
|
50214
50214
|
}
|
|
50215
50215
|
const dest = resolve50(process.cwd(), "switchroom.yaml");
|
|
50216
|
-
if (
|
|
50216
|
+
if (existsSync83(dest)) {
|
|
50217
50217
|
console.error(source_default.yellow("switchroom.yaml already exists \u2014 skipping example copy"));
|
|
50218
50218
|
return;
|
|
50219
50219
|
}
|
|
50220
50220
|
const embedded = EMBEDDED_EXAMPLES[name];
|
|
50221
50221
|
if (embedded !== undefined) {
|
|
50222
|
-
|
|
50222
|
+
writeFileSync39(dest, embedded, { encoding: "utf8" });
|
|
50223
50223
|
console.log(source_default.green(`Copied ${name}.yaml -> switchroom.yaml`));
|
|
50224
50224
|
return;
|
|
50225
50225
|
}
|
|
50226
50226
|
const exampleFile = resolve50(import.meta.dirname, `../../examples/${name}.yaml`);
|
|
50227
|
-
if (!
|
|
50227
|
+
if (!existsSync83(exampleFile)) {
|
|
50228
50228
|
throw new Error(`Example config not found: ${name}.yaml (available: ${Object.keys(EMBEDDED_EXAMPLES).join(", ")})`);
|
|
50229
50229
|
}
|
|
50230
50230
|
copyFileSync12(exampleFile, dest);
|
|
@@ -50235,8 +50235,8 @@ function findUnwritableAgentDirs(config, opts) {
|
|
|
50235
50235
|
const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
|
|
50236
50236
|
const unwritable = [];
|
|
50237
50237
|
for (const name of targets) {
|
|
50238
|
-
const startSh =
|
|
50239
|
-
if (!
|
|
50238
|
+
const startSh = join81(agentsDir, name, "start.sh");
|
|
50239
|
+
if (!existsSync83(startSh))
|
|
50240
50240
|
continue;
|
|
50241
50241
|
try {
|
|
50242
50242
|
accessSync3(startSh, fsConstants6.W_OK);
|
|
@@ -50437,7 +50437,7 @@ var init_apply = __esm(() => {
|
|
|
50437
50437
|
switchroom: switchroom_default,
|
|
50438
50438
|
minimal: minimal_default
|
|
50439
50439
|
};
|
|
50440
|
-
DEFAULT_COMPOSE_PATH2 =
|
|
50440
|
+
DEFAULT_COMPOSE_PATH2 = join81(homedir48(), ".switchroom", "compose", "docker-compose.yml");
|
|
50441
50441
|
IN_AGENT_CONTAINER_APPLY_MSG = "`switchroom apply`'s full per-agent scaffold cannot run from inside an " + "agent container \u2014 this is a host/hostd operation by construction " + "(no vault at the container HOME, and no `docker compose` v2 plugin here).\nTo roll the fleet to a new version, drive the hostd rollout (`mcp__hostd__rollout`): it runs a `--compose-only` apply plus a per-agent restart-reconcile, and each agent refreshes its own templates " + `on restart \u2014 the roll completes without any agent running a full apply.
|
|
50442
50442
|
` + "A full host-side `sudo switchroom apply` is only needed for structural changes (compose regeneration / new-agent scaffolding), and is run by the operator on the host, never from inside an agent.";
|
|
50443
50443
|
SELF_ELEVATE_PRESERVED_ENV = [
|
|
@@ -63884,7 +63884,7 @@ __export(exports_server2, {
|
|
|
63884
63884
|
TOOLS: () => TOOLS2
|
|
63885
63885
|
});
|
|
63886
63886
|
import { randomBytes as randomBytes16 } from "node:crypto";
|
|
63887
|
-
import { existsSync as
|
|
63887
|
+
import { existsSync as existsSync92, readFileSync as readFileSync79 } from "node:fs";
|
|
63888
63888
|
function selfSocketPath() {
|
|
63889
63889
|
return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
|
|
63890
63890
|
}
|
|
@@ -63902,7 +63902,7 @@ async function dispatchTool2(name, args) {
|
|
|
63902
63902
|
return errorText2("hostd MCP: SWITCHROOM_AGENT_NAME env var is not set \u2014 cannot " + "determine which per-agent socket to talk to.");
|
|
63903
63903
|
}
|
|
63904
63904
|
const sockPath = selfSocketPath();
|
|
63905
|
-
if (!
|
|
63905
|
+
if (!existsSync92(sockPath)) {
|
|
63906
63906
|
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.`);
|
|
63907
63907
|
}
|
|
63908
63908
|
let req;
|
|
@@ -64090,18 +64090,18 @@ function resolveAuditLogPath() {
|
|
|
64090
64090
|
if (process.env.HOSTD_AUDIT_LOG_PATH)
|
|
64091
64091
|
return process.env.HOSTD_AUDIT_LOG_PATH;
|
|
64092
64092
|
const bindMounted = "/host-home/.switchroom/host-control-audit.log";
|
|
64093
|
-
if (
|
|
64093
|
+
if (existsSync92(bindMounted))
|
|
64094
64094
|
return bindMounted;
|
|
64095
64095
|
return defaultAuditLogPath2();
|
|
64096
64096
|
}
|
|
64097
64097
|
function getLastUpdateApplyStatus() {
|
|
64098
64098
|
const path8 = resolveAuditLogPath();
|
|
64099
|
-
if (!
|
|
64099
|
+
if (!existsSync92(path8)) {
|
|
64100
64100
|
return errorText2(`get_status: audit log not found at ${path8}. No update_apply has run yet?`);
|
|
64101
64101
|
}
|
|
64102
64102
|
let raw;
|
|
64103
64103
|
try {
|
|
64104
|
-
raw =
|
|
64104
|
+
raw = readFileSync79(path8, "utf-8");
|
|
64105
64105
|
} catch (err2) {
|
|
64106
64106
|
return errorText2(`get_status: failed to read audit log at ${path8}: ${err2.message}`);
|
|
64107
64107
|
}
|
|
@@ -64768,20 +64768,20 @@ __export(exports_scan, {
|
|
|
64768
64768
|
ledgerPathForBase: () => ledgerPathForBase
|
|
64769
64769
|
});
|
|
64770
64770
|
import {
|
|
64771
|
-
readFileSync as
|
|
64771
|
+
readFileSync as readFileSync81,
|
|
64772
64772
|
readdirSync as readdirSync36,
|
|
64773
|
-
existsSync as
|
|
64774
|
-
mkdirSync as
|
|
64775
|
-
writeFileSync as
|
|
64773
|
+
existsSync as existsSync95,
|
|
64774
|
+
mkdirSync as mkdirSync54,
|
|
64775
|
+
writeFileSync as writeFileSync45
|
|
64776
64776
|
} from "node:fs";
|
|
64777
|
-
import { resolve as resolve55, dirname as
|
|
64778
|
-
import { homedir as
|
|
64779
|
-
function resolveSwitchroomBase(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ??
|
|
64777
|
+
import { resolve as resolve55, dirname as dirname32 } from "node:path";
|
|
64778
|
+
import { homedir as homedir57 } from "node:os";
|
|
64779
|
+
function resolveSwitchroomBase(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir57()) {
|
|
64780
64780
|
return resolve55(home2, ".switchroom");
|
|
64781
64781
|
}
|
|
64782
64782
|
function listAgents(base) {
|
|
64783
64783
|
const dir = resolve55(base, "agents");
|
|
64784
|
-
if (!
|
|
64784
|
+
if (!existsSync95(dir))
|
|
64785
64785
|
return [];
|
|
64786
64786
|
try {
|
|
64787
64787
|
return readdirSync36(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
|
|
@@ -64810,16 +64810,16 @@ function runScan(opts = {}) {
|
|
|
64810
64810
|
let gwText = "";
|
|
64811
64811
|
let sawArtifact = false;
|
|
64812
64812
|
try {
|
|
64813
|
-
if (
|
|
64814
|
-
turnsText =
|
|
64813
|
+
if (existsSync95(turnsPath)) {
|
|
64814
|
+
turnsText = readFileSync81(turnsPath, "utf-8");
|
|
64815
64815
|
sawArtifact = true;
|
|
64816
64816
|
}
|
|
64817
64817
|
} catch (e) {
|
|
64818
64818
|
log(`fleet-health: WARN skipping ${agent} turns.jsonl unreadable: ${String(e)}`);
|
|
64819
64819
|
}
|
|
64820
64820
|
try {
|
|
64821
|
-
if (
|
|
64822
|
-
gwText =
|
|
64821
|
+
if (existsSync95(gwPath)) {
|
|
64822
|
+
gwText = readFileSync81(gwPath, "utf-8");
|
|
64823
64823
|
sawArtifact = true;
|
|
64824
64824
|
}
|
|
64825
64825
|
} catch (e) {
|
|
@@ -64857,20 +64857,20 @@ function runScan(opts = {}) {
|
|
|
64857
64857
|
function readLedgerIfPresent(base) {
|
|
64858
64858
|
const path8 = ledgerPathForBase(base);
|
|
64859
64859
|
try {
|
|
64860
|
-
if (!
|
|
64860
|
+
if (!existsSync95(path8))
|
|
64861
64861
|
return null;
|
|
64862
|
-
return JSON.parse(
|
|
64862
|
+
return JSON.parse(readFileSync81(path8, "utf-8"));
|
|
64863
64863
|
} catch {
|
|
64864
64864
|
return null;
|
|
64865
64865
|
}
|
|
64866
64866
|
}
|
|
64867
64867
|
function ledgerPathForBase(base) {
|
|
64868
|
-
return fleetHealthLedgerPath(
|
|
64868
|
+
return fleetHealthLedgerPath(dirname32(base));
|
|
64869
64869
|
}
|
|
64870
64870
|
function writeLedger(base, ledger) {
|
|
64871
64871
|
const path8 = ledgerPathForBase(base);
|
|
64872
|
-
|
|
64873
|
-
|
|
64872
|
+
mkdirSync54(dirname32(path8), { recursive: true });
|
|
64873
|
+
writeFileSync45(path8, JSON.stringify(ledger, null, 2) + `
|
|
64874
64874
|
`, "utf-8");
|
|
64875
64875
|
return path8;
|
|
64876
64876
|
}
|
|
@@ -64889,8 +64889,8 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
64889
64889
|
import { dirname, join } from "node:path";
|
|
64890
64890
|
|
|
64891
64891
|
// src/build-info.ts
|
|
64892
|
-
var VERSION = "0.18.
|
|
64893
|
-
var COMMIT_SHA = "
|
|
64892
|
+
var VERSION = "0.18.8";
|
|
64893
|
+
var COMMIT_SHA = "9255fe22";
|
|
64894
64894
|
|
|
64895
64895
|
// src/cli/resolve-version.ts
|
|
64896
64896
|
function readPackageVersion() {
|
|
@@ -79126,6 +79126,8 @@ Demoting memory ${source_default.cyan(memoryId)}`));
|
|
|
79126
79126
|
|
|
79127
79127
|
// src/cli/web.ts
|
|
79128
79128
|
init_source();
|
|
79129
|
+
import { join as join50 } from "node:path";
|
|
79130
|
+
import { homedir as homedir28 } from "node:os";
|
|
79129
79131
|
|
|
79130
79132
|
// src/web/server.ts
|
|
79131
79133
|
init_merge();
|
|
@@ -83126,10 +83128,100 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
83126
83128
|
|
|
83127
83129
|
// src/cli/web.ts
|
|
83128
83130
|
init_helpers();
|
|
83131
|
+
init_loader();
|
|
83132
|
+
|
|
83133
|
+
// src/web/startup-guard.ts
|
|
83134
|
+
import { existsSync as existsSync56, readFileSync as readFileSync51, writeFileSync as writeFileSync26, mkdirSync as mkdirSync31, statSync as statSync24, unlinkSync as unlinkSync13 } from "node:fs";
|
|
83135
|
+
import { dirname as dirname16 } from "node:path";
|
|
83136
|
+
function detectConfigMountFault(configPath, deps = {}) {
|
|
83137
|
+
const stat = deps.stat ?? ((p) => statSync24(p));
|
|
83138
|
+
let st;
|
|
83139
|
+
try {
|
|
83140
|
+
st = stat(configPath);
|
|
83141
|
+
} catch {
|
|
83142
|
+
return null;
|
|
83143
|
+
}
|
|
83144
|
+
if (!st.isDirectory())
|
|
83145
|
+
return null;
|
|
83146
|
+
return {
|
|
83147
|
+
kind: "directory-inode",
|
|
83148
|
+
path: configPath,
|
|
83149
|
+
message: `switchroom-web: the config bind source ${configPath} resolved to a DIRECTORY, ` + `not a file \u2014 reads fail with EISDIR and the server cannot start.
|
|
83150
|
+
|
|
83151
|
+
` + `Cause: the single-file bind mount was (re)created while the host source was ` + `momentarily absent, so Docker auto-created an empty directory, OR a stale ` + `directory inode survived a plain \`docker restart\` inside the container's mount ` + `namespace (a restart does NOT re-resolve a single-file bind).
|
|
83152
|
+
|
|
83153
|
+
` + `Remedy: RECREATE the container (not restart) so the bind re-resolves to the file:
|
|
83154
|
+
` + ` docker compose -p switchroom-web -f ~/.switchroom/web/docker-compose.yml up -d --force-recreate
|
|
83155
|
+
` + `and if the host source itself is a directory, remove it and restore the file first.`
|
|
83156
|
+
};
|
|
83157
|
+
}
|
|
83158
|
+
function nextCrashBackoff(prior, now, options = {}) {
|
|
83159
|
+
const baseMs = options.baseMs ?? 1000;
|
|
83160
|
+
const maxMs = options.maxMs ?? 60000;
|
|
83161
|
+
const resetAfterMs = options.resetAfterMs ?? 300000;
|
|
83162
|
+
const withinWindow = prior !== null && now - prior.lastTs <= resetAfterMs && now >= prior.lastTs;
|
|
83163
|
+
const count = withinWindow ? prior.count + 1 : 1;
|
|
83164
|
+
const exp = Math.min(count - 1, 30);
|
|
83165
|
+
const delayMs = count <= 1 ? 0 : Math.min(baseMs * 2 ** (exp - 1), maxMs);
|
|
83166
|
+
return { state: { count, lastTs: now }, delayMs };
|
|
83167
|
+
}
|
|
83168
|
+
function readCrashState(statePath) {
|
|
83169
|
+
try {
|
|
83170
|
+
if (!existsSync56(statePath))
|
|
83171
|
+
return null;
|
|
83172
|
+
const raw = JSON.parse(readFileSync51(statePath, "utf-8"));
|
|
83173
|
+
if (typeof raw.count !== "number" || typeof raw.lastTs !== "number")
|
|
83174
|
+
return null;
|
|
83175
|
+
return { count: raw.count, lastTs: raw.lastTs };
|
|
83176
|
+
} catch {
|
|
83177
|
+
return null;
|
|
83178
|
+
}
|
|
83179
|
+
}
|
|
83180
|
+
function writeCrashState(statePath, state) {
|
|
83181
|
+
try {
|
|
83182
|
+
mkdirSync31(dirname16(statePath), { recursive: true });
|
|
83183
|
+
writeFileSync26(statePath, JSON.stringify(state), { mode: 384 });
|
|
83184
|
+
} catch {}
|
|
83185
|
+
}
|
|
83186
|
+
function clearCrashState(statePath) {
|
|
83187
|
+
try {
|
|
83188
|
+
if (existsSync56(statePath))
|
|
83189
|
+
unlinkSync13(statePath);
|
|
83190
|
+
} catch {}
|
|
83191
|
+
}
|
|
83192
|
+
|
|
83193
|
+
// src/cli/web.ts
|
|
83129
83194
|
init_posthog();
|
|
83195
|
+
async function handleWebStartupFailure(err, configPath, stateFile) {
|
|
83196
|
+
if (err instanceof ConfigError && configPath) {
|
|
83197
|
+
const fault = detectConfigMountFault(configPath);
|
|
83198
|
+
if (fault) {
|
|
83199
|
+
console.error(source_default.red(fault.message));
|
|
83200
|
+
}
|
|
83201
|
+
}
|
|
83202
|
+
const { state, delayMs } = nextCrashBackoff(readCrashState(stateFile), Date.now());
|
|
83203
|
+
writeCrashState(stateFile, state);
|
|
83204
|
+
if (delayMs > 0) {
|
|
83205
|
+
console.error(source_default.yellow(`switchroom-web: startup has failed ${state.count}\u00d7 in a row \u2014 backing off ` + `${Math.round(delayMs / 1000)}s before exit so \`restart: always\` doesn't ` + `crash-loop the host (issue #2915).`));
|
|
83206
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
83207
|
+
}
|
|
83208
|
+
throw err;
|
|
83209
|
+
}
|
|
83210
|
+
function webCrashStatePath() {
|
|
83211
|
+
return join50(homedir28(), ".switchroom", "web", ".crashloop-state.json");
|
|
83212
|
+
}
|
|
83130
83213
|
function registerWebCommand(program3) {
|
|
83131
83214
|
program3.command("web").description("Start the web dashboard for monitoring agents").option("-p, --port <port>", "Port to listen on", "8080").option("-b, --bind <host>", "Host/IP to bind to (default: 127.0.0.1, localhost-only)", "127.0.0.1").action(withConfigError(async (opts) => {
|
|
83132
|
-
const
|
|
83215
|
+
const configPath = getConfigPath(program3);
|
|
83216
|
+
const stateFile = webCrashStatePath();
|
|
83217
|
+
let config;
|
|
83218
|
+
try {
|
|
83219
|
+
config = getConfig(program3);
|
|
83220
|
+
} catch (err) {
|
|
83221
|
+
await handleWebStartupFailure(err, configPath, stateFile);
|
|
83222
|
+
throw err;
|
|
83223
|
+
}
|
|
83224
|
+
clearCrashState(stateFile);
|
|
83133
83225
|
const port = parseInt(opts.port, 10);
|
|
83134
83226
|
const hostname = opts.bind;
|
|
83135
83227
|
if (isNaN(port) || port < 1 || port > 65535) {
|
|
@@ -83181,8 +83273,8 @@ Starting Switchroom dashboard...
|
|
|
83181
83273
|
init_source();
|
|
83182
83274
|
init_loader();
|
|
83183
83275
|
init_scaffold();
|
|
83184
|
-
import { existsSync as
|
|
83185
|
-
import { resolve as resolve33, dirname as
|
|
83276
|
+
import { existsSync as existsSync57, copyFileSync as copyFileSync9, readFileSync as readFileSync52, writeFileSync as writeFileSync27, mkdirSync as mkdirSync32 } from "node:fs";
|
|
83277
|
+
import { resolve as resolve33, dirname as dirname17 } from "node:path";
|
|
83186
83278
|
init_state();
|
|
83187
83279
|
init_vault();
|
|
83188
83280
|
init_manager();
|
|
@@ -83452,7 +83544,7 @@ async function stepConfigFile(configPath, nonInteractive) {
|
|
|
83452
83544
|
existingConfig = null;
|
|
83453
83545
|
}
|
|
83454
83546
|
}
|
|
83455
|
-
if (existingConfig &&
|
|
83547
|
+
if (existingConfig && existsSync57(existingConfig)) {
|
|
83456
83548
|
if (!nonInteractive) {
|
|
83457
83549
|
const useExisting = await askYesNo(` Found ${source_default.cyan(existingConfig)}. Use it?`, true);
|
|
83458
83550
|
if (!useExisting) {
|
|
@@ -83484,10 +83576,10 @@ async function copyExampleConfig(nonInteractive) {
|
|
|
83484
83576
|
}
|
|
83485
83577
|
const srcFile = resolve33(examplesDir, `${choice}.yaml`);
|
|
83486
83578
|
const destFile = resolvePath("~/.switchroom/switchroom.yaml");
|
|
83487
|
-
if (!
|
|
83579
|
+
if (!existsSync57(srcFile)) {
|
|
83488
83580
|
throw new ConfigError(`Example config not found: ${choice}.yaml`);
|
|
83489
83581
|
}
|
|
83490
|
-
|
|
83582
|
+
mkdirSync32(dirname17(destFile), { recursive: true });
|
|
83491
83583
|
copyFileSync9(srcFile, destFile);
|
|
83492
83584
|
console.log(source_default.green(` Copied ${choice}.yaml -> ${destFile}`));
|
|
83493
83585
|
console.log(source_default.yellow(` Edit ${destFile} to customize, then re-run switchroom setup.`));
|
|
@@ -83499,10 +83591,10 @@ async function copyExampleConfig(nonInteractive) {
|
|
|
83499
83591
|
async function writeDetectedTimezone(destFile, nonInteractive, detect = detectServerTimezone, prompt = ask) {
|
|
83500
83592
|
const detected = detect();
|
|
83501
83593
|
if (detected !== undefined && detected !== "UTC" && isValidTimezone(detected)) {
|
|
83502
|
-
const before2 =
|
|
83594
|
+
const before2 = readFileSync52(destFile, "utf-8");
|
|
83503
83595
|
const after2 = setSwitchroomTimezone(before2, detected);
|
|
83504
83596
|
if (after2 !== before2)
|
|
83505
|
-
|
|
83597
|
+
writeFileSync27(destFile, after2);
|
|
83506
83598
|
console.log(source_default.green(` Detected timezone ${detected} \u2014 wrote switchroom.timezone.`));
|
|
83507
83599
|
return;
|
|
83508
83600
|
}
|
|
@@ -83520,10 +83612,10 @@ async function writeDetectedTimezone(destFile, nonInteractive, detect = detectSe
|
|
|
83520
83612
|
console.error(source_default.yellow(` \u26a0 "${zone}" is not a valid IANA zone (expected Region/City like ` + '"Australia/Melbourne"). Skipping \u2014 agents will use UTC. ' + "Edit switchroom.timezone in switchroom.yaml by hand."));
|
|
83521
83613
|
return;
|
|
83522
83614
|
}
|
|
83523
|
-
const before =
|
|
83615
|
+
const before = readFileSync52(destFile, "utf-8");
|
|
83524
83616
|
const after = setSwitchroomTimezone(before, zone);
|
|
83525
83617
|
if (after !== before)
|
|
83526
|
-
|
|
83618
|
+
writeFileSync27(destFile, after);
|
|
83527
83619
|
console.log(source_default.green(` Wrote switchroom.timezone: ${zone}.`));
|
|
83528
83620
|
}
|
|
83529
83621
|
async function stepBotToken(config, nonInteractive) {
|
|
@@ -83599,7 +83691,7 @@ async function resolveOrPromptToken(rawToken, label, config, nonInteractive) {
|
|
|
83599
83691
|
try {
|
|
83600
83692
|
const { openVault: openVault2 } = await Promise.resolve().then(() => (init_vault(), exports_vault));
|
|
83601
83693
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
83602
|
-
if (
|
|
83694
|
+
if (existsSync57(vaultPath)) {
|
|
83603
83695
|
const secrets = openVault2(passphrase, vaultPath);
|
|
83604
83696
|
const key = rawToken.replace("vault:", "");
|
|
83605
83697
|
const entry = secrets[key];
|
|
@@ -83627,7 +83719,7 @@ async function resolveOrPromptToken(rawToken, label, config, nonInteractive) {
|
|
|
83627
83719
|
async function storeTokenInVault(config, vaultRef, token) {
|
|
83628
83720
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
83629
83721
|
const key = vaultRef.replace("vault:", "");
|
|
83630
|
-
if (!
|
|
83722
|
+
if (!existsSync57(vaultPath)) {
|
|
83631
83723
|
console.log(source_default.gray(" Creating encrypted vault..."));
|
|
83632
83724
|
let passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
83633
83725
|
if (!passphrase) {
|
|
@@ -83813,7 +83905,7 @@ async function stepMemoryBackend(config, nonInteractive, switchroomConfigPath) {
|
|
|
83813
83905
|
try {
|
|
83814
83906
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
83815
83907
|
const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
83816
|
-
if (passphrase &&
|
|
83908
|
+
if (passphrase && existsSync57(vaultPath)) {
|
|
83817
83909
|
const existing = getStringSecret(passphrase, vaultPath, "hindsight-api-key");
|
|
83818
83910
|
if (existing) {
|
|
83819
83911
|
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`."));
|
|
@@ -84029,13 +84121,13 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
|
|
|
84029
84121
|
return;
|
|
84030
84122
|
}
|
|
84031
84123
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
84032
|
-
if (!
|
|
84124
|
+
if (!existsSync57(vaultPath)) {
|
|
84033
84125
|
console.log(source_default.gray(" Skipping (vault not created yet)."));
|
|
84034
84126
|
return;
|
|
84035
84127
|
}
|
|
84036
84128
|
const credPathRaw = config.vault?.broker?.autoUnlockCredentialPath ?? "~/.switchroom/vault-auto-unlock";
|
|
84037
84129
|
const credPath = resolvePath(credPathRaw);
|
|
84038
|
-
if (config.vault?.broker?.autoUnlock === true &&
|
|
84130
|
+
if (config.vault?.broker?.autoUnlock === true && existsSync57(credPath)) {
|
|
84039
84131
|
console.log(source_default.green(` ${STEP_DONE} Already configured (${credPath})`));
|
|
84040
84132
|
return;
|
|
84041
84133
|
}
|
|
@@ -84101,12 +84193,12 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
|
|
|
84101
84193
|
const choice = await askChoice(" Approval posture", [PASSPHRASE_CHOICE, TELEGRAM_ID_CHOICE]);
|
|
84102
84194
|
if (choice === TELEGRAM_ID_CHOICE) {
|
|
84103
84195
|
try {
|
|
84104
|
-
const yamlPath =
|
|
84105
|
-
if (
|
|
84106
|
-
const content =
|
|
84196
|
+
const yamlPath = existsSync57(resolve33(process.cwd(), "switchroom.yaml")) ? resolve33(process.cwd(), "switchroom.yaml") : resolve33(process.cwd(), "switchroom.yml");
|
|
84197
|
+
if (existsSync57(yamlPath)) {
|
|
84198
|
+
const content = readFileSync52(yamlPath, "utf-8");
|
|
84107
84199
|
const result = insertVaultBrokerApprovalAuth(content, "telegram-id");
|
|
84108
84200
|
if (result.kind === "rewritten") {
|
|
84109
|
-
|
|
84201
|
+
writeFileSync27(yamlPath, result.content, "utf-8");
|
|
84110
84202
|
console.log(source_default.green(` ${STEP_DONE} Set vault.broker.approvalAuth: telegram-id in ${yamlPath}`));
|
|
84111
84203
|
} else if (result.kind === "already-set") {
|
|
84112
84204
|
console.log(source_default.gray(" approvalAuth already set \u2014 leaving it alone."));
|
|
@@ -84137,8 +84229,8 @@ async function stepDangerousMode(config, nonInteractive) {
|
|
|
84137
84229
|
resolve33(process.cwd(), "switchroom.yml")
|
|
84138
84230
|
];
|
|
84139
84231
|
for (const configPath of configPaths) {
|
|
84140
|
-
if (
|
|
84141
|
-
let content =
|
|
84232
|
+
if (existsSync57(configPath)) {
|
|
84233
|
+
let content = readFileSync52(configPath, "utf-8");
|
|
84142
84234
|
const agentNames = Object.keys(config.agents);
|
|
84143
84235
|
for (const name of agentNames) {
|
|
84144
84236
|
const agentPattern = new RegExp(`(^ ${name}:\\s*\\n)`, "m");
|
|
@@ -84152,7 +84244,7 @@ async function stepDangerousMode(config, nonInteractive) {
|
|
|
84152
84244
|
}
|
|
84153
84245
|
config.agents[name].dangerous_mode = true;
|
|
84154
84246
|
}
|
|
84155
|
-
|
|
84247
|
+
writeFileSync27(configPath, content, "utf-8");
|
|
84156
84248
|
console.log(source_default.green(` ${STEP_DONE} Enabled dangerous_mode for all agents in ${configPath}`));
|
|
84157
84249
|
break;
|
|
84158
84250
|
}
|
|
@@ -84268,10 +84360,10 @@ init_source();
|
|
|
84268
84360
|
init_loader();
|
|
84269
84361
|
init_lifecycle();
|
|
84270
84362
|
init_compose_env();
|
|
84271
|
-
import { cpSync as cpSync2, existsSync as
|
|
84363
|
+
import { cpSync as cpSync2, existsSync as existsSync64, mkdirSync as mkdirSync34, readFileSync as readFileSync57, realpathSync as realpathSync6, rmSync as rmSync12, statSync as statSync29, chownSync as chownSync5 } from "node:fs";
|
|
84272
84364
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
84273
|
-
import { join as
|
|
84274
|
-
import { homedir as
|
|
84365
|
+
import { join as join64, dirname as dirname20, resolve as resolve38 } from "node:path";
|
|
84366
|
+
import { homedir as homedir39 } from "node:os";
|
|
84275
84367
|
|
|
84276
84368
|
// src/cli/release-yaml.ts
|
|
84277
84369
|
var import_yaml19 = __toESM(require_dist(), 1);
|
|
@@ -84296,7 +84388,7 @@ init_operator_uid();
|
|
|
84296
84388
|
init_atomic();
|
|
84297
84389
|
|
|
84298
84390
|
// src/cli/preflight-mounts.ts
|
|
84299
|
-
import { statSync as
|
|
84391
|
+
import { statSync as statSync28 } from "node:fs";
|
|
84300
84392
|
function parseHostBindSources(composeText) {
|
|
84301
84393
|
const out = [];
|
|
84302
84394
|
const FILE_HINT = /\.(ya?ml|db|log|toml|json|token|id)$|\/\.vault-token$|machine-id$|localtime$|vault-auto-unlock$|\/webkite$/;
|
|
@@ -84323,7 +84415,7 @@ function parseHostBindSources(composeText) {
|
|
|
84323
84415
|
return out;
|
|
84324
84416
|
}
|
|
84325
84417
|
function validateBindSources(composeText, deps = {}) {
|
|
84326
|
-
const stat = deps.stat ?? ((p) =>
|
|
84418
|
+
const stat = deps.stat ?? ((p) => statSync28(p));
|
|
84327
84419
|
const sources = parseHostBindSources(composeText);
|
|
84328
84420
|
const issues = [];
|
|
84329
84421
|
const seen = new Set;
|
|
@@ -84363,11 +84455,11 @@ ${lines.join(`
|
|
|
84363
84455
|
function defaultPersistPin(configPath) {
|
|
84364
84456
|
return (pin) => {
|
|
84365
84457
|
const path4 = configPath ?? findConfigFile();
|
|
84366
|
-
const before =
|
|
84458
|
+
const before = readFileSync57(path4, "utf8");
|
|
84367
84459
|
const after = setReleasePinInConfig(before, pin);
|
|
84368
84460
|
if (after === before)
|
|
84369
84461
|
return;
|
|
84370
|
-
writeConfigFileSync(path4, after,
|
|
84462
|
+
writeConfigFileSync(path4, after, statSync29(path4).mode & 511);
|
|
84371
84463
|
try {
|
|
84372
84464
|
if (typeof process.geteuid === "function" && process.geteuid() === 0) {
|
|
84373
84465
|
const uid = resolveOperatorUid();
|
|
@@ -84377,18 +84469,18 @@ function defaultPersistPin(configPath) {
|
|
|
84377
84469
|
} catch {}
|
|
84378
84470
|
};
|
|
84379
84471
|
}
|
|
84380
|
-
var DEFAULT_COMPOSE_PATH =
|
|
84472
|
+
var DEFAULT_COMPOSE_PATH = join64(homedir39(), ".switchroom", "compose", "docker-compose.yml");
|
|
84381
84473
|
function runningFromSwitchroomCheckout(scriptPath) {
|
|
84382
|
-
let dir =
|
|
84474
|
+
let dir = dirname20(scriptPath);
|
|
84383
84475
|
for (let i = 0;i < 12; i++) {
|
|
84384
|
-
if (
|
|
84476
|
+
if (existsSync64(join64(dir, ".git"))) {
|
|
84385
84477
|
try {
|
|
84386
|
-
const pkg = JSON.parse(
|
|
84478
|
+
const pkg = JSON.parse(readFileSync57(join64(dir, "package.json"), "utf-8"));
|
|
84387
84479
|
if (pkg.name === "switchroom")
|
|
84388
84480
|
return true;
|
|
84389
84481
|
} catch {}
|
|
84390
84482
|
}
|
|
84391
|
-
const parent =
|
|
84483
|
+
const parent = dirname20(dir);
|
|
84392
84484
|
if (parent === dir)
|
|
84393
84485
|
break;
|
|
84394
84486
|
dir = parent;
|
|
@@ -84468,7 +84560,7 @@ function planUpdate(opts) {
|
|
|
84468
84560
|
steps.push({
|
|
84469
84561
|
name: "pull-images",
|
|
84470
84562
|
description: "Pull broker / kernel / agent images from GHCR",
|
|
84471
|
-
skipReason: opts.skipImages ? "--skip-images flag set" : !
|
|
84563
|
+
skipReason: opts.skipImages ? "--skip-images flag set" : !existsSync64(composePath) ? `compose file not found at ${composePath} (run \`switchroom apply --compose-only\` first)` : undefined,
|
|
84472
84564
|
run: () => {
|
|
84473
84565
|
const r = runner("docker", [
|
|
84474
84566
|
"compose",
|
|
@@ -84594,17 +84686,17 @@ function planUpdate(opts) {
|
|
|
84594
84686
|
return;
|
|
84595
84687
|
}
|
|
84596
84688
|
const source = resolve38(import.meta.dirname, "../../skills");
|
|
84597
|
-
const dest =
|
|
84598
|
-
if (!
|
|
84689
|
+
const dest = join64(homedir39(), ".switchroom", "skills", "_bundled");
|
|
84690
|
+
if (!existsSync64(source)) {
|
|
84599
84691
|
process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
|
|
84600
84692
|
`);
|
|
84601
84693
|
return;
|
|
84602
84694
|
}
|
|
84603
84695
|
try {
|
|
84604
|
-
if (
|
|
84696
|
+
if (existsSync64(dest)) {
|
|
84605
84697
|
rmSync12(dest, { recursive: true, force: true });
|
|
84606
84698
|
}
|
|
84607
|
-
|
|
84699
|
+
mkdirSync34(dirname20(dest), { recursive: true });
|
|
84608
84700
|
cpSync2(source, dest, { recursive: true, dereference: false });
|
|
84609
84701
|
} catch (err) {
|
|
84610
84702
|
throw new Error(`sync-bundled-skills failed: ${err.message}`);
|
|
@@ -84640,7 +84732,7 @@ function planUpdate(opts) {
|
|
|
84640
84732
|
description: "docker compose up -d --remove-orphans (recreates services with new images / compose)",
|
|
84641
84733
|
run: () => {
|
|
84642
84734
|
try {
|
|
84643
|
-
const composeText =
|
|
84735
|
+
const composeText = readFileSync57(composePath, "utf8");
|
|
84644
84736
|
const pf = validateBindSources(composeText);
|
|
84645
84737
|
if (!pf.ok)
|
|
84646
84738
|
throw new Error(formatPreflightError(pf));
|
|
@@ -84715,14 +84807,14 @@ function defaultStatusProbe(composePath) {
|
|
|
84715
84807
|
} catch {}
|
|
84716
84808
|
if (scriptPath) {
|
|
84717
84809
|
try {
|
|
84718
|
-
cliBuiltAt = new Date(
|
|
84810
|
+
cliBuiltAt = new Date(statSync29(scriptPath).mtimeMs).toISOString();
|
|
84719
84811
|
} catch {}
|
|
84720
|
-
let dir =
|
|
84812
|
+
let dir = dirname20(scriptPath);
|
|
84721
84813
|
for (let i = 0;i < 8; i++) {
|
|
84722
|
-
const pkgPath =
|
|
84723
|
-
if (
|
|
84814
|
+
const pkgPath = join64(dir, "package.json");
|
|
84815
|
+
if (existsSync64(pkgPath)) {
|
|
84724
84816
|
try {
|
|
84725
|
-
const pkg = JSON.parse(
|
|
84817
|
+
const pkg = JSON.parse(readFileSync57(pkgPath, "utf-8"));
|
|
84726
84818
|
if (typeof pkg.version === "string")
|
|
84727
84819
|
cliVersion = pkg.version;
|
|
84728
84820
|
} catch (err) {
|
|
@@ -84730,7 +84822,7 @@ function defaultStatusProbe(composePath) {
|
|
|
84730
84822
|
}
|
|
84731
84823
|
break;
|
|
84732
84824
|
}
|
|
84733
|
-
const parent =
|
|
84825
|
+
const parent = dirname20(dir);
|
|
84734
84826
|
if (parent === dir)
|
|
84735
84827
|
break;
|
|
84736
84828
|
dir = parent;
|
|
@@ -84743,7 +84835,7 @@ function defaultStatusProbe(composePath) {
|
|
|
84743
84835
|
warnings.push("could not resolve CLI version (no package.json found above the resolved script path)");
|
|
84744
84836
|
}
|
|
84745
84837
|
const services = [];
|
|
84746
|
-
if (!
|
|
84838
|
+
if (!existsSync64(composePath)) {
|
|
84747
84839
|
warnings.push(`compose file not found at ${composePath}; service status unknown`);
|
|
84748
84840
|
return { cliVersion, cliBuiltAt, services, warnings };
|
|
84749
84841
|
}
|
|
@@ -84939,8 +85031,8 @@ function registerUpdateCommand(program3) {
|
|
|
84939
85031
|
// src/cli/rollout.ts
|
|
84940
85032
|
init_helpers();
|
|
84941
85033
|
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
84942
|
-
import { readFileSync as
|
|
84943
|
-
import { homedir as
|
|
85034
|
+
import { readFileSync as readFileSync58, chownSync as chownSync6, statSync as statSync30 } from "node:fs";
|
|
85035
|
+
import { homedir as homedir40 } from "node:os";
|
|
84944
85036
|
init_operator_uid();
|
|
84945
85037
|
init_atomic();
|
|
84946
85038
|
init_audit_reader();
|
|
@@ -85169,10 +85261,10 @@ function executeRollout(steps, target, deps, execOpts = {}) {
|
|
|
85169
85261
|
return { ok: true, rolled, warnings };
|
|
85170
85262
|
}
|
|
85171
85263
|
function resolveRollbackTarget(auditLogPath) {
|
|
85172
|
-
const logPath = auditLogPath ?? defaultAuditLogPath2(
|
|
85264
|
+
const logPath = auditLogPath ?? defaultAuditLogPath2(homedir40());
|
|
85173
85265
|
let raw;
|
|
85174
85266
|
try {
|
|
85175
|
-
raw =
|
|
85267
|
+
raw = readFileSync58(logPath, "utf8");
|
|
85176
85268
|
} catch {
|
|
85177
85269
|
return null;
|
|
85178
85270
|
}
|
|
@@ -85286,11 +85378,11 @@ function registerRolloutCommand(program3) {
|
|
|
85286
85378
|
emitPhase: (phase) => process.stdout.write(encodeRolloutPhaseLine(phase) + `
|
|
85287
85379
|
`),
|
|
85288
85380
|
persistPin: (pin) => {
|
|
85289
|
-
const before =
|
|
85381
|
+
const before = readFileSync58(configPath, "utf8");
|
|
85290
85382
|
const after = setReleasePinInConfig(before, pin);
|
|
85291
85383
|
if (after === before)
|
|
85292
85384
|
return;
|
|
85293
|
-
writeConfigFileSync(configPath, after,
|
|
85385
|
+
writeConfigFileSync(configPath, after, statSync30(configPath).mode & 511);
|
|
85294
85386
|
try {
|
|
85295
85387
|
if (typeof process.geteuid === "function" && process.geteuid() === 0) {
|
|
85296
85388
|
const uid = resolveOperatorUid();
|
|
@@ -85360,8 +85452,8 @@ init_source();
|
|
|
85360
85452
|
init_helpers();
|
|
85361
85453
|
init_lifecycle();
|
|
85362
85454
|
import { execSync as execSync4 } from "node:child_process";
|
|
85363
|
-
import { existsSync as
|
|
85364
|
-
import { dirname as
|
|
85455
|
+
import { existsSync as existsSync65, readFileSync as readFileSync59 } from "node:fs";
|
|
85456
|
+
import { dirname as dirname21, join as join65 } from "node:path";
|
|
85365
85457
|
function getClaudeCodeVersion() {
|
|
85366
85458
|
try {
|
|
85367
85459
|
const out = execSync4("claude --version 2>/dev/null", {
|
|
@@ -85411,16 +85503,16 @@ function formatUptime3(timestamp) {
|
|
|
85411
85503
|
function locateSwitchroomInstallDir() {
|
|
85412
85504
|
let dir = import.meta.dirname;
|
|
85413
85505
|
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
85414
|
-
const pkgPath =
|
|
85415
|
-
if (
|
|
85506
|
+
const pkgPath = join65(dir, "package.json");
|
|
85507
|
+
if (existsSync65(pkgPath)) {
|
|
85416
85508
|
try {
|
|
85417
|
-
const pkg = JSON.parse(
|
|
85418
|
-
if (pkg.name === "switchroom" &&
|
|
85509
|
+
const pkg = JSON.parse(readFileSync59(pkgPath, "utf-8"));
|
|
85510
|
+
if (pkg.name === "switchroom" && existsSync65(join65(dir, ".git"))) {
|
|
85419
85511
|
return dir;
|
|
85420
85512
|
}
|
|
85421
85513
|
} catch {}
|
|
85422
85514
|
}
|
|
85423
|
-
dir =
|
|
85515
|
+
dir = dirname21(dir);
|
|
85424
85516
|
}
|
|
85425
85517
|
return null;
|
|
85426
85518
|
}
|
|
@@ -85593,18 +85685,18 @@ import { resolve as resolve40 } from "node:path";
|
|
|
85593
85685
|
|
|
85594
85686
|
// src/agents/session-retention.ts
|
|
85595
85687
|
import {
|
|
85596
|
-
existsSync as
|
|
85688
|
+
existsSync as existsSync66,
|
|
85597
85689
|
readdirSync as readdirSync22,
|
|
85598
|
-
statSync as
|
|
85599
|
-
unlinkSync as
|
|
85690
|
+
statSync as statSync31,
|
|
85691
|
+
unlinkSync as unlinkSync14
|
|
85600
85692
|
} from "node:fs";
|
|
85601
|
-
import { join as
|
|
85693
|
+
import { join as join66 } from "node:path";
|
|
85602
85694
|
var DEFAULT_SESSION_RETENTION_MAX_COUNT = 20;
|
|
85603
85695
|
var DEFAULT_SESSION_RETENTION_MAX_AGE_DAYS = 30;
|
|
85604
85696
|
var MIN_KEEP = 2;
|
|
85605
85697
|
function collectSessionJsonl(claudeConfigDir) {
|
|
85606
|
-
const projects =
|
|
85607
|
-
if (!
|
|
85698
|
+
const projects = join66(claudeConfigDir, "projects");
|
|
85699
|
+
if (!existsSync66(projects))
|
|
85608
85700
|
return [];
|
|
85609
85701
|
const found = [];
|
|
85610
85702
|
const walk2 = (dir) => {
|
|
@@ -85615,10 +85707,10 @@ function collectSessionJsonl(claudeConfigDir) {
|
|
|
85615
85707
|
return;
|
|
85616
85708
|
}
|
|
85617
85709
|
for (const name of entries) {
|
|
85618
|
-
const full =
|
|
85710
|
+
const full = join66(dir, name);
|
|
85619
85711
|
let st;
|
|
85620
85712
|
try {
|
|
85621
|
-
st =
|
|
85713
|
+
st = statSync31(full);
|
|
85622
85714
|
} catch {
|
|
85623
85715
|
continue;
|
|
85624
85716
|
}
|
|
@@ -85665,7 +85757,7 @@ function pruneSessionJsonl(claudeConfigDir, opts = {}) {
|
|
|
85665
85757
|
if (!eligible)
|
|
85666
85758
|
continue;
|
|
85667
85759
|
try {
|
|
85668
|
-
|
|
85760
|
+
unlinkSync14(f.path);
|
|
85669
85761
|
deleted++;
|
|
85670
85762
|
} catch (err) {
|
|
85671
85763
|
onWarn(`session-retention: could not delete ${f.path}: ${err.message}`);
|
|
@@ -85735,18 +85827,18 @@ function registerHandoffCommand(program3) {
|
|
|
85735
85827
|
// src/issues/store.ts
|
|
85736
85828
|
import {
|
|
85737
85829
|
closeSync as closeSync12,
|
|
85738
|
-
existsSync as
|
|
85739
|
-
mkdirSync as
|
|
85830
|
+
existsSync as existsSync67,
|
|
85831
|
+
mkdirSync as mkdirSync35,
|
|
85740
85832
|
openSync as openSync12,
|
|
85741
85833
|
readdirSync as readdirSync23,
|
|
85742
|
-
readFileSync as
|
|
85834
|
+
readFileSync as readFileSync60,
|
|
85743
85835
|
renameSync as renameSync15,
|
|
85744
|
-
statSync as
|
|
85745
|
-
unlinkSync as
|
|
85746
|
-
writeFileSync as
|
|
85836
|
+
statSync as statSync32,
|
|
85837
|
+
unlinkSync as unlinkSync15,
|
|
85838
|
+
writeFileSync as writeFileSync28,
|
|
85747
85839
|
writeSync as writeSync9
|
|
85748
85840
|
} from "node:fs";
|
|
85749
|
-
import { join as
|
|
85841
|
+
import { join as join67 } from "node:path";
|
|
85750
85842
|
import { randomBytes as randomBytes12 } from "node:crypto";
|
|
85751
85843
|
import { execSync as execSync5 } from "node:child_process";
|
|
85752
85844
|
|
|
@@ -86144,12 +86236,12 @@ function redactedMarker(ruleId) {
|
|
|
86144
86236
|
var ISSUES_FILE = "issues.jsonl";
|
|
86145
86237
|
var ISSUES_LOCK = "issues.lock";
|
|
86146
86238
|
function readAll(stateDir) {
|
|
86147
|
-
const path4 =
|
|
86148
|
-
if (!
|
|
86239
|
+
const path4 = join67(stateDir, ISSUES_FILE);
|
|
86240
|
+
if (!existsSync67(path4))
|
|
86149
86241
|
return [];
|
|
86150
86242
|
let raw;
|
|
86151
86243
|
try {
|
|
86152
|
-
raw =
|
|
86244
|
+
raw = readFileSync60(path4, "utf-8");
|
|
86153
86245
|
} catch {
|
|
86154
86246
|
return [];
|
|
86155
86247
|
}
|
|
@@ -86222,7 +86314,7 @@ function record(stateDir, input, nowFn = Date.now) {
|
|
|
86222
86314
|
});
|
|
86223
86315
|
}
|
|
86224
86316
|
function resolve41(stateDir, fingerprint, nowFn = Date.now) {
|
|
86225
|
-
if (!
|
|
86317
|
+
if (!existsSync67(join67(stateDir, ISSUES_FILE)))
|
|
86226
86318
|
return 0;
|
|
86227
86319
|
return withLock(stateDir, () => {
|
|
86228
86320
|
const all = readAll(stateDir);
|
|
@@ -86240,7 +86332,7 @@ function resolve41(stateDir, fingerprint, nowFn = Date.now) {
|
|
|
86240
86332
|
});
|
|
86241
86333
|
}
|
|
86242
86334
|
function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
86243
|
-
if (!
|
|
86335
|
+
if (!existsSync67(join67(stateDir, ISSUES_FILE)))
|
|
86244
86336
|
return 0;
|
|
86245
86337
|
return withLock(stateDir, () => {
|
|
86246
86338
|
const all = readAll(stateDir);
|
|
@@ -86258,7 +86350,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
|
86258
86350
|
});
|
|
86259
86351
|
}
|
|
86260
86352
|
function prune(stateDir, opts = {}) {
|
|
86261
|
-
if (!
|
|
86353
|
+
if (!existsSync67(join67(stateDir, ISSUES_FILE)))
|
|
86262
86354
|
return 0;
|
|
86263
86355
|
return withLock(stateDir, () => {
|
|
86264
86356
|
const all = readAll(stateDir);
|
|
@@ -86288,16 +86380,16 @@ function prune(stateDir, opts = {}) {
|
|
|
86288
86380
|
});
|
|
86289
86381
|
}
|
|
86290
86382
|
function ensureDir(stateDir) {
|
|
86291
|
-
|
|
86383
|
+
mkdirSync35(stateDir, { recursive: true });
|
|
86292
86384
|
}
|
|
86293
86385
|
function writeAll(stateDir, events) {
|
|
86294
|
-
const path4 =
|
|
86386
|
+
const path4 = join67(stateDir, ISSUES_FILE);
|
|
86295
86387
|
sweepOrphanTmpFiles(stateDir);
|
|
86296
86388
|
const tmp = `${path4}.tmp-${process.pid}-${randomBytes12(4).toString("hex")}`;
|
|
86297
86389
|
const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
|
|
86298
86390
|
`) + `
|
|
86299
86391
|
`;
|
|
86300
|
-
|
|
86392
|
+
writeFileSync28(tmp, body, "utf-8");
|
|
86301
86393
|
renameSync15(tmp, path4);
|
|
86302
86394
|
}
|
|
86303
86395
|
var ORPHAN_TMP_TTL_MS = 60000;
|
|
@@ -86313,11 +86405,11 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
86313
86405
|
for (const entry of entries) {
|
|
86314
86406
|
if (!entry.startsWith(TMP_PREFIX))
|
|
86315
86407
|
continue;
|
|
86316
|
-
const tmpPath =
|
|
86408
|
+
const tmpPath = join67(stateDir, entry);
|
|
86317
86409
|
try {
|
|
86318
|
-
const stat =
|
|
86410
|
+
const stat = statSync32(tmpPath);
|
|
86319
86411
|
if (stat.mtimeMs < cutoff) {
|
|
86320
|
-
|
|
86412
|
+
unlinkSync15(tmpPath);
|
|
86321
86413
|
}
|
|
86322
86414
|
} catch {}
|
|
86323
86415
|
}
|
|
@@ -86325,7 +86417,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
86325
86417
|
var LOCK_RETRY_MS = 25;
|
|
86326
86418
|
var LOCK_TIMEOUT_MS = 1e4;
|
|
86327
86419
|
function withLock(stateDir, fn) {
|
|
86328
|
-
const lockPath =
|
|
86420
|
+
const lockPath = join67(stateDir, ISSUES_LOCK);
|
|
86329
86421
|
const startedAt = Date.now();
|
|
86330
86422
|
let fd = null;
|
|
86331
86423
|
while (fd === null) {
|
|
@@ -86353,27 +86445,27 @@ function withLock(stateDir, fn) {
|
|
|
86353
86445
|
closeSync12(fd);
|
|
86354
86446
|
} catch {}
|
|
86355
86447
|
try {
|
|
86356
|
-
|
|
86448
|
+
unlinkSync15(lockPath);
|
|
86357
86449
|
} catch {}
|
|
86358
86450
|
}
|
|
86359
86451
|
}
|
|
86360
86452
|
function tryStealStaleLock(lockPath) {
|
|
86361
86453
|
let pidStr;
|
|
86362
86454
|
try {
|
|
86363
|
-
pidStr =
|
|
86455
|
+
pidStr = readFileSync60(lockPath, "utf-8").trim();
|
|
86364
86456
|
} catch {
|
|
86365
86457
|
return true;
|
|
86366
86458
|
}
|
|
86367
86459
|
const pid = Number(pidStr);
|
|
86368
86460
|
if (!Number.isFinite(pid) || pid <= 0) {
|
|
86369
86461
|
try {
|
|
86370
|
-
|
|
86462
|
+
unlinkSync15(lockPath);
|
|
86371
86463
|
} catch {}
|
|
86372
86464
|
return true;
|
|
86373
86465
|
}
|
|
86374
86466
|
if (pid === process.pid) {
|
|
86375
86467
|
try {
|
|
86376
|
-
|
|
86468
|
+
unlinkSync15(lockPath);
|
|
86377
86469
|
} catch {}
|
|
86378
86470
|
return true;
|
|
86379
86471
|
}
|
|
@@ -86388,7 +86480,7 @@ function tryStealStaleLock(lockPath) {
|
|
|
86388
86480
|
return false;
|
|
86389
86481
|
}
|
|
86390
86482
|
try {
|
|
86391
|
-
|
|
86483
|
+
unlinkSync15(lockPath);
|
|
86392
86484
|
} catch {}
|
|
86393
86485
|
return true;
|
|
86394
86486
|
}
|
|
@@ -86608,21 +86700,21 @@ function relTime(deltaMs) {
|
|
|
86608
86700
|
|
|
86609
86701
|
// src/cli/deps.ts
|
|
86610
86702
|
init_source();
|
|
86611
|
-
import { existsSync as
|
|
86612
|
-
import { homedir as
|
|
86613
|
-
import { join as
|
|
86703
|
+
import { existsSync as existsSync70 } from "node:fs";
|
|
86704
|
+
import { homedir as homedir43 } from "node:os";
|
|
86705
|
+
import { join as join70, resolve as resolve42 } from "node:path";
|
|
86614
86706
|
|
|
86615
86707
|
// src/deps/python.ts
|
|
86616
86708
|
import { createHash as createHash12 } from "node:crypto";
|
|
86617
86709
|
import {
|
|
86618
|
-
existsSync as
|
|
86619
|
-
mkdirSync as
|
|
86620
|
-
readFileSync as
|
|
86710
|
+
existsSync as existsSync68,
|
|
86711
|
+
mkdirSync as mkdirSync36,
|
|
86712
|
+
readFileSync as readFileSync61,
|
|
86621
86713
|
rmSync as rmSync13,
|
|
86622
|
-
writeFileSync as
|
|
86714
|
+
writeFileSync as writeFileSync29
|
|
86623
86715
|
} from "node:fs";
|
|
86624
|
-
import { dirname as
|
|
86625
|
-
import { homedir as
|
|
86716
|
+
import { dirname as dirname22, join as join68 } from "node:path";
|
|
86717
|
+
import { homedir as homedir41 } from "node:os";
|
|
86626
86718
|
import { execFileSync as execFileSync19 } from "node:child_process";
|
|
86627
86719
|
|
|
86628
86720
|
class PythonEnvError extends Error {
|
|
@@ -86634,26 +86726,26 @@ class PythonEnvError extends Error {
|
|
|
86634
86726
|
}
|
|
86635
86727
|
}
|
|
86636
86728
|
function defaultPythonCacheRoot() {
|
|
86637
|
-
return
|
|
86729
|
+
return join68(homedir41(), ".switchroom", "deps", "python");
|
|
86638
86730
|
}
|
|
86639
86731
|
function hashFile(path4) {
|
|
86640
|
-
return createHash12("sha256").update(
|
|
86732
|
+
return createHash12("sha256").update(readFileSync61(path4)).digest("hex");
|
|
86641
86733
|
}
|
|
86642
86734
|
function ensurePythonEnv(opts) {
|
|
86643
86735
|
const { skillName, requirementsPath, force = false } = opts;
|
|
86644
86736
|
const cacheRoot = opts.cacheRoot ?? defaultPythonCacheRoot();
|
|
86645
86737
|
const hostPython = opts.pythonBin ?? "python3";
|
|
86646
|
-
if (!
|
|
86738
|
+
if (!existsSync68(requirementsPath)) {
|
|
86647
86739
|
throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
|
|
86648
86740
|
}
|
|
86649
|
-
const venvDir =
|
|
86650
|
-
const stampPath =
|
|
86651
|
-
const binDir =
|
|
86652
|
-
const pythonBin =
|
|
86653
|
-
const pipBin =
|
|
86741
|
+
const venvDir = join68(cacheRoot, skillName);
|
|
86742
|
+
const stampPath = join68(venvDir, ".requirements.sha256");
|
|
86743
|
+
const binDir = join68(venvDir, "bin");
|
|
86744
|
+
const pythonBin = join68(binDir, "python");
|
|
86745
|
+
const pipBin = join68(binDir, "pip");
|
|
86654
86746
|
const targetHash = hashFile(requirementsPath);
|
|
86655
|
-
if (!force &&
|
|
86656
|
-
const existingHash =
|
|
86747
|
+
if (!force && existsSync68(stampPath) && existsSync68(pythonBin)) {
|
|
86748
|
+
const existingHash = readFileSync61(stampPath, "utf8").trim();
|
|
86657
86749
|
if (existingHash === targetHash) {
|
|
86658
86750
|
return {
|
|
86659
86751
|
skillName,
|
|
@@ -86665,10 +86757,10 @@ function ensurePythonEnv(opts) {
|
|
|
86665
86757
|
};
|
|
86666
86758
|
}
|
|
86667
86759
|
}
|
|
86668
|
-
if (
|
|
86760
|
+
if (existsSync68(venvDir)) {
|
|
86669
86761
|
rmSync13(venvDir, { recursive: true, force: true });
|
|
86670
86762
|
}
|
|
86671
|
-
|
|
86763
|
+
mkdirSync36(dirname22(venvDir), { recursive: true });
|
|
86672
86764
|
try {
|
|
86673
86765
|
execFileSync19(hostPython, ["-m", "venv", venvDir], { stdio: "pipe" });
|
|
86674
86766
|
} catch (err) {
|
|
@@ -86687,7 +86779,7 @@ function ensurePythonEnv(opts) {
|
|
|
86687
86779
|
const e = err;
|
|
86688
86780
|
throw new PythonEnvError(`Failed to install requirements for skill "${skillName}": ${e.message}`, e.stderr?.toString());
|
|
86689
86781
|
}
|
|
86690
|
-
|
|
86782
|
+
writeFileSync29(stampPath, targetHash + `
|
|
86691
86783
|
`);
|
|
86692
86784
|
return {
|
|
86693
86785
|
skillName,
|
|
@@ -86703,14 +86795,14 @@ function ensurePythonEnv(opts) {
|
|
|
86703
86795
|
import { createHash as createHash13 } from "node:crypto";
|
|
86704
86796
|
import {
|
|
86705
86797
|
copyFileSync as copyFileSync10,
|
|
86706
|
-
existsSync as
|
|
86707
|
-
mkdirSync as
|
|
86708
|
-
readFileSync as
|
|
86798
|
+
existsSync as existsSync69,
|
|
86799
|
+
mkdirSync as mkdirSync37,
|
|
86800
|
+
readFileSync as readFileSync62,
|
|
86709
86801
|
rmSync as rmSync14,
|
|
86710
|
-
writeFileSync as
|
|
86802
|
+
writeFileSync as writeFileSync30
|
|
86711
86803
|
} from "node:fs";
|
|
86712
|
-
import { dirname as
|
|
86713
|
-
import { homedir as
|
|
86804
|
+
import { dirname as dirname23, join as join69 } from "node:path";
|
|
86805
|
+
import { homedir as homedir42 } from "node:os";
|
|
86714
86806
|
import { execFileSync as execFileSync20 } from "node:child_process";
|
|
86715
86807
|
|
|
86716
86808
|
class NodeEnvError extends Error {
|
|
@@ -86733,23 +86825,23 @@ var LOCKFILES_FOR = {
|
|
|
86733
86825
|
npm: ["package-lock.json"]
|
|
86734
86826
|
};
|
|
86735
86827
|
function defaultNodeCacheRoot() {
|
|
86736
|
-
return
|
|
86828
|
+
return join69(homedir42(), ".switchroom", "deps", "node");
|
|
86737
86829
|
}
|
|
86738
86830
|
function hashDepInputs(packageJsonPath) {
|
|
86739
|
-
const sourceDir =
|
|
86831
|
+
const sourceDir = dirname23(packageJsonPath);
|
|
86740
86832
|
const hasher = createHash13("sha256");
|
|
86741
86833
|
hasher.update(`package.json
|
|
86742
86834
|
`);
|
|
86743
|
-
hasher.update(
|
|
86835
|
+
hasher.update(readFileSync62(packageJsonPath));
|
|
86744
86836
|
for (const lockName of ALL_LOCKFILES) {
|
|
86745
|
-
const lockPath =
|
|
86746
|
-
if (
|
|
86837
|
+
const lockPath = join69(sourceDir, lockName);
|
|
86838
|
+
if (existsSync69(lockPath)) {
|
|
86747
86839
|
hasher.update(`
|
|
86748
86840
|
`);
|
|
86749
86841
|
hasher.update(lockName);
|
|
86750
86842
|
hasher.update(`
|
|
86751
86843
|
`);
|
|
86752
|
-
hasher.update(
|
|
86844
|
+
hasher.update(readFileSync62(lockPath));
|
|
86753
86845
|
}
|
|
86754
86846
|
}
|
|
86755
86847
|
return hasher.digest("hex");
|
|
@@ -86758,17 +86850,17 @@ function ensureNodeEnv(opts) {
|
|
|
86758
86850
|
const { skillName, packageJsonPath, force = false } = opts;
|
|
86759
86851
|
const cacheRoot = opts.cacheRoot ?? defaultNodeCacheRoot();
|
|
86760
86852
|
const installer = opts.installer ?? "bun";
|
|
86761
|
-
if (!
|
|
86853
|
+
if (!existsSync69(packageJsonPath)) {
|
|
86762
86854
|
throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
|
|
86763
86855
|
}
|
|
86764
|
-
const sourceDir =
|
|
86765
|
-
const envDir =
|
|
86766
|
-
const stampPath =
|
|
86767
|
-
const nodeModulesDir =
|
|
86768
|
-
const binDir =
|
|
86856
|
+
const sourceDir = dirname23(packageJsonPath);
|
|
86857
|
+
const envDir = join69(cacheRoot, skillName);
|
|
86858
|
+
const stampPath = join69(envDir, ".package.sha256");
|
|
86859
|
+
const nodeModulesDir = join69(envDir, "node_modules");
|
|
86860
|
+
const binDir = join69(nodeModulesDir, ".bin");
|
|
86769
86861
|
const targetHash = hashDepInputs(packageJsonPath);
|
|
86770
|
-
if (!force &&
|
|
86771
|
-
const existingHash =
|
|
86862
|
+
if (!force && existsSync69(stampPath) && existsSync69(nodeModulesDir)) {
|
|
86863
|
+
const existingHash = readFileSync62(stampPath, "utf8").trim();
|
|
86772
86864
|
if (existingHash === targetHash) {
|
|
86773
86865
|
return {
|
|
86774
86866
|
skillName,
|
|
@@ -86779,16 +86871,16 @@ function ensureNodeEnv(opts) {
|
|
|
86779
86871
|
};
|
|
86780
86872
|
}
|
|
86781
86873
|
}
|
|
86782
|
-
if (
|
|
86874
|
+
if (existsSync69(envDir)) {
|
|
86783
86875
|
rmSync14(envDir, { recursive: true, force: true });
|
|
86784
86876
|
}
|
|
86785
|
-
|
|
86786
|
-
copyFileSync10(packageJsonPath,
|
|
86877
|
+
mkdirSync37(envDir, { recursive: true });
|
|
86878
|
+
copyFileSync10(packageJsonPath, join69(envDir, "package.json"));
|
|
86787
86879
|
let copiedLockfile = false;
|
|
86788
86880
|
for (const lockName of LOCKFILES_FOR[installer]) {
|
|
86789
|
-
const lockPath =
|
|
86790
|
-
if (
|
|
86791
|
-
copyFileSync10(lockPath,
|
|
86881
|
+
const lockPath = join69(sourceDir, lockName);
|
|
86882
|
+
if (existsSync69(lockPath)) {
|
|
86883
|
+
copyFileSync10(lockPath, join69(envDir, lockName));
|
|
86792
86884
|
copiedLockfile = true;
|
|
86793
86885
|
}
|
|
86794
86886
|
}
|
|
@@ -86804,7 +86896,7 @@ function ensureNodeEnv(opts) {
|
|
|
86804
86896
|
const e = err;
|
|
86805
86897
|
throw new NodeEnvError(`Failed to install node deps for skill "${skillName}" with ${installer}: ${e.message}`, e.stderr?.toString());
|
|
86806
86898
|
}
|
|
86807
|
-
|
|
86899
|
+
writeFileSync30(stampPath, targetHash + `
|
|
86808
86900
|
`);
|
|
86809
86901
|
return {
|
|
86810
86902
|
skillName,
|
|
@@ -86817,28 +86909,28 @@ function ensureNodeEnv(opts) {
|
|
|
86817
86909
|
|
|
86818
86910
|
// src/cli/deps.ts
|
|
86819
86911
|
function builtinSkillsRoot() {
|
|
86820
|
-
return resolve42(
|
|
86912
|
+
return resolve42(homedir43(), ".switchroom/skills/_bundled");
|
|
86821
86913
|
}
|
|
86822
86914
|
function registerDepsCommand(program3) {
|
|
86823
86915
|
const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
|
|
86824
86916
|
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) => {
|
|
86825
86917
|
const skillsRoot = builtinSkillsRoot();
|
|
86826
|
-
if (!
|
|
86918
|
+
if (!existsSync70(skillsRoot)) {
|
|
86827
86919
|
console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
|
|
86828
86920
|
process.exit(1);
|
|
86829
86921
|
}
|
|
86830
|
-
const skillDir =
|
|
86831
|
-
if (!
|
|
86922
|
+
const skillDir = join70(skillsRoot, skill);
|
|
86923
|
+
if (!existsSync70(skillDir)) {
|
|
86832
86924
|
console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
|
|
86833
86925
|
process.exit(1);
|
|
86834
86926
|
}
|
|
86835
|
-
const requirementsPath =
|
|
86836
|
-
const packageJsonPath =
|
|
86837
|
-
const wantPython = opts.python ?? (!opts.python && !opts.node &&
|
|
86838
|
-
const wantNode = opts.node ?? (!opts.python && !opts.node &&
|
|
86927
|
+
const requirementsPath = join70(skillDir, "requirements.txt");
|
|
86928
|
+
const packageJsonPath = join70(skillDir, "package.json");
|
|
86929
|
+
const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync70(requirementsPath));
|
|
86930
|
+
const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync70(packageJsonPath));
|
|
86839
86931
|
let did = 0;
|
|
86840
86932
|
if (wantPython) {
|
|
86841
|
-
if (!
|
|
86933
|
+
if (!existsSync70(requirementsPath)) {
|
|
86842
86934
|
console.error(source_default.red(`Skill "${skill}" has no requirements.txt at ${requirementsPath}`));
|
|
86843
86935
|
process.exit(1);
|
|
86844
86936
|
}
|
|
@@ -86862,7 +86954,7 @@ function registerDepsCommand(program3) {
|
|
|
86862
86954
|
}
|
|
86863
86955
|
}
|
|
86864
86956
|
if (wantNode) {
|
|
86865
|
-
if (!
|
|
86957
|
+
if (!existsSync70(packageJsonPath)) {
|
|
86866
86958
|
console.error(source_default.red(`Skill "${skill}" has no package.json at ${packageJsonPath}`));
|
|
86867
86959
|
process.exit(1);
|
|
86868
86960
|
}
|
|
@@ -86895,7 +86987,7 @@ function registerDepsCommand(program3) {
|
|
|
86895
86987
|
// src/cli/workspace.ts
|
|
86896
86988
|
init_helpers();
|
|
86897
86989
|
init_loader();
|
|
86898
|
-
import { existsSync as
|
|
86990
|
+
import { existsSync as existsSync71 } from "node:fs";
|
|
86899
86991
|
import { resolve as resolve43, sep as sep3 } from "node:path";
|
|
86900
86992
|
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
86901
86993
|
|
|
@@ -87672,7 +87764,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
87672
87764
|
if (!dir)
|
|
87673
87765
|
return;
|
|
87674
87766
|
const gitDir = resolve43(dir, ".git");
|
|
87675
|
-
if (!
|
|
87767
|
+
if (!existsSync71(gitDir)) {
|
|
87676
87768
|
process.stdout.write(`Workspace is not a git repository. Re-run \`switchroom agent create ${agentName}\` ` + `or manually \`git init\` in ${dir} to enable versioning.
|
|
87677
87769
|
`);
|
|
87678
87770
|
return;
|
|
@@ -87726,7 +87818,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
87726
87818
|
if (!dir)
|
|
87727
87819
|
return;
|
|
87728
87820
|
const gitDir = resolve43(dir, ".git");
|
|
87729
|
-
if (!
|
|
87821
|
+
if (!existsSync71(gitDir)) {
|
|
87730
87822
|
process.stdout.write(`Workspace is not a git repository.
|
|
87731
87823
|
`);
|
|
87732
87824
|
return;
|
|
@@ -87751,7 +87843,7 @@ function resolveAgentWorkspaceDirOrExit(program3, agentName) {
|
|
|
87751
87843
|
const agentsDir = resolveAgentsDir(config);
|
|
87752
87844
|
const agentDir = resolve43(agentsDir, agentName);
|
|
87753
87845
|
const dir = resolveAgentWorkspaceDir(agentDir);
|
|
87754
|
-
if (!
|
|
87846
|
+
if (!existsSync71(dir)) {
|
|
87755
87847
|
process.stderr.write(`workspace: ${dir} does not exist yet. Run \`switchroom setup\` or \`switchroom agent scaffold ${agentName}\` to seed it.
|
|
87756
87848
|
`);
|
|
87757
87849
|
return;
|
|
@@ -87787,8 +87879,8 @@ function safeParseInt(value, fallback) {
|
|
|
87787
87879
|
init_helpers();
|
|
87788
87880
|
init_loader();
|
|
87789
87881
|
init_merge();
|
|
87790
|
-
import { copyFileSync as copyFileSync11, existsSync as
|
|
87791
|
-
import { join as
|
|
87882
|
+
import { copyFileSync as copyFileSync11, existsSync as existsSync72, readFileSync as readFileSync63, writeFileSync as writeFileSync31 } from "node:fs";
|
|
87883
|
+
import { join as join71, resolve as resolve44 } from "node:path";
|
|
87792
87884
|
init_scaffold();
|
|
87793
87885
|
init_profiles();
|
|
87794
87886
|
init_schema();
|
|
@@ -87805,7 +87897,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
87805
87897
|
const agentsDir = resolveAgentsDir(config);
|
|
87806
87898
|
const agentDir = resolve44(agentsDir, agentName);
|
|
87807
87899
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
87808
|
-
if (!
|
|
87900
|
+
if (!existsSync72(workspaceDir)) {
|
|
87809
87901
|
console.error(`soul: ${workspaceDir} does not exist yet. Run \`switchroom setup\` ` + `or \`switchroom agent scaffold ${agentName}\` to seed it.`);
|
|
87810
87902
|
process.exit(1);
|
|
87811
87903
|
}
|
|
@@ -87814,7 +87906,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
87814
87906
|
profileName,
|
|
87815
87907
|
profilePath,
|
|
87816
87908
|
workspaceDir,
|
|
87817
|
-
soulPath:
|
|
87909
|
+
soulPath: join71(workspaceDir, "SOUL.md"),
|
|
87818
87910
|
soul: merged.soul
|
|
87819
87911
|
};
|
|
87820
87912
|
}
|
|
@@ -87831,11 +87923,11 @@ function registerSoulCommand(program3) {
|
|
|
87831
87923
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
87832
87924
|
if (!t)
|
|
87833
87925
|
return;
|
|
87834
|
-
if (!
|
|
87926
|
+
if (!existsSync72(t.soulPath)) {
|
|
87835
87927
|
console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
|
|
87836
87928
|
process.exit(1);
|
|
87837
87929
|
}
|
|
87838
|
-
process.stdout.write(
|
|
87930
|
+
process.stdout.write(readFileSync63(t.soulPath, "utf-8"));
|
|
87839
87931
|
}));
|
|
87840
87932
|
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) => {
|
|
87841
87933
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
@@ -87846,7 +87938,7 @@ function registerSoulCommand(program3) {
|
|
|
87846
87938
|
console.error(`soul: profile "${t.profileName}" ships no SOUL.md.hbs \u2014 ` + `nothing to re-seed from.`);
|
|
87847
87939
|
process.exit(1);
|
|
87848
87940
|
}
|
|
87849
|
-
const exists =
|
|
87941
|
+
const exists = existsSync72(t.soulPath);
|
|
87850
87942
|
if (exists && !opts.yes) {
|
|
87851
87943
|
if (!isInteractive()) {
|
|
87852
87944
|
console.error(`soul: ${t.soulPath} already exists. Re-run with --yes to ` + `replace it (the current file is backed up to SOUL.md.bak).`);
|
|
@@ -87861,12 +87953,12 @@ function registerSoulCommand(program3) {
|
|
|
87861
87953
|
let backupPath;
|
|
87862
87954
|
if (exists) {
|
|
87863
87955
|
backupPath = `${t.soulPath}.bak`;
|
|
87864
|
-
if (
|
|
87956
|
+
if (existsSync72(backupPath)) {
|
|
87865
87957
|
backupPath = `${t.soulPath}.bak.${Date.now()}`;
|
|
87866
87958
|
}
|
|
87867
87959
|
copyFileSync11(t.soulPath, backupPath);
|
|
87868
87960
|
}
|
|
87869
|
-
|
|
87961
|
+
writeFileSync31(t.soulPath, content, "utf-8");
|
|
87870
87962
|
if (backupPath) {
|
|
87871
87963
|
console.log(`soul: re-seeded ${agentName}'s SOUL.md from profile ` + `"${t.profileName}".
|
|
87872
87964
|
` + ` Previous version saved to ${backupPath}`);
|
|
@@ -87880,8 +87972,8 @@ function registerSoulCommand(program3) {
|
|
|
87880
87972
|
// src/cli/debug.ts
|
|
87881
87973
|
init_helpers();
|
|
87882
87974
|
init_loader();
|
|
87883
|
-
import { existsSync as
|
|
87884
|
-
import { resolve as resolve45, join as
|
|
87975
|
+
import { existsSync as existsSync73, readFileSync as readFileSync64, readdirSync as readdirSync24, statSync as statSync33 } from "node:fs";
|
|
87976
|
+
import { resolve as resolve45, join as join72 } from "node:path";
|
|
87885
87977
|
import { createHash as createHash14 } from "node:crypto";
|
|
87886
87978
|
init_merge();
|
|
87887
87979
|
init_hindsight2();
|
|
@@ -87892,11 +87984,11 @@ function estimateTokens(bytes) {
|
|
|
87892
87984
|
return Math.round(bytes / 3.7);
|
|
87893
87985
|
}
|
|
87894
87986
|
function readMcpServerNames(agentDir) {
|
|
87895
|
-
const mcpPath =
|
|
87896
|
-
if (!
|
|
87987
|
+
const mcpPath = join72(agentDir, ".mcp.json");
|
|
87988
|
+
if (!existsSync73(mcpPath))
|
|
87897
87989
|
return [];
|
|
87898
87990
|
try {
|
|
87899
|
-
const parsed = JSON.parse(
|
|
87991
|
+
const parsed = JSON.parse(readFileSync64(mcpPath, "utf-8"));
|
|
87900
87992
|
return Object.keys(parsed.mcpServers ?? {});
|
|
87901
87993
|
} catch {
|
|
87902
87994
|
return null;
|
|
@@ -87906,8 +87998,8 @@ function sha256(content) {
|
|
|
87906
87998
|
return createHash14("sha256").update(content).digest("hex").slice(0, 16);
|
|
87907
87999
|
}
|
|
87908
88000
|
function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
87909
|
-
const projectsDir =
|
|
87910
|
-
if (!
|
|
88001
|
+
const projectsDir = join72(claudeConfigDir, "projects");
|
|
88002
|
+
if (!existsSync73(projectsDir))
|
|
87911
88003
|
return;
|
|
87912
88004
|
try {
|
|
87913
88005
|
const entries = readdirSync24(projectsDir, { withFileTypes: true });
|
|
@@ -87915,11 +88007,11 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
87915
88007
|
for (const entry of entries) {
|
|
87916
88008
|
if (!entry.isDirectory())
|
|
87917
88009
|
continue;
|
|
87918
|
-
const projectPath =
|
|
87919
|
-
const transcriptPath =
|
|
87920
|
-
if (!
|
|
88010
|
+
const projectPath = join72(projectsDir, entry.name);
|
|
88011
|
+
const transcriptPath = join72(projectPath, "transcript.jsonl");
|
|
88012
|
+
if (!existsSync73(transcriptPath))
|
|
87921
88013
|
continue;
|
|
87922
|
-
const stat3 =
|
|
88014
|
+
const stat3 = statSync33(transcriptPath);
|
|
87923
88015
|
if (!latest || stat3.mtimeMs > latest.mtime) {
|
|
87924
88016
|
latest = { path: transcriptPath, mtime: stat3.mtimeMs };
|
|
87925
88017
|
}
|
|
@@ -87931,7 +88023,7 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
87931
88023
|
}
|
|
87932
88024
|
function extractLatestUserMessage(transcriptPath) {
|
|
87933
88025
|
try {
|
|
87934
|
-
const content =
|
|
88026
|
+
const content = readFileSync64(transcriptPath, "utf-8");
|
|
87935
88027
|
const lines = content.trim().split(`
|
|
87936
88028
|
`).filter(Boolean);
|
|
87937
88029
|
for (let i = lines.length - 1;i >= 0; i--) {
|
|
@@ -87980,16 +88072,16 @@ function registerDebugCommand(program3) {
|
|
|
87980
88072
|
}
|
|
87981
88073
|
const agentsDir = resolveAgentsDir(config);
|
|
87982
88074
|
const agentDir = resolve45(agentsDir, agentName);
|
|
87983
|
-
if (!
|
|
88075
|
+
if (!existsSync73(agentDir)) {
|
|
87984
88076
|
console.error(`Agent directory not found: ${agentDir}`);
|
|
87985
88077
|
process.exit(1);
|
|
87986
88078
|
}
|
|
87987
88079
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
87988
|
-
const claudeConfigDir =
|
|
87989
|
-
const claudeMdPath =
|
|
87990
|
-
const soulMdPath =
|
|
87991
|
-
const workspaceSoulMdPath =
|
|
87992
|
-
const handoffPath =
|
|
88080
|
+
const claudeConfigDir = join72(agentDir, ".claude");
|
|
88081
|
+
const claudeMdPath = join72(agentDir, "CLAUDE.md");
|
|
88082
|
+
const soulMdPath = join72(agentDir, "SOUL.md");
|
|
88083
|
+
const workspaceSoulMdPath = join72(workspaceDir, "SOUL.md");
|
|
88084
|
+
const handoffPath = join72(agentDir, ".handoff.md");
|
|
87993
88085
|
const lastN = parseInt(opts.last, 10);
|
|
87994
88086
|
if (isNaN(lastN) || lastN < 1) {
|
|
87995
88087
|
console.error("--last must be a positive integer");
|
|
@@ -88035,7 +88127,7 @@ function registerDebugCommand(program3) {
|
|
|
88035
88127
|
}
|
|
88036
88128
|
console.log(`=== Append System Prompt (per-session) ===
|
|
88037
88129
|
`);
|
|
88038
|
-
const handoffContent =
|
|
88130
|
+
const handoffContent = existsSync73(handoffPath) ? readFileSync64(handoffPath, "utf-8") : "";
|
|
88039
88131
|
if (handoffContent.trim().length > 0) {
|
|
88040
88132
|
console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
|
|
88041
88133
|
console.log(handoffContent);
|
|
@@ -88046,7 +88138,7 @@ function registerDebugCommand(program3) {
|
|
|
88046
88138
|
}
|
|
88047
88139
|
console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
|
|
88048
88140
|
`);
|
|
88049
|
-
const claudeMdContent =
|
|
88141
|
+
const claudeMdContent = existsSync73(claudeMdPath) ? readFileSync64(claudeMdPath, "utf-8") : "";
|
|
88050
88142
|
if (claudeMdContent.trim().length > 0) {
|
|
88051
88143
|
console.log(`(${formatBytes(claudeMdContent.length)})`);
|
|
88052
88144
|
console.log(claudeMdContent);
|
|
@@ -88057,7 +88149,7 @@ function registerDebugCommand(program3) {
|
|
|
88057
88149
|
}
|
|
88058
88150
|
console.log(`=== Persona (SOUL.md) ===
|
|
88059
88151
|
`);
|
|
88060
|
-
const soulMdContent =
|
|
88152
|
+
const soulMdContent = existsSync73(soulMdPath) ? readFileSync64(soulMdPath, "utf-8") : existsSync73(workspaceSoulMdPath) ? readFileSync64(workspaceSoulMdPath, "utf-8") : "";
|
|
88061
88153
|
if (soulMdContent.trim().length > 0) {
|
|
88062
88154
|
console.log(`(${formatBytes(soulMdContent.length)})`);
|
|
88063
88155
|
console.log(soulMdContent);
|
|
@@ -88118,11 +88210,11 @@ function registerDebugCommand(program3) {
|
|
|
88118
88210
|
const soulMdBytes = soulMdContent.length;
|
|
88119
88211
|
const perTurnBytes = dynamicResult.concatenated.length;
|
|
88120
88212
|
const userBytes = userMessage?.text.length ?? 0;
|
|
88121
|
-
const fleetDir =
|
|
88122
|
-
const fleetInvPath =
|
|
88123
|
-
const fleetClaudePath =
|
|
88124
|
-
const fleetInvBytes =
|
|
88125
|
-
const fleetClaudeBytes =
|
|
88213
|
+
const fleetDir = join72(agentsDir, "..", "fleet");
|
|
88214
|
+
const fleetInvPath = join72(fleetDir, "switchroom-invariants.md");
|
|
88215
|
+
const fleetClaudePath = join72(fleetDir, "CLAUDE.md");
|
|
88216
|
+
const fleetInvBytes = existsSync73(fleetInvPath) ? readFileSync64(fleetInvPath, "utf-8").length : 0;
|
|
88217
|
+
const fleetClaudeBytes = existsSync73(fleetClaudePath) ? readFileSync64(fleetClaudePath, "utf-8").length : 0;
|
|
88126
88218
|
const fleetBytes = fleetInvBytes + fleetClaudeBytes;
|
|
88127
88219
|
const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
|
|
88128
88220
|
console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
|
|
@@ -88155,44 +88247,44 @@ init_source();
|
|
|
88155
88247
|
|
|
88156
88248
|
// src/worktree/claim.ts
|
|
88157
88249
|
import { execFileSync as execFileSync21 } from "node:child_process";
|
|
88158
|
-
import { closeSync as closeSync13, mkdirSync as
|
|
88159
|
-
import { join as
|
|
88160
|
-
import { homedir as
|
|
88250
|
+
import { closeSync as closeSync13, mkdirSync as mkdirSync39, openSync as openSync13, existsSync as existsSync75, unlinkSync as unlinkSync17 } from "node:fs";
|
|
88251
|
+
import { join as join74, resolve as resolve47 } from "node:path";
|
|
88252
|
+
import { homedir as homedir45 } from "node:os";
|
|
88161
88253
|
import { randomBytes as randomBytes13 } from "node:crypto";
|
|
88162
88254
|
|
|
88163
88255
|
// src/worktree/registry.ts
|
|
88164
88256
|
import {
|
|
88165
|
-
mkdirSync as
|
|
88166
|
-
writeFileSync as
|
|
88167
|
-
readFileSync as
|
|
88257
|
+
mkdirSync as mkdirSync38,
|
|
88258
|
+
writeFileSync as writeFileSync32,
|
|
88259
|
+
readFileSync as readFileSync65,
|
|
88168
88260
|
readdirSync as readdirSync25,
|
|
88169
|
-
unlinkSync as
|
|
88170
|
-
existsSync as
|
|
88261
|
+
unlinkSync as unlinkSync16,
|
|
88262
|
+
existsSync as existsSync74,
|
|
88171
88263
|
renameSync as renameSync16
|
|
88172
88264
|
} from "node:fs";
|
|
88173
|
-
import { join as
|
|
88174
|
-
import { homedir as
|
|
88265
|
+
import { join as join73, resolve as resolve46 } from "node:path";
|
|
88266
|
+
import { homedir as homedir44 } from "node:os";
|
|
88175
88267
|
function registryDir() {
|
|
88176
|
-
return resolve46(process.env.SWITCHROOM_WORKTREE_DIR ??
|
|
88268
|
+
return resolve46(process.env.SWITCHROOM_WORKTREE_DIR ?? join73(homedir44(), ".switchroom", "worktrees"));
|
|
88177
88269
|
}
|
|
88178
88270
|
function recordPath(id) {
|
|
88179
|
-
return
|
|
88271
|
+
return join73(registryDir(), `${id}.json`);
|
|
88180
88272
|
}
|
|
88181
88273
|
function ensureDir2() {
|
|
88182
|
-
|
|
88274
|
+
mkdirSync38(registryDir(), { recursive: true });
|
|
88183
88275
|
}
|
|
88184
88276
|
function writeRecord(record2) {
|
|
88185
88277
|
ensureDir2();
|
|
88186
88278
|
const target = recordPath(record2.id);
|
|
88187
88279
|
const tmp = `${target}.tmp${process.pid}`;
|
|
88188
|
-
|
|
88280
|
+
writeFileSync32(tmp, JSON.stringify(record2, null, 2) + `
|
|
88189
88281
|
`, { mode: 384 });
|
|
88190
88282
|
renameSync16(tmp, target);
|
|
88191
88283
|
}
|
|
88192
88284
|
function readRecord(id) {
|
|
88193
88285
|
const path7 = recordPath(id);
|
|
88194
88286
|
try {
|
|
88195
|
-
const raw =
|
|
88287
|
+
const raw = readFileSync65(path7, "utf8");
|
|
88196
88288
|
return JSON.parse(raw);
|
|
88197
88289
|
} catch {
|
|
88198
88290
|
return null;
|
|
@@ -88201,7 +88293,7 @@ function readRecord(id) {
|
|
|
88201
88293
|
function deleteRecord(id) {
|
|
88202
88294
|
const path7 = recordPath(id);
|
|
88203
88295
|
try {
|
|
88204
|
-
|
|
88296
|
+
unlinkSync16(path7);
|
|
88205
88297
|
} catch {}
|
|
88206
88298
|
}
|
|
88207
88299
|
function listRecords() {
|
|
@@ -88225,9 +88317,9 @@ function countByRepo(repoPath) {
|
|
|
88225
88317
|
// src/worktree/claim.ts
|
|
88226
88318
|
function acquireRepoLock(repoPath) {
|
|
88227
88319
|
const lockDir = registryDir();
|
|
88228
|
-
|
|
88320
|
+
mkdirSync39(lockDir, { recursive: true });
|
|
88229
88321
|
const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
|
|
88230
|
-
const lockPath =
|
|
88322
|
+
const lockPath = join74(lockDir, `.lock-${lockName}`);
|
|
88231
88323
|
const deadline = Date.now() + 5000;
|
|
88232
88324
|
let fd = null;
|
|
88233
88325
|
while (fd === null) {
|
|
@@ -88248,13 +88340,13 @@ function acquireRepoLock(repoPath) {
|
|
|
88248
88340
|
closeSync13(fd);
|
|
88249
88341
|
} catch {}
|
|
88250
88342
|
try {
|
|
88251
|
-
|
|
88343
|
+
unlinkSync17(lockPath);
|
|
88252
88344
|
} catch {}
|
|
88253
88345
|
};
|
|
88254
88346
|
}
|
|
88255
88347
|
var DEFAULT_CONCURRENCY = 5;
|
|
88256
88348
|
function worktreesBaseDir() {
|
|
88257
|
-
return resolve47(process.env.SWITCHROOM_WORKTREE_BASE ??
|
|
88349
|
+
return resolve47(process.env.SWITCHROOM_WORKTREE_BASE ?? join74(homedir45(), ".switchroom", "worktree-checkouts"));
|
|
88258
88350
|
}
|
|
88259
88351
|
function shortId() {
|
|
88260
88352
|
return randomBytes13(4).toString("hex");
|
|
@@ -88276,12 +88368,12 @@ function resolveRepoPath(repo, codeRepos) {
|
|
|
88276
88368
|
}
|
|
88277
88369
|
function expandHome(p) {
|
|
88278
88370
|
if (p.startsWith("~/"))
|
|
88279
|
-
return
|
|
88371
|
+
return join74(homedir45(), p.slice(2));
|
|
88280
88372
|
return p;
|
|
88281
88373
|
}
|
|
88282
88374
|
async function claimWorktree(input, codeRepos) {
|
|
88283
88375
|
const repoPath = resolveRepoPath(input.repo, codeRepos);
|
|
88284
|
-
if (!
|
|
88376
|
+
if (!existsSync75(repoPath)) {
|
|
88285
88377
|
throw new Error(`Repository path does not exist: ${repoPath}`);
|
|
88286
88378
|
}
|
|
88287
88379
|
let concurrencyCap = DEFAULT_CONCURRENCY;
|
|
@@ -88303,8 +88395,8 @@ async function claimWorktree(input, codeRepos) {
|
|
|
88303
88395
|
const taskSuffix = input.taskName ? sanitizeTaskName(input.taskName) : "task";
|
|
88304
88396
|
branch = `task/${taskSuffix}-${id}`;
|
|
88305
88397
|
const baseDir = worktreesBaseDir();
|
|
88306
|
-
|
|
88307
|
-
worktreePath =
|
|
88398
|
+
mkdirSync39(baseDir, { recursive: true });
|
|
88399
|
+
worktreePath = join74(baseDir, `${id}-${taskSuffix}`);
|
|
88308
88400
|
const ambientOwner = process.env.SWITCHROOM_AGENT_NAME;
|
|
88309
88401
|
const ownerAgent = input.ownerAgent ?? (ambientOwner != null && ambientOwner !== "" ? ambientOwner : undefined);
|
|
88310
88402
|
const now = new Date().toISOString();
|
|
@@ -88337,7 +88429,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
88337
88429
|
|
|
88338
88430
|
// src/worktree/release.ts
|
|
88339
88431
|
import { execFileSync as execFileSync22 } from "node:child_process";
|
|
88340
|
-
import { existsSync as
|
|
88432
|
+
import { existsSync as existsSync76 } from "node:fs";
|
|
88341
88433
|
function releaseWorktree(input) {
|
|
88342
88434
|
const { id } = input;
|
|
88343
88435
|
const record2 = readRecord(id);
|
|
@@ -88345,7 +88437,7 @@ function releaseWorktree(input) {
|
|
|
88345
88437
|
return { released: true };
|
|
88346
88438
|
}
|
|
88347
88439
|
let gitSuccess = true;
|
|
88348
|
-
if (
|
|
88440
|
+
if (existsSync76(record2.path)) {
|
|
88349
88441
|
try {
|
|
88350
88442
|
execFileSync22("git", ["worktree", "remove", "--force", record2.path], {
|
|
88351
88443
|
cwd: record2.repo,
|
|
@@ -88384,7 +88476,7 @@ function listWorktrees() {
|
|
|
88384
88476
|
|
|
88385
88477
|
// src/worktree/reaper.ts
|
|
88386
88478
|
import { execFileSync as execFileSync23 } from "node:child_process";
|
|
88387
|
-
import { existsSync as
|
|
88479
|
+
import { existsSync as existsSync77 } from "node:fs";
|
|
88388
88480
|
var STALE_THRESHOLD_MS = 10 * 60 * 1000;
|
|
88389
88481
|
function isPathInUse(path7) {
|
|
88390
88482
|
try {
|
|
@@ -88411,7 +88503,7 @@ function hasUncommittedChanges(repoPath, worktreePath) {
|
|
|
88411
88503
|
function reapRecord(record2) {
|
|
88412
88504
|
const { id, path: path7, repo, branch, ownerAgent } = record2;
|
|
88413
88505
|
let warning = null;
|
|
88414
|
-
if (
|
|
88506
|
+
if (existsSync77(path7)) {
|
|
88415
88507
|
if (hasUncommittedChanges(repo, path7)) {
|
|
88416
88508
|
warning = `[worktree-reaper] Reaped worktree with uncommitted changes: ` + `id=${id} branch=${branch} agent=${ownerAgent ?? "unknown"} path=${path7}`;
|
|
88417
88509
|
}
|
|
@@ -88432,7 +88524,7 @@ function runReaper(nowMs) {
|
|
|
88432
88524
|
const warnings = [];
|
|
88433
88525
|
for (const record2 of records) {
|
|
88434
88526
|
const heartbeatAge = now - new Date(record2.heartbeatAt).getTime();
|
|
88435
|
-
const worktreeExists =
|
|
88527
|
+
const worktreeExists = existsSync77(record2.path);
|
|
88436
88528
|
if (!worktreeExists) {
|
|
88437
88529
|
deleteRecord(record2.id);
|
|
88438
88530
|
reaped.push(record2.id);
|
|
@@ -88452,16 +88544,16 @@ function runReaper(nowMs) {
|
|
|
88452
88544
|
// src/worktree/gc.ts
|
|
88453
88545
|
import { execFileSync as execFileSync24 } from "node:child_process";
|
|
88454
88546
|
import {
|
|
88455
|
-
existsSync as
|
|
88456
|
-
readFileSync as
|
|
88547
|
+
existsSync as existsSync78,
|
|
88548
|
+
readFileSync as readFileSync66,
|
|
88457
88549
|
readdirSync as readdirSync26,
|
|
88458
|
-
statSync as
|
|
88550
|
+
statSync as statSync34,
|
|
88459
88551
|
renameSync as renameSync17,
|
|
88460
|
-
mkdirSync as
|
|
88552
|
+
mkdirSync as mkdirSync40,
|
|
88461
88553
|
rmSync as rmSync15
|
|
88462
88554
|
} from "node:fs";
|
|
88463
|
-
import { homedir as
|
|
88464
|
-
import { join as
|
|
88555
|
+
import { homedir as homedir46 } from "node:os";
|
|
88556
|
+
import { join as join75, resolve as resolve48 } from "node:path";
|
|
88465
88557
|
function parseGitdirPointer(dotGitFileContents) {
|
|
88466
88558
|
const m = /^gitdir:\s*(.+?)\s*$/m.exec(dotGitFileContents);
|
|
88467
88559
|
return m ? m[1] : null;
|
|
@@ -88576,17 +88668,17 @@ function defaultPrSignal(repo, branch, exec) {
|
|
|
88576
88668
|
}
|
|
88577
88669
|
}
|
|
88578
88670
|
function trashRoot() {
|
|
88579
|
-
return resolve48(process.env.SWITCHROOM_WORKTREE_TRASH ??
|
|
88671
|
+
return resolve48(process.env.SWITCHROOM_WORKTREE_TRASH ?? join75(homedir46(), ".switchroom", "worktree-gc-trash"));
|
|
88580
88672
|
}
|
|
88581
88673
|
function planGc(roots, deps = {}) {
|
|
88582
|
-
const exists = deps.existsSync ??
|
|
88674
|
+
const exists = deps.existsSync ?? existsSync78;
|
|
88583
88675
|
const readDir = deps.readDir ?? ((p) => readdirSync26(p));
|
|
88584
|
-
const readFile4 = deps.readFile ?? ((p) =>
|
|
88585
|
-
const stat3 = deps.stat ?? ((p) =>
|
|
88676
|
+
const readFile4 = deps.readFile ?? ((p) => readFileSync66(p, "utf8"));
|
|
88677
|
+
const stat3 = deps.stat ?? ((p) => statSync34(p));
|
|
88586
88678
|
const exec = deps.exec ?? defaultExec;
|
|
88587
88679
|
const prSignal = deps.prSignal ?? ((repo, branch) => defaultPrSignal(repo, branch, exec));
|
|
88588
88680
|
const stamp = deps.dateStamp ?? "undated";
|
|
88589
|
-
const trash =
|
|
88681
|
+
const trash = join75(trashRoot(), stamp);
|
|
88590
88682
|
let claimed;
|
|
88591
88683
|
try {
|
|
88592
88684
|
claimed = new Set(listRecords().map((r) => resolve48(r.path)));
|
|
@@ -88622,10 +88714,10 @@ function planGc(roots, deps = {}) {
|
|
|
88622
88714
|
continue;
|
|
88623
88715
|
}
|
|
88624
88716
|
for (const name of entries) {
|
|
88625
|
-
const dir =
|
|
88717
|
+
const dir = join75(root, name);
|
|
88626
88718
|
if (isEphemeralPath(dir))
|
|
88627
88719
|
continue;
|
|
88628
|
-
const dotGit =
|
|
88720
|
+
const dotGit = join75(dir, ".git");
|
|
88629
88721
|
if (!exists(dotGit))
|
|
88630
88722
|
continue;
|
|
88631
88723
|
let st;
|
|
@@ -88654,7 +88746,7 @@ function planGc(roots, deps = {}) {
|
|
|
88654
88746
|
ownerRepos.add(repoRoot);
|
|
88655
88747
|
if (exists(ptr))
|
|
88656
88748
|
continue;
|
|
88657
|
-
orphans.push({ dir, owner: repoRoot, dest:
|
|
88749
|
+
orphans.push({ dir, owner: repoRoot, dest: join75(trash, name) });
|
|
88658
88750
|
}
|
|
88659
88751
|
}
|
|
88660
88752
|
const registered = [];
|
|
@@ -88709,7 +88801,7 @@ function planGc(roots, deps = {}) {
|
|
|
88709
88801
|
}
|
|
88710
88802
|
function applyGc(plan, deps = {}) {
|
|
88711
88803
|
const exec = deps.exec ?? defaultExec;
|
|
88712
|
-
const mkdirp = deps.mkdirp ?? ((p) => void
|
|
88804
|
+
const mkdirp = deps.mkdirp ?? ((p) => void mkdirSync40(p, { recursive: true }));
|
|
88713
88805
|
const move = deps.move ?? ((src, dest) => {
|
|
88714
88806
|
try {
|
|
88715
88807
|
renameSync17(src, dest);
|
|
@@ -88758,14 +88850,14 @@ function selectPurgeTargets(entries, olderThanDays) {
|
|
|
88758
88850
|
return entries.filter((e) => e.ageDays >= olderThanDays).map((e) => e.path);
|
|
88759
88851
|
}
|
|
88760
88852
|
function listTrashEntries(nowMs, deps = {}) {
|
|
88761
|
-
const exists = deps.existsSync ??
|
|
88853
|
+
const exists = deps.existsSync ?? existsSync78;
|
|
88762
88854
|
const readDir = deps.readDir ?? ((p) => readdirSync26(p));
|
|
88763
88855
|
const root = trashRoot();
|
|
88764
88856
|
if (!exists(root))
|
|
88765
88857
|
return [];
|
|
88766
88858
|
const out = [];
|
|
88767
88859
|
for (const stamp of readDir(root)) {
|
|
88768
|
-
const stampDir =
|
|
88860
|
+
const stampDir = join75(root, stamp);
|
|
88769
88861
|
let names;
|
|
88770
88862
|
try {
|
|
88771
88863
|
names = readDir(stampDir);
|
|
@@ -88773,10 +88865,10 @@ function listTrashEntries(nowMs, deps = {}) {
|
|
|
88773
88865
|
continue;
|
|
88774
88866
|
}
|
|
88775
88867
|
for (const name of names) {
|
|
88776
|
-
const p =
|
|
88868
|
+
const p = join75(stampDir, name);
|
|
88777
88869
|
let mtimeMs = nowMs;
|
|
88778
88870
|
try {
|
|
88779
|
-
mtimeMs =
|
|
88871
|
+
mtimeMs = statSync34(p).mtimeMs;
|
|
88780
88872
|
} catch {}
|
|
88781
88873
|
out.push({ path: p, ageDays: (nowMs - mtimeMs) / 86400000 });
|
|
88782
88874
|
}
|
|
@@ -88797,7 +88889,7 @@ function purgeTrash(paths) {
|
|
|
88797
88889
|
return { deleted, errors: errors2 };
|
|
88798
88890
|
}
|
|
88799
88891
|
function defaultRoots() {
|
|
88800
|
-
return [
|
|
88892
|
+
return [join75(homedir46(), "code")];
|
|
88801
88893
|
}
|
|
88802
88894
|
|
|
88803
88895
|
// src/cli/worktree.ts
|
|
@@ -88986,12 +89078,12 @@ init_drive();
|
|
|
88986
89078
|
init_scaffold_integration();
|
|
88987
89079
|
import {
|
|
88988
89080
|
chmodSync as chmodSync9,
|
|
88989
|
-
mkdirSync as
|
|
89081
|
+
mkdirSync as mkdirSync41,
|
|
88990
89082
|
readdirSync as readdirSync27,
|
|
88991
89083
|
rmSync as rmSync16,
|
|
88992
|
-
writeFileSync as
|
|
89084
|
+
writeFileSync as writeFileSync33
|
|
88993
89085
|
} from "node:fs";
|
|
88994
|
-
import { join as
|
|
89086
|
+
import { join as join76 } from "node:path";
|
|
88995
89087
|
function encodeCredentialsFilename(email) {
|
|
88996
89088
|
const SAFE = new Set([
|
|
88997
89089
|
..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
|
@@ -89181,17 +89273,17 @@ function resolveCredentialsDir(env2) {
|
|
|
89181
89273
|
if (explicit && explicit.length > 0)
|
|
89182
89274
|
return explicit;
|
|
89183
89275
|
const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
|
|
89184
|
-
return
|
|
89276
|
+
return join76(stateBase, "google-workspace-mcp", "credentials");
|
|
89185
89277
|
}
|
|
89186
89278
|
function writeSeedFile(dir, email, seed) {
|
|
89187
|
-
|
|
89279
|
+
mkdirSync41(dir, { recursive: true, mode: 448 });
|
|
89188
89280
|
chmodSync9(dir, 448);
|
|
89189
89281
|
for (const name of readdirSync27(dir)) {
|
|
89190
|
-
rmSync16(
|
|
89282
|
+
rmSync16(join76(dir, name), { force: true, recursive: true });
|
|
89191
89283
|
}
|
|
89192
89284
|
const filename = encodeCredentialsFilename(email);
|
|
89193
|
-
const filePath =
|
|
89194
|
-
|
|
89285
|
+
const filePath = join76(dir, filename);
|
|
89286
|
+
writeFileSync33(filePath, JSON.stringify(seed), { mode: 384 });
|
|
89195
89287
|
chmodSync9(filePath, 384);
|
|
89196
89288
|
return filePath;
|
|
89197
89289
|
}
|
|
@@ -89349,11 +89441,19 @@ function registerDriveMcpLauncherCommand(program3) {
|
|
|
89349
89441
|
// src/cli/m365-mcp-launcher.ts
|
|
89350
89442
|
init_scaffold_integration();
|
|
89351
89443
|
import { spawn as spawn5 } from "node:child_process";
|
|
89352
|
-
import { writeFileSync as
|
|
89353
|
-
import { dirname as
|
|
89444
|
+
import { writeFileSync as writeFileSync34, mkdirSync as mkdirSync42 } from "node:fs";
|
|
89445
|
+
import { dirname as dirname24, join as join77 } from "node:path";
|
|
89354
89446
|
var SOFTERIA_TOKEN_ENV = "MS365_MCP_OAUTH_TOKEN";
|
|
89355
89447
|
var DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
|
|
89356
89448
|
var MAX_REFRESH_INTERVAL_MS = 60 * 60 * 1000;
|
|
89449
|
+
var SOFTERIA_RESTART_BASE_MS = 1000;
|
|
89450
|
+
var SOFTERIA_RESTART_MAX_MS = 30 * 1000;
|
|
89451
|
+
var SOFTERIA_STABLE_MS = 60 * 1000;
|
|
89452
|
+
var SOFTERIA_MAX_RESTARTS = 6;
|
|
89453
|
+
function computeRestartBackoffMs(attempt) {
|
|
89454
|
+
const raw = SOFTERIA_RESTART_BASE_MS * 2 ** Math.max(0, attempt);
|
|
89455
|
+
return Math.min(raw, SOFTERIA_RESTART_MAX_MS);
|
|
89456
|
+
}
|
|
89357
89457
|
function buildSofteriaArgs(opts = {}) {
|
|
89358
89458
|
const pkg = `${MICROSOFT_WORKSPACE_MCP_PACKAGE}@${MICROSOFT_WORKSPACE_MCP_PINNED_VERSION}`;
|
|
89359
89459
|
const args = ["-y", pkg];
|
|
@@ -89375,14 +89475,14 @@ function computeRefreshDelayMs(expiresAt, now, leadMs = DEFAULT_REFRESH_LEAD_MS)
|
|
|
89375
89475
|
function writeRefreshHeartbeat(agentName, data) {
|
|
89376
89476
|
const path7 = heartbeatPath(agentName);
|
|
89377
89477
|
try {
|
|
89378
|
-
|
|
89379
|
-
|
|
89478
|
+
mkdirSync42(dirname24(path7), { recursive: true });
|
|
89479
|
+
writeFileSync34(path7, JSON.stringify(data, null, 2), { mode: 420 });
|
|
89380
89480
|
} catch {}
|
|
89381
89481
|
}
|
|
89382
89482
|
function heartbeatPath(agentName) {
|
|
89383
89483
|
const override = process.env.SWITCHROOM_M365_HEARTBEAT_DIR;
|
|
89384
89484
|
if (override) {
|
|
89385
|
-
return
|
|
89485
|
+
return join77(override, `m365-launcher-${agentName}.heartbeat.json`);
|
|
89386
89486
|
}
|
|
89387
89487
|
return "/state/agent/m365-launcher.heartbeat.json";
|
|
89388
89488
|
}
|
|
@@ -89441,13 +89541,23 @@ async function runMs365McpLauncher(opts, rt) {
|
|
|
89441
89541
|
let currentChild = null;
|
|
89442
89542
|
let teardownStdio = null;
|
|
89443
89543
|
let refreshTimer = null;
|
|
89544
|
+
let restartTimer = null;
|
|
89444
89545
|
let restartingForRefresh = false;
|
|
89546
|
+
let shuttingDown = false;
|
|
89445
89547
|
let resolveLauncher = null;
|
|
89548
|
+
let currentCreds = null;
|
|
89549
|
+
let restartAttempts = 0;
|
|
89550
|
+
let lastSpawnMs = 0;
|
|
89446
89551
|
const exitLauncher = (code) => {
|
|
89552
|
+
shuttingDown = true;
|
|
89447
89553
|
if (refreshTimer) {
|
|
89448
89554
|
clearTimer(refreshTimer);
|
|
89449
89555
|
refreshTimer = null;
|
|
89450
89556
|
}
|
|
89557
|
+
if (restartTimer) {
|
|
89558
|
+
clearTimer(restartTimer);
|
|
89559
|
+
restartTimer = null;
|
|
89560
|
+
}
|
|
89451
89561
|
if (resolveLauncher) {
|
|
89452
89562
|
const r = resolveLauncher;
|
|
89453
89563
|
resolveLauncher = null;
|
|
@@ -89457,6 +89567,7 @@ async function runMs365McpLauncher(opts, rt) {
|
|
|
89457
89567
|
const launchChild = (accessToken) => {
|
|
89458
89568
|
const env2 = buildSofteriaEnv(accessToken);
|
|
89459
89569
|
const child = rt.spawnSofteria(env2);
|
|
89570
|
+
lastSpawnMs = now();
|
|
89460
89571
|
teardownStdio = wireStdio(child);
|
|
89461
89572
|
child.once("exit", (code, signal) => {
|
|
89462
89573
|
if (teardownStdio) {
|
|
@@ -89467,6 +89578,35 @@ async function runMs365McpLauncher(opts, rt) {
|
|
|
89467
89578
|
return;
|
|
89468
89579
|
}
|
|
89469
89580
|
const resolved = code ?? (signal ? 128 : 0);
|
|
89581
|
+
if (shuttingDown)
|
|
89582
|
+
return;
|
|
89583
|
+
const crashed = resolved !== 0;
|
|
89584
|
+
const tokenValid = currentCreds != null && currentCreds.expiresAt > now();
|
|
89585
|
+
if (crashed && tokenValid) {
|
|
89586
|
+
if (now() - lastSpawnMs >= SOFTERIA_STABLE_MS)
|
|
89587
|
+
restartAttempts = 0;
|
|
89588
|
+
if (restartAttempts < SOFTERIA_MAX_RESTARTS) {
|
|
89589
|
+
const delayMs = computeRestartBackoffMs(restartAttempts);
|
|
89590
|
+
restartAttempts += 1;
|
|
89591
|
+
currentChild = null;
|
|
89592
|
+
log(`m365-launcher: softeria exited (code=${resolved} signal=${signal}); re-spawning with cached token in ${Math.round(delayMs / 1000)}s (attempt ${restartAttempts}/${SOFTERIA_MAX_RESTARTS}, no broker call)`);
|
|
89593
|
+
restartTimer = setTimer(() => {
|
|
89594
|
+
restartTimer = null;
|
|
89595
|
+
if (shuttingDown)
|
|
89596
|
+
return;
|
|
89597
|
+
if (currentCreds != null && currentCreds.expiresAt > now()) {
|
|
89598
|
+
currentChild = launchChild(currentCreds.accessToken);
|
|
89599
|
+
} else {
|
|
89600
|
+
log("m365-launcher: cached token expired during backoff \u2014 exiting for fresh creds");
|
|
89601
|
+
exitLauncher(resolved);
|
|
89602
|
+
}
|
|
89603
|
+
}, delayMs);
|
|
89604
|
+
return;
|
|
89605
|
+
}
|
|
89606
|
+
log(`m365-launcher: softeria crash-looped ${restartAttempts}x within ${Math.round(SOFTERIA_STABLE_MS / 1000)}s \u2014 giving up, exiting`);
|
|
89607
|
+
exitLauncher(resolved);
|
|
89608
|
+
return;
|
|
89609
|
+
}
|
|
89470
89610
|
log(`m365-launcher: softeria exited unexpectedly code=${resolved} signal=${signal}`);
|
|
89471
89611
|
exitLauncher(resolved);
|
|
89472
89612
|
});
|
|
@@ -89484,6 +89624,10 @@ async function runMs365McpLauncher(opts, rt) {
|
|
|
89484
89624
|
refreshTimer = setTimer(async () => {
|
|
89485
89625
|
try {
|
|
89486
89626
|
log("m365-launcher: refreshing token + restarting softeria");
|
|
89627
|
+
if (restartTimer) {
|
|
89628
|
+
clearTimer(restartTimer);
|
|
89629
|
+
restartTimer = null;
|
|
89630
|
+
}
|
|
89487
89631
|
restartingForRefresh = true;
|
|
89488
89632
|
try {
|
|
89489
89633
|
process.stdin.pause();
|
|
@@ -89493,6 +89637,8 @@ async function runMs365McpLauncher(opts, rt) {
|
|
|
89493
89637
|
await killChild(currentChild);
|
|
89494
89638
|
}
|
|
89495
89639
|
restartingForRefresh = false;
|
|
89640
|
+
currentCreds = fresh;
|
|
89641
|
+
restartAttempts = 0;
|
|
89496
89642
|
currentChild = launchChild(fresh.accessToken);
|
|
89497
89643
|
try {
|
|
89498
89644
|
process.stdin.resume();
|
|
@@ -89519,6 +89665,7 @@ async function runMs365McpLauncher(opts, rt) {
|
|
|
89519
89665
|
log(`m365-launcher: initial broker call failed \u2014 ${msg}`);
|
|
89520
89666
|
return 1;
|
|
89521
89667
|
}
|
|
89668
|
+
currentCreds = initial;
|
|
89522
89669
|
currentChild = launchChild(initial.accessToken);
|
|
89523
89670
|
scheduleRefresh(initial.expiresAt);
|
|
89524
89671
|
const onSignal = (signal) => {
|
|
@@ -89567,8 +89714,8 @@ function registerM365McpLauncherCommand(program3) {
|
|
|
89567
89714
|
// src/cli/notion-mcp-launcher.ts
|
|
89568
89715
|
init_scaffold_integration();
|
|
89569
89716
|
import { spawn as spawn6 } from "node:child_process";
|
|
89570
|
-
import { existsSync as
|
|
89571
|
-
import { dirname as
|
|
89717
|
+
import { existsSync as existsSync79, mkdirSync as mkdirSync43, writeFileSync as writeFileSync35 } from "node:fs";
|
|
89718
|
+
import { dirname as dirname25 } from "node:path";
|
|
89572
89719
|
var HEARTBEAT_WRITE_INTERVAL_MS = 30 * 1000;
|
|
89573
89720
|
var DEFAULT_HEARTBEAT_PATH = "/state/agent/notion-launcher.heartbeat.json";
|
|
89574
89721
|
var DEFAULT_VAULT_KEY = "notion/integration-token";
|
|
@@ -89578,10 +89725,10 @@ function buildNotionMcpArgs(opts) {
|
|
|
89578
89725
|
}
|
|
89579
89726
|
function defaultWriteHeartbeat(path7, contents) {
|
|
89580
89727
|
try {
|
|
89581
|
-
const dir =
|
|
89582
|
-
if (!
|
|
89583
|
-
|
|
89584
|
-
|
|
89728
|
+
const dir = dirname25(path7);
|
|
89729
|
+
if (!existsSync79(dir))
|
|
89730
|
+
mkdirSync43(dir, { recursive: true });
|
|
89731
|
+
writeFileSync35(path7, contents);
|
|
89585
89732
|
} catch {}
|
|
89586
89733
|
}
|
|
89587
89734
|
async function runNotionMcpLauncher(opts, runtime) {
|
|
@@ -89691,7 +89838,7 @@ function registerNotionMcpLauncherCommand(program3) {
|
|
|
89691
89838
|
|
|
89692
89839
|
// src/cli/deliver-file.ts
|
|
89693
89840
|
init_client2();
|
|
89694
|
-
import { readFileSync as
|
|
89841
|
+
import { readFileSync as readFileSync67, statSync as statSync35 } from "node:fs";
|
|
89695
89842
|
import { basename as basename11 } from "node:path";
|
|
89696
89843
|
|
|
89697
89844
|
// src/delivery/onedrive.ts
|
|
@@ -90030,8 +90177,8 @@ async function defaultResolveProvider() {
|
|
|
90030
90177
|
}
|
|
90031
90178
|
async function runDeliverFile(localPath, deps = {}) {
|
|
90032
90179
|
const agentName = safeAgentName(deps.agentName ?? process.env.SWITCHROOM_AGENT_NAME);
|
|
90033
|
-
const sizeOf = deps.fileSize ?? ((p) =>
|
|
90034
|
-
const read = deps.readFile ?? ((p) => new Uint8Array(
|
|
90180
|
+
const sizeOf = deps.fileSize ?? ((p) => statSync35(p).size);
|
|
90181
|
+
const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync67(p)));
|
|
90035
90182
|
const resolveProvider = deps.resolveProvider ?? defaultResolveProvider;
|
|
90036
90183
|
let size;
|
|
90037
90184
|
try {
|
|
@@ -90352,9 +90499,9 @@ function runRedactStdin() {
|
|
|
90352
90499
|
}
|
|
90353
90500
|
|
|
90354
90501
|
// src/cli/status-ask.ts
|
|
90355
|
-
import { readFileSync as
|
|
90356
|
-
import { join as
|
|
90357
|
-
import { homedir as
|
|
90502
|
+
import { readFileSync as readFileSync71, existsSync as existsSync84, readdirSync as readdirSync29 } from "node:fs";
|
|
90503
|
+
import { join as join82 } from "node:path";
|
|
90504
|
+
import { homedir as homedir49 } from "node:os";
|
|
90358
90505
|
|
|
90359
90506
|
// src/status-ask/report.ts
|
|
90360
90507
|
function parseJsonl(content) {
|
|
@@ -90628,7 +90775,7 @@ function runReport(opts) {
|
|
|
90628
90775
|
for (const src of sources) {
|
|
90629
90776
|
let content;
|
|
90630
90777
|
try {
|
|
90631
|
-
content =
|
|
90778
|
+
content = readFileSync71(src.path, "utf-8");
|
|
90632
90779
|
} catch (err) {
|
|
90633
90780
|
process.stderr.write(`status-ask report: cannot read ${src.path}: ${err instanceof Error ? err.message : String(err)}
|
|
90634
90781
|
`);
|
|
@@ -90675,7 +90822,7 @@ function runReport(opts) {
|
|
|
90675
90822
|
function resolveSources(explicitPath) {
|
|
90676
90823
|
if (explicitPath != null && explicitPath.trim() !== "") {
|
|
90677
90824
|
const trimmed = explicitPath.trim();
|
|
90678
|
-
if (!
|
|
90825
|
+
if (!existsSync84(trimmed)) {
|
|
90679
90826
|
process.stderr.write(`status-ask report: ${trimmed}: file not found
|
|
90680
90827
|
`);
|
|
90681
90828
|
process.exit(1);
|
|
@@ -90689,9 +90836,9 @@ function resolveSources(explicitPath) {
|
|
|
90689
90836
|
const config = loadConfig();
|
|
90690
90837
|
agentsDir = resolveAgentsDir(config);
|
|
90691
90838
|
} catch {
|
|
90692
|
-
agentsDir =
|
|
90839
|
+
agentsDir = join82(homedir49(), ".switchroom", "agents");
|
|
90693
90840
|
}
|
|
90694
|
-
if (!
|
|
90841
|
+
if (!existsSync84(agentsDir))
|
|
90695
90842
|
return [];
|
|
90696
90843
|
const sources = [];
|
|
90697
90844
|
let entries;
|
|
@@ -90701,8 +90848,8 @@ function resolveSources(explicitPath) {
|
|
|
90701
90848
|
return [];
|
|
90702
90849
|
}
|
|
90703
90850
|
for (const name of entries) {
|
|
90704
|
-
const path8 =
|
|
90705
|
-
if (
|
|
90851
|
+
const path8 = join82(agentsDir, name, "runtime-metrics.jsonl");
|
|
90852
|
+
if (existsSync84(path8)) {
|
|
90706
90853
|
sources.push({ path: path8, agent: name });
|
|
90707
90854
|
}
|
|
90708
90855
|
}
|
|
@@ -90732,45 +90879,45 @@ var import_yaml21 = __toESM(require_dist(), 1);
|
|
|
90732
90879
|
init_paths();
|
|
90733
90880
|
import {
|
|
90734
90881
|
closeSync as closeSync14,
|
|
90735
|
-
existsSync as
|
|
90882
|
+
existsSync as existsSync85,
|
|
90736
90883
|
fsyncSync as fsyncSync8,
|
|
90737
|
-
mkdirSync as
|
|
90884
|
+
mkdirSync as mkdirSync48,
|
|
90738
90885
|
openSync as openSync14,
|
|
90739
90886
|
readdirSync as readdirSync30,
|
|
90740
|
-
readFileSync as
|
|
90887
|
+
readFileSync as readFileSync72,
|
|
90741
90888
|
renameSync as renameSync19,
|
|
90742
|
-
statSync as
|
|
90743
|
-
unlinkSync as
|
|
90889
|
+
statSync as statSync36,
|
|
90890
|
+
unlinkSync as unlinkSync18,
|
|
90744
90891
|
writeSync as writeSync10
|
|
90745
90892
|
} from "node:fs";
|
|
90746
|
-
import { join as
|
|
90893
|
+
import { join as join83, resolve as resolve51 } from "node:path";
|
|
90747
90894
|
var STAGING_SUBDIR = ".staging";
|
|
90748
90895
|
function overlayPathsFor(agent, opts = {}) {
|
|
90749
90896
|
const base = opts.root ? resolve51(opts.root, agent) : resolve51(resolveDualPath(`~/.switchroom/agents/${agent}`));
|
|
90750
|
-
const scheduleDir =
|
|
90751
|
-
const scheduleStagingDir =
|
|
90752
|
-
const skillsDir =
|
|
90753
|
-
const skillsStagingDir =
|
|
90897
|
+
const scheduleDir = join83(base, "schedule.d");
|
|
90898
|
+
const scheduleStagingDir = join83(scheduleDir, STAGING_SUBDIR);
|
|
90899
|
+
const skillsDir = join83(base, "skills.d");
|
|
90900
|
+
const skillsStagingDir = join83(skillsDir, STAGING_SUBDIR);
|
|
90754
90901
|
return {
|
|
90755
90902
|
agentRoot: base,
|
|
90756
90903
|
scheduleDir,
|
|
90757
90904
|
scheduleStagingDir,
|
|
90758
90905
|
skillsDir,
|
|
90759
90906
|
skillsStagingDir,
|
|
90760
|
-
lockPath:
|
|
90907
|
+
lockPath: join83(base, ".lock"),
|
|
90761
90908
|
stagingDir: scheduleStagingDir
|
|
90762
90909
|
};
|
|
90763
90910
|
}
|
|
90764
90911
|
function ensureDirs(paths) {
|
|
90765
|
-
|
|
90766
|
-
|
|
90912
|
+
mkdirSync48(paths.scheduleDir, { recursive: true });
|
|
90913
|
+
mkdirSync48(paths.scheduleStagingDir, { recursive: true });
|
|
90767
90914
|
}
|
|
90768
90915
|
function ensureSkillsDirs(paths) {
|
|
90769
|
-
|
|
90770
|
-
|
|
90916
|
+
mkdirSync48(paths.skillsDir, { recursive: true });
|
|
90917
|
+
mkdirSync48(paths.skillsStagingDir, { recursive: true });
|
|
90771
90918
|
}
|
|
90772
90919
|
function withAgentLock(paths, fn) {
|
|
90773
|
-
|
|
90920
|
+
mkdirSync48(paths.agentRoot, { recursive: true });
|
|
90774
90921
|
const start = Date.now();
|
|
90775
90922
|
const TIMEOUT_MS = 5000;
|
|
90776
90923
|
let fd = null;
|
|
@@ -90783,9 +90930,9 @@ function withAgentLock(paths, fn) {
|
|
|
90783
90930
|
if (e.code !== "EEXIST")
|
|
90784
90931
|
throw err;
|
|
90785
90932
|
try {
|
|
90786
|
-
const age = Date.now() -
|
|
90933
|
+
const age = Date.now() - statSync36(paths.lockPath).mtimeMs;
|
|
90787
90934
|
if (age > 30000) {
|
|
90788
|
-
|
|
90935
|
+
unlinkSync18(paths.lockPath);
|
|
90789
90936
|
continue;
|
|
90790
90937
|
}
|
|
90791
90938
|
} catch {}
|
|
@@ -90803,7 +90950,7 @@ function withAgentLock(paths, fn) {
|
|
|
90803
90950
|
closeSync14(fd);
|
|
90804
90951
|
} catch {}
|
|
90805
90952
|
try {
|
|
90806
|
-
|
|
90953
|
+
unlinkSync18(paths.lockPath);
|
|
90807
90954
|
} catch {}
|
|
90808
90955
|
}
|
|
90809
90956
|
}
|
|
@@ -90811,8 +90958,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
90811
90958
|
const paths = overlayPathsFor(agent, opts);
|
|
90812
90959
|
return withAgentLock(paths, () => {
|
|
90813
90960
|
ensureDirs(paths);
|
|
90814
|
-
const stagingPath =
|
|
90815
|
-
const finalPath =
|
|
90961
|
+
const stagingPath = join83(paths.scheduleStagingDir, `${slug}.yaml`);
|
|
90962
|
+
const finalPath = join83(paths.scheduleDir, `${slug}.yaml`);
|
|
90816
90963
|
const fd = openSync14(stagingPath, "w", 384);
|
|
90817
90964
|
try {
|
|
90818
90965
|
writeSync10(fd, yamlText);
|
|
@@ -90828,8 +90975,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
90828
90975
|
const paths = overlayPathsFor(agent, opts);
|
|
90829
90976
|
return withAgentLock(paths, () => {
|
|
90830
90977
|
ensureSkillsDirs(paths);
|
|
90831
|
-
const stagingPath =
|
|
90832
|
-
const finalPath =
|
|
90978
|
+
const stagingPath = join83(paths.skillsStagingDir, `${slug}.yaml`);
|
|
90979
|
+
const finalPath = join83(paths.skillsDir, `${slug}.yaml`);
|
|
90833
90980
|
const fd = openSync14(stagingPath, "w", 384);
|
|
90834
90981
|
try {
|
|
90835
90982
|
writeSync10(fd, yamlText);
|
|
@@ -90844,24 +90991,24 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
90844
90991
|
function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
90845
90992
|
const paths = overlayPathsFor(agent, opts);
|
|
90846
90993
|
return withAgentLock(paths, () => {
|
|
90847
|
-
const finalPath =
|
|
90848
|
-
if (!
|
|
90994
|
+
const finalPath = join83(paths.skillsDir, `${slug}.yaml`);
|
|
90995
|
+
if (!existsSync85(finalPath))
|
|
90849
90996
|
return false;
|
|
90850
|
-
|
|
90997
|
+
unlinkSync18(finalPath);
|
|
90851
90998
|
return true;
|
|
90852
90999
|
});
|
|
90853
91000
|
}
|
|
90854
91001
|
function listSkillsOverlayEntries(agent, opts = {}) {
|
|
90855
91002
|
const paths = overlayPathsFor(agent, opts);
|
|
90856
|
-
if (!
|
|
91003
|
+
if (!existsSync85(paths.skillsDir))
|
|
90857
91004
|
return [];
|
|
90858
91005
|
const out = [];
|
|
90859
91006
|
for (const name of readdirSync30(paths.skillsDir)) {
|
|
90860
91007
|
if (!/\.ya?ml$/i.test(name))
|
|
90861
91008
|
continue;
|
|
90862
|
-
const full =
|
|
91009
|
+
const full = join83(paths.skillsDir, name);
|
|
90863
91010
|
try {
|
|
90864
|
-
const raw =
|
|
91011
|
+
const raw = readFileSync72(full, "utf-8");
|
|
90865
91012
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
90866
91013
|
out.push({ slug, path: full, raw });
|
|
90867
91014
|
} catch {}
|
|
@@ -90871,24 +91018,24 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
90871
91018
|
function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
90872
91019
|
const paths = overlayPathsFor(agent, opts);
|
|
90873
91020
|
return withAgentLock(paths, () => {
|
|
90874
|
-
const finalPath =
|
|
90875
|
-
if (!
|
|
91021
|
+
const finalPath = join83(paths.scheduleDir, `${slug}.yaml`);
|
|
91022
|
+
if (!existsSync85(finalPath))
|
|
90876
91023
|
return false;
|
|
90877
|
-
|
|
91024
|
+
unlinkSync18(finalPath);
|
|
90878
91025
|
return true;
|
|
90879
91026
|
});
|
|
90880
91027
|
}
|
|
90881
91028
|
function listOverlayEntries(agent, opts = {}) {
|
|
90882
91029
|
const paths = overlayPathsFor(agent, opts);
|
|
90883
|
-
if (!
|
|
91030
|
+
if (!existsSync85(paths.scheduleDir))
|
|
90884
91031
|
return [];
|
|
90885
91032
|
const out = [];
|
|
90886
91033
|
for (const name of readdirSync30(paths.scheduleDir)) {
|
|
90887
91034
|
if (!/\.ya?ml$/i.test(name))
|
|
90888
91035
|
continue;
|
|
90889
|
-
const full =
|
|
91036
|
+
const full = join83(paths.scheduleDir, name);
|
|
90890
91037
|
try {
|
|
90891
|
-
const raw =
|
|
91038
|
+
const raw = readFileSync72(full, "utf-8");
|
|
90892
91039
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
90893
91040
|
out.push({ slug, path: full, raw });
|
|
90894
91041
|
} catch {}
|
|
@@ -91039,27 +91186,27 @@ function reconcileAgentCronOnly(agent) {
|
|
|
91039
91186
|
// src/cli/agent-config-pending.ts
|
|
91040
91187
|
import {
|
|
91041
91188
|
closeSync as closeSync15,
|
|
91042
|
-
existsSync as
|
|
91189
|
+
existsSync as existsSync86,
|
|
91043
91190
|
fsyncSync as fsyncSync9,
|
|
91044
|
-
mkdirSync as
|
|
91191
|
+
mkdirSync as mkdirSync49,
|
|
91045
91192
|
openSync as openSync15,
|
|
91046
91193
|
readdirSync as readdirSync31,
|
|
91047
|
-
readFileSync as
|
|
91194
|
+
readFileSync as readFileSync73,
|
|
91048
91195
|
renameSync as renameSync20,
|
|
91049
|
-
unlinkSync as
|
|
91050
|
-
writeFileSync as
|
|
91196
|
+
unlinkSync as unlinkSync19,
|
|
91197
|
+
writeFileSync as writeFileSync40,
|
|
91051
91198
|
writeSync as writeSync11
|
|
91052
91199
|
} from "node:fs";
|
|
91053
|
-
import { join as
|
|
91200
|
+
import { join as join84 } from "node:path";
|
|
91054
91201
|
import { randomBytes as randomBytes15 } from "node:crypto";
|
|
91055
91202
|
var STAGE_ID_PREFIX = "cap_";
|
|
91056
91203
|
function pendingDir(agent, opts = {}) {
|
|
91057
91204
|
const paths = overlayPathsFor(agent, opts);
|
|
91058
|
-
return
|
|
91205
|
+
return join84(paths.scheduleDir, ".pending");
|
|
91059
91206
|
}
|
|
91060
91207
|
function ensurePendingDir(agent, opts = {}) {
|
|
91061
91208
|
const dir = pendingDir(agent, opts);
|
|
91062
|
-
|
|
91209
|
+
mkdirSync49(dir, { recursive: true });
|
|
91063
91210
|
return dir;
|
|
91064
91211
|
}
|
|
91065
91212
|
function newStageId() {
|
|
@@ -91068,8 +91215,8 @@ function newStageId() {
|
|
|
91068
91215
|
function stagePendingScheduleEntry(opts) {
|
|
91069
91216
|
const dir = ensurePendingDir(opts.agent, { root: opts.root });
|
|
91070
91217
|
const stageId = opts.stageId ?? newStageId();
|
|
91071
|
-
const yamlPath =
|
|
91072
|
-
const metaPath =
|
|
91218
|
+
const yamlPath = join84(dir, `${stageId}.yaml`);
|
|
91219
|
+
const metaPath = join84(dir, `${stageId}.meta.json`);
|
|
91073
91220
|
const meta = {
|
|
91074
91221
|
v: 1,
|
|
91075
91222
|
stage_id: stageId,
|
|
@@ -91090,25 +91237,25 @@ function stagePendingScheduleEntry(opts) {
|
|
|
91090
91237
|
}
|
|
91091
91238
|
renameSync20(yamlTmp, yamlPath);
|
|
91092
91239
|
}
|
|
91093
|
-
|
|
91240
|
+
writeFileSync40(metaPath, JSON.stringify(meta, null, 2) + `
|
|
91094
91241
|
`, { mode: 384 });
|
|
91095
91242
|
return { stageId, yamlPath, metaPath };
|
|
91096
91243
|
}
|
|
91097
91244
|
function listPendingScheduleEntries(agent, opts = {}) {
|
|
91098
91245
|
const dir = pendingDir(agent, opts);
|
|
91099
|
-
if (!
|
|
91246
|
+
if (!existsSync86(dir))
|
|
91100
91247
|
return [];
|
|
91101
91248
|
const out = [];
|
|
91102
91249
|
for (const name of readdirSync31(dir).sort()) {
|
|
91103
91250
|
if (!name.endsWith(".meta.json"))
|
|
91104
91251
|
continue;
|
|
91105
91252
|
const stageId = name.slice(0, -".meta.json".length);
|
|
91106
|
-
const metaPath =
|
|
91107
|
-
const yamlPath =
|
|
91108
|
-
if (!
|
|
91253
|
+
const metaPath = join84(dir, name);
|
|
91254
|
+
const yamlPath = join84(dir, `${stageId}.yaml`);
|
|
91255
|
+
if (!existsSync86(yamlPath))
|
|
91109
91256
|
continue;
|
|
91110
91257
|
try {
|
|
91111
|
-
const meta = JSON.parse(
|
|
91258
|
+
const meta = JSON.parse(readFileSync73(metaPath, "utf-8"));
|
|
91112
91259
|
if (meta?.v !== 1 || typeof meta.stage_id !== "string")
|
|
91113
91260
|
continue;
|
|
91114
91261
|
out.push({ stageId: meta.stage_id, agent: meta.agent, yamlPath, metaPath, meta });
|
|
@@ -91123,12 +91270,12 @@ function commitPendingScheduleEntry(opts) {
|
|
|
91123
91270
|
return { committed: false, reason: "not_found" };
|
|
91124
91271
|
const slug = match.meta.entry.name ?? match.stageId;
|
|
91125
91272
|
const paths = overlayPathsFor(opts.agent, { root: opts.root });
|
|
91126
|
-
const finalPath =
|
|
91127
|
-
if (
|
|
91273
|
+
const finalPath = join84(paths.scheduleDir, `${slug}.yaml`);
|
|
91274
|
+
if (existsSync86(finalPath)) {
|
|
91128
91275
|
return { committed: false, reason: "slug_collision" };
|
|
91129
91276
|
}
|
|
91130
91277
|
renameSync20(match.yamlPath, finalPath);
|
|
91131
|
-
|
|
91278
|
+
unlinkSync19(match.metaPath);
|
|
91132
91279
|
return { committed: true, path: finalPath, slug };
|
|
91133
91280
|
}
|
|
91134
91281
|
function denyPendingScheduleEntry(opts) {
|
|
@@ -91137,16 +91284,16 @@ function denyPendingScheduleEntry(opts) {
|
|
|
91137
91284
|
if (!match)
|
|
91138
91285
|
return { denied: false, reason: "not_found" };
|
|
91139
91286
|
try {
|
|
91140
|
-
|
|
91287
|
+
unlinkSync19(match.yamlPath);
|
|
91141
91288
|
} catch {}
|
|
91142
91289
|
try {
|
|
91143
|
-
|
|
91290
|
+
unlinkSync19(match.metaPath);
|
|
91144
91291
|
} catch {}
|
|
91145
91292
|
return { denied: true };
|
|
91146
91293
|
}
|
|
91147
91294
|
|
|
91148
91295
|
// src/cli/agent-config-write.ts
|
|
91149
|
-
import { existsSync as
|
|
91296
|
+
import { existsSync as existsSync87, readFileSync as readFileSync74 } from "node:fs";
|
|
91150
91297
|
import { execFileSync as execFileSync26 } from "node:child_process";
|
|
91151
91298
|
|
|
91152
91299
|
// src/scheduler/schedule-report.ts
|
|
@@ -91558,8 +91705,8 @@ function scheduleRemove(opts) {
|
|
|
91558
91705
|
}
|
|
91559
91706
|
let priorContent = null;
|
|
91560
91707
|
try {
|
|
91561
|
-
if (
|
|
91562
|
-
priorContent =
|
|
91708
|
+
if (existsSync87(match.path))
|
|
91709
|
+
priorContent = readFileSync74(match.path, "utf-8");
|
|
91563
91710
|
} catch {}
|
|
91564
91711
|
deleteOverlayEntry(agent, match.slug, { root: opts.root });
|
|
91565
91712
|
const reconcileFn = opts.reconcile === undefined ? opts.root ? null : reconcileAgentCronOnly : opts.reconcile;
|
|
@@ -91762,7 +91909,7 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
91762
91909
|
}
|
|
91763
91910
|
let blob;
|
|
91764
91911
|
if (opts.jsonl) {
|
|
91765
|
-
blob =
|
|
91912
|
+
blob = existsSync87(opts.jsonl) ? readFileSync74(opts.jsonl, "utf-8") : "";
|
|
91766
91913
|
} else {
|
|
91767
91914
|
try {
|
|
91768
91915
|
blob = execFileSync26("docker", ["exec", `switchroom-${agent}`, "cat", "/state/agent/scheduler.jsonl"], {
|
|
@@ -91794,11 +91941,11 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
91794
91941
|
|
|
91795
91942
|
// src/cli/agent-config-skill-write.ts
|
|
91796
91943
|
var import_yaml22 = __toESM(require_dist(), 1);
|
|
91797
|
-
import { existsSync as
|
|
91944
|
+
import { existsSync as existsSync88 } from "node:fs";
|
|
91798
91945
|
init_reconcile_default_skills();
|
|
91799
91946
|
init_agent_config();
|
|
91800
91947
|
var import_yaml23 = __toESM(require_dist(), 1);
|
|
91801
|
-
import { join as
|
|
91948
|
+
import { join as join85 } from "node:path";
|
|
91802
91949
|
var MAX_SKILLS_PER_AGENT = 20;
|
|
91803
91950
|
var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
|
|
91804
91951
|
function exitCodeFor2(code) {
|
|
@@ -91873,8 +92020,8 @@ function skillInstall(opts) {
|
|
|
91873
92020
|
return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
|
|
91874
92021
|
}
|
|
91875
92022
|
const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
|
|
91876
|
-
const skillPath =
|
|
91877
|
-
if (!
|
|
92023
|
+
const skillPath = join85(poolDir, skillName);
|
|
92024
|
+
if (!existsSync88(skillPath)) {
|
|
91878
92025
|
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.`);
|
|
91879
92026
|
}
|
|
91880
92027
|
const yamlText = import_yaml22.stringify({ skills: [skillName] });
|
|
@@ -92038,21 +92185,21 @@ function registerAgentConfigSkillWriteCommands(program3) {
|
|
|
92038
92185
|
// src/cli/skill.ts
|
|
92039
92186
|
import {
|
|
92040
92187
|
closeSync as closeSync16,
|
|
92041
|
-
existsSync as
|
|
92188
|
+
existsSync as existsSync89,
|
|
92042
92189
|
lstatSync as lstatSync9,
|
|
92043
|
-
mkdirSync as
|
|
92190
|
+
mkdirSync as mkdirSync50,
|
|
92044
92191
|
mkdtempSync as mkdtempSync5,
|
|
92045
92192
|
openSync as openSync16,
|
|
92046
|
-
readFileSync as
|
|
92193
|
+
readFileSync as readFileSync75,
|
|
92047
92194
|
readdirSync as readdirSync32,
|
|
92048
92195
|
realpathSync as realpathSync7,
|
|
92049
92196
|
renameSync as renameSync21,
|
|
92050
92197
|
rmSync as rmSync18,
|
|
92051
|
-
statSync as
|
|
92052
|
-
writeFileSync as
|
|
92198
|
+
statSync as statSync37,
|
|
92199
|
+
writeFileSync as writeFileSync41
|
|
92053
92200
|
} from "node:fs";
|
|
92054
|
-
import { tmpdir as tmpdir5, homedir as
|
|
92055
|
-
import { dirname as
|
|
92201
|
+
import { tmpdir as tmpdir5, homedir as homedir50 } from "node:os";
|
|
92202
|
+
import { dirname as dirname30, join as join86, relative as relative2, resolve as resolve52 } from "node:path";
|
|
92056
92203
|
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
92057
92204
|
|
|
92058
92205
|
// src/cli/skill-common.ts
|
|
@@ -92286,10 +92433,10 @@ function scanForClaudeP2(content) {
|
|
|
92286
92433
|
function resolveSkillsPoolDir2(override) {
|
|
92287
92434
|
const raw = override ?? "~/.switchroom/skills";
|
|
92288
92435
|
if (raw.startsWith("~/")) {
|
|
92289
|
-
return
|
|
92436
|
+
return join86(homedir50(), raw.slice(2));
|
|
92290
92437
|
}
|
|
92291
92438
|
if (raw === "~")
|
|
92292
|
-
return
|
|
92439
|
+
return homedir50();
|
|
92293
92440
|
return resolve52(raw);
|
|
92294
92441
|
}
|
|
92295
92442
|
function readStdinSync() {
|
|
@@ -92318,14 +92465,14 @@ function isTarballPath(p) {
|
|
|
92318
92465
|
}
|
|
92319
92466
|
function loadFromDir(dir) {
|
|
92320
92467
|
const abs = realpathSync7(dir);
|
|
92321
|
-
if (!
|
|
92468
|
+
if (!statSync37(abs).isDirectory()) {
|
|
92322
92469
|
fail3(`--from path is not a directory: ${dir}`);
|
|
92323
92470
|
}
|
|
92324
92471
|
const files = {};
|
|
92325
92472
|
const walk2 = (sub) => {
|
|
92326
92473
|
const entries = readdirSync32(sub, { withFileTypes: true });
|
|
92327
92474
|
for (const ent of entries) {
|
|
92328
|
-
const full =
|
|
92475
|
+
const full = join86(sub, ent.name);
|
|
92329
92476
|
const rel = relative2(abs, full);
|
|
92330
92477
|
if (ent.isSymbolicLink()) {
|
|
92331
92478
|
fail3(`refusing to read symlink inside --from dir: ${rel}`);
|
|
@@ -92335,7 +92482,7 @@ function loadFromDir(dir) {
|
|
|
92335
92482
|
continue;
|
|
92336
92483
|
}
|
|
92337
92484
|
if (ent.isFile()) {
|
|
92338
|
-
const buf =
|
|
92485
|
+
const buf = readFileSync75(full);
|
|
92339
92486
|
files[rel.replace(/\\/g, "/")] = buf.toString("utf-8");
|
|
92340
92487
|
}
|
|
92341
92488
|
}
|
|
@@ -92360,7 +92507,7 @@ function loadFromTarball(tarPath) {
|
|
|
92360
92507
|
fail3(`tarball contains disallowed path: ${JSON.stringify(entry)} \u2014 ` + `refusing to extract before any file is written`);
|
|
92361
92508
|
}
|
|
92362
92509
|
}
|
|
92363
|
-
const staging = mkdtempSync5(
|
|
92510
|
+
const staging = mkdtempSync5(join86(tmpdir5(), "skill-apply-extract-"));
|
|
92364
92511
|
try {
|
|
92365
92512
|
const flags = isGz ? ["-xzf"] : ["-xf"];
|
|
92366
92513
|
const r = spawnSync15("tar", [
|
|
@@ -92384,7 +92531,7 @@ function loadFromTarball(tarPath) {
|
|
|
92384
92531
|
}
|
|
92385
92532
|
}
|
|
92386
92533
|
function loadSingleFile(filePath) {
|
|
92387
|
-
const content =
|
|
92534
|
+
const content = readFileSync75(filePath, "utf-8");
|
|
92388
92535
|
return { "SKILL.md": content };
|
|
92389
92536
|
}
|
|
92390
92537
|
function loadFromStdin() {
|
|
@@ -92446,10 +92593,10 @@ function validatePayload(name, files) {
|
|
|
92446
92593
|
errors2.push(`${path8} fails \`bash -n\` syntax check: ${(r.stderr ?? "").trim()}`);
|
|
92447
92594
|
}
|
|
92448
92595
|
} else if (PY_SCRIPT_RE2.test(path8)) {
|
|
92449
|
-
const tmp = mkdtempSync5(
|
|
92450
|
-
const tmpPy =
|
|
92596
|
+
const tmp = mkdtempSync5(join86(tmpdir5(), "skill-apply-py-"));
|
|
92597
|
+
const tmpPy = join86(tmp, "check.py");
|
|
92451
92598
|
try {
|
|
92452
|
-
|
|
92599
|
+
writeFileSync41(tmpPy, content);
|
|
92453
92600
|
const r = spawnSync15("python3", ["-m", "py_compile", tmpPy], {
|
|
92454
92601
|
encoding: "utf-8"
|
|
92455
92602
|
});
|
|
@@ -92467,15 +92614,15 @@ function validatePayload(name, files) {
|
|
|
92467
92614
|
function diffSummary(currentDir, files) {
|
|
92468
92615
|
const lines = [];
|
|
92469
92616
|
const currentFiles = {};
|
|
92470
|
-
if (
|
|
92617
|
+
if (existsSync89(currentDir)) {
|
|
92471
92618
|
const walk2 = (sub) => {
|
|
92472
92619
|
for (const ent of readdirSync32(sub, { withFileTypes: true })) {
|
|
92473
|
-
const full =
|
|
92620
|
+
const full = join86(sub, ent.name);
|
|
92474
92621
|
const rel = relative2(currentDir, full);
|
|
92475
92622
|
if (ent.isDirectory()) {
|
|
92476
92623
|
walk2(full);
|
|
92477
92624
|
} else if (ent.isFile()) {
|
|
92478
|
-
currentFiles[rel.replace(/\\/g, "/")] =
|
|
92625
|
+
currentFiles[rel.replace(/\\/g, "/")] = readFileSync75(full, "utf-8");
|
|
92479
92626
|
}
|
|
92480
92627
|
}
|
|
92481
92628
|
};
|
|
@@ -92503,10 +92650,10 @@ function diffSummary(currentDir, files) {
|
|
|
92503
92650
|
`);
|
|
92504
92651
|
}
|
|
92505
92652
|
function writePayload(poolDir, name, files) {
|
|
92506
|
-
if (!
|
|
92507
|
-
|
|
92653
|
+
if (!existsSync89(poolDir)) {
|
|
92654
|
+
mkdirSync50(poolDir, { recursive: true, mode: 493 });
|
|
92508
92655
|
}
|
|
92509
|
-
const target =
|
|
92656
|
+
const target = join86(poolDir, name);
|
|
92510
92657
|
let targetIsSymlink = false;
|
|
92511
92658
|
try {
|
|
92512
92659
|
const st = lstatSync9(target);
|
|
@@ -92517,15 +92664,15 @@ function writePayload(poolDir, name, files) {
|
|
|
92517
92664
|
if (targetIsSymlink) {
|
|
92518
92665
|
fail3(`refusing to overwrite symlink at ${target}; investigate manually`);
|
|
92519
92666
|
}
|
|
92520
|
-
const staging = mkdtempSync5(
|
|
92667
|
+
const staging = mkdtempSync5(join86(poolDir, `.skill-apply-stage-${name}-`));
|
|
92521
92668
|
let oldRename = null;
|
|
92522
92669
|
try {
|
|
92523
92670
|
for (const [path8, content] of Object.entries(files)) {
|
|
92524
|
-
const full =
|
|
92525
|
-
|
|
92671
|
+
const full = join86(staging, path8);
|
|
92672
|
+
mkdirSync50(dirname30(full), { recursive: true, mode: 493 });
|
|
92526
92673
|
const fd = openSync16(full, "wx");
|
|
92527
92674
|
try {
|
|
92528
|
-
|
|
92675
|
+
writeFileSync41(fd, content);
|
|
92529
92676
|
} finally {
|
|
92530
92677
|
closeSync16(fd);
|
|
92531
92678
|
}
|
|
@@ -92552,9 +92699,9 @@ function writePayload(poolDir, name, files) {
|
|
|
92552
92699
|
try {
|
|
92553
92700
|
rmSync18(staging, { recursive: true, force: true });
|
|
92554
92701
|
} catch {}
|
|
92555
|
-
if (oldRename &&
|
|
92702
|
+
if (oldRename && existsSync89(oldRename)) {
|
|
92556
92703
|
try {
|
|
92557
|
-
if (
|
|
92704
|
+
if (existsSync89(target)) {
|
|
92558
92705
|
rmSync18(target, { recursive: true, force: true });
|
|
92559
92706
|
}
|
|
92560
92707
|
renameSync21(oldRename, target);
|
|
@@ -92575,10 +92722,10 @@ function registerSkillCommand(program3) {
|
|
|
92575
92722
|
files = loadFromStdin();
|
|
92576
92723
|
} else {
|
|
92577
92724
|
const fromPath = resolve52(opts.from);
|
|
92578
|
-
if (!
|
|
92725
|
+
if (!existsSync89(fromPath)) {
|
|
92579
92726
|
fail3(`--from path does not exist: ${opts.from}`);
|
|
92580
92727
|
}
|
|
92581
|
-
const st =
|
|
92728
|
+
const st = statSync37(fromPath);
|
|
92582
92729
|
if (st.isDirectory()) {
|
|
92583
92730
|
files = loadFromDir(fromPath);
|
|
92584
92731
|
} else if (isTarballPath(fromPath)) {
|
|
@@ -92599,7 +92746,7 @@ function registerSkillCommand(program3) {
|
|
|
92599
92746
|
}
|
|
92600
92747
|
const config = loadConfig();
|
|
92601
92748
|
const poolDir = resolveSkillsPoolDir2(config.switchroom?.skills_dir);
|
|
92602
|
-
const currentDir =
|
|
92749
|
+
const currentDir = join86(poolDir, name);
|
|
92603
92750
|
console.log(source_default.bold(`Skill: ${name}`) + source_default.gray(` (${Object.keys(files).length} files, ${sumBytes(files)} bytes)`));
|
|
92604
92751
|
console.log(source_default.bold("Diff vs current pool content:"));
|
|
92605
92752
|
console.log(diffSummary(currentDir, files));
|
|
@@ -92631,21 +92778,21 @@ function sumBytes(files) {
|
|
|
92631
92778
|
init_esm();
|
|
92632
92779
|
import {
|
|
92633
92780
|
closeSync as closeSync17,
|
|
92634
|
-
existsSync as
|
|
92781
|
+
existsSync as existsSync90,
|
|
92635
92782
|
lstatSync as lstatSync10,
|
|
92636
|
-
mkdirSync as
|
|
92783
|
+
mkdirSync as mkdirSync51,
|
|
92637
92784
|
mkdtempSync as mkdtempSync6,
|
|
92638
92785
|
openSync as openSync17,
|
|
92639
|
-
readFileSync as
|
|
92786
|
+
readFileSync as readFileSync76,
|
|
92640
92787
|
readdirSync as readdirSync33,
|
|
92641
92788
|
renameSync as renameSync22,
|
|
92642
92789
|
rmSync as rmSync19,
|
|
92643
|
-
statSync as
|
|
92790
|
+
statSync as statSync38,
|
|
92644
92791
|
utimesSync,
|
|
92645
|
-
writeFileSync as
|
|
92792
|
+
writeFileSync as writeFileSync42
|
|
92646
92793
|
} from "node:fs";
|
|
92647
|
-
import { dirname as
|
|
92648
|
-
import { homedir as
|
|
92794
|
+
import { dirname as dirname31, join as join87, relative as relative3, resolve as resolve53 } from "node:path";
|
|
92795
|
+
import { homedir as homedir51, tmpdir as tmpdir6 } from "node:os";
|
|
92649
92796
|
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
92650
92797
|
init_helpers();
|
|
92651
92798
|
init_agent_config();
|
|
@@ -92656,15 +92803,15 @@ var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
|
|
|
92656
92803
|
var PERSONAL_SKILLS_SUBPATH = "personal-skills";
|
|
92657
92804
|
function resolveConfigSkillsDir(agent) {
|
|
92658
92805
|
const override = process.env.SWITCHROOM_CONFIG_DIR;
|
|
92659
|
-
const candidate = override ? resolve53(override) :
|
|
92660
|
-
if (!
|
|
92806
|
+
const candidate = override ? resolve53(override) : join87(homedir51(), ".switchroom-config");
|
|
92807
|
+
if (!existsSync90(candidate))
|
|
92661
92808
|
return null;
|
|
92662
|
-
return
|
|
92809
|
+
return join87(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
|
|
92663
92810
|
}
|
|
92664
92811
|
var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
|
|
92665
92812
|
function sweepMirrorPriors(configSkillsRoot) {
|
|
92666
92813
|
try {
|
|
92667
|
-
if (!
|
|
92814
|
+
if (!existsSync90(configSkillsRoot))
|
|
92668
92815
|
return;
|
|
92669
92816
|
const now = Date.now();
|
|
92670
92817
|
for (const ent of readdirSync33(configSkillsRoot)) {
|
|
@@ -92677,7 +92824,7 @@ function sweepMirrorPriors(configSkillsRoot) {
|
|
|
92677
92824
|
if (now - ts < MIRROR_PRIOR_TTL_MS)
|
|
92678
92825
|
continue;
|
|
92679
92826
|
try {
|
|
92680
|
-
rmSync19(
|
|
92827
|
+
rmSync19(join87(configSkillsRoot, ent), { recursive: true, force: true });
|
|
92681
92828
|
} catch {}
|
|
92682
92829
|
}
|
|
92683
92830
|
} catch {}
|
|
@@ -92686,7 +92833,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
92686
92833
|
const configSkillsRoot = resolveConfigSkillsDir(agent);
|
|
92687
92834
|
if (!configSkillsRoot)
|
|
92688
92835
|
return;
|
|
92689
|
-
const dest =
|
|
92836
|
+
const dest = join87(configSkillsRoot, name);
|
|
92690
92837
|
try {
|
|
92691
92838
|
if (liveSkillDir !== null) {
|
|
92692
92839
|
try {
|
|
@@ -92700,32 +92847,32 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
92700
92847
|
}
|
|
92701
92848
|
if (liveSkillDir === null) {
|
|
92702
92849
|
sweepMirrorPriors(configSkillsRoot);
|
|
92703
|
-
if (
|
|
92704
|
-
const trash =
|
|
92850
|
+
if (existsSync90(dest)) {
|
|
92851
|
+
const trash = join87(configSkillsRoot, `.${name}-trash-${Date.now()}`);
|
|
92705
92852
|
renameSync22(dest, trash);
|
|
92706
92853
|
}
|
|
92707
92854
|
return;
|
|
92708
92855
|
}
|
|
92709
|
-
|
|
92856
|
+
mkdirSync51(configSkillsRoot, { recursive: true, mode: 493 });
|
|
92710
92857
|
sweepMirrorPriors(configSkillsRoot);
|
|
92711
|
-
const staging = mkdtempSync6(
|
|
92858
|
+
const staging = mkdtempSync6(join87(configSkillsRoot, `.${name}-staging-`));
|
|
92712
92859
|
const walk2 = (src, dst) => {
|
|
92713
|
-
|
|
92860
|
+
mkdirSync51(dst, { recursive: true, mode: 493 });
|
|
92714
92861
|
for (const ent of readdirSync33(src, { withFileTypes: true })) {
|
|
92715
|
-
const s =
|
|
92716
|
-
const d =
|
|
92862
|
+
const s = join87(src, ent.name);
|
|
92863
|
+
const d = join87(dst, ent.name);
|
|
92717
92864
|
if (ent.isSymbolicLink())
|
|
92718
92865
|
continue;
|
|
92719
92866
|
if (ent.isDirectory())
|
|
92720
92867
|
walk2(s, d);
|
|
92721
92868
|
else if (ent.isFile()) {
|
|
92722
|
-
|
|
92869
|
+
writeFileSync42(d, readFileSync76(s));
|
|
92723
92870
|
}
|
|
92724
92871
|
}
|
|
92725
92872
|
};
|
|
92726
92873
|
walk2(liveSkillDir, staging);
|
|
92727
|
-
if (
|
|
92728
|
-
const prior =
|
|
92874
|
+
if (existsSync90(dest)) {
|
|
92875
|
+
const prior = join87(configSkillsRoot, `.${name}-prior-${Date.now()}`);
|
|
92729
92876
|
renameSync22(dest, prior);
|
|
92730
92877
|
}
|
|
92731
92878
|
renameSync22(staging, dest);
|
|
@@ -92752,17 +92899,17 @@ function resolveAgent(opts) {
|
|
|
92752
92899
|
function resolveAgentsRoot(opts) {
|
|
92753
92900
|
if (opts.root)
|
|
92754
92901
|
return resolve53(opts.root);
|
|
92755
|
-
return
|
|
92902
|
+
return join87(homedir51(), ".switchroom", "agents");
|
|
92756
92903
|
}
|
|
92757
92904
|
function personalSkillDir(agentsRoot, agent, name) {
|
|
92758
|
-
return
|
|
92905
|
+
return join87(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
|
|
92759
92906
|
}
|
|
92760
92907
|
function trashDir(agentsRoot, agent) {
|
|
92761
|
-
return
|
|
92908
|
+
return join87(agentsRoot, agent, ".claude", TRASH_DIRNAME);
|
|
92762
92909
|
}
|
|
92763
92910
|
function countPersonalSkills(agentsRoot, agent) {
|
|
92764
|
-
const skillsDir =
|
|
92765
|
-
if (!
|
|
92911
|
+
const skillsDir = join87(agentsRoot, agent, ".claude", "skills");
|
|
92912
|
+
if (!existsSync90(skillsDir))
|
|
92766
92913
|
return 0;
|
|
92767
92914
|
let n = 0;
|
|
92768
92915
|
for (const ent of readdirSync33(skillsDir, { withFileTypes: true })) {
|
|
@@ -92793,13 +92940,13 @@ function readStdinSync2() {
|
|
|
92793
92940
|
}
|
|
92794
92941
|
function loadFromDir2(dir) {
|
|
92795
92942
|
const abs = resolve53(dir);
|
|
92796
|
-
if (!
|
|
92943
|
+
if (!statSync38(abs).isDirectory()) {
|
|
92797
92944
|
fail4(`--from path is not a directory: ${dir}`);
|
|
92798
92945
|
}
|
|
92799
92946
|
const files = {};
|
|
92800
92947
|
const walk2 = (sub) => {
|
|
92801
92948
|
for (const ent of readdirSync33(sub, { withFileTypes: true })) {
|
|
92802
|
-
const full =
|
|
92949
|
+
const full = join87(sub, ent.name);
|
|
92803
92950
|
if (ent.isSymbolicLink()) {
|
|
92804
92951
|
fail4(`refusing to read symlink in --from dir: ${relative3(abs, full)}`);
|
|
92805
92952
|
}
|
|
@@ -92809,7 +92956,7 @@ function loadFromDir2(dir) {
|
|
|
92809
92956
|
}
|
|
92810
92957
|
if (ent.isFile()) {
|
|
92811
92958
|
const rel = relative3(abs, full).replace(/\\/g, "/");
|
|
92812
|
-
files[rel] =
|
|
92959
|
+
files[rel] = readFileSync76(full, "utf-8");
|
|
92813
92960
|
}
|
|
92814
92961
|
}
|
|
92815
92962
|
};
|
|
@@ -92852,10 +92999,10 @@ function behavioralValidate(files) {
|
|
|
92852
92999
|
errors2.push(`${path8} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
|
|
92853
93000
|
}
|
|
92854
93001
|
} else if (PY_SCRIPT_RE.test(path8)) {
|
|
92855
|
-
const tmp = mkdtempSync6(
|
|
92856
|
-
const tmpPy =
|
|
93002
|
+
const tmp = mkdtempSync6(join87(tmpdir6(), "skill-personal-py-"));
|
|
93003
|
+
const tmpPy = join87(tmp, "check.py");
|
|
92857
93004
|
try {
|
|
92858
|
-
|
|
93005
|
+
writeFileSync42(tmpPy, content);
|
|
92859
93006
|
const r = spawnSync16("python3", ["-m", "py_compile", tmpPy], {
|
|
92860
93007
|
encoding: "utf-8"
|
|
92861
93008
|
});
|
|
@@ -92871,15 +93018,15 @@ function behavioralValidate(files) {
|
|
|
92871
93018
|
}
|
|
92872
93019
|
function sweepTrash(agentsRoot, agent) {
|
|
92873
93020
|
const trash = trashDir(agentsRoot, agent);
|
|
92874
|
-
if (!
|
|
93021
|
+
if (!existsSync90(trash))
|
|
92875
93022
|
return;
|
|
92876
93023
|
const now = Date.now();
|
|
92877
93024
|
for (const ent of readdirSync33(trash, { withFileTypes: true })) {
|
|
92878
93025
|
if (!ent.isDirectory())
|
|
92879
93026
|
continue;
|
|
92880
|
-
const entPath =
|
|
93027
|
+
const entPath = join87(trash, ent.name);
|
|
92881
93028
|
try {
|
|
92882
|
-
const st =
|
|
93029
|
+
const st = statSync38(entPath);
|
|
92883
93030
|
if (now - st.mtimeMs > TRASH_TTL_MS) {
|
|
92884
93031
|
rmSync19(entPath, { recursive: true, force: true });
|
|
92885
93032
|
}
|
|
@@ -92897,16 +93044,16 @@ function writePersonalSkill(targetDir, files) {
|
|
|
92897
93044
|
if (targetIsSymlink) {
|
|
92898
93045
|
fail4(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
|
|
92899
93046
|
}
|
|
92900
|
-
|
|
92901
|
-
const staging = mkdtempSync6(
|
|
93047
|
+
mkdirSync51(dirname31(targetDir), { recursive: true, mode: 493 });
|
|
93048
|
+
const staging = mkdtempSync6(join87(dirname31(targetDir), `.skill-personal-stage-`));
|
|
92902
93049
|
let oldRename = null;
|
|
92903
93050
|
try {
|
|
92904
93051
|
for (const [path8, content] of Object.entries(files)) {
|
|
92905
|
-
const full =
|
|
92906
|
-
|
|
93052
|
+
const full = join87(staging, path8);
|
|
93053
|
+
mkdirSync51(dirname31(full), { recursive: true, mode: 493 });
|
|
92907
93054
|
const fd = openSync17(full, "wx");
|
|
92908
93055
|
try {
|
|
92909
|
-
|
|
93056
|
+
writeFileSync42(fd, content);
|
|
92910
93057
|
} finally {
|
|
92911
93058
|
closeSync17(fd);
|
|
92912
93059
|
}
|
|
@@ -92933,9 +93080,9 @@ function writePersonalSkill(targetDir, files) {
|
|
|
92933
93080
|
try {
|
|
92934
93081
|
rmSync19(staging, { recursive: true, force: true });
|
|
92935
93082
|
} catch {}
|
|
92936
|
-
if (oldRename &&
|
|
93083
|
+
if (oldRename && existsSync90(oldRename)) {
|
|
92937
93084
|
try {
|
|
92938
|
-
if (
|
|
93085
|
+
if (existsSync90(targetDir)) {
|
|
92939
93086
|
rmSync19(targetDir, { recursive: true, force: true });
|
|
92940
93087
|
}
|
|
92941
93088
|
renameSync22(oldRename, targetDir);
|
|
@@ -93001,15 +93148,15 @@ function loadFiles(opts) {
|
|
|
93001
93148
|
return loadFromStdin2();
|
|
93002
93149
|
}
|
|
93003
93150
|
const p = resolve53(opts.from);
|
|
93004
|
-
if (!
|
|
93151
|
+
if (!existsSync90(p)) {
|
|
93005
93152
|
fail4(`--from path does not exist: ${opts.from}`);
|
|
93006
93153
|
}
|
|
93007
|
-
const st =
|
|
93154
|
+
const st = statSync38(p);
|
|
93008
93155
|
if (st.isDirectory()) {
|
|
93009
93156
|
return loadFromDir2(p);
|
|
93010
93157
|
}
|
|
93011
93158
|
if (p.endsWith(".md")) {
|
|
93012
|
-
return { "SKILL.md":
|
|
93159
|
+
return { "SKILL.md": readFileSync76(p, "utf-8") };
|
|
93013
93160
|
}
|
|
93014
93161
|
fail4(`--from must be a directory or a .md file. Got: ${opts.from}`);
|
|
93015
93162
|
}
|
|
@@ -93049,10 +93196,10 @@ function editPersonalAction(name, opts) {
|
|
|
93049
93196
|
}
|
|
93050
93197
|
var CLONE_SOURCE_RE = /^(shared|bundled):([a-z0-9][a-z0-9_-]{0,62})$/;
|
|
93051
93198
|
function defaultSharedRoot() {
|
|
93052
|
-
return
|
|
93199
|
+
return join87(homedir51(), ".switchroom", "skills");
|
|
93053
93200
|
}
|
|
93054
93201
|
function defaultBundledRoot() {
|
|
93055
|
-
return
|
|
93202
|
+
return join87(homedir51(), ".switchroom", "skills", "_bundled");
|
|
93056
93203
|
}
|
|
93057
93204
|
function resolveCloneSource(source, opts) {
|
|
93058
93205
|
const m = CLONE_SOURCE_RE.exec(source);
|
|
@@ -93062,8 +93209,8 @@ function resolveCloneSource(source, opts) {
|
|
|
93062
93209
|
const tier = m[1];
|
|
93063
93210
|
const slug = m[2];
|
|
93064
93211
|
const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
|
|
93065
|
-
const dir =
|
|
93066
|
-
if (!
|
|
93212
|
+
const dir = join87(root, slug);
|
|
93213
|
+
if (!existsSync90(dir)) {
|
|
93067
93214
|
fail4(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
|
|
93068
93215
|
}
|
|
93069
93216
|
const st = lstatSync10(dir);
|
|
@@ -93078,7 +93225,7 @@ function readSourceFiles(dir) {
|
|
|
93078
93225
|
const skipped = [];
|
|
93079
93226
|
const walk2 = (sub) => {
|
|
93080
93227
|
for (const ent of readdirSync33(sub, { withFileTypes: true })) {
|
|
93081
|
-
const full =
|
|
93228
|
+
const full = join87(sub, ent.name);
|
|
93082
93229
|
if (ent.isSymbolicLink()) {
|
|
93083
93230
|
continue;
|
|
93084
93231
|
}
|
|
@@ -93098,7 +93245,7 @@ function readSourceFiles(dir) {
|
|
|
93098
93245
|
fail4(`clone source has oversized file ${rel} (${st.size} bytes > ${CLONE_MAX_FILE_BYTES}); ` + `refuse to read`, 3);
|
|
93099
93246
|
}
|
|
93100
93247
|
} catch {}
|
|
93101
|
-
files[rel] =
|
|
93248
|
+
files[rel] = readFileSync76(full, "utf-8");
|
|
93102
93249
|
}
|
|
93103
93250
|
}
|
|
93104
93251
|
};
|
|
@@ -93187,9 +93334,9 @@ function removePersonalAction(name, opts) {
|
|
|
93187
93334
|
throw err2;
|
|
93188
93335
|
}
|
|
93189
93336
|
const trashRoot2 = trashDir(agentsRoot, agent);
|
|
93190
|
-
|
|
93337
|
+
mkdirSync51(trashRoot2, { recursive: true, mode: 493 });
|
|
93191
93338
|
const ts = Date.now();
|
|
93192
|
-
const trashTarget =
|
|
93339
|
+
const trashTarget = join87(trashRoot2, `${name}-${ts}`);
|
|
93193
93340
|
renameSync22(target, trashTarget);
|
|
93194
93341
|
const now = new Date(ts);
|
|
93195
93342
|
utimesSync(trashTarget, now, now);
|
|
@@ -93208,16 +93355,16 @@ function listPersonalAction(opts) {
|
|
|
93208
93355
|
const agent = resolveAgent(opts);
|
|
93209
93356
|
const agentsRoot = resolveAgentsRoot(opts);
|
|
93210
93357
|
sweepTrash(agentsRoot, agent);
|
|
93211
|
-
const skillsDir =
|
|
93358
|
+
const skillsDir = join87(agentsRoot, agent, ".claude", "skills");
|
|
93212
93359
|
const personal = [];
|
|
93213
|
-
if (
|
|
93360
|
+
if (existsSync90(skillsDir)) {
|
|
93214
93361
|
for (const ent of readdirSync33(skillsDir, { withFileTypes: true })) {
|
|
93215
93362
|
if (!ent.isDirectory())
|
|
93216
93363
|
continue;
|
|
93217
93364
|
if (!ent.name.startsWith(PERSONAL_PREFIX))
|
|
93218
93365
|
continue;
|
|
93219
93366
|
const skillName = ent.name.slice(PERSONAL_PREFIX.length);
|
|
93220
|
-
const skillPath =
|
|
93367
|
+
const skillPath = join87(skillsDir, ent.name);
|
|
93221
93368
|
let fileCount = 0;
|
|
93222
93369
|
let totalBytes = 0;
|
|
93223
93370
|
const walk2 = (sub) => {
|
|
@@ -93225,10 +93372,10 @@ function listPersonalAction(opts) {
|
|
|
93225
93372
|
if (e.isFile()) {
|
|
93226
93373
|
fileCount += 1;
|
|
93227
93374
|
try {
|
|
93228
|
-
totalBytes +=
|
|
93375
|
+
totalBytes += statSync38(join87(sub, e.name)).size;
|
|
93229
93376
|
} catch {}
|
|
93230
93377
|
} else if (e.isDirectory()) {
|
|
93231
|
-
walk2(
|
|
93378
|
+
walk2(join87(sub, e.name));
|
|
93232
93379
|
}
|
|
93233
93380
|
}
|
|
93234
93381
|
};
|
|
@@ -93266,12 +93413,12 @@ function registerSkillPersonalCommands(program3) {
|
|
|
93266
93413
|
|
|
93267
93414
|
// src/cli/self-improve-propose-skill.ts
|
|
93268
93415
|
import { createConnection as createConnection4 } from "node:net";
|
|
93269
|
-
import { homedir as
|
|
93270
|
-
import { join as
|
|
93271
|
-
import { readFileSync as
|
|
93416
|
+
import { homedir as homedir52 } from "node:os";
|
|
93417
|
+
import { join as join88 } from "node:path";
|
|
93418
|
+
import { readFileSync as readFileSync77 } from "node:fs";
|
|
93272
93419
|
var IPC_CONNECT_TIMEOUT_MS = 5000;
|
|
93273
93420
|
function gatewaySocketPath() {
|
|
93274
|
-
return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ?
|
|
93421
|
+
return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ? join88(process.env.TELEGRAM_STATE_DIR, "gateway.sock") : join88(homedir52(), ".claude", "channels", "telegram", "gateway.sock"));
|
|
93275
93422
|
}
|
|
93276
93423
|
function fail5(msg, code = 1) {
|
|
93277
93424
|
console.error(msg);
|
|
@@ -93306,7 +93453,7 @@ function registerSelfImproveProposeSkillCommand(program3) {
|
|
|
93306
93453
|
fail5("agent name required (--agent or $SWITCHROOM_AGENT_NAME)");
|
|
93307
93454
|
let draft;
|
|
93308
93455
|
try {
|
|
93309
|
-
draft = JSON.parse(
|
|
93456
|
+
draft = JSON.parse(readFileSync77(opts.draft, "utf-8"));
|
|
93310
93457
|
} catch (e) {
|
|
93311
93458
|
fail5(`failed to read/parse --draft: ${e.message}`);
|
|
93312
93459
|
}
|
|
@@ -93343,28 +93490,28 @@ function registerSelfImproveProposeSkillCommand(program3) {
|
|
|
93343
93490
|
init_esm();
|
|
93344
93491
|
init_helpers();
|
|
93345
93492
|
var import_yaml25 = __toESM(require_dist(), 1);
|
|
93346
|
-
import { existsSync as
|
|
93347
|
-
import { homedir as
|
|
93348
|
-
import { join as
|
|
93493
|
+
import { existsSync as existsSync91, readdirSync as readdirSync34, readFileSync as readFileSync78, statSync as statSync39 } from "node:fs";
|
|
93494
|
+
import { homedir as homedir53 } from "node:os";
|
|
93495
|
+
import { join as join89, resolve as resolve54 } from "node:path";
|
|
93349
93496
|
var PERSONAL_PREFIX2 = "personal-";
|
|
93350
93497
|
var BUNDLED_SUBDIR = "_bundled";
|
|
93351
93498
|
var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
|
|
93352
93499
|
function defaultAgentsRoot() {
|
|
93353
|
-
return resolve54(
|
|
93500
|
+
return resolve54(homedir53(), ".switchroom/agents");
|
|
93354
93501
|
}
|
|
93355
93502
|
function defaultSharedRoot2() {
|
|
93356
|
-
return resolve54(
|
|
93503
|
+
return resolve54(homedir53(), ".switchroom/skills");
|
|
93357
93504
|
}
|
|
93358
93505
|
function defaultBundledRoot2() {
|
|
93359
|
-
return resolve54(
|
|
93506
|
+
return resolve54(homedir53(), ".switchroom/skills/_bundled");
|
|
93360
93507
|
}
|
|
93361
93508
|
function readSkillFrontmatter(skillDir) {
|
|
93362
|
-
const mdPath =
|
|
93363
|
-
if (!
|
|
93509
|
+
const mdPath = join89(skillDir, "SKILL.md");
|
|
93510
|
+
if (!existsSync91(mdPath))
|
|
93364
93511
|
return null;
|
|
93365
93512
|
let content;
|
|
93366
93513
|
try {
|
|
93367
|
-
content =
|
|
93514
|
+
content = readFileSync78(mdPath, "utf-8");
|
|
93368
93515
|
} catch {
|
|
93369
93516
|
return null;
|
|
93370
93517
|
}
|
|
@@ -93392,9 +93539,9 @@ function readSkillFrontmatter(skillDir) {
|
|
|
93392
93539
|
return { fm: parsed };
|
|
93393
93540
|
}
|
|
93394
93541
|
function statSkillMd(skillDir) {
|
|
93395
|
-
const mdPath =
|
|
93542
|
+
const mdPath = join89(skillDir, "SKILL.md");
|
|
93396
93543
|
try {
|
|
93397
|
-
const st =
|
|
93544
|
+
const st = statSync39(mdPath);
|
|
93398
93545
|
return { size: st.size, mtime: st.mtime.toISOString() };
|
|
93399
93546
|
} catch {
|
|
93400
93547
|
return null;
|
|
@@ -93403,8 +93550,8 @@ function statSkillMd(skillDir) {
|
|
|
93403
93550
|
function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
93404
93551
|
if (!AGENT_NAME_RE3.test(agent))
|
|
93405
93552
|
return [];
|
|
93406
|
-
const skillsDir =
|
|
93407
|
-
if (!
|
|
93553
|
+
const skillsDir = join89(agentsRoot, agent, ".claude/skills");
|
|
93554
|
+
if (!existsSync91(skillsDir))
|
|
93408
93555
|
return [];
|
|
93409
93556
|
const out = [];
|
|
93410
93557
|
let entries;
|
|
@@ -93416,9 +93563,9 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
93416
93563
|
for (const ent of entries) {
|
|
93417
93564
|
if (!ent.startsWith(PERSONAL_PREFIX2))
|
|
93418
93565
|
continue;
|
|
93419
|
-
const dirPath =
|
|
93566
|
+
const dirPath = join89(skillsDir, ent);
|
|
93420
93567
|
try {
|
|
93421
|
-
if (!
|
|
93568
|
+
if (!statSync39(dirPath).isDirectory())
|
|
93422
93569
|
continue;
|
|
93423
93570
|
} catch {
|
|
93424
93571
|
continue;
|
|
@@ -93442,7 +93589,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
93442
93589
|
return out;
|
|
93443
93590
|
}
|
|
93444
93591
|
function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
93445
|
-
if (!
|
|
93592
|
+
if (!existsSync91(sharedRoot))
|
|
93446
93593
|
return [];
|
|
93447
93594
|
const out = [];
|
|
93448
93595
|
let entries;
|
|
@@ -93456,9 +93603,9 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
|
93456
93603
|
continue;
|
|
93457
93604
|
if (ent.startsWith("."))
|
|
93458
93605
|
continue;
|
|
93459
|
-
const dirPath =
|
|
93606
|
+
const dirPath = join89(sharedRoot, ent);
|
|
93460
93607
|
try {
|
|
93461
|
-
if (!
|
|
93608
|
+
if (!statSync39(dirPath).isDirectory())
|
|
93462
93609
|
continue;
|
|
93463
93610
|
} catch {
|
|
93464
93611
|
continue;
|
|
@@ -93480,7 +93627,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
|
93480
93627
|
return out;
|
|
93481
93628
|
}
|
|
93482
93629
|
function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
93483
|
-
if (!
|
|
93630
|
+
if (!existsSync91(bundledRoot))
|
|
93484
93631
|
return [];
|
|
93485
93632
|
const out = [];
|
|
93486
93633
|
let entries;
|
|
@@ -93492,9 +93639,9 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
|
93492
93639
|
for (const ent of entries) {
|
|
93493
93640
|
if (ent.startsWith("."))
|
|
93494
93641
|
continue;
|
|
93495
|
-
const dirPath =
|
|
93642
|
+
const dirPath = join89(bundledRoot, ent);
|
|
93496
93643
|
try {
|
|
93497
|
-
if (!
|
|
93644
|
+
if (!statSync39(dirPath).isDirectory())
|
|
93498
93645
|
continue;
|
|
93499
93646
|
} catch {
|
|
93500
93647
|
continue;
|
|
@@ -93638,18 +93785,18 @@ init_source();
|
|
|
93638
93785
|
init_helpers();
|
|
93639
93786
|
init_operator_uid();
|
|
93640
93787
|
import {
|
|
93641
|
-
existsSync as
|
|
93642
|
-
mkdirSync as
|
|
93788
|
+
existsSync as existsSync93,
|
|
93789
|
+
mkdirSync as mkdirSync52,
|
|
93643
93790
|
readdirSync as readdirSync35,
|
|
93644
|
-
readFileSync as
|
|
93645
|
-
writeFileSync as
|
|
93646
|
-
statSync as
|
|
93791
|
+
readFileSync as readFileSync80,
|
|
93792
|
+
writeFileSync as writeFileSync43,
|
|
93793
|
+
statSync as statSync40,
|
|
93647
93794
|
lstatSync as lstatSync11,
|
|
93648
93795
|
realpathSync as realpathSync8,
|
|
93649
93796
|
copyFileSync as copyFileSync13
|
|
93650
93797
|
} from "node:fs";
|
|
93651
|
-
import { homedir as
|
|
93652
|
-
import { join as
|
|
93798
|
+
import { homedir as homedir54 } from "node:os";
|
|
93799
|
+
import { join as join90 } from "node:path";
|
|
93653
93800
|
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
93654
93801
|
|
|
93655
93802
|
// src/cli/deploy-version-guard.ts
|
|
@@ -93871,7 +94018,7 @@ networks:
|
|
|
93871
94018
|
# operator surface; the daemon's stderr lands in \`docker logs switchroom-hostd\`.
|
|
93872
94019
|
`;
|
|
93873
94020
|
}
|
|
93874
|
-
function resolveHostdHostHome(env2 = process.env, home2 =
|
|
94021
|
+
function resolveHostdHostHome(env2 = process.env, home2 = homedir54()) {
|
|
93875
94022
|
const fromEnv = env2.SWITCHROOM_HOST_HOME?.trim();
|
|
93876
94023
|
const resolved = fromEnv && fromEnv.length > 0 ? fromEnv : home2;
|
|
93877
94024
|
if (resolved === "/host-home" || resolved.startsWith("/host-home/")) {
|
|
@@ -93882,7 +94029,7 @@ function resolveHostdHostHome(env2 = process.env, home2 = homedir53()) {
|
|
|
93882
94029
|
return resolved;
|
|
93883
94030
|
}
|
|
93884
94031
|
function resolveHostdSkillsTarget(hostHome) {
|
|
93885
|
-
const skillsPath =
|
|
94032
|
+
const skillsPath = join90(hostHome, ".switchroom", "skills");
|
|
93886
94033
|
let st;
|
|
93887
94034
|
try {
|
|
93888
94035
|
st = lstatSync11(skillsPath);
|
|
@@ -93899,21 +94046,21 @@ function resolveHostdSkillsTarget(hostHome) {
|
|
|
93899
94046
|
console.warn(`switchroom hostd install: ~/.switchroom/skills is a symlink whose target ` + `does not resolve (dangling) \u2014 skipping the skills bind mount. Bundled ` + `skills will be unavailable to rollout/update until the symlink is fixed.`);
|
|
93900
94047
|
return;
|
|
93901
94048
|
}
|
|
93902
|
-
if (!
|
|
94049
|
+
if (!existsSync93(target)) {
|
|
93903
94050
|
console.warn(`switchroom hostd install: ~/.switchroom/skills resolves to "${target}", ` + `which does not exist \u2014 skipping the skills bind mount. Bundled skills ` + `will be unavailable to rollout/update until the symlink target exists.`);
|
|
93904
94051
|
return;
|
|
93905
94052
|
}
|
|
93906
94053
|
return target;
|
|
93907
94054
|
}
|
|
93908
94055
|
function hostdDir() {
|
|
93909
|
-
return
|
|
94056
|
+
return join90(homedir54(), ".switchroom", "hostd");
|
|
93910
94057
|
}
|
|
93911
94058
|
function hostdComposePath() {
|
|
93912
|
-
return
|
|
94059
|
+
return join90(hostdDir(), "docker-compose.yml");
|
|
93913
94060
|
}
|
|
93914
94061
|
function backupExistingCompose() {
|
|
93915
94062
|
const p = hostdComposePath();
|
|
93916
|
-
if (!
|
|
94063
|
+
if (!existsSync93(p))
|
|
93917
94064
|
return null;
|
|
93918
94065
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
93919
94066
|
const bak = `${p}.bak-${ts}`;
|
|
@@ -93946,7 +94093,7 @@ async function doInstall(opts, program3) {
|
|
|
93946
94093
|
}
|
|
93947
94094
|
const dir = hostdDir();
|
|
93948
94095
|
const composePath = hostdComposePath();
|
|
93949
|
-
|
|
94096
|
+
mkdirSync52(dir, { recursive: true });
|
|
93950
94097
|
const imageTag = resolveHostdImageTag(opts.tag, cfg.release);
|
|
93951
94098
|
const guard = checkDowngrade({
|
|
93952
94099
|
container: "switchroom-hostd",
|
|
@@ -93979,7 +94126,7 @@ async function doInstall(opts, program3) {
|
|
|
93979
94126
|
const bak = backupExistingCompose();
|
|
93980
94127
|
if (bak)
|
|
93981
94128
|
console.log(source_default.dim(` Backed up existing compose to ${bak}`));
|
|
93982
|
-
|
|
94129
|
+
writeFileSync43(composePath, yaml, "utf8");
|
|
93983
94130
|
console.log(source_default.green(` \u2713 Wrote ${composePath}`));
|
|
93984
94131
|
const adminAgents = Object.entries(cfg.agents ?? {}).filter(([, a]) => a?.admin === true).map(([name]) => name);
|
|
93985
94132
|
console.log(source_default.dim(` agents served (one socket each): ${allAgents.length === 0 ? "(none)" : allAgents.join(", ")}`));
|
|
@@ -94011,7 +94158,7 @@ function doStatus() {
|
|
|
94011
94158
|
const composeYml = hostdComposePath();
|
|
94012
94159
|
console.log(source_default.bold("switchroom-hostd"));
|
|
94013
94160
|
console.log("");
|
|
94014
|
-
if (!
|
|
94161
|
+
if (!existsSync93(composeYml)) {
|
|
94015
94162
|
console.log(source_default.yellow(" compose: not installed"));
|
|
94016
94163
|
console.log(source_default.dim(" run `switchroom hostd install` to set up."));
|
|
94017
94164
|
return;
|
|
@@ -94032,15 +94179,15 @@ function doStatus() {
|
|
|
94032
94179
|
} else {
|
|
94033
94180
|
console.log(source_default.green(` container: ${ps.stdout.trim()}`));
|
|
94034
94181
|
}
|
|
94035
|
-
if (
|
|
94182
|
+
if (existsSync93(dir)) {
|
|
94036
94183
|
const entries = [];
|
|
94037
94184
|
try {
|
|
94038
94185
|
for (const name of readdirSync35(dir)) {
|
|
94039
94186
|
if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
|
|
94040
94187
|
continue;
|
|
94041
|
-
const sockPath =
|
|
94042
|
-
if (
|
|
94043
|
-
const st =
|
|
94188
|
+
const sockPath = join90(dir, name, "sock");
|
|
94189
|
+
if (existsSync93(sockPath)) {
|
|
94190
|
+
const st = statSync40(sockPath);
|
|
94044
94191
|
if ((st.mode & 61440) === 49152) {
|
|
94045
94192
|
entries.push(`${name} \u2192 ${sockPath}`);
|
|
94046
94193
|
}
|
|
@@ -94058,7 +94205,7 @@ function doStatus() {
|
|
|
94058
94205
|
}
|
|
94059
94206
|
function doUninstall() {
|
|
94060
94207
|
const composeYml = hostdComposePath();
|
|
94061
|
-
if (!
|
|
94208
|
+
if (!existsSync93(composeYml)) {
|
|
94062
94209
|
console.log(source_default.yellow(" No hostd install detected (no compose file at this path)."));
|
|
94063
94210
|
return;
|
|
94064
94211
|
}
|
|
@@ -94082,12 +94229,12 @@ function registerHostdCommand(program3) {
|
|
|
94082
94229
|
hostd.command("uninstall").description("Stop the hostd container. Leaves the compose file in place for re-install.").action(() => doUninstall());
|
|
94083
94230
|
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) => {
|
|
94084
94231
|
const logPath = opts.path ?? defaultAuditLogPath2();
|
|
94085
|
-
if (!
|
|
94232
|
+
if (!existsSync93(logPath)) {
|
|
94086
94233
|
console.error(source_default.yellow(`Audit log not found at ${logPath}.`) + source_default.gray(`
|
|
94087
94234
|
The log is created when hostd handles its first privileged-verb request.`));
|
|
94088
94235
|
return;
|
|
94089
94236
|
}
|
|
94090
|
-
const raw =
|
|
94237
|
+
const raw = readFileSync80(logPath, "utf-8");
|
|
94091
94238
|
const limit = Math.max(1, parseInt(opts.tail ?? "50", 10) || 50);
|
|
94092
94239
|
const filters = {
|
|
94093
94240
|
agent: opts.agent,
|
|
@@ -94130,9 +94277,9 @@ The log is created when hostd handles its first privileged-verb request.`));
|
|
|
94130
94277
|
init_source();
|
|
94131
94278
|
init_helpers();
|
|
94132
94279
|
init_operator_uid();
|
|
94133
|
-
import { existsSync as
|
|
94134
|
-
import { homedir as
|
|
94135
|
-
import { join as
|
|
94280
|
+
import { existsSync as existsSync94, mkdirSync as mkdirSync53, writeFileSync as writeFileSync44, copyFileSync as copyFileSync14 } from "node:fs";
|
|
94281
|
+
import { homedir as homedir55 } from "node:os";
|
|
94282
|
+
import { join as join91 } from "node:path";
|
|
94136
94283
|
import { spawnSync as spawnSync21 } from "node:child_process";
|
|
94137
94284
|
function resolveWebImageTag(explicitTag, release) {
|
|
94138
94285
|
if (explicitTag)
|
|
@@ -94217,14 +94364,14 @@ services:
|
|
|
94217
94364
|
`;
|
|
94218
94365
|
}
|
|
94219
94366
|
function webdDir() {
|
|
94220
|
-
return
|
|
94367
|
+
return join91(homedir55(), ".switchroom", "web");
|
|
94221
94368
|
}
|
|
94222
94369
|
function webdComposePath() {
|
|
94223
|
-
return
|
|
94370
|
+
return join91(webdDir(), "docker-compose.yml");
|
|
94224
94371
|
}
|
|
94225
94372
|
function backupExistingCompose2() {
|
|
94226
94373
|
const p = webdComposePath();
|
|
94227
|
-
if (!
|
|
94374
|
+
if (!existsSync94(p))
|
|
94228
94375
|
return null;
|
|
94229
94376
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
94230
94377
|
const bak = `${p}.bak-${ts}`;
|
|
@@ -94249,7 +94396,7 @@ async function doInstall2(opts, program3) {
|
|
|
94249
94396
|
}
|
|
94250
94397
|
const dir = webdDir();
|
|
94251
94398
|
const composePath = webdComposePath();
|
|
94252
|
-
|
|
94399
|
+
mkdirSync53(dir, { recursive: true });
|
|
94253
94400
|
const cfg = getConfig(program3);
|
|
94254
94401
|
const imageTag = resolveWebImageTag(opts.tag, cfg.release);
|
|
94255
94402
|
const guard = checkDowngrade({
|
|
@@ -94262,7 +94409,7 @@ async function doInstall2(opts, program3) {
|
|
|
94262
94409
|
return;
|
|
94263
94410
|
}
|
|
94264
94411
|
const yaml = renderWebComposeFile({
|
|
94265
|
-
hostHome:
|
|
94412
|
+
hostHome: homedir55(),
|
|
94266
94413
|
imageTag,
|
|
94267
94414
|
operatorUid
|
|
94268
94415
|
});
|
|
@@ -94275,7 +94422,7 @@ async function doInstall2(opts, program3) {
|
|
|
94275
94422
|
const bak = backupExistingCompose2();
|
|
94276
94423
|
if (bak)
|
|
94277
94424
|
console.log(source_default.dim(` Backed up existing compose to ${bak}`));
|
|
94278
|
-
|
|
94425
|
+
writeFileSync44(composePath, yaml, "utf8");
|
|
94279
94426
|
console.log(source_default.green(` \u2713 Wrote ${composePath}`));
|
|
94280
94427
|
console.log(source_default.dim(` running as uid ${operatorUid} (operator), network_mode: host`));
|
|
94281
94428
|
console.log(source_default.dim(` Pulling ghcr.io/switchroom/switchroom-web:${imageTag}\u2026`));
|
|
@@ -94308,7 +94455,7 @@ function doStatus2() {
|
|
|
94308
94455
|
const composeYml = webdComposePath();
|
|
94309
94456
|
console.log(source_default.bold("switchroom-web"));
|
|
94310
94457
|
console.log("");
|
|
94311
|
-
if (!
|
|
94458
|
+
if (!existsSync94(composeYml)) {
|
|
94312
94459
|
console.log(source_default.yellow(" compose: not installed"));
|
|
94313
94460
|
console.log(source_default.dim(" run `switchroom webd install` to set up."));
|
|
94314
94461
|
return;
|
|
@@ -94332,7 +94479,7 @@ function doStatus2() {
|
|
|
94332
94479
|
}
|
|
94333
94480
|
function doUninstall2() {
|
|
94334
94481
|
const composeYml = webdComposePath();
|
|
94335
|
-
if (!
|
|
94482
|
+
if (!existsSync94(composeYml)) {
|
|
94336
94483
|
console.log(source_default.yellow(" No web-service install detected (no compose file at this path)."));
|
|
94337
94484
|
return;
|
|
94338
94485
|
}
|
|
@@ -94361,10 +94508,10 @@ function registerWebdCommand(program3) {
|
|
|
94361
94508
|
|
|
94362
94509
|
// src/cli/host-repair.ts
|
|
94363
94510
|
init_source();
|
|
94364
|
-
import { homedir as
|
|
94365
|
-
import { join as
|
|
94511
|
+
import { homedir as homedir56 } from "node:os";
|
|
94512
|
+
import { join as join92 } from "node:path";
|
|
94366
94513
|
var ARTIFACT_ALLOWLIST = {
|
|
94367
|
-
dockerComposePluginDir: (home2) =>
|
|
94514
|
+
dockerComposePluginDir: (home2) => join92(home2, ".docker", "cli-plugins", "docker-compose"),
|
|
94368
94515
|
stateSentinel: "/state"
|
|
94369
94516
|
};
|
|
94370
94517
|
function isStateBogusAutoDir(probe2) {
|
|
@@ -94413,7 +94560,7 @@ function isStateBogusAutoDir(probe2) {
|
|
|
94413
94560
|
return true;
|
|
94414
94561
|
}
|
|
94415
94562
|
function planMountRepairs(probe2, opts = {}) {
|
|
94416
|
-
const home2 = opts.hostHome ??
|
|
94563
|
+
const home2 = opts.hostHome ?? homedir56();
|
|
94417
94564
|
const items = [];
|
|
94418
94565
|
const dockerComposePath = ARTIFACT_ALLOWLIST.dockerComposePluginDir(home2);
|
|
94419
94566
|
const dockerComposeSt = probe2.lstat(dockerComposePath);
|