switchroom 0.18.6 → 0.18.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-scheduler/index.js +1 -0
- package/dist/auth-broker/index.js +1 -0
- package/dist/cli/autoaccept-poll.js +140 -33
- package/dist/cli/notion-write-pretool.mjs +1 -0
- package/dist/cli/switchroom.js +1172 -812
- package/dist/host-control/main.js +2 -1
- package/dist/vault/approvals/kernel-server.js +1 -0
- package/dist/vault/broker/server.js +1 -0
- package/package.json +3 -3
- package/profiles/_base/cron-session.sh.hbs +55 -16
- package/profiles/_base/start.sh.hbs +146 -50
- package/profiles/default/CLAUDE.md.hbs +1 -1
- package/skills/switchroom-runtime/SKILL.md +2 -0
- package/telegram-plugin/dist/bridge/bridge.js +22 -0
- package/telegram-plugin/dist/gateway/gateway.js +2965 -862
- package/telegram-plugin/dist/server.js +24 -0
- package/telegram-plugin/flood-circuit-breaker.ts +123 -0
- package/telegram-plugin/gateway/activity-card-store.ts +63 -18
- package/telegram-plugin/gateway/always-allow-persist-queue.ts +438 -0
- package/telegram-plugin/gateway/approval-timeout-inbound-builders.ts +150 -0
- package/telegram-plugin/gateway/boot-card.ts +27 -0
- package/telegram-plugin/gateway/busy-ack.ts +106 -0
- package/telegram-plugin/gateway/clean-shutdown-marker.ts +68 -20
- package/telegram-plugin/gateway/gateway.ts +1618 -198
- package/telegram-plugin/gateway/inbound-spool.ts +2 -1
- package/telegram-plugin/gateway/inject-handler.test.ts +19 -0
- package/telegram-plugin/gateway/inject-handler.ts +17 -0
- package/telegram-plugin/gateway/ipc-protocol.ts +44 -2
- package/telegram-plugin/gateway/ipc-server.ts +40 -0
- package/telegram-plugin/gateway/mental-model-propose-diff.ts +61 -5
- package/telegram-plugin/gateway/model-command.ts +227 -54
- package/telegram-plugin/gateway/pending-card-expiry.ts +98 -0
- package/telegram-plugin/gateway/pending-card-store.ts +173 -0
- package/telegram-plugin/gateway/pending-inbound-buffer.ts +12 -2
- package/telegram-plugin/gateway/resume-inbound-builder.ts +240 -2
- package/telegram-plugin/gateway/session-model-file.ts +198 -0
- package/telegram-plugin/gateway/session-model-source.ts +73 -0
- package/telegram-plugin/gateway/status-pin-store.ts +82 -22
- package/telegram-plugin/gateway/worker-feed-dispatch.ts +24 -1
- package/telegram-plugin/gateway/worker-pin-reaper.ts +114 -0
- package/telegram-plugin/hooks/hooks.json +10 -10
- package/telegram-plugin/hooks/run-hook.sh +84 -0
- package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +30 -7
- package/telegram-plugin/model-label.ts +69 -0
- package/telegram-plugin/model-unavailable.ts +26 -0
- package/telegram-plugin/operator-events.ts +24 -0
- package/telegram-plugin/permission-diff.ts +128 -0
- package/telegram-plugin/pty-partial-handler.ts +39 -0
- package/telegram-plugin/registry/subagents-schema.ts +80 -1
- package/telegram-plugin/registry/subagents.test.ts +90 -0
- package/telegram-plugin/render/rich-render.ts +79 -1
- package/telegram-plugin/retry-api-call.ts +62 -0
- package/telegram-plugin/session-tail.ts +28 -0
- package/telegram-plugin/shared/bot-runtime.ts +8 -1
- package/telegram-plugin/silence-poke.ts +14 -0
- package/telegram-plugin/silent-end.ts +49 -4
- package/telegram-plugin/stream-controller.ts +156 -38
- package/telegram-plugin/subagent-watcher.ts +222 -37
- package/telegram-plugin/tests/activity-card-store.test.ts +47 -2
- package/telegram-plugin/tests/always-allow-persist-queue.test.ts +529 -0
- package/telegram-plugin/tests/approval-card-restart-outcome.test.ts +218 -0
- package/telegram-plugin/tests/approval-timeout-inbound-builders.test.ts +94 -0
- package/telegram-plugin/tests/boot-card-flood-suppress.test.ts +111 -0
- package/telegram-plugin/tests/busy-ack-wiring.test.ts +118 -0
- package/telegram-plugin/tests/busy-ack.test.ts +121 -0
- package/telegram-plugin/tests/button-tap-turn-gated.test.ts +263 -0
- package/telegram-plugin/tests/flood-circuit-breaker.test.ts +74 -0
- package/telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts +85 -27
- package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +179 -25
- package/telegram-plugin/tests/ipc-server-query-pending-permission.test.ts +157 -0
- package/telegram-plugin/tests/mental-model-name-entity-corruption.test.ts +119 -0
- package/telegram-plugin/tests/mental-model-propose-callback-gate.test.ts +8 -5
- package/telegram-plugin/tests/model-command.test.ts +203 -43
- package/telegram-plugin/tests/model-label.test.ts +64 -0
- package/telegram-plugin/tests/model-unavailable.test.ts +41 -0
- package/telegram-plugin/tests/operator-events.test.ts +1 -0
- package/telegram-plugin/tests/pending-card-durability-wiring.test.ts +202 -0
- package/telegram-plugin/tests/pending-card-expiry.test.ts +190 -0
- package/telegram-plugin/tests/pending-card-store.test.ts +173 -0
- package/telegram-plugin/tests/permission-diff.test.ts +111 -0
- package/telegram-plugin/tests/pty-partial-handler.test.ts +56 -0
- package/telegram-plugin/tests/render/render-outbound-chunks.test.ts +98 -0
- package/telegram-plugin/tests/resume-inbound-builder.test.ts +286 -0
- package/telegram-plugin/tests/retry-api-call.test.ts +59 -0
- package/telegram-plugin/tests/run-hook-wrapper.test.ts +132 -0
- package/telegram-plugin/tests/session-model-file.test.ts +132 -0
- package/telegram-plugin/tests/session-model-source.test.ts +67 -0
- package/telegram-plugin/tests/session-tail.test.ts +64 -0
- package/telegram-plugin/tests/silent-end.test.ts +46 -1
- package/telegram-plugin/tests/slot-banner-boot-recovery.test.ts +3 -3
- package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +3 -3
- package/telegram-plugin/tests/status-pin-store.test.ts +62 -6
- package/telegram-plugin/tests/stream-controller-chunk-cap.test.ts +122 -0
- package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +39 -0
- package/telegram-plugin/tests/subagent-watcher-boot-promotion-replay.test.ts +107 -4
- package/telegram-plugin/tests/subagent-watcher-handback-gaps.test.ts +42 -4
- package/telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts +47 -0
- package/telegram-plugin/tests/subagent-watcher-terminated-ids-cap.test.ts +150 -0
- package/telegram-plugin/tests/subagent-watcher.test.ts +54 -0
- package/telegram-plugin/tests/tool-activity-summary.test.ts +37 -0
- package/telegram-plugin/tests/typing-wrap.test.ts +23 -0
- package/telegram-plugin/tests/voice-send.test.ts +308 -0
- package/telegram-plugin/tests/worker-activity-feed.test.ts +11 -0
- package/telegram-plugin/tests/worker-feed-dispatch.test.ts +126 -0
- package/telegram-plugin/tests/worker-pin-reaper.test.ts +132 -0
- package/telegram-plugin/tool-activity-summary.ts +22 -2
- package/telegram-plugin/typing-wrap.ts +72 -25
- package/telegram-plugin/uat/scenarios/jtbd-deliberate-restart-resumes-dm.test.ts +118 -0
- package/telegram-plugin/uat/scenarios/jtbd-midflight-busy-ack-dm.test.ts +201 -0
- package/telegram-plugin/uat/scenarios/jtbd-worker-pin-lifecycle-dm.test.ts +208 -0
- package/telegram-plugin/uat/scenarios/vault-card-survives-gateway-restart-dm.test.ts +140 -0
- package/telegram-plugin/uat/scenarios/vault-deny-resumes-turn-dm.test.ts +84 -0
- package/telegram-plugin/uat/scenarios/vault-timeout-wakes-agent-dm.test.ts +91 -0
- package/telegram-plugin/voice-ondemand.ts +25 -1
- package/telegram-plugin/voice-send.ts +154 -0
- package/telegram-plugin/worker-activity-feed.ts +9 -0
package/dist/cli/switchroom.js
CHANGED
|
@@ -13741,6 +13741,7 @@ var init_schema = __esm(() => {
|
|
|
13741
13741
|
max_turns_in_briefing: exports_external.number().int().positive().optional().describe("Cap on recent user/assistant turn pairs fed to the summarizer."),
|
|
13742
13742
|
resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
|
|
13743
13743
|
resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale."),
|
|
13744
|
+
boot_resume: exports_external.enum(["always", "in-flight", "never"]).optional().describe("How the gateway auto-resumes a turn that was IN FLIGHT when the " + "agent restarted. 'in-flight' (default) resumes genuinely " + "interrupted work even after a deliberate/operator restart \u2014 a " + "sanctioned restart landing mid-turn no longer silently drops the " + "work. 'always' forces resume unconditionally (same as the " + "SWITCHROOM_BOOT_RESUME_ALWAYS=1 escape hatch). 'never' is the " + "quota-saving posture: don't auto-replay work across a clean " + "restart \u2014 but the user is STILL sent a passive notice of what was " + "in flight (silence is never used). Independent of the at-most-once " + "resume ledger and the bounded resume-chain loop-guard, which always " + "apply. Threaded to the gateway as SWITCHROOM_BOOT_RESUME."),
|
|
13744
13745
|
session_retention_max_count: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): keep at most this many " + "newest session transcripts under .claude/projects; older ones " + "past both this count and the age bound are pruned by the Stop " + "hook. The newest sessions (and the handoff source) are always " + "kept. Default 20; set 0 to disable the count bound."),
|
|
13745
13746
|
session_retention_max_age_days: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): prune session transcripts " + "older than this many days (a file is deleted only when it is BOTH " + "over the count bound and older than this). Default 30; set 0 to " + "disable the age bound.")
|
|
13746
13747
|
}).optional();
|
|
@@ -26944,6 +26945,7 @@ ${baseAppend}` : TELEGRAM_FORMATTING_FLOOR_CARD;
|
|
|
26944
26945
|
handoffEnabled: agentConfig.session_continuity?.enabled !== false,
|
|
26945
26946
|
resumeMode: agentConfig.session_continuity?.resume_mode ?? "handoff",
|
|
26946
26947
|
resumeMaxBytes: agentConfig.session_continuity?.resume_max_bytes ?? 2000000,
|
|
26948
|
+
bootResumeMode: agentConfig.session_continuity?.boot_resume ?? "in-flight",
|
|
26947
26949
|
resumeModeHasContinuePath: (() => {
|
|
26948
26950
|
const mode = agentConfig.session_continuity?.resume_mode ?? "handoff";
|
|
26949
26951
|
return mode === "auto" || mode === "continue";
|
|
@@ -28046,7 +28048,8 @@ ${baseAppend}` : TELEGRAM_FORMATTING_FLOOR_CARD;
|
|
|
28046
28048
|
sessionMaxTurns: agentConfig.session?.max_turns,
|
|
28047
28049
|
handoffEnabled: agentConfig.session_continuity?.enabled !== false,
|
|
28048
28050
|
resumeMode: agentConfig.session_continuity?.resume_mode ?? "handoff",
|
|
28049
|
-
resumeMaxBytes: agentConfig.session_continuity?.resume_max_bytes ?? 2000000
|
|
28051
|
+
resumeMaxBytes: agentConfig.session_continuity?.resume_max_bytes ?? 2000000,
|
|
28052
|
+
bootResumeMode: agentConfig.session_continuity?.boot_resume ?? "in-flight"
|
|
28050
28053
|
};
|
|
28051
28054
|
const beforeStartSh = existsSync15(startShPath) ? readFileSync13(startShPath, "utf-8") : "";
|
|
28052
28055
|
const afterStartSh = renderTemplate(join10(basePath, "start.sh.hbs"), startShContext);
|
|
@@ -30047,7 +30050,25 @@ import { chownSync as chownSync2 } from "node:fs";
|
|
|
30047
30050
|
import { mkdir, readFile, writeFile, rename, copyFile } from "node:fs/promises";
|
|
30048
30051
|
import { homedir as homedir6 } from "node:os";
|
|
30049
30052
|
import { basename as basename4, dirname as dirname6, join as join13 } from "node:path";
|
|
30050
|
-
|
|
30053
|
+
function agentHadLiteLLMRouting(composeContent, agentName) {
|
|
30054
|
+
const lines = composeContent.split(`
|
|
30055
|
+
`);
|
|
30056
|
+
const header = ` agent-${agentName}:`;
|
|
30057
|
+
let i = lines.indexOf(header);
|
|
30058
|
+
if (i < 0)
|
|
30059
|
+
return false;
|
|
30060
|
+
for (i = i + 1;i < lines.length; i++) {
|
|
30061
|
+
const line = lines[i];
|
|
30062
|
+
if (/^ {2}\S/.test(line))
|
|
30063
|
+
break;
|
|
30064
|
+
if (/^\S/.test(line))
|
|
30065
|
+
break;
|
|
30066
|
+
if (/^\s+SWITCHROOM_LITELLM:\s*"1"\s*$/.test(line))
|
|
30067
|
+
return true;
|
|
30068
|
+
}
|
|
30069
|
+
return false;
|
|
30070
|
+
}
|
|
30071
|
+
async function resolveLiteLLMConfirmedAgents(config, previousCompose, deps) {
|
|
30051
30072
|
const confirmed = new Set;
|
|
30052
30073
|
const topEnabled = config.litellm?.enabled ?? false;
|
|
30053
30074
|
const optedIn = [];
|
|
@@ -30062,16 +30083,52 @@ async function resolveLiteLLMConfirmedAgents(config) {
|
|
|
30062
30083
|
}
|
|
30063
30084
|
if (optedIn.length === 0)
|
|
30064
30085
|
return confirmed;
|
|
30065
|
-
|
|
30066
|
-
|
|
30067
|
-
|
|
30068
|
-
|
|
30069
|
-
|
|
30070
|
-
|
|
30071
|
-
confirmed.add(name);
|
|
30072
|
-
} catch {}
|
|
30086
|
+
let getKey = deps?.getKey ?? null;
|
|
30087
|
+
if (!getKey) {
|
|
30088
|
+
try {
|
|
30089
|
+
({ getViaBrokerStructured: getKey } = await Promise.resolve().then(() => (init_client(), exports_client)));
|
|
30090
|
+
} catch {
|
|
30091
|
+
getKey = null;
|
|
30073
30092
|
}
|
|
30074
|
-
}
|
|
30093
|
+
}
|
|
30094
|
+
const unreachableAgents = [];
|
|
30095
|
+
for (const name of optedIn) {
|
|
30096
|
+
if (!getKey) {
|
|
30097
|
+
unreachableAgents.push(name);
|
|
30098
|
+
continue;
|
|
30099
|
+
}
|
|
30100
|
+
try {
|
|
30101
|
+
const r = await getKey(`litellm/${name}/api-key`);
|
|
30102
|
+
if (r.kind === "ok") {
|
|
30103
|
+
confirmed.add(name);
|
|
30104
|
+
} else if (r.kind === "unreachable") {
|
|
30105
|
+
unreachableAgents.push(name);
|
|
30106
|
+
} else if (r.kind === "denied" && r.code !== "DENIED") {
|
|
30107
|
+
unreachableAgents.push(name);
|
|
30108
|
+
}
|
|
30109
|
+
} catch {
|
|
30110
|
+
unreachableAgents.push(name);
|
|
30111
|
+
}
|
|
30112
|
+
}
|
|
30113
|
+
if (unreachableAgents.length > 0) {
|
|
30114
|
+
const preserved = [];
|
|
30115
|
+
const couldNotPreserve = [];
|
|
30116
|
+
for (const name of unreachableAgents) {
|
|
30117
|
+
if (previousCompose && agentHadLiteLLMRouting(previousCompose, name)) {
|
|
30118
|
+
confirmed.add(name);
|
|
30119
|
+
preserved.push(name);
|
|
30120
|
+
} else {
|
|
30121
|
+
couldNotPreserve.push(name);
|
|
30122
|
+
}
|
|
30123
|
+
}
|
|
30124
|
+
console.warn(`switchroom: vault-broker unreachable or unable to answer (locked/internal) while resolving LiteLLM key status for ${unreachableAgents.length} opted-in agent(s): ${unreachableAgents.join(", ")}. Refusing to silently strip proxy routing (that would boot them on untracked direct OAuth). Re-run \`switchroom apply\` once the broker is back to reconcile.`);
|
|
30125
|
+
if (preserved.length > 0) {
|
|
30126
|
+
console.warn(`switchroom: PRESERVED existing proxy routing from the on-disk compose for: ${preserved.join(", ")}.`);
|
|
30127
|
+
}
|
|
30128
|
+
if (couldNotPreserve.length > 0) {
|
|
30129
|
+
console.warn(`switchroom: no prior routing verdict on disk for: ${couldNotPreserve.join(", ")} ` + `\u2014 leaving them unrouted (they were not previously routed).`);
|
|
30130
|
+
}
|
|
30131
|
+
}
|
|
30075
30132
|
return confirmed;
|
|
30076
30133
|
}
|
|
30077
30134
|
function resolveHostSwitchroomConfigPath(rawPath) {
|
|
@@ -30109,7 +30166,13 @@ async function computeComposeContent(opts) {
|
|
|
30109
30166
|
const imageTag = resolveImageTag(release);
|
|
30110
30167
|
const operatorUid = resolveOperatorUid();
|
|
30111
30168
|
const resolvedConfigPath = opts.switchroomConfigPath !== undefined ? resolveHostSwitchroomConfigPath(opts.switchroomConfigPath) : undefined;
|
|
30112
|
-
|
|
30169
|
+
let previous = null;
|
|
30170
|
+
try {
|
|
30171
|
+
previous = await readFile(opts.composePath, "utf8");
|
|
30172
|
+
} catch {
|
|
30173
|
+
previous = null;
|
|
30174
|
+
}
|
|
30175
|
+
const litellmConfirmedAgents = await resolveLiteLLMConfirmedAgents(opts.config, previous);
|
|
30113
30176
|
const content = generateCompose({
|
|
30114
30177
|
config: opts.config,
|
|
30115
30178
|
imageTag,
|
|
@@ -30121,12 +30184,6 @@ async function computeComposeContent(opts) {
|
|
|
30121
30184
|
switchroomConfigPath: resolvedConfigPath,
|
|
30122
30185
|
operatorUid
|
|
30123
30186
|
});
|
|
30124
|
-
let previous = null;
|
|
30125
|
-
try {
|
|
30126
|
-
previous = await readFile(opts.composePath, "utf8");
|
|
30127
|
-
} catch {
|
|
30128
|
-
previous = null;
|
|
30129
|
-
}
|
|
30130
30187
|
const previousImageTag = previous ? AGENT_IMAGE_TAG_RE.exec(previous)?.[1] ?? null : null;
|
|
30131
30188
|
return { content, imageTag, previous, previousImageTag };
|
|
30132
30189
|
}
|
|
@@ -40412,19 +40469,19 @@ var init_thinking_effort_risk = __esm(() => {
|
|
|
40412
40469
|
|
|
40413
40470
|
// src/manifest.ts
|
|
40414
40471
|
import {
|
|
40415
|
-
existsSync as
|
|
40416
|
-
readFileSync as
|
|
40472
|
+
existsSync as existsSync58,
|
|
40473
|
+
readFileSync as readFileSync53,
|
|
40417
40474
|
readdirSync as readdirSync19
|
|
40418
40475
|
} from "node:fs";
|
|
40419
|
-
import { dirname as
|
|
40476
|
+
import { dirname as dirname18, join as join51 } from "node:path";
|
|
40420
40477
|
import { execSync as execSync2 } from "node:child_process";
|
|
40421
40478
|
function locateManifestPath() {
|
|
40422
40479
|
let dir = import.meta.dirname;
|
|
40423
40480
|
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
40424
|
-
const candidate =
|
|
40425
|
-
if (
|
|
40481
|
+
const candidate = join51(dir, "dependencies.json");
|
|
40482
|
+
if (existsSync58(candidate))
|
|
40426
40483
|
return candidate;
|
|
40427
|
-
dir =
|
|
40484
|
+
dir = dirname18(dir);
|
|
40428
40485
|
}
|
|
40429
40486
|
return null;
|
|
40430
40487
|
}
|
|
@@ -40435,7 +40492,7 @@ function loadManifest(manifestPath) {
|
|
|
40435
40492
|
}
|
|
40436
40493
|
let raw;
|
|
40437
40494
|
try {
|
|
40438
|
-
raw =
|
|
40495
|
+
raw = readFileSync53(path4, "utf-8");
|
|
40439
40496
|
} catch (err) {
|
|
40440
40497
|
throw new Error(`Failed to read manifest at ${path4}: ${err.message}`);
|
|
40441
40498
|
}
|
|
@@ -40491,16 +40548,16 @@ function probeClaudeVersion() {
|
|
|
40491
40548
|
}
|
|
40492
40549
|
function probePlaywrightMcpVersion() {
|
|
40493
40550
|
const home2 = process.env.HOME ?? "";
|
|
40494
|
-
const npxCache =
|
|
40495
|
-
if (!
|
|
40551
|
+
const npxCache = join51(home2, ".npm/_npx");
|
|
40552
|
+
if (!existsSync58(npxCache))
|
|
40496
40553
|
return null;
|
|
40497
40554
|
try {
|
|
40498
40555
|
const entries = readdirSync19(npxCache);
|
|
40499
40556
|
for (const entry of entries) {
|
|
40500
|
-
const pkgPath =
|
|
40501
|
-
if (
|
|
40557
|
+
const pkgPath = join51(npxCache, entry, "node_modules/@playwright/mcp/package.json");
|
|
40558
|
+
if (existsSync58(pkgPath)) {
|
|
40502
40559
|
try {
|
|
40503
|
-
const pkg = JSON.parse(
|
|
40560
|
+
const pkg = JSON.parse(readFileSync53(pkgPath, "utf-8"));
|
|
40504
40561
|
if (pkg.version)
|
|
40505
40562
|
return pkg.version;
|
|
40506
40563
|
} catch {}
|
|
@@ -40802,7 +40859,7 @@ var init_doctor_memory = __esm(() => {
|
|
|
40802
40859
|
});
|
|
40803
40860
|
|
|
40804
40861
|
// src/cli/doctor-docker.ts
|
|
40805
|
-
import { readFileSync as
|
|
40862
|
+
import { readFileSync as readFileSync54 } from "node:fs";
|
|
40806
40863
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
40807
40864
|
function imageTagOf(ref) {
|
|
40808
40865
|
if (!ref)
|
|
@@ -40818,7 +40875,7 @@ function isDockerMode(opts) {
|
|
|
40818
40875
|
return true;
|
|
40819
40876
|
if (opts?.composePath) {
|
|
40820
40877
|
try {
|
|
40821
|
-
|
|
40878
|
+
readFileSync54(opts.composePath, "utf8");
|
|
40822
40879
|
return true;
|
|
40823
40880
|
} catch {}
|
|
40824
40881
|
}
|
|
@@ -41100,11 +41157,11 @@ var init_doctor_docker = __esm(() => {
|
|
|
41100
41157
|
});
|
|
41101
41158
|
|
|
41102
41159
|
// src/cli/doctor-auth-broker.ts
|
|
41103
|
-
import { existsSync as
|
|
41160
|
+
import { existsSync as existsSync59, readFileSync as readFileSync55 } from "node:fs";
|
|
41104
41161
|
import { createHash as createHash11 } from "node:crypto";
|
|
41105
41162
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
41106
|
-
import { homedir as
|
|
41107
|
-
import { join as
|
|
41163
|
+
import { homedir as homedir29 } from "node:os";
|
|
41164
|
+
import { join as join52 } from "node:path";
|
|
41108
41165
|
function defaultDockerInspect(container, format) {
|
|
41109
41166
|
try {
|
|
41110
41167
|
const r = spawnSync9("docker", ["inspect", "-f", format, container], { encoding: "utf-8", timeout: 5000 });
|
|
@@ -41204,8 +41261,8 @@ function checkAuthBrokerPerAgentSockets(config, deps = {}) {
|
|
|
41204
41261
|
}
|
|
41205
41262
|
function checkAuthBrokerDrift(deps = {}) {
|
|
41206
41263
|
const stateDir = resolveStateDir(deps);
|
|
41207
|
-
const indexPath =
|
|
41208
|
-
if (!
|
|
41264
|
+
const indexPath = join52(stateDir, "sha-index.json");
|
|
41265
|
+
if (!existsSync59(indexPath)) {
|
|
41209
41266
|
return {
|
|
41210
41267
|
name: "auth-broker: drift",
|
|
41211
41268
|
status: "ok",
|
|
@@ -41214,7 +41271,7 @@ function checkAuthBrokerDrift(deps = {}) {
|
|
|
41214
41271
|
}
|
|
41215
41272
|
let index;
|
|
41216
41273
|
try {
|
|
41217
|
-
index = JSON.parse(
|
|
41274
|
+
index = JSON.parse(readFileSync55(indexPath, "utf-8"));
|
|
41218
41275
|
} catch (err) {
|
|
41219
41276
|
return {
|
|
41220
41277
|
name: "auth-broker: drift",
|
|
@@ -41223,18 +41280,18 @@ function checkAuthBrokerDrift(deps = {}) {
|
|
|
41223
41280
|
fix: "Inspect `~/.switchroom/state/auth-broker/sha-index.json` for corruption."
|
|
41224
41281
|
};
|
|
41225
41282
|
}
|
|
41226
|
-
const home2 = deps.home ??
|
|
41283
|
+
const home2 = deps.home ?? homedir29();
|
|
41227
41284
|
const divergent = [];
|
|
41228
41285
|
const missingOnDisk = [];
|
|
41229
41286
|
for (const [label, expected] of Object.entries(index)) {
|
|
41230
41287
|
const credsPath = accountCredentialsPath(label, home2);
|
|
41231
|
-
if (!
|
|
41288
|
+
if (!existsSync59(credsPath)) {
|
|
41232
41289
|
missingOnDisk.push(label);
|
|
41233
41290
|
continue;
|
|
41234
41291
|
}
|
|
41235
41292
|
let got;
|
|
41236
41293
|
try {
|
|
41237
|
-
got = sha256Hex(
|
|
41294
|
+
got = sha256Hex(readFileSync55(credsPath, "utf-8"));
|
|
41238
41295
|
} catch (err) {
|
|
41239
41296
|
divergent.push(`${label} (read failed: ${err.message})`);
|
|
41240
41297
|
continue;
|
|
@@ -41265,8 +41322,8 @@ function checkAuthBrokerDrift(deps = {}) {
|
|
|
41265
41322
|
}
|
|
41266
41323
|
function checkAuthBrokerThresholdViolations(deps = {}) {
|
|
41267
41324
|
const stateDir = resolveStateDir(deps);
|
|
41268
|
-
const path4 =
|
|
41269
|
-
if (!
|
|
41325
|
+
const path4 = join52(stateDir, "threshold-violations.json");
|
|
41326
|
+
if (!existsSync59(path4)) {
|
|
41270
41327
|
return {
|
|
41271
41328
|
name: "auth-broker: threshold violations",
|
|
41272
41329
|
status: "ok",
|
|
@@ -41275,7 +41332,7 @@ function checkAuthBrokerThresholdViolations(deps = {}) {
|
|
|
41275
41332
|
}
|
|
41276
41333
|
let violations;
|
|
41277
41334
|
try {
|
|
41278
|
-
violations = JSON.parse(
|
|
41335
|
+
violations = JSON.parse(readFileSync55(path4, "utf-8"));
|
|
41279
41336
|
} catch (err) {
|
|
41280
41337
|
return {
|
|
41281
41338
|
name: "auth-broker: threshold violations",
|
|
@@ -41309,9 +41366,9 @@ function checkAuthBrokerActiveAccount(config, deps = {}) {
|
|
|
41309
41366
|
fix: "Run `switchroom auth use <label>` to pin a fleet-wide account, then `switchroom apply`. Without an active account every agent boot fails."
|
|
41310
41367
|
};
|
|
41311
41368
|
}
|
|
41312
|
-
const home2 = deps.home ??
|
|
41369
|
+
const home2 = deps.home ?? homedir29();
|
|
41313
41370
|
const dir = accountDir(active, home2);
|
|
41314
|
-
if (!
|
|
41371
|
+
if (!existsSync59(dir)) {
|
|
41315
41372
|
return {
|
|
41316
41373
|
name: "auth-broker: fleet active account",
|
|
41317
41374
|
status: "fail",
|
|
@@ -41320,7 +41377,7 @@ function checkAuthBrokerActiveAccount(config, deps = {}) {
|
|
|
41320
41377
|
};
|
|
41321
41378
|
}
|
|
41322
41379
|
const creds = accountCredentialsPath(active, home2);
|
|
41323
|
-
if (!
|
|
41380
|
+
if (!existsSync59(creds)) {
|
|
41324
41381
|
return {
|
|
41325
41382
|
name: "auth-broker: fleet active account",
|
|
41326
41383
|
status: "fail",
|
|
@@ -41457,17 +41514,17 @@ var init_doctor_hostd = () => {};
|
|
|
41457
41514
|
import {
|
|
41458
41515
|
accessSync,
|
|
41459
41516
|
constants as fsConstants4,
|
|
41460
|
-
existsSync as
|
|
41517
|
+
existsSync as existsSync60,
|
|
41461
41518
|
realpathSync as realpathSync5,
|
|
41462
|
-
statSync as
|
|
41519
|
+
statSync as statSync25
|
|
41463
41520
|
} from "node:fs";
|
|
41464
|
-
import { userInfo, homedir as
|
|
41465
|
-
import { join as
|
|
41521
|
+
import { userInfo, homedir as homedir30 } from "node:os";
|
|
41522
|
+
import { join as join53 } from "node:path";
|
|
41466
41523
|
function resolveVaultPath2(config) {
|
|
41467
41524
|
return config.vault?.path ? config.vault.path.replace(/^~/, process.env.HOME ?? "") : resolveStatePath("vault.enc");
|
|
41468
41525
|
}
|
|
41469
41526
|
function defaultStatVault(path4) {
|
|
41470
|
-
if (!
|
|
41527
|
+
if (!existsSync60(path4)) {
|
|
41471
41528
|
return { exists: false, readable: false, uid: -1, mode: 0, realPath: path4 };
|
|
41472
41529
|
}
|
|
41473
41530
|
let real = path4;
|
|
@@ -41477,7 +41534,7 @@ function defaultStatVault(path4) {
|
|
|
41477
41534
|
let uid = -1;
|
|
41478
41535
|
let mode = 0;
|
|
41479
41536
|
try {
|
|
41480
|
-
const s =
|
|
41537
|
+
const s = statSync25(real);
|
|
41481
41538
|
uid = s.uid;
|
|
41482
41539
|
mode = s.mode & 511;
|
|
41483
41540
|
} catch {
|
|
@@ -41600,7 +41657,7 @@ async function runSecretAccessChecks(config, deps = {}) {
|
|
|
41600
41657
|
};
|
|
41601
41658
|
const passphrase = deps.passphrase ?? process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
41602
41659
|
if (!passphrase) {
|
|
41603
|
-
const sock = deps.brokerOperatorSocket ??
|
|
41660
|
+
const sock = deps.brokerOperatorSocket ?? join53(homedir30(), ".switchroom", "broker-operator", "sock");
|
|
41604
41661
|
const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
|
|
41605
41662
|
for (const name of Object.keys(config.agents ?? {})) {
|
|
41606
41663
|
const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[name]);
|
|
@@ -41685,8 +41742,8 @@ import {
|
|
|
41685
41742
|
existsSync as realExistsSync,
|
|
41686
41743
|
readFileSync as realReadFileSync
|
|
41687
41744
|
} from "node:fs";
|
|
41688
|
-
import { join as
|
|
41689
|
-
import { homedir as
|
|
41745
|
+
import { join as join54, resolve as resolve34 } from "node:path";
|
|
41746
|
+
import { homedir as homedir31 } from "node:os";
|
|
41690
41747
|
function resolveDeps(config, deps) {
|
|
41691
41748
|
let agentsDir = deps.agentsDir;
|
|
41692
41749
|
if (agentsDir === undefined) {
|
|
@@ -41809,8 +41866,8 @@ function checkScaffoldWiring(config, driveAgents, d) {
|
|
|
41809
41866
|
});
|
|
41810
41867
|
continue;
|
|
41811
41868
|
}
|
|
41812
|
-
const mcpPath =
|
|
41813
|
-
const claudeJsonPath =
|
|
41869
|
+
const mcpPath = join54(agentDir, ".mcp.json");
|
|
41870
|
+
const claudeJsonPath = join54(agentDir, ".claude", ".claude.json");
|
|
41814
41871
|
const mcpRead = readJson(d, mcpPath);
|
|
41815
41872
|
const trustRead = readJson(d, claudeJsonPath);
|
|
41816
41873
|
if (mcpRead.kind === "unreadable" || trustRead.kind === "unreadable") {
|
|
@@ -41923,7 +41980,7 @@ async function runDriveBrokerReachabilityChecks(config, deps = {}) {
|
|
|
41923
41980
|
}
|
|
41924
41981
|
];
|
|
41925
41982
|
}
|
|
41926
|
-
const sock = deps.brokerOperatorSocket ??
|
|
41983
|
+
const sock = deps.brokerOperatorSocket ?? join54(homedir31(), ".switchroom", "broker-operator", "sock");
|
|
41927
41984
|
const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
|
|
41928
41985
|
const results = [];
|
|
41929
41986
|
for (const agent of driveAgents) {
|
|
@@ -41977,8 +42034,8 @@ import {
|
|
|
41977
42034
|
readSync as realReadSync,
|
|
41978
42035
|
closeSync as realCloseSync
|
|
41979
42036
|
} from "node:fs";
|
|
41980
|
-
import { join as
|
|
41981
|
-
import { homedir as
|
|
42037
|
+
import { join as join55 } from "node:path";
|
|
42038
|
+
import { homedir as homedir32 } from "node:os";
|
|
41982
42039
|
function defaultReadHead(p, n) {
|
|
41983
42040
|
let fd;
|
|
41984
42041
|
try {
|
|
@@ -42006,7 +42063,7 @@ function resolveDeps2(config, deps) {
|
|
|
42006
42063
|
}
|
|
42007
42064
|
}
|
|
42008
42065
|
return {
|
|
42009
|
-
homeDir: deps.homeDir ??
|
|
42066
|
+
homeDir: deps.homeDir ?? homedir32(),
|
|
42010
42067
|
agentsDir,
|
|
42011
42068
|
existsSync: deps.existsSync ?? ((p) => realExistsSync2(p)),
|
|
42012
42069
|
readFileSync: deps.readFileSync ?? ((p) => realReadFileSync2(p, "utf-8")),
|
|
@@ -42035,7 +42092,7 @@ function runWebkiteChecks(config, deps = {}) {
|
|
|
42035
42092
|
return [];
|
|
42036
42093
|
const d = resolveDeps2(config, deps);
|
|
42037
42094
|
const results = [];
|
|
42038
|
-
const binPath =
|
|
42095
|
+
const binPath = join55(d.homeDir, ".switchroom", "bin", "webkite");
|
|
42039
42096
|
if (!d.existsSync(binPath)) {
|
|
42040
42097
|
results.push({
|
|
42041
42098
|
name: "webkite: binary",
|
|
@@ -42065,12 +42122,12 @@ function runWebkiteChecks(config, deps = {}) {
|
|
|
42065
42122
|
});
|
|
42066
42123
|
}
|
|
42067
42124
|
}
|
|
42068
|
-
const cloakDir =
|
|
42125
|
+
const cloakDir = join55(d.homeDir, ".cloakbrowser");
|
|
42069
42126
|
let chromeFound = false;
|
|
42070
42127
|
if (d.existsSync(cloakDir)) {
|
|
42071
42128
|
try {
|
|
42072
42129
|
for (const entry of d.readdirSync(cloakDir)) {
|
|
42073
|
-
if (entry.startsWith("chromium-") && d.existsSync(
|
|
42130
|
+
if (entry.startsWith("chromium-") && d.existsSync(join55(cloakDir, entry, "chrome"))) {
|
|
42074
42131
|
chromeFound = true;
|
|
42075
42132
|
break;
|
|
42076
42133
|
}
|
|
@@ -42096,9 +42153,9 @@ function runWebkiteChecks(config, deps = {}) {
|
|
|
42096
42153
|
return results;
|
|
42097
42154
|
}
|
|
42098
42155
|
for (const agent of enabledAgents) {
|
|
42099
|
-
const agentDir =
|
|
42100
|
-
const settingsPath =
|
|
42101
|
-
const mcpPath =
|
|
42156
|
+
const agentDir = join55(d.agentsDir, agent);
|
|
42157
|
+
const settingsPath = join55(agentDir, ".claude", "settings.json");
|
|
42158
|
+
const mcpPath = join55(agentDir, ".mcp.json");
|
|
42102
42159
|
if (!d.existsSync(settingsPath) && !d.existsSync(mcpPath)) {
|
|
42103
42160
|
continue;
|
|
42104
42161
|
}
|
|
@@ -42228,7 +42285,7 @@ var init_doctor_cron_session = __esm(() => {
|
|
|
42228
42285
|
});
|
|
42229
42286
|
|
|
42230
42287
|
// src/cli/doctor-scaffold-wiring.ts
|
|
42231
|
-
import { join as
|
|
42288
|
+
import { join as join56, resolve as resolve36 } from "node:path";
|
|
42232
42289
|
function readJson2(d, path4) {
|
|
42233
42290
|
if (!d.existsSync(path4))
|
|
42234
42291
|
return { kind: "absent" };
|
|
@@ -42271,8 +42328,8 @@ function checkIntegrationScaffoldWiring(args) {
|
|
|
42271
42328
|
});
|
|
42272
42329
|
continue;
|
|
42273
42330
|
}
|
|
42274
|
-
const mcpPath =
|
|
42275
|
-
const claudeJsonPath =
|
|
42331
|
+
const mcpPath = join56(agentDir, ".mcp.json");
|
|
42332
|
+
const claudeJsonPath = join56(agentDir, ".claude", ".claude.json");
|
|
42276
42333
|
const mcpRead = readJson2(deps, mcpPath);
|
|
42277
42334
|
const trustRead = readJson2(deps, claudeJsonPath);
|
|
42278
42335
|
if (mcpRead.kind === "unreadable" || trustRead.kind === "unreadable") {
|
|
@@ -42336,14 +42393,14 @@ import {
|
|
|
42336
42393
|
existsSync as realExistsSync3,
|
|
42337
42394
|
readFileSync as realReadFileSync3
|
|
42338
42395
|
} from "node:fs";
|
|
42339
|
-
import { join as
|
|
42340
|
-
import { homedir as
|
|
42396
|
+
import { join as join57 } from "node:path";
|
|
42397
|
+
import { homedir as homedir33 } from "node:os";
|
|
42341
42398
|
function resolveDeps3(deps) {
|
|
42342
|
-
const home2 = deps.homeDir?.() ??
|
|
42399
|
+
const home2 = deps.homeDir?.() ?? homedir33();
|
|
42343
42400
|
return {
|
|
42344
42401
|
existsSync: deps.existsSync ?? realExistsSync3,
|
|
42345
42402
|
readFileSync: deps.readFileSync ?? realReadFileSync3,
|
|
42346
|
-
agentsDir:
|
|
42403
|
+
agentsDir: join57(home2, ".switchroom", "agents"),
|
|
42347
42404
|
now: deps.now ?? Date.now
|
|
42348
42405
|
};
|
|
42349
42406
|
}
|
|
@@ -42426,7 +42483,7 @@ function checkOAuthClient2(config, anyAgentEnabled) {
|
|
|
42426
42483
|
];
|
|
42427
42484
|
}
|
|
42428
42485
|
function readHeartbeat(d, agentName) {
|
|
42429
|
-
const path4 =
|
|
42486
|
+
const path4 = join57(d.agentsDir, agentName, "m365-launcher.heartbeat.json");
|
|
42430
42487
|
if (!d.existsSync(path4)) {
|
|
42431
42488
|
return { error: "heartbeat file missing \u2014 launcher has not yet started" };
|
|
42432
42489
|
}
|
|
@@ -42523,15 +42580,15 @@ import {
|
|
|
42523
42580
|
readFileSync as realReadFileSync4,
|
|
42524
42581
|
statSync as realStatSync2
|
|
42525
42582
|
} from "node:fs";
|
|
42526
|
-
import { join as
|
|
42527
|
-
import { homedir as
|
|
42583
|
+
import { join as join58 } from "node:path";
|
|
42584
|
+
import { homedir as homedir34 } from "node:os";
|
|
42528
42585
|
function resolveDeps4(deps) {
|
|
42529
|
-
const home2 = deps.homeDir?.() ??
|
|
42586
|
+
const home2 = deps.homeDir?.() ?? homedir34();
|
|
42530
42587
|
return {
|
|
42531
42588
|
existsSync: deps.existsSync ?? realExistsSync4,
|
|
42532
42589
|
readFileSync: deps.readFileSync ?? realReadFileSync4,
|
|
42533
42590
|
statSync: deps.statSync ?? realStatSync2,
|
|
42534
|
-
agentsDir:
|
|
42591
|
+
agentsDir: join58(home2, ".switchroom", "agents"),
|
|
42535
42592
|
now: deps.now ?? Date.now,
|
|
42536
42593
|
vaultAclReader: deps.vaultAclReader ?? (async () => ({ kind: "unreachable", msg: "no default reader wired" }))
|
|
42537
42594
|
};
|
|
@@ -42656,7 +42713,7 @@ function checkLauncherHeartbeat2(notionAgents, d) {
|
|
|
42656
42713
|
return [];
|
|
42657
42714
|
const results = [];
|
|
42658
42715
|
for (const name of notionAgents) {
|
|
42659
|
-
const heartbeatPath =
|
|
42716
|
+
const heartbeatPath = join58(d.agentsDir, name, "notion-launcher.heartbeat.json");
|
|
42660
42717
|
if (!d.existsSync(heartbeatPath)) {
|
|
42661
42718
|
results.push({
|
|
42662
42719
|
name: `notion:launcher-heartbeat:${name}`,
|
|
@@ -42877,11 +42934,11 @@ import {
|
|
|
42877
42934
|
readdirSync as realReaddirSync2,
|
|
42878
42935
|
statSync as realStatSync3
|
|
42879
42936
|
} from "node:fs";
|
|
42880
|
-
import { homedir as
|
|
42881
|
-
import { join as
|
|
42937
|
+
import { homedir as homedir35 } from "node:os";
|
|
42938
|
+
import { join as join59 } from "node:path";
|
|
42882
42939
|
function runCredentialsMigrationChecks(config, deps = {}) {
|
|
42883
|
-
const credDir = deps.credentialsDir ??
|
|
42884
|
-
const
|
|
42940
|
+
const credDir = deps.credentialsDir ?? join59(homedir35(), ".switchroom", "credentials");
|
|
42941
|
+
const existsSync61 = deps.existsSync ?? ((p) => realExistsSync5(p));
|
|
42885
42942
|
const readdirSync21 = deps.readdirSync ?? ((p) => realReaddirSync2(p));
|
|
42886
42943
|
const isDirectory = deps.isDirectory ?? ((p) => {
|
|
42887
42944
|
try {
|
|
@@ -42890,7 +42947,7 @@ function runCredentialsMigrationChecks(config, deps = {}) {
|
|
|
42890
42947
|
return false;
|
|
42891
42948
|
}
|
|
42892
42949
|
});
|
|
42893
|
-
if (!
|
|
42950
|
+
if (!existsSync61(credDir))
|
|
42894
42951
|
return [];
|
|
42895
42952
|
const agentNames = new Set(Object.keys(config.agents ?? {}));
|
|
42896
42953
|
let entries;
|
|
@@ -42908,7 +42965,7 @@ function runCredentialsMigrationChecks(config, deps = {}) {
|
|
|
42908
42965
|
const flat = [];
|
|
42909
42966
|
const perAgentDirs = [];
|
|
42910
42967
|
for (const e of entries) {
|
|
42911
|
-
const full =
|
|
42968
|
+
const full = join59(credDir, e);
|
|
42912
42969
|
if (isDirectory(full) && agentNames.has(e)) {
|
|
42913
42970
|
perAgentDirs.push(e);
|
|
42914
42971
|
} else {
|
|
@@ -43031,19 +43088,19 @@ var init_doctor_inlined_secrets = __esm(() => {
|
|
|
43031
43088
|
|
|
43032
43089
|
// src/cli/doctor-audit-integrity.ts
|
|
43033
43090
|
import { readFileSync as fsReadFileSync2 } from "node:fs";
|
|
43034
|
-
import { homedir as
|
|
43035
|
-
import { join as
|
|
43091
|
+
import { homedir as homedir36 } from "node:os";
|
|
43092
|
+
import { join as join60 } from "node:path";
|
|
43036
43093
|
function rootWrittenLogs(home2) {
|
|
43037
43094
|
return [
|
|
43038
|
-
{ label: "vault-broker", path:
|
|
43095
|
+
{ label: "vault-broker", path: join60(home2, ".switchroom", "vault-audit.log") },
|
|
43039
43096
|
{
|
|
43040
43097
|
label: "hostd",
|
|
43041
|
-
path:
|
|
43098
|
+
path: join60(home2, ".switchroom", "host-control-audit.log")
|
|
43042
43099
|
}
|
|
43043
43100
|
];
|
|
43044
43101
|
}
|
|
43045
43102
|
function runAuditIntegrityChecks(deps = {}) {
|
|
43046
|
-
const home2 = deps.homeDir ??
|
|
43103
|
+
const home2 = deps.homeDir ?? homedir36();
|
|
43047
43104
|
const read = deps.readFileSync ?? ((p) => fsReadFileSync2(p, "utf8"));
|
|
43048
43105
|
const results = [];
|
|
43049
43106
|
for (const { label, path: path4 } of rootWrittenLogs(home2)) {
|
|
@@ -43100,16 +43157,16 @@ var init_doctor_audit_integrity = __esm(() => {
|
|
|
43100
43157
|
});
|
|
43101
43158
|
|
|
43102
43159
|
// src/cli/doctor-agent-smoke.ts
|
|
43103
|
-
import { existsSync as
|
|
43104
|
-
import { homedir as
|
|
43105
|
-
import { join as
|
|
43160
|
+
import { existsSync as existsSync61 } from "node:fs";
|
|
43161
|
+
import { homedir as homedir37 } from "node:os";
|
|
43162
|
+
import { join as join61 } from "node:path";
|
|
43106
43163
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
43107
43164
|
async function runAgentSmokeChecks(config, deps = {}) {
|
|
43108
43165
|
if (deps.fast)
|
|
43109
43166
|
return [];
|
|
43110
|
-
const home2 = deps.homeDir ??
|
|
43111
|
-
const sock = deps.operatorSockPath ??
|
|
43112
|
-
if (!deps.hostdRequestImpl && !
|
|
43167
|
+
const home2 = deps.homeDir ?? homedir37();
|
|
43168
|
+
const sock = deps.operatorSockPath ?? join61(home2, ".switchroom", "hostd", "operator", "sock");
|
|
43169
|
+
if (!deps.hostdRequestImpl && !existsSync61(sock)) {
|
|
43113
43170
|
return [
|
|
43114
43171
|
{
|
|
43115
43172
|
name: "agent liveness",
|
|
@@ -43187,9 +43244,9 @@ var init_doctor_agent_smoke = __esm(() => {
|
|
|
43187
43244
|
|
|
43188
43245
|
// src/cli/doctor-vault-broker-durability.ts
|
|
43189
43246
|
import { execFileSync as execFileSync18 } from "node:child_process";
|
|
43190
|
-
import { existsSync as
|
|
43191
|
-
import { homedir as
|
|
43192
|
-
import { join as
|
|
43247
|
+
import { existsSync as existsSync62, statSync as statSync26 } from "node:fs";
|
|
43248
|
+
import { homedir as homedir38 } from "node:os";
|
|
43249
|
+
import { join as join62 } from "node:path";
|
|
43193
43250
|
function probeBindMountInode(hostPath, brokerContainerPath, opts) {
|
|
43194
43251
|
const statHost = opts?.statHost ?? defaultStatHost;
|
|
43195
43252
|
const statBroker = opts?.statBroker ?? defaultStatBroker;
|
|
@@ -43211,10 +43268,10 @@ function probeBindMountInode(hostPath, brokerContainerPath, opts) {
|
|
|
43211
43268
|
};
|
|
43212
43269
|
}
|
|
43213
43270
|
function defaultStatHost(p) {
|
|
43214
|
-
if (!
|
|
43271
|
+
if (!existsSync62(p))
|
|
43215
43272
|
return null;
|
|
43216
43273
|
try {
|
|
43217
|
-
const s =
|
|
43274
|
+
const s = statSync26(p, { bigint: true });
|
|
43218
43275
|
return { ino: s.ino, size: Number(s.size) };
|
|
43219
43276
|
} catch {
|
|
43220
43277
|
return null;
|
|
@@ -43332,22 +43389,22 @@ function defaultBrokerStatusProbe() {
|
|
|
43332
43389
|
}
|
|
43333
43390
|
}
|
|
43334
43391
|
function runVaultBrokerDurabilityChecks(_config, opts) {
|
|
43335
|
-
const home2 =
|
|
43392
|
+
const home2 = homedir38();
|
|
43336
43393
|
const probe2 = opts?.inodeProbe ?? probeBindMountInode;
|
|
43337
43394
|
return [
|
|
43338
43395
|
probeBrokerUnlocked(opts?.statusProbe),
|
|
43339
43396
|
probeAutoUnlockBlob(home2),
|
|
43340
43397
|
probeMachineIdMount(),
|
|
43341
|
-
formatBindMountResult("vault-broker: vault.enc bind mount",
|
|
43342
|
-
formatBindMountResult("vault-broker: vault-grants.db bind mount (#1737)",
|
|
43343
|
-
formatBindMountResult("vault-broker: vault-audit.log bind mount (#1025)",
|
|
43398
|
+
formatBindMountResult("vault-broker: vault.enc bind mount", join62(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc", probe2(join62(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc")),
|
|
43399
|
+
formatBindMountResult("vault-broker: vault-grants.db bind mount (#1737)", join62(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db", probe2(join62(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db")),
|
|
43400
|
+
formatBindMountResult("vault-broker: vault-audit.log bind mount (#1025)", join62(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log", probe2(join62(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log")),
|
|
43344
43401
|
probeKernelDbDurability(home2, {
|
|
43345
43402
|
statBroker: opts?.kernelStatBroker
|
|
43346
43403
|
})
|
|
43347
43404
|
];
|
|
43348
43405
|
}
|
|
43349
43406
|
function probeKernelDbDurability(home2, opts) {
|
|
43350
|
-
const hostDir =
|
|
43407
|
+
const hostDir = join62(home2, ".switchroom", "approvals");
|
|
43351
43408
|
const containerDir = "/state/approvals";
|
|
43352
43409
|
const name = "approval-kernel: approvals bind mount (allow_always durability)";
|
|
43353
43410
|
const kernelStat = opts?.statBroker ?? defaultKernelStatBroker;
|
|
@@ -43415,8 +43472,8 @@ function defaultKernelStatBroker(p) {
|
|
|
43415
43472
|
return { kind: "ok-with-stat", ino: inoStr, size };
|
|
43416
43473
|
}
|
|
43417
43474
|
function probeAutoUnlockBlob(home2) {
|
|
43418
|
-
const blobPath =
|
|
43419
|
-
if (!
|
|
43475
|
+
const blobPath = join62(home2, ".switchroom", "vault-auto-unlock");
|
|
43476
|
+
if (!existsSync62(blobPath)) {
|
|
43420
43477
|
return {
|
|
43421
43478
|
name: "vault-broker: auto-unlock blob",
|
|
43422
43479
|
status: "warn",
|
|
@@ -43424,7 +43481,7 @@ function probeAutoUnlockBlob(home2) {
|
|
|
43424
43481
|
fix: "Run `switchroom vault broker enable-auto-unlock` to seal the blob with the current passphrase + machine-id"
|
|
43425
43482
|
};
|
|
43426
43483
|
}
|
|
43427
|
-
const sz =
|
|
43484
|
+
const sz = statSync26(blobPath).size;
|
|
43428
43485
|
if (sz === 0) {
|
|
43429
43486
|
return {
|
|
43430
43487
|
name: "vault-broker: auto-unlock blob",
|
|
@@ -43440,7 +43497,7 @@ function probeAutoUnlockBlob(home2) {
|
|
|
43440
43497
|
};
|
|
43441
43498
|
}
|
|
43442
43499
|
function probeMachineIdMount() {
|
|
43443
|
-
const hostExists =
|
|
43500
|
+
const hostExists = existsSync62("/etc/machine-id");
|
|
43444
43501
|
if (!hostExists) {
|
|
43445
43502
|
return {
|
|
43446
43503
|
name: "vault-broker: machine-id passthrough",
|
|
@@ -43588,25 +43645,25 @@ import { execSync as execSync3, spawnSync as spawnSync11 } from "node:child_proc
|
|
|
43588
43645
|
import {
|
|
43589
43646
|
accessSync as accessSync2,
|
|
43590
43647
|
constants as fsConstants5,
|
|
43591
|
-
existsSync as
|
|
43648
|
+
existsSync as existsSync63,
|
|
43592
43649
|
lstatSync as lstatSync7,
|
|
43593
|
-
mkdirSync as
|
|
43594
|
-
readFileSync as
|
|
43650
|
+
mkdirSync as mkdirSync33,
|
|
43651
|
+
readFileSync as readFileSync56,
|
|
43595
43652
|
readdirSync as readdirSync21,
|
|
43596
|
-
statSync as
|
|
43653
|
+
statSync as statSync27
|
|
43597
43654
|
} from "node:fs";
|
|
43598
|
-
import { dirname as
|
|
43655
|
+
import { dirname as dirname19, join as join63, resolve as resolve37 } from "node:path";
|
|
43599
43656
|
import { createPublicKey, createPrivateKey } from "node:crypto";
|
|
43600
43657
|
function findInNvm(bin) {
|
|
43601
|
-
const nvmRoot =
|
|
43602
|
-
if (!
|
|
43658
|
+
const nvmRoot = join63(process.env.HOME ?? "", ".nvm", "versions", "node");
|
|
43659
|
+
if (!existsSync63(nvmRoot))
|
|
43603
43660
|
return null;
|
|
43604
43661
|
try {
|
|
43605
43662
|
const versions = readdirSync21(nvmRoot).sort().reverse();
|
|
43606
43663
|
for (const v of versions) {
|
|
43607
|
-
const candidate =
|
|
43664
|
+
const candidate = join63(nvmRoot, v, "bin", bin);
|
|
43608
43665
|
try {
|
|
43609
|
-
const s =
|
|
43666
|
+
const s = statSync27(candidate);
|
|
43610
43667
|
if (s.isFile() || s.isSymbolicLink()) {
|
|
43611
43668
|
return candidate;
|
|
43612
43669
|
}
|
|
@@ -43769,21 +43826,21 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
|
|
|
43769
43826
|
if (envBrowsersPath && envBrowsersPath.length > 0) {
|
|
43770
43827
|
cacheLocations.push(envBrowsersPath);
|
|
43771
43828
|
}
|
|
43772
|
-
cacheLocations.push(
|
|
43829
|
+
cacheLocations.push(join63(homeDir, ".cache", "ms-playwright"));
|
|
43773
43830
|
for (const cacheDir of cacheLocations) {
|
|
43774
|
-
if (!
|
|
43831
|
+
if (!existsSync63(cacheDir))
|
|
43775
43832
|
continue;
|
|
43776
43833
|
try {
|
|
43777
43834
|
const entries = readdirSync21(cacheDir).filter((e) => e.startsWith("chromium"));
|
|
43778
43835
|
for (const entry of entries) {
|
|
43779
43836
|
const candidates2 = [
|
|
43780
|
-
|
|
43781
|
-
|
|
43782
|
-
|
|
43783
|
-
|
|
43837
|
+
join63(cacheDir, entry, "chrome-linux64", "chrome"),
|
|
43838
|
+
join63(cacheDir, entry, "chrome-linux", "chrome"),
|
|
43839
|
+
join63(cacheDir, entry, "chrome-linux64", "headless_shell"),
|
|
43840
|
+
join63(cacheDir, entry, "chrome-linux", "headless_shell")
|
|
43784
43841
|
];
|
|
43785
43842
|
for (const path4 of candidates2) {
|
|
43786
|
-
if (
|
|
43843
|
+
if (existsSync63(path4))
|
|
43787
43844
|
return path4;
|
|
43788
43845
|
}
|
|
43789
43846
|
}
|
|
@@ -43805,7 +43862,7 @@ function checkChromium() {
|
|
|
43805
43862
|
}
|
|
43806
43863
|
function checkDepsCacheWritable(depsRoot = resolvePath("~/.switchroom/deps")) {
|
|
43807
43864
|
try {
|
|
43808
|
-
|
|
43865
|
+
mkdirSync33(depsRoot, { recursive: true });
|
|
43809
43866
|
accessSync2(depsRoot, fsConstants5.W_OK);
|
|
43810
43867
|
return {
|
|
43811
43868
|
name: "~/.switchroom/deps writable",
|
|
@@ -43912,7 +43969,7 @@ function checkDeployMounts(opts) {
|
|
|
43912
43969
|
const home2 = opts?.home ?? process.env.HOME ?? "/root";
|
|
43913
43970
|
const { pathKind } = opts?.deps ?? DEFAULT_DEPLOY_MOUNTS_DEPS;
|
|
43914
43971
|
const results = [];
|
|
43915
|
-
const dockerComposePlugin =
|
|
43972
|
+
const dockerComposePlugin = join63(home2, ".docker", "cli-plugins", "docker-compose");
|
|
43916
43973
|
const pluginKind = pathKind(dockerComposePlugin);
|
|
43917
43974
|
if (pluginKind === "dir") {
|
|
43918
43975
|
results.push({
|
|
@@ -43950,8 +44007,8 @@ function checkDeployMounts(opts) {
|
|
|
43950
44007
|
function checkLegacyState() {
|
|
43951
44008
|
const results = [];
|
|
43952
44009
|
const h = process.env.HOME ?? "/root";
|
|
43953
|
-
const clerkDir =
|
|
43954
|
-
const clerkPresent =
|
|
44010
|
+
const clerkDir = join63(h, LEGACY_STATE_DIR);
|
|
44011
|
+
const clerkPresent = existsSync63(clerkDir);
|
|
43955
44012
|
results.push({
|
|
43956
44013
|
name: "legacy ~/.clerk state",
|
|
43957
44014
|
status: clerkPresent ? "warn" : "ok",
|
|
@@ -43960,7 +44017,7 @@ function checkLegacyState() {
|
|
|
43960
44017
|
fix: "Legacy state detected. Run `mv ~/.clerk ~/.switchroom` and rename " + "any top-level `clerk:` key in switchroom.yaml to `switchroom:`. " + "This back-compat shim is REMOVED in v0.13.0 \u2014 no automatic " + "migration exists."
|
|
43961
44018
|
} : {}
|
|
43962
44019
|
});
|
|
43963
|
-
const legacySock =
|
|
44020
|
+
const legacySock = join63(h, ".switchroom", "vault-broker.sock");
|
|
43964
44021
|
let sockStat = null;
|
|
43965
44022
|
try {
|
|
43966
44023
|
sockStat = lstatSync7(legacySock);
|
|
@@ -44081,7 +44138,7 @@ function checkVault(config) {
|
|
|
44081
44138
|
detail: "Approval auth: passphrase (two-factor)"
|
|
44082
44139
|
};
|
|
44083
44140
|
const pairsResult = checkVaultBrokerSocketPairs(config);
|
|
44084
|
-
if (!
|
|
44141
|
+
if (!existsSync63(vaultPath)) {
|
|
44085
44142
|
return [
|
|
44086
44143
|
postureResult,
|
|
44087
44144
|
{
|
|
@@ -44359,8 +44416,8 @@ async function checkHindsight(config) {
|
|
|
44359
44416
|
}
|
|
44360
44417
|
function checkPendingRetainsQueue(dir) {
|
|
44361
44418
|
const home2 = process.env.HOME ?? "";
|
|
44362
|
-
const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ??
|
|
44363
|
-
if (!
|
|
44419
|
+
const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ?? join63(home2, ".hindsight", "pending-retains");
|
|
44420
|
+
if (!existsSync63(pendingDir)) {
|
|
44364
44421
|
return {
|
|
44365
44422
|
name: "pending-retains queue",
|
|
44366
44423
|
status: "ok",
|
|
@@ -44419,7 +44476,7 @@ function classifyReadError(err) {
|
|
|
44419
44476
|
}
|
|
44420
44477
|
function tryReadHostFile(path4) {
|
|
44421
44478
|
try {
|
|
44422
|
-
return { kind: "ok", content:
|
|
44479
|
+
return { kind: "ok", content: readFileSync56(path4, "utf-8") };
|
|
44423
44480
|
} catch (err) {
|
|
44424
44481
|
const kind = classifyReadError(err);
|
|
44425
44482
|
const error = err?.message ?? String(err);
|
|
@@ -44431,11 +44488,11 @@ function tryReadHostFile(path4) {
|
|
|
44431
44488
|
}
|
|
44432
44489
|
}
|
|
44433
44490
|
function parseEnvFile(path4) {
|
|
44434
|
-
if (!
|
|
44491
|
+
if (!existsSync63(path4))
|
|
44435
44492
|
return {};
|
|
44436
44493
|
let content;
|
|
44437
44494
|
try {
|
|
44438
|
-
content =
|
|
44495
|
+
content = readFileSync56(path4, "utf-8");
|
|
44439
44496
|
} catch {
|
|
44440
44497
|
return {};
|
|
44441
44498
|
}
|
|
@@ -44490,7 +44547,7 @@ async function checkTelegram(config) {
|
|
|
44490
44547
|
const plugin = agentConfig.channels?.telegram?.plugin ?? "switchroom";
|
|
44491
44548
|
if (plugin !== "switchroom")
|
|
44492
44549
|
continue;
|
|
44493
|
-
const envPath =
|
|
44550
|
+
const envPath = join63(agentsDir, name, "telegram", ".env");
|
|
44494
44551
|
const read = tryReadHostFile(envPath);
|
|
44495
44552
|
if (read.kind === "eacces") {
|
|
44496
44553
|
results.push({
|
|
@@ -44542,7 +44599,7 @@ async function checkTelegram(config) {
|
|
|
44542
44599
|
}
|
|
44543
44600
|
function checkStartShStale(agentName, startShPath) {
|
|
44544
44601
|
const label = `${agentName}: start.sh scheduler block`;
|
|
44545
|
-
if (!
|
|
44602
|
+
if (!existsSync63(startShPath)) {
|
|
44546
44603
|
return {
|
|
44547
44604
|
name: label,
|
|
44548
44605
|
status: "warn",
|
|
@@ -44552,7 +44609,7 @@ function checkStartShStale(agentName, startShPath) {
|
|
|
44552
44609
|
}
|
|
44553
44610
|
let content;
|
|
44554
44611
|
try {
|
|
44555
|
-
content =
|
|
44612
|
+
content = readFileSync56(startShPath, "utf-8");
|
|
44556
44613
|
} catch (err) {
|
|
44557
44614
|
return {
|
|
44558
44615
|
name: label,
|
|
@@ -44573,7 +44630,7 @@ function checkStartShStale(agentName, startShPath) {
|
|
|
44573
44630
|
}
|
|
44574
44631
|
function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
44575
44632
|
const label = `${agentName}: $HOME/.switchroom symlink (#910)`;
|
|
44576
|
-
const path4 =
|
|
44633
|
+
const path4 = join63(agentDir, "home", ".switchroom");
|
|
44577
44634
|
let stats;
|
|
44578
44635
|
try {
|
|
44579
44636
|
stats = lstatSync7(path4);
|
|
@@ -44610,8 +44667,8 @@ function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
|
44610
44667
|
}
|
|
44611
44668
|
function checkRepoHygiene(repoRoot) {
|
|
44612
44669
|
const results = [];
|
|
44613
|
-
const exportDir =
|
|
44614
|
-
if (
|
|
44670
|
+
const exportDir = join63(repoRoot, "clerk-export");
|
|
44671
|
+
if (existsSync63(exportDir)) {
|
|
44615
44672
|
results.push({
|
|
44616
44673
|
name: "repo hygiene: clerk-export/ on disk (#1072)",
|
|
44617
44674
|
status: "warn",
|
|
@@ -44619,8 +44676,8 @@ function checkRepoHygiene(repoRoot) {
|
|
|
44619
44676
|
fix: `Run scripts/migrate-clerk-export-to-vault.sh to move the bundle ` + `into the vault, then delete the on-disk copy.`
|
|
44620
44677
|
});
|
|
44621
44678
|
}
|
|
44622
|
-
const knownTarball =
|
|
44623
|
-
if (
|
|
44679
|
+
const knownTarball = join63(repoRoot, "clerk-export-with-secrets.tar.gz");
|
|
44680
|
+
if (existsSync63(knownTarball)) {
|
|
44624
44681
|
results.push({
|
|
44625
44682
|
name: "repo hygiene: clerk-export-with-secrets.tar.gz on disk (#1072)",
|
|
44626
44683
|
status: "warn",
|
|
@@ -44637,7 +44694,7 @@ function checkRepoHygiene(repoRoot) {
|
|
|
44637
44694
|
results.push({
|
|
44638
44695
|
name: `repo hygiene: ${name} on disk (#1072)`,
|
|
44639
44696
|
status: "warn",
|
|
44640
|
-
detail: `${
|
|
44697
|
+
detail: `${join63(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
|
|
44641
44698
|
fix: `Inspect, migrate any secrets into the vault, then delete the ` + `archive.`
|
|
44642
44699
|
});
|
|
44643
44700
|
}
|
|
@@ -44660,12 +44717,12 @@ function checkRepoHygiene(repoRoot) {
|
|
|
44660
44717
|
}
|
|
44661
44718
|
function isSwitchroomCheckout(dir) {
|
|
44662
44719
|
try {
|
|
44663
|
-
if (!
|
|
44720
|
+
if (!existsSync63(join63(dir, ".git")))
|
|
44664
44721
|
return false;
|
|
44665
|
-
const pkgPath =
|
|
44666
|
-
if (!
|
|
44722
|
+
const pkgPath = join63(dir, "package.json");
|
|
44723
|
+
if (!existsSync63(pkgPath))
|
|
44667
44724
|
return false;
|
|
44668
|
-
const pkg = JSON.parse(
|
|
44725
|
+
const pkg = JSON.parse(readFileSync56(pkgPath, "utf-8"));
|
|
44669
44726
|
return pkg.name === "switchroom";
|
|
44670
44727
|
} catch {
|
|
44671
44728
|
return false;
|
|
@@ -44678,7 +44735,7 @@ function checkAgents(config, configPath) {
|
|
|
44678
44735
|
const authStatuses = getAllAuthStatuses(config);
|
|
44679
44736
|
for (const [name, agentConfig] of Object.entries(config.agents)) {
|
|
44680
44737
|
const agentDir = resolve37(agentsDir, name);
|
|
44681
|
-
if (!
|
|
44738
|
+
if (!existsSync63(agentDir)) {
|
|
44682
44739
|
results.push({
|
|
44683
44740
|
name: `${name}: scaffold`,
|
|
44684
44741
|
status: "fail",
|
|
@@ -44699,7 +44756,7 @@ function checkAgents(config, configPath) {
|
|
|
44699
44756
|
fix: `Rotate the bot token (e.g. via \`switchroom vault\`), then run ` + `\`switchroom agent unquarantine ${name}\` and \`switchroom agent restart ${name}\``
|
|
44700
44757
|
});
|
|
44701
44758
|
}
|
|
44702
|
-
results.push(checkStartShStale(name,
|
|
44759
|
+
results.push(checkStartShStale(name, join63(agentDir, "start.sh")));
|
|
44703
44760
|
results.push(checkLeakedHomeSwitchroom(name, agentDir));
|
|
44704
44761
|
const status = statuses[name];
|
|
44705
44762
|
const active = status?.active ?? "unknown";
|
|
@@ -44776,8 +44833,8 @@ function checkAgents(config, configPath) {
|
|
|
44776
44833
|
}
|
|
44777
44834
|
}
|
|
44778
44835
|
if (agentConfig.channels?.telegram?.plugin === "switchroom") {
|
|
44779
|
-
const mcpJsonPath =
|
|
44780
|
-
if (!
|
|
44836
|
+
const mcpJsonPath = join63(agentDir, ".mcp.json");
|
|
44837
|
+
if (!existsSync63(mcpJsonPath)) {
|
|
44781
44838
|
results.push({
|
|
44782
44839
|
name: `${name}: .mcp.json`,
|
|
44783
44840
|
status: "fail",
|
|
@@ -44786,7 +44843,7 @@ function checkAgents(config, configPath) {
|
|
|
44786
44843
|
});
|
|
44787
44844
|
} else {
|
|
44788
44845
|
try {
|
|
44789
|
-
const mcp = JSON.parse(
|
|
44846
|
+
const mcp = JSON.parse(readFileSync56(mcpJsonPath, "utf-8"));
|
|
44790
44847
|
const hasSwitchroomTelegram = !!mcp.mcpServers?.["switchroom-telegram"];
|
|
44791
44848
|
const memoryEnabled = isHindsightEnabled(config);
|
|
44792
44849
|
const hasHindsight = !!mcp.mcpServers?.hindsight;
|
|
@@ -44863,7 +44920,7 @@ function mffEnvPath(config) {
|
|
|
44863
44920
|
return agent ? resolve37(home2, ".switchroom/credentials", agent, "my-family-finance/.env") : resolve37(home2, ".switchroom/credentials/my-family-finance/.env");
|
|
44864
44921
|
}
|
|
44865
44922
|
function mffEnvState(envPath) {
|
|
44866
|
-
if (!
|
|
44923
|
+
if (!existsSync63(envPath))
|
|
44867
44924
|
return "absent";
|
|
44868
44925
|
try {
|
|
44869
44926
|
accessSync2(envPath, fsConstants5.R_OK);
|
|
@@ -44881,7 +44938,7 @@ function checkMffVaultKeyPresent(passphrase, vaultPath) {
|
|
|
44881
44938
|
fix: "Export SWITCHROOM_VAULT_PASSPHRASE to enable MFF vault probes"
|
|
44882
44939
|
};
|
|
44883
44940
|
}
|
|
44884
|
-
if (!
|
|
44941
|
+
if (!existsSync63(vaultPath)) {
|
|
44885
44942
|
return {
|
|
44886
44943
|
name: "mff: vault key present",
|
|
44887
44944
|
status: "fail",
|
|
@@ -44934,7 +44991,7 @@ function deriveEd25519PublicKeyBytes(keyMaterial) {
|
|
|
44934
44991
|
}
|
|
44935
44992
|
}
|
|
44936
44993
|
function checkMffVaultKeyFormat(passphrase, vaultPath) {
|
|
44937
|
-
if (!passphrase || !
|
|
44994
|
+
if (!passphrase || !existsSync63(vaultPath)) {
|
|
44938
44995
|
return {
|
|
44939
44996
|
name: "mff: vault key format",
|
|
44940
44997
|
status: "warn",
|
|
@@ -45077,9 +45134,9 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
|
|
|
45077
45134
|
detail: "skipped (MFF_API_URL not set)"
|
|
45078
45135
|
};
|
|
45079
45136
|
}
|
|
45080
|
-
const credDir =
|
|
45081
|
-
const authScript =
|
|
45082
|
-
if (!
|
|
45137
|
+
const credDir = dirname19(envPath);
|
|
45138
|
+
const authScript = join63(credDir, "claude-auth.py");
|
|
45139
|
+
if (!existsSync63(authScript)) {
|
|
45083
45140
|
return {
|
|
45084
45141
|
name: "mff: auth flow",
|
|
45085
45142
|
status: "warn",
|
|
@@ -45281,11 +45338,11 @@ function runDockerSection(config) {
|
|
|
45281
45338
|
let composeYaml;
|
|
45282
45339
|
let dockerfileAgent;
|
|
45283
45340
|
try {
|
|
45284
|
-
composeYaml =
|
|
45341
|
+
composeYaml = readFileSync56(composePath, "utf8");
|
|
45285
45342
|
} catch {}
|
|
45286
45343
|
const dockerfilePath = resolve37(process.env.HOME ?? "", ".switchroom", "docker", "Dockerfile.agent");
|
|
45287
45344
|
try {
|
|
45288
|
-
dockerfileAgent =
|
|
45345
|
+
dockerfileAgent = readFileSync56(dockerfilePath, "utf8");
|
|
45289
45346
|
} catch {}
|
|
45290
45347
|
return runDockerChecks({
|
|
45291
45348
|
config,
|
|
@@ -48605,8 +48662,8 @@ agents:
|
|
|
48605
48662
|
var init_minimal = () => {};
|
|
48606
48663
|
|
|
48607
48664
|
// src/agents/connection-health.ts
|
|
48608
|
-
import { mkdirSync as
|
|
48609
|
-
import { join as
|
|
48665
|
+
import { mkdirSync as mkdirSync44, writeFileSync as writeFileSync36 } from "node:fs";
|
|
48666
|
+
import { join as join78 } from "node:path";
|
|
48610
48667
|
async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
|
|
48611
48668
|
const reqs = computeMcpSecretRequirements(config).filter((r) => r.agent === agentName);
|
|
48612
48669
|
if (reqs.length === 0)
|
|
@@ -48663,10 +48720,10 @@ async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
|
|
|
48663
48720
|
return issues;
|
|
48664
48721
|
}
|
|
48665
48722
|
function writeConnectionHealthFile(agentDir, health, deps) {
|
|
48666
|
-
const dir =
|
|
48667
|
-
const path7 =
|
|
48668
|
-
(deps?.mkdir ?? ((p, o) =>
|
|
48669
|
-
(deps?.writeFile ?? ((p, d) =>
|
|
48723
|
+
const dir = join78(agentDir, ".claude");
|
|
48724
|
+
const path7 = join78(dir, CONNECTION_HEALTH_FILENAME);
|
|
48725
|
+
(deps?.mkdir ?? ((p, o) => mkdirSync44(p, o)))(dir, { recursive: true });
|
|
48726
|
+
(deps?.writeFile ?? ((p, d) => writeFileSync36(p, d)))(path7, JSON.stringify(health, null, 2) + `
|
|
48670
48727
|
`);
|
|
48671
48728
|
}
|
|
48672
48729
|
async function refreshAgentConnectionHealth(config, agentName, agentDir, deps) {
|
|
@@ -48687,10 +48744,10 @@ var CONNECTION_HEALTH_FILENAME = "connection-health.json";
|
|
|
48687
48744
|
var init_connection_health = () => {};
|
|
48688
48745
|
|
|
48689
48746
|
// src/cli/update-prompt-hook.ts
|
|
48690
|
-
import { existsSync as
|
|
48691
|
-
import { join as
|
|
48747
|
+
import { existsSync as existsSync80, readFileSync as readFileSync68, writeFileSync as writeFileSync37, chmodSync as chmodSync10, mkdirSync as mkdirSync45 } from "node:fs";
|
|
48748
|
+
import { join as join79 } from "node:path";
|
|
48692
48749
|
function containerHookCommand() {
|
|
48693
|
-
return
|
|
48750
|
+
return join79(CONTAINER_AGENT_DIR, ".claude", "hooks", HOOK_FILENAME);
|
|
48694
48751
|
}
|
|
48695
48752
|
function updatePromptHookScript() {
|
|
48696
48753
|
return `#!/bin/bash
|
|
@@ -48756,14 +48813,14 @@ exit 0
|
|
|
48756
48813
|
`;
|
|
48757
48814
|
}
|
|
48758
48815
|
function installUpdatePromptHook(agentDir) {
|
|
48759
|
-
const hooksDir =
|
|
48760
|
-
|
|
48761
|
-
const scriptPath =
|
|
48816
|
+
const hooksDir = join79(agentDir, ".claude", "hooks");
|
|
48817
|
+
mkdirSync45(hooksDir, { recursive: true });
|
|
48818
|
+
const scriptPath = join79(hooksDir, HOOK_FILENAME);
|
|
48762
48819
|
const desired = updatePromptHookScript();
|
|
48763
48820
|
let installed = false;
|
|
48764
|
-
const existing =
|
|
48821
|
+
const existing = existsSync80(scriptPath) ? readFileSync68(scriptPath, "utf-8") : "";
|
|
48765
48822
|
if (existing !== desired) {
|
|
48766
|
-
|
|
48823
|
+
writeFileSync37(scriptPath, desired, { mode: 493 });
|
|
48767
48824
|
chmodSync10(scriptPath, 493);
|
|
48768
48825
|
installed = true;
|
|
48769
48826
|
} else {
|
|
@@ -48771,11 +48828,11 @@ function installUpdatePromptHook(agentDir) {
|
|
|
48771
48828
|
chmodSync10(scriptPath, 493);
|
|
48772
48829
|
} catch {}
|
|
48773
48830
|
}
|
|
48774
|
-
const settingsPath =
|
|
48775
|
-
if (!
|
|
48831
|
+
const settingsPath = join79(agentDir, ".claude", "settings.json");
|
|
48832
|
+
if (!existsSync80(settingsPath)) {
|
|
48776
48833
|
return { scriptPath, settingsPath, installed };
|
|
48777
48834
|
}
|
|
48778
|
-
const raw =
|
|
48835
|
+
const raw = readFileSync68(settingsPath, "utf-8");
|
|
48779
48836
|
let parsed;
|
|
48780
48837
|
try {
|
|
48781
48838
|
parsed = JSON.parse(raw);
|
|
@@ -48811,7 +48868,7 @@ function installUpdatePromptHook(agentDir) {
|
|
|
48811
48868
|
if (mutated) {
|
|
48812
48869
|
hooks.UserPromptSubmit = list2;
|
|
48813
48870
|
parsed.hooks = hooks;
|
|
48814
|
-
|
|
48871
|
+
writeFileSync37(settingsPath, JSON.stringify(parsed, null, 2) + `
|
|
48815
48872
|
`, { mode: 384 });
|
|
48816
48873
|
installed = true;
|
|
48817
48874
|
} else if (!alreadyCorrect) {
|
|
@@ -48820,7 +48877,7 @@ function installUpdatePromptHook(agentDir) {
|
|
|
48820
48877
|
});
|
|
48821
48878
|
hooks.UserPromptSubmit = list2;
|
|
48822
48879
|
parsed.hooks = hooks;
|
|
48823
|
-
|
|
48880
|
+
writeFileSync37(settingsPath, JSON.stringify(parsed, null, 2) + `
|
|
48824
48881
|
`, { mode: 384 });
|
|
48825
48882
|
installed = true;
|
|
48826
48883
|
}
|
|
@@ -48883,6 +48940,7 @@ var init_install_detect = () => {};
|
|
|
48883
48940
|
// src/litellm/provision.ts
|
|
48884
48941
|
var exports_provision = {};
|
|
48885
48942
|
__export(exports_provision, {
|
|
48943
|
+
validateKey: () => validateKey,
|
|
48886
48944
|
ensureTeam: () => ensureTeam,
|
|
48887
48945
|
ensureKey: () => ensureKey,
|
|
48888
48946
|
LiteLLMProvisionError: () => LiteLLMProvisionError
|
|
@@ -48904,6 +48962,12 @@ function looksLikeAlreadyExists(status, body) {
|
|
|
48904
48962
|
return true;
|
|
48905
48963
|
return false;
|
|
48906
48964
|
}
|
|
48965
|
+
function looksLikeUnknownKey(status, body) {
|
|
48966
|
+
if (status !== 400 && status !== 401 && status !== 403 && status !== 404)
|
|
48967
|
+
return false;
|
|
48968
|
+
const t = body.toLowerCase();
|
|
48969
|
+
return t.includes("not found") || t.includes("does not exist") || t.includes("no such key") || t.includes("no key found");
|
|
48970
|
+
}
|
|
48907
48971
|
async function ensureTeam(baseUrl, masterKey, teamName, fetchFn = fetch) {
|
|
48908
48972
|
const url = `${normalizeBase(baseUrl)}/team/new`;
|
|
48909
48973
|
let resp;
|
|
@@ -48923,7 +48987,7 @@ async function ensureTeam(baseUrl, masterKey, teamName, fetchFn = fetch) {
|
|
|
48923
48987
|
return;
|
|
48924
48988
|
throw new LiteLLMProvisionError(`LiteLLM /team/new returned ${resp.status}`, resp.status, body);
|
|
48925
48989
|
}
|
|
48926
|
-
async function
|
|
48990
|
+
async function generateKeyOnce(opts, fetchFn) {
|
|
48927
48991
|
const url = `${normalizeBase(opts.baseUrl)}/key/generate`;
|
|
48928
48992
|
const payload = {
|
|
48929
48993
|
key_alias: opts.alias
|
|
@@ -48943,23 +49007,129 @@ async function ensureKey(opts, fetchFn = fetch) {
|
|
|
48943
49007
|
body: JSON.stringify(payload)
|
|
48944
49008
|
});
|
|
48945
49009
|
} catch (err) {
|
|
48946
|
-
|
|
49010
|
+
return {
|
|
49011
|
+
kind: "error",
|
|
49012
|
+
error: new LiteLLMProvisionError(`LiteLLM /key/generate request failed: ${err.message}`)
|
|
49013
|
+
};
|
|
48947
49014
|
}
|
|
48948
49015
|
if (!resp.ok) {
|
|
48949
49016
|
const body = await safeText(resp);
|
|
48950
|
-
|
|
49017
|
+
if (looksLikeAlreadyExists(resp.status, body)) {
|
|
49018
|
+
return { kind: "duplicate-alias", status: resp.status, body };
|
|
49019
|
+
}
|
|
49020
|
+
return {
|
|
49021
|
+
kind: "error",
|
|
49022
|
+
error: new LiteLLMProvisionError(`LiteLLM /key/generate returned ${resp.status}`, resp.status, body)
|
|
49023
|
+
};
|
|
48951
49024
|
}
|
|
48952
49025
|
let json;
|
|
48953
49026
|
try {
|
|
48954
49027
|
json = await resp.json();
|
|
48955
49028
|
} catch (err) {
|
|
48956
|
-
|
|
49029
|
+
return {
|
|
49030
|
+
kind: "error",
|
|
49031
|
+
error: new LiteLLMProvisionError(`LiteLLM /key/generate returned non-JSON body: ${err.message}`, resp.status)
|
|
49032
|
+
};
|
|
48957
49033
|
}
|
|
48958
49034
|
const key = json?.key;
|
|
48959
49035
|
if (typeof key !== "string" || key.length === 0) {
|
|
48960
|
-
|
|
49036
|
+
return {
|
|
49037
|
+
kind: "error",
|
|
49038
|
+
error: new LiteLLMProvisionError(`LiteLLM /key/generate response missing a "key" field`, resp.status, JSON.stringify(json))
|
|
49039
|
+
};
|
|
49040
|
+
}
|
|
49041
|
+
return { kind: "ok", key };
|
|
49042
|
+
}
|
|
49043
|
+
async function resolveTokensForAlias(baseUrl, masterKey, alias, fetchFn) {
|
|
49044
|
+
const url = `${normalizeBase(baseUrl)}/key/list?key_alias=${encodeURIComponent(alias)}` + `&return_full_object=true&include_team_keys=true`;
|
|
49045
|
+
let resp;
|
|
49046
|
+
try {
|
|
49047
|
+
resp = await fetchFn(url, { method: "GET", headers: authHeaders3(masterKey) });
|
|
49048
|
+
} catch {
|
|
49049
|
+
return [];
|
|
49050
|
+
}
|
|
49051
|
+
if (!resp.ok)
|
|
49052
|
+
return [];
|
|
49053
|
+
let json;
|
|
49054
|
+
try {
|
|
49055
|
+
json = await resp.json();
|
|
49056
|
+
} catch {
|
|
49057
|
+
return [];
|
|
49058
|
+
}
|
|
49059
|
+
const keys = json?.keys;
|
|
49060
|
+
if (!Array.isArray(keys))
|
|
49061
|
+
return [];
|
|
49062
|
+
const tokens = [];
|
|
49063
|
+
for (const k of keys) {
|
|
49064
|
+
if (typeof k === "string" && k.length > 0)
|
|
49065
|
+
tokens.push(k);
|
|
49066
|
+
else if (k && typeof k === "object") {
|
|
49067
|
+
const tok = k.token;
|
|
49068
|
+
const raw = k.key;
|
|
49069
|
+
if (typeof tok === "string" && tok.length > 0)
|
|
49070
|
+
tokens.push(tok);
|
|
49071
|
+
else if (typeof raw === "string" && raw.length > 0)
|
|
49072
|
+
tokens.push(raw);
|
|
49073
|
+
}
|
|
49074
|
+
}
|
|
49075
|
+
return tokens;
|
|
49076
|
+
}
|
|
49077
|
+
async function deleteOrphanedAlias(opts, fetchFn, log) {
|
|
49078
|
+
const tokens = await resolveTokensForAlias(opts.baseUrl, opts.masterKey, opts.alias, fetchFn);
|
|
49079
|
+
const body = { key_aliases: [opts.alias] };
|
|
49080
|
+
if (tokens.length > 0)
|
|
49081
|
+
body.keys = tokens;
|
|
49082
|
+
const manual = `Manual remediation: delete the key with alias '${opts.alias}' in the LiteLLM ` + `admin UI, or run ` + `\`curl -X POST "${normalizeBase(opts.baseUrl)}/key/delete" -H "Authorization: Bearer <master-key>" ` + `-H "content-type: application/json" -d '{"key_aliases":["${opts.alias}"]}'\`, ` + `then re-run \`switchroom apply\`.`;
|
|
49083
|
+
const url = `${normalizeBase(opts.baseUrl)}/key/delete`;
|
|
49084
|
+
let resp;
|
|
49085
|
+
try {
|
|
49086
|
+
resp = await fetchFn(url, {
|
|
49087
|
+
method: "POST",
|
|
49088
|
+
headers: authHeaders3(opts.masterKey),
|
|
49089
|
+
body: JSON.stringify(body)
|
|
49090
|
+
});
|
|
49091
|
+
} catch (err) {
|
|
49092
|
+
throw new LiteLLMProvisionError(`LiteLLM /key/delete request failed while recovering orphaned alias ` + `'${opts.alias}': ${err.message}. ${manual}`);
|
|
49093
|
+
}
|
|
49094
|
+
if (!resp.ok) {
|
|
49095
|
+
const errBody = await safeText(resp);
|
|
49096
|
+
throw new LiteLLMProvisionError(`LiteLLM /key/delete returned ${resp.status} while recovering orphaned ` + `alias '${opts.alias}'. ${manual}`, resp.status, errBody);
|
|
49097
|
+
}
|
|
49098
|
+
log(`litellm: deleted orphaned key(s) for alias '${opts.alias}'` + (tokens.length > 0 ? ` (${tokens.length} token(s) resolved)` : "") + ` \u2014 regenerating`);
|
|
49099
|
+
}
|
|
49100
|
+
async function ensureKey(opts, fetchFn = fetch) {
|
|
49101
|
+
const log = opts.log ?? (() => {});
|
|
49102
|
+
const first = await generateKeyOnce(opts, fetchFn);
|
|
49103
|
+
if (first.kind === "ok")
|
|
49104
|
+
return { key: first.key };
|
|
49105
|
+
if (first.kind === "error")
|
|
49106
|
+
throw first.error;
|
|
49107
|
+
log(`litellm: key_alias '${opts.alias}' already exists in LiteLLM but the vault ` + `has no recoverable key (orphaned by a prior failed provision) \u2014 ` + `self-healing: deleting the orphan and regenerating`);
|
|
49108
|
+
await deleteOrphanedAlias(opts, fetchFn, log);
|
|
49109
|
+
const second = await generateKeyOnce(opts, fetchFn);
|
|
49110
|
+
if (second.kind === "ok") {
|
|
49111
|
+
log(`litellm: regenerated key for alias '${opts.alias}' after orphan recovery`);
|
|
49112
|
+
return { key: second.key };
|
|
49113
|
+
}
|
|
49114
|
+
if (second.kind === "duplicate-alias") {
|
|
49115
|
+
throw new LiteLLMProvisionError(`LiteLLM /key/generate still reports alias '${opts.alias}' already exists ` + `after deleting the orphan \u2014 the delete did not take effect. Manual ` + `remediation: remove the key with alias '${opts.alias}' via the LiteLLM ` + `admin UI (or POST /key/delete {"key_aliases":["${opts.alias}"]}) then ` + `re-run \`switchroom apply\`.`, second.status, second.body);
|
|
49116
|
+
}
|
|
49117
|
+
throw second.error;
|
|
49118
|
+
}
|
|
49119
|
+
async function validateKey(opts, fetchFn = fetch) {
|
|
49120
|
+
const url = `${normalizeBase(opts.baseUrl)}/key/info?key=${encodeURIComponent(opts.key)}`;
|
|
49121
|
+
let resp;
|
|
49122
|
+
try {
|
|
49123
|
+
resp = await fetchFn(url, { method: "GET", headers: authHeaders3(opts.masterKey) });
|
|
49124
|
+
} catch (err) {
|
|
49125
|
+
return { kind: "unreachable", detail: err.message };
|
|
48961
49126
|
}
|
|
48962
|
-
|
|
49127
|
+
if (resp.ok)
|
|
49128
|
+
return { kind: "valid" };
|
|
49129
|
+
const body = await safeText(resp);
|
|
49130
|
+
if (looksLikeUnknownKey(resp.status, body))
|
|
49131
|
+
return { kind: "unknown" };
|
|
49132
|
+
return { kind: "unreachable", detail: `HTTP ${resp.status}: ${body.slice(0, 200)}` };
|
|
48963
49133
|
}
|
|
48964
49134
|
async function safeText(resp) {
|
|
48965
49135
|
try {
|
|
@@ -48999,8 +49169,8 @@ __export(exports_voice_sidecar_token, {
|
|
|
48999
49169
|
VOICE_SIDECAR_TOKEN_ENV: () => VOICE_SIDECAR_TOKEN_ENV
|
|
49000
49170
|
});
|
|
49001
49171
|
import { randomBytes as randomBytes14 } from "node:crypto";
|
|
49002
|
-
import { chmodSync as chmodSync11, chownSync as chownSync7, existsSync as
|
|
49003
|
-
import { dirname as
|
|
49172
|
+
import { chmodSync as chmodSync11, chownSync as chownSync7, existsSync as existsSync82, mkdirSync as mkdirSync46, readFileSync as readFileSync69, rmSync as rmSync17, writeFileSync as writeFileSync38 } from "node:fs";
|
|
49173
|
+
import { dirname as dirname28 } from "node:path";
|
|
49004
49174
|
async function defaultResolveOrSeedToken(home2, writeErr) {
|
|
49005
49175
|
const [{ getViaBrokerStructured: getViaBrokerStructured2, putViaBroker: putViaBroker2 }, { resolveOperatorVaultPassphrase }] = await Promise.all([
|
|
49006
49176
|
Promise.resolve().then(() => (init_client(), exports_client)),
|
|
@@ -49034,8 +49204,8 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
|
|
|
49034
49204
|
const envPath = composeEnvPath(composePath);
|
|
49035
49205
|
if (engine !== "local") {
|
|
49036
49206
|
try {
|
|
49037
|
-
if (
|
|
49038
|
-
const body =
|
|
49207
|
+
if (existsSync82(envPath)) {
|
|
49208
|
+
const body = readFileSync69(envPath, "utf-8");
|
|
49039
49209
|
if (body.includes(`${VOICE_SIDECAR_TOKEN_ENV}=`))
|
|
49040
49210
|
rmSync17(envPath);
|
|
49041
49211
|
}
|
|
@@ -49054,11 +49224,11 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
|
|
|
49054
49224
|
if (!token)
|
|
49055
49225
|
return;
|
|
49056
49226
|
try {
|
|
49057
|
-
|
|
49227
|
+
mkdirSync46(dirname28(envPath), { recursive: true });
|
|
49058
49228
|
let body = "";
|
|
49059
49229
|
try {
|
|
49060
|
-
if (
|
|
49061
|
-
body =
|
|
49230
|
+
if (existsSync82(envPath))
|
|
49231
|
+
body = readFileSync69(envPath, "utf-8");
|
|
49062
49232
|
} catch {}
|
|
49063
49233
|
const line = `${VOICE_SIDECAR_TOKEN_ENV}=${token}`;
|
|
49064
49234
|
const keyRe = new RegExp(`^${VOICE_SIDECAR_TOKEN_ENV}=.*$`, "m");
|
|
@@ -49066,7 +49236,7 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
|
|
|
49066
49236
|
`) ? body + `
|
|
49067
49237
|
` : body) + line + `
|
|
49068
49238
|
`;
|
|
49069
|
-
|
|
49239
|
+
writeFileSync38(envPath, next, {
|
|
49070
49240
|
encoding: "utf-8",
|
|
49071
49241
|
mode: 384
|
|
49072
49242
|
});
|
|
@@ -49116,12 +49286,12 @@ __export(exports_apply, {
|
|
|
49116
49286
|
DEFAULT_COMPOSE_PATH: () => DEFAULT_COMPOSE_PATH2,
|
|
49117
49287
|
COMPOSE_PROJECT: () => COMPOSE_PROJECT2
|
|
49118
49288
|
});
|
|
49119
|
-
import { accessSync as accessSync3, chownSync as chownSync8, constants as fsConstants6, copyFileSync as copyFileSync12, existsSync as
|
|
49289
|
+
import { accessSync as accessSync3, chownSync as chownSync8, constants as fsConstants6, copyFileSync as copyFileSync12, existsSync as existsSync83, mkdirSync as mkdirSync47, readFileSync as readFileSync70, readdirSync as readdirSync28, renameSync as renameSync18, writeFileSync as writeFileSync39 } from "node:fs";
|
|
49120
49290
|
import { mkdir as mkdir2 } from "node:fs/promises";
|
|
49121
49291
|
import { spawnSync as childSpawnSync } from "node:child_process";
|
|
49122
49292
|
import readline from "node:readline";
|
|
49123
|
-
import { dirname as
|
|
49124
|
-
import { homedir as
|
|
49293
|
+
import { dirname as dirname29, join as join81, resolve as resolve50 } from "node:path";
|
|
49294
|
+
import { homedir as homedir48 } from "node:os";
|
|
49125
49295
|
import { execFileSync as execFileSync25 } from "node:child_process";
|
|
49126
49296
|
function effectiveLiteLLMEnabled(config, agentResolvedLitellm) {
|
|
49127
49297
|
return agentResolvedLitellm?.enabled ?? config.litellm?.enabled ?? false;
|
|
@@ -49132,7 +49302,7 @@ async function resolveOperatorVaultPassphrase(home2) {
|
|
|
49132
49302
|
return envPass;
|
|
49133
49303
|
try {
|
|
49134
49304
|
const { readAutoUnlockFile: readAutoUnlockFile2 } = await Promise.resolve().then(() => (init_auto_unlock(), exports_auto_unlock));
|
|
49135
|
-
const blobPath =
|
|
49305
|
+
const blobPath = join81(home2, ".switchroom", "vault-auto-unlock");
|
|
49136
49306
|
const pass = readAutoUnlockFile2(blobPath);
|
|
49137
49307
|
return pass && pass.length > 0 ? pass : null;
|
|
49138
49308
|
} catch {
|
|
@@ -49154,12 +49324,12 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
49154
49324
|
const needsHindsight = config.litellm?.enabled === true && config.memory?.backend === "hindsight";
|
|
49155
49325
|
if (optedIn.length === 0 && !needsHindsight)
|
|
49156
49326
|
return;
|
|
49157
|
-
const [{ getViaBrokerStructured: getViaBrokerStructured2, putViaBroker: putViaBroker2 }, { ensureTeam: ensureTeam2, ensureKey: ensureKey2 }, { addAgentSecret: addAgentSecret2 }] = await Promise.all([
|
|
49327
|
+
const [{ getViaBrokerStructured: getViaBrokerStructured2, putViaBroker: putViaBroker2 }, { ensureTeam: ensureTeam2, ensureKey: ensureKey2, validateKey: validateKey2 }, { addAgentSecret: addAgentSecret2 }] = await Promise.all([
|
|
49158
49328
|
Promise.resolve().then(() => (init_client(), exports_client)),
|
|
49159
49329
|
Promise.resolve().then(() => (init_provision(), exports_provision)),
|
|
49160
49330
|
Promise.resolve().then(() => (init_telegram_yaml(), exports_telegram_yaml))
|
|
49161
49331
|
]);
|
|
49162
|
-
const passphrase = await resolveOperatorVaultPassphrase(ctx.home ??
|
|
49332
|
+
const passphrase = await resolveOperatorVaultPassphrase(ctx.home ?? homedir48());
|
|
49163
49333
|
if (passphrase === null) {
|
|
49164
49334
|
const { getViaBrokerStructured: getViaBrokerStructured3 } = await Promise.resolve().then(() => (init_client(), exports_client));
|
|
49165
49335
|
const alreadyProvisioned = [];
|
|
@@ -49176,7 +49346,21 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
49176
49346
|
unprovisionedNew.push(name);
|
|
49177
49347
|
}
|
|
49178
49348
|
}
|
|
49179
|
-
|
|
49349
|
+
let hindsightPending = false;
|
|
49350
|
+
if (needsHindsight) {
|
|
49351
|
+
const h = await getViaBrokerStructured3("litellm/hindsight/api-key");
|
|
49352
|
+
if (h.kind === "ok") {
|
|
49353
|
+
hindsightPending = false;
|
|
49354
|
+
writeOut(source_default.gray(` ~ litellm/hindsight: key already provisioned (skipped) \u2014 unaffected by missing passphrase.
|
|
49355
|
+
`));
|
|
49356
|
+
} else if (h.kind === "unreachable") {
|
|
49357
|
+
hindsightPending = false;
|
|
49358
|
+
ctx.writeErr(source_default.yellow(` ! litellm/hindsight: vault-broker unreachable \u2014 could not verify the ` + `service key; it keeps its previous state.
|
|
49359
|
+
`));
|
|
49360
|
+
} else {
|
|
49361
|
+
hindsightPending = true;
|
|
49362
|
+
}
|
|
49363
|
+
}
|
|
49180
49364
|
if (alreadyProvisioned.length > 0) {
|
|
49181
49365
|
writeOut(source_default.gray(` ~ litellm: ${alreadyProvisioned.length} agent(s) already provisioned (${alreadyProvisioned.join(", ")}) \u2014 unaffected by missing passphrase.
|
|
49182
49366
|
`));
|
|
@@ -49206,9 +49390,9 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
49206
49390
|
const oauthAccount = config.auth?.active;
|
|
49207
49391
|
let pendingConfigEdits = false;
|
|
49208
49392
|
let configText = null;
|
|
49209
|
-
if (switchroomConfigPath &&
|
|
49393
|
+
if (switchroomConfigPath && existsSync83(switchroomConfigPath)) {
|
|
49210
49394
|
try {
|
|
49211
|
-
configText =
|
|
49395
|
+
configText = readFileSync70(switchroomConfigPath, "utf-8");
|
|
49212
49396
|
} catch (err) {
|
|
49213
49397
|
ctx.writeErr(source_default.yellow(` ! litellm: could not read config for ACL grants (${err.message}); keys will be provisioned but agents may lack read-ACL.
|
|
49214
49398
|
`));
|
|
@@ -49227,43 +49411,62 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
49227
49411
|
continue;
|
|
49228
49412
|
}
|
|
49229
49413
|
const vaultKey = `litellm/${name}/api-key`;
|
|
49414
|
+
const alias = `agent:${name}`;
|
|
49230
49415
|
const existing = await getViaBrokerStructured2(vaultKey);
|
|
49231
|
-
if (existing.kind === "ok") {
|
|
49232
|
-
writeOut(source_default.gray(` ~ litellm/${name}: key already provisioned (skipped)
|
|
49233
|
-
`));
|
|
49234
|
-
if (configText !== null) {
|
|
49235
|
-
const after = addAgentSecret2(configText, name, vaultKey);
|
|
49236
|
-
if (after !== configText) {
|
|
49237
|
-
configText = after;
|
|
49238
|
-
pendingConfigEdits = true;
|
|
49239
|
-
}
|
|
49240
|
-
}
|
|
49241
|
-
continue;
|
|
49242
|
-
}
|
|
49243
49416
|
if (existing.kind === "unreachable") {
|
|
49244
49417
|
ctx.writeErr(source_default.yellow(` ! litellm/${name}: vault-broker unreachable (${existing.msg ?? "no detail"}) \u2014 ` + `skipping key provisioning; agent keeps its previous routing env.
|
|
49245
49418
|
`));
|
|
49246
49419
|
continue;
|
|
49247
49420
|
}
|
|
49248
49421
|
let masterKey = adminKeyRef;
|
|
49422
|
+
let masterKeyErr = null;
|
|
49249
49423
|
if (isVaultReference(adminKeyRef)) {
|
|
49250
49424
|
const refKey = parseVaultReference(adminKeyRef);
|
|
49251
49425
|
const resolved = await getViaBrokerStructured2(refKey);
|
|
49252
49426
|
if (resolved.kind !== "ok") {
|
|
49253
|
-
|
|
49254
|
-
|
|
49255
|
-
|
|
49256
|
-
|
|
49257
|
-
|
|
49427
|
+
masterKey = null;
|
|
49428
|
+
masterKeyErr = `litellm: admin_key vault ref '${adminKeyRef}' did not resolve (${resolved.kind}${"msg" in resolved && resolved.msg ? `: ${resolved.msg}` : ""}).`;
|
|
49429
|
+
} else if (resolved.entry.kind !== "string") {
|
|
49430
|
+
masterKey = null;
|
|
49431
|
+
masterKeyErr = `litellm: admin_key vault ref '${adminKeyRef}' is not a string secret.`;
|
|
49432
|
+
} else {
|
|
49433
|
+
masterKey = resolved.entry.value;
|
|
49258
49434
|
}
|
|
49259
|
-
|
|
49260
|
-
|
|
49261
|
-
|
|
49262
|
-
|
|
49263
|
-
|
|
49435
|
+
}
|
|
49436
|
+
if (existing.kind === "ok") {
|
|
49437
|
+
const storedKey = existing.entry.kind === "string" ? existing.entry.value : null;
|
|
49438
|
+
let driftReprovision = false;
|
|
49439
|
+
if (storedKey && masterKey) {
|
|
49440
|
+
const validation = await validateKey2({ baseUrl, masterKey, key: storedKey });
|
|
49441
|
+
if (validation.kind === "unknown") {
|
|
49442
|
+
driftReprovision = true;
|
|
49443
|
+
ctx.writeErr(source_default.yellow(` ! litellm/${name}: stored virtual key not recognized by the proxy ` + `(DB drift) \u2014 re-provisioning a fresh key.
|
|
49444
|
+
`));
|
|
49445
|
+
} else if (validation.kind === "unreachable") {
|
|
49446
|
+
ctx.writeErr(source_default.yellow(` ! litellm/${name}: proxy unreachable during key validation (${validation.detail}) \u2014 keeping the stored key unverified.
|
|
49447
|
+
`));
|
|
49448
|
+
} else {
|
|
49449
|
+
writeOut(source_default.gray(` ~ litellm/${name}: key already provisioned + validated (skipped)
|
|
49450
|
+
`));
|
|
49451
|
+
}
|
|
49452
|
+
} else {
|
|
49453
|
+
writeOut(source_default.gray(` ~ litellm/${name}: key already provisioned (skipped)
|
|
49454
|
+
`));
|
|
49455
|
+
}
|
|
49456
|
+
if (!driftReprovision) {
|
|
49457
|
+
if (configText !== null) {
|
|
49458
|
+
const after = addAgentSecret2(configText, name, vaultKey);
|
|
49459
|
+
if (after !== configText) {
|
|
49460
|
+
configText = after;
|
|
49461
|
+
pendingConfigEdits = true;
|
|
49462
|
+
}
|
|
49463
|
+
}
|
|
49264
49464
|
continue;
|
|
49265
49465
|
}
|
|
49266
|
-
|
|
49466
|
+
}
|
|
49467
|
+
if (!masterKey) {
|
|
49468
|
+
failures.push({ agent: name, message: masterKeyErr ?? `litellm: admin_key unresolved` });
|
|
49469
|
+
continue;
|
|
49267
49470
|
}
|
|
49268
49471
|
await ensureTeam2(baseUrl, masterKey, team);
|
|
49269
49472
|
const metadata = {
|
|
@@ -49281,9 +49484,11 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
49281
49484
|
const { key } = await ensureKey2({
|
|
49282
49485
|
baseUrl,
|
|
49283
49486
|
masterKey,
|
|
49284
|
-
alias
|
|
49487
|
+
alias,
|
|
49285
49488
|
team,
|
|
49286
|
-
metadata
|
|
49489
|
+
metadata,
|
|
49490
|
+
log: (m) => ctx.writeErr(source_default.gray(` ~ litellm/${name}: ${m}
|
|
49491
|
+
`))
|
|
49287
49492
|
});
|
|
49288
49493
|
const put = await putViaBroker2(vaultKey, { kind: "string", value: key }, { passphrase });
|
|
49289
49494
|
if (put.kind !== "ok") {
|
|
@@ -49310,7 +49515,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
49310
49515
|
}
|
|
49311
49516
|
if (pendingConfigEdits && configText !== null && switchroomConfigPath) {
|
|
49312
49517
|
try {
|
|
49313
|
-
|
|
49518
|
+
writeFileSync39(switchroomConfigPath, configText, "utf-8");
|
|
49314
49519
|
writeOut(source_default.gray(` ~ litellm: granted read-ACL on per-agent keys (updated ${switchroomConfigPath})
|
|
49315
49520
|
`));
|
|
49316
49521
|
} catch (err) {
|
|
@@ -49345,7 +49550,9 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
49345
49550
|
masterKey,
|
|
49346
49551
|
alias: "service:hindsight",
|
|
49347
49552
|
team,
|
|
49348
|
-
metadata: { service: "hindsight", env: "fleet", ...oauthAccount ? { oauth_account: oauthAccount } : {} }
|
|
49553
|
+
metadata: { service: "hindsight", env: "fleet", ...oauthAccount ? { oauth_account: oauthAccount } : {} },
|
|
49554
|
+
log: (m) => ctx.writeErr(source_default.gray(` ~ litellm/hindsight: ${m}
|
|
49555
|
+
`))
|
|
49349
49556
|
});
|
|
49350
49557
|
const put = await putViaBroker2(hindsightVaultKey, { kind: "string", value: key }, { passphrase });
|
|
49351
49558
|
if (put.kind === "ok") {
|
|
@@ -49365,12 +49572,12 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
49365
49572
|
function resolveVaultBindMountDir(homeDir, ctx) {
|
|
49366
49573
|
const isCustomPath = ctx.migrationKind === "custom-path-skipped";
|
|
49367
49574
|
if (isCustomPath && ctx.customVaultPath) {
|
|
49368
|
-
return
|
|
49575
|
+
return dirname29(ctx.customVaultPath);
|
|
49369
49576
|
}
|
|
49370
|
-
return
|
|
49577
|
+
return join81(homeDir, ".switchroom", "vault");
|
|
49371
49578
|
}
|
|
49372
49579
|
function inspectVaultBindMountDir(vaultDir) {
|
|
49373
|
-
if (!
|
|
49580
|
+
if (!existsSync83(vaultDir))
|
|
49374
49581
|
return { kind: "missing" };
|
|
49375
49582
|
const entries = readdirSync28(vaultDir);
|
|
49376
49583
|
const unknown = [];
|
|
@@ -49398,61 +49605,61 @@ function hasVaultRefs(value) {
|
|
|
49398
49605
|
async function ensureHostMountSources(config) {
|
|
49399
49606
|
const home2 = resolveHostHomeForCompose();
|
|
49400
49607
|
const dirs = [
|
|
49401
|
-
|
|
49402
|
-
|
|
49403
|
-
|
|
49404
|
-
|
|
49405
|
-
|
|
49608
|
+
join81(home2, ".switchroom", "approvals"),
|
|
49609
|
+
join81(home2, ".switchroom", "scheduler"),
|
|
49610
|
+
join81(home2, ".switchroom", "logs"),
|
|
49611
|
+
join81(home2, ".switchroom", "compose"),
|
|
49612
|
+
join81(home2, ".switchroom", "broker-operator")
|
|
49406
49613
|
];
|
|
49407
49614
|
for (const name of Object.keys(config.agents)) {
|
|
49408
|
-
dirs.push(
|
|
49409
|
-
dirs.push(
|
|
49410
|
-
dirs.push(
|
|
49411
|
-
dirs.push(
|
|
49412
|
-
if (
|
|
49413
|
-
dirs.push(
|
|
49615
|
+
dirs.push(join81(home2, ".switchroom", "agents", name));
|
|
49616
|
+
dirs.push(join81(home2, ".switchroom", "logs", name));
|
|
49617
|
+
dirs.push(join81(home2, ".claude", "projects", name));
|
|
49618
|
+
dirs.push(join81(home2, ".switchroom", "audit", name));
|
|
49619
|
+
if (existsSync83(join81(home2, ".switchroom-config"))) {
|
|
49620
|
+
dirs.push(join81(home2, ".switchroom-config", "agents", name, "personal-skills"));
|
|
49414
49621
|
}
|
|
49415
49622
|
}
|
|
49416
49623
|
for (const dir of dirs) {
|
|
49417
49624
|
await mkdir2(dir, { recursive: true });
|
|
49418
49625
|
}
|
|
49419
|
-
const autoUnlockPath =
|
|
49420
|
-
if (!
|
|
49421
|
-
|
|
49626
|
+
const autoUnlockPath = join81(home2, ".switchroom", "vault-auto-unlock");
|
|
49627
|
+
if (!existsSync83(autoUnlockPath)) {
|
|
49628
|
+
writeFileSync39(autoUnlockPath, "", { mode: 384 });
|
|
49422
49629
|
}
|
|
49423
|
-
const auditLogPath =
|
|
49424
|
-
if (!
|
|
49425
|
-
|
|
49630
|
+
const auditLogPath = join81(home2, ".switchroom", "vault-audit.log");
|
|
49631
|
+
if (!existsSync83(auditLogPath)) {
|
|
49632
|
+
writeFileSync39(auditLogPath, "", { mode: 420 });
|
|
49426
49633
|
}
|
|
49427
|
-
const grantsDbPath =
|
|
49428
|
-
if (!
|
|
49429
|
-
|
|
49634
|
+
const grantsDbPath = join81(home2, ".switchroom", "vault-grants.db");
|
|
49635
|
+
if (!existsSync83(grantsDbPath)) {
|
|
49636
|
+
writeFileSync39(grantsDbPath, "", { mode: 384 });
|
|
49430
49637
|
}
|
|
49431
|
-
const hostdAuditLogPath =
|
|
49432
|
-
if (!
|
|
49433
|
-
|
|
49638
|
+
const hostdAuditLogPath = join81(home2, ".switchroom", "host-control-audit.log");
|
|
49639
|
+
if (!existsSync83(hostdAuditLogPath)) {
|
|
49640
|
+
writeFileSync39(hostdAuditLogPath, "", { mode: 420 });
|
|
49434
49641
|
}
|
|
49435
49642
|
for (const name of Object.keys(config.agents)) {
|
|
49436
|
-
const tokenPath =
|
|
49437
|
-
if (!
|
|
49438
|
-
|
|
49643
|
+
const tokenPath = join81(home2, ".switchroom", "agents", name, ".vault-token");
|
|
49644
|
+
if (!existsSync83(tokenPath)) {
|
|
49645
|
+
writeFileSync39(tokenPath, "", { mode: 384 });
|
|
49439
49646
|
}
|
|
49440
49647
|
try {
|
|
49441
49648
|
const uid = allocateAgentUid(name);
|
|
49442
49649
|
chownSync8(tokenPath, uid, uid);
|
|
49443
49650
|
} catch {}
|
|
49444
49651
|
}
|
|
49445
|
-
const fleetDir =
|
|
49652
|
+
const fleetDir = join81(home2, ".switchroom", "fleet");
|
|
49446
49653
|
await mkdir2(fleetDir, { recursive: true });
|
|
49447
|
-
const invariantsPath =
|
|
49654
|
+
const invariantsPath = join81(fleetDir, "switchroom-invariants.md");
|
|
49448
49655
|
const invariantsCanonical = renderFleetInvariants();
|
|
49449
|
-
const invariantsCurrent =
|
|
49656
|
+
const invariantsCurrent = existsSync83(invariantsPath) ? readFileSync70(invariantsPath, "utf-8") : null;
|
|
49450
49657
|
if (invariantsCurrent !== invariantsCanonical) {
|
|
49451
|
-
|
|
49658
|
+
writeFileSync39(invariantsPath, invariantsCanonical, { mode: 420 });
|
|
49452
49659
|
}
|
|
49453
|
-
const fleetClaudePath =
|
|
49454
|
-
if (!
|
|
49455
|
-
|
|
49660
|
+
const fleetClaudePath = join81(fleetDir, "CLAUDE.md");
|
|
49661
|
+
if (!existsSync83(fleetClaudePath)) {
|
|
49662
|
+
writeFileSync39(fleetClaudePath, [
|
|
49456
49663
|
"# Switchroom fleet defaults",
|
|
49457
49664
|
"",
|
|
49458
49665
|
"Operator-owned fleet brain. Every agent reads this via",
|
|
@@ -49490,7 +49697,7 @@ function isInAgentContainer(vaultPresent, composeV2Present, env2 = process.env)
|
|
|
49490
49697
|
function runApplyPreflight(config, opts = {}) {
|
|
49491
49698
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
49492
49699
|
const detect = opts.detectComposeV2 ?? detectComposeV2;
|
|
49493
|
-
const vaultMissing = hasVaultRefs(config) && !
|
|
49700
|
+
const vaultMissing = hasVaultRefs(config) && !existsSync83(vaultPath);
|
|
49494
49701
|
const composeErr = detect();
|
|
49495
49702
|
if ((vaultMissing || composeErr) && isInAgentContainer(!vaultMissing, composeErr === null)) {
|
|
49496
49703
|
throw new Error(IN_AGENT_CONTAINER_APPLY_MSG);
|
|
@@ -49504,7 +49711,7 @@ function runApplyPreflight(config, opts = {}) {
|
|
|
49504
49711
|
detectAndReportLegacyGdriveSlots(vaultPath);
|
|
49505
49712
|
}
|
|
49506
49713
|
function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
49507
|
-
if (!
|
|
49714
|
+
if (!existsSync83(vaultPath))
|
|
49508
49715
|
return;
|
|
49509
49716
|
const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
49510
49717
|
if (!passphrase)
|
|
@@ -49543,18 +49750,18 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
|
49543
49750
|
`));
|
|
49544
49751
|
}
|
|
49545
49752
|
}
|
|
49546
|
-
function writeInstallTypeCache(homeDir =
|
|
49753
|
+
function writeInstallTypeCache(homeDir = homedir48()) {
|
|
49547
49754
|
const ctx = detectInstallType();
|
|
49548
|
-
const dir =
|
|
49549
|
-
const out =
|
|
49755
|
+
const dir = join81(homeDir, ".switchroom");
|
|
49756
|
+
const out = join81(dir, "install-type.json");
|
|
49550
49757
|
const tmp = `${out}.tmp`;
|
|
49551
|
-
|
|
49758
|
+
mkdirSync47(dir, { recursive: true });
|
|
49552
49759
|
const payload = {
|
|
49553
49760
|
install_type: ctx.install_type,
|
|
49554
49761
|
detected_at: new Date().toISOString(),
|
|
49555
49762
|
source_paths: ctx.source_paths
|
|
49556
49763
|
};
|
|
49557
|
-
|
|
49764
|
+
writeFileSync39(tmp, JSON.stringify(payload, null, 2), { mode: 420 });
|
|
49558
49765
|
renameSync18(tmp, out);
|
|
49559
49766
|
return out;
|
|
49560
49767
|
}
|
|
@@ -49613,17 +49820,17 @@ Applying switchroom config...
|
|
|
49613
49820
|
writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
|
|
49614
49821
|
`));
|
|
49615
49822
|
try {
|
|
49616
|
-
installUpdatePromptHook(
|
|
49823
|
+
installUpdatePromptHook(join81(agentsDir, name));
|
|
49617
49824
|
} catch (hookErr) {
|
|
49618
49825
|
writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
|
|
49619
49826
|
`));
|
|
49620
49827
|
}
|
|
49621
|
-
await refreshAgentConnectionHealth(config, name,
|
|
49828
|
+
await refreshAgentConnectionHealth(config, name, join81(agentsDir, name), {
|
|
49622
49829
|
vaultAclReader: connHealthVaultAclReader
|
|
49623
49830
|
});
|
|
49624
49831
|
try {
|
|
49625
49832
|
const uid = allocateAgentUid(name);
|
|
49626
|
-
alignAgentUid(name,
|
|
49833
|
+
alignAgentUid(name, join81(agentsDir, name), uid, {
|
|
49627
49834
|
confirm: !options.nonInteractive,
|
|
49628
49835
|
writeOut
|
|
49629
49836
|
});
|
|
@@ -49660,7 +49867,7 @@ Applying switchroom config...
|
|
|
49660
49867
|
writeOut,
|
|
49661
49868
|
writeErr,
|
|
49662
49869
|
failures,
|
|
49663
|
-
home:
|
|
49870
|
+
home: homedir48()
|
|
49664
49871
|
});
|
|
49665
49872
|
}
|
|
49666
49873
|
await ensureHostMountSources(config);
|
|
@@ -49668,7 +49875,7 @@ Applying switchroom config...
|
|
|
49668
49875
|
for (const name of agentNames) {
|
|
49669
49876
|
try {
|
|
49670
49877
|
const uid = allocateAgentUid(name);
|
|
49671
|
-
alignAgentUid(name,
|
|
49878
|
+
alignAgentUid(name, join81(agentsDir, name), uid, {
|
|
49672
49879
|
confirm: !options.nonInteractive,
|
|
49673
49880
|
writeOut
|
|
49674
49881
|
});
|
|
@@ -49682,7 +49889,7 @@ Applying switchroom config...
|
|
|
49682
49889
|
}
|
|
49683
49890
|
const vaultPathConfigured = config.vault?.path;
|
|
49684
49891
|
const customVaultPath = vaultPathConfigured ? resolvePath(vaultPathConfigured) : undefined;
|
|
49685
|
-
const migrationResult = migrateVaultLayout(
|
|
49892
|
+
const migrationResult = migrateVaultLayout(homedir48(), {
|
|
49686
49893
|
customVaultPath
|
|
49687
49894
|
});
|
|
49688
49895
|
switch (migrationResult.kind) {
|
|
@@ -49708,7 +49915,7 @@ Applying switchroom config...
|
|
|
49708
49915
|
writeErr(formatDivergentRecoveryMessage(migrationResult.details));
|
|
49709
49916
|
process.exit(4);
|
|
49710
49917
|
}
|
|
49711
|
-
const postMigrationInspect = inspectVaultLayout(
|
|
49918
|
+
const postMigrationInspect = inspectVaultLayout(homedir48());
|
|
49712
49919
|
const acceptable = [
|
|
49713
49920
|
"no-vault",
|
|
49714
49921
|
"already-migrated",
|
|
@@ -49723,7 +49930,7 @@ Expected one of: ${acceptable.join(", ")}
|
|
|
49723
49930
|
`));
|
|
49724
49931
|
process.exit(5);
|
|
49725
49932
|
}
|
|
49726
|
-
const vaultDir = resolveVaultBindMountDir(
|
|
49933
|
+
const vaultDir = resolveVaultBindMountDir(homedir48(), {
|
|
49727
49934
|
migrationKind: migrationResult.kind,
|
|
49728
49935
|
customVaultPath
|
|
49729
49936
|
});
|
|
@@ -49754,7 +49961,7 @@ vault.enc.lock (PID-file flock from saveVault), and
|
|
|
49754
49961
|
});
|
|
49755
49962
|
if (!skipScaffold) {
|
|
49756
49963
|
const { provisionVoiceSidecarToken: provisionVoiceSidecarToken2 } = await Promise.resolve().then(() => (init_voice_sidecar_token(), exports_voice_sidecar_token));
|
|
49757
|
-
await provisionVoiceSidecarToken2(composePath,
|
|
49964
|
+
await provisionVoiceSidecarToken2(composePath, homedir48(), {
|
|
49758
49965
|
writeOut,
|
|
49759
49966
|
writeErr,
|
|
49760
49967
|
operatorUid
|
|
@@ -49770,7 +49977,7 @@ Wrote `) + displayComposePath + source_default.gray(` (${composeBytes} bytes)
|
|
|
49770
49977
|
writeOut(source_default.gray(` (If pull returns 401, login to ghcr.io first: see docs/operators/install.md#ghcr-auth)
|
|
49771
49978
|
`));
|
|
49772
49979
|
if (process.geteuid?.() === 0 && operatorUid !== undefined) {
|
|
49773
|
-
const restored = restoreOperatorOwnership(
|
|
49980
|
+
const restored = restoreOperatorOwnership(homedir48(), operatorUid);
|
|
49774
49981
|
if (restored.length > 0) {
|
|
49775
49982
|
writeOut(source_default.gray(` Restored operator ownership of ${restored.length} ~/.switchroom path(s)
|
|
49776
49983
|
`));
|
|
@@ -49875,7 +50082,7 @@ Dry-run: validating switchroom config (no changes will be written)...
|
|
|
49875
50082
|
}
|
|
49876
50083
|
}
|
|
49877
50084
|
try {
|
|
49878
|
-
const probe2 = await probeVaultProvisioning(config, agentNames,
|
|
50085
|
+
const probe2 = await probeVaultProvisioning(config, agentNames, homedir48());
|
|
49879
50086
|
if (probe2.alreadyProvisioned.length > 0) {
|
|
49880
50087
|
info.push(`litellm: ${probe2.alreadyProvisioned.length} agent(s) already provisioned (${probe2.alreadyProvisioned.join(", ")}).`);
|
|
49881
50088
|
}
|
|
@@ -50006,18 +50213,18 @@ function copyExampleConfig2(name) {
|
|
|
50006
50213
|
throw new Error(`Invalid example name: ${name} (must match /^[a-z0-9_-]+$/)`);
|
|
50007
50214
|
}
|
|
50008
50215
|
const dest = resolve50(process.cwd(), "switchroom.yaml");
|
|
50009
|
-
if (
|
|
50216
|
+
if (existsSync83(dest)) {
|
|
50010
50217
|
console.error(source_default.yellow("switchroom.yaml already exists \u2014 skipping example copy"));
|
|
50011
50218
|
return;
|
|
50012
50219
|
}
|
|
50013
50220
|
const embedded = EMBEDDED_EXAMPLES[name];
|
|
50014
50221
|
if (embedded !== undefined) {
|
|
50015
|
-
|
|
50222
|
+
writeFileSync39(dest, embedded, { encoding: "utf8" });
|
|
50016
50223
|
console.log(source_default.green(`Copied ${name}.yaml -> switchroom.yaml`));
|
|
50017
50224
|
return;
|
|
50018
50225
|
}
|
|
50019
50226
|
const exampleFile = resolve50(import.meta.dirname, `../../examples/${name}.yaml`);
|
|
50020
|
-
if (!
|
|
50227
|
+
if (!existsSync83(exampleFile)) {
|
|
50021
50228
|
throw new Error(`Example config not found: ${name}.yaml (available: ${Object.keys(EMBEDDED_EXAMPLES).join(", ")})`);
|
|
50022
50229
|
}
|
|
50023
50230
|
copyFileSync12(exampleFile, dest);
|
|
@@ -50028,8 +50235,8 @@ function findUnwritableAgentDirs(config, opts) {
|
|
|
50028
50235
|
const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
|
|
50029
50236
|
const unwritable = [];
|
|
50030
50237
|
for (const name of targets) {
|
|
50031
|
-
const startSh =
|
|
50032
|
-
if (!
|
|
50238
|
+
const startSh = join81(agentsDir, name, "start.sh");
|
|
50239
|
+
if (!existsSync83(startSh))
|
|
50033
50240
|
continue;
|
|
50034
50241
|
try {
|
|
50035
50242
|
accessSync3(startSh, fsConstants6.W_OK);
|
|
@@ -50230,7 +50437,7 @@ var init_apply = __esm(() => {
|
|
|
50230
50437
|
switchroom: switchroom_default,
|
|
50231
50438
|
minimal: minimal_default
|
|
50232
50439
|
};
|
|
50233
|
-
DEFAULT_COMPOSE_PATH2 =
|
|
50440
|
+
DEFAULT_COMPOSE_PATH2 = join81(homedir48(), ".switchroom", "compose", "docker-compose.yml");
|
|
50234
50441
|
IN_AGENT_CONTAINER_APPLY_MSG = "`switchroom apply`'s full per-agent scaffold cannot run from inside an " + "agent container \u2014 this is a host/hostd operation by construction " + "(no vault at the container HOME, and no `docker compose` v2 plugin here).\nTo roll the fleet to a new version, drive the hostd rollout (`mcp__hostd__rollout`): it runs a `--compose-only` apply plus a per-agent restart-reconcile, and each agent refreshes its own templates " + `on restart \u2014 the roll completes without any agent running a full apply.
|
|
50235
50442
|
` + "A full host-side `sudo switchroom apply` is only needed for structural changes (compose regeneration / new-agent scaffolding), and is run by the operator on the host, never from inside an agent.";
|
|
50236
50443
|
SELF_ELEVATE_PRESERVED_ENV = [
|
|
@@ -63677,7 +63884,7 @@ __export(exports_server2, {
|
|
|
63677
63884
|
TOOLS: () => TOOLS2
|
|
63678
63885
|
});
|
|
63679
63886
|
import { randomBytes as randomBytes16 } from "node:crypto";
|
|
63680
|
-
import { existsSync as
|
|
63887
|
+
import { existsSync as existsSync92, readFileSync as readFileSync79 } from "node:fs";
|
|
63681
63888
|
function selfSocketPath() {
|
|
63682
63889
|
return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
|
|
63683
63890
|
}
|
|
@@ -63695,7 +63902,7 @@ async function dispatchTool2(name, args) {
|
|
|
63695
63902
|
return errorText2("hostd MCP: SWITCHROOM_AGENT_NAME env var is not set \u2014 cannot " + "determine which per-agent socket to talk to.");
|
|
63696
63903
|
}
|
|
63697
63904
|
const sockPath = selfSocketPath();
|
|
63698
|
-
if (!
|
|
63905
|
+
if (!existsSync92(sockPath)) {
|
|
63699
63906
|
return errorText2(`hostd MCP: socket not bound at ${sockPath}. The host-control ` + `daemon is either not installed (run \`switchroom hostd install\`) ` + `or this agent isn't admin-flagged in switchroom.yaml. RFC C ` + `bind-mounts the per-agent socket only when host_control.enabled ` + `is true AND the agent has admin: true.`);
|
|
63700
63907
|
}
|
|
63701
63908
|
let req;
|
|
@@ -63883,18 +64090,18 @@ function resolveAuditLogPath() {
|
|
|
63883
64090
|
if (process.env.HOSTD_AUDIT_LOG_PATH)
|
|
63884
64091
|
return process.env.HOSTD_AUDIT_LOG_PATH;
|
|
63885
64092
|
const bindMounted = "/host-home/.switchroom/host-control-audit.log";
|
|
63886
|
-
if (
|
|
64093
|
+
if (existsSync92(bindMounted))
|
|
63887
64094
|
return bindMounted;
|
|
63888
64095
|
return defaultAuditLogPath2();
|
|
63889
64096
|
}
|
|
63890
64097
|
function getLastUpdateApplyStatus() {
|
|
63891
64098
|
const path8 = resolveAuditLogPath();
|
|
63892
|
-
if (!
|
|
64099
|
+
if (!existsSync92(path8)) {
|
|
63893
64100
|
return errorText2(`get_status: audit log not found at ${path8}. No update_apply has run yet?`);
|
|
63894
64101
|
}
|
|
63895
64102
|
let raw;
|
|
63896
64103
|
try {
|
|
63897
|
-
raw =
|
|
64104
|
+
raw = readFileSync79(path8, "utf-8");
|
|
63898
64105
|
} catch (err2) {
|
|
63899
64106
|
return errorText2(`get_status: failed to read audit log at ${path8}: ${err2.message}`);
|
|
63900
64107
|
}
|
|
@@ -64561,20 +64768,20 @@ __export(exports_scan, {
|
|
|
64561
64768
|
ledgerPathForBase: () => ledgerPathForBase
|
|
64562
64769
|
});
|
|
64563
64770
|
import {
|
|
64564
|
-
readFileSync as
|
|
64771
|
+
readFileSync as readFileSync81,
|
|
64565
64772
|
readdirSync as readdirSync36,
|
|
64566
|
-
existsSync as
|
|
64567
|
-
mkdirSync as
|
|
64568
|
-
writeFileSync as
|
|
64773
|
+
existsSync as existsSync95,
|
|
64774
|
+
mkdirSync as mkdirSync54,
|
|
64775
|
+
writeFileSync as writeFileSync45
|
|
64569
64776
|
} from "node:fs";
|
|
64570
|
-
import { resolve as resolve55, dirname as
|
|
64571
|
-
import { homedir as
|
|
64572
|
-
function resolveSwitchroomBase(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ??
|
|
64777
|
+
import { resolve as resolve55, dirname as dirname32 } from "node:path";
|
|
64778
|
+
import { homedir as homedir57 } from "node:os";
|
|
64779
|
+
function resolveSwitchroomBase(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir57()) {
|
|
64573
64780
|
return resolve55(home2, ".switchroom");
|
|
64574
64781
|
}
|
|
64575
64782
|
function listAgents(base) {
|
|
64576
64783
|
const dir = resolve55(base, "agents");
|
|
64577
|
-
if (!
|
|
64784
|
+
if (!existsSync95(dir))
|
|
64578
64785
|
return [];
|
|
64579
64786
|
try {
|
|
64580
64787
|
return readdirSync36(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
|
|
@@ -64603,16 +64810,16 @@ function runScan(opts = {}) {
|
|
|
64603
64810
|
let gwText = "";
|
|
64604
64811
|
let sawArtifact = false;
|
|
64605
64812
|
try {
|
|
64606
|
-
if (
|
|
64607
|
-
turnsText =
|
|
64813
|
+
if (existsSync95(turnsPath)) {
|
|
64814
|
+
turnsText = readFileSync81(turnsPath, "utf-8");
|
|
64608
64815
|
sawArtifact = true;
|
|
64609
64816
|
}
|
|
64610
64817
|
} catch (e) {
|
|
64611
64818
|
log(`fleet-health: WARN skipping ${agent} turns.jsonl unreadable: ${String(e)}`);
|
|
64612
64819
|
}
|
|
64613
64820
|
try {
|
|
64614
|
-
if (
|
|
64615
|
-
gwText =
|
|
64821
|
+
if (existsSync95(gwPath)) {
|
|
64822
|
+
gwText = readFileSync81(gwPath, "utf-8");
|
|
64616
64823
|
sawArtifact = true;
|
|
64617
64824
|
}
|
|
64618
64825
|
} catch (e) {
|
|
@@ -64650,20 +64857,20 @@ function runScan(opts = {}) {
|
|
|
64650
64857
|
function readLedgerIfPresent(base) {
|
|
64651
64858
|
const path8 = ledgerPathForBase(base);
|
|
64652
64859
|
try {
|
|
64653
|
-
if (!
|
|
64860
|
+
if (!existsSync95(path8))
|
|
64654
64861
|
return null;
|
|
64655
|
-
return JSON.parse(
|
|
64862
|
+
return JSON.parse(readFileSync81(path8, "utf-8"));
|
|
64656
64863
|
} catch {
|
|
64657
64864
|
return null;
|
|
64658
64865
|
}
|
|
64659
64866
|
}
|
|
64660
64867
|
function ledgerPathForBase(base) {
|
|
64661
|
-
return fleetHealthLedgerPath(
|
|
64868
|
+
return fleetHealthLedgerPath(dirname32(base));
|
|
64662
64869
|
}
|
|
64663
64870
|
function writeLedger(base, ledger) {
|
|
64664
64871
|
const path8 = ledgerPathForBase(base);
|
|
64665
|
-
|
|
64666
|
-
|
|
64872
|
+
mkdirSync54(dirname32(path8), { recursive: true });
|
|
64873
|
+
writeFileSync45(path8, JSON.stringify(ledger, null, 2) + `
|
|
64667
64874
|
`, "utf-8");
|
|
64668
64875
|
return path8;
|
|
64669
64876
|
}
|
|
@@ -64682,8 +64889,8 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
64682
64889
|
import { dirname, join } from "node:path";
|
|
64683
64890
|
|
|
64684
64891
|
// src/build-info.ts
|
|
64685
|
-
var VERSION = "0.18.
|
|
64686
|
-
var COMMIT_SHA = "
|
|
64892
|
+
var VERSION = "0.18.8";
|
|
64893
|
+
var COMMIT_SHA = "9255fe22";
|
|
64687
64894
|
|
|
64688
64895
|
// src/cli/resolve-version.ts
|
|
64689
64896
|
function readPackageVersion() {
|
|
@@ -78919,6 +79126,8 @@ Demoting memory ${source_default.cyan(memoryId)}`));
|
|
|
78919
79126
|
|
|
78920
79127
|
// src/cli/web.ts
|
|
78921
79128
|
init_source();
|
|
79129
|
+
import { join as join50 } from "node:path";
|
|
79130
|
+
import { homedir as homedir28 } from "node:os";
|
|
78922
79131
|
|
|
78923
79132
|
// src/web/server.ts
|
|
78924
79133
|
init_merge();
|
|
@@ -79554,7 +79763,8 @@ var SUBAGENTS_SCHEMA_SQL = `
|
|
|
79554
79763
|
status TEXT NOT NULL,
|
|
79555
79764
|
result_summary TEXT,
|
|
79556
79765
|
jsonl_agent_id TEXT,
|
|
79557
|
-
parent_agent_id TEXT
|
|
79766
|
+
parent_agent_id TEXT,
|
|
79767
|
+
model TEXT
|
|
79558
79768
|
);
|
|
79559
79769
|
CREATE INDEX IF NOT EXISTS subagents_turn ON subagents(parent_turn_key);
|
|
79560
79770
|
CREATE INDEX IF NOT EXISTS subagents_status ON subagents(status);
|
|
@@ -79570,6 +79780,10 @@ function applySubagentsSchema(db) {
|
|
|
79570
79780
|
if (!hasParentAgentId) {
|
|
79571
79781
|
db.exec("ALTER TABLE subagents ADD COLUMN parent_agent_id TEXT");
|
|
79572
79782
|
}
|
|
79783
|
+
const hasModel = cols.some((c) => c.name === "model");
|
|
79784
|
+
if (!hasModel) {
|
|
79785
|
+
db.exec("ALTER TABLE subagents ADD COLUMN model TEXT");
|
|
79786
|
+
}
|
|
79573
79787
|
db.exec("CREATE INDEX IF NOT EXISTS subagents_jsonl_id ON subagents(jsonl_agent_id)");
|
|
79574
79788
|
}
|
|
79575
79789
|
function mapSubagentRow(row) {
|
|
@@ -79586,7 +79800,8 @@ function mapSubagentRow(row) {
|
|
|
79586
79800
|
status: row.status,
|
|
79587
79801
|
result_summary: row.result_summary,
|
|
79588
79802
|
jsonl_agent_id: row.jsonl_agent_id,
|
|
79589
|
-
parent_agent_id: row.parent_agent_id ?? null
|
|
79803
|
+
parent_agent_id: row.parent_agent_id ?? null,
|
|
79804
|
+
model: row.model ?? null
|
|
79590
79805
|
};
|
|
79591
79806
|
}
|
|
79592
79807
|
function listSubagents(db, opts = {}) {
|
|
@@ -82913,10 +83128,100 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
82913
83128
|
|
|
82914
83129
|
// src/cli/web.ts
|
|
82915
83130
|
init_helpers();
|
|
83131
|
+
init_loader();
|
|
83132
|
+
|
|
83133
|
+
// src/web/startup-guard.ts
|
|
83134
|
+
import { existsSync as existsSync56, readFileSync as readFileSync51, writeFileSync as writeFileSync26, mkdirSync as mkdirSync31, statSync as statSync24, unlinkSync as unlinkSync13 } from "node:fs";
|
|
83135
|
+
import { dirname as dirname16 } from "node:path";
|
|
83136
|
+
function detectConfigMountFault(configPath, deps = {}) {
|
|
83137
|
+
const stat = deps.stat ?? ((p) => statSync24(p));
|
|
83138
|
+
let st;
|
|
83139
|
+
try {
|
|
83140
|
+
st = stat(configPath);
|
|
83141
|
+
} catch {
|
|
83142
|
+
return null;
|
|
83143
|
+
}
|
|
83144
|
+
if (!st.isDirectory())
|
|
83145
|
+
return null;
|
|
83146
|
+
return {
|
|
83147
|
+
kind: "directory-inode",
|
|
83148
|
+
path: configPath,
|
|
83149
|
+
message: `switchroom-web: the config bind source ${configPath} resolved to a DIRECTORY, ` + `not a file \u2014 reads fail with EISDIR and the server cannot start.
|
|
83150
|
+
|
|
83151
|
+
` + `Cause: the single-file bind mount was (re)created while the host source was ` + `momentarily absent, so Docker auto-created an empty directory, OR a stale ` + `directory inode survived a plain \`docker restart\` inside the container's mount ` + `namespace (a restart does NOT re-resolve a single-file bind).
|
|
83152
|
+
|
|
83153
|
+
` + `Remedy: RECREATE the container (not restart) so the bind re-resolves to the file:
|
|
83154
|
+
` + ` docker compose -p switchroom-web -f ~/.switchroom/web/docker-compose.yml up -d --force-recreate
|
|
83155
|
+
` + `and if the host source itself is a directory, remove it and restore the file first.`
|
|
83156
|
+
};
|
|
83157
|
+
}
|
|
83158
|
+
function nextCrashBackoff(prior, now, options = {}) {
|
|
83159
|
+
const baseMs = options.baseMs ?? 1000;
|
|
83160
|
+
const maxMs = options.maxMs ?? 60000;
|
|
83161
|
+
const resetAfterMs = options.resetAfterMs ?? 300000;
|
|
83162
|
+
const withinWindow = prior !== null && now - prior.lastTs <= resetAfterMs && now >= prior.lastTs;
|
|
83163
|
+
const count = withinWindow ? prior.count + 1 : 1;
|
|
83164
|
+
const exp = Math.min(count - 1, 30);
|
|
83165
|
+
const delayMs = count <= 1 ? 0 : Math.min(baseMs * 2 ** (exp - 1), maxMs);
|
|
83166
|
+
return { state: { count, lastTs: now }, delayMs };
|
|
83167
|
+
}
|
|
83168
|
+
function readCrashState(statePath) {
|
|
83169
|
+
try {
|
|
83170
|
+
if (!existsSync56(statePath))
|
|
83171
|
+
return null;
|
|
83172
|
+
const raw = JSON.parse(readFileSync51(statePath, "utf-8"));
|
|
83173
|
+
if (typeof raw.count !== "number" || typeof raw.lastTs !== "number")
|
|
83174
|
+
return null;
|
|
83175
|
+
return { count: raw.count, lastTs: raw.lastTs };
|
|
83176
|
+
} catch {
|
|
83177
|
+
return null;
|
|
83178
|
+
}
|
|
83179
|
+
}
|
|
83180
|
+
function writeCrashState(statePath, state) {
|
|
83181
|
+
try {
|
|
83182
|
+
mkdirSync31(dirname16(statePath), { recursive: true });
|
|
83183
|
+
writeFileSync26(statePath, JSON.stringify(state), { mode: 384 });
|
|
83184
|
+
} catch {}
|
|
83185
|
+
}
|
|
83186
|
+
function clearCrashState(statePath) {
|
|
83187
|
+
try {
|
|
83188
|
+
if (existsSync56(statePath))
|
|
83189
|
+
unlinkSync13(statePath);
|
|
83190
|
+
} catch {}
|
|
83191
|
+
}
|
|
83192
|
+
|
|
83193
|
+
// src/cli/web.ts
|
|
82916
83194
|
init_posthog();
|
|
83195
|
+
async function handleWebStartupFailure(err, configPath, stateFile) {
|
|
83196
|
+
if (err instanceof ConfigError && configPath) {
|
|
83197
|
+
const fault = detectConfigMountFault(configPath);
|
|
83198
|
+
if (fault) {
|
|
83199
|
+
console.error(source_default.red(fault.message));
|
|
83200
|
+
}
|
|
83201
|
+
}
|
|
83202
|
+
const { state, delayMs } = nextCrashBackoff(readCrashState(stateFile), Date.now());
|
|
83203
|
+
writeCrashState(stateFile, state);
|
|
83204
|
+
if (delayMs > 0) {
|
|
83205
|
+
console.error(source_default.yellow(`switchroom-web: startup has failed ${state.count}\u00d7 in a row \u2014 backing off ` + `${Math.round(delayMs / 1000)}s before exit so \`restart: always\` doesn't ` + `crash-loop the host (issue #2915).`));
|
|
83206
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
83207
|
+
}
|
|
83208
|
+
throw err;
|
|
83209
|
+
}
|
|
83210
|
+
function webCrashStatePath() {
|
|
83211
|
+
return join50(homedir28(), ".switchroom", "web", ".crashloop-state.json");
|
|
83212
|
+
}
|
|
82917
83213
|
function registerWebCommand(program3) {
|
|
82918
83214
|
program3.command("web").description("Start the web dashboard for monitoring agents").option("-p, --port <port>", "Port to listen on", "8080").option("-b, --bind <host>", "Host/IP to bind to (default: 127.0.0.1, localhost-only)", "127.0.0.1").action(withConfigError(async (opts) => {
|
|
82919
|
-
const
|
|
83215
|
+
const configPath = getConfigPath(program3);
|
|
83216
|
+
const stateFile = webCrashStatePath();
|
|
83217
|
+
let config;
|
|
83218
|
+
try {
|
|
83219
|
+
config = getConfig(program3);
|
|
83220
|
+
} catch (err) {
|
|
83221
|
+
await handleWebStartupFailure(err, configPath, stateFile);
|
|
83222
|
+
throw err;
|
|
83223
|
+
}
|
|
83224
|
+
clearCrashState(stateFile);
|
|
82920
83225
|
const port = parseInt(opts.port, 10);
|
|
82921
83226
|
const hostname = opts.bind;
|
|
82922
83227
|
if (isNaN(port) || port < 1 || port > 65535) {
|
|
@@ -82968,8 +83273,8 @@ Starting Switchroom dashboard...
|
|
|
82968
83273
|
init_source();
|
|
82969
83274
|
init_loader();
|
|
82970
83275
|
init_scaffold();
|
|
82971
|
-
import { existsSync as
|
|
82972
|
-
import { resolve as resolve33, dirname as
|
|
83276
|
+
import { existsSync as existsSync57, copyFileSync as copyFileSync9, readFileSync as readFileSync52, writeFileSync as writeFileSync27, mkdirSync as mkdirSync32 } from "node:fs";
|
|
83277
|
+
import { resolve as resolve33, dirname as dirname17 } from "node:path";
|
|
82973
83278
|
init_state();
|
|
82974
83279
|
init_vault();
|
|
82975
83280
|
init_manager();
|
|
@@ -83239,7 +83544,7 @@ async function stepConfigFile(configPath, nonInteractive) {
|
|
|
83239
83544
|
existingConfig = null;
|
|
83240
83545
|
}
|
|
83241
83546
|
}
|
|
83242
|
-
if (existingConfig &&
|
|
83547
|
+
if (existingConfig && existsSync57(existingConfig)) {
|
|
83243
83548
|
if (!nonInteractive) {
|
|
83244
83549
|
const useExisting = await askYesNo(` Found ${source_default.cyan(existingConfig)}. Use it?`, true);
|
|
83245
83550
|
if (!useExisting) {
|
|
@@ -83271,10 +83576,10 @@ async function copyExampleConfig(nonInteractive) {
|
|
|
83271
83576
|
}
|
|
83272
83577
|
const srcFile = resolve33(examplesDir, `${choice}.yaml`);
|
|
83273
83578
|
const destFile = resolvePath("~/.switchroom/switchroom.yaml");
|
|
83274
|
-
if (!
|
|
83579
|
+
if (!existsSync57(srcFile)) {
|
|
83275
83580
|
throw new ConfigError(`Example config not found: ${choice}.yaml`);
|
|
83276
83581
|
}
|
|
83277
|
-
|
|
83582
|
+
mkdirSync32(dirname17(destFile), { recursive: true });
|
|
83278
83583
|
copyFileSync9(srcFile, destFile);
|
|
83279
83584
|
console.log(source_default.green(` Copied ${choice}.yaml -> ${destFile}`));
|
|
83280
83585
|
console.log(source_default.yellow(` Edit ${destFile} to customize, then re-run switchroom setup.`));
|
|
@@ -83286,10 +83591,10 @@ async function copyExampleConfig(nonInteractive) {
|
|
|
83286
83591
|
async function writeDetectedTimezone(destFile, nonInteractive, detect = detectServerTimezone, prompt = ask) {
|
|
83287
83592
|
const detected = detect();
|
|
83288
83593
|
if (detected !== undefined && detected !== "UTC" && isValidTimezone(detected)) {
|
|
83289
|
-
const before2 =
|
|
83594
|
+
const before2 = readFileSync52(destFile, "utf-8");
|
|
83290
83595
|
const after2 = setSwitchroomTimezone(before2, detected);
|
|
83291
83596
|
if (after2 !== before2)
|
|
83292
|
-
|
|
83597
|
+
writeFileSync27(destFile, after2);
|
|
83293
83598
|
console.log(source_default.green(` Detected timezone ${detected} \u2014 wrote switchroom.timezone.`));
|
|
83294
83599
|
return;
|
|
83295
83600
|
}
|
|
@@ -83307,10 +83612,10 @@ async function writeDetectedTimezone(destFile, nonInteractive, detect = detectSe
|
|
|
83307
83612
|
console.error(source_default.yellow(` \u26a0 "${zone}" is not a valid IANA zone (expected Region/City like ` + '"Australia/Melbourne"). Skipping \u2014 agents will use UTC. ' + "Edit switchroom.timezone in switchroom.yaml by hand."));
|
|
83308
83613
|
return;
|
|
83309
83614
|
}
|
|
83310
|
-
const before =
|
|
83615
|
+
const before = readFileSync52(destFile, "utf-8");
|
|
83311
83616
|
const after = setSwitchroomTimezone(before, zone);
|
|
83312
83617
|
if (after !== before)
|
|
83313
|
-
|
|
83618
|
+
writeFileSync27(destFile, after);
|
|
83314
83619
|
console.log(source_default.green(` Wrote switchroom.timezone: ${zone}.`));
|
|
83315
83620
|
}
|
|
83316
83621
|
async function stepBotToken(config, nonInteractive) {
|
|
@@ -83386,7 +83691,7 @@ async function resolveOrPromptToken(rawToken, label, config, nonInteractive) {
|
|
|
83386
83691
|
try {
|
|
83387
83692
|
const { openVault: openVault2 } = await Promise.resolve().then(() => (init_vault(), exports_vault));
|
|
83388
83693
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
83389
|
-
if (
|
|
83694
|
+
if (existsSync57(vaultPath)) {
|
|
83390
83695
|
const secrets = openVault2(passphrase, vaultPath);
|
|
83391
83696
|
const key = rawToken.replace("vault:", "");
|
|
83392
83697
|
const entry = secrets[key];
|
|
@@ -83414,7 +83719,7 @@ async function resolveOrPromptToken(rawToken, label, config, nonInteractive) {
|
|
|
83414
83719
|
async function storeTokenInVault(config, vaultRef, token) {
|
|
83415
83720
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
83416
83721
|
const key = vaultRef.replace("vault:", "");
|
|
83417
|
-
if (!
|
|
83722
|
+
if (!existsSync57(vaultPath)) {
|
|
83418
83723
|
console.log(source_default.gray(" Creating encrypted vault..."));
|
|
83419
83724
|
let passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
83420
83725
|
if (!passphrase) {
|
|
@@ -83600,7 +83905,7 @@ async function stepMemoryBackend(config, nonInteractive, switchroomConfigPath) {
|
|
|
83600
83905
|
try {
|
|
83601
83906
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
83602
83907
|
const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
83603
|
-
if (passphrase &&
|
|
83908
|
+
if (passphrase && existsSync57(vaultPath)) {
|
|
83604
83909
|
const existing = getStringSecret(passphrase, vaultPath, "hindsight-api-key");
|
|
83605
83910
|
if (existing) {
|
|
83606
83911
|
console.log(source_default.gray(" Note: legacy 'hindsight-api-key' is in your vault but is no longer used. You can remove it with `switchroom vault rm hindsight-api-key`."));
|
|
@@ -83816,13 +84121,13 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
|
|
|
83816
84121
|
return;
|
|
83817
84122
|
}
|
|
83818
84123
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
83819
|
-
if (!
|
|
84124
|
+
if (!existsSync57(vaultPath)) {
|
|
83820
84125
|
console.log(source_default.gray(" Skipping (vault not created yet)."));
|
|
83821
84126
|
return;
|
|
83822
84127
|
}
|
|
83823
84128
|
const credPathRaw = config.vault?.broker?.autoUnlockCredentialPath ?? "~/.switchroom/vault-auto-unlock";
|
|
83824
84129
|
const credPath = resolvePath(credPathRaw);
|
|
83825
|
-
if (config.vault?.broker?.autoUnlock === true &&
|
|
84130
|
+
if (config.vault?.broker?.autoUnlock === true && existsSync57(credPath)) {
|
|
83826
84131
|
console.log(source_default.green(` ${STEP_DONE} Already configured (${credPath})`));
|
|
83827
84132
|
return;
|
|
83828
84133
|
}
|
|
@@ -83888,12 +84193,12 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
|
|
|
83888
84193
|
const choice = await askChoice(" Approval posture", [PASSPHRASE_CHOICE, TELEGRAM_ID_CHOICE]);
|
|
83889
84194
|
if (choice === TELEGRAM_ID_CHOICE) {
|
|
83890
84195
|
try {
|
|
83891
|
-
const yamlPath =
|
|
83892
|
-
if (
|
|
83893
|
-
const content =
|
|
84196
|
+
const yamlPath = existsSync57(resolve33(process.cwd(), "switchroom.yaml")) ? resolve33(process.cwd(), "switchroom.yaml") : resolve33(process.cwd(), "switchroom.yml");
|
|
84197
|
+
if (existsSync57(yamlPath)) {
|
|
84198
|
+
const content = readFileSync52(yamlPath, "utf-8");
|
|
83894
84199
|
const result = insertVaultBrokerApprovalAuth(content, "telegram-id");
|
|
83895
84200
|
if (result.kind === "rewritten") {
|
|
83896
|
-
|
|
84201
|
+
writeFileSync27(yamlPath, result.content, "utf-8");
|
|
83897
84202
|
console.log(source_default.green(` ${STEP_DONE} Set vault.broker.approvalAuth: telegram-id in ${yamlPath}`));
|
|
83898
84203
|
} else if (result.kind === "already-set") {
|
|
83899
84204
|
console.log(source_default.gray(" approvalAuth already set \u2014 leaving it alone."));
|
|
@@ -83924,8 +84229,8 @@ async function stepDangerousMode(config, nonInteractive) {
|
|
|
83924
84229
|
resolve33(process.cwd(), "switchroom.yml")
|
|
83925
84230
|
];
|
|
83926
84231
|
for (const configPath of configPaths) {
|
|
83927
|
-
if (
|
|
83928
|
-
let content =
|
|
84232
|
+
if (existsSync57(configPath)) {
|
|
84233
|
+
let content = readFileSync52(configPath, "utf-8");
|
|
83929
84234
|
const agentNames = Object.keys(config.agents);
|
|
83930
84235
|
for (const name of agentNames) {
|
|
83931
84236
|
const agentPattern = new RegExp(`(^ ${name}:\\s*\\n)`, "m");
|
|
@@ -83939,7 +84244,7 @@ async function stepDangerousMode(config, nonInteractive) {
|
|
|
83939
84244
|
}
|
|
83940
84245
|
config.agents[name].dangerous_mode = true;
|
|
83941
84246
|
}
|
|
83942
|
-
|
|
84247
|
+
writeFileSync27(configPath, content, "utf-8");
|
|
83943
84248
|
console.log(source_default.green(` ${STEP_DONE} Enabled dangerous_mode for all agents in ${configPath}`));
|
|
83944
84249
|
break;
|
|
83945
84250
|
}
|
|
@@ -84055,10 +84360,10 @@ init_source();
|
|
|
84055
84360
|
init_loader();
|
|
84056
84361
|
init_lifecycle();
|
|
84057
84362
|
init_compose_env();
|
|
84058
|
-
import { cpSync as cpSync2, existsSync as
|
|
84363
|
+
import { cpSync as cpSync2, existsSync as existsSync64, mkdirSync as mkdirSync34, readFileSync as readFileSync57, realpathSync as realpathSync6, rmSync as rmSync12, statSync as statSync29, chownSync as chownSync5 } from "node:fs";
|
|
84059
84364
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
84060
|
-
import { join as
|
|
84061
|
-
import { homedir as
|
|
84365
|
+
import { join as join64, dirname as dirname20, resolve as resolve38 } from "node:path";
|
|
84366
|
+
import { homedir as homedir39 } from "node:os";
|
|
84062
84367
|
|
|
84063
84368
|
// src/cli/release-yaml.ts
|
|
84064
84369
|
var import_yaml19 = __toESM(require_dist(), 1);
|
|
@@ -84083,7 +84388,7 @@ init_operator_uid();
|
|
|
84083
84388
|
init_atomic();
|
|
84084
84389
|
|
|
84085
84390
|
// src/cli/preflight-mounts.ts
|
|
84086
|
-
import { statSync as
|
|
84391
|
+
import { statSync as statSync28 } from "node:fs";
|
|
84087
84392
|
function parseHostBindSources(composeText) {
|
|
84088
84393
|
const out = [];
|
|
84089
84394
|
const FILE_HINT = /\.(ya?ml|db|log|toml|json|token|id)$|\/\.vault-token$|machine-id$|localtime$|vault-auto-unlock$|\/webkite$/;
|
|
@@ -84110,7 +84415,7 @@ function parseHostBindSources(composeText) {
|
|
|
84110
84415
|
return out;
|
|
84111
84416
|
}
|
|
84112
84417
|
function validateBindSources(composeText, deps = {}) {
|
|
84113
|
-
const stat = deps.stat ?? ((p) =>
|
|
84418
|
+
const stat = deps.stat ?? ((p) => statSync28(p));
|
|
84114
84419
|
const sources = parseHostBindSources(composeText);
|
|
84115
84420
|
const issues = [];
|
|
84116
84421
|
const seen = new Set;
|
|
@@ -84150,11 +84455,11 @@ ${lines.join(`
|
|
|
84150
84455
|
function defaultPersistPin(configPath) {
|
|
84151
84456
|
return (pin) => {
|
|
84152
84457
|
const path4 = configPath ?? findConfigFile();
|
|
84153
|
-
const before =
|
|
84458
|
+
const before = readFileSync57(path4, "utf8");
|
|
84154
84459
|
const after = setReleasePinInConfig(before, pin);
|
|
84155
84460
|
if (after === before)
|
|
84156
84461
|
return;
|
|
84157
|
-
writeConfigFileSync(path4, after,
|
|
84462
|
+
writeConfigFileSync(path4, after, statSync29(path4).mode & 511);
|
|
84158
84463
|
try {
|
|
84159
84464
|
if (typeof process.geteuid === "function" && process.geteuid() === 0) {
|
|
84160
84465
|
const uid = resolveOperatorUid();
|
|
@@ -84164,18 +84469,18 @@ function defaultPersistPin(configPath) {
|
|
|
84164
84469
|
} catch {}
|
|
84165
84470
|
};
|
|
84166
84471
|
}
|
|
84167
|
-
var DEFAULT_COMPOSE_PATH =
|
|
84472
|
+
var DEFAULT_COMPOSE_PATH = join64(homedir39(), ".switchroom", "compose", "docker-compose.yml");
|
|
84168
84473
|
function runningFromSwitchroomCheckout(scriptPath) {
|
|
84169
|
-
let dir =
|
|
84474
|
+
let dir = dirname20(scriptPath);
|
|
84170
84475
|
for (let i = 0;i < 12; i++) {
|
|
84171
|
-
if (
|
|
84476
|
+
if (existsSync64(join64(dir, ".git"))) {
|
|
84172
84477
|
try {
|
|
84173
|
-
const pkg = JSON.parse(
|
|
84478
|
+
const pkg = JSON.parse(readFileSync57(join64(dir, "package.json"), "utf-8"));
|
|
84174
84479
|
if (pkg.name === "switchroom")
|
|
84175
84480
|
return true;
|
|
84176
84481
|
} catch {}
|
|
84177
84482
|
}
|
|
84178
|
-
const parent =
|
|
84483
|
+
const parent = dirname20(dir);
|
|
84179
84484
|
if (parent === dir)
|
|
84180
84485
|
break;
|
|
84181
84486
|
dir = parent;
|
|
@@ -84255,7 +84560,7 @@ function planUpdate(opts) {
|
|
|
84255
84560
|
steps.push({
|
|
84256
84561
|
name: "pull-images",
|
|
84257
84562
|
description: "Pull broker / kernel / agent images from GHCR",
|
|
84258
|
-
skipReason: opts.skipImages ? "--skip-images flag set" : !
|
|
84563
|
+
skipReason: opts.skipImages ? "--skip-images flag set" : !existsSync64(composePath) ? `compose file not found at ${composePath} (run \`switchroom apply --compose-only\` first)` : undefined,
|
|
84259
84564
|
run: () => {
|
|
84260
84565
|
const r = runner("docker", [
|
|
84261
84566
|
"compose",
|
|
@@ -84381,17 +84686,17 @@ function planUpdate(opts) {
|
|
|
84381
84686
|
return;
|
|
84382
84687
|
}
|
|
84383
84688
|
const source = resolve38(import.meta.dirname, "../../skills");
|
|
84384
|
-
const dest =
|
|
84385
|
-
if (!
|
|
84689
|
+
const dest = join64(homedir39(), ".switchroom", "skills", "_bundled");
|
|
84690
|
+
if (!existsSync64(source)) {
|
|
84386
84691
|
process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
|
|
84387
84692
|
`);
|
|
84388
84693
|
return;
|
|
84389
84694
|
}
|
|
84390
84695
|
try {
|
|
84391
|
-
if (
|
|
84696
|
+
if (existsSync64(dest)) {
|
|
84392
84697
|
rmSync12(dest, { recursive: true, force: true });
|
|
84393
84698
|
}
|
|
84394
|
-
|
|
84699
|
+
mkdirSync34(dirname20(dest), { recursive: true });
|
|
84395
84700
|
cpSync2(source, dest, { recursive: true, dereference: false });
|
|
84396
84701
|
} catch (err) {
|
|
84397
84702
|
throw new Error(`sync-bundled-skills failed: ${err.message}`);
|
|
@@ -84427,7 +84732,7 @@ function planUpdate(opts) {
|
|
|
84427
84732
|
description: "docker compose up -d --remove-orphans (recreates services with new images / compose)",
|
|
84428
84733
|
run: () => {
|
|
84429
84734
|
try {
|
|
84430
|
-
const composeText =
|
|
84735
|
+
const composeText = readFileSync57(composePath, "utf8");
|
|
84431
84736
|
const pf = validateBindSources(composeText);
|
|
84432
84737
|
if (!pf.ok)
|
|
84433
84738
|
throw new Error(formatPreflightError(pf));
|
|
@@ -84502,14 +84807,14 @@ function defaultStatusProbe(composePath) {
|
|
|
84502
84807
|
} catch {}
|
|
84503
84808
|
if (scriptPath) {
|
|
84504
84809
|
try {
|
|
84505
|
-
cliBuiltAt = new Date(
|
|
84810
|
+
cliBuiltAt = new Date(statSync29(scriptPath).mtimeMs).toISOString();
|
|
84506
84811
|
} catch {}
|
|
84507
|
-
let dir =
|
|
84812
|
+
let dir = dirname20(scriptPath);
|
|
84508
84813
|
for (let i = 0;i < 8; i++) {
|
|
84509
|
-
const pkgPath =
|
|
84510
|
-
if (
|
|
84814
|
+
const pkgPath = join64(dir, "package.json");
|
|
84815
|
+
if (existsSync64(pkgPath)) {
|
|
84511
84816
|
try {
|
|
84512
|
-
const pkg = JSON.parse(
|
|
84817
|
+
const pkg = JSON.parse(readFileSync57(pkgPath, "utf-8"));
|
|
84513
84818
|
if (typeof pkg.version === "string")
|
|
84514
84819
|
cliVersion = pkg.version;
|
|
84515
84820
|
} catch (err) {
|
|
@@ -84517,7 +84822,7 @@ function defaultStatusProbe(composePath) {
|
|
|
84517
84822
|
}
|
|
84518
84823
|
break;
|
|
84519
84824
|
}
|
|
84520
|
-
const parent =
|
|
84825
|
+
const parent = dirname20(dir);
|
|
84521
84826
|
if (parent === dir)
|
|
84522
84827
|
break;
|
|
84523
84828
|
dir = parent;
|
|
@@ -84530,7 +84835,7 @@ function defaultStatusProbe(composePath) {
|
|
|
84530
84835
|
warnings.push("could not resolve CLI version (no package.json found above the resolved script path)");
|
|
84531
84836
|
}
|
|
84532
84837
|
const services = [];
|
|
84533
|
-
if (!
|
|
84838
|
+
if (!existsSync64(composePath)) {
|
|
84534
84839
|
warnings.push(`compose file not found at ${composePath}; service status unknown`);
|
|
84535
84840
|
return { cliVersion, cliBuiltAt, services, warnings };
|
|
84536
84841
|
}
|
|
@@ -84726,8 +85031,8 @@ function registerUpdateCommand(program3) {
|
|
|
84726
85031
|
// src/cli/rollout.ts
|
|
84727
85032
|
init_helpers();
|
|
84728
85033
|
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
84729
|
-
import { readFileSync as
|
|
84730
|
-
import { homedir as
|
|
85034
|
+
import { readFileSync as readFileSync58, chownSync as chownSync6, statSync as statSync30 } from "node:fs";
|
|
85035
|
+
import { homedir as homedir40 } from "node:os";
|
|
84731
85036
|
init_operator_uid();
|
|
84732
85037
|
init_atomic();
|
|
84733
85038
|
init_audit_reader();
|
|
@@ -84956,10 +85261,10 @@ function executeRollout(steps, target, deps, execOpts = {}) {
|
|
|
84956
85261
|
return { ok: true, rolled, warnings };
|
|
84957
85262
|
}
|
|
84958
85263
|
function resolveRollbackTarget(auditLogPath) {
|
|
84959
|
-
const logPath = auditLogPath ?? defaultAuditLogPath2(
|
|
85264
|
+
const logPath = auditLogPath ?? defaultAuditLogPath2(homedir40());
|
|
84960
85265
|
let raw;
|
|
84961
85266
|
try {
|
|
84962
|
-
raw =
|
|
85267
|
+
raw = readFileSync58(logPath, "utf8");
|
|
84963
85268
|
} catch {
|
|
84964
85269
|
return null;
|
|
84965
85270
|
}
|
|
@@ -85073,11 +85378,11 @@ function registerRolloutCommand(program3) {
|
|
|
85073
85378
|
emitPhase: (phase) => process.stdout.write(encodeRolloutPhaseLine(phase) + `
|
|
85074
85379
|
`),
|
|
85075
85380
|
persistPin: (pin) => {
|
|
85076
|
-
const before =
|
|
85381
|
+
const before = readFileSync58(configPath, "utf8");
|
|
85077
85382
|
const after = setReleasePinInConfig(before, pin);
|
|
85078
85383
|
if (after === before)
|
|
85079
85384
|
return;
|
|
85080
|
-
writeConfigFileSync(configPath, after,
|
|
85385
|
+
writeConfigFileSync(configPath, after, statSync30(configPath).mode & 511);
|
|
85081
85386
|
try {
|
|
85082
85387
|
if (typeof process.geteuid === "function" && process.geteuid() === 0) {
|
|
85083
85388
|
const uid = resolveOperatorUid();
|
|
@@ -85147,8 +85452,8 @@ init_source();
|
|
|
85147
85452
|
init_helpers();
|
|
85148
85453
|
init_lifecycle();
|
|
85149
85454
|
import { execSync as execSync4 } from "node:child_process";
|
|
85150
|
-
import { existsSync as
|
|
85151
|
-
import { dirname as
|
|
85455
|
+
import { existsSync as existsSync65, readFileSync as readFileSync59 } from "node:fs";
|
|
85456
|
+
import { dirname as dirname21, join as join65 } from "node:path";
|
|
85152
85457
|
function getClaudeCodeVersion() {
|
|
85153
85458
|
try {
|
|
85154
85459
|
const out = execSync4("claude --version 2>/dev/null", {
|
|
@@ -85198,16 +85503,16 @@ function formatUptime3(timestamp) {
|
|
|
85198
85503
|
function locateSwitchroomInstallDir() {
|
|
85199
85504
|
let dir = import.meta.dirname;
|
|
85200
85505
|
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
85201
|
-
const pkgPath =
|
|
85202
|
-
if (
|
|
85506
|
+
const pkgPath = join65(dir, "package.json");
|
|
85507
|
+
if (existsSync65(pkgPath)) {
|
|
85203
85508
|
try {
|
|
85204
|
-
const pkg = JSON.parse(
|
|
85205
|
-
if (pkg.name === "switchroom" &&
|
|
85509
|
+
const pkg = JSON.parse(readFileSync59(pkgPath, "utf-8"));
|
|
85510
|
+
if (pkg.name === "switchroom" && existsSync65(join65(dir, ".git"))) {
|
|
85206
85511
|
return dir;
|
|
85207
85512
|
}
|
|
85208
85513
|
} catch {}
|
|
85209
85514
|
}
|
|
85210
|
-
dir =
|
|
85515
|
+
dir = dirname21(dir);
|
|
85211
85516
|
}
|
|
85212
85517
|
return null;
|
|
85213
85518
|
}
|
|
@@ -85380,18 +85685,18 @@ import { resolve as resolve40 } from "node:path";
|
|
|
85380
85685
|
|
|
85381
85686
|
// src/agents/session-retention.ts
|
|
85382
85687
|
import {
|
|
85383
|
-
existsSync as
|
|
85688
|
+
existsSync as existsSync66,
|
|
85384
85689
|
readdirSync as readdirSync22,
|
|
85385
|
-
statSync as
|
|
85386
|
-
unlinkSync as
|
|
85690
|
+
statSync as statSync31,
|
|
85691
|
+
unlinkSync as unlinkSync14
|
|
85387
85692
|
} from "node:fs";
|
|
85388
|
-
import { join as
|
|
85693
|
+
import { join as join66 } from "node:path";
|
|
85389
85694
|
var DEFAULT_SESSION_RETENTION_MAX_COUNT = 20;
|
|
85390
85695
|
var DEFAULT_SESSION_RETENTION_MAX_AGE_DAYS = 30;
|
|
85391
85696
|
var MIN_KEEP = 2;
|
|
85392
85697
|
function collectSessionJsonl(claudeConfigDir) {
|
|
85393
|
-
const projects =
|
|
85394
|
-
if (!
|
|
85698
|
+
const projects = join66(claudeConfigDir, "projects");
|
|
85699
|
+
if (!existsSync66(projects))
|
|
85395
85700
|
return [];
|
|
85396
85701
|
const found = [];
|
|
85397
85702
|
const walk2 = (dir) => {
|
|
@@ -85402,10 +85707,10 @@ function collectSessionJsonl(claudeConfigDir) {
|
|
|
85402
85707
|
return;
|
|
85403
85708
|
}
|
|
85404
85709
|
for (const name of entries) {
|
|
85405
|
-
const full =
|
|
85710
|
+
const full = join66(dir, name);
|
|
85406
85711
|
let st;
|
|
85407
85712
|
try {
|
|
85408
|
-
st =
|
|
85713
|
+
st = statSync31(full);
|
|
85409
85714
|
} catch {
|
|
85410
85715
|
continue;
|
|
85411
85716
|
}
|
|
@@ -85452,7 +85757,7 @@ function pruneSessionJsonl(claudeConfigDir, opts = {}) {
|
|
|
85452
85757
|
if (!eligible)
|
|
85453
85758
|
continue;
|
|
85454
85759
|
try {
|
|
85455
|
-
|
|
85760
|
+
unlinkSync14(f.path);
|
|
85456
85761
|
deleted++;
|
|
85457
85762
|
} catch (err) {
|
|
85458
85763
|
onWarn(`session-retention: could not delete ${f.path}: ${err.message}`);
|
|
@@ -85522,18 +85827,18 @@ function registerHandoffCommand(program3) {
|
|
|
85522
85827
|
// src/issues/store.ts
|
|
85523
85828
|
import {
|
|
85524
85829
|
closeSync as closeSync12,
|
|
85525
|
-
existsSync as
|
|
85526
|
-
mkdirSync as
|
|
85830
|
+
existsSync as existsSync67,
|
|
85831
|
+
mkdirSync as mkdirSync35,
|
|
85527
85832
|
openSync as openSync12,
|
|
85528
85833
|
readdirSync as readdirSync23,
|
|
85529
|
-
readFileSync as
|
|
85834
|
+
readFileSync as readFileSync60,
|
|
85530
85835
|
renameSync as renameSync15,
|
|
85531
|
-
statSync as
|
|
85532
|
-
unlinkSync as
|
|
85533
|
-
writeFileSync as
|
|
85836
|
+
statSync as statSync32,
|
|
85837
|
+
unlinkSync as unlinkSync15,
|
|
85838
|
+
writeFileSync as writeFileSync28,
|
|
85534
85839
|
writeSync as writeSync9
|
|
85535
85840
|
} from "node:fs";
|
|
85536
|
-
import { join as
|
|
85841
|
+
import { join as join67 } from "node:path";
|
|
85537
85842
|
import { randomBytes as randomBytes12 } from "node:crypto";
|
|
85538
85843
|
import { execSync as execSync5 } from "node:child_process";
|
|
85539
85844
|
|
|
@@ -85931,12 +86236,12 @@ function redactedMarker(ruleId) {
|
|
|
85931
86236
|
var ISSUES_FILE = "issues.jsonl";
|
|
85932
86237
|
var ISSUES_LOCK = "issues.lock";
|
|
85933
86238
|
function readAll(stateDir) {
|
|
85934
|
-
const path4 =
|
|
85935
|
-
if (!
|
|
86239
|
+
const path4 = join67(stateDir, ISSUES_FILE);
|
|
86240
|
+
if (!existsSync67(path4))
|
|
85936
86241
|
return [];
|
|
85937
86242
|
let raw;
|
|
85938
86243
|
try {
|
|
85939
|
-
raw =
|
|
86244
|
+
raw = readFileSync60(path4, "utf-8");
|
|
85940
86245
|
} catch {
|
|
85941
86246
|
return [];
|
|
85942
86247
|
}
|
|
@@ -86009,7 +86314,7 @@ function record(stateDir, input, nowFn = Date.now) {
|
|
|
86009
86314
|
});
|
|
86010
86315
|
}
|
|
86011
86316
|
function resolve41(stateDir, fingerprint, nowFn = Date.now) {
|
|
86012
|
-
if (!
|
|
86317
|
+
if (!existsSync67(join67(stateDir, ISSUES_FILE)))
|
|
86013
86318
|
return 0;
|
|
86014
86319
|
return withLock(stateDir, () => {
|
|
86015
86320
|
const all = readAll(stateDir);
|
|
@@ -86027,7 +86332,7 @@ function resolve41(stateDir, fingerprint, nowFn = Date.now) {
|
|
|
86027
86332
|
});
|
|
86028
86333
|
}
|
|
86029
86334
|
function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
86030
|
-
if (!
|
|
86335
|
+
if (!existsSync67(join67(stateDir, ISSUES_FILE)))
|
|
86031
86336
|
return 0;
|
|
86032
86337
|
return withLock(stateDir, () => {
|
|
86033
86338
|
const all = readAll(stateDir);
|
|
@@ -86045,7 +86350,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
|
86045
86350
|
});
|
|
86046
86351
|
}
|
|
86047
86352
|
function prune(stateDir, opts = {}) {
|
|
86048
|
-
if (!
|
|
86353
|
+
if (!existsSync67(join67(stateDir, ISSUES_FILE)))
|
|
86049
86354
|
return 0;
|
|
86050
86355
|
return withLock(stateDir, () => {
|
|
86051
86356
|
const all = readAll(stateDir);
|
|
@@ -86075,16 +86380,16 @@ function prune(stateDir, opts = {}) {
|
|
|
86075
86380
|
});
|
|
86076
86381
|
}
|
|
86077
86382
|
function ensureDir(stateDir) {
|
|
86078
|
-
|
|
86383
|
+
mkdirSync35(stateDir, { recursive: true });
|
|
86079
86384
|
}
|
|
86080
86385
|
function writeAll(stateDir, events) {
|
|
86081
|
-
const path4 =
|
|
86386
|
+
const path4 = join67(stateDir, ISSUES_FILE);
|
|
86082
86387
|
sweepOrphanTmpFiles(stateDir);
|
|
86083
86388
|
const tmp = `${path4}.tmp-${process.pid}-${randomBytes12(4).toString("hex")}`;
|
|
86084
86389
|
const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
|
|
86085
86390
|
`) + `
|
|
86086
86391
|
`;
|
|
86087
|
-
|
|
86392
|
+
writeFileSync28(tmp, body, "utf-8");
|
|
86088
86393
|
renameSync15(tmp, path4);
|
|
86089
86394
|
}
|
|
86090
86395
|
var ORPHAN_TMP_TTL_MS = 60000;
|
|
@@ -86100,11 +86405,11 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
86100
86405
|
for (const entry of entries) {
|
|
86101
86406
|
if (!entry.startsWith(TMP_PREFIX))
|
|
86102
86407
|
continue;
|
|
86103
|
-
const tmpPath =
|
|
86408
|
+
const tmpPath = join67(stateDir, entry);
|
|
86104
86409
|
try {
|
|
86105
|
-
const stat =
|
|
86410
|
+
const stat = statSync32(tmpPath);
|
|
86106
86411
|
if (stat.mtimeMs < cutoff) {
|
|
86107
|
-
|
|
86412
|
+
unlinkSync15(tmpPath);
|
|
86108
86413
|
}
|
|
86109
86414
|
} catch {}
|
|
86110
86415
|
}
|
|
@@ -86112,7 +86417,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
86112
86417
|
var LOCK_RETRY_MS = 25;
|
|
86113
86418
|
var LOCK_TIMEOUT_MS = 1e4;
|
|
86114
86419
|
function withLock(stateDir, fn) {
|
|
86115
|
-
const lockPath =
|
|
86420
|
+
const lockPath = join67(stateDir, ISSUES_LOCK);
|
|
86116
86421
|
const startedAt = Date.now();
|
|
86117
86422
|
let fd = null;
|
|
86118
86423
|
while (fd === null) {
|
|
@@ -86140,27 +86445,27 @@ function withLock(stateDir, fn) {
|
|
|
86140
86445
|
closeSync12(fd);
|
|
86141
86446
|
} catch {}
|
|
86142
86447
|
try {
|
|
86143
|
-
|
|
86448
|
+
unlinkSync15(lockPath);
|
|
86144
86449
|
} catch {}
|
|
86145
86450
|
}
|
|
86146
86451
|
}
|
|
86147
86452
|
function tryStealStaleLock(lockPath) {
|
|
86148
86453
|
let pidStr;
|
|
86149
86454
|
try {
|
|
86150
|
-
pidStr =
|
|
86455
|
+
pidStr = readFileSync60(lockPath, "utf-8").trim();
|
|
86151
86456
|
} catch {
|
|
86152
86457
|
return true;
|
|
86153
86458
|
}
|
|
86154
86459
|
const pid = Number(pidStr);
|
|
86155
86460
|
if (!Number.isFinite(pid) || pid <= 0) {
|
|
86156
86461
|
try {
|
|
86157
|
-
|
|
86462
|
+
unlinkSync15(lockPath);
|
|
86158
86463
|
} catch {}
|
|
86159
86464
|
return true;
|
|
86160
86465
|
}
|
|
86161
86466
|
if (pid === process.pid) {
|
|
86162
86467
|
try {
|
|
86163
|
-
|
|
86468
|
+
unlinkSync15(lockPath);
|
|
86164
86469
|
} catch {}
|
|
86165
86470
|
return true;
|
|
86166
86471
|
}
|
|
@@ -86175,7 +86480,7 @@ function tryStealStaleLock(lockPath) {
|
|
|
86175
86480
|
return false;
|
|
86176
86481
|
}
|
|
86177
86482
|
try {
|
|
86178
|
-
|
|
86483
|
+
unlinkSync15(lockPath);
|
|
86179
86484
|
} catch {}
|
|
86180
86485
|
return true;
|
|
86181
86486
|
}
|
|
@@ -86395,21 +86700,21 @@ function relTime(deltaMs) {
|
|
|
86395
86700
|
|
|
86396
86701
|
// src/cli/deps.ts
|
|
86397
86702
|
init_source();
|
|
86398
|
-
import { existsSync as
|
|
86399
|
-
import { homedir as
|
|
86400
|
-
import { join as
|
|
86703
|
+
import { existsSync as existsSync70 } from "node:fs";
|
|
86704
|
+
import { homedir as homedir43 } from "node:os";
|
|
86705
|
+
import { join as join70, resolve as resolve42 } from "node:path";
|
|
86401
86706
|
|
|
86402
86707
|
// src/deps/python.ts
|
|
86403
86708
|
import { createHash as createHash12 } from "node:crypto";
|
|
86404
86709
|
import {
|
|
86405
|
-
existsSync as
|
|
86406
|
-
mkdirSync as
|
|
86407
|
-
readFileSync as
|
|
86710
|
+
existsSync as existsSync68,
|
|
86711
|
+
mkdirSync as mkdirSync36,
|
|
86712
|
+
readFileSync as readFileSync61,
|
|
86408
86713
|
rmSync as rmSync13,
|
|
86409
|
-
writeFileSync as
|
|
86714
|
+
writeFileSync as writeFileSync29
|
|
86410
86715
|
} from "node:fs";
|
|
86411
|
-
import { dirname as
|
|
86412
|
-
import { homedir as
|
|
86716
|
+
import { dirname as dirname22, join as join68 } from "node:path";
|
|
86717
|
+
import { homedir as homedir41 } from "node:os";
|
|
86413
86718
|
import { execFileSync as execFileSync19 } from "node:child_process";
|
|
86414
86719
|
|
|
86415
86720
|
class PythonEnvError extends Error {
|
|
@@ -86421,26 +86726,26 @@ class PythonEnvError extends Error {
|
|
|
86421
86726
|
}
|
|
86422
86727
|
}
|
|
86423
86728
|
function defaultPythonCacheRoot() {
|
|
86424
|
-
return
|
|
86729
|
+
return join68(homedir41(), ".switchroom", "deps", "python");
|
|
86425
86730
|
}
|
|
86426
86731
|
function hashFile(path4) {
|
|
86427
|
-
return createHash12("sha256").update(
|
|
86732
|
+
return createHash12("sha256").update(readFileSync61(path4)).digest("hex");
|
|
86428
86733
|
}
|
|
86429
86734
|
function ensurePythonEnv(opts) {
|
|
86430
86735
|
const { skillName, requirementsPath, force = false } = opts;
|
|
86431
86736
|
const cacheRoot = opts.cacheRoot ?? defaultPythonCacheRoot();
|
|
86432
86737
|
const hostPython = opts.pythonBin ?? "python3";
|
|
86433
|
-
if (!
|
|
86738
|
+
if (!existsSync68(requirementsPath)) {
|
|
86434
86739
|
throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
|
|
86435
86740
|
}
|
|
86436
|
-
const venvDir =
|
|
86437
|
-
const stampPath =
|
|
86438
|
-
const binDir =
|
|
86439
|
-
const pythonBin =
|
|
86440
|
-
const pipBin =
|
|
86741
|
+
const venvDir = join68(cacheRoot, skillName);
|
|
86742
|
+
const stampPath = join68(venvDir, ".requirements.sha256");
|
|
86743
|
+
const binDir = join68(venvDir, "bin");
|
|
86744
|
+
const pythonBin = join68(binDir, "python");
|
|
86745
|
+
const pipBin = join68(binDir, "pip");
|
|
86441
86746
|
const targetHash = hashFile(requirementsPath);
|
|
86442
|
-
if (!force &&
|
|
86443
|
-
const existingHash =
|
|
86747
|
+
if (!force && existsSync68(stampPath) && existsSync68(pythonBin)) {
|
|
86748
|
+
const existingHash = readFileSync61(stampPath, "utf8").trim();
|
|
86444
86749
|
if (existingHash === targetHash) {
|
|
86445
86750
|
return {
|
|
86446
86751
|
skillName,
|
|
@@ -86452,10 +86757,10 @@ function ensurePythonEnv(opts) {
|
|
|
86452
86757
|
};
|
|
86453
86758
|
}
|
|
86454
86759
|
}
|
|
86455
|
-
if (
|
|
86760
|
+
if (existsSync68(venvDir)) {
|
|
86456
86761
|
rmSync13(venvDir, { recursive: true, force: true });
|
|
86457
86762
|
}
|
|
86458
|
-
|
|
86763
|
+
mkdirSync36(dirname22(venvDir), { recursive: true });
|
|
86459
86764
|
try {
|
|
86460
86765
|
execFileSync19(hostPython, ["-m", "venv", venvDir], { stdio: "pipe" });
|
|
86461
86766
|
} catch (err) {
|
|
@@ -86474,7 +86779,7 @@ function ensurePythonEnv(opts) {
|
|
|
86474
86779
|
const e = err;
|
|
86475
86780
|
throw new PythonEnvError(`Failed to install requirements for skill "${skillName}": ${e.message}`, e.stderr?.toString());
|
|
86476
86781
|
}
|
|
86477
|
-
|
|
86782
|
+
writeFileSync29(stampPath, targetHash + `
|
|
86478
86783
|
`);
|
|
86479
86784
|
return {
|
|
86480
86785
|
skillName,
|
|
@@ -86490,14 +86795,14 @@ function ensurePythonEnv(opts) {
|
|
|
86490
86795
|
import { createHash as createHash13 } from "node:crypto";
|
|
86491
86796
|
import {
|
|
86492
86797
|
copyFileSync as copyFileSync10,
|
|
86493
|
-
existsSync as
|
|
86494
|
-
mkdirSync as
|
|
86495
|
-
readFileSync as
|
|
86798
|
+
existsSync as existsSync69,
|
|
86799
|
+
mkdirSync as mkdirSync37,
|
|
86800
|
+
readFileSync as readFileSync62,
|
|
86496
86801
|
rmSync as rmSync14,
|
|
86497
|
-
writeFileSync as
|
|
86802
|
+
writeFileSync as writeFileSync30
|
|
86498
86803
|
} from "node:fs";
|
|
86499
|
-
import { dirname as
|
|
86500
|
-
import { homedir as
|
|
86804
|
+
import { dirname as dirname23, join as join69 } from "node:path";
|
|
86805
|
+
import { homedir as homedir42 } from "node:os";
|
|
86501
86806
|
import { execFileSync as execFileSync20 } from "node:child_process";
|
|
86502
86807
|
|
|
86503
86808
|
class NodeEnvError extends Error {
|
|
@@ -86520,23 +86825,23 @@ var LOCKFILES_FOR = {
|
|
|
86520
86825
|
npm: ["package-lock.json"]
|
|
86521
86826
|
};
|
|
86522
86827
|
function defaultNodeCacheRoot() {
|
|
86523
|
-
return
|
|
86828
|
+
return join69(homedir42(), ".switchroom", "deps", "node");
|
|
86524
86829
|
}
|
|
86525
86830
|
function hashDepInputs(packageJsonPath) {
|
|
86526
|
-
const sourceDir =
|
|
86831
|
+
const sourceDir = dirname23(packageJsonPath);
|
|
86527
86832
|
const hasher = createHash13("sha256");
|
|
86528
86833
|
hasher.update(`package.json
|
|
86529
86834
|
`);
|
|
86530
|
-
hasher.update(
|
|
86835
|
+
hasher.update(readFileSync62(packageJsonPath));
|
|
86531
86836
|
for (const lockName of ALL_LOCKFILES) {
|
|
86532
|
-
const lockPath =
|
|
86533
|
-
if (
|
|
86837
|
+
const lockPath = join69(sourceDir, lockName);
|
|
86838
|
+
if (existsSync69(lockPath)) {
|
|
86534
86839
|
hasher.update(`
|
|
86535
86840
|
`);
|
|
86536
86841
|
hasher.update(lockName);
|
|
86537
86842
|
hasher.update(`
|
|
86538
86843
|
`);
|
|
86539
|
-
hasher.update(
|
|
86844
|
+
hasher.update(readFileSync62(lockPath));
|
|
86540
86845
|
}
|
|
86541
86846
|
}
|
|
86542
86847
|
return hasher.digest("hex");
|
|
@@ -86545,17 +86850,17 @@ function ensureNodeEnv(opts) {
|
|
|
86545
86850
|
const { skillName, packageJsonPath, force = false } = opts;
|
|
86546
86851
|
const cacheRoot = opts.cacheRoot ?? defaultNodeCacheRoot();
|
|
86547
86852
|
const installer = opts.installer ?? "bun";
|
|
86548
|
-
if (!
|
|
86853
|
+
if (!existsSync69(packageJsonPath)) {
|
|
86549
86854
|
throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
|
|
86550
86855
|
}
|
|
86551
|
-
const sourceDir =
|
|
86552
|
-
const envDir =
|
|
86553
|
-
const stampPath =
|
|
86554
|
-
const nodeModulesDir =
|
|
86555
|
-
const binDir =
|
|
86856
|
+
const sourceDir = dirname23(packageJsonPath);
|
|
86857
|
+
const envDir = join69(cacheRoot, skillName);
|
|
86858
|
+
const stampPath = join69(envDir, ".package.sha256");
|
|
86859
|
+
const nodeModulesDir = join69(envDir, "node_modules");
|
|
86860
|
+
const binDir = join69(nodeModulesDir, ".bin");
|
|
86556
86861
|
const targetHash = hashDepInputs(packageJsonPath);
|
|
86557
|
-
if (!force &&
|
|
86558
|
-
const existingHash =
|
|
86862
|
+
if (!force && existsSync69(stampPath) && existsSync69(nodeModulesDir)) {
|
|
86863
|
+
const existingHash = readFileSync62(stampPath, "utf8").trim();
|
|
86559
86864
|
if (existingHash === targetHash) {
|
|
86560
86865
|
return {
|
|
86561
86866
|
skillName,
|
|
@@ -86566,16 +86871,16 @@ function ensureNodeEnv(opts) {
|
|
|
86566
86871
|
};
|
|
86567
86872
|
}
|
|
86568
86873
|
}
|
|
86569
|
-
if (
|
|
86874
|
+
if (existsSync69(envDir)) {
|
|
86570
86875
|
rmSync14(envDir, { recursive: true, force: true });
|
|
86571
86876
|
}
|
|
86572
|
-
|
|
86573
|
-
copyFileSync10(packageJsonPath,
|
|
86877
|
+
mkdirSync37(envDir, { recursive: true });
|
|
86878
|
+
copyFileSync10(packageJsonPath, join69(envDir, "package.json"));
|
|
86574
86879
|
let copiedLockfile = false;
|
|
86575
86880
|
for (const lockName of LOCKFILES_FOR[installer]) {
|
|
86576
|
-
const lockPath =
|
|
86577
|
-
if (
|
|
86578
|
-
copyFileSync10(lockPath,
|
|
86881
|
+
const lockPath = join69(sourceDir, lockName);
|
|
86882
|
+
if (existsSync69(lockPath)) {
|
|
86883
|
+
copyFileSync10(lockPath, join69(envDir, lockName));
|
|
86579
86884
|
copiedLockfile = true;
|
|
86580
86885
|
}
|
|
86581
86886
|
}
|
|
@@ -86591,7 +86896,7 @@ function ensureNodeEnv(opts) {
|
|
|
86591
86896
|
const e = err;
|
|
86592
86897
|
throw new NodeEnvError(`Failed to install node deps for skill "${skillName}" with ${installer}: ${e.message}`, e.stderr?.toString());
|
|
86593
86898
|
}
|
|
86594
|
-
|
|
86899
|
+
writeFileSync30(stampPath, targetHash + `
|
|
86595
86900
|
`);
|
|
86596
86901
|
return {
|
|
86597
86902
|
skillName,
|
|
@@ -86604,28 +86909,28 @@ function ensureNodeEnv(opts) {
|
|
|
86604
86909
|
|
|
86605
86910
|
// src/cli/deps.ts
|
|
86606
86911
|
function builtinSkillsRoot() {
|
|
86607
|
-
return resolve42(
|
|
86912
|
+
return resolve42(homedir43(), ".switchroom/skills/_bundled");
|
|
86608
86913
|
}
|
|
86609
86914
|
function registerDepsCommand(program3) {
|
|
86610
86915
|
const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
|
|
86611
86916
|
deps.command("rebuild <skill>").description("Rebuild the Python venv and/or Node node_modules cache for a skill").option("-p, --python", "Rebuild only the Python env").option("-n, --node", "Rebuild only the Node env").action(async (skill, opts) => {
|
|
86612
86917
|
const skillsRoot = builtinSkillsRoot();
|
|
86613
|
-
if (!
|
|
86918
|
+
if (!existsSync70(skillsRoot)) {
|
|
86614
86919
|
console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
|
|
86615
86920
|
process.exit(1);
|
|
86616
86921
|
}
|
|
86617
|
-
const skillDir =
|
|
86618
|
-
if (!
|
|
86922
|
+
const skillDir = join70(skillsRoot, skill);
|
|
86923
|
+
if (!existsSync70(skillDir)) {
|
|
86619
86924
|
console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
|
|
86620
86925
|
process.exit(1);
|
|
86621
86926
|
}
|
|
86622
|
-
const requirementsPath =
|
|
86623
|
-
const packageJsonPath =
|
|
86624
|
-
const wantPython = opts.python ?? (!opts.python && !opts.node &&
|
|
86625
|
-
const wantNode = opts.node ?? (!opts.python && !opts.node &&
|
|
86927
|
+
const requirementsPath = join70(skillDir, "requirements.txt");
|
|
86928
|
+
const packageJsonPath = join70(skillDir, "package.json");
|
|
86929
|
+
const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync70(requirementsPath));
|
|
86930
|
+
const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync70(packageJsonPath));
|
|
86626
86931
|
let did = 0;
|
|
86627
86932
|
if (wantPython) {
|
|
86628
|
-
if (!
|
|
86933
|
+
if (!existsSync70(requirementsPath)) {
|
|
86629
86934
|
console.error(source_default.red(`Skill "${skill}" has no requirements.txt at ${requirementsPath}`));
|
|
86630
86935
|
process.exit(1);
|
|
86631
86936
|
}
|
|
@@ -86649,7 +86954,7 @@ function registerDepsCommand(program3) {
|
|
|
86649
86954
|
}
|
|
86650
86955
|
}
|
|
86651
86956
|
if (wantNode) {
|
|
86652
|
-
if (!
|
|
86957
|
+
if (!existsSync70(packageJsonPath)) {
|
|
86653
86958
|
console.error(source_default.red(`Skill "${skill}" has no package.json at ${packageJsonPath}`));
|
|
86654
86959
|
process.exit(1);
|
|
86655
86960
|
}
|
|
@@ -86682,7 +86987,7 @@ function registerDepsCommand(program3) {
|
|
|
86682
86987
|
// src/cli/workspace.ts
|
|
86683
86988
|
init_helpers();
|
|
86684
86989
|
init_loader();
|
|
86685
|
-
import { existsSync as
|
|
86990
|
+
import { existsSync as existsSync71 } from "node:fs";
|
|
86686
86991
|
import { resolve as resolve43, sep as sep3 } from "node:path";
|
|
86687
86992
|
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
86688
86993
|
|
|
@@ -87459,7 +87764,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
87459
87764
|
if (!dir)
|
|
87460
87765
|
return;
|
|
87461
87766
|
const gitDir = resolve43(dir, ".git");
|
|
87462
|
-
if (!
|
|
87767
|
+
if (!existsSync71(gitDir)) {
|
|
87463
87768
|
process.stdout.write(`Workspace is not a git repository. Re-run \`switchroom agent create ${agentName}\` ` + `or manually \`git init\` in ${dir} to enable versioning.
|
|
87464
87769
|
`);
|
|
87465
87770
|
return;
|
|
@@ -87513,7 +87818,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
87513
87818
|
if (!dir)
|
|
87514
87819
|
return;
|
|
87515
87820
|
const gitDir = resolve43(dir, ".git");
|
|
87516
|
-
if (!
|
|
87821
|
+
if (!existsSync71(gitDir)) {
|
|
87517
87822
|
process.stdout.write(`Workspace is not a git repository.
|
|
87518
87823
|
`);
|
|
87519
87824
|
return;
|
|
@@ -87538,7 +87843,7 @@ function resolveAgentWorkspaceDirOrExit(program3, agentName) {
|
|
|
87538
87843
|
const agentsDir = resolveAgentsDir(config);
|
|
87539
87844
|
const agentDir = resolve43(agentsDir, agentName);
|
|
87540
87845
|
const dir = resolveAgentWorkspaceDir(agentDir);
|
|
87541
|
-
if (!
|
|
87846
|
+
if (!existsSync71(dir)) {
|
|
87542
87847
|
process.stderr.write(`workspace: ${dir} does not exist yet. Run \`switchroom setup\` or \`switchroom agent scaffold ${agentName}\` to seed it.
|
|
87543
87848
|
`);
|
|
87544
87849
|
return;
|
|
@@ -87574,8 +87879,8 @@ function safeParseInt(value, fallback) {
|
|
|
87574
87879
|
init_helpers();
|
|
87575
87880
|
init_loader();
|
|
87576
87881
|
init_merge();
|
|
87577
|
-
import { copyFileSync as copyFileSync11, existsSync as
|
|
87578
|
-
import { join as
|
|
87882
|
+
import { copyFileSync as copyFileSync11, existsSync as existsSync72, readFileSync as readFileSync63, writeFileSync as writeFileSync31 } from "node:fs";
|
|
87883
|
+
import { join as join71, resolve as resolve44 } from "node:path";
|
|
87579
87884
|
init_scaffold();
|
|
87580
87885
|
init_profiles();
|
|
87581
87886
|
init_schema();
|
|
@@ -87592,7 +87897,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
87592
87897
|
const agentsDir = resolveAgentsDir(config);
|
|
87593
87898
|
const agentDir = resolve44(agentsDir, agentName);
|
|
87594
87899
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
87595
|
-
if (!
|
|
87900
|
+
if (!existsSync72(workspaceDir)) {
|
|
87596
87901
|
console.error(`soul: ${workspaceDir} does not exist yet. Run \`switchroom setup\` ` + `or \`switchroom agent scaffold ${agentName}\` to seed it.`);
|
|
87597
87902
|
process.exit(1);
|
|
87598
87903
|
}
|
|
@@ -87601,7 +87906,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
87601
87906
|
profileName,
|
|
87602
87907
|
profilePath,
|
|
87603
87908
|
workspaceDir,
|
|
87604
|
-
soulPath:
|
|
87909
|
+
soulPath: join71(workspaceDir, "SOUL.md"),
|
|
87605
87910
|
soul: merged.soul
|
|
87606
87911
|
};
|
|
87607
87912
|
}
|
|
@@ -87618,11 +87923,11 @@ function registerSoulCommand(program3) {
|
|
|
87618
87923
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
87619
87924
|
if (!t)
|
|
87620
87925
|
return;
|
|
87621
|
-
if (!
|
|
87926
|
+
if (!existsSync72(t.soulPath)) {
|
|
87622
87927
|
console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
|
|
87623
87928
|
process.exit(1);
|
|
87624
87929
|
}
|
|
87625
|
-
process.stdout.write(
|
|
87930
|
+
process.stdout.write(readFileSync63(t.soulPath, "utf-8"));
|
|
87626
87931
|
}));
|
|
87627
87932
|
cmd.command("reset <agent>").description("Re-seed SOUL.md from the agent's current profile " + "(backs the existing file up to SOUL.md.bak first)").option("-y, --yes", "Skip the confirmation prompt").action(withConfigError(async (agentName, opts) => {
|
|
87628
87933
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
@@ -87633,7 +87938,7 @@ function registerSoulCommand(program3) {
|
|
|
87633
87938
|
console.error(`soul: profile "${t.profileName}" ships no SOUL.md.hbs \u2014 ` + `nothing to re-seed from.`);
|
|
87634
87939
|
process.exit(1);
|
|
87635
87940
|
}
|
|
87636
|
-
const exists =
|
|
87941
|
+
const exists = existsSync72(t.soulPath);
|
|
87637
87942
|
if (exists && !opts.yes) {
|
|
87638
87943
|
if (!isInteractive()) {
|
|
87639
87944
|
console.error(`soul: ${t.soulPath} already exists. Re-run with --yes to ` + `replace it (the current file is backed up to SOUL.md.bak).`);
|
|
@@ -87648,12 +87953,12 @@ function registerSoulCommand(program3) {
|
|
|
87648
87953
|
let backupPath;
|
|
87649
87954
|
if (exists) {
|
|
87650
87955
|
backupPath = `${t.soulPath}.bak`;
|
|
87651
|
-
if (
|
|
87956
|
+
if (existsSync72(backupPath)) {
|
|
87652
87957
|
backupPath = `${t.soulPath}.bak.${Date.now()}`;
|
|
87653
87958
|
}
|
|
87654
87959
|
copyFileSync11(t.soulPath, backupPath);
|
|
87655
87960
|
}
|
|
87656
|
-
|
|
87961
|
+
writeFileSync31(t.soulPath, content, "utf-8");
|
|
87657
87962
|
if (backupPath) {
|
|
87658
87963
|
console.log(`soul: re-seeded ${agentName}'s SOUL.md from profile ` + `"${t.profileName}".
|
|
87659
87964
|
` + ` Previous version saved to ${backupPath}`);
|
|
@@ -87667,8 +87972,8 @@ function registerSoulCommand(program3) {
|
|
|
87667
87972
|
// src/cli/debug.ts
|
|
87668
87973
|
init_helpers();
|
|
87669
87974
|
init_loader();
|
|
87670
|
-
import { existsSync as
|
|
87671
|
-
import { resolve as resolve45, join as
|
|
87975
|
+
import { existsSync as existsSync73, readFileSync as readFileSync64, readdirSync as readdirSync24, statSync as statSync33 } from "node:fs";
|
|
87976
|
+
import { resolve as resolve45, join as join72 } from "node:path";
|
|
87672
87977
|
import { createHash as createHash14 } from "node:crypto";
|
|
87673
87978
|
init_merge();
|
|
87674
87979
|
init_hindsight2();
|
|
@@ -87679,11 +87984,11 @@ function estimateTokens(bytes) {
|
|
|
87679
87984
|
return Math.round(bytes / 3.7);
|
|
87680
87985
|
}
|
|
87681
87986
|
function readMcpServerNames(agentDir) {
|
|
87682
|
-
const mcpPath =
|
|
87683
|
-
if (!
|
|
87987
|
+
const mcpPath = join72(agentDir, ".mcp.json");
|
|
87988
|
+
if (!existsSync73(mcpPath))
|
|
87684
87989
|
return [];
|
|
87685
87990
|
try {
|
|
87686
|
-
const parsed = JSON.parse(
|
|
87991
|
+
const parsed = JSON.parse(readFileSync64(mcpPath, "utf-8"));
|
|
87687
87992
|
return Object.keys(parsed.mcpServers ?? {});
|
|
87688
87993
|
} catch {
|
|
87689
87994
|
return null;
|
|
@@ -87693,8 +87998,8 @@ function sha256(content) {
|
|
|
87693
87998
|
return createHash14("sha256").update(content).digest("hex").slice(0, 16);
|
|
87694
87999
|
}
|
|
87695
88000
|
function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
87696
|
-
const projectsDir =
|
|
87697
|
-
if (!
|
|
88001
|
+
const projectsDir = join72(claudeConfigDir, "projects");
|
|
88002
|
+
if (!existsSync73(projectsDir))
|
|
87698
88003
|
return;
|
|
87699
88004
|
try {
|
|
87700
88005
|
const entries = readdirSync24(projectsDir, { withFileTypes: true });
|
|
@@ -87702,11 +88007,11 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
87702
88007
|
for (const entry of entries) {
|
|
87703
88008
|
if (!entry.isDirectory())
|
|
87704
88009
|
continue;
|
|
87705
|
-
const projectPath =
|
|
87706
|
-
const transcriptPath =
|
|
87707
|
-
if (!
|
|
88010
|
+
const projectPath = join72(projectsDir, entry.name);
|
|
88011
|
+
const transcriptPath = join72(projectPath, "transcript.jsonl");
|
|
88012
|
+
if (!existsSync73(transcriptPath))
|
|
87708
88013
|
continue;
|
|
87709
|
-
const stat3 =
|
|
88014
|
+
const stat3 = statSync33(transcriptPath);
|
|
87710
88015
|
if (!latest || stat3.mtimeMs > latest.mtime) {
|
|
87711
88016
|
latest = { path: transcriptPath, mtime: stat3.mtimeMs };
|
|
87712
88017
|
}
|
|
@@ -87718,7 +88023,7 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
87718
88023
|
}
|
|
87719
88024
|
function extractLatestUserMessage(transcriptPath) {
|
|
87720
88025
|
try {
|
|
87721
|
-
const content =
|
|
88026
|
+
const content = readFileSync64(transcriptPath, "utf-8");
|
|
87722
88027
|
const lines = content.trim().split(`
|
|
87723
88028
|
`).filter(Boolean);
|
|
87724
88029
|
for (let i = lines.length - 1;i >= 0; i--) {
|
|
@@ -87767,16 +88072,16 @@ function registerDebugCommand(program3) {
|
|
|
87767
88072
|
}
|
|
87768
88073
|
const agentsDir = resolveAgentsDir(config);
|
|
87769
88074
|
const agentDir = resolve45(agentsDir, agentName);
|
|
87770
|
-
if (!
|
|
88075
|
+
if (!existsSync73(agentDir)) {
|
|
87771
88076
|
console.error(`Agent directory not found: ${agentDir}`);
|
|
87772
88077
|
process.exit(1);
|
|
87773
88078
|
}
|
|
87774
88079
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
87775
|
-
const claudeConfigDir =
|
|
87776
|
-
const claudeMdPath =
|
|
87777
|
-
const soulMdPath =
|
|
87778
|
-
const workspaceSoulMdPath =
|
|
87779
|
-
const handoffPath =
|
|
88080
|
+
const claudeConfigDir = join72(agentDir, ".claude");
|
|
88081
|
+
const claudeMdPath = join72(agentDir, "CLAUDE.md");
|
|
88082
|
+
const soulMdPath = join72(agentDir, "SOUL.md");
|
|
88083
|
+
const workspaceSoulMdPath = join72(workspaceDir, "SOUL.md");
|
|
88084
|
+
const handoffPath = join72(agentDir, ".handoff.md");
|
|
87780
88085
|
const lastN = parseInt(opts.last, 10);
|
|
87781
88086
|
if (isNaN(lastN) || lastN < 1) {
|
|
87782
88087
|
console.error("--last must be a positive integer");
|
|
@@ -87822,7 +88127,7 @@ function registerDebugCommand(program3) {
|
|
|
87822
88127
|
}
|
|
87823
88128
|
console.log(`=== Append System Prompt (per-session) ===
|
|
87824
88129
|
`);
|
|
87825
|
-
const handoffContent =
|
|
88130
|
+
const handoffContent = existsSync73(handoffPath) ? readFileSync64(handoffPath, "utf-8") : "";
|
|
87826
88131
|
if (handoffContent.trim().length > 0) {
|
|
87827
88132
|
console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
|
|
87828
88133
|
console.log(handoffContent);
|
|
@@ -87833,7 +88138,7 @@ function registerDebugCommand(program3) {
|
|
|
87833
88138
|
}
|
|
87834
88139
|
console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
|
|
87835
88140
|
`);
|
|
87836
|
-
const claudeMdContent =
|
|
88141
|
+
const claudeMdContent = existsSync73(claudeMdPath) ? readFileSync64(claudeMdPath, "utf-8") : "";
|
|
87837
88142
|
if (claudeMdContent.trim().length > 0) {
|
|
87838
88143
|
console.log(`(${formatBytes(claudeMdContent.length)})`);
|
|
87839
88144
|
console.log(claudeMdContent);
|
|
@@ -87844,7 +88149,7 @@ function registerDebugCommand(program3) {
|
|
|
87844
88149
|
}
|
|
87845
88150
|
console.log(`=== Persona (SOUL.md) ===
|
|
87846
88151
|
`);
|
|
87847
|
-
const soulMdContent =
|
|
88152
|
+
const soulMdContent = existsSync73(soulMdPath) ? readFileSync64(soulMdPath, "utf-8") : existsSync73(workspaceSoulMdPath) ? readFileSync64(workspaceSoulMdPath, "utf-8") : "";
|
|
87848
88153
|
if (soulMdContent.trim().length > 0) {
|
|
87849
88154
|
console.log(`(${formatBytes(soulMdContent.length)})`);
|
|
87850
88155
|
console.log(soulMdContent);
|
|
@@ -87905,11 +88210,11 @@ function registerDebugCommand(program3) {
|
|
|
87905
88210
|
const soulMdBytes = soulMdContent.length;
|
|
87906
88211
|
const perTurnBytes = dynamicResult.concatenated.length;
|
|
87907
88212
|
const userBytes = userMessage?.text.length ?? 0;
|
|
87908
|
-
const fleetDir =
|
|
87909
|
-
const fleetInvPath =
|
|
87910
|
-
const fleetClaudePath =
|
|
87911
|
-
const fleetInvBytes =
|
|
87912
|
-
const fleetClaudeBytes =
|
|
88213
|
+
const fleetDir = join72(agentsDir, "..", "fleet");
|
|
88214
|
+
const fleetInvPath = join72(fleetDir, "switchroom-invariants.md");
|
|
88215
|
+
const fleetClaudePath = join72(fleetDir, "CLAUDE.md");
|
|
88216
|
+
const fleetInvBytes = existsSync73(fleetInvPath) ? readFileSync64(fleetInvPath, "utf-8").length : 0;
|
|
88217
|
+
const fleetClaudeBytes = existsSync73(fleetClaudePath) ? readFileSync64(fleetClaudePath, "utf-8").length : 0;
|
|
87913
88218
|
const fleetBytes = fleetInvBytes + fleetClaudeBytes;
|
|
87914
88219
|
const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
|
|
87915
88220
|
console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
|
|
@@ -87942,44 +88247,44 @@ init_source();
|
|
|
87942
88247
|
|
|
87943
88248
|
// src/worktree/claim.ts
|
|
87944
88249
|
import { execFileSync as execFileSync21 } from "node:child_process";
|
|
87945
|
-
import { closeSync as closeSync13, mkdirSync as
|
|
87946
|
-
import { join as
|
|
87947
|
-
import { homedir as
|
|
88250
|
+
import { closeSync as closeSync13, mkdirSync as mkdirSync39, openSync as openSync13, existsSync as existsSync75, unlinkSync as unlinkSync17 } from "node:fs";
|
|
88251
|
+
import { join as join74, resolve as resolve47 } from "node:path";
|
|
88252
|
+
import { homedir as homedir45 } from "node:os";
|
|
87948
88253
|
import { randomBytes as randomBytes13 } from "node:crypto";
|
|
87949
88254
|
|
|
87950
88255
|
// src/worktree/registry.ts
|
|
87951
88256
|
import {
|
|
87952
|
-
mkdirSync as
|
|
87953
|
-
writeFileSync as
|
|
87954
|
-
readFileSync as
|
|
88257
|
+
mkdirSync as mkdirSync38,
|
|
88258
|
+
writeFileSync as writeFileSync32,
|
|
88259
|
+
readFileSync as readFileSync65,
|
|
87955
88260
|
readdirSync as readdirSync25,
|
|
87956
|
-
unlinkSync as
|
|
87957
|
-
existsSync as
|
|
88261
|
+
unlinkSync as unlinkSync16,
|
|
88262
|
+
existsSync as existsSync74,
|
|
87958
88263
|
renameSync as renameSync16
|
|
87959
88264
|
} from "node:fs";
|
|
87960
|
-
import { join as
|
|
87961
|
-
import { homedir as
|
|
88265
|
+
import { join as join73, resolve as resolve46 } from "node:path";
|
|
88266
|
+
import { homedir as homedir44 } from "node:os";
|
|
87962
88267
|
function registryDir() {
|
|
87963
|
-
return resolve46(process.env.SWITCHROOM_WORKTREE_DIR ??
|
|
88268
|
+
return resolve46(process.env.SWITCHROOM_WORKTREE_DIR ?? join73(homedir44(), ".switchroom", "worktrees"));
|
|
87964
88269
|
}
|
|
87965
88270
|
function recordPath(id) {
|
|
87966
|
-
return
|
|
88271
|
+
return join73(registryDir(), `${id}.json`);
|
|
87967
88272
|
}
|
|
87968
88273
|
function ensureDir2() {
|
|
87969
|
-
|
|
88274
|
+
mkdirSync38(registryDir(), { recursive: true });
|
|
87970
88275
|
}
|
|
87971
88276
|
function writeRecord(record2) {
|
|
87972
88277
|
ensureDir2();
|
|
87973
88278
|
const target = recordPath(record2.id);
|
|
87974
88279
|
const tmp = `${target}.tmp${process.pid}`;
|
|
87975
|
-
|
|
88280
|
+
writeFileSync32(tmp, JSON.stringify(record2, null, 2) + `
|
|
87976
88281
|
`, { mode: 384 });
|
|
87977
88282
|
renameSync16(tmp, target);
|
|
87978
88283
|
}
|
|
87979
88284
|
function readRecord(id) {
|
|
87980
88285
|
const path7 = recordPath(id);
|
|
87981
88286
|
try {
|
|
87982
|
-
const raw =
|
|
88287
|
+
const raw = readFileSync65(path7, "utf8");
|
|
87983
88288
|
return JSON.parse(raw);
|
|
87984
88289
|
} catch {
|
|
87985
88290
|
return null;
|
|
@@ -87988,7 +88293,7 @@ function readRecord(id) {
|
|
|
87988
88293
|
function deleteRecord(id) {
|
|
87989
88294
|
const path7 = recordPath(id);
|
|
87990
88295
|
try {
|
|
87991
|
-
|
|
88296
|
+
unlinkSync16(path7);
|
|
87992
88297
|
} catch {}
|
|
87993
88298
|
}
|
|
87994
88299
|
function listRecords() {
|
|
@@ -88012,9 +88317,9 @@ function countByRepo(repoPath) {
|
|
|
88012
88317
|
// src/worktree/claim.ts
|
|
88013
88318
|
function acquireRepoLock(repoPath) {
|
|
88014
88319
|
const lockDir = registryDir();
|
|
88015
|
-
|
|
88320
|
+
mkdirSync39(lockDir, { recursive: true });
|
|
88016
88321
|
const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
|
|
88017
|
-
const lockPath =
|
|
88322
|
+
const lockPath = join74(lockDir, `.lock-${lockName}`);
|
|
88018
88323
|
const deadline = Date.now() + 5000;
|
|
88019
88324
|
let fd = null;
|
|
88020
88325
|
while (fd === null) {
|
|
@@ -88035,13 +88340,13 @@ function acquireRepoLock(repoPath) {
|
|
|
88035
88340
|
closeSync13(fd);
|
|
88036
88341
|
} catch {}
|
|
88037
88342
|
try {
|
|
88038
|
-
|
|
88343
|
+
unlinkSync17(lockPath);
|
|
88039
88344
|
} catch {}
|
|
88040
88345
|
};
|
|
88041
88346
|
}
|
|
88042
88347
|
var DEFAULT_CONCURRENCY = 5;
|
|
88043
88348
|
function worktreesBaseDir() {
|
|
88044
|
-
return resolve47(process.env.SWITCHROOM_WORKTREE_BASE ??
|
|
88349
|
+
return resolve47(process.env.SWITCHROOM_WORKTREE_BASE ?? join74(homedir45(), ".switchroom", "worktree-checkouts"));
|
|
88045
88350
|
}
|
|
88046
88351
|
function shortId() {
|
|
88047
88352
|
return randomBytes13(4).toString("hex");
|
|
@@ -88063,12 +88368,12 @@ function resolveRepoPath(repo, codeRepos) {
|
|
|
88063
88368
|
}
|
|
88064
88369
|
function expandHome(p) {
|
|
88065
88370
|
if (p.startsWith("~/"))
|
|
88066
|
-
return
|
|
88371
|
+
return join74(homedir45(), p.slice(2));
|
|
88067
88372
|
return p;
|
|
88068
88373
|
}
|
|
88069
88374
|
async function claimWorktree(input, codeRepos) {
|
|
88070
88375
|
const repoPath = resolveRepoPath(input.repo, codeRepos);
|
|
88071
|
-
if (!
|
|
88376
|
+
if (!existsSync75(repoPath)) {
|
|
88072
88377
|
throw new Error(`Repository path does not exist: ${repoPath}`);
|
|
88073
88378
|
}
|
|
88074
88379
|
let concurrencyCap = DEFAULT_CONCURRENCY;
|
|
@@ -88090,8 +88395,8 @@ async function claimWorktree(input, codeRepos) {
|
|
|
88090
88395
|
const taskSuffix = input.taskName ? sanitizeTaskName(input.taskName) : "task";
|
|
88091
88396
|
branch = `task/${taskSuffix}-${id}`;
|
|
88092
88397
|
const baseDir = worktreesBaseDir();
|
|
88093
|
-
|
|
88094
|
-
worktreePath =
|
|
88398
|
+
mkdirSync39(baseDir, { recursive: true });
|
|
88399
|
+
worktreePath = join74(baseDir, `${id}-${taskSuffix}`);
|
|
88095
88400
|
const ambientOwner = process.env.SWITCHROOM_AGENT_NAME;
|
|
88096
88401
|
const ownerAgent = input.ownerAgent ?? (ambientOwner != null && ambientOwner !== "" ? ambientOwner : undefined);
|
|
88097
88402
|
const now = new Date().toISOString();
|
|
@@ -88124,7 +88429,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
88124
88429
|
|
|
88125
88430
|
// src/worktree/release.ts
|
|
88126
88431
|
import { execFileSync as execFileSync22 } from "node:child_process";
|
|
88127
|
-
import { existsSync as
|
|
88432
|
+
import { existsSync as existsSync76 } from "node:fs";
|
|
88128
88433
|
function releaseWorktree(input) {
|
|
88129
88434
|
const { id } = input;
|
|
88130
88435
|
const record2 = readRecord(id);
|
|
@@ -88132,7 +88437,7 @@ function releaseWorktree(input) {
|
|
|
88132
88437
|
return { released: true };
|
|
88133
88438
|
}
|
|
88134
88439
|
let gitSuccess = true;
|
|
88135
|
-
if (
|
|
88440
|
+
if (existsSync76(record2.path)) {
|
|
88136
88441
|
try {
|
|
88137
88442
|
execFileSync22("git", ["worktree", "remove", "--force", record2.path], {
|
|
88138
88443
|
cwd: record2.repo,
|
|
@@ -88171,7 +88476,7 @@ function listWorktrees() {
|
|
|
88171
88476
|
|
|
88172
88477
|
// src/worktree/reaper.ts
|
|
88173
88478
|
import { execFileSync as execFileSync23 } from "node:child_process";
|
|
88174
|
-
import { existsSync as
|
|
88479
|
+
import { existsSync as existsSync77 } from "node:fs";
|
|
88175
88480
|
var STALE_THRESHOLD_MS = 10 * 60 * 1000;
|
|
88176
88481
|
function isPathInUse(path7) {
|
|
88177
88482
|
try {
|
|
@@ -88198,7 +88503,7 @@ function hasUncommittedChanges(repoPath, worktreePath) {
|
|
|
88198
88503
|
function reapRecord(record2) {
|
|
88199
88504
|
const { id, path: path7, repo, branch, ownerAgent } = record2;
|
|
88200
88505
|
let warning = null;
|
|
88201
|
-
if (
|
|
88506
|
+
if (existsSync77(path7)) {
|
|
88202
88507
|
if (hasUncommittedChanges(repo, path7)) {
|
|
88203
88508
|
warning = `[worktree-reaper] Reaped worktree with uncommitted changes: ` + `id=${id} branch=${branch} agent=${ownerAgent ?? "unknown"} path=${path7}`;
|
|
88204
88509
|
}
|
|
@@ -88219,7 +88524,7 @@ function runReaper(nowMs) {
|
|
|
88219
88524
|
const warnings = [];
|
|
88220
88525
|
for (const record2 of records) {
|
|
88221
88526
|
const heartbeatAge = now - new Date(record2.heartbeatAt).getTime();
|
|
88222
|
-
const worktreeExists =
|
|
88527
|
+
const worktreeExists = existsSync77(record2.path);
|
|
88223
88528
|
if (!worktreeExists) {
|
|
88224
88529
|
deleteRecord(record2.id);
|
|
88225
88530
|
reaped.push(record2.id);
|
|
@@ -88239,16 +88544,16 @@ function runReaper(nowMs) {
|
|
|
88239
88544
|
// src/worktree/gc.ts
|
|
88240
88545
|
import { execFileSync as execFileSync24 } from "node:child_process";
|
|
88241
88546
|
import {
|
|
88242
|
-
existsSync as
|
|
88243
|
-
readFileSync as
|
|
88547
|
+
existsSync as existsSync78,
|
|
88548
|
+
readFileSync as readFileSync66,
|
|
88244
88549
|
readdirSync as readdirSync26,
|
|
88245
|
-
statSync as
|
|
88550
|
+
statSync as statSync34,
|
|
88246
88551
|
renameSync as renameSync17,
|
|
88247
|
-
mkdirSync as
|
|
88552
|
+
mkdirSync as mkdirSync40,
|
|
88248
88553
|
rmSync as rmSync15
|
|
88249
88554
|
} from "node:fs";
|
|
88250
|
-
import { homedir as
|
|
88251
|
-
import { join as
|
|
88555
|
+
import { homedir as homedir46 } from "node:os";
|
|
88556
|
+
import { join as join75, resolve as resolve48 } from "node:path";
|
|
88252
88557
|
function parseGitdirPointer(dotGitFileContents) {
|
|
88253
88558
|
const m = /^gitdir:\s*(.+?)\s*$/m.exec(dotGitFileContents);
|
|
88254
88559
|
return m ? m[1] : null;
|
|
@@ -88363,17 +88668,17 @@ function defaultPrSignal(repo, branch, exec) {
|
|
|
88363
88668
|
}
|
|
88364
88669
|
}
|
|
88365
88670
|
function trashRoot() {
|
|
88366
|
-
return resolve48(process.env.SWITCHROOM_WORKTREE_TRASH ??
|
|
88671
|
+
return resolve48(process.env.SWITCHROOM_WORKTREE_TRASH ?? join75(homedir46(), ".switchroom", "worktree-gc-trash"));
|
|
88367
88672
|
}
|
|
88368
88673
|
function planGc(roots, deps = {}) {
|
|
88369
|
-
const exists = deps.existsSync ??
|
|
88674
|
+
const exists = deps.existsSync ?? existsSync78;
|
|
88370
88675
|
const readDir = deps.readDir ?? ((p) => readdirSync26(p));
|
|
88371
|
-
const readFile4 = deps.readFile ?? ((p) =>
|
|
88372
|
-
const stat3 = deps.stat ?? ((p) =>
|
|
88676
|
+
const readFile4 = deps.readFile ?? ((p) => readFileSync66(p, "utf8"));
|
|
88677
|
+
const stat3 = deps.stat ?? ((p) => statSync34(p));
|
|
88373
88678
|
const exec = deps.exec ?? defaultExec;
|
|
88374
88679
|
const prSignal = deps.prSignal ?? ((repo, branch) => defaultPrSignal(repo, branch, exec));
|
|
88375
88680
|
const stamp = deps.dateStamp ?? "undated";
|
|
88376
|
-
const trash =
|
|
88681
|
+
const trash = join75(trashRoot(), stamp);
|
|
88377
88682
|
let claimed;
|
|
88378
88683
|
try {
|
|
88379
88684
|
claimed = new Set(listRecords().map((r) => resolve48(r.path)));
|
|
@@ -88409,10 +88714,10 @@ function planGc(roots, deps = {}) {
|
|
|
88409
88714
|
continue;
|
|
88410
88715
|
}
|
|
88411
88716
|
for (const name of entries) {
|
|
88412
|
-
const dir =
|
|
88717
|
+
const dir = join75(root, name);
|
|
88413
88718
|
if (isEphemeralPath(dir))
|
|
88414
88719
|
continue;
|
|
88415
|
-
const dotGit =
|
|
88720
|
+
const dotGit = join75(dir, ".git");
|
|
88416
88721
|
if (!exists(dotGit))
|
|
88417
88722
|
continue;
|
|
88418
88723
|
let st;
|
|
@@ -88441,7 +88746,7 @@ function planGc(roots, deps = {}) {
|
|
|
88441
88746
|
ownerRepos.add(repoRoot);
|
|
88442
88747
|
if (exists(ptr))
|
|
88443
88748
|
continue;
|
|
88444
|
-
orphans.push({ dir, owner: repoRoot, dest:
|
|
88749
|
+
orphans.push({ dir, owner: repoRoot, dest: join75(trash, name) });
|
|
88445
88750
|
}
|
|
88446
88751
|
}
|
|
88447
88752
|
const registered = [];
|
|
@@ -88496,7 +88801,7 @@ function planGc(roots, deps = {}) {
|
|
|
88496
88801
|
}
|
|
88497
88802
|
function applyGc(plan, deps = {}) {
|
|
88498
88803
|
const exec = deps.exec ?? defaultExec;
|
|
88499
|
-
const mkdirp = deps.mkdirp ?? ((p) => void
|
|
88804
|
+
const mkdirp = deps.mkdirp ?? ((p) => void mkdirSync40(p, { recursive: true }));
|
|
88500
88805
|
const move = deps.move ?? ((src, dest) => {
|
|
88501
88806
|
try {
|
|
88502
88807
|
renameSync17(src, dest);
|
|
@@ -88545,14 +88850,14 @@ function selectPurgeTargets(entries, olderThanDays) {
|
|
|
88545
88850
|
return entries.filter((e) => e.ageDays >= olderThanDays).map((e) => e.path);
|
|
88546
88851
|
}
|
|
88547
88852
|
function listTrashEntries(nowMs, deps = {}) {
|
|
88548
|
-
const exists = deps.existsSync ??
|
|
88853
|
+
const exists = deps.existsSync ?? existsSync78;
|
|
88549
88854
|
const readDir = deps.readDir ?? ((p) => readdirSync26(p));
|
|
88550
88855
|
const root = trashRoot();
|
|
88551
88856
|
if (!exists(root))
|
|
88552
88857
|
return [];
|
|
88553
88858
|
const out = [];
|
|
88554
88859
|
for (const stamp of readDir(root)) {
|
|
88555
|
-
const stampDir =
|
|
88860
|
+
const stampDir = join75(root, stamp);
|
|
88556
88861
|
let names;
|
|
88557
88862
|
try {
|
|
88558
88863
|
names = readDir(stampDir);
|
|
@@ -88560,10 +88865,10 @@ function listTrashEntries(nowMs, deps = {}) {
|
|
|
88560
88865
|
continue;
|
|
88561
88866
|
}
|
|
88562
88867
|
for (const name of names) {
|
|
88563
|
-
const p =
|
|
88868
|
+
const p = join75(stampDir, name);
|
|
88564
88869
|
let mtimeMs = nowMs;
|
|
88565
88870
|
try {
|
|
88566
|
-
mtimeMs =
|
|
88871
|
+
mtimeMs = statSync34(p).mtimeMs;
|
|
88567
88872
|
} catch {}
|
|
88568
88873
|
out.push({ path: p, ageDays: (nowMs - mtimeMs) / 86400000 });
|
|
88569
88874
|
}
|
|
@@ -88584,7 +88889,7 @@ function purgeTrash(paths) {
|
|
|
88584
88889
|
return { deleted, errors: errors2 };
|
|
88585
88890
|
}
|
|
88586
88891
|
function defaultRoots() {
|
|
88587
|
-
return [
|
|
88892
|
+
return [join75(homedir46(), "code")];
|
|
88588
88893
|
}
|
|
88589
88894
|
|
|
88590
88895
|
// src/cli/worktree.ts
|
|
@@ -88773,12 +89078,12 @@ init_drive();
|
|
|
88773
89078
|
init_scaffold_integration();
|
|
88774
89079
|
import {
|
|
88775
89080
|
chmodSync as chmodSync9,
|
|
88776
|
-
mkdirSync as
|
|
89081
|
+
mkdirSync as mkdirSync41,
|
|
88777
89082
|
readdirSync as readdirSync27,
|
|
88778
89083
|
rmSync as rmSync16,
|
|
88779
|
-
writeFileSync as
|
|
89084
|
+
writeFileSync as writeFileSync33
|
|
88780
89085
|
} from "node:fs";
|
|
88781
|
-
import { join as
|
|
89086
|
+
import { join as join76 } from "node:path";
|
|
88782
89087
|
function encodeCredentialsFilename(email) {
|
|
88783
89088
|
const SAFE = new Set([
|
|
88784
89089
|
..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
|
@@ -88968,17 +89273,17 @@ function resolveCredentialsDir(env2) {
|
|
|
88968
89273
|
if (explicit && explicit.length > 0)
|
|
88969
89274
|
return explicit;
|
|
88970
89275
|
const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
|
|
88971
|
-
return
|
|
89276
|
+
return join76(stateBase, "google-workspace-mcp", "credentials");
|
|
88972
89277
|
}
|
|
88973
89278
|
function writeSeedFile(dir, email, seed) {
|
|
88974
|
-
|
|
89279
|
+
mkdirSync41(dir, { recursive: true, mode: 448 });
|
|
88975
89280
|
chmodSync9(dir, 448);
|
|
88976
89281
|
for (const name of readdirSync27(dir)) {
|
|
88977
|
-
rmSync16(
|
|
89282
|
+
rmSync16(join76(dir, name), { force: true, recursive: true });
|
|
88978
89283
|
}
|
|
88979
89284
|
const filename = encodeCredentialsFilename(email);
|
|
88980
|
-
const filePath =
|
|
88981
|
-
|
|
89285
|
+
const filePath = join76(dir, filename);
|
|
89286
|
+
writeFileSync33(filePath, JSON.stringify(seed), { mode: 384 });
|
|
88982
89287
|
chmodSync9(filePath, 384);
|
|
88983
89288
|
return filePath;
|
|
88984
89289
|
}
|
|
@@ -89136,11 +89441,19 @@ function registerDriveMcpLauncherCommand(program3) {
|
|
|
89136
89441
|
// src/cli/m365-mcp-launcher.ts
|
|
89137
89442
|
init_scaffold_integration();
|
|
89138
89443
|
import { spawn as spawn5 } from "node:child_process";
|
|
89139
|
-
import { writeFileSync as
|
|
89140
|
-
import { dirname as
|
|
89444
|
+
import { writeFileSync as writeFileSync34, mkdirSync as mkdirSync42 } from "node:fs";
|
|
89445
|
+
import { dirname as dirname24, join as join77 } from "node:path";
|
|
89141
89446
|
var SOFTERIA_TOKEN_ENV = "MS365_MCP_OAUTH_TOKEN";
|
|
89142
89447
|
var DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
|
|
89143
89448
|
var MAX_REFRESH_INTERVAL_MS = 60 * 60 * 1000;
|
|
89449
|
+
var SOFTERIA_RESTART_BASE_MS = 1000;
|
|
89450
|
+
var SOFTERIA_RESTART_MAX_MS = 30 * 1000;
|
|
89451
|
+
var SOFTERIA_STABLE_MS = 60 * 1000;
|
|
89452
|
+
var SOFTERIA_MAX_RESTARTS = 6;
|
|
89453
|
+
function computeRestartBackoffMs(attempt) {
|
|
89454
|
+
const raw = SOFTERIA_RESTART_BASE_MS * 2 ** Math.max(0, attempt);
|
|
89455
|
+
return Math.min(raw, SOFTERIA_RESTART_MAX_MS);
|
|
89456
|
+
}
|
|
89144
89457
|
function buildSofteriaArgs(opts = {}) {
|
|
89145
89458
|
const pkg = `${MICROSOFT_WORKSPACE_MCP_PACKAGE}@${MICROSOFT_WORKSPACE_MCP_PINNED_VERSION}`;
|
|
89146
89459
|
const args = ["-y", pkg];
|
|
@@ -89162,14 +89475,14 @@ function computeRefreshDelayMs(expiresAt, now, leadMs = DEFAULT_REFRESH_LEAD_MS)
|
|
|
89162
89475
|
function writeRefreshHeartbeat(agentName, data) {
|
|
89163
89476
|
const path7 = heartbeatPath(agentName);
|
|
89164
89477
|
try {
|
|
89165
|
-
|
|
89166
|
-
|
|
89478
|
+
mkdirSync42(dirname24(path7), { recursive: true });
|
|
89479
|
+
writeFileSync34(path7, JSON.stringify(data, null, 2), { mode: 420 });
|
|
89167
89480
|
} catch {}
|
|
89168
89481
|
}
|
|
89169
89482
|
function heartbeatPath(agentName) {
|
|
89170
89483
|
const override = process.env.SWITCHROOM_M365_HEARTBEAT_DIR;
|
|
89171
89484
|
if (override) {
|
|
89172
|
-
return
|
|
89485
|
+
return join77(override, `m365-launcher-${agentName}.heartbeat.json`);
|
|
89173
89486
|
}
|
|
89174
89487
|
return "/state/agent/m365-launcher.heartbeat.json";
|
|
89175
89488
|
}
|
|
@@ -89228,13 +89541,23 @@ async function runMs365McpLauncher(opts, rt) {
|
|
|
89228
89541
|
let currentChild = null;
|
|
89229
89542
|
let teardownStdio = null;
|
|
89230
89543
|
let refreshTimer = null;
|
|
89544
|
+
let restartTimer = null;
|
|
89231
89545
|
let restartingForRefresh = false;
|
|
89546
|
+
let shuttingDown = false;
|
|
89232
89547
|
let resolveLauncher = null;
|
|
89548
|
+
let currentCreds = null;
|
|
89549
|
+
let restartAttempts = 0;
|
|
89550
|
+
let lastSpawnMs = 0;
|
|
89233
89551
|
const exitLauncher = (code) => {
|
|
89552
|
+
shuttingDown = true;
|
|
89234
89553
|
if (refreshTimer) {
|
|
89235
89554
|
clearTimer(refreshTimer);
|
|
89236
89555
|
refreshTimer = null;
|
|
89237
89556
|
}
|
|
89557
|
+
if (restartTimer) {
|
|
89558
|
+
clearTimer(restartTimer);
|
|
89559
|
+
restartTimer = null;
|
|
89560
|
+
}
|
|
89238
89561
|
if (resolveLauncher) {
|
|
89239
89562
|
const r = resolveLauncher;
|
|
89240
89563
|
resolveLauncher = null;
|
|
@@ -89244,6 +89567,7 @@ async function runMs365McpLauncher(opts, rt) {
|
|
|
89244
89567
|
const launchChild = (accessToken) => {
|
|
89245
89568
|
const env2 = buildSofteriaEnv(accessToken);
|
|
89246
89569
|
const child = rt.spawnSofteria(env2);
|
|
89570
|
+
lastSpawnMs = now();
|
|
89247
89571
|
teardownStdio = wireStdio(child);
|
|
89248
89572
|
child.once("exit", (code, signal) => {
|
|
89249
89573
|
if (teardownStdio) {
|
|
@@ -89254,6 +89578,35 @@ async function runMs365McpLauncher(opts, rt) {
|
|
|
89254
89578
|
return;
|
|
89255
89579
|
}
|
|
89256
89580
|
const resolved = code ?? (signal ? 128 : 0);
|
|
89581
|
+
if (shuttingDown)
|
|
89582
|
+
return;
|
|
89583
|
+
const crashed = resolved !== 0;
|
|
89584
|
+
const tokenValid = currentCreds != null && currentCreds.expiresAt > now();
|
|
89585
|
+
if (crashed && tokenValid) {
|
|
89586
|
+
if (now() - lastSpawnMs >= SOFTERIA_STABLE_MS)
|
|
89587
|
+
restartAttempts = 0;
|
|
89588
|
+
if (restartAttempts < SOFTERIA_MAX_RESTARTS) {
|
|
89589
|
+
const delayMs = computeRestartBackoffMs(restartAttempts);
|
|
89590
|
+
restartAttempts += 1;
|
|
89591
|
+
currentChild = null;
|
|
89592
|
+
log(`m365-launcher: softeria exited (code=${resolved} signal=${signal}); re-spawning with cached token in ${Math.round(delayMs / 1000)}s (attempt ${restartAttempts}/${SOFTERIA_MAX_RESTARTS}, no broker call)`);
|
|
89593
|
+
restartTimer = setTimer(() => {
|
|
89594
|
+
restartTimer = null;
|
|
89595
|
+
if (shuttingDown)
|
|
89596
|
+
return;
|
|
89597
|
+
if (currentCreds != null && currentCreds.expiresAt > now()) {
|
|
89598
|
+
currentChild = launchChild(currentCreds.accessToken);
|
|
89599
|
+
} else {
|
|
89600
|
+
log("m365-launcher: cached token expired during backoff \u2014 exiting for fresh creds");
|
|
89601
|
+
exitLauncher(resolved);
|
|
89602
|
+
}
|
|
89603
|
+
}, delayMs);
|
|
89604
|
+
return;
|
|
89605
|
+
}
|
|
89606
|
+
log(`m365-launcher: softeria crash-looped ${restartAttempts}x within ${Math.round(SOFTERIA_STABLE_MS / 1000)}s \u2014 giving up, exiting`);
|
|
89607
|
+
exitLauncher(resolved);
|
|
89608
|
+
return;
|
|
89609
|
+
}
|
|
89257
89610
|
log(`m365-launcher: softeria exited unexpectedly code=${resolved} signal=${signal}`);
|
|
89258
89611
|
exitLauncher(resolved);
|
|
89259
89612
|
});
|
|
@@ -89271,6 +89624,10 @@ async function runMs365McpLauncher(opts, rt) {
|
|
|
89271
89624
|
refreshTimer = setTimer(async () => {
|
|
89272
89625
|
try {
|
|
89273
89626
|
log("m365-launcher: refreshing token + restarting softeria");
|
|
89627
|
+
if (restartTimer) {
|
|
89628
|
+
clearTimer(restartTimer);
|
|
89629
|
+
restartTimer = null;
|
|
89630
|
+
}
|
|
89274
89631
|
restartingForRefresh = true;
|
|
89275
89632
|
try {
|
|
89276
89633
|
process.stdin.pause();
|
|
@@ -89280,6 +89637,8 @@ async function runMs365McpLauncher(opts, rt) {
|
|
|
89280
89637
|
await killChild(currentChild);
|
|
89281
89638
|
}
|
|
89282
89639
|
restartingForRefresh = false;
|
|
89640
|
+
currentCreds = fresh;
|
|
89641
|
+
restartAttempts = 0;
|
|
89283
89642
|
currentChild = launchChild(fresh.accessToken);
|
|
89284
89643
|
try {
|
|
89285
89644
|
process.stdin.resume();
|
|
@@ -89306,6 +89665,7 @@ async function runMs365McpLauncher(opts, rt) {
|
|
|
89306
89665
|
log(`m365-launcher: initial broker call failed \u2014 ${msg}`);
|
|
89307
89666
|
return 1;
|
|
89308
89667
|
}
|
|
89668
|
+
currentCreds = initial;
|
|
89309
89669
|
currentChild = launchChild(initial.accessToken);
|
|
89310
89670
|
scheduleRefresh(initial.expiresAt);
|
|
89311
89671
|
const onSignal = (signal) => {
|
|
@@ -89354,8 +89714,8 @@ function registerM365McpLauncherCommand(program3) {
|
|
|
89354
89714
|
// src/cli/notion-mcp-launcher.ts
|
|
89355
89715
|
init_scaffold_integration();
|
|
89356
89716
|
import { spawn as spawn6 } from "node:child_process";
|
|
89357
|
-
import { existsSync as
|
|
89358
|
-
import { dirname as
|
|
89717
|
+
import { existsSync as existsSync79, mkdirSync as mkdirSync43, writeFileSync as writeFileSync35 } from "node:fs";
|
|
89718
|
+
import { dirname as dirname25 } from "node:path";
|
|
89359
89719
|
var HEARTBEAT_WRITE_INTERVAL_MS = 30 * 1000;
|
|
89360
89720
|
var DEFAULT_HEARTBEAT_PATH = "/state/agent/notion-launcher.heartbeat.json";
|
|
89361
89721
|
var DEFAULT_VAULT_KEY = "notion/integration-token";
|
|
@@ -89365,10 +89725,10 @@ function buildNotionMcpArgs(opts) {
|
|
|
89365
89725
|
}
|
|
89366
89726
|
function defaultWriteHeartbeat(path7, contents) {
|
|
89367
89727
|
try {
|
|
89368
|
-
const dir =
|
|
89369
|
-
if (!
|
|
89370
|
-
|
|
89371
|
-
|
|
89728
|
+
const dir = dirname25(path7);
|
|
89729
|
+
if (!existsSync79(dir))
|
|
89730
|
+
mkdirSync43(dir, { recursive: true });
|
|
89731
|
+
writeFileSync35(path7, contents);
|
|
89372
89732
|
} catch {}
|
|
89373
89733
|
}
|
|
89374
89734
|
async function runNotionMcpLauncher(opts, runtime) {
|
|
@@ -89478,7 +89838,7 @@ function registerNotionMcpLauncherCommand(program3) {
|
|
|
89478
89838
|
|
|
89479
89839
|
// src/cli/deliver-file.ts
|
|
89480
89840
|
init_client2();
|
|
89481
|
-
import { readFileSync as
|
|
89841
|
+
import { readFileSync as readFileSync67, statSync as statSync35 } from "node:fs";
|
|
89482
89842
|
import { basename as basename11 } from "node:path";
|
|
89483
89843
|
|
|
89484
89844
|
// src/delivery/onedrive.ts
|
|
@@ -89817,8 +90177,8 @@ async function defaultResolveProvider() {
|
|
|
89817
90177
|
}
|
|
89818
90178
|
async function runDeliverFile(localPath, deps = {}) {
|
|
89819
90179
|
const agentName = safeAgentName(deps.agentName ?? process.env.SWITCHROOM_AGENT_NAME);
|
|
89820
|
-
const sizeOf = deps.fileSize ?? ((p) =>
|
|
89821
|
-
const read = deps.readFile ?? ((p) => new Uint8Array(
|
|
90180
|
+
const sizeOf = deps.fileSize ?? ((p) => statSync35(p).size);
|
|
90181
|
+
const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync67(p)));
|
|
89822
90182
|
const resolveProvider = deps.resolveProvider ?? defaultResolveProvider;
|
|
89823
90183
|
let size;
|
|
89824
90184
|
try {
|
|
@@ -90139,9 +90499,9 @@ function runRedactStdin() {
|
|
|
90139
90499
|
}
|
|
90140
90500
|
|
|
90141
90501
|
// src/cli/status-ask.ts
|
|
90142
|
-
import { readFileSync as
|
|
90143
|
-
import { join as
|
|
90144
|
-
import { homedir as
|
|
90502
|
+
import { readFileSync as readFileSync71, existsSync as existsSync84, readdirSync as readdirSync29 } from "node:fs";
|
|
90503
|
+
import { join as join82 } from "node:path";
|
|
90504
|
+
import { homedir as homedir49 } from "node:os";
|
|
90145
90505
|
|
|
90146
90506
|
// src/status-ask/report.ts
|
|
90147
90507
|
function parseJsonl(content) {
|
|
@@ -90415,7 +90775,7 @@ function runReport(opts) {
|
|
|
90415
90775
|
for (const src of sources) {
|
|
90416
90776
|
let content;
|
|
90417
90777
|
try {
|
|
90418
|
-
content =
|
|
90778
|
+
content = readFileSync71(src.path, "utf-8");
|
|
90419
90779
|
} catch (err) {
|
|
90420
90780
|
process.stderr.write(`status-ask report: cannot read ${src.path}: ${err instanceof Error ? err.message : String(err)}
|
|
90421
90781
|
`);
|
|
@@ -90462,7 +90822,7 @@ function runReport(opts) {
|
|
|
90462
90822
|
function resolveSources(explicitPath) {
|
|
90463
90823
|
if (explicitPath != null && explicitPath.trim() !== "") {
|
|
90464
90824
|
const trimmed = explicitPath.trim();
|
|
90465
|
-
if (!
|
|
90825
|
+
if (!existsSync84(trimmed)) {
|
|
90466
90826
|
process.stderr.write(`status-ask report: ${trimmed}: file not found
|
|
90467
90827
|
`);
|
|
90468
90828
|
process.exit(1);
|
|
@@ -90476,9 +90836,9 @@ function resolveSources(explicitPath) {
|
|
|
90476
90836
|
const config = loadConfig();
|
|
90477
90837
|
agentsDir = resolveAgentsDir(config);
|
|
90478
90838
|
} catch {
|
|
90479
|
-
agentsDir =
|
|
90839
|
+
agentsDir = join82(homedir49(), ".switchroom", "agents");
|
|
90480
90840
|
}
|
|
90481
|
-
if (!
|
|
90841
|
+
if (!existsSync84(agentsDir))
|
|
90482
90842
|
return [];
|
|
90483
90843
|
const sources = [];
|
|
90484
90844
|
let entries;
|
|
@@ -90488,8 +90848,8 @@ function resolveSources(explicitPath) {
|
|
|
90488
90848
|
return [];
|
|
90489
90849
|
}
|
|
90490
90850
|
for (const name of entries) {
|
|
90491
|
-
const path8 =
|
|
90492
|
-
if (
|
|
90851
|
+
const path8 = join82(agentsDir, name, "runtime-metrics.jsonl");
|
|
90852
|
+
if (existsSync84(path8)) {
|
|
90493
90853
|
sources.push({ path: path8, agent: name });
|
|
90494
90854
|
}
|
|
90495
90855
|
}
|
|
@@ -90519,45 +90879,45 @@ var import_yaml21 = __toESM(require_dist(), 1);
|
|
|
90519
90879
|
init_paths();
|
|
90520
90880
|
import {
|
|
90521
90881
|
closeSync as closeSync14,
|
|
90522
|
-
existsSync as
|
|
90882
|
+
existsSync as existsSync85,
|
|
90523
90883
|
fsyncSync as fsyncSync8,
|
|
90524
|
-
mkdirSync as
|
|
90884
|
+
mkdirSync as mkdirSync48,
|
|
90525
90885
|
openSync as openSync14,
|
|
90526
90886
|
readdirSync as readdirSync30,
|
|
90527
|
-
readFileSync as
|
|
90887
|
+
readFileSync as readFileSync72,
|
|
90528
90888
|
renameSync as renameSync19,
|
|
90529
|
-
statSync as
|
|
90530
|
-
unlinkSync as
|
|
90889
|
+
statSync as statSync36,
|
|
90890
|
+
unlinkSync as unlinkSync18,
|
|
90531
90891
|
writeSync as writeSync10
|
|
90532
90892
|
} from "node:fs";
|
|
90533
|
-
import { join as
|
|
90893
|
+
import { join as join83, resolve as resolve51 } from "node:path";
|
|
90534
90894
|
var STAGING_SUBDIR = ".staging";
|
|
90535
90895
|
function overlayPathsFor(agent, opts = {}) {
|
|
90536
90896
|
const base = opts.root ? resolve51(opts.root, agent) : resolve51(resolveDualPath(`~/.switchroom/agents/${agent}`));
|
|
90537
|
-
const scheduleDir =
|
|
90538
|
-
const scheduleStagingDir =
|
|
90539
|
-
const skillsDir =
|
|
90540
|
-
const skillsStagingDir =
|
|
90897
|
+
const scheduleDir = join83(base, "schedule.d");
|
|
90898
|
+
const scheduleStagingDir = join83(scheduleDir, STAGING_SUBDIR);
|
|
90899
|
+
const skillsDir = join83(base, "skills.d");
|
|
90900
|
+
const skillsStagingDir = join83(skillsDir, STAGING_SUBDIR);
|
|
90541
90901
|
return {
|
|
90542
90902
|
agentRoot: base,
|
|
90543
90903
|
scheduleDir,
|
|
90544
90904
|
scheduleStagingDir,
|
|
90545
90905
|
skillsDir,
|
|
90546
90906
|
skillsStagingDir,
|
|
90547
|
-
lockPath:
|
|
90907
|
+
lockPath: join83(base, ".lock"),
|
|
90548
90908
|
stagingDir: scheduleStagingDir
|
|
90549
90909
|
};
|
|
90550
90910
|
}
|
|
90551
90911
|
function ensureDirs(paths) {
|
|
90552
|
-
|
|
90553
|
-
|
|
90912
|
+
mkdirSync48(paths.scheduleDir, { recursive: true });
|
|
90913
|
+
mkdirSync48(paths.scheduleStagingDir, { recursive: true });
|
|
90554
90914
|
}
|
|
90555
90915
|
function ensureSkillsDirs(paths) {
|
|
90556
|
-
|
|
90557
|
-
|
|
90916
|
+
mkdirSync48(paths.skillsDir, { recursive: true });
|
|
90917
|
+
mkdirSync48(paths.skillsStagingDir, { recursive: true });
|
|
90558
90918
|
}
|
|
90559
90919
|
function withAgentLock(paths, fn) {
|
|
90560
|
-
|
|
90920
|
+
mkdirSync48(paths.agentRoot, { recursive: true });
|
|
90561
90921
|
const start = Date.now();
|
|
90562
90922
|
const TIMEOUT_MS = 5000;
|
|
90563
90923
|
let fd = null;
|
|
@@ -90570,9 +90930,9 @@ function withAgentLock(paths, fn) {
|
|
|
90570
90930
|
if (e.code !== "EEXIST")
|
|
90571
90931
|
throw err;
|
|
90572
90932
|
try {
|
|
90573
|
-
const age = Date.now() -
|
|
90933
|
+
const age = Date.now() - statSync36(paths.lockPath).mtimeMs;
|
|
90574
90934
|
if (age > 30000) {
|
|
90575
|
-
|
|
90935
|
+
unlinkSync18(paths.lockPath);
|
|
90576
90936
|
continue;
|
|
90577
90937
|
}
|
|
90578
90938
|
} catch {}
|
|
@@ -90590,7 +90950,7 @@ function withAgentLock(paths, fn) {
|
|
|
90590
90950
|
closeSync14(fd);
|
|
90591
90951
|
} catch {}
|
|
90592
90952
|
try {
|
|
90593
|
-
|
|
90953
|
+
unlinkSync18(paths.lockPath);
|
|
90594
90954
|
} catch {}
|
|
90595
90955
|
}
|
|
90596
90956
|
}
|
|
@@ -90598,8 +90958,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
90598
90958
|
const paths = overlayPathsFor(agent, opts);
|
|
90599
90959
|
return withAgentLock(paths, () => {
|
|
90600
90960
|
ensureDirs(paths);
|
|
90601
|
-
const stagingPath =
|
|
90602
|
-
const finalPath =
|
|
90961
|
+
const stagingPath = join83(paths.scheduleStagingDir, `${slug}.yaml`);
|
|
90962
|
+
const finalPath = join83(paths.scheduleDir, `${slug}.yaml`);
|
|
90603
90963
|
const fd = openSync14(stagingPath, "w", 384);
|
|
90604
90964
|
try {
|
|
90605
90965
|
writeSync10(fd, yamlText);
|
|
@@ -90615,8 +90975,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
90615
90975
|
const paths = overlayPathsFor(agent, opts);
|
|
90616
90976
|
return withAgentLock(paths, () => {
|
|
90617
90977
|
ensureSkillsDirs(paths);
|
|
90618
|
-
const stagingPath =
|
|
90619
|
-
const finalPath =
|
|
90978
|
+
const stagingPath = join83(paths.skillsStagingDir, `${slug}.yaml`);
|
|
90979
|
+
const finalPath = join83(paths.skillsDir, `${slug}.yaml`);
|
|
90620
90980
|
const fd = openSync14(stagingPath, "w", 384);
|
|
90621
90981
|
try {
|
|
90622
90982
|
writeSync10(fd, yamlText);
|
|
@@ -90631,24 +90991,24 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
90631
90991
|
function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
90632
90992
|
const paths = overlayPathsFor(agent, opts);
|
|
90633
90993
|
return withAgentLock(paths, () => {
|
|
90634
|
-
const finalPath =
|
|
90635
|
-
if (!
|
|
90994
|
+
const finalPath = join83(paths.skillsDir, `${slug}.yaml`);
|
|
90995
|
+
if (!existsSync85(finalPath))
|
|
90636
90996
|
return false;
|
|
90637
|
-
|
|
90997
|
+
unlinkSync18(finalPath);
|
|
90638
90998
|
return true;
|
|
90639
90999
|
});
|
|
90640
91000
|
}
|
|
90641
91001
|
function listSkillsOverlayEntries(agent, opts = {}) {
|
|
90642
91002
|
const paths = overlayPathsFor(agent, opts);
|
|
90643
|
-
if (!
|
|
91003
|
+
if (!existsSync85(paths.skillsDir))
|
|
90644
91004
|
return [];
|
|
90645
91005
|
const out = [];
|
|
90646
91006
|
for (const name of readdirSync30(paths.skillsDir)) {
|
|
90647
91007
|
if (!/\.ya?ml$/i.test(name))
|
|
90648
91008
|
continue;
|
|
90649
|
-
const full =
|
|
91009
|
+
const full = join83(paths.skillsDir, name);
|
|
90650
91010
|
try {
|
|
90651
|
-
const raw =
|
|
91011
|
+
const raw = readFileSync72(full, "utf-8");
|
|
90652
91012
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
90653
91013
|
out.push({ slug, path: full, raw });
|
|
90654
91014
|
} catch {}
|
|
@@ -90658,24 +91018,24 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
90658
91018
|
function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
90659
91019
|
const paths = overlayPathsFor(agent, opts);
|
|
90660
91020
|
return withAgentLock(paths, () => {
|
|
90661
|
-
const finalPath =
|
|
90662
|
-
if (!
|
|
91021
|
+
const finalPath = join83(paths.scheduleDir, `${slug}.yaml`);
|
|
91022
|
+
if (!existsSync85(finalPath))
|
|
90663
91023
|
return false;
|
|
90664
|
-
|
|
91024
|
+
unlinkSync18(finalPath);
|
|
90665
91025
|
return true;
|
|
90666
91026
|
});
|
|
90667
91027
|
}
|
|
90668
91028
|
function listOverlayEntries(agent, opts = {}) {
|
|
90669
91029
|
const paths = overlayPathsFor(agent, opts);
|
|
90670
|
-
if (!
|
|
91030
|
+
if (!existsSync85(paths.scheduleDir))
|
|
90671
91031
|
return [];
|
|
90672
91032
|
const out = [];
|
|
90673
91033
|
for (const name of readdirSync30(paths.scheduleDir)) {
|
|
90674
91034
|
if (!/\.ya?ml$/i.test(name))
|
|
90675
91035
|
continue;
|
|
90676
|
-
const full =
|
|
91036
|
+
const full = join83(paths.scheduleDir, name);
|
|
90677
91037
|
try {
|
|
90678
|
-
const raw =
|
|
91038
|
+
const raw = readFileSync72(full, "utf-8");
|
|
90679
91039
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
90680
91040
|
out.push({ slug, path: full, raw });
|
|
90681
91041
|
} catch {}
|
|
@@ -90826,27 +91186,27 @@ function reconcileAgentCronOnly(agent) {
|
|
|
90826
91186
|
// src/cli/agent-config-pending.ts
|
|
90827
91187
|
import {
|
|
90828
91188
|
closeSync as closeSync15,
|
|
90829
|
-
existsSync as
|
|
91189
|
+
existsSync as existsSync86,
|
|
90830
91190
|
fsyncSync as fsyncSync9,
|
|
90831
|
-
mkdirSync as
|
|
91191
|
+
mkdirSync as mkdirSync49,
|
|
90832
91192
|
openSync as openSync15,
|
|
90833
91193
|
readdirSync as readdirSync31,
|
|
90834
|
-
readFileSync as
|
|
91194
|
+
readFileSync as readFileSync73,
|
|
90835
91195
|
renameSync as renameSync20,
|
|
90836
|
-
unlinkSync as
|
|
90837
|
-
writeFileSync as
|
|
91196
|
+
unlinkSync as unlinkSync19,
|
|
91197
|
+
writeFileSync as writeFileSync40,
|
|
90838
91198
|
writeSync as writeSync11
|
|
90839
91199
|
} from "node:fs";
|
|
90840
|
-
import { join as
|
|
91200
|
+
import { join as join84 } from "node:path";
|
|
90841
91201
|
import { randomBytes as randomBytes15 } from "node:crypto";
|
|
90842
91202
|
var STAGE_ID_PREFIX = "cap_";
|
|
90843
91203
|
function pendingDir(agent, opts = {}) {
|
|
90844
91204
|
const paths = overlayPathsFor(agent, opts);
|
|
90845
|
-
return
|
|
91205
|
+
return join84(paths.scheduleDir, ".pending");
|
|
90846
91206
|
}
|
|
90847
91207
|
function ensurePendingDir(agent, opts = {}) {
|
|
90848
91208
|
const dir = pendingDir(agent, opts);
|
|
90849
|
-
|
|
91209
|
+
mkdirSync49(dir, { recursive: true });
|
|
90850
91210
|
return dir;
|
|
90851
91211
|
}
|
|
90852
91212
|
function newStageId() {
|
|
@@ -90855,8 +91215,8 @@ function newStageId() {
|
|
|
90855
91215
|
function stagePendingScheduleEntry(opts) {
|
|
90856
91216
|
const dir = ensurePendingDir(opts.agent, { root: opts.root });
|
|
90857
91217
|
const stageId = opts.stageId ?? newStageId();
|
|
90858
|
-
const yamlPath =
|
|
90859
|
-
const metaPath =
|
|
91218
|
+
const yamlPath = join84(dir, `${stageId}.yaml`);
|
|
91219
|
+
const metaPath = join84(dir, `${stageId}.meta.json`);
|
|
90860
91220
|
const meta = {
|
|
90861
91221
|
v: 1,
|
|
90862
91222
|
stage_id: stageId,
|
|
@@ -90877,25 +91237,25 @@ function stagePendingScheduleEntry(opts) {
|
|
|
90877
91237
|
}
|
|
90878
91238
|
renameSync20(yamlTmp, yamlPath);
|
|
90879
91239
|
}
|
|
90880
|
-
|
|
91240
|
+
writeFileSync40(metaPath, JSON.stringify(meta, null, 2) + `
|
|
90881
91241
|
`, { mode: 384 });
|
|
90882
91242
|
return { stageId, yamlPath, metaPath };
|
|
90883
91243
|
}
|
|
90884
91244
|
function listPendingScheduleEntries(agent, opts = {}) {
|
|
90885
91245
|
const dir = pendingDir(agent, opts);
|
|
90886
|
-
if (!
|
|
91246
|
+
if (!existsSync86(dir))
|
|
90887
91247
|
return [];
|
|
90888
91248
|
const out = [];
|
|
90889
91249
|
for (const name of readdirSync31(dir).sort()) {
|
|
90890
91250
|
if (!name.endsWith(".meta.json"))
|
|
90891
91251
|
continue;
|
|
90892
91252
|
const stageId = name.slice(0, -".meta.json".length);
|
|
90893
|
-
const metaPath =
|
|
90894
|
-
const yamlPath =
|
|
90895
|
-
if (!
|
|
91253
|
+
const metaPath = join84(dir, name);
|
|
91254
|
+
const yamlPath = join84(dir, `${stageId}.yaml`);
|
|
91255
|
+
if (!existsSync86(yamlPath))
|
|
90896
91256
|
continue;
|
|
90897
91257
|
try {
|
|
90898
|
-
const meta = JSON.parse(
|
|
91258
|
+
const meta = JSON.parse(readFileSync73(metaPath, "utf-8"));
|
|
90899
91259
|
if (meta?.v !== 1 || typeof meta.stage_id !== "string")
|
|
90900
91260
|
continue;
|
|
90901
91261
|
out.push({ stageId: meta.stage_id, agent: meta.agent, yamlPath, metaPath, meta });
|
|
@@ -90910,12 +91270,12 @@ function commitPendingScheduleEntry(opts) {
|
|
|
90910
91270
|
return { committed: false, reason: "not_found" };
|
|
90911
91271
|
const slug = match.meta.entry.name ?? match.stageId;
|
|
90912
91272
|
const paths = overlayPathsFor(opts.agent, { root: opts.root });
|
|
90913
|
-
const finalPath =
|
|
90914
|
-
if (
|
|
91273
|
+
const finalPath = join84(paths.scheduleDir, `${slug}.yaml`);
|
|
91274
|
+
if (existsSync86(finalPath)) {
|
|
90915
91275
|
return { committed: false, reason: "slug_collision" };
|
|
90916
91276
|
}
|
|
90917
91277
|
renameSync20(match.yamlPath, finalPath);
|
|
90918
|
-
|
|
91278
|
+
unlinkSync19(match.metaPath);
|
|
90919
91279
|
return { committed: true, path: finalPath, slug };
|
|
90920
91280
|
}
|
|
90921
91281
|
function denyPendingScheduleEntry(opts) {
|
|
@@ -90924,16 +91284,16 @@ function denyPendingScheduleEntry(opts) {
|
|
|
90924
91284
|
if (!match)
|
|
90925
91285
|
return { denied: false, reason: "not_found" };
|
|
90926
91286
|
try {
|
|
90927
|
-
|
|
91287
|
+
unlinkSync19(match.yamlPath);
|
|
90928
91288
|
} catch {}
|
|
90929
91289
|
try {
|
|
90930
|
-
|
|
91290
|
+
unlinkSync19(match.metaPath);
|
|
90931
91291
|
} catch {}
|
|
90932
91292
|
return { denied: true };
|
|
90933
91293
|
}
|
|
90934
91294
|
|
|
90935
91295
|
// src/cli/agent-config-write.ts
|
|
90936
|
-
import { existsSync as
|
|
91296
|
+
import { existsSync as existsSync87, readFileSync as readFileSync74 } from "node:fs";
|
|
90937
91297
|
import { execFileSync as execFileSync26 } from "node:child_process";
|
|
90938
91298
|
|
|
90939
91299
|
// src/scheduler/schedule-report.ts
|
|
@@ -91345,8 +91705,8 @@ function scheduleRemove(opts) {
|
|
|
91345
91705
|
}
|
|
91346
91706
|
let priorContent = null;
|
|
91347
91707
|
try {
|
|
91348
|
-
if (
|
|
91349
|
-
priorContent =
|
|
91708
|
+
if (existsSync87(match.path))
|
|
91709
|
+
priorContent = readFileSync74(match.path, "utf-8");
|
|
91350
91710
|
} catch {}
|
|
91351
91711
|
deleteOverlayEntry(agent, match.slug, { root: opts.root });
|
|
91352
91712
|
const reconcileFn = opts.reconcile === undefined ? opts.root ? null : reconcileAgentCronOnly : opts.reconcile;
|
|
@@ -91549,7 +91909,7 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
91549
91909
|
}
|
|
91550
91910
|
let blob;
|
|
91551
91911
|
if (opts.jsonl) {
|
|
91552
|
-
blob =
|
|
91912
|
+
blob = existsSync87(opts.jsonl) ? readFileSync74(opts.jsonl, "utf-8") : "";
|
|
91553
91913
|
} else {
|
|
91554
91914
|
try {
|
|
91555
91915
|
blob = execFileSync26("docker", ["exec", `switchroom-${agent}`, "cat", "/state/agent/scheduler.jsonl"], {
|
|
@@ -91581,11 +91941,11 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
91581
91941
|
|
|
91582
91942
|
// src/cli/agent-config-skill-write.ts
|
|
91583
91943
|
var import_yaml22 = __toESM(require_dist(), 1);
|
|
91584
|
-
import { existsSync as
|
|
91944
|
+
import { existsSync as existsSync88 } from "node:fs";
|
|
91585
91945
|
init_reconcile_default_skills();
|
|
91586
91946
|
init_agent_config();
|
|
91587
91947
|
var import_yaml23 = __toESM(require_dist(), 1);
|
|
91588
|
-
import { join as
|
|
91948
|
+
import { join as join85 } from "node:path";
|
|
91589
91949
|
var MAX_SKILLS_PER_AGENT = 20;
|
|
91590
91950
|
var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
|
|
91591
91951
|
function exitCodeFor2(code) {
|
|
@@ -91660,8 +92020,8 @@ function skillInstall(opts) {
|
|
|
91660
92020
|
return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
|
|
91661
92021
|
}
|
|
91662
92022
|
const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
|
|
91663
|
-
const skillPath =
|
|
91664
|
-
if (!
|
|
92023
|
+
const skillPath = join85(poolDir, skillName);
|
|
92024
|
+
if (!existsSync88(skillPath)) {
|
|
91665
92025
|
return err("E_SKILL_NOT_FOUND", `bundled skill not found at ${skillPath}. The operator needs to ` + `place the skill at this path before the agent can opt in.`);
|
|
91666
92026
|
}
|
|
91667
92027
|
const yamlText = import_yaml22.stringify({ skills: [skillName] });
|
|
@@ -91825,21 +92185,21 @@ function registerAgentConfigSkillWriteCommands(program3) {
|
|
|
91825
92185
|
// src/cli/skill.ts
|
|
91826
92186
|
import {
|
|
91827
92187
|
closeSync as closeSync16,
|
|
91828
|
-
existsSync as
|
|
92188
|
+
existsSync as existsSync89,
|
|
91829
92189
|
lstatSync as lstatSync9,
|
|
91830
|
-
mkdirSync as
|
|
92190
|
+
mkdirSync as mkdirSync50,
|
|
91831
92191
|
mkdtempSync as mkdtempSync5,
|
|
91832
92192
|
openSync as openSync16,
|
|
91833
|
-
readFileSync as
|
|
92193
|
+
readFileSync as readFileSync75,
|
|
91834
92194
|
readdirSync as readdirSync32,
|
|
91835
92195
|
realpathSync as realpathSync7,
|
|
91836
92196
|
renameSync as renameSync21,
|
|
91837
92197
|
rmSync as rmSync18,
|
|
91838
|
-
statSync as
|
|
91839
|
-
writeFileSync as
|
|
92198
|
+
statSync as statSync37,
|
|
92199
|
+
writeFileSync as writeFileSync41
|
|
91840
92200
|
} from "node:fs";
|
|
91841
|
-
import { tmpdir as tmpdir5, homedir as
|
|
91842
|
-
import { dirname as
|
|
92201
|
+
import { tmpdir as tmpdir5, homedir as homedir50 } from "node:os";
|
|
92202
|
+
import { dirname as dirname30, join as join86, relative as relative2, resolve as resolve52 } from "node:path";
|
|
91843
92203
|
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
91844
92204
|
|
|
91845
92205
|
// src/cli/skill-common.ts
|
|
@@ -92073,10 +92433,10 @@ function scanForClaudeP2(content) {
|
|
|
92073
92433
|
function resolveSkillsPoolDir2(override) {
|
|
92074
92434
|
const raw = override ?? "~/.switchroom/skills";
|
|
92075
92435
|
if (raw.startsWith("~/")) {
|
|
92076
|
-
return
|
|
92436
|
+
return join86(homedir50(), raw.slice(2));
|
|
92077
92437
|
}
|
|
92078
92438
|
if (raw === "~")
|
|
92079
|
-
return
|
|
92439
|
+
return homedir50();
|
|
92080
92440
|
return resolve52(raw);
|
|
92081
92441
|
}
|
|
92082
92442
|
function readStdinSync() {
|
|
@@ -92105,14 +92465,14 @@ function isTarballPath(p) {
|
|
|
92105
92465
|
}
|
|
92106
92466
|
function loadFromDir(dir) {
|
|
92107
92467
|
const abs = realpathSync7(dir);
|
|
92108
|
-
if (!
|
|
92468
|
+
if (!statSync37(abs).isDirectory()) {
|
|
92109
92469
|
fail3(`--from path is not a directory: ${dir}`);
|
|
92110
92470
|
}
|
|
92111
92471
|
const files = {};
|
|
92112
92472
|
const walk2 = (sub) => {
|
|
92113
92473
|
const entries = readdirSync32(sub, { withFileTypes: true });
|
|
92114
92474
|
for (const ent of entries) {
|
|
92115
|
-
const full =
|
|
92475
|
+
const full = join86(sub, ent.name);
|
|
92116
92476
|
const rel = relative2(abs, full);
|
|
92117
92477
|
if (ent.isSymbolicLink()) {
|
|
92118
92478
|
fail3(`refusing to read symlink inside --from dir: ${rel}`);
|
|
@@ -92122,7 +92482,7 @@ function loadFromDir(dir) {
|
|
|
92122
92482
|
continue;
|
|
92123
92483
|
}
|
|
92124
92484
|
if (ent.isFile()) {
|
|
92125
|
-
const buf =
|
|
92485
|
+
const buf = readFileSync75(full);
|
|
92126
92486
|
files[rel.replace(/\\/g, "/")] = buf.toString("utf-8");
|
|
92127
92487
|
}
|
|
92128
92488
|
}
|
|
@@ -92147,7 +92507,7 @@ function loadFromTarball(tarPath) {
|
|
|
92147
92507
|
fail3(`tarball contains disallowed path: ${JSON.stringify(entry)} \u2014 ` + `refusing to extract before any file is written`);
|
|
92148
92508
|
}
|
|
92149
92509
|
}
|
|
92150
|
-
const staging = mkdtempSync5(
|
|
92510
|
+
const staging = mkdtempSync5(join86(tmpdir5(), "skill-apply-extract-"));
|
|
92151
92511
|
try {
|
|
92152
92512
|
const flags = isGz ? ["-xzf"] : ["-xf"];
|
|
92153
92513
|
const r = spawnSync15("tar", [
|
|
@@ -92171,7 +92531,7 @@ function loadFromTarball(tarPath) {
|
|
|
92171
92531
|
}
|
|
92172
92532
|
}
|
|
92173
92533
|
function loadSingleFile(filePath) {
|
|
92174
|
-
const content =
|
|
92534
|
+
const content = readFileSync75(filePath, "utf-8");
|
|
92175
92535
|
return { "SKILL.md": content };
|
|
92176
92536
|
}
|
|
92177
92537
|
function loadFromStdin() {
|
|
@@ -92233,10 +92593,10 @@ function validatePayload(name, files) {
|
|
|
92233
92593
|
errors2.push(`${path8} fails \`bash -n\` syntax check: ${(r.stderr ?? "").trim()}`);
|
|
92234
92594
|
}
|
|
92235
92595
|
} else if (PY_SCRIPT_RE2.test(path8)) {
|
|
92236
|
-
const tmp = mkdtempSync5(
|
|
92237
|
-
const tmpPy =
|
|
92596
|
+
const tmp = mkdtempSync5(join86(tmpdir5(), "skill-apply-py-"));
|
|
92597
|
+
const tmpPy = join86(tmp, "check.py");
|
|
92238
92598
|
try {
|
|
92239
|
-
|
|
92599
|
+
writeFileSync41(tmpPy, content);
|
|
92240
92600
|
const r = spawnSync15("python3", ["-m", "py_compile", tmpPy], {
|
|
92241
92601
|
encoding: "utf-8"
|
|
92242
92602
|
});
|
|
@@ -92254,15 +92614,15 @@ function validatePayload(name, files) {
|
|
|
92254
92614
|
function diffSummary(currentDir, files) {
|
|
92255
92615
|
const lines = [];
|
|
92256
92616
|
const currentFiles = {};
|
|
92257
|
-
if (
|
|
92617
|
+
if (existsSync89(currentDir)) {
|
|
92258
92618
|
const walk2 = (sub) => {
|
|
92259
92619
|
for (const ent of readdirSync32(sub, { withFileTypes: true })) {
|
|
92260
|
-
const full =
|
|
92620
|
+
const full = join86(sub, ent.name);
|
|
92261
92621
|
const rel = relative2(currentDir, full);
|
|
92262
92622
|
if (ent.isDirectory()) {
|
|
92263
92623
|
walk2(full);
|
|
92264
92624
|
} else if (ent.isFile()) {
|
|
92265
|
-
currentFiles[rel.replace(/\\/g, "/")] =
|
|
92625
|
+
currentFiles[rel.replace(/\\/g, "/")] = readFileSync75(full, "utf-8");
|
|
92266
92626
|
}
|
|
92267
92627
|
}
|
|
92268
92628
|
};
|
|
@@ -92290,10 +92650,10 @@ function diffSummary(currentDir, files) {
|
|
|
92290
92650
|
`);
|
|
92291
92651
|
}
|
|
92292
92652
|
function writePayload(poolDir, name, files) {
|
|
92293
|
-
if (!
|
|
92294
|
-
|
|
92653
|
+
if (!existsSync89(poolDir)) {
|
|
92654
|
+
mkdirSync50(poolDir, { recursive: true, mode: 493 });
|
|
92295
92655
|
}
|
|
92296
|
-
const target =
|
|
92656
|
+
const target = join86(poolDir, name);
|
|
92297
92657
|
let targetIsSymlink = false;
|
|
92298
92658
|
try {
|
|
92299
92659
|
const st = lstatSync9(target);
|
|
@@ -92304,15 +92664,15 @@ function writePayload(poolDir, name, files) {
|
|
|
92304
92664
|
if (targetIsSymlink) {
|
|
92305
92665
|
fail3(`refusing to overwrite symlink at ${target}; investigate manually`);
|
|
92306
92666
|
}
|
|
92307
|
-
const staging = mkdtempSync5(
|
|
92667
|
+
const staging = mkdtempSync5(join86(poolDir, `.skill-apply-stage-${name}-`));
|
|
92308
92668
|
let oldRename = null;
|
|
92309
92669
|
try {
|
|
92310
92670
|
for (const [path8, content] of Object.entries(files)) {
|
|
92311
|
-
const full =
|
|
92312
|
-
|
|
92671
|
+
const full = join86(staging, path8);
|
|
92672
|
+
mkdirSync50(dirname30(full), { recursive: true, mode: 493 });
|
|
92313
92673
|
const fd = openSync16(full, "wx");
|
|
92314
92674
|
try {
|
|
92315
|
-
|
|
92675
|
+
writeFileSync41(fd, content);
|
|
92316
92676
|
} finally {
|
|
92317
92677
|
closeSync16(fd);
|
|
92318
92678
|
}
|
|
@@ -92339,9 +92699,9 @@ function writePayload(poolDir, name, files) {
|
|
|
92339
92699
|
try {
|
|
92340
92700
|
rmSync18(staging, { recursive: true, force: true });
|
|
92341
92701
|
} catch {}
|
|
92342
|
-
if (oldRename &&
|
|
92702
|
+
if (oldRename && existsSync89(oldRename)) {
|
|
92343
92703
|
try {
|
|
92344
|
-
if (
|
|
92704
|
+
if (existsSync89(target)) {
|
|
92345
92705
|
rmSync18(target, { recursive: true, force: true });
|
|
92346
92706
|
}
|
|
92347
92707
|
renameSync21(oldRename, target);
|
|
@@ -92362,10 +92722,10 @@ function registerSkillCommand(program3) {
|
|
|
92362
92722
|
files = loadFromStdin();
|
|
92363
92723
|
} else {
|
|
92364
92724
|
const fromPath = resolve52(opts.from);
|
|
92365
|
-
if (!
|
|
92725
|
+
if (!existsSync89(fromPath)) {
|
|
92366
92726
|
fail3(`--from path does not exist: ${opts.from}`);
|
|
92367
92727
|
}
|
|
92368
|
-
const st =
|
|
92728
|
+
const st = statSync37(fromPath);
|
|
92369
92729
|
if (st.isDirectory()) {
|
|
92370
92730
|
files = loadFromDir(fromPath);
|
|
92371
92731
|
} else if (isTarballPath(fromPath)) {
|
|
@@ -92386,7 +92746,7 @@ function registerSkillCommand(program3) {
|
|
|
92386
92746
|
}
|
|
92387
92747
|
const config = loadConfig();
|
|
92388
92748
|
const poolDir = resolveSkillsPoolDir2(config.switchroom?.skills_dir);
|
|
92389
|
-
const currentDir =
|
|
92749
|
+
const currentDir = join86(poolDir, name);
|
|
92390
92750
|
console.log(source_default.bold(`Skill: ${name}`) + source_default.gray(` (${Object.keys(files).length} files, ${sumBytes(files)} bytes)`));
|
|
92391
92751
|
console.log(source_default.bold("Diff vs current pool content:"));
|
|
92392
92752
|
console.log(diffSummary(currentDir, files));
|
|
@@ -92418,21 +92778,21 @@ function sumBytes(files) {
|
|
|
92418
92778
|
init_esm();
|
|
92419
92779
|
import {
|
|
92420
92780
|
closeSync as closeSync17,
|
|
92421
|
-
existsSync as
|
|
92781
|
+
existsSync as existsSync90,
|
|
92422
92782
|
lstatSync as lstatSync10,
|
|
92423
|
-
mkdirSync as
|
|
92783
|
+
mkdirSync as mkdirSync51,
|
|
92424
92784
|
mkdtempSync as mkdtempSync6,
|
|
92425
92785
|
openSync as openSync17,
|
|
92426
|
-
readFileSync as
|
|
92786
|
+
readFileSync as readFileSync76,
|
|
92427
92787
|
readdirSync as readdirSync33,
|
|
92428
92788
|
renameSync as renameSync22,
|
|
92429
92789
|
rmSync as rmSync19,
|
|
92430
|
-
statSync as
|
|
92790
|
+
statSync as statSync38,
|
|
92431
92791
|
utimesSync,
|
|
92432
|
-
writeFileSync as
|
|
92792
|
+
writeFileSync as writeFileSync42
|
|
92433
92793
|
} from "node:fs";
|
|
92434
|
-
import { dirname as
|
|
92435
|
-
import { homedir as
|
|
92794
|
+
import { dirname as dirname31, join as join87, relative as relative3, resolve as resolve53 } from "node:path";
|
|
92795
|
+
import { homedir as homedir51, tmpdir as tmpdir6 } from "node:os";
|
|
92436
92796
|
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
92437
92797
|
init_helpers();
|
|
92438
92798
|
init_agent_config();
|
|
@@ -92443,15 +92803,15 @@ var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
|
|
|
92443
92803
|
var PERSONAL_SKILLS_SUBPATH = "personal-skills";
|
|
92444
92804
|
function resolveConfigSkillsDir(agent) {
|
|
92445
92805
|
const override = process.env.SWITCHROOM_CONFIG_DIR;
|
|
92446
|
-
const candidate = override ? resolve53(override) :
|
|
92447
|
-
if (!
|
|
92806
|
+
const candidate = override ? resolve53(override) : join87(homedir51(), ".switchroom-config");
|
|
92807
|
+
if (!existsSync90(candidate))
|
|
92448
92808
|
return null;
|
|
92449
|
-
return
|
|
92809
|
+
return join87(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
|
|
92450
92810
|
}
|
|
92451
92811
|
var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
|
|
92452
92812
|
function sweepMirrorPriors(configSkillsRoot) {
|
|
92453
92813
|
try {
|
|
92454
|
-
if (!
|
|
92814
|
+
if (!existsSync90(configSkillsRoot))
|
|
92455
92815
|
return;
|
|
92456
92816
|
const now = Date.now();
|
|
92457
92817
|
for (const ent of readdirSync33(configSkillsRoot)) {
|
|
@@ -92464,7 +92824,7 @@ function sweepMirrorPriors(configSkillsRoot) {
|
|
|
92464
92824
|
if (now - ts < MIRROR_PRIOR_TTL_MS)
|
|
92465
92825
|
continue;
|
|
92466
92826
|
try {
|
|
92467
|
-
rmSync19(
|
|
92827
|
+
rmSync19(join87(configSkillsRoot, ent), { recursive: true, force: true });
|
|
92468
92828
|
} catch {}
|
|
92469
92829
|
}
|
|
92470
92830
|
} catch {}
|
|
@@ -92473,7 +92833,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
92473
92833
|
const configSkillsRoot = resolveConfigSkillsDir(agent);
|
|
92474
92834
|
if (!configSkillsRoot)
|
|
92475
92835
|
return;
|
|
92476
|
-
const dest =
|
|
92836
|
+
const dest = join87(configSkillsRoot, name);
|
|
92477
92837
|
try {
|
|
92478
92838
|
if (liveSkillDir !== null) {
|
|
92479
92839
|
try {
|
|
@@ -92487,32 +92847,32 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
92487
92847
|
}
|
|
92488
92848
|
if (liveSkillDir === null) {
|
|
92489
92849
|
sweepMirrorPriors(configSkillsRoot);
|
|
92490
|
-
if (
|
|
92491
|
-
const trash =
|
|
92850
|
+
if (existsSync90(dest)) {
|
|
92851
|
+
const trash = join87(configSkillsRoot, `.${name}-trash-${Date.now()}`);
|
|
92492
92852
|
renameSync22(dest, trash);
|
|
92493
92853
|
}
|
|
92494
92854
|
return;
|
|
92495
92855
|
}
|
|
92496
|
-
|
|
92856
|
+
mkdirSync51(configSkillsRoot, { recursive: true, mode: 493 });
|
|
92497
92857
|
sweepMirrorPriors(configSkillsRoot);
|
|
92498
|
-
const staging = mkdtempSync6(
|
|
92858
|
+
const staging = mkdtempSync6(join87(configSkillsRoot, `.${name}-staging-`));
|
|
92499
92859
|
const walk2 = (src, dst) => {
|
|
92500
|
-
|
|
92860
|
+
mkdirSync51(dst, { recursive: true, mode: 493 });
|
|
92501
92861
|
for (const ent of readdirSync33(src, { withFileTypes: true })) {
|
|
92502
|
-
const s =
|
|
92503
|
-
const d =
|
|
92862
|
+
const s = join87(src, ent.name);
|
|
92863
|
+
const d = join87(dst, ent.name);
|
|
92504
92864
|
if (ent.isSymbolicLink())
|
|
92505
92865
|
continue;
|
|
92506
92866
|
if (ent.isDirectory())
|
|
92507
92867
|
walk2(s, d);
|
|
92508
92868
|
else if (ent.isFile()) {
|
|
92509
|
-
|
|
92869
|
+
writeFileSync42(d, readFileSync76(s));
|
|
92510
92870
|
}
|
|
92511
92871
|
}
|
|
92512
92872
|
};
|
|
92513
92873
|
walk2(liveSkillDir, staging);
|
|
92514
|
-
if (
|
|
92515
|
-
const prior =
|
|
92874
|
+
if (existsSync90(dest)) {
|
|
92875
|
+
const prior = join87(configSkillsRoot, `.${name}-prior-${Date.now()}`);
|
|
92516
92876
|
renameSync22(dest, prior);
|
|
92517
92877
|
}
|
|
92518
92878
|
renameSync22(staging, dest);
|
|
@@ -92539,17 +92899,17 @@ function resolveAgent(opts) {
|
|
|
92539
92899
|
function resolveAgentsRoot(opts) {
|
|
92540
92900
|
if (opts.root)
|
|
92541
92901
|
return resolve53(opts.root);
|
|
92542
|
-
return
|
|
92902
|
+
return join87(homedir51(), ".switchroom", "agents");
|
|
92543
92903
|
}
|
|
92544
92904
|
function personalSkillDir(agentsRoot, agent, name) {
|
|
92545
|
-
return
|
|
92905
|
+
return join87(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
|
|
92546
92906
|
}
|
|
92547
92907
|
function trashDir(agentsRoot, agent) {
|
|
92548
|
-
return
|
|
92908
|
+
return join87(agentsRoot, agent, ".claude", TRASH_DIRNAME);
|
|
92549
92909
|
}
|
|
92550
92910
|
function countPersonalSkills(agentsRoot, agent) {
|
|
92551
|
-
const skillsDir =
|
|
92552
|
-
if (!
|
|
92911
|
+
const skillsDir = join87(agentsRoot, agent, ".claude", "skills");
|
|
92912
|
+
if (!existsSync90(skillsDir))
|
|
92553
92913
|
return 0;
|
|
92554
92914
|
let n = 0;
|
|
92555
92915
|
for (const ent of readdirSync33(skillsDir, { withFileTypes: true })) {
|
|
@@ -92580,13 +92940,13 @@ function readStdinSync2() {
|
|
|
92580
92940
|
}
|
|
92581
92941
|
function loadFromDir2(dir) {
|
|
92582
92942
|
const abs = resolve53(dir);
|
|
92583
|
-
if (!
|
|
92943
|
+
if (!statSync38(abs).isDirectory()) {
|
|
92584
92944
|
fail4(`--from path is not a directory: ${dir}`);
|
|
92585
92945
|
}
|
|
92586
92946
|
const files = {};
|
|
92587
92947
|
const walk2 = (sub) => {
|
|
92588
92948
|
for (const ent of readdirSync33(sub, { withFileTypes: true })) {
|
|
92589
|
-
const full =
|
|
92949
|
+
const full = join87(sub, ent.name);
|
|
92590
92950
|
if (ent.isSymbolicLink()) {
|
|
92591
92951
|
fail4(`refusing to read symlink in --from dir: ${relative3(abs, full)}`);
|
|
92592
92952
|
}
|
|
@@ -92596,7 +92956,7 @@ function loadFromDir2(dir) {
|
|
|
92596
92956
|
}
|
|
92597
92957
|
if (ent.isFile()) {
|
|
92598
92958
|
const rel = relative3(abs, full).replace(/\\/g, "/");
|
|
92599
|
-
files[rel] =
|
|
92959
|
+
files[rel] = readFileSync76(full, "utf-8");
|
|
92600
92960
|
}
|
|
92601
92961
|
}
|
|
92602
92962
|
};
|
|
@@ -92639,10 +92999,10 @@ function behavioralValidate(files) {
|
|
|
92639
92999
|
errors2.push(`${path8} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
|
|
92640
93000
|
}
|
|
92641
93001
|
} else if (PY_SCRIPT_RE.test(path8)) {
|
|
92642
|
-
const tmp = mkdtempSync6(
|
|
92643
|
-
const tmpPy =
|
|
93002
|
+
const tmp = mkdtempSync6(join87(tmpdir6(), "skill-personal-py-"));
|
|
93003
|
+
const tmpPy = join87(tmp, "check.py");
|
|
92644
93004
|
try {
|
|
92645
|
-
|
|
93005
|
+
writeFileSync42(tmpPy, content);
|
|
92646
93006
|
const r = spawnSync16("python3", ["-m", "py_compile", tmpPy], {
|
|
92647
93007
|
encoding: "utf-8"
|
|
92648
93008
|
});
|
|
@@ -92658,15 +93018,15 @@ function behavioralValidate(files) {
|
|
|
92658
93018
|
}
|
|
92659
93019
|
function sweepTrash(agentsRoot, agent) {
|
|
92660
93020
|
const trash = trashDir(agentsRoot, agent);
|
|
92661
|
-
if (!
|
|
93021
|
+
if (!existsSync90(trash))
|
|
92662
93022
|
return;
|
|
92663
93023
|
const now = Date.now();
|
|
92664
93024
|
for (const ent of readdirSync33(trash, { withFileTypes: true })) {
|
|
92665
93025
|
if (!ent.isDirectory())
|
|
92666
93026
|
continue;
|
|
92667
|
-
const entPath =
|
|
93027
|
+
const entPath = join87(trash, ent.name);
|
|
92668
93028
|
try {
|
|
92669
|
-
const st =
|
|
93029
|
+
const st = statSync38(entPath);
|
|
92670
93030
|
if (now - st.mtimeMs > TRASH_TTL_MS) {
|
|
92671
93031
|
rmSync19(entPath, { recursive: true, force: true });
|
|
92672
93032
|
}
|
|
@@ -92684,16 +93044,16 @@ function writePersonalSkill(targetDir, files) {
|
|
|
92684
93044
|
if (targetIsSymlink) {
|
|
92685
93045
|
fail4(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
|
|
92686
93046
|
}
|
|
92687
|
-
|
|
92688
|
-
const staging = mkdtempSync6(
|
|
93047
|
+
mkdirSync51(dirname31(targetDir), { recursive: true, mode: 493 });
|
|
93048
|
+
const staging = mkdtempSync6(join87(dirname31(targetDir), `.skill-personal-stage-`));
|
|
92689
93049
|
let oldRename = null;
|
|
92690
93050
|
try {
|
|
92691
93051
|
for (const [path8, content] of Object.entries(files)) {
|
|
92692
|
-
const full =
|
|
92693
|
-
|
|
93052
|
+
const full = join87(staging, path8);
|
|
93053
|
+
mkdirSync51(dirname31(full), { recursive: true, mode: 493 });
|
|
92694
93054
|
const fd = openSync17(full, "wx");
|
|
92695
93055
|
try {
|
|
92696
|
-
|
|
93056
|
+
writeFileSync42(fd, content);
|
|
92697
93057
|
} finally {
|
|
92698
93058
|
closeSync17(fd);
|
|
92699
93059
|
}
|
|
@@ -92720,9 +93080,9 @@ function writePersonalSkill(targetDir, files) {
|
|
|
92720
93080
|
try {
|
|
92721
93081
|
rmSync19(staging, { recursive: true, force: true });
|
|
92722
93082
|
} catch {}
|
|
92723
|
-
if (oldRename &&
|
|
93083
|
+
if (oldRename && existsSync90(oldRename)) {
|
|
92724
93084
|
try {
|
|
92725
|
-
if (
|
|
93085
|
+
if (existsSync90(targetDir)) {
|
|
92726
93086
|
rmSync19(targetDir, { recursive: true, force: true });
|
|
92727
93087
|
}
|
|
92728
93088
|
renameSync22(oldRename, targetDir);
|
|
@@ -92788,15 +93148,15 @@ function loadFiles(opts) {
|
|
|
92788
93148
|
return loadFromStdin2();
|
|
92789
93149
|
}
|
|
92790
93150
|
const p = resolve53(opts.from);
|
|
92791
|
-
if (!
|
|
93151
|
+
if (!existsSync90(p)) {
|
|
92792
93152
|
fail4(`--from path does not exist: ${opts.from}`);
|
|
92793
93153
|
}
|
|
92794
|
-
const st =
|
|
93154
|
+
const st = statSync38(p);
|
|
92795
93155
|
if (st.isDirectory()) {
|
|
92796
93156
|
return loadFromDir2(p);
|
|
92797
93157
|
}
|
|
92798
93158
|
if (p.endsWith(".md")) {
|
|
92799
|
-
return { "SKILL.md":
|
|
93159
|
+
return { "SKILL.md": readFileSync76(p, "utf-8") };
|
|
92800
93160
|
}
|
|
92801
93161
|
fail4(`--from must be a directory or a .md file. Got: ${opts.from}`);
|
|
92802
93162
|
}
|
|
@@ -92836,10 +93196,10 @@ function editPersonalAction(name, opts) {
|
|
|
92836
93196
|
}
|
|
92837
93197
|
var CLONE_SOURCE_RE = /^(shared|bundled):([a-z0-9][a-z0-9_-]{0,62})$/;
|
|
92838
93198
|
function defaultSharedRoot() {
|
|
92839
|
-
return
|
|
93199
|
+
return join87(homedir51(), ".switchroom", "skills");
|
|
92840
93200
|
}
|
|
92841
93201
|
function defaultBundledRoot() {
|
|
92842
|
-
return
|
|
93202
|
+
return join87(homedir51(), ".switchroom", "skills", "_bundled");
|
|
92843
93203
|
}
|
|
92844
93204
|
function resolveCloneSource(source, opts) {
|
|
92845
93205
|
const m = CLONE_SOURCE_RE.exec(source);
|
|
@@ -92849,8 +93209,8 @@ function resolveCloneSource(source, opts) {
|
|
|
92849
93209
|
const tier = m[1];
|
|
92850
93210
|
const slug = m[2];
|
|
92851
93211
|
const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
|
|
92852
|
-
const dir =
|
|
92853
|
-
if (!
|
|
93212
|
+
const dir = join87(root, slug);
|
|
93213
|
+
if (!existsSync90(dir)) {
|
|
92854
93214
|
fail4(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
|
|
92855
93215
|
}
|
|
92856
93216
|
const st = lstatSync10(dir);
|
|
@@ -92865,7 +93225,7 @@ function readSourceFiles(dir) {
|
|
|
92865
93225
|
const skipped = [];
|
|
92866
93226
|
const walk2 = (sub) => {
|
|
92867
93227
|
for (const ent of readdirSync33(sub, { withFileTypes: true })) {
|
|
92868
|
-
const full =
|
|
93228
|
+
const full = join87(sub, ent.name);
|
|
92869
93229
|
if (ent.isSymbolicLink()) {
|
|
92870
93230
|
continue;
|
|
92871
93231
|
}
|
|
@@ -92885,7 +93245,7 @@ function readSourceFiles(dir) {
|
|
|
92885
93245
|
fail4(`clone source has oversized file ${rel} (${st.size} bytes > ${CLONE_MAX_FILE_BYTES}); ` + `refuse to read`, 3);
|
|
92886
93246
|
}
|
|
92887
93247
|
} catch {}
|
|
92888
|
-
files[rel] =
|
|
93248
|
+
files[rel] = readFileSync76(full, "utf-8");
|
|
92889
93249
|
}
|
|
92890
93250
|
}
|
|
92891
93251
|
};
|
|
@@ -92974,9 +93334,9 @@ function removePersonalAction(name, opts) {
|
|
|
92974
93334
|
throw err2;
|
|
92975
93335
|
}
|
|
92976
93336
|
const trashRoot2 = trashDir(agentsRoot, agent);
|
|
92977
|
-
|
|
93337
|
+
mkdirSync51(trashRoot2, { recursive: true, mode: 493 });
|
|
92978
93338
|
const ts = Date.now();
|
|
92979
|
-
const trashTarget =
|
|
93339
|
+
const trashTarget = join87(trashRoot2, `${name}-${ts}`);
|
|
92980
93340
|
renameSync22(target, trashTarget);
|
|
92981
93341
|
const now = new Date(ts);
|
|
92982
93342
|
utimesSync(trashTarget, now, now);
|
|
@@ -92995,16 +93355,16 @@ function listPersonalAction(opts) {
|
|
|
92995
93355
|
const agent = resolveAgent(opts);
|
|
92996
93356
|
const agentsRoot = resolveAgentsRoot(opts);
|
|
92997
93357
|
sweepTrash(agentsRoot, agent);
|
|
92998
|
-
const skillsDir =
|
|
93358
|
+
const skillsDir = join87(agentsRoot, agent, ".claude", "skills");
|
|
92999
93359
|
const personal = [];
|
|
93000
|
-
if (
|
|
93360
|
+
if (existsSync90(skillsDir)) {
|
|
93001
93361
|
for (const ent of readdirSync33(skillsDir, { withFileTypes: true })) {
|
|
93002
93362
|
if (!ent.isDirectory())
|
|
93003
93363
|
continue;
|
|
93004
93364
|
if (!ent.name.startsWith(PERSONAL_PREFIX))
|
|
93005
93365
|
continue;
|
|
93006
93366
|
const skillName = ent.name.slice(PERSONAL_PREFIX.length);
|
|
93007
|
-
const skillPath =
|
|
93367
|
+
const skillPath = join87(skillsDir, ent.name);
|
|
93008
93368
|
let fileCount = 0;
|
|
93009
93369
|
let totalBytes = 0;
|
|
93010
93370
|
const walk2 = (sub) => {
|
|
@@ -93012,10 +93372,10 @@ function listPersonalAction(opts) {
|
|
|
93012
93372
|
if (e.isFile()) {
|
|
93013
93373
|
fileCount += 1;
|
|
93014
93374
|
try {
|
|
93015
|
-
totalBytes +=
|
|
93375
|
+
totalBytes += statSync38(join87(sub, e.name)).size;
|
|
93016
93376
|
} catch {}
|
|
93017
93377
|
} else if (e.isDirectory()) {
|
|
93018
|
-
walk2(
|
|
93378
|
+
walk2(join87(sub, e.name));
|
|
93019
93379
|
}
|
|
93020
93380
|
}
|
|
93021
93381
|
};
|
|
@@ -93053,12 +93413,12 @@ function registerSkillPersonalCommands(program3) {
|
|
|
93053
93413
|
|
|
93054
93414
|
// src/cli/self-improve-propose-skill.ts
|
|
93055
93415
|
import { createConnection as createConnection4 } from "node:net";
|
|
93056
|
-
import { homedir as
|
|
93057
|
-
import { join as
|
|
93058
|
-
import { readFileSync as
|
|
93416
|
+
import { homedir as homedir52 } from "node:os";
|
|
93417
|
+
import { join as join88 } from "node:path";
|
|
93418
|
+
import { readFileSync as readFileSync77 } from "node:fs";
|
|
93059
93419
|
var IPC_CONNECT_TIMEOUT_MS = 5000;
|
|
93060
93420
|
function gatewaySocketPath() {
|
|
93061
|
-
return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ?
|
|
93421
|
+
return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ? join88(process.env.TELEGRAM_STATE_DIR, "gateway.sock") : join88(homedir52(), ".claude", "channels", "telegram", "gateway.sock"));
|
|
93062
93422
|
}
|
|
93063
93423
|
function fail5(msg, code = 1) {
|
|
93064
93424
|
console.error(msg);
|
|
@@ -93093,7 +93453,7 @@ function registerSelfImproveProposeSkillCommand(program3) {
|
|
|
93093
93453
|
fail5("agent name required (--agent or $SWITCHROOM_AGENT_NAME)");
|
|
93094
93454
|
let draft;
|
|
93095
93455
|
try {
|
|
93096
|
-
draft = JSON.parse(
|
|
93456
|
+
draft = JSON.parse(readFileSync77(opts.draft, "utf-8"));
|
|
93097
93457
|
} catch (e) {
|
|
93098
93458
|
fail5(`failed to read/parse --draft: ${e.message}`);
|
|
93099
93459
|
}
|
|
@@ -93130,28 +93490,28 @@ function registerSelfImproveProposeSkillCommand(program3) {
|
|
|
93130
93490
|
init_esm();
|
|
93131
93491
|
init_helpers();
|
|
93132
93492
|
var import_yaml25 = __toESM(require_dist(), 1);
|
|
93133
|
-
import { existsSync as
|
|
93134
|
-
import { homedir as
|
|
93135
|
-
import { join as
|
|
93493
|
+
import { existsSync as existsSync91, readdirSync as readdirSync34, readFileSync as readFileSync78, statSync as statSync39 } from "node:fs";
|
|
93494
|
+
import { homedir as homedir53 } from "node:os";
|
|
93495
|
+
import { join as join89, resolve as resolve54 } from "node:path";
|
|
93136
93496
|
var PERSONAL_PREFIX2 = "personal-";
|
|
93137
93497
|
var BUNDLED_SUBDIR = "_bundled";
|
|
93138
93498
|
var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
|
|
93139
93499
|
function defaultAgentsRoot() {
|
|
93140
|
-
return resolve54(
|
|
93500
|
+
return resolve54(homedir53(), ".switchroom/agents");
|
|
93141
93501
|
}
|
|
93142
93502
|
function defaultSharedRoot2() {
|
|
93143
|
-
return resolve54(
|
|
93503
|
+
return resolve54(homedir53(), ".switchroom/skills");
|
|
93144
93504
|
}
|
|
93145
93505
|
function defaultBundledRoot2() {
|
|
93146
|
-
return resolve54(
|
|
93506
|
+
return resolve54(homedir53(), ".switchroom/skills/_bundled");
|
|
93147
93507
|
}
|
|
93148
93508
|
function readSkillFrontmatter(skillDir) {
|
|
93149
|
-
const mdPath =
|
|
93150
|
-
if (!
|
|
93509
|
+
const mdPath = join89(skillDir, "SKILL.md");
|
|
93510
|
+
if (!existsSync91(mdPath))
|
|
93151
93511
|
return null;
|
|
93152
93512
|
let content;
|
|
93153
93513
|
try {
|
|
93154
|
-
content =
|
|
93514
|
+
content = readFileSync78(mdPath, "utf-8");
|
|
93155
93515
|
} catch {
|
|
93156
93516
|
return null;
|
|
93157
93517
|
}
|
|
@@ -93179,9 +93539,9 @@ function readSkillFrontmatter(skillDir) {
|
|
|
93179
93539
|
return { fm: parsed };
|
|
93180
93540
|
}
|
|
93181
93541
|
function statSkillMd(skillDir) {
|
|
93182
|
-
const mdPath =
|
|
93542
|
+
const mdPath = join89(skillDir, "SKILL.md");
|
|
93183
93543
|
try {
|
|
93184
|
-
const st =
|
|
93544
|
+
const st = statSync39(mdPath);
|
|
93185
93545
|
return { size: st.size, mtime: st.mtime.toISOString() };
|
|
93186
93546
|
} catch {
|
|
93187
93547
|
return null;
|
|
@@ -93190,8 +93550,8 @@ function statSkillMd(skillDir) {
|
|
|
93190
93550
|
function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
93191
93551
|
if (!AGENT_NAME_RE3.test(agent))
|
|
93192
93552
|
return [];
|
|
93193
|
-
const skillsDir =
|
|
93194
|
-
if (!
|
|
93553
|
+
const skillsDir = join89(agentsRoot, agent, ".claude/skills");
|
|
93554
|
+
if (!existsSync91(skillsDir))
|
|
93195
93555
|
return [];
|
|
93196
93556
|
const out = [];
|
|
93197
93557
|
let entries;
|
|
@@ -93203,9 +93563,9 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
93203
93563
|
for (const ent of entries) {
|
|
93204
93564
|
if (!ent.startsWith(PERSONAL_PREFIX2))
|
|
93205
93565
|
continue;
|
|
93206
|
-
const dirPath =
|
|
93566
|
+
const dirPath = join89(skillsDir, ent);
|
|
93207
93567
|
try {
|
|
93208
|
-
if (!
|
|
93568
|
+
if (!statSync39(dirPath).isDirectory())
|
|
93209
93569
|
continue;
|
|
93210
93570
|
} catch {
|
|
93211
93571
|
continue;
|
|
@@ -93229,7 +93589,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
93229
93589
|
return out;
|
|
93230
93590
|
}
|
|
93231
93591
|
function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
93232
|
-
if (!
|
|
93592
|
+
if (!existsSync91(sharedRoot))
|
|
93233
93593
|
return [];
|
|
93234
93594
|
const out = [];
|
|
93235
93595
|
let entries;
|
|
@@ -93243,9 +93603,9 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
|
93243
93603
|
continue;
|
|
93244
93604
|
if (ent.startsWith("."))
|
|
93245
93605
|
continue;
|
|
93246
|
-
const dirPath =
|
|
93606
|
+
const dirPath = join89(sharedRoot, ent);
|
|
93247
93607
|
try {
|
|
93248
|
-
if (!
|
|
93608
|
+
if (!statSync39(dirPath).isDirectory())
|
|
93249
93609
|
continue;
|
|
93250
93610
|
} catch {
|
|
93251
93611
|
continue;
|
|
@@ -93267,7 +93627,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
|
93267
93627
|
return out;
|
|
93268
93628
|
}
|
|
93269
93629
|
function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
93270
|
-
if (!
|
|
93630
|
+
if (!existsSync91(bundledRoot))
|
|
93271
93631
|
return [];
|
|
93272
93632
|
const out = [];
|
|
93273
93633
|
let entries;
|
|
@@ -93279,9 +93639,9 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
|
93279
93639
|
for (const ent of entries) {
|
|
93280
93640
|
if (ent.startsWith("."))
|
|
93281
93641
|
continue;
|
|
93282
|
-
const dirPath =
|
|
93642
|
+
const dirPath = join89(bundledRoot, ent);
|
|
93283
93643
|
try {
|
|
93284
|
-
if (!
|
|
93644
|
+
if (!statSync39(dirPath).isDirectory())
|
|
93285
93645
|
continue;
|
|
93286
93646
|
} catch {
|
|
93287
93647
|
continue;
|
|
@@ -93425,18 +93785,18 @@ init_source();
|
|
|
93425
93785
|
init_helpers();
|
|
93426
93786
|
init_operator_uid();
|
|
93427
93787
|
import {
|
|
93428
|
-
existsSync as
|
|
93429
|
-
mkdirSync as
|
|
93788
|
+
existsSync as existsSync93,
|
|
93789
|
+
mkdirSync as mkdirSync52,
|
|
93430
93790
|
readdirSync as readdirSync35,
|
|
93431
|
-
readFileSync as
|
|
93432
|
-
writeFileSync as
|
|
93433
|
-
statSync as
|
|
93791
|
+
readFileSync as readFileSync80,
|
|
93792
|
+
writeFileSync as writeFileSync43,
|
|
93793
|
+
statSync as statSync40,
|
|
93434
93794
|
lstatSync as lstatSync11,
|
|
93435
93795
|
realpathSync as realpathSync8,
|
|
93436
93796
|
copyFileSync as copyFileSync13
|
|
93437
93797
|
} from "node:fs";
|
|
93438
|
-
import { homedir as
|
|
93439
|
-
import { join as
|
|
93798
|
+
import { homedir as homedir54 } from "node:os";
|
|
93799
|
+
import { join as join90 } from "node:path";
|
|
93440
93800
|
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
93441
93801
|
|
|
93442
93802
|
// src/cli/deploy-version-guard.ts
|
|
@@ -93658,7 +94018,7 @@ networks:
|
|
|
93658
94018
|
# operator surface; the daemon's stderr lands in \`docker logs switchroom-hostd\`.
|
|
93659
94019
|
`;
|
|
93660
94020
|
}
|
|
93661
|
-
function resolveHostdHostHome(env2 = process.env, home2 =
|
|
94021
|
+
function resolveHostdHostHome(env2 = process.env, home2 = homedir54()) {
|
|
93662
94022
|
const fromEnv = env2.SWITCHROOM_HOST_HOME?.trim();
|
|
93663
94023
|
const resolved = fromEnv && fromEnv.length > 0 ? fromEnv : home2;
|
|
93664
94024
|
if (resolved === "/host-home" || resolved.startsWith("/host-home/")) {
|
|
@@ -93669,7 +94029,7 @@ function resolveHostdHostHome(env2 = process.env, home2 = homedir53()) {
|
|
|
93669
94029
|
return resolved;
|
|
93670
94030
|
}
|
|
93671
94031
|
function resolveHostdSkillsTarget(hostHome) {
|
|
93672
|
-
const skillsPath =
|
|
94032
|
+
const skillsPath = join90(hostHome, ".switchroom", "skills");
|
|
93673
94033
|
let st;
|
|
93674
94034
|
try {
|
|
93675
94035
|
st = lstatSync11(skillsPath);
|
|
@@ -93686,21 +94046,21 @@ function resolveHostdSkillsTarget(hostHome) {
|
|
|
93686
94046
|
console.warn(`switchroom hostd install: ~/.switchroom/skills is a symlink whose target ` + `does not resolve (dangling) \u2014 skipping the skills bind mount. Bundled ` + `skills will be unavailable to rollout/update until the symlink is fixed.`);
|
|
93687
94047
|
return;
|
|
93688
94048
|
}
|
|
93689
|
-
if (!
|
|
94049
|
+
if (!existsSync93(target)) {
|
|
93690
94050
|
console.warn(`switchroom hostd install: ~/.switchroom/skills resolves to "${target}", ` + `which does not exist \u2014 skipping the skills bind mount. Bundled skills ` + `will be unavailable to rollout/update until the symlink target exists.`);
|
|
93691
94051
|
return;
|
|
93692
94052
|
}
|
|
93693
94053
|
return target;
|
|
93694
94054
|
}
|
|
93695
94055
|
function hostdDir() {
|
|
93696
|
-
return
|
|
94056
|
+
return join90(homedir54(), ".switchroom", "hostd");
|
|
93697
94057
|
}
|
|
93698
94058
|
function hostdComposePath() {
|
|
93699
|
-
return
|
|
94059
|
+
return join90(hostdDir(), "docker-compose.yml");
|
|
93700
94060
|
}
|
|
93701
94061
|
function backupExistingCompose() {
|
|
93702
94062
|
const p = hostdComposePath();
|
|
93703
|
-
if (!
|
|
94063
|
+
if (!existsSync93(p))
|
|
93704
94064
|
return null;
|
|
93705
94065
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
93706
94066
|
const bak = `${p}.bak-${ts}`;
|
|
@@ -93733,7 +94093,7 @@ async function doInstall(opts, program3) {
|
|
|
93733
94093
|
}
|
|
93734
94094
|
const dir = hostdDir();
|
|
93735
94095
|
const composePath = hostdComposePath();
|
|
93736
|
-
|
|
94096
|
+
mkdirSync52(dir, { recursive: true });
|
|
93737
94097
|
const imageTag = resolveHostdImageTag(opts.tag, cfg.release);
|
|
93738
94098
|
const guard = checkDowngrade({
|
|
93739
94099
|
container: "switchroom-hostd",
|
|
@@ -93766,7 +94126,7 @@ async function doInstall(opts, program3) {
|
|
|
93766
94126
|
const bak = backupExistingCompose();
|
|
93767
94127
|
if (bak)
|
|
93768
94128
|
console.log(source_default.dim(` Backed up existing compose to ${bak}`));
|
|
93769
|
-
|
|
94129
|
+
writeFileSync43(composePath, yaml, "utf8");
|
|
93770
94130
|
console.log(source_default.green(` \u2713 Wrote ${composePath}`));
|
|
93771
94131
|
const adminAgents = Object.entries(cfg.agents ?? {}).filter(([, a]) => a?.admin === true).map(([name]) => name);
|
|
93772
94132
|
console.log(source_default.dim(` agents served (one socket each): ${allAgents.length === 0 ? "(none)" : allAgents.join(", ")}`));
|
|
@@ -93798,7 +94158,7 @@ function doStatus() {
|
|
|
93798
94158
|
const composeYml = hostdComposePath();
|
|
93799
94159
|
console.log(source_default.bold("switchroom-hostd"));
|
|
93800
94160
|
console.log("");
|
|
93801
|
-
if (!
|
|
94161
|
+
if (!existsSync93(composeYml)) {
|
|
93802
94162
|
console.log(source_default.yellow(" compose: not installed"));
|
|
93803
94163
|
console.log(source_default.dim(" run `switchroom hostd install` to set up."));
|
|
93804
94164
|
return;
|
|
@@ -93819,15 +94179,15 @@ function doStatus() {
|
|
|
93819
94179
|
} else {
|
|
93820
94180
|
console.log(source_default.green(` container: ${ps.stdout.trim()}`));
|
|
93821
94181
|
}
|
|
93822
|
-
if (
|
|
94182
|
+
if (existsSync93(dir)) {
|
|
93823
94183
|
const entries = [];
|
|
93824
94184
|
try {
|
|
93825
94185
|
for (const name of readdirSync35(dir)) {
|
|
93826
94186
|
if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
|
|
93827
94187
|
continue;
|
|
93828
|
-
const sockPath =
|
|
93829
|
-
if (
|
|
93830
|
-
const st =
|
|
94188
|
+
const sockPath = join90(dir, name, "sock");
|
|
94189
|
+
if (existsSync93(sockPath)) {
|
|
94190
|
+
const st = statSync40(sockPath);
|
|
93831
94191
|
if ((st.mode & 61440) === 49152) {
|
|
93832
94192
|
entries.push(`${name} \u2192 ${sockPath}`);
|
|
93833
94193
|
}
|
|
@@ -93845,7 +94205,7 @@ function doStatus() {
|
|
|
93845
94205
|
}
|
|
93846
94206
|
function doUninstall() {
|
|
93847
94207
|
const composeYml = hostdComposePath();
|
|
93848
|
-
if (!
|
|
94208
|
+
if (!existsSync93(composeYml)) {
|
|
93849
94209
|
console.log(source_default.yellow(" No hostd install detected (no compose file at this path)."));
|
|
93850
94210
|
return;
|
|
93851
94211
|
}
|
|
@@ -93869,12 +94229,12 @@ function registerHostdCommand(program3) {
|
|
|
93869
94229
|
hostd.command("uninstall").description("Stop the hostd container. Leaves the compose file in place for re-install.").action(() => doUninstall());
|
|
93870
94230
|
hostd.command("audit").description("Tail and filter the hostd audit log (privileged-verb call history)").option("--tail <n>", "Number of matching entries to show (default: 50)", "50").option("--agent <name>", "Filter to a specific caller agent").option("--op <verb>", "Filter to a specific hostd verb (e.g. update_apply, agent_restart)").option("--error", "Show only failed (error/denied) entries").option("--verbose", "Show the captured stderr / error tail under each failed row").option("--path <file>", "Override audit log path (for debugging)").action((opts) => {
|
|
93871
94231
|
const logPath = opts.path ?? defaultAuditLogPath2();
|
|
93872
|
-
if (!
|
|
94232
|
+
if (!existsSync93(logPath)) {
|
|
93873
94233
|
console.error(source_default.yellow(`Audit log not found at ${logPath}.`) + source_default.gray(`
|
|
93874
94234
|
The log is created when hostd handles its first privileged-verb request.`));
|
|
93875
94235
|
return;
|
|
93876
94236
|
}
|
|
93877
|
-
const raw =
|
|
94237
|
+
const raw = readFileSync80(logPath, "utf-8");
|
|
93878
94238
|
const limit = Math.max(1, parseInt(opts.tail ?? "50", 10) || 50);
|
|
93879
94239
|
const filters = {
|
|
93880
94240
|
agent: opts.agent,
|
|
@@ -93917,9 +94277,9 @@ The log is created when hostd handles its first privileged-verb request.`));
|
|
|
93917
94277
|
init_source();
|
|
93918
94278
|
init_helpers();
|
|
93919
94279
|
init_operator_uid();
|
|
93920
|
-
import { existsSync as
|
|
93921
|
-
import { homedir as
|
|
93922
|
-
import { join as
|
|
94280
|
+
import { existsSync as existsSync94, mkdirSync as mkdirSync53, writeFileSync as writeFileSync44, copyFileSync as copyFileSync14 } from "node:fs";
|
|
94281
|
+
import { homedir as homedir55 } from "node:os";
|
|
94282
|
+
import { join as join91 } from "node:path";
|
|
93923
94283
|
import { spawnSync as spawnSync21 } from "node:child_process";
|
|
93924
94284
|
function resolveWebImageTag(explicitTag, release) {
|
|
93925
94285
|
if (explicitTag)
|
|
@@ -94004,14 +94364,14 @@ services:
|
|
|
94004
94364
|
`;
|
|
94005
94365
|
}
|
|
94006
94366
|
function webdDir() {
|
|
94007
|
-
return
|
|
94367
|
+
return join91(homedir55(), ".switchroom", "web");
|
|
94008
94368
|
}
|
|
94009
94369
|
function webdComposePath() {
|
|
94010
|
-
return
|
|
94370
|
+
return join91(webdDir(), "docker-compose.yml");
|
|
94011
94371
|
}
|
|
94012
94372
|
function backupExistingCompose2() {
|
|
94013
94373
|
const p = webdComposePath();
|
|
94014
|
-
if (!
|
|
94374
|
+
if (!existsSync94(p))
|
|
94015
94375
|
return null;
|
|
94016
94376
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
94017
94377
|
const bak = `${p}.bak-${ts}`;
|
|
@@ -94036,7 +94396,7 @@ async function doInstall2(opts, program3) {
|
|
|
94036
94396
|
}
|
|
94037
94397
|
const dir = webdDir();
|
|
94038
94398
|
const composePath = webdComposePath();
|
|
94039
|
-
|
|
94399
|
+
mkdirSync53(dir, { recursive: true });
|
|
94040
94400
|
const cfg = getConfig(program3);
|
|
94041
94401
|
const imageTag = resolveWebImageTag(opts.tag, cfg.release);
|
|
94042
94402
|
const guard = checkDowngrade({
|
|
@@ -94049,7 +94409,7 @@ async function doInstall2(opts, program3) {
|
|
|
94049
94409
|
return;
|
|
94050
94410
|
}
|
|
94051
94411
|
const yaml = renderWebComposeFile({
|
|
94052
|
-
hostHome:
|
|
94412
|
+
hostHome: homedir55(),
|
|
94053
94413
|
imageTag,
|
|
94054
94414
|
operatorUid
|
|
94055
94415
|
});
|
|
@@ -94062,7 +94422,7 @@ async function doInstall2(opts, program3) {
|
|
|
94062
94422
|
const bak = backupExistingCompose2();
|
|
94063
94423
|
if (bak)
|
|
94064
94424
|
console.log(source_default.dim(` Backed up existing compose to ${bak}`));
|
|
94065
|
-
|
|
94425
|
+
writeFileSync44(composePath, yaml, "utf8");
|
|
94066
94426
|
console.log(source_default.green(` \u2713 Wrote ${composePath}`));
|
|
94067
94427
|
console.log(source_default.dim(` running as uid ${operatorUid} (operator), network_mode: host`));
|
|
94068
94428
|
console.log(source_default.dim(` Pulling ghcr.io/switchroom/switchroom-web:${imageTag}\u2026`));
|
|
@@ -94095,7 +94455,7 @@ function doStatus2() {
|
|
|
94095
94455
|
const composeYml = webdComposePath();
|
|
94096
94456
|
console.log(source_default.bold("switchroom-web"));
|
|
94097
94457
|
console.log("");
|
|
94098
|
-
if (!
|
|
94458
|
+
if (!existsSync94(composeYml)) {
|
|
94099
94459
|
console.log(source_default.yellow(" compose: not installed"));
|
|
94100
94460
|
console.log(source_default.dim(" run `switchroom webd install` to set up."));
|
|
94101
94461
|
return;
|
|
@@ -94119,7 +94479,7 @@ function doStatus2() {
|
|
|
94119
94479
|
}
|
|
94120
94480
|
function doUninstall2() {
|
|
94121
94481
|
const composeYml = webdComposePath();
|
|
94122
|
-
if (!
|
|
94482
|
+
if (!existsSync94(composeYml)) {
|
|
94123
94483
|
console.log(source_default.yellow(" No web-service install detected (no compose file at this path)."));
|
|
94124
94484
|
return;
|
|
94125
94485
|
}
|
|
@@ -94148,10 +94508,10 @@ function registerWebdCommand(program3) {
|
|
|
94148
94508
|
|
|
94149
94509
|
// src/cli/host-repair.ts
|
|
94150
94510
|
init_source();
|
|
94151
|
-
import { homedir as
|
|
94152
|
-
import { join as
|
|
94511
|
+
import { homedir as homedir56 } from "node:os";
|
|
94512
|
+
import { join as join92 } from "node:path";
|
|
94153
94513
|
var ARTIFACT_ALLOWLIST = {
|
|
94154
|
-
dockerComposePluginDir: (home2) =>
|
|
94514
|
+
dockerComposePluginDir: (home2) => join92(home2, ".docker", "cli-plugins", "docker-compose"),
|
|
94155
94515
|
stateSentinel: "/state"
|
|
94156
94516
|
};
|
|
94157
94517
|
function isStateBogusAutoDir(probe2) {
|
|
@@ -94200,7 +94560,7 @@ function isStateBogusAutoDir(probe2) {
|
|
|
94200
94560
|
return true;
|
|
94201
94561
|
}
|
|
94202
94562
|
function planMountRepairs(probe2, opts = {}) {
|
|
94203
|
-
const home2 = opts.hostHome ??
|
|
94563
|
+
const home2 = opts.hostHome ?? homedir56();
|
|
94204
94564
|
const items = [];
|
|
94205
94565
|
const dockerComposePath = ARTIFACT_ALLOWLIST.dockerComposePluginDir(home2);
|
|
94206
94566
|
const dockerComposeSt = probe2.lstat(dockerComposePath);
|