switchroom 0.19.1 → 0.19.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-scheduler/index.js +31 -1
- package/dist/auth-broker/index.js +565 -48
- package/dist/cli/autoaccept-poll.js +31 -1
- package/dist/cli/drive-write-pretool.mjs +32 -2
- package/dist/cli/ms-365-write-pretool.mjs +32 -2
- package/dist/cli/switchroom.js +1148 -274
- package/dist/host-control/main.js +3 -3
- package/dist/vault/approvals/kernel-server.js +2 -2
- package/dist/vault/broker/server.js +2 -2
- package/package.json +3 -2
- package/profiles/_base/start.sh.hbs +1 -0
- package/profiles/default/CLAUDE.md.hbs +8 -0
- package/skills/mental-model-curator/SKILL.md +68 -2
- package/skills/switchroom-cli/SKILL.md +25 -0
- package/telegram-plugin/auth-snapshot-format.ts +143 -12
- package/telegram-plugin/dist/bridge/bridge.js +8 -2
- package/telegram-plugin/dist/gateway/gateway.js +1427 -689
- package/telegram-plugin/dist/server.js +8 -2
- package/telegram-plugin/external-spend.ts +135 -0
- package/telegram-plugin/flushed-turn-supersede.ts +117 -13
- package/telegram-plugin/gateway/auth-add-flow.ts +215 -6
- package/telegram-plugin/gateway/auth-command.ts +138 -5
- package/telegram-plugin/gateway/gateway.ts +141 -158
- package/telegram-plugin/gateway/inbound-interceptors.ts +13 -3
- package/telegram-plugin/gateway/model-command.ts +309 -1
- package/telegram-plugin/gateway/narrative-lane.ts +23 -9
- package/telegram-plugin/gateway/outbound-send-path.ts +68 -15
- package/telegram-plugin/gateway/session-model-source.ts +90 -10
- package/telegram-plugin/gateway/status-pin-store.ts +64 -4
- package/telegram-plugin/gateway/stream-render.ts +22 -5
- package/telegram-plugin/gateway/usage-mask.ts +29 -0
- package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +19 -2
- package/telegram-plugin/quota-bar-format.ts +78 -12
- package/telegram-plugin/quota-check.ts +17 -2
- package/telegram-plugin/reply-owner-resolve.ts +76 -11
- package/telegram-plugin/session-tail.ts +27 -3
- package/telegram-plugin/tests/activity-card-wiring.test.ts +47 -0
- package/telegram-plugin/tests/auth-add-flow.test.ts +367 -5
- package/telegram-plugin/tests/auth-snapshot-format.test.ts +41 -0
- package/telegram-plugin/tests/external-spend.test.ts +168 -0
- package/telegram-plugin/tests/flushed-turn-supersede.test.ts +117 -0
- package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +219 -29
- package/telegram-plugin/tests/model-command.test.ts +220 -0
- package/telegram-plugin/tests/quota-bar-format.test.ts +43 -0
- package/telegram-plugin/tests/quota-check.test.ts +57 -0
- package/telegram-plugin/tests/reply-owner-resolve.test.ts +257 -13
- package/telegram-plugin/tests/send-reply-golden.test.ts +154 -0
- package/telegram-plugin/tests/session-model-source.test.ts +142 -0
- package/telegram-plugin/tests/session-tail-first-attach.test.ts +115 -2
- package/telegram-plugin/tests/status-pin-store.test.ts +198 -0
- package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +50 -0
- package/telegram-plugin/tests/usage-footer-freshness.test.ts +141 -0
- package/telegram-plugin/tests/usage-mask.test.ts +35 -0
- package/telegram-plugin/tests/worker-feed-dispatch.test.ts +27 -0
- package/telegram-plugin/tests/worker-feed-pin-persistence.test.ts +131 -1
- package/vendor/hindsight-memory/CHANGELOG.md +102 -0
- package/vendor/hindsight-memory/README.md +2 -1
- package/vendor/hindsight-memory/hooks/hooks.json +12 -0
- package/vendor/hindsight-memory/scripts/directive_verify.py +100 -3
- package/vendor/hindsight-memory/scripts/lib/config.py +150 -1
- package/vendor/hindsight-memory/scripts/lib/content.py +55 -5
- package/vendor/hindsight-memory/scripts/lib/directives.py +152 -15
- package/vendor/hindsight-memory/scripts/lib/parallel_recall.py +142 -0
- package/vendor/hindsight-memory/scripts/lib/state.py +31 -0
- package/vendor/hindsight-memory/scripts/recall.py +789 -143
- package/vendor/hindsight-memory/scripts/reconcile_tail.py +22 -1
- package/vendor/hindsight-memory/scripts/retain.py +71 -2
- package/vendor/hindsight-memory/scripts/subagent_retain.py +501 -0
- package/vendor/hindsight-memory/scripts/tests/test_directive_verify.py +169 -0
- package/vendor/hindsight-memory/scripts/tests/test_directives.py +177 -0
- package/vendor/hindsight-memory/scripts/tests/test_lesson_tagging.py +200 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_context_turns_default.py +200 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_envelope_strip_telemetry.py +477 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_integration.py +51 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_parallel_deadline.py +409 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_tag_weights.py +96 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_transcript_fallback.py +413 -0
- package/vendor/hindsight-memory/scripts/tests/test_reconcile_durability.py +49 -0
- package/vendor/hindsight-memory/scripts/tests/test_subagent_retain.py +439 -0
- package/vendor/hindsight-memory/settings.json +3 -1
package/dist/cli/switchroom.js
CHANGED
|
@@ -2120,7 +2120,7 @@ var init_esm = __esm(() => {
|
|
|
2120
2120
|
});
|
|
2121
2121
|
|
|
2122
2122
|
// src/build-info.ts
|
|
2123
|
-
var VERSION = "0.19.
|
|
2123
|
+
var VERSION = "0.19.3", COMMIT_SHA = "41896be4";
|
|
2124
2124
|
|
|
2125
2125
|
// src/cli/resolve-version.ts
|
|
2126
2126
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -15452,14 +15452,27 @@ __export(exports_atomic, {
|
|
|
15452
15452
|
atomicWriteFileSync: () => atomicWriteFileSync
|
|
15453
15453
|
});
|
|
15454
15454
|
import { randomBytes } from "node:crypto";
|
|
15455
|
-
import { closeSync as closeSync2, constants, fsyncSync, openSync as openSync2, renameSync, rmSync, writeSync } from "node:fs";
|
|
15456
|
-
function atomicWriteFileSync(destPath, contents,
|
|
15455
|
+
import { closeSync as closeSync2, constants, fchmodSync, fchownSync, fsyncSync, openSync as openSync2, renameSync, rmSync, writeSync } from "node:fs";
|
|
15456
|
+
function atomicWriteFileSync(destPath, contents, modeOrOpts = 384) {
|
|
15457
|
+
const opts = typeof modeOrOpts === "number" ? { mode: modeOrOpts } : modeOrOpts;
|
|
15458
|
+
const mode = opts.mode ?? 384;
|
|
15457
15459
|
const tmp = `${destPath}.tmp-${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
15458
15460
|
const buf = typeof contents === "string" ? Buffer.from(contents, "utf-8") : contents;
|
|
15459
15461
|
let fd = null;
|
|
15460
15462
|
try {
|
|
15461
15463
|
fd = openSync2(tmp, TMP_OPEN_FLAGS, mode);
|
|
15462
15464
|
writeSync(fd, buf, 0, buf.length, 0);
|
|
15465
|
+
fchmodSync(fd, mode);
|
|
15466
|
+
if (opts.uid !== undefined) {
|
|
15467
|
+
try {
|
|
15468
|
+
fchownSync(fd, opts.uid, opts.gid ?? opts.uid);
|
|
15469
|
+
} catch (chownErr) {
|
|
15470
|
+
if (opts.onChownError)
|
|
15471
|
+
opts.onChownError(chownErr);
|
|
15472
|
+
else
|
|
15473
|
+
throw chownErr;
|
|
15474
|
+
}
|
|
15475
|
+
}
|
|
15463
15476
|
fsyncSync(fd);
|
|
15464
15477
|
closeSync2(fd);
|
|
15465
15478
|
fd = null;
|
|
@@ -15655,6 +15668,53 @@ function resolveHindsightLlm(llm, litellm) {
|
|
|
15655
15668
|
const model = llm?.model?.trim() || (litellm ? litellm.model ?? HINDSIGHT_DEFAULT_LITELLM_MODEL : HINDSIGHT_DEFAULT_MODEL);
|
|
15656
15669
|
return { provider, model };
|
|
15657
15670
|
}
|
|
15671
|
+
function isLoopbackHttpUrl(url) {
|
|
15672
|
+
try {
|
|
15673
|
+
const u = new URL(url.includes("://") ? url : `http://${url}`);
|
|
15674
|
+
const h = (u.hostname || "").toLowerCase();
|
|
15675
|
+
return h === "localhost" || h === "127.0.0.1" || h === "::1";
|
|
15676
|
+
} catch {
|
|
15677
|
+
return false;
|
|
15678
|
+
}
|
|
15679
|
+
}
|
|
15680
|
+
function collectHindsightLlmBaseUrls(llm, litellm) {
|
|
15681
|
+
const out = [];
|
|
15682
|
+
const seen = new Set;
|
|
15683
|
+
const push = (raw) => {
|
|
15684
|
+
const v = raw?.trim();
|
|
15685
|
+
if (!v || seen.has(v))
|
|
15686
|
+
return;
|
|
15687
|
+
seen.add(v);
|
|
15688
|
+
out.push(v);
|
|
15689
|
+
};
|
|
15690
|
+
push(litellm?.baseUrl);
|
|
15691
|
+
for (const [k, v] of resolveHindsightPerOpLlm(llm)) {
|
|
15692
|
+
if (k.endsWith("_BASE_URL"))
|
|
15693
|
+
push(v);
|
|
15694
|
+
}
|
|
15695
|
+
return out;
|
|
15696
|
+
}
|
|
15697
|
+
function hindsightNeedsHostNetwork(llm, litellm) {
|
|
15698
|
+
if (litellm?.baseUrl?.trim())
|
|
15699
|
+
return true;
|
|
15700
|
+
return collectHindsightLlmBaseUrls(llm).some(isLoopbackHttpUrl);
|
|
15701
|
+
}
|
|
15702
|
+
function pickHindsightLiteLlmProbeUrl(llm, litellm) {
|
|
15703
|
+
return collectHindsightLlmBaseUrls(llm, litellm)[0];
|
|
15704
|
+
}
|
|
15705
|
+
function buildLiteLlmAwareHealthPy(apiPort, litellmBaseUrl) {
|
|
15706
|
+
let host = "127.0.0.1";
|
|
15707
|
+
let port = "80";
|
|
15708
|
+
try {
|
|
15709
|
+
const u = new URL(litellmBaseUrl.includes("://") ? litellmBaseUrl : `http://${litellmBaseUrl}`);
|
|
15710
|
+
host = u.hostname || host;
|
|
15711
|
+
port = u.port || (u.protocol === "https:" ? "443" : "80");
|
|
15712
|
+
} catch {}
|
|
15713
|
+
return `import socket,urllib.request,sys;` + `r=urllib.request.urlopen('http://localhost:${apiPort}/health',timeout=4);` + `(r.getcode()==200) or sys.exit(1);` + `s=socket.create_connection(('${host}',${Number(port)}),2);s.close()`;
|
|
15714
|
+
}
|
|
15715
|
+
function buildLiteLlmAwareHealthCmd(apiPort, litellmBaseUrl) {
|
|
15716
|
+
return `python3 -c "${buildLiteLlmAwareHealthPy(apiPort, litellmBaseUrl)}"`;
|
|
15717
|
+
}
|
|
15658
15718
|
function startHindsight(ports, litellm, imageTag, llm, mirrorDir) {
|
|
15659
15719
|
const apiPort = ports?.apiPort ?? HINDSIGHT_DEFAULT_API_PORT;
|
|
15660
15720
|
const uiPort = ports?.uiPort ?? HINDSIGHT_DEFAULT_UI_PORT;
|
|
@@ -15687,7 +15747,9 @@ function startHindsight(ports, litellm, imageTag, llm, mirrorDir) {
|
|
|
15687
15747
|
"-e",
|
|
15688
15748
|
`HINDSIGHT_API_CONSOLIDATION_LLM_PARALLELISM=${HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM}`,
|
|
15689
15749
|
"-e",
|
|
15690
|
-
`HINDSIGHT_API_CONSOLIDATION_MAX_MEMORIES_PER_ROUND=${HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND}
|
|
15750
|
+
`HINDSIGHT_API_CONSOLIDATION_MAX_MEMORIES_PER_ROUND=${HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND}`,
|
|
15751
|
+
"-e",
|
|
15752
|
+
`HINDSIGHT_API_WORKER_ID=${HINDSIGHT_DEFAULT_WORKER_ID}`
|
|
15691
15753
|
];
|
|
15692
15754
|
if (llmProvider === "claude-code") {
|
|
15693
15755
|
envArgs.push("-e", `ANTHROPIC_MODEL=${llmModel}`);
|
|
@@ -15710,8 +15772,15 @@ x-litellm-tags: service:hindsight`);
|
|
|
15710
15772
|
`--memory-reservation=${HINDSIGHT_DEFAULT_MEM_RESERVATION}`,
|
|
15711
15773
|
`--pids-limit=${HINDSIGHT_DEFAULT_PIDS_LIMIT}`,
|
|
15712
15774
|
`--shm-size=${HINDSIGHT_DEFAULT_SHM_SIZE}`,
|
|
15713
|
-
|
|
15714
|
-
|
|
15775
|
+
...(() => {
|
|
15776
|
+
const hostNet = hindsightNeedsHostNetwork(llm, litellm);
|
|
15777
|
+
const healthApiPort = hostNet ? apiPort : 8888;
|
|
15778
|
+
const probe = pickHindsightLiteLlmProbeUrl(llm, litellm);
|
|
15779
|
+
return [
|
|
15780
|
+
"--health-cmd",
|
|
15781
|
+
probe ? buildLiteLlmAwareHealthCmd(healthApiPort, probe) : HINDSIGHT_HEALTHCHECK_CMD
|
|
15782
|
+
];
|
|
15783
|
+
})(),
|
|
15715
15784
|
"--health-interval",
|
|
15716
15785
|
"30s",
|
|
15717
15786
|
"--health-timeout",
|
|
@@ -15721,8 +15790,11 @@ x-litellm-tags: service:hindsight`);
|
|
|
15721
15790
|
"--health-start-period",
|
|
15722
15791
|
"60s"
|
|
15723
15792
|
];
|
|
15724
|
-
if (litellm) {
|
|
15793
|
+
if (hindsightNeedsHostNetwork(llm, litellm)) {
|
|
15725
15794
|
args.push("--network", "host");
|
|
15795
|
+
if (!litellm) {
|
|
15796
|
+
envArgs.push("-e", `HINDSIGHT_API_PORT=${apiPort}`, "-e", `HINDSIGHT_CP_DATAPLANE_API_URL=http://localhost:${apiPort}`);
|
|
15797
|
+
}
|
|
15726
15798
|
} else {
|
|
15727
15799
|
args.push("-p", `127.0.0.1:${apiPort}:8888`, "-p", `127.0.0.1:${uiPort}:9999`);
|
|
15728
15800
|
}
|
|
@@ -15744,13 +15816,40 @@ x-litellm-tags: service:hindsight`);
|
|
|
15744
15816
|
}
|
|
15745
15817
|
execFileSync("docker", args, { stdio: "pipe" });
|
|
15746
15818
|
}
|
|
15747
|
-
function
|
|
15819
|
+
function listHindsightDataVolumeMounts(exec = (cmd, args) => execFileSync(cmd, args, { stdio: "pipe", encoding: "utf-8" })) {
|
|
15748
15820
|
try {
|
|
15749
|
-
|
|
15750
|
-
|
|
15751
|
-
|
|
15752
|
-
|
|
15753
|
-
|
|
15821
|
+
const out = exec("docker", [
|
|
15822
|
+
"ps",
|
|
15823
|
+
"-a",
|
|
15824
|
+
"--filter",
|
|
15825
|
+
`volume=${HINDSIGHT_DATA_VOLUME}`,
|
|
15826
|
+
"--format",
|
|
15827
|
+
"{{.Names}}"
|
|
15828
|
+
]);
|
|
15829
|
+
return out.split(`
|
|
15830
|
+
`).map((l) => l.trim()).filter((n) => n.length > 0);
|
|
15831
|
+
} catch {
|
|
15832
|
+
return [];
|
|
15833
|
+
}
|
|
15834
|
+
}
|
|
15835
|
+
function stopHindsight(exec = (cmd, args) => {
|
|
15836
|
+
execFileSync(cmd, args, { stdio: "pipe" });
|
|
15837
|
+
}, listMounts = () => listHindsightDataVolumeMounts()) {
|
|
15838
|
+
const names = new Set([HINDSIGHT_DEFAULT_WORKER_ID, ...listMounts()]);
|
|
15839
|
+
for (const name of names) {
|
|
15840
|
+
if (name !== HINDSIGHT_DEFAULT_WORKER_ID) {
|
|
15841
|
+
console.error(`stopHindsight: removing hindsight data-volume twin ${name} (not the canonical ${HINDSIGHT_DEFAULT_WORKER_ID})`);
|
|
15842
|
+
}
|
|
15843
|
+
try {
|
|
15844
|
+
exec("docker", ["update", "--restart=no", name]);
|
|
15845
|
+
} catch {}
|
|
15846
|
+
try {
|
|
15847
|
+
exec("docker", ["stop", name]);
|
|
15848
|
+
} catch {}
|
|
15849
|
+
try {
|
|
15850
|
+
exec("docker", ["rm", "-f", name]);
|
|
15851
|
+
} catch {}
|
|
15852
|
+
}
|
|
15754
15853
|
}
|
|
15755
15854
|
function pullHindsightImage(imageTag) {
|
|
15756
15855
|
execFileSync("docker", ["pull", hindsightImageRef(imageTag)], { stdio: "inherit" });
|
|
@@ -15808,17 +15907,30 @@ function getHindsightStatus() {
|
|
|
15808
15907
|
return null;
|
|
15809
15908
|
}
|
|
15810
15909
|
}
|
|
15811
|
-
function generateHindsightComposeSnippet(llm, mirrorDir) {
|
|
15812
|
-
const { provider: llmProvider, model: llmModel } = resolveHindsightLlm(llm);
|
|
15910
|
+
function generateHindsightComposeSnippet(llm, mirrorDir, litellm) {
|
|
15911
|
+
const { provider: llmProvider, model: llmModel } = resolveHindsightLlm(llm, litellm);
|
|
15813
15912
|
const perOpLlm = resolveHindsightPerOpLlm(llm);
|
|
15913
|
+
const hostNetwork = hindsightNeedsHostNetwork(llm, litellm);
|
|
15914
|
+
const apiPort = HINDSIGHT_DEFAULT_API_PORT;
|
|
15915
|
+
const internalApiPort = hostNetwork ? apiPort : 8888;
|
|
15916
|
+
const probeUrl = pickHindsightLiteLlmProbeUrl(llm, litellm);
|
|
15917
|
+
const healthPy = probeUrl ? buildLiteLlmAwareHealthPy(internalApiPort, probeUrl) : HINDSIGHT_HEALTHCHECK_PY;
|
|
15814
15918
|
const environment = [
|
|
15815
15919
|
` - HINDSIGHT_API_MAX_OBSERVATIONS_PER_SCOPE=${HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE}`,
|
|
15816
15920
|
` - HINDSIGHT_API_LLM_PROVIDER=${llmProvider}`,
|
|
15817
15921
|
` - HINDSIGHT_API_LLM_MODEL=${llmModel}`,
|
|
15818
15922
|
...perOpLlm.map(([k, v]) => ` - ${k}=${v}`),
|
|
15819
15923
|
...llmProvider === "claude-code" ? [` - ANTHROPIC_MODEL=${llmModel}`] : [],
|
|
15820
|
-
|
|
15924
|
+
...hostNetwork ? [
|
|
15925
|
+
` - HINDSIGHT_API_PORT=${apiPort}`,
|
|
15926
|
+
` - HINDSIGHT_CP_DATAPLANE_API_URL=http://localhost:${apiPort}`
|
|
15927
|
+
] : [` - HINDSIGHT_CP_DATAPLANE_API_URL=http://localhost:8888`]
|
|
15821
15928
|
];
|
|
15929
|
+
if (litellm?.baseUrl?.trim() && litellm.apiKey?.trim()) {
|
|
15930
|
+
const litellmRoot = litellm.baseUrl.replace(/\/+$/, "");
|
|
15931
|
+
const anthropicBaseUrl = isClaudeModel(llmModel) ? `${litellmRoot}/anthropic` : litellmRoot;
|
|
15932
|
+
environment.push(` - ANTHROPIC_BASE_URL=${anthropicBaseUrl}`, ` - ANTHROPIC_CUSTOM_HEADERS=x-litellm-api-key: Bearer ${litellm.apiKey}\\nx-litellm-customer-id: hindsight\\nx-litellm-tags: service:hindsight`);
|
|
15933
|
+
}
|
|
15822
15934
|
const initService = mirrorDir ? [
|
|
15823
15935
|
" switchroom-hindsight-creds-init:",
|
|
15824
15936
|
` image: ${HINDSIGHT_IMAGE}`,
|
|
@@ -15835,6 +15947,13 @@ function generateHindsightComposeSnippet(llm, mirrorDir) {
|
|
|
15835
15947
|
" switchroom-hindsight-creds-init:",
|
|
15836
15948
|
" condition: service_completed_successfully"
|
|
15837
15949
|
] : [];
|
|
15950
|
+
const networkOrPorts = hostNetwork ? [
|
|
15951
|
+
" network_mode: host"
|
|
15952
|
+
] : [
|
|
15953
|
+
" ports:",
|
|
15954
|
+
` - "127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}:8888"`,
|
|
15955
|
+
' - "127.0.0.1:19999:9999"'
|
|
15956
|
+
];
|
|
15838
15957
|
return [
|
|
15839
15958
|
"services:",
|
|
15840
15959
|
...initService,
|
|
@@ -15842,9 +15961,7 @@ function generateHindsightComposeSnippet(llm, mirrorDir) {
|
|
|
15842
15961
|
` image: ${HINDSIGHT_IMAGE}`,
|
|
15843
15962
|
" container_name: switchroom-hindsight",
|
|
15844
15963
|
...dependsOn,
|
|
15845
|
-
|
|
15846
|
-
` - "127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}:8888"`,
|
|
15847
|
-
' - "127.0.0.1:19999:9999"',
|
|
15964
|
+
...networkOrPorts,
|
|
15848
15965
|
" environment:",
|
|
15849
15966
|
...environment,
|
|
15850
15967
|
` - HINDSIGHT_API_MCP_STATELESS=${HINDSIGHT_DEFAULT_MCP_STATELESS}`,
|
|
@@ -15857,12 +15974,13 @@ function generateHindsightComposeSnippet(llm, mirrorDir) {
|
|
|
15857
15974
|
` - HINDSIGHT_API_WORKER_CONSOLIDATION_MAX_SLOTS=${HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS}`,
|
|
15858
15975
|
` - HINDSIGHT_API_CONSOLIDATION_LLM_PARALLELISM=${HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM}`,
|
|
15859
15976
|
` - HINDSIGHT_API_CONSOLIDATION_MAX_MEMORIES_PER_ROUND=${HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND}`,
|
|
15977
|
+
` - HINDSIGHT_API_WORKER_ID=${HINDSIGHT_DEFAULT_WORKER_ID}`,
|
|
15860
15978
|
` mem_limit: ${HINDSIGHT_DEFAULT_MEM_LIMIT}`,
|
|
15861
15979
|
` mem_reservation: ${HINDSIGHT_DEFAULT_MEM_RESERVATION}`,
|
|
15862
15980
|
` pids_limit: ${HINDSIGHT_DEFAULT_PIDS_LIMIT}`,
|
|
15863
15981
|
` shm_size: ${HINDSIGHT_DEFAULT_SHM_SIZE}`,
|
|
15864
15982
|
" healthcheck:",
|
|
15865
|
-
` test: ${JSON.stringify(["CMD", "python3", "-c",
|
|
15983
|
+
` test: ${JSON.stringify(["CMD", "python3", "-c", healthPy])}`,
|
|
15866
15984
|
" interval: 30s",
|
|
15867
15985
|
" timeout: 5s",
|
|
15868
15986
|
" retries: 3",
|
|
@@ -15946,7 +16064,7 @@ async function ensureHindsightConsumer(configPath, account, uid = HINDSIGHT_DEFA
|
|
|
15946
16064
|
atomicWriteFileSync2(configPath, tail, mode);
|
|
15947
16065
|
return { added: true, reason: "added" };
|
|
15948
16066
|
}
|
|
15949
|
-
var HINDSIGHT_DEFAULT_API_PORT = 18888, HINDSIGHT_DEFAULT_UI_PORT = 9999, HINDSIGHT_DEFAULT_MCP_URL, HINDSIGHT_DEFAULT_API_BASE_URL, HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE = 1000, HINDSIGHT_CONSUMER_NAME = "hindsight", HINDSIGHT_DEFAULT_UID = 11000, HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight", HINDSIGHT_IMAGE, HINDSIGHT_DEFAULT_MODEL = "claude-sonnet-5", HINDSIGHT_DEFAULT_LITELLM_MODEL = "openrouter/google/gemini-3.1-flash-lite", HINDSIGHT_DEFAULT_MCP_STATELESS = true, HINDSIGHT_BROKER_SOCK_VOLUME, HINDSIGHT_CREDS_MIRROR_VOLUME, HINDSIGHT_CRED_DIR = "/run/claude-creds", HINDSIGHT_DEFAULT_RERANKER_BUCKET_BATCHING = "true", HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES = 150, HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT = 4, HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8, HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_BATCH_SIZE = 12, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 1, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM = 2, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 100, HINDSIGHT_DEFAULT_MEM_LIMIT = "8g", HINDSIGHT_DEFAULT_MEM_RESERVATION = "4g", HINDSIGHT_DEFAULT_PIDS_LIMIT = 1000, HINDSIGHT_DEFAULT_SHM_SIZE = "2g", HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)', HINDSIGHT_HEALTHCHECK_CMD, HINDSIGHT_LLM_OPS;
|
|
16067
|
+
var HINDSIGHT_DEFAULT_API_PORT = 18888, HINDSIGHT_DEFAULT_UI_PORT = 9999, HINDSIGHT_DEFAULT_MCP_URL, HINDSIGHT_DEFAULT_API_BASE_URL, HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE = 1000, HINDSIGHT_CONSUMER_NAME = "hindsight", HINDSIGHT_DEFAULT_WORKER_ID = "switchroom-hindsight", HINDSIGHT_DATA_VOLUME = "switchroom-hindsight-data", HINDSIGHT_DEFAULT_UID = 11000, HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight", HINDSIGHT_IMAGE, HINDSIGHT_DEFAULT_MODEL = "claude-sonnet-5", HINDSIGHT_DEFAULT_LITELLM_MODEL = "openrouter/google/gemini-3.1-flash-lite", HINDSIGHT_DEFAULT_MCP_STATELESS = true, HINDSIGHT_BROKER_SOCK_VOLUME, HINDSIGHT_CREDS_MIRROR_VOLUME, HINDSIGHT_CRED_DIR = "/run/claude-creds", HINDSIGHT_DEFAULT_RERANKER_BUCKET_BATCHING = "true", HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES = 150, HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT = 4, HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8, HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_BATCH_SIZE = 12, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 1, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM = 2, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 100, HINDSIGHT_DEFAULT_MEM_LIMIT = "8g", HINDSIGHT_DEFAULT_MEM_RESERVATION = "4g", HINDSIGHT_DEFAULT_PIDS_LIMIT = 1000, HINDSIGHT_DEFAULT_SHM_SIZE = "2g", HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)', HINDSIGHT_HEALTHCHECK_CMD, HINDSIGHT_LLM_OPS;
|
|
15950
16068
|
var init_hindsight = __esm(() => {
|
|
15951
16069
|
init_model_command();
|
|
15952
16070
|
HINDSIGHT_DEFAULT_MCP_URL = `http://127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}/mcp/`;
|
|
@@ -16509,7 +16627,7 @@ var HINDSIGHT_SHIM_CLI_PATH = "/usr/local/bin/switchroom", HINDSIGHT_SHIM_AGENT_
|
|
|
16509
16627
|
var init_hindsight2 = __esm(() => {
|
|
16510
16628
|
init_users();
|
|
16511
16629
|
init_hindsight();
|
|
16512
|
-
DEFAULT_RETAIN_MISSION = "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise.";
|
|
16630
|
+
DEFAULT_RETAIN_MISSION = "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise, " + "including in-flight workflow/process narration (a sub-task started, " + "paused, or is still running) \u2014 only retain the outcome once a task " + "actually completes or a decision is made.";
|
|
16513
16631
|
PROFILE_MEMORY_DEFAULTS = {
|
|
16514
16632
|
"health-coach": {
|
|
16515
16633
|
disposition: { skepticism: 2, literalism: 2, empathy: 5 },
|
|
@@ -26989,6 +27107,7 @@ function renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig) {
|
|
|
26989
27107
|
settings.recallTypes = ["world", "experience", "observation"];
|
|
26990
27108
|
settings.recallSkipTrivial = true;
|
|
26991
27109
|
settings.directiveCaptureNudge = true;
|
|
27110
|
+
settings.recallTagWeights = { sidechain: 0.8 };
|
|
26992
27111
|
if (additionalBanks.length > 0) {
|
|
26993
27112
|
settings.recallAdditionalBanks = [...additionalBanks];
|
|
26994
27113
|
}
|
|
@@ -29763,6 +29882,7 @@ function describeAgents(config, litellmConfirmedAgents) {
|
|
|
29763
29882
|
smallFastModel: resolved.litellm?.small_fast_model ?? config.litellm?.small_fast_model ?? "claude-haiku-4-5-20251001"
|
|
29764
29883
|
},
|
|
29765
29884
|
model: resolveMainModel(resolved.model),
|
|
29885
|
+
tmuxSupervisor: resolved.experimental?.legacy_pty !== true,
|
|
29766
29886
|
timezone: resolveTimezone(config, resolved, {
|
|
29767
29887
|
onUtcFallback: () => {
|
|
29768
29888
|
console.warn(` \u26a0 timezone: no explicit timezone set and server detection resolved to UTC ` + `for agent "${name}" \u2014 cron schedules and the per-turn time hint will ` + `run in UTC. Add \`switchroom.timezone: "Region/City"\` (e.g. ` + `"Australia/Melbourne") to switchroom.yaml to silence this warning.`);
|
|
@@ -30033,11 +30153,25 @@ function generateCompose(opts) {
|
|
|
30033
30153
|
lines.push(` SWITCHROOM_CONFIG: /state/config/switchroom.yaml`);
|
|
30034
30154
|
}
|
|
30035
30155
|
lines.push(` SWITCHROOM_AUTH_BROKER_STATE_DIR: /state/auth-broker`);
|
|
30156
|
+
let authBrokerNeedsHostGateway = false;
|
|
30157
|
+
{
|
|
30158
|
+
const llBase = config.litellm?.base_url;
|
|
30159
|
+
if (typeof llBase === "string" && llBase.trim()) {
|
|
30160
|
+
const raw = llBase.trim().replace(/\/+$/, "");
|
|
30161
|
+
const bridged = raw.replace(/^http:\/\/127\.0\.0\.1(?=[:/]|$)/i, "http://host.docker.internal").replace(/^http:\/\/localhost(?=[:/]|$)/i, "http://host.docker.internal").replace(/^https:\/\/127\.0\.0\.1(?=[:/]|$)/i, "https://host.docker.internal").replace(/^https:\/\/localhost(?=[:/]|$)/i, "https://host.docker.internal");
|
|
30162
|
+
lines.push(` SWITCHROOM_LITELLM_BASE: ${JSON.stringify(bridged)}`);
|
|
30163
|
+
authBrokerNeedsHostGateway = true;
|
|
30164
|
+
}
|
|
30165
|
+
}
|
|
30036
30166
|
lines.push(` SWITCHROOM_ACCOUNTS_DIR: /state/accounts`);
|
|
30037
30167
|
lines.push(` SWITCHROOM_AGENTS_DIR: /state/agents`);
|
|
30038
30168
|
if (opts.operatorUid !== undefined) {
|
|
30039
30169
|
lines.push(` SWITCHROOM_AUTH_BROKER_OPERATOR_UID: "${opts.operatorUid}"`);
|
|
30040
30170
|
}
|
|
30171
|
+
if (authBrokerNeedsHostGateway) {
|
|
30172
|
+
lines.push(` extra_hosts:`);
|
|
30173
|
+
lines.push(` - "host.docker.internal:host-gateway"`);
|
|
30174
|
+
}
|
|
30041
30175
|
lines.push(` volumes:`);
|
|
30042
30176
|
for (const a of describeAgents(config, opts.litellmConfirmedAgents)) {
|
|
30043
30177
|
lines.push(` - auth-broker-${a.name}-sock:/run/switchroom/auth-broker/${a.name}`);
|
|
@@ -30211,6 +30345,7 @@ function emitAgentService(lines, a, imageTag, buildMode, buildContext, homePrefi
|
|
|
30211
30345
|
env2.ANTHROPIC_SMALL_FAST_MODEL = a.litellm.smallFastModel;
|
|
30212
30346
|
}
|
|
30213
30347
|
env2.SWITCHROOM_VOICE_ENGINE = voiceEngine;
|
|
30348
|
+
env2.SWITCHROOM_TMUX_SUPERVISOR = a.tmuxSupervisor ? "1" : "0";
|
|
30214
30349
|
for (const [k, v] of Object.entries(a.userEnv)) {
|
|
30215
30350
|
if (env2[k] === undefined)
|
|
30216
30351
|
env2[k] = v;
|
|
@@ -32692,7 +32827,7 @@ function decodeResponse2(line) {
|
|
|
32692
32827
|
}
|
|
32693
32828
|
return ResponseSchema2.parse(parsed);
|
|
32694
32829
|
}
|
|
32695
|
-
var MAX_FRAME_BYTES2, PROTOCOL_VERSION = 1, ProviderNameSchema, GetCredentialsRequestSchema, ListStateRequestSchema, SetActiveRequestSchema, MarkExhaustedRequestSchema, MarkThrottledRequestSchema, RefreshAccountRequestSchema, AnthropicCredentialsSchema, GoogleCredentialsSchema, MicrosoftCredentialsSchema, ProviderCredentialsSchema, AddAccountRequestSchema, RmAccountRequestSchema, SetOverrideRequestSchema, ListGoogleAccountsRequestSchema, ListMicrosoftAccountsRequestSchema, ProbeQuotaRequestSchema, ClaimNotificationRequestSchema, RequestSchema2, GetCredentialsDataSchema, AccountStateSchema, AgentStateSchema, ConsumerStateSchema, ListStateDataSchema, SetActiveDataSchema, MarkExhaustedDataSchema, MarkThrottledDataSchema, RefreshAccountDataSchema, AddAccountDataSchema, RmAccountDataSchema, SetOverrideDataSchema, ClaimNotificationDataSchema, GoogleAccountStateSchema, ListGoogleAccountsDataSchema, MicrosoftAccountStateSchema, ListMicrosoftAccountsDataSchema, ErrorBodySchema, SuccessResponseSchema, ErrorResponseSchema2, ResponseSchema2;
|
|
32830
|
+
var MAX_FRAME_BYTES2, PROTOCOL_VERSION = 1, ProviderNameSchema, GetCredentialsRequestSchema, ListStateRequestSchema, SetActiveRequestSchema, MarkExhaustedRequestSchema, MarkThrottledRequestSchema, RefreshAccountRequestSchema, AnthropicCredentialsSchema, GoogleCredentialsSchema, MicrosoftCredentialsSchema, ProviderCredentialsSchema, AddAccountRequestSchema, RmAccountRequestSchema, SetOverrideRequestSchema, ListGoogleAccountsRequestSchema, ListMicrosoftAccountsRequestSchema, ProbeQuotaRequestSchema, ClaimNotificationRequestSchema, GetExternalSpendRequestSchema, RequestSchema2, GetCredentialsDataSchema, AccountStateSchema, AgentStateSchema, ConsumerStateSchema, ListStateDataSchema, SetActiveDataSchema, MarkExhaustedDataSchema, MarkThrottledDataSchema, RefreshAccountDataSchema, AddAccountDataSchema, RmAccountDataSchema, SetOverrideDataSchema, ClaimNotificationDataSchema, GetExternalSpendDataSchema, GoogleAccountStateSchema, ListGoogleAccountsDataSchema, MicrosoftAccountStateSchema, ListMicrosoftAccountsDataSchema, ErrorBodySchema, SuccessResponseSchema, ErrorResponseSchema2, ResponseSchema2;
|
|
32696
32831
|
var init_protocol2 = __esm(() => {
|
|
32697
32832
|
init_zod();
|
|
32698
32833
|
MAX_FRAME_BYTES2 = 64 * 1024;
|
|
@@ -32823,6 +32958,12 @@ var init_protocol2 = __esm(() => {
|
|
|
32823
32958
|
key: exports_external.string().min(1).max(512),
|
|
32824
32959
|
windowMs: exports_external.number().int().positive().max(86400000)
|
|
32825
32960
|
});
|
|
32961
|
+
GetExternalSpendRequestSchema = exports_external.object({
|
|
32962
|
+
v: exports_external.literal(PROTOCOL_VERSION),
|
|
32963
|
+
op: exports_external.literal("get-external-spend"),
|
|
32964
|
+
id: exports_external.string().min(1),
|
|
32965
|
+
forceLive: exports_external.boolean().optional()
|
|
32966
|
+
});
|
|
32826
32967
|
RequestSchema2 = exports_external.discriminatedUnion("op", [
|
|
32827
32968
|
GetCredentialsRequestSchema,
|
|
32828
32969
|
ListStateRequestSchema,
|
|
@@ -32836,7 +32977,8 @@ var init_protocol2 = __esm(() => {
|
|
|
32836
32977
|
ListGoogleAccountsRequestSchema,
|
|
32837
32978
|
ListMicrosoftAccountsRequestSchema,
|
|
32838
32979
|
ProbeQuotaRequestSchema,
|
|
32839
|
-
ClaimNotificationRequestSchema
|
|
32980
|
+
ClaimNotificationRequestSchema,
|
|
32981
|
+
GetExternalSpendRequestSchema
|
|
32840
32982
|
]);
|
|
32841
32983
|
GetCredentialsDataSchema = exports_external.object({
|
|
32842
32984
|
account: exports_external.string(),
|
|
@@ -32847,6 +32989,8 @@ var init_protocol2 = __esm(() => {
|
|
|
32847
32989
|
label: exports_external.string(),
|
|
32848
32990
|
expiresAt: exports_external.number().optional(),
|
|
32849
32991
|
exhausted: exports_external.boolean(),
|
|
32992
|
+
in_service: exports_external.boolean().optional(),
|
|
32993
|
+
entitlement_blocked: exports_external.boolean().optional(),
|
|
32850
32994
|
exhausted_until: exports_external.number().optional(),
|
|
32851
32995
|
throttled_until: exports_external.number().optional(),
|
|
32852
32996
|
threshold_violations: exports_external.number().int().nonnegative().optional(),
|
|
@@ -32903,6 +33047,18 @@ var init_protocol2 = __esm(() => {
|
|
|
32903
33047
|
ClaimNotificationDataSchema = exports_external.object({
|
|
32904
33048
|
granted: exports_external.boolean()
|
|
32905
33049
|
});
|
|
33050
|
+
GetExternalSpendDataSchema = exports_external.object({
|
|
33051
|
+
available: exports_external.boolean(),
|
|
33052
|
+
day24hUsd: exports_external.number().optional(),
|
|
33053
|
+
day7dUsd: exports_external.number().optional(),
|
|
33054
|
+
top: exports_external.array(exports_external.object({
|
|
33055
|
+
label: exports_external.string(),
|
|
33056
|
+
usd: exports_external.number()
|
|
33057
|
+
})).optional(),
|
|
33058
|
+
capturedAtMs: exports_external.number().int().nonnegative().optional(),
|
|
33059
|
+
served: exports_external.enum(["live", "cache"]).optional(),
|
|
33060
|
+
reason: exports_external.string().optional()
|
|
33061
|
+
});
|
|
32906
33062
|
GoogleAccountStateSchema = exports_external.object({
|
|
32907
33063
|
account: exports_external.string(),
|
|
32908
33064
|
expiresAt: exports_external.number(),
|
|
@@ -33073,6 +33229,15 @@ class AuthBrokerClient {
|
|
|
33073
33229
|
}
|
|
33074
33230
|
return parsed;
|
|
33075
33231
|
}
|
|
33232
|
+
async getExternalSpend(forceLive) {
|
|
33233
|
+
const data = await this.send({
|
|
33234
|
+
v: PROTOCOL_VERSION,
|
|
33235
|
+
id: randomUUID(),
|
|
33236
|
+
op: "get-external-spend",
|
|
33237
|
+
...forceLive ? { forceLive: true } : {}
|
|
33238
|
+
});
|
|
33239
|
+
return data;
|
|
33240
|
+
}
|
|
33076
33241
|
async setActive(account) {
|
|
33077
33242
|
const data = await this.send({
|
|
33078
33243
|
v: PROTOCOL_VERSION,
|
|
@@ -36497,7 +36662,8 @@ async function inspectBankHealth(mcpUrl, bankId, opts) {
|
|
|
36497
36662
|
pendingOperations: 0,
|
|
36498
36663
|
newestDocumentAt: null,
|
|
36499
36664
|
unextractedDocuments: [],
|
|
36500
|
-
mentalModels: []
|
|
36665
|
+
mentalModels: [],
|
|
36666
|
+
activeDirectiveCount: null
|
|
36501
36667
|
};
|
|
36502
36668
|
const stats = await getJson(`${base}/v1/default/banks/${bank}/stats`, opts);
|
|
36503
36669
|
if (!stats.ok)
|
|
@@ -36508,6 +36674,8 @@ async function inspectBankHealth(mcpUrl, bankId, opts) {
|
|
|
36508
36674
|
const models = await getJson(`${base}/v1/default/banks/${bank}/mental-models`, opts);
|
|
36509
36675
|
if (!models.ok)
|
|
36510
36676
|
return { ...empty, reason: models.reason };
|
|
36677
|
+
const directives = await getJson(`${base}/v1/default/banks/${bank}/directives?active_only=true`, opts);
|
|
36678
|
+
const activeDirectiveCount = directives.ok && Array.isArray(directives.data.items) ? directives.data.items.length : null;
|
|
36511
36679
|
const docItems = docs.data.items ?? [];
|
|
36512
36680
|
let newestDocumentAt = null;
|
|
36513
36681
|
const unextracted = [];
|
|
@@ -36534,6 +36702,7 @@ async function inspectBankHealth(mcpUrl, bankId, opts) {
|
|
|
36534
36702
|
pendingOperations: stats.data.pending_operations ?? 0,
|
|
36535
36703
|
newestDocumentAt,
|
|
36536
36704
|
unextractedDocuments: unextracted,
|
|
36705
|
+
activeDirectiveCount,
|
|
36537
36706
|
mentalModels: (models.data.items ?? []).filter((m) => typeof m?.id === "string" && typeof m?.name === "string").map((m) => {
|
|
36538
36707
|
const { basedOnCounts, totalSourceFacts, derivedFromModelIds } = summarizeBasedOn(m.reflect_response);
|
|
36539
36708
|
return {
|
|
@@ -41862,6 +42031,29 @@ var init_hindsight_tools = __esm(() => {
|
|
|
41862
42031
|
|
|
41863
42032
|
// src/cli/doctor-memory.ts
|
|
41864
42033
|
import { execFileSync as execFileSync18 } from "node:child_process";
|
|
42034
|
+
function classifyDirectiveCount(count, bankLabel) {
|
|
42035
|
+
if (count === null)
|
|
42036
|
+
return null;
|
|
42037
|
+
const name = `${bankLabel} directives`;
|
|
42038
|
+
if (count > MAX_DIRECTIVES) {
|
|
42039
|
+
const truncated = count - MAX_DIRECTIVES;
|
|
42040
|
+
return {
|
|
42041
|
+
name,
|
|
42042
|
+
status: "fail",
|
|
42043
|
+
detail: `${count} active directives \u2014 exceeds MAX_DIRECTIVES=${MAX_DIRECTIVES}, so the ` + `${truncated} lowest-priority directive(s) are SILENTLY truncated from the ` + `<active_directives> recall block and never reach the agent`,
|
|
42044
|
+
fix: "Retire or merge stale directives so the active count is at or below " + `${MAX_DIRECTIVES} (deletes stay operator-approved). The mental-model-curator ` + "skill's directive merge/retire pass is the durable path."
|
|
42045
|
+
};
|
|
42046
|
+
}
|
|
42047
|
+
if (count > DIRECTIVE_WARN_THRESHOLD) {
|
|
42048
|
+
return {
|
|
42049
|
+
name,
|
|
42050
|
+
status: "warn",
|
|
42051
|
+
detail: `${count} active directives \u2014 approaching the MAX_DIRECTIVES=${MAX_DIRECTIVES} cap ` + `above which the recall block silently truncates the overflow`,
|
|
42052
|
+
fix: "Prune or merge low-value directives before the count crosses " + `${MAX_DIRECTIVES}. Review with \`switchroom memory --directives <bank>\`.`
|
|
42053
|
+
};
|
|
42054
|
+
}
|
|
42055
|
+
return null;
|
|
42056
|
+
}
|
|
41865
42057
|
function classifyShmSize(bytes) {
|
|
41866
42058
|
const mib = Math.round(bytes / 1024 / 1024);
|
|
41867
42059
|
if (bytes < MIN_HINDSIGHT_SHM_BYTES) {
|
|
@@ -41964,9 +42156,124 @@ function classifyAutohealStatus(logs) {
|
|
|
41964
42156
|
}
|
|
41965
42157
|
return { name: "hindsight autoheal", status: "ok", detail: "no auto-restarts" };
|
|
41966
42158
|
}
|
|
42159
|
+
function classifyLlmVerification(logs) {
|
|
42160
|
+
const latestFailed = new Map;
|
|
42161
|
+
const failDetail = new Map;
|
|
42162
|
+
let sawAny = false;
|
|
42163
|
+
for (const line of logs.split(`
|
|
42164
|
+
`)) {
|
|
42165
|
+
const fail3 = line.match(/LLM connection verification failed for '([^']+)' config:\s*(.*?)(?:\.\s*Server will start|$)/);
|
|
42166
|
+
if (fail3) {
|
|
42167
|
+
sawAny = true;
|
|
42168
|
+
const cfg = fail3[1];
|
|
42169
|
+
latestFailed.set(cfg, true);
|
|
42170
|
+
failDetail.set(cfg, (fail3[2] ?? "").trim());
|
|
42171
|
+
continue;
|
|
42172
|
+
}
|
|
42173
|
+
if (/connection verified successfully/.test(line) || /LLM verification passed/.test(line)) {
|
|
42174
|
+
sawAny = true;
|
|
42175
|
+
latestFailed.set("default", false);
|
|
42176
|
+
}
|
|
42177
|
+
}
|
|
42178
|
+
if (!sawAny)
|
|
42179
|
+
return null;
|
|
42180
|
+
const failedConfigs = [...latestFailed.entries()].filter(([, failed]) => failed).map(([cfg]) => cfg);
|
|
42181
|
+
if (failedConfigs.length === 0) {
|
|
42182
|
+
return {
|
|
42183
|
+
name: "hindsight LLM verification",
|
|
42184
|
+
status: "ok",
|
|
42185
|
+
detail: "boot LLM connection verified"
|
|
42186
|
+
};
|
|
42187
|
+
}
|
|
42188
|
+
const named = failedConfigs.map((cfg) => {
|
|
42189
|
+
const err = failDetail.get(cfg);
|
|
42190
|
+
return err ? `'${cfg}' (${err})` : `'${cfg}'`;
|
|
42191
|
+
}).join(", ");
|
|
42192
|
+
return {
|
|
42193
|
+
name: "hindsight LLM verification",
|
|
42194
|
+
status: "fail",
|
|
42195
|
+
detail: `boot LLM connection verification FAILED for config(s): ${named} \u2014 ` + `hindsight is serving but every LLM-dependent op (retain/reflect/` + `consolidation extraction) for that config will fail silently until the ` + `provider is reachable`,
|
|
42196
|
+
fix: "Almost always a model-name drift: the pinned `hindsight.llm.*.model` no " + "longer exists in the LiteLLM proxy's model_list (run `switchroom doctor` " + "\u2014 the LiteLLM model-routing check names the exact missing model), or the " + "provider is down. Fix the model reference (or the proxy), then " + "`switchroom memory --restart` so hindsight re-verifies."
|
|
42197
|
+
};
|
|
42198
|
+
}
|
|
42199
|
+
function classifyHindsightDataVolumeMounts(names) {
|
|
42200
|
+
const uniq = [...new Set(names.filter(Boolean))];
|
|
42201
|
+
if (uniq.length === 0) {
|
|
42202
|
+
return {
|
|
42203
|
+
name: "hindsight data-volume exclusive",
|
|
42204
|
+
status: "ok",
|
|
42205
|
+
detail: "no container currently mounts the live data volume"
|
|
42206
|
+
};
|
|
42207
|
+
}
|
|
42208
|
+
const twins = uniq.filter((n) => n !== HINDSIGHT_DEFAULT_WORKER_ID);
|
|
42209
|
+
if (uniq.length === 1 && uniq[0] === HINDSIGHT_DEFAULT_WORKER_ID) {
|
|
42210
|
+
return {
|
|
42211
|
+
name: "hindsight data-volume exclusive",
|
|
42212
|
+
status: "ok",
|
|
42213
|
+
detail: `only ${HINDSIGHT_DEFAULT_WORKER_ID} mounts ${HINDSIGHT_DATA_VOLUME}`
|
|
42214
|
+
};
|
|
42215
|
+
}
|
|
42216
|
+
if (twins.length > 0) {
|
|
42217
|
+
return {
|
|
42218
|
+
name: "hindsight data-volume exclusive",
|
|
42219
|
+
status: "fail",
|
|
42220
|
+
detail: `multiple containers mount ${HINDSIGHT_DATA_VOLUME}: [${uniq.join(", ")}] \u2014 ` + `dual postmasters corrupt the embedded PG checkpoint`,
|
|
42221
|
+
fix: "`switchroom memory --restart` (stopHindsight now disables restart + removes " + "every volume twin), or manually `docker update --restart=no <twin> && " + "docker rm -f <twin>` before starting the live container."
|
|
42222
|
+
};
|
|
42223
|
+
}
|
|
42224
|
+
return {
|
|
42225
|
+
name: "hindsight data-volume exclusive",
|
|
42226
|
+
status: "warn",
|
|
42227
|
+
detail: `unexpected volume mount set: [${uniq.join(", ")}]`
|
|
42228
|
+
};
|
|
42229
|
+
}
|
|
42230
|
+
function classifyHindsightNetworkMode(networkMode, litellmConfigured) {
|
|
42231
|
+
if (!litellmConfigured) {
|
|
42232
|
+
return {
|
|
42233
|
+
name: "hindsight network mode",
|
|
42234
|
+
status: "ok",
|
|
42235
|
+
detail: networkMode ? `${networkMode} (LiteLLM not configured)` : "unknown (LiteLLM not configured)"
|
|
42236
|
+
};
|
|
42237
|
+
}
|
|
42238
|
+
const mode = (networkMode ?? "").trim() || "unknown";
|
|
42239
|
+
if (mode === "host") {
|
|
42240
|
+
return {
|
|
42241
|
+
name: "hindsight network mode",
|
|
42242
|
+
status: "ok",
|
|
42243
|
+
detail: "host \u2014 LiteLLM 127.0.0.1 reachable"
|
|
42244
|
+
};
|
|
42245
|
+
}
|
|
42246
|
+
return {
|
|
42247
|
+
name: "hindsight network mode",
|
|
42248
|
+
status: "fail",
|
|
42249
|
+
detail: `NetworkMode=${mode} while LiteLLM routing is configured \u2014 retains will die with ` + `"Connection error" to 127.0.0.1:4010 while /health stays green`,
|
|
42250
|
+
fix: "`switchroom memory --restart` so startHindsight recreates with --network host. " + "Do not `docker start` a bridge/mis-networked container."
|
|
42251
|
+
};
|
|
42252
|
+
}
|
|
42253
|
+
function classifyLiteLlmReachability(reachable, endpoint) {
|
|
42254
|
+
if (reachable === null)
|
|
42255
|
+
return null;
|
|
42256
|
+
if (reachable) {
|
|
42257
|
+
return {
|
|
42258
|
+
name: "hindsight LiteLLM reachability",
|
|
42259
|
+
status: "ok",
|
|
42260
|
+
detail: `TCP ok to ${endpoint}`
|
|
42261
|
+
};
|
|
42262
|
+
}
|
|
42263
|
+
return {
|
|
42264
|
+
name: "hindsight LiteLLM reachability",
|
|
42265
|
+
status: "fail",
|
|
42266
|
+
detail: `cannot TCP-connect to ${endpoint} \u2014 hindsight LLM ops will fail`,
|
|
42267
|
+
fix: "Start/fix the LiteLLM proxy (host :4010), then `switchroom memory --restart` " + "if hindsight was started while it was down."
|
|
42268
|
+
};
|
|
42269
|
+
}
|
|
41967
42270
|
function checkHindsightContainerHealth(opts) {
|
|
41968
42271
|
const name = opts?.containerName ?? "switchroom-hindsight";
|
|
41969
|
-
const exec = opts?.exec ?? ((cmd, args) => execFileSync18(cmd, args, {
|
|
42272
|
+
const exec = opts?.exec ?? ((cmd, args) => execFileSync18(cmd, args, {
|
|
42273
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
42274
|
+
timeout: 8000,
|
|
42275
|
+
maxBuffer: 16 * 1024 * 1024
|
|
42276
|
+
}).toString());
|
|
41970
42277
|
const results = [];
|
|
41971
42278
|
let shmRaw;
|
|
41972
42279
|
try {
|
|
@@ -41978,6 +42285,29 @@ function checkHindsightContainerHealth(opts) {
|
|
|
41978
42285
|
if (Number.isFinite(shmBytes) && shmBytes > 0) {
|
|
41979
42286
|
results.push(classifyShmSize(shmBytes));
|
|
41980
42287
|
}
|
|
42288
|
+
try {
|
|
42289
|
+
const startedAt = exec("docker", [
|
|
42290
|
+
"inspect",
|
|
42291
|
+
name,
|
|
42292
|
+
"--format",
|
|
42293
|
+
"{{.State.StartedAt}}"
|
|
42294
|
+
]).trim();
|
|
42295
|
+
const startMs = Date.parse(startedAt);
|
|
42296
|
+
if (Number.isFinite(startMs)) {
|
|
42297
|
+
const untilIso = new Date(startMs + 10 * 60000).toISOString();
|
|
42298
|
+
const bootLogs = exec("docker", [
|
|
42299
|
+
"logs",
|
|
42300
|
+
"--since",
|
|
42301
|
+
startedAt,
|
|
42302
|
+
"--until",
|
|
42303
|
+
untilIso,
|
|
42304
|
+
name
|
|
42305
|
+
]);
|
|
42306
|
+
const verifyRow = classifyLlmVerification(bootLogs);
|
|
42307
|
+
if (verifyRow)
|
|
42308
|
+
results.push(verifyRow);
|
|
42309
|
+
}
|
|
42310
|
+
} catch {}
|
|
41981
42311
|
try {
|
|
41982
42312
|
const logs = exec("docker", ["logs", "--since", "10m", name]);
|
|
41983
42313
|
results.push(classifyExtractionLogs(logs));
|
|
@@ -41986,6 +42316,69 @@ function checkHindsightContainerHealth(opts) {
|
|
|
41986
42316
|
const autohealLogs = exec("docker", ["logs", "--since", "1h", "switchroom-hindsight-autoheal"]);
|
|
41987
42317
|
results.push(classifyAutohealStatus(autohealLogs));
|
|
41988
42318
|
} catch {}
|
|
42319
|
+
try {
|
|
42320
|
+
const mounts = listHindsightDataVolumeMounts(exec);
|
|
42321
|
+
results.push(classifyHindsightDataVolumeMounts(mounts));
|
|
42322
|
+
} catch {}
|
|
42323
|
+
try {
|
|
42324
|
+
const netMode = exec("docker", [
|
|
42325
|
+
"inspect",
|
|
42326
|
+
name,
|
|
42327
|
+
"--format",
|
|
42328
|
+
"{{.HostConfig.NetworkMode}}"
|
|
42329
|
+
]).trim();
|
|
42330
|
+
const envBlob = exec("docker", [
|
|
42331
|
+
"inspect",
|
|
42332
|
+
name,
|
|
42333
|
+
"--format",
|
|
42334
|
+
"{{range .Config.Env}}{{println .}}{{end}}"
|
|
42335
|
+
]);
|
|
42336
|
+
const perOpBasePresent = /HINDSIGHT_API_(RETAIN|REFLECT|CONSOLIDATION)_LLM_BASE_URL=/.test(envBlob);
|
|
42337
|
+
const anthropicBaseMatch = envBlob.match(/(?:^|\n)ANTHROPIC_BASE_URL=(\S+)/);
|
|
42338
|
+
const anthropicLooksLikeLiteLlm = (() => {
|
|
42339
|
+
const raw = anthropicBaseMatch?.[1]?.trim();
|
|
42340
|
+
if (!raw)
|
|
42341
|
+
return false;
|
|
42342
|
+
try {
|
|
42343
|
+
const u = new URL(raw.includes("://") ? raw : `http://${raw}`);
|
|
42344
|
+
const port = u.port || (u.protocol === "https:" ? "443" : "80");
|
|
42345
|
+
const path5 = (u.pathname || "").replace(/\/+$/, "");
|
|
42346
|
+
if (port === "4010")
|
|
42347
|
+
return true;
|
|
42348
|
+
if (/\/anthropic$/i.test(path5))
|
|
42349
|
+
return true;
|
|
42350
|
+
if (/litellm/i.test(u.hostname || ""))
|
|
42351
|
+
return true;
|
|
42352
|
+
return false;
|
|
42353
|
+
} catch {
|
|
42354
|
+
return false;
|
|
42355
|
+
}
|
|
42356
|
+
})();
|
|
42357
|
+
const litellmConfigured = perOpBasePresent || anthropicLooksLikeLiteLlm;
|
|
42358
|
+
results.push(classifyHindsightNetworkMode(netMode, litellmConfigured));
|
|
42359
|
+
if (litellmConfigured) {
|
|
42360
|
+
const m = envBlob.match(/HINDSIGHT_API_RETAIN_LLM_BASE_URL=(\S+)/) ?? envBlob.match(/HINDSIGHT_API_REFLECT_LLM_BASE_URL=(\S+)/) ?? envBlob.match(/HINDSIGHT_API_CONSOLIDATION_LLM_BASE_URL=(\S+)/) ?? (anthropicLooksLikeLiteLlm ? anthropicBaseMatch : null);
|
|
42361
|
+
let endpoint = "127.0.0.1:4010";
|
|
42362
|
+
if (m) {
|
|
42363
|
+
try {
|
|
42364
|
+
const u = new URL(m[1].includes("://") ? m[1] : `http://${m[1]}`);
|
|
42365
|
+
endpoint = `${u.hostname || "127.0.0.1"}:${u.port || "80"}`;
|
|
42366
|
+
} catch {}
|
|
42367
|
+
}
|
|
42368
|
+
const [host, portStr] = endpoint.split(":");
|
|
42369
|
+
const port = Number(portStr) || 80;
|
|
42370
|
+
let reachable = null;
|
|
42371
|
+
try {
|
|
42372
|
+
exec("bash", ["-c", `echo > /dev/tcp/${host}/${port}`]);
|
|
42373
|
+
reachable = true;
|
|
42374
|
+
} catch {
|
|
42375
|
+
reachable = false;
|
|
42376
|
+
}
|
|
42377
|
+
const row = classifyLiteLlmReachability(reachable, endpoint);
|
|
42378
|
+
if (row)
|
|
42379
|
+
results.push(row);
|
|
42380
|
+
}
|
|
42381
|
+
} catch {}
|
|
41989
42382
|
return results;
|
|
41990
42383
|
}
|
|
41991
42384
|
function classifyHindsightHealthProbe(status, port) {
|
|
@@ -42073,12 +42466,145 @@ function classifyToolContract(advertised) {
|
|
|
42073
42466
|
}
|
|
42074
42467
|
return results;
|
|
42075
42468
|
}
|
|
42076
|
-
var MIN_HINDSIGHT_SHM_BYTES, CONSOLIDATION_BACKLOG_WARN = 25, CONSOLIDATION_BACKLOG_FAIL = 200;
|
|
42469
|
+
var MIN_HINDSIGHT_SHM_BYTES, MAX_DIRECTIVES = 15, DIRECTIVE_WARN_THRESHOLD = 12, CONSOLIDATION_BACKLOG_WARN = 25, CONSOLIDATION_BACKLOG_FAIL = 200;
|
|
42077
42470
|
var init_doctor_memory = __esm(() => {
|
|
42078
42471
|
init_hindsight_tools();
|
|
42472
|
+
init_hindsight();
|
|
42079
42473
|
MIN_HINDSIGHT_SHM_BYTES = 1024 * 1024 * 1024;
|
|
42080
42474
|
});
|
|
42081
42475
|
|
|
42476
|
+
// src/litellm/model-validation.ts
|
|
42477
|
+
function isExplicitLitellmRoute(model) {
|
|
42478
|
+
return model.includes("/");
|
|
42479
|
+
}
|
|
42480
|
+
function collectReferencedModels(config) {
|
|
42481
|
+
const refs = [];
|
|
42482
|
+
const litellmEnabled = config.litellm?.enabled === true;
|
|
42483
|
+
if (!litellmEnabled)
|
|
42484
|
+
return refs;
|
|
42485
|
+
const llm = config.hindsight?.llm;
|
|
42486
|
+
if (llm) {
|
|
42487
|
+
const push = (model, consumer) => {
|
|
42488
|
+
if (model)
|
|
42489
|
+
refs.push({ model, consumer });
|
|
42490
|
+
};
|
|
42491
|
+
push(llm.model, "hindsight.llm.model (global)");
|
|
42492
|
+
push(llm.retain?.model, "hindsight.llm.retain");
|
|
42493
|
+
push(llm.reflect?.model, "hindsight.llm.reflect");
|
|
42494
|
+
push(llm.consolidation?.model, "hindsight.llm.consolidation");
|
|
42495
|
+
}
|
|
42496
|
+
for (const [name, agent] of Object.entries(config.agents ?? {})) {
|
|
42497
|
+
if (!agent)
|
|
42498
|
+
continue;
|
|
42499
|
+
const agentRoutes = agent.litellm?.enabled !== false;
|
|
42500
|
+
if (!agentRoutes)
|
|
42501
|
+
continue;
|
|
42502
|
+
if (agent.model && isExplicitLitellmRoute(agent.model)) {
|
|
42503
|
+
refs.push({ model: agent.model, consumer: `agents.${name}.model` });
|
|
42504
|
+
}
|
|
42505
|
+
if (agent.fallback_model && isExplicitLitellmRoute(agent.fallback_model)) {
|
|
42506
|
+
refs.push({ model: agent.fallback_model, consumer: `agents.${name}.fallback_model` });
|
|
42507
|
+
}
|
|
42508
|
+
}
|
|
42509
|
+
return refs;
|
|
42510
|
+
}
|
|
42511
|
+
function validateModelReferences(refs, proxyModels) {
|
|
42512
|
+
const missing = new Map;
|
|
42513
|
+
for (const { model, consumer } of refs) {
|
|
42514
|
+
if (proxyModels.has(model))
|
|
42515
|
+
continue;
|
|
42516
|
+
const list = missing.get(model);
|
|
42517
|
+
if (list) {
|
|
42518
|
+
if (!list.includes(consumer))
|
|
42519
|
+
list.push(consumer);
|
|
42520
|
+
} else {
|
|
42521
|
+
missing.set(model, [consumer]);
|
|
42522
|
+
}
|
|
42523
|
+
}
|
|
42524
|
+
return [...missing.entries()].map(([model, consumers]) => ({ model, consumers }));
|
|
42525
|
+
}
|
|
42526
|
+
async function fetchProxyModels(baseUrl, apiKey, fetchFn = fetch, timeoutMs = 8000) {
|
|
42527
|
+
const url = `${baseUrl.replace(/\/+$/, "")}/v1/models`;
|
|
42528
|
+
const controller = new AbortController;
|
|
42529
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
42530
|
+
try {
|
|
42531
|
+
const res = await fetchFn(url, {
|
|
42532
|
+
method: "GET",
|
|
42533
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
42534
|
+
...{ signal: controller.signal }
|
|
42535
|
+
});
|
|
42536
|
+
if (!res.ok) {
|
|
42537
|
+
return { kind: "unreachable", msg: `proxy returned HTTP ${res.status} for ${url}` };
|
|
42538
|
+
}
|
|
42539
|
+
const body = await res.json();
|
|
42540
|
+
const data = body?.data;
|
|
42541
|
+
if (!Array.isArray(data)) {
|
|
42542
|
+
return { kind: "unreachable", msg: `proxy ${url} response had no model \`data\` array` };
|
|
42543
|
+
}
|
|
42544
|
+
const models = data.map((m) => m?.id).filter((id) => typeof id === "string");
|
|
42545
|
+
return { kind: "ok", models };
|
|
42546
|
+
} catch (err) {
|
|
42547
|
+
return { kind: "unreachable", msg: err.message ?? String(err) };
|
|
42548
|
+
} finally {
|
|
42549
|
+
clearTimeout(timer);
|
|
42550
|
+
}
|
|
42551
|
+
}
|
|
42552
|
+
function classifyModelReferences(missing, refCount) {
|
|
42553
|
+
if (missing.length === 0) {
|
|
42554
|
+
return {
|
|
42555
|
+
name: "litellm model routing",
|
|
42556
|
+
status: "ok",
|
|
42557
|
+
detail: `all ${refCount} referenced model(s) present in the proxy model_list`
|
|
42558
|
+
};
|
|
42559
|
+
}
|
|
42560
|
+
const detail = missing.map((m) => `\`${m.model}\` (referenced by ${m.consumers.join(", ")})`).join("; ");
|
|
42561
|
+
return {
|
|
42562
|
+
name: "litellm model routing",
|
|
42563
|
+
status: "fail",
|
|
42564
|
+
detail: `${missing.length} model reference(s) NOT in the LiteLLM proxy model_list: ` + `${detail} \u2014 every LLM call from that consumer will 400 ("Invalid model ` + `name") until the proxy lists it or the reference is corrected`,
|
|
42565
|
+
fix: "Either add the missing model group back to the LiteLLM proxy config " + "(the operator-maintained `litellm-config.yaml` `model_list`) and redeploy " + "the proxy, or repoint the consumer at a model the proxy DOES route " + "(`switchroom doctor` lists them via the proxy `/v1/models`). For " + "hindsight, then `switchroom memory --restart` so it re-verifies."
|
|
42566
|
+
};
|
|
42567
|
+
}
|
|
42568
|
+
async function runLitellmModelChecks(config, opts) {
|
|
42569
|
+
const refs = collectReferencedModels(config);
|
|
42570
|
+
if (refs.length === 0)
|
|
42571
|
+
return [];
|
|
42572
|
+
const litellm = config.litellm;
|
|
42573
|
+
const baseUrl = litellm?.base_url;
|
|
42574
|
+
const adminKeyRef = litellm?.admin_key;
|
|
42575
|
+
if (!baseUrl || !adminKeyRef) {
|
|
42576
|
+
return [
|
|
42577
|
+
{
|
|
42578
|
+
name: "litellm model routing",
|
|
42579
|
+
status: "warn",
|
|
42580
|
+
detail: `${refs.length} model reference(s) to validate but litellm ` + `${!baseUrl ? "base_url" : "admin_key"} is unresolved \u2014 cannot query ` + `the proxy model_list`
|
|
42581
|
+
}
|
|
42582
|
+
];
|
|
42583
|
+
}
|
|
42584
|
+
const apiKey = opts.resolveSecret(adminKeyRef);
|
|
42585
|
+
if (!apiKey) {
|
|
42586
|
+
return [
|
|
42587
|
+
{
|
|
42588
|
+
name: "litellm model routing",
|
|
42589
|
+
status: "warn",
|
|
42590
|
+
detail: `could not resolve the litellm admin_key (\`${adminKeyRef}\`) to query ` + `the proxy model_list \u2014 ${refs.length} model reference(s) left unverified`
|
|
42591
|
+
}
|
|
42592
|
+
];
|
|
42593
|
+
}
|
|
42594
|
+
const probe2 = await fetchProxyModels(baseUrl, apiKey, opts.fetchFn);
|
|
42595
|
+
if (probe2.kind === "unreachable") {
|
|
42596
|
+
return [
|
|
42597
|
+
{
|
|
42598
|
+
name: "litellm model routing",
|
|
42599
|
+
status: "warn",
|
|
42600
|
+
detail: `LiteLLM proxy unreachable (${probe2.msg}) \u2014 ${refs.length} model ` + `reference(s) left unverified; not failing on an environment limitation`
|
|
42601
|
+
}
|
|
42602
|
+
];
|
|
42603
|
+
}
|
|
42604
|
+
const missing = validateModelReferences(refs, new Set(probe2.models));
|
|
42605
|
+
return [classifyModelReferences(missing, refs.length)];
|
|
42606
|
+
}
|
|
42607
|
+
|
|
42082
42608
|
// src/cli/doctor-docker.ts
|
|
42083
42609
|
import { readFileSync as readFileSync56 } from "node:fs";
|
|
42084
42610
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
@@ -45264,6 +45790,69 @@ var init_doctor_timezone = __esm(() => {
|
|
|
45264
45790
|
init_timezone();
|
|
45265
45791
|
});
|
|
45266
45792
|
|
|
45793
|
+
// src/cli/doctor-fix-session-model.ts
|
|
45794
|
+
import { join as join67 } from "node:path";
|
|
45795
|
+
function describeReconcileChanges(changes) {
|
|
45796
|
+
if (changes.length === 0)
|
|
45797
|
+
return "Reconcile rewrote no other files.";
|
|
45798
|
+
const MAX_LISTED = 6;
|
|
45799
|
+
const listed = changes.slice(0, MAX_LISTED).join(", ");
|
|
45800
|
+
const more = changes.length > MAX_LISTED ? ` (+${changes.length - MAX_LISTED} more)` : "";
|
|
45801
|
+
return `Full per-agent reconcile applied \u2014 rewrote ${changes.length} managed file(s): ` + listed + more;
|
|
45802
|
+
}
|
|
45803
|
+
function fixSessionModelCarrierDrift(config, deps) {
|
|
45804
|
+
const agentsDir = resolveAgentsDir(config);
|
|
45805
|
+
const results = [];
|
|
45806
|
+
for (const name of Object.keys(config.agents ?? {})) {
|
|
45807
|
+
const startShPath = join67(agentsDir, name, "start.sh");
|
|
45808
|
+
const label = `${name}: start.sh /model session carrier (--fix)`;
|
|
45809
|
+
const pre = deps.check(name, startShPath);
|
|
45810
|
+
if (pre.status === "ok") {
|
|
45811
|
+
results.push({
|
|
45812
|
+
name: label,
|
|
45813
|
+
status: "ok",
|
|
45814
|
+
detail: "rev5 carrier already present \u2014 nothing to fix"
|
|
45815
|
+
});
|
|
45816
|
+
continue;
|
|
45817
|
+
}
|
|
45818
|
+
if (pre.status === "skip") {
|
|
45819
|
+
results.push({ ...pre, name: label });
|
|
45820
|
+
continue;
|
|
45821
|
+
}
|
|
45822
|
+
let reconcileChanges;
|
|
45823
|
+
try {
|
|
45824
|
+
reconcileChanges = deps.reconcile(name).changes;
|
|
45825
|
+
} catch (err) {
|
|
45826
|
+
results.push({
|
|
45827
|
+
name: label,
|
|
45828
|
+
status: "fail",
|
|
45829
|
+
detail: `drift detected (${pre.detail ?? "carrier check failed"}) but reconcile failed: ${err.message}`,
|
|
45830
|
+
fix: "Fix the reconcile error, or run `switchroom apply` manually."
|
|
45831
|
+
});
|
|
45832
|
+
continue;
|
|
45833
|
+
}
|
|
45834
|
+
const post = deps.check(name, startShPath);
|
|
45835
|
+
if (post.status === "ok") {
|
|
45836
|
+
results.push({
|
|
45837
|
+
name: label,
|
|
45838
|
+
status: "ok",
|
|
45839
|
+
detail: "healed: start.sh regenerated with the rev5 `.session-model` carrier \u2014 takes effect on the agent's next restart. " + describeReconcileChanges(reconcileChanges)
|
|
45840
|
+
});
|
|
45841
|
+
} else {
|
|
45842
|
+
results.push({
|
|
45843
|
+
name: label,
|
|
45844
|
+
status: "fail",
|
|
45845
|
+
detail: `regenerated start.sh still fails the carrier check: ${post.detail ?? post.status} \u2014 the installed template may be stale`,
|
|
45846
|
+
fix: "Update switchroom (the bundled profiles/_base/start.sh.hbs must carry the rev5 carrier), then re-run `switchroom doctor --fix`."
|
|
45847
|
+
});
|
|
45848
|
+
}
|
|
45849
|
+
}
|
|
45850
|
+
return results;
|
|
45851
|
+
}
|
|
45852
|
+
var init_doctor_fix_session_model = __esm(() => {
|
|
45853
|
+
init_loader();
|
|
45854
|
+
});
|
|
45855
|
+
|
|
45267
45856
|
// src/cli/doctor.ts
|
|
45268
45857
|
var exports_doctor = {};
|
|
45269
45858
|
__export(exports_doctor, {
|
|
@@ -45289,6 +45878,7 @@ __export(exports_doctor, {
|
|
|
45289
45878
|
checkTelegram: () => checkTelegram,
|
|
45290
45879
|
checkTcp: () => checkTcp,
|
|
45291
45880
|
checkStartShStale: () => checkStartShStale,
|
|
45881
|
+
checkStartShSessionModelCarrier: () => checkStartShSessionModelCarrier,
|
|
45292
45882
|
checkSkillsPrerequisites: () => checkSkillsPrerequisites,
|
|
45293
45883
|
checkRepoHygiene: () => checkRepoHygiene,
|
|
45294
45884
|
checkPendingRetainsQueues: () => checkPendingRetainsQueues,
|
|
@@ -45322,16 +45912,16 @@ import {
|
|
|
45322
45912
|
readdirSync as readdirSync23,
|
|
45323
45913
|
statSync as statSync38
|
|
45324
45914
|
} from "node:fs";
|
|
45325
|
-
import { dirname as dirname20, join as
|
|
45915
|
+
import { dirname as dirname20, join as join68, resolve as resolve39 } from "node:path";
|
|
45326
45916
|
import { createPublicKey, createPrivateKey } from "node:crypto";
|
|
45327
45917
|
function findInNvm(bin) {
|
|
45328
|
-
const nvmRoot =
|
|
45918
|
+
const nvmRoot = join68(process.env.HOME ?? "", ".nvm", "versions", "node");
|
|
45329
45919
|
if (!existsSync66(nvmRoot))
|
|
45330
45920
|
return null;
|
|
45331
45921
|
try {
|
|
45332
45922
|
const versions = readdirSync23(nvmRoot).sort().reverse();
|
|
45333
45923
|
for (const v of versions) {
|
|
45334
|
-
const candidate =
|
|
45924
|
+
const candidate = join68(nvmRoot, v, "bin", bin);
|
|
45335
45925
|
try {
|
|
45336
45926
|
const s = statSync38(candidate);
|
|
45337
45927
|
if (s.isFile() || s.isSymbolicLink()) {
|
|
@@ -45358,7 +45948,7 @@ function whichOnPath(bin) {
|
|
|
45358
45948
|
for (const dir of pathEnv.split(":")) {
|
|
45359
45949
|
if (!dir)
|
|
45360
45950
|
continue;
|
|
45361
|
-
const candidate =
|
|
45951
|
+
const candidate = join68(dir, bin);
|
|
45362
45952
|
if (isX(candidate))
|
|
45363
45953
|
return candidate;
|
|
45364
45954
|
}
|
|
@@ -45524,7 +46114,7 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
|
|
|
45524
46114
|
if (envBrowsersPath && envBrowsersPath.length > 0) {
|
|
45525
46115
|
cacheLocations.push(envBrowsersPath);
|
|
45526
46116
|
}
|
|
45527
|
-
cacheLocations.push(
|
|
46117
|
+
cacheLocations.push(join68(homeDir, ".cache", "ms-playwright"));
|
|
45528
46118
|
for (const cacheDir of cacheLocations) {
|
|
45529
46119
|
if (!existsSync66(cacheDir))
|
|
45530
46120
|
continue;
|
|
@@ -45532,10 +46122,10 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
|
|
|
45532
46122
|
const entries = readdirSync23(cacheDir).filter((e) => e.startsWith("chromium"));
|
|
45533
46123
|
for (const entry of entries) {
|
|
45534
46124
|
const candidates2 = [
|
|
45535
|
-
|
|
45536
|
-
|
|
45537
|
-
|
|
45538
|
-
|
|
46125
|
+
join68(cacheDir, entry, "chrome-linux64", "chrome"),
|
|
46126
|
+
join68(cacheDir, entry, "chrome-linux", "chrome"),
|
|
46127
|
+
join68(cacheDir, entry, "chrome-linux64", "headless_shell"),
|
|
46128
|
+
join68(cacheDir, entry, "chrome-linux", "headless_shell")
|
|
45539
46129
|
];
|
|
45540
46130
|
for (const path5 of candidates2) {
|
|
45541
46131
|
if (existsSync66(path5))
|
|
@@ -45667,7 +46257,7 @@ function checkDeployMounts(opts) {
|
|
|
45667
46257
|
const home2 = opts?.home ?? process.env.HOME ?? "/root";
|
|
45668
46258
|
const { pathKind } = opts?.deps ?? DEFAULT_DEPLOY_MOUNTS_DEPS;
|
|
45669
46259
|
const results = [];
|
|
45670
|
-
const dockerComposePlugin =
|
|
46260
|
+
const dockerComposePlugin = join68(home2, ".docker", "cli-plugins", "docker-compose");
|
|
45671
46261
|
const pluginKind = pathKind(dockerComposePlugin);
|
|
45672
46262
|
if (pluginKind === "dir") {
|
|
45673
46263
|
results.push({
|
|
@@ -45705,7 +46295,7 @@ function checkDeployMounts(opts) {
|
|
|
45705
46295
|
function checkLegacyState() {
|
|
45706
46296
|
const results = [];
|
|
45707
46297
|
const h = process.env.HOME ?? "/root";
|
|
45708
|
-
const clerkDir =
|
|
46298
|
+
const clerkDir = join68(h, LEGACY_STATE_DIR);
|
|
45709
46299
|
const clerkPresent = existsSync66(clerkDir);
|
|
45710
46300
|
results.push({
|
|
45711
46301
|
name: "legacy ~/.clerk state",
|
|
@@ -45715,7 +46305,7 @@ function checkLegacyState() {
|
|
|
45715
46305
|
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."
|
|
45716
46306
|
} : {}
|
|
45717
46307
|
});
|
|
45718
|
-
const legacySock =
|
|
46308
|
+
const legacySock = join68(h, ".switchroom", "vault-broker.sock");
|
|
45719
46309
|
let sockStat = null;
|
|
45720
46310
|
try {
|
|
45721
46311
|
sockStat = lstatSync9(legacySock);
|
|
@@ -45979,6 +46569,9 @@ async function checkBankIngestHealth(config, url, opts) {
|
|
|
45979
46569
|
});
|
|
45980
46570
|
continue;
|
|
45981
46571
|
}
|
|
46572
|
+
const directiveRow = classifyDirectiveCount(h.activeDirectiveCount, label);
|
|
46573
|
+
if (directiveRow)
|
|
46574
|
+
results.push(directiveRow);
|
|
45982
46575
|
if (h.totalDocuments === 0) {
|
|
45983
46576
|
results.push({
|
|
45984
46577
|
name: label,
|
|
@@ -46281,7 +46874,7 @@ async function checkTelegram(config) {
|
|
|
46281
46874
|
const plugin = agentConfig.channels?.telegram?.plugin ?? "switchroom";
|
|
46282
46875
|
if (plugin !== "switchroom")
|
|
46283
46876
|
continue;
|
|
46284
|
-
const envPath =
|
|
46877
|
+
const envPath = join68(agentsDir, name, "telegram", ".env");
|
|
46285
46878
|
const read = tryReadHostFile(envPath);
|
|
46286
46879
|
if (read.kind === "eacces") {
|
|
46287
46880
|
results.push({
|
|
@@ -46362,9 +46955,56 @@ function checkStartShStale(agentName, startShPath) {
|
|
|
46362
46955
|
}
|
|
46363
46956
|
return { name: label, status: "ok", detail: "supervisor block present" };
|
|
46364
46957
|
}
|
|
46958
|
+
function checkStartShSessionModelCarrier(agentName, startShPath) {
|
|
46959
|
+
const label = `${agentName}: start.sh /model session carrier`;
|
|
46960
|
+
if (!existsSync66(startShPath)) {
|
|
46961
|
+
return {
|
|
46962
|
+
name: label,
|
|
46963
|
+
status: "warn",
|
|
46964
|
+
detail: `${startShPath} not found`,
|
|
46965
|
+
fix: `Run \`switchroom doctor --fix\` (or \`switchroom apply\`) to scaffold start.sh (rev5 \`.session-model\` carrier).`
|
|
46966
|
+
};
|
|
46967
|
+
}
|
|
46968
|
+
let content;
|
|
46969
|
+
try {
|
|
46970
|
+
content = readFileSync59(startShPath, "utf-8");
|
|
46971
|
+
} catch (err) {
|
|
46972
|
+
return {
|
|
46973
|
+
name: label,
|
|
46974
|
+
status: "skip",
|
|
46975
|
+
detail: `unreadable from host (${err.message}) \u2014 agent-UID-owned; the session-model carrier block can't be checked from the operator UID`,
|
|
46976
|
+
fix: `Verify in-agent: docker exec switchroom-${agentName} sh -c 'grep -E "\\[ -f .*\\.session-model\\" \\]" /state/agent/start.sh && echo ok'`
|
|
46977
|
+
};
|
|
46978
|
+
}
|
|
46979
|
+
const fileTestPaths = [...content.matchAll(/\[\s*-f\s+([^\]]+?)\s*\]/g)].map((m) => m[1].replace(/["']/g, "").trim());
|
|
46980
|
+
const hasBareSessionModelTest = fileTestPaths.some((p) => /(?:^|\/)\.session-model$/.test(p));
|
|
46981
|
+
const hasJsonCarrierParse = /configuredDefaultAtWrite/.test(content) || /session-model-boot-attempts/.test(content);
|
|
46982
|
+
if (hasBareSessionModelTest && hasJsonCarrierParse) {
|
|
46983
|
+
return {
|
|
46984
|
+
name: label,
|
|
46985
|
+
status: "ok",
|
|
46986
|
+
detail: "rev5 `.session-model` consume-once carrier present"
|
|
46987
|
+
};
|
|
46988
|
+
}
|
|
46989
|
+
if (hasBareSessionModelTest && !hasJsonCarrierParse) {
|
|
46990
|
+
return {
|
|
46991
|
+
name: label,
|
|
46992
|
+
status: "fail",
|
|
46993
|
+
detail: "start.sh tests `.session-model` but lacks rev5 JSON apply (`configuredDefaultAtWrite` / boot-attempts) \u2014 /model switches may not stick",
|
|
46994
|
+
fix: "Run `switchroom doctor --fix` (or `switchroom apply`) to regenerate start.sh from profiles/_base/start.sh.hbs (rev5 carrier). Takes effect on the next agent restart (no need to bounce until release)."
|
|
46995
|
+
};
|
|
46996
|
+
}
|
|
46997
|
+
const legacyOnly = /\.session-model-override/.test(content) && !hasBareSessionModelTest;
|
|
46998
|
+
return {
|
|
46999
|
+
name: label,
|
|
47000
|
+
status: "fail",
|
|
47001
|
+
detail: legacyOnly ? "start.sh only reads legacy `.session-model-override`; gateway writes rev5 `.session-model` \u2014 Telegram /model relaunches boot the yaml pin (silent miss)" : "start.sh missing rev5 `.session-model` carrier file test \u2014 Telegram /model cannot apply session overrides across relaunch",
|
|
47002
|
+
fix: "Run `switchroom doctor --fix` (or `switchroom apply`) to regenerate start.sh from the latest template (rev5 `.session-model` carrier). The rewrite is on disk immediately; session switches apply on the next agent restart/release bounce."
|
|
47003
|
+
};
|
|
47004
|
+
}
|
|
46365
47005
|
function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
46366
47006
|
const label = `${agentName}: $HOME/.switchroom symlink (#910)`;
|
|
46367
|
-
const path5 =
|
|
47007
|
+
const path5 = join68(agentDir, "home", ".switchroom");
|
|
46368
47008
|
let stats;
|
|
46369
47009
|
try {
|
|
46370
47010
|
stats = lstatSync9(path5);
|
|
@@ -46401,7 +47041,7 @@ function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
|
46401
47041
|
}
|
|
46402
47042
|
function checkRepoHygiene(repoRoot) {
|
|
46403
47043
|
const results = [];
|
|
46404
|
-
const exportDir =
|
|
47044
|
+
const exportDir = join68(repoRoot, "clerk-export");
|
|
46405
47045
|
if (existsSync66(exportDir)) {
|
|
46406
47046
|
results.push({
|
|
46407
47047
|
name: "repo hygiene: clerk-export/ on disk (#1072)",
|
|
@@ -46410,7 +47050,7 @@ function checkRepoHygiene(repoRoot) {
|
|
|
46410
47050
|
fix: `Run scripts/migrate-clerk-export-to-vault.sh to move the bundle ` + `into the vault, then delete the on-disk copy.`
|
|
46411
47051
|
});
|
|
46412
47052
|
}
|
|
46413
|
-
const knownTarball =
|
|
47053
|
+
const knownTarball = join68(repoRoot, "clerk-export-with-secrets.tar.gz");
|
|
46414
47054
|
if (existsSync66(knownTarball)) {
|
|
46415
47055
|
results.push({
|
|
46416
47056
|
name: "repo hygiene: clerk-export-with-secrets.tar.gz on disk (#1072)",
|
|
@@ -46428,7 +47068,7 @@ function checkRepoHygiene(repoRoot) {
|
|
|
46428
47068
|
results.push({
|
|
46429
47069
|
name: `repo hygiene: ${name} on disk (#1072)`,
|
|
46430
47070
|
status: "warn",
|
|
46431
|
-
detail: `${
|
|
47071
|
+
detail: `${join68(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
|
|
46432
47072
|
fix: `Inspect, migrate any secrets into the vault, then delete the ` + `archive.`
|
|
46433
47073
|
});
|
|
46434
47074
|
}
|
|
@@ -46451,9 +47091,9 @@ function checkRepoHygiene(repoRoot) {
|
|
|
46451
47091
|
}
|
|
46452
47092
|
function isSwitchroomCheckout(dir) {
|
|
46453
47093
|
try {
|
|
46454
|
-
if (!existsSync66(
|
|
47094
|
+
if (!existsSync66(join68(dir, ".git")))
|
|
46455
47095
|
return false;
|
|
46456
|
-
const pkgPath =
|
|
47096
|
+
const pkgPath = join68(dir, "package.json");
|
|
46457
47097
|
if (!existsSync66(pkgPath))
|
|
46458
47098
|
return false;
|
|
46459
47099
|
const pkg = JSON.parse(readFileSync59(pkgPath, "utf-8"));
|
|
@@ -46490,7 +47130,8 @@ function checkAgents(config, configPath) {
|
|
|
46490
47130
|
fix: `Rotate the bot token (e.g. via \`switchroom vault\`), then run ` + `\`switchroom agent unquarantine ${name}\` and \`switchroom agent restart ${name}\``
|
|
46491
47131
|
});
|
|
46492
47132
|
}
|
|
46493
|
-
results.push(checkStartShStale(name,
|
|
47133
|
+
results.push(checkStartShStale(name, join68(agentDir, "start.sh")));
|
|
47134
|
+
results.push(checkStartShSessionModelCarrier(name, join68(agentDir, "start.sh")));
|
|
46494
47135
|
results.push(checkLeakedHomeSwitchroom(name, agentDir));
|
|
46495
47136
|
const status = statuses[name];
|
|
46496
47137
|
const active = status?.active ?? "unknown";
|
|
@@ -46567,7 +47208,7 @@ function checkAgents(config, configPath) {
|
|
|
46567
47208
|
}
|
|
46568
47209
|
}
|
|
46569
47210
|
if (agentConfig.channels?.telegram?.plugin === "switchroom") {
|
|
46570
|
-
const mcpJsonPath =
|
|
47211
|
+
const mcpJsonPath = join68(agentDir, ".mcp.json");
|
|
46571
47212
|
if (!existsSync66(mcpJsonPath)) {
|
|
46572
47213
|
results.push({
|
|
46573
47214
|
name: `${name}: .mcp.json`,
|
|
@@ -46869,7 +47510,7 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
|
|
|
46869
47510
|
};
|
|
46870
47511
|
}
|
|
46871
47512
|
const credDir = dirname20(envPath);
|
|
46872
|
-
const authScript =
|
|
47513
|
+
const authScript = join68(credDir, "claude-auth.py");
|
|
46873
47514
|
if (!existsSync66(authScript)) {
|
|
46874
47515
|
return {
|
|
46875
47516
|
name: "mff: auth flow",
|
|
@@ -47086,7 +47727,7 @@ function runDockerSection(config) {
|
|
|
47086
47727
|
});
|
|
47087
47728
|
}
|
|
47088
47729
|
function registerDoctorCommand(program3) {
|
|
47089
|
-
program3.command("doctor").description("Diagnose Switchroom's setup: deps, vault, memory, agents, MCP wireup").option("--json", "Output as JSON").option("--skill <name>", "Run probes for a specific skill only (e.g. mff)").option("--fast", "Skip in-agent (hostd) liveness probes \u2014 offline/quick; the host-unverifiable rows stay `skip`").action(withConfigError(async (opts) => {
|
|
47730
|
+
program3.command("doctor").description("Diagnose Switchroom's setup: deps, vault, memory, agents, MCP wireup").option("--json", "Output as JSON").option("--skill <name>", "Run probes for a specific skill only (e.g. mff)").option("--fast", "Skip in-agent (hostd) liveness probes \u2014 offline/quick; the host-unverifiable rows stay `skip`").option("--fix", "Auto-remediate rev5 /model `.session-model` carrier drift by running the FULL per-agent reconcile for each drifted agent (start.sh + all switchroom-managed files, same as `switchroom apply` for that agent; staged switchroom.yaml edits land too \u2014 files rewritten are listed in the result row). Takes effect on the agent's next restart").action(withConfigError(async (opts) => {
|
|
47090
47731
|
try {
|
|
47091
47732
|
getConfigPath(program3);
|
|
47092
47733
|
} catch (_e) {
|
|
@@ -47155,7 +47796,23 @@ function registerDoctorCommand(program3) {
|
|
|
47155
47796
|
return { kind: "unreachable", msg: err.message };
|
|
47156
47797
|
}
|
|
47157
47798
|
};
|
|
47799
|
+
const fixSections = opts.fix ? [
|
|
47800
|
+
{
|
|
47801
|
+
title: "Auto-remediation (--fix)",
|
|
47802
|
+
results: fixSessionModelCarrierDrift(config, {
|
|
47803
|
+
check: checkStartShSessionModelCarrier,
|
|
47804
|
+
reconcile: (name) => {
|
|
47805
|
+
const agentConfig = config.agents?.[name];
|
|
47806
|
+
if (!agentConfig)
|
|
47807
|
+
throw new Error(`agent ${name} not in config`);
|
|
47808
|
+
const result = reconcileAgent(name, agentConfig, resolveAgentsDir(config), config.telegram, config, configPath, { preserveClaudeMd: true });
|
|
47809
|
+
return { changes: result.changes };
|
|
47810
|
+
}
|
|
47811
|
+
})
|
|
47812
|
+
}
|
|
47813
|
+
] : [];
|
|
47158
47814
|
const sections = [
|
|
47815
|
+
...fixSections,
|
|
47159
47816
|
{ title: "Dependencies", results: checkDependencies() },
|
|
47160
47817
|
{ title: "Skills Prerequisites", results: checkSkillsPrerequisites() },
|
|
47161
47818
|
{ title: "Manifest Drift", results: await checkManifestDrift() },
|
|
@@ -47174,6 +47831,22 @@ function registerDoctorCommand(program3) {
|
|
|
47174
47831
|
},
|
|
47175
47832
|
{ title: "Vault access", results: await runSecretAccessChecks(config) },
|
|
47176
47833
|
{ title: "Memory (Hindsight)", results: await checkHindsight(config) },
|
|
47834
|
+
{
|
|
47835
|
+
title: "LiteLLM model routing (#3407)",
|
|
47836
|
+
results: await runLitellmModelChecks(config, {
|
|
47837
|
+
resolveSecret: (ref) => {
|
|
47838
|
+
if (!isVaultReference(ref))
|
|
47839
|
+
return ref;
|
|
47840
|
+
if (!passphrase || !existsSync66(vaultPath))
|
|
47841
|
+
return null;
|
|
47842
|
+
try {
|
|
47843
|
+
return getStringSecret(passphrase, vaultPath, parseVaultReference(ref));
|
|
47844
|
+
} catch {
|
|
47845
|
+
return null;
|
|
47846
|
+
}
|
|
47847
|
+
}
|
|
47848
|
+
})
|
|
47849
|
+
},
|
|
47177
47850
|
{ title: "Telegram", results: await checkTelegram(config) },
|
|
47178
47851
|
{ title: "Agents", results: checkAgents(config, configPath) },
|
|
47179
47852
|
{
|
|
@@ -47286,12 +47959,14 @@ var init_doctor = __esm(() => {
|
|
|
47286
47959
|
init_helpers();
|
|
47287
47960
|
init_lifecycle();
|
|
47288
47961
|
init_quarantine();
|
|
47962
|
+
init_scaffold();
|
|
47289
47963
|
init_manager();
|
|
47290
47964
|
init_accounts();
|
|
47291
47965
|
init_manifest();
|
|
47292
47966
|
init_hindsight2();
|
|
47293
47967
|
init_hindsight();
|
|
47294
47968
|
init_doctor_memory();
|
|
47969
|
+
init_resolver();
|
|
47295
47970
|
init_doctor_docker();
|
|
47296
47971
|
init_doctor_auth_broker();
|
|
47297
47972
|
init_doctor_hostd();
|
|
@@ -47309,6 +47984,7 @@ var init_doctor = __esm(() => {
|
|
|
47309
47984
|
init_doctor_agent_smoke();
|
|
47310
47985
|
init_doctor_vault_broker_durability();
|
|
47311
47986
|
init_doctor_timezone();
|
|
47987
|
+
init_doctor_fix_session_model();
|
|
47312
47988
|
DEFAULT_DEPLOY_MOUNTS_DEPS = { pathKind: defaultPathKind };
|
|
47313
47989
|
MANIFEST_WARN_ONLY = new Set([
|
|
47314
47990
|
"@playwright/mcp",
|
|
@@ -47894,7 +48570,7 @@ var init_fleet_defaults = __esm(() => {
|
|
|
47894
48570
|
|
|
47895
48571
|
// src/agents/connection-health.ts
|
|
47896
48572
|
import { mkdirSync as mkdirSync46, writeFileSync as writeFileSync28 } from "node:fs";
|
|
47897
|
-
import { join as
|
|
48573
|
+
import { join as join84 } from "node:path";
|
|
47898
48574
|
async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
|
|
47899
48575
|
const reqs = computeMcpSecretRequirements(config).filter((r) => r.agent === agentName);
|
|
47900
48576
|
if (reqs.length === 0)
|
|
@@ -47951,8 +48627,8 @@ async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
|
|
|
47951
48627
|
return issues;
|
|
47952
48628
|
}
|
|
47953
48629
|
function writeConnectionHealthFile(agentDir, health, deps) {
|
|
47954
|
-
const dir =
|
|
47955
|
-
const path8 =
|
|
48630
|
+
const dir = join84(agentDir, ".claude");
|
|
48631
|
+
const path8 = join84(dir, CONNECTION_HEALTH_FILENAME);
|
|
47956
48632
|
(deps?.mkdir ?? ((p, o) => mkdirSync46(p, o)))(dir, { recursive: true });
|
|
47957
48633
|
(deps?.writeFile ?? ((p, d) => writeFileSync28(p, d)))(path8, JSON.stringify(health, null, 2) + `
|
|
47958
48634
|
`);
|
|
@@ -47976,9 +48652,9 @@ var init_connection_health = () => {};
|
|
|
47976
48652
|
|
|
47977
48653
|
// src/cli/update-prompt-hook.ts
|
|
47978
48654
|
import { existsSync as existsSync83, readFileSync as readFileSync72, writeFileSync as writeFileSync29, chmodSync as chmodSync12, mkdirSync as mkdirSync47 } from "node:fs";
|
|
47979
|
-
import { join as
|
|
48655
|
+
import { join as join85 } from "node:path";
|
|
47980
48656
|
function containerHookCommand() {
|
|
47981
|
-
return
|
|
48657
|
+
return join85(CONTAINER_AGENT_DIR, ".claude", "hooks", HOOK_FILENAME);
|
|
47982
48658
|
}
|
|
47983
48659
|
function updatePromptHookScript() {
|
|
47984
48660
|
return `#!/bin/bash
|
|
@@ -48044,9 +48720,9 @@ exit 0
|
|
|
48044
48720
|
`;
|
|
48045
48721
|
}
|
|
48046
48722
|
function installUpdatePromptHook(agentDir) {
|
|
48047
|
-
const hooksDir =
|
|
48723
|
+
const hooksDir = join85(agentDir, ".claude", "hooks");
|
|
48048
48724
|
mkdirSync47(hooksDir, { recursive: true });
|
|
48049
|
-
const scriptPath =
|
|
48725
|
+
const scriptPath = join85(hooksDir, HOOK_FILENAME);
|
|
48050
48726
|
const desired = updatePromptHookScript();
|
|
48051
48727
|
let installed = false;
|
|
48052
48728
|
const existing = existsSync83(scriptPath) ? readFileSync72(scriptPath, "utf-8") : "";
|
|
@@ -48059,7 +48735,7 @@ function installUpdatePromptHook(agentDir) {
|
|
|
48059
48735
|
chmodSync12(scriptPath, 493);
|
|
48060
48736
|
} catch {}
|
|
48061
48737
|
}
|
|
48062
|
-
const settingsPath =
|
|
48738
|
+
const settingsPath = join85(agentDir, ".claude", "settings.json");
|
|
48063
48739
|
if (!existsSync83(settingsPath)) {
|
|
48064
48740
|
return { scriptPath, settingsPath, installed };
|
|
48065
48741
|
}
|
|
@@ -48117,6 +48793,10 @@ function installUpdatePromptHook(agentDir) {
|
|
|
48117
48793
|
var HOOK_FILENAME = "update-card-on-prompt.sh", CONTAINER_AGENT_DIR = "/state/agent";
|
|
48118
48794
|
var init_update_prompt_hook = () => {};
|
|
48119
48795
|
|
|
48796
|
+
// src/litellm/external-spend.ts
|
|
48797
|
+
var LITELLM_MASTER_KEY_STATE_BASENAME = "litellm-master-key";
|
|
48798
|
+
var init_external_spend = () => {};
|
|
48799
|
+
|
|
48120
48800
|
// src/cli/install-detect.ts
|
|
48121
48801
|
import * as fs5 from "node:fs";
|
|
48122
48802
|
import * as path8 from "node:path";
|
|
@@ -48594,6 +49274,7 @@ __export(exports_apply, {
|
|
|
48594
49274
|
provisionLiteLLMKeys: () => provisionLiteLLMKeys,
|
|
48595
49275
|
probeVaultProvisioning: () => probeVaultProvisioning,
|
|
48596
49276
|
parseComposeServiceNames: () => parseComposeServiceNames,
|
|
49277
|
+
materializeLitellmMasterKeyForBroker: () => materializeLitellmMasterKeyForBroker,
|
|
48597
49278
|
isInAgentContainer: () => isInAgentContainer,
|
|
48598
49279
|
inspectVaultBindMountDir: () => inspectVaultBindMountDir,
|
|
48599
49280
|
formatScaffoldFailureResolution: () => formatScaffoldFailureResolution,
|
|
@@ -48606,11 +49287,11 @@ __export(exports_apply, {
|
|
|
48606
49287
|
DEFAULT_COMPOSE_PATH: () => DEFAULT_COMPOSE_PATH2,
|
|
48607
49288
|
COMPOSE_PROJECT: () => COMPOSE_PROJECT2
|
|
48608
49289
|
});
|
|
48609
|
-
import { accessSync as accessSync3, chownSync as chownSync8, constants as fsConstants6, copyFileSync as copyFileSync12, existsSync as existsSync86, mkdirSync as mkdirSync49, readFileSync as readFileSync74, readdirSync as readdirSync30, renameSync as renameSync20, statSync as statSync47, writeFileSync as writeFileSync31 } from "node:fs";
|
|
49290
|
+
import { accessSync as accessSync3, chmodSync as chmodSync14, chownSync as chownSync8, constants as fsConstants6, copyFileSync as copyFileSync12, existsSync as existsSync86, mkdirSync as mkdirSync49, readFileSync as readFileSync74, readdirSync as readdirSync30, renameSync as renameSync20, statSync as statSync47, writeFileSync as writeFileSync31 } from "node:fs";
|
|
48610
49291
|
import { mkdir as mkdir2 } from "node:fs/promises";
|
|
48611
49292
|
import { spawnSync as childSpawnSync } from "node:child_process";
|
|
48612
49293
|
import readline from "node:readline";
|
|
48613
|
-
import { dirname as dirname30, join as
|
|
49294
|
+
import { dirname as dirname30, join as join87, resolve as resolve52 } from "node:path";
|
|
48614
49295
|
import { homedir as homedir49 } from "node:os";
|
|
48615
49296
|
import { execFileSync as execFileSync27 } from "node:child_process";
|
|
48616
49297
|
function effectiveLiteLLMEnabled(config, agentResolvedLitellm) {
|
|
@@ -48622,13 +49303,28 @@ async function resolveOperatorVaultPassphrase(home2) {
|
|
|
48622
49303
|
return envPass;
|
|
48623
49304
|
try {
|
|
48624
49305
|
const { readAutoUnlockFile: readAutoUnlockFile2 } = await Promise.resolve().then(() => (init_auto_unlock(), exports_auto_unlock));
|
|
48625
|
-
const blobPath =
|
|
49306
|
+
const blobPath = join87(home2, ".switchroom", "vault-auto-unlock");
|
|
48626
49307
|
const pass = readAutoUnlockFile2(blobPath);
|
|
48627
49308
|
return pass && pass.length > 0 ? pass : null;
|
|
48628
49309
|
} catch {
|
|
48629
49310
|
return null;
|
|
48630
49311
|
}
|
|
48631
49312
|
}
|
|
49313
|
+
function materializeLitellmMasterKeyForBroker(masterKey, home2 = process.env.HOME ?? "/root") {
|
|
49314
|
+
try {
|
|
49315
|
+
const stateDir = join87(home2, ".switchroom", "state", "auth-broker");
|
|
49316
|
+
mkdirSync49(stateDir, { recursive: true, mode: 448 });
|
|
49317
|
+
const path9 = join87(stateDir, LITELLM_MASTER_KEY_STATE_BASENAME);
|
|
49318
|
+
writeFileSync31(path9, masterKey.trim() + `
|
|
49319
|
+
`, { mode: 384 });
|
|
49320
|
+
try {
|
|
49321
|
+
chmodSync14(path9, 384);
|
|
49322
|
+
} catch {}
|
|
49323
|
+
return { ok: true, path: path9 };
|
|
49324
|
+
} catch (err) {
|
|
49325
|
+
return { ok: false, error: err.message };
|
|
49326
|
+
}
|
|
49327
|
+
}
|
|
48632
49328
|
async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ctx) {
|
|
48633
49329
|
const { writeOut, failures } = ctx;
|
|
48634
49330
|
const optedIn = [];
|
|
@@ -48644,6 +49340,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
48644
49340
|
const needsHindsight = config.litellm?.enabled === true && config.memory?.backend === "hindsight";
|
|
48645
49341
|
if (optedIn.length === 0 && !needsHindsight)
|
|
48646
49342
|
return;
|
|
49343
|
+
let brokerKeyMaterialized = false;
|
|
48647
49344
|
const [{ getViaBrokerStructured: getViaBrokerStructured2, putViaBroker: putViaBroker2 }, { ensureTeam: ensureTeam2, ensureKey: ensureKey2, validateKey: validateKey2, bindKeyToTeam: bindKeyToTeam2 }, { addAgentSecret: addAgentSecret2 }] = await Promise.all([
|
|
48648
49345
|
Promise.resolve().then(() => (init_client(), exports_client)),
|
|
48649
49346
|
Promise.resolve().then(() => (init_provision(), exports_provision)),
|
|
@@ -48753,6 +49450,14 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
48753
49450
|
masterKey = resolved.entry.value;
|
|
48754
49451
|
}
|
|
48755
49452
|
}
|
|
49453
|
+
if (masterKey && !brokerKeyMaterialized) {
|
|
49454
|
+
const mat = materializeLitellmMasterKeyForBroker(masterKey, ctx.home ?? homedir49());
|
|
49455
|
+
brokerKeyMaterialized = true;
|
|
49456
|
+
if (!mat.ok) {
|
|
49457
|
+
ctx.writeErr(source_default.yellow(` ! litellm: could not materialize master key for auth-broker external-spend (${mat.error})
|
|
49458
|
+
`));
|
|
49459
|
+
}
|
|
49460
|
+
}
|
|
48756
49461
|
if (existing.kind === "ok") {
|
|
48757
49462
|
const storedKey = existing.entry.kind === "string" ? existing.entry.value : null;
|
|
48758
49463
|
let driftReprovision = false;
|
|
@@ -48935,7 +49640,7 @@ function resolveVaultBindMountDir(homeDir, ctx) {
|
|
|
48935
49640
|
if (isCustomPath && ctx.customVaultPath) {
|
|
48936
49641
|
return dirname30(ctx.customVaultPath);
|
|
48937
49642
|
}
|
|
48938
|
-
return
|
|
49643
|
+
return join87(homeDir, ".switchroom", "vault");
|
|
48939
49644
|
}
|
|
48940
49645
|
function inspectVaultBindMountDir(vaultDir) {
|
|
48941
49646
|
if (!existsSync86(vaultDir))
|
|
@@ -48966,41 +49671,41 @@ function hasVaultRefs(value) {
|
|
|
48966
49671
|
async function ensureHostMountSources(config) {
|
|
48967
49672
|
const home2 = resolveHostHomeForCompose();
|
|
48968
49673
|
const dirs = [
|
|
48969
|
-
|
|
48970
|
-
|
|
48971
|
-
|
|
48972
|
-
|
|
48973
|
-
|
|
49674
|
+
join87(home2, ".switchroom", "approvals"),
|
|
49675
|
+
join87(home2, ".switchroom", "scheduler"),
|
|
49676
|
+
join87(home2, ".switchroom", "logs"),
|
|
49677
|
+
join87(home2, ".switchroom", "compose"),
|
|
49678
|
+
join87(home2, ".switchroom", "broker-operator")
|
|
48974
49679
|
];
|
|
48975
49680
|
for (const name of Object.keys(config.agents)) {
|
|
48976
|
-
dirs.push(
|
|
48977
|
-
dirs.push(
|
|
48978
|
-
dirs.push(
|
|
48979
|
-
dirs.push(
|
|
48980
|
-
if (existsSync86(
|
|
48981
|
-
dirs.push(
|
|
49681
|
+
dirs.push(join87(home2, ".switchroom", "agents", name));
|
|
49682
|
+
dirs.push(join87(home2, ".switchroom", "logs", name));
|
|
49683
|
+
dirs.push(join87(home2, ".claude", "projects", name));
|
|
49684
|
+
dirs.push(join87(home2, ".switchroom", "audit", name));
|
|
49685
|
+
if (existsSync86(join87(home2, ".switchroom-config"))) {
|
|
49686
|
+
dirs.push(join87(home2, ".switchroom-config", "agents", name, "personal-skills"));
|
|
48982
49687
|
}
|
|
48983
49688
|
}
|
|
48984
49689
|
for (const dir of dirs) {
|
|
48985
49690
|
await mkdir2(dir, { recursive: true });
|
|
48986
49691
|
}
|
|
48987
|
-
const autoUnlockPath =
|
|
49692
|
+
const autoUnlockPath = join87(home2, ".switchroom", "vault-auto-unlock");
|
|
48988
49693
|
if (!existsSync86(autoUnlockPath)) {
|
|
48989
49694
|
writeFileSync31(autoUnlockPath, "", { mode: 384 });
|
|
48990
49695
|
}
|
|
48991
|
-
const auditLogPath =
|
|
49696
|
+
const auditLogPath = join87(home2, ".switchroom", "vault-audit.log");
|
|
48992
49697
|
if (!existsSync86(auditLogPath)) {
|
|
48993
49698
|
writeFileSync31(auditLogPath, "", { mode: 420 });
|
|
48994
49699
|
}
|
|
48995
49700
|
const grantsDbDir = getGrantsDbDir(home2);
|
|
48996
49701
|
mkdirSync49(grantsDbDir, { recursive: true, mode: 448 });
|
|
48997
49702
|
migrateLegacyGrantsDbLocation(getGrantsDbPath(home2));
|
|
48998
|
-
const hostdAuditLogPath =
|
|
49703
|
+
const hostdAuditLogPath = join87(home2, ".switchroom", "host-control-audit.log");
|
|
48999
49704
|
if (!existsSync86(hostdAuditLogPath)) {
|
|
49000
49705
|
writeFileSync31(hostdAuditLogPath, "", { mode: 420 });
|
|
49001
49706
|
}
|
|
49002
49707
|
for (const name of Object.keys(config.agents)) {
|
|
49003
|
-
const tokenPath =
|
|
49708
|
+
const tokenPath = join87(home2, ".switchroom", "agents", name, ".vault-token");
|
|
49004
49709
|
if (!existsSync86(tokenPath)) {
|
|
49005
49710
|
writeFileSync31(tokenPath, "", { mode: 384 });
|
|
49006
49711
|
}
|
|
@@ -49009,15 +49714,15 @@ async function ensureHostMountSources(config) {
|
|
|
49009
49714
|
chownSync8(tokenPath, uid, uid);
|
|
49010
49715
|
} catch {}
|
|
49011
49716
|
}
|
|
49012
|
-
const fleetDir =
|
|
49717
|
+
const fleetDir = join87(home2, ".switchroom", "fleet");
|
|
49013
49718
|
await mkdir2(fleetDir, { recursive: true });
|
|
49014
|
-
const invariantsPath =
|
|
49719
|
+
const invariantsPath = join87(fleetDir, "switchroom-invariants.md");
|
|
49015
49720
|
const invariantsCanonical = renderFleetInvariants();
|
|
49016
49721
|
const invariantsCurrent = existsSync86(invariantsPath) ? readFileSync74(invariantsPath, "utf-8") : null;
|
|
49017
49722
|
if (invariantsCurrent !== invariantsCanonical) {
|
|
49018
49723
|
writeFileSync31(invariantsPath, invariantsCanonical, { mode: 420 });
|
|
49019
49724
|
}
|
|
49020
|
-
const fleetClaudePath =
|
|
49725
|
+
const fleetClaudePath = join87(fleetDir, "CLAUDE.md");
|
|
49021
49726
|
if (!existsSync86(fleetClaudePath)) {
|
|
49022
49727
|
writeFileSync31(fleetClaudePath, renderFleetDefaultsClaudeMd(), {
|
|
49023
49728
|
mode: 420
|
|
@@ -49103,8 +49808,8 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
|
49103
49808
|
}
|
|
49104
49809
|
function writeInstallTypeCache(homeDir = homedir49()) {
|
|
49105
49810
|
const ctx = detectInstallType();
|
|
49106
|
-
const dir =
|
|
49107
|
-
const out =
|
|
49811
|
+
const dir = join87(homeDir, ".switchroom");
|
|
49812
|
+
const out = join87(dir, "install-type.json");
|
|
49108
49813
|
const tmp = `${out}.tmp`;
|
|
49109
49814
|
mkdirSync49(dir, { recursive: true });
|
|
49110
49815
|
const payload = {
|
|
@@ -49171,17 +49876,17 @@ Applying switchroom config...
|
|
|
49171
49876
|
writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
|
|
49172
49877
|
`));
|
|
49173
49878
|
try {
|
|
49174
|
-
installUpdatePromptHook(
|
|
49879
|
+
installUpdatePromptHook(join87(agentsDir, name));
|
|
49175
49880
|
} catch (hookErr) {
|
|
49176
49881
|
writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
|
|
49177
49882
|
`));
|
|
49178
49883
|
}
|
|
49179
|
-
await refreshAgentConnectionHealth(config, name,
|
|
49884
|
+
await refreshAgentConnectionHealth(config, name, join87(agentsDir, name), {
|
|
49180
49885
|
vaultAclReader: connHealthVaultAclReader
|
|
49181
49886
|
});
|
|
49182
49887
|
try {
|
|
49183
49888
|
const uid = allocateAgentUid(name);
|
|
49184
|
-
alignAgentUid(name,
|
|
49889
|
+
alignAgentUid(name, join87(agentsDir, name), uid, {
|
|
49185
49890
|
confirm: !options.nonInteractive,
|
|
49186
49891
|
writeOut
|
|
49187
49892
|
});
|
|
@@ -49226,7 +49931,7 @@ Applying switchroom config...
|
|
|
49226
49931
|
for (const name of agentNames) {
|
|
49227
49932
|
try {
|
|
49228
49933
|
const uid = allocateAgentUid(name);
|
|
49229
|
-
alignAgentUid(name,
|
|
49934
|
+
alignAgentUid(name, join87(agentsDir, name), uid, {
|
|
49230
49935
|
confirm: !options.nonInteractive,
|
|
49231
49936
|
writeOut
|
|
49232
49937
|
});
|
|
@@ -49586,7 +50291,7 @@ function findUnwritableAgentDirs(config, opts) {
|
|
|
49586
50291
|
const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
|
|
49587
50292
|
const unwritable = [];
|
|
49588
50293
|
for (const name of targets) {
|
|
49589
|
-
const startSh =
|
|
50294
|
+
const startSh = join87(agentsDir, name, "start.sh");
|
|
49590
50295
|
if (!existsSync86(startSh))
|
|
49591
50296
|
continue;
|
|
49592
50297
|
try {
|
|
@@ -49782,6 +50487,7 @@ var init_apply = __esm(() => {
|
|
|
49782
50487
|
init_connection_health();
|
|
49783
50488
|
init_update_prompt_hook();
|
|
49784
50489
|
init_compose();
|
|
50490
|
+
init_external_spend();
|
|
49785
50491
|
init_write_compose();
|
|
49786
50492
|
init_profiles();
|
|
49787
50493
|
init_install_detect();
|
|
@@ -49791,7 +50497,7 @@ var init_apply = __esm(() => {
|
|
|
49791
50497
|
switchroom: switchroom_default,
|
|
49792
50498
|
minimal: minimal_default
|
|
49793
50499
|
};
|
|
49794
|
-
DEFAULT_COMPOSE_PATH2 =
|
|
50500
|
+
DEFAULT_COMPOSE_PATH2 = join87(homedir49(), ".switchroom", "compose", "docker-compose.yml");
|
|
49795
50501
|
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.
|
|
49796
50502
|
` + "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.";
|
|
49797
50503
|
SELF_ELEVATE_PRESERVED_ENV = [
|
|
@@ -64018,6 +64724,12 @@ var init_mapping = __esm(() => {
|
|
|
64018
64724
|
severity: 1,
|
|
64019
64725
|
job_spec: "feel-like-a-colleague",
|
|
64020
64726
|
signature: "represent:obligation-escalation"
|
|
64727
|
+
},
|
|
64728
|
+
"litellm-header-passthrough-misconfig": {
|
|
64729
|
+
failure_mode: "constraint-violation",
|
|
64730
|
+
severity: 3,
|
|
64731
|
+
job_spec: "keep-my-subscription-honest",
|
|
64732
|
+
signature: "litellm-header-passthrough:oauth-leak-scope"
|
|
64021
64733
|
}
|
|
64022
64734
|
};
|
|
64023
64735
|
ALL_JOB_SPECS = [
|
|
@@ -64208,6 +64920,122 @@ var init_test_agents = __esm(() => {
|
|
|
64208
64920
|
];
|
|
64209
64921
|
});
|
|
64210
64922
|
|
|
64923
|
+
// src/litellm/header-passthrough-guard.ts
|
|
64924
|
+
function isClaudeAllowlistedGroup(name) {
|
|
64925
|
+
if (name.endsWith("-openrouter"))
|
|
64926
|
+
return false;
|
|
64927
|
+
return name.startsWith("claude-") || name === "sonnet" || name === "fable";
|
|
64928
|
+
}
|
|
64929
|
+
function flagTruthy(v) {
|
|
64930
|
+
return v === true || v === "true";
|
|
64931
|
+
}
|
|
64932
|
+
function* iterGroups(mgs) {
|
|
64933
|
+
if (!mgs || typeof mgs !== "object")
|
|
64934
|
+
return;
|
|
64935
|
+
if (Array.isArray(mgs)) {
|
|
64936
|
+
for (const entry of mgs) {
|
|
64937
|
+
if (!entry || typeof entry !== "object")
|
|
64938
|
+
continue;
|
|
64939
|
+
const e = entry;
|
|
64940
|
+
const name = typeof e.model_group === "string" && e.model_group || typeof e.group_name === "string" && e.group_name || typeof e.model_name === "string" && e.model_name || null;
|
|
64941
|
+
if (name)
|
|
64942
|
+
yield [name, e];
|
|
64943
|
+
}
|
|
64944
|
+
return;
|
|
64945
|
+
}
|
|
64946
|
+
for (const [name, settings] of Object.entries(mgs)) {
|
|
64947
|
+
if (settings && typeof settings === "object") {
|
|
64948
|
+
yield [name, settings];
|
|
64949
|
+
}
|
|
64950
|
+
}
|
|
64951
|
+
}
|
|
64952
|
+
function detectHeaderMisconfig(parsed) {
|
|
64953
|
+
const violations = [];
|
|
64954
|
+
if (!parsed || typeof parsed !== "object")
|
|
64955
|
+
return violations;
|
|
64956
|
+
const root = parsed;
|
|
64957
|
+
const ls = root.litellm_settings;
|
|
64958
|
+
if (ls && typeof ls === "object" && flagTruthy(ls[FORWARD_HEADERS_FLAG])) {
|
|
64959
|
+
violations.push({
|
|
64960
|
+
scope: "global",
|
|
64961
|
+
detail: `${FORWARD_HEADERS_FLAG}: true under litellm_settings \u2014 the GLOBAL ` + `master switch forwards the subscription OAuth Authorization header ` + `to EVERY upstream (OpenRouter/OpenAI included). Scope it to Claude ` + `groups only.`
|
|
64962
|
+
});
|
|
64963
|
+
}
|
|
64964
|
+
for (const [group, settings] of iterGroups(root.model_group_settings)) {
|
|
64965
|
+
if (flagTruthy(settings[FORWARD_HEADERS_FLAG]) && !isClaudeAllowlistedGroup(group)) {
|
|
64966
|
+
violations.push({
|
|
64967
|
+
scope: "model_group",
|
|
64968
|
+
group,
|
|
64969
|
+
detail: `${FORWARD_HEADERS_FLAG}: true on non-Claude group '${group}' \u2014 ` + `forwards the subscription OAuth Authorization header to a ` + `non-subscription upstream. Remove it from this group.`
|
|
64970
|
+
});
|
|
64971
|
+
}
|
|
64972
|
+
}
|
|
64973
|
+
return violations;
|
|
64974
|
+
}
|
|
64975
|
+
function parseLitellmConfig(text) {
|
|
64976
|
+
try {
|
|
64977
|
+
return import_yaml26.parse(text);
|
|
64978
|
+
} catch {
|
|
64979
|
+
return null;
|
|
64980
|
+
}
|
|
64981
|
+
}
|
|
64982
|
+
var import_yaml26, FORWARD_HEADERS_FLAG = "forward_client_headers_to_llm_api", DEFAULT_LITELLM_CONFIG_PATH = "/data/coolify/services/vhz4jc1tzvk6gdql8jueiwq4/litellm-config.yaml";
|
|
64983
|
+
var init_header_passthrough_guard = __esm(() => {
|
|
64984
|
+
import_yaml26 = __toESM(require_dist(), 1);
|
|
64985
|
+
});
|
|
64986
|
+
|
|
64987
|
+
// src/fleet-health/litellm-config-sensor.ts
|
|
64988
|
+
import { readFileSync as readFileSync85, existsSync as existsSync98 } from "node:fs";
|
|
64989
|
+
function resolveLitellmConfigPath(explicit) {
|
|
64990
|
+
return explicit ?? process.env.LITELLM_CONFIG_PATH ?? DEFAULT_LITELLM_CONFIG_PATH;
|
|
64991
|
+
}
|
|
64992
|
+
function scanLitellmConfig(opts = {}) {
|
|
64993
|
+
const path9 = resolveLitellmConfigPath(opts.path);
|
|
64994
|
+
const exists = opts.existsFn ?? existsSync98;
|
|
64995
|
+
const read = opts.readFn ?? ((p) => readFileSync85(p, "utf-8"));
|
|
64996
|
+
const log = opts.log ?? (() => {});
|
|
64997
|
+
const nowIso = opts.nowIso ?? new Date().toISOString();
|
|
64998
|
+
if (!exists(path9)) {
|
|
64999
|
+
log(`fleet-health: litellm-config sensor SKIPPED \u2014 config file absent at ${path9} ` + `(set LITELLM_CONFIG_PATH if it moved; hermetic CI/dev expected to skip)`);
|
|
65000
|
+
return { status: "skipped", path: path9, findings: [] };
|
|
65001
|
+
}
|
|
65002
|
+
let text;
|
|
65003
|
+
try {
|
|
65004
|
+
text = read(path9);
|
|
65005
|
+
} catch (e) {
|
|
65006
|
+
log(`fleet-health: litellm-config sensor SKIPPED \u2014 ${path9} unreadable: ${String(e)}`);
|
|
65007
|
+
return { status: "skipped", path: path9, findings: [] };
|
|
65008
|
+
}
|
|
65009
|
+
const parsed = parseLitellmConfig(text);
|
|
65010
|
+
if (parsed == null) {
|
|
65011
|
+
log(`fleet-health: litellm-config sensor SKIPPED \u2014 ${path9} unparseable YAML`);
|
|
65012
|
+
return { status: "skipped", path: path9, findings: [] };
|
|
65013
|
+
}
|
|
65014
|
+
const violations = detectHeaderMisconfig(parsed);
|
|
65015
|
+
if (violations.length === 0) {
|
|
65016
|
+
log(`fleet-health: litellm-config sensor OK \u2014 header passthrough correctly scoped (${path9})`);
|
|
65017
|
+
return { status: "ok", path: path9, findings: [] };
|
|
65018
|
+
}
|
|
65019
|
+
const findings = violations.map((v, i) => {
|
|
65020
|
+
const where = v.scope === "global" ? "litellm_settings (global)" : `group '${v.group}'`;
|
|
65021
|
+
return {
|
|
65022
|
+
signal: "litellm-header-passthrough-misconfig",
|
|
65023
|
+
agent: LITELLM_PROXY_PSEUDO_AGENT,
|
|
65024
|
+
turn_id: `litellm-config:${v.scope}:${v.group ?? "global"}`,
|
|
65025
|
+
log_pointer: `${path9}: ${where} \u2014 ${v.detail}`,
|
|
65026
|
+
ts: nowIso
|
|
65027
|
+
};
|
|
65028
|
+
});
|
|
65029
|
+
for (const f of findings) {
|
|
65030
|
+
log(`fleet-health: litellm-config sensor VIOLATION \u2014 ${f.log_pointer}`);
|
|
65031
|
+
}
|
|
65032
|
+
return { status: "violation", path: path9, findings };
|
|
65033
|
+
}
|
|
65034
|
+
var LITELLM_PROXY_PSEUDO_AGENT = "litellm-proxy";
|
|
65035
|
+
var init_litellm_config_sensor = __esm(() => {
|
|
65036
|
+
init_header_passthrough_guard();
|
|
65037
|
+
});
|
|
65038
|
+
|
|
64211
65039
|
// src/fleet-health/scan.ts
|
|
64212
65040
|
var exports_scan = {};
|
|
64213
65041
|
__export(exports_scan, {
|
|
@@ -64219,9 +65047,9 @@ __export(exports_scan, {
|
|
|
64219
65047
|
ledgerPathForBase: () => ledgerPathForBase
|
|
64220
65048
|
});
|
|
64221
65049
|
import {
|
|
64222
|
-
readFileSync as
|
|
65050
|
+
readFileSync as readFileSync86,
|
|
64223
65051
|
readdirSync as readdirSync38,
|
|
64224
|
-
existsSync as
|
|
65052
|
+
existsSync as existsSync99,
|
|
64225
65053
|
mkdirSync as mkdirSync56,
|
|
64226
65054
|
writeFileSync as writeFileSync37
|
|
64227
65055
|
} from "node:fs";
|
|
@@ -64232,7 +65060,7 @@ function resolveSwitchroomBase(home2 = process.env.SWITCHROOM_HOME ?? process.en
|
|
|
64232
65060
|
}
|
|
64233
65061
|
function listAgents(base) {
|
|
64234
65062
|
const dir = resolve57(base, "agents");
|
|
64235
|
-
if (!
|
|
65063
|
+
if (!existsSync99(dir))
|
|
64236
65064
|
return [];
|
|
64237
65065
|
try {
|
|
64238
65066
|
return readdirSync38(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
|
|
@@ -64261,16 +65089,16 @@ function runScan(opts = {}) {
|
|
|
64261
65089
|
let gwText = "";
|
|
64262
65090
|
let sawArtifact = false;
|
|
64263
65091
|
try {
|
|
64264
|
-
if (
|
|
64265
|
-
turnsText =
|
|
65092
|
+
if (existsSync99(turnsPath)) {
|
|
65093
|
+
turnsText = readFileSync86(turnsPath, "utf-8");
|
|
64266
65094
|
sawArtifact = true;
|
|
64267
65095
|
}
|
|
64268
65096
|
} catch (e) {
|
|
64269
65097
|
log(`fleet-health: WARN skipping ${agent} turns.jsonl unreadable: ${String(e)}`);
|
|
64270
65098
|
}
|
|
64271
65099
|
try {
|
|
64272
|
-
if (
|
|
64273
|
-
gwText =
|
|
65100
|
+
if (existsSync99(gwPath)) {
|
|
65101
|
+
gwText = readFileSync86(gwPath, "utf-8");
|
|
64274
65102
|
sawArtifact = true;
|
|
64275
65103
|
}
|
|
64276
65104
|
} catch (e) {
|
|
@@ -64292,6 +65120,8 @@ function runScan(opts = {}) {
|
|
|
64292
65120
|
skipped.push(agent);
|
|
64293
65121
|
}
|
|
64294
65122
|
}
|
|
65123
|
+
const litellm = scanLitellmConfig({ path: opts.litellmConfigPath, log });
|
|
65124
|
+
findings.push(...litellm.findings);
|
|
64295
65125
|
const prior = readLedgerIfPresent(base);
|
|
64296
65126
|
const ledger = buildLedger(findings, {
|
|
64297
65127
|
ownerAgent: opts.ownerAgent,
|
|
@@ -64310,9 +65140,9 @@ function runScan(opts = {}) {
|
|
|
64310
65140
|
function readLedgerIfPresent(base) {
|
|
64311
65141
|
const path9 = ledgerPathForBase(base);
|
|
64312
65142
|
try {
|
|
64313
|
-
if (!
|
|
65143
|
+
if (!existsSync99(path9))
|
|
64314
65144
|
return null;
|
|
64315
|
-
return JSON.parse(
|
|
65145
|
+
return JSON.parse(readFileSync86(path9, "utf-8"));
|
|
64316
65146
|
} catch {
|
|
64317
65147
|
return null;
|
|
64318
65148
|
}
|
|
@@ -64332,6 +65162,7 @@ var init_scan = __esm(() => {
|
|
|
64332
65162
|
init_detect();
|
|
64333
65163
|
init_ledger();
|
|
64334
65164
|
init_test_agents();
|
|
65165
|
+
init_litellm_config_sensor();
|
|
64335
65166
|
});
|
|
64336
65167
|
|
|
64337
65168
|
// src/cli/index.ts
|
|
@@ -70086,6 +70917,10 @@ function isThin(d) {
|
|
|
70086
70917
|
}
|
|
70087
70918
|
function classifyState(args) {
|
|
70088
70919
|
const { exhausted, exhaustedUntil, window, now } = args;
|
|
70920
|
+
if (args.entitlementBlocked === true)
|
|
70921
|
+
return "org-disabled";
|
|
70922
|
+
if (args.inService === false)
|
|
70923
|
+
return "retired";
|
|
70089
70924
|
if (window.probeThin)
|
|
70090
70925
|
return "unprobed";
|
|
70091
70926
|
const norm = refillNormalizedUtils({
|
|
@@ -70130,17 +70965,30 @@ function buildScheduleRows(state, probe, now) {
|
|
|
70130
70965
|
const exhaustedUntil = typeof a.exhausted_until === "number" ? new Date(a.exhausted_until) : null;
|
|
70131
70966
|
const exhausted = a.exhausted && (exhaustedUntil?.getTime() ?? 0) > now.getTime();
|
|
70132
70967
|
const rank = state.fallback_order.indexOf(a.label);
|
|
70968
|
+
const isActive = a.label === state.active;
|
|
70969
|
+
const inService = isActive ? true : a.in_service !== false;
|
|
70970
|
+
const entitlementBlocked = isActive ? false : a.entitlement_blocked === true;
|
|
70133
70971
|
return {
|
|
70134
70972
|
label: a.label,
|
|
70135
|
-
isActive
|
|
70973
|
+
isActive,
|
|
70136
70974
|
fallbackRank: rank === -1 ? null : rank + 1,
|
|
70975
|
+
inService,
|
|
70976
|
+
entitlementBlocked,
|
|
70137
70977
|
window,
|
|
70138
70978
|
exhausted,
|
|
70139
70979
|
exhaustedUntil,
|
|
70140
|
-
state: classifyState({
|
|
70980
|
+
state: classifyState({
|
|
70981
|
+
exhausted,
|
|
70982
|
+
exhaustedUntil,
|
|
70983
|
+
window,
|
|
70984
|
+
now,
|
|
70985
|
+
inService,
|
|
70986
|
+
entitlementBlocked
|
|
70987
|
+
})
|
|
70141
70988
|
};
|
|
70142
70989
|
});
|
|
70143
|
-
const
|
|
70990
|
+
const outOfService = (r) => !r.inService || r.entitlementBlocked;
|
|
70991
|
+
const key = (r) => outOfService(r) ? 1e5 : r.isActive ? -1 : r.fallbackRank ?? 9999;
|
|
70144
70992
|
return rows.sort((a, b) => key(a) - key(b) || a.label.localeCompare(b.label));
|
|
70145
70993
|
}
|
|
70146
70994
|
var EM_DASH = "\u2014";
|
|
@@ -70193,6 +71041,10 @@ function formatWeeklyCell(w, now, tz) {
|
|
|
70193
71041
|
return `${pct} \u00b7 ${formatResetDay(w.weeklyResetAt, tz)} (${rel})`;
|
|
70194
71042
|
}
|
|
70195
71043
|
function poolLabel(row) {
|
|
71044
|
+
if (row.entitlementBlocked)
|
|
71045
|
+
return "disabled";
|
|
71046
|
+
if (!row.inService)
|
|
71047
|
+
return "retired";
|
|
70196
71048
|
if (row.isActive)
|
|
70197
71049
|
return row.fallbackRank ? `active #${row.fallbackRank}` : "active";
|
|
70198
71050
|
if (row.fallbackRank)
|
|
@@ -70204,6 +71056,10 @@ function formatStateCell(row, now) {
|
|
|
70204
71056
|
const rel = horizon ? ` \u00b7 ${formatDuration(horizon.getTime() - now.getTime())}` : "";
|
|
70205
71057
|
const overageNote = row.window.overageServeBlocking && row.window.overageReason ? ` (overage off: ${row.window.overageReason})` : row.window.overageServeBlocking ? " (overage off)" : "";
|
|
70206
71058
|
switch (row.state) {
|
|
71059
|
+
case "org-disabled":
|
|
71060
|
+
return "DISABLED (org)";
|
|
71061
|
+
case "retired":
|
|
71062
|
+
return "retired";
|
|
70207
71063
|
case "quota-exhausted":
|
|
70208
71064
|
return `quota-exhausted${rel}`;
|
|
70209
71065
|
case "healthy":
|
|
@@ -70225,10 +71081,16 @@ function pad3(s, n) {
|
|
|
70225
71081
|
return s.padEnd(n);
|
|
70226
71082
|
}
|
|
70227
71083
|
function glyphFor(row, color) {
|
|
70228
|
-
const g = row.isActive ? "\u25cf" : row.exhausted ? "!" : row.state === "unprobed" ? "\u00b7" : "\u2713";
|
|
71084
|
+
const g = row.isActive ? "\u25cf" : row.state === "org-disabled" ? "\u2298" : row.state === "retired" ? "\u2205" : row.exhausted ? "!" : row.state === "unprobed" ? "\u00b7" : "\u2713";
|
|
70229
71085
|
if (!color)
|
|
70230
71086
|
return g;
|
|
70231
|
-
|
|
71087
|
+
if (row.isActive)
|
|
71088
|
+
return source_default.green(g);
|
|
71089
|
+
if (row.state === "org-disabled")
|
|
71090
|
+
return source_default.red(g);
|
|
71091
|
+
if (row.state === "retired")
|
|
71092
|
+
return source_default.gray(g);
|
|
71093
|
+
return row.exhausted ? source_default.red(g) : source_default.gray(g);
|
|
70232
71094
|
}
|
|
70233
71095
|
function colorState(text, state, color) {
|
|
70234
71096
|
if (!color)
|
|
@@ -70236,6 +71098,7 @@ function colorState(text, state, color) {
|
|
|
70236
71098
|
switch (state) {
|
|
70237
71099
|
case "healthy":
|
|
70238
71100
|
return source_default.green(text);
|
|
71101
|
+
case "org-disabled":
|
|
70239
71102
|
case "weekly-walled":
|
|
70240
71103
|
return source_default.red(text);
|
|
70241
71104
|
case "quota-exhausted":
|
|
@@ -70449,8 +71312,11 @@ function formatQuotaUtilCell(a, now = Date.now()) {
|
|
|
70449
71312
|
function printAccountsTable(state) {
|
|
70450
71313
|
console.log(source_default.bold(" ACCOUNT STATUS EXPIRES QUOTA 5h\u00b77d QUOTA-RESET"));
|
|
70451
71314
|
for (const a of state.accounts) {
|
|
70452
|
-
const
|
|
70453
|
-
const
|
|
71315
|
+
const isActive = a.label === state.active;
|
|
71316
|
+
const orgBlocked = a.entitlement_blocked === true;
|
|
71317
|
+
const retired = a.in_service === false;
|
|
71318
|
+
const marker = isActive ? source_default.green("\u25cf") : orgBlocked ? source_default.red("\u2298") : retired ? source_default.gray("\u2205") : a.exhausted ? source_default.red("!") : source_default.gray("\u2713");
|
|
71319
|
+
const status = isActive ? source_default.green("active ") : orgBlocked ? source_default.red("DISABLED ") : retired ? source_default.gray("retired ") : a.exhausted ? source_default.red("exhausted") : "available";
|
|
70454
71320
|
const label = a.label.padEnd(32);
|
|
70455
71321
|
const exp = formatExpiry2(a.expiresAt).padEnd(10);
|
|
70456
71322
|
const util3 = formatQuotaUtilCell(a).padEnd(20);
|
|
@@ -70499,8 +71365,16 @@ function printUsageTable(state) {
|
|
|
70499
71365
|
console.log(source_default.bold(" ACCOUNT PREMIUM (Fable) HEADROOM STANDARD HEADROOM"));
|
|
70500
71366
|
const WIDTH = 44;
|
|
70501
71367
|
for (const a of state.accounts) {
|
|
70502
|
-
const
|
|
71368
|
+
const isActive = a.label === state.active;
|
|
71369
|
+
const orgBlocked = a.entitlement_blocked === true;
|
|
71370
|
+
const retired = a.in_service === false;
|
|
71371
|
+
const marker = isActive ? source_default.green("*") : orgBlocked ? source_default.red("\u2298") : retired ? source_default.gray("\u2205") : " ";
|
|
70503
71372
|
const label = a.label.padEnd(32);
|
|
71373
|
+
if (orgBlocked || retired) {
|
|
71374
|
+
const note = orgBlocked ? source_default.red("DISABLED (org) \u2014 no fleet routing") : source_default.gray("retired \u2014 removed from fleet rotation");
|
|
71375
|
+
console.log(` ${marker} ${label} ${note}`);
|
|
71376
|
+
continue;
|
|
71377
|
+
}
|
|
70504
71378
|
const premium = usageHeadroomCell(a.usage_ledger?.premium ?? null);
|
|
70505
71379
|
const standard = usageHeadroomCell(a.usage_ledger?.standard ?? null);
|
|
70506
71380
|
const premiumCol = premium.color(premium.plain.padEnd(WIDTH));
|
|
@@ -78331,7 +79205,7 @@ Cross-agent reflection plan
|
|
|
78331
79205
|
}
|
|
78332
79206
|
console.log();
|
|
78333
79207
|
}));
|
|
78334
|
-
memory.command("setup").description("Manage the Hindsight Docker container").option("--stop", "Stop and remove the Hindsight container").option("--status", "Show Hindsight container status").option("--recreate", "Pull the latest image and recreate the container (reusing its current port). Used by `switchroom update` to keep the hindsight singleton current.").option("--tag <version>", "Pin the hindsight image tag to pull + run (e.g. v0.15.18), overriding the " + "default floating `:latest`. Threaded through by `switchroom rollout` so a " + "version-pinned roll recreates hindsight on the SAME tag as the rest of the " + "fleet. Omit for `:latest` (the standalone default).").option("--provider <provider>", "LLM provider (ollama, openai, anthropic)").action(async (opts) => {
|
|
79208
|
+
memory.command("setup").description("Manage the Hindsight Docker container").option("--stop", "Stop and remove the Hindsight container").option("--status", "Show Hindsight container status").option("--recreate", "Pull the latest image and recreate the container (reusing its current port). Used by `switchroom update` to keep the hindsight singleton current. Also required after any `hindsight.llm` model/provider config change \u2014 env is only re-derived on recreate, a plain docker restart is not enough (see docs/operators/hindsight-model-change.md).").option("--tag <version>", "Pin the hindsight image tag to pull + run (e.g. v0.15.18), overriding the " + "default floating `:latest`. Threaded through by `switchroom rollout` so a " + "version-pinned roll recreates hindsight on the SAME tag as the rest of the " + "fleet. Omit for `:latest` (the standalone default).").option("--provider <provider>", "LLM provider (ollama, openai, anthropic)").action(async (opts) => {
|
|
78335
79209
|
if (opts.status) {
|
|
78336
79210
|
if (!isDockerAvailable()) {
|
|
78337
79211
|
console.log(source_default.red(" Docker is not available."));
|
|
@@ -84110,7 +84984,7 @@ init_lifecycle();
|
|
|
84110
84984
|
init_compose_env();
|
|
84111
84985
|
import { cpSync as cpSync2, existsSync as existsSync67, mkdirSync as mkdirSync35, readFileSync as readFileSync60, realpathSync as realpathSync6, rmSync as rmSync12, statSync as statSync40, chownSync as chownSync5 } from "node:fs";
|
|
84112
84986
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
84113
|
-
import { join as
|
|
84987
|
+
import { join as join69, dirname as dirname21, resolve as resolve40 } from "node:path";
|
|
84114
84988
|
import { homedir as homedir40 } from "node:os";
|
|
84115
84989
|
|
|
84116
84990
|
// src/cli/release-yaml.ts
|
|
@@ -84218,13 +85092,13 @@ function defaultPersistPin(configPath) {
|
|
|
84218
85092
|
} catch {}
|
|
84219
85093
|
};
|
|
84220
85094
|
}
|
|
84221
|
-
var DEFAULT_COMPOSE_PATH =
|
|
85095
|
+
var DEFAULT_COMPOSE_PATH = join69(homedir40(), ".switchroom", "compose", "docker-compose.yml");
|
|
84222
85096
|
function runningFromSwitchroomCheckout(scriptPath) {
|
|
84223
85097
|
let dir = dirname21(scriptPath);
|
|
84224
85098
|
for (let i = 0;i < 12; i++) {
|
|
84225
|
-
if (existsSync67(
|
|
85099
|
+
if (existsSync67(join69(dir, ".git"))) {
|
|
84226
85100
|
try {
|
|
84227
|
-
const pkg = JSON.parse(readFileSync60(
|
|
85101
|
+
const pkg = JSON.parse(readFileSync60(join69(dir, "package.json"), "utf-8"));
|
|
84228
85102
|
if (pkg.name === "switchroom")
|
|
84229
85103
|
return true;
|
|
84230
85104
|
} catch {}
|
|
@@ -84435,7 +85309,7 @@ function planUpdate(opts) {
|
|
|
84435
85309
|
return;
|
|
84436
85310
|
}
|
|
84437
85311
|
const source = resolve40(import.meta.dirname, "../../skills");
|
|
84438
|
-
const dest =
|
|
85312
|
+
const dest = join69(homedir40(), ".switchroom", "skills", "_bundled");
|
|
84439
85313
|
if (!existsSync67(source)) {
|
|
84440
85314
|
process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
|
|
84441
85315
|
`);
|
|
@@ -84560,7 +85434,7 @@ function defaultStatusProbe(composePath) {
|
|
|
84560
85434
|
} catch {}
|
|
84561
85435
|
let dir = dirname21(scriptPath);
|
|
84562
85436
|
for (let i = 0;i < 8; i++) {
|
|
84563
|
-
const pkgPath =
|
|
85437
|
+
const pkgPath = join69(dir, "package.json");
|
|
84564
85438
|
if (existsSync67(pkgPath)) {
|
|
84565
85439
|
try {
|
|
84566
85440
|
const pkg = JSON.parse(readFileSync60(pkgPath, "utf-8"));
|
|
@@ -85263,7 +86137,7 @@ init_lifecycle();
|
|
|
85263
86137
|
init_resolve_version();
|
|
85264
86138
|
import { execSync as execSync3 } from "node:child_process";
|
|
85265
86139
|
import { existsSync as existsSync68, readFileSync as readFileSync62 } from "node:fs";
|
|
85266
|
-
import { dirname as dirname22, join as
|
|
86140
|
+
import { dirname as dirname22, join as join70 } from "node:path";
|
|
85267
86141
|
function getClaudeCodeVersion() {
|
|
85268
86142
|
try {
|
|
85269
86143
|
const out = execSync3("claude --version 2>/dev/null", {
|
|
@@ -85313,11 +86187,11 @@ function formatUptime3(timestamp) {
|
|
|
85313
86187
|
function locateSwitchroomInstallDir() {
|
|
85314
86188
|
let dir = import.meta.dirname;
|
|
85315
86189
|
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
85316
|
-
const pkgPath =
|
|
86190
|
+
const pkgPath = join70(dir, "package.json");
|
|
85317
86191
|
if (existsSync68(pkgPath)) {
|
|
85318
86192
|
try {
|
|
85319
86193
|
const pkg = JSON.parse(readFileSync62(pkgPath, "utf-8"));
|
|
85320
|
-
if (pkg.name === "switchroom" && existsSync68(
|
|
86194
|
+
if (pkg.name === "switchroom" && existsSync68(join70(dir, ".git"))) {
|
|
85321
86195
|
return dir;
|
|
85322
86196
|
}
|
|
85323
86197
|
} catch {}
|
|
@@ -85500,12 +86374,12 @@ import {
|
|
|
85500
86374
|
statSync as statSync42,
|
|
85501
86375
|
unlinkSync as unlinkSync14
|
|
85502
86376
|
} from "node:fs";
|
|
85503
|
-
import { join as
|
|
86377
|
+
import { join as join71 } from "node:path";
|
|
85504
86378
|
var DEFAULT_SESSION_RETENTION_MAX_COUNT = 20;
|
|
85505
86379
|
var DEFAULT_SESSION_RETENTION_MAX_AGE_DAYS = 30;
|
|
85506
86380
|
var MIN_KEEP = 2;
|
|
85507
86381
|
function collectSessionJsonl(claudeConfigDir) {
|
|
85508
|
-
const projects =
|
|
86382
|
+
const projects = join71(claudeConfigDir, "projects");
|
|
85509
86383
|
if (!existsSync69(projects))
|
|
85510
86384
|
return [];
|
|
85511
86385
|
const found = [];
|
|
@@ -85517,7 +86391,7 @@ function collectSessionJsonl(claudeConfigDir) {
|
|
|
85517
86391
|
return;
|
|
85518
86392
|
}
|
|
85519
86393
|
for (const name of entries) {
|
|
85520
|
-
const full =
|
|
86394
|
+
const full = join71(dir, name);
|
|
85521
86395
|
let st;
|
|
85522
86396
|
try {
|
|
85523
86397
|
st = statSync42(full);
|
|
@@ -85648,7 +86522,7 @@ import {
|
|
|
85648
86522
|
writeFileSync as writeFileSync19,
|
|
85649
86523
|
writeSync as writeSync9
|
|
85650
86524
|
} from "node:fs";
|
|
85651
|
-
import { join as
|
|
86525
|
+
import { join as join72 } from "node:path";
|
|
85652
86526
|
import { randomBytes as randomBytes12 } from "node:crypto";
|
|
85653
86527
|
import { execSync as execSync4 } from "node:child_process";
|
|
85654
86528
|
|
|
@@ -86079,7 +86953,7 @@ function redactedMarker(ruleId) {
|
|
|
86079
86953
|
var ISSUES_FILE = "issues.jsonl";
|
|
86080
86954
|
var ISSUES_LOCK = "issues.lock";
|
|
86081
86955
|
function readAll(stateDir) {
|
|
86082
|
-
const path5 =
|
|
86956
|
+
const path5 = join72(stateDir, ISSUES_FILE);
|
|
86083
86957
|
if (!existsSync70(path5))
|
|
86084
86958
|
return [];
|
|
86085
86959
|
let raw;
|
|
@@ -86157,7 +87031,7 @@ function record(stateDir, input, nowFn = Date.now) {
|
|
|
86157
87031
|
});
|
|
86158
87032
|
}
|
|
86159
87033
|
function resolve43(stateDir, fingerprint, nowFn = Date.now) {
|
|
86160
|
-
if (!existsSync70(
|
|
87034
|
+
if (!existsSync70(join72(stateDir, ISSUES_FILE)))
|
|
86161
87035
|
return 0;
|
|
86162
87036
|
return withLock(stateDir, () => {
|
|
86163
87037
|
const all = readAll(stateDir);
|
|
@@ -86175,7 +87049,7 @@ function resolve43(stateDir, fingerprint, nowFn = Date.now) {
|
|
|
86175
87049
|
});
|
|
86176
87050
|
}
|
|
86177
87051
|
function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
86178
|
-
if (!existsSync70(
|
|
87052
|
+
if (!existsSync70(join72(stateDir, ISSUES_FILE)))
|
|
86179
87053
|
return 0;
|
|
86180
87054
|
return withLock(stateDir, () => {
|
|
86181
87055
|
const all = readAll(stateDir);
|
|
@@ -86193,7 +87067,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
|
86193
87067
|
});
|
|
86194
87068
|
}
|
|
86195
87069
|
function prune(stateDir, opts = {}) {
|
|
86196
|
-
if (!existsSync70(
|
|
87070
|
+
if (!existsSync70(join72(stateDir, ISSUES_FILE)))
|
|
86197
87071
|
return 0;
|
|
86198
87072
|
return withLock(stateDir, () => {
|
|
86199
87073
|
const all = readAll(stateDir);
|
|
@@ -86226,7 +87100,7 @@ function ensureDir(stateDir) {
|
|
|
86226
87100
|
mkdirSync36(stateDir, { recursive: true });
|
|
86227
87101
|
}
|
|
86228
87102
|
function writeAll(stateDir, events) {
|
|
86229
|
-
const path5 =
|
|
87103
|
+
const path5 = join72(stateDir, ISSUES_FILE);
|
|
86230
87104
|
sweepOrphanTmpFiles(stateDir);
|
|
86231
87105
|
const tmp = `${path5}.tmp-${process.pid}-${randomBytes12(4).toString("hex")}`;
|
|
86232
87106
|
const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
|
|
@@ -86248,7 +87122,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
86248
87122
|
for (const entry of entries) {
|
|
86249
87123
|
if (!entry.startsWith(TMP_PREFIX))
|
|
86250
87124
|
continue;
|
|
86251
|
-
const tmpPath =
|
|
87125
|
+
const tmpPath = join72(stateDir, entry);
|
|
86252
87126
|
try {
|
|
86253
87127
|
const stat = statSync43(tmpPath);
|
|
86254
87128
|
if (stat.mtimeMs < cutoff) {
|
|
@@ -86260,7 +87134,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
86260
87134
|
var LOCK_RETRY_MS = 25;
|
|
86261
87135
|
var LOCK_TIMEOUT_MS = 1e4;
|
|
86262
87136
|
function withLock(stateDir, fn) {
|
|
86263
|
-
const lockPath =
|
|
87137
|
+
const lockPath = join72(stateDir, ISSUES_LOCK);
|
|
86264
87138
|
const startedAt = Date.now();
|
|
86265
87139
|
let fd = null;
|
|
86266
87140
|
while (fd === null) {
|
|
@@ -86545,7 +87419,7 @@ function relTime(deltaMs) {
|
|
|
86545
87419
|
init_source();
|
|
86546
87420
|
import { existsSync as existsSync73 } from "node:fs";
|
|
86547
87421
|
import { homedir as homedir44 } from "node:os";
|
|
86548
|
-
import { join as
|
|
87422
|
+
import { join as join75, resolve as resolve44 } from "node:path";
|
|
86549
87423
|
|
|
86550
87424
|
// src/deps/python.ts
|
|
86551
87425
|
import { createHash as createHash13 } from "node:crypto";
|
|
@@ -86556,7 +87430,7 @@ import {
|
|
|
86556
87430
|
rmSync as rmSync13,
|
|
86557
87431
|
writeFileSync as writeFileSync20
|
|
86558
87432
|
} from "node:fs";
|
|
86559
|
-
import { dirname as dirname23, join as
|
|
87433
|
+
import { dirname as dirname23, join as join73 } from "node:path";
|
|
86560
87434
|
import { homedir as homedir42 } from "node:os";
|
|
86561
87435
|
import { execFileSync as execFileSync21 } from "node:child_process";
|
|
86562
87436
|
|
|
@@ -86569,7 +87443,7 @@ class PythonEnvError extends Error {
|
|
|
86569
87443
|
}
|
|
86570
87444
|
}
|
|
86571
87445
|
function defaultPythonCacheRoot() {
|
|
86572
|
-
return
|
|
87446
|
+
return join73(homedir42(), ".switchroom", "deps", "python");
|
|
86573
87447
|
}
|
|
86574
87448
|
function hashFile(path5) {
|
|
86575
87449
|
return createHash13("sha256").update(readFileSync64(path5)).digest("hex");
|
|
@@ -86581,11 +87455,11 @@ function ensurePythonEnv(opts) {
|
|
|
86581
87455
|
if (!existsSync71(requirementsPath)) {
|
|
86582
87456
|
throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
|
|
86583
87457
|
}
|
|
86584
|
-
const venvDir =
|
|
86585
|
-
const stampPath =
|
|
86586
|
-
const binDir =
|
|
86587
|
-
const pythonBin =
|
|
86588
|
-
const pipBin =
|
|
87458
|
+
const venvDir = join73(cacheRoot, skillName);
|
|
87459
|
+
const stampPath = join73(venvDir, ".requirements.sha256");
|
|
87460
|
+
const binDir = join73(venvDir, "bin");
|
|
87461
|
+
const pythonBin = join73(binDir, "python");
|
|
87462
|
+
const pipBin = join73(binDir, "pip");
|
|
86589
87463
|
const targetHash = hashFile(requirementsPath);
|
|
86590
87464
|
if (!force && existsSync71(stampPath) && existsSync71(pythonBin)) {
|
|
86591
87465
|
const existingHash = readFileSync64(stampPath, "utf8").trim();
|
|
@@ -86644,7 +87518,7 @@ import {
|
|
|
86644
87518
|
rmSync as rmSync14,
|
|
86645
87519
|
writeFileSync as writeFileSync21
|
|
86646
87520
|
} from "node:fs";
|
|
86647
|
-
import { dirname as dirname24, join as
|
|
87521
|
+
import { dirname as dirname24, join as join74 } from "node:path";
|
|
86648
87522
|
import { homedir as homedir43 } from "node:os";
|
|
86649
87523
|
import { execFileSync as execFileSync22 } from "node:child_process";
|
|
86650
87524
|
|
|
@@ -86668,7 +87542,7 @@ var LOCKFILES_FOR = {
|
|
|
86668
87542
|
npm: ["package-lock.json"]
|
|
86669
87543
|
};
|
|
86670
87544
|
function defaultNodeCacheRoot() {
|
|
86671
|
-
return
|
|
87545
|
+
return join74(homedir43(), ".switchroom", "deps", "node");
|
|
86672
87546
|
}
|
|
86673
87547
|
function hashDepInputs(packageJsonPath) {
|
|
86674
87548
|
const sourceDir = dirname24(packageJsonPath);
|
|
@@ -86677,7 +87551,7 @@ function hashDepInputs(packageJsonPath) {
|
|
|
86677
87551
|
`);
|
|
86678
87552
|
hasher.update(readFileSync65(packageJsonPath));
|
|
86679
87553
|
for (const lockName of ALL_LOCKFILES) {
|
|
86680
|
-
const lockPath =
|
|
87554
|
+
const lockPath = join74(sourceDir, lockName);
|
|
86681
87555
|
if (existsSync72(lockPath)) {
|
|
86682
87556
|
hasher.update(`
|
|
86683
87557
|
`);
|
|
@@ -86697,10 +87571,10 @@ function ensureNodeEnv(opts) {
|
|
|
86697
87571
|
throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
|
|
86698
87572
|
}
|
|
86699
87573
|
const sourceDir = dirname24(packageJsonPath);
|
|
86700
|
-
const envDir =
|
|
86701
|
-
const stampPath =
|
|
86702
|
-
const nodeModulesDir =
|
|
86703
|
-
const binDir =
|
|
87574
|
+
const envDir = join74(cacheRoot, skillName);
|
|
87575
|
+
const stampPath = join74(envDir, ".package.sha256");
|
|
87576
|
+
const nodeModulesDir = join74(envDir, "node_modules");
|
|
87577
|
+
const binDir = join74(nodeModulesDir, ".bin");
|
|
86704
87578
|
const targetHash = hashDepInputs(packageJsonPath);
|
|
86705
87579
|
if (!force && existsSync72(stampPath) && existsSync72(nodeModulesDir)) {
|
|
86706
87580
|
const existingHash = readFileSync65(stampPath, "utf8").trim();
|
|
@@ -86718,12 +87592,12 @@ function ensureNodeEnv(opts) {
|
|
|
86718
87592
|
rmSync14(envDir, { recursive: true, force: true });
|
|
86719
87593
|
}
|
|
86720
87594
|
mkdirSync38(envDir, { recursive: true });
|
|
86721
|
-
copyFileSync10(packageJsonPath,
|
|
87595
|
+
copyFileSync10(packageJsonPath, join74(envDir, "package.json"));
|
|
86722
87596
|
let copiedLockfile = false;
|
|
86723
87597
|
for (const lockName of LOCKFILES_FOR[installer]) {
|
|
86724
|
-
const lockPath =
|
|
87598
|
+
const lockPath = join74(sourceDir, lockName);
|
|
86725
87599
|
if (existsSync72(lockPath)) {
|
|
86726
|
-
copyFileSync10(lockPath,
|
|
87600
|
+
copyFileSync10(lockPath, join74(envDir, lockName));
|
|
86727
87601
|
copiedLockfile = true;
|
|
86728
87602
|
}
|
|
86729
87603
|
}
|
|
@@ -86762,13 +87636,13 @@ function registerDepsCommand(program3) {
|
|
|
86762
87636
|
console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
|
|
86763
87637
|
process.exit(1);
|
|
86764
87638
|
}
|
|
86765
|
-
const skillDir =
|
|
87639
|
+
const skillDir = join75(skillsRoot, skill);
|
|
86766
87640
|
if (!existsSync73(skillDir)) {
|
|
86767
87641
|
console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
|
|
86768
87642
|
process.exit(1);
|
|
86769
87643
|
}
|
|
86770
|
-
const requirementsPath =
|
|
86771
|
-
const packageJsonPath =
|
|
87644
|
+
const requirementsPath = join75(skillDir, "requirements.txt");
|
|
87645
|
+
const packageJsonPath = join75(skillDir, "package.json");
|
|
86772
87646
|
const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync73(requirementsPath));
|
|
86773
87647
|
const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync73(packageJsonPath));
|
|
86774
87648
|
let did = 0;
|
|
@@ -87723,7 +88597,7 @@ init_helpers();
|
|
|
87723
88597
|
init_loader();
|
|
87724
88598
|
init_merge();
|
|
87725
88599
|
import { copyFileSync as copyFileSync11, existsSync as existsSync75, readFileSync as readFileSync66, writeFileSync as writeFileSync22 } from "node:fs";
|
|
87726
|
-
import { join as
|
|
88600
|
+
import { join as join76, resolve as resolve46 } from "node:path";
|
|
87727
88601
|
init_scaffold();
|
|
87728
88602
|
init_profiles();
|
|
87729
88603
|
init_schema();
|
|
@@ -87749,7 +88623,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
87749
88623
|
profileName,
|
|
87750
88624
|
profilePath,
|
|
87751
88625
|
workspaceDir,
|
|
87752
|
-
soulPath:
|
|
88626
|
+
soulPath: join76(workspaceDir, "SOUL.md"),
|
|
87753
88627
|
soul: merged.soul
|
|
87754
88628
|
};
|
|
87755
88629
|
}
|
|
@@ -87816,7 +88690,7 @@ function registerSoulCommand(program3) {
|
|
|
87816
88690
|
init_helpers();
|
|
87817
88691
|
init_loader();
|
|
87818
88692
|
import { existsSync as existsSync76, readFileSync as readFileSync67, readdirSync as readdirSync26, statSync as statSync44 } from "node:fs";
|
|
87819
|
-
import { resolve as resolve47, join as
|
|
88693
|
+
import { resolve as resolve47, join as join77 } from "node:path";
|
|
87820
88694
|
import { createHash as createHash15 } from "node:crypto";
|
|
87821
88695
|
init_merge();
|
|
87822
88696
|
init_hindsight2();
|
|
@@ -87827,7 +88701,7 @@ function estimateTokens(bytes) {
|
|
|
87827
88701
|
return Math.round(bytes / 3.7);
|
|
87828
88702
|
}
|
|
87829
88703
|
function readMcpServerNames(agentDir) {
|
|
87830
|
-
const mcpPath =
|
|
88704
|
+
const mcpPath = join77(agentDir, ".mcp.json");
|
|
87831
88705
|
if (!existsSync76(mcpPath))
|
|
87832
88706
|
return [];
|
|
87833
88707
|
try {
|
|
@@ -87841,7 +88715,7 @@ function sha256(content) {
|
|
|
87841
88715
|
return createHash15("sha256").update(content).digest("hex").slice(0, 16);
|
|
87842
88716
|
}
|
|
87843
88717
|
function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
87844
|
-
const projectsDir =
|
|
88718
|
+
const projectsDir = join77(claudeConfigDir, "projects");
|
|
87845
88719
|
if (!existsSync76(projectsDir))
|
|
87846
88720
|
return;
|
|
87847
88721
|
try {
|
|
@@ -87850,8 +88724,8 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
87850
88724
|
for (const entry of entries) {
|
|
87851
88725
|
if (!entry.isDirectory())
|
|
87852
88726
|
continue;
|
|
87853
|
-
const projectPath =
|
|
87854
|
-
const transcriptPath =
|
|
88727
|
+
const projectPath = join77(projectsDir, entry.name);
|
|
88728
|
+
const transcriptPath = join77(projectPath, "transcript.jsonl");
|
|
87855
88729
|
if (!existsSync76(transcriptPath))
|
|
87856
88730
|
continue;
|
|
87857
88731
|
const stat3 = statSync44(transcriptPath);
|
|
@@ -87920,11 +88794,11 @@ function registerDebugCommand(program3) {
|
|
|
87920
88794
|
process.exit(1);
|
|
87921
88795
|
}
|
|
87922
88796
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
87923
|
-
const claudeConfigDir =
|
|
87924
|
-
const claudeMdPath =
|
|
87925
|
-
const soulMdPath =
|
|
87926
|
-
const workspaceSoulMdPath =
|
|
87927
|
-
const handoffPath =
|
|
88797
|
+
const claudeConfigDir = join77(agentDir, ".claude");
|
|
88798
|
+
const claudeMdPath = join77(agentDir, "CLAUDE.md");
|
|
88799
|
+
const soulMdPath = join77(agentDir, "SOUL.md");
|
|
88800
|
+
const workspaceSoulMdPath = join77(workspaceDir, "SOUL.md");
|
|
88801
|
+
const handoffPath = join77(agentDir, ".handoff.md");
|
|
87928
88802
|
const lastN = parseInt(opts.last, 10);
|
|
87929
88803
|
if (isNaN(lastN) || lastN < 1) {
|
|
87930
88804
|
console.error("--last must be a positive integer");
|
|
@@ -88053,9 +88927,9 @@ function registerDebugCommand(program3) {
|
|
|
88053
88927
|
const soulMdBytes = soulMdContent.length;
|
|
88054
88928
|
const perTurnBytes = dynamicResult.concatenated.length;
|
|
88055
88929
|
const userBytes = userMessage?.text.length ?? 0;
|
|
88056
|
-
const fleetDir =
|
|
88057
|
-
const fleetInvPath =
|
|
88058
|
-
const fleetClaudePath =
|
|
88930
|
+
const fleetDir = join77(agentsDir, "..", "fleet");
|
|
88931
|
+
const fleetInvPath = join77(fleetDir, "switchroom-invariants.md");
|
|
88932
|
+
const fleetClaudePath = join77(fleetDir, "CLAUDE.md");
|
|
88059
88933
|
const fleetInvBytes = existsSync76(fleetInvPath) ? readFileSync67(fleetInvPath, "utf-8").length : 0;
|
|
88060
88934
|
const fleetClaudeBytes = existsSync76(fleetClaudePath) ? readFileSync67(fleetClaudePath, "utf-8").length : 0;
|
|
88061
88935
|
const fleetBytes = fleetInvBytes + fleetClaudeBytes;
|
|
@@ -88091,7 +88965,7 @@ init_source();
|
|
|
88091
88965
|
// src/worktree/claim.ts
|
|
88092
88966
|
import { execFileSync as execFileSync23 } from "node:child_process";
|
|
88093
88967
|
import { closeSync as closeSync13, mkdirSync as mkdirSync40, openSync as openSync13, existsSync as existsSync78, unlinkSync as unlinkSync17 } from "node:fs";
|
|
88094
|
-
import { join as
|
|
88968
|
+
import { join as join79, resolve as resolve49 } from "node:path";
|
|
88095
88969
|
import { homedir as homedir46 } from "node:os";
|
|
88096
88970
|
import { randomBytes as randomBytes13 } from "node:crypto";
|
|
88097
88971
|
|
|
@@ -88105,13 +88979,13 @@ import {
|
|
|
88105
88979
|
existsSync as existsSync77,
|
|
88106
88980
|
renameSync as renameSync17
|
|
88107
88981
|
} from "node:fs";
|
|
88108
|
-
import { join as
|
|
88982
|
+
import { join as join78, resolve as resolve48 } from "node:path";
|
|
88109
88983
|
import { homedir as homedir45 } from "node:os";
|
|
88110
88984
|
function registryDir() {
|
|
88111
|
-
return resolve48(process.env.SWITCHROOM_WORKTREE_DIR ??
|
|
88985
|
+
return resolve48(process.env.SWITCHROOM_WORKTREE_DIR ?? join78(homedir45(), ".switchroom", "worktrees"));
|
|
88112
88986
|
}
|
|
88113
88987
|
function recordPath(id) {
|
|
88114
|
-
return
|
|
88988
|
+
return join78(registryDir(), `${id}.json`);
|
|
88115
88989
|
}
|
|
88116
88990
|
function ensureDir2() {
|
|
88117
88991
|
mkdirSync39(registryDir(), { recursive: true });
|
|
@@ -88162,7 +89036,7 @@ function acquireRepoLock(repoPath) {
|
|
|
88162
89036
|
const lockDir = registryDir();
|
|
88163
89037
|
mkdirSync40(lockDir, { recursive: true });
|
|
88164
89038
|
const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
|
|
88165
|
-
const lockPath =
|
|
89039
|
+
const lockPath = join79(lockDir, `.lock-${lockName}`);
|
|
88166
89040
|
const deadline = Date.now() + 5000;
|
|
88167
89041
|
let fd = null;
|
|
88168
89042
|
while (fd === null) {
|
|
@@ -88189,7 +89063,7 @@ function acquireRepoLock(repoPath) {
|
|
|
88189
89063
|
}
|
|
88190
89064
|
var DEFAULT_CONCURRENCY = 5;
|
|
88191
89065
|
function worktreesBaseDir() {
|
|
88192
|
-
return resolve49(process.env.SWITCHROOM_WORKTREE_BASE ??
|
|
89066
|
+
return resolve49(process.env.SWITCHROOM_WORKTREE_BASE ?? join79(homedir46(), ".switchroom", "worktree-checkouts"));
|
|
88193
89067
|
}
|
|
88194
89068
|
function shortId() {
|
|
88195
89069
|
return randomBytes13(4).toString("hex");
|
|
@@ -88211,7 +89085,7 @@ function resolveRepoPath(repo, codeRepos) {
|
|
|
88211
89085
|
}
|
|
88212
89086
|
function expandHome(p) {
|
|
88213
89087
|
if (p.startsWith("~/"))
|
|
88214
|
-
return
|
|
89088
|
+
return join79(homedir46(), p.slice(2));
|
|
88215
89089
|
return p;
|
|
88216
89090
|
}
|
|
88217
89091
|
async function claimWorktree(input, codeRepos) {
|
|
@@ -88239,7 +89113,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
88239
89113
|
branch = `task/${taskSuffix}-${id}`;
|
|
88240
89114
|
const baseDir = worktreesBaseDir();
|
|
88241
89115
|
mkdirSync40(baseDir, { recursive: true });
|
|
88242
|
-
worktreePath =
|
|
89116
|
+
worktreePath = join79(baseDir, `${id}-${taskSuffix}`);
|
|
88243
89117
|
const ambientOwner = process.env.SWITCHROOM_AGENT_NAME;
|
|
88244
89118
|
const ownerAgent = input.ownerAgent ?? (ambientOwner != null && ambientOwner !== "" ? ambientOwner : undefined);
|
|
88245
89119
|
const now = new Date().toISOString();
|
|
@@ -88470,7 +89344,7 @@ import {
|
|
|
88470
89344
|
rmSync as rmSync15
|
|
88471
89345
|
} from "node:fs";
|
|
88472
89346
|
import { homedir as homedir47 } from "node:os";
|
|
88473
|
-
import { join as
|
|
89347
|
+
import { join as join80, resolve as resolve50 } from "node:path";
|
|
88474
89348
|
function parseGitdirPointer(dotGitFileContents) {
|
|
88475
89349
|
const m = /^gitdir:\s*(.+?)\s*$/m.exec(dotGitFileContents);
|
|
88476
89350
|
return m ? m[1] : null;
|
|
@@ -88585,7 +89459,7 @@ function defaultPrSignal(repo, branch, exec) {
|
|
|
88585
89459
|
}
|
|
88586
89460
|
}
|
|
88587
89461
|
function trashRoot() {
|
|
88588
|
-
return resolve50(process.env.SWITCHROOM_WORKTREE_TRASH ??
|
|
89462
|
+
return resolve50(process.env.SWITCHROOM_WORKTREE_TRASH ?? join80(homedir47(), ".switchroom", "worktree-gc-trash"));
|
|
88589
89463
|
}
|
|
88590
89464
|
function planGc(roots, deps = {}) {
|
|
88591
89465
|
const exists = deps.existsSync ?? existsSync81;
|
|
@@ -88595,7 +89469,7 @@ function planGc(roots, deps = {}) {
|
|
|
88595
89469
|
const exec = deps.exec ?? defaultExec;
|
|
88596
89470
|
const prSignal = deps.prSignal ?? ((repo, branch) => defaultPrSignal(repo, branch, exec));
|
|
88597
89471
|
const stamp = deps.dateStamp ?? "undated";
|
|
88598
|
-
const trash =
|
|
89472
|
+
const trash = join80(trashRoot(), stamp);
|
|
88599
89473
|
let claimed;
|
|
88600
89474
|
try {
|
|
88601
89475
|
claimed = new Set(listRecords().map((r) => resolve50(r.path)));
|
|
@@ -88631,10 +89505,10 @@ function planGc(roots, deps = {}) {
|
|
|
88631
89505
|
continue;
|
|
88632
89506
|
}
|
|
88633
89507
|
for (const name of entries) {
|
|
88634
|
-
const dir =
|
|
89508
|
+
const dir = join80(root, name);
|
|
88635
89509
|
if (isEphemeralPath(dir))
|
|
88636
89510
|
continue;
|
|
88637
|
-
const dotGit =
|
|
89511
|
+
const dotGit = join80(dir, ".git");
|
|
88638
89512
|
if (!exists(dotGit))
|
|
88639
89513
|
continue;
|
|
88640
89514
|
let st;
|
|
@@ -88663,7 +89537,7 @@ function planGc(roots, deps = {}) {
|
|
|
88663
89537
|
ownerRepos.add(repoRoot);
|
|
88664
89538
|
if (exists(ptr))
|
|
88665
89539
|
continue;
|
|
88666
|
-
orphans.push({ dir, owner: repoRoot, dest:
|
|
89540
|
+
orphans.push({ dir, owner: repoRoot, dest: join80(trash, name) });
|
|
88667
89541
|
}
|
|
88668
89542
|
}
|
|
88669
89543
|
const registered = [];
|
|
@@ -88774,7 +89648,7 @@ function listTrashEntries(nowMs, deps = {}) {
|
|
|
88774
89648
|
return [];
|
|
88775
89649
|
const out = [];
|
|
88776
89650
|
for (const stamp of readDir(root)) {
|
|
88777
|
-
const stampDir =
|
|
89651
|
+
const stampDir = join80(root, stamp);
|
|
88778
89652
|
let names;
|
|
88779
89653
|
try {
|
|
88780
89654
|
names = readDir(stampDir);
|
|
@@ -88782,7 +89656,7 @@ function listTrashEntries(nowMs, deps = {}) {
|
|
|
88782
89656
|
continue;
|
|
88783
89657
|
}
|
|
88784
89658
|
for (const name of names) {
|
|
88785
|
-
const p =
|
|
89659
|
+
const p = join80(stampDir, name);
|
|
88786
89660
|
let mtimeMs = nowMs;
|
|
88787
89661
|
try {
|
|
88788
89662
|
mtimeMs = statSync45(p).mtimeMs;
|
|
@@ -88806,7 +89680,7 @@ function purgeTrash(paths) {
|
|
|
88806
89680
|
return { deleted, errors: errors2 };
|
|
88807
89681
|
}
|
|
88808
89682
|
function defaultRoots() {
|
|
88809
|
-
return [
|
|
89683
|
+
return [join80(homedir47(), "code")];
|
|
88810
89684
|
}
|
|
88811
89685
|
|
|
88812
89686
|
// src/cli/worktree.ts
|
|
@@ -89034,7 +89908,7 @@ import {
|
|
|
89034
89908
|
rmSync as rmSync16,
|
|
89035
89909
|
writeFileSync as writeFileSync24
|
|
89036
89910
|
} from "node:fs";
|
|
89037
|
-
import { join as
|
|
89911
|
+
import { join as join81 } from "node:path";
|
|
89038
89912
|
function encodeCredentialsFilename(email) {
|
|
89039
89913
|
const SAFE = new Set([
|
|
89040
89914
|
..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
|
@@ -89224,16 +90098,16 @@ function resolveCredentialsDir(env2) {
|
|
|
89224
90098
|
if (explicit && explicit.length > 0)
|
|
89225
90099
|
return explicit;
|
|
89226
90100
|
const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
|
|
89227
|
-
return
|
|
90101
|
+
return join81(stateBase, "google-workspace-mcp", "credentials");
|
|
89228
90102
|
}
|
|
89229
90103
|
function writeSeedFile(dir, email, seed) {
|
|
89230
90104
|
mkdirSync42(dir, { recursive: true, mode: 448 });
|
|
89231
90105
|
chmodSync11(dir, 448);
|
|
89232
90106
|
for (const name of readdirSync29(dir)) {
|
|
89233
|
-
rmSync16(
|
|
90107
|
+
rmSync16(join81(dir, name), { force: true, recursive: true });
|
|
89234
90108
|
}
|
|
89235
90109
|
const filename = encodeCredentialsFilename(email);
|
|
89236
|
-
const filePath =
|
|
90110
|
+
const filePath = join81(dir, filename);
|
|
89237
90111
|
writeFileSync24(filePath, JSON.stringify(seed), { mode: 384 });
|
|
89238
90112
|
chmodSync11(filePath, 384);
|
|
89239
90113
|
return filePath;
|
|
@@ -89393,7 +90267,7 @@ function registerDriveMcpLauncherCommand(program3) {
|
|
|
89393
90267
|
init_scaffold_integration();
|
|
89394
90268
|
import { spawn as spawn5 } from "node:child_process";
|
|
89395
90269
|
import { writeFileSync as writeFileSync25, mkdirSync as mkdirSync43 } from "node:fs";
|
|
89396
|
-
import { dirname as dirname25, join as
|
|
90270
|
+
import { dirname as dirname25, join as join82 } from "node:path";
|
|
89397
90271
|
var SOFTERIA_TOKEN_ENV = "MS365_MCP_OAUTH_TOKEN";
|
|
89398
90272
|
var DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
|
|
89399
90273
|
var MAX_REFRESH_INTERVAL_MS = 60 * 60 * 1000;
|
|
@@ -89438,7 +90312,7 @@ function heartbeatPath(agentName, account) {
|
|
|
89438
90312
|
const override = process.env.SWITCHROOM_M365_HEARTBEAT_DIR;
|
|
89439
90313
|
if (override) {
|
|
89440
90314
|
const base = slug ? `m365-launcher-${agentName}-${slug}` : `m365-launcher-${agentName}`;
|
|
89441
|
-
return
|
|
90315
|
+
return join82(override, `${base}.heartbeat.json`);
|
|
89442
90316
|
}
|
|
89443
90317
|
return slug ? `/state/agent/m365-launcher-${slug}.heartbeat.json` : "/state/agent/m365-launcher.heartbeat.json";
|
|
89444
90318
|
}
|
|
@@ -89796,7 +90670,7 @@ function registerNotionMcpLauncherCommand(program3) {
|
|
|
89796
90670
|
init_hindsight();
|
|
89797
90671
|
import { mkdirSync as mkdirSync45, readFileSync as readFileSync70, renameSync as renameSync19, writeFileSync as writeFileSync27 } from "node:fs";
|
|
89798
90672
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
89799
|
-
import { join as
|
|
90673
|
+
import { join as join83 } from "node:path";
|
|
89800
90674
|
import { createInterface as createInterface6 } from "node:readline";
|
|
89801
90675
|
var SHIM_SUPPORTED_PROTOCOL_VERSIONS = [
|
|
89802
90676
|
"2025-06-18",
|
|
@@ -90019,12 +90893,12 @@ class HindsightShim {
|
|
|
90019
90893
|
`));
|
|
90020
90894
|
}
|
|
90021
90895
|
get cachePath() {
|
|
90022
|
-
return
|
|
90896
|
+
return join83(this.opts.cacheDir, TOOLS_CACHE_FILENAME);
|
|
90023
90897
|
}
|
|
90024
90898
|
writeCache(result) {
|
|
90025
90899
|
try {
|
|
90026
90900
|
mkdirSync45(this.opts.cacheDir, { recursive: true });
|
|
90027
|
-
const tmp =
|
|
90901
|
+
const tmp = join83(this.opts.cacheDir, `.${TOOLS_CACHE_FILENAME}.${process.pid}.tmp`);
|
|
90028
90902
|
writeFileSync27(tmp, JSON.stringify(result, null, 2) + `
|
|
90029
90903
|
`);
|
|
90030
90904
|
renameSync19(tmp, this.cachePath);
|
|
@@ -90197,7 +91071,7 @@ function resolveShimOptionsFromEnv(env2) {
|
|
|
90197
91071
|
return {
|
|
90198
91072
|
url: env2.HINDSIGHT_MCP_URL || HINDSIGHT_DEFAULT_MCP_URL,
|
|
90199
91073
|
bankId: env2.HINDSIGHT_BANK_ID || "",
|
|
90200
|
-
cacheDir: env2.HINDSIGHT_SHIM_CACHE_DIR ||
|
|
91074
|
+
cacheDir: env2.HINDSIGHT_SHIM_CACHE_DIR || join83(home2, ".hindsight-shim")
|
|
90201
91075
|
};
|
|
90202
91076
|
}
|
|
90203
91077
|
function registerHindsightMcpShimCommand(program3) {
|
|
@@ -90872,7 +91746,7 @@ function runRedactStdin() {
|
|
|
90872
91746
|
|
|
90873
91747
|
// src/cli/status-ask.ts
|
|
90874
91748
|
import { readFileSync as readFileSync75, existsSync as existsSync87, readdirSync as readdirSync31 } from "node:fs";
|
|
90875
|
-
import { join as
|
|
91749
|
+
import { join as join88 } from "node:path";
|
|
90876
91750
|
import { homedir as homedir50 } from "node:os";
|
|
90877
91751
|
|
|
90878
91752
|
// src/status-ask/report.ts
|
|
@@ -91208,7 +92082,7 @@ function resolveSources(explicitPath) {
|
|
|
91208
92082
|
const config = loadConfig();
|
|
91209
92083
|
agentsDir = resolveAgentsDir(config);
|
|
91210
92084
|
} catch {
|
|
91211
|
-
agentsDir =
|
|
92085
|
+
agentsDir = join88(homedir50(), ".switchroom", "agents");
|
|
91212
92086
|
}
|
|
91213
92087
|
if (!existsSync87(agentsDir))
|
|
91214
92088
|
return [];
|
|
@@ -91220,7 +92094,7 @@ function resolveSources(explicitPath) {
|
|
|
91220
92094
|
return [];
|
|
91221
92095
|
}
|
|
91222
92096
|
for (const name of entries) {
|
|
91223
|
-
const path9 =
|
|
92097
|
+
const path9 = join88(agentsDir, name, "runtime-metrics.jsonl");
|
|
91224
92098
|
if (existsSync87(path9)) {
|
|
91225
92099
|
sources.push({ path: path9, agent: name });
|
|
91226
92100
|
}
|
|
@@ -91262,21 +92136,21 @@ import {
|
|
|
91262
92136
|
unlinkSync as unlinkSync18,
|
|
91263
92137
|
writeSync as writeSync10
|
|
91264
92138
|
} from "node:fs";
|
|
91265
|
-
import { join as
|
|
92139
|
+
import { join as join89, resolve as resolve53 } from "node:path";
|
|
91266
92140
|
var STAGING_SUBDIR = ".staging";
|
|
91267
92141
|
function overlayPathsFor(agent, opts = {}) {
|
|
91268
92142
|
const base = opts.root ? resolve53(opts.root, agent) : resolve53(resolveDualPath(`~/.switchroom/agents/${agent}`));
|
|
91269
|
-
const scheduleDir =
|
|
91270
|
-
const scheduleStagingDir =
|
|
91271
|
-
const skillsDir =
|
|
91272
|
-
const skillsStagingDir =
|
|
92143
|
+
const scheduleDir = join89(base, "schedule.d");
|
|
92144
|
+
const scheduleStagingDir = join89(scheduleDir, STAGING_SUBDIR);
|
|
92145
|
+
const skillsDir = join89(base, "skills.d");
|
|
92146
|
+
const skillsStagingDir = join89(skillsDir, STAGING_SUBDIR);
|
|
91273
92147
|
return {
|
|
91274
92148
|
agentRoot: base,
|
|
91275
92149
|
scheduleDir,
|
|
91276
92150
|
scheduleStagingDir,
|
|
91277
92151
|
skillsDir,
|
|
91278
92152
|
skillsStagingDir,
|
|
91279
|
-
lockPath:
|
|
92153
|
+
lockPath: join89(base, ".lock"),
|
|
91280
92154
|
stagingDir: scheduleStagingDir
|
|
91281
92155
|
};
|
|
91282
92156
|
}
|
|
@@ -91330,8 +92204,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
91330
92204
|
const paths = overlayPathsFor(agent, opts);
|
|
91331
92205
|
return withAgentLock(paths, () => {
|
|
91332
92206
|
ensureDirs(paths);
|
|
91333
|
-
const stagingPath =
|
|
91334
|
-
const finalPath =
|
|
92207
|
+
const stagingPath = join89(paths.scheduleStagingDir, `${slug}.yaml`);
|
|
92208
|
+
const finalPath = join89(paths.scheduleDir, `${slug}.yaml`);
|
|
91335
92209
|
const fd = openSync14(stagingPath, "w", 384);
|
|
91336
92210
|
try {
|
|
91337
92211
|
writeSync10(fd, yamlText);
|
|
@@ -91347,8 +92221,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
91347
92221
|
const paths = overlayPathsFor(agent, opts);
|
|
91348
92222
|
return withAgentLock(paths, () => {
|
|
91349
92223
|
ensureSkillsDirs(paths);
|
|
91350
|
-
const stagingPath =
|
|
91351
|
-
const finalPath =
|
|
92224
|
+
const stagingPath = join89(paths.skillsStagingDir, `${slug}.yaml`);
|
|
92225
|
+
const finalPath = join89(paths.skillsDir, `${slug}.yaml`);
|
|
91352
92226
|
const fd = openSync14(stagingPath, "w", 384);
|
|
91353
92227
|
try {
|
|
91354
92228
|
writeSync10(fd, yamlText);
|
|
@@ -91363,7 +92237,7 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
91363
92237
|
function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
91364
92238
|
const paths = overlayPathsFor(agent, opts);
|
|
91365
92239
|
return withAgentLock(paths, () => {
|
|
91366
|
-
const finalPath =
|
|
92240
|
+
const finalPath = join89(paths.skillsDir, `${slug}.yaml`);
|
|
91367
92241
|
if (!existsSync88(finalPath))
|
|
91368
92242
|
return false;
|
|
91369
92243
|
unlinkSync18(finalPath);
|
|
@@ -91378,7 +92252,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
91378
92252
|
for (const name of readdirSync32(paths.skillsDir)) {
|
|
91379
92253
|
if (!/\.ya?ml$/i.test(name))
|
|
91380
92254
|
continue;
|
|
91381
|
-
const full =
|
|
92255
|
+
const full = join89(paths.skillsDir, name);
|
|
91382
92256
|
try {
|
|
91383
92257
|
const raw = readFileSync76(full, "utf-8");
|
|
91384
92258
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
@@ -91390,7 +92264,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
91390
92264
|
function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
91391
92265
|
const paths = overlayPathsFor(agent, opts);
|
|
91392
92266
|
return withAgentLock(paths, () => {
|
|
91393
|
-
const finalPath =
|
|
92267
|
+
const finalPath = join89(paths.scheduleDir, `${slug}.yaml`);
|
|
91394
92268
|
if (!existsSync88(finalPath))
|
|
91395
92269
|
return false;
|
|
91396
92270
|
unlinkSync18(finalPath);
|
|
@@ -91405,7 +92279,7 @@ function listOverlayEntries(agent, opts = {}) {
|
|
|
91405
92279
|
for (const name of readdirSync32(paths.scheduleDir)) {
|
|
91406
92280
|
if (!/\.ya?ml$/i.test(name))
|
|
91407
92281
|
continue;
|
|
91408
|
-
const full =
|
|
92282
|
+
const full = join89(paths.scheduleDir, name);
|
|
91409
92283
|
try {
|
|
91410
92284
|
const raw = readFileSync76(full, "utf-8");
|
|
91411
92285
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
@@ -91638,12 +92512,12 @@ import {
|
|
|
91638
92512
|
writeFileSync as writeFileSync32,
|
|
91639
92513
|
writeSync as writeSync11
|
|
91640
92514
|
} from "node:fs";
|
|
91641
|
-
import { join as
|
|
92515
|
+
import { join as join90 } from "node:path";
|
|
91642
92516
|
import { randomBytes as randomBytes15 } from "node:crypto";
|
|
91643
92517
|
var STAGE_ID_PREFIX = "cap_";
|
|
91644
92518
|
function pendingDir(agent, opts = {}) {
|
|
91645
92519
|
const paths = overlayPathsFor(agent, opts);
|
|
91646
|
-
return
|
|
92520
|
+
return join90(paths.scheduleDir, ".pending");
|
|
91647
92521
|
}
|
|
91648
92522
|
function ensurePendingDir(agent, opts = {}) {
|
|
91649
92523
|
const dir = pendingDir(agent, opts);
|
|
@@ -91656,8 +92530,8 @@ function newStageId() {
|
|
|
91656
92530
|
function stagePendingScheduleEntry(opts) {
|
|
91657
92531
|
const dir = ensurePendingDir(opts.agent, { root: opts.root });
|
|
91658
92532
|
const stageId = opts.stageId ?? newStageId();
|
|
91659
|
-
const yamlPath =
|
|
91660
|
-
const metaPath =
|
|
92533
|
+
const yamlPath = join90(dir, `${stageId}.yaml`);
|
|
92534
|
+
const metaPath = join90(dir, `${stageId}.meta.json`);
|
|
91661
92535
|
const meta = {
|
|
91662
92536
|
v: 1,
|
|
91663
92537
|
stage_id: stageId,
|
|
@@ -91691,8 +92565,8 @@ function listPendingScheduleEntries(agent, opts = {}) {
|
|
|
91691
92565
|
if (!name.endsWith(".meta.json"))
|
|
91692
92566
|
continue;
|
|
91693
92567
|
const stageId = name.slice(0, -".meta.json".length);
|
|
91694
|
-
const metaPath =
|
|
91695
|
-
const yamlPath =
|
|
92568
|
+
const metaPath = join90(dir, name);
|
|
92569
|
+
const yamlPath = join90(dir, `${stageId}.yaml`);
|
|
91696
92570
|
if (!existsSync89(yamlPath))
|
|
91697
92571
|
continue;
|
|
91698
92572
|
try {
|
|
@@ -91711,7 +92585,7 @@ function commitPendingScheduleEntry(opts) {
|
|
|
91711
92585
|
return { committed: false, reason: "not_found" };
|
|
91712
92586
|
const slug = match.meta.entry.name ?? match.stageId;
|
|
91713
92587
|
const paths = overlayPathsFor(opts.agent, { root: opts.root });
|
|
91714
|
-
const finalPath =
|
|
92588
|
+
const finalPath = join90(paths.scheduleDir, `${slug}.yaml`);
|
|
91715
92589
|
if (existsSync89(finalPath)) {
|
|
91716
92590
|
return { committed: false, reason: "slug_collision" };
|
|
91717
92591
|
}
|
|
@@ -92364,7 +93238,7 @@ import { existsSync as existsSync91 } from "node:fs";
|
|
|
92364
93238
|
init_reconcile_default_skills();
|
|
92365
93239
|
init_agent_config();
|
|
92366
93240
|
var import_yaml23 = __toESM(require_dist(), 1);
|
|
92367
|
-
import { join as
|
|
93241
|
+
import { join as join91 } from "node:path";
|
|
92368
93242
|
var MAX_SKILLS_PER_AGENT = 20;
|
|
92369
93243
|
var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
|
|
92370
93244
|
function exitCodeFor2(code) {
|
|
@@ -92439,7 +93313,7 @@ function skillInstall(opts) {
|
|
|
92439
93313
|
return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
|
|
92440
93314
|
}
|
|
92441
93315
|
const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
|
|
92442
|
-
const skillPath =
|
|
93316
|
+
const skillPath = join91(poolDir, skillName);
|
|
92443
93317
|
if (!existsSync91(skillPath)) {
|
|
92444
93318
|
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.`);
|
|
92445
93319
|
}
|
|
@@ -92618,7 +93492,7 @@ import {
|
|
|
92618
93492
|
writeFileSync as writeFileSync33
|
|
92619
93493
|
} from "node:fs";
|
|
92620
93494
|
import { tmpdir as tmpdir6, homedir as homedir51 } from "node:os";
|
|
92621
|
-
import { dirname as dirname31, join as
|
|
93495
|
+
import { dirname as dirname31, join as join92, relative as relative2, resolve as resolve54 } from "node:path";
|
|
92622
93496
|
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
92623
93497
|
|
|
92624
93498
|
// src/cli/skill-common.ts
|
|
@@ -92852,7 +93726,7 @@ function scanForClaudeP2(content) {
|
|
|
92852
93726
|
function resolveSkillsPoolDir2(override) {
|
|
92853
93727
|
const raw = override ?? "~/.switchroom/skills";
|
|
92854
93728
|
if (raw.startsWith("~/")) {
|
|
92855
|
-
return
|
|
93729
|
+
return join92(homedir51(), raw.slice(2));
|
|
92856
93730
|
}
|
|
92857
93731
|
if (raw === "~")
|
|
92858
93732
|
return homedir51();
|
|
@@ -92891,7 +93765,7 @@ function loadFromDir(dir) {
|
|
|
92891
93765
|
const walk2 = (sub) => {
|
|
92892
93766
|
const entries = readdirSync34(sub, { withFileTypes: true });
|
|
92893
93767
|
for (const ent of entries) {
|
|
92894
|
-
const full =
|
|
93768
|
+
const full = join92(sub, ent.name);
|
|
92895
93769
|
const rel = relative2(abs, full);
|
|
92896
93770
|
if (ent.isSymbolicLink()) {
|
|
92897
93771
|
fail3(`refusing to read symlink inside --from dir: ${rel}`);
|
|
@@ -92926,7 +93800,7 @@ function loadFromTarball(tarPath) {
|
|
|
92926
93800
|
fail3(`tarball contains disallowed path: ${JSON.stringify(entry)} \u2014 ` + `refusing to extract before any file is written`);
|
|
92927
93801
|
}
|
|
92928
93802
|
}
|
|
92929
|
-
const staging = mkdtempSync5(
|
|
93803
|
+
const staging = mkdtempSync5(join92(tmpdir6(), "skill-apply-extract-"));
|
|
92930
93804
|
try {
|
|
92931
93805
|
const flags = isGz ? ["-xzf"] : ["-xf"];
|
|
92932
93806
|
const r = spawnSync16("tar", [
|
|
@@ -93012,8 +93886,8 @@ function validatePayload(name, files) {
|
|
|
93012
93886
|
errors2.push(`${path9} fails \`bash -n\` syntax check: ${(r.stderr ?? "").trim()}`);
|
|
93013
93887
|
}
|
|
93014
93888
|
} else if (PY_SCRIPT_RE2.test(path9)) {
|
|
93015
|
-
const tmp = mkdtempSync5(
|
|
93016
|
-
const tmpPy =
|
|
93889
|
+
const tmp = mkdtempSync5(join92(tmpdir6(), "skill-apply-py-"));
|
|
93890
|
+
const tmpPy = join92(tmp, "check.py");
|
|
93017
93891
|
try {
|
|
93018
93892
|
writeFileSync33(tmpPy, content);
|
|
93019
93893
|
const r = spawnSync16("python3", ["-m", "py_compile", tmpPy], {
|
|
@@ -93036,7 +93910,7 @@ function diffSummary(currentDir, files) {
|
|
|
93036
93910
|
if (existsSync92(currentDir)) {
|
|
93037
93911
|
const walk2 = (sub) => {
|
|
93038
93912
|
for (const ent of readdirSync34(sub, { withFileTypes: true })) {
|
|
93039
|
-
const full =
|
|
93913
|
+
const full = join92(sub, ent.name);
|
|
93040
93914
|
const rel = relative2(currentDir, full);
|
|
93041
93915
|
if (ent.isDirectory()) {
|
|
93042
93916
|
walk2(full);
|
|
@@ -93072,7 +93946,7 @@ function writePayload(poolDir, name, files) {
|
|
|
93072
93946
|
if (!existsSync92(poolDir)) {
|
|
93073
93947
|
mkdirSync52(poolDir, { recursive: true, mode: 493 });
|
|
93074
93948
|
}
|
|
93075
|
-
const target =
|
|
93949
|
+
const target = join92(poolDir, name);
|
|
93076
93950
|
let targetIsSymlink = false;
|
|
93077
93951
|
try {
|
|
93078
93952
|
const st = lstatSync11(target);
|
|
@@ -93083,11 +93957,11 @@ function writePayload(poolDir, name, files) {
|
|
|
93083
93957
|
if (targetIsSymlink) {
|
|
93084
93958
|
fail3(`refusing to overwrite symlink at ${target}; investigate manually`);
|
|
93085
93959
|
}
|
|
93086
|
-
const staging = mkdtempSync5(
|
|
93960
|
+
const staging = mkdtempSync5(join92(poolDir, `.skill-apply-stage-${name}-`));
|
|
93087
93961
|
let oldRename = null;
|
|
93088
93962
|
try {
|
|
93089
93963
|
for (const [path9, content] of Object.entries(files)) {
|
|
93090
|
-
const full =
|
|
93964
|
+
const full = join92(staging, path9);
|
|
93091
93965
|
mkdirSync52(dirname31(full), { recursive: true, mode: 493 });
|
|
93092
93966
|
const fd = openSync16(full, "wx");
|
|
93093
93967
|
try {
|
|
@@ -93165,7 +94039,7 @@ function registerSkillCommand(program3) {
|
|
|
93165
94039
|
}
|
|
93166
94040
|
const config = loadConfig();
|
|
93167
94041
|
const poolDir = resolveSkillsPoolDir2(config.switchroom?.skills_dir);
|
|
93168
|
-
const currentDir =
|
|
94042
|
+
const currentDir = join92(poolDir, name);
|
|
93169
94043
|
console.log(source_default.bold(`Skill: ${name}`) + source_default.gray(` (${Object.keys(files).length} files, ${sumBytes(files)} bytes)`));
|
|
93170
94044
|
console.log(source_default.bold("Diff vs current pool content:"));
|
|
93171
94045
|
console.log(diffSummary(currentDir, files));
|
|
@@ -93210,7 +94084,7 @@ import {
|
|
|
93210
94084
|
utimesSync,
|
|
93211
94085
|
writeFileSync as writeFileSync34
|
|
93212
94086
|
} from "node:fs";
|
|
93213
|
-
import { dirname as dirname32, join as
|
|
94087
|
+
import { dirname as dirname32, join as join93, relative as relative3, resolve as resolve55 } from "node:path";
|
|
93214
94088
|
import { homedir as homedir52, tmpdir as tmpdir7 } from "node:os";
|
|
93215
94089
|
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
93216
94090
|
init_helpers();
|
|
@@ -93222,10 +94096,10 @@ var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
|
|
|
93222
94096
|
var PERSONAL_SKILLS_SUBPATH = "personal-skills";
|
|
93223
94097
|
function resolveConfigSkillsDir(agent) {
|
|
93224
94098
|
const override = process.env.SWITCHROOM_CONFIG_DIR;
|
|
93225
|
-
const candidate = override ? resolve55(override) :
|
|
94099
|
+
const candidate = override ? resolve55(override) : join93(homedir52(), ".switchroom-config");
|
|
93226
94100
|
if (!existsSync93(candidate))
|
|
93227
94101
|
return null;
|
|
93228
|
-
return
|
|
94102
|
+
return join93(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
|
|
93229
94103
|
}
|
|
93230
94104
|
var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
|
|
93231
94105
|
function sweepMirrorPriors(configSkillsRoot) {
|
|
@@ -93243,7 +94117,7 @@ function sweepMirrorPriors(configSkillsRoot) {
|
|
|
93243
94117
|
if (now - ts < MIRROR_PRIOR_TTL_MS)
|
|
93244
94118
|
continue;
|
|
93245
94119
|
try {
|
|
93246
|
-
rmSync19(
|
|
94120
|
+
rmSync19(join93(configSkillsRoot, ent), { recursive: true, force: true });
|
|
93247
94121
|
} catch {}
|
|
93248
94122
|
}
|
|
93249
94123
|
} catch {}
|
|
@@ -93252,7 +94126,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
93252
94126
|
const configSkillsRoot = resolveConfigSkillsDir(agent);
|
|
93253
94127
|
if (!configSkillsRoot)
|
|
93254
94128
|
return;
|
|
93255
|
-
const dest =
|
|
94129
|
+
const dest = join93(configSkillsRoot, name);
|
|
93256
94130
|
try {
|
|
93257
94131
|
if (liveSkillDir !== null) {
|
|
93258
94132
|
try {
|
|
@@ -93267,19 +94141,19 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
93267
94141
|
if (liveSkillDir === null) {
|
|
93268
94142
|
sweepMirrorPriors(configSkillsRoot);
|
|
93269
94143
|
if (existsSync93(dest)) {
|
|
93270
|
-
const trash =
|
|
94144
|
+
const trash = join93(configSkillsRoot, `.${name}-trash-${Date.now()}`);
|
|
93271
94145
|
renameSync24(dest, trash);
|
|
93272
94146
|
}
|
|
93273
94147
|
return;
|
|
93274
94148
|
}
|
|
93275
94149
|
mkdirSync53(configSkillsRoot, { recursive: true, mode: 493 });
|
|
93276
94150
|
sweepMirrorPriors(configSkillsRoot);
|
|
93277
|
-
const staging = mkdtempSync6(
|
|
94151
|
+
const staging = mkdtempSync6(join93(configSkillsRoot, `.${name}-staging-`));
|
|
93278
94152
|
const walk2 = (src, dst) => {
|
|
93279
94153
|
mkdirSync53(dst, { recursive: true, mode: 493 });
|
|
93280
94154
|
for (const ent of readdirSync35(src, { withFileTypes: true })) {
|
|
93281
|
-
const s =
|
|
93282
|
-
const d =
|
|
94155
|
+
const s = join93(src, ent.name);
|
|
94156
|
+
const d = join93(dst, ent.name);
|
|
93283
94157
|
if (ent.isSymbolicLink())
|
|
93284
94158
|
continue;
|
|
93285
94159
|
if (ent.isDirectory())
|
|
@@ -93291,7 +94165,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
93291
94165
|
};
|
|
93292
94166
|
walk2(liveSkillDir, staging);
|
|
93293
94167
|
if (existsSync93(dest)) {
|
|
93294
|
-
const prior =
|
|
94168
|
+
const prior = join93(configSkillsRoot, `.${name}-prior-${Date.now()}`);
|
|
93295
94169
|
renameSync24(dest, prior);
|
|
93296
94170
|
}
|
|
93297
94171
|
renameSync24(staging, dest);
|
|
@@ -93321,16 +94195,16 @@ function resolveAgent(opts) {
|
|
|
93321
94195
|
function resolveAgentsRoot(opts) {
|
|
93322
94196
|
if (opts.root)
|
|
93323
94197
|
return resolve55(opts.root);
|
|
93324
|
-
return
|
|
94198
|
+
return join93(homedir52(), ".switchroom", "agents");
|
|
93325
94199
|
}
|
|
93326
94200
|
function personalSkillDir(agentsRoot, agent, name) {
|
|
93327
|
-
return
|
|
94201
|
+
return join93(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
|
|
93328
94202
|
}
|
|
93329
94203
|
function trashDir(agentsRoot, agent) {
|
|
93330
|
-
return
|
|
94204
|
+
return join93(agentsRoot, agent, ".claude", TRASH_DIRNAME);
|
|
93331
94205
|
}
|
|
93332
94206
|
function countPersonalSkills(agentsRoot, agent) {
|
|
93333
|
-
const skillsDir =
|
|
94207
|
+
const skillsDir = join93(agentsRoot, agent, ".claude", "skills");
|
|
93334
94208
|
if (!existsSync93(skillsDir))
|
|
93335
94209
|
return 0;
|
|
93336
94210
|
let n = 0;
|
|
@@ -93368,7 +94242,7 @@ function loadFromDir2(dir) {
|
|
|
93368
94242
|
const files = {};
|
|
93369
94243
|
const walk2 = (sub) => {
|
|
93370
94244
|
for (const ent of readdirSync35(sub, { withFileTypes: true })) {
|
|
93371
|
-
const full =
|
|
94245
|
+
const full = join93(sub, ent.name);
|
|
93372
94246
|
if (ent.isSymbolicLink()) {
|
|
93373
94247
|
fail4(`refusing to read symlink in --from dir: ${relative3(abs, full)}`);
|
|
93374
94248
|
}
|
|
@@ -93421,8 +94295,8 @@ function behavioralValidate(files) {
|
|
|
93421
94295
|
errors2.push(`${path9} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
|
|
93422
94296
|
}
|
|
93423
94297
|
} else if (PY_SCRIPT_RE.test(path9)) {
|
|
93424
|
-
const tmp = mkdtempSync6(
|
|
93425
|
-
const tmpPy =
|
|
94298
|
+
const tmp = mkdtempSync6(join93(tmpdir7(), "skill-personal-py-"));
|
|
94299
|
+
const tmpPy = join93(tmp, "check.py");
|
|
93426
94300
|
try {
|
|
93427
94301
|
writeFileSync34(tmpPy, content);
|
|
93428
94302
|
const r = spawnSync17("python3", ["-m", "py_compile", tmpPy], {
|
|
@@ -93446,7 +94320,7 @@ function sweepTrash(agentsRoot, agent) {
|
|
|
93446
94320
|
for (const ent of readdirSync35(trash, { withFileTypes: true })) {
|
|
93447
94321
|
if (!ent.isDirectory())
|
|
93448
94322
|
continue;
|
|
93449
|
-
const entPath =
|
|
94323
|
+
const entPath = join93(trash, ent.name);
|
|
93450
94324
|
try {
|
|
93451
94325
|
const st = statSync50(entPath);
|
|
93452
94326
|
if (now - st.mtimeMs > TRASH_TTL_MS) {
|
|
@@ -93467,11 +94341,11 @@ function writePersonalSkill(targetDir, files) {
|
|
|
93467
94341
|
fail4(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
|
|
93468
94342
|
}
|
|
93469
94343
|
mkdirSync53(dirname32(targetDir), { recursive: true, mode: 493 });
|
|
93470
|
-
const staging = mkdtempSync6(
|
|
94344
|
+
const staging = mkdtempSync6(join93(dirname32(targetDir), `.skill-personal-stage-`));
|
|
93471
94345
|
let oldRename = null;
|
|
93472
94346
|
try {
|
|
93473
94347
|
for (const [path9, content] of Object.entries(files)) {
|
|
93474
|
-
const full =
|
|
94348
|
+
const full = join93(staging, path9);
|
|
93475
94349
|
mkdirSync53(dirname32(full), { recursive: true, mode: 493 });
|
|
93476
94350
|
const fd = openSync17(full, "wx");
|
|
93477
94351
|
try {
|
|
@@ -93618,10 +94492,10 @@ function editPersonalAction(name, opts) {
|
|
|
93618
94492
|
}
|
|
93619
94493
|
var CLONE_SOURCE_RE = /^(shared|bundled):([a-z0-9][a-z0-9_-]{0,62})$/;
|
|
93620
94494
|
function defaultSharedRoot() {
|
|
93621
|
-
return
|
|
94495
|
+
return join93(homedir52(), ".switchroom", "skills");
|
|
93622
94496
|
}
|
|
93623
94497
|
function defaultBundledRoot() {
|
|
93624
|
-
return
|
|
94498
|
+
return join93(homedir52(), ".switchroom", "skills", "_bundled");
|
|
93625
94499
|
}
|
|
93626
94500
|
function resolveCloneSource(source, opts) {
|
|
93627
94501
|
const m = CLONE_SOURCE_RE.exec(source);
|
|
@@ -93631,7 +94505,7 @@ function resolveCloneSource(source, opts) {
|
|
|
93631
94505
|
const tier = m[1];
|
|
93632
94506
|
const slug = m[2];
|
|
93633
94507
|
const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
|
|
93634
|
-
const dir =
|
|
94508
|
+
const dir = join93(root, slug);
|
|
93635
94509
|
if (!existsSync93(dir)) {
|
|
93636
94510
|
fail4(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
|
|
93637
94511
|
}
|
|
@@ -93647,7 +94521,7 @@ function readSourceFiles(dir) {
|
|
|
93647
94521
|
const skipped = [];
|
|
93648
94522
|
const walk2 = (sub) => {
|
|
93649
94523
|
for (const ent of readdirSync35(sub, { withFileTypes: true })) {
|
|
93650
|
-
const full =
|
|
94524
|
+
const full = join93(sub, ent.name);
|
|
93651
94525
|
if (ent.isSymbolicLink()) {
|
|
93652
94526
|
continue;
|
|
93653
94527
|
}
|
|
@@ -93758,7 +94632,7 @@ function removePersonalAction(name, opts) {
|
|
|
93758
94632
|
const trashRoot2 = trashDir(agentsRoot, agent);
|
|
93759
94633
|
mkdirSync53(trashRoot2, { recursive: true, mode: 493 });
|
|
93760
94634
|
const ts = Date.now();
|
|
93761
|
-
const trashTarget =
|
|
94635
|
+
const trashTarget = join93(trashRoot2, `${name}-${ts}`);
|
|
93762
94636
|
renameSync24(target, trashTarget);
|
|
93763
94637
|
const now = new Date(ts);
|
|
93764
94638
|
utimesSync(trashTarget, now, now);
|
|
@@ -93777,7 +94651,7 @@ function listPersonalAction(opts) {
|
|
|
93777
94651
|
const agent = resolveAgent(opts);
|
|
93778
94652
|
const agentsRoot = resolveAgentsRoot(opts);
|
|
93779
94653
|
sweepTrash(agentsRoot, agent);
|
|
93780
|
-
const skillsDir =
|
|
94654
|
+
const skillsDir = join93(agentsRoot, agent, ".claude", "skills");
|
|
93781
94655
|
const personal = [];
|
|
93782
94656
|
if (existsSync93(skillsDir)) {
|
|
93783
94657
|
for (const ent of readdirSync35(skillsDir, { withFileTypes: true })) {
|
|
@@ -93786,7 +94660,7 @@ function listPersonalAction(opts) {
|
|
|
93786
94660
|
if (!ent.name.startsWith(PERSONAL_PREFIX))
|
|
93787
94661
|
continue;
|
|
93788
94662
|
const skillName = ent.name.slice(PERSONAL_PREFIX.length);
|
|
93789
|
-
const skillPath =
|
|
94663
|
+
const skillPath = join93(skillsDir, ent.name);
|
|
93790
94664
|
let fileCount = 0;
|
|
93791
94665
|
let totalBytes = 0;
|
|
93792
94666
|
const walk2 = (sub) => {
|
|
@@ -93794,10 +94668,10 @@ function listPersonalAction(opts) {
|
|
|
93794
94668
|
if (e.isFile()) {
|
|
93795
94669
|
fileCount += 1;
|
|
93796
94670
|
try {
|
|
93797
|
-
totalBytes += statSync50(
|
|
94671
|
+
totalBytes += statSync50(join93(sub, e.name)).size;
|
|
93798
94672
|
} catch {}
|
|
93799
94673
|
} else if (e.isDirectory()) {
|
|
93800
|
-
walk2(
|
|
94674
|
+
walk2(join93(sub, e.name));
|
|
93801
94675
|
}
|
|
93802
94676
|
}
|
|
93803
94677
|
};
|
|
@@ -93836,11 +94710,11 @@ function registerSkillPersonalCommands(program3) {
|
|
|
93836
94710
|
// src/cli/self-improve-propose-skill.ts
|
|
93837
94711
|
import { createConnection as createConnection4 } from "node:net";
|
|
93838
94712
|
import { homedir as homedir53 } from "node:os";
|
|
93839
|
-
import { join as
|
|
94713
|
+
import { join as join94 } from "node:path";
|
|
93840
94714
|
import { readFileSync as readFileSync81 } from "node:fs";
|
|
93841
94715
|
var IPC_CONNECT_TIMEOUT_MS = 5000;
|
|
93842
94716
|
function gatewaySocketPath() {
|
|
93843
|
-
return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ?
|
|
94717
|
+
return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ? join94(process.env.TELEGRAM_STATE_DIR, "gateway.sock") : join94(homedir53(), ".claude", "channels", "telegram", "gateway.sock"));
|
|
93844
94718
|
}
|
|
93845
94719
|
function fail5(msg, code = 1) {
|
|
93846
94720
|
console.error(msg);
|
|
@@ -93914,7 +94788,7 @@ init_helpers();
|
|
|
93914
94788
|
var import_yaml25 = __toESM(require_dist(), 1);
|
|
93915
94789
|
import { existsSync as existsSync94, readdirSync as readdirSync36, readFileSync as readFileSync82, statSync as statSync51 } from "node:fs";
|
|
93916
94790
|
import { homedir as homedir54 } from "node:os";
|
|
93917
|
-
import { join as
|
|
94791
|
+
import { join as join95, resolve as resolve56 } from "node:path";
|
|
93918
94792
|
var PERSONAL_PREFIX2 = "personal-";
|
|
93919
94793
|
var BUNDLED_SUBDIR = "_bundled";
|
|
93920
94794
|
var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
|
|
@@ -93928,7 +94802,7 @@ function defaultBundledRoot2() {
|
|
|
93928
94802
|
return resolve56(homedir54(), ".switchroom/skills/_bundled");
|
|
93929
94803
|
}
|
|
93930
94804
|
function readSkillFrontmatter(skillDir) {
|
|
93931
|
-
const mdPath =
|
|
94805
|
+
const mdPath = join95(skillDir, "SKILL.md");
|
|
93932
94806
|
if (!existsSync94(mdPath))
|
|
93933
94807
|
return null;
|
|
93934
94808
|
let content;
|
|
@@ -93961,7 +94835,7 @@ function readSkillFrontmatter(skillDir) {
|
|
|
93961
94835
|
return { fm: parsed };
|
|
93962
94836
|
}
|
|
93963
94837
|
function statSkillMd(skillDir) {
|
|
93964
|
-
const mdPath =
|
|
94838
|
+
const mdPath = join95(skillDir, "SKILL.md");
|
|
93965
94839
|
try {
|
|
93966
94840
|
const st = statSync51(mdPath);
|
|
93967
94841
|
return { size: st.size, mtime: st.mtime.toISOString() };
|
|
@@ -93972,7 +94846,7 @@ function statSkillMd(skillDir) {
|
|
|
93972
94846
|
function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
93973
94847
|
if (!AGENT_NAME_RE3.test(agent))
|
|
93974
94848
|
return [];
|
|
93975
|
-
const skillsDir =
|
|
94849
|
+
const skillsDir = join95(agentsRoot, agent, ".claude/skills");
|
|
93976
94850
|
if (!existsSync94(skillsDir))
|
|
93977
94851
|
return [];
|
|
93978
94852
|
const out = [];
|
|
@@ -93985,7 +94859,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
93985
94859
|
for (const ent of entries) {
|
|
93986
94860
|
if (!ent.startsWith(PERSONAL_PREFIX2))
|
|
93987
94861
|
continue;
|
|
93988
|
-
const dirPath =
|
|
94862
|
+
const dirPath = join95(skillsDir, ent);
|
|
93989
94863
|
try {
|
|
93990
94864
|
if (!statSync51(dirPath).isDirectory())
|
|
93991
94865
|
continue;
|
|
@@ -94025,7 +94899,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
|
94025
94899
|
continue;
|
|
94026
94900
|
if (ent.startsWith("."))
|
|
94027
94901
|
continue;
|
|
94028
|
-
const dirPath =
|
|
94902
|
+
const dirPath = join95(sharedRoot, ent);
|
|
94029
94903
|
try {
|
|
94030
94904
|
if (!statSync51(dirPath).isDirectory())
|
|
94031
94905
|
continue;
|
|
@@ -94061,7 +94935,7 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
|
94061
94935
|
for (const ent of entries) {
|
|
94062
94936
|
if (ent.startsWith("."))
|
|
94063
94937
|
continue;
|
|
94064
|
-
const dirPath =
|
|
94938
|
+
const dirPath = join95(bundledRoot, ent);
|
|
94065
94939
|
try {
|
|
94066
94940
|
if (!statSync51(dirPath).isDirectory())
|
|
94067
94941
|
continue;
|
|
@@ -94218,7 +95092,7 @@ import {
|
|
|
94218
95092
|
copyFileSync as copyFileSync13
|
|
94219
95093
|
} from "node:fs";
|
|
94220
95094
|
import { homedir as homedir55 } from "node:os";
|
|
94221
|
-
import { join as
|
|
95095
|
+
import { join as join96 } from "node:path";
|
|
94222
95096
|
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
94223
95097
|
|
|
94224
95098
|
// src/cli/singleton-stale-cleanup.ts
|
|
@@ -94567,7 +95441,7 @@ function resolveHostdHostHome(env2 = process.env, home2 = homedir55()) {
|
|
|
94567
95441
|
return resolved;
|
|
94568
95442
|
}
|
|
94569
95443
|
function resolveHostdSkillsTarget(hostHome) {
|
|
94570
|
-
const skillsPath =
|
|
95444
|
+
const skillsPath = join96(hostHome, ".switchroom", "skills");
|
|
94571
95445
|
let st;
|
|
94572
95446
|
try {
|
|
94573
95447
|
st = lstatSync13(skillsPath);
|
|
@@ -94591,10 +95465,10 @@ function resolveHostdSkillsTarget(hostHome) {
|
|
|
94591
95465
|
return target;
|
|
94592
95466
|
}
|
|
94593
95467
|
function hostdDir() {
|
|
94594
|
-
return
|
|
95468
|
+
return join96(homedir55(), ".switchroom", "hostd");
|
|
94595
95469
|
}
|
|
94596
95470
|
function hostdComposePath() {
|
|
94597
|
-
return
|
|
95471
|
+
return join96(hostdDir(), "docker-compose.yml");
|
|
94598
95472
|
}
|
|
94599
95473
|
function backupExistingCompose() {
|
|
94600
95474
|
const p = hostdComposePath();
|
|
@@ -94723,7 +95597,7 @@ function doStatus() {
|
|
|
94723
95597
|
for (const name of readdirSync37(dir)) {
|
|
94724
95598
|
if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
|
|
94725
95599
|
continue;
|
|
94726
|
-
const sockPath =
|
|
95600
|
+
const sockPath = join96(dir, name, "sock");
|
|
94727
95601
|
if (existsSync96(sockPath)) {
|
|
94728
95602
|
const st = statSync52(sockPath);
|
|
94729
95603
|
if ((st.mode & 61440) === 49152) {
|
|
@@ -94817,7 +95691,7 @@ init_helpers();
|
|
|
94817
95691
|
init_operator_uid();
|
|
94818
95692
|
import { chownSync as chownSync9, existsSync as existsSync97, mkdirSync as mkdirSync55, writeFileSync as writeFileSync36, copyFileSync as copyFileSync14 } from "node:fs";
|
|
94819
95693
|
import { homedir as homedir56 } from "node:os";
|
|
94820
|
-
import { join as
|
|
95694
|
+
import { join as join97 } from "node:path";
|
|
94821
95695
|
import { spawnSync as spawnSync21 } from "node:child_process";
|
|
94822
95696
|
function resolveWebImageTag(explicitTag, release) {
|
|
94823
95697
|
if (explicitTag)
|
|
@@ -94917,10 +95791,10 @@ services:
|
|
|
94917
95791
|
`;
|
|
94918
95792
|
}
|
|
94919
95793
|
function webdDir() {
|
|
94920
|
-
return
|
|
95794
|
+
return join97(homedir56(), ".switchroom", "web");
|
|
94921
95795
|
}
|
|
94922
95796
|
function webdComposePath() {
|
|
94923
|
-
return
|
|
95797
|
+
return join97(webdDir(), "docker-compose.yml");
|
|
94924
95798
|
}
|
|
94925
95799
|
function backupExistingCompose2() {
|
|
94926
95800
|
const p = webdComposePath();
|
|
@@ -95080,9 +95954,9 @@ function registerWebdCommand(program3) {
|
|
|
95080
95954
|
// src/cli/host-repair.ts
|
|
95081
95955
|
init_source();
|
|
95082
95956
|
import { homedir as homedir57 } from "node:os";
|
|
95083
|
-
import { join as
|
|
95957
|
+
import { join as join98 } from "node:path";
|
|
95084
95958
|
var ARTIFACT_ALLOWLIST = {
|
|
95085
|
-
dockerComposePluginDir: (home2) =>
|
|
95959
|
+
dockerComposePluginDir: (home2) => join98(home2, ".docker", "cli-plugins", "docker-compose"),
|
|
95086
95960
|
stateSentinel: "/state"
|
|
95087
95961
|
};
|
|
95088
95962
|
function isStateBogusAutoDir(probe2) {
|