switchroom 0.19.2 → 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 +2 -0
- package/dist/auth-broker/index.js +13 -0
- package/dist/cli/autoaccept-poll.js +2 -0
- package/dist/cli/drive-write-pretool.mjs +2 -0
- package/dist/cli/ms-365-write-pretool.mjs +2 -0
- package/dist/cli/switchroom.js +404 -245
- package/dist/host-control/main.js +1 -1
- package/package.json +1 -1
- package/profiles/default/CLAUDE.md.hbs +8 -0
- package/skills/mental-model-curator/SKILL.md +68 -2
- package/telegram-plugin/auth-snapshot-format.ts +104 -12
- package/telegram-plugin/dist/bridge/bridge.js +8 -2
- package/telegram-plugin/dist/gateway/gateway.js +1194 -794
- package/telegram-plugin/dist/server.js +8 -2
- 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 +68 -101
- package/telegram-plugin/gateway/inbound-interceptors.ts +13 -3
- package/telegram-plugin/gateway/model-command.ts +203 -1
- package/telegram-plugin/gateway/outbound-send-path.ts +68 -15
- package/telegram-plugin/gateway/session-model-source.ts +90 -10
- package/telegram-plugin/gateway/stream-render.ts +22 -5
- package/telegram-plugin/quota-bar-format.ts +60 -12
- package/telegram-plugin/reply-owner-resolve.ts +76 -11
- package/telegram-plugin/session-tail.ts +27 -3
- 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/flushed-turn-supersede.test.ts +117 -0
- package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +185 -29
- package/telegram-plugin/tests/model-command.test.ts +220 -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/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";
|
|
@@ -27107,6 +27107,7 @@ function renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig) {
|
|
|
27107
27107
|
settings.recallTypes = ["world", "experience", "observation"];
|
|
27108
27108
|
settings.recallSkipTrivial = true;
|
|
27109
27109
|
settings.directiveCaptureNudge = true;
|
|
27110
|
+
settings.recallTagWeights = { sidechain: 0.8 };
|
|
27110
27111
|
if (additionalBanks.length > 0) {
|
|
27111
27112
|
settings.recallAdditionalBanks = [...additionalBanks];
|
|
27112
27113
|
}
|
|
@@ -29881,6 +29882,7 @@ function describeAgents(config, litellmConfirmedAgents) {
|
|
|
29881
29882
|
smallFastModel: resolved.litellm?.small_fast_model ?? config.litellm?.small_fast_model ?? "claude-haiku-4-5-20251001"
|
|
29882
29883
|
},
|
|
29883
29884
|
model: resolveMainModel(resolved.model),
|
|
29885
|
+
tmuxSupervisor: resolved.experimental?.legacy_pty !== true,
|
|
29884
29886
|
timezone: resolveTimezone(config, resolved, {
|
|
29885
29887
|
onUtcFallback: () => {
|
|
29886
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.`);
|
|
@@ -30343,6 +30345,7 @@ function emitAgentService(lines, a, imageTag, buildMode, buildContext, homePrefi
|
|
|
30343
30345
|
env2.ANTHROPIC_SMALL_FAST_MODEL = a.litellm.smallFastModel;
|
|
30344
30346
|
}
|
|
30345
30347
|
env2.SWITCHROOM_VOICE_ENGINE = voiceEngine;
|
|
30348
|
+
env2.SWITCHROOM_TMUX_SUPERVISOR = a.tmuxSupervisor ? "1" : "0";
|
|
30346
30349
|
for (const [k, v] of Object.entries(a.userEnv)) {
|
|
30347
30350
|
if (env2[k] === undefined)
|
|
30348
30351
|
env2[k] = v;
|
|
@@ -32986,6 +32989,8 @@ var init_protocol2 = __esm(() => {
|
|
|
32986
32989
|
label: exports_external.string(),
|
|
32987
32990
|
expiresAt: exports_external.number().optional(),
|
|
32988
32991
|
exhausted: exports_external.boolean(),
|
|
32992
|
+
in_service: exports_external.boolean().optional(),
|
|
32993
|
+
entitlement_blocked: exports_external.boolean().optional(),
|
|
32989
32994
|
exhausted_until: exports_external.number().optional(),
|
|
32990
32995
|
throttled_until: exports_external.number().optional(),
|
|
32991
32996
|
threshold_violations: exports_external.number().int().nonnegative().optional(),
|
|
@@ -36657,7 +36662,8 @@ async function inspectBankHealth(mcpUrl, bankId, opts) {
|
|
|
36657
36662
|
pendingOperations: 0,
|
|
36658
36663
|
newestDocumentAt: null,
|
|
36659
36664
|
unextractedDocuments: [],
|
|
36660
|
-
mentalModels: []
|
|
36665
|
+
mentalModels: [],
|
|
36666
|
+
activeDirectiveCount: null
|
|
36661
36667
|
};
|
|
36662
36668
|
const stats = await getJson(`${base}/v1/default/banks/${bank}/stats`, opts);
|
|
36663
36669
|
if (!stats.ok)
|
|
@@ -36668,6 +36674,8 @@ async function inspectBankHealth(mcpUrl, bankId, opts) {
|
|
|
36668
36674
|
const models = await getJson(`${base}/v1/default/banks/${bank}/mental-models`, opts);
|
|
36669
36675
|
if (!models.ok)
|
|
36670
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;
|
|
36671
36679
|
const docItems = docs.data.items ?? [];
|
|
36672
36680
|
let newestDocumentAt = null;
|
|
36673
36681
|
const unextracted = [];
|
|
@@ -36694,6 +36702,7 @@ async function inspectBankHealth(mcpUrl, bankId, opts) {
|
|
|
36694
36702
|
pendingOperations: stats.data.pending_operations ?? 0,
|
|
36695
36703
|
newestDocumentAt,
|
|
36696
36704
|
unextractedDocuments: unextracted,
|
|
36705
|
+
activeDirectiveCount,
|
|
36697
36706
|
mentalModels: (models.data.items ?? []).filter((m) => typeof m?.id === "string" && typeof m?.name === "string").map((m) => {
|
|
36698
36707
|
const { basedOnCounts, totalSourceFacts, derivedFromModelIds } = summarizeBasedOn(m.reflect_response);
|
|
36699
36708
|
return {
|
|
@@ -42022,6 +42031,29 @@ var init_hindsight_tools = __esm(() => {
|
|
|
42022
42031
|
|
|
42023
42032
|
// src/cli/doctor-memory.ts
|
|
42024
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
|
+
}
|
|
42025
42057
|
function classifyShmSize(bytes) {
|
|
42026
42058
|
const mib = Math.round(bytes / 1024 / 1024);
|
|
42027
42059
|
if (bytes < MIN_HINDSIGHT_SHM_BYTES) {
|
|
@@ -42434,7 +42466,7 @@ function classifyToolContract(advertised) {
|
|
|
42434
42466
|
}
|
|
42435
42467
|
return results;
|
|
42436
42468
|
}
|
|
42437
|
-
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;
|
|
42438
42470
|
var init_doctor_memory = __esm(() => {
|
|
42439
42471
|
init_hindsight_tools();
|
|
42440
42472
|
init_hindsight();
|
|
@@ -45758,6 +45790,69 @@ var init_doctor_timezone = __esm(() => {
|
|
|
45758
45790
|
init_timezone();
|
|
45759
45791
|
});
|
|
45760
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
|
+
|
|
45761
45856
|
// src/cli/doctor.ts
|
|
45762
45857
|
var exports_doctor = {};
|
|
45763
45858
|
__export(exports_doctor, {
|
|
@@ -45817,16 +45912,16 @@ import {
|
|
|
45817
45912
|
readdirSync as readdirSync23,
|
|
45818
45913
|
statSync as statSync38
|
|
45819
45914
|
} from "node:fs";
|
|
45820
|
-
import { dirname as dirname20, join as
|
|
45915
|
+
import { dirname as dirname20, join as join68, resolve as resolve39 } from "node:path";
|
|
45821
45916
|
import { createPublicKey, createPrivateKey } from "node:crypto";
|
|
45822
45917
|
function findInNvm(bin) {
|
|
45823
|
-
const nvmRoot =
|
|
45918
|
+
const nvmRoot = join68(process.env.HOME ?? "", ".nvm", "versions", "node");
|
|
45824
45919
|
if (!existsSync66(nvmRoot))
|
|
45825
45920
|
return null;
|
|
45826
45921
|
try {
|
|
45827
45922
|
const versions = readdirSync23(nvmRoot).sort().reverse();
|
|
45828
45923
|
for (const v of versions) {
|
|
45829
|
-
const candidate =
|
|
45924
|
+
const candidate = join68(nvmRoot, v, "bin", bin);
|
|
45830
45925
|
try {
|
|
45831
45926
|
const s = statSync38(candidate);
|
|
45832
45927
|
if (s.isFile() || s.isSymbolicLink()) {
|
|
@@ -45853,7 +45948,7 @@ function whichOnPath(bin) {
|
|
|
45853
45948
|
for (const dir of pathEnv.split(":")) {
|
|
45854
45949
|
if (!dir)
|
|
45855
45950
|
continue;
|
|
45856
|
-
const candidate =
|
|
45951
|
+
const candidate = join68(dir, bin);
|
|
45857
45952
|
if (isX(candidate))
|
|
45858
45953
|
return candidate;
|
|
45859
45954
|
}
|
|
@@ -46019,7 +46114,7 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
|
|
|
46019
46114
|
if (envBrowsersPath && envBrowsersPath.length > 0) {
|
|
46020
46115
|
cacheLocations.push(envBrowsersPath);
|
|
46021
46116
|
}
|
|
46022
|
-
cacheLocations.push(
|
|
46117
|
+
cacheLocations.push(join68(homeDir, ".cache", "ms-playwright"));
|
|
46023
46118
|
for (const cacheDir of cacheLocations) {
|
|
46024
46119
|
if (!existsSync66(cacheDir))
|
|
46025
46120
|
continue;
|
|
@@ -46027,10 +46122,10 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
|
|
|
46027
46122
|
const entries = readdirSync23(cacheDir).filter((e) => e.startsWith("chromium"));
|
|
46028
46123
|
for (const entry of entries) {
|
|
46029
46124
|
const candidates2 = [
|
|
46030
|
-
|
|
46031
|
-
|
|
46032
|
-
|
|
46033
|
-
|
|
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")
|
|
46034
46129
|
];
|
|
46035
46130
|
for (const path5 of candidates2) {
|
|
46036
46131
|
if (existsSync66(path5))
|
|
@@ -46162,7 +46257,7 @@ function checkDeployMounts(opts) {
|
|
|
46162
46257
|
const home2 = opts?.home ?? process.env.HOME ?? "/root";
|
|
46163
46258
|
const { pathKind } = opts?.deps ?? DEFAULT_DEPLOY_MOUNTS_DEPS;
|
|
46164
46259
|
const results = [];
|
|
46165
|
-
const dockerComposePlugin =
|
|
46260
|
+
const dockerComposePlugin = join68(home2, ".docker", "cli-plugins", "docker-compose");
|
|
46166
46261
|
const pluginKind = pathKind(dockerComposePlugin);
|
|
46167
46262
|
if (pluginKind === "dir") {
|
|
46168
46263
|
results.push({
|
|
@@ -46200,7 +46295,7 @@ function checkDeployMounts(opts) {
|
|
|
46200
46295
|
function checkLegacyState() {
|
|
46201
46296
|
const results = [];
|
|
46202
46297
|
const h = process.env.HOME ?? "/root";
|
|
46203
|
-
const clerkDir =
|
|
46298
|
+
const clerkDir = join68(h, LEGACY_STATE_DIR);
|
|
46204
46299
|
const clerkPresent = existsSync66(clerkDir);
|
|
46205
46300
|
results.push({
|
|
46206
46301
|
name: "legacy ~/.clerk state",
|
|
@@ -46210,7 +46305,7 @@ function checkLegacyState() {
|
|
|
46210
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."
|
|
46211
46306
|
} : {}
|
|
46212
46307
|
});
|
|
46213
|
-
const legacySock =
|
|
46308
|
+
const legacySock = join68(h, ".switchroom", "vault-broker.sock");
|
|
46214
46309
|
let sockStat = null;
|
|
46215
46310
|
try {
|
|
46216
46311
|
sockStat = lstatSync9(legacySock);
|
|
@@ -46474,6 +46569,9 @@ async function checkBankIngestHealth(config, url, opts) {
|
|
|
46474
46569
|
});
|
|
46475
46570
|
continue;
|
|
46476
46571
|
}
|
|
46572
|
+
const directiveRow = classifyDirectiveCount(h.activeDirectiveCount, label);
|
|
46573
|
+
if (directiveRow)
|
|
46574
|
+
results.push(directiveRow);
|
|
46477
46575
|
if (h.totalDocuments === 0) {
|
|
46478
46576
|
results.push({
|
|
46479
46577
|
name: label,
|
|
@@ -46776,7 +46874,7 @@ async function checkTelegram(config) {
|
|
|
46776
46874
|
const plugin = agentConfig.channels?.telegram?.plugin ?? "switchroom";
|
|
46777
46875
|
if (plugin !== "switchroom")
|
|
46778
46876
|
continue;
|
|
46779
|
-
const envPath =
|
|
46877
|
+
const envPath = join68(agentsDir, name, "telegram", ".env");
|
|
46780
46878
|
const read = tryReadHostFile(envPath);
|
|
46781
46879
|
if (read.kind === "eacces") {
|
|
46782
46880
|
results.push({
|
|
@@ -46864,7 +46962,7 @@ function checkStartShSessionModelCarrier(agentName, startShPath) {
|
|
|
46864
46962
|
name: label,
|
|
46865
46963
|
status: "warn",
|
|
46866
46964
|
detail: `${startShPath} not found`,
|
|
46867
|
-
fix: `Run \`switchroom apply\` to scaffold start.sh (rev5 \`.session-model\` carrier).`
|
|
46965
|
+
fix: `Run \`switchroom doctor --fix\` (or \`switchroom apply\`) to scaffold start.sh (rev5 \`.session-model\` carrier).`
|
|
46868
46966
|
};
|
|
46869
46967
|
}
|
|
46870
46968
|
let content;
|
|
@@ -46893,7 +46991,7 @@ function checkStartShSessionModelCarrier(agentName, startShPath) {
|
|
|
46893
46991
|
name: label,
|
|
46894
46992
|
status: "fail",
|
|
46895
46993
|
detail: "start.sh tests `.session-model` but lacks rev5 JSON apply (`configuredDefaultAtWrite` / boot-attempts) \u2014 /model switches may not stick",
|
|
46896
|
-
fix: "Run `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)."
|
|
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)."
|
|
46897
46995
|
};
|
|
46898
46996
|
}
|
|
46899
46997
|
const legacyOnly = /\.session-model-override/.test(content) && !hasBareSessionModelTest;
|
|
@@ -46901,12 +46999,12 @@ function checkStartShSessionModelCarrier(agentName, startShPath) {
|
|
|
46901
46999
|
name: label,
|
|
46902
47000
|
status: "fail",
|
|
46903
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",
|
|
46904
|
-
fix: "Run `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."
|
|
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."
|
|
46905
47003
|
};
|
|
46906
47004
|
}
|
|
46907
47005
|
function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
46908
47006
|
const label = `${agentName}: $HOME/.switchroom symlink (#910)`;
|
|
46909
|
-
const path5 =
|
|
47007
|
+
const path5 = join68(agentDir, "home", ".switchroom");
|
|
46910
47008
|
let stats;
|
|
46911
47009
|
try {
|
|
46912
47010
|
stats = lstatSync9(path5);
|
|
@@ -46943,7 +47041,7 @@ function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
|
46943
47041
|
}
|
|
46944
47042
|
function checkRepoHygiene(repoRoot) {
|
|
46945
47043
|
const results = [];
|
|
46946
|
-
const exportDir =
|
|
47044
|
+
const exportDir = join68(repoRoot, "clerk-export");
|
|
46947
47045
|
if (existsSync66(exportDir)) {
|
|
46948
47046
|
results.push({
|
|
46949
47047
|
name: "repo hygiene: clerk-export/ on disk (#1072)",
|
|
@@ -46952,7 +47050,7 @@ function checkRepoHygiene(repoRoot) {
|
|
|
46952
47050
|
fix: `Run scripts/migrate-clerk-export-to-vault.sh to move the bundle ` + `into the vault, then delete the on-disk copy.`
|
|
46953
47051
|
});
|
|
46954
47052
|
}
|
|
46955
|
-
const knownTarball =
|
|
47053
|
+
const knownTarball = join68(repoRoot, "clerk-export-with-secrets.tar.gz");
|
|
46956
47054
|
if (existsSync66(knownTarball)) {
|
|
46957
47055
|
results.push({
|
|
46958
47056
|
name: "repo hygiene: clerk-export-with-secrets.tar.gz on disk (#1072)",
|
|
@@ -46970,7 +47068,7 @@ function checkRepoHygiene(repoRoot) {
|
|
|
46970
47068
|
results.push({
|
|
46971
47069
|
name: `repo hygiene: ${name} on disk (#1072)`,
|
|
46972
47070
|
status: "warn",
|
|
46973
|
-
detail: `${
|
|
47071
|
+
detail: `${join68(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
|
|
46974
47072
|
fix: `Inspect, migrate any secrets into the vault, then delete the ` + `archive.`
|
|
46975
47073
|
});
|
|
46976
47074
|
}
|
|
@@ -46993,9 +47091,9 @@ function checkRepoHygiene(repoRoot) {
|
|
|
46993
47091
|
}
|
|
46994
47092
|
function isSwitchroomCheckout(dir) {
|
|
46995
47093
|
try {
|
|
46996
|
-
if (!existsSync66(
|
|
47094
|
+
if (!existsSync66(join68(dir, ".git")))
|
|
46997
47095
|
return false;
|
|
46998
|
-
const pkgPath =
|
|
47096
|
+
const pkgPath = join68(dir, "package.json");
|
|
46999
47097
|
if (!existsSync66(pkgPath))
|
|
47000
47098
|
return false;
|
|
47001
47099
|
const pkg = JSON.parse(readFileSync59(pkgPath, "utf-8"));
|
|
@@ -47032,8 +47130,8 @@ function checkAgents(config, configPath) {
|
|
|
47032
47130
|
fix: `Rotate the bot token (e.g. via \`switchroom vault\`), then run ` + `\`switchroom agent unquarantine ${name}\` and \`switchroom agent restart ${name}\``
|
|
47033
47131
|
});
|
|
47034
47132
|
}
|
|
47035
|
-
results.push(checkStartShStale(name,
|
|
47036
|
-
results.push(checkStartShSessionModelCarrier(name,
|
|
47133
|
+
results.push(checkStartShStale(name, join68(agentDir, "start.sh")));
|
|
47134
|
+
results.push(checkStartShSessionModelCarrier(name, join68(agentDir, "start.sh")));
|
|
47037
47135
|
results.push(checkLeakedHomeSwitchroom(name, agentDir));
|
|
47038
47136
|
const status = statuses[name];
|
|
47039
47137
|
const active = status?.active ?? "unknown";
|
|
@@ -47110,7 +47208,7 @@ function checkAgents(config, configPath) {
|
|
|
47110
47208
|
}
|
|
47111
47209
|
}
|
|
47112
47210
|
if (agentConfig.channels?.telegram?.plugin === "switchroom") {
|
|
47113
|
-
const mcpJsonPath =
|
|
47211
|
+
const mcpJsonPath = join68(agentDir, ".mcp.json");
|
|
47114
47212
|
if (!existsSync66(mcpJsonPath)) {
|
|
47115
47213
|
results.push({
|
|
47116
47214
|
name: `${name}: .mcp.json`,
|
|
@@ -47412,7 +47510,7 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
|
|
|
47412
47510
|
};
|
|
47413
47511
|
}
|
|
47414
47512
|
const credDir = dirname20(envPath);
|
|
47415
|
-
const authScript =
|
|
47513
|
+
const authScript = join68(credDir, "claude-auth.py");
|
|
47416
47514
|
if (!existsSync66(authScript)) {
|
|
47417
47515
|
return {
|
|
47418
47516
|
name: "mff: auth flow",
|
|
@@ -47629,7 +47727,7 @@ function runDockerSection(config) {
|
|
|
47629
47727
|
});
|
|
47630
47728
|
}
|
|
47631
47729
|
function registerDoctorCommand(program3) {
|
|
47632
|
-
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) => {
|
|
47633
47731
|
try {
|
|
47634
47732
|
getConfigPath(program3);
|
|
47635
47733
|
} catch (_e) {
|
|
@@ -47698,7 +47796,23 @@ function registerDoctorCommand(program3) {
|
|
|
47698
47796
|
return { kind: "unreachable", msg: err.message };
|
|
47699
47797
|
}
|
|
47700
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
|
+
] : [];
|
|
47701
47814
|
const sections = [
|
|
47815
|
+
...fixSections,
|
|
47702
47816
|
{ title: "Dependencies", results: checkDependencies() },
|
|
47703
47817
|
{ title: "Skills Prerequisites", results: checkSkillsPrerequisites() },
|
|
47704
47818
|
{ title: "Manifest Drift", results: await checkManifestDrift() },
|
|
@@ -47845,6 +47959,7 @@ var init_doctor = __esm(() => {
|
|
|
47845
47959
|
init_helpers();
|
|
47846
47960
|
init_lifecycle();
|
|
47847
47961
|
init_quarantine();
|
|
47962
|
+
init_scaffold();
|
|
47848
47963
|
init_manager();
|
|
47849
47964
|
init_accounts();
|
|
47850
47965
|
init_manifest();
|
|
@@ -47869,6 +47984,7 @@ var init_doctor = __esm(() => {
|
|
|
47869
47984
|
init_doctor_agent_smoke();
|
|
47870
47985
|
init_doctor_vault_broker_durability();
|
|
47871
47986
|
init_doctor_timezone();
|
|
47987
|
+
init_doctor_fix_session_model();
|
|
47872
47988
|
DEFAULT_DEPLOY_MOUNTS_DEPS = { pathKind: defaultPathKind };
|
|
47873
47989
|
MANIFEST_WARN_ONLY = new Set([
|
|
47874
47990
|
"@playwright/mcp",
|
|
@@ -48454,7 +48570,7 @@ var init_fleet_defaults = __esm(() => {
|
|
|
48454
48570
|
|
|
48455
48571
|
// src/agents/connection-health.ts
|
|
48456
48572
|
import { mkdirSync as mkdirSync46, writeFileSync as writeFileSync28 } from "node:fs";
|
|
48457
|
-
import { join as
|
|
48573
|
+
import { join as join84 } from "node:path";
|
|
48458
48574
|
async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
|
|
48459
48575
|
const reqs = computeMcpSecretRequirements(config).filter((r) => r.agent === agentName);
|
|
48460
48576
|
if (reqs.length === 0)
|
|
@@ -48511,8 +48627,8 @@ async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
|
|
|
48511
48627
|
return issues;
|
|
48512
48628
|
}
|
|
48513
48629
|
function writeConnectionHealthFile(agentDir, health, deps) {
|
|
48514
|
-
const dir =
|
|
48515
|
-
const path8 =
|
|
48630
|
+
const dir = join84(agentDir, ".claude");
|
|
48631
|
+
const path8 = join84(dir, CONNECTION_HEALTH_FILENAME);
|
|
48516
48632
|
(deps?.mkdir ?? ((p, o) => mkdirSync46(p, o)))(dir, { recursive: true });
|
|
48517
48633
|
(deps?.writeFile ?? ((p, d) => writeFileSync28(p, d)))(path8, JSON.stringify(health, null, 2) + `
|
|
48518
48634
|
`);
|
|
@@ -48536,9 +48652,9 @@ var init_connection_health = () => {};
|
|
|
48536
48652
|
|
|
48537
48653
|
// src/cli/update-prompt-hook.ts
|
|
48538
48654
|
import { existsSync as existsSync83, readFileSync as readFileSync72, writeFileSync as writeFileSync29, chmodSync as chmodSync12, mkdirSync as mkdirSync47 } from "node:fs";
|
|
48539
|
-
import { join as
|
|
48655
|
+
import { join as join85 } from "node:path";
|
|
48540
48656
|
function containerHookCommand() {
|
|
48541
|
-
return
|
|
48657
|
+
return join85(CONTAINER_AGENT_DIR, ".claude", "hooks", HOOK_FILENAME);
|
|
48542
48658
|
}
|
|
48543
48659
|
function updatePromptHookScript() {
|
|
48544
48660
|
return `#!/bin/bash
|
|
@@ -48604,9 +48720,9 @@ exit 0
|
|
|
48604
48720
|
`;
|
|
48605
48721
|
}
|
|
48606
48722
|
function installUpdatePromptHook(agentDir) {
|
|
48607
|
-
const hooksDir =
|
|
48723
|
+
const hooksDir = join85(agentDir, ".claude", "hooks");
|
|
48608
48724
|
mkdirSync47(hooksDir, { recursive: true });
|
|
48609
|
-
const scriptPath =
|
|
48725
|
+
const scriptPath = join85(hooksDir, HOOK_FILENAME);
|
|
48610
48726
|
const desired = updatePromptHookScript();
|
|
48611
48727
|
let installed = false;
|
|
48612
48728
|
const existing = existsSync83(scriptPath) ? readFileSync72(scriptPath, "utf-8") : "";
|
|
@@ -48619,7 +48735,7 @@ function installUpdatePromptHook(agentDir) {
|
|
|
48619
48735
|
chmodSync12(scriptPath, 493);
|
|
48620
48736
|
} catch {}
|
|
48621
48737
|
}
|
|
48622
|
-
const settingsPath =
|
|
48738
|
+
const settingsPath = join85(agentDir, ".claude", "settings.json");
|
|
48623
48739
|
if (!existsSync83(settingsPath)) {
|
|
48624
48740
|
return { scriptPath, settingsPath, installed };
|
|
48625
48741
|
}
|
|
@@ -49175,7 +49291,7 @@ import { accessSync as accessSync3, chmodSync as chmodSync14, chownSync as chown
|
|
|
49175
49291
|
import { mkdir as mkdir2 } from "node:fs/promises";
|
|
49176
49292
|
import { spawnSync as childSpawnSync } from "node:child_process";
|
|
49177
49293
|
import readline from "node:readline";
|
|
49178
|
-
import { dirname as dirname30, join as
|
|
49294
|
+
import { dirname as dirname30, join as join87, resolve as resolve52 } from "node:path";
|
|
49179
49295
|
import { homedir as homedir49 } from "node:os";
|
|
49180
49296
|
import { execFileSync as execFileSync27 } from "node:child_process";
|
|
49181
49297
|
function effectiveLiteLLMEnabled(config, agentResolvedLitellm) {
|
|
@@ -49187,7 +49303,7 @@ async function resolveOperatorVaultPassphrase(home2) {
|
|
|
49187
49303
|
return envPass;
|
|
49188
49304
|
try {
|
|
49189
49305
|
const { readAutoUnlockFile: readAutoUnlockFile2 } = await Promise.resolve().then(() => (init_auto_unlock(), exports_auto_unlock));
|
|
49190
|
-
const blobPath =
|
|
49306
|
+
const blobPath = join87(home2, ".switchroom", "vault-auto-unlock");
|
|
49191
49307
|
const pass = readAutoUnlockFile2(blobPath);
|
|
49192
49308
|
return pass && pass.length > 0 ? pass : null;
|
|
49193
49309
|
} catch {
|
|
@@ -49196,9 +49312,9 @@ async function resolveOperatorVaultPassphrase(home2) {
|
|
|
49196
49312
|
}
|
|
49197
49313
|
function materializeLitellmMasterKeyForBroker(masterKey, home2 = process.env.HOME ?? "/root") {
|
|
49198
49314
|
try {
|
|
49199
|
-
const stateDir =
|
|
49315
|
+
const stateDir = join87(home2, ".switchroom", "state", "auth-broker");
|
|
49200
49316
|
mkdirSync49(stateDir, { recursive: true, mode: 448 });
|
|
49201
|
-
const path9 =
|
|
49317
|
+
const path9 = join87(stateDir, LITELLM_MASTER_KEY_STATE_BASENAME);
|
|
49202
49318
|
writeFileSync31(path9, masterKey.trim() + `
|
|
49203
49319
|
`, { mode: 384 });
|
|
49204
49320
|
try {
|
|
@@ -49524,7 +49640,7 @@ function resolveVaultBindMountDir(homeDir, ctx) {
|
|
|
49524
49640
|
if (isCustomPath && ctx.customVaultPath) {
|
|
49525
49641
|
return dirname30(ctx.customVaultPath);
|
|
49526
49642
|
}
|
|
49527
|
-
return
|
|
49643
|
+
return join87(homeDir, ".switchroom", "vault");
|
|
49528
49644
|
}
|
|
49529
49645
|
function inspectVaultBindMountDir(vaultDir) {
|
|
49530
49646
|
if (!existsSync86(vaultDir))
|
|
@@ -49555,41 +49671,41 @@ function hasVaultRefs(value) {
|
|
|
49555
49671
|
async function ensureHostMountSources(config) {
|
|
49556
49672
|
const home2 = resolveHostHomeForCompose();
|
|
49557
49673
|
const dirs = [
|
|
49558
|
-
|
|
49559
|
-
|
|
49560
|
-
|
|
49561
|
-
|
|
49562
|
-
|
|
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")
|
|
49563
49679
|
];
|
|
49564
49680
|
for (const name of Object.keys(config.agents)) {
|
|
49565
|
-
dirs.push(
|
|
49566
|
-
dirs.push(
|
|
49567
|
-
dirs.push(
|
|
49568
|
-
dirs.push(
|
|
49569
|
-
if (existsSync86(
|
|
49570
|
-
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"));
|
|
49571
49687
|
}
|
|
49572
49688
|
}
|
|
49573
49689
|
for (const dir of dirs) {
|
|
49574
49690
|
await mkdir2(dir, { recursive: true });
|
|
49575
49691
|
}
|
|
49576
|
-
const autoUnlockPath =
|
|
49692
|
+
const autoUnlockPath = join87(home2, ".switchroom", "vault-auto-unlock");
|
|
49577
49693
|
if (!existsSync86(autoUnlockPath)) {
|
|
49578
49694
|
writeFileSync31(autoUnlockPath, "", { mode: 384 });
|
|
49579
49695
|
}
|
|
49580
|
-
const auditLogPath =
|
|
49696
|
+
const auditLogPath = join87(home2, ".switchroom", "vault-audit.log");
|
|
49581
49697
|
if (!existsSync86(auditLogPath)) {
|
|
49582
49698
|
writeFileSync31(auditLogPath, "", { mode: 420 });
|
|
49583
49699
|
}
|
|
49584
49700
|
const grantsDbDir = getGrantsDbDir(home2);
|
|
49585
49701
|
mkdirSync49(grantsDbDir, { recursive: true, mode: 448 });
|
|
49586
49702
|
migrateLegacyGrantsDbLocation(getGrantsDbPath(home2));
|
|
49587
|
-
const hostdAuditLogPath =
|
|
49703
|
+
const hostdAuditLogPath = join87(home2, ".switchroom", "host-control-audit.log");
|
|
49588
49704
|
if (!existsSync86(hostdAuditLogPath)) {
|
|
49589
49705
|
writeFileSync31(hostdAuditLogPath, "", { mode: 420 });
|
|
49590
49706
|
}
|
|
49591
49707
|
for (const name of Object.keys(config.agents)) {
|
|
49592
|
-
const tokenPath =
|
|
49708
|
+
const tokenPath = join87(home2, ".switchroom", "agents", name, ".vault-token");
|
|
49593
49709
|
if (!existsSync86(tokenPath)) {
|
|
49594
49710
|
writeFileSync31(tokenPath, "", { mode: 384 });
|
|
49595
49711
|
}
|
|
@@ -49598,15 +49714,15 @@ async function ensureHostMountSources(config) {
|
|
|
49598
49714
|
chownSync8(tokenPath, uid, uid);
|
|
49599
49715
|
} catch {}
|
|
49600
49716
|
}
|
|
49601
|
-
const fleetDir =
|
|
49717
|
+
const fleetDir = join87(home2, ".switchroom", "fleet");
|
|
49602
49718
|
await mkdir2(fleetDir, { recursive: true });
|
|
49603
|
-
const invariantsPath =
|
|
49719
|
+
const invariantsPath = join87(fleetDir, "switchroom-invariants.md");
|
|
49604
49720
|
const invariantsCanonical = renderFleetInvariants();
|
|
49605
49721
|
const invariantsCurrent = existsSync86(invariantsPath) ? readFileSync74(invariantsPath, "utf-8") : null;
|
|
49606
49722
|
if (invariantsCurrent !== invariantsCanonical) {
|
|
49607
49723
|
writeFileSync31(invariantsPath, invariantsCanonical, { mode: 420 });
|
|
49608
49724
|
}
|
|
49609
|
-
const fleetClaudePath =
|
|
49725
|
+
const fleetClaudePath = join87(fleetDir, "CLAUDE.md");
|
|
49610
49726
|
if (!existsSync86(fleetClaudePath)) {
|
|
49611
49727
|
writeFileSync31(fleetClaudePath, renderFleetDefaultsClaudeMd(), {
|
|
49612
49728
|
mode: 420
|
|
@@ -49692,8 +49808,8 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
|
49692
49808
|
}
|
|
49693
49809
|
function writeInstallTypeCache(homeDir = homedir49()) {
|
|
49694
49810
|
const ctx = detectInstallType();
|
|
49695
|
-
const dir =
|
|
49696
|
-
const out =
|
|
49811
|
+
const dir = join87(homeDir, ".switchroom");
|
|
49812
|
+
const out = join87(dir, "install-type.json");
|
|
49697
49813
|
const tmp = `${out}.tmp`;
|
|
49698
49814
|
mkdirSync49(dir, { recursive: true });
|
|
49699
49815
|
const payload = {
|
|
@@ -49760,17 +49876,17 @@ Applying switchroom config...
|
|
|
49760
49876
|
writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
|
|
49761
49877
|
`));
|
|
49762
49878
|
try {
|
|
49763
|
-
installUpdatePromptHook(
|
|
49879
|
+
installUpdatePromptHook(join87(agentsDir, name));
|
|
49764
49880
|
} catch (hookErr) {
|
|
49765
49881
|
writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
|
|
49766
49882
|
`));
|
|
49767
49883
|
}
|
|
49768
|
-
await refreshAgentConnectionHealth(config, name,
|
|
49884
|
+
await refreshAgentConnectionHealth(config, name, join87(agentsDir, name), {
|
|
49769
49885
|
vaultAclReader: connHealthVaultAclReader
|
|
49770
49886
|
});
|
|
49771
49887
|
try {
|
|
49772
49888
|
const uid = allocateAgentUid(name);
|
|
49773
|
-
alignAgentUid(name,
|
|
49889
|
+
alignAgentUid(name, join87(agentsDir, name), uid, {
|
|
49774
49890
|
confirm: !options.nonInteractive,
|
|
49775
49891
|
writeOut
|
|
49776
49892
|
});
|
|
@@ -49815,7 +49931,7 @@ Applying switchroom config...
|
|
|
49815
49931
|
for (const name of agentNames) {
|
|
49816
49932
|
try {
|
|
49817
49933
|
const uid = allocateAgentUid(name);
|
|
49818
|
-
alignAgentUid(name,
|
|
49934
|
+
alignAgentUid(name, join87(agentsDir, name), uid, {
|
|
49819
49935
|
confirm: !options.nonInteractive,
|
|
49820
49936
|
writeOut
|
|
49821
49937
|
});
|
|
@@ -50175,7 +50291,7 @@ function findUnwritableAgentDirs(config, opts) {
|
|
|
50175
50291
|
const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
|
|
50176
50292
|
const unwritable = [];
|
|
50177
50293
|
for (const name of targets) {
|
|
50178
|
-
const startSh =
|
|
50294
|
+
const startSh = join87(agentsDir, name, "start.sh");
|
|
50179
50295
|
if (!existsSync86(startSh))
|
|
50180
50296
|
continue;
|
|
50181
50297
|
try {
|
|
@@ -50381,7 +50497,7 @@ var init_apply = __esm(() => {
|
|
|
50381
50497
|
switchroom: switchroom_default,
|
|
50382
50498
|
minimal: minimal_default
|
|
50383
50499
|
};
|
|
50384
|
-
DEFAULT_COMPOSE_PATH2 =
|
|
50500
|
+
DEFAULT_COMPOSE_PATH2 = join87(homedir49(), ".switchroom", "compose", "docker-compose.yml");
|
|
50385
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.
|
|
50386
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.";
|
|
50387
50503
|
SELF_ELEVATE_PRESERVED_ENV = [
|
|
@@ -70801,6 +70917,10 @@ function isThin(d) {
|
|
|
70801
70917
|
}
|
|
70802
70918
|
function classifyState(args) {
|
|
70803
70919
|
const { exhausted, exhaustedUntil, window, now } = args;
|
|
70920
|
+
if (args.entitlementBlocked === true)
|
|
70921
|
+
return "org-disabled";
|
|
70922
|
+
if (args.inService === false)
|
|
70923
|
+
return "retired";
|
|
70804
70924
|
if (window.probeThin)
|
|
70805
70925
|
return "unprobed";
|
|
70806
70926
|
const norm = refillNormalizedUtils({
|
|
@@ -70845,17 +70965,30 @@ function buildScheduleRows(state, probe, now) {
|
|
|
70845
70965
|
const exhaustedUntil = typeof a.exhausted_until === "number" ? new Date(a.exhausted_until) : null;
|
|
70846
70966
|
const exhausted = a.exhausted && (exhaustedUntil?.getTime() ?? 0) > now.getTime();
|
|
70847
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;
|
|
70848
70971
|
return {
|
|
70849
70972
|
label: a.label,
|
|
70850
|
-
isActive
|
|
70973
|
+
isActive,
|
|
70851
70974
|
fallbackRank: rank === -1 ? null : rank + 1,
|
|
70975
|
+
inService,
|
|
70976
|
+
entitlementBlocked,
|
|
70852
70977
|
window,
|
|
70853
70978
|
exhausted,
|
|
70854
70979
|
exhaustedUntil,
|
|
70855
|
-
state: classifyState({
|
|
70980
|
+
state: classifyState({
|
|
70981
|
+
exhausted,
|
|
70982
|
+
exhaustedUntil,
|
|
70983
|
+
window,
|
|
70984
|
+
now,
|
|
70985
|
+
inService,
|
|
70986
|
+
entitlementBlocked
|
|
70987
|
+
})
|
|
70856
70988
|
};
|
|
70857
70989
|
});
|
|
70858
|
-
const
|
|
70990
|
+
const outOfService = (r) => !r.inService || r.entitlementBlocked;
|
|
70991
|
+
const key = (r) => outOfService(r) ? 1e5 : r.isActive ? -1 : r.fallbackRank ?? 9999;
|
|
70859
70992
|
return rows.sort((a, b) => key(a) - key(b) || a.label.localeCompare(b.label));
|
|
70860
70993
|
}
|
|
70861
70994
|
var EM_DASH = "\u2014";
|
|
@@ -70908,6 +71041,10 @@ function formatWeeklyCell(w, now, tz) {
|
|
|
70908
71041
|
return `${pct} \u00b7 ${formatResetDay(w.weeklyResetAt, tz)} (${rel})`;
|
|
70909
71042
|
}
|
|
70910
71043
|
function poolLabel(row) {
|
|
71044
|
+
if (row.entitlementBlocked)
|
|
71045
|
+
return "disabled";
|
|
71046
|
+
if (!row.inService)
|
|
71047
|
+
return "retired";
|
|
70911
71048
|
if (row.isActive)
|
|
70912
71049
|
return row.fallbackRank ? `active #${row.fallbackRank}` : "active";
|
|
70913
71050
|
if (row.fallbackRank)
|
|
@@ -70919,6 +71056,10 @@ function formatStateCell(row, now) {
|
|
|
70919
71056
|
const rel = horizon ? ` \u00b7 ${formatDuration(horizon.getTime() - now.getTime())}` : "";
|
|
70920
71057
|
const overageNote = row.window.overageServeBlocking && row.window.overageReason ? ` (overage off: ${row.window.overageReason})` : row.window.overageServeBlocking ? " (overage off)" : "";
|
|
70921
71058
|
switch (row.state) {
|
|
71059
|
+
case "org-disabled":
|
|
71060
|
+
return "DISABLED (org)";
|
|
71061
|
+
case "retired":
|
|
71062
|
+
return "retired";
|
|
70922
71063
|
case "quota-exhausted":
|
|
70923
71064
|
return `quota-exhausted${rel}`;
|
|
70924
71065
|
case "healthy":
|
|
@@ -70940,10 +71081,16 @@ function pad3(s, n) {
|
|
|
70940
71081
|
return s.padEnd(n);
|
|
70941
71082
|
}
|
|
70942
71083
|
function glyphFor(row, color) {
|
|
70943
|
-
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";
|
|
70944
71085
|
if (!color)
|
|
70945
71086
|
return g;
|
|
70946
|
-
|
|
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);
|
|
70947
71094
|
}
|
|
70948
71095
|
function colorState(text, state, color) {
|
|
70949
71096
|
if (!color)
|
|
@@ -70951,6 +71098,7 @@ function colorState(text, state, color) {
|
|
|
70951
71098
|
switch (state) {
|
|
70952
71099
|
case "healthy":
|
|
70953
71100
|
return source_default.green(text);
|
|
71101
|
+
case "org-disabled":
|
|
70954
71102
|
case "weekly-walled":
|
|
70955
71103
|
return source_default.red(text);
|
|
70956
71104
|
case "quota-exhausted":
|
|
@@ -71164,8 +71312,11 @@ function formatQuotaUtilCell(a, now = Date.now()) {
|
|
|
71164
71312
|
function printAccountsTable(state) {
|
|
71165
71313
|
console.log(source_default.bold(" ACCOUNT STATUS EXPIRES QUOTA 5h\u00b77d QUOTA-RESET"));
|
|
71166
71314
|
for (const a of state.accounts) {
|
|
71167
|
-
const
|
|
71168
|
-
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";
|
|
71169
71320
|
const label = a.label.padEnd(32);
|
|
71170
71321
|
const exp = formatExpiry2(a.expiresAt).padEnd(10);
|
|
71171
71322
|
const util3 = formatQuotaUtilCell(a).padEnd(20);
|
|
@@ -71214,8 +71365,16 @@ function printUsageTable(state) {
|
|
|
71214
71365
|
console.log(source_default.bold(" ACCOUNT PREMIUM (Fable) HEADROOM STANDARD HEADROOM"));
|
|
71215
71366
|
const WIDTH = 44;
|
|
71216
71367
|
for (const a of state.accounts) {
|
|
71217
|
-
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") : " ";
|
|
71218
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
|
+
}
|
|
71219
71378
|
const premium = usageHeadroomCell(a.usage_ledger?.premium ?? null);
|
|
71220
71379
|
const standard = usageHeadroomCell(a.usage_ledger?.standard ?? null);
|
|
71221
71380
|
const premiumCol = premium.color(premium.plain.padEnd(WIDTH));
|
|
@@ -84825,7 +84984,7 @@ init_lifecycle();
|
|
|
84825
84984
|
init_compose_env();
|
|
84826
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";
|
|
84827
84986
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
84828
|
-
import { join as
|
|
84987
|
+
import { join as join69, dirname as dirname21, resolve as resolve40 } from "node:path";
|
|
84829
84988
|
import { homedir as homedir40 } from "node:os";
|
|
84830
84989
|
|
|
84831
84990
|
// src/cli/release-yaml.ts
|
|
@@ -84933,13 +85092,13 @@ function defaultPersistPin(configPath) {
|
|
|
84933
85092
|
} catch {}
|
|
84934
85093
|
};
|
|
84935
85094
|
}
|
|
84936
|
-
var DEFAULT_COMPOSE_PATH =
|
|
85095
|
+
var DEFAULT_COMPOSE_PATH = join69(homedir40(), ".switchroom", "compose", "docker-compose.yml");
|
|
84937
85096
|
function runningFromSwitchroomCheckout(scriptPath) {
|
|
84938
85097
|
let dir = dirname21(scriptPath);
|
|
84939
85098
|
for (let i = 0;i < 12; i++) {
|
|
84940
|
-
if (existsSync67(
|
|
85099
|
+
if (existsSync67(join69(dir, ".git"))) {
|
|
84941
85100
|
try {
|
|
84942
|
-
const pkg = JSON.parse(readFileSync60(
|
|
85101
|
+
const pkg = JSON.parse(readFileSync60(join69(dir, "package.json"), "utf-8"));
|
|
84943
85102
|
if (pkg.name === "switchroom")
|
|
84944
85103
|
return true;
|
|
84945
85104
|
} catch {}
|
|
@@ -85150,7 +85309,7 @@ function planUpdate(opts) {
|
|
|
85150
85309
|
return;
|
|
85151
85310
|
}
|
|
85152
85311
|
const source = resolve40(import.meta.dirname, "../../skills");
|
|
85153
|
-
const dest =
|
|
85312
|
+
const dest = join69(homedir40(), ".switchroom", "skills", "_bundled");
|
|
85154
85313
|
if (!existsSync67(source)) {
|
|
85155
85314
|
process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
|
|
85156
85315
|
`);
|
|
@@ -85275,7 +85434,7 @@ function defaultStatusProbe(composePath) {
|
|
|
85275
85434
|
} catch {}
|
|
85276
85435
|
let dir = dirname21(scriptPath);
|
|
85277
85436
|
for (let i = 0;i < 8; i++) {
|
|
85278
|
-
const pkgPath =
|
|
85437
|
+
const pkgPath = join69(dir, "package.json");
|
|
85279
85438
|
if (existsSync67(pkgPath)) {
|
|
85280
85439
|
try {
|
|
85281
85440
|
const pkg = JSON.parse(readFileSync60(pkgPath, "utf-8"));
|
|
@@ -85978,7 +86137,7 @@ init_lifecycle();
|
|
|
85978
86137
|
init_resolve_version();
|
|
85979
86138
|
import { execSync as execSync3 } from "node:child_process";
|
|
85980
86139
|
import { existsSync as existsSync68, readFileSync as readFileSync62 } from "node:fs";
|
|
85981
|
-
import { dirname as dirname22, join as
|
|
86140
|
+
import { dirname as dirname22, join as join70 } from "node:path";
|
|
85982
86141
|
function getClaudeCodeVersion() {
|
|
85983
86142
|
try {
|
|
85984
86143
|
const out = execSync3("claude --version 2>/dev/null", {
|
|
@@ -86028,11 +86187,11 @@ function formatUptime3(timestamp) {
|
|
|
86028
86187
|
function locateSwitchroomInstallDir() {
|
|
86029
86188
|
let dir = import.meta.dirname;
|
|
86030
86189
|
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
86031
|
-
const pkgPath =
|
|
86190
|
+
const pkgPath = join70(dir, "package.json");
|
|
86032
86191
|
if (existsSync68(pkgPath)) {
|
|
86033
86192
|
try {
|
|
86034
86193
|
const pkg = JSON.parse(readFileSync62(pkgPath, "utf-8"));
|
|
86035
|
-
if (pkg.name === "switchroom" && existsSync68(
|
|
86194
|
+
if (pkg.name === "switchroom" && existsSync68(join70(dir, ".git"))) {
|
|
86036
86195
|
return dir;
|
|
86037
86196
|
}
|
|
86038
86197
|
} catch {}
|
|
@@ -86215,12 +86374,12 @@ import {
|
|
|
86215
86374
|
statSync as statSync42,
|
|
86216
86375
|
unlinkSync as unlinkSync14
|
|
86217
86376
|
} from "node:fs";
|
|
86218
|
-
import { join as
|
|
86377
|
+
import { join as join71 } from "node:path";
|
|
86219
86378
|
var DEFAULT_SESSION_RETENTION_MAX_COUNT = 20;
|
|
86220
86379
|
var DEFAULT_SESSION_RETENTION_MAX_AGE_DAYS = 30;
|
|
86221
86380
|
var MIN_KEEP = 2;
|
|
86222
86381
|
function collectSessionJsonl(claudeConfigDir) {
|
|
86223
|
-
const projects =
|
|
86382
|
+
const projects = join71(claudeConfigDir, "projects");
|
|
86224
86383
|
if (!existsSync69(projects))
|
|
86225
86384
|
return [];
|
|
86226
86385
|
const found = [];
|
|
@@ -86232,7 +86391,7 @@ function collectSessionJsonl(claudeConfigDir) {
|
|
|
86232
86391
|
return;
|
|
86233
86392
|
}
|
|
86234
86393
|
for (const name of entries) {
|
|
86235
|
-
const full =
|
|
86394
|
+
const full = join71(dir, name);
|
|
86236
86395
|
let st;
|
|
86237
86396
|
try {
|
|
86238
86397
|
st = statSync42(full);
|
|
@@ -86363,7 +86522,7 @@ import {
|
|
|
86363
86522
|
writeFileSync as writeFileSync19,
|
|
86364
86523
|
writeSync as writeSync9
|
|
86365
86524
|
} from "node:fs";
|
|
86366
|
-
import { join as
|
|
86525
|
+
import { join as join72 } from "node:path";
|
|
86367
86526
|
import { randomBytes as randomBytes12 } from "node:crypto";
|
|
86368
86527
|
import { execSync as execSync4 } from "node:child_process";
|
|
86369
86528
|
|
|
@@ -86794,7 +86953,7 @@ function redactedMarker(ruleId) {
|
|
|
86794
86953
|
var ISSUES_FILE = "issues.jsonl";
|
|
86795
86954
|
var ISSUES_LOCK = "issues.lock";
|
|
86796
86955
|
function readAll(stateDir) {
|
|
86797
|
-
const path5 =
|
|
86956
|
+
const path5 = join72(stateDir, ISSUES_FILE);
|
|
86798
86957
|
if (!existsSync70(path5))
|
|
86799
86958
|
return [];
|
|
86800
86959
|
let raw;
|
|
@@ -86872,7 +87031,7 @@ function record(stateDir, input, nowFn = Date.now) {
|
|
|
86872
87031
|
});
|
|
86873
87032
|
}
|
|
86874
87033
|
function resolve43(stateDir, fingerprint, nowFn = Date.now) {
|
|
86875
|
-
if (!existsSync70(
|
|
87034
|
+
if (!existsSync70(join72(stateDir, ISSUES_FILE)))
|
|
86876
87035
|
return 0;
|
|
86877
87036
|
return withLock(stateDir, () => {
|
|
86878
87037
|
const all = readAll(stateDir);
|
|
@@ -86890,7 +87049,7 @@ function resolve43(stateDir, fingerprint, nowFn = Date.now) {
|
|
|
86890
87049
|
});
|
|
86891
87050
|
}
|
|
86892
87051
|
function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
86893
|
-
if (!existsSync70(
|
|
87052
|
+
if (!existsSync70(join72(stateDir, ISSUES_FILE)))
|
|
86894
87053
|
return 0;
|
|
86895
87054
|
return withLock(stateDir, () => {
|
|
86896
87055
|
const all = readAll(stateDir);
|
|
@@ -86908,7 +87067,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
|
86908
87067
|
});
|
|
86909
87068
|
}
|
|
86910
87069
|
function prune(stateDir, opts = {}) {
|
|
86911
|
-
if (!existsSync70(
|
|
87070
|
+
if (!existsSync70(join72(stateDir, ISSUES_FILE)))
|
|
86912
87071
|
return 0;
|
|
86913
87072
|
return withLock(stateDir, () => {
|
|
86914
87073
|
const all = readAll(stateDir);
|
|
@@ -86941,7 +87100,7 @@ function ensureDir(stateDir) {
|
|
|
86941
87100
|
mkdirSync36(stateDir, { recursive: true });
|
|
86942
87101
|
}
|
|
86943
87102
|
function writeAll(stateDir, events) {
|
|
86944
|
-
const path5 =
|
|
87103
|
+
const path5 = join72(stateDir, ISSUES_FILE);
|
|
86945
87104
|
sweepOrphanTmpFiles(stateDir);
|
|
86946
87105
|
const tmp = `${path5}.tmp-${process.pid}-${randomBytes12(4).toString("hex")}`;
|
|
86947
87106
|
const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
|
|
@@ -86963,7 +87122,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
86963
87122
|
for (const entry of entries) {
|
|
86964
87123
|
if (!entry.startsWith(TMP_PREFIX))
|
|
86965
87124
|
continue;
|
|
86966
|
-
const tmpPath =
|
|
87125
|
+
const tmpPath = join72(stateDir, entry);
|
|
86967
87126
|
try {
|
|
86968
87127
|
const stat = statSync43(tmpPath);
|
|
86969
87128
|
if (stat.mtimeMs < cutoff) {
|
|
@@ -86975,7 +87134,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
86975
87134
|
var LOCK_RETRY_MS = 25;
|
|
86976
87135
|
var LOCK_TIMEOUT_MS = 1e4;
|
|
86977
87136
|
function withLock(stateDir, fn) {
|
|
86978
|
-
const lockPath =
|
|
87137
|
+
const lockPath = join72(stateDir, ISSUES_LOCK);
|
|
86979
87138
|
const startedAt = Date.now();
|
|
86980
87139
|
let fd = null;
|
|
86981
87140
|
while (fd === null) {
|
|
@@ -87260,7 +87419,7 @@ function relTime(deltaMs) {
|
|
|
87260
87419
|
init_source();
|
|
87261
87420
|
import { existsSync as existsSync73 } from "node:fs";
|
|
87262
87421
|
import { homedir as homedir44 } from "node:os";
|
|
87263
|
-
import { join as
|
|
87422
|
+
import { join as join75, resolve as resolve44 } from "node:path";
|
|
87264
87423
|
|
|
87265
87424
|
// src/deps/python.ts
|
|
87266
87425
|
import { createHash as createHash13 } from "node:crypto";
|
|
@@ -87271,7 +87430,7 @@ import {
|
|
|
87271
87430
|
rmSync as rmSync13,
|
|
87272
87431
|
writeFileSync as writeFileSync20
|
|
87273
87432
|
} from "node:fs";
|
|
87274
|
-
import { dirname as dirname23, join as
|
|
87433
|
+
import { dirname as dirname23, join as join73 } from "node:path";
|
|
87275
87434
|
import { homedir as homedir42 } from "node:os";
|
|
87276
87435
|
import { execFileSync as execFileSync21 } from "node:child_process";
|
|
87277
87436
|
|
|
@@ -87284,7 +87443,7 @@ class PythonEnvError extends Error {
|
|
|
87284
87443
|
}
|
|
87285
87444
|
}
|
|
87286
87445
|
function defaultPythonCacheRoot() {
|
|
87287
|
-
return
|
|
87446
|
+
return join73(homedir42(), ".switchroom", "deps", "python");
|
|
87288
87447
|
}
|
|
87289
87448
|
function hashFile(path5) {
|
|
87290
87449
|
return createHash13("sha256").update(readFileSync64(path5)).digest("hex");
|
|
@@ -87296,11 +87455,11 @@ function ensurePythonEnv(opts) {
|
|
|
87296
87455
|
if (!existsSync71(requirementsPath)) {
|
|
87297
87456
|
throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
|
|
87298
87457
|
}
|
|
87299
|
-
const venvDir =
|
|
87300
|
-
const stampPath =
|
|
87301
|
-
const binDir =
|
|
87302
|
-
const pythonBin =
|
|
87303
|
-
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");
|
|
87304
87463
|
const targetHash = hashFile(requirementsPath);
|
|
87305
87464
|
if (!force && existsSync71(stampPath) && existsSync71(pythonBin)) {
|
|
87306
87465
|
const existingHash = readFileSync64(stampPath, "utf8").trim();
|
|
@@ -87359,7 +87518,7 @@ import {
|
|
|
87359
87518
|
rmSync as rmSync14,
|
|
87360
87519
|
writeFileSync as writeFileSync21
|
|
87361
87520
|
} from "node:fs";
|
|
87362
|
-
import { dirname as dirname24, join as
|
|
87521
|
+
import { dirname as dirname24, join as join74 } from "node:path";
|
|
87363
87522
|
import { homedir as homedir43 } from "node:os";
|
|
87364
87523
|
import { execFileSync as execFileSync22 } from "node:child_process";
|
|
87365
87524
|
|
|
@@ -87383,7 +87542,7 @@ var LOCKFILES_FOR = {
|
|
|
87383
87542
|
npm: ["package-lock.json"]
|
|
87384
87543
|
};
|
|
87385
87544
|
function defaultNodeCacheRoot() {
|
|
87386
|
-
return
|
|
87545
|
+
return join74(homedir43(), ".switchroom", "deps", "node");
|
|
87387
87546
|
}
|
|
87388
87547
|
function hashDepInputs(packageJsonPath) {
|
|
87389
87548
|
const sourceDir = dirname24(packageJsonPath);
|
|
@@ -87392,7 +87551,7 @@ function hashDepInputs(packageJsonPath) {
|
|
|
87392
87551
|
`);
|
|
87393
87552
|
hasher.update(readFileSync65(packageJsonPath));
|
|
87394
87553
|
for (const lockName of ALL_LOCKFILES) {
|
|
87395
|
-
const lockPath =
|
|
87554
|
+
const lockPath = join74(sourceDir, lockName);
|
|
87396
87555
|
if (existsSync72(lockPath)) {
|
|
87397
87556
|
hasher.update(`
|
|
87398
87557
|
`);
|
|
@@ -87412,10 +87571,10 @@ function ensureNodeEnv(opts) {
|
|
|
87412
87571
|
throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
|
|
87413
87572
|
}
|
|
87414
87573
|
const sourceDir = dirname24(packageJsonPath);
|
|
87415
|
-
const envDir =
|
|
87416
|
-
const stampPath =
|
|
87417
|
-
const nodeModulesDir =
|
|
87418
|
-
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");
|
|
87419
87578
|
const targetHash = hashDepInputs(packageJsonPath);
|
|
87420
87579
|
if (!force && existsSync72(stampPath) && existsSync72(nodeModulesDir)) {
|
|
87421
87580
|
const existingHash = readFileSync65(stampPath, "utf8").trim();
|
|
@@ -87433,12 +87592,12 @@ function ensureNodeEnv(opts) {
|
|
|
87433
87592
|
rmSync14(envDir, { recursive: true, force: true });
|
|
87434
87593
|
}
|
|
87435
87594
|
mkdirSync38(envDir, { recursive: true });
|
|
87436
|
-
copyFileSync10(packageJsonPath,
|
|
87595
|
+
copyFileSync10(packageJsonPath, join74(envDir, "package.json"));
|
|
87437
87596
|
let copiedLockfile = false;
|
|
87438
87597
|
for (const lockName of LOCKFILES_FOR[installer]) {
|
|
87439
|
-
const lockPath =
|
|
87598
|
+
const lockPath = join74(sourceDir, lockName);
|
|
87440
87599
|
if (existsSync72(lockPath)) {
|
|
87441
|
-
copyFileSync10(lockPath,
|
|
87600
|
+
copyFileSync10(lockPath, join74(envDir, lockName));
|
|
87442
87601
|
copiedLockfile = true;
|
|
87443
87602
|
}
|
|
87444
87603
|
}
|
|
@@ -87477,13 +87636,13 @@ function registerDepsCommand(program3) {
|
|
|
87477
87636
|
console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
|
|
87478
87637
|
process.exit(1);
|
|
87479
87638
|
}
|
|
87480
|
-
const skillDir =
|
|
87639
|
+
const skillDir = join75(skillsRoot, skill);
|
|
87481
87640
|
if (!existsSync73(skillDir)) {
|
|
87482
87641
|
console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
|
|
87483
87642
|
process.exit(1);
|
|
87484
87643
|
}
|
|
87485
|
-
const requirementsPath =
|
|
87486
|
-
const packageJsonPath =
|
|
87644
|
+
const requirementsPath = join75(skillDir, "requirements.txt");
|
|
87645
|
+
const packageJsonPath = join75(skillDir, "package.json");
|
|
87487
87646
|
const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync73(requirementsPath));
|
|
87488
87647
|
const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync73(packageJsonPath));
|
|
87489
87648
|
let did = 0;
|
|
@@ -88438,7 +88597,7 @@ init_helpers();
|
|
|
88438
88597
|
init_loader();
|
|
88439
88598
|
init_merge();
|
|
88440
88599
|
import { copyFileSync as copyFileSync11, existsSync as existsSync75, readFileSync as readFileSync66, writeFileSync as writeFileSync22 } from "node:fs";
|
|
88441
|
-
import { join as
|
|
88600
|
+
import { join as join76, resolve as resolve46 } from "node:path";
|
|
88442
88601
|
init_scaffold();
|
|
88443
88602
|
init_profiles();
|
|
88444
88603
|
init_schema();
|
|
@@ -88464,7 +88623,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
88464
88623
|
profileName,
|
|
88465
88624
|
profilePath,
|
|
88466
88625
|
workspaceDir,
|
|
88467
|
-
soulPath:
|
|
88626
|
+
soulPath: join76(workspaceDir, "SOUL.md"),
|
|
88468
88627
|
soul: merged.soul
|
|
88469
88628
|
};
|
|
88470
88629
|
}
|
|
@@ -88531,7 +88690,7 @@ function registerSoulCommand(program3) {
|
|
|
88531
88690
|
init_helpers();
|
|
88532
88691
|
init_loader();
|
|
88533
88692
|
import { existsSync as existsSync76, readFileSync as readFileSync67, readdirSync as readdirSync26, statSync as statSync44 } from "node:fs";
|
|
88534
|
-
import { resolve as resolve47, join as
|
|
88693
|
+
import { resolve as resolve47, join as join77 } from "node:path";
|
|
88535
88694
|
import { createHash as createHash15 } from "node:crypto";
|
|
88536
88695
|
init_merge();
|
|
88537
88696
|
init_hindsight2();
|
|
@@ -88542,7 +88701,7 @@ function estimateTokens(bytes) {
|
|
|
88542
88701
|
return Math.round(bytes / 3.7);
|
|
88543
88702
|
}
|
|
88544
88703
|
function readMcpServerNames(agentDir) {
|
|
88545
|
-
const mcpPath =
|
|
88704
|
+
const mcpPath = join77(agentDir, ".mcp.json");
|
|
88546
88705
|
if (!existsSync76(mcpPath))
|
|
88547
88706
|
return [];
|
|
88548
88707
|
try {
|
|
@@ -88556,7 +88715,7 @@ function sha256(content) {
|
|
|
88556
88715
|
return createHash15("sha256").update(content).digest("hex").slice(0, 16);
|
|
88557
88716
|
}
|
|
88558
88717
|
function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
88559
|
-
const projectsDir =
|
|
88718
|
+
const projectsDir = join77(claudeConfigDir, "projects");
|
|
88560
88719
|
if (!existsSync76(projectsDir))
|
|
88561
88720
|
return;
|
|
88562
88721
|
try {
|
|
@@ -88565,8 +88724,8 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
88565
88724
|
for (const entry of entries) {
|
|
88566
88725
|
if (!entry.isDirectory())
|
|
88567
88726
|
continue;
|
|
88568
|
-
const projectPath =
|
|
88569
|
-
const transcriptPath =
|
|
88727
|
+
const projectPath = join77(projectsDir, entry.name);
|
|
88728
|
+
const transcriptPath = join77(projectPath, "transcript.jsonl");
|
|
88570
88729
|
if (!existsSync76(transcriptPath))
|
|
88571
88730
|
continue;
|
|
88572
88731
|
const stat3 = statSync44(transcriptPath);
|
|
@@ -88635,11 +88794,11 @@ function registerDebugCommand(program3) {
|
|
|
88635
88794
|
process.exit(1);
|
|
88636
88795
|
}
|
|
88637
88796
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
88638
|
-
const claudeConfigDir =
|
|
88639
|
-
const claudeMdPath =
|
|
88640
|
-
const soulMdPath =
|
|
88641
|
-
const workspaceSoulMdPath =
|
|
88642
|
-
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");
|
|
88643
88802
|
const lastN = parseInt(opts.last, 10);
|
|
88644
88803
|
if (isNaN(lastN) || lastN < 1) {
|
|
88645
88804
|
console.error("--last must be a positive integer");
|
|
@@ -88768,9 +88927,9 @@ function registerDebugCommand(program3) {
|
|
|
88768
88927
|
const soulMdBytes = soulMdContent.length;
|
|
88769
88928
|
const perTurnBytes = dynamicResult.concatenated.length;
|
|
88770
88929
|
const userBytes = userMessage?.text.length ?? 0;
|
|
88771
|
-
const fleetDir =
|
|
88772
|
-
const fleetInvPath =
|
|
88773
|
-
const fleetClaudePath =
|
|
88930
|
+
const fleetDir = join77(agentsDir, "..", "fleet");
|
|
88931
|
+
const fleetInvPath = join77(fleetDir, "switchroom-invariants.md");
|
|
88932
|
+
const fleetClaudePath = join77(fleetDir, "CLAUDE.md");
|
|
88774
88933
|
const fleetInvBytes = existsSync76(fleetInvPath) ? readFileSync67(fleetInvPath, "utf-8").length : 0;
|
|
88775
88934
|
const fleetClaudeBytes = existsSync76(fleetClaudePath) ? readFileSync67(fleetClaudePath, "utf-8").length : 0;
|
|
88776
88935
|
const fleetBytes = fleetInvBytes + fleetClaudeBytes;
|
|
@@ -88806,7 +88965,7 @@ init_source();
|
|
|
88806
88965
|
// src/worktree/claim.ts
|
|
88807
88966
|
import { execFileSync as execFileSync23 } from "node:child_process";
|
|
88808
88967
|
import { closeSync as closeSync13, mkdirSync as mkdirSync40, openSync as openSync13, existsSync as existsSync78, unlinkSync as unlinkSync17 } from "node:fs";
|
|
88809
|
-
import { join as
|
|
88968
|
+
import { join as join79, resolve as resolve49 } from "node:path";
|
|
88810
88969
|
import { homedir as homedir46 } from "node:os";
|
|
88811
88970
|
import { randomBytes as randomBytes13 } from "node:crypto";
|
|
88812
88971
|
|
|
@@ -88820,13 +88979,13 @@ import {
|
|
|
88820
88979
|
existsSync as existsSync77,
|
|
88821
88980
|
renameSync as renameSync17
|
|
88822
88981
|
} from "node:fs";
|
|
88823
|
-
import { join as
|
|
88982
|
+
import { join as join78, resolve as resolve48 } from "node:path";
|
|
88824
88983
|
import { homedir as homedir45 } from "node:os";
|
|
88825
88984
|
function registryDir() {
|
|
88826
|
-
return resolve48(process.env.SWITCHROOM_WORKTREE_DIR ??
|
|
88985
|
+
return resolve48(process.env.SWITCHROOM_WORKTREE_DIR ?? join78(homedir45(), ".switchroom", "worktrees"));
|
|
88827
88986
|
}
|
|
88828
88987
|
function recordPath(id) {
|
|
88829
|
-
return
|
|
88988
|
+
return join78(registryDir(), `${id}.json`);
|
|
88830
88989
|
}
|
|
88831
88990
|
function ensureDir2() {
|
|
88832
88991
|
mkdirSync39(registryDir(), { recursive: true });
|
|
@@ -88877,7 +89036,7 @@ function acquireRepoLock(repoPath) {
|
|
|
88877
89036
|
const lockDir = registryDir();
|
|
88878
89037
|
mkdirSync40(lockDir, { recursive: true });
|
|
88879
89038
|
const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
|
|
88880
|
-
const lockPath =
|
|
89039
|
+
const lockPath = join79(lockDir, `.lock-${lockName}`);
|
|
88881
89040
|
const deadline = Date.now() + 5000;
|
|
88882
89041
|
let fd = null;
|
|
88883
89042
|
while (fd === null) {
|
|
@@ -88904,7 +89063,7 @@ function acquireRepoLock(repoPath) {
|
|
|
88904
89063
|
}
|
|
88905
89064
|
var DEFAULT_CONCURRENCY = 5;
|
|
88906
89065
|
function worktreesBaseDir() {
|
|
88907
|
-
return resolve49(process.env.SWITCHROOM_WORKTREE_BASE ??
|
|
89066
|
+
return resolve49(process.env.SWITCHROOM_WORKTREE_BASE ?? join79(homedir46(), ".switchroom", "worktree-checkouts"));
|
|
88908
89067
|
}
|
|
88909
89068
|
function shortId() {
|
|
88910
89069
|
return randomBytes13(4).toString("hex");
|
|
@@ -88926,7 +89085,7 @@ function resolveRepoPath(repo, codeRepos) {
|
|
|
88926
89085
|
}
|
|
88927
89086
|
function expandHome(p) {
|
|
88928
89087
|
if (p.startsWith("~/"))
|
|
88929
|
-
return
|
|
89088
|
+
return join79(homedir46(), p.slice(2));
|
|
88930
89089
|
return p;
|
|
88931
89090
|
}
|
|
88932
89091
|
async function claimWorktree(input, codeRepos) {
|
|
@@ -88954,7 +89113,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
88954
89113
|
branch = `task/${taskSuffix}-${id}`;
|
|
88955
89114
|
const baseDir = worktreesBaseDir();
|
|
88956
89115
|
mkdirSync40(baseDir, { recursive: true });
|
|
88957
|
-
worktreePath =
|
|
89116
|
+
worktreePath = join79(baseDir, `${id}-${taskSuffix}`);
|
|
88958
89117
|
const ambientOwner = process.env.SWITCHROOM_AGENT_NAME;
|
|
88959
89118
|
const ownerAgent = input.ownerAgent ?? (ambientOwner != null && ambientOwner !== "" ? ambientOwner : undefined);
|
|
88960
89119
|
const now = new Date().toISOString();
|
|
@@ -89185,7 +89344,7 @@ import {
|
|
|
89185
89344
|
rmSync as rmSync15
|
|
89186
89345
|
} from "node:fs";
|
|
89187
89346
|
import { homedir as homedir47 } from "node:os";
|
|
89188
|
-
import { join as
|
|
89347
|
+
import { join as join80, resolve as resolve50 } from "node:path";
|
|
89189
89348
|
function parseGitdirPointer(dotGitFileContents) {
|
|
89190
89349
|
const m = /^gitdir:\s*(.+?)\s*$/m.exec(dotGitFileContents);
|
|
89191
89350
|
return m ? m[1] : null;
|
|
@@ -89300,7 +89459,7 @@ function defaultPrSignal(repo, branch, exec) {
|
|
|
89300
89459
|
}
|
|
89301
89460
|
}
|
|
89302
89461
|
function trashRoot() {
|
|
89303
|
-
return resolve50(process.env.SWITCHROOM_WORKTREE_TRASH ??
|
|
89462
|
+
return resolve50(process.env.SWITCHROOM_WORKTREE_TRASH ?? join80(homedir47(), ".switchroom", "worktree-gc-trash"));
|
|
89304
89463
|
}
|
|
89305
89464
|
function planGc(roots, deps = {}) {
|
|
89306
89465
|
const exists = deps.existsSync ?? existsSync81;
|
|
@@ -89310,7 +89469,7 @@ function planGc(roots, deps = {}) {
|
|
|
89310
89469
|
const exec = deps.exec ?? defaultExec;
|
|
89311
89470
|
const prSignal = deps.prSignal ?? ((repo, branch) => defaultPrSignal(repo, branch, exec));
|
|
89312
89471
|
const stamp = deps.dateStamp ?? "undated";
|
|
89313
|
-
const trash =
|
|
89472
|
+
const trash = join80(trashRoot(), stamp);
|
|
89314
89473
|
let claimed;
|
|
89315
89474
|
try {
|
|
89316
89475
|
claimed = new Set(listRecords().map((r) => resolve50(r.path)));
|
|
@@ -89346,10 +89505,10 @@ function planGc(roots, deps = {}) {
|
|
|
89346
89505
|
continue;
|
|
89347
89506
|
}
|
|
89348
89507
|
for (const name of entries) {
|
|
89349
|
-
const dir =
|
|
89508
|
+
const dir = join80(root, name);
|
|
89350
89509
|
if (isEphemeralPath(dir))
|
|
89351
89510
|
continue;
|
|
89352
|
-
const dotGit =
|
|
89511
|
+
const dotGit = join80(dir, ".git");
|
|
89353
89512
|
if (!exists(dotGit))
|
|
89354
89513
|
continue;
|
|
89355
89514
|
let st;
|
|
@@ -89378,7 +89537,7 @@ function planGc(roots, deps = {}) {
|
|
|
89378
89537
|
ownerRepos.add(repoRoot);
|
|
89379
89538
|
if (exists(ptr))
|
|
89380
89539
|
continue;
|
|
89381
|
-
orphans.push({ dir, owner: repoRoot, dest:
|
|
89540
|
+
orphans.push({ dir, owner: repoRoot, dest: join80(trash, name) });
|
|
89382
89541
|
}
|
|
89383
89542
|
}
|
|
89384
89543
|
const registered = [];
|
|
@@ -89489,7 +89648,7 @@ function listTrashEntries(nowMs, deps = {}) {
|
|
|
89489
89648
|
return [];
|
|
89490
89649
|
const out = [];
|
|
89491
89650
|
for (const stamp of readDir(root)) {
|
|
89492
|
-
const stampDir =
|
|
89651
|
+
const stampDir = join80(root, stamp);
|
|
89493
89652
|
let names;
|
|
89494
89653
|
try {
|
|
89495
89654
|
names = readDir(stampDir);
|
|
@@ -89497,7 +89656,7 @@ function listTrashEntries(nowMs, deps = {}) {
|
|
|
89497
89656
|
continue;
|
|
89498
89657
|
}
|
|
89499
89658
|
for (const name of names) {
|
|
89500
|
-
const p =
|
|
89659
|
+
const p = join80(stampDir, name);
|
|
89501
89660
|
let mtimeMs = nowMs;
|
|
89502
89661
|
try {
|
|
89503
89662
|
mtimeMs = statSync45(p).mtimeMs;
|
|
@@ -89521,7 +89680,7 @@ function purgeTrash(paths) {
|
|
|
89521
89680
|
return { deleted, errors: errors2 };
|
|
89522
89681
|
}
|
|
89523
89682
|
function defaultRoots() {
|
|
89524
|
-
return [
|
|
89683
|
+
return [join80(homedir47(), "code")];
|
|
89525
89684
|
}
|
|
89526
89685
|
|
|
89527
89686
|
// src/cli/worktree.ts
|
|
@@ -89749,7 +89908,7 @@ import {
|
|
|
89749
89908
|
rmSync as rmSync16,
|
|
89750
89909
|
writeFileSync as writeFileSync24
|
|
89751
89910
|
} from "node:fs";
|
|
89752
|
-
import { join as
|
|
89911
|
+
import { join as join81 } from "node:path";
|
|
89753
89912
|
function encodeCredentialsFilename(email) {
|
|
89754
89913
|
const SAFE = new Set([
|
|
89755
89914
|
..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
|
@@ -89939,16 +90098,16 @@ function resolveCredentialsDir(env2) {
|
|
|
89939
90098
|
if (explicit && explicit.length > 0)
|
|
89940
90099
|
return explicit;
|
|
89941
90100
|
const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
|
|
89942
|
-
return
|
|
90101
|
+
return join81(stateBase, "google-workspace-mcp", "credentials");
|
|
89943
90102
|
}
|
|
89944
90103
|
function writeSeedFile(dir, email, seed) {
|
|
89945
90104
|
mkdirSync42(dir, { recursive: true, mode: 448 });
|
|
89946
90105
|
chmodSync11(dir, 448);
|
|
89947
90106
|
for (const name of readdirSync29(dir)) {
|
|
89948
|
-
rmSync16(
|
|
90107
|
+
rmSync16(join81(dir, name), { force: true, recursive: true });
|
|
89949
90108
|
}
|
|
89950
90109
|
const filename = encodeCredentialsFilename(email);
|
|
89951
|
-
const filePath =
|
|
90110
|
+
const filePath = join81(dir, filename);
|
|
89952
90111
|
writeFileSync24(filePath, JSON.stringify(seed), { mode: 384 });
|
|
89953
90112
|
chmodSync11(filePath, 384);
|
|
89954
90113
|
return filePath;
|
|
@@ -90108,7 +90267,7 @@ function registerDriveMcpLauncherCommand(program3) {
|
|
|
90108
90267
|
init_scaffold_integration();
|
|
90109
90268
|
import { spawn as spawn5 } from "node:child_process";
|
|
90110
90269
|
import { writeFileSync as writeFileSync25, mkdirSync as mkdirSync43 } from "node:fs";
|
|
90111
|
-
import { dirname as dirname25, join as
|
|
90270
|
+
import { dirname as dirname25, join as join82 } from "node:path";
|
|
90112
90271
|
var SOFTERIA_TOKEN_ENV = "MS365_MCP_OAUTH_TOKEN";
|
|
90113
90272
|
var DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
|
|
90114
90273
|
var MAX_REFRESH_INTERVAL_MS = 60 * 60 * 1000;
|
|
@@ -90153,7 +90312,7 @@ function heartbeatPath(agentName, account) {
|
|
|
90153
90312
|
const override = process.env.SWITCHROOM_M365_HEARTBEAT_DIR;
|
|
90154
90313
|
if (override) {
|
|
90155
90314
|
const base = slug ? `m365-launcher-${agentName}-${slug}` : `m365-launcher-${agentName}`;
|
|
90156
|
-
return
|
|
90315
|
+
return join82(override, `${base}.heartbeat.json`);
|
|
90157
90316
|
}
|
|
90158
90317
|
return slug ? `/state/agent/m365-launcher-${slug}.heartbeat.json` : "/state/agent/m365-launcher.heartbeat.json";
|
|
90159
90318
|
}
|
|
@@ -90511,7 +90670,7 @@ function registerNotionMcpLauncherCommand(program3) {
|
|
|
90511
90670
|
init_hindsight();
|
|
90512
90671
|
import { mkdirSync as mkdirSync45, readFileSync as readFileSync70, renameSync as renameSync19, writeFileSync as writeFileSync27 } from "node:fs";
|
|
90513
90672
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
90514
|
-
import { join as
|
|
90673
|
+
import { join as join83 } from "node:path";
|
|
90515
90674
|
import { createInterface as createInterface6 } from "node:readline";
|
|
90516
90675
|
var SHIM_SUPPORTED_PROTOCOL_VERSIONS = [
|
|
90517
90676
|
"2025-06-18",
|
|
@@ -90734,12 +90893,12 @@ class HindsightShim {
|
|
|
90734
90893
|
`));
|
|
90735
90894
|
}
|
|
90736
90895
|
get cachePath() {
|
|
90737
|
-
return
|
|
90896
|
+
return join83(this.opts.cacheDir, TOOLS_CACHE_FILENAME);
|
|
90738
90897
|
}
|
|
90739
90898
|
writeCache(result) {
|
|
90740
90899
|
try {
|
|
90741
90900
|
mkdirSync45(this.opts.cacheDir, { recursive: true });
|
|
90742
|
-
const tmp =
|
|
90901
|
+
const tmp = join83(this.opts.cacheDir, `.${TOOLS_CACHE_FILENAME}.${process.pid}.tmp`);
|
|
90743
90902
|
writeFileSync27(tmp, JSON.stringify(result, null, 2) + `
|
|
90744
90903
|
`);
|
|
90745
90904
|
renameSync19(tmp, this.cachePath);
|
|
@@ -90912,7 +91071,7 @@ function resolveShimOptionsFromEnv(env2) {
|
|
|
90912
91071
|
return {
|
|
90913
91072
|
url: env2.HINDSIGHT_MCP_URL || HINDSIGHT_DEFAULT_MCP_URL,
|
|
90914
91073
|
bankId: env2.HINDSIGHT_BANK_ID || "",
|
|
90915
|
-
cacheDir: env2.HINDSIGHT_SHIM_CACHE_DIR ||
|
|
91074
|
+
cacheDir: env2.HINDSIGHT_SHIM_CACHE_DIR || join83(home2, ".hindsight-shim")
|
|
90916
91075
|
};
|
|
90917
91076
|
}
|
|
90918
91077
|
function registerHindsightMcpShimCommand(program3) {
|
|
@@ -91587,7 +91746,7 @@ function runRedactStdin() {
|
|
|
91587
91746
|
|
|
91588
91747
|
// src/cli/status-ask.ts
|
|
91589
91748
|
import { readFileSync as readFileSync75, existsSync as existsSync87, readdirSync as readdirSync31 } from "node:fs";
|
|
91590
|
-
import { join as
|
|
91749
|
+
import { join as join88 } from "node:path";
|
|
91591
91750
|
import { homedir as homedir50 } from "node:os";
|
|
91592
91751
|
|
|
91593
91752
|
// src/status-ask/report.ts
|
|
@@ -91923,7 +92082,7 @@ function resolveSources(explicitPath) {
|
|
|
91923
92082
|
const config = loadConfig();
|
|
91924
92083
|
agentsDir = resolveAgentsDir(config);
|
|
91925
92084
|
} catch {
|
|
91926
|
-
agentsDir =
|
|
92085
|
+
agentsDir = join88(homedir50(), ".switchroom", "agents");
|
|
91927
92086
|
}
|
|
91928
92087
|
if (!existsSync87(agentsDir))
|
|
91929
92088
|
return [];
|
|
@@ -91935,7 +92094,7 @@ function resolveSources(explicitPath) {
|
|
|
91935
92094
|
return [];
|
|
91936
92095
|
}
|
|
91937
92096
|
for (const name of entries) {
|
|
91938
|
-
const path9 =
|
|
92097
|
+
const path9 = join88(agentsDir, name, "runtime-metrics.jsonl");
|
|
91939
92098
|
if (existsSync87(path9)) {
|
|
91940
92099
|
sources.push({ path: path9, agent: name });
|
|
91941
92100
|
}
|
|
@@ -91977,21 +92136,21 @@ import {
|
|
|
91977
92136
|
unlinkSync as unlinkSync18,
|
|
91978
92137
|
writeSync as writeSync10
|
|
91979
92138
|
} from "node:fs";
|
|
91980
|
-
import { join as
|
|
92139
|
+
import { join as join89, resolve as resolve53 } from "node:path";
|
|
91981
92140
|
var STAGING_SUBDIR = ".staging";
|
|
91982
92141
|
function overlayPathsFor(agent, opts = {}) {
|
|
91983
92142
|
const base = opts.root ? resolve53(opts.root, agent) : resolve53(resolveDualPath(`~/.switchroom/agents/${agent}`));
|
|
91984
|
-
const scheduleDir =
|
|
91985
|
-
const scheduleStagingDir =
|
|
91986
|
-
const skillsDir =
|
|
91987
|
-
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);
|
|
91988
92147
|
return {
|
|
91989
92148
|
agentRoot: base,
|
|
91990
92149
|
scheduleDir,
|
|
91991
92150
|
scheduleStagingDir,
|
|
91992
92151
|
skillsDir,
|
|
91993
92152
|
skillsStagingDir,
|
|
91994
|
-
lockPath:
|
|
92153
|
+
lockPath: join89(base, ".lock"),
|
|
91995
92154
|
stagingDir: scheduleStagingDir
|
|
91996
92155
|
};
|
|
91997
92156
|
}
|
|
@@ -92045,8 +92204,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
92045
92204
|
const paths = overlayPathsFor(agent, opts);
|
|
92046
92205
|
return withAgentLock(paths, () => {
|
|
92047
92206
|
ensureDirs(paths);
|
|
92048
|
-
const stagingPath =
|
|
92049
|
-
const finalPath =
|
|
92207
|
+
const stagingPath = join89(paths.scheduleStagingDir, `${slug}.yaml`);
|
|
92208
|
+
const finalPath = join89(paths.scheduleDir, `${slug}.yaml`);
|
|
92050
92209
|
const fd = openSync14(stagingPath, "w", 384);
|
|
92051
92210
|
try {
|
|
92052
92211
|
writeSync10(fd, yamlText);
|
|
@@ -92062,8 +92221,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
92062
92221
|
const paths = overlayPathsFor(agent, opts);
|
|
92063
92222
|
return withAgentLock(paths, () => {
|
|
92064
92223
|
ensureSkillsDirs(paths);
|
|
92065
|
-
const stagingPath =
|
|
92066
|
-
const finalPath =
|
|
92224
|
+
const stagingPath = join89(paths.skillsStagingDir, `${slug}.yaml`);
|
|
92225
|
+
const finalPath = join89(paths.skillsDir, `${slug}.yaml`);
|
|
92067
92226
|
const fd = openSync14(stagingPath, "w", 384);
|
|
92068
92227
|
try {
|
|
92069
92228
|
writeSync10(fd, yamlText);
|
|
@@ -92078,7 +92237,7 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
92078
92237
|
function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
92079
92238
|
const paths = overlayPathsFor(agent, opts);
|
|
92080
92239
|
return withAgentLock(paths, () => {
|
|
92081
|
-
const finalPath =
|
|
92240
|
+
const finalPath = join89(paths.skillsDir, `${slug}.yaml`);
|
|
92082
92241
|
if (!existsSync88(finalPath))
|
|
92083
92242
|
return false;
|
|
92084
92243
|
unlinkSync18(finalPath);
|
|
@@ -92093,7 +92252,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
92093
92252
|
for (const name of readdirSync32(paths.skillsDir)) {
|
|
92094
92253
|
if (!/\.ya?ml$/i.test(name))
|
|
92095
92254
|
continue;
|
|
92096
|
-
const full =
|
|
92255
|
+
const full = join89(paths.skillsDir, name);
|
|
92097
92256
|
try {
|
|
92098
92257
|
const raw = readFileSync76(full, "utf-8");
|
|
92099
92258
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
@@ -92105,7 +92264,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
92105
92264
|
function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
92106
92265
|
const paths = overlayPathsFor(agent, opts);
|
|
92107
92266
|
return withAgentLock(paths, () => {
|
|
92108
|
-
const finalPath =
|
|
92267
|
+
const finalPath = join89(paths.scheduleDir, `${slug}.yaml`);
|
|
92109
92268
|
if (!existsSync88(finalPath))
|
|
92110
92269
|
return false;
|
|
92111
92270
|
unlinkSync18(finalPath);
|
|
@@ -92120,7 +92279,7 @@ function listOverlayEntries(agent, opts = {}) {
|
|
|
92120
92279
|
for (const name of readdirSync32(paths.scheduleDir)) {
|
|
92121
92280
|
if (!/\.ya?ml$/i.test(name))
|
|
92122
92281
|
continue;
|
|
92123
|
-
const full =
|
|
92282
|
+
const full = join89(paths.scheduleDir, name);
|
|
92124
92283
|
try {
|
|
92125
92284
|
const raw = readFileSync76(full, "utf-8");
|
|
92126
92285
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
@@ -92353,12 +92512,12 @@ import {
|
|
|
92353
92512
|
writeFileSync as writeFileSync32,
|
|
92354
92513
|
writeSync as writeSync11
|
|
92355
92514
|
} from "node:fs";
|
|
92356
|
-
import { join as
|
|
92515
|
+
import { join as join90 } from "node:path";
|
|
92357
92516
|
import { randomBytes as randomBytes15 } from "node:crypto";
|
|
92358
92517
|
var STAGE_ID_PREFIX = "cap_";
|
|
92359
92518
|
function pendingDir(agent, opts = {}) {
|
|
92360
92519
|
const paths = overlayPathsFor(agent, opts);
|
|
92361
|
-
return
|
|
92520
|
+
return join90(paths.scheduleDir, ".pending");
|
|
92362
92521
|
}
|
|
92363
92522
|
function ensurePendingDir(agent, opts = {}) {
|
|
92364
92523
|
const dir = pendingDir(agent, opts);
|
|
@@ -92371,8 +92530,8 @@ function newStageId() {
|
|
|
92371
92530
|
function stagePendingScheduleEntry(opts) {
|
|
92372
92531
|
const dir = ensurePendingDir(opts.agent, { root: opts.root });
|
|
92373
92532
|
const stageId = opts.stageId ?? newStageId();
|
|
92374
|
-
const yamlPath =
|
|
92375
|
-
const metaPath =
|
|
92533
|
+
const yamlPath = join90(dir, `${stageId}.yaml`);
|
|
92534
|
+
const metaPath = join90(dir, `${stageId}.meta.json`);
|
|
92376
92535
|
const meta = {
|
|
92377
92536
|
v: 1,
|
|
92378
92537
|
stage_id: stageId,
|
|
@@ -92406,8 +92565,8 @@ function listPendingScheduleEntries(agent, opts = {}) {
|
|
|
92406
92565
|
if (!name.endsWith(".meta.json"))
|
|
92407
92566
|
continue;
|
|
92408
92567
|
const stageId = name.slice(0, -".meta.json".length);
|
|
92409
|
-
const metaPath =
|
|
92410
|
-
const yamlPath =
|
|
92568
|
+
const metaPath = join90(dir, name);
|
|
92569
|
+
const yamlPath = join90(dir, `${stageId}.yaml`);
|
|
92411
92570
|
if (!existsSync89(yamlPath))
|
|
92412
92571
|
continue;
|
|
92413
92572
|
try {
|
|
@@ -92426,7 +92585,7 @@ function commitPendingScheduleEntry(opts) {
|
|
|
92426
92585
|
return { committed: false, reason: "not_found" };
|
|
92427
92586
|
const slug = match.meta.entry.name ?? match.stageId;
|
|
92428
92587
|
const paths = overlayPathsFor(opts.agent, { root: opts.root });
|
|
92429
|
-
const finalPath =
|
|
92588
|
+
const finalPath = join90(paths.scheduleDir, `${slug}.yaml`);
|
|
92430
92589
|
if (existsSync89(finalPath)) {
|
|
92431
92590
|
return { committed: false, reason: "slug_collision" };
|
|
92432
92591
|
}
|
|
@@ -93079,7 +93238,7 @@ import { existsSync as existsSync91 } from "node:fs";
|
|
|
93079
93238
|
init_reconcile_default_skills();
|
|
93080
93239
|
init_agent_config();
|
|
93081
93240
|
var import_yaml23 = __toESM(require_dist(), 1);
|
|
93082
|
-
import { join as
|
|
93241
|
+
import { join as join91 } from "node:path";
|
|
93083
93242
|
var MAX_SKILLS_PER_AGENT = 20;
|
|
93084
93243
|
var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
|
|
93085
93244
|
function exitCodeFor2(code) {
|
|
@@ -93154,7 +93313,7 @@ function skillInstall(opts) {
|
|
|
93154
93313
|
return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
|
|
93155
93314
|
}
|
|
93156
93315
|
const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
|
|
93157
|
-
const skillPath =
|
|
93316
|
+
const skillPath = join91(poolDir, skillName);
|
|
93158
93317
|
if (!existsSync91(skillPath)) {
|
|
93159
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.`);
|
|
93160
93319
|
}
|
|
@@ -93333,7 +93492,7 @@ import {
|
|
|
93333
93492
|
writeFileSync as writeFileSync33
|
|
93334
93493
|
} from "node:fs";
|
|
93335
93494
|
import { tmpdir as tmpdir6, homedir as homedir51 } from "node:os";
|
|
93336
|
-
import { dirname as dirname31, join as
|
|
93495
|
+
import { dirname as dirname31, join as join92, relative as relative2, resolve as resolve54 } from "node:path";
|
|
93337
93496
|
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
93338
93497
|
|
|
93339
93498
|
// src/cli/skill-common.ts
|
|
@@ -93567,7 +93726,7 @@ function scanForClaudeP2(content) {
|
|
|
93567
93726
|
function resolveSkillsPoolDir2(override) {
|
|
93568
93727
|
const raw = override ?? "~/.switchroom/skills";
|
|
93569
93728
|
if (raw.startsWith("~/")) {
|
|
93570
|
-
return
|
|
93729
|
+
return join92(homedir51(), raw.slice(2));
|
|
93571
93730
|
}
|
|
93572
93731
|
if (raw === "~")
|
|
93573
93732
|
return homedir51();
|
|
@@ -93606,7 +93765,7 @@ function loadFromDir(dir) {
|
|
|
93606
93765
|
const walk2 = (sub) => {
|
|
93607
93766
|
const entries = readdirSync34(sub, { withFileTypes: true });
|
|
93608
93767
|
for (const ent of entries) {
|
|
93609
|
-
const full =
|
|
93768
|
+
const full = join92(sub, ent.name);
|
|
93610
93769
|
const rel = relative2(abs, full);
|
|
93611
93770
|
if (ent.isSymbolicLink()) {
|
|
93612
93771
|
fail3(`refusing to read symlink inside --from dir: ${rel}`);
|
|
@@ -93641,7 +93800,7 @@ function loadFromTarball(tarPath) {
|
|
|
93641
93800
|
fail3(`tarball contains disallowed path: ${JSON.stringify(entry)} \u2014 ` + `refusing to extract before any file is written`);
|
|
93642
93801
|
}
|
|
93643
93802
|
}
|
|
93644
|
-
const staging = mkdtempSync5(
|
|
93803
|
+
const staging = mkdtempSync5(join92(tmpdir6(), "skill-apply-extract-"));
|
|
93645
93804
|
try {
|
|
93646
93805
|
const flags = isGz ? ["-xzf"] : ["-xf"];
|
|
93647
93806
|
const r = spawnSync16("tar", [
|
|
@@ -93727,8 +93886,8 @@ function validatePayload(name, files) {
|
|
|
93727
93886
|
errors2.push(`${path9} fails \`bash -n\` syntax check: ${(r.stderr ?? "").trim()}`);
|
|
93728
93887
|
}
|
|
93729
93888
|
} else if (PY_SCRIPT_RE2.test(path9)) {
|
|
93730
|
-
const tmp = mkdtempSync5(
|
|
93731
|
-
const tmpPy =
|
|
93889
|
+
const tmp = mkdtempSync5(join92(tmpdir6(), "skill-apply-py-"));
|
|
93890
|
+
const tmpPy = join92(tmp, "check.py");
|
|
93732
93891
|
try {
|
|
93733
93892
|
writeFileSync33(tmpPy, content);
|
|
93734
93893
|
const r = spawnSync16("python3", ["-m", "py_compile", tmpPy], {
|
|
@@ -93751,7 +93910,7 @@ function diffSummary(currentDir, files) {
|
|
|
93751
93910
|
if (existsSync92(currentDir)) {
|
|
93752
93911
|
const walk2 = (sub) => {
|
|
93753
93912
|
for (const ent of readdirSync34(sub, { withFileTypes: true })) {
|
|
93754
|
-
const full =
|
|
93913
|
+
const full = join92(sub, ent.name);
|
|
93755
93914
|
const rel = relative2(currentDir, full);
|
|
93756
93915
|
if (ent.isDirectory()) {
|
|
93757
93916
|
walk2(full);
|
|
@@ -93787,7 +93946,7 @@ function writePayload(poolDir, name, files) {
|
|
|
93787
93946
|
if (!existsSync92(poolDir)) {
|
|
93788
93947
|
mkdirSync52(poolDir, { recursive: true, mode: 493 });
|
|
93789
93948
|
}
|
|
93790
|
-
const target =
|
|
93949
|
+
const target = join92(poolDir, name);
|
|
93791
93950
|
let targetIsSymlink = false;
|
|
93792
93951
|
try {
|
|
93793
93952
|
const st = lstatSync11(target);
|
|
@@ -93798,11 +93957,11 @@ function writePayload(poolDir, name, files) {
|
|
|
93798
93957
|
if (targetIsSymlink) {
|
|
93799
93958
|
fail3(`refusing to overwrite symlink at ${target}; investigate manually`);
|
|
93800
93959
|
}
|
|
93801
|
-
const staging = mkdtempSync5(
|
|
93960
|
+
const staging = mkdtempSync5(join92(poolDir, `.skill-apply-stage-${name}-`));
|
|
93802
93961
|
let oldRename = null;
|
|
93803
93962
|
try {
|
|
93804
93963
|
for (const [path9, content] of Object.entries(files)) {
|
|
93805
|
-
const full =
|
|
93964
|
+
const full = join92(staging, path9);
|
|
93806
93965
|
mkdirSync52(dirname31(full), { recursive: true, mode: 493 });
|
|
93807
93966
|
const fd = openSync16(full, "wx");
|
|
93808
93967
|
try {
|
|
@@ -93880,7 +94039,7 @@ function registerSkillCommand(program3) {
|
|
|
93880
94039
|
}
|
|
93881
94040
|
const config = loadConfig();
|
|
93882
94041
|
const poolDir = resolveSkillsPoolDir2(config.switchroom?.skills_dir);
|
|
93883
|
-
const currentDir =
|
|
94042
|
+
const currentDir = join92(poolDir, name);
|
|
93884
94043
|
console.log(source_default.bold(`Skill: ${name}`) + source_default.gray(` (${Object.keys(files).length} files, ${sumBytes(files)} bytes)`));
|
|
93885
94044
|
console.log(source_default.bold("Diff vs current pool content:"));
|
|
93886
94045
|
console.log(diffSummary(currentDir, files));
|
|
@@ -93925,7 +94084,7 @@ import {
|
|
|
93925
94084
|
utimesSync,
|
|
93926
94085
|
writeFileSync as writeFileSync34
|
|
93927
94086
|
} from "node:fs";
|
|
93928
|
-
import { dirname as dirname32, join as
|
|
94087
|
+
import { dirname as dirname32, join as join93, relative as relative3, resolve as resolve55 } from "node:path";
|
|
93929
94088
|
import { homedir as homedir52, tmpdir as tmpdir7 } from "node:os";
|
|
93930
94089
|
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
93931
94090
|
init_helpers();
|
|
@@ -93937,10 +94096,10 @@ var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
|
|
|
93937
94096
|
var PERSONAL_SKILLS_SUBPATH = "personal-skills";
|
|
93938
94097
|
function resolveConfigSkillsDir(agent) {
|
|
93939
94098
|
const override = process.env.SWITCHROOM_CONFIG_DIR;
|
|
93940
|
-
const candidate = override ? resolve55(override) :
|
|
94099
|
+
const candidate = override ? resolve55(override) : join93(homedir52(), ".switchroom-config");
|
|
93941
94100
|
if (!existsSync93(candidate))
|
|
93942
94101
|
return null;
|
|
93943
|
-
return
|
|
94102
|
+
return join93(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
|
|
93944
94103
|
}
|
|
93945
94104
|
var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
|
|
93946
94105
|
function sweepMirrorPriors(configSkillsRoot) {
|
|
@@ -93958,7 +94117,7 @@ function sweepMirrorPriors(configSkillsRoot) {
|
|
|
93958
94117
|
if (now - ts < MIRROR_PRIOR_TTL_MS)
|
|
93959
94118
|
continue;
|
|
93960
94119
|
try {
|
|
93961
|
-
rmSync19(
|
|
94120
|
+
rmSync19(join93(configSkillsRoot, ent), { recursive: true, force: true });
|
|
93962
94121
|
} catch {}
|
|
93963
94122
|
}
|
|
93964
94123
|
} catch {}
|
|
@@ -93967,7 +94126,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
93967
94126
|
const configSkillsRoot = resolveConfigSkillsDir(agent);
|
|
93968
94127
|
if (!configSkillsRoot)
|
|
93969
94128
|
return;
|
|
93970
|
-
const dest =
|
|
94129
|
+
const dest = join93(configSkillsRoot, name);
|
|
93971
94130
|
try {
|
|
93972
94131
|
if (liveSkillDir !== null) {
|
|
93973
94132
|
try {
|
|
@@ -93982,19 +94141,19 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
93982
94141
|
if (liveSkillDir === null) {
|
|
93983
94142
|
sweepMirrorPriors(configSkillsRoot);
|
|
93984
94143
|
if (existsSync93(dest)) {
|
|
93985
|
-
const trash =
|
|
94144
|
+
const trash = join93(configSkillsRoot, `.${name}-trash-${Date.now()}`);
|
|
93986
94145
|
renameSync24(dest, trash);
|
|
93987
94146
|
}
|
|
93988
94147
|
return;
|
|
93989
94148
|
}
|
|
93990
94149
|
mkdirSync53(configSkillsRoot, { recursive: true, mode: 493 });
|
|
93991
94150
|
sweepMirrorPriors(configSkillsRoot);
|
|
93992
|
-
const staging = mkdtempSync6(
|
|
94151
|
+
const staging = mkdtempSync6(join93(configSkillsRoot, `.${name}-staging-`));
|
|
93993
94152
|
const walk2 = (src, dst) => {
|
|
93994
94153
|
mkdirSync53(dst, { recursive: true, mode: 493 });
|
|
93995
94154
|
for (const ent of readdirSync35(src, { withFileTypes: true })) {
|
|
93996
|
-
const s =
|
|
93997
|
-
const d =
|
|
94155
|
+
const s = join93(src, ent.name);
|
|
94156
|
+
const d = join93(dst, ent.name);
|
|
93998
94157
|
if (ent.isSymbolicLink())
|
|
93999
94158
|
continue;
|
|
94000
94159
|
if (ent.isDirectory())
|
|
@@ -94006,7 +94165,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
94006
94165
|
};
|
|
94007
94166
|
walk2(liveSkillDir, staging);
|
|
94008
94167
|
if (existsSync93(dest)) {
|
|
94009
|
-
const prior =
|
|
94168
|
+
const prior = join93(configSkillsRoot, `.${name}-prior-${Date.now()}`);
|
|
94010
94169
|
renameSync24(dest, prior);
|
|
94011
94170
|
}
|
|
94012
94171
|
renameSync24(staging, dest);
|
|
@@ -94036,16 +94195,16 @@ function resolveAgent(opts) {
|
|
|
94036
94195
|
function resolveAgentsRoot(opts) {
|
|
94037
94196
|
if (opts.root)
|
|
94038
94197
|
return resolve55(opts.root);
|
|
94039
|
-
return
|
|
94198
|
+
return join93(homedir52(), ".switchroom", "agents");
|
|
94040
94199
|
}
|
|
94041
94200
|
function personalSkillDir(agentsRoot, agent, name) {
|
|
94042
|
-
return
|
|
94201
|
+
return join93(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
|
|
94043
94202
|
}
|
|
94044
94203
|
function trashDir(agentsRoot, agent) {
|
|
94045
|
-
return
|
|
94204
|
+
return join93(agentsRoot, agent, ".claude", TRASH_DIRNAME);
|
|
94046
94205
|
}
|
|
94047
94206
|
function countPersonalSkills(agentsRoot, agent) {
|
|
94048
|
-
const skillsDir =
|
|
94207
|
+
const skillsDir = join93(agentsRoot, agent, ".claude", "skills");
|
|
94049
94208
|
if (!existsSync93(skillsDir))
|
|
94050
94209
|
return 0;
|
|
94051
94210
|
let n = 0;
|
|
@@ -94083,7 +94242,7 @@ function loadFromDir2(dir) {
|
|
|
94083
94242
|
const files = {};
|
|
94084
94243
|
const walk2 = (sub) => {
|
|
94085
94244
|
for (const ent of readdirSync35(sub, { withFileTypes: true })) {
|
|
94086
|
-
const full =
|
|
94245
|
+
const full = join93(sub, ent.name);
|
|
94087
94246
|
if (ent.isSymbolicLink()) {
|
|
94088
94247
|
fail4(`refusing to read symlink in --from dir: ${relative3(abs, full)}`);
|
|
94089
94248
|
}
|
|
@@ -94136,8 +94295,8 @@ function behavioralValidate(files) {
|
|
|
94136
94295
|
errors2.push(`${path9} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
|
|
94137
94296
|
}
|
|
94138
94297
|
} else if (PY_SCRIPT_RE.test(path9)) {
|
|
94139
|
-
const tmp = mkdtempSync6(
|
|
94140
|
-
const tmpPy =
|
|
94298
|
+
const tmp = mkdtempSync6(join93(tmpdir7(), "skill-personal-py-"));
|
|
94299
|
+
const tmpPy = join93(tmp, "check.py");
|
|
94141
94300
|
try {
|
|
94142
94301
|
writeFileSync34(tmpPy, content);
|
|
94143
94302
|
const r = spawnSync17("python3", ["-m", "py_compile", tmpPy], {
|
|
@@ -94161,7 +94320,7 @@ function sweepTrash(agentsRoot, agent) {
|
|
|
94161
94320
|
for (const ent of readdirSync35(trash, { withFileTypes: true })) {
|
|
94162
94321
|
if (!ent.isDirectory())
|
|
94163
94322
|
continue;
|
|
94164
|
-
const entPath =
|
|
94323
|
+
const entPath = join93(trash, ent.name);
|
|
94165
94324
|
try {
|
|
94166
94325
|
const st = statSync50(entPath);
|
|
94167
94326
|
if (now - st.mtimeMs > TRASH_TTL_MS) {
|
|
@@ -94182,11 +94341,11 @@ function writePersonalSkill(targetDir, files) {
|
|
|
94182
94341
|
fail4(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
|
|
94183
94342
|
}
|
|
94184
94343
|
mkdirSync53(dirname32(targetDir), { recursive: true, mode: 493 });
|
|
94185
|
-
const staging = mkdtempSync6(
|
|
94344
|
+
const staging = mkdtempSync6(join93(dirname32(targetDir), `.skill-personal-stage-`));
|
|
94186
94345
|
let oldRename = null;
|
|
94187
94346
|
try {
|
|
94188
94347
|
for (const [path9, content] of Object.entries(files)) {
|
|
94189
|
-
const full =
|
|
94348
|
+
const full = join93(staging, path9);
|
|
94190
94349
|
mkdirSync53(dirname32(full), { recursive: true, mode: 493 });
|
|
94191
94350
|
const fd = openSync17(full, "wx");
|
|
94192
94351
|
try {
|
|
@@ -94333,10 +94492,10 @@ function editPersonalAction(name, opts) {
|
|
|
94333
94492
|
}
|
|
94334
94493
|
var CLONE_SOURCE_RE = /^(shared|bundled):([a-z0-9][a-z0-9_-]{0,62})$/;
|
|
94335
94494
|
function defaultSharedRoot() {
|
|
94336
|
-
return
|
|
94495
|
+
return join93(homedir52(), ".switchroom", "skills");
|
|
94337
94496
|
}
|
|
94338
94497
|
function defaultBundledRoot() {
|
|
94339
|
-
return
|
|
94498
|
+
return join93(homedir52(), ".switchroom", "skills", "_bundled");
|
|
94340
94499
|
}
|
|
94341
94500
|
function resolveCloneSource(source, opts) {
|
|
94342
94501
|
const m = CLONE_SOURCE_RE.exec(source);
|
|
@@ -94346,7 +94505,7 @@ function resolveCloneSource(source, opts) {
|
|
|
94346
94505
|
const tier = m[1];
|
|
94347
94506
|
const slug = m[2];
|
|
94348
94507
|
const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
|
|
94349
|
-
const dir =
|
|
94508
|
+
const dir = join93(root, slug);
|
|
94350
94509
|
if (!existsSync93(dir)) {
|
|
94351
94510
|
fail4(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
|
|
94352
94511
|
}
|
|
@@ -94362,7 +94521,7 @@ function readSourceFiles(dir) {
|
|
|
94362
94521
|
const skipped = [];
|
|
94363
94522
|
const walk2 = (sub) => {
|
|
94364
94523
|
for (const ent of readdirSync35(sub, { withFileTypes: true })) {
|
|
94365
|
-
const full =
|
|
94524
|
+
const full = join93(sub, ent.name);
|
|
94366
94525
|
if (ent.isSymbolicLink()) {
|
|
94367
94526
|
continue;
|
|
94368
94527
|
}
|
|
@@ -94473,7 +94632,7 @@ function removePersonalAction(name, opts) {
|
|
|
94473
94632
|
const trashRoot2 = trashDir(agentsRoot, agent);
|
|
94474
94633
|
mkdirSync53(trashRoot2, { recursive: true, mode: 493 });
|
|
94475
94634
|
const ts = Date.now();
|
|
94476
|
-
const trashTarget =
|
|
94635
|
+
const trashTarget = join93(trashRoot2, `${name}-${ts}`);
|
|
94477
94636
|
renameSync24(target, trashTarget);
|
|
94478
94637
|
const now = new Date(ts);
|
|
94479
94638
|
utimesSync(trashTarget, now, now);
|
|
@@ -94492,7 +94651,7 @@ function listPersonalAction(opts) {
|
|
|
94492
94651
|
const agent = resolveAgent(opts);
|
|
94493
94652
|
const agentsRoot = resolveAgentsRoot(opts);
|
|
94494
94653
|
sweepTrash(agentsRoot, agent);
|
|
94495
|
-
const skillsDir =
|
|
94654
|
+
const skillsDir = join93(agentsRoot, agent, ".claude", "skills");
|
|
94496
94655
|
const personal = [];
|
|
94497
94656
|
if (existsSync93(skillsDir)) {
|
|
94498
94657
|
for (const ent of readdirSync35(skillsDir, { withFileTypes: true })) {
|
|
@@ -94501,7 +94660,7 @@ function listPersonalAction(opts) {
|
|
|
94501
94660
|
if (!ent.name.startsWith(PERSONAL_PREFIX))
|
|
94502
94661
|
continue;
|
|
94503
94662
|
const skillName = ent.name.slice(PERSONAL_PREFIX.length);
|
|
94504
|
-
const skillPath =
|
|
94663
|
+
const skillPath = join93(skillsDir, ent.name);
|
|
94505
94664
|
let fileCount = 0;
|
|
94506
94665
|
let totalBytes = 0;
|
|
94507
94666
|
const walk2 = (sub) => {
|
|
@@ -94509,10 +94668,10 @@ function listPersonalAction(opts) {
|
|
|
94509
94668
|
if (e.isFile()) {
|
|
94510
94669
|
fileCount += 1;
|
|
94511
94670
|
try {
|
|
94512
|
-
totalBytes += statSync50(
|
|
94671
|
+
totalBytes += statSync50(join93(sub, e.name)).size;
|
|
94513
94672
|
} catch {}
|
|
94514
94673
|
} else if (e.isDirectory()) {
|
|
94515
|
-
walk2(
|
|
94674
|
+
walk2(join93(sub, e.name));
|
|
94516
94675
|
}
|
|
94517
94676
|
}
|
|
94518
94677
|
};
|
|
@@ -94551,11 +94710,11 @@ function registerSkillPersonalCommands(program3) {
|
|
|
94551
94710
|
// src/cli/self-improve-propose-skill.ts
|
|
94552
94711
|
import { createConnection as createConnection4 } from "node:net";
|
|
94553
94712
|
import { homedir as homedir53 } from "node:os";
|
|
94554
|
-
import { join as
|
|
94713
|
+
import { join as join94 } from "node:path";
|
|
94555
94714
|
import { readFileSync as readFileSync81 } from "node:fs";
|
|
94556
94715
|
var IPC_CONNECT_TIMEOUT_MS = 5000;
|
|
94557
94716
|
function gatewaySocketPath() {
|
|
94558
|
-
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"));
|
|
94559
94718
|
}
|
|
94560
94719
|
function fail5(msg, code = 1) {
|
|
94561
94720
|
console.error(msg);
|
|
@@ -94629,7 +94788,7 @@ init_helpers();
|
|
|
94629
94788
|
var import_yaml25 = __toESM(require_dist(), 1);
|
|
94630
94789
|
import { existsSync as existsSync94, readdirSync as readdirSync36, readFileSync as readFileSync82, statSync as statSync51 } from "node:fs";
|
|
94631
94790
|
import { homedir as homedir54 } from "node:os";
|
|
94632
|
-
import { join as
|
|
94791
|
+
import { join as join95, resolve as resolve56 } from "node:path";
|
|
94633
94792
|
var PERSONAL_PREFIX2 = "personal-";
|
|
94634
94793
|
var BUNDLED_SUBDIR = "_bundled";
|
|
94635
94794
|
var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
|
|
@@ -94643,7 +94802,7 @@ function defaultBundledRoot2() {
|
|
|
94643
94802
|
return resolve56(homedir54(), ".switchroom/skills/_bundled");
|
|
94644
94803
|
}
|
|
94645
94804
|
function readSkillFrontmatter(skillDir) {
|
|
94646
|
-
const mdPath =
|
|
94805
|
+
const mdPath = join95(skillDir, "SKILL.md");
|
|
94647
94806
|
if (!existsSync94(mdPath))
|
|
94648
94807
|
return null;
|
|
94649
94808
|
let content;
|
|
@@ -94676,7 +94835,7 @@ function readSkillFrontmatter(skillDir) {
|
|
|
94676
94835
|
return { fm: parsed };
|
|
94677
94836
|
}
|
|
94678
94837
|
function statSkillMd(skillDir) {
|
|
94679
|
-
const mdPath =
|
|
94838
|
+
const mdPath = join95(skillDir, "SKILL.md");
|
|
94680
94839
|
try {
|
|
94681
94840
|
const st = statSync51(mdPath);
|
|
94682
94841
|
return { size: st.size, mtime: st.mtime.toISOString() };
|
|
@@ -94687,7 +94846,7 @@ function statSkillMd(skillDir) {
|
|
|
94687
94846
|
function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
94688
94847
|
if (!AGENT_NAME_RE3.test(agent))
|
|
94689
94848
|
return [];
|
|
94690
|
-
const skillsDir =
|
|
94849
|
+
const skillsDir = join95(agentsRoot, agent, ".claude/skills");
|
|
94691
94850
|
if (!existsSync94(skillsDir))
|
|
94692
94851
|
return [];
|
|
94693
94852
|
const out = [];
|
|
@@ -94700,7 +94859,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
94700
94859
|
for (const ent of entries) {
|
|
94701
94860
|
if (!ent.startsWith(PERSONAL_PREFIX2))
|
|
94702
94861
|
continue;
|
|
94703
|
-
const dirPath =
|
|
94862
|
+
const dirPath = join95(skillsDir, ent);
|
|
94704
94863
|
try {
|
|
94705
94864
|
if (!statSync51(dirPath).isDirectory())
|
|
94706
94865
|
continue;
|
|
@@ -94740,7 +94899,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
|
94740
94899
|
continue;
|
|
94741
94900
|
if (ent.startsWith("."))
|
|
94742
94901
|
continue;
|
|
94743
|
-
const dirPath =
|
|
94902
|
+
const dirPath = join95(sharedRoot, ent);
|
|
94744
94903
|
try {
|
|
94745
94904
|
if (!statSync51(dirPath).isDirectory())
|
|
94746
94905
|
continue;
|
|
@@ -94776,7 +94935,7 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
|
94776
94935
|
for (const ent of entries) {
|
|
94777
94936
|
if (ent.startsWith("."))
|
|
94778
94937
|
continue;
|
|
94779
|
-
const dirPath =
|
|
94938
|
+
const dirPath = join95(bundledRoot, ent);
|
|
94780
94939
|
try {
|
|
94781
94940
|
if (!statSync51(dirPath).isDirectory())
|
|
94782
94941
|
continue;
|
|
@@ -94933,7 +95092,7 @@ import {
|
|
|
94933
95092
|
copyFileSync as copyFileSync13
|
|
94934
95093
|
} from "node:fs";
|
|
94935
95094
|
import { homedir as homedir55 } from "node:os";
|
|
94936
|
-
import { join as
|
|
95095
|
+
import { join as join96 } from "node:path";
|
|
94937
95096
|
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
94938
95097
|
|
|
94939
95098
|
// src/cli/singleton-stale-cleanup.ts
|
|
@@ -95282,7 +95441,7 @@ function resolveHostdHostHome(env2 = process.env, home2 = homedir55()) {
|
|
|
95282
95441
|
return resolved;
|
|
95283
95442
|
}
|
|
95284
95443
|
function resolveHostdSkillsTarget(hostHome) {
|
|
95285
|
-
const skillsPath =
|
|
95444
|
+
const skillsPath = join96(hostHome, ".switchroom", "skills");
|
|
95286
95445
|
let st;
|
|
95287
95446
|
try {
|
|
95288
95447
|
st = lstatSync13(skillsPath);
|
|
@@ -95306,10 +95465,10 @@ function resolveHostdSkillsTarget(hostHome) {
|
|
|
95306
95465
|
return target;
|
|
95307
95466
|
}
|
|
95308
95467
|
function hostdDir() {
|
|
95309
|
-
return
|
|
95468
|
+
return join96(homedir55(), ".switchroom", "hostd");
|
|
95310
95469
|
}
|
|
95311
95470
|
function hostdComposePath() {
|
|
95312
|
-
return
|
|
95471
|
+
return join96(hostdDir(), "docker-compose.yml");
|
|
95313
95472
|
}
|
|
95314
95473
|
function backupExistingCompose() {
|
|
95315
95474
|
const p = hostdComposePath();
|
|
@@ -95438,7 +95597,7 @@ function doStatus() {
|
|
|
95438
95597
|
for (const name of readdirSync37(dir)) {
|
|
95439
95598
|
if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
|
|
95440
95599
|
continue;
|
|
95441
|
-
const sockPath =
|
|
95600
|
+
const sockPath = join96(dir, name, "sock");
|
|
95442
95601
|
if (existsSync96(sockPath)) {
|
|
95443
95602
|
const st = statSync52(sockPath);
|
|
95444
95603
|
if ((st.mode & 61440) === 49152) {
|
|
@@ -95532,7 +95691,7 @@ init_helpers();
|
|
|
95532
95691
|
init_operator_uid();
|
|
95533
95692
|
import { chownSync as chownSync9, existsSync as existsSync97, mkdirSync as mkdirSync55, writeFileSync as writeFileSync36, copyFileSync as copyFileSync14 } from "node:fs";
|
|
95534
95693
|
import { homedir as homedir56 } from "node:os";
|
|
95535
|
-
import { join as
|
|
95694
|
+
import { join as join97 } from "node:path";
|
|
95536
95695
|
import { spawnSync as spawnSync21 } from "node:child_process";
|
|
95537
95696
|
function resolveWebImageTag(explicitTag, release) {
|
|
95538
95697
|
if (explicitTag)
|
|
@@ -95632,10 +95791,10 @@ services:
|
|
|
95632
95791
|
`;
|
|
95633
95792
|
}
|
|
95634
95793
|
function webdDir() {
|
|
95635
|
-
return
|
|
95794
|
+
return join97(homedir56(), ".switchroom", "web");
|
|
95636
95795
|
}
|
|
95637
95796
|
function webdComposePath() {
|
|
95638
|
-
return
|
|
95797
|
+
return join97(webdDir(), "docker-compose.yml");
|
|
95639
95798
|
}
|
|
95640
95799
|
function backupExistingCompose2() {
|
|
95641
95800
|
const p = webdComposePath();
|
|
@@ -95795,9 +95954,9 @@ function registerWebdCommand(program3) {
|
|
|
95795
95954
|
// src/cli/host-repair.ts
|
|
95796
95955
|
init_source();
|
|
95797
95956
|
import { homedir as homedir57 } from "node:os";
|
|
95798
|
-
import { join as
|
|
95957
|
+
import { join as join98 } from "node:path";
|
|
95799
95958
|
var ARTIFACT_ALLOWLIST = {
|
|
95800
|
-
dockerComposePluginDir: (home2) =>
|
|
95959
|
+
dockerComposePluginDir: (home2) => join98(home2, ".docker", "cli-plugins", "docker-compose"),
|
|
95801
95960
|
stateSentinel: "/state"
|
|
95802
95961
|
};
|
|
95803
95962
|
function isStateBogusAutoDir(probe2) {
|