switchroom 0.18.15 → 0.18.18
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 +16 -0
- package/dist/auth-broker/index.js +445 -10
- package/dist/cli/notion-write-pretool.mjs +16 -0
- package/dist/cli/switchroom.js +654 -479
- package/dist/host-control/main.js +20 -1
- package/dist/vault/approvals/kernel-server.js +16 -0
- package/dist/vault/broker/server.js +16 -0
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +81 -139
- package/telegram-plugin/bridge/bridge.ts +7 -1
- package/telegram-plugin/dist/bridge/bridge.js +26 -1
- package/telegram-plugin/dist/gateway/gateway.js +1758 -661
- package/telegram-plugin/dist/server.js +26 -1
- package/telegram-plugin/draft-stream.ts +78 -3
- package/telegram-plugin/fleet-fallback-resume.ts +26 -3
- package/telegram-plugin/gateway/approval-hold.ts +49 -0
- package/telegram-plugin/gateway/bridge-dead-watchdog.ts +64 -22
- package/telegram-plugin/gateway/effort-command.ts +9 -7
- package/telegram-plugin/gateway/gateway.ts +627 -291
- package/telegram-plugin/gateway/linear-activity.ts +20 -4
- package/telegram-plugin/gateway/litellm-local-notice-wiring.ts +200 -0
- package/telegram-plugin/gateway/model-command.ts +96 -18
- package/telegram-plugin/gateway/pending-session-command.ts +10 -8
- package/telegram-plugin/gateway/premium-recovery-wiring.ts +122 -0
- package/telegram-plugin/gateway/session-model-file.ts +141 -172
- package/telegram-plugin/gateway/tier-downgrade-wiring.ts +121 -0
- package/telegram-plugin/gateway/unhandled-rejection-policy.ts +14 -1
- package/telegram-plugin/litellm-local-notice.ts +189 -0
- package/telegram-plugin/llm-error-present.ts +436 -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/quota-watch.ts +16 -4
- package/telegram-plugin/raw-error-scrub.ts +73 -0
- package/telegram-plugin/retry-api-call.ts +8 -2
- package/telegram-plugin/runtime-metrics.ts +16 -0
- package/telegram-plugin/send-gate-degraded.test.ts +161 -8
- 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 +246 -23
- package/telegram-plugin/session-tail.ts +16 -0
- package/telegram-plugin/shared/local-time.ts +69 -0
- package/telegram-plugin/stream-controller.ts +143 -20
- package/telegram-plugin/stream-reply-handler.ts +12 -2
- 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/bot-api.harness.ts +7 -2
- package/telegram-plugin/tests/bridge-dead-watchdog.test.ts +61 -0
- package/telegram-plugin/tests/draft-stream.test.ts +110 -1
- package/telegram-plugin/tests/effort-command.test.ts +4 -4
- package/telegram-plugin/tests/fleet-fallback-resume.test.ts +39 -0
- package/telegram-plugin/tests/flood-windows-persistence.test.ts +5 -4
- package/telegram-plugin/tests/gateway-pending-command-wiring.test.ts +33 -19
- package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +47 -127
- package/telegram-plugin/tests/linear-create-issue.test.ts +30 -2
- package/telegram-plugin/tests/litellm-local-notice.test.ts +417 -0
- package/telegram-plugin/tests/llm-error-present.test.ts +380 -0
- package/telegram-plugin/tests/model-command.test.ts +84 -1
- 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/quota-watch.test.ts +21 -0
- package/telegram-plugin/tests/reaction-gate-routing.test.ts +8 -3
- package/telegram-plugin/tests/retry-api-call.test.ts +21 -0
- package/telegram-plugin/tests/session-model-file.test.ts +7 -155
- package/telegram-plugin/tests/stream-controller-send-gate.test.ts +521 -0
- package/telegram-plugin/tests/stream-reply-handler.test.ts +44 -0
- 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/unhandled-rejection-policy.test.ts +27 -1
- package/telegram-plugin/tests/worker-activity-feed.test.ts +212 -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/worker-activity-feed.ts +543 -368
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.18", COMMIT_SHA = "a256b3b1";
|
|
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."),
|
|
@@ -13843,6 +13856,9 @@ var init_schema = __esm(() => {
|
|
|
13843
13856
|
window_ms: exports_external.number().int().nonnegative().optional().describe("Sliding-window (ms) for merging consecutive inbound messages from " + "the same sender+topic into ONE Claude turn. Each new message resets " + "the timer; the turn starts once the sender pauses for this long. " + "Catches forwarded bursts, pasted text the Telegram client split " + "into several messages, and mixed text+media forwards. Default 500. " + "Set 0 to disable (every message becomes its own turn). Raise for " + "users who think in multiple short messages; the trade-off is the " + "single-message turn start is delayed by this much (the \uD83D\uDC40 ack still " + "fires immediately, so perceived latency is unchanged)."),
|
|
13844
13857
|
max_attachments: exports_external.number().int().positive().optional().describe("Maximum number of media attachments carried into ONE coalesced " + "Claude turn. Default 10 \u2014 a full Telegram album (media_group caps " + "at 10) or a text+multi-image forwarded burst arrives as a single " + "turn; the agent sees numbered attachment fields (image_path, " + "image_path_2, \u2026). Set 1 to restore the historical " + "single-attachment-per-turn behaviour. Excess attachments beyond " + "the cap spill into the next turn. Each attachment is downloaded, " + "so a high cap on a slow link delays turn start.")
|
|
13845
13858
|
}).optional().describe("Inbound coalescing \u2014 how the gateway groups rapid consecutive messages " + "into a single turn so a forwarded album or split paste doesn't fan out " + "into N separate turns. Cascades from defaults.channels.telegram.coalesce."),
|
|
13859
|
+
litellm_notice: exports_external.object({
|
|
13860
|
+
window_ms: exports_external.number().int().positive().optional().describe("Per-agent cooldown window (ms) for the litellm-local 429 notice. " + "When the agent trips the LiteLLM proxy's OWN tpm_limit/rpm_limit " + "cap (a `litellm-local` classified 429 \u2014 see docs/auth.md \u00a7 " + "LiteLLM-proxy-local 429s), the gateway posts ONE calm notice " + "naming the fleet token limiter, then counts further hits silently " + "for this long; the first notice after the window expires says " + "how many were absorbed. Default 900000 (15 min). Invalid values " + "fall back to the default.")
|
|
13861
|
+
}).optional().describe("Debounce tuning for the litellm-local throttle notice \u2014 the calm " + "'fleet token limiter engaged' message posted when the LiteLLM " + "proxy's own rate cap trips (never an Anthropic account limit). " + "Cascades from defaults.channels.telegram.litellm_notice."),
|
|
13846
13862
|
interrupt: exports_external.object({
|
|
13847
13863
|
safe_boundary: exports_external.boolean().optional().describe("When true (the default), a `!`-prefix interrupt that arrives while " + "the agent is mid-tool-call is DEFERRED: the SIGINT and the " + "replacement turn wait until the in-flight tool call finishes (a " + "clean boundary) instead of C-c'ing the agent mid-write/mid-bash. If " + "no tool is in flight the interrupt still fires immediately. Bounded " + "by max_wait_ms so a long tool never strands the user. Set false to " + "fire synchronously the moment `!` is received (historical " + "behaviour). Rapid repeated `!` while one is pending coalesce into a " + "single deferred interrupt carrying the latest body."),
|
|
13848
13864
|
max_wait_ms: exports_external.number().int().positive().optional().describe("Upper bound (ms) the gateway waits for a safe boundary before firing " + "a deferred `!` interrupt anyway. Only consulted when safe_boundary is " + "true. Default 8000. Keep it short \u2014 the user explicitly asked to " + "interrupt, so a long in-flight tool shouldn't ghost them; the cap " + "trades a tiny risk of a mid-tool C-c for a guaranteed response.")
|
|
@@ -17113,18 +17129,18 @@ function alignAgentTreeOwnershipIfRoot(name, agentDir) {
|
|
|
17113
17129
|
}
|
|
17114
17130
|
return uid;
|
|
17115
17131
|
}
|
|
17116
|
-
function findAgentUnreadablePaths(candidates, agentUid) {
|
|
17132
|
+
function findAgentUnreadablePaths(candidates, agentUid, fs = { lstatSync, statSync: statSync3 }) {
|
|
17117
17133
|
const bad = [];
|
|
17118
17134
|
for (const p of candidates) {
|
|
17119
17135
|
let ls;
|
|
17120
17136
|
try {
|
|
17121
|
-
ls = lstatSync(p);
|
|
17137
|
+
ls = fs.lstatSync(p);
|
|
17122
17138
|
} catch {
|
|
17123
17139
|
continue;
|
|
17124
17140
|
}
|
|
17125
17141
|
if (ls.isSymbolicLink())
|
|
17126
17142
|
continue;
|
|
17127
|
-
const st =
|
|
17143
|
+
const st = fs.statSync(p);
|
|
17128
17144
|
if (st.uid === agentUid)
|
|
17129
17145
|
continue;
|
|
17130
17146
|
const otherReadable = st.isDirectory() ? (st.mode & 5) === 5 : (st.mode & 4) === 4;
|
|
@@ -26683,6 +26699,37 @@ function channelsToEnv(agent) {
|
|
|
26683
26699
|
if (tg.edit_budget_threshold !== undefined) {
|
|
26684
26700
|
out.SWITCHROOM_TG_EDIT_BUDGET_THRESHOLD = String(tg.edit_budget_threshold);
|
|
26685
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
|
+
}
|
|
26686
26733
|
if (tg.clear_status_on_completion !== undefined) {
|
|
26687
26734
|
out.SWITCHROOM_TG_CLEAR_STATUS_ON_COMPLETION = tg.clear_status_on_completion ? "1" : "0";
|
|
26688
26735
|
}
|
|
@@ -28995,6 +29042,9 @@ function buildAccessJson2(agentConfig, telegramConfig, resolvedTopicId, userId)
|
|
|
28995
29042
|
if (typeof tg?.coalesce?.max_attachments === "number") {
|
|
28996
29043
|
access.coalesceMaxAttachments = tg.coalesce.max_attachments;
|
|
28997
29044
|
}
|
|
29045
|
+
if (typeof tg?.litellm_notice?.window_ms === "number") {
|
|
29046
|
+
access.litellmNoticeWindowMs = tg.litellm_notice.window_ms;
|
|
29047
|
+
}
|
|
28998
29048
|
if (typeof tg?.interrupt?.safe_boundary === "boolean") {
|
|
28999
29049
|
access.interruptSafeBoundary = tg.interrupt.safe_boundary;
|
|
29000
29050
|
}
|
|
@@ -29763,6 +29813,7 @@ function generateCompose(opts) {
|
|
|
29763
29813
|
const hostControlEnabled = config.host_control?.enabled !== false;
|
|
29764
29814
|
const hostHomeForChecks = opts.homeDir ?? process.env.HOME ?? "";
|
|
29765
29815
|
const probeHome = opts.probeHomeDir ?? hostHomeForChecks;
|
|
29816
|
+
const precreateHostDirs = opts.precreateHostDirs ?? (opts.homeDir !== undefined || opts.probeHomeDir !== undefined);
|
|
29766
29817
|
const switchroomConfigPath = resolveConfigMountSource(opts.switchroomConfigPath, homePrefix);
|
|
29767
29818
|
const bundledSkillsPoolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
|
|
29768
29819
|
const voiceEngine = opts.voiceEngine ?? loadHostCapabilities()?.voice.engine ?? "cloud";
|
|
@@ -29967,7 +30018,7 @@ function generateCompose(opts) {
|
|
|
29967
30018
|
telemetryDisabled,
|
|
29968
30019
|
posthogKeyOverride,
|
|
29969
30020
|
posthogHostOverride
|
|
29970
|
-
}, bundledSkillsPoolDir, hostControlEnabled, opts.operatorUid, voiceEngine);
|
|
30021
|
+
}, bundledSkillsPoolDir, hostControlEnabled, opts.operatorUid, voiceEngine, precreateHostDirs);
|
|
29971
30022
|
}
|
|
29972
30023
|
lines.push(`volumes:`);
|
|
29973
30024
|
for (const a of describeAgents(config, opts.litellmConfirmedAgents)) {
|
|
@@ -30001,7 +30052,7 @@ function generateCompose(opts) {
|
|
|
30001
30052
|
return lines.join(`
|
|
30002
30053
|
`);
|
|
30003
30054
|
}
|
|
30004
|
-
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) {
|
|
30005
30056
|
lines.push(` agent-${a.name}:`);
|
|
30006
30057
|
emitImageOrBuild(lines, "agent", imageTag, buildMode, buildContext);
|
|
30007
30058
|
lines.push(` container_name: ${containerNamePrefix}-${a.name}`);
|
|
@@ -30164,22 +30215,30 @@ function emitAgentService(lines, a, imageTag, buildMode, buildContext, homePrefi
|
|
|
30164
30215
|
if (conditionalMountPresent(`${probeHome}/.switchroom/credentials/${a.name}`, hostHomeForChecks, probeHome)) {
|
|
30165
30216
|
lines.push(` - ${homePrefix}/.switchroom/credentials/${a.name}:${homePrefix}/.switchroom/credentials:ro`);
|
|
30166
30217
|
}
|
|
30167
|
-
|
|
30168
|
-
mkdirSync12(`${probeHome}/.switchroom/audit/${a.name}`, { recursive: true });
|
|
30169
|
-
} catch {}
|
|
30170
|
-
try {
|
|
30171
|
-
const blockedDir = `${probeHome}/.switchroom/blocked-approvals`;
|
|
30172
|
-
mkdirSync12(blockedDir, { recursive: true });
|
|
30173
|
-
chmodSync3(blockedDir, 1023);
|
|
30174
|
-
} catch {}
|
|
30175
|
-
try {
|
|
30176
|
-
mkdirSync12(`${probeHome}/.switchroom/agents/${a.name}/schedule.d`, { recursive: true });
|
|
30177
|
-
} catch {}
|
|
30178
|
-
lines.push(` - ${homePrefix}/.switchroom/audit/${a.name}:${homePrefix}/.switchroom/audit/${a.name}:rw`);
|
|
30179
|
-
if (conditionalMountPresent(`${probeHome}/.switchroom-config`, hostHomeForChecks, probeHome)) {
|
|
30218
|
+
if (precreateHostDirs) {
|
|
30180
30219
|
try {
|
|
30181
|
-
mkdirSync12(`${probeHome}/.switchroom
|
|
30220
|
+
mkdirSync12(`${probeHome}/.switchroom/audit/${a.name}`, { recursive: true });
|
|
30182
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
|
+
}
|
|
30183
30242
|
lines.push(` - ${homePrefix}/.switchroom-config/agents/${a.name}/personal-skills:${homePrefix}/.switchroom-config/agents/${a.name}/personal-skills:rw`);
|
|
30184
30243
|
}
|
|
30185
30244
|
if (existsSync18(`${probeHome}/.switchroom/bin/webkite`)) {
|
|
@@ -43707,6 +43766,74 @@ function runCredentialsMigrationChecks(config, deps = {}) {
|
|
|
43707
43766
|
}
|
|
43708
43767
|
var init_doctor_credentials_migration = () => {};
|
|
43709
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
|
+
|
|
43710
43837
|
// src/cli/doctor-inlined-secrets.ts
|
|
43711
43838
|
import { readFileSync as fsReadFileSync } from "node:fs";
|
|
43712
43839
|
function isSecretShapedKey(key) {
|
|
@@ -43804,7 +43931,7 @@ var init_doctor_inlined_secrets = __esm(() => {
|
|
|
43804
43931
|
// src/cli/doctor-approval-attribution.ts
|
|
43805
43932
|
import { execFileSync as execFileSync19 } from "node:child_process";
|
|
43806
43933
|
import { readFileSync as fsReadFileSync2 } from "node:fs";
|
|
43807
|
-
import { join as
|
|
43934
|
+
import { join as join62 } from "node:path";
|
|
43808
43935
|
function parseApproverSet(canonical) {
|
|
43809
43936
|
try {
|
|
43810
43937
|
const arr = JSON.parse(canonical);
|
|
@@ -43924,7 +44051,7 @@ function loadLiveAllowFromAccessFiles(agentNames, agentsDir, readFile2 = (p) =>
|
|
|
43924
44051
|
let readAny = false;
|
|
43925
44052
|
for (const name of agentNames) {
|
|
43926
44053
|
try {
|
|
43927
|
-
const raw = readFile2(
|
|
44054
|
+
const raw = readFile2(join62(agentsDir, name, "telegram", "access.json"));
|
|
43928
44055
|
const parsed = JSON.parse(raw);
|
|
43929
44056
|
readAny = true;
|
|
43930
44057
|
if (Array.isArray(parsed.allowFrom)) {
|
|
@@ -43957,13 +44084,13 @@ var init_doctor_approval_attribution = __esm(() => {
|
|
|
43957
44084
|
// src/cli/doctor-audit-integrity.ts
|
|
43958
44085
|
import { readFileSync as fsReadFileSync3 } from "node:fs";
|
|
43959
44086
|
import { homedir as homedir37 } from "node:os";
|
|
43960
|
-
import { join as
|
|
44087
|
+
import { join as join63 } from "node:path";
|
|
43961
44088
|
function rootWrittenLogs(home2) {
|
|
43962
44089
|
return [
|
|
43963
|
-
{ label: "vault-broker", path:
|
|
44090
|
+
{ label: "vault-broker", path: join63(home2, ".switchroom", "vault-audit.log") },
|
|
43964
44091
|
{
|
|
43965
44092
|
label: "hostd",
|
|
43966
|
-
path:
|
|
44093
|
+
path: join63(home2, ".switchroom", "host-control-audit.log")
|
|
43967
44094
|
}
|
|
43968
44095
|
];
|
|
43969
44096
|
}
|
|
@@ -43972,7 +44099,7 @@ function runAuditIntegrityChecks(deps = {}) {
|
|
|
43972
44099
|
const read = deps.readFileSync ?? ((p) => fsReadFileSync3(p, "utf8"));
|
|
43973
44100
|
const readFailOpen = deps.readFailOpenState ?? readFailOpenState;
|
|
43974
44101
|
const results = [];
|
|
43975
|
-
const vaultAuditLog =
|
|
44102
|
+
const vaultAuditLog = join63(home2, ".switchroom", "vault-audit.log");
|
|
43976
44103
|
const failOpen = readFailOpen(failOpenStatePath(vaultAuditLog));
|
|
43977
44104
|
if (failOpen.failOpenCount > 0) {
|
|
43978
44105
|
const when = failOpen.lastFailureTs ? ` (last at ${failOpen.lastFailureTs})` : ``;
|
|
@@ -44045,16 +44172,16 @@ var init_doctor_audit_integrity = __esm(() => {
|
|
|
44045
44172
|
});
|
|
44046
44173
|
|
|
44047
44174
|
// src/cli/doctor-agent-smoke.ts
|
|
44048
|
-
import { existsSync as
|
|
44175
|
+
import { existsSync as existsSync63 } from "node:fs";
|
|
44049
44176
|
import { homedir as homedir38 } from "node:os";
|
|
44050
|
-
import { join as
|
|
44177
|
+
import { join as join64 } from "node:path";
|
|
44051
44178
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
44052
44179
|
async function runAgentSmokeChecks(config, deps = {}) {
|
|
44053
44180
|
if (deps.fast)
|
|
44054
44181
|
return [];
|
|
44055
44182
|
const home2 = deps.homeDir ?? homedir38();
|
|
44056
|
-
const sock = deps.operatorSockPath ??
|
|
44057
|
-
if (!deps.hostdRequestImpl && !
|
|
44183
|
+
const sock = deps.operatorSockPath ?? join64(home2, ".switchroom", "hostd", "operator", "sock");
|
|
44184
|
+
if (!deps.hostdRequestImpl && !existsSync63(sock)) {
|
|
44058
44185
|
return [
|
|
44059
44186
|
{
|
|
44060
44187
|
name: "agent liveness",
|
|
@@ -44132,9 +44259,9 @@ var init_doctor_agent_smoke = __esm(() => {
|
|
|
44132
44259
|
|
|
44133
44260
|
// src/cli/doctor-vault-broker-durability.ts
|
|
44134
44261
|
import { execFileSync as execFileSync20 } from "node:child_process";
|
|
44135
|
-
import { existsSync as
|
|
44262
|
+
import { existsSync as existsSync64, statSync as statSync37 } from "node:fs";
|
|
44136
44263
|
import { homedir as homedir39 } from "node:os";
|
|
44137
|
-
import { join as
|
|
44264
|
+
import { join as join65 } from "node:path";
|
|
44138
44265
|
function probeBindMountInode(hostPath, brokerContainerPath, opts) {
|
|
44139
44266
|
const statHost = opts?.statHost ?? defaultStatHost;
|
|
44140
44267
|
const statBroker = opts?.statBroker ?? defaultStatBroker;
|
|
@@ -44156,10 +44283,10 @@ function probeBindMountInode(hostPath, brokerContainerPath, opts) {
|
|
|
44156
44283
|
};
|
|
44157
44284
|
}
|
|
44158
44285
|
function defaultStatHost(p) {
|
|
44159
|
-
if (!
|
|
44286
|
+
if (!existsSync64(p))
|
|
44160
44287
|
return null;
|
|
44161
44288
|
try {
|
|
44162
|
-
const s =
|
|
44289
|
+
const s = statSync37(p, { bigint: true });
|
|
44163
44290
|
return { ino: s.ino, size: Number(s.size) };
|
|
44164
44291
|
} catch {
|
|
44165
44292
|
return null;
|
|
@@ -44283,16 +44410,16 @@ function runVaultBrokerDurabilityChecks(_config, opts) {
|
|
|
44283
44410
|
probeBrokerUnlocked(opts?.statusProbe),
|
|
44284
44411
|
probeAutoUnlockBlob(home2),
|
|
44285
44412
|
probeMachineIdMount(),
|
|
44286
|
-
formatBindMountResult("vault-broker: vault.enc bind mount",
|
|
44287
|
-
formatBindMountResult("vault-broker: vault-grants.db bind mount (#1737)",
|
|
44288
|
-
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")),
|
|
44289
44416
|
probeKernelDbDurability(home2, {
|
|
44290
44417
|
statBroker: opts?.kernelStatBroker
|
|
44291
44418
|
})
|
|
44292
44419
|
];
|
|
44293
44420
|
}
|
|
44294
44421
|
function probeKernelDbDurability(home2, opts) {
|
|
44295
|
-
const hostDir =
|
|
44422
|
+
const hostDir = join65(home2, ".switchroom", "approvals");
|
|
44296
44423
|
const containerDir = "/state/approvals";
|
|
44297
44424
|
const name = "approval-kernel: approvals bind mount (allow_always durability)";
|
|
44298
44425
|
const kernelStat = opts?.statBroker ?? defaultKernelStatBroker;
|
|
@@ -44360,8 +44487,8 @@ function defaultKernelStatBroker(p) {
|
|
|
44360
44487
|
return { kind: "ok-with-stat", ino: inoStr, size };
|
|
44361
44488
|
}
|
|
44362
44489
|
function probeAutoUnlockBlob(home2) {
|
|
44363
|
-
const blobPath =
|
|
44364
|
-
if (!
|
|
44490
|
+
const blobPath = join65(home2, ".switchroom", "vault-auto-unlock");
|
|
44491
|
+
if (!existsSync64(blobPath)) {
|
|
44365
44492
|
return {
|
|
44366
44493
|
name: "vault-broker: auto-unlock blob",
|
|
44367
44494
|
status: "warn",
|
|
@@ -44369,7 +44496,7 @@ function probeAutoUnlockBlob(home2) {
|
|
|
44369
44496
|
fix: "Run `switchroom vault broker enable-auto-unlock` to seal the blob with the current passphrase + machine-id"
|
|
44370
44497
|
};
|
|
44371
44498
|
}
|
|
44372
|
-
const sz =
|
|
44499
|
+
const sz = statSync37(blobPath).size;
|
|
44373
44500
|
if (sz === 0) {
|
|
44374
44501
|
return {
|
|
44375
44502
|
name: "vault-broker: auto-unlock blob",
|
|
@@ -44385,7 +44512,7 @@ function probeAutoUnlockBlob(home2) {
|
|
|
44385
44512
|
};
|
|
44386
44513
|
}
|
|
44387
44514
|
function probeMachineIdMount() {
|
|
44388
|
-
const hostExists =
|
|
44515
|
+
const hostExists = existsSync64("/etc/machine-id");
|
|
44389
44516
|
if (!hostExists) {
|
|
44390
44517
|
return {
|
|
44391
44518
|
name: "vault-broker: machine-id passthrough",
|
|
@@ -44536,25 +44663,25 @@ import { Socket as Socket3 } from "node:net";
|
|
|
44536
44663
|
import {
|
|
44537
44664
|
accessSync as accessSync2,
|
|
44538
44665
|
constants as fsConstants5,
|
|
44539
|
-
existsSync as
|
|
44540
|
-
lstatSync as
|
|
44666
|
+
existsSync as existsSync65,
|
|
44667
|
+
lstatSync as lstatSync9,
|
|
44541
44668
|
mkdirSync as mkdirSync33,
|
|
44542
44669
|
readFileSync as readFileSync58,
|
|
44543
44670
|
readdirSync as readdirSync22,
|
|
44544
|
-
statSync as
|
|
44671
|
+
statSync as statSync38
|
|
44545
44672
|
} from "node:fs";
|
|
44546
|
-
import { dirname as dirname19, join as
|
|
44673
|
+
import { dirname as dirname19, join as join66, resolve as resolve38 } from "node:path";
|
|
44547
44674
|
import { createPublicKey, createPrivateKey } from "node:crypto";
|
|
44548
44675
|
function findInNvm(bin) {
|
|
44549
|
-
const nvmRoot =
|
|
44550
|
-
if (!
|
|
44676
|
+
const nvmRoot = join66(process.env.HOME ?? "", ".nvm", "versions", "node");
|
|
44677
|
+
if (!existsSync65(nvmRoot))
|
|
44551
44678
|
return null;
|
|
44552
44679
|
try {
|
|
44553
44680
|
const versions = readdirSync22(nvmRoot).sort().reverse();
|
|
44554
44681
|
for (const v of versions) {
|
|
44555
|
-
const candidate =
|
|
44682
|
+
const candidate = join66(nvmRoot, v, "bin", bin);
|
|
44556
44683
|
try {
|
|
44557
|
-
const s =
|
|
44684
|
+
const s = statSync38(candidate);
|
|
44558
44685
|
if (s.isFile() || s.isSymbolicLink()) {
|
|
44559
44686
|
return candidate;
|
|
44560
44687
|
}
|
|
@@ -44579,7 +44706,7 @@ function whichOnPath(bin) {
|
|
|
44579
44706
|
for (const dir of pathEnv.split(":")) {
|
|
44580
44707
|
if (!dir)
|
|
44581
44708
|
continue;
|
|
44582
|
-
const candidate =
|
|
44709
|
+
const candidate = join66(dir, bin);
|
|
44583
44710
|
if (isX(candidate))
|
|
44584
44711
|
return candidate;
|
|
44585
44712
|
}
|
|
@@ -44745,21 +44872,21 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
|
|
|
44745
44872
|
if (envBrowsersPath && envBrowsersPath.length > 0) {
|
|
44746
44873
|
cacheLocations.push(envBrowsersPath);
|
|
44747
44874
|
}
|
|
44748
|
-
cacheLocations.push(
|
|
44875
|
+
cacheLocations.push(join66(homeDir, ".cache", "ms-playwright"));
|
|
44749
44876
|
for (const cacheDir of cacheLocations) {
|
|
44750
|
-
if (!
|
|
44877
|
+
if (!existsSync65(cacheDir))
|
|
44751
44878
|
continue;
|
|
44752
44879
|
try {
|
|
44753
44880
|
const entries = readdirSync22(cacheDir).filter((e) => e.startsWith("chromium"));
|
|
44754
44881
|
for (const entry of entries) {
|
|
44755
44882
|
const candidates2 = [
|
|
44756
|
-
|
|
44757
|
-
|
|
44758
|
-
|
|
44759
|
-
|
|
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")
|
|
44760
44887
|
];
|
|
44761
44888
|
for (const path4 of candidates2) {
|
|
44762
|
-
if (
|
|
44889
|
+
if (existsSync65(path4))
|
|
44763
44890
|
return path4;
|
|
44764
44891
|
}
|
|
44765
44892
|
}
|
|
@@ -44878,7 +45005,7 @@ function checkUserDeclaredMcps(name, agentConfig, config, renderedMcpServers) {
|
|
|
44878
45005
|
}
|
|
44879
45006
|
function defaultPathKind(p) {
|
|
44880
45007
|
try {
|
|
44881
|
-
const s =
|
|
45008
|
+
const s = lstatSync9(p);
|
|
44882
45009
|
return s.isDirectory() ? "dir" : "file-or-symlink";
|
|
44883
45010
|
} catch {
|
|
44884
45011
|
return "absent";
|
|
@@ -44888,7 +45015,7 @@ function checkDeployMounts(opts) {
|
|
|
44888
45015
|
const home2 = opts?.home ?? process.env.HOME ?? "/root";
|
|
44889
45016
|
const { pathKind } = opts?.deps ?? DEFAULT_DEPLOY_MOUNTS_DEPS;
|
|
44890
45017
|
const results = [];
|
|
44891
|
-
const dockerComposePlugin =
|
|
45018
|
+
const dockerComposePlugin = join66(home2, ".docker", "cli-plugins", "docker-compose");
|
|
44892
45019
|
const pluginKind = pathKind(dockerComposePlugin);
|
|
44893
45020
|
if (pluginKind === "dir") {
|
|
44894
45021
|
results.push({
|
|
@@ -44926,8 +45053,8 @@ function checkDeployMounts(opts) {
|
|
|
44926
45053
|
function checkLegacyState() {
|
|
44927
45054
|
const results = [];
|
|
44928
45055
|
const h = process.env.HOME ?? "/root";
|
|
44929
|
-
const clerkDir =
|
|
44930
|
-
const clerkPresent =
|
|
45056
|
+
const clerkDir = join66(h, LEGACY_STATE_DIR);
|
|
45057
|
+
const clerkPresent = existsSync65(clerkDir);
|
|
44931
45058
|
results.push({
|
|
44932
45059
|
name: "legacy ~/.clerk state",
|
|
44933
45060
|
status: clerkPresent ? "warn" : "ok",
|
|
@@ -44936,10 +45063,10 @@ function checkLegacyState() {
|
|
|
44936
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."
|
|
44937
45064
|
} : {}
|
|
44938
45065
|
});
|
|
44939
|
-
const legacySock =
|
|
45066
|
+
const legacySock = join66(h, ".switchroom", "vault-broker.sock");
|
|
44940
45067
|
let sockStat = null;
|
|
44941
45068
|
try {
|
|
44942
|
-
sockStat =
|
|
45069
|
+
sockStat = lstatSync9(legacySock);
|
|
44943
45070
|
} catch {}
|
|
44944
45071
|
if (sockStat) {
|
|
44945
45072
|
results.push({
|
|
@@ -45057,7 +45184,7 @@ function checkVault(config) {
|
|
|
45057
45184
|
detail: "Approval auth: passphrase (two-factor)"
|
|
45058
45185
|
};
|
|
45059
45186
|
const pairsResult = checkVaultBrokerSocketPairs(config);
|
|
45060
|
-
if (!
|
|
45187
|
+
if (!existsSync65(vaultPath)) {
|
|
45061
45188
|
return [
|
|
45062
45189
|
postureResult,
|
|
45063
45190
|
{
|
|
@@ -45443,7 +45570,7 @@ function tryReadHostFile(path4) {
|
|
|
45443
45570
|
}
|
|
45444
45571
|
}
|
|
45445
45572
|
function parseEnvFile(path4) {
|
|
45446
|
-
if (!
|
|
45573
|
+
if (!existsSync65(path4))
|
|
45447
45574
|
return {};
|
|
45448
45575
|
let content;
|
|
45449
45576
|
try {
|
|
@@ -45502,7 +45629,7 @@ async function checkTelegram(config) {
|
|
|
45502
45629
|
const plugin = agentConfig.channels?.telegram?.plugin ?? "switchroom";
|
|
45503
45630
|
if (plugin !== "switchroom")
|
|
45504
45631
|
continue;
|
|
45505
|
-
const envPath =
|
|
45632
|
+
const envPath = join66(agentsDir, name, "telegram", ".env");
|
|
45506
45633
|
const read = tryReadHostFile(envPath);
|
|
45507
45634
|
if (read.kind === "eacces") {
|
|
45508
45635
|
results.push({
|
|
@@ -45554,7 +45681,7 @@ async function checkTelegram(config) {
|
|
|
45554
45681
|
}
|
|
45555
45682
|
function checkStartShStale(agentName, startShPath) {
|
|
45556
45683
|
const label = `${agentName}: start.sh scheduler block`;
|
|
45557
|
-
if (!
|
|
45684
|
+
if (!existsSync65(startShPath)) {
|
|
45558
45685
|
return {
|
|
45559
45686
|
name: label,
|
|
45560
45687
|
status: "warn",
|
|
@@ -45585,10 +45712,10 @@ function checkStartShStale(agentName, startShPath) {
|
|
|
45585
45712
|
}
|
|
45586
45713
|
function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
45587
45714
|
const label = `${agentName}: $HOME/.switchroom symlink (#910)`;
|
|
45588
|
-
const path4 =
|
|
45715
|
+
const path4 = join66(agentDir, "home", ".switchroom");
|
|
45589
45716
|
let stats;
|
|
45590
45717
|
try {
|
|
45591
|
-
stats =
|
|
45718
|
+
stats = lstatSync9(path4);
|
|
45592
45719
|
} catch (err) {
|
|
45593
45720
|
if (err.code === "ENOENT") {
|
|
45594
45721
|
return {
|
|
@@ -45622,8 +45749,8 @@ function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
|
45622
45749
|
}
|
|
45623
45750
|
function checkRepoHygiene(repoRoot) {
|
|
45624
45751
|
const results = [];
|
|
45625
|
-
const exportDir =
|
|
45626
|
-
if (
|
|
45752
|
+
const exportDir = join66(repoRoot, "clerk-export");
|
|
45753
|
+
if (existsSync65(exportDir)) {
|
|
45627
45754
|
results.push({
|
|
45628
45755
|
name: "repo hygiene: clerk-export/ on disk (#1072)",
|
|
45629
45756
|
status: "warn",
|
|
@@ -45631,8 +45758,8 @@ function checkRepoHygiene(repoRoot) {
|
|
|
45631
45758
|
fix: `Run scripts/migrate-clerk-export-to-vault.sh to move the bundle ` + `into the vault, then delete the on-disk copy.`
|
|
45632
45759
|
});
|
|
45633
45760
|
}
|
|
45634
|
-
const knownTarball =
|
|
45635
|
-
if (
|
|
45761
|
+
const knownTarball = join66(repoRoot, "clerk-export-with-secrets.tar.gz");
|
|
45762
|
+
if (existsSync65(knownTarball)) {
|
|
45636
45763
|
results.push({
|
|
45637
45764
|
name: "repo hygiene: clerk-export-with-secrets.tar.gz on disk (#1072)",
|
|
45638
45765
|
status: "warn",
|
|
@@ -45649,7 +45776,7 @@ function checkRepoHygiene(repoRoot) {
|
|
|
45649
45776
|
results.push({
|
|
45650
45777
|
name: `repo hygiene: ${name} on disk (#1072)`,
|
|
45651
45778
|
status: "warn",
|
|
45652
|
-
detail: `${
|
|
45779
|
+
detail: `${join66(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
|
|
45653
45780
|
fix: `Inspect, migrate any secrets into the vault, then delete the ` + `archive.`
|
|
45654
45781
|
});
|
|
45655
45782
|
}
|
|
@@ -45672,10 +45799,10 @@ function checkRepoHygiene(repoRoot) {
|
|
|
45672
45799
|
}
|
|
45673
45800
|
function isSwitchroomCheckout(dir) {
|
|
45674
45801
|
try {
|
|
45675
|
-
if (!
|
|
45802
|
+
if (!existsSync65(join66(dir, ".git")))
|
|
45676
45803
|
return false;
|
|
45677
|
-
const pkgPath =
|
|
45678
|
-
if (!
|
|
45804
|
+
const pkgPath = join66(dir, "package.json");
|
|
45805
|
+
if (!existsSync65(pkgPath))
|
|
45679
45806
|
return false;
|
|
45680
45807
|
const pkg = JSON.parse(readFileSync58(pkgPath, "utf-8"));
|
|
45681
45808
|
return pkg.name === "switchroom";
|
|
@@ -45690,7 +45817,7 @@ function checkAgents(config, configPath) {
|
|
|
45690
45817
|
const authStatuses = getAllAuthStatuses(config);
|
|
45691
45818
|
for (const [name, agentConfig] of Object.entries(config.agents)) {
|
|
45692
45819
|
const agentDir = resolve38(agentsDir, name);
|
|
45693
|
-
if (!
|
|
45820
|
+
if (!existsSync65(agentDir)) {
|
|
45694
45821
|
results.push({
|
|
45695
45822
|
name: `${name}: scaffold`,
|
|
45696
45823
|
status: "fail",
|
|
@@ -45711,7 +45838,7 @@ function checkAgents(config, configPath) {
|
|
|
45711
45838
|
fix: `Rotate the bot token (e.g. via \`switchroom vault\`), then run ` + `\`switchroom agent unquarantine ${name}\` and \`switchroom agent restart ${name}\``
|
|
45712
45839
|
});
|
|
45713
45840
|
}
|
|
45714
|
-
results.push(checkStartShStale(name,
|
|
45841
|
+
results.push(checkStartShStale(name, join66(agentDir, "start.sh")));
|
|
45715
45842
|
results.push(checkLeakedHomeSwitchroom(name, agentDir));
|
|
45716
45843
|
const status = statuses[name];
|
|
45717
45844
|
const active = status?.active ?? "unknown";
|
|
@@ -45788,8 +45915,8 @@ function checkAgents(config, configPath) {
|
|
|
45788
45915
|
}
|
|
45789
45916
|
}
|
|
45790
45917
|
if (agentConfig.channels?.telegram?.plugin === "switchroom") {
|
|
45791
|
-
const mcpJsonPath =
|
|
45792
|
-
if (!
|
|
45918
|
+
const mcpJsonPath = join66(agentDir, ".mcp.json");
|
|
45919
|
+
if (!existsSync65(mcpJsonPath)) {
|
|
45793
45920
|
results.push({
|
|
45794
45921
|
name: `${name}: .mcp.json`,
|
|
45795
45922
|
status: "fail",
|
|
@@ -45875,7 +46002,7 @@ function mffEnvPath(config) {
|
|
|
45875
46002
|
return agent ? resolve38(home2, ".switchroom/credentials", agent, "my-family-finance/.env") : resolve38(home2, ".switchroom/credentials/my-family-finance/.env");
|
|
45876
46003
|
}
|
|
45877
46004
|
function mffEnvState(envPath) {
|
|
45878
|
-
if (!
|
|
46005
|
+
if (!existsSync65(envPath))
|
|
45879
46006
|
return "absent";
|
|
45880
46007
|
try {
|
|
45881
46008
|
accessSync2(envPath, fsConstants5.R_OK);
|
|
@@ -45893,7 +46020,7 @@ function checkMffVaultKeyPresent(passphrase, vaultPath) {
|
|
|
45893
46020
|
fix: "Export SWITCHROOM_VAULT_PASSPHRASE to enable MFF vault probes"
|
|
45894
46021
|
};
|
|
45895
46022
|
}
|
|
45896
|
-
if (!
|
|
46023
|
+
if (!existsSync65(vaultPath)) {
|
|
45897
46024
|
return {
|
|
45898
46025
|
name: "mff: vault key present",
|
|
45899
46026
|
status: "fail",
|
|
@@ -45946,7 +46073,7 @@ function deriveEd25519PublicKeyBytes(keyMaterial) {
|
|
|
45946
46073
|
}
|
|
45947
46074
|
}
|
|
45948
46075
|
function checkMffVaultKeyFormat(passphrase, vaultPath) {
|
|
45949
|
-
if (!passphrase || !
|
|
46076
|
+
if (!passphrase || !existsSync65(vaultPath)) {
|
|
45950
46077
|
return {
|
|
45951
46078
|
name: "mff: vault key format",
|
|
45952
46079
|
status: "warn",
|
|
@@ -46090,8 +46217,8 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
|
|
|
46090
46217
|
};
|
|
46091
46218
|
}
|
|
46092
46219
|
const credDir = dirname19(envPath);
|
|
46093
|
-
const authScript =
|
|
46094
|
-
if (!
|
|
46220
|
+
const authScript = join66(credDir, "claude-auth.py");
|
|
46221
|
+
if (!existsSync65(authScript)) {
|
|
46095
46222
|
return {
|
|
46096
46223
|
name: "mff: auth flow",
|
|
46097
46224
|
status: "warn",
|
|
@@ -46397,6 +46524,10 @@ function registerDoctorCommand(program3) {
|
|
|
46397
46524
|
{ title: "Memory (Hindsight)", results: await checkHindsight(config) },
|
|
46398
46525
|
{ title: "Telegram", results: await checkTelegram(config) },
|
|
46399
46526
|
{ title: "Agents", results: checkAgents(config, configPath) },
|
|
46527
|
+
{
|
|
46528
|
+
title: "Agent dotfile ownership (#3157)",
|
|
46529
|
+
results: runAgentDotfileOwnershipChecks(config)
|
|
46530
|
+
},
|
|
46400
46531
|
{ title: "Credentials", results: runCredentialsMigrationChecks(config) },
|
|
46401
46532
|
{ title: "Audit integrity (WS10-F4)", results: runAuditIntegrityChecks() },
|
|
46402
46533
|
{
|
|
@@ -46519,6 +46650,7 @@ var init_doctor = __esm(() => {
|
|
|
46519
46650
|
init_doctor_notion();
|
|
46520
46651
|
init_doctor_credentials_migration();
|
|
46521
46652
|
init_doctor_secret_access();
|
|
46653
|
+
init_doctor_agent_dotfile_ownership();
|
|
46522
46654
|
init_doctor_inlined_secrets();
|
|
46523
46655
|
init_doctor_approval_attribution();
|
|
46524
46656
|
init_doctor_audit_integrity();
|
|
@@ -47110,7 +47242,7 @@ var init_fleet_defaults = __esm(() => {
|
|
|
47110
47242
|
|
|
47111
47243
|
// src/agents/connection-health.ts
|
|
47112
47244
|
import { mkdirSync as mkdirSync44, writeFileSync as writeFileSync27 } from "node:fs";
|
|
47113
|
-
import { join as
|
|
47245
|
+
import { join as join81 } from "node:path";
|
|
47114
47246
|
async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
|
|
47115
47247
|
const reqs = computeMcpSecretRequirements(config).filter((r) => r.agent === agentName);
|
|
47116
47248
|
if (reqs.length === 0)
|
|
@@ -47167,8 +47299,8 @@ async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
|
|
|
47167
47299
|
return issues;
|
|
47168
47300
|
}
|
|
47169
47301
|
function writeConnectionHealthFile(agentDir, health, deps) {
|
|
47170
|
-
const dir =
|
|
47171
|
-
const path7 =
|
|
47302
|
+
const dir = join81(agentDir, ".claude");
|
|
47303
|
+
const path7 = join81(dir, CONNECTION_HEALTH_FILENAME);
|
|
47172
47304
|
(deps?.mkdir ?? ((p, o) => mkdirSync44(p, o)))(dir, { recursive: true });
|
|
47173
47305
|
(deps?.writeFile ?? ((p, d) => writeFileSync27(p, d)))(path7, JSON.stringify(health, null, 2) + `
|
|
47174
47306
|
`);
|
|
@@ -47191,10 +47323,10 @@ var CONNECTION_HEALTH_FILENAME = "connection-health.json";
|
|
|
47191
47323
|
var init_connection_health = () => {};
|
|
47192
47324
|
|
|
47193
47325
|
// src/cli/update-prompt-hook.ts
|
|
47194
|
-
import { existsSync as
|
|
47195
|
-
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";
|
|
47196
47328
|
function containerHookCommand() {
|
|
47197
|
-
return
|
|
47329
|
+
return join82(CONTAINER_AGENT_DIR, ".claude", "hooks", HOOK_FILENAME);
|
|
47198
47330
|
}
|
|
47199
47331
|
function updatePromptHookScript() {
|
|
47200
47332
|
return `#!/bin/bash
|
|
@@ -47260,12 +47392,12 @@ exit 0
|
|
|
47260
47392
|
`;
|
|
47261
47393
|
}
|
|
47262
47394
|
function installUpdatePromptHook(agentDir) {
|
|
47263
|
-
const hooksDir =
|
|
47395
|
+
const hooksDir = join82(agentDir, ".claude", "hooks");
|
|
47264
47396
|
mkdirSync45(hooksDir, { recursive: true });
|
|
47265
|
-
const scriptPath =
|
|
47397
|
+
const scriptPath = join82(hooksDir, HOOK_FILENAME);
|
|
47266
47398
|
const desired = updatePromptHookScript();
|
|
47267
47399
|
let installed = false;
|
|
47268
|
-
const existing =
|
|
47400
|
+
const existing = existsSync82(scriptPath) ? readFileSync70(scriptPath, "utf-8") : "";
|
|
47269
47401
|
if (existing !== desired) {
|
|
47270
47402
|
writeFileSync28(scriptPath, desired, { mode: 493 });
|
|
47271
47403
|
chmodSync11(scriptPath, 493);
|
|
@@ -47275,8 +47407,8 @@ function installUpdatePromptHook(agentDir) {
|
|
|
47275
47407
|
chmodSync11(scriptPath, 493);
|
|
47276
47408
|
} catch {}
|
|
47277
47409
|
}
|
|
47278
|
-
const settingsPath =
|
|
47279
|
-
if (!
|
|
47410
|
+
const settingsPath = join82(agentDir, ".claude", "settings.json");
|
|
47411
|
+
if (!existsSync82(settingsPath)) {
|
|
47280
47412
|
return { scriptPath, settingsPath, installed };
|
|
47281
47413
|
}
|
|
47282
47414
|
const raw = readFileSync70(settingsPath, "utf-8");
|
|
@@ -47704,7 +47836,7 @@ __export(exports_voice_sidecar_token, {
|
|
|
47704
47836
|
VOICE_SIDECAR_TOKEN_ENV: () => VOICE_SIDECAR_TOKEN_ENV
|
|
47705
47837
|
});
|
|
47706
47838
|
import { randomBytes as randomBytes14 } from "node:crypto";
|
|
47707
|
-
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";
|
|
47708
47840
|
import { dirname as dirname28 } from "node:path";
|
|
47709
47841
|
async function defaultResolveOrSeedToken(home2, writeErr) {
|
|
47710
47842
|
const [{ getViaBrokerStructured: getViaBrokerStructured2, putViaBroker: putViaBroker2 }, { resolveOperatorVaultPassphrase }] = await Promise.all([
|
|
@@ -47739,7 +47871,7 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
|
|
|
47739
47871
|
const envPath = composeEnvPath(composePath);
|
|
47740
47872
|
if (engine !== "local") {
|
|
47741
47873
|
try {
|
|
47742
|
-
if (
|
|
47874
|
+
if (existsSync84(envPath)) {
|
|
47743
47875
|
const body = readFileSync71(envPath, "utf-8");
|
|
47744
47876
|
if (body.includes(`${VOICE_SIDECAR_TOKEN_ENV}=`))
|
|
47745
47877
|
rmSync17(envPath);
|
|
@@ -47762,7 +47894,7 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
|
|
|
47762
47894
|
mkdirSync46(dirname28(envPath), { recursive: true });
|
|
47763
47895
|
let body = "";
|
|
47764
47896
|
try {
|
|
47765
|
-
if (
|
|
47897
|
+
if (existsSync84(envPath))
|
|
47766
47898
|
body = readFileSync71(envPath, "utf-8");
|
|
47767
47899
|
} catch {}
|
|
47768
47900
|
const line = `${VOICE_SIDECAR_TOKEN_ENV}=${token}`;
|
|
@@ -47822,11 +47954,11 @@ __export(exports_apply, {
|
|
|
47822
47954
|
DEFAULT_COMPOSE_PATH: () => DEFAULT_COMPOSE_PATH2,
|
|
47823
47955
|
COMPOSE_PROJECT: () => COMPOSE_PROJECT2
|
|
47824
47956
|
});
|
|
47825
|
-
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";
|
|
47826
47958
|
import { mkdir as mkdir2 } from "node:fs/promises";
|
|
47827
47959
|
import { spawnSync as childSpawnSync } from "node:child_process";
|
|
47828
47960
|
import readline from "node:readline";
|
|
47829
|
-
import { dirname as dirname29, join as
|
|
47961
|
+
import { dirname as dirname29, join as join84, resolve as resolve51 } from "node:path";
|
|
47830
47962
|
import { homedir as homedir49 } from "node:os";
|
|
47831
47963
|
import { execFileSync as execFileSync27 } from "node:child_process";
|
|
47832
47964
|
function effectiveLiteLLMEnabled(config, agentResolvedLitellm) {
|
|
@@ -47838,7 +47970,7 @@ async function resolveOperatorVaultPassphrase(home2) {
|
|
|
47838
47970
|
return envPass;
|
|
47839
47971
|
try {
|
|
47840
47972
|
const { readAutoUnlockFile: readAutoUnlockFile2 } = await Promise.resolve().then(() => (init_auto_unlock(), exports_auto_unlock));
|
|
47841
|
-
const blobPath =
|
|
47973
|
+
const blobPath = join84(home2, ".switchroom", "vault-auto-unlock");
|
|
47842
47974
|
const pass = readAutoUnlockFile2(blobPath);
|
|
47843
47975
|
return pass && pass.length > 0 ? pass : null;
|
|
47844
47976
|
} catch {
|
|
@@ -47926,7 +48058,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
47926
48058
|
const oauthAccount = config.auth?.active;
|
|
47927
48059
|
let pendingConfigEdits = false;
|
|
47928
48060
|
let configText = null;
|
|
47929
|
-
if (switchroomConfigPath &&
|
|
48061
|
+
if (switchroomConfigPath && existsSync85(switchroomConfigPath)) {
|
|
47930
48062
|
try {
|
|
47931
48063
|
configText = readFileSync72(switchroomConfigPath, "utf-8");
|
|
47932
48064
|
} catch (err) {
|
|
@@ -48083,7 +48215,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
48083
48215
|
try {
|
|
48084
48216
|
let cfgMode = 420;
|
|
48085
48217
|
try {
|
|
48086
|
-
cfgMode =
|
|
48218
|
+
cfgMode = statSync47(switchroomConfigPath).mode & 511;
|
|
48087
48219
|
} catch {}
|
|
48088
48220
|
writeConfigFileSync(switchroomConfigPath, configText, cfgMode);
|
|
48089
48221
|
writeOut(source_default.gray(` ~ litellm: granted read-ACL on per-agent keys (updated ${switchroomConfigPath})
|
|
@@ -48151,10 +48283,10 @@ function resolveVaultBindMountDir(homeDir, ctx) {
|
|
|
48151
48283
|
if (isCustomPath && ctx.customVaultPath) {
|
|
48152
48284
|
return dirname29(ctx.customVaultPath);
|
|
48153
48285
|
}
|
|
48154
|
-
return
|
|
48286
|
+
return join84(homeDir, ".switchroom", "vault");
|
|
48155
48287
|
}
|
|
48156
48288
|
function inspectVaultBindMountDir(vaultDir) {
|
|
48157
|
-
if (!
|
|
48289
|
+
if (!existsSync85(vaultDir))
|
|
48158
48290
|
return { kind: "missing" };
|
|
48159
48291
|
const entries = readdirSync29(vaultDir);
|
|
48160
48292
|
const unknown = [];
|
|
@@ -48182,43 +48314,43 @@ function hasVaultRefs(value) {
|
|
|
48182
48314
|
async function ensureHostMountSources(config) {
|
|
48183
48315
|
const home2 = resolveHostHomeForCompose();
|
|
48184
48316
|
const dirs = [
|
|
48185
|
-
|
|
48186
|
-
|
|
48187
|
-
|
|
48188
|
-
|
|
48189
|
-
|
|
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")
|
|
48190
48322
|
];
|
|
48191
48323
|
for (const name of Object.keys(config.agents)) {
|
|
48192
|
-
dirs.push(
|
|
48193
|
-
dirs.push(
|
|
48194
|
-
dirs.push(
|
|
48195
|
-
dirs.push(
|
|
48196
|
-
if (
|
|
48197
|
-
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"));
|
|
48198
48330
|
}
|
|
48199
48331
|
}
|
|
48200
48332
|
for (const dir of dirs) {
|
|
48201
48333
|
await mkdir2(dir, { recursive: true });
|
|
48202
48334
|
}
|
|
48203
|
-
const autoUnlockPath =
|
|
48204
|
-
if (!
|
|
48335
|
+
const autoUnlockPath = join84(home2, ".switchroom", "vault-auto-unlock");
|
|
48336
|
+
if (!existsSync85(autoUnlockPath)) {
|
|
48205
48337
|
writeFileSync30(autoUnlockPath, "", { mode: 384 });
|
|
48206
48338
|
}
|
|
48207
|
-
const auditLogPath =
|
|
48208
|
-
if (!
|
|
48339
|
+
const auditLogPath = join84(home2, ".switchroom", "vault-audit.log");
|
|
48340
|
+
if (!existsSync85(auditLogPath)) {
|
|
48209
48341
|
writeFileSync30(auditLogPath, "", { mode: 420 });
|
|
48210
48342
|
}
|
|
48211
|
-
const grantsDbPath =
|
|
48212
|
-
if (!
|
|
48343
|
+
const grantsDbPath = join84(home2, ".switchroom", "vault-grants.db");
|
|
48344
|
+
if (!existsSync85(grantsDbPath)) {
|
|
48213
48345
|
writeFileSync30(grantsDbPath, "", { mode: 384 });
|
|
48214
48346
|
}
|
|
48215
|
-
const hostdAuditLogPath =
|
|
48216
|
-
if (!
|
|
48347
|
+
const hostdAuditLogPath = join84(home2, ".switchroom", "host-control-audit.log");
|
|
48348
|
+
if (!existsSync85(hostdAuditLogPath)) {
|
|
48217
48349
|
writeFileSync30(hostdAuditLogPath, "", { mode: 420 });
|
|
48218
48350
|
}
|
|
48219
48351
|
for (const name of Object.keys(config.agents)) {
|
|
48220
|
-
const tokenPath =
|
|
48221
|
-
if (!
|
|
48352
|
+
const tokenPath = join84(home2, ".switchroom", "agents", name, ".vault-token");
|
|
48353
|
+
if (!existsSync85(tokenPath)) {
|
|
48222
48354
|
writeFileSync30(tokenPath, "", { mode: 384 });
|
|
48223
48355
|
}
|
|
48224
48356
|
try {
|
|
@@ -48226,16 +48358,16 @@ async function ensureHostMountSources(config) {
|
|
|
48226
48358
|
chownSync8(tokenPath, uid, uid);
|
|
48227
48359
|
} catch {}
|
|
48228
48360
|
}
|
|
48229
|
-
const fleetDir =
|
|
48361
|
+
const fleetDir = join84(home2, ".switchroom", "fleet");
|
|
48230
48362
|
await mkdir2(fleetDir, { recursive: true });
|
|
48231
|
-
const invariantsPath =
|
|
48363
|
+
const invariantsPath = join84(fleetDir, "switchroom-invariants.md");
|
|
48232
48364
|
const invariantsCanonical = renderFleetInvariants();
|
|
48233
|
-
const invariantsCurrent =
|
|
48365
|
+
const invariantsCurrent = existsSync85(invariantsPath) ? readFileSync72(invariantsPath, "utf-8") : null;
|
|
48234
48366
|
if (invariantsCurrent !== invariantsCanonical) {
|
|
48235
48367
|
writeFileSync30(invariantsPath, invariantsCanonical, { mode: 420 });
|
|
48236
48368
|
}
|
|
48237
|
-
const fleetClaudePath =
|
|
48238
|
-
if (!
|
|
48369
|
+
const fleetClaudePath = join84(fleetDir, "CLAUDE.md");
|
|
48370
|
+
if (!existsSync85(fleetClaudePath)) {
|
|
48239
48371
|
writeFileSync30(fleetClaudePath, renderFleetDefaultsClaudeMd(), {
|
|
48240
48372
|
mode: 420
|
|
48241
48373
|
});
|
|
@@ -48265,7 +48397,7 @@ function isInAgentContainer(vaultPresent, composeV2Present, env2 = process.env)
|
|
|
48265
48397
|
function runApplyPreflight(config, opts = {}) {
|
|
48266
48398
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
48267
48399
|
const detect = opts.detectComposeV2 ?? detectComposeV2;
|
|
48268
|
-
const vaultMissing = hasVaultRefs(config) && !
|
|
48400
|
+
const vaultMissing = hasVaultRefs(config) && !existsSync85(vaultPath);
|
|
48269
48401
|
const composeErr = detect();
|
|
48270
48402
|
if ((vaultMissing || composeErr) && isInAgentContainer(!vaultMissing, composeErr === null)) {
|
|
48271
48403
|
throw new Error(IN_AGENT_CONTAINER_APPLY_MSG);
|
|
@@ -48279,7 +48411,7 @@ function runApplyPreflight(config, opts = {}) {
|
|
|
48279
48411
|
detectAndReportLegacyGdriveSlots(vaultPath);
|
|
48280
48412
|
}
|
|
48281
48413
|
function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
48282
|
-
if (!
|
|
48414
|
+
if (!existsSync85(vaultPath))
|
|
48283
48415
|
return;
|
|
48284
48416
|
const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
48285
48417
|
if (!passphrase)
|
|
@@ -48320,8 +48452,8 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
|
48320
48452
|
}
|
|
48321
48453
|
function writeInstallTypeCache(homeDir = homedir49()) {
|
|
48322
48454
|
const ctx = detectInstallType();
|
|
48323
|
-
const dir =
|
|
48324
|
-
const out =
|
|
48455
|
+
const dir = join84(homeDir, ".switchroom");
|
|
48456
|
+
const out = join84(dir, "install-type.json");
|
|
48325
48457
|
const tmp = `${out}.tmp`;
|
|
48326
48458
|
mkdirSync47(dir, { recursive: true });
|
|
48327
48459
|
const payload = {
|
|
@@ -48388,17 +48520,17 @@ Applying switchroom config...
|
|
|
48388
48520
|
writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
|
|
48389
48521
|
`));
|
|
48390
48522
|
try {
|
|
48391
|
-
installUpdatePromptHook(
|
|
48523
|
+
installUpdatePromptHook(join84(agentsDir, name));
|
|
48392
48524
|
} catch (hookErr) {
|
|
48393
48525
|
writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
|
|
48394
48526
|
`));
|
|
48395
48527
|
}
|
|
48396
|
-
await refreshAgentConnectionHealth(config, name,
|
|
48528
|
+
await refreshAgentConnectionHealth(config, name, join84(agentsDir, name), {
|
|
48397
48529
|
vaultAclReader: connHealthVaultAclReader
|
|
48398
48530
|
});
|
|
48399
48531
|
try {
|
|
48400
48532
|
const uid = allocateAgentUid(name);
|
|
48401
|
-
alignAgentUid(name,
|
|
48533
|
+
alignAgentUid(name, join84(agentsDir, name), uid, {
|
|
48402
48534
|
confirm: !options.nonInteractive,
|
|
48403
48535
|
writeOut
|
|
48404
48536
|
});
|
|
@@ -48443,7 +48575,7 @@ Applying switchroom config...
|
|
|
48443
48575
|
for (const name of agentNames) {
|
|
48444
48576
|
try {
|
|
48445
48577
|
const uid = allocateAgentUid(name);
|
|
48446
|
-
alignAgentUid(name,
|
|
48578
|
+
alignAgentUid(name, join84(agentsDir, name), uid, {
|
|
48447
48579
|
confirm: !options.nonInteractive,
|
|
48448
48580
|
writeOut
|
|
48449
48581
|
});
|
|
@@ -48781,7 +48913,7 @@ function copyExampleConfig2(name) {
|
|
|
48781
48913
|
throw new Error(`Invalid example name: ${name} (must match /^[a-z0-9_-]+$/)`);
|
|
48782
48914
|
}
|
|
48783
48915
|
const dest = resolve51(process.cwd(), "switchroom.yaml");
|
|
48784
|
-
if (
|
|
48916
|
+
if (existsSync85(dest)) {
|
|
48785
48917
|
console.error(source_default.yellow("switchroom.yaml already exists \u2014 skipping example copy"));
|
|
48786
48918
|
return;
|
|
48787
48919
|
}
|
|
@@ -48792,7 +48924,7 @@ function copyExampleConfig2(name) {
|
|
|
48792
48924
|
return;
|
|
48793
48925
|
}
|
|
48794
48926
|
const exampleFile = resolve51(import.meta.dirname, `../../examples/${name}.yaml`);
|
|
48795
|
-
if (!
|
|
48927
|
+
if (!existsSync85(exampleFile)) {
|
|
48796
48928
|
throw new Error(`Example config not found: ${name}.yaml (available: ${Object.keys(EMBEDDED_EXAMPLES).join(", ")})`);
|
|
48797
48929
|
}
|
|
48798
48930
|
copyFileSync12(exampleFile, dest);
|
|
@@ -48803,8 +48935,8 @@ function findUnwritableAgentDirs(config, opts) {
|
|
|
48803
48935
|
const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
|
|
48804
48936
|
const unwritable = [];
|
|
48805
48937
|
for (const name of targets) {
|
|
48806
|
-
const startSh =
|
|
48807
|
-
if (!
|
|
48938
|
+
const startSh = join84(agentsDir, name, "start.sh");
|
|
48939
|
+
if (!existsSync85(startSh))
|
|
48808
48940
|
continue;
|
|
48809
48941
|
try {
|
|
48810
48942
|
accessSync3(startSh, fsConstants6.W_OK);
|
|
@@ -49007,7 +49139,7 @@ var init_apply = __esm(() => {
|
|
|
49007
49139
|
switchroom: switchroom_default,
|
|
49008
49140
|
minimal: minimal_default
|
|
49009
49141
|
};
|
|
49010
|
-
DEFAULT_COMPOSE_PATH2 =
|
|
49142
|
+
DEFAULT_COMPOSE_PATH2 = join84(homedir49(), ".switchroom", "compose", "docker-compose.yml");
|
|
49011
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.
|
|
49012
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.";
|
|
49013
49145
|
SELF_ELEVATE_PRESERVED_ENV = [
|
|
@@ -62456,7 +62588,7 @@ __export(exports_server2, {
|
|
|
62456
62588
|
TOOLS: () => TOOLS2
|
|
62457
62589
|
});
|
|
62458
62590
|
import { randomBytes as randomBytes16 } from "node:crypto";
|
|
62459
|
-
import { existsSync as
|
|
62591
|
+
import { existsSync as existsSync94, readFileSync as readFileSync81 } from "node:fs";
|
|
62460
62592
|
function selfSocketPath() {
|
|
62461
62593
|
return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
|
|
62462
62594
|
}
|
|
@@ -62483,7 +62615,7 @@ async function dispatchTool2(name, args) {
|
|
|
62483
62615
|
return errorText2("hostd MCP: SWITCHROOM_AGENT_NAME env var is not set \u2014 cannot " + "determine which per-agent socket to talk to.");
|
|
62484
62616
|
}
|
|
62485
62617
|
const sockPath = selfSocketPath();
|
|
62486
|
-
if (!
|
|
62618
|
+
if (!existsSync94(sockPath)) {
|
|
62487
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.`);
|
|
62488
62620
|
}
|
|
62489
62621
|
let req;
|
|
@@ -62734,13 +62866,13 @@ function resolveAuditLogPath() {
|
|
|
62734
62866
|
if (process.env.HOSTD_AUDIT_LOG_PATH)
|
|
62735
62867
|
return process.env.HOSTD_AUDIT_LOG_PATH;
|
|
62736
62868
|
const bindMounted = "/host-home/.switchroom/host-control-audit.log";
|
|
62737
|
-
if (
|
|
62869
|
+
if (existsSync94(bindMounted))
|
|
62738
62870
|
return bindMounted;
|
|
62739
62871
|
return defaultAuditLogPath2();
|
|
62740
62872
|
}
|
|
62741
62873
|
function getLastUpdateApplyStatus() {
|
|
62742
62874
|
const path8 = resolveAuditLogPath();
|
|
62743
|
-
if (!
|
|
62875
|
+
if (!existsSync94(path8)) {
|
|
62744
62876
|
return errorText2(`get_status: audit log not found at ${path8}. No update_apply has run yet?`);
|
|
62745
62877
|
}
|
|
62746
62878
|
let raw;
|
|
@@ -63422,7 +63554,7 @@ __export(exports_scan, {
|
|
|
63422
63554
|
import {
|
|
63423
63555
|
readFileSync as readFileSync83,
|
|
63424
63556
|
readdirSync as readdirSync37,
|
|
63425
|
-
existsSync as
|
|
63557
|
+
existsSync as existsSync97,
|
|
63426
63558
|
mkdirSync as mkdirSync54,
|
|
63427
63559
|
writeFileSync as writeFileSync36
|
|
63428
63560
|
} from "node:fs";
|
|
@@ -63433,7 +63565,7 @@ function resolveSwitchroomBase(home2 = process.env.SWITCHROOM_HOME ?? process.en
|
|
|
63433
63565
|
}
|
|
63434
63566
|
function listAgents(base) {
|
|
63435
63567
|
const dir = resolve56(base, "agents");
|
|
63436
|
-
if (!
|
|
63568
|
+
if (!existsSync97(dir))
|
|
63437
63569
|
return [];
|
|
63438
63570
|
try {
|
|
63439
63571
|
return readdirSync37(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
|
|
@@ -63462,7 +63594,7 @@ function runScan(opts = {}) {
|
|
|
63462
63594
|
let gwText = "";
|
|
63463
63595
|
let sawArtifact = false;
|
|
63464
63596
|
try {
|
|
63465
|
-
if (
|
|
63597
|
+
if (existsSync97(turnsPath)) {
|
|
63466
63598
|
turnsText = readFileSync83(turnsPath, "utf-8");
|
|
63467
63599
|
sawArtifact = true;
|
|
63468
63600
|
}
|
|
@@ -63470,7 +63602,7 @@ function runScan(opts = {}) {
|
|
|
63470
63602
|
log(`fleet-health: WARN skipping ${agent} turns.jsonl unreadable: ${String(e)}`);
|
|
63471
63603
|
}
|
|
63472
63604
|
try {
|
|
63473
|
-
if (
|
|
63605
|
+
if (existsSync97(gwPath)) {
|
|
63474
63606
|
gwText = readFileSync83(gwPath, "utf-8");
|
|
63475
63607
|
sawArtifact = true;
|
|
63476
63608
|
}
|
|
@@ -63509,7 +63641,7 @@ function runScan(opts = {}) {
|
|
|
63509
63641
|
function readLedgerIfPresent(base) {
|
|
63510
63642
|
const path8 = ledgerPathForBase(base);
|
|
63511
63643
|
try {
|
|
63512
|
-
if (!
|
|
63644
|
+
if (!existsSync97(path8))
|
|
63513
63645
|
return null;
|
|
63514
63646
|
return JSON.parse(readFileSync83(path8, "utf-8"));
|
|
63515
63647
|
} catch {
|
|
@@ -69654,6 +69786,36 @@ function printConsumersTable(state) {
|
|
|
69654
69786
|
console.log(` ${c.name.padEnd(10)} ${acct} ${status}`);
|
|
69655
69787
|
}
|
|
69656
69788
|
}
|
|
69789
|
+
function usageHeadroomCell(t) {
|
|
69790
|
+
const noColor = (s) => s;
|
|
69791
|
+
if (t == null)
|
|
69792
|
+
return { plain: "no data", color: source_default.gray };
|
|
69793
|
+
if (t.headroomPct == null)
|
|
69794
|
+
return { plain: "unknown", color: source_default.gray };
|
|
69795
|
+
const left = Math.round(t.headroomPct);
|
|
69796
|
+
const peak = t.peakUtilizationPct == null ? "?" : `${Math.round(t.peakUtilizationPct)}`;
|
|
69797
|
+
const base = `${left}% left (peak ${peak}%, n=${t.observations})`;
|
|
69798
|
+
if (t.walled)
|
|
69799
|
+
return { plain: `WALLED - ${base}`, color: source_default.red };
|
|
69800
|
+
if (t.refilled)
|
|
69801
|
+
return { plain: `refilled - ${base}`, color: source_default.green };
|
|
69802
|
+
if (left <= 10)
|
|
69803
|
+
return { plain: base, color: source_default.yellow };
|
|
69804
|
+
return { plain: base, color: noColor };
|
|
69805
|
+
}
|
|
69806
|
+
function printUsageTable(state) {
|
|
69807
|
+
console.log(source_default.bold(" ACCOUNT PREMIUM (Fable) HEADROOM STANDARD HEADROOM"));
|
|
69808
|
+
const WIDTH = 44;
|
|
69809
|
+
for (const a of state.accounts) {
|
|
69810
|
+
const marker = a.label === state.active ? source_default.green("*") : " ";
|
|
69811
|
+
const label = a.label.padEnd(32);
|
|
69812
|
+
const premium = usageHeadroomCell(a.usage_ledger?.premium ?? null);
|
|
69813
|
+
const standard = usageHeadroomCell(a.usage_ledger?.standard ?? null);
|
|
69814
|
+
const premiumCol = premium.color(premium.plain.padEnd(WIDTH));
|
|
69815
|
+
const standardCol = standard.color(standard.plain);
|
|
69816
|
+
console.log(` ${marker} ${label} ${premiumCol} ${standardCol}`);
|
|
69817
|
+
}
|
|
69818
|
+
}
|
|
69657
69819
|
function printAgentDetail(state, agent) {
|
|
69658
69820
|
console.log();
|
|
69659
69821
|
console.log(source_default.bold(` ${agent.name}`));
|
|
@@ -69832,6 +69994,19 @@ function registerAuthCommand(program3) {
|
|
|
69832
69994
|
printAccountsTable(state);
|
|
69833
69995
|
console.log();
|
|
69834
69996
|
}));
|
|
69997
|
+
auth.command("usage").description("Per-account premium (Fable) + standard usage headroom, from the durable ledger").option("--json", "Output the raw per-account usage_ledger summaries as JSON").action(withConfigError(async (opts) => {
|
|
69998
|
+
const state = await brokerCall((client) => client.listState());
|
|
69999
|
+
if (opts.json) {
|
|
70000
|
+
console.log(JSON.stringify(state.accounts.map((a) => ({ label: a.label, usage_ledger: a.usage_ledger ?? null })), null, 2));
|
|
70001
|
+
return;
|
|
70002
|
+
}
|
|
70003
|
+
console.log();
|
|
70004
|
+
printUsageTable(state);
|
|
70005
|
+
console.log();
|
|
70006
|
+
console.log(source_default.gray(" Premium = flagship (Fable) 7d_oi tier; standard = 5h/7d. Passive: harvested from probe headers, no extra requests."));
|
|
70007
|
+
console.log(source_default.gray(" 'X% left' is current (refill-aware); 'peak' is the max over retained history (~48h) and may predate the latest reset."));
|
|
70008
|
+
console.log();
|
|
70009
|
+
}));
|
|
69835
70010
|
function pinAuthActiveInYaml(label, quiet = false) {
|
|
69836
70011
|
try {
|
|
69837
70012
|
const yamlPath = getConfigPath(program3);
|
|
@@ -83200,9 +83375,9 @@ init_source();
|
|
|
83200
83375
|
init_loader();
|
|
83201
83376
|
init_lifecycle();
|
|
83202
83377
|
init_compose_env();
|
|
83203
|
-
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";
|
|
83204
83379
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
83205
|
-
import { join as
|
|
83380
|
+
import { join as join67, dirname as dirname20, resolve as resolve39 } from "node:path";
|
|
83206
83381
|
import { homedir as homedir40 } from "node:os";
|
|
83207
83382
|
|
|
83208
83383
|
// src/cli/release-yaml.ts
|
|
@@ -83228,7 +83403,7 @@ init_operator_uid();
|
|
|
83228
83403
|
init_atomic();
|
|
83229
83404
|
|
|
83230
83405
|
// src/cli/preflight-mounts.ts
|
|
83231
|
-
import { statSync as
|
|
83406
|
+
import { statSync as statSync39 } from "node:fs";
|
|
83232
83407
|
function parseHostBindSources(composeText) {
|
|
83233
83408
|
const out = [];
|
|
83234
83409
|
const FILE_HINT = /\.(ya?ml|db|log|toml|json|token|id)$|\/\.vault-token$|machine-id$|localtime$|vault-auto-unlock$|\/webkite$/;
|
|
@@ -83255,7 +83430,7 @@ function parseHostBindSources(composeText) {
|
|
|
83255
83430
|
return out;
|
|
83256
83431
|
}
|
|
83257
83432
|
function validateBindSources(composeText, deps = {}) {
|
|
83258
|
-
const stat = deps.stat ?? ((p) =>
|
|
83433
|
+
const stat = deps.stat ?? ((p) => statSync39(p));
|
|
83259
83434
|
const sources = parseHostBindSources(composeText);
|
|
83260
83435
|
const issues = [];
|
|
83261
83436
|
const seen = new Set;
|
|
@@ -83300,7 +83475,7 @@ function defaultPersistPin(configPath) {
|
|
|
83300
83475
|
const after = setReleasePinInConfig(before, pin);
|
|
83301
83476
|
if (after === before)
|
|
83302
83477
|
return;
|
|
83303
|
-
writeConfigFileSync(path4, after,
|
|
83478
|
+
writeConfigFileSync(path4, after, statSync40(path4).mode & 511);
|
|
83304
83479
|
try {
|
|
83305
83480
|
if (typeof process.geteuid === "function" && process.geteuid() === 0) {
|
|
83306
83481
|
const uid = resolveOperatorUid();
|
|
@@ -83310,13 +83485,13 @@ function defaultPersistPin(configPath) {
|
|
|
83310
83485
|
} catch {}
|
|
83311
83486
|
};
|
|
83312
83487
|
}
|
|
83313
|
-
var DEFAULT_COMPOSE_PATH =
|
|
83488
|
+
var DEFAULT_COMPOSE_PATH = join67(homedir40(), ".switchroom", "compose", "docker-compose.yml");
|
|
83314
83489
|
function runningFromSwitchroomCheckout(scriptPath) {
|
|
83315
83490
|
let dir = dirname20(scriptPath);
|
|
83316
83491
|
for (let i = 0;i < 12; i++) {
|
|
83317
|
-
if (
|
|
83492
|
+
if (existsSync66(join67(dir, ".git"))) {
|
|
83318
83493
|
try {
|
|
83319
|
-
const pkg = JSON.parse(readFileSync59(
|
|
83494
|
+
const pkg = JSON.parse(readFileSync59(join67(dir, "package.json"), "utf-8"));
|
|
83320
83495
|
if (pkg.name === "switchroom")
|
|
83321
83496
|
return true;
|
|
83322
83497
|
} catch {}
|
|
@@ -83401,7 +83576,7 @@ function planUpdate(opts) {
|
|
|
83401
83576
|
steps.push({
|
|
83402
83577
|
name: "pull-images",
|
|
83403
83578
|
description: "Pull broker / kernel / agent images from GHCR",
|
|
83404
|
-
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,
|
|
83405
83580
|
run: () => {
|
|
83406
83581
|
const r = runner("docker", [
|
|
83407
83582
|
"compose",
|
|
@@ -83527,14 +83702,14 @@ function planUpdate(opts) {
|
|
|
83527
83702
|
return;
|
|
83528
83703
|
}
|
|
83529
83704
|
const source = resolve39(import.meta.dirname, "../../skills");
|
|
83530
|
-
const dest =
|
|
83531
|
-
if (!
|
|
83705
|
+
const dest = join67(homedir40(), ".switchroom", "skills", "_bundled");
|
|
83706
|
+
if (!existsSync66(source)) {
|
|
83532
83707
|
process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
|
|
83533
83708
|
`);
|
|
83534
83709
|
return;
|
|
83535
83710
|
}
|
|
83536
83711
|
try {
|
|
83537
|
-
if (
|
|
83712
|
+
if (existsSync66(dest)) {
|
|
83538
83713
|
rmSync12(dest, { recursive: true, force: true });
|
|
83539
83714
|
}
|
|
83540
83715
|
mkdirSync34(dirname20(dest), { recursive: true });
|
|
@@ -83648,12 +83823,12 @@ function defaultStatusProbe(composePath) {
|
|
|
83648
83823
|
} catch {}
|
|
83649
83824
|
if (scriptPath) {
|
|
83650
83825
|
try {
|
|
83651
|
-
cliBuiltAt = new Date(
|
|
83826
|
+
cliBuiltAt = new Date(statSync40(scriptPath).mtimeMs).toISOString();
|
|
83652
83827
|
} catch {}
|
|
83653
83828
|
let dir = dirname20(scriptPath);
|
|
83654
83829
|
for (let i = 0;i < 8; i++) {
|
|
83655
|
-
const pkgPath =
|
|
83656
|
-
if (
|
|
83830
|
+
const pkgPath = join67(dir, "package.json");
|
|
83831
|
+
if (existsSync66(pkgPath)) {
|
|
83657
83832
|
try {
|
|
83658
83833
|
const pkg = JSON.parse(readFileSync59(pkgPath, "utf-8"));
|
|
83659
83834
|
if (typeof pkg.version === "string")
|
|
@@ -83676,7 +83851,7 @@ function defaultStatusProbe(composePath) {
|
|
|
83676
83851
|
warnings.push("could not resolve CLI version (no package.json found above the resolved script path)");
|
|
83677
83852
|
}
|
|
83678
83853
|
const services = [];
|
|
83679
|
-
if (!
|
|
83854
|
+
if (!existsSync66(composePath)) {
|
|
83680
83855
|
warnings.push(`compose file not found at ${composePath}; service status unknown`);
|
|
83681
83856
|
return { cliVersion, cliBuiltAt, services, warnings };
|
|
83682
83857
|
}
|
|
@@ -83872,7 +84047,7 @@ function registerUpdateCommand(program3) {
|
|
|
83872
84047
|
// src/cli/rollout.ts
|
|
83873
84048
|
init_helpers();
|
|
83874
84049
|
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
83875
|
-
import { readFileSync as readFileSync60, chownSync as chownSync6, statSync as
|
|
84050
|
+
import { readFileSync as readFileSync60, chownSync as chownSync6, statSync as statSync41 } from "node:fs";
|
|
83876
84051
|
import { homedir as homedir41 } from "node:os";
|
|
83877
84052
|
init_operator_uid();
|
|
83878
84053
|
init_atomic();
|
|
@@ -84276,7 +84451,7 @@ function registerRolloutCommand(program3) {
|
|
|
84276
84451
|
const after = setReleasePinInConfig(before, pin);
|
|
84277
84452
|
if (after === before)
|
|
84278
84453
|
return;
|
|
84279
|
-
writeConfigFileSync(configPath, after,
|
|
84454
|
+
writeConfigFileSync(configPath, after, statSync41(configPath).mode & 511);
|
|
84280
84455
|
try {
|
|
84281
84456
|
if (typeof process.geteuid === "function" && process.geteuid() === 0) {
|
|
84282
84457
|
const uid = resolveOperatorUid();
|
|
@@ -84350,8 +84525,8 @@ init_helpers();
|
|
|
84350
84525
|
init_lifecycle();
|
|
84351
84526
|
init_resolve_version();
|
|
84352
84527
|
import { execSync as execSync3 } from "node:child_process";
|
|
84353
|
-
import { existsSync as
|
|
84354
|
-
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";
|
|
84355
84530
|
function getClaudeCodeVersion() {
|
|
84356
84531
|
try {
|
|
84357
84532
|
const out = execSync3("claude --version 2>/dev/null", {
|
|
@@ -84401,11 +84576,11 @@ function formatUptime3(timestamp) {
|
|
|
84401
84576
|
function locateSwitchroomInstallDir() {
|
|
84402
84577
|
let dir = import.meta.dirname;
|
|
84403
84578
|
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
84404
|
-
const pkgPath =
|
|
84405
|
-
if (
|
|
84579
|
+
const pkgPath = join68(dir, "package.json");
|
|
84580
|
+
if (existsSync67(pkgPath)) {
|
|
84406
84581
|
try {
|
|
84407
84582
|
const pkg = JSON.parse(readFileSync61(pkgPath, "utf-8"));
|
|
84408
|
-
if (pkg.name === "switchroom" &&
|
|
84583
|
+
if (pkg.name === "switchroom" && existsSync67(join68(dir, ".git"))) {
|
|
84409
84584
|
return dir;
|
|
84410
84585
|
}
|
|
84411
84586
|
} catch {}
|
|
@@ -84583,18 +84758,18 @@ import { resolve as resolve41 } from "node:path";
|
|
|
84583
84758
|
|
|
84584
84759
|
// src/agents/session-retention.ts
|
|
84585
84760
|
import {
|
|
84586
|
-
existsSync as
|
|
84761
|
+
existsSync as existsSync68,
|
|
84587
84762
|
readdirSync as readdirSync23,
|
|
84588
|
-
statSync as
|
|
84763
|
+
statSync as statSync42,
|
|
84589
84764
|
unlinkSync as unlinkSync14
|
|
84590
84765
|
} from "node:fs";
|
|
84591
|
-
import { join as
|
|
84766
|
+
import { join as join69 } from "node:path";
|
|
84592
84767
|
var DEFAULT_SESSION_RETENTION_MAX_COUNT = 20;
|
|
84593
84768
|
var DEFAULT_SESSION_RETENTION_MAX_AGE_DAYS = 30;
|
|
84594
84769
|
var MIN_KEEP = 2;
|
|
84595
84770
|
function collectSessionJsonl(claudeConfigDir) {
|
|
84596
|
-
const projects =
|
|
84597
|
-
if (!
|
|
84771
|
+
const projects = join69(claudeConfigDir, "projects");
|
|
84772
|
+
if (!existsSync68(projects))
|
|
84598
84773
|
return [];
|
|
84599
84774
|
const found = [];
|
|
84600
84775
|
const walk2 = (dir) => {
|
|
@@ -84605,10 +84780,10 @@ function collectSessionJsonl(claudeConfigDir) {
|
|
|
84605
84780
|
return;
|
|
84606
84781
|
}
|
|
84607
84782
|
for (const name of entries) {
|
|
84608
|
-
const full =
|
|
84783
|
+
const full = join69(dir, name);
|
|
84609
84784
|
let st;
|
|
84610
84785
|
try {
|
|
84611
|
-
st =
|
|
84786
|
+
st = statSync42(full);
|
|
84612
84787
|
} catch {
|
|
84613
84788
|
continue;
|
|
84614
84789
|
}
|
|
@@ -84725,18 +84900,18 @@ function registerHandoffCommand(program3) {
|
|
|
84725
84900
|
// src/issues/store.ts
|
|
84726
84901
|
import {
|
|
84727
84902
|
closeSync as closeSync12,
|
|
84728
|
-
existsSync as
|
|
84903
|
+
existsSync as existsSync69,
|
|
84729
84904
|
mkdirSync as mkdirSync35,
|
|
84730
84905
|
openSync as openSync12,
|
|
84731
84906
|
readdirSync as readdirSync24,
|
|
84732
84907
|
readFileSync as readFileSync62,
|
|
84733
84908
|
renameSync as renameSync15,
|
|
84734
|
-
statSync as
|
|
84909
|
+
statSync as statSync43,
|
|
84735
84910
|
unlinkSync as unlinkSync15,
|
|
84736
84911
|
writeFileSync as writeFileSync19,
|
|
84737
84912
|
writeSync as writeSync9
|
|
84738
84913
|
} from "node:fs";
|
|
84739
|
-
import { join as
|
|
84914
|
+
import { join as join70 } from "node:path";
|
|
84740
84915
|
import { randomBytes as randomBytes12 } from "node:crypto";
|
|
84741
84916
|
import { execSync as execSync4 } from "node:child_process";
|
|
84742
84917
|
|
|
@@ -85167,8 +85342,8 @@ function redactedMarker(ruleId) {
|
|
|
85167
85342
|
var ISSUES_FILE = "issues.jsonl";
|
|
85168
85343
|
var ISSUES_LOCK = "issues.lock";
|
|
85169
85344
|
function readAll(stateDir) {
|
|
85170
|
-
const path4 =
|
|
85171
|
-
if (!
|
|
85345
|
+
const path4 = join70(stateDir, ISSUES_FILE);
|
|
85346
|
+
if (!existsSync69(path4))
|
|
85172
85347
|
return [];
|
|
85173
85348
|
let raw;
|
|
85174
85349
|
try {
|
|
@@ -85245,7 +85420,7 @@ function record(stateDir, input, nowFn = Date.now) {
|
|
|
85245
85420
|
});
|
|
85246
85421
|
}
|
|
85247
85422
|
function resolve42(stateDir, fingerprint, nowFn = Date.now) {
|
|
85248
|
-
if (!
|
|
85423
|
+
if (!existsSync69(join70(stateDir, ISSUES_FILE)))
|
|
85249
85424
|
return 0;
|
|
85250
85425
|
return withLock(stateDir, () => {
|
|
85251
85426
|
const all = readAll(stateDir);
|
|
@@ -85263,7 +85438,7 @@ function resolve42(stateDir, fingerprint, nowFn = Date.now) {
|
|
|
85263
85438
|
});
|
|
85264
85439
|
}
|
|
85265
85440
|
function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
85266
|
-
if (!
|
|
85441
|
+
if (!existsSync69(join70(stateDir, ISSUES_FILE)))
|
|
85267
85442
|
return 0;
|
|
85268
85443
|
return withLock(stateDir, () => {
|
|
85269
85444
|
const all = readAll(stateDir);
|
|
@@ -85281,7 +85456,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
|
85281
85456
|
});
|
|
85282
85457
|
}
|
|
85283
85458
|
function prune(stateDir, opts = {}) {
|
|
85284
|
-
if (!
|
|
85459
|
+
if (!existsSync69(join70(stateDir, ISSUES_FILE)))
|
|
85285
85460
|
return 0;
|
|
85286
85461
|
return withLock(stateDir, () => {
|
|
85287
85462
|
const all = readAll(stateDir);
|
|
@@ -85314,7 +85489,7 @@ function ensureDir(stateDir) {
|
|
|
85314
85489
|
mkdirSync35(stateDir, { recursive: true });
|
|
85315
85490
|
}
|
|
85316
85491
|
function writeAll(stateDir, events) {
|
|
85317
|
-
const path4 =
|
|
85492
|
+
const path4 = join70(stateDir, ISSUES_FILE);
|
|
85318
85493
|
sweepOrphanTmpFiles(stateDir);
|
|
85319
85494
|
const tmp = `${path4}.tmp-${process.pid}-${randomBytes12(4).toString("hex")}`;
|
|
85320
85495
|
const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
|
|
@@ -85336,9 +85511,9 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
85336
85511
|
for (const entry of entries) {
|
|
85337
85512
|
if (!entry.startsWith(TMP_PREFIX))
|
|
85338
85513
|
continue;
|
|
85339
|
-
const tmpPath =
|
|
85514
|
+
const tmpPath = join70(stateDir, entry);
|
|
85340
85515
|
try {
|
|
85341
|
-
const stat =
|
|
85516
|
+
const stat = statSync43(tmpPath);
|
|
85342
85517
|
if (stat.mtimeMs < cutoff) {
|
|
85343
85518
|
unlinkSync15(tmpPath);
|
|
85344
85519
|
}
|
|
@@ -85348,7 +85523,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
85348
85523
|
var LOCK_RETRY_MS = 25;
|
|
85349
85524
|
var LOCK_TIMEOUT_MS = 1e4;
|
|
85350
85525
|
function withLock(stateDir, fn) {
|
|
85351
|
-
const lockPath =
|
|
85526
|
+
const lockPath = join70(stateDir, ISSUES_LOCK);
|
|
85352
85527
|
const startedAt = Date.now();
|
|
85353
85528
|
let fd = null;
|
|
85354
85529
|
while (fd === null) {
|
|
@@ -85631,20 +85806,20 @@ function relTime(deltaMs) {
|
|
|
85631
85806
|
|
|
85632
85807
|
// src/cli/deps.ts
|
|
85633
85808
|
init_source();
|
|
85634
|
-
import { existsSync as
|
|
85809
|
+
import { existsSync as existsSync72 } from "node:fs";
|
|
85635
85810
|
import { homedir as homedir44 } from "node:os";
|
|
85636
|
-
import { join as
|
|
85811
|
+
import { join as join73, resolve as resolve43 } from "node:path";
|
|
85637
85812
|
|
|
85638
85813
|
// src/deps/python.ts
|
|
85639
85814
|
import { createHash as createHash13 } from "node:crypto";
|
|
85640
85815
|
import {
|
|
85641
|
-
existsSync as
|
|
85816
|
+
existsSync as existsSync70,
|
|
85642
85817
|
mkdirSync as mkdirSync36,
|
|
85643
85818
|
readFileSync as readFileSync63,
|
|
85644
85819
|
rmSync as rmSync13,
|
|
85645
85820
|
writeFileSync as writeFileSync20
|
|
85646
85821
|
} from "node:fs";
|
|
85647
|
-
import { dirname as dirname22, join as
|
|
85822
|
+
import { dirname as dirname22, join as join71 } from "node:path";
|
|
85648
85823
|
import { homedir as homedir42 } from "node:os";
|
|
85649
85824
|
import { execFileSync as execFileSync21 } from "node:child_process";
|
|
85650
85825
|
|
|
@@ -85657,7 +85832,7 @@ class PythonEnvError extends Error {
|
|
|
85657
85832
|
}
|
|
85658
85833
|
}
|
|
85659
85834
|
function defaultPythonCacheRoot() {
|
|
85660
|
-
return
|
|
85835
|
+
return join71(homedir42(), ".switchroom", "deps", "python");
|
|
85661
85836
|
}
|
|
85662
85837
|
function hashFile(path4) {
|
|
85663
85838
|
return createHash13("sha256").update(readFileSync63(path4)).digest("hex");
|
|
@@ -85666,16 +85841,16 @@ function ensurePythonEnv(opts) {
|
|
|
85666
85841
|
const { skillName, requirementsPath, force = false } = opts;
|
|
85667
85842
|
const cacheRoot = opts.cacheRoot ?? defaultPythonCacheRoot();
|
|
85668
85843
|
const hostPython = opts.pythonBin ?? "python3";
|
|
85669
|
-
if (!
|
|
85844
|
+
if (!existsSync70(requirementsPath)) {
|
|
85670
85845
|
throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
|
|
85671
85846
|
}
|
|
85672
|
-
const venvDir =
|
|
85673
|
-
const stampPath =
|
|
85674
|
-
const binDir =
|
|
85675
|
-
const pythonBin =
|
|
85676
|
-
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");
|
|
85677
85852
|
const targetHash = hashFile(requirementsPath);
|
|
85678
|
-
if (!force &&
|
|
85853
|
+
if (!force && existsSync70(stampPath) && existsSync70(pythonBin)) {
|
|
85679
85854
|
const existingHash = readFileSync63(stampPath, "utf8").trim();
|
|
85680
85855
|
if (existingHash === targetHash) {
|
|
85681
85856
|
return {
|
|
@@ -85688,7 +85863,7 @@ function ensurePythonEnv(opts) {
|
|
|
85688
85863
|
};
|
|
85689
85864
|
}
|
|
85690
85865
|
}
|
|
85691
|
-
if (
|
|
85866
|
+
if (existsSync70(venvDir)) {
|
|
85692
85867
|
rmSync13(venvDir, { recursive: true, force: true });
|
|
85693
85868
|
}
|
|
85694
85869
|
mkdirSync36(dirname22(venvDir), { recursive: true });
|
|
@@ -85726,13 +85901,13 @@ function ensurePythonEnv(opts) {
|
|
|
85726
85901
|
import { createHash as createHash14 } from "node:crypto";
|
|
85727
85902
|
import {
|
|
85728
85903
|
copyFileSync as copyFileSync10,
|
|
85729
|
-
existsSync as
|
|
85904
|
+
existsSync as existsSync71,
|
|
85730
85905
|
mkdirSync as mkdirSync37,
|
|
85731
85906
|
readFileSync as readFileSync64,
|
|
85732
85907
|
rmSync as rmSync14,
|
|
85733
85908
|
writeFileSync as writeFileSync21
|
|
85734
85909
|
} from "node:fs";
|
|
85735
|
-
import { dirname as dirname23, join as
|
|
85910
|
+
import { dirname as dirname23, join as join72 } from "node:path";
|
|
85736
85911
|
import { homedir as homedir43 } from "node:os";
|
|
85737
85912
|
import { execFileSync as execFileSync22 } from "node:child_process";
|
|
85738
85913
|
|
|
@@ -85756,7 +85931,7 @@ var LOCKFILES_FOR = {
|
|
|
85756
85931
|
npm: ["package-lock.json"]
|
|
85757
85932
|
};
|
|
85758
85933
|
function defaultNodeCacheRoot() {
|
|
85759
|
-
return
|
|
85934
|
+
return join72(homedir43(), ".switchroom", "deps", "node");
|
|
85760
85935
|
}
|
|
85761
85936
|
function hashDepInputs(packageJsonPath) {
|
|
85762
85937
|
const sourceDir = dirname23(packageJsonPath);
|
|
@@ -85765,8 +85940,8 @@ function hashDepInputs(packageJsonPath) {
|
|
|
85765
85940
|
`);
|
|
85766
85941
|
hasher.update(readFileSync64(packageJsonPath));
|
|
85767
85942
|
for (const lockName of ALL_LOCKFILES) {
|
|
85768
|
-
const lockPath =
|
|
85769
|
-
if (
|
|
85943
|
+
const lockPath = join72(sourceDir, lockName);
|
|
85944
|
+
if (existsSync71(lockPath)) {
|
|
85770
85945
|
hasher.update(`
|
|
85771
85946
|
`);
|
|
85772
85947
|
hasher.update(lockName);
|
|
@@ -85781,16 +85956,16 @@ function ensureNodeEnv(opts) {
|
|
|
85781
85956
|
const { skillName, packageJsonPath, force = false } = opts;
|
|
85782
85957
|
const cacheRoot = opts.cacheRoot ?? defaultNodeCacheRoot();
|
|
85783
85958
|
const installer = opts.installer ?? "bun";
|
|
85784
|
-
if (!
|
|
85959
|
+
if (!existsSync71(packageJsonPath)) {
|
|
85785
85960
|
throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
|
|
85786
85961
|
}
|
|
85787
85962
|
const sourceDir = dirname23(packageJsonPath);
|
|
85788
|
-
const envDir =
|
|
85789
|
-
const stampPath =
|
|
85790
|
-
const nodeModulesDir =
|
|
85791
|
-
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");
|
|
85792
85967
|
const targetHash = hashDepInputs(packageJsonPath);
|
|
85793
|
-
if (!force &&
|
|
85968
|
+
if (!force && existsSync71(stampPath) && existsSync71(nodeModulesDir)) {
|
|
85794
85969
|
const existingHash = readFileSync64(stampPath, "utf8").trim();
|
|
85795
85970
|
if (existingHash === targetHash) {
|
|
85796
85971
|
return {
|
|
@@ -85802,16 +85977,16 @@ function ensureNodeEnv(opts) {
|
|
|
85802
85977
|
};
|
|
85803
85978
|
}
|
|
85804
85979
|
}
|
|
85805
|
-
if (
|
|
85980
|
+
if (existsSync71(envDir)) {
|
|
85806
85981
|
rmSync14(envDir, { recursive: true, force: true });
|
|
85807
85982
|
}
|
|
85808
85983
|
mkdirSync37(envDir, { recursive: true });
|
|
85809
|
-
copyFileSync10(packageJsonPath,
|
|
85984
|
+
copyFileSync10(packageJsonPath, join72(envDir, "package.json"));
|
|
85810
85985
|
let copiedLockfile = false;
|
|
85811
85986
|
for (const lockName of LOCKFILES_FOR[installer]) {
|
|
85812
|
-
const lockPath =
|
|
85813
|
-
if (
|
|
85814
|
-
copyFileSync10(lockPath,
|
|
85987
|
+
const lockPath = join72(sourceDir, lockName);
|
|
85988
|
+
if (existsSync71(lockPath)) {
|
|
85989
|
+
copyFileSync10(lockPath, join72(envDir, lockName));
|
|
85815
85990
|
copiedLockfile = true;
|
|
85816
85991
|
}
|
|
85817
85992
|
}
|
|
@@ -85846,22 +86021,22 @@ function registerDepsCommand(program3) {
|
|
|
85846
86021
|
const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
|
|
85847
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) => {
|
|
85848
86023
|
const skillsRoot = builtinSkillsRoot();
|
|
85849
|
-
if (!
|
|
86024
|
+
if (!existsSync72(skillsRoot)) {
|
|
85850
86025
|
console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
|
|
85851
86026
|
process.exit(1);
|
|
85852
86027
|
}
|
|
85853
|
-
const skillDir =
|
|
85854
|
-
if (!
|
|
86028
|
+
const skillDir = join73(skillsRoot, skill);
|
|
86029
|
+
if (!existsSync72(skillDir)) {
|
|
85855
86030
|
console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
|
|
85856
86031
|
process.exit(1);
|
|
85857
86032
|
}
|
|
85858
|
-
const requirementsPath =
|
|
85859
|
-
const packageJsonPath =
|
|
85860
|
-
const wantPython = opts.python ?? (!opts.python && !opts.node &&
|
|
85861
|
-
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));
|
|
85862
86037
|
let did = 0;
|
|
85863
86038
|
if (wantPython) {
|
|
85864
|
-
if (!
|
|
86039
|
+
if (!existsSync72(requirementsPath)) {
|
|
85865
86040
|
console.error(source_default.red(`Skill "${skill}" has no requirements.txt at ${requirementsPath}`));
|
|
85866
86041
|
process.exit(1);
|
|
85867
86042
|
}
|
|
@@ -85885,7 +86060,7 @@ function registerDepsCommand(program3) {
|
|
|
85885
86060
|
}
|
|
85886
86061
|
}
|
|
85887
86062
|
if (wantNode) {
|
|
85888
|
-
if (!
|
|
86063
|
+
if (!existsSync72(packageJsonPath)) {
|
|
85889
86064
|
console.error(source_default.red(`Skill "${skill}" has no package.json at ${packageJsonPath}`));
|
|
85890
86065
|
process.exit(1);
|
|
85891
86066
|
}
|
|
@@ -85918,7 +86093,7 @@ function registerDepsCommand(program3) {
|
|
|
85918
86093
|
// src/cli/workspace.ts
|
|
85919
86094
|
init_helpers();
|
|
85920
86095
|
init_loader();
|
|
85921
|
-
import { existsSync as
|
|
86096
|
+
import { existsSync as existsSync73 } from "node:fs";
|
|
85922
86097
|
import { resolve as resolve44, sep as sep3 } from "node:path";
|
|
85923
86098
|
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
85924
86099
|
|
|
@@ -86695,7 +86870,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
86695
86870
|
if (!dir)
|
|
86696
86871
|
return;
|
|
86697
86872
|
const gitDir = resolve44(dir, ".git");
|
|
86698
|
-
if (!
|
|
86873
|
+
if (!existsSync73(gitDir)) {
|
|
86699
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.
|
|
86700
86875
|
`);
|
|
86701
86876
|
return;
|
|
@@ -86749,7 +86924,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
86749
86924
|
if (!dir)
|
|
86750
86925
|
return;
|
|
86751
86926
|
const gitDir = resolve44(dir, ".git");
|
|
86752
|
-
if (!
|
|
86927
|
+
if (!existsSync73(gitDir)) {
|
|
86753
86928
|
process.stdout.write(`Workspace is not a git repository.
|
|
86754
86929
|
`);
|
|
86755
86930
|
return;
|
|
@@ -86774,7 +86949,7 @@ function resolveAgentWorkspaceDirOrExit(program3, agentName) {
|
|
|
86774
86949
|
const agentsDir = resolveAgentsDir(config);
|
|
86775
86950
|
const agentDir = resolve44(agentsDir, agentName);
|
|
86776
86951
|
const dir = resolveAgentWorkspaceDir(agentDir);
|
|
86777
|
-
if (!
|
|
86952
|
+
if (!existsSync73(dir)) {
|
|
86778
86953
|
process.stderr.write(`workspace: ${dir} does not exist yet. Run \`switchroom setup\` or \`switchroom agent scaffold ${agentName}\` to seed it.
|
|
86779
86954
|
`);
|
|
86780
86955
|
return;
|
|
@@ -86810,8 +86985,8 @@ function safeParseInt(value, fallback) {
|
|
|
86810
86985
|
init_helpers();
|
|
86811
86986
|
init_loader();
|
|
86812
86987
|
init_merge();
|
|
86813
|
-
import { copyFileSync as copyFileSync11, existsSync as
|
|
86814
|
-
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";
|
|
86815
86990
|
init_scaffold();
|
|
86816
86991
|
init_profiles();
|
|
86817
86992
|
init_schema();
|
|
@@ -86828,7 +87003,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
86828
87003
|
const agentsDir = resolveAgentsDir(config);
|
|
86829
87004
|
const agentDir = resolve45(agentsDir, agentName);
|
|
86830
87005
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
86831
|
-
if (!
|
|
87006
|
+
if (!existsSync74(workspaceDir)) {
|
|
86832
87007
|
console.error(`soul: ${workspaceDir} does not exist yet. Run \`switchroom setup\` ` + `or \`switchroom agent scaffold ${agentName}\` to seed it.`);
|
|
86833
87008
|
process.exit(1);
|
|
86834
87009
|
}
|
|
@@ -86837,7 +87012,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
86837
87012
|
profileName,
|
|
86838
87013
|
profilePath,
|
|
86839
87014
|
workspaceDir,
|
|
86840
|
-
soulPath:
|
|
87015
|
+
soulPath: join74(workspaceDir, "SOUL.md"),
|
|
86841
87016
|
soul: merged.soul
|
|
86842
87017
|
};
|
|
86843
87018
|
}
|
|
@@ -86854,7 +87029,7 @@ function registerSoulCommand(program3) {
|
|
|
86854
87029
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
86855
87030
|
if (!t)
|
|
86856
87031
|
return;
|
|
86857
|
-
if (!
|
|
87032
|
+
if (!existsSync74(t.soulPath)) {
|
|
86858
87033
|
console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
|
|
86859
87034
|
process.exit(1);
|
|
86860
87035
|
}
|
|
@@ -86869,7 +87044,7 @@ function registerSoulCommand(program3) {
|
|
|
86869
87044
|
console.error(`soul: profile "${t.profileName}" ships no SOUL.md.hbs \u2014 ` + `nothing to re-seed from.`);
|
|
86870
87045
|
process.exit(1);
|
|
86871
87046
|
}
|
|
86872
|
-
const exists =
|
|
87047
|
+
const exists = existsSync74(t.soulPath);
|
|
86873
87048
|
if (exists && !opts.yes) {
|
|
86874
87049
|
if (!isInteractive()) {
|
|
86875
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).`);
|
|
@@ -86884,7 +87059,7 @@ function registerSoulCommand(program3) {
|
|
|
86884
87059
|
let backupPath;
|
|
86885
87060
|
if (exists) {
|
|
86886
87061
|
backupPath = `${t.soulPath}.bak`;
|
|
86887
|
-
if (
|
|
87062
|
+
if (existsSync74(backupPath)) {
|
|
86888
87063
|
backupPath = `${t.soulPath}.bak.${Date.now()}`;
|
|
86889
87064
|
}
|
|
86890
87065
|
copyFileSync11(t.soulPath, backupPath);
|
|
@@ -86903,8 +87078,8 @@ function registerSoulCommand(program3) {
|
|
|
86903
87078
|
// src/cli/debug.ts
|
|
86904
87079
|
init_helpers();
|
|
86905
87080
|
init_loader();
|
|
86906
|
-
import { existsSync as
|
|
86907
|
-
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";
|
|
86908
87083
|
import { createHash as createHash15 } from "node:crypto";
|
|
86909
87084
|
init_merge();
|
|
86910
87085
|
init_hindsight2();
|
|
@@ -86915,8 +87090,8 @@ function estimateTokens(bytes) {
|
|
|
86915
87090
|
return Math.round(bytes / 3.7);
|
|
86916
87091
|
}
|
|
86917
87092
|
function readMcpServerNames(agentDir) {
|
|
86918
|
-
const mcpPath =
|
|
86919
|
-
if (!
|
|
87093
|
+
const mcpPath = join75(agentDir, ".mcp.json");
|
|
87094
|
+
if (!existsSync75(mcpPath))
|
|
86920
87095
|
return [];
|
|
86921
87096
|
try {
|
|
86922
87097
|
const parsed = JSON.parse(readFileSync66(mcpPath, "utf-8"));
|
|
@@ -86929,8 +87104,8 @@ function sha256(content) {
|
|
|
86929
87104
|
return createHash15("sha256").update(content).digest("hex").slice(0, 16);
|
|
86930
87105
|
}
|
|
86931
87106
|
function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
86932
|
-
const projectsDir =
|
|
86933
|
-
if (!
|
|
87107
|
+
const projectsDir = join75(claudeConfigDir, "projects");
|
|
87108
|
+
if (!existsSync75(projectsDir))
|
|
86934
87109
|
return;
|
|
86935
87110
|
try {
|
|
86936
87111
|
const entries = readdirSync25(projectsDir, { withFileTypes: true });
|
|
@@ -86938,11 +87113,11 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
86938
87113
|
for (const entry of entries) {
|
|
86939
87114
|
if (!entry.isDirectory())
|
|
86940
87115
|
continue;
|
|
86941
|
-
const projectPath =
|
|
86942
|
-
const transcriptPath =
|
|
86943
|
-
if (!
|
|
87116
|
+
const projectPath = join75(projectsDir, entry.name);
|
|
87117
|
+
const transcriptPath = join75(projectPath, "transcript.jsonl");
|
|
87118
|
+
if (!existsSync75(transcriptPath))
|
|
86944
87119
|
continue;
|
|
86945
|
-
const stat3 =
|
|
87120
|
+
const stat3 = statSync44(transcriptPath);
|
|
86946
87121
|
if (!latest || stat3.mtimeMs > latest.mtime) {
|
|
86947
87122
|
latest = { path: transcriptPath, mtime: stat3.mtimeMs };
|
|
86948
87123
|
}
|
|
@@ -87003,16 +87178,16 @@ function registerDebugCommand(program3) {
|
|
|
87003
87178
|
}
|
|
87004
87179
|
const agentsDir = resolveAgentsDir(config);
|
|
87005
87180
|
const agentDir = resolve46(agentsDir, agentName);
|
|
87006
|
-
if (!
|
|
87181
|
+
if (!existsSync75(agentDir)) {
|
|
87007
87182
|
console.error(`Agent directory not found: ${agentDir}`);
|
|
87008
87183
|
process.exit(1);
|
|
87009
87184
|
}
|
|
87010
87185
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
87011
|
-
const claudeConfigDir =
|
|
87012
|
-
const claudeMdPath =
|
|
87013
|
-
const soulMdPath =
|
|
87014
|
-
const workspaceSoulMdPath =
|
|
87015
|
-
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");
|
|
87016
87191
|
const lastN = parseInt(opts.last, 10);
|
|
87017
87192
|
if (isNaN(lastN) || lastN < 1) {
|
|
87018
87193
|
console.error("--last must be a positive integer");
|
|
@@ -87058,7 +87233,7 @@ function registerDebugCommand(program3) {
|
|
|
87058
87233
|
}
|
|
87059
87234
|
console.log(`=== Append System Prompt (per-session) ===
|
|
87060
87235
|
`);
|
|
87061
|
-
const handoffContent =
|
|
87236
|
+
const handoffContent = existsSync75(handoffPath) ? readFileSync66(handoffPath, "utf-8") : "";
|
|
87062
87237
|
if (handoffContent.trim().length > 0) {
|
|
87063
87238
|
console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
|
|
87064
87239
|
console.log(handoffContent);
|
|
@@ -87069,7 +87244,7 @@ function registerDebugCommand(program3) {
|
|
|
87069
87244
|
}
|
|
87070
87245
|
console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
|
|
87071
87246
|
`);
|
|
87072
|
-
const claudeMdContent =
|
|
87247
|
+
const claudeMdContent = existsSync75(claudeMdPath) ? readFileSync66(claudeMdPath, "utf-8") : "";
|
|
87073
87248
|
if (claudeMdContent.trim().length > 0) {
|
|
87074
87249
|
console.log(`(${formatBytes(claudeMdContent.length)})`);
|
|
87075
87250
|
console.log(claudeMdContent);
|
|
@@ -87080,7 +87255,7 @@ function registerDebugCommand(program3) {
|
|
|
87080
87255
|
}
|
|
87081
87256
|
console.log(`=== Persona (SOUL.md) ===
|
|
87082
87257
|
`);
|
|
87083
|
-
const soulMdContent =
|
|
87258
|
+
const soulMdContent = existsSync75(soulMdPath) ? readFileSync66(soulMdPath, "utf-8") : existsSync75(workspaceSoulMdPath) ? readFileSync66(workspaceSoulMdPath, "utf-8") : "";
|
|
87084
87259
|
if (soulMdContent.trim().length > 0) {
|
|
87085
87260
|
console.log(`(${formatBytes(soulMdContent.length)})`);
|
|
87086
87261
|
console.log(soulMdContent);
|
|
@@ -87141,11 +87316,11 @@ function registerDebugCommand(program3) {
|
|
|
87141
87316
|
const soulMdBytes = soulMdContent.length;
|
|
87142
87317
|
const perTurnBytes = dynamicResult.concatenated.length;
|
|
87143
87318
|
const userBytes = userMessage?.text.length ?? 0;
|
|
87144
|
-
const fleetDir =
|
|
87145
|
-
const fleetInvPath =
|
|
87146
|
-
const fleetClaudePath =
|
|
87147
|
-
const fleetInvBytes =
|
|
87148
|
-
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;
|
|
87149
87324
|
const fleetBytes = fleetInvBytes + fleetClaudeBytes;
|
|
87150
87325
|
const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
|
|
87151
87326
|
console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
|
|
@@ -87178,8 +87353,8 @@ init_source();
|
|
|
87178
87353
|
|
|
87179
87354
|
// src/worktree/claim.ts
|
|
87180
87355
|
import { execFileSync as execFileSync23 } from "node:child_process";
|
|
87181
|
-
import { closeSync as closeSync13, mkdirSync as mkdirSync39, openSync as openSync13, existsSync as
|
|
87182
|
-
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";
|
|
87183
87358
|
import { homedir as homedir46 } from "node:os";
|
|
87184
87359
|
import { randomBytes as randomBytes13 } from "node:crypto";
|
|
87185
87360
|
|
|
@@ -87190,16 +87365,16 @@ import {
|
|
|
87190
87365
|
readFileSync as readFileSync67,
|
|
87191
87366
|
readdirSync as readdirSync26,
|
|
87192
87367
|
unlinkSync as unlinkSync16,
|
|
87193
|
-
existsSync as
|
|
87368
|
+
existsSync as existsSync76,
|
|
87194
87369
|
renameSync as renameSync16
|
|
87195
87370
|
} from "node:fs";
|
|
87196
|
-
import { join as
|
|
87371
|
+
import { join as join76, resolve as resolve47 } from "node:path";
|
|
87197
87372
|
import { homedir as homedir45 } from "node:os";
|
|
87198
87373
|
function registryDir() {
|
|
87199
|
-
return resolve47(process.env.SWITCHROOM_WORKTREE_DIR ??
|
|
87374
|
+
return resolve47(process.env.SWITCHROOM_WORKTREE_DIR ?? join76(homedir45(), ".switchroom", "worktrees"));
|
|
87200
87375
|
}
|
|
87201
87376
|
function recordPath(id) {
|
|
87202
|
-
return
|
|
87377
|
+
return join76(registryDir(), `${id}.json`);
|
|
87203
87378
|
}
|
|
87204
87379
|
function ensureDir2() {
|
|
87205
87380
|
mkdirSync38(registryDir(), { recursive: true });
|
|
@@ -87250,7 +87425,7 @@ function acquireRepoLock(repoPath) {
|
|
|
87250
87425
|
const lockDir = registryDir();
|
|
87251
87426
|
mkdirSync39(lockDir, { recursive: true });
|
|
87252
87427
|
const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
|
|
87253
|
-
const lockPath =
|
|
87428
|
+
const lockPath = join77(lockDir, `.lock-${lockName}`);
|
|
87254
87429
|
const deadline = Date.now() + 5000;
|
|
87255
87430
|
let fd = null;
|
|
87256
87431
|
while (fd === null) {
|
|
@@ -87277,7 +87452,7 @@ function acquireRepoLock(repoPath) {
|
|
|
87277
87452
|
}
|
|
87278
87453
|
var DEFAULT_CONCURRENCY = 5;
|
|
87279
87454
|
function worktreesBaseDir() {
|
|
87280
|
-
return resolve48(process.env.SWITCHROOM_WORKTREE_BASE ??
|
|
87455
|
+
return resolve48(process.env.SWITCHROOM_WORKTREE_BASE ?? join77(homedir46(), ".switchroom", "worktree-checkouts"));
|
|
87281
87456
|
}
|
|
87282
87457
|
function shortId() {
|
|
87283
87458
|
return randomBytes13(4).toString("hex");
|
|
@@ -87299,12 +87474,12 @@ function resolveRepoPath(repo, codeRepos) {
|
|
|
87299
87474
|
}
|
|
87300
87475
|
function expandHome(p) {
|
|
87301
87476
|
if (p.startsWith("~/"))
|
|
87302
|
-
return
|
|
87477
|
+
return join77(homedir46(), p.slice(2));
|
|
87303
87478
|
return p;
|
|
87304
87479
|
}
|
|
87305
87480
|
async function claimWorktree(input, codeRepos) {
|
|
87306
87481
|
const repoPath = resolveRepoPath(input.repo, codeRepos);
|
|
87307
|
-
if (!
|
|
87482
|
+
if (!existsSync77(repoPath)) {
|
|
87308
87483
|
throw new Error(`Repository path does not exist: ${repoPath}`);
|
|
87309
87484
|
}
|
|
87310
87485
|
let concurrencyCap = DEFAULT_CONCURRENCY;
|
|
@@ -87327,7 +87502,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
87327
87502
|
branch = `task/${taskSuffix}-${id}`;
|
|
87328
87503
|
const baseDir = worktreesBaseDir();
|
|
87329
87504
|
mkdirSync39(baseDir, { recursive: true });
|
|
87330
|
-
worktreePath =
|
|
87505
|
+
worktreePath = join77(baseDir, `${id}-${taskSuffix}`);
|
|
87331
87506
|
const ambientOwner = process.env.SWITCHROOM_AGENT_NAME;
|
|
87332
87507
|
const ownerAgent = input.ownerAgent ?? (ambientOwner != null && ambientOwner !== "" ? ambientOwner : undefined);
|
|
87333
87508
|
const now = new Date().toISOString();
|
|
@@ -87360,7 +87535,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
87360
87535
|
|
|
87361
87536
|
// src/worktree/release.ts
|
|
87362
87537
|
import { execFileSync as execFileSync24 } from "node:child_process";
|
|
87363
|
-
import { existsSync as
|
|
87538
|
+
import { existsSync as existsSync78 } from "node:fs";
|
|
87364
87539
|
function releaseWorktree(input) {
|
|
87365
87540
|
const { id } = input;
|
|
87366
87541
|
const record2 = readRecord(id);
|
|
@@ -87368,7 +87543,7 @@ function releaseWorktree(input) {
|
|
|
87368
87543
|
return { released: true };
|
|
87369
87544
|
}
|
|
87370
87545
|
let gitSuccess = true;
|
|
87371
|
-
if (
|
|
87546
|
+
if (existsSync78(record2.path)) {
|
|
87372
87547
|
try {
|
|
87373
87548
|
execFileSync24("git", ["worktree", "remove", "--force", record2.path], {
|
|
87374
87549
|
cwd: record2.repo,
|
|
@@ -87407,7 +87582,7 @@ function listWorktrees() {
|
|
|
87407
87582
|
|
|
87408
87583
|
// src/worktree/reaper.ts
|
|
87409
87584
|
import { execFileSync as execFileSync25 } from "node:child_process";
|
|
87410
|
-
import { existsSync as
|
|
87585
|
+
import { existsSync as existsSync79 } from "node:fs";
|
|
87411
87586
|
var STALE_THRESHOLD_MS = 10 * 60 * 1000;
|
|
87412
87587
|
function reapSkipReasonText(action) {
|
|
87413
87588
|
switch (action) {
|
|
@@ -87468,7 +87643,7 @@ function planReaper(nowMs, deps = {}) {
|
|
|
87468
87643
|
const plan = [];
|
|
87469
87644
|
for (const record2 of listRecords()) {
|
|
87470
87645
|
const heartbeatAge = now - new Date(record2.heartbeatAt).getTime();
|
|
87471
|
-
const worktreeExists =
|
|
87646
|
+
const worktreeExists = existsSync79(record2.path);
|
|
87472
87647
|
if (!worktreeExists) {
|
|
87473
87648
|
plan.push({
|
|
87474
87649
|
record: record2,
|
|
@@ -87549,16 +87724,16 @@ function runReaper(nowMs, deps = {}) {
|
|
|
87549
87724
|
// src/worktree/gc.ts
|
|
87550
87725
|
import { execFileSync as execFileSync26 } from "node:child_process";
|
|
87551
87726
|
import {
|
|
87552
|
-
existsSync as
|
|
87727
|
+
existsSync as existsSync80,
|
|
87553
87728
|
readFileSync as readFileSync68,
|
|
87554
87729
|
readdirSync as readdirSync27,
|
|
87555
|
-
statSync as
|
|
87730
|
+
statSync as statSync45,
|
|
87556
87731
|
renameSync as renameSync17,
|
|
87557
87732
|
mkdirSync as mkdirSync40,
|
|
87558
87733
|
rmSync as rmSync15
|
|
87559
87734
|
} from "node:fs";
|
|
87560
87735
|
import { homedir as homedir47 } from "node:os";
|
|
87561
|
-
import { join as
|
|
87736
|
+
import { join as join78, resolve as resolve49 } from "node:path";
|
|
87562
87737
|
function parseGitdirPointer(dotGitFileContents) {
|
|
87563
87738
|
const m = /^gitdir:\s*(.+?)\s*$/m.exec(dotGitFileContents);
|
|
87564
87739
|
return m ? m[1] : null;
|
|
@@ -87673,17 +87848,17 @@ function defaultPrSignal(repo, branch, exec) {
|
|
|
87673
87848
|
}
|
|
87674
87849
|
}
|
|
87675
87850
|
function trashRoot() {
|
|
87676
|
-
return resolve49(process.env.SWITCHROOM_WORKTREE_TRASH ??
|
|
87851
|
+
return resolve49(process.env.SWITCHROOM_WORKTREE_TRASH ?? join78(homedir47(), ".switchroom", "worktree-gc-trash"));
|
|
87677
87852
|
}
|
|
87678
87853
|
function planGc(roots, deps = {}) {
|
|
87679
|
-
const exists = deps.existsSync ??
|
|
87854
|
+
const exists = deps.existsSync ?? existsSync80;
|
|
87680
87855
|
const readDir = deps.readDir ?? ((p) => readdirSync27(p));
|
|
87681
87856
|
const readFile4 = deps.readFile ?? ((p) => readFileSync68(p, "utf8"));
|
|
87682
|
-
const stat3 = deps.stat ?? ((p) =>
|
|
87857
|
+
const stat3 = deps.stat ?? ((p) => statSync45(p));
|
|
87683
87858
|
const exec = deps.exec ?? defaultExec;
|
|
87684
87859
|
const prSignal = deps.prSignal ?? ((repo, branch) => defaultPrSignal(repo, branch, exec));
|
|
87685
87860
|
const stamp = deps.dateStamp ?? "undated";
|
|
87686
|
-
const trash =
|
|
87861
|
+
const trash = join78(trashRoot(), stamp);
|
|
87687
87862
|
let claimed;
|
|
87688
87863
|
try {
|
|
87689
87864
|
claimed = new Set(listRecords().map((r) => resolve49(r.path)));
|
|
@@ -87719,10 +87894,10 @@ function planGc(roots, deps = {}) {
|
|
|
87719
87894
|
continue;
|
|
87720
87895
|
}
|
|
87721
87896
|
for (const name of entries) {
|
|
87722
|
-
const dir =
|
|
87897
|
+
const dir = join78(root, name);
|
|
87723
87898
|
if (isEphemeralPath(dir))
|
|
87724
87899
|
continue;
|
|
87725
|
-
const dotGit =
|
|
87900
|
+
const dotGit = join78(dir, ".git");
|
|
87726
87901
|
if (!exists(dotGit))
|
|
87727
87902
|
continue;
|
|
87728
87903
|
let st;
|
|
@@ -87751,7 +87926,7 @@ function planGc(roots, deps = {}) {
|
|
|
87751
87926
|
ownerRepos.add(repoRoot);
|
|
87752
87927
|
if (exists(ptr))
|
|
87753
87928
|
continue;
|
|
87754
|
-
orphans.push({ dir, owner: repoRoot, dest:
|
|
87929
|
+
orphans.push({ dir, owner: repoRoot, dest: join78(trash, name) });
|
|
87755
87930
|
}
|
|
87756
87931
|
}
|
|
87757
87932
|
const registered = [];
|
|
@@ -87855,14 +88030,14 @@ function selectPurgeTargets(entries, olderThanDays) {
|
|
|
87855
88030
|
return entries.filter((e) => e.ageDays >= olderThanDays).map((e) => e.path);
|
|
87856
88031
|
}
|
|
87857
88032
|
function listTrashEntries(nowMs, deps = {}) {
|
|
87858
|
-
const exists = deps.existsSync ??
|
|
88033
|
+
const exists = deps.existsSync ?? existsSync80;
|
|
87859
88034
|
const readDir = deps.readDir ?? ((p) => readdirSync27(p));
|
|
87860
88035
|
const root = trashRoot();
|
|
87861
88036
|
if (!exists(root))
|
|
87862
88037
|
return [];
|
|
87863
88038
|
const out = [];
|
|
87864
88039
|
for (const stamp of readDir(root)) {
|
|
87865
|
-
const stampDir =
|
|
88040
|
+
const stampDir = join78(root, stamp);
|
|
87866
88041
|
let names;
|
|
87867
88042
|
try {
|
|
87868
88043
|
names = readDir(stampDir);
|
|
@@ -87870,10 +88045,10 @@ function listTrashEntries(nowMs, deps = {}) {
|
|
|
87870
88045
|
continue;
|
|
87871
88046
|
}
|
|
87872
88047
|
for (const name of names) {
|
|
87873
|
-
const p =
|
|
88048
|
+
const p = join78(stampDir, name);
|
|
87874
88049
|
let mtimeMs = nowMs;
|
|
87875
88050
|
try {
|
|
87876
|
-
mtimeMs =
|
|
88051
|
+
mtimeMs = statSync45(p).mtimeMs;
|
|
87877
88052
|
} catch {}
|
|
87878
88053
|
out.push({ path: p, ageDays: (nowMs - mtimeMs) / 86400000 });
|
|
87879
88054
|
}
|
|
@@ -87894,7 +88069,7 @@ function purgeTrash(paths) {
|
|
|
87894
88069
|
return { deleted, errors: errors2 };
|
|
87895
88070
|
}
|
|
87896
88071
|
function defaultRoots() {
|
|
87897
|
-
return [
|
|
88072
|
+
return [join78(homedir47(), "code")];
|
|
87898
88073
|
}
|
|
87899
88074
|
|
|
87900
88075
|
// src/cli/worktree.ts
|
|
@@ -88122,7 +88297,7 @@ import {
|
|
|
88122
88297
|
rmSync as rmSync16,
|
|
88123
88298
|
writeFileSync as writeFileSync24
|
|
88124
88299
|
} from "node:fs";
|
|
88125
|
-
import { join as
|
|
88300
|
+
import { join as join79 } from "node:path";
|
|
88126
88301
|
function encodeCredentialsFilename(email) {
|
|
88127
88302
|
const SAFE = new Set([
|
|
88128
88303
|
..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
|
@@ -88312,16 +88487,16 @@ function resolveCredentialsDir(env2) {
|
|
|
88312
88487
|
if (explicit && explicit.length > 0)
|
|
88313
88488
|
return explicit;
|
|
88314
88489
|
const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
|
|
88315
|
-
return
|
|
88490
|
+
return join79(stateBase, "google-workspace-mcp", "credentials");
|
|
88316
88491
|
}
|
|
88317
88492
|
function writeSeedFile(dir, email, seed) {
|
|
88318
88493
|
mkdirSync41(dir, { recursive: true, mode: 448 });
|
|
88319
88494
|
chmodSync10(dir, 448);
|
|
88320
88495
|
for (const name of readdirSync28(dir)) {
|
|
88321
|
-
rmSync16(
|
|
88496
|
+
rmSync16(join79(dir, name), { force: true, recursive: true });
|
|
88322
88497
|
}
|
|
88323
88498
|
const filename = encodeCredentialsFilename(email);
|
|
88324
|
-
const filePath =
|
|
88499
|
+
const filePath = join79(dir, filename);
|
|
88325
88500
|
writeFileSync24(filePath, JSON.stringify(seed), { mode: 384 });
|
|
88326
88501
|
chmodSync10(filePath, 384);
|
|
88327
88502
|
return filePath;
|
|
@@ -88481,7 +88656,7 @@ function registerDriveMcpLauncherCommand(program3) {
|
|
|
88481
88656
|
init_scaffold_integration();
|
|
88482
88657
|
import { spawn as spawn5 } from "node:child_process";
|
|
88483
88658
|
import { writeFileSync as writeFileSync25, mkdirSync as mkdirSync42 } from "node:fs";
|
|
88484
|
-
import { dirname as dirname24, join as
|
|
88659
|
+
import { dirname as dirname24, join as join80 } from "node:path";
|
|
88485
88660
|
var SOFTERIA_TOKEN_ENV = "MS365_MCP_OAUTH_TOKEN";
|
|
88486
88661
|
var DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
|
|
88487
88662
|
var MAX_REFRESH_INTERVAL_MS = 60 * 60 * 1000;
|
|
@@ -88521,7 +88696,7 @@ function writeRefreshHeartbeat(agentName, data) {
|
|
|
88521
88696
|
function heartbeatPath(agentName) {
|
|
88522
88697
|
const override = process.env.SWITCHROOM_M365_HEARTBEAT_DIR;
|
|
88523
88698
|
if (override) {
|
|
88524
|
-
return
|
|
88699
|
+
return join80(override, `m365-launcher-${agentName}.heartbeat.json`);
|
|
88525
88700
|
}
|
|
88526
88701
|
return "/state/agent/m365-launcher.heartbeat.json";
|
|
88527
88702
|
}
|
|
@@ -88753,7 +88928,7 @@ function registerM365McpLauncherCommand(program3) {
|
|
|
88753
88928
|
// src/cli/notion-mcp-launcher.ts
|
|
88754
88929
|
init_scaffold_integration();
|
|
88755
88930
|
import { spawn as spawn6 } from "node:child_process";
|
|
88756
|
-
import { existsSync as
|
|
88931
|
+
import { existsSync as existsSync81, mkdirSync as mkdirSync43, writeFileSync as writeFileSync26 } from "node:fs";
|
|
88757
88932
|
import { dirname as dirname25 } from "node:path";
|
|
88758
88933
|
var HEARTBEAT_WRITE_INTERVAL_MS = 30 * 1000;
|
|
88759
88934
|
var DEFAULT_HEARTBEAT_PATH = "/state/agent/notion-launcher.heartbeat.json";
|
|
@@ -88765,7 +88940,7 @@ function buildNotionMcpArgs(opts) {
|
|
|
88765
88940
|
function defaultWriteHeartbeat(path7, contents) {
|
|
88766
88941
|
try {
|
|
88767
88942
|
const dir = dirname25(path7);
|
|
88768
|
-
if (!
|
|
88943
|
+
if (!existsSync81(dir))
|
|
88769
88944
|
mkdirSync43(dir, { recursive: true });
|
|
88770
88945
|
writeFileSync26(path7, contents);
|
|
88771
88946
|
} catch {}
|
|
@@ -88877,7 +89052,7 @@ function registerNotionMcpLauncherCommand(program3) {
|
|
|
88877
89052
|
|
|
88878
89053
|
// src/cli/deliver-file.ts
|
|
88879
89054
|
init_client2();
|
|
88880
|
-
import { readFileSync as readFileSync69, statSync as
|
|
89055
|
+
import { readFileSync as readFileSync69, statSync as statSync46 } from "node:fs";
|
|
88881
89056
|
import { basename as basename11 } from "node:path";
|
|
88882
89057
|
|
|
88883
89058
|
// src/delivery/onedrive.ts
|
|
@@ -89216,7 +89391,7 @@ async function defaultResolveProvider() {
|
|
|
89216
89391
|
}
|
|
89217
89392
|
async function runDeliverFile(localPath, deps = {}) {
|
|
89218
89393
|
const agentName = safeAgentName(deps.agentName ?? process.env.SWITCHROOM_AGENT_NAME);
|
|
89219
|
-
const sizeOf = deps.fileSize ?? ((p) =>
|
|
89394
|
+
const sizeOf = deps.fileSize ?? ((p) => statSync46(p).size);
|
|
89220
89395
|
const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync69(p)));
|
|
89221
89396
|
const resolveProvider = deps.resolveProvider ?? defaultResolveProvider;
|
|
89222
89397
|
let size;
|
|
@@ -89538,8 +89713,8 @@ function runRedactStdin() {
|
|
|
89538
89713
|
}
|
|
89539
89714
|
|
|
89540
89715
|
// src/cli/status-ask.ts
|
|
89541
|
-
import { readFileSync as readFileSync73, existsSync as
|
|
89542
|
-
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";
|
|
89543
89718
|
import { homedir as homedir50 } from "node:os";
|
|
89544
89719
|
|
|
89545
89720
|
// src/status-ask/report.ts
|
|
@@ -89861,7 +90036,7 @@ function runReport(opts) {
|
|
|
89861
90036
|
function resolveSources(explicitPath) {
|
|
89862
90037
|
if (explicitPath != null && explicitPath.trim() !== "") {
|
|
89863
90038
|
const trimmed = explicitPath.trim();
|
|
89864
|
-
if (!
|
|
90039
|
+
if (!existsSync86(trimmed)) {
|
|
89865
90040
|
process.stderr.write(`status-ask report: ${trimmed}: file not found
|
|
89866
90041
|
`);
|
|
89867
90042
|
process.exit(1);
|
|
@@ -89875,9 +90050,9 @@ function resolveSources(explicitPath) {
|
|
|
89875
90050
|
const config = loadConfig();
|
|
89876
90051
|
agentsDir = resolveAgentsDir(config);
|
|
89877
90052
|
} catch {
|
|
89878
|
-
agentsDir =
|
|
90053
|
+
agentsDir = join85(homedir50(), ".switchroom", "agents");
|
|
89879
90054
|
}
|
|
89880
|
-
if (!
|
|
90055
|
+
if (!existsSync86(agentsDir))
|
|
89881
90056
|
return [];
|
|
89882
90057
|
const sources = [];
|
|
89883
90058
|
let entries;
|
|
@@ -89887,8 +90062,8 @@ function resolveSources(explicitPath) {
|
|
|
89887
90062
|
return [];
|
|
89888
90063
|
}
|
|
89889
90064
|
for (const name of entries) {
|
|
89890
|
-
const path8 =
|
|
89891
|
-
if (
|
|
90065
|
+
const path8 = join85(agentsDir, name, "runtime-metrics.jsonl");
|
|
90066
|
+
if (existsSync86(path8)) {
|
|
89892
90067
|
sources.push({ path: path8, agent: name });
|
|
89893
90068
|
}
|
|
89894
90069
|
}
|
|
@@ -89918,32 +90093,32 @@ var import_yaml21 = __toESM(require_dist(), 1);
|
|
|
89918
90093
|
init_paths();
|
|
89919
90094
|
import {
|
|
89920
90095
|
closeSync as closeSync14,
|
|
89921
|
-
existsSync as
|
|
90096
|
+
existsSync as existsSync87,
|
|
89922
90097
|
fsyncSync as fsyncSync8,
|
|
89923
90098
|
mkdirSync as mkdirSync48,
|
|
89924
90099
|
openSync as openSync14,
|
|
89925
90100
|
readdirSync as readdirSync31,
|
|
89926
90101
|
readFileSync as readFileSync74,
|
|
89927
90102
|
renameSync as renameSync19,
|
|
89928
|
-
statSync as
|
|
90103
|
+
statSync as statSync48,
|
|
89929
90104
|
unlinkSync as unlinkSync18,
|
|
89930
90105
|
writeSync as writeSync10
|
|
89931
90106
|
} from "node:fs";
|
|
89932
|
-
import { join as
|
|
90107
|
+
import { join as join86, resolve as resolve52 } from "node:path";
|
|
89933
90108
|
var STAGING_SUBDIR = ".staging";
|
|
89934
90109
|
function overlayPathsFor(agent, opts = {}) {
|
|
89935
90110
|
const base = opts.root ? resolve52(opts.root, agent) : resolve52(resolveDualPath(`~/.switchroom/agents/${agent}`));
|
|
89936
|
-
const scheduleDir =
|
|
89937
|
-
const scheduleStagingDir =
|
|
89938
|
-
const skillsDir =
|
|
89939
|
-
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);
|
|
89940
90115
|
return {
|
|
89941
90116
|
agentRoot: base,
|
|
89942
90117
|
scheduleDir,
|
|
89943
90118
|
scheduleStagingDir,
|
|
89944
90119
|
skillsDir,
|
|
89945
90120
|
skillsStagingDir,
|
|
89946
|
-
lockPath:
|
|
90121
|
+
lockPath: join86(base, ".lock"),
|
|
89947
90122
|
stagingDir: scheduleStagingDir
|
|
89948
90123
|
};
|
|
89949
90124
|
}
|
|
@@ -89969,7 +90144,7 @@ function withAgentLock(paths, fn) {
|
|
|
89969
90144
|
if (e.code !== "EEXIST")
|
|
89970
90145
|
throw err;
|
|
89971
90146
|
try {
|
|
89972
|
-
const age = Date.now() -
|
|
90147
|
+
const age = Date.now() - statSync48(paths.lockPath).mtimeMs;
|
|
89973
90148
|
if (age > 30000) {
|
|
89974
90149
|
unlinkSync18(paths.lockPath);
|
|
89975
90150
|
continue;
|
|
@@ -89997,8 +90172,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
89997
90172
|
const paths = overlayPathsFor(agent, opts);
|
|
89998
90173
|
return withAgentLock(paths, () => {
|
|
89999
90174
|
ensureDirs(paths);
|
|
90000
|
-
const stagingPath =
|
|
90001
|
-
const finalPath =
|
|
90175
|
+
const stagingPath = join86(paths.scheduleStagingDir, `${slug}.yaml`);
|
|
90176
|
+
const finalPath = join86(paths.scheduleDir, `${slug}.yaml`);
|
|
90002
90177
|
const fd = openSync14(stagingPath, "w", 384);
|
|
90003
90178
|
try {
|
|
90004
90179
|
writeSync10(fd, yamlText);
|
|
@@ -90014,8 +90189,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
90014
90189
|
const paths = overlayPathsFor(agent, opts);
|
|
90015
90190
|
return withAgentLock(paths, () => {
|
|
90016
90191
|
ensureSkillsDirs(paths);
|
|
90017
|
-
const stagingPath =
|
|
90018
|
-
const finalPath =
|
|
90192
|
+
const stagingPath = join86(paths.skillsStagingDir, `${slug}.yaml`);
|
|
90193
|
+
const finalPath = join86(paths.skillsDir, `${slug}.yaml`);
|
|
90019
90194
|
const fd = openSync14(stagingPath, "w", 384);
|
|
90020
90195
|
try {
|
|
90021
90196
|
writeSync10(fd, yamlText);
|
|
@@ -90030,8 +90205,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
90030
90205
|
function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
90031
90206
|
const paths = overlayPathsFor(agent, opts);
|
|
90032
90207
|
return withAgentLock(paths, () => {
|
|
90033
|
-
const finalPath =
|
|
90034
|
-
if (!
|
|
90208
|
+
const finalPath = join86(paths.skillsDir, `${slug}.yaml`);
|
|
90209
|
+
if (!existsSync87(finalPath))
|
|
90035
90210
|
return false;
|
|
90036
90211
|
unlinkSync18(finalPath);
|
|
90037
90212
|
return true;
|
|
@@ -90039,13 +90214,13 @@ function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
|
90039
90214
|
}
|
|
90040
90215
|
function listSkillsOverlayEntries(agent, opts = {}) {
|
|
90041
90216
|
const paths = overlayPathsFor(agent, opts);
|
|
90042
|
-
if (!
|
|
90217
|
+
if (!existsSync87(paths.skillsDir))
|
|
90043
90218
|
return [];
|
|
90044
90219
|
const out = [];
|
|
90045
90220
|
for (const name of readdirSync31(paths.skillsDir)) {
|
|
90046
90221
|
if (!/\.ya?ml$/i.test(name))
|
|
90047
90222
|
continue;
|
|
90048
|
-
const full =
|
|
90223
|
+
const full = join86(paths.skillsDir, name);
|
|
90049
90224
|
try {
|
|
90050
90225
|
const raw = readFileSync74(full, "utf-8");
|
|
90051
90226
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
@@ -90057,8 +90232,8 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
90057
90232
|
function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
90058
90233
|
const paths = overlayPathsFor(agent, opts);
|
|
90059
90234
|
return withAgentLock(paths, () => {
|
|
90060
|
-
const finalPath =
|
|
90061
|
-
if (!
|
|
90235
|
+
const finalPath = join86(paths.scheduleDir, `${slug}.yaml`);
|
|
90236
|
+
if (!existsSync87(finalPath))
|
|
90062
90237
|
return false;
|
|
90063
90238
|
unlinkSync18(finalPath);
|
|
90064
90239
|
return true;
|
|
@@ -90066,13 +90241,13 @@ function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
|
90066
90241
|
}
|
|
90067
90242
|
function listOverlayEntries(agent, opts = {}) {
|
|
90068
90243
|
const paths = overlayPathsFor(agent, opts);
|
|
90069
|
-
if (!
|
|
90244
|
+
if (!existsSync87(paths.scheduleDir))
|
|
90070
90245
|
return [];
|
|
90071
90246
|
const out = [];
|
|
90072
90247
|
for (const name of readdirSync31(paths.scheduleDir)) {
|
|
90073
90248
|
if (!/\.ya?ml$/i.test(name))
|
|
90074
90249
|
continue;
|
|
90075
|
-
const full =
|
|
90250
|
+
const full = join86(paths.scheduleDir, name);
|
|
90076
90251
|
try {
|
|
90077
90252
|
const raw = readFileSync74(full, "utf-8");
|
|
90078
90253
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
@@ -90294,7 +90469,7 @@ function reconcileAgentCronOnly(agent) {
|
|
|
90294
90469
|
// src/cli/agent-config-pending.ts
|
|
90295
90470
|
import {
|
|
90296
90471
|
closeSync as closeSync15,
|
|
90297
|
-
existsSync as
|
|
90472
|
+
existsSync as existsSync88,
|
|
90298
90473
|
fsyncSync as fsyncSync9,
|
|
90299
90474
|
mkdirSync as mkdirSync49,
|
|
90300
90475
|
openSync as openSync15,
|
|
@@ -90305,12 +90480,12 @@ import {
|
|
|
90305
90480
|
writeFileSync as writeFileSync31,
|
|
90306
90481
|
writeSync as writeSync11
|
|
90307
90482
|
} from "node:fs";
|
|
90308
|
-
import { join as
|
|
90483
|
+
import { join as join87 } from "node:path";
|
|
90309
90484
|
import { randomBytes as randomBytes15 } from "node:crypto";
|
|
90310
90485
|
var STAGE_ID_PREFIX = "cap_";
|
|
90311
90486
|
function pendingDir(agent, opts = {}) {
|
|
90312
90487
|
const paths = overlayPathsFor(agent, opts);
|
|
90313
|
-
return
|
|
90488
|
+
return join87(paths.scheduleDir, ".pending");
|
|
90314
90489
|
}
|
|
90315
90490
|
function ensurePendingDir(agent, opts = {}) {
|
|
90316
90491
|
const dir = pendingDir(agent, opts);
|
|
@@ -90323,8 +90498,8 @@ function newStageId() {
|
|
|
90323
90498
|
function stagePendingScheduleEntry(opts) {
|
|
90324
90499
|
const dir = ensurePendingDir(opts.agent, { root: opts.root });
|
|
90325
90500
|
const stageId = opts.stageId ?? newStageId();
|
|
90326
|
-
const yamlPath =
|
|
90327
|
-
const metaPath =
|
|
90501
|
+
const yamlPath = join87(dir, `${stageId}.yaml`);
|
|
90502
|
+
const metaPath = join87(dir, `${stageId}.meta.json`);
|
|
90328
90503
|
const meta = {
|
|
90329
90504
|
v: 1,
|
|
90330
90505
|
stage_id: stageId,
|
|
@@ -90351,16 +90526,16 @@ function stagePendingScheduleEntry(opts) {
|
|
|
90351
90526
|
}
|
|
90352
90527
|
function listPendingScheduleEntries(agent, opts = {}) {
|
|
90353
90528
|
const dir = pendingDir(agent, opts);
|
|
90354
|
-
if (!
|
|
90529
|
+
if (!existsSync88(dir))
|
|
90355
90530
|
return [];
|
|
90356
90531
|
const out = [];
|
|
90357
90532
|
for (const name of readdirSync32(dir).sort()) {
|
|
90358
90533
|
if (!name.endsWith(".meta.json"))
|
|
90359
90534
|
continue;
|
|
90360
90535
|
const stageId = name.slice(0, -".meta.json".length);
|
|
90361
|
-
const metaPath =
|
|
90362
|
-
const yamlPath =
|
|
90363
|
-
if (!
|
|
90536
|
+
const metaPath = join87(dir, name);
|
|
90537
|
+
const yamlPath = join87(dir, `${stageId}.yaml`);
|
|
90538
|
+
if (!existsSync88(yamlPath))
|
|
90364
90539
|
continue;
|
|
90365
90540
|
try {
|
|
90366
90541
|
const meta = JSON.parse(readFileSync75(metaPath, "utf-8"));
|
|
@@ -90378,8 +90553,8 @@ function commitPendingScheduleEntry(opts) {
|
|
|
90378
90553
|
return { committed: false, reason: "not_found" };
|
|
90379
90554
|
const slug = match.meta.entry.name ?? match.stageId;
|
|
90380
90555
|
const paths = overlayPathsFor(opts.agent, { root: opts.root });
|
|
90381
|
-
const finalPath =
|
|
90382
|
-
if (
|
|
90556
|
+
const finalPath = join87(paths.scheduleDir, `${slug}.yaml`);
|
|
90557
|
+
if (existsSync88(finalPath)) {
|
|
90383
90558
|
return { committed: false, reason: "slug_collision" };
|
|
90384
90559
|
}
|
|
90385
90560
|
renameSync20(match.yamlPath, finalPath);
|
|
@@ -90401,7 +90576,7 @@ function denyPendingScheduleEntry(opts) {
|
|
|
90401
90576
|
}
|
|
90402
90577
|
|
|
90403
90578
|
// src/cli/agent-config-write.ts
|
|
90404
|
-
import { existsSync as
|
|
90579
|
+
import { existsSync as existsSync89, readFileSync as readFileSync76 } from "node:fs";
|
|
90405
90580
|
import { execFileSync as execFileSync28 } from "node:child_process";
|
|
90406
90581
|
|
|
90407
90582
|
// src/scheduler/schedule-report.ts
|
|
@@ -90791,7 +90966,7 @@ function scheduleRemove(opts) {
|
|
|
90791
90966
|
}
|
|
90792
90967
|
let priorContent = null;
|
|
90793
90968
|
try {
|
|
90794
|
-
if (
|
|
90969
|
+
if (existsSync89(match.path))
|
|
90795
90970
|
priorContent = readFileSync76(match.path, "utf-8");
|
|
90796
90971
|
} catch {}
|
|
90797
90972
|
deleteOverlayEntry(agent, match.slug, { root: opts.root });
|
|
@@ -90995,7 +91170,7 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
90995
91170
|
}
|
|
90996
91171
|
let blob;
|
|
90997
91172
|
if (opts.jsonl) {
|
|
90998
|
-
blob =
|
|
91173
|
+
blob = existsSync89(opts.jsonl) ? readFileSync76(opts.jsonl, "utf-8") : "";
|
|
90999
91174
|
} else {
|
|
91000
91175
|
try {
|
|
91001
91176
|
blob = execFileSync28("docker", ["exec", `switchroom-${agent}`, "cat", "/state/agent/scheduler.jsonl"], {
|
|
@@ -91027,11 +91202,11 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
91027
91202
|
|
|
91028
91203
|
// src/cli/agent-config-skill-write.ts
|
|
91029
91204
|
var import_yaml22 = __toESM(require_dist(), 1);
|
|
91030
|
-
import { existsSync as
|
|
91205
|
+
import { existsSync as existsSync90 } from "node:fs";
|
|
91031
91206
|
init_reconcile_default_skills();
|
|
91032
91207
|
init_agent_config();
|
|
91033
91208
|
var import_yaml23 = __toESM(require_dist(), 1);
|
|
91034
|
-
import { join as
|
|
91209
|
+
import { join as join88 } from "node:path";
|
|
91035
91210
|
var MAX_SKILLS_PER_AGENT = 20;
|
|
91036
91211
|
var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
|
|
91037
91212
|
function exitCodeFor2(code) {
|
|
@@ -91106,8 +91281,8 @@ function skillInstall(opts) {
|
|
|
91106
91281
|
return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
|
|
91107
91282
|
}
|
|
91108
91283
|
const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
|
|
91109
|
-
const skillPath =
|
|
91110
|
-
if (!
|
|
91284
|
+
const skillPath = join88(poolDir, skillName);
|
|
91285
|
+
if (!existsSync90(skillPath)) {
|
|
91111
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.`);
|
|
91112
91287
|
}
|
|
91113
91288
|
const yamlText = import_yaml22.stringify({ skills: [skillName] });
|
|
@@ -91271,8 +91446,8 @@ function registerAgentConfigSkillWriteCommands(program3) {
|
|
|
91271
91446
|
// src/cli/skill.ts
|
|
91272
91447
|
import {
|
|
91273
91448
|
closeSync as closeSync16,
|
|
91274
|
-
existsSync as
|
|
91275
|
-
lstatSync as
|
|
91449
|
+
existsSync as existsSync91,
|
|
91450
|
+
lstatSync as lstatSync11,
|
|
91276
91451
|
mkdirSync as mkdirSync50,
|
|
91277
91452
|
mkdtempSync as mkdtempSync5,
|
|
91278
91453
|
openSync as openSync16,
|
|
@@ -91281,11 +91456,11 @@ import {
|
|
|
91281
91456
|
realpathSync as realpathSync7,
|
|
91282
91457
|
renameSync as renameSync21,
|
|
91283
91458
|
rmSync as rmSync18,
|
|
91284
|
-
statSync as
|
|
91459
|
+
statSync as statSync49,
|
|
91285
91460
|
writeFileSync as writeFileSync32
|
|
91286
91461
|
} from "node:fs";
|
|
91287
91462
|
import { tmpdir as tmpdir5, homedir as homedir51 } from "node:os";
|
|
91288
|
-
import { dirname as dirname30, join as
|
|
91463
|
+
import { dirname as dirname30, join as join89, relative as relative2, resolve as resolve53 } from "node:path";
|
|
91289
91464
|
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
91290
91465
|
|
|
91291
91466
|
// src/cli/skill-common.ts
|
|
@@ -91519,7 +91694,7 @@ function scanForClaudeP2(content) {
|
|
|
91519
91694
|
function resolveSkillsPoolDir2(override) {
|
|
91520
91695
|
const raw = override ?? "~/.switchroom/skills";
|
|
91521
91696
|
if (raw.startsWith("~/")) {
|
|
91522
|
-
return
|
|
91697
|
+
return join89(homedir51(), raw.slice(2));
|
|
91523
91698
|
}
|
|
91524
91699
|
if (raw === "~")
|
|
91525
91700
|
return homedir51();
|
|
@@ -91551,14 +91726,14 @@ function isTarballPath(p) {
|
|
|
91551
91726
|
}
|
|
91552
91727
|
function loadFromDir(dir) {
|
|
91553
91728
|
const abs = realpathSync7(dir);
|
|
91554
|
-
if (!
|
|
91729
|
+
if (!statSync49(abs).isDirectory()) {
|
|
91555
91730
|
fail3(`--from path is not a directory: ${dir}`);
|
|
91556
91731
|
}
|
|
91557
91732
|
const files = {};
|
|
91558
91733
|
const walk2 = (sub) => {
|
|
91559
91734
|
const entries = readdirSync33(sub, { withFileTypes: true });
|
|
91560
91735
|
for (const ent of entries) {
|
|
91561
|
-
const full =
|
|
91736
|
+
const full = join89(sub, ent.name);
|
|
91562
91737
|
const rel = relative2(abs, full);
|
|
91563
91738
|
if (ent.isSymbolicLink()) {
|
|
91564
91739
|
fail3(`refusing to read symlink inside --from dir: ${rel}`);
|
|
@@ -91593,7 +91768,7 @@ function loadFromTarball(tarPath) {
|
|
|
91593
91768
|
fail3(`tarball contains disallowed path: ${JSON.stringify(entry)} \u2014 ` + `refusing to extract before any file is written`);
|
|
91594
91769
|
}
|
|
91595
91770
|
}
|
|
91596
|
-
const staging = mkdtempSync5(
|
|
91771
|
+
const staging = mkdtempSync5(join89(tmpdir5(), "skill-apply-extract-"));
|
|
91597
91772
|
try {
|
|
91598
91773
|
const flags = isGz ? ["-xzf"] : ["-xf"];
|
|
91599
91774
|
const r = spawnSync16("tar", [
|
|
@@ -91679,8 +91854,8 @@ function validatePayload(name, files) {
|
|
|
91679
91854
|
errors2.push(`${path8} fails \`bash -n\` syntax check: ${(r.stderr ?? "").trim()}`);
|
|
91680
91855
|
}
|
|
91681
91856
|
} else if (PY_SCRIPT_RE2.test(path8)) {
|
|
91682
|
-
const tmp = mkdtempSync5(
|
|
91683
|
-
const tmpPy =
|
|
91857
|
+
const tmp = mkdtempSync5(join89(tmpdir5(), "skill-apply-py-"));
|
|
91858
|
+
const tmpPy = join89(tmp, "check.py");
|
|
91684
91859
|
try {
|
|
91685
91860
|
writeFileSync32(tmpPy, content);
|
|
91686
91861
|
const r = spawnSync16("python3", ["-m", "py_compile", tmpPy], {
|
|
@@ -91700,10 +91875,10 @@ function validatePayload(name, files) {
|
|
|
91700
91875
|
function diffSummary(currentDir, files) {
|
|
91701
91876
|
const lines = [];
|
|
91702
91877
|
const currentFiles = {};
|
|
91703
|
-
if (
|
|
91878
|
+
if (existsSync91(currentDir)) {
|
|
91704
91879
|
const walk2 = (sub) => {
|
|
91705
91880
|
for (const ent of readdirSync33(sub, { withFileTypes: true })) {
|
|
91706
|
-
const full =
|
|
91881
|
+
const full = join89(sub, ent.name);
|
|
91707
91882
|
const rel = relative2(currentDir, full);
|
|
91708
91883
|
if (ent.isDirectory()) {
|
|
91709
91884
|
walk2(full);
|
|
@@ -91736,13 +91911,13 @@ function diffSummary(currentDir, files) {
|
|
|
91736
91911
|
`);
|
|
91737
91912
|
}
|
|
91738
91913
|
function writePayload(poolDir, name, files) {
|
|
91739
|
-
if (!
|
|
91914
|
+
if (!existsSync91(poolDir)) {
|
|
91740
91915
|
mkdirSync50(poolDir, { recursive: true, mode: 493 });
|
|
91741
91916
|
}
|
|
91742
|
-
const target =
|
|
91917
|
+
const target = join89(poolDir, name);
|
|
91743
91918
|
let targetIsSymlink = false;
|
|
91744
91919
|
try {
|
|
91745
|
-
const st =
|
|
91920
|
+
const st = lstatSync11(target);
|
|
91746
91921
|
if (st.isSymbolicLink()) {
|
|
91747
91922
|
targetIsSymlink = true;
|
|
91748
91923
|
}
|
|
@@ -91750,11 +91925,11 @@ function writePayload(poolDir, name, files) {
|
|
|
91750
91925
|
if (targetIsSymlink) {
|
|
91751
91926
|
fail3(`refusing to overwrite symlink at ${target}; investigate manually`);
|
|
91752
91927
|
}
|
|
91753
|
-
const staging = mkdtempSync5(
|
|
91928
|
+
const staging = mkdtempSync5(join89(poolDir, `.skill-apply-stage-${name}-`));
|
|
91754
91929
|
let oldRename = null;
|
|
91755
91930
|
try {
|
|
91756
91931
|
for (const [path8, content] of Object.entries(files)) {
|
|
91757
|
-
const full =
|
|
91932
|
+
const full = join89(staging, path8);
|
|
91758
91933
|
mkdirSync50(dirname30(full), { recursive: true, mode: 493 });
|
|
91759
91934
|
const fd = openSync16(full, "wx");
|
|
91760
91935
|
try {
|
|
@@ -91769,7 +91944,7 @@ function writePayload(poolDir, name, files) {
|
|
|
91769
91944
|
}
|
|
91770
91945
|
let targetExists = false;
|
|
91771
91946
|
try {
|
|
91772
|
-
|
|
91947
|
+
lstatSync11(target);
|
|
91773
91948
|
targetExists = true;
|
|
91774
91949
|
} catch {}
|
|
91775
91950
|
if (targetExists) {
|
|
@@ -91785,9 +91960,9 @@ function writePayload(poolDir, name, files) {
|
|
|
91785
91960
|
try {
|
|
91786
91961
|
rmSync18(staging, { recursive: true, force: true });
|
|
91787
91962
|
} catch {}
|
|
91788
|
-
if (oldRename &&
|
|
91963
|
+
if (oldRename && existsSync91(oldRename)) {
|
|
91789
91964
|
try {
|
|
91790
|
-
if (
|
|
91965
|
+
if (existsSync91(target)) {
|
|
91791
91966
|
rmSync18(target, { recursive: true, force: true });
|
|
91792
91967
|
}
|
|
91793
91968
|
renameSync21(oldRename, target);
|
|
@@ -91808,10 +91983,10 @@ function registerSkillCommand(program3) {
|
|
|
91808
91983
|
files = loadFromStdin();
|
|
91809
91984
|
} else {
|
|
91810
91985
|
const fromPath = resolve53(opts.from);
|
|
91811
|
-
if (!
|
|
91986
|
+
if (!existsSync91(fromPath)) {
|
|
91812
91987
|
fail3(`--from path does not exist: ${opts.from}`);
|
|
91813
91988
|
}
|
|
91814
|
-
const st =
|
|
91989
|
+
const st = statSync49(fromPath);
|
|
91815
91990
|
if (st.isDirectory()) {
|
|
91816
91991
|
files = loadFromDir(fromPath);
|
|
91817
91992
|
} else if (isTarballPath(fromPath)) {
|
|
@@ -91832,7 +92007,7 @@ function registerSkillCommand(program3) {
|
|
|
91832
92007
|
}
|
|
91833
92008
|
const config = loadConfig();
|
|
91834
92009
|
const poolDir = resolveSkillsPoolDir2(config.switchroom?.skills_dir);
|
|
91835
|
-
const currentDir =
|
|
92010
|
+
const currentDir = join89(poolDir, name);
|
|
91836
92011
|
console.log(source_default.bold(`Skill: ${name}`) + source_default.gray(` (${Object.keys(files).length} files, ${sumBytes(files)} bytes)`));
|
|
91837
92012
|
console.log(source_default.bold("Diff vs current pool content:"));
|
|
91838
92013
|
console.log(diffSummary(currentDir, files));
|
|
@@ -91864,8 +92039,8 @@ function sumBytes(files) {
|
|
|
91864
92039
|
init_esm();
|
|
91865
92040
|
import {
|
|
91866
92041
|
closeSync as closeSync17,
|
|
91867
|
-
existsSync as
|
|
91868
|
-
lstatSync as
|
|
92042
|
+
existsSync as existsSync92,
|
|
92043
|
+
lstatSync as lstatSync12,
|
|
91869
92044
|
mkdirSync as mkdirSync51,
|
|
91870
92045
|
mkdtempSync as mkdtempSync6,
|
|
91871
92046
|
openSync as openSync17,
|
|
@@ -91873,11 +92048,11 @@ import {
|
|
|
91873
92048
|
readdirSync as readdirSync34,
|
|
91874
92049
|
renameSync as renameSync22,
|
|
91875
92050
|
rmSync as rmSync19,
|
|
91876
|
-
statSync as
|
|
92051
|
+
statSync as statSync50,
|
|
91877
92052
|
utimesSync,
|
|
91878
92053
|
writeFileSync as writeFileSync33
|
|
91879
92054
|
} from "node:fs";
|
|
91880
|
-
import { dirname as dirname31, join as
|
|
92055
|
+
import { dirname as dirname31, join as join90, relative as relative3, resolve as resolve54 } from "node:path";
|
|
91881
92056
|
import { homedir as homedir52, tmpdir as tmpdir6 } from "node:os";
|
|
91882
92057
|
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
91883
92058
|
init_helpers();
|
|
@@ -91889,15 +92064,15 @@ var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
|
|
|
91889
92064
|
var PERSONAL_SKILLS_SUBPATH = "personal-skills";
|
|
91890
92065
|
function resolveConfigSkillsDir(agent) {
|
|
91891
92066
|
const override = process.env.SWITCHROOM_CONFIG_DIR;
|
|
91892
|
-
const candidate = override ? resolve54(override) :
|
|
91893
|
-
if (!
|
|
92067
|
+
const candidate = override ? resolve54(override) : join90(homedir52(), ".switchroom-config");
|
|
92068
|
+
if (!existsSync92(candidate))
|
|
91894
92069
|
return null;
|
|
91895
|
-
return
|
|
92070
|
+
return join90(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
|
|
91896
92071
|
}
|
|
91897
92072
|
var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
|
|
91898
92073
|
function sweepMirrorPriors(configSkillsRoot) {
|
|
91899
92074
|
try {
|
|
91900
|
-
if (!
|
|
92075
|
+
if (!existsSync92(configSkillsRoot))
|
|
91901
92076
|
return;
|
|
91902
92077
|
const now = Date.now();
|
|
91903
92078
|
for (const ent of readdirSync34(configSkillsRoot)) {
|
|
@@ -91910,7 +92085,7 @@ function sweepMirrorPriors(configSkillsRoot) {
|
|
|
91910
92085
|
if (now - ts < MIRROR_PRIOR_TTL_MS)
|
|
91911
92086
|
continue;
|
|
91912
92087
|
try {
|
|
91913
|
-
rmSync19(
|
|
92088
|
+
rmSync19(join90(configSkillsRoot, ent), { recursive: true, force: true });
|
|
91914
92089
|
} catch {}
|
|
91915
92090
|
}
|
|
91916
92091
|
} catch {}
|
|
@@ -91919,11 +92094,11 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
91919
92094
|
const configSkillsRoot = resolveConfigSkillsDir(agent);
|
|
91920
92095
|
if (!configSkillsRoot)
|
|
91921
92096
|
return;
|
|
91922
|
-
const dest =
|
|
92097
|
+
const dest = join90(configSkillsRoot, name);
|
|
91923
92098
|
try {
|
|
91924
92099
|
if (liveSkillDir !== null) {
|
|
91925
92100
|
try {
|
|
91926
|
-
const st =
|
|
92101
|
+
const st = lstatSync12(liveSkillDir);
|
|
91927
92102
|
if (st.isSymbolicLink()) {
|
|
91928
92103
|
process.stderr.write(source_default.yellow(`warning: refusing to mirror ${liveSkillDir} \u2014 source is a symlink
|
|
91929
92104
|
`));
|
|
@@ -91933,20 +92108,20 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
91933
92108
|
}
|
|
91934
92109
|
if (liveSkillDir === null) {
|
|
91935
92110
|
sweepMirrorPriors(configSkillsRoot);
|
|
91936
|
-
if (
|
|
91937
|
-
const trash =
|
|
92111
|
+
if (existsSync92(dest)) {
|
|
92112
|
+
const trash = join90(configSkillsRoot, `.${name}-trash-${Date.now()}`);
|
|
91938
92113
|
renameSync22(dest, trash);
|
|
91939
92114
|
}
|
|
91940
92115
|
return;
|
|
91941
92116
|
}
|
|
91942
92117
|
mkdirSync51(configSkillsRoot, { recursive: true, mode: 493 });
|
|
91943
92118
|
sweepMirrorPriors(configSkillsRoot);
|
|
91944
|
-
const staging = mkdtempSync6(
|
|
92119
|
+
const staging = mkdtempSync6(join90(configSkillsRoot, `.${name}-staging-`));
|
|
91945
92120
|
const walk2 = (src, dst) => {
|
|
91946
92121
|
mkdirSync51(dst, { recursive: true, mode: 493 });
|
|
91947
92122
|
for (const ent of readdirSync34(src, { withFileTypes: true })) {
|
|
91948
|
-
const s =
|
|
91949
|
-
const d =
|
|
92123
|
+
const s = join90(src, ent.name);
|
|
92124
|
+
const d = join90(dst, ent.name);
|
|
91950
92125
|
if (ent.isSymbolicLink())
|
|
91951
92126
|
continue;
|
|
91952
92127
|
if (ent.isDirectory())
|
|
@@ -91957,8 +92132,8 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
91957
92132
|
}
|
|
91958
92133
|
};
|
|
91959
92134
|
walk2(liveSkillDir, staging);
|
|
91960
|
-
if (
|
|
91961
|
-
const prior =
|
|
92135
|
+
if (existsSync92(dest)) {
|
|
92136
|
+
const prior = join90(configSkillsRoot, `.${name}-prior-${Date.now()}`);
|
|
91962
92137
|
renameSync22(dest, prior);
|
|
91963
92138
|
}
|
|
91964
92139
|
renameSync22(staging, dest);
|
|
@@ -91988,17 +92163,17 @@ function resolveAgent(opts) {
|
|
|
91988
92163
|
function resolveAgentsRoot(opts) {
|
|
91989
92164
|
if (opts.root)
|
|
91990
92165
|
return resolve54(opts.root);
|
|
91991
|
-
return
|
|
92166
|
+
return join90(homedir52(), ".switchroom", "agents");
|
|
91992
92167
|
}
|
|
91993
92168
|
function personalSkillDir(agentsRoot, agent, name) {
|
|
91994
|
-
return
|
|
92169
|
+
return join90(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
|
|
91995
92170
|
}
|
|
91996
92171
|
function trashDir(agentsRoot, agent) {
|
|
91997
|
-
return
|
|
92172
|
+
return join90(agentsRoot, agent, ".claude", TRASH_DIRNAME);
|
|
91998
92173
|
}
|
|
91999
92174
|
function countPersonalSkills(agentsRoot, agent) {
|
|
92000
|
-
const skillsDir =
|
|
92001
|
-
if (!
|
|
92175
|
+
const skillsDir = join90(agentsRoot, agent, ".claude", "skills");
|
|
92176
|
+
if (!existsSync92(skillsDir))
|
|
92002
92177
|
return 0;
|
|
92003
92178
|
let n = 0;
|
|
92004
92179
|
for (const ent of readdirSync34(skillsDir, { withFileTypes: true })) {
|
|
@@ -92029,13 +92204,13 @@ function readStdinSync2() {
|
|
|
92029
92204
|
}
|
|
92030
92205
|
function loadFromDir2(dir) {
|
|
92031
92206
|
const abs = resolve54(dir);
|
|
92032
|
-
if (!
|
|
92207
|
+
if (!statSync50(abs).isDirectory()) {
|
|
92033
92208
|
fail4(`--from path is not a directory: ${dir}`);
|
|
92034
92209
|
}
|
|
92035
92210
|
const files = {};
|
|
92036
92211
|
const walk2 = (sub) => {
|
|
92037
92212
|
for (const ent of readdirSync34(sub, { withFileTypes: true })) {
|
|
92038
|
-
const full =
|
|
92213
|
+
const full = join90(sub, ent.name);
|
|
92039
92214
|
if (ent.isSymbolicLink()) {
|
|
92040
92215
|
fail4(`refusing to read symlink in --from dir: ${relative3(abs, full)}`);
|
|
92041
92216
|
}
|
|
@@ -92088,8 +92263,8 @@ function behavioralValidate(files) {
|
|
|
92088
92263
|
errors2.push(`${path8} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
|
|
92089
92264
|
}
|
|
92090
92265
|
} else if (PY_SCRIPT_RE.test(path8)) {
|
|
92091
|
-
const tmp = mkdtempSync6(
|
|
92092
|
-
const tmpPy =
|
|
92266
|
+
const tmp = mkdtempSync6(join90(tmpdir6(), "skill-personal-py-"));
|
|
92267
|
+
const tmpPy = join90(tmp, "check.py");
|
|
92093
92268
|
try {
|
|
92094
92269
|
writeFileSync33(tmpPy, content);
|
|
92095
92270
|
const r = spawnSync17("python3", ["-m", "py_compile", tmpPy], {
|
|
@@ -92107,15 +92282,15 @@ function behavioralValidate(files) {
|
|
|
92107
92282
|
}
|
|
92108
92283
|
function sweepTrash(agentsRoot, agent) {
|
|
92109
92284
|
const trash = trashDir(agentsRoot, agent);
|
|
92110
|
-
if (!
|
|
92285
|
+
if (!existsSync92(trash))
|
|
92111
92286
|
return;
|
|
92112
92287
|
const now = Date.now();
|
|
92113
92288
|
for (const ent of readdirSync34(trash, { withFileTypes: true })) {
|
|
92114
92289
|
if (!ent.isDirectory())
|
|
92115
92290
|
continue;
|
|
92116
|
-
const entPath =
|
|
92291
|
+
const entPath = join90(trash, ent.name);
|
|
92117
92292
|
try {
|
|
92118
|
-
const st =
|
|
92293
|
+
const st = statSync50(entPath);
|
|
92119
92294
|
if (now - st.mtimeMs > TRASH_TTL_MS) {
|
|
92120
92295
|
rmSync19(entPath, { recursive: true, force: true });
|
|
92121
92296
|
}
|
|
@@ -92125,7 +92300,7 @@ function sweepTrash(agentsRoot, agent) {
|
|
|
92125
92300
|
function writePersonalSkill(targetDir, files) {
|
|
92126
92301
|
let targetIsSymlink = false;
|
|
92127
92302
|
try {
|
|
92128
|
-
const st =
|
|
92303
|
+
const st = lstatSync12(targetDir);
|
|
92129
92304
|
if (st.isSymbolicLink()) {
|
|
92130
92305
|
targetIsSymlink = true;
|
|
92131
92306
|
}
|
|
@@ -92134,11 +92309,11 @@ function writePersonalSkill(targetDir, files) {
|
|
|
92134
92309
|
fail4(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
|
|
92135
92310
|
}
|
|
92136
92311
|
mkdirSync51(dirname31(targetDir), { recursive: true, mode: 493 });
|
|
92137
|
-
const staging = mkdtempSync6(
|
|
92312
|
+
const staging = mkdtempSync6(join90(dirname31(targetDir), `.skill-personal-stage-`));
|
|
92138
92313
|
let oldRename = null;
|
|
92139
92314
|
try {
|
|
92140
92315
|
for (const [path8, content] of Object.entries(files)) {
|
|
92141
|
-
const full =
|
|
92316
|
+
const full = join90(staging, path8);
|
|
92142
92317
|
mkdirSync51(dirname31(full), { recursive: true, mode: 493 });
|
|
92143
92318
|
const fd = openSync17(full, "wx");
|
|
92144
92319
|
try {
|
|
@@ -92153,7 +92328,7 @@ function writePersonalSkill(targetDir, files) {
|
|
|
92153
92328
|
}
|
|
92154
92329
|
let targetExists = false;
|
|
92155
92330
|
try {
|
|
92156
|
-
|
|
92331
|
+
lstatSync12(targetDir);
|
|
92157
92332
|
targetExists = true;
|
|
92158
92333
|
} catch {}
|
|
92159
92334
|
if (targetExists) {
|
|
@@ -92169,9 +92344,9 @@ function writePersonalSkill(targetDir, files) {
|
|
|
92169
92344
|
try {
|
|
92170
92345
|
rmSync19(staging, { recursive: true, force: true });
|
|
92171
92346
|
} catch {}
|
|
92172
|
-
if (oldRename &&
|
|
92347
|
+
if (oldRename && existsSync92(oldRename)) {
|
|
92173
92348
|
try {
|
|
92174
|
-
if (
|
|
92349
|
+
if (existsSync92(targetDir)) {
|
|
92175
92350
|
rmSync19(targetDir, { recursive: true, force: true });
|
|
92176
92351
|
}
|
|
92177
92352
|
renameSync22(oldRename, targetDir);
|
|
@@ -92188,7 +92363,7 @@ function loadValidateWrite(agentsRoot, agent, name, files, ensureNew) {
|
|
|
92188
92363
|
const target = personalSkillDir(agentsRoot, agent, name);
|
|
92189
92364
|
const exists = (() => {
|
|
92190
92365
|
try {
|
|
92191
|
-
|
|
92366
|
+
lstatSync12(target);
|
|
92192
92367
|
return true;
|
|
92193
92368
|
} catch {
|
|
92194
92369
|
return false;
|
|
@@ -92237,10 +92412,10 @@ function loadFiles(opts) {
|
|
|
92237
92412
|
return loadFromStdin2();
|
|
92238
92413
|
}
|
|
92239
92414
|
const p = resolve54(opts.from);
|
|
92240
|
-
if (!
|
|
92415
|
+
if (!existsSync92(p)) {
|
|
92241
92416
|
fail4(`--from path does not exist: ${opts.from}`);
|
|
92242
92417
|
}
|
|
92243
|
-
const st =
|
|
92418
|
+
const st = statSync50(p);
|
|
92244
92419
|
if (st.isDirectory()) {
|
|
92245
92420
|
return loadFromDir2(p);
|
|
92246
92421
|
}
|
|
@@ -92285,10 +92460,10 @@ function editPersonalAction(name, opts) {
|
|
|
92285
92460
|
}
|
|
92286
92461
|
var CLONE_SOURCE_RE = /^(shared|bundled):([a-z0-9][a-z0-9_-]{0,62})$/;
|
|
92287
92462
|
function defaultSharedRoot() {
|
|
92288
|
-
return
|
|
92463
|
+
return join90(homedir52(), ".switchroom", "skills");
|
|
92289
92464
|
}
|
|
92290
92465
|
function defaultBundledRoot() {
|
|
92291
|
-
return
|
|
92466
|
+
return join90(homedir52(), ".switchroom", "skills", "_bundled");
|
|
92292
92467
|
}
|
|
92293
92468
|
function resolveCloneSource(source, opts) {
|
|
92294
92469
|
const m = CLONE_SOURCE_RE.exec(source);
|
|
@@ -92298,11 +92473,11 @@ function resolveCloneSource(source, opts) {
|
|
|
92298
92473
|
const tier = m[1];
|
|
92299
92474
|
const slug = m[2];
|
|
92300
92475
|
const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
|
|
92301
|
-
const dir =
|
|
92302
|
-
if (!
|
|
92476
|
+
const dir = join90(root, slug);
|
|
92477
|
+
if (!existsSync92(dir)) {
|
|
92303
92478
|
fail4(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
|
|
92304
92479
|
}
|
|
92305
|
-
const st =
|
|
92480
|
+
const st = lstatSync12(dir);
|
|
92306
92481
|
if (st.isSymbolicLink()) {
|
|
92307
92482
|
fail4(`clone source ${JSON.stringify(source)} is a symlink at ${dir}; ` + `point clone at the canonical pool path instead`);
|
|
92308
92483
|
}
|
|
@@ -92314,7 +92489,7 @@ function readSourceFiles(dir) {
|
|
|
92314
92489
|
const skipped = [];
|
|
92315
92490
|
const walk2 = (sub) => {
|
|
92316
92491
|
for (const ent of readdirSync34(sub, { withFileTypes: true })) {
|
|
92317
|
-
const full =
|
|
92492
|
+
const full = join90(sub, ent.name);
|
|
92318
92493
|
if (ent.isSymbolicLink()) {
|
|
92319
92494
|
continue;
|
|
92320
92495
|
}
|
|
@@ -92329,7 +92504,7 @@ function readSourceFiles(dir) {
|
|
|
92329
92504
|
continue;
|
|
92330
92505
|
}
|
|
92331
92506
|
try {
|
|
92332
|
-
const st =
|
|
92507
|
+
const st = lstatSync12(full);
|
|
92333
92508
|
if (st.size > CLONE_MAX_FILE_BYTES) {
|
|
92334
92509
|
fail4(`clone source has oversized file ${rel} (${st.size} bytes > ${CLONE_MAX_FILE_BYTES}); ` + `refuse to read`, 3);
|
|
92335
92510
|
}
|
|
@@ -92411,7 +92586,7 @@ function removePersonalAction(name, opts) {
|
|
|
92411
92586
|
}
|
|
92412
92587
|
const target = personalSkillDir(agentsRoot, agent, name);
|
|
92413
92588
|
try {
|
|
92414
|
-
const st =
|
|
92589
|
+
const st = lstatSync12(target);
|
|
92415
92590
|
if (st.isSymbolicLink()) {
|
|
92416
92591
|
fail4(`refusing to remove symlink at ${target}; investigate manually`);
|
|
92417
92592
|
}
|
|
@@ -92425,7 +92600,7 @@ function removePersonalAction(name, opts) {
|
|
|
92425
92600
|
const trashRoot2 = trashDir(agentsRoot, agent);
|
|
92426
92601
|
mkdirSync51(trashRoot2, { recursive: true, mode: 493 });
|
|
92427
92602
|
const ts = Date.now();
|
|
92428
|
-
const trashTarget =
|
|
92603
|
+
const trashTarget = join90(trashRoot2, `${name}-${ts}`);
|
|
92429
92604
|
renameSync22(target, trashTarget);
|
|
92430
92605
|
const now = new Date(ts);
|
|
92431
92606
|
utimesSync(trashTarget, now, now);
|
|
@@ -92444,16 +92619,16 @@ function listPersonalAction(opts) {
|
|
|
92444
92619
|
const agent = resolveAgent(opts);
|
|
92445
92620
|
const agentsRoot = resolveAgentsRoot(opts);
|
|
92446
92621
|
sweepTrash(agentsRoot, agent);
|
|
92447
|
-
const skillsDir =
|
|
92622
|
+
const skillsDir = join90(agentsRoot, agent, ".claude", "skills");
|
|
92448
92623
|
const personal = [];
|
|
92449
|
-
if (
|
|
92624
|
+
if (existsSync92(skillsDir)) {
|
|
92450
92625
|
for (const ent of readdirSync34(skillsDir, { withFileTypes: true })) {
|
|
92451
92626
|
if (!ent.isDirectory())
|
|
92452
92627
|
continue;
|
|
92453
92628
|
if (!ent.name.startsWith(PERSONAL_PREFIX))
|
|
92454
92629
|
continue;
|
|
92455
92630
|
const skillName = ent.name.slice(PERSONAL_PREFIX.length);
|
|
92456
|
-
const skillPath =
|
|
92631
|
+
const skillPath = join90(skillsDir, ent.name);
|
|
92457
92632
|
let fileCount = 0;
|
|
92458
92633
|
let totalBytes = 0;
|
|
92459
92634
|
const walk2 = (sub) => {
|
|
@@ -92461,10 +92636,10 @@ function listPersonalAction(opts) {
|
|
|
92461
92636
|
if (e.isFile()) {
|
|
92462
92637
|
fileCount += 1;
|
|
92463
92638
|
try {
|
|
92464
|
-
totalBytes +=
|
|
92639
|
+
totalBytes += statSync50(join90(sub, e.name)).size;
|
|
92465
92640
|
} catch {}
|
|
92466
92641
|
} else if (e.isDirectory()) {
|
|
92467
|
-
walk2(
|
|
92642
|
+
walk2(join90(sub, e.name));
|
|
92468
92643
|
}
|
|
92469
92644
|
}
|
|
92470
92645
|
};
|
|
@@ -92503,11 +92678,11 @@ function registerSkillPersonalCommands(program3) {
|
|
|
92503
92678
|
// src/cli/self-improve-propose-skill.ts
|
|
92504
92679
|
import { createConnection as createConnection4 } from "node:net";
|
|
92505
92680
|
import { homedir as homedir53 } from "node:os";
|
|
92506
|
-
import { join as
|
|
92681
|
+
import { join as join91 } from "node:path";
|
|
92507
92682
|
import { readFileSync as readFileSync79 } from "node:fs";
|
|
92508
92683
|
var IPC_CONNECT_TIMEOUT_MS = 5000;
|
|
92509
92684
|
function gatewaySocketPath() {
|
|
92510
|
-
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"));
|
|
92511
92686
|
}
|
|
92512
92687
|
function fail5(msg, code = 1) {
|
|
92513
92688
|
console.error(msg);
|
|
@@ -92579,9 +92754,9 @@ function registerSelfImproveProposeSkillCommand(program3) {
|
|
|
92579
92754
|
init_esm();
|
|
92580
92755
|
init_helpers();
|
|
92581
92756
|
var import_yaml25 = __toESM(require_dist(), 1);
|
|
92582
|
-
import { existsSync as
|
|
92757
|
+
import { existsSync as existsSync93, readdirSync as readdirSync35, readFileSync as readFileSync80, statSync as statSync51 } from "node:fs";
|
|
92583
92758
|
import { homedir as homedir54 } from "node:os";
|
|
92584
|
-
import { join as
|
|
92759
|
+
import { join as join92, resolve as resolve55 } from "node:path";
|
|
92585
92760
|
var PERSONAL_PREFIX2 = "personal-";
|
|
92586
92761
|
var BUNDLED_SUBDIR = "_bundled";
|
|
92587
92762
|
var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
|
|
@@ -92595,8 +92770,8 @@ function defaultBundledRoot2() {
|
|
|
92595
92770
|
return resolve55(homedir54(), ".switchroom/skills/_bundled");
|
|
92596
92771
|
}
|
|
92597
92772
|
function readSkillFrontmatter(skillDir) {
|
|
92598
|
-
const mdPath =
|
|
92599
|
-
if (!
|
|
92773
|
+
const mdPath = join92(skillDir, "SKILL.md");
|
|
92774
|
+
if (!existsSync93(mdPath))
|
|
92600
92775
|
return null;
|
|
92601
92776
|
let content;
|
|
92602
92777
|
try {
|
|
@@ -92628,9 +92803,9 @@ function readSkillFrontmatter(skillDir) {
|
|
|
92628
92803
|
return { fm: parsed };
|
|
92629
92804
|
}
|
|
92630
92805
|
function statSkillMd(skillDir) {
|
|
92631
|
-
const mdPath =
|
|
92806
|
+
const mdPath = join92(skillDir, "SKILL.md");
|
|
92632
92807
|
try {
|
|
92633
|
-
const st =
|
|
92808
|
+
const st = statSync51(mdPath);
|
|
92634
92809
|
return { size: st.size, mtime: st.mtime.toISOString() };
|
|
92635
92810
|
} catch {
|
|
92636
92811
|
return null;
|
|
@@ -92639,8 +92814,8 @@ function statSkillMd(skillDir) {
|
|
|
92639
92814
|
function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
92640
92815
|
if (!AGENT_NAME_RE3.test(agent))
|
|
92641
92816
|
return [];
|
|
92642
|
-
const skillsDir =
|
|
92643
|
-
if (!
|
|
92817
|
+
const skillsDir = join92(agentsRoot, agent, ".claude/skills");
|
|
92818
|
+
if (!existsSync93(skillsDir))
|
|
92644
92819
|
return [];
|
|
92645
92820
|
const out = [];
|
|
92646
92821
|
let entries;
|
|
@@ -92652,9 +92827,9 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
92652
92827
|
for (const ent of entries) {
|
|
92653
92828
|
if (!ent.startsWith(PERSONAL_PREFIX2))
|
|
92654
92829
|
continue;
|
|
92655
|
-
const dirPath =
|
|
92830
|
+
const dirPath = join92(skillsDir, ent);
|
|
92656
92831
|
try {
|
|
92657
|
-
if (!
|
|
92832
|
+
if (!statSync51(dirPath).isDirectory())
|
|
92658
92833
|
continue;
|
|
92659
92834
|
} catch {
|
|
92660
92835
|
continue;
|
|
@@ -92678,7 +92853,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
92678
92853
|
return out;
|
|
92679
92854
|
}
|
|
92680
92855
|
function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
92681
|
-
if (!
|
|
92856
|
+
if (!existsSync93(sharedRoot))
|
|
92682
92857
|
return [];
|
|
92683
92858
|
const out = [];
|
|
92684
92859
|
let entries;
|
|
@@ -92692,9 +92867,9 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
|
92692
92867
|
continue;
|
|
92693
92868
|
if (ent.startsWith("."))
|
|
92694
92869
|
continue;
|
|
92695
|
-
const dirPath =
|
|
92870
|
+
const dirPath = join92(sharedRoot, ent);
|
|
92696
92871
|
try {
|
|
92697
|
-
if (!
|
|
92872
|
+
if (!statSync51(dirPath).isDirectory())
|
|
92698
92873
|
continue;
|
|
92699
92874
|
} catch {
|
|
92700
92875
|
continue;
|
|
@@ -92716,7 +92891,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
|
92716
92891
|
return out;
|
|
92717
92892
|
}
|
|
92718
92893
|
function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
92719
|
-
if (!
|
|
92894
|
+
if (!existsSync93(bundledRoot))
|
|
92720
92895
|
return [];
|
|
92721
92896
|
const out = [];
|
|
92722
92897
|
let entries;
|
|
@@ -92728,9 +92903,9 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
|
92728
92903
|
for (const ent of entries) {
|
|
92729
92904
|
if (ent.startsWith("."))
|
|
92730
92905
|
continue;
|
|
92731
|
-
const dirPath =
|
|
92906
|
+
const dirPath = join92(bundledRoot, ent);
|
|
92732
92907
|
try {
|
|
92733
|
-
if (!
|
|
92908
|
+
if (!statSync51(dirPath).isDirectory())
|
|
92734
92909
|
continue;
|
|
92735
92910
|
} catch {
|
|
92736
92911
|
continue;
|
|
@@ -92874,18 +93049,18 @@ init_source();
|
|
|
92874
93049
|
init_helpers();
|
|
92875
93050
|
init_operator_uid();
|
|
92876
93051
|
import {
|
|
92877
|
-
existsSync as
|
|
93052
|
+
existsSync as existsSync95,
|
|
92878
93053
|
mkdirSync as mkdirSync52,
|
|
92879
93054
|
readdirSync as readdirSync36,
|
|
92880
93055
|
readFileSync as readFileSync82,
|
|
92881
93056
|
writeFileSync as writeFileSync34,
|
|
92882
|
-
statSync as
|
|
92883
|
-
lstatSync as
|
|
93057
|
+
statSync as statSync52,
|
|
93058
|
+
lstatSync as lstatSync13,
|
|
92884
93059
|
realpathSync as realpathSync8,
|
|
92885
93060
|
copyFileSync as copyFileSync13
|
|
92886
93061
|
} from "node:fs";
|
|
92887
93062
|
import { homedir as homedir55 } from "node:os";
|
|
92888
|
-
import { join as
|
|
93063
|
+
import { join as join93 } from "node:path";
|
|
92889
93064
|
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
92890
93065
|
|
|
92891
93066
|
// src/cli/singleton-stale-cleanup.ts
|
|
@@ -93234,10 +93409,10 @@ function resolveHostdHostHome(env2 = process.env, home2 = homedir55()) {
|
|
|
93234
93409
|
return resolved;
|
|
93235
93410
|
}
|
|
93236
93411
|
function resolveHostdSkillsTarget(hostHome) {
|
|
93237
|
-
const skillsPath =
|
|
93412
|
+
const skillsPath = join93(hostHome, ".switchroom", "skills");
|
|
93238
93413
|
let st;
|
|
93239
93414
|
try {
|
|
93240
|
-
st =
|
|
93415
|
+
st = lstatSync13(skillsPath);
|
|
93241
93416
|
} catch {
|
|
93242
93417
|
return;
|
|
93243
93418
|
}
|
|
@@ -93251,21 +93426,21 @@ function resolveHostdSkillsTarget(hostHome) {
|
|
|
93251
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.`);
|
|
93252
93427
|
return;
|
|
93253
93428
|
}
|
|
93254
|
-
if (!
|
|
93429
|
+
if (!existsSync95(target)) {
|
|
93255
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.`);
|
|
93256
93431
|
return;
|
|
93257
93432
|
}
|
|
93258
93433
|
return target;
|
|
93259
93434
|
}
|
|
93260
93435
|
function hostdDir() {
|
|
93261
|
-
return
|
|
93436
|
+
return join93(homedir55(), ".switchroom", "hostd");
|
|
93262
93437
|
}
|
|
93263
93438
|
function hostdComposePath() {
|
|
93264
|
-
return
|
|
93439
|
+
return join93(hostdDir(), "docker-compose.yml");
|
|
93265
93440
|
}
|
|
93266
93441
|
function backupExistingCompose() {
|
|
93267
93442
|
const p = hostdComposePath();
|
|
93268
|
-
if (!
|
|
93443
|
+
if (!existsSync95(p))
|
|
93269
93444
|
return null;
|
|
93270
93445
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
93271
93446
|
const bak = `${p}.bak-${ts}`;
|
|
@@ -93363,7 +93538,7 @@ function doStatus() {
|
|
|
93363
93538
|
const composeYml = hostdComposePath();
|
|
93364
93539
|
console.log(source_default.bold("switchroom-hostd"));
|
|
93365
93540
|
console.log("");
|
|
93366
|
-
if (!
|
|
93541
|
+
if (!existsSync95(composeYml)) {
|
|
93367
93542
|
console.log(source_default.yellow(" compose: not installed"));
|
|
93368
93543
|
console.log(source_default.dim(" run `switchroom hostd install` to set up."));
|
|
93369
93544
|
return;
|
|
@@ -93384,15 +93559,15 @@ function doStatus() {
|
|
|
93384
93559
|
} else {
|
|
93385
93560
|
console.log(source_default.green(` container: ${ps.stdout.trim()}`));
|
|
93386
93561
|
}
|
|
93387
|
-
if (
|
|
93562
|
+
if (existsSync95(dir)) {
|
|
93388
93563
|
const entries = [];
|
|
93389
93564
|
try {
|
|
93390
93565
|
for (const name of readdirSync36(dir)) {
|
|
93391
93566
|
if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
|
|
93392
93567
|
continue;
|
|
93393
|
-
const sockPath =
|
|
93394
|
-
if (
|
|
93395
|
-
const st =
|
|
93568
|
+
const sockPath = join93(dir, name, "sock");
|
|
93569
|
+
if (existsSync95(sockPath)) {
|
|
93570
|
+
const st = statSync52(sockPath);
|
|
93396
93571
|
if ((st.mode & 61440) === 49152) {
|
|
93397
93572
|
entries.push(`${name} \u2192 ${sockPath}`);
|
|
93398
93573
|
}
|
|
@@ -93410,7 +93585,7 @@ function doStatus() {
|
|
|
93410
93585
|
}
|
|
93411
93586
|
function doUninstall() {
|
|
93412
93587
|
const composeYml = hostdComposePath();
|
|
93413
|
-
if (!
|
|
93588
|
+
if (!existsSync95(composeYml)) {
|
|
93414
93589
|
console.log(source_default.yellow(" No hostd install detected (no compose file at this path)."));
|
|
93415
93590
|
return;
|
|
93416
93591
|
}
|
|
@@ -93434,7 +93609,7 @@ function registerHostdCommand(program3) {
|
|
|
93434
93609
|
hostd.command("uninstall").description("Stop the hostd container. Leaves the compose file in place for re-install.").action(() => doUninstall());
|
|
93435
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) => {
|
|
93436
93611
|
const logPath = opts.path ?? defaultAuditLogPath2();
|
|
93437
|
-
if (!
|
|
93612
|
+
if (!existsSync95(logPath)) {
|
|
93438
93613
|
console.error(source_default.yellow(`Audit log not found at ${logPath}.`) + source_default.gray(`
|
|
93439
93614
|
The log is created when hostd handles its first privileged-verb request.`));
|
|
93440
93615
|
return;
|
|
@@ -93482,9 +93657,9 @@ The log is created when hostd handles its first privileged-verb request.`));
|
|
|
93482
93657
|
init_source();
|
|
93483
93658
|
init_helpers();
|
|
93484
93659
|
init_operator_uid();
|
|
93485
|
-
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";
|
|
93486
93661
|
import { homedir as homedir56 } from "node:os";
|
|
93487
|
-
import { join as
|
|
93662
|
+
import { join as join94 } from "node:path";
|
|
93488
93663
|
import { spawnSync as spawnSync21 } from "node:child_process";
|
|
93489
93664
|
function resolveWebImageTag(explicitTag, release) {
|
|
93490
93665
|
if (explicitTag)
|
|
@@ -93579,14 +93754,14 @@ services:
|
|
|
93579
93754
|
`;
|
|
93580
93755
|
}
|
|
93581
93756
|
function webdDir() {
|
|
93582
|
-
return
|
|
93757
|
+
return join94(homedir56(), ".switchroom", "web");
|
|
93583
93758
|
}
|
|
93584
93759
|
function webdComposePath() {
|
|
93585
|
-
return
|
|
93760
|
+
return join94(webdDir(), "docker-compose.yml");
|
|
93586
93761
|
}
|
|
93587
93762
|
function backupExistingCompose2() {
|
|
93588
93763
|
const p = webdComposePath();
|
|
93589
|
-
if (!
|
|
93764
|
+
if (!existsSync96(p))
|
|
93590
93765
|
return null;
|
|
93591
93766
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
93592
93767
|
const bak = `${p}.bak-${ts}`;
|
|
@@ -93685,7 +93860,7 @@ function doStatus2() {
|
|
|
93685
93860
|
const composeYml = webdComposePath();
|
|
93686
93861
|
console.log(source_default.bold("switchroom-web"));
|
|
93687
93862
|
console.log("");
|
|
93688
|
-
if (!
|
|
93863
|
+
if (!existsSync96(composeYml)) {
|
|
93689
93864
|
console.log(source_default.yellow(" compose: not installed"));
|
|
93690
93865
|
console.log(source_default.dim(" run `switchroom webd install` to set up."));
|
|
93691
93866
|
return;
|
|
@@ -93709,7 +93884,7 @@ function doStatus2() {
|
|
|
93709
93884
|
}
|
|
93710
93885
|
function doUninstall2() {
|
|
93711
93886
|
const composeYml = webdComposePath();
|
|
93712
|
-
if (!
|
|
93887
|
+
if (!existsSync96(composeYml)) {
|
|
93713
93888
|
console.log(source_default.yellow(" No web-service install detected (no compose file at this path)."));
|
|
93714
93889
|
return;
|
|
93715
93890
|
}
|
|
@@ -93739,9 +93914,9 @@ function registerWebdCommand(program3) {
|
|
|
93739
93914
|
// src/cli/host-repair.ts
|
|
93740
93915
|
init_source();
|
|
93741
93916
|
import { homedir as homedir57 } from "node:os";
|
|
93742
|
-
import { join as
|
|
93917
|
+
import { join as join95 } from "node:path";
|
|
93743
93918
|
var ARTIFACT_ALLOWLIST = {
|
|
93744
|
-
dockerComposePluginDir: (home2) =>
|
|
93919
|
+
dockerComposePluginDir: (home2) => join95(home2, ".docker", "cli-plugins", "docker-compose"),
|
|
93745
93920
|
stateSentinel: "/state"
|
|
93746
93921
|
};
|
|
93747
93922
|
function isStateBogusAutoDir(probe2) {
|
|
@@ -93895,11 +94070,11 @@ function applyMountRepairs(items, deps) {
|
|
|
93895
94070
|
function registerHostCommand(program3) {
|
|
93896
94071
|
const host = program3.command("host").description("Host-level maintenance operations for switchroom");
|
|
93897
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) => {
|
|
93898
|
-
const { rmdirSync: rmdirSync2, rmSync: rmSync20, lstatSync:
|
|
94073
|
+
const { rmdirSync: rmdirSync2, rmSync: rmSync20, lstatSync: lstatSync14, readdirSync: readdirSync37 } = await import("node:fs");
|
|
93899
94074
|
const probe2 = {
|
|
93900
94075
|
lstat(path8) {
|
|
93901
94076
|
try {
|
|
93902
|
-
return
|
|
94077
|
+
return lstatSync14(path8);
|
|
93903
94078
|
} catch {
|
|
93904
94079
|
return null;
|
|
93905
94080
|
}
|