switchroom 0.18.17 → 0.18.19
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 +13 -0
- package/dist/auth-broker/index.js +13 -0
- package/dist/cli/notion-write-pretool.mjs +13 -0
- package/dist/cli/switchroom.js +605 -479
- package/dist/host-control/main.js +17 -1
- package/dist/vault/approvals/kernel-server.js +13 -0
- package/dist/vault/broker/server.js +13 -0
- package/package.json +1 -1
- package/telegram-plugin/bridge/bridge.ts +7 -1
- package/telegram-plugin/dist/bridge/bridge.js +26 -1
- package/telegram-plugin/dist/gateway/gateway.js +1544 -619
- package/telegram-plugin/dist/server.js +32 -1
- package/telegram-plugin/fleet-fallback-resume.ts +26 -3
- package/telegram-plugin/format.ts +137 -213
- package/telegram-plugin/gateway/approval-hold.ts +49 -0
- package/telegram-plugin/gateway/bridge-dead-watchdog.ts +61 -18
- package/telegram-plugin/gateway/gateway.ts +399 -85
- package/telegram-plugin/gateway/linear-activity.ts +20 -4
- package/telegram-plugin/gateway/outbound-send-path.ts +9 -7
- package/telegram-plugin/gateway/premium-recovery-wiring.ts +122 -0
- package/telegram-plugin/gateway/session-model-file.ts +103 -0
- package/telegram-plugin/gateway/tier-downgrade-wiring.ts +121 -0
- package/telegram-plugin/gateway/unhandled-rejection-policy.ts +14 -1
- package/telegram-plugin/llm-error-present.ts +474 -0
- package/telegram-plugin/operator-events.ts +7 -1
- package/telegram-plugin/permission-title.ts +172 -10
- package/telegram-plugin/premium-recovery.ts +101 -0
- package/telegram-plugin/raw-error-scrub.ts +73 -0
- package/telegram-plugin/retry-api-call.ts +8 -2
- package/telegram-plugin/send-gate-degraded.test.ts +152 -1
- package/telegram-plugin/send-gate-observability.test.ts +140 -0
- package/telegram-plugin/send-gate-observability.ts +65 -20
- package/telegram-plugin/send-gate.test.ts +143 -1
- package/telegram-plugin/send-gate.ts +212 -19
- package/telegram-plugin/session-tail.ts +16 -0
- package/telegram-plugin/shared/local-time.ts +69 -0
- package/telegram-plugin/stream-reply-handler.ts +5 -14
- package/telegram-plugin/tests/approval-hold-harness.ts +6 -6
- package/telegram-plugin/tests/approval-hold-outcome.test.ts +10 -2
- package/telegram-plugin/tests/bridge-dead-watchdog.test.ts +61 -0
- package/telegram-plugin/tests/fleet-fallback-resume.test.ts +39 -0
- package/telegram-plugin/tests/flood-windows-persistence.test.ts +3 -2
- package/telegram-plugin/tests/format-consistency.test.ts +68 -53
- package/telegram-plugin/tests/formatting-parse-regression.test.ts +5 -6
- package/telegram-plugin/tests/formatting-torture-set.ts +1 -1
- package/telegram-plugin/tests/gateway-outbound-redact.test.ts +26 -0
- package/telegram-plugin/tests/linear-create-issue.test.ts +30 -2
- package/telegram-plugin/tests/llm-error-present.test.ts +481 -0
- package/telegram-plugin/tests/outbound-send-path.test.ts +4 -3
- package/telegram-plugin/tests/paragraph-normalizer.test.ts +42 -100
- package/telegram-plugin/tests/permission-title.test.ts +167 -4
- package/telegram-plugin/tests/premium-recovery-wiring.test.ts +150 -0
- package/telegram-plugin/tests/premium-recovery.test.ts +165 -0
- package/telegram-plugin/tests/reaction-gate-routing.test.ts +6 -1
- package/telegram-plugin/tests/retry-api-call.test.ts +21 -0
- package/telegram-plugin/tests/stream-reply-handler.test.ts +9 -12
- package/telegram-plugin/tests/telegram-format.test.ts +86 -31
- package/telegram-plugin/tests/tier-downgrade-wiring.test.ts +165 -0
- package/telegram-plugin/tests/tier-downgrade.test.ts +141 -0
- package/telegram-plugin/tests/turn-flush-safety.test.ts +17 -21
- package/telegram-plugin/tests/unhandled-rejection-policy.test.ts +27 -1
- package/telegram-plugin/tests/worker-activity-feed.test.ts +5 -2
- package/telegram-plugin/tests/worker-feed-coalesce.test.ts +492 -0
- package/telegram-plugin/tier-downgrade.ts +198 -0
- package/telegram-plugin/tool-activity-summary.ts +99 -0
- package/telegram-plugin/turn-flush-safety.ts +4 -3
- package/telegram-plugin/worker-activity-feed.ts +509 -409
package/dist/cli/switchroom.js
CHANGED
|
@@ -2120,7 +2120,7 @@ var init_esm = __esm(() => {
|
|
|
2120
2120
|
});
|
|
2121
2121
|
|
|
2122
2122
|
// src/build-info.ts
|
|
2123
|
-
var VERSION = "0.18.
|
|
2123
|
+
var VERSION = "0.18.19", COMMIT_SHA = "34c72776";
|
|
2124
2124
|
|
|
2125
2125
|
// src/cli/resolve-version.ts
|
|
2126
2126
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -13818,6 +13818,19 @@ var init_schema = __esm(() => {
|
|
|
13818
13818
|
approval_timeout_minutes: exports_external.number().int().nonnegative().optional().describe("Operator approval-card lifetime (minutes) for the tool-use 'Allow " + "once' card and the vault grant decision wait. After this long with " + "no operator tap, the card auto-denies (a TIMEOUT, not a denial \u2014 the " + "agent is told not to retry). Default 60. hostd-gated verbs " + "(mcp__hostd__*) keep their own longer window; the hostd " + "config-propose card is not governed by this key."),
|
|
13819
13819
|
sub_agent_tick_interval_ms: exports_external.number().int().nonnegative().optional().describe("Heartbeat tick interval (ms) for sub-agent rendering. Forces a " + "re-render of the elapsed-time counter while sub-agents are running, " + "even during silent stretches between tool calls. Default 10000 (10 s). " + "Set to 0 to disable the elapsed-ticker path."),
|
|
13820
13820
|
edit_budget_threshold: exports_external.number().int().nonnegative().optional().describe("Telegram API edit budget per minute before the progress-card driver " + "falls back to a slower coalesce window. When a chat accumulates more " + "than this many card edits in the trailing 60 s, the driver switches " + "to a wider coalesce interval until the rate drops back. Default 18. " + "Increase if your gateway frequently bumps the Telegram edit-rate ceiling " + "with many parallel sub-agents; decrease for a more conservative buffer."),
|
|
13821
|
+
send_gate: exports_external.object({
|
|
13822
|
+
enabled: exports_external.boolean().optional().describe("Master switch for the deterministic outbound send gate " + "(telegram-plugin/send-gate.ts) \u2014 the token-bucket scheduler every " + "Bot API call transits so per-surface throttles can't add up past a " + "flood ceiling. ON by default. Precedence: the operator break-glass " + "env var SWITCHROOM_TELEGRAM_SEND_GATE (0/false/off/no) ALWAYS wins " + "when explicitly set; this key only decides when that env var is " + "unset. Omit to keep the gate on."),
|
|
13823
|
+
global_per_sec: exports_external.number().positive().optional().describe("Global bucket sustained rate (Bot API calls/sec across ALL chats). " + "Default 25 (headroom under Telegram's ~30/s). Must be > 0 \u2014 a zero " + "or negative rate would wedge all outbound sends. Omit to keep 25."),
|
|
13824
|
+
global_burst: exports_external.number().int().positive().describe("Global bucket burst capacity. Default 4; worst-case 1s window " + "admits global_burst + global_per_sec = 29 < 30, a real margin under " + "the ceiling. Must be an integer >= 1 (a 0 capacity never admits a " + "token and wedges sends). Omit to keep 4.").optional(),
|
|
13825
|
+
per_chat_per_sec: exports_external.number().positive().optional().describe("Per-chat sustained rate (calls/sec to a single chat). Default 1. " + "Must be > 0 (zero/negative wedges that chat). Omit to keep 1."),
|
|
13826
|
+
per_chat_burst: exports_external.number().int().positive().optional().describe("Per-chat burst capacity. Default 3. Must be an integer >= 1 " + "(0 wedges the chat's bucket). Omit to keep 3."),
|
|
13827
|
+
per_group_per_min: exports_external.number().positive().optional().describe("Per-group sustained rate (calls/min to a single group/supergroup). " + "Default 18 (headroom under Telegram's ~20/min group ceiling). Must " + "be > 0. Omit to keep 18."),
|
|
13828
|
+
per_group_burst: exports_external.number().int().positive().optional().describe("Per-group burst capacity. Default 2. Must be an integer >= 1 " + "(0 wedges the group's bucket). Omit to keep 2."),
|
|
13829
|
+
edit_floor_ms: exports_external.number().int().nonnegative().optional().describe("Minimum ms between successive edits of the SAME message_id " + "(last-write-wins coalescing enforces this floor). Default 1500 " + "(Telegram's ~1 edit/sec/message practical ceiling). 0 disables the " + "floor. Must be an integer >= 0. Omit to keep 1500.")
|
|
13830
|
+
}).optional().describe("Tunable rate limits for the deterministic outbound send gate " + "(telegram-plugin/send-gate.ts). Every key is optional and defaults to " + "the send gate's built-in value, so omitting the whole block reproduces " + "today's exact behaviour \u2014 this is pure operator tuning, no default is " + "changed. Cascades from defaults.channels.telegram.send_gate."),
|
|
13831
|
+
worker_feed: exports_external.object({
|
|
13832
|
+
max_rows: exports_external.number().int().positive().optional().describe("Max live-worker rows rendered in the COMBINED worker-activity feed " + "(2+ background workers in one chat/thread coalesce into ONE message; " + "telegram-plugin/worker-activity-feed.ts) before a compact " + "'+M more working\u2026' spill line \u2014 keeps the coalesced body compact and " + "legible (and under the rich-message wire ceiling). Default 8. A " + "single-worker chat renders the full \uD83D\uDEE0 Worker card and ignores this. " + "Must be an integer >= 1. Omit to keep 8.")
|
|
13833
|
+
}).optional().describe("Tuning for the coalesced worker-activity feed " + "(telegram-plugin/worker-activity-feed.ts). Cascades from " + "defaults.channels.telegram.worker_feed."),
|
|
13821
13834
|
stickers: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Sticker aliases for the `send_sticker` MCP tool (#576). Maps a " + "short alias name (e.g. 'happy', 'thinking') to a Telegram file_id. " + "Operator-curated \u2014 capture file_ids from inbound stickers the user " + "sends and add them here. The agent calls send_sticker(chat_id, " + "alias='happy') and the gateway resolves to the file_id at send " + "time. Aliases enable persona-flavored expressiveness without " + "exposing raw file_ids in the agent prompt. Personal-assistant / " + "health-coach personas benefit; coding agents typically don't " + "configure any."),
|
|
13822
13835
|
voice_in: exports_external.object({
|
|
13823
13836
|
enabled: exports_external.boolean().optional().describe("Master switch for voice-message transcription."),
|
|
@@ -17116,18 +17129,18 @@ function alignAgentTreeOwnershipIfRoot(name, agentDir) {
|
|
|
17116
17129
|
}
|
|
17117
17130
|
return uid;
|
|
17118
17131
|
}
|
|
17119
|
-
function findAgentUnreadablePaths(candidates, agentUid) {
|
|
17132
|
+
function findAgentUnreadablePaths(candidates, agentUid, fs = { lstatSync, statSync: statSync3 }) {
|
|
17120
17133
|
const bad = [];
|
|
17121
17134
|
for (const p of candidates) {
|
|
17122
17135
|
let ls;
|
|
17123
17136
|
try {
|
|
17124
|
-
ls = lstatSync(p);
|
|
17137
|
+
ls = fs.lstatSync(p);
|
|
17125
17138
|
} catch {
|
|
17126
17139
|
continue;
|
|
17127
17140
|
}
|
|
17128
17141
|
if (ls.isSymbolicLink())
|
|
17129
17142
|
continue;
|
|
17130
|
-
const st =
|
|
17143
|
+
const st = fs.statSync(p);
|
|
17131
17144
|
if (st.uid === agentUid)
|
|
17132
17145
|
continue;
|
|
17133
17146
|
const otherReadable = st.isDirectory() ? (st.mode & 5) === 5 : (st.mode & 4) === 4;
|
|
@@ -26686,6 +26699,37 @@ function channelsToEnv(agent) {
|
|
|
26686
26699
|
if (tg.edit_budget_threshold !== undefined) {
|
|
26687
26700
|
out.SWITCHROOM_TG_EDIT_BUDGET_THRESHOLD = String(tg.edit_budget_threshold);
|
|
26688
26701
|
}
|
|
26702
|
+
const sg = tg.send_gate;
|
|
26703
|
+
if (sg) {
|
|
26704
|
+
if (sg.enabled !== undefined) {
|
|
26705
|
+
out.SWITCHROOM_TG_SEND_GATE_ENABLED = sg.enabled ? "1" : "0";
|
|
26706
|
+
}
|
|
26707
|
+
if (sg.global_per_sec !== undefined) {
|
|
26708
|
+
out.SWITCHROOM_TG_SEND_GATE_GLOBAL_PER_SEC = String(sg.global_per_sec);
|
|
26709
|
+
}
|
|
26710
|
+
if (sg.global_burst !== undefined) {
|
|
26711
|
+
out.SWITCHROOM_TG_SEND_GATE_GLOBAL_BURST = String(sg.global_burst);
|
|
26712
|
+
}
|
|
26713
|
+
if (sg.per_chat_per_sec !== undefined) {
|
|
26714
|
+
out.SWITCHROOM_TG_SEND_GATE_PER_CHAT_PER_SEC = String(sg.per_chat_per_sec);
|
|
26715
|
+
}
|
|
26716
|
+
if (sg.per_chat_burst !== undefined) {
|
|
26717
|
+
out.SWITCHROOM_TG_SEND_GATE_PER_CHAT_BURST = String(sg.per_chat_burst);
|
|
26718
|
+
}
|
|
26719
|
+
if (sg.per_group_per_min !== undefined) {
|
|
26720
|
+
out.SWITCHROOM_TG_SEND_GATE_PER_GROUP_PER_MIN = String(sg.per_group_per_min);
|
|
26721
|
+
}
|
|
26722
|
+
if (sg.per_group_burst !== undefined) {
|
|
26723
|
+
out.SWITCHROOM_TG_SEND_GATE_PER_GROUP_BURST = String(sg.per_group_burst);
|
|
26724
|
+
}
|
|
26725
|
+
if (sg.edit_floor_ms !== undefined) {
|
|
26726
|
+
out.SWITCHROOM_TG_SEND_GATE_EDIT_FLOOR_MS = String(sg.edit_floor_ms);
|
|
26727
|
+
}
|
|
26728
|
+
}
|
|
26729
|
+
const wf = tg?.worker_feed;
|
|
26730
|
+
if (wf?.max_rows !== undefined) {
|
|
26731
|
+
out.SWITCHROOM_TG_WORKER_FEED_MAX_ROWS = String(wf.max_rows);
|
|
26732
|
+
}
|
|
26689
26733
|
if (tg.clear_status_on_completion !== undefined) {
|
|
26690
26734
|
out.SWITCHROOM_TG_CLEAR_STATUS_ON_COMPLETION = tg.clear_status_on_completion ? "1" : "0";
|
|
26691
26735
|
}
|
|
@@ -29769,6 +29813,7 @@ function generateCompose(opts) {
|
|
|
29769
29813
|
const hostControlEnabled = config.host_control?.enabled !== false;
|
|
29770
29814
|
const hostHomeForChecks = opts.homeDir ?? process.env.HOME ?? "";
|
|
29771
29815
|
const probeHome = opts.probeHomeDir ?? hostHomeForChecks;
|
|
29816
|
+
const precreateHostDirs = opts.precreateHostDirs ?? (opts.homeDir !== undefined || opts.probeHomeDir !== undefined);
|
|
29772
29817
|
const switchroomConfigPath = resolveConfigMountSource(opts.switchroomConfigPath, homePrefix);
|
|
29773
29818
|
const bundledSkillsPoolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
|
|
29774
29819
|
const voiceEngine = opts.voiceEngine ?? loadHostCapabilities()?.voice.engine ?? "cloud";
|
|
@@ -29973,7 +30018,7 @@ function generateCompose(opts) {
|
|
|
29973
30018
|
telemetryDisabled,
|
|
29974
30019
|
posthogKeyOverride,
|
|
29975
30020
|
posthogHostOverride
|
|
29976
|
-
}, bundledSkillsPoolDir, hostControlEnabled, opts.operatorUid, voiceEngine);
|
|
30021
|
+
}, bundledSkillsPoolDir, hostControlEnabled, opts.operatorUid, voiceEngine, precreateHostDirs);
|
|
29977
30022
|
}
|
|
29978
30023
|
lines.push(`volumes:`);
|
|
29979
30024
|
for (const a of describeAgents(config, opts.litellmConfirmedAgents)) {
|
|
@@ -30007,7 +30052,7 @@ function generateCompose(opts) {
|
|
|
30007
30052
|
return lines.join(`
|
|
30008
30053
|
`);
|
|
30009
30054
|
}
|
|
30010
|
-
function emitAgentService(lines, a, imageTag, buildMode, buildContext, homePrefix, hostHomeForChecks, probeHome, switchroomConfigPath, containerNamePrefix, posthog, bundledSkillsPoolDir, hostControlEnabled, operatorUid, voiceEngine) {
|
|
30055
|
+
function emitAgentService(lines, a, imageTag, buildMode, buildContext, homePrefix, hostHomeForChecks, probeHome, switchroomConfigPath, containerNamePrefix, posthog, bundledSkillsPoolDir, hostControlEnabled, operatorUid, voiceEngine, precreateHostDirs) {
|
|
30011
30056
|
lines.push(` agent-${a.name}:`);
|
|
30012
30057
|
emitImageOrBuild(lines, "agent", imageTag, buildMode, buildContext);
|
|
30013
30058
|
lines.push(` container_name: ${containerNamePrefix}-${a.name}`);
|
|
@@ -30170,22 +30215,30 @@ function emitAgentService(lines, a, imageTag, buildMode, buildContext, homePrefi
|
|
|
30170
30215
|
if (conditionalMountPresent(`${probeHome}/.switchroom/credentials/${a.name}`, hostHomeForChecks, probeHome)) {
|
|
30171
30216
|
lines.push(` - ${homePrefix}/.switchroom/credentials/${a.name}:${homePrefix}/.switchroom/credentials:ro`);
|
|
30172
30217
|
}
|
|
30173
|
-
|
|
30174
|
-
mkdirSync12(`${probeHome}/.switchroom/audit/${a.name}`, { recursive: true });
|
|
30175
|
-
} catch {}
|
|
30176
|
-
try {
|
|
30177
|
-
const blockedDir = `${probeHome}/.switchroom/blocked-approvals`;
|
|
30178
|
-
mkdirSync12(blockedDir, { recursive: true });
|
|
30179
|
-
chmodSync3(blockedDir, 1023);
|
|
30180
|
-
} catch {}
|
|
30181
|
-
try {
|
|
30182
|
-
mkdirSync12(`${probeHome}/.switchroom/agents/${a.name}/schedule.d`, { recursive: true });
|
|
30183
|
-
} catch {}
|
|
30184
|
-
lines.push(` - ${homePrefix}/.switchroom/audit/${a.name}:${homePrefix}/.switchroom/audit/${a.name}:rw`);
|
|
30185
|
-
if (conditionalMountPresent(`${probeHome}/.switchroom-config`, hostHomeForChecks, probeHome)) {
|
|
30218
|
+
if (precreateHostDirs) {
|
|
30186
30219
|
try {
|
|
30187
|
-
mkdirSync12(`${probeHome}/.switchroom
|
|
30220
|
+
mkdirSync12(`${probeHome}/.switchroom/audit/${a.name}`, { recursive: true });
|
|
30188
30221
|
} catch {}
|
|
30222
|
+
}
|
|
30223
|
+
if (precreateHostDirs) {
|
|
30224
|
+
try {
|
|
30225
|
+
const blockedDir = `${probeHome}/.switchroom/blocked-approvals`;
|
|
30226
|
+
mkdirSync12(blockedDir, { recursive: true });
|
|
30227
|
+
chmodSync3(blockedDir, 1023);
|
|
30228
|
+
} catch {}
|
|
30229
|
+
}
|
|
30230
|
+
if (precreateHostDirs) {
|
|
30231
|
+
try {
|
|
30232
|
+
mkdirSync12(`${probeHome}/.switchroom/agents/${a.name}/schedule.d`, { recursive: true });
|
|
30233
|
+
} catch {}
|
|
30234
|
+
}
|
|
30235
|
+
lines.push(` - ${homePrefix}/.switchroom/audit/${a.name}:${homePrefix}/.switchroom/audit/${a.name}:rw`);
|
|
30236
|
+
if (conditionalMountPresent(`${probeHome}/.switchroom-config`, hostHomeForChecks, probeHome)) {
|
|
30237
|
+
if (precreateHostDirs) {
|
|
30238
|
+
try {
|
|
30239
|
+
mkdirSync12(`${probeHome}/.switchroom-config/agents/${a.name}/personal-skills`, { recursive: true });
|
|
30240
|
+
} catch {}
|
|
30241
|
+
}
|
|
30189
30242
|
lines.push(` - ${homePrefix}/.switchroom-config/agents/${a.name}/personal-skills:${homePrefix}/.switchroom-config/agents/${a.name}/personal-skills:rw`);
|
|
30190
30243
|
}
|
|
30191
30244
|
if (existsSync18(`${probeHome}/.switchroom/bin/webkite`)) {
|
|
@@ -43713,6 +43766,74 @@ function runCredentialsMigrationChecks(config, deps = {}) {
|
|
|
43713
43766
|
}
|
|
43714
43767
|
var init_doctor_credentials_migration = () => {};
|
|
43715
43768
|
|
|
43769
|
+
// src/cli/doctor-agent-dotfile-ownership.ts
|
|
43770
|
+
import { existsSync as existsSync62, lstatSync as lstatSync8, statSync as statSync36 } from "node:fs";
|
|
43771
|
+
import { join as join61 } from "node:path";
|
|
43772
|
+
function agentDotfileCandidates(agentDir) {
|
|
43773
|
+
return [
|
|
43774
|
+
join61(agentDir, ".claude", "settings.json"),
|
|
43775
|
+
join61(agentDir, ".claude.json"),
|
|
43776
|
+
join61(agentDir, ".claude", ".claude.json"),
|
|
43777
|
+
join61(agentDir, ".mcp.json"),
|
|
43778
|
+
join61(agentDir, ".claude-cron", ".mcp.json")
|
|
43779
|
+
];
|
|
43780
|
+
}
|
|
43781
|
+
function runAgentDotfileOwnershipChecks(config, deps = {}) {
|
|
43782
|
+
const results = [];
|
|
43783
|
+
const agents = Object.keys(config.agents ?? {});
|
|
43784
|
+
if (agents.length === 0)
|
|
43785
|
+
return results;
|
|
43786
|
+
const exists = deps.existsSync ?? existsSync62;
|
|
43787
|
+
const scanFs = deps.scanFs ?? { lstatSync: lstatSync8, statSync: statSync36 };
|
|
43788
|
+
let agentsDir = deps.agentsDir;
|
|
43789
|
+
if (agentsDir === undefined) {
|
|
43790
|
+
try {
|
|
43791
|
+
agentsDir = resolveAgentsDir(config);
|
|
43792
|
+
} catch {
|
|
43793
|
+
agentsDir = undefined;
|
|
43794
|
+
}
|
|
43795
|
+
}
|
|
43796
|
+
if (agentsDir === undefined) {
|
|
43797
|
+
return [
|
|
43798
|
+
{
|
|
43799
|
+
name: "agent dotfile ownership",
|
|
43800
|
+
status: "warn",
|
|
43801
|
+
detail: "could not resolve the agents directory (no switchroom block) \u2014 skipped dotfile-ownership check"
|
|
43802
|
+
}
|
|
43803
|
+
];
|
|
43804
|
+
}
|
|
43805
|
+
for (const name of agents) {
|
|
43806
|
+
const agentDir = join61(agentsDir, name);
|
|
43807
|
+
if (!exists(agentDir)) {
|
|
43808
|
+
continue;
|
|
43809
|
+
}
|
|
43810
|
+
const isRoot = config.agents[name]?.root === true;
|
|
43811
|
+
const runtimeUid = isRoot ? 0 : allocateAgentUid(name);
|
|
43812
|
+
const candidates = agentDotfileCandidates(agentDir);
|
|
43813
|
+
const unreadable = findAgentUnreadablePaths(candidates, runtimeUid, scanFs);
|
|
43814
|
+
if (unreadable.length === 0) {
|
|
43815
|
+
results.push({
|
|
43816
|
+
name: `dotfile ownership: ${name}`,
|
|
43817
|
+
status: "ok",
|
|
43818
|
+
detail: isRoot ? `runs as uid 0 (root-tier) \u2014 dotfiles readable` : `dotfiles readable by agent uid ${runtimeUid}`
|
|
43819
|
+
});
|
|
43820
|
+
continue;
|
|
43821
|
+
}
|
|
43822
|
+
results.push({
|
|
43823
|
+
name: `dotfile ownership: ${name}`,
|
|
43824
|
+
status: "fail",
|
|
43825
|
+
detail: `${unreadable.length} dotfile(s) not readable by the agent's runtime ` + `uid ${runtimeUid}: ${unreadable.join(", ")}. Claude Code loads no ` + `permission allowlist from an unreadable settings.json \u2192 the agent ` + `prompts the operator to approve every tool call (card storm).`,
|
|
43826
|
+
fix: `sudo chown -h -R ${runtimeUid}:${runtimeUid} ${agentDir} # then \`switchroom agent restart ${name}\``
|
|
43827
|
+
});
|
|
43828
|
+
}
|
|
43829
|
+
return results;
|
|
43830
|
+
}
|
|
43831
|
+
var init_doctor_agent_dotfile_ownership = __esm(() => {
|
|
43832
|
+
init_loader();
|
|
43833
|
+
init_agent_uid();
|
|
43834
|
+
init_agent_owned_tree();
|
|
43835
|
+
});
|
|
43836
|
+
|
|
43716
43837
|
// src/cli/doctor-inlined-secrets.ts
|
|
43717
43838
|
import { readFileSync as fsReadFileSync } from "node:fs";
|
|
43718
43839
|
function isSecretShapedKey(key) {
|
|
@@ -43810,7 +43931,7 @@ var init_doctor_inlined_secrets = __esm(() => {
|
|
|
43810
43931
|
// src/cli/doctor-approval-attribution.ts
|
|
43811
43932
|
import { execFileSync as execFileSync19 } from "node:child_process";
|
|
43812
43933
|
import { readFileSync as fsReadFileSync2 } from "node:fs";
|
|
43813
|
-
import { join as
|
|
43934
|
+
import { join as join62 } from "node:path";
|
|
43814
43935
|
function parseApproverSet(canonical) {
|
|
43815
43936
|
try {
|
|
43816
43937
|
const arr = JSON.parse(canonical);
|
|
@@ -43930,7 +44051,7 @@ function loadLiveAllowFromAccessFiles(agentNames, agentsDir, readFile2 = (p) =>
|
|
|
43930
44051
|
let readAny = false;
|
|
43931
44052
|
for (const name of agentNames) {
|
|
43932
44053
|
try {
|
|
43933
|
-
const raw = readFile2(
|
|
44054
|
+
const raw = readFile2(join62(agentsDir, name, "telegram", "access.json"));
|
|
43934
44055
|
const parsed = JSON.parse(raw);
|
|
43935
44056
|
readAny = true;
|
|
43936
44057
|
if (Array.isArray(parsed.allowFrom)) {
|
|
@@ -43963,13 +44084,13 @@ var init_doctor_approval_attribution = __esm(() => {
|
|
|
43963
44084
|
// src/cli/doctor-audit-integrity.ts
|
|
43964
44085
|
import { readFileSync as fsReadFileSync3 } from "node:fs";
|
|
43965
44086
|
import { homedir as homedir37 } from "node:os";
|
|
43966
|
-
import { join as
|
|
44087
|
+
import { join as join63 } from "node:path";
|
|
43967
44088
|
function rootWrittenLogs(home2) {
|
|
43968
44089
|
return [
|
|
43969
|
-
{ label: "vault-broker", path:
|
|
44090
|
+
{ label: "vault-broker", path: join63(home2, ".switchroom", "vault-audit.log") },
|
|
43970
44091
|
{
|
|
43971
44092
|
label: "hostd",
|
|
43972
|
-
path:
|
|
44093
|
+
path: join63(home2, ".switchroom", "host-control-audit.log")
|
|
43973
44094
|
}
|
|
43974
44095
|
];
|
|
43975
44096
|
}
|
|
@@ -43978,7 +44099,7 @@ function runAuditIntegrityChecks(deps = {}) {
|
|
|
43978
44099
|
const read = deps.readFileSync ?? ((p) => fsReadFileSync3(p, "utf8"));
|
|
43979
44100
|
const readFailOpen = deps.readFailOpenState ?? readFailOpenState;
|
|
43980
44101
|
const results = [];
|
|
43981
|
-
const vaultAuditLog =
|
|
44102
|
+
const vaultAuditLog = join63(home2, ".switchroom", "vault-audit.log");
|
|
43982
44103
|
const failOpen = readFailOpen(failOpenStatePath(vaultAuditLog));
|
|
43983
44104
|
if (failOpen.failOpenCount > 0) {
|
|
43984
44105
|
const when = failOpen.lastFailureTs ? ` (last at ${failOpen.lastFailureTs})` : ``;
|
|
@@ -44051,16 +44172,16 @@ var init_doctor_audit_integrity = __esm(() => {
|
|
|
44051
44172
|
});
|
|
44052
44173
|
|
|
44053
44174
|
// src/cli/doctor-agent-smoke.ts
|
|
44054
|
-
import { existsSync as
|
|
44175
|
+
import { existsSync as existsSync63 } from "node:fs";
|
|
44055
44176
|
import { homedir as homedir38 } from "node:os";
|
|
44056
|
-
import { join as
|
|
44177
|
+
import { join as join64 } from "node:path";
|
|
44057
44178
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
44058
44179
|
async function runAgentSmokeChecks(config, deps = {}) {
|
|
44059
44180
|
if (deps.fast)
|
|
44060
44181
|
return [];
|
|
44061
44182
|
const home2 = deps.homeDir ?? homedir38();
|
|
44062
|
-
const sock = deps.operatorSockPath ??
|
|
44063
|
-
if (!deps.hostdRequestImpl && !
|
|
44183
|
+
const sock = deps.operatorSockPath ?? join64(home2, ".switchroom", "hostd", "operator", "sock");
|
|
44184
|
+
if (!deps.hostdRequestImpl && !existsSync63(sock)) {
|
|
44064
44185
|
return [
|
|
44065
44186
|
{
|
|
44066
44187
|
name: "agent liveness",
|
|
@@ -44138,9 +44259,9 @@ var init_doctor_agent_smoke = __esm(() => {
|
|
|
44138
44259
|
|
|
44139
44260
|
// src/cli/doctor-vault-broker-durability.ts
|
|
44140
44261
|
import { execFileSync as execFileSync20 } from "node:child_process";
|
|
44141
|
-
import { existsSync as
|
|
44262
|
+
import { existsSync as existsSync64, statSync as statSync37 } from "node:fs";
|
|
44142
44263
|
import { homedir as homedir39 } from "node:os";
|
|
44143
|
-
import { join as
|
|
44264
|
+
import { join as join65 } from "node:path";
|
|
44144
44265
|
function probeBindMountInode(hostPath, brokerContainerPath, opts) {
|
|
44145
44266
|
const statHost = opts?.statHost ?? defaultStatHost;
|
|
44146
44267
|
const statBroker = opts?.statBroker ?? defaultStatBroker;
|
|
@@ -44162,10 +44283,10 @@ function probeBindMountInode(hostPath, brokerContainerPath, opts) {
|
|
|
44162
44283
|
};
|
|
44163
44284
|
}
|
|
44164
44285
|
function defaultStatHost(p) {
|
|
44165
|
-
if (!
|
|
44286
|
+
if (!existsSync64(p))
|
|
44166
44287
|
return null;
|
|
44167
44288
|
try {
|
|
44168
|
-
const s =
|
|
44289
|
+
const s = statSync37(p, { bigint: true });
|
|
44169
44290
|
return { ino: s.ino, size: Number(s.size) };
|
|
44170
44291
|
} catch {
|
|
44171
44292
|
return null;
|
|
@@ -44289,16 +44410,16 @@ function runVaultBrokerDurabilityChecks(_config, opts) {
|
|
|
44289
44410
|
probeBrokerUnlocked(opts?.statusProbe),
|
|
44290
44411
|
probeAutoUnlockBlob(home2),
|
|
44291
44412
|
probeMachineIdMount(),
|
|
44292
|
-
formatBindMountResult("vault-broker: vault.enc bind mount",
|
|
44293
|
-
formatBindMountResult("vault-broker: vault-grants.db bind mount (#1737)",
|
|
44294
|
-
formatBindMountResult("vault-broker: vault-audit.log bind mount (#1025)",
|
|
44413
|
+
formatBindMountResult("vault-broker: vault.enc bind mount", join65(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc", probe2(join65(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc")),
|
|
44414
|
+
formatBindMountResult("vault-broker: vault-grants.db bind mount (#1737)", join65(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db", probe2(join65(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db")),
|
|
44415
|
+
formatBindMountResult("vault-broker: vault-audit.log bind mount (#1025)", join65(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log", probe2(join65(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log")),
|
|
44295
44416
|
probeKernelDbDurability(home2, {
|
|
44296
44417
|
statBroker: opts?.kernelStatBroker
|
|
44297
44418
|
})
|
|
44298
44419
|
];
|
|
44299
44420
|
}
|
|
44300
44421
|
function probeKernelDbDurability(home2, opts) {
|
|
44301
|
-
const hostDir =
|
|
44422
|
+
const hostDir = join65(home2, ".switchroom", "approvals");
|
|
44302
44423
|
const containerDir = "/state/approvals";
|
|
44303
44424
|
const name = "approval-kernel: approvals bind mount (allow_always durability)";
|
|
44304
44425
|
const kernelStat = opts?.statBroker ?? defaultKernelStatBroker;
|
|
@@ -44366,8 +44487,8 @@ function defaultKernelStatBroker(p) {
|
|
|
44366
44487
|
return { kind: "ok-with-stat", ino: inoStr, size };
|
|
44367
44488
|
}
|
|
44368
44489
|
function probeAutoUnlockBlob(home2) {
|
|
44369
|
-
const blobPath =
|
|
44370
|
-
if (!
|
|
44490
|
+
const blobPath = join65(home2, ".switchroom", "vault-auto-unlock");
|
|
44491
|
+
if (!existsSync64(blobPath)) {
|
|
44371
44492
|
return {
|
|
44372
44493
|
name: "vault-broker: auto-unlock blob",
|
|
44373
44494
|
status: "warn",
|
|
@@ -44375,7 +44496,7 @@ function probeAutoUnlockBlob(home2) {
|
|
|
44375
44496
|
fix: "Run `switchroom vault broker enable-auto-unlock` to seal the blob with the current passphrase + machine-id"
|
|
44376
44497
|
};
|
|
44377
44498
|
}
|
|
44378
|
-
const sz =
|
|
44499
|
+
const sz = statSync37(blobPath).size;
|
|
44379
44500
|
if (sz === 0) {
|
|
44380
44501
|
return {
|
|
44381
44502
|
name: "vault-broker: auto-unlock blob",
|
|
@@ -44391,7 +44512,7 @@ function probeAutoUnlockBlob(home2) {
|
|
|
44391
44512
|
};
|
|
44392
44513
|
}
|
|
44393
44514
|
function probeMachineIdMount() {
|
|
44394
|
-
const hostExists =
|
|
44515
|
+
const hostExists = existsSync64("/etc/machine-id");
|
|
44395
44516
|
if (!hostExists) {
|
|
44396
44517
|
return {
|
|
44397
44518
|
name: "vault-broker: machine-id passthrough",
|
|
@@ -44542,25 +44663,25 @@ import { Socket as Socket3 } from "node:net";
|
|
|
44542
44663
|
import {
|
|
44543
44664
|
accessSync as accessSync2,
|
|
44544
44665
|
constants as fsConstants5,
|
|
44545
|
-
existsSync as
|
|
44546
|
-
lstatSync as
|
|
44666
|
+
existsSync as existsSync65,
|
|
44667
|
+
lstatSync as lstatSync9,
|
|
44547
44668
|
mkdirSync as mkdirSync33,
|
|
44548
44669
|
readFileSync as readFileSync58,
|
|
44549
44670
|
readdirSync as readdirSync22,
|
|
44550
|
-
statSync as
|
|
44671
|
+
statSync as statSync38
|
|
44551
44672
|
} from "node:fs";
|
|
44552
|
-
import { dirname as dirname19, join as
|
|
44673
|
+
import { dirname as dirname19, join as join66, resolve as resolve38 } from "node:path";
|
|
44553
44674
|
import { createPublicKey, createPrivateKey } from "node:crypto";
|
|
44554
44675
|
function findInNvm(bin) {
|
|
44555
|
-
const nvmRoot =
|
|
44556
|
-
if (!
|
|
44676
|
+
const nvmRoot = join66(process.env.HOME ?? "", ".nvm", "versions", "node");
|
|
44677
|
+
if (!existsSync65(nvmRoot))
|
|
44557
44678
|
return null;
|
|
44558
44679
|
try {
|
|
44559
44680
|
const versions = readdirSync22(nvmRoot).sort().reverse();
|
|
44560
44681
|
for (const v of versions) {
|
|
44561
|
-
const candidate =
|
|
44682
|
+
const candidate = join66(nvmRoot, v, "bin", bin);
|
|
44562
44683
|
try {
|
|
44563
|
-
const s =
|
|
44684
|
+
const s = statSync38(candidate);
|
|
44564
44685
|
if (s.isFile() || s.isSymbolicLink()) {
|
|
44565
44686
|
return candidate;
|
|
44566
44687
|
}
|
|
@@ -44585,7 +44706,7 @@ function whichOnPath(bin) {
|
|
|
44585
44706
|
for (const dir of pathEnv.split(":")) {
|
|
44586
44707
|
if (!dir)
|
|
44587
44708
|
continue;
|
|
44588
|
-
const candidate =
|
|
44709
|
+
const candidate = join66(dir, bin);
|
|
44589
44710
|
if (isX(candidate))
|
|
44590
44711
|
return candidate;
|
|
44591
44712
|
}
|
|
@@ -44751,21 +44872,21 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
|
|
|
44751
44872
|
if (envBrowsersPath && envBrowsersPath.length > 0) {
|
|
44752
44873
|
cacheLocations.push(envBrowsersPath);
|
|
44753
44874
|
}
|
|
44754
|
-
cacheLocations.push(
|
|
44875
|
+
cacheLocations.push(join66(homeDir, ".cache", "ms-playwright"));
|
|
44755
44876
|
for (const cacheDir of cacheLocations) {
|
|
44756
|
-
if (!
|
|
44877
|
+
if (!existsSync65(cacheDir))
|
|
44757
44878
|
continue;
|
|
44758
44879
|
try {
|
|
44759
44880
|
const entries = readdirSync22(cacheDir).filter((e) => e.startsWith("chromium"));
|
|
44760
44881
|
for (const entry of entries) {
|
|
44761
44882
|
const candidates2 = [
|
|
44762
|
-
|
|
44763
|
-
|
|
44764
|
-
|
|
44765
|
-
|
|
44883
|
+
join66(cacheDir, entry, "chrome-linux64", "chrome"),
|
|
44884
|
+
join66(cacheDir, entry, "chrome-linux", "chrome"),
|
|
44885
|
+
join66(cacheDir, entry, "chrome-linux64", "headless_shell"),
|
|
44886
|
+
join66(cacheDir, entry, "chrome-linux", "headless_shell")
|
|
44766
44887
|
];
|
|
44767
44888
|
for (const path4 of candidates2) {
|
|
44768
|
-
if (
|
|
44889
|
+
if (existsSync65(path4))
|
|
44769
44890
|
return path4;
|
|
44770
44891
|
}
|
|
44771
44892
|
}
|
|
@@ -44884,7 +45005,7 @@ function checkUserDeclaredMcps(name, agentConfig, config, renderedMcpServers) {
|
|
|
44884
45005
|
}
|
|
44885
45006
|
function defaultPathKind(p) {
|
|
44886
45007
|
try {
|
|
44887
|
-
const s =
|
|
45008
|
+
const s = lstatSync9(p);
|
|
44888
45009
|
return s.isDirectory() ? "dir" : "file-or-symlink";
|
|
44889
45010
|
} catch {
|
|
44890
45011
|
return "absent";
|
|
@@ -44894,7 +45015,7 @@ function checkDeployMounts(opts) {
|
|
|
44894
45015
|
const home2 = opts?.home ?? process.env.HOME ?? "/root";
|
|
44895
45016
|
const { pathKind } = opts?.deps ?? DEFAULT_DEPLOY_MOUNTS_DEPS;
|
|
44896
45017
|
const results = [];
|
|
44897
|
-
const dockerComposePlugin =
|
|
45018
|
+
const dockerComposePlugin = join66(home2, ".docker", "cli-plugins", "docker-compose");
|
|
44898
45019
|
const pluginKind = pathKind(dockerComposePlugin);
|
|
44899
45020
|
if (pluginKind === "dir") {
|
|
44900
45021
|
results.push({
|
|
@@ -44932,8 +45053,8 @@ function checkDeployMounts(opts) {
|
|
|
44932
45053
|
function checkLegacyState() {
|
|
44933
45054
|
const results = [];
|
|
44934
45055
|
const h = process.env.HOME ?? "/root";
|
|
44935
|
-
const clerkDir =
|
|
44936
|
-
const clerkPresent =
|
|
45056
|
+
const clerkDir = join66(h, LEGACY_STATE_DIR);
|
|
45057
|
+
const clerkPresent = existsSync65(clerkDir);
|
|
44937
45058
|
results.push({
|
|
44938
45059
|
name: "legacy ~/.clerk state",
|
|
44939
45060
|
status: clerkPresent ? "warn" : "ok",
|
|
@@ -44942,10 +45063,10 @@ function checkLegacyState() {
|
|
|
44942
45063
|
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."
|
|
44943
45064
|
} : {}
|
|
44944
45065
|
});
|
|
44945
|
-
const legacySock =
|
|
45066
|
+
const legacySock = join66(h, ".switchroom", "vault-broker.sock");
|
|
44946
45067
|
let sockStat = null;
|
|
44947
45068
|
try {
|
|
44948
|
-
sockStat =
|
|
45069
|
+
sockStat = lstatSync9(legacySock);
|
|
44949
45070
|
} catch {}
|
|
44950
45071
|
if (sockStat) {
|
|
44951
45072
|
results.push({
|
|
@@ -45063,7 +45184,7 @@ function checkVault(config) {
|
|
|
45063
45184
|
detail: "Approval auth: passphrase (two-factor)"
|
|
45064
45185
|
};
|
|
45065
45186
|
const pairsResult = checkVaultBrokerSocketPairs(config);
|
|
45066
|
-
if (!
|
|
45187
|
+
if (!existsSync65(vaultPath)) {
|
|
45067
45188
|
return [
|
|
45068
45189
|
postureResult,
|
|
45069
45190
|
{
|
|
@@ -45449,7 +45570,7 @@ function tryReadHostFile(path4) {
|
|
|
45449
45570
|
}
|
|
45450
45571
|
}
|
|
45451
45572
|
function parseEnvFile(path4) {
|
|
45452
|
-
if (!
|
|
45573
|
+
if (!existsSync65(path4))
|
|
45453
45574
|
return {};
|
|
45454
45575
|
let content;
|
|
45455
45576
|
try {
|
|
@@ -45508,7 +45629,7 @@ async function checkTelegram(config) {
|
|
|
45508
45629
|
const plugin = agentConfig.channels?.telegram?.plugin ?? "switchroom";
|
|
45509
45630
|
if (plugin !== "switchroom")
|
|
45510
45631
|
continue;
|
|
45511
|
-
const envPath =
|
|
45632
|
+
const envPath = join66(agentsDir, name, "telegram", ".env");
|
|
45512
45633
|
const read = tryReadHostFile(envPath);
|
|
45513
45634
|
if (read.kind === "eacces") {
|
|
45514
45635
|
results.push({
|
|
@@ -45560,7 +45681,7 @@ async function checkTelegram(config) {
|
|
|
45560
45681
|
}
|
|
45561
45682
|
function checkStartShStale(agentName, startShPath) {
|
|
45562
45683
|
const label = `${agentName}: start.sh scheduler block`;
|
|
45563
|
-
if (!
|
|
45684
|
+
if (!existsSync65(startShPath)) {
|
|
45564
45685
|
return {
|
|
45565
45686
|
name: label,
|
|
45566
45687
|
status: "warn",
|
|
@@ -45591,10 +45712,10 @@ function checkStartShStale(agentName, startShPath) {
|
|
|
45591
45712
|
}
|
|
45592
45713
|
function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
45593
45714
|
const label = `${agentName}: $HOME/.switchroom symlink (#910)`;
|
|
45594
|
-
const path4 =
|
|
45715
|
+
const path4 = join66(agentDir, "home", ".switchroom");
|
|
45595
45716
|
let stats;
|
|
45596
45717
|
try {
|
|
45597
|
-
stats =
|
|
45718
|
+
stats = lstatSync9(path4);
|
|
45598
45719
|
} catch (err) {
|
|
45599
45720
|
if (err.code === "ENOENT") {
|
|
45600
45721
|
return {
|
|
@@ -45628,8 +45749,8 @@ function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
|
45628
45749
|
}
|
|
45629
45750
|
function checkRepoHygiene(repoRoot) {
|
|
45630
45751
|
const results = [];
|
|
45631
|
-
const exportDir =
|
|
45632
|
-
if (
|
|
45752
|
+
const exportDir = join66(repoRoot, "clerk-export");
|
|
45753
|
+
if (existsSync65(exportDir)) {
|
|
45633
45754
|
results.push({
|
|
45634
45755
|
name: "repo hygiene: clerk-export/ on disk (#1072)",
|
|
45635
45756
|
status: "warn",
|
|
@@ -45637,8 +45758,8 @@ function checkRepoHygiene(repoRoot) {
|
|
|
45637
45758
|
fix: `Run scripts/migrate-clerk-export-to-vault.sh to move the bundle ` + `into the vault, then delete the on-disk copy.`
|
|
45638
45759
|
});
|
|
45639
45760
|
}
|
|
45640
|
-
const knownTarball =
|
|
45641
|
-
if (
|
|
45761
|
+
const knownTarball = join66(repoRoot, "clerk-export-with-secrets.tar.gz");
|
|
45762
|
+
if (existsSync65(knownTarball)) {
|
|
45642
45763
|
results.push({
|
|
45643
45764
|
name: "repo hygiene: clerk-export-with-secrets.tar.gz on disk (#1072)",
|
|
45644
45765
|
status: "warn",
|
|
@@ -45655,7 +45776,7 @@ function checkRepoHygiene(repoRoot) {
|
|
|
45655
45776
|
results.push({
|
|
45656
45777
|
name: `repo hygiene: ${name} on disk (#1072)`,
|
|
45657
45778
|
status: "warn",
|
|
45658
|
-
detail: `${
|
|
45779
|
+
detail: `${join66(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
|
|
45659
45780
|
fix: `Inspect, migrate any secrets into the vault, then delete the ` + `archive.`
|
|
45660
45781
|
});
|
|
45661
45782
|
}
|
|
@@ -45678,10 +45799,10 @@ function checkRepoHygiene(repoRoot) {
|
|
|
45678
45799
|
}
|
|
45679
45800
|
function isSwitchroomCheckout(dir) {
|
|
45680
45801
|
try {
|
|
45681
|
-
if (!
|
|
45802
|
+
if (!existsSync65(join66(dir, ".git")))
|
|
45682
45803
|
return false;
|
|
45683
|
-
const pkgPath =
|
|
45684
|
-
if (!
|
|
45804
|
+
const pkgPath = join66(dir, "package.json");
|
|
45805
|
+
if (!existsSync65(pkgPath))
|
|
45685
45806
|
return false;
|
|
45686
45807
|
const pkg = JSON.parse(readFileSync58(pkgPath, "utf-8"));
|
|
45687
45808
|
return pkg.name === "switchroom";
|
|
@@ -45696,7 +45817,7 @@ function checkAgents(config, configPath) {
|
|
|
45696
45817
|
const authStatuses = getAllAuthStatuses(config);
|
|
45697
45818
|
for (const [name, agentConfig] of Object.entries(config.agents)) {
|
|
45698
45819
|
const agentDir = resolve38(agentsDir, name);
|
|
45699
|
-
if (!
|
|
45820
|
+
if (!existsSync65(agentDir)) {
|
|
45700
45821
|
results.push({
|
|
45701
45822
|
name: `${name}: scaffold`,
|
|
45702
45823
|
status: "fail",
|
|
@@ -45717,7 +45838,7 @@ function checkAgents(config, configPath) {
|
|
|
45717
45838
|
fix: `Rotate the bot token (e.g. via \`switchroom vault\`), then run ` + `\`switchroom agent unquarantine ${name}\` and \`switchroom agent restart ${name}\``
|
|
45718
45839
|
});
|
|
45719
45840
|
}
|
|
45720
|
-
results.push(checkStartShStale(name,
|
|
45841
|
+
results.push(checkStartShStale(name, join66(agentDir, "start.sh")));
|
|
45721
45842
|
results.push(checkLeakedHomeSwitchroom(name, agentDir));
|
|
45722
45843
|
const status = statuses[name];
|
|
45723
45844
|
const active = status?.active ?? "unknown";
|
|
@@ -45794,8 +45915,8 @@ function checkAgents(config, configPath) {
|
|
|
45794
45915
|
}
|
|
45795
45916
|
}
|
|
45796
45917
|
if (agentConfig.channels?.telegram?.plugin === "switchroom") {
|
|
45797
|
-
const mcpJsonPath =
|
|
45798
|
-
if (!
|
|
45918
|
+
const mcpJsonPath = join66(agentDir, ".mcp.json");
|
|
45919
|
+
if (!existsSync65(mcpJsonPath)) {
|
|
45799
45920
|
results.push({
|
|
45800
45921
|
name: `${name}: .mcp.json`,
|
|
45801
45922
|
status: "fail",
|
|
@@ -45881,7 +46002,7 @@ function mffEnvPath(config) {
|
|
|
45881
46002
|
return agent ? resolve38(home2, ".switchroom/credentials", agent, "my-family-finance/.env") : resolve38(home2, ".switchroom/credentials/my-family-finance/.env");
|
|
45882
46003
|
}
|
|
45883
46004
|
function mffEnvState(envPath) {
|
|
45884
|
-
if (!
|
|
46005
|
+
if (!existsSync65(envPath))
|
|
45885
46006
|
return "absent";
|
|
45886
46007
|
try {
|
|
45887
46008
|
accessSync2(envPath, fsConstants5.R_OK);
|
|
@@ -45899,7 +46020,7 @@ function checkMffVaultKeyPresent(passphrase, vaultPath) {
|
|
|
45899
46020
|
fix: "Export SWITCHROOM_VAULT_PASSPHRASE to enable MFF vault probes"
|
|
45900
46021
|
};
|
|
45901
46022
|
}
|
|
45902
|
-
if (!
|
|
46023
|
+
if (!existsSync65(vaultPath)) {
|
|
45903
46024
|
return {
|
|
45904
46025
|
name: "mff: vault key present",
|
|
45905
46026
|
status: "fail",
|
|
@@ -45952,7 +46073,7 @@ function deriveEd25519PublicKeyBytes(keyMaterial) {
|
|
|
45952
46073
|
}
|
|
45953
46074
|
}
|
|
45954
46075
|
function checkMffVaultKeyFormat(passphrase, vaultPath) {
|
|
45955
|
-
if (!passphrase || !
|
|
46076
|
+
if (!passphrase || !existsSync65(vaultPath)) {
|
|
45956
46077
|
return {
|
|
45957
46078
|
name: "mff: vault key format",
|
|
45958
46079
|
status: "warn",
|
|
@@ -46096,8 +46217,8 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
|
|
|
46096
46217
|
};
|
|
46097
46218
|
}
|
|
46098
46219
|
const credDir = dirname19(envPath);
|
|
46099
|
-
const authScript =
|
|
46100
|
-
if (!
|
|
46220
|
+
const authScript = join66(credDir, "claude-auth.py");
|
|
46221
|
+
if (!existsSync65(authScript)) {
|
|
46101
46222
|
return {
|
|
46102
46223
|
name: "mff: auth flow",
|
|
46103
46224
|
status: "warn",
|
|
@@ -46403,6 +46524,10 @@ function registerDoctorCommand(program3) {
|
|
|
46403
46524
|
{ title: "Memory (Hindsight)", results: await checkHindsight(config) },
|
|
46404
46525
|
{ title: "Telegram", results: await checkTelegram(config) },
|
|
46405
46526
|
{ title: "Agents", results: checkAgents(config, configPath) },
|
|
46527
|
+
{
|
|
46528
|
+
title: "Agent dotfile ownership (#3157)",
|
|
46529
|
+
results: runAgentDotfileOwnershipChecks(config)
|
|
46530
|
+
},
|
|
46406
46531
|
{ title: "Credentials", results: runCredentialsMigrationChecks(config) },
|
|
46407
46532
|
{ title: "Audit integrity (WS10-F4)", results: runAuditIntegrityChecks() },
|
|
46408
46533
|
{
|
|
@@ -46525,6 +46650,7 @@ var init_doctor = __esm(() => {
|
|
|
46525
46650
|
init_doctor_notion();
|
|
46526
46651
|
init_doctor_credentials_migration();
|
|
46527
46652
|
init_doctor_secret_access();
|
|
46653
|
+
init_doctor_agent_dotfile_ownership();
|
|
46528
46654
|
init_doctor_inlined_secrets();
|
|
46529
46655
|
init_doctor_approval_attribution();
|
|
46530
46656
|
init_doctor_audit_integrity();
|
|
@@ -47116,7 +47242,7 @@ var init_fleet_defaults = __esm(() => {
|
|
|
47116
47242
|
|
|
47117
47243
|
// src/agents/connection-health.ts
|
|
47118
47244
|
import { mkdirSync as mkdirSync44, writeFileSync as writeFileSync27 } from "node:fs";
|
|
47119
|
-
import { join as
|
|
47245
|
+
import { join as join81 } from "node:path";
|
|
47120
47246
|
async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
|
|
47121
47247
|
const reqs = computeMcpSecretRequirements(config).filter((r) => r.agent === agentName);
|
|
47122
47248
|
if (reqs.length === 0)
|
|
@@ -47173,8 +47299,8 @@ async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
|
|
|
47173
47299
|
return issues;
|
|
47174
47300
|
}
|
|
47175
47301
|
function writeConnectionHealthFile(agentDir, health, deps) {
|
|
47176
|
-
const dir =
|
|
47177
|
-
const path7 =
|
|
47302
|
+
const dir = join81(agentDir, ".claude");
|
|
47303
|
+
const path7 = join81(dir, CONNECTION_HEALTH_FILENAME);
|
|
47178
47304
|
(deps?.mkdir ?? ((p, o) => mkdirSync44(p, o)))(dir, { recursive: true });
|
|
47179
47305
|
(deps?.writeFile ?? ((p, d) => writeFileSync27(p, d)))(path7, JSON.stringify(health, null, 2) + `
|
|
47180
47306
|
`);
|
|
@@ -47197,10 +47323,10 @@ var CONNECTION_HEALTH_FILENAME = "connection-health.json";
|
|
|
47197
47323
|
var init_connection_health = () => {};
|
|
47198
47324
|
|
|
47199
47325
|
// src/cli/update-prompt-hook.ts
|
|
47200
|
-
import { existsSync as
|
|
47201
|
-
import { join as
|
|
47326
|
+
import { existsSync as existsSync82, readFileSync as readFileSync70, writeFileSync as writeFileSync28, chmodSync as chmodSync11, mkdirSync as mkdirSync45 } from "node:fs";
|
|
47327
|
+
import { join as join82 } from "node:path";
|
|
47202
47328
|
function containerHookCommand() {
|
|
47203
|
-
return
|
|
47329
|
+
return join82(CONTAINER_AGENT_DIR, ".claude", "hooks", HOOK_FILENAME);
|
|
47204
47330
|
}
|
|
47205
47331
|
function updatePromptHookScript() {
|
|
47206
47332
|
return `#!/bin/bash
|
|
@@ -47266,12 +47392,12 @@ exit 0
|
|
|
47266
47392
|
`;
|
|
47267
47393
|
}
|
|
47268
47394
|
function installUpdatePromptHook(agentDir) {
|
|
47269
|
-
const hooksDir =
|
|
47395
|
+
const hooksDir = join82(agentDir, ".claude", "hooks");
|
|
47270
47396
|
mkdirSync45(hooksDir, { recursive: true });
|
|
47271
|
-
const scriptPath =
|
|
47397
|
+
const scriptPath = join82(hooksDir, HOOK_FILENAME);
|
|
47272
47398
|
const desired = updatePromptHookScript();
|
|
47273
47399
|
let installed = false;
|
|
47274
|
-
const existing =
|
|
47400
|
+
const existing = existsSync82(scriptPath) ? readFileSync70(scriptPath, "utf-8") : "";
|
|
47275
47401
|
if (existing !== desired) {
|
|
47276
47402
|
writeFileSync28(scriptPath, desired, { mode: 493 });
|
|
47277
47403
|
chmodSync11(scriptPath, 493);
|
|
@@ -47281,8 +47407,8 @@ function installUpdatePromptHook(agentDir) {
|
|
|
47281
47407
|
chmodSync11(scriptPath, 493);
|
|
47282
47408
|
} catch {}
|
|
47283
47409
|
}
|
|
47284
|
-
const settingsPath =
|
|
47285
|
-
if (!
|
|
47410
|
+
const settingsPath = join82(agentDir, ".claude", "settings.json");
|
|
47411
|
+
if (!existsSync82(settingsPath)) {
|
|
47286
47412
|
return { scriptPath, settingsPath, installed };
|
|
47287
47413
|
}
|
|
47288
47414
|
const raw = readFileSync70(settingsPath, "utf-8");
|
|
@@ -47710,7 +47836,7 @@ __export(exports_voice_sidecar_token, {
|
|
|
47710
47836
|
VOICE_SIDECAR_TOKEN_ENV: () => VOICE_SIDECAR_TOKEN_ENV
|
|
47711
47837
|
});
|
|
47712
47838
|
import { randomBytes as randomBytes14 } from "node:crypto";
|
|
47713
|
-
import { chmodSync as chmodSync12, chownSync as chownSync7, existsSync as
|
|
47839
|
+
import { chmodSync as chmodSync12, chownSync as chownSync7, existsSync as existsSync84, mkdirSync as mkdirSync46, readFileSync as readFileSync71, rmSync as rmSync17, writeFileSync as writeFileSync29 } from "node:fs";
|
|
47714
47840
|
import { dirname as dirname28 } from "node:path";
|
|
47715
47841
|
async function defaultResolveOrSeedToken(home2, writeErr) {
|
|
47716
47842
|
const [{ getViaBrokerStructured: getViaBrokerStructured2, putViaBroker: putViaBroker2 }, { resolveOperatorVaultPassphrase }] = await Promise.all([
|
|
@@ -47745,7 +47871,7 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
|
|
|
47745
47871
|
const envPath = composeEnvPath(composePath);
|
|
47746
47872
|
if (engine !== "local") {
|
|
47747
47873
|
try {
|
|
47748
|
-
if (
|
|
47874
|
+
if (existsSync84(envPath)) {
|
|
47749
47875
|
const body = readFileSync71(envPath, "utf-8");
|
|
47750
47876
|
if (body.includes(`${VOICE_SIDECAR_TOKEN_ENV}=`))
|
|
47751
47877
|
rmSync17(envPath);
|
|
@@ -47768,7 +47894,7 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
|
|
|
47768
47894
|
mkdirSync46(dirname28(envPath), { recursive: true });
|
|
47769
47895
|
let body = "";
|
|
47770
47896
|
try {
|
|
47771
|
-
if (
|
|
47897
|
+
if (existsSync84(envPath))
|
|
47772
47898
|
body = readFileSync71(envPath, "utf-8");
|
|
47773
47899
|
} catch {}
|
|
47774
47900
|
const line = `${VOICE_SIDECAR_TOKEN_ENV}=${token}`;
|
|
@@ -47828,11 +47954,11 @@ __export(exports_apply, {
|
|
|
47828
47954
|
DEFAULT_COMPOSE_PATH: () => DEFAULT_COMPOSE_PATH2,
|
|
47829
47955
|
COMPOSE_PROJECT: () => COMPOSE_PROJECT2
|
|
47830
47956
|
});
|
|
47831
|
-
import { accessSync as accessSync3, chownSync as chownSync8, constants as fsConstants6, copyFileSync as copyFileSync12, existsSync as
|
|
47957
|
+
import { accessSync as accessSync3, chownSync as chownSync8, constants as fsConstants6, copyFileSync as copyFileSync12, existsSync as existsSync85, mkdirSync as mkdirSync47, readFileSync as readFileSync72, readdirSync as readdirSync29, renameSync as renameSync18, statSync as statSync47, writeFileSync as writeFileSync30 } from "node:fs";
|
|
47832
47958
|
import { mkdir as mkdir2 } from "node:fs/promises";
|
|
47833
47959
|
import { spawnSync as childSpawnSync } from "node:child_process";
|
|
47834
47960
|
import readline from "node:readline";
|
|
47835
|
-
import { dirname as dirname29, join as
|
|
47961
|
+
import { dirname as dirname29, join as join84, resolve as resolve51 } from "node:path";
|
|
47836
47962
|
import { homedir as homedir49 } from "node:os";
|
|
47837
47963
|
import { execFileSync as execFileSync27 } from "node:child_process";
|
|
47838
47964
|
function effectiveLiteLLMEnabled(config, agentResolvedLitellm) {
|
|
@@ -47844,7 +47970,7 @@ async function resolveOperatorVaultPassphrase(home2) {
|
|
|
47844
47970
|
return envPass;
|
|
47845
47971
|
try {
|
|
47846
47972
|
const { readAutoUnlockFile: readAutoUnlockFile2 } = await Promise.resolve().then(() => (init_auto_unlock(), exports_auto_unlock));
|
|
47847
|
-
const blobPath =
|
|
47973
|
+
const blobPath = join84(home2, ".switchroom", "vault-auto-unlock");
|
|
47848
47974
|
const pass = readAutoUnlockFile2(blobPath);
|
|
47849
47975
|
return pass && pass.length > 0 ? pass : null;
|
|
47850
47976
|
} catch {
|
|
@@ -47932,7 +48058,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
47932
48058
|
const oauthAccount = config.auth?.active;
|
|
47933
48059
|
let pendingConfigEdits = false;
|
|
47934
48060
|
let configText = null;
|
|
47935
|
-
if (switchroomConfigPath &&
|
|
48061
|
+
if (switchroomConfigPath && existsSync85(switchroomConfigPath)) {
|
|
47936
48062
|
try {
|
|
47937
48063
|
configText = readFileSync72(switchroomConfigPath, "utf-8");
|
|
47938
48064
|
} catch (err) {
|
|
@@ -48089,7 +48215,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
48089
48215
|
try {
|
|
48090
48216
|
let cfgMode = 420;
|
|
48091
48217
|
try {
|
|
48092
|
-
cfgMode =
|
|
48218
|
+
cfgMode = statSync47(switchroomConfigPath).mode & 511;
|
|
48093
48219
|
} catch {}
|
|
48094
48220
|
writeConfigFileSync(switchroomConfigPath, configText, cfgMode);
|
|
48095
48221
|
writeOut(source_default.gray(` ~ litellm: granted read-ACL on per-agent keys (updated ${switchroomConfigPath})
|
|
@@ -48157,10 +48283,10 @@ function resolveVaultBindMountDir(homeDir, ctx) {
|
|
|
48157
48283
|
if (isCustomPath && ctx.customVaultPath) {
|
|
48158
48284
|
return dirname29(ctx.customVaultPath);
|
|
48159
48285
|
}
|
|
48160
|
-
return
|
|
48286
|
+
return join84(homeDir, ".switchroom", "vault");
|
|
48161
48287
|
}
|
|
48162
48288
|
function inspectVaultBindMountDir(vaultDir) {
|
|
48163
|
-
if (!
|
|
48289
|
+
if (!existsSync85(vaultDir))
|
|
48164
48290
|
return { kind: "missing" };
|
|
48165
48291
|
const entries = readdirSync29(vaultDir);
|
|
48166
48292
|
const unknown = [];
|
|
@@ -48188,43 +48314,43 @@ function hasVaultRefs(value) {
|
|
|
48188
48314
|
async function ensureHostMountSources(config) {
|
|
48189
48315
|
const home2 = resolveHostHomeForCompose();
|
|
48190
48316
|
const dirs = [
|
|
48191
|
-
|
|
48192
|
-
|
|
48193
|
-
|
|
48194
|
-
|
|
48195
|
-
|
|
48317
|
+
join84(home2, ".switchroom", "approvals"),
|
|
48318
|
+
join84(home2, ".switchroom", "scheduler"),
|
|
48319
|
+
join84(home2, ".switchroom", "logs"),
|
|
48320
|
+
join84(home2, ".switchroom", "compose"),
|
|
48321
|
+
join84(home2, ".switchroom", "broker-operator")
|
|
48196
48322
|
];
|
|
48197
48323
|
for (const name of Object.keys(config.agents)) {
|
|
48198
|
-
dirs.push(
|
|
48199
|
-
dirs.push(
|
|
48200
|
-
dirs.push(
|
|
48201
|
-
dirs.push(
|
|
48202
|
-
if (
|
|
48203
|
-
dirs.push(
|
|
48324
|
+
dirs.push(join84(home2, ".switchroom", "agents", name));
|
|
48325
|
+
dirs.push(join84(home2, ".switchroom", "logs", name));
|
|
48326
|
+
dirs.push(join84(home2, ".claude", "projects", name));
|
|
48327
|
+
dirs.push(join84(home2, ".switchroom", "audit", name));
|
|
48328
|
+
if (existsSync85(join84(home2, ".switchroom-config"))) {
|
|
48329
|
+
dirs.push(join84(home2, ".switchroom-config", "agents", name, "personal-skills"));
|
|
48204
48330
|
}
|
|
48205
48331
|
}
|
|
48206
48332
|
for (const dir of dirs) {
|
|
48207
48333
|
await mkdir2(dir, { recursive: true });
|
|
48208
48334
|
}
|
|
48209
|
-
const autoUnlockPath =
|
|
48210
|
-
if (!
|
|
48335
|
+
const autoUnlockPath = join84(home2, ".switchroom", "vault-auto-unlock");
|
|
48336
|
+
if (!existsSync85(autoUnlockPath)) {
|
|
48211
48337
|
writeFileSync30(autoUnlockPath, "", { mode: 384 });
|
|
48212
48338
|
}
|
|
48213
|
-
const auditLogPath =
|
|
48214
|
-
if (!
|
|
48339
|
+
const auditLogPath = join84(home2, ".switchroom", "vault-audit.log");
|
|
48340
|
+
if (!existsSync85(auditLogPath)) {
|
|
48215
48341
|
writeFileSync30(auditLogPath, "", { mode: 420 });
|
|
48216
48342
|
}
|
|
48217
|
-
const grantsDbPath =
|
|
48218
|
-
if (!
|
|
48343
|
+
const grantsDbPath = join84(home2, ".switchroom", "vault-grants.db");
|
|
48344
|
+
if (!existsSync85(grantsDbPath)) {
|
|
48219
48345
|
writeFileSync30(grantsDbPath, "", { mode: 384 });
|
|
48220
48346
|
}
|
|
48221
|
-
const hostdAuditLogPath =
|
|
48222
|
-
if (!
|
|
48347
|
+
const hostdAuditLogPath = join84(home2, ".switchroom", "host-control-audit.log");
|
|
48348
|
+
if (!existsSync85(hostdAuditLogPath)) {
|
|
48223
48349
|
writeFileSync30(hostdAuditLogPath, "", { mode: 420 });
|
|
48224
48350
|
}
|
|
48225
48351
|
for (const name of Object.keys(config.agents)) {
|
|
48226
|
-
const tokenPath =
|
|
48227
|
-
if (!
|
|
48352
|
+
const tokenPath = join84(home2, ".switchroom", "agents", name, ".vault-token");
|
|
48353
|
+
if (!existsSync85(tokenPath)) {
|
|
48228
48354
|
writeFileSync30(tokenPath, "", { mode: 384 });
|
|
48229
48355
|
}
|
|
48230
48356
|
try {
|
|
@@ -48232,16 +48358,16 @@ async function ensureHostMountSources(config) {
|
|
|
48232
48358
|
chownSync8(tokenPath, uid, uid);
|
|
48233
48359
|
} catch {}
|
|
48234
48360
|
}
|
|
48235
|
-
const fleetDir =
|
|
48361
|
+
const fleetDir = join84(home2, ".switchroom", "fleet");
|
|
48236
48362
|
await mkdir2(fleetDir, { recursive: true });
|
|
48237
|
-
const invariantsPath =
|
|
48363
|
+
const invariantsPath = join84(fleetDir, "switchroom-invariants.md");
|
|
48238
48364
|
const invariantsCanonical = renderFleetInvariants();
|
|
48239
|
-
const invariantsCurrent =
|
|
48365
|
+
const invariantsCurrent = existsSync85(invariantsPath) ? readFileSync72(invariantsPath, "utf-8") : null;
|
|
48240
48366
|
if (invariantsCurrent !== invariantsCanonical) {
|
|
48241
48367
|
writeFileSync30(invariantsPath, invariantsCanonical, { mode: 420 });
|
|
48242
48368
|
}
|
|
48243
|
-
const fleetClaudePath =
|
|
48244
|
-
if (!
|
|
48369
|
+
const fleetClaudePath = join84(fleetDir, "CLAUDE.md");
|
|
48370
|
+
if (!existsSync85(fleetClaudePath)) {
|
|
48245
48371
|
writeFileSync30(fleetClaudePath, renderFleetDefaultsClaudeMd(), {
|
|
48246
48372
|
mode: 420
|
|
48247
48373
|
});
|
|
@@ -48271,7 +48397,7 @@ function isInAgentContainer(vaultPresent, composeV2Present, env2 = process.env)
|
|
|
48271
48397
|
function runApplyPreflight(config, opts = {}) {
|
|
48272
48398
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
48273
48399
|
const detect = opts.detectComposeV2 ?? detectComposeV2;
|
|
48274
|
-
const vaultMissing = hasVaultRefs(config) && !
|
|
48400
|
+
const vaultMissing = hasVaultRefs(config) && !existsSync85(vaultPath);
|
|
48275
48401
|
const composeErr = detect();
|
|
48276
48402
|
if ((vaultMissing || composeErr) && isInAgentContainer(!vaultMissing, composeErr === null)) {
|
|
48277
48403
|
throw new Error(IN_AGENT_CONTAINER_APPLY_MSG);
|
|
@@ -48285,7 +48411,7 @@ function runApplyPreflight(config, opts = {}) {
|
|
|
48285
48411
|
detectAndReportLegacyGdriveSlots(vaultPath);
|
|
48286
48412
|
}
|
|
48287
48413
|
function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
48288
|
-
if (!
|
|
48414
|
+
if (!existsSync85(vaultPath))
|
|
48289
48415
|
return;
|
|
48290
48416
|
const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
48291
48417
|
if (!passphrase)
|
|
@@ -48326,8 +48452,8 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
|
48326
48452
|
}
|
|
48327
48453
|
function writeInstallTypeCache(homeDir = homedir49()) {
|
|
48328
48454
|
const ctx = detectInstallType();
|
|
48329
|
-
const dir =
|
|
48330
|
-
const out =
|
|
48455
|
+
const dir = join84(homeDir, ".switchroom");
|
|
48456
|
+
const out = join84(dir, "install-type.json");
|
|
48331
48457
|
const tmp = `${out}.tmp`;
|
|
48332
48458
|
mkdirSync47(dir, { recursive: true });
|
|
48333
48459
|
const payload = {
|
|
@@ -48394,17 +48520,17 @@ Applying switchroom config...
|
|
|
48394
48520
|
writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
|
|
48395
48521
|
`));
|
|
48396
48522
|
try {
|
|
48397
|
-
installUpdatePromptHook(
|
|
48523
|
+
installUpdatePromptHook(join84(agentsDir, name));
|
|
48398
48524
|
} catch (hookErr) {
|
|
48399
48525
|
writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
|
|
48400
48526
|
`));
|
|
48401
48527
|
}
|
|
48402
|
-
await refreshAgentConnectionHealth(config, name,
|
|
48528
|
+
await refreshAgentConnectionHealth(config, name, join84(agentsDir, name), {
|
|
48403
48529
|
vaultAclReader: connHealthVaultAclReader
|
|
48404
48530
|
});
|
|
48405
48531
|
try {
|
|
48406
48532
|
const uid = allocateAgentUid(name);
|
|
48407
|
-
alignAgentUid(name,
|
|
48533
|
+
alignAgentUid(name, join84(agentsDir, name), uid, {
|
|
48408
48534
|
confirm: !options.nonInteractive,
|
|
48409
48535
|
writeOut
|
|
48410
48536
|
});
|
|
@@ -48449,7 +48575,7 @@ Applying switchroom config...
|
|
|
48449
48575
|
for (const name of agentNames) {
|
|
48450
48576
|
try {
|
|
48451
48577
|
const uid = allocateAgentUid(name);
|
|
48452
|
-
alignAgentUid(name,
|
|
48578
|
+
alignAgentUid(name, join84(agentsDir, name), uid, {
|
|
48453
48579
|
confirm: !options.nonInteractive,
|
|
48454
48580
|
writeOut
|
|
48455
48581
|
});
|
|
@@ -48787,7 +48913,7 @@ function copyExampleConfig2(name) {
|
|
|
48787
48913
|
throw new Error(`Invalid example name: ${name} (must match /^[a-z0-9_-]+$/)`);
|
|
48788
48914
|
}
|
|
48789
48915
|
const dest = resolve51(process.cwd(), "switchroom.yaml");
|
|
48790
|
-
if (
|
|
48916
|
+
if (existsSync85(dest)) {
|
|
48791
48917
|
console.error(source_default.yellow("switchroom.yaml already exists \u2014 skipping example copy"));
|
|
48792
48918
|
return;
|
|
48793
48919
|
}
|
|
@@ -48798,7 +48924,7 @@ function copyExampleConfig2(name) {
|
|
|
48798
48924
|
return;
|
|
48799
48925
|
}
|
|
48800
48926
|
const exampleFile = resolve51(import.meta.dirname, `../../examples/${name}.yaml`);
|
|
48801
|
-
if (!
|
|
48927
|
+
if (!existsSync85(exampleFile)) {
|
|
48802
48928
|
throw new Error(`Example config not found: ${name}.yaml (available: ${Object.keys(EMBEDDED_EXAMPLES).join(", ")})`);
|
|
48803
48929
|
}
|
|
48804
48930
|
copyFileSync12(exampleFile, dest);
|
|
@@ -48809,8 +48935,8 @@ function findUnwritableAgentDirs(config, opts) {
|
|
|
48809
48935
|
const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
|
|
48810
48936
|
const unwritable = [];
|
|
48811
48937
|
for (const name of targets) {
|
|
48812
|
-
const startSh =
|
|
48813
|
-
if (!
|
|
48938
|
+
const startSh = join84(agentsDir, name, "start.sh");
|
|
48939
|
+
if (!existsSync85(startSh))
|
|
48814
48940
|
continue;
|
|
48815
48941
|
try {
|
|
48816
48942
|
accessSync3(startSh, fsConstants6.W_OK);
|
|
@@ -49013,7 +49139,7 @@ var init_apply = __esm(() => {
|
|
|
49013
49139
|
switchroom: switchroom_default,
|
|
49014
49140
|
minimal: minimal_default
|
|
49015
49141
|
};
|
|
49016
|
-
DEFAULT_COMPOSE_PATH2 =
|
|
49142
|
+
DEFAULT_COMPOSE_PATH2 = join84(homedir49(), ".switchroom", "compose", "docker-compose.yml");
|
|
49017
49143
|
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.
|
|
49018
49144
|
` + "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.";
|
|
49019
49145
|
SELF_ELEVATE_PRESERVED_ENV = [
|
|
@@ -62462,7 +62588,7 @@ __export(exports_server2, {
|
|
|
62462
62588
|
TOOLS: () => TOOLS2
|
|
62463
62589
|
});
|
|
62464
62590
|
import { randomBytes as randomBytes16 } from "node:crypto";
|
|
62465
|
-
import { existsSync as
|
|
62591
|
+
import { existsSync as existsSync94, readFileSync as readFileSync81 } from "node:fs";
|
|
62466
62592
|
function selfSocketPath() {
|
|
62467
62593
|
return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
|
|
62468
62594
|
}
|
|
@@ -62489,7 +62615,7 @@ async function dispatchTool2(name, args) {
|
|
|
62489
62615
|
return errorText2("hostd MCP: SWITCHROOM_AGENT_NAME env var is not set \u2014 cannot " + "determine which per-agent socket to talk to.");
|
|
62490
62616
|
}
|
|
62491
62617
|
const sockPath = selfSocketPath();
|
|
62492
|
-
if (!
|
|
62618
|
+
if (!existsSync94(sockPath)) {
|
|
62493
62619
|
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.`);
|
|
62494
62620
|
}
|
|
62495
62621
|
let req;
|
|
@@ -62740,13 +62866,13 @@ function resolveAuditLogPath() {
|
|
|
62740
62866
|
if (process.env.HOSTD_AUDIT_LOG_PATH)
|
|
62741
62867
|
return process.env.HOSTD_AUDIT_LOG_PATH;
|
|
62742
62868
|
const bindMounted = "/host-home/.switchroom/host-control-audit.log";
|
|
62743
|
-
if (
|
|
62869
|
+
if (existsSync94(bindMounted))
|
|
62744
62870
|
return bindMounted;
|
|
62745
62871
|
return defaultAuditLogPath2();
|
|
62746
62872
|
}
|
|
62747
62873
|
function getLastUpdateApplyStatus() {
|
|
62748
62874
|
const path8 = resolveAuditLogPath();
|
|
62749
|
-
if (!
|
|
62875
|
+
if (!existsSync94(path8)) {
|
|
62750
62876
|
return errorText2(`get_status: audit log not found at ${path8}. No update_apply has run yet?`);
|
|
62751
62877
|
}
|
|
62752
62878
|
let raw;
|
|
@@ -63428,7 +63554,7 @@ __export(exports_scan, {
|
|
|
63428
63554
|
import {
|
|
63429
63555
|
readFileSync as readFileSync83,
|
|
63430
63556
|
readdirSync as readdirSync37,
|
|
63431
|
-
existsSync as
|
|
63557
|
+
existsSync as existsSync97,
|
|
63432
63558
|
mkdirSync as mkdirSync54,
|
|
63433
63559
|
writeFileSync as writeFileSync36
|
|
63434
63560
|
} from "node:fs";
|
|
@@ -63439,7 +63565,7 @@ function resolveSwitchroomBase(home2 = process.env.SWITCHROOM_HOME ?? process.en
|
|
|
63439
63565
|
}
|
|
63440
63566
|
function listAgents(base) {
|
|
63441
63567
|
const dir = resolve56(base, "agents");
|
|
63442
|
-
if (!
|
|
63568
|
+
if (!existsSync97(dir))
|
|
63443
63569
|
return [];
|
|
63444
63570
|
try {
|
|
63445
63571
|
return readdirSync37(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
|
|
@@ -63468,7 +63594,7 @@ function runScan(opts = {}) {
|
|
|
63468
63594
|
let gwText = "";
|
|
63469
63595
|
let sawArtifact = false;
|
|
63470
63596
|
try {
|
|
63471
|
-
if (
|
|
63597
|
+
if (existsSync97(turnsPath)) {
|
|
63472
63598
|
turnsText = readFileSync83(turnsPath, "utf-8");
|
|
63473
63599
|
sawArtifact = true;
|
|
63474
63600
|
}
|
|
@@ -63476,7 +63602,7 @@ function runScan(opts = {}) {
|
|
|
63476
63602
|
log(`fleet-health: WARN skipping ${agent} turns.jsonl unreadable: ${String(e)}`);
|
|
63477
63603
|
}
|
|
63478
63604
|
try {
|
|
63479
|
-
if (
|
|
63605
|
+
if (existsSync97(gwPath)) {
|
|
63480
63606
|
gwText = readFileSync83(gwPath, "utf-8");
|
|
63481
63607
|
sawArtifact = true;
|
|
63482
63608
|
}
|
|
@@ -63515,7 +63641,7 @@ function runScan(opts = {}) {
|
|
|
63515
63641
|
function readLedgerIfPresent(base) {
|
|
63516
63642
|
const path8 = ledgerPathForBase(base);
|
|
63517
63643
|
try {
|
|
63518
|
-
if (!
|
|
63644
|
+
if (!existsSync97(path8))
|
|
63519
63645
|
return null;
|
|
63520
63646
|
return JSON.parse(readFileSync83(path8, "utf-8"));
|
|
63521
63647
|
} catch {
|
|
@@ -83249,9 +83375,9 @@ init_source();
|
|
|
83249
83375
|
init_loader();
|
|
83250
83376
|
init_lifecycle();
|
|
83251
83377
|
init_compose_env();
|
|
83252
|
-
import { cpSync as cpSync2, existsSync as
|
|
83378
|
+
import { cpSync as cpSync2, existsSync as existsSync66, mkdirSync as mkdirSync34, readFileSync as readFileSync59, realpathSync as realpathSync6, rmSync as rmSync12, statSync as statSync40, chownSync as chownSync5 } from "node:fs";
|
|
83253
83379
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
83254
|
-
import { join as
|
|
83380
|
+
import { join as join67, dirname as dirname20, resolve as resolve39 } from "node:path";
|
|
83255
83381
|
import { homedir as homedir40 } from "node:os";
|
|
83256
83382
|
|
|
83257
83383
|
// src/cli/release-yaml.ts
|
|
@@ -83277,7 +83403,7 @@ init_operator_uid();
|
|
|
83277
83403
|
init_atomic();
|
|
83278
83404
|
|
|
83279
83405
|
// src/cli/preflight-mounts.ts
|
|
83280
|
-
import { statSync as
|
|
83406
|
+
import { statSync as statSync39 } from "node:fs";
|
|
83281
83407
|
function parseHostBindSources(composeText) {
|
|
83282
83408
|
const out = [];
|
|
83283
83409
|
const FILE_HINT = /\.(ya?ml|db|log|toml|json|token|id)$|\/\.vault-token$|machine-id$|localtime$|vault-auto-unlock$|\/webkite$/;
|
|
@@ -83304,7 +83430,7 @@ function parseHostBindSources(composeText) {
|
|
|
83304
83430
|
return out;
|
|
83305
83431
|
}
|
|
83306
83432
|
function validateBindSources(composeText, deps = {}) {
|
|
83307
|
-
const stat = deps.stat ?? ((p) =>
|
|
83433
|
+
const stat = deps.stat ?? ((p) => statSync39(p));
|
|
83308
83434
|
const sources = parseHostBindSources(composeText);
|
|
83309
83435
|
const issues = [];
|
|
83310
83436
|
const seen = new Set;
|
|
@@ -83349,7 +83475,7 @@ function defaultPersistPin(configPath) {
|
|
|
83349
83475
|
const after = setReleasePinInConfig(before, pin);
|
|
83350
83476
|
if (after === before)
|
|
83351
83477
|
return;
|
|
83352
|
-
writeConfigFileSync(path4, after,
|
|
83478
|
+
writeConfigFileSync(path4, after, statSync40(path4).mode & 511);
|
|
83353
83479
|
try {
|
|
83354
83480
|
if (typeof process.geteuid === "function" && process.geteuid() === 0) {
|
|
83355
83481
|
const uid = resolveOperatorUid();
|
|
@@ -83359,13 +83485,13 @@ function defaultPersistPin(configPath) {
|
|
|
83359
83485
|
} catch {}
|
|
83360
83486
|
};
|
|
83361
83487
|
}
|
|
83362
|
-
var DEFAULT_COMPOSE_PATH =
|
|
83488
|
+
var DEFAULT_COMPOSE_PATH = join67(homedir40(), ".switchroom", "compose", "docker-compose.yml");
|
|
83363
83489
|
function runningFromSwitchroomCheckout(scriptPath) {
|
|
83364
83490
|
let dir = dirname20(scriptPath);
|
|
83365
83491
|
for (let i = 0;i < 12; i++) {
|
|
83366
|
-
if (
|
|
83492
|
+
if (existsSync66(join67(dir, ".git"))) {
|
|
83367
83493
|
try {
|
|
83368
|
-
const pkg = JSON.parse(readFileSync59(
|
|
83494
|
+
const pkg = JSON.parse(readFileSync59(join67(dir, "package.json"), "utf-8"));
|
|
83369
83495
|
if (pkg.name === "switchroom")
|
|
83370
83496
|
return true;
|
|
83371
83497
|
} catch {}
|
|
@@ -83450,7 +83576,7 @@ function planUpdate(opts) {
|
|
|
83450
83576
|
steps.push({
|
|
83451
83577
|
name: "pull-images",
|
|
83452
83578
|
description: "Pull broker / kernel / agent images from GHCR",
|
|
83453
|
-
skipReason: opts.skipImages ? "--skip-images flag set" : !
|
|
83579
|
+
skipReason: opts.skipImages ? "--skip-images flag set" : !existsSync66(composePath) ? `compose file not found at ${composePath} (run \`switchroom apply --compose-only\` first)` : undefined,
|
|
83454
83580
|
run: () => {
|
|
83455
83581
|
const r = runner("docker", [
|
|
83456
83582
|
"compose",
|
|
@@ -83576,14 +83702,14 @@ function planUpdate(opts) {
|
|
|
83576
83702
|
return;
|
|
83577
83703
|
}
|
|
83578
83704
|
const source = resolve39(import.meta.dirname, "../../skills");
|
|
83579
|
-
const dest =
|
|
83580
|
-
if (!
|
|
83705
|
+
const dest = join67(homedir40(), ".switchroom", "skills", "_bundled");
|
|
83706
|
+
if (!existsSync66(source)) {
|
|
83581
83707
|
process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
|
|
83582
83708
|
`);
|
|
83583
83709
|
return;
|
|
83584
83710
|
}
|
|
83585
83711
|
try {
|
|
83586
|
-
if (
|
|
83712
|
+
if (existsSync66(dest)) {
|
|
83587
83713
|
rmSync12(dest, { recursive: true, force: true });
|
|
83588
83714
|
}
|
|
83589
83715
|
mkdirSync34(dirname20(dest), { recursive: true });
|
|
@@ -83697,12 +83823,12 @@ function defaultStatusProbe(composePath) {
|
|
|
83697
83823
|
} catch {}
|
|
83698
83824
|
if (scriptPath) {
|
|
83699
83825
|
try {
|
|
83700
|
-
cliBuiltAt = new Date(
|
|
83826
|
+
cliBuiltAt = new Date(statSync40(scriptPath).mtimeMs).toISOString();
|
|
83701
83827
|
} catch {}
|
|
83702
83828
|
let dir = dirname20(scriptPath);
|
|
83703
83829
|
for (let i = 0;i < 8; i++) {
|
|
83704
|
-
const pkgPath =
|
|
83705
|
-
if (
|
|
83830
|
+
const pkgPath = join67(dir, "package.json");
|
|
83831
|
+
if (existsSync66(pkgPath)) {
|
|
83706
83832
|
try {
|
|
83707
83833
|
const pkg = JSON.parse(readFileSync59(pkgPath, "utf-8"));
|
|
83708
83834
|
if (typeof pkg.version === "string")
|
|
@@ -83725,7 +83851,7 @@ function defaultStatusProbe(composePath) {
|
|
|
83725
83851
|
warnings.push("could not resolve CLI version (no package.json found above the resolved script path)");
|
|
83726
83852
|
}
|
|
83727
83853
|
const services = [];
|
|
83728
|
-
if (!
|
|
83854
|
+
if (!existsSync66(composePath)) {
|
|
83729
83855
|
warnings.push(`compose file not found at ${composePath}; service status unknown`);
|
|
83730
83856
|
return { cliVersion, cliBuiltAt, services, warnings };
|
|
83731
83857
|
}
|
|
@@ -83921,7 +84047,7 @@ function registerUpdateCommand(program3) {
|
|
|
83921
84047
|
// src/cli/rollout.ts
|
|
83922
84048
|
init_helpers();
|
|
83923
84049
|
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
83924
|
-
import { readFileSync as readFileSync60, chownSync as chownSync6, statSync as
|
|
84050
|
+
import { readFileSync as readFileSync60, chownSync as chownSync6, statSync as statSync41 } from "node:fs";
|
|
83925
84051
|
import { homedir as homedir41 } from "node:os";
|
|
83926
84052
|
init_operator_uid();
|
|
83927
84053
|
init_atomic();
|
|
@@ -84325,7 +84451,7 @@ function registerRolloutCommand(program3) {
|
|
|
84325
84451
|
const after = setReleasePinInConfig(before, pin);
|
|
84326
84452
|
if (after === before)
|
|
84327
84453
|
return;
|
|
84328
|
-
writeConfigFileSync(configPath, after,
|
|
84454
|
+
writeConfigFileSync(configPath, after, statSync41(configPath).mode & 511);
|
|
84329
84455
|
try {
|
|
84330
84456
|
if (typeof process.geteuid === "function" && process.geteuid() === 0) {
|
|
84331
84457
|
const uid = resolveOperatorUid();
|
|
@@ -84399,8 +84525,8 @@ init_helpers();
|
|
|
84399
84525
|
init_lifecycle();
|
|
84400
84526
|
init_resolve_version();
|
|
84401
84527
|
import { execSync as execSync3 } from "node:child_process";
|
|
84402
|
-
import { existsSync as
|
|
84403
|
-
import { dirname as dirname21, join as
|
|
84528
|
+
import { existsSync as existsSync67, readFileSync as readFileSync61 } from "node:fs";
|
|
84529
|
+
import { dirname as dirname21, join as join68 } from "node:path";
|
|
84404
84530
|
function getClaudeCodeVersion() {
|
|
84405
84531
|
try {
|
|
84406
84532
|
const out = execSync3("claude --version 2>/dev/null", {
|
|
@@ -84450,11 +84576,11 @@ function formatUptime3(timestamp) {
|
|
|
84450
84576
|
function locateSwitchroomInstallDir() {
|
|
84451
84577
|
let dir = import.meta.dirname;
|
|
84452
84578
|
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
84453
|
-
const pkgPath =
|
|
84454
|
-
if (
|
|
84579
|
+
const pkgPath = join68(dir, "package.json");
|
|
84580
|
+
if (existsSync67(pkgPath)) {
|
|
84455
84581
|
try {
|
|
84456
84582
|
const pkg = JSON.parse(readFileSync61(pkgPath, "utf-8"));
|
|
84457
|
-
if (pkg.name === "switchroom" &&
|
|
84583
|
+
if (pkg.name === "switchroom" && existsSync67(join68(dir, ".git"))) {
|
|
84458
84584
|
return dir;
|
|
84459
84585
|
}
|
|
84460
84586
|
} catch {}
|
|
@@ -84632,18 +84758,18 @@ import { resolve as resolve41 } from "node:path";
|
|
|
84632
84758
|
|
|
84633
84759
|
// src/agents/session-retention.ts
|
|
84634
84760
|
import {
|
|
84635
|
-
existsSync as
|
|
84761
|
+
existsSync as existsSync68,
|
|
84636
84762
|
readdirSync as readdirSync23,
|
|
84637
|
-
statSync as
|
|
84763
|
+
statSync as statSync42,
|
|
84638
84764
|
unlinkSync as unlinkSync14
|
|
84639
84765
|
} from "node:fs";
|
|
84640
|
-
import { join as
|
|
84766
|
+
import { join as join69 } from "node:path";
|
|
84641
84767
|
var DEFAULT_SESSION_RETENTION_MAX_COUNT = 20;
|
|
84642
84768
|
var DEFAULT_SESSION_RETENTION_MAX_AGE_DAYS = 30;
|
|
84643
84769
|
var MIN_KEEP = 2;
|
|
84644
84770
|
function collectSessionJsonl(claudeConfigDir) {
|
|
84645
|
-
const projects =
|
|
84646
|
-
if (!
|
|
84771
|
+
const projects = join69(claudeConfigDir, "projects");
|
|
84772
|
+
if (!existsSync68(projects))
|
|
84647
84773
|
return [];
|
|
84648
84774
|
const found = [];
|
|
84649
84775
|
const walk2 = (dir) => {
|
|
@@ -84654,10 +84780,10 @@ function collectSessionJsonl(claudeConfigDir) {
|
|
|
84654
84780
|
return;
|
|
84655
84781
|
}
|
|
84656
84782
|
for (const name of entries) {
|
|
84657
|
-
const full =
|
|
84783
|
+
const full = join69(dir, name);
|
|
84658
84784
|
let st;
|
|
84659
84785
|
try {
|
|
84660
|
-
st =
|
|
84786
|
+
st = statSync42(full);
|
|
84661
84787
|
} catch {
|
|
84662
84788
|
continue;
|
|
84663
84789
|
}
|
|
@@ -84774,18 +84900,18 @@ function registerHandoffCommand(program3) {
|
|
|
84774
84900
|
// src/issues/store.ts
|
|
84775
84901
|
import {
|
|
84776
84902
|
closeSync as closeSync12,
|
|
84777
|
-
existsSync as
|
|
84903
|
+
existsSync as existsSync69,
|
|
84778
84904
|
mkdirSync as mkdirSync35,
|
|
84779
84905
|
openSync as openSync12,
|
|
84780
84906
|
readdirSync as readdirSync24,
|
|
84781
84907
|
readFileSync as readFileSync62,
|
|
84782
84908
|
renameSync as renameSync15,
|
|
84783
|
-
statSync as
|
|
84909
|
+
statSync as statSync43,
|
|
84784
84910
|
unlinkSync as unlinkSync15,
|
|
84785
84911
|
writeFileSync as writeFileSync19,
|
|
84786
84912
|
writeSync as writeSync9
|
|
84787
84913
|
} from "node:fs";
|
|
84788
|
-
import { join as
|
|
84914
|
+
import { join as join70 } from "node:path";
|
|
84789
84915
|
import { randomBytes as randomBytes12 } from "node:crypto";
|
|
84790
84916
|
import { execSync as execSync4 } from "node:child_process";
|
|
84791
84917
|
|
|
@@ -85216,8 +85342,8 @@ function redactedMarker(ruleId) {
|
|
|
85216
85342
|
var ISSUES_FILE = "issues.jsonl";
|
|
85217
85343
|
var ISSUES_LOCK = "issues.lock";
|
|
85218
85344
|
function readAll(stateDir) {
|
|
85219
|
-
const path4 =
|
|
85220
|
-
if (!
|
|
85345
|
+
const path4 = join70(stateDir, ISSUES_FILE);
|
|
85346
|
+
if (!existsSync69(path4))
|
|
85221
85347
|
return [];
|
|
85222
85348
|
let raw;
|
|
85223
85349
|
try {
|
|
@@ -85294,7 +85420,7 @@ function record(stateDir, input, nowFn = Date.now) {
|
|
|
85294
85420
|
});
|
|
85295
85421
|
}
|
|
85296
85422
|
function resolve42(stateDir, fingerprint, nowFn = Date.now) {
|
|
85297
|
-
if (!
|
|
85423
|
+
if (!existsSync69(join70(stateDir, ISSUES_FILE)))
|
|
85298
85424
|
return 0;
|
|
85299
85425
|
return withLock(stateDir, () => {
|
|
85300
85426
|
const all = readAll(stateDir);
|
|
@@ -85312,7 +85438,7 @@ function resolve42(stateDir, fingerprint, nowFn = Date.now) {
|
|
|
85312
85438
|
});
|
|
85313
85439
|
}
|
|
85314
85440
|
function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
85315
|
-
if (!
|
|
85441
|
+
if (!existsSync69(join70(stateDir, ISSUES_FILE)))
|
|
85316
85442
|
return 0;
|
|
85317
85443
|
return withLock(stateDir, () => {
|
|
85318
85444
|
const all = readAll(stateDir);
|
|
@@ -85330,7 +85456,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
|
85330
85456
|
});
|
|
85331
85457
|
}
|
|
85332
85458
|
function prune(stateDir, opts = {}) {
|
|
85333
|
-
if (!
|
|
85459
|
+
if (!existsSync69(join70(stateDir, ISSUES_FILE)))
|
|
85334
85460
|
return 0;
|
|
85335
85461
|
return withLock(stateDir, () => {
|
|
85336
85462
|
const all = readAll(stateDir);
|
|
@@ -85363,7 +85489,7 @@ function ensureDir(stateDir) {
|
|
|
85363
85489
|
mkdirSync35(stateDir, { recursive: true });
|
|
85364
85490
|
}
|
|
85365
85491
|
function writeAll(stateDir, events) {
|
|
85366
|
-
const path4 =
|
|
85492
|
+
const path4 = join70(stateDir, ISSUES_FILE);
|
|
85367
85493
|
sweepOrphanTmpFiles(stateDir);
|
|
85368
85494
|
const tmp = `${path4}.tmp-${process.pid}-${randomBytes12(4).toString("hex")}`;
|
|
85369
85495
|
const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
|
|
@@ -85385,9 +85511,9 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
85385
85511
|
for (const entry of entries) {
|
|
85386
85512
|
if (!entry.startsWith(TMP_PREFIX))
|
|
85387
85513
|
continue;
|
|
85388
|
-
const tmpPath =
|
|
85514
|
+
const tmpPath = join70(stateDir, entry);
|
|
85389
85515
|
try {
|
|
85390
|
-
const stat =
|
|
85516
|
+
const stat = statSync43(tmpPath);
|
|
85391
85517
|
if (stat.mtimeMs < cutoff) {
|
|
85392
85518
|
unlinkSync15(tmpPath);
|
|
85393
85519
|
}
|
|
@@ -85397,7 +85523,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
85397
85523
|
var LOCK_RETRY_MS = 25;
|
|
85398
85524
|
var LOCK_TIMEOUT_MS = 1e4;
|
|
85399
85525
|
function withLock(stateDir, fn) {
|
|
85400
|
-
const lockPath =
|
|
85526
|
+
const lockPath = join70(stateDir, ISSUES_LOCK);
|
|
85401
85527
|
const startedAt = Date.now();
|
|
85402
85528
|
let fd = null;
|
|
85403
85529
|
while (fd === null) {
|
|
@@ -85680,20 +85806,20 @@ function relTime(deltaMs) {
|
|
|
85680
85806
|
|
|
85681
85807
|
// src/cli/deps.ts
|
|
85682
85808
|
init_source();
|
|
85683
|
-
import { existsSync as
|
|
85809
|
+
import { existsSync as existsSync72 } from "node:fs";
|
|
85684
85810
|
import { homedir as homedir44 } from "node:os";
|
|
85685
|
-
import { join as
|
|
85811
|
+
import { join as join73, resolve as resolve43 } from "node:path";
|
|
85686
85812
|
|
|
85687
85813
|
// src/deps/python.ts
|
|
85688
85814
|
import { createHash as createHash13 } from "node:crypto";
|
|
85689
85815
|
import {
|
|
85690
|
-
existsSync as
|
|
85816
|
+
existsSync as existsSync70,
|
|
85691
85817
|
mkdirSync as mkdirSync36,
|
|
85692
85818
|
readFileSync as readFileSync63,
|
|
85693
85819
|
rmSync as rmSync13,
|
|
85694
85820
|
writeFileSync as writeFileSync20
|
|
85695
85821
|
} from "node:fs";
|
|
85696
|
-
import { dirname as dirname22, join as
|
|
85822
|
+
import { dirname as dirname22, join as join71 } from "node:path";
|
|
85697
85823
|
import { homedir as homedir42 } from "node:os";
|
|
85698
85824
|
import { execFileSync as execFileSync21 } from "node:child_process";
|
|
85699
85825
|
|
|
@@ -85706,7 +85832,7 @@ class PythonEnvError extends Error {
|
|
|
85706
85832
|
}
|
|
85707
85833
|
}
|
|
85708
85834
|
function defaultPythonCacheRoot() {
|
|
85709
|
-
return
|
|
85835
|
+
return join71(homedir42(), ".switchroom", "deps", "python");
|
|
85710
85836
|
}
|
|
85711
85837
|
function hashFile(path4) {
|
|
85712
85838
|
return createHash13("sha256").update(readFileSync63(path4)).digest("hex");
|
|
@@ -85715,16 +85841,16 @@ function ensurePythonEnv(opts) {
|
|
|
85715
85841
|
const { skillName, requirementsPath, force = false } = opts;
|
|
85716
85842
|
const cacheRoot = opts.cacheRoot ?? defaultPythonCacheRoot();
|
|
85717
85843
|
const hostPython = opts.pythonBin ?? "python3";
|
|
85718
|
-
if (!
|
|
85844
|
+
if (!existsSync70(requirementsPath)) {
|
|
85719
85845
|
throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
|
|
85720
85846
|
}
|
|
85721
|
-
const venvDir =
|
|
85722
|
-
const stampPath =
|
|
85723
|
-
const binDir =
|
|
85724
|
-
const pythonBin =
|
|
85725
|
-
const pipBin =
|
|
85847
|
+
const venvDir = join71(cacheRoot, skillName);
|
|
85848
|
+
const stampPath = join71(venvDir, ".requirements.sha256");
|
|
85849
|
+
const binDir = join71(venvDir, "bin");
|
|
85850
|
+
const pythonBin = join71(binDir, "python");
|
|
85851
|
+
const pipBin = join71(binDir, "pip");
|
|
85726
85852
|
const targetHash = hashFile(requirementsPath);
|
|
85727
|
-
if (!force &&
|
|
85853
|
+
if (!force && existsSync70(stampPath) && existsSync70(pythonBin)) {
|
|
85728
85854
|
const existingHash = readFileSync63(stampPath, "utf8").trim();
|
|
85729
85855
|
if (existingHash === targetHash) {
|
|
85730
85856
|
return {
|
|
@@ -85737,7 +85863,7 @@ function ensurePythonEnv(opts) {
|
|
|
85737
85863
|
};
|
|
85738
85864
|
}
|
|
85739
85865
|
}
|
|
85740
|
-
if (
|
|
85866
|
+
if (existsSync70(venvDir)) {
|
|
85741
85867
|
rmSync13(venvDir, { recursive: true, force: true });
|
|
85742
85868
|
}
|
|
85743
85869
|
mkdirSync36(dirname22(venvDir), { recursive: true });
|
|
@@ -85775,13 +85901,13 @@ function ensurePythonEnv(opts) {
|
|
|
85775
85901
|
import { createHash as createHash14 } from "node:crypto";
|
|
85776
85902
|
import {
|
|
85777
85903
|
copyFileSync as copyFileSync10,
|
|
85778
|
-
existsSync as
|
|
85904
|
+
existsSync as existsSync71,
|
|
85779
85905
|
mkdirSync as mkdirSync37,
|
|
85780
85906
|
readFileSync as readFileSync64,
|
|
85781
85907
|
rmSync as rmSync14,
|
|
85782
85908
|
writeFileSync as writeFileSync21
|
|
85783
85909
|
} from "node:fs";
|
|
85784
|
-
import { dirname as dirname23, join as
|
|
85910
|
+
import { dirname as dirname23, join as join72 } from "node:path";
|
|
85785
85911
|
import { homedir as homedir43 } from "node:os";
|
|
85786
85912
|
import { execFileSync as execFileSync22 } from "node:child_process";
|
|
85787
85913
|
|
|
@@ -85805,7 +85931,7 @@ var LOCKFILES_FOR = {
|
|
|
85805
85931
|
npm: ["package-lock.json"]
|
|
85806
85932
|
};
|
|
85807
85933
|
function defaultNodeCacheRoot() {
|
|
85808
|
-
return
|
|
85934
|
+
return join72(homedir43(), ".switchroom", "deps", "node");
|
|
85809
85935
|
}
|
|
85810
85936
|
function hashDepInputs(packageJsonPath) {
|
|
85811
85937
|
const sourceDir = dirname23(packageJsonPath);
|
|
@@ -85814,8 +85940,8 @@ function hashDepInputs(packageJsonPath) {
|
|
|
85814
85940
|
`);
|
|
85815
85941
|
hasher.update(readFileSync64(packageJsonPath));
|
|
85816
85942
|
for (const lockName of ALL_LOCKFILES) {
|
|
85817
|
-
const lockPath =
|
|
85818
|
-
if (
|
|
85943
|
+
const lockPath = join72(sourceDir, lockName);
|
|
85944
|
+
if (existsSync71(lockPath)) {
|
|
85819
85945
|
hasher.update(`
|
|
85820
85946
|
`);
|
|
85821
85947
|
hasher.update(lockName);
|
|
@@ -85830,16 +85956,16 @@ function ensureNodeEnv(opts) {
|
|
|
85830
85956
|
const { skillName, packageJsonPath, force = false } = opts;
|
|
85831
85957
|
const cacheRoot = opts.cacheRoot ?? defaultNodeCacheRoot();
|
|
85832
85958
|
const installer = opts.installer ?? "bun";
|
|
85833
|
-
if (!
|
|
85959
|
+
if (!existsSync71(packageJsonPath)) {
|
|
85834
85960
|
throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
|
|
85835
85961
|
}
|
|
85836
85962
|
const sourceDir = dirname23(packageJsonPath);
|
|
85837
|
-
const envDir =
|
|
85838
|
-
const stampPath =
|
|
85839
|
-
const nodeModulesDir =
|
|
85840
|
-
const binDir =
|
|
85963
|
+
const envDir = join72(cacheRoot, skillName);
|
|
85964
|
+
const stampPath = join72(envDir, ".package.sha256");
|
|
85965
|
+
const nodeModulesDir = join72(envDir, "node_modules");
|
|
85966
|
+
const binDir = join72(nodeModulesDir, ".bin");
|
|
85841
85967
|
const targetHash = hashDepInputs(packageJsonPath);
|
|
85842
|
-
if (!force &&
|
|
85968
|
+
if (!force && existsSync71(stampPath) && existsSync71(nodeModulesDir)) {
|
|
85843
85969
|
const existingHash = readFileSync64(stampPath, "utf8").trim();
|
|
85844
85970
|
if (existingHash === targetHash) {
|
|
85845
85971
|
return {
|
|
@@ -85851,16 +85977,16 @@ function ensureNodeEnv(opts) {
|
|
|
85851
85977
|
};
|
|
85852
85978
|
}
|
|
85853
85979
|
}
|
|
85854
|
-
if (
|
|
85980
|
+
if (existsSync71(envDir)) {
|
|
85855
85981
|
rmSync14(envDir, { recursive: true, force: true });
|
|
85856
85982
|
}
|
|
85857
85983
|
mkdirSync37(envDir, { recursive: true });
|
|
85858
|
-
copyFileSync10(packageJsonPath,
|
|
85984
|
+
copyFileSync10(packageJsonPath, join72(envDir, "package.json"));
|
|
85859
85985
|
let copiedLockfile = false;
|
|
85860
85986
|
for (const lockName of LOCKFILES_FOR[installer]) {
|
|
85861
|
-
const lockPath =
|
|
85862
|
-
if (
|
|
85863
|
-
copyFileSync10(lockPath,
|
|
85987
|
+
const lockPath = join72(sourceDir, lockName);
|
|
85988
|
+
if (existsSync71(lockPath)) {
|
|
85989
|
+
copyFileSync10(lockPath, join72(envDir, lockName));
|
|
85864
85990
|
copiedLockfile = true;
|
|
85865
85991
|
}
|
|
85866
85992
|
}
|
|
@@ -85895,22 +86021,22 @@ function registerDepsCommand(program3) {
|
|
|
85895
86021
|
const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
|
|
85896
86022
|
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) => {
|
|
85897
86023
|
const skillsRoot = builtinSkillsRoot();
|
|
85898
|
-
if (!
|
|
86024
|
+
if (!existsSync72(skillsRoot)) {
|
|
85899
86025
|
console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
|
|
85900
86026
|
process.exit(1);
|
|
85901
86027
|
}
|
|
85902
|
-
const skillDir =
|
|
85903
|
-
if (!
|
|
86028
|
+
const skillDir = join73(skillsRoot, skill);
|
|
86029
|
+
if (!existsSync72(skillDir)) {
|
|
85904
86030
|
console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
|
|
85905
86031
|
process.exit(1);
|
|
85906
86032
|
}
|
|
85907
|
-
const requirementsPath =
|
|
85908
|
-
const packageJsonPath =
|
|
85909
|
-
const wantPython = opts.python ?? (!opts.python && !opts.node &&
|
|
85910
|
-
const wantNode = opts.node ?? (!opts.python && !opts.node &&
|
|
86033
|
+
const requirementsPath = join73(skillDir, "requirements.txt");
|
|
86034
|
+
const packageJsonPath = join73(skillDir, "package.json");
|
|
86035
|
+
const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync72(requirementsPath));
|
|
86036
|
+
const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync72(packageJsonPath));
|
|
85911
86037
|
let did = 0;
|
|
85912
86038
|
if (wantPython) {
|
|
85913
|
-
if (!
|
|
86039
|
+
if (!existsSync72(requirementsPath)) {
|
|
85914
86040
|
console.error(source_default.red(`Skill "${skill}" has no requirements.txt at ${requirementsPath}`));
|
|
85915
86041
|
process.exit(1);
|
|
85916
86042
|
}
|
|
@@ -85934,7 +86060,7 @@ function registerDepsCommand(program3) {
|
|
|
85934
86060
|
}
|
|
85935
86061
|
}
|
|
85936
86062
|
if (wantNode) {
|
|
85937
|
-
if (!
|
|
86063
|
+
if (!existsSync72(packageJsonPath)) {
|
|
85938
86064
|
console.error(source_default.red(`Skill "${skill}" has no package.json at ${packageJsonPath}`));
|
|
85939
86065
|
process.exit(1);
|
|
85940
86066
|
}
|
|
@@ -85967,7 +86093,7 @@ function registerDepsCommand(program3) {
|
|
|
85967
86093
|
// src/cli/workspace.ts
|
|
85968
86094
|
init_helpers();
|
|
85969
86095
|
init_loader();
|
|
85970
|
-
import { existsSync as
|
|
86096
|
+
import { existsSync as existsSync73 } from "node:fs";
|
|
85971
86097
|
import { resolve as resolve44, sep as sep3 } from "node:path";
|
|
85972
86098
|
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
85973
86099
|
|
|
@@ -86744,7 +86870,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
86744
86870
|
if (!dir)
|
|
86745
86871
|
return;
|
|
86746
86872
|
const gitDir = resolve44(dir, ".git");
|
|
86747
|
-
if (!
|
|
86873
|
+
if (!existsSync73(gitDir)) {
|
|
86748
86874
|
process.stdout.write(`Workspace is not a git repository. Re-run \`switchroom agent create ${agentName}\` ` + `or manually \`git init\` in ${dir} to enable versioning.
|
|
86749
86875
|
`);
|
|
86750
86876
|
return;
|
|
@@ -86798,7 +86924,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
86798
86924
|
if (!dir)
|
|
86799
86925
|
return;
|
|
86800
86926
|
const gitDir = resolve44(dir, ".git");
|
|
86801
|
-
if (!
|
|
86927
|
+
if (!existsSync73(gitDir)) {
|
|
86802
86928
|
process.stdout.write(`Workspace is not a git repository.
|
|
86803
86929
|
`);
|
|
86804
86930
|
return;
|
|
@@ -86823,7 +86949,7 @@ function resolveAgentWorkspaceDirOrExit(program3, agentName) {
|
|
|
86823
86949
|
const agentsDir = resolveAgentsDir(config);
|
|
86824
86950
|
const agentDir = resolve44(agentsDir, agentName);
|
|
86825
86951
|
const dir = resolveAgentWorkspaceDir(agentDir);
|
|
86826
|
-
if (!
|
|
86952
|
+
if (!existsSync73(dir)) {
|
|
86827
86953
|
process.stderr.write(`workspace: ${dir} does not exist yet. Run \`switchroom setup\` or \`switchroom agent scaffold ${agentName}\` to seed it.
|
|
86828
86954
|
`);
|
|
86829
86955
|
return;
|
|
@@ -86859,8 +86985,8 @@ function safeParseInt(value, fallback) {
|
|
|
86859
86985
|
init_helpers();
|
|
86860
86986
|
init_loader();
|
|
86861
86987
|
init_merge();
|
|
86862
|
-
import { copyFileSync as copyFileSync11, existsSync as
|
|
86863
|
-
import { join as
|
|
86988
|
+
import { copyFileSync as copyFileSync11, existsSync as existsSync74, readFileSync as readFileSync65, writeFileSync as writeFileSync22 } from "node:fs";
|
|
86989
|
+
import { join as join74, resolve as resolve45 } from "node:path";
|
|
86864
86990
|
init_scaffold();
|
|
86865
86991
|
init_profiles();
|
|
86866
86992
|
init_schema();
|
|
@@ -86877,7 +87003,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
86877
87003
|
const agentsDir = resolveAgentsDir(config);
|
|
86878
87004
|
const agentDir = resolve45(agentsDir, agentName);
|
|
86879
87005
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
86880
|
-
if (!
|
|
87006
|
+
if (!existsSync74(workspaceDir)) {
|
|
86881
87007
|
console.error(`soul: ${workspaceDir} does not exist yet. Run \`switchroom setup\` ` + `or \`switchroom agent scaffold ${agentName}\` to seed it.`);
|
|
86882
87008
|
process.exit(1);
|
|
86883
87009
|
}
|
|
@@ -86886,7 +87012,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
86886
87012
|
profileName,
|
|
86887
87013
|
profilePath,
|
|
86888
87014
|
workspaceDir,
|
|
86889
|
-
soulPath:
|
|
87015
|
+
soulPath: join74(workspaceDir, "SOUL.md"),
|
|
86890
87016
|
soul: merged.soul
|
|
86891
87017
|
};
|
|
86892
87018
|
}
|
|
@@ -86903,7 +87029,7 @@ function registerSoulCommand(program3) {
|
|
|
86903
87029
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
86904
87030
|
if (!t)
|
|
86905
87031
|
return;
|
|
86906
|
-
if (!
|
|
87032
|
+
if (!existsSync74(t.soulPath)) {
|
|
86907
87033
|
console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
|
|
86908
87034
|
process.exit(1);
|
|
86909
87035
|
}
|
|
@@ -86918,7 +87044,7 @@ function registerSoulCommand(program3) {
|
|
|
86918
87044
|
console.error(`soul: profile "${t.profileName}" ships no SOUL.md.hbs \u2014 ` + `nothing to re-seed from.`);
|
|
86919
87045
|
process.exit(1);
|
|
86920
87046
|
}
|
|
86921
|
-
const exists =
|
|
87047
|
+
const exists = existsSync74(t.soulPath);
|
|
86922
87048
|
if (exists && !opts.yes) {
|
|
86923
87049
|
if (!isInteractive()) {
|
|
86924
87050
|
console.error(`soul: ${t.soulPath} already exists. Re-run with --yes to ` + `replace it (the current file is backed up to SOUL.md.bak).`);
|
|
@@ -86933,7 +87059,7 @@ function registerSoulCommand(program3) {
|
|
|
86933
87059
|
let backupPath;
|
|
86934
87060
|
if (exists) {
|
|
86935
87061
|
backupPath = `${t.soulPath}.bak`;
|
|
86936
|
-
if (
|
|
87062
|
+
if (existsSync74(backupPath)) {
|
|
86937
87063
|
backupPath = `${t.soulPath}.bak.${Date.now()}`;
|
|
86938
87064
|
}
|
|
86939
87065
|
copyFileSync11(t.soulPath, backupPath);
|
|
@@ -86952,8 +87078,8 @@ function registerSoulCommand(program3) {
|
|
|
86952
87078
|
// src/cli/debug.ts
|
|
86953
87079
|
init_helpers();
|
|
86954
87080
|
init_loader();
|
|
86955
|
-
import { existsSync as
|
|
86956
|
-
import { resolve as resolve46, join as
|
|
87081
|
+
import { existsSync as existsSync75, readFileSync as readFileSync66, readdirSync as readdirSync25, statSync as statSync44 } from "node:fs";
|
|
87082
|
+
import { resolve as resolve46, join as join75 } from "node:path";
|
|
86957
87083
|
import { createHash as createHash15 } from "node:crypto";
|
|
86958
87084
|
init_merge();
|
|
86959
87085
|
init_hindsight2();
|
|
@@ -86964,8 +87090,8 @@ function estimateTokens(bytes) {
|
|
|
86964
87090
|
return Math.round(bytes / 3.7);
|
|
86965
87091
|
}
|
|
86966
87092
|
function readMcpServerNames(agentDir) {
|
|
86967
|
-
const mcpPath =
|
|
86968
|
-
if (!
|
|
87093
|
+
const mcpPath = join75(agentDir, ".mcp.json");
|
|
87094
|
+
if (!existsSync75(mcpPath))
|
|
86969
87095
|
return [];
|
|
86970
87096
|
try {
|
|
86971
87097
|
const parsed = JSON.parse(readFileSync66(mcpPath, "utf-8"));
|
|
@@ -86978,8 +87104,8 @@ function sha256(content) {
|
|
|
86978
87104
|
return createHash15("sha256").update(content).digest("hex").slice(0, 16);
|
|
86979
87105
|
}
|
|
86980
87106
|
function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
86981
|
-
const projectsDir =
|
|
86982
|
-
if (!
|
|
87107
|
+
const projectsDir = join75(claudeConfigDir, "projects");
|
|
87108
|
+
if (!existsSync75(projectsDir))
|
|
86983
87109
|
return;
|
|
86984
87110
|
try {
|
|
86985
87111
|
const entries = readdirSync25(projectsDir, { withFileTypes: true });
|
|
@@ -86987,11 +87113,11 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
86987
87113
|
for (const entry of entries) {
|
|
86988
87114
|
if (!entry.isDirectory())
|
|
86989
87115
|
continue;
|
|
86990
|
-
const projectPath =
|
|
86991
|
-
const transcriptPath =
|
|
86992
|
-
if (!
|
|
87116
|
+
const projectPath = join75(projectsDir, entry.name);
|
|
87117
|
+
const transcriptPath = join75(projectPath, "transcript.jsonl");
|
|
87118
|
+
if (!existsSync75(transcriptPath))
|
|
86993
87119
|
continue;
|
|
86994
|
-
const stat3 =
|
|
87120
|
+
const stat3 = statSync44(transcriptPath);
|
|
86995
87121
|
if (!latest || stat3.mtimeMs > latest.mtime) {
|
|
86996
87122
|
latest = { path: transcriptPath, mtime: stat3.mtimeMs };
|
|
86997
87123
|
}
|
|
@@ -87052,16 +87178,16 @@ function registerDebugCommand(program3) {
|
|
|
87052
87178
|
}
|
|
87053
87179
|
const agentsDir = resolveAgentsDir(config);
|
|
87054
87180
|
const agentDir = resolve46(agentsDir, agentName);
|
|
87055
|
-
if (!
|
|
87181
|
+
if (!existsSync75(agentDir)) {
|
|
87056
87182
|
console.error(`Agent directory not found: ${agentDir}`);
|
|
87057
87183
|
process.exit(1);
|
|
87058
87184
|
}
|
|
87059
87185
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
87060
|
-
const claudeConfigDir =
|
|
87061
|
-
const claudeMdPath =
|
|
87062
|
-
const soulMdPath =
|
|
87063
|
-
const workspaceSoulMdPath =
|
|
87064
|
-
const handoffPath =
|
|
87186
|
+
const claudeConfigDir = join75(agentDir, ".claude");
|
|
87187
|
+
const claudeMdPath = join75(agentDir, "CLAUDE.md");
|
|
87188
|
+
const soulMdPath = join75(agentDir, "SOUL.md");
|
|
87189
|
+
const workspaceSoulMdPath = join75(workspaceDir, "SOUL.md");
|
|
87190
|
+
const handoffPath = join75(agentDir, ".handoff.md");
|
|
87065
87191
|
const lastN = parseInt(opts.last, 10);
|
|
87066
87192
|
if (isNaN(lastN) || lastN < 1) {
|
|
87067
87193
|
console.error("--last must be a positive integer");
|
|
@@ -87107,7 +87233,7 @@ function registerDebugCommand(program3) {
|
|
|
87107
87233
|
}
|
|
87108
87234
|
console.log(`=== Append System Prompt (per-session) ===
|
|
87109
87235
|
`);
|
|
87110
|
-
const handoffContent =
|
|
87236
|
+
const handoffContent = existsSync75(handoffPath) ? readFileSync66(handoffPath, "utf-8") : "";
|
|
87111
87237
|
if (handoffContent.trim().length > 0) {
|
|
87112
87238
|
console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
|
|
87113
87239
|
console.log(handoffContent);
|
|
@@ -87118,7 +87244,7 @@ function registerDebugCommand(program3) {
|
|
|
87118
87244
|
}
|
|
87119
87245
|
console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
|
|
87120
87246
|
`);
|
|
87121
|
-
const claudeMdContent =
|
|
87247
|
+
const claudeMdContent = existsSync75(claudeMdPath) ? readFileSync66(claudeMdPath, "utf-8") : "";
|
|
87122
87248
|
if (claudeMdContent.trim().length > 0) {
|
|
87123
87249
|
console.log(`(${formatBytes(claudeMdContent.length)})`);
|
|
87124
87250
|
console.log(claudeMdContent);
|
|
@@ -87129,7 +87255,7 @@ function registerDebugCommand(program3) {
|
|
|
87129
87255
|
}
|
|
87130
87256
|
console.log(`=== Persona (SOUL.md) ===
|
|
87131
87257
|
`);
|
|
87132
|
-
const soulMdContent =
|
|
87258
|
+
const soulMdContent = existsSync75(soulMdPath) ? readFileSync66(soulMdPath, "utf-8") : existsSync75(workspaceSoulMdPath) ? readFileSync66(workspaceSoulMdPath, "utf-8") : "";
|
|
87133
87259
|
if (soulMdContent.trim().length > 0) {
|
|
87134
87260
|
console.log(`(${formatBytes(soulMdContent.length)})`);
|
|
87135
87261
|
console.log(soulMdContent);
|
|
@@ -87190,11 +87316,11 @@ function registerDebugCommand(program3) {
|
|
|
87190
87316
|
const soulMdBytes = soulMdContent.length;
|
|
87191
87317
|
const perTurnBytes = dynamicResult.concatenated.length;
|
|
87192
87318
|
const userBytes = userMessage?.text.length ?? 0;
|
|
87193
|
-
const fleetDir =
|
|
87194
|
-
const fleetInvPath =
|
|
87195
|
-
const fleetClaudePath =
|
|
87196
|
-
const fleetInvBytes =
|
|
87197
|
-
const fleetClaudeBytes =
|
|
87319
|
+
const fleetDir = join75(agentsDir, "..", "fleet");
|
|
87320
|
+
const fleetInvPath = join75(fleetDir, "switchroom-invariants.md");
|
|
87321
|
+
const fleetClaudePath = join75(fleetDir, "CLAUDE.md");
|
|
87322
|
+
const fleetInvBytes = existsSync75(fleetInvPath) ? readFileSync66(fleetInvPath, "utf-8").length : 0;
|
|
87323
|
+
const fleetClaudeBytes = existsSync75(fleetClaudePath) ? readFileSync66(fleetClaudePath, "utf-8").length : 0;
|
|
87198
87324
|
const fleetBytes = fleetInvBytes + fleetClaudeBytes;
|
|
87199
87325
|
const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
|
|
87200
87326
|
console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
|
|
@@ -87227,8 +87353,8 @@ init_source();
|
|
|
87227
87353
|
|
|
87228
87354
|
// src/worktree/claim.ts
|
|
87229
87355
|
import { execFileSync as execFileSync23 } from "node:child_process";
|
|
87230
|
-
import { closeSync as closeSync13, mkdirSync as mkdirSync39, openSync as openSync13, existsSync as
|
|
87231
|
-
import { join as
|
|
87356
|
+
import { closeSync as closeSync13, mkdirSync as mkdirSync39, openSync as openSync13, existsSync as existsSync77, unlinkSync as unlinkSync17 } from "node:fs";
|
|
87357
|
+
import { join as join77, resolve as resolve48 } from "node:path";
|
|
87232
87358
|
import { homedir as homedir46 } from "node:os";
|
|
87233
87359
|
import { randomBytes as randomBytes13 } from "node:crypto";
|
|
87234
87360
|
|
|
@@ -87239,16 +87365,16 @@ import {
|
|
|
87239
87365
|
readFileSync as readFileSync67,
|
|
87240
87366
|
readdirSync as readdirSync26,
|
|
87241
87367
|
unlinkSync as unlinkSync16,
|
|
87242
|
-
existsSync as
|
|
87368
|
+
existsSync as existsSync76,
|
|
87243
87369
|
renameSync as renameSync16
|
|
87244
87370
|
} from "node:fs";
|
|
87245
|
-
import { join as
|
|
87371
|
+
import { join as join76, resolve as resolve47 } from "node:path";
|
|
87246
87372
|
import { homedir as homedir45 } from "node:os";
|
|
87247
87373
|
function registryDir() {
|
|
87248
|
-
return resolve47(process.env.SWITCHROOM_WORKTREE_DIR ??
|
|
87374
|
+
return resolve47(process.env.SWITCHROOM_WORKTREE_DIR ?? join76(homedir45(), ".switchroom", "worktrees"));
|
|
87249
87375
|
}
|
|
87250
87376
|
function recordPath(id) {
|
|
87251
|
-
return
|
|
87377
|
+
return join76(registryDir(), `${id}.json`);
|
|
87252
87378
|
}
|
|
87253
87379
|
function ensureDir2() {
|
|
87254
87380
|
mkdirSync38(registryDir(), { recursive: true });
|
|
@@ -87299,7 +87425,7 @@ function acquireRepoLock(repoPath) {
|
|
|
87299
87425
|
const lockDir = registryDir();
|
|
87300
87426
|
mkdirSync39(lockDir, { recursive: true });
|
|
87301
87427
|
const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
|
|
87302
|
-
const lockPath =
|
|
87428
|
+
const lockPath = join77(lockDir, `.lock-${lockName}`);
|
|
87303
87429
|
const deadline = Date.now() + 5000;
|
|
87304
87430
|
let fd = null;
|
|
87305
87431
|
while (fd === null) {
|
|
@@ -87326,7 +87452,7 @@ function acquireRepoLock(repoPath) {
|
|
|
87326
87452
|
}
|
|
87327
87453
|
var DEFAULT_CONCURRENCY = 5;
|
|
87328
87454
|
function worktreesBaseDir() {
|
|
87329
|
-
return resolve48(process.env.SWITCHROOM_WORKTREE_BASE ??
|
|
87455
|
+
return resolve48(process.env.SWITCHROOM_WORKTREE_BASE ?? join77(homedir46(), ".switchroom", "worktree-checkouts"));
|
|
87330
87456
|
}
|
|
87331
87457
|
function shortId() {
|
|
87332
87458
|
return randomBytes13(4).toString("hex");
|
|
@@ -87348,12 +87474,12 @@ function resolveRepoPath(repo, codeRepos) {
|
|
|
87348
87474
|
}
|
|
87349
87475
|
function expandHome(p) {
|
|
87350
87476
|
if (p.startsWith("~/"))
|
|
87351
|
-
return
|
|
87477
|
+
return join77(homedir46(), p.slice(2));
|
|
87352
87478
|
return p;
|
|
87353
87479
|
}
|
|
87354
87480
|
async function claimWorktree(input, codeRepos) {
|
|
87355
87481
|
const repoPath = resolveRepoPath(input.repo, codeRepos);
|
|
87356
|
-
if (!
|
|
87482
|
+
if (!existsSync77(repoPath)) {
|
|
87357
87483
|
throw new Error(`Repository path does not exist: ${repoPath}`);
|
|
87358
87484
|
}
|
|
87359
87485
|
let concurrencyCap = DEFAULT_CONCURRENCY;
|
|
@@ -87376,7 +87502,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
87376
87502
|
branch = `task/${taskSuffix}-${id}`;
|
|
87377
87503
|
const baseDir = worktreesBaseDir();
|
|
87378
87504
|
mkdirSync39(baseDir, { recursive: true });
|
|
87379
|
-
worktreePath =
|
|
87505
|
+
worktreePath = join77(baseDir, `${id}-${taskSuffix}`);
|
|
87380
87506
|
const ambientOwner = process.env.SWITCHROOM_AGENT_NAME;
|
|
87381
87507
|
const ownerAgent = input.ownerAgent ?? (ambientOwner != null && ambientOwner !== "" ? ambientOwner : undefined);
|
|
87382
87508
|
const now = new Date().toISOString();
|
|
@@ -87409,7 +87535,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
87409
87535
|
|
|
87410
87536
|
// src/worktree/release.ts
|
|
87411
87537
|
import { execFileSync as execFileSync24 } from "node:child_process";
|
|
87412
|
-
import { existsSync as
|
|
87538
|
+
import { existsSync as existsSync78 } from "node:fs";
|
|
87413
87539
|
function releaseWorktree(input) {
|
|
87414
87540
|
const { id } = input;
|
|
87415
87541
|
const record2 = readRecord(id);
|
|
@@ -87417,7 +87543,7 @@ function releaseWorktree(input) {
|
|
|
87417
87543
|
return { released: true };
|
|
87418
87544
|
}
|
|
87419
87545
|
let gitSuccess = true;
|
|
87420
|
-
if (
|
|
87546
|
+
if (existsSync78(record2.path)) {
|
|
87421
87547
|
try {
|
|
87422
87548
|
execFileSync24("git", ["worktree", "remove", "--force", record2.path], {
|
|
87423
87549
|
cwd: record2.repo,
|
|
@@ -87456,7 +87582,7 @@ function listWorktrees() {
|
|
|
87456
87582
|
|
|
87457
87583
|
// src/worktree/reaper.ts
|
|
87458
87584
|
import { execFileSync as execFileSync25 } from "node:child_process";
|
|
87459
|
-
import { existsSync as
|
|
87585
|
+
import { existsSync as existsSync79 } from "node:fs";
|
|
87460
87586
|
var STALE_THRESHOLD_MS = 10 * 60 * 1000;
|
|
87461
87587
|
function reapSkipReasonText(action) {
|
|
87462
87588
|
switch (action) {
|
|
@@ -87517,7 +87643,7 @@ function planReaper(nowMs, deps = {}) {
|
|
|
87517
87643
|
const plan = [];
|
|
87518
87644
|
for (const record2 of listRecords()) {
|
|
87519
87645
|
const heartbeatAge = now - new Date(record2.heartbeatAt).getTime();
|
|
87520
|
-
const worktreeExists =
|
|
87646
|
+
const worktreeExists = existsSync79(record2.path);
|
|
87521
87647
|
if (!worktreeExists) {
|
|
87522
87648
|
plan.push({
|
|
87523
87649
|
record: record2,
|
|
@@ -87598,16 +87724,16 @@ function runReaper(nowMs, deps = {}) {
|
|
|
87598
87724
|
// src/worktree/gc.ts
|
|
87599
87725
|
import { execFileSync as execFileSync26 } from "node:child_process";
|
|
87600
87726
|
import {
|
|
87601
|
-
existsSync as
|
|
87727
|
+
existsSync as existsSync80,
|
|
87602
87728
|
readFileSync as readFileSync68,
|
|
87603
87729
|
readdirSync as readdirSync27,
|
|
87604
|
-
statSync as
|
|
87730
|
+
statSync as statSync45,
|
|
87605
87731
|
renameSync as renameSync17,
|
|
87606
87732
|
mkdirSync as mkdirSync40,
|
|
87607
87733
|
rmSync as rmSync15
|
|
87608
87734
|
} from "node:fs";
|
|
87609
87735
|
import { homedir as homedir47 } from "node:os";
|
|
87610
|
-
import { join as
|
|
87736
|
+
import { join as join78, resolve as resolve49 } from "node:path";
|
|
87611
87737
|
function parseGitdirPointer(dotGitFileContents) {
|
|
87612
87738
|
const m = /^gitdir:\s*(.+?)\s*$/m.exec(dotGitFileContents);
|
|
87613
87739
|
return m ? m[1] : null;
|
|
@@ -87722,17 +87848,17 @@ function defaultPrSignal(repo, branch, exec) {
|
|
|
87722
87848
|
}
|
|
87723
87849
|
}
|
|
87724
87850
|
function trashRoot() {
|
|
87725
|
-
return resolve49(process.env.SWITCHROOM_WORKTREE_TRASH ??
|
|
87851
|
+
return resolve49(process.env.SWITCHROOM_WORKTREE_TRASH ?? join78(homedir47(), ".switchroom", "worktree-gc-trash"));
|
|
87726
87852
|
}
|
|
87727
87853
|
function planGc(roots, deps = {}) {
|
|
87728
|
-
const exists = deps.existsSync ??
|
|
87854
|
+
const exists = deps.existsSync ?? existsSync80;
|
|
87729
87855
|
const readDir = deps.readDir ?? ((p) => readdirSync27(p));
|
|
87730
87856
|
const readFile4 = deps.readFile ?? ((p) => readFileSync68(p, "utf8"));
|
|
87731
|
-
const stat3 = deps.stat ?? ((p) =>
|
|
87857
|
+
const stat3 = deps.stat ?? ((p) => statSync45(p));
|
|
87732
87858
|
const exec = deps.exec ?? defaultExec;
|
|
87733
87859
|
const prSignal = deps.prSignal ?? ((repo, branch) => defaultPrSignal(repo, branch, exec));
|
|
87734
87860
|
const stamp = deps.dateStamp ?? "undated";
|
|
87735
|
-
const trash =
|
|
87861
|
+
const trash = join78(trashRoot(), stamp);
|
|
87736
87862
|
let claimed;
|
|
87737
87863
|
try {
|
|
87738
87864
|
claimed = new Set(listRecords().map((r) => resolve49(r.path)));
|
|
@@ -87768,10 +87894,10 @@ function planGc(roots, deps = {}) {
|
|
|
87768
87894
|
continue;
|
|
87769
87895
|
}
|
|
87770
87896
|
for (const name of entries) {
|
|
87771
|
-
const dir =
|
|
87897
|
+
const dir = join78(root, name);
|
|
87772
87898
|
if (isEphemeralPath(dir))
|
|
87773
87899
|
continue;
|
|
87774
|
-
const dotGit =
|
|
87900
|
+
const dotGit = join78(dir, ".git");
|
|
87775
87901
|
if (!exists(dotGit))
|
|
87776
87902
|
continue;
|
|
87777
87903
|
let st;
|
|
@@ -87800,7 +87926,7 @@ function planGc(roots, deps = {}) {
|
|
|
87800
87926
|
ownerRepos.add(repoRoot);
|
|
87801
87927
|
if (exists(ptr))
|
|
87802
87928
|
continue;
|
|
87803
|
-
orphans.push({ dir, owner: repoRoot, dest:
|
|
87929
|
+
orphans.push({ dir, owner: repoRoot, dest: join78(trash, name) });
|
|
87804
87930
|
}
|
|
87805
87931
|
}
|
|
87806
87932
|
const registered = [];
|
|
@@ -87904,14 +88030,14 @@ function selectPurgeTargets(entries, olderThanDays) {
|
|
|
87904
88030
|
return entries.filter((e) => e.ageDays >= olderThanDays).map((e) => e.path);
|
|
87905
88031
|
}
|
|
87906
88032
|
function listTrashEntries(nowMs, deps = {}) {
|
|
87907
|
-
const exists = deps.existsSync ??
|
|
88033
|
+
const exists = deps.existsSync ?? existsSync80;
|
|
87908
88034
|
const readDir = deps.readDir ?? ((p) => readdirSync27(p));
|
|
87909
88035
|
const root = trashRoot();
|
|
87910
88036
|
if (!exists(root))
|
|
87911
88037
|
return [];
|
|
87912
88038
|
const out = [];
|
|
87913
88039
|
for (const stamp of readDir(root)) {
|
|
87914
|
-
const stampDir =
|
|
88040
|
+
const stampDir = join78(root, stamp);
|
|
87915
88041
|
let names;
|
|
87916
88042
|
try {
|
|
87917
88043
|
names = readDir(stampDir);
|
|
@@ -87919,10 +88045,10 @@ function listTrashEntries(nowMs, deps = {}) {
|
|
|
87919
88045
|
continue;
|
|
87920
88046
|
}
|
|
87921
88047
|
for (const name of names) {
|
|
87922
|
-
const p =
|
|
88048
|
+
const p = join78(stampDir, name);
|
|
87923
88049
|
let mtimeMs = nowMs;
|
|
87924
88050
|
try {
|
|
87925
|
-
mtimeMs =
|
|
88051
|
+
mtimeMs = statSync45(p).mtimeMs;
|
|
87926
88052
|
} catch {}
|
|
87927
88053
|
out.push({ path: p, ageDays: (nowMs - mtimeMs) / 86400000 });
|
|
87928
88054
|
}
|
|
@@ -87943,7 +88069,7 @@ function purgeTrash(paths) {
|
|
|
87943
88069
|
return { deleted, errors: errors2 };
|
|
87944
88070
|
}
|
|
87945
88071
|
function defaultRoots() {
|
|
87946
|
-
return [
|
|
88072
|
+
return [join78(homedir47(), "code")];
|
|
87947
88073
|
}
|
|
87948
88074
|
|
|
87949
88075
|
// src/cli/worktree.ts
|
|
@@ -88171,7 +88297,7 @@ import {
|
|
|
88171
88297
|
rmSync as rmSync16,
|
|
88172
88298
|
writeFileSync as writeFileSync24
|
|
88173
88299
|
} from "node:fs";
|
|
88174
|
-
import { join as
|
|
88300
|
+
import { join as join79 } from "node:path";
|
|
88175
88301
|
function encodeCredentialsFilename(email) {
|
|
88176
88302
|
const SAFE = new Set([
|
|
88177
88303
|
..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
|
@@ -88361,16 +88487,16 @@ function resolveCredentialsDir(env2) {
|
|
|
88361
88487
|
if (explicit && explicit.length > 0)
|
|
88362
88488
|
return explicit;
|
|
88363
88489
|
const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
|
|
88364
|
-
return
|
|
88490
|
+
return join79(stateBase, "google-workspace-mcp", "credentials");
|
|
88365
88491
|
}
|
|
88366
88492
|
function writeSeedFile(dir, email, seed) {
|
|
88367
88493
|
mkdirSync41(dir, { recursive: true, mode: 448 });
|
|
88368
88494
|
chmodSync10(dir, 448);
|
|
88369
88495
|
for (const name of readdirSync28(dir)) {
|
|
88370
|
-
rmSync16(
|
|
88496
|
+
rmSync16(join79(dir, name), { force: true, recursive: true });
|
|
88371
88497
|
}
|
|
88372
88498
|
const filename = encodeCredentialsFilename(email);
|
|
88373
|
-
const filePath =
|
|
88499
|
+
const filePath = join79(dir, filename);
|
|
88374
88500
|
writeFileSync24(filePath, JSON.stringify(seed), { mode: 384 });
|
|
88375
88501
|
chmodSync10(filePath, 384);
|
|
88376
88502
|
return filePath;
|
|
@@ -88530,7 +88656,7 @@ function registerDriveMcpLauncherCommand(program3) {
|
|
|
88530
88656
|
init_scaffold_integration();
|
|
88531
88657
|
import { spawn as spawn5 } from "node:child_process";
|
|
88532
88658
|
import { writeFileSync as writeFileSync25, mkdirSync as mkdirSync42 } from "node:fs";
|
|
88533
|
-
import { dirname as dirname24, join as
|
|
88659
|
+
import { dirname as dirname24, join as join80 } from "node:path";
|
|
88534
88660
|
var SOFTERIA_TOKEN_ENV = "MS365_MCP_OAUTH_TOKEN";
|
|
88535
88661
|
var DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
|
|
88536
88662
|
var MAX_REFRESH_INTERVAL_MS = 60 * 60 * 1000;
|
|
@@ -88570,7 +88696,7 @@ function writeRefreshHeartbeat(agentName, data) {
|
|
|
88570
88696
|
function heartbeatPath(agentName) {
|
|
88571
88697
|
const override = process.env.SWITCHROOM_M365_HEARTBEAT_DIR;
|
|
88572
88698
|
if (override) {
|
|
88573
|
-
return
|
|
88699
|
+
return join80(override, `m365-launcher-${agentName}.heartbeat.json`);
|
|
88574
88700
|
}
|
|
88575
88701
|
return "/state/agent/m365-launcher.heartbeat.json";
|
|
88576
88702
|
}
|
|
@@ -88802,7 +88928,7 @@ function registerM365McpLauncherCommand(program3) {
|
|
|
88802
88928
|
// src/cli/notion-mcp-launcher.ts
|
|
88803
88929
|
init_scaffold_integration();
|
|
88804
88930
|
import { spawn as spawn6 } from "node:child_process";
|
|
88805
|
-
import { existsSync as
|
|
88931
|
+
import { existsSync as existsSync81, mkdirSync as mkdirSync43, writeFileSync as writeFileSync26 } from "node:fs";
|
|
88806
88932
|
import { dirname as dirname25 } from "node:path";
|
|
88807
88933
|
var HEARTBEAT_WRITE_INTERVAL_MS = 30 * 1000;
|
|
88808
88934
|
var DEFAULT_HEARTBEAT_PATH = "/state/agent/notion-launcher.heartbeat.json";
|
|
@@ -88814,7 +88940,7 @@ function buildNotionMcpArgs(opts) {
|
|
|
88814
88940
|
function defaultWriteHeartbeat(path7, contents) {
|
|
88815
88941
|
try {
|
|
88816
88942
|
const dir = dirname25(path7);
|
|
88817
|
-
if (!
|
|
88943
|
+
if (!existsSync81(dir))
|
|
88818
88944
|
mkdirSync43(dir, { recursive: true });
|
|
88819
88945
|
writeFileSync26(path7, contents);
|
|
88820
88946
|
} catch {}
|
|
@@ -88926,7 +89052,7 @@ function registerNotionMcpLauncherCommand(program3) {
|
|
|
88926
89052
|
|
|
88927
89053
|
// src/cli/deliver-file.ts
|
|
88928
89054
|
init_client2();
|
|
88929
|
-
import { readFileSync as readFileSync69, statSync as
|
|
89055
|
+
import { readFileSync as readFileSync69, statSync as statSync46 } from "node:fs";
|
|
88930
89056
|
import { basename as basename11 } from "node:path";
|
|
88931
89057
|
|
|
88932
89058
|
// src/delivery/onedrive.ts
|
|
@@ -89265,7 +89391,7 @@ async function defaultResolveProvider() {
|
|
|
89265
89391
|
}
|
|
89266
89392
|
async function runDeliverFile(localPath, deps = {}) {
|
|
89267
89393
|
const agentName = safeAgentName(deps.agentName ?? process.env.SWITCHROOM_AGENT_NAME);
|
|
89268
|
-
const sizeOf = deps.fileSize ?? ((p) =>
|
|
89394
|
+
const sizeOf = deps.fileSize ?? ((p) => statSync46(p).size);
|
|
89269
89395
|
const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync69(p)));
|
|
89270
89396
|
const resolveProvider = deps.resolveProvider ?? defaultResolveProvider;
|
|
89271
89397
|
let size;
|
|
@@ -89587,8 +89713,8 @@ function runRedactStdin() {
|
|
|
89587
89713
|
}
|
|
89588
89714
|
|
|
89589
89715
|
// src/cli/status-ask.ts
|
|
89590
|
-
import { readFileSync as readFileSync73, existsSync as
|
|
89591
|
-
import { join as
|
|
89716
|
+
import { readFileSync as readFileSync73, existsSync as existsSync86, readdirSync as readdirSync30 } from "node:fs";
|
|
89717
|
+
import { join as join85 } from "node:path";
|
|
89592
89718
|
import { homedir as homedir50 } from "node:os";
|
|
89593
89719
|
|
|
89594
89720
|
// src/status-ask/report.ts
|
|
@@ -89910,7 +90036,7 @@ function runReport(opts) {
|
|
|
89910
90036
|
function resolveSources(explicitPath) {
|
|
89911
90037
|
if (explicitPath != null && explicitPath.trim() !== "") {
|
|
89912
90038
|
const trimmed = explicitPath.trim();
|
|
89913
|
-
if (!
|
|
90039
|
+
if (!existsSync86(trimmed)) {
|
|
89914
90040
|
process.stderr.write(`status-ask report: ${trimmed}: file not found
|
|
89915
90041
|
`);
|
|
89916
90042
|
process.exit(1);
|
|
@@ -89924,9 +90050,9 @@ function resolveSources(explicitPath) {
|
|
|
89924
90050
|
const config = loadConfig();
|
|
89925
90051
|
agentsDir = resolveAgentsDir(config);
|
|
89926
90052
|
} catch {
|
|
89927
|
-
agentsDir =
|
|
90053
|
+
agentsDir = join85(homedir50(), ".switchroom", "agents");
|
|
89928
90054
|
}
|
|
89929
|
-
if (!
|
|
90055
|
+
if (!existsSync86(agentsDir))
|
|
89930
90056
|
return [];
|
|
89931
90057
|
const sources = [];
|
|
89932
90058
|
let entries;
|
|
@@ -89936,8 +90062,8 @@ function resolveSources(explicitPath) {
|
|
|
89936
90062
|
return [];
|
|
89937
90063
|
}
|
|
89938
90064
|
for (const name of entries) {
|
|
89939
|
-
const path8 =
|
|
89940
|
-
if (
|
|
90065
|
+
const path8 = join85(agentsDir, name, "runtime-metrics.jsonl");
|
|
90066
|
+
if (existsSync86(path8)) {
|
|
89941
90067
|
sources.push({ path: path8, agent: name });
|
|
89942
90068
|
}
|
|
89943
90069
|
}
|
|
@@ -89967,32 +90093,32 @@ var import_yaml21 = __toESM(require_dist(), 1);
|
|
|
89967
90093
|
init_paths();
|
|
89968
90094
|
import {
|
|
89969
90095
|
closeSync as closeSync14,
|
|
89970
|
-
existsSync as
|
|
90096
|
+
existsSync as existsSync87,
|
|
89971
90097
|
fsyncSync as fsyncSync8,
|
|
89972
90098
|
mkdirSync as mkdirSync48,
|
|
89973
90099
|
openSync as openSync14,
|
|
89974
90100
|
readdirSync as readdirSync31,
|
|
89975
90101
|
readFileSync as readFileSync74,
|
|
89976
90102
|
renameSync as renameSync19,
|
|
89977
|
-
statSync as
|
|
90103
|
+
statSync as statSync48,
|
|
89978
90104
|
unlinkSync as unlinkSync18,
|
|
89979
90105
|
writeSync as writeSync10
|
|
89980
90106
|
} from "node:fs";
|
|
89981
|
-
import { join as
|
|
90107
|
+
import { join as join86, resolve as resolve52 } from "node:path";
|
|
89982
90108
|
var STAGING_SUBDIR = ".staging";
|
|
89983
90109
|
function overlayPathsFor(agent, opts = {}) {
|
|
89984
90110
|
const base = opts.root ? resolve52(opts.root, agent) : resolve52(resolveDualPath(`~/.switchroom/agents/${agent}`));
|
|
89985
|
-
const scheduleDir =
|
|
89986
|
-
const scheduleStagingDir =
|
|
89987
|
-
const skillsDir =
|
|
89988
|
-
const skillsStagingDir =
|
|
90111
|
+
const scheduleDir = join86(base, "schedule.d");
|
|
90112
|
+
const scheduleStagingDir = join86(scheduleDir, STAGING_SUBDIR);
|
|
90113
|
+
const skillsDir = join86(base, "skills.d");
|
|
90114
|
+
const skillsStagingDir = join86(skillsDir, STAGING_SUBDIR);
|
|
89989
90115
|
return {
|
|
89990
90116
|
agentRoot: base,
|
|
89991
90117
|
scheduleDir,
|
|
89992
90118
|
scheduleStagingDir,
|
|
89993
90119
|
skillsDir,
|
|
89994
90120
|
skillsStagingDir,
|
|
89995
|
-
lockPath:
|
|
90121
|
+
lockPath: join86(base, ".lock"),
|
|
89996
90122
|
stagingDir: scheduleStagingDir
|
|
89997
90123
|
};
|
|
89998
90124
|
}
|
|
@@ -90018,7 +90144,7 @@ function withAgentLock(paths, fn) {
|
|
|
90018
90144
|
if (e.code !== "EEXIST")
|
|
90019
90145
|
throw err;
|
|
90020
90146
|
try {
|
|
90021
|
-
const age = Date.now() -
|
|
90147
|
+
const age = Date.now() - statSync48(paths.lockPath).mtimeMs;
|
|
90022
90148
|
if (age > 30000) {
|
|
90023
90149
|
unlinkSync18(paths.lockPath);
|
|
90024
90150
|
continue;
|
|
@@ -90046,8 +90172,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
90046
90172
|
const paths = overlayPathsFor(agent, opts);
|
|
90047
90173
|
return withAgentLock(paths, () => {
|
|
90048
90174
|
ensureDirs(paths);
|
|
90049
|
-
const stagingPath =
|
|
90050
|
-
const finalPath =
|
|
90175
|
+
const stagingPath = join86(paths.scheduleStagingDir, `${slug}.yaml`);
|
|
90176
|
+
const finalPath = join86(paths.scheduleDir, `${slug}.yaml`);
|
|
90051
90177
|
const fd = openSync14(stagingPath, "w", 384);
|
|
90052
90178
|
try {
|
|
90053
90179
|
writeSync10(fd, yamlText);
|
|
@@ -90063,8 +90189,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
90063
90189
|
const paths = overlayPathsFor(agent, opts);
|
|
90064
90190
|
return withAgentLock(paths, () => {
|
|
90065
90191
|
ensureSkillsDirs(paths);
|
|
90066
|
-
const stagingPath =
|
|
90067
|
-
const finalPath =
|
|
90192
|
+
const stagingPath = join86(paths.skillsStagingDir, `${slug}.yaml`);
|
|
90193
|
+
const finalPath = join86(paths.skillsDir, `${slug}.yaml`);
|
|
90068
90194
|
const fd = openSync14(stagingPath, "w", 384);
|
|
90069
90195
|
try {
|
|
90070
90196
|
writeSync10(fd, yamlText);
|
|
@@ -90079,8 +90205,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
90079
90205
|
function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
90080
90206
|
const paths = overlayPathsFor(agent, opts);
|
|
90081
90207
|
return withAgentLock(paths, () => {
|
|
90082
|
-
const finalPath =
|
|
90083
|
-
if (!
|
|
90208
|
+
const finalPath = join86(paths.skillsDir, `${slug}.yaml`);
|
|
90209
|
+
if (!existsSync87(finalPath))
|
|
90084
90210
|
return false;
|
|
90085
90211
|
unlinkSync18(finalPath);
|
|
90086
90212
|
return true;
|
|
@@ -90088,13 +90214,13 @@ function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
|
90088
90214
|
}
|
|
90089
90215
|
function listSkillsOverlayEntries(agent, opts = {}) {
|
|
90090
90216
|
const paths = overlayPathsFor(agent, opts);
|
|
90091
|
-
if (!
|
|
90217
|
+
if (!existsSync87(paths.skillsDir))
|
|
90092
90218
|
return [];
|
|
90093
90219
|
const out = [];
|
|
90094
90220
|
for (const name of readdirSync31(paths.skillsDir)) {
|
|
90095
90221
|
if (!/\.ya?ml$/i.test(name))
|
|
90096
90222
|
continue;
|
|
90097
|
-
const full =
|
|
90223
|
+
const full = join86(paths.skillsDir, name);
|
|
90098
90224
|
try {
|
|
90099
90225
|
const raw = readFileSync74(full, "utf-8");
|
|
90100
90226
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
@@ -90106,8 +90232,8 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
90106
90232
|
function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
90107
90233
|
const paths = overlayPathsFor(agent, opts);
|
|
90108
90234
|
return withAgentLock(paths, () => {
|
|
90109
|
-
const finalPath =
|
|
90110
|
-
if (!
|
|
90235
|
+
const finalPath = join86(paths.scheduleDir, `${slug}.yaml`);
|
|
90236
|
+
if (!existsSync87(finalPath))
|
|
90111
90237
|
return false;
|
|
90112
90238
|
unlinkSync18(finalPath);
|
|
90113
90239
|
return true;
|
|
@@ -90115,13 +90241,13 @@ function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
|
90115
90241
|
}
|
|
90116
90242
|
function listOverlayEntries(agent, opts = {}) {
|
|
90117
90243
|
const paths = overlayPathsFor(agent, opts);
|
|
90118
|
-
if (!
|
|
90244
|
+
if (!existsSync87(paths.scheduleDir))
|
|
90119
90245
|
return [];
|
|
90120
90246
|
const out = [];
|
|
90121
90247
|
for (const name of readdirSync31(paths.scheduleDir)) {
|
|
90122
90248
|
if (!/\.ya?ml$/i.test(name))
|
|
90123
90249
|
continue;
|
|
90124
|
-
const full =
|
|
90250
|
+
const full = join86(paths.scheduleDir, name);
|
|
90125
90251
|
try {
|
|
90126
90252
|
const raw = readFileSync74(full, "utf-8");
|
|
90127
90253
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
@@ -90343,7 +90469,7 @@ function reconcileAgentCronOnly(agent) {
|
|
|
90343
90469
|
// src/cli/agent-config-pending.ts
|
|
90344
90470
|
import {
|
|
90345
90471
|
closeSync as closeSync15,
|
|
90346
|
-
existsSync as
|
|
90472
|
+
existsSync as existsSync88,
|
|
90347
90473
|
fsyncSync as fsyncSync9,
|
|
90348
90474
|
mkdirSync as mkdirSync49,
|
|
90349
90475
|
openSync as openSync15,
|
|
@@ -90354,12 +90480,12 @@ import {
|
|
|
90354
90480
|
writeFileSync as writeFileSync31,
|
|
90355
90481
|
writeSync as writeSync11
|
|
90356
90482
|
} from "node:fs";
|
|
90357
|
-
import { join as
|
|
90483
|
+
import { join as join87 } from "node:path";
|
|
90358
90484
|
import { randomBytes as randomBytes15 } from "node:crypto";
|
|
90359
90485
|
var STAGE_ID_PREFIX = "cap_";
|
|
90360
90486
|
function pendingDir(agent, opts = {}) {
|
|
90361
90487
|
const paths = overlayPathsFor(agent, opts);
|
|
90362
|
-
return
|
|
90488
|
+
return join87(paths.scheduleDir, ".pending");
|
|
90363
90489
|
}
|
|
90364
90490
|
function ensurePendingDir(agent, opts = {}) {
|
|
90365
90491
|
const dir = pendingDir(agent, opts);
|
|
@@ -90372,8 +90498,8 @@ function newStageId() {
|
|
|
90372
90498
|
function stagePendingScheduleEntry(opts) {
|
|
90373
90499
|
const dir = ensurePendingDir(opts.agent, { root: opts.root });
|
|
90374
90500
|
const stageId = opts.stageId ?? newStageId();
|
|
90375
|
-
const yamlPath =
|
|
90376
|
-
const metaPath =
|
|
90501
|
+
const yamlPath = join87(dir, `${stageId}.yaml`);
|
|
90502
|
+
const metaPath = join87(dir, `${stageId}.meta.json`);
|
|
90377
90503
|
const meta = {
|
|
90378
90504
|
v: 1,
|
|
90379
90505
|
stage_id: stageId,
|
|
@@ -90400,16 +90526,16 @@ function stagePendingScheduleEntry(opts) {
|
|
|
90400
90526
|
}
|
|
90401
90527
|
function listPendingScheduleEntries(agent, opts = {}) {
|
|
90402
90528
|
const dir = pendingDir(agent, opts);
|
|
90403
|
-
if (!
|
|
90529
|
+
if (!existsSync88(dir))
|
|
90404
90530
|
return [];
|
|
90405
90531
|
const out = [];
|
|
90406
90532
|
for (const name of readdirSync32(dir).sort()) {
|
|
90407
90533
|
if (!name.endsWith(".meta.json"))
|
|
90408
90534
|
continue;
|
|
90409
90535
|
const stageId = name.slice(0, -".meta.json".length);
|
|
90410
|
-
const metaPath =
|
|
90411
|
-
const yamlPath =
|
|
90412
|
-
if (!
|
|
90536
|
+
const metaPath = join87(dir, name);
|
|
90537
|
+
const yamlPath = join87(dir, `${stageId}.yaml`);
|
|
90538
|
+
if (!existsSync88(yamlPath))
|
|
90413
90539
|
continue;
|
|
90414
90540
|
try {
|
|
90415
90541
|
const meta = JSON.parse(readFileSync75(metaPath, "utf-8"));
|
|
@@ -90427,8 +90553,8 @@ function commitPendingScheduleEntry(opts) {
|
|
|
90427
90553
|
return { committed: false, reason: "not_found" };
|
|
90428
90554
|
const slug = match.meta.entry.name ?? match.stageId;
|
|
90429
90555
|
const paths = overlayPathsFor(opts.agent, { root: opts.root });
|
|
90430
|
-
const finalPath =
|
|
90431
|
-
if (
|
|
90556
|
+
const finalPath = join87(paths.scheduleDir, `${slug}.yaml`);
|
|
90557
|
+
if (existsSync88(finalPath)) {
|
|
90432
90558
|
return { committed: false, reason: "slug_collision" };
|
|
90433
90559
|
}
|
|
90434
90560
|
renameSync20(match.yamlPath, finalPath);
|
|
@@ -90450,7 +90576,7 @@ function denyPendingScheduleEntry(opts) {
|
|
|
90450
90576
|
}
|
|
90451
90577
|
|
|
90452
90578
|
// src/cli/agent-config-write.ts
|
|
90453
|
-
import { existsSync as
|
|
90579
|
+
import { existsSync as existsSync89, readFileSync as readFileSync76 } from "node:fs";
|
|
90454
90580
|
import { execFileSync as execFileSync28 } from "node:child_process";
|
|
90455
90581
|
|
|
90456
90582
|
// src/scheduler/schedule-report.ts
|
|
@@ -90840,7 +90966,7 @@ function scheduleRemove(opts) {
|
|
|
90840
90966
|
}
|
|
90841
90967
|
let priorContent = null;
|
|
90842
90968
|
try {
|
|
90843
|
-
if (
|
|
90969
|
+
if (existsSync89(match.path))
|
|
90844
90970
|
priorContent = readFileSync76(match.path, "utf-8");
|
|
90845
90971
|
} catch {}
|
|
90846
90972
|
deleteOverlayEntry(agent, match.slug, { root: opts.root });
|
|
@@ -91044,7 +91170,7 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
91044
91170
|
}
|
|
91045
91171
|
let blob;
|
|
91046
91172
|
if (opts.jsonl) {
|
|
91047
|
-
blob =
|
|
91173
|
+
blob = existsSync89(opts.jsonl) ? readFileSync76(opts.jsonl, "utf-8") : "";
|
|
91048
91174
|
} else {
|
|
91049
91175
|
try {
|
|
91050
91176
|
blob = execFileSync28("docker", ["exec", `switchroom-${agent}`, "cat", "/state/agent/scheduler.jsonl"], {
|
|
@@ -91076,11 +91202,11 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
91076
91202
|
|
|
91077
91203
|
// src/cli/agent-config-skill-write.ts
|
|
91078
91204
|
var import_yaml22 = __toESM(require_dist(), 1);
|
|
91079
|
-
import { existsSync as
|
|
91205
|
+
import { existsSync as existsSync90 } from "node:fs";
|
|
91080
91206
|
init_reconcile_default_skills();
|
|
91081
91207
|
init_agent_config();
|
|
91082
91208
|
var import_yaml23 = __toESM(require_dist(), 1);
|
|
91083
|
-
import { join as
|
|
91209
|
+
import { join as join88 } from "node:path";
|
|
91084
91210
|
var MAX_SKILLS_PER_AGENT = 20;
|
|
91085
91211
|
var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
|
|
91086
91212
|
function exitCodeFor2(code) {
|
|
@@ -91155,8 +91281,8 @@ function skillInstall(opts) {
|
|
|
91155
91281
|
return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
|
|
91156
91282
|
}
|
|
91157
91283
|
const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
|
|
91158
|
-
const skillPath =
|
|
91159
|
-
if (!
|
|
91284
|
+
const skillPath = join88(poolDir, skillName);
|
|
91285
|
+
if (!existsSync90(skillPath)) {
|
|
91160
91286
|
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.`);
|
|
91161
91287
|
}
|
|
91162
91288
|
const yamlText = import_yaml22.stringify({ skills: [skillName] });
|
|
@@ -91320,8 +91446,8 @@ function registerAgentConfigSkillWriteCommands(program3) {
|
|
|
91320
91446
|
// src/cli/skill.ts
|
|
91321
91447
|
import {
|
|
91322
91448
|
closeSync as closeSync16,
|
|
91323
|
-
existsSync as
|
|
91324
|
-
lstatSync as
|
|
91449
|
+
existsSync as existsSync91,
|
|
91450
|
+
lstatSync as lstatSync11,
|
|
91325
91451
|
mkdirSync as mkdirSync50,
|
|
91326
91452
|
mkdtempSync as mkdtempSync5,
|
|
91327
91453
|
openSync as openSync16,
|
|
@@ -91330,11 +91456,11 @@ import {
|
|
|
91330
91456
|
realpathSync as realpathSync7,
|
|
91331
91457
|
renameSync as renameSync21,
|
|
91332
91458
|
rmSync as rmSync18,
|
|
91333
|
-
statSync as
|
|
91459
|
+
statSync as statSync49,
|
|
91334
91460
|
writeFileSync as writeFileSync32
|
|
91335
91461
|
} from "node:fs";
|
|
91336
91462
|
import { tmpdir as tmpdir5, homedir as homedir51 } from "node:os";
|
|
91337
|
-
import { dirname as dirname30, join as
|
|
91463
|
+
import { dirname as dirname30, join as join89, relative as relative2, resolve as resolve53 } from "node:path";
|
|
91338
91464
|
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
91339
91465
|
|
|
91340
91466
|
// src/cli/skill-common.ts
|
|
@@ -91568,7 +91694,7 @@ function scanForClaudeP2(content) {
|
|
|
91568
91694
|
function resolveSkillsPoolDir2(override) {
|
|
91569
91695
|
const raw = override ?? "~/.switchroom/skills";
|
|
91570
91696
|
if (raw.startsWith("~/")) {
|
|
91571
|
-
return
|
|
91697
|
+
return join89(homedir51(), raw.slice(2));
|
|
91572
91698
|
}
|
|
91573
91699
|
if (raw === "~")
|
|
91574
91700
|
return homedir51();
|
|
@@ -91600,14 +91726,14 @@ function isTarballPath(p) {
|
|
|
91600
91726
|
}
|
|
91601
91727
|
function loadFromDir(dir) {
|
|
91602
91728
|
const abs = realpathSync7(dir);
|
|
91603
|
-
if (!
|
|
91729
|
+
if (!statSync49(abs).isDirectory()) {
|
|
91604
91730
|
fail3(`--from path is not a directory: ${dir}`);
|
|
91605
91731
|
}
|
|
91606
91732
|
const files = {};
|
|
91607
91733
|
const walk2 = (sub) => {
|
|
91608
91734
|
const entries = readdirSync33(sub, { withFileTypes: true });
|
|
91609
91735
|
for (const ent of entries) {
|
|
91610
|
-
const full =
|
|
91736
|
+
const full = join89(sub, ent.name);
|
|
91611
91737
|
const rel = relative2(abs, full);
|
|
91612
91738
|
if (ent.isSymbolicLink()) {
|
|
91613
91739
|
fail3(`refusing to read symlink inside --from dir: ${rel}`);
|
|
@@ -91642,7 +91768,7 @@ function loadFromTarball(tarPath) {
|
|
|
91642
91768
|
fail3(`tarball contains disallowed path: ${JSON.stringify(entry)} \u2014 ` + `refusing to extract before any file is written`);
|
|
91643
91769
|
}
|
|
91644
91770
|
}
|
|
91645
|
-
const staging = mkdtempSync5(
|
|
91771
|
+
const staging = mkdtempSync5(join89(tmpdir5(), "skill-apply-extract-"));
|
|
91646
91772
|
try {
|
|
91647
91773
|
const flags = isGz ? ["-xzf"] : ["-xf"];
|
|
91648
91774
|
const r = spawnSync16("tar", [
|
|
@@ -91728,8 +91854,8 @@ function validatePayload(name, files) {
|
|
|
91728
91854
|
errors2.push(`${path8} fails \`bash -n\` syntax check: ${(r.stderr ?? "").trim()}`);
|
|
91729
91855
|
}
|
|
91730
91856
|
} else if (PY_SCRIPT_RE2.test(path8)) {
|
|
91731
|
-
const tmp = mkdtempSync5(
|
|
91732
|
-
const tmpPy =
|
|
91857
|
+
const tmp = mkdtempSync5(join89(tmpdir5(), "skill-apply-py-"));
|
|
91858
|
+
const tmpPy = join89(tmp, "check.py");
|
|
91733
91859
|
try {
|
|
91734
91860
|
writeFileSync32(tmpPy, content);
|
|
91735
91861
|
const r = spawnSync16("python3", ["-m", "py_compile", tmpPy], {
|
|
@@ -91749,10 +91875,10 @@ function validatePayload(name, files) {
|
|
|
91749
91875
|
function diffSummary(currentDir, files) {
|
|
91750
91876
|
const lines = [];
|
|
91751
91877
|
const currentFiles = {};
|
|
91752
|
-
if (
|
|
91878
|
+
if (existsSync91(currentDir)) {
|
|
91753
91879
|
const walk2 = (sub) => {
|
|
91754
91880
|
for (const ent of readdirSync33(sub, { withFileTypes: true })) {
|
|
91755
|
-
const full =
|
|
91881
|
+
const full = join89(sub, ent.name);
|
|
91756
91882
|
const rel = relative2(currentDir, full);
|
|
91757
91883
|
if (ent.isDirectory()) {
|
|
91758
91884
|
walk2(full);
|
|
@@ -91785,13 +91911,13 @@ function diffSummary(currentDir, files) {
|
|
|
91785
91911
|
`);
|
|
91786
91912
|
}
|
|
91787
91913
|
function writePayload(poolDir, name, files) {
|
|
91788
|
-
if (!
|
|
91914
|
+
if (!existsSync91(poolDir)) {
|
|
91789
91915
|
mkdirSync50(poolDir, { recursive: true, mode: 493 });
|
|
91790
91916
|
}
|
|
91791
|
-
const target =
|
|
91917
|
+
const target = join89(poolDir, name);
|
|
91792
91918
|
let targetIsSymlink = false;
|
|
91793
91919
|
try {
|
|
91794
|
-
const st =
|
|
91920
|
+
const st = lstatSync11(target);
|
|
91795
91921
|
if (st.isSymbolicLink()) {
|
|
91796
91922
|
targetIsSymlink = true;
|
|
91797
91923
|
}
|
|
@@ -91799,11 +91925,11 @@ function writePayload(poolDir, name, files) {
|
|
|
91799
91925
|
if (targetIsSymlink) {
|
|
91800
91926
|
fail3(`refusing to overwrite symlink at ${target}; investigate manually`);
|
|
91801
91927
|
}
|
|
91802
|
-
const staging = mkdtempSync5(
|
|
91928
|
+
const staging = mkdtempSync5(join89(poolDir, `.skill-apply-stage-${name}-`));
|
|
91803
91929
|
let oldRename = null;
|
|
91804
91930
|
try {
|
|
91805
91931
|
for (const [path8, content] of Object.entries(files)) {
|
|
91806
|
-
const full =
|
|
91932
|
+
const full = join89(staging, path8);
|
|
91807
91933
|
mkdirSync50(dirname30(full), { recursive: true, mode: 493 });
|
|
91808
91934
|
const fd = openSync16(full, "wx");
|
|
91809
91935
|
try {
|
|
@@ -91818,7 +91944,7 @@ function writePayload(poolDir, name, files) {
|
|
|
91818
91944
|
}
|
|
91819
91945
|
let targetExists = false;
|
|
91820
91946
|
try {
|
|
91821
|
-
|
|
91947
|
+
lstatSync11(target);
|
|
91822
91948
|
targetExists = true;
|
|
91823
91949
|
} catch {}
|
|
91824
91950
|
if (targetExists) {
|
|
@@ -91834,9 +91960,9 @@ function writePayload(poolDir, name, files) {
|
|
|
91834
91960
|
try {
|
|
91835
91961
|
rmSync18(staging, { recursive: true, force: true });
|
|
91836
91962
|
} catch {}
|
|
91837
|
-
if (oldRename &&
|
|
91963
|
+
if (oldRename && existsSync91(oldRename)) {
|
|
91838
91964
|
try {
|
|
91839
|
-
if (
|
|
91965
|
+
if (existsSync91(target)) {
|
|
91840
91966
|
rmSync18(target, { recursive: true, force: true });
|
|
91841
91967
|
}
|
|
91842
91968
|
renameSync21(oldRename, target);
|
|
@@ -91857,10 +91983,10 @@ function registerSkillCommand(program3) {
|
|
|
91857
91983
|
files = loadFromStdin();
|
|
91858
91984
|
} else {
|
|
91859
91985
|
const fromPath = resolve53(opts.from);
|
|
91860
|
-
if (!
|
|
91986
|
+
if (!existsSync91(fromPath)) {
|
|
91861
91987
|
fail3(`--from path does not exist: ${opts.from}`);
|
|
91862
91988
|
}
|
|
91863
|
-
const st =
|
|
91989
|
+
const st = statSync49(fromPath);
|
|
91864
91990
|
if (st.isDirectory()) {
|
|
91865
91991
|
files = loadFromDir(fromPath);
|
|
91866
91992
|
} else if (isTarballPath(fromPath)) {
|
|
@@ -91881,7 +92007,7 @@ function registerSkillCommand(program3) {
|
|
|
91881
92007
|
}
|
|
91882
92008
|
const config = loadConfig();
|
|
91883
92009
|
const poolDir = resolveSkillsPoolDir2(config.switchroom?.skills_dir);
|
|
91884
|
-
const currentDir =
|
|
92010
|
+
const currentDir = join89(poolDir, name);
|
|
91885
92011
|
console.log(source_default.bold(`Skill: ${name}`) + source_default.gray(` (${Object.keys(files).length} files, ${sumBytes(files)} bytes)`));
|
|
91886
92012
|
console.log(source_default.bold("Diff vs current pool content:"));
|
|
91887
92013
|
console.log(diffSummary(currentDir, files));
|
|
@@ -91913,8 +92039,8 @@ function sumBytes(files) {
|
|
|
91913
92039
|
init_esm();
|
|
91914
92040
|
import {
|
|
91915
92041
|
closeSync as closeSync17,
|
|
91916
|
-
existsSync as
|
|
91917
|
-
lstatSync as
|
|
92042
|
+
existsSync as existsSync92,
|
|
92043
|
+
lstatSync as lstatSync12,
|
|
91918
92044
|
mkdirSync as mkdirSync51,
|
|
91919
92045
|
mkdtempSync as mkdtempSync6,
|
|
91920
92046
|
openSync as openSync17,
|
|
@@ -91922,11 +92048,11 @@ import {
|
|
|
91922
92048
|
readdirSync as readdirSync34,
|
|
91923
92049
|
renameSync as renameSync22,
|
|
91924
92050
|
rmSync as rmSync19,
|
|
91925
|
-
statSync as
|
|
92051
|
+
statSync as statSync50,
|
|
91926
92052
|
utimesSync,
|
|
91927
92053
|
writeFileSync as writeFileSync33
|
|
91928
92054
|
} from "node:fs";
|
|
91929
|
-
import { dirname as dirname31, join as
|
|
92055
|
+
import { dirname as dirname31, join as join90, relative as relative3, resolve as resolve54 } from "node:path";
|
|
91930
92056
|
import { homedir as homedir52, tmpdir as tmpdir6 } from "node:os";
|
|
91931
92057
|
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
91932
92058
|
init_helpers();
|
|
@@ -91938,15 +92064,15 @@ var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
|
|
|
91938
92064
|
var PERSONAL_SKILLS_SUBPATH = "personal-skills";
|
|
91939
92065
|
function resolveConfigSkillsDir(agent) {
|
|
91940
92066
|
const override = process.env.SWITCHROOM_CONFIG_DIR;
|
|
91941
|
-
const candidate = override ? resolve54(override) :
|
|
91942
|
-
if (!
|
|
92067
|
+
const candidate = override ? resolve54(override) : join90(homedir52(), ".switchroom-config");
|
|
92068
|
+
if (!existsSync92(candidate))
|
|
91943
92069
|
return null;
|
|
91944
|
-
return
|
|
92070
|
+
return join90(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
|
|
91945
92071
|
}
|
|
91946
92072
|
var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
|
|
91947
92073
|
function sweepMirrorPriors(configSkillsRoot) {
|
|
91948
92074
|
try {
|
|
91949
|
-
if (!
|
|
92075
|
+
if (!existsSync92(configSkillsRoot))
|
|
91950
92076
|
return;
|
|
91951
92077
|
const now = Date.now();
|
|
91952
92078
|
for (const ent of readdirSync34(configSkillsRoot)) {
|
|
@@ -91959,7 +92085,7 @@ function sweepMirrorPriors(configSkillsRoot) {
|
|
|
91959
92085
|
if (now - ts < MIRROR_PRIOR_TTL_MS)
|
|
91960
92086
|
continue;
|
|
91961
92087
|
try {
|
|
91962
|
-
rmSync19(
|
|
92088
|
+
rmSync19(join90(configSkillsRoot, ent), { recursive: true, force: true });
|
|
91963
92089
|
} catch {}
|
|
91964
92090
|
}
|
|
91965
92091
|
} catch {}
|
|
@@ -91968,11 +92094,11 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
91968
92094
|
const configSkillsRoot = resolveConfigSkillsDir(agent);
|
|
91969
92095
|
if (!configSkillsRoot)
|
|
91970
92096
|
return;
|
|
91971
|
-
const dest =
|
|
92097
|
+
const dest = join90(configSkillsRoot, name);
|
|
91972
92098
|
try {
|
|
91973
92099
|
if (liveSkillDir !== null) {
|
|
91974
92100
|
try {
|
|
91975
|
-
const st =
|
|
92101
|
+
const st = lstatSync12(liveSkillDir);
|
|
91976
92102
|
if (st.isSymbolicLink()) {
|
|
91977
92103
|
process.stderr.write(source_default.yellow(`warning: refusing to mirror ${liveSkillDir} \u2014 source is a symlink
|
|
91978
92104
|
`));
|
|
@@ -91982,20 +92108,20 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
91982
92108
|
}
|
|
91983
92109
|
if (liveSkillDir === null) {
|
|
91984
92110
|
sweepMirrorPriors(configSkillsRoot);
|
|
91985
|
-
if (
|
|
91986
|
-
const trash =
|
|
92111
|
+
if (existsSync92(dest)) {
|
|
92112
|
+
const trash = join90(configSkillsRoot, `.${name}-trash-${Date.now()}`);
|
|
91987
92113
|
renameSync22(dest, trash);
|
|
91988
92114
|
}
|
|
91989
92115
|
return;
|
|
91990
92116
|
}
|
|
91991
92117
|
mkdirSync51(configSkillsRoot, { recursive: true, mode: 493 });
|
|
91992
92118
|
sweepMirrorPriors(configSkillsRoot);
|
|
91993
|
-
const staging = mkdtempSync6(
|
|
92119
|
+
const staging = mkdtempSync6(join90(configSkillsRoot, `.${name}-staging-`));
|
|
91994
92120
|
const walk2 = (src, dst) => {
|
|
91995
92121
|
mkdirSync51(dst, { recursive: true, mode: 493 });
|
|
91996
92122
|
for (const ent of readdirSync34(src, { withFileTypes: true })) {
|
|
91997
|
-
const s =
|
|
91998
|
-
const d =
|
|
92123
|
+
const s = join90(src, ent.name);
|
|
92124
|
+
const d = join90(dst, ent.name);
|
|
91999
92125
|
if (ent.isSymbolicLink())
|
|
92000
92126
|
continue;
|
|
92001
92127
|
if (ent.isDirectory())
|
|
@@ -92006,8 +92132,8 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
92006
92132
|
}
|
|
92007
92133
|
};
|
|
92008
92134
|
walk2(liveSkillDir, staging);
|
|
92009
|
-
if (
|
|
92010
|
-
const prior =
|
|
92135
|
+
if (existsSync92(dest)) {
|
|
92136
|
+
const prior = join90(configSkillsRoot, `.${name}-prior-${Date.now()}`);
|
|
92011
92137
|
renameSync22(dest, prior);
|
|
92012
92138
|
}
|
|
92013
92139
|
renameSync22(staging, dest);
|
|
@@ -92037,17 +92163,17 @@ function resolveAgent(opts) {
|
|
|
92037
92163
|
function resolveAgentsRoot(opts) {
|
|
92038
92164
|
if (opts.root)
|
|
92039
92165
|
return resolve54(opts.root);
|
|
92040
|
-
return
|
|
92166
|
+
return join90(homedir52(), ".switchroom", "agents");
|
|
92041
92167
|
}
|
|
92042
92168
|
function personalSkillDir(agentsRoot, agent, name) {
|
|
92043
|
-
return
|
|
92169
|
+
return join90(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
|
|
92044
92170
|
}
|
|
92045
92171
|
function trashDir(agentsRoot, agent) {
|
|
92046
|
-
return
|
|
92172
|
+
return join90(agentsRoot, agent, ".claude", TRASH_DIRNAME);
|
|
92047
92173
|
}
|
|
92048
92174
|
function countPersonalSkills(agentsRoot, agent) {
|
|
92049
|
-
const skillsDir =
|
|
92050
|
-
if (!
|
|
92175
|
+
const skillsDir = join90(agentsRoot, agent, ".claude", "skills");
|
|
92176
|
+
if (!existsSync92(skillsDir))
|
|
92051
92177
|
return 0;
|
|
92052
92178
|
let n = 0;
|
|
92053
92179
|
for (const ent of readdirSync34(skillsDir, { withFileTypes: true })) {
|
|
@@ -92078,13 +92204,13 @@ function readStdinSync2() {
|
|
|
92078
92204
|
}
|
|
92079
92205
|
function loadFromDir2(dir) {
|
|
92080
92206
|
const abs = resolve54(dir);
|
|
92081
|
-
if (!
|
|
92207
|
+
if (!statSync50(abs).isDirectory()) {
|
|
92082
92208
|
fail4(`--from path is not a directory: ${dir}`);
|
|
92083
92209
|
}
|
|
92084
92210
|
const files = {};
|
|
92085
92211
|
const walk2 = (sub) => {
|
|
92086
92212
|
for (const ent of readdirSync34(sub, { withFileTypes: true })) {
|
|
92087
|
-
const full =
|
|
92213
|
+
const full = join90(sub, ent.name);
|
|
92088
92214
|
if (ent.isSymbolicLink()) {
|
|
92089
92215
|
fail4(`refusing to read symlink in --from dir: ${relative3(abs, full)}`);
|
|
92090
92216
|
}
|
|
@@ -92137,8 +92263,8 @@ function behavioralValidate(files) {
|
|
|
92137
92263
|
errors2.push(`${path8} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
|
|
92138
92264
|
}
|
|
92139
92265
|
} else if (PY_SCRIPT_RE.test(path8)) {
|
|
92140
|
-
const tmp = mkdtempSync6(
|
|
92141
|
-
const tmpPy =
|
|
92266
|
+
const tmp = mkdtempSync6(join90(tmpdir6(), "skill-personal-py-"));
|
|
92267
|
+
const tmpPy = join90(tmp, "check.py");
|
|
92142
92268
|
try {
|
|
92143
92269
|
writeFileSync33(tmpPy, content);
|
|
92144
92270
|
const r = spawnSync17("python3", ["-m", "py_compile", tmpPy], {
|
|
@@ -92156,15 +92282,15 @@ function behavioralValidate(files) {
|
|
|
92156
92282
|
}
|
|
92157
92283
|
function sweepTrash(agentsRoot, agent) {
|
|
92158
92284
|
const trash = trashDir(agentsRoot, agent);
|
|
92159
|
-
if (!
|
|
92285
|
+
if (!existsSync92(trash))
|
|
92160
92286
|
return;
|
|
92161
92287
|
const now = Date.now();
|
|
92162
92288
|
for (const ent of readdirSync34(trash, { withFileTypes: true })) {
|
|
92163
92289
|
if (!ent.isDirectory())
|
|
92164
92290
|
continue;
|
|
92165
|
-
const entPath =
|
|
92291
|
+
const entPath = join90(trash, ent.name);
|
|
92166
92292
|
try {
|
|
92167
|
-
const st =
|
|
92293
|
+
const st = statSync50(entPath);
|
|
92168
92294
|
if (now - st.mtimeMs > TRASH_TTL_MS) {
|
|
92169
92295
|
rmSync19(entPath, { recursive: true, force: true });
|
|
92170
92296
|
}
|
|
@@ -92174,7 +92300,7 @@ function sweepTrash(agentsRoot, agent) {
|
|
|
92174
92300
|
function writePersonalSkill(targetDir, files) {
|
|
92175
92301
|
let targetIsSymlink = false;
|
|
92176
92302
|
try {
|
|
92177
|
-
const st =
|
|
92303
|
+
const st = lstatSync12(targetDir);
|
|
92178
92304
|
if (st.isSymbolicLink()) {
|
|
92179
92305
|
targetIsSymlink = true;
|
|
92180
92306
|
}
|
|
@@ -92183,11 +92309,11 @@ function writePersonalSkill(targetDir, files) {
|
|
|
92183
92309
|
fail4(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
|
|
92184
92310
|
}
|
|
92185
92311
|
mkdirSync51(dirname31(targetDir), { recursive: true, mode: 493 });
|
|
92186
|
-
const staging = mkdtempSync6(
|
|
92312
|
+
const staging = mkdtempSync6(join90(dirname31(targetDir), `.skill-personal-stage-`));
|
|
92187
92313
|
let oldRename = null;
|
|
92188
92314
|
try {
|
|
92189
92315
|
for (const [path8, content] of Object.entries(files)) {
|
|
92190
|
-
const full =
|
|
92316
|
+
const full = join90(staging, path8);
|
|
92191
92317
|
mkdirSync51(dirname31(full), { recursive: true, mode: 493 });
|
|
92192
92318
|
const fd = openSync17(full, "wx");
|
|
92193
92319
|
try {
|
|
@@ -92202,7 +92328,7 @@ function writePersonalSkill(targetDir, files) {
|
|
|
92202
92328
|
}
|
|
92203
92329
|
let targetExists = false;
|
|
92204
92330
|
try {
|
|
92205
|
-
|
|
92331
|
+
lstatSync12(targetDir);
|
|
92206
92332
|
targetExists = true;
|
|
92207
92333
|
} catch {}
|
|
92208
92334
|
if (targetExists) {
|
|
@@ -92218,9 +92344,9 @@ function writePersonalSkill(targetDir, files) {
|
|
|
92218
92344
|
try {
|
|
92219
92345
|
rmSync19(staging, { recursive: true, force: true });
|
|
92220
92346
|
} catch {}
|
|
92221
|
-
if (oldRename &&
|
|
92347
|
+
if (oldRename && existsSync92(oldRename)) {
|
|
92222
92348
|
try {
|
|
92223
|
-
if (
|
|
92349
|
+
if (existsSync92(targetDir)) {
|
|
92224
92350
|
rmSync19(targetDir, { recursive: true, force: true });
|
|
92225
92351
|
}
|
|
92226
92352
|
renameSync22(oldRename, targetDir);
|
|
@@ -92237,7 +92363,7 @@ function loadValidateWrite(agentsRoot, agent, name, files, ensureNew) {
|
|
|
92237
92363
|
const target = personalSkillDir(agentsRoot, agent, name);
|
|
92238
92364
|
const exists = (() => {
|
|
92239
92365
|
try {
|
|
92240
|
-
|
|
92366
|
+
lstatSync12(target);
|
|
92241
92367
|
return true;
|
|
92242
92368
|
} catch {
|
|
92243
92369
|
return false;
|
|
@@ -92286,10 +92412,10 @@ function loadFiles(opts) {
|
|
|
92286
92412
|
return loadFromStdin2();
|
|
92287
92413
|
}
|
|
92288
92414
|
const p = resolve54(opts.from);
|
|
92289
|
-
if (!
|
|
92415
|
+
if (!existsSync92(p)) {
|
|
92290
92416
|
fail4(`--from path does not exist: ${opts.from}`);
|
|
92291
92417
|
}
|
|
92292
|
-
const st =
|
|
92418
|
+
const st = statSync50(p);
|
|
92293
92419
|
if (st.isDirectory()) {
|
|
92294
92420
|
return loadFromDir2(p);
|
|
92295
92421
|
}
|
|
@@ -92334,10 +92460,10 @@ function editPersonalAction(name, opts) {
|
|
|
92334
92460
|
}
|
|
92335
92461
|
var CLONE_SOURCE_RE = /^(shared|bundled):([a-z0-9][a-z0-9_-]{0,62})$/;
|
|
92336
92462
|
function defaultSharedRoot() {
|
|
92337
|
-
return
|
|
92463
|
+
return join90(homedir52(), ".switchroom", "skills");
|
|
92338
92464
|
}
|
|
92339
92465
|
function defaultBundledRoot() {
|
|
92340
|
-
return
|
|
92466
|
+
return join90(homedir52(), ".switchroom", "skills", "_bundled");
|
|
92341
92467
|
}
|
|
92342
92468
|
function resolveCloneSource(source, opts) {
|
|
92343
92469
|
const m = CLONE_SOURCE_RE.exec(source);
|
|
@@ -92347,11 +92473,11 @@ function resolveCloneSource(source, opts) {
|
|
|
92347
92473
|
const tier = m[1];
|
|
92348
92474
|
const slug = m[2];
|
|
92349
92475
|
const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
|
|
92350
|
-
const dir =
|
|
92351
|
-
if (!
|
|
92476
|
+
const dir = join90(root, slug);
|
|
92477
|
+
if (!existsSync92(dir)) {
|
|
92352
92478
|
fail4(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
|
|
92353
92479
|
}
|
|
92354
|
-
const st =
|
|
92480
|
+
const st = lstatSync12(dir);
|
|
92355
92481
|
if (st.isSymbolicLink()) {
|
|
92356
92482
|
fail4(`clone source ${JSON.stringify(source)} is a symlink at ${dir}; ` + `point clone at the canonical pool path instead`);
|
|
92357
92483
|
}
|
|
@@ -92363,7 +92489,7 @@ function readSourceFiles(dir) {
|
|
|
92363
92489
|
const skipped = [];
|
|
92364
92490
|
const walk2 = (sub) => {
|
|
92365
92491
|
for (const ent of readdirSync34(sub, { withFileTypes: true })) {
|
|
92366
|
-
const full =
|
|
92492
|
+
const full = join90(sub, ent.name);
|
|
92367
92493
|
if (ent.isSymbolicLink()) {
|
|
92368
92494
|
continue;
|
|
92369
92495
|
}
|
|
@@ -92378,7 +92504,7 @@ function readSourceFiles(dir) {
|
|
|
92378
92504
|
continue;
|
|
92379
92505
|
}
|
|
92380
92506
|
try {
|
|
92381
|
-
const st =
|
|
92507
|
+
const st = lstatSync12(full);
|
|
92382
92508
|
if (st.size > CLONE_MAX_FILE_BYTES) {
|
|
92383
92509
|
fail4(`clone source has oversized file ${rel} (${st.size} bytes > ${CLONE_MAX_FILE_BYTES}); ` + `refuse to read`, 3);
|
|
92384
92510
|
}
|
|
@@ -92460,7 +92586,7 @@ function removePersonalAction(name, opts) {
|
|
|
92460
92586
|
}
|
|
92461
92587
|
const target = personalSkillDir(agentsRoot, agent, name);
|
|
92462
92588
|
try {
|
|
92463
|
-
const st =
|
|
92589
|
+
const st = lstatSync12(target);
|
|
92464
92590
|
if (st.isSymbolicLink()) {
|
|
92465
92591
|
fail4(`refusing to remove symlink at ${target}; investigate manually`);
|
|
92466
92592
|
}
|
|
@@ -92474,7 +92600,7 @@ function removePersonalAction(name, opts) {
|
|
|
92474
92600
|
const trashRoot2 = trashDir(agentsRoot, agent);
|
|
92475
92601
|
mkdirSync51(trashRoot2, { recursive: true, mode: 493 });
|
|
92476
92602
|
const ts = Date.now();
|
|
92477
|
-
const trashTarget =
|
|
92603
|
+
const trashTarget = join90(trashRoot2, `${name}-${ts}`);
|
|
92478
92604
|
renameSync22(target, trashTarget);
|
|
92479
92605
|
const now = new Date(ts);
|
|
92480
92606
|
utimesSync(trashTarget, now, now);
|
|
@@ -92493,16 +92619,16 @@ function listPersonalAction(opts) {
|
|
|
92493
92619
|
const agent = resolveAgent(opts);
|
|
92494
92620
|
const agentsRoot = resolveAgentsRoot(opts);
|
|
92495
92621
|
sweepTrash(agentsRoot, agent);
|
|
92496
|
-
const skillsDir =
|
|
92622
|
+
const skillsDir = join90(agentsRoot, agent, ".claude", "skills");
|
|
92497
92623
|
const personal = [];
|
|
92498
|
-
if (
|
|
92624
|
+
if (existsSync92(skillsDir)) {
|
|
92499
92625
|
for (const ent of readdirSync34(skillsDir, { withFileTypes: true })) {
|
|
92500
92626
|
if (!ent.isDirectory())
|
|
92501
92627
|
continue;
|
|
92502
92628
|
if (!ent.name.startsWith(PERSONAL_PREFIX))
|
|
92503
92629
|
continue;
|
|
92504
92630
|
const skillName = ent.name.slice(PERSONAL_PREFIX.length);
|
|
92505
|
-
const skillPath =
|
|
92631
|
+
const skillPath = join90(skillsDir, ent.name);
|
|
92506
92632
|
let fileCount = 0;
|
|
92507
92633
|
let totalBytes = 0;
|
|
92508
92634
|
const walk2 = (sub) => {
|
|
@@ -92510,10 +92636,10 @@ function listPersonalAction(opts) {
|
|
|
92510
92636
|
if (e.isFile()) {
|
|
92511
92637
|
fileCount += 1;
|
|
92512
92638
|
try {
|
|
92513
|
-
totalBytes +=
|
|
92639
|
+
totalBytes += statSync50(join90(sub, e.name)).size;
|
|
92514
92640
|
} catch {}
|
|
92515
92641
|
} else if (e.isDirectory()) {
|
|
92516
|
-
walk2(
|
|
92642
|
+
walk2(join90(sub, e.name));
|
|
92517
92643
|
}
|
|
92518
92644
|
}
|
|
92519
92645
|
};
|
|
@@ -92552,11 +92678,11 @@ function registerSkillPersonalCommands(program3) {
|
|
|
92552
92678
|
// src/cli/self-improve-propose-skill.ts
|
|
92553
92679
|
import { createConnection as createConnection4 } from "node:net";
|
|
92554
92680
|
import { homedir as homedir53 } from "node:os";
|
|
92555
|
-
import { join as
|
|
92681
|
+
import { join as join91 } from "node:path";
|
|
92556
92682
|
import { readFileSync as readFileSync79 } from "node:fs";
|
|
92557
92683
|
var IPC_CONNECT_TIMEOUT_MS = 5000;
|
|
92558
92684
|
function gatewaySocketPath() {
|
|
92559
|
-
return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ?
|
|
92685
|
+
return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ? join91(process.env.TELEGRAM_STATE_DIR, "gateway.sock") : join91(homedir53(), ".claude", "channels", "telegram", "gateway.sock"));
|
|
92560
92686
|
}
|
|
92561
92687
|
function fail5(msg, code = 1) {
|
|
92562
92688
|
console.error(msg);
|
|
@@ -92628,9 +92754,9 @@ function registerSelfImproveProposeSkillCommand(program3) {
|
|
|
92628
92754
|
init_esm();
|
|
92629
92755
|
init_helpers();
|
|
92630
92756
|
var import_yaml25 = __toESM(require_dist(), 1);
|
|
92631
|
-
import { existsSync as
|
|
92757
|
+
import { existsSync as existsSync93, readdirSync as readdirSync35, readFileSync as readFileSync80, statSync as statSync51 } from "node:fs";
|
|
92632
92758
|
import { homedir as homedir54 } from "node:os";
|
|
92633
|
-
import { join as
|
|
92759
|
+
import { join as join92, resolve as resolve55 } from "node:path";
|
|
92634
92760
|
var PERSONAL_PREFIX2 = "personal-";
|
|
92635
92761
|
var BUNDLED_SUBDIR = "_bundled";
|
|
92636
92762
|
var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
|
|
@@ -92644,8 +92770,8 @@ function defaultBundledRoot2() {
|
|
|
92644
92770
|
return resolve55(homedir54(), ".switchroom/skills/_bundled");
|
|
92645
92771
|
}
|
|
92646
92772
|
function readSkillFrontmatter(skillDir) {
|
|
92647
|
-
const mdPath =
|
|
92648
|
-
if (!
|
|
92773
|
+
const mdPath = join92(skillDir, "SKILL.md");
|
|
92774
|
+
if (!existsSync93(mdPath))
|
|
92649
92775
|
return null;
|
|
92650
92776
|
let content;
|
|
92651
92777
|
try {
|
|
@@ -92677,9 +92803,9 @@ function readSkillFrontmatter(skillDir) {
|
|
|
92677
92803
|
return { fm: parsed };
|
|
92678
92804
|
}
|
|
92679
92805
|
function statSkillMd(skillDir) {
|
|
92680
|
-
const mdPath =
|
|
92806
|
+
const mdPath = join92(skillDir, "SKILL.md");
|
|
92681
92807
|
try {
|
|
92682
|
-
const st =
|
|
92808
|
+
const st = statSync51(mdPath);
|
|
92683
92809
|
return { size: st.size, mtime: st.mtime.toISOString() };
|
|
92684
92810
|
} catch {
|
|
92685
92811
|
return null;
|
|
@@ -92688,8 +92814,8 @@ function statSkillMd(skillDir) {
|
|
|
92688
92814
|
function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
92689
92815
|
if (!AGENT_NAME_RE3.test(agent))
|
|
92690
92816
|
return [];
|
|
92691
|
-
const skillsDir =
|
|
92692
|
-
if (!
|
|
92817
|
+
const skillsDir = join92(agentsRoot, agent, ".claude/skills");
|
|
92818
|
+
if (!existsSync93(skillsDir))
|
|
92693
92819
|
return [];
|
|
92694
92820
|
const out = [];
|
|
92695
92821
|
let entries;
|
|
@@ -92701,9 +92827,9 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
92701
92827
|
for (const ent of entries) {
|
|
92702
92828
|
if (!ent.startsWith(PERSONAL_PREFIX2))
|
|
92703
92829
|
continue;
|
|
92704
|
-
const dirPath =
|
|
92830
|
+
const dirPath = join92(skillsDir, ent);
|
|
92705
92831
|
try {
|
|
92706
|
-
if (!
|
|
92832
|
+
if (!statSync51(dirPath).isDirectory())
|
|
92707
92833
|
continue;
|
|
92708
92834
|
} catch {
|
|
92709
92835
|
continue;
|
|
@@ -92727,7 +92853,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
92727
92853
|
return out;
|
|
92728
92854
|
}
|
|
92729
92855
|
function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
92730
|
-
if (!
|
|
92856
|
+
if (!existsSync93(sharedRoot))
|
|
92731
92857
|
return [];
|
|
92732
92858
|
const out = [];
|
|
92733
92859
|
let entries;
|
|
@@ -92741,9 +92867,9 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
|
92741
92867
|
continue;
|
|
92742
92868
|
if (ent.startsWith("."))
|
|
92743
92869
|
continue;
|
|
92744
|
-
const dirPath =
|
|
92870
|
+
const dirPath = join92(sharedRoot, ent);
|
|
92745
92871
|
try {
|
|
92746
|
-
if (!
|
|
92872
|
+
if (!statSync51(dirPath).isDirectory())
|
|
92747
92873
|
continue;
|
|
92748
92874
|
} catch {
|
|
92749
92875
|
continue;
|
|
@@ -92765,7 +92891,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
|
92765
92891
|
return out;
|
|
92766
92892
|
}
|
|
92767
92893
|
function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
92768
|
-
if (!
|
|
92894
|
+
if (!existsSync93(bundledRoot))
|
|
92769
92895
|
return [];
|
|
92770
92896
|
const out = [];
|
|
92771
92897
|
let entries;
|
|
@@ -92777,9 +92903,9 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
|
92777
92903
|
for (const ent of entries) {
|
|
92778
92904
|
if (ent.startsWith("."))
|
|
92779
92905
|
continue;
|
|
92780
|
-
const dirPath =
|
|
92906
|
+
const dirPath = join92(bundledRoot, ent);
|
|
92781
92907
|
try {
|
|
92782
|
-
if (!
|
|
92908
|
+
if (!statSync51(dirPath).isDirectory())
|
|
92783
92909
|
continue;
|
|
92784
92910
|
} catch {
|
|
92785
92911
|
continue;
|
|
@@ -92923,18 +93049,18 @@ init_source();
|
|
|
92923
93049
|
init_helpers();
|
|
92924
93050
|
init_operator_uid();
|
|
92925
93051
|
import {
|
|
92926
|
-
existsSync as
|
|
93052
|
+
existsSync as existsSync95,
|
|
92927
93053
|
mkdirSync as mkdirSync52,
|
|
92928
93054
|
readdirSync as readdirSync36,
|
|
92929
93055
|
readFileSync as readFileSync82,
|
|
92930
93056
|
writeFileSync as writeFileSync34,
|
|
92931
|
-
statSync as
|
|
92932
|
-
lstatSync as
|
|
93057
|
+
statSync as statSync52,
|
|
93058
|
+
lstatSync as lstatSync13,
|
|
92933
93059
|
realpathSync as realpathSync8,
|
|
92934
93060
|
copyFileSync as copyFileSync13
|
|
92935
93061
|
} from "node:fs";
|
|
92936
93062
|
import { homedir as homedir55 } from "node:os";
|
|
92937
|
-
import { join as
|
|
93063
|
+
import { join as join93 } from "node:path";
|
|
92938
93064
|
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
92939
93065
|
|
|
92940
93066
|
// src/cli/singleton-stale-cleanup.ts
|
|
@@ -93283,10 +93409,10 @@ function resolveHostdHostHome(env2 = process.env, home2 = homedir55()) {
|
|
|
93283
93409
|
return resolved;
|
|
93284
93410
|
}
|
|
93285
93411
|
function resolveHostdSkillsTarget(hostHome) {
|
|
93286
|
-
const skillsPath =
|
|
93412
|
+
const skillsPath = join93(hostHome, ".switchroom", "skills");
|
|
93287
93413
|
let st;
|
|
93288
93414
|
try {
|
|
93289
|
-
st =
|
|
93415
|
+
st = lstatSync13(skillsPath);
|
|
93290
93416
|
} catch {
|
|
93291
93417
|
return;
|
|
93292
93418
|
}
|
|
@@ -93300,21 +93426,21 @@ function resolveHostdSkillsTarget(hostHome) {
|
|
|
93300
93426
|
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.`);
|
|
93301
93427
|
return;
|
|
93302
93428
|
}
|
|
93303
|
-
if (!
|
|
93429
|
+
if (!existsSync95(target)) {
|
|
93304
93430
|
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.`);
|
|
93305
93431
|
return;
|
|
93306
93432
|
}
|
|
93307
93433
|
return target;
|
|
93308
93434
|
}
|
|
93309
93435
|
function hostdDir() {
|
|
93310
|
-
return
|
|
93436
|
+
return join93(homedir55(), ".switchroom", "hostd");
|
|
93311
93437
|
}
|
|
93312
93438
|
function hostdComposePath() {
|
|
93313
|
-
return
|
|
93439
|
+
return join93(hostdDir(), "docker-compose.yml");
|
|
93314
93440
|
}
|
|
93315
93441
|
function backupExistingCompose() {
|
|
93316
93442
|
const p = hostdComposePath();
|
|
93317
|
-
if (!
|
|
93443
|
+
if (!existsSync95(p))
|
|
93318
93444
|
return null;
|
|
93319
93445
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
93320
93446
|
const bak = `${p}.bak-${ts}`;
|
|
@@ -93412,7 +93538,7 @@ function doStatus() {
|
|
|
93412
93538
|
const composeYml = hostdComposePath();
|
|
93413
93539
|
console.log(source_default.bold("switchroom-hostd"));
|
|
93414
93540
|
console.log("");
|
|
93415
|
-
if (!
|
|
93541
|
+
if (!existsSync95(composeYml)) {
|
|
93416
93542
|
console.log(source_default.yellow(" compose: not installed"));
|
|
93417
93543
|
console.log(source_default.dim(" run `switchroom hostd install` to set up."));
|
|
93418
93544
|
return;
|
|
@@ -93433,15 +93559,15 @@ function doStatus() {
|
|
|
93433
93559
|
} else {
|
|
93434
93560
|
console.log(source_default.green(` container: ${ps.stdout.trim()}`));
|
|
93435
93561
|
}
|
|
93436
|
-
if (
|
|
93562
|
+
if (existsSync95(dir)) {
|
|
93437
93563
|
const entries = [];
|
|
93438
93564
|
try {
|
|
93439
93565
|
for (const name of readdirSync36(dir)) {
|
|
93440
93566
|
if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
|
|
93441
93567
|
continue;
|
|
93442
|
-
const sockPath =
|
|
93443
|
-
if (
|
|
93444
|
-
const st =
|
|
93568
|
+
const sockPath = join93(dir, name, "sock");
|
|
93569
|
+
if (existsSync95(sockPath)) {
|
|
93570
|
+
const st = statSync52(sockPath);
|
|
93445
93571
|
if ((st.mode & 61440) === 49152) {
|
|
93446
93572
|
entries.push(`${name} \u2192 ${sockPath}`);
|
|
93447
93573
|
}
|
|
@@ -93459,7 +93585,7 @@ function doStatus() {
|
|
|
93459
93585
|
}
|
|
93460
93586
|
function doUninstall() {
|
|
93461
93587
|
const composeYml = hostdComposePath();
|
|
93462
|
-
if (!
|
|
93588
|
+
if (!existsSync95(composeYml)) {
|
|
93463
93589
|
console.log(source_default.yellow(" No hostd install detected (no compose file at this path)."));
|
|
93464
93590
|
return;
|
|
93465
93591
|
}
|
|
@@ -93483,7 +93609,7 @@ function registerHostdCommand(program3) {
|
|
|
93483
93609
|
hostd.command("uninstall").description("Stop the hostd container. Leaves the compose file in place for re-install.").action(() => doUninstall());
|
|
93484
93610
|
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) => {
|
|
93485
93611
|
const logPath = opts.path ?? defaultAuditLogPath2();
|
|
93486
|
-
if (!
|
|
93612
|
+
if (!existsSync95(logPath)) {
|
|
93487
93613
|
console.error(source_default.yellow(`Audit log not found at ${logPath}.`) + source_default.gray(`
|
|
93488
93614
|
The log is created when hostd handles its first privileged-verb request.`));
|
|
93489
93615
|
return;
|
|
@@ -93531,9 +93657,9 @@ The log is created when hostd handles its first privileged-verb request.`));
|
|
|
93531
93657
|
init_source();
|
|
93532
93658
|
init_helpers();
|
|
93533
93659
|
init_operator_uid();
|
|
93534
|
-
import { chownSync as chownSync9, existsSync as
|
|
93660
|
+
import { chownSync as chownSync9, existsSync as existsSync96, mkdirSync as mkdirSync53, writeFileSync as writeFileSync35, copyFileSync as copyFileSync14 } from "node:fs";
|
|
93535
93661
|
import { homedir as homedir56 } from "node:os";
|
|
93536
|
-
import { join as
|
|
93662
|
+
import { join as join94 } from "node:path";
|
|
93537
93663
|
import { spawnSync as spawnSync21 } from "node:child_process";
|
|
93538
93664
|
function resolveWebImageTag(explicitTag, release) {
|
|
93539
93665
|
if (explicitTag)
|
|
@@ -93628,14 +93754,14 @@ services:
|
|
|
93628
93754
|
`;
|
|
93629
93755
|
}
|
|
93630
93756
|
function webdDir() {
|
|
93631
|
-
return
|
|
93757
|
+
return join94(homedir56(), ".switchroom", "web");
|
|
93632
93758
|
}
|
|
93633
93759
|
function webdComposePath() {
|
|
93634
|
-
return
|
|
93760
|
+
return join94(webdDir(), "docker-compose.yml");
|
|
93635
93761
|
}
|
|
93636
93762
|
function backupExistingCompose2() {
|
|
93637
93763
|
const p = webdComposePath();
|
|
93638
|
-
if (!
|
|
93764
|
+
if (!existsSync96(p))
|
|
93639
93765
|
return null;
|
|
93640
93766
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
93641
93767
|
const bak = `${p}.bak-${ts}`;
|
|
@@ -93734,7 +93860,7 @@ function doStatus2() {
|
|
|
93734
93860
|
const composeYml = webdComposePath();
|
|
93735
93861
|
console.log(source_default.bold("switchroom-web"));
|
|
93736
93862
|
console.log("");
|
|
93737
|
-
if (!
|
|
93863
|
+
if (!existsSync96(composeYml)) {
|
|
93738
93864
|
console.log(source_default.yellow(" compose: not installed"));
|
|
93739
93865
|
console.log(source_default.dim(" run `switchroom webd install` to set up."));
|
|
93740
93866
|
return;
|
|
@@ -93758,7 +93884,7 @@ function doStatus2() {
|
|
|
93758
93884
|
}
|
|
93759
93885
|
function doUninstall2() {
|
|
93760
93886
|
const composeYml = webdComposePath();
|
|
93761
|
-
if (!
|
|
93887
|
+
if (!existsSync96(composeYml)) {
|
|
93762
93888
|
console.log(source_default.yellow(" No web-service install detected (no compose file at this path)."));
|
|
93763
93889
|
return;
|
|
93764
93890
|
}
|
|
@@ -93788,9 +93914,9 @@ function registerWebdCommand(program3) {
|
|
|
93788
93914
|
// src/cli/host-repair.ts
|
|
93789
93915
|
init_source();
|
|
93790
93916
|
import { homedir as homedir57 } from "node:os";
|
|
93791
|
-
import { join as
|
|
93917
|
+
import { join as join95 } from "node:path";
|
|
93792
93918
|
var ARTIFACT_ALLOWLIST = {
|
|
93793
|
-
dockerComposePluginDir: (home2) =>
|
|
93919
|
+
dockerComposePluginDir: (home2) => join95(home2, ".docker", "cli-plugins", "docker-compose"),
|
|
93794
93920
|
stateSentinel: "/state"
|
|
93795
93921
|
};
|
|
93796
93922
|
function isStateBogusAutoDir(probe2) {
|
|
@@ -93944,11 +94070,11 @@ function applyMountRepairs(items, deps) {
|
|
|
93944
94070
|
function registerHostCommand(program3) {
|
|
93945
94071
|
const host = program3.command("host").description("Host-level maintenance operations for switchroom");
|
|
93946
94072
|
host.command("repair-mounts").description("Detect and remove known auto-dir artifacts left by a container-context deploy " + "(2026-06-23 outage class). Default: dry-run. Pass --yes to apply.").option("--yes", "Actually perform the removals (default is dry-run)").action(async (opts) => {
|
|
93947
|
-
const { rmdirSync: rmdirSync2, rmSync: rmSync20, lstatSync:
|
|
94073
|
+
const { rmdirSync: rmdirSync2, rmSync: rmSync20, lstatSync: lstatSync14, readdirSync: readdirSync37 } = await import("node:fs");
|
|
93948
94074
|
const probe2 = {
|
|
93949
94075
|
lstat(path8) {
|
|
93950
94076
|
try {
|
|
93951
|
-
return
|
|
94077
|
+
return lstatSync14(path8);
|
|
93952
94078
|
} catch {
|
|
93953
94079
|
return null;
|
|
93954
94080
|
}
|