switchroom 0.19.25 → 0.19.26
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 +6 -2
- package/dist/auth-broker/index.js +9 -2
- package/dist/cli/notion-write-pretool.mjs +6 -2
- package/dist/cli/switchroom.js +908 -527
- package/dist/host-control/main.js +10 -3
- package/dist/vault/approvals/kernel-server.js +10 -2
- package/dist/vault/broker/server.js +10 -2
- package/package.json +1 -1
- package/profiles/_base/cron-session.sh.hbs +6 -0
- package/profiles/_base/start.sh.hbs +40 -4
- package/telegram-plugin/dist/gateway/gateway.js +275 -109
- package/telegram-plugin/gateway/gateway.ts +53 -52
- package/telegram-plugin/gateway/periodic-sweep-guard.ts +86 -0
- package/telegram-plugin/gateway/status-pin-retarget.ts +144 -0
- package/telegram-plugin/status-no-truncate.ts +49 -0
- package/telegram-plugin/status-pin-driver.ts +28 -0
- package/telegram-plugin/status-pin.ts +33 -4
- package/telegram-plugin/tests/card-type-distinguishability.test.ts +268 -0
- package/telegram-plugin/tests/periodic-sweep-guard.test.ts +151 -0
- package/telegram-plugin/tests/pinned-card-collapse.test.ts +29 -18
- package/telegram-plugin/tests/status-pin-retarget.test.ts +216 -0
- package/telegram-plugin/tests/status-pin-shutdown-wiring.test.ts +94 -0
- package/telegram-plugin/tests/status-pin-store.test.ts +87 -21
- package/telegram-plugin/tests/status-pin.test.ts +128 -2
- package/telegram-plugin/tests/worker-activity-feed.test.ts +10 -10
- package/telegram-plugin/tests/worker-feed-coalesce.test.ts +37 -21
- package/telegram-plugin/tests/worker-visibility-prose-silent-harness.test.ts +1 -1
- package/telegram-plugin/tier-downgrade.ts +3 -2
- package/telegram-plugin/tool-activity-summary.ts +61 -18
- package/telegram-plugin/uat/assertions.ts +21 -2
- package/telegram-plugin/uat/feed-matcher.test.ts +29 -0
- package/telegram-plugin/uat/scenarios/jtbd-liveness-narration-channel.test.ts +9 -2
- package/telegram-plugin/uat/scenarios/jtbd-liveness-narration-dm.test.ts +9 -2
- package/telegram-plugin/worker-activity-feed.ts +38 -17
- package/vendor/hindsight-memory/scripts/tests/test_recall_request_timeout.py +241 -0
package/dist/cli/switchroom.js
CHANGED
|
@@ -2120,7 +2120,7 @@ var init_esm = __esm(() => {
|
|
|
2120
2120
|
});
|
|
2121
2121
|
|
|
2122
2122
|
// src/build-info.ts
|
|
2123
|
-
var VERSION = "0.19.
|
|
2123
|
+
var VERSION = "0.19.26", COMMIT_SHA = "f9314b89";
|
|
2124
2124
|
|
|
2125
2125
|
// src/cli/resolve-version.ts
|
|
2126
2126
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -13726,13 +13726,15 @@ var init_schema = __esm(() => {
|
|
|
13726
13726
|
recall: exports_external.object({
|
|
13727
13727
|
max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget. Plugin default is 12. " + "0 disables the cap (all memories Hindsight returns are injected)."),
|
|
13728
13728
|
cache_ttl_secs: exports_external.number().int().min(0).optional().describe("Per-session recall cache TTL in seconds. When > 0, identical " + "(prompt, bank) within the same session reuse the cached recall " + "result instead of round-tripping to Hindsight. 0 disables. " + "Default is 600 (10 min) for switchroom-managed agents."),
|
|
13729
|
+
hook_timeout_seconds: exports_external.number().int().min(1).optional().describe("Ceiling (seconds) Claude Code gives the UserPromptSubmit recall " + "hook before killing it. Stamped into the installed plugin's " + "hooks/hooks.json, so it survives `switchroom apply` reinstalling " + "the plugin. Default 12. Raising it lets slow banks finish at the " + "cost of pre-turn dead air; `parallel_deadline_seconds` and " + "`request_timeout_seconds` are both kept under it. A value below " + "3s is raised to 3s and reported: the fan-out deadline must be at " + "least 1s AND still leave 2s of post-deadline headroom, so a " + "lower ceiling admits no usable envelope at all."),
|
|
13730
|
+
parallel_deadline_seconds: exports_external.number().int().min(1).optional().describe("Shared deadline (seconds) for the whole parallel multi-bank " + "recall fan-out. Slots unfinished when it elapses are abandoned " + "and reported as timed out. Defaults to `hook_timeout_seconds` " + "minus 2s of headroom for block formatting, cache write and " + "stdout flush, so a straggler bank can never push the hook past " + "its ceiling. Set explicitly to override that derivation; a value " + "that would leave less than 2s under the hook ceiling \u2014 including " + "one set EQUAL to it \u2014 is clamped back to `hook_timeout_seconds` " + "minus 2, and the clamp is reported. Equality is not allowed: at " + "zero headroom the hook is killed mid-write and the turn loses " + "both the memories and the recall_log row explaining why."),
|
|
13729
13731
|
query_max_tokens: exports_external.number().int().min(0).optional().describe("Cap on the number of DISTINCT BM25 terms the recall hook may " + "put on the wire. `recallMaxQueryChars` bounds characters, which " + "is not the cost driver: Hindsight OR-joins every query token " + "into one tsquery and Postgres native FTS ranks the entire " + "matched set before the top-60 heapsort, so cost tracks TERMS. " + "An 800-char composed query is ~96 distinct terms and matched " + "119,510 of 135,565 rows on the `overlord` bank (14.0s for the " + "3-arm " + "BM25 UNION), past the per-bank timeout \u2014 96.8% of that agent's " + "own-bank recalls returned nothing. Plugin default is 24 " + "(measured 48,433 rows / 2.7s on the same bank). Terms are " + "chosen recency-first (the latest turn beats prior context), " + "then by selectivity. 0 disables shaping (rollback lever)."),
|
|
13730
13732
|
query_stop_terms: exports_external.array(exports_external.string().min(1).regex(/^[\w./-]+$/)).optional().describe("Extra terms dropped from the BM25 recall query, on top of the " + "built-in English stopword list. For BANK-SPECIFIC " + "high-document-frequency words a generic stoplist cannot know " + "about: on `overlord`, `switchroom` matches 20% of the bank and " + "`agent` another 20%, purely because that is what the corpus is " + "about, and each such term drags tens of thousands of rows into " + "the ranking. Defaults to []."),
|
|
13731
|
-
request_timeout_seconds: exports_external.number().int().min(1).optional().describe("Per-bank HTTP read timeout, in seconds, for one recall " + "request.
|
|
13733
|
+
request_timeout_seconds: exports_external.number().int().min(1).optional().describe("Per-bank HTTP read timeout, in seconds, for one recall " + "request. Even parallelised, each bank carries its own deadline " + "so ONE hung bank returns empty instead of consuming the shared " + "deadline and starving its siblings. The plugin default is 12 " + "(raised from a hardcoded 8 in #3757, which fired on 96.8% of " + "one agent's own-bank recalls). Switchroom defaults it to the " + "effective `parallel_deadline_seconds` instead \u2014 10 at the " + "shipped ceiling \u2014 because the shared fan-out deadline is " + "already the tighter outer guard, so a per-bank value above it " + "can never bind. An explicitly configured value above the " + "effective deadline is clamped down to it, and the clamp is " + "reported."),
|
|
13732
13734
|
own_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the agent's " + "own bank when recall fans out to more than one bank. The merged " + "set is sorted globally by relevance and head-sliced, which is " + "winner-take-all across banks: when both banks return more " + "candidates than the cap, one bank's score distribution can fill " + "every slot and the agent gets a dossier about its operator with " + "none of its own session memory. A floor, not a quota: at most " + "this many slots, only if the own bank returned that many, and " + "only up to HALF the cap shared with `additional_bank_min_slots` " + "\u2014 the rest is always won on pure relevance, so composition still " + "moves with the scores. Fixes score-based crowd-out only; a " + "timed-out bank returns no candidates and reservation is a no-op " + "there. 0 disables (default). Switchroom-managed agents use 2 " + "against the fleet-deployed cap of 6."),
|
|
13733
13735
|
additional_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the " + "additional (profile / shared / sender) banks. Symmetric with " + "`own_bank_min_slots` \u2014 same floor-not-quota semantics, and the " + "two share the same half-of-cap reservation budget. When they sum " + "above that budget the own-bank floor is honoured first. 0 " + "disables (default). Switchroom-managed agents use 1 against the " + "fleet-deployed cap of 6. Observe `injected_own_bank_count` / " + "`injected_additional_bank_count` via " + "`switchroom memory recall-log`."),
|
|
13734
13736
|
types: exports_external.array(exports_external.string()).optional().describe("Hindsight fact types to recall. Switchroom default is " + '["world", "experience", "observation"] \u2014 the synthesized ' + "`observation` tier is on by default. Set to " + '["world", "experience"] to opt out of observation-backed ' + "recall for this agent (or fleet-wide under defaults)."),
|
|
13735
|
-
additional_banks: exports_external.array(exports_external.string()).optional().describe("Extra Hindsight banks to recall from on every turn, merged into " + "the agent's own bank results \u2014 e.g. a shared operator/household " + "profile bank authored via `switchroom memory profile`. Each is " + "recalled with
|
|
13737
|
+
additional_banks: exports_external.array(exports_external.string()).optional().describe("Extra Hindsight banks to recall from on every turn, merged into " + "the agent's own bank results \u2014 e.g. a shared operator/household " + "profile bank authored via `switchroom memory profile`. Each is " + "recalled with the `request_timeout_seconds` per-bank timeout " + "(defaults to the effective `parallel_deadline_seconds`, 10s at " + "the shipped ceiling) and is non-fatal on failure. Stays " + "within the single tenant: all banks are the operator's data, in " + "the operator's Hindsight instance (see the `single-tenant` " + "invariant). Defaults to [] (no extra banks)."),
|
|
13736
13738
|
sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Per-speaker recall routing: a map of Telegram sender \u2192 extra " + "recall bank. When a message arrives, the agent also recalls the " + "speaker's bank (matched by Telegram username \u2014 a leading @ is " + "optional \u2014 or numeric user_id), merged " + "into its own results \u2014 so each trusted user gets their own " + "profile context. Additive recall scoping within the single " + "tenant: never an access boundary (who may drive an agent stays " + "the per-agent user assignment in `access.allowFrom`). Author the " + "banks via `switchroom memory profile`."),
|
|
13737
13739
|
skip_trivial: exports_external.boolean().optional().describe("Skip recall on plausibly-stateless trivial turns (time/date/" + "greeting). Switchroom default true \u2014 saves the recall arm + " + "injected tokens on turns that never need memory, guarded so it " + "never skips a turn that references user/project/session state. " + "Set false to always run recall."),
|
|
13738
13740
|
topic_filter_mode: exports_external.enum(["soft-preamble", "hard-filter"]).optional().describe("Supergroup-mode cross-topic memory behaviour. Default " + "(unset) \u2192 soft-preamble: recall returns memories from all " + "topics, and a 'Current topic: \u2026' preamble tells the model " + "to self-scope. hard-filter: drop any recalled memory whose " + "metadata.thread_id differs from the active inbound's topic. " + "Flip to hard-filter when the recall_log shows binding " + "failures (model surfacing the right memory but applying " + "it to the wrong topic).")
|
|
@@ -14091,6 +14093,8 @@ var init_schema = __esm(() => {
|
|
|
14091
14093
|
recall: exports_external.object({
|
|
14092
14094
|
max_memories: exports_external.number().int().min(0).optional(),
|
|
14093
14095
|
cache_ttl_secs: exports_external.number().int().min(0).optional(),
|
|
14096
|
+
hook_timeout_seconds: exports_external.number().int().min(1).optional(),
|
|
14097
|
+
parallel_deadline_seconds: exports_external.number().int().min(1).optional(),
|
|
14094
14098
|
query_max_tokens: exports_external.number().int().min(0).optional(),
|
|
14095
14099
|
query_stop_terms: exports_external.array(exports_external.string().min(1).regex(/^[\w./-]+$/)).optional(),
|
|
14096
14100
|
request_timeout_seconds: exports_external.number().int().min(1).optional(),
|
|
@@ -17992,6 +17996,106 @@ var init_generation_stamp = __esm(() => {
|
|
|
17992
17996
|
STAMPED_FILES = ["start.sh", "CLAUDE.md", ".mcp.json"];
|
|
17993
17997
|
});
|
|
17994
17998
|
|
|
17999
|
+
// src/setup/hindsight-recall-tunables.ts
|
|
18000
|
+
function resolveHindsightRecallTunables(recall) {
|
|
18001
|
+
const clamps = [];
|
|
18002
|
+
const usable = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? Math.floor(v) : undefined;
|
|
18003
|
+
let hookTimeoutSeconds = usable(recall?.hook_timeout_seconds) ?? DEFAULT_RECALL_HOOK_TIMEOUT_SECONDS;
|
|
18004
|
+
if (hookTimeoutSeconds < MIN_RECALL_HOOK_TIMEOUT_SECONDS) {
|
|
18005
|
+
clamps.push(`memory.recall.hook_timeout_seconds ${hookTimeoutSeconds}s is below the minimum ` + `${MIN_RECALL_HOOK_TIMEOUT_SECONDS}s \u2014 raised to ${MIN_RECALL_HOOK_TIMEOUT_SECONDS}s ` + `(the fan-out deadline must be at least 1s AND leave ` + `${RECALL_DEADLINE_HEADROOM_SECONDS}s of post-deadline headroom, so a lower ` + `ceiling admits no usable envelope at all)`);
|
|
18006
|
+
hookTimeoutSeconds = MIN_RECALL_HOOK_TIMEOUT_SECONDS;
|
|
18007
|
+
}
|
|
18008
|
+
const maxDeadline = hookTimeoutSeconds - RECALL_DEADLINE_HEADROOM_SECONDS;
|
|
18009
|
+
let parallelDeadlineSeconds = usable(recall?.parallel_deadline_seconds) ?? maxDeadline;
|
|
18010
|
+
if (parallelDeadlineSeconds > maxDeadline) {
|
|
18011
|
+
clamps.push(`memory.recall.parallel_deadline_seconds ${parallelDeadlineSeconds}s leaves less ` + `than ${RECALL_DEADLINE_HEADROOM_SECONDS}s under hook_timeout_seconds ` + `${hookTimeoutSeconds}s \u2014 clamped to ${maxDeadline}s (Claude Code SIGKILLs the ` + `hook at the ceiling, so a deadline at or past it loses both the memories and ` + `the recall_log row that would have explained why)`);
|
|
18012
|
+
parallelDeadlineSeconds = maxDeadline;
|
|
18013
|
+
}
|
|
18014
|
+
let requestTimeoutSeconds = usable(recall?.request_timeout_seconds) ?? Math.min(DEFAULT_RECALL_REQUEST_TIMEOUT_SECONDS, parallelDeadlineSeconds);
|
|
18015
|
+
if (requestTimeoutSeconds > parallelDeadlineSeconds) {
|
|
18016
|
+
clamps.push(`memory.recall.request_timeout_seconds ${requestTimeoutSeconds}s exceeds the ` + `effective parallel deadline ${parallelDeadlineSeconds}s \u2014 clamped to ` + `${parallelDeadlineSeconds}s (a per-bank timeout past the shared deadline can ` + `never fire)`);
|
|
18017
|
+
requestTimeoutSeconds = parallelDeadlineSeconds;
|
|
18018
|
+
}
|
|
18019
|
+
return {
|
|
18020
|
+
hookTimeoutSeconds,
|
|
18021
|
+
parallelDeadlineSeconds,
|
|
18022
|
+
requestTimeoutSeconds,
|
|
18023
|
+
clamps
|
|
18024
|
+
};
|
|
18025
|
+
}
|
|
18026
|
+
function renderHindsightHooksOverrides(raw, tunables) {
|
|
18027
|
+
let parsed;
|
|
18028
|
+
try {
|
|
18029
|
+
parsed = JSON.parse(raw);
|
|
18030
|
+
} catch {
|
|
18031
|
+
return null;
|
|
18032
|
+
}
|
|
18033
|
+
if (parsed == null || typeof parsed !== "object")
|
|
18034
|
+
return null;
|
|
18035
|
+
const root = parsed;
|
|
18036
|
+
const hooks = root.hooks;
|
|
18037
|
+
if (hooks == null || typeof hooks !== "object")
|
|
18038
|
+
return null;
|
|
18039
|
+
const matchers = hooks[MANAGED_HOOK_EVENT];
|
|
18040
|
+
if (!Array.isArray(matchers))
|
|
18041
|
+
return null;
|
|
18042
|
+
let stamped = false;
|
|
18043
|
+
for (const matcher of matchers) {
|
|
18044
|
+
if (matcher == null || typeof matcher !== "object")
|
|
18045
|
+
continue;
|
|
18046
|
+
const inner = matcher.hooks;
|
|
18047
|
+
if (!Array.isArray(inner))
|
|
18048
|
+
continue;
|
|
18049
|
+
for (const hook of inner) {
|
|
18050
|
+
if (hook == null || typeof hook !== "object")
|
|
18051
|
+
continue;
|
|
18052
|
+
const h = hook;
|
|
18053
|
+
if (typeof h.command !== "string")
|
|
18054
|
+
continue;
|
|
18055
|
+
if (!h.command.includes(RECALL_HOOK_COMMAND_MARKER))
|
|
18056
|
+
continue;
|
|
18057
|
+
h.timeout = tunables.hookTimeoutSeconds;
|
|
18058
|
+
stamped = true;
|
|
18059
|
+
}
|
|
18060
|
+
}
|
|
18061
|
+
if (!stamped)
|
|
18062
|
+
return null;
|
|
18063
|
+
return JSON.stringify(root, null, 2) + `
|
|
18064
|
+
`;
|
|
18065
|
+
}
|
|
18066
|
+
function readHooksRecallTimeout(raw) {
|
|
18067
|
+
let parsed;
|
|
18068
|
+
try {
|
|
18069
|
+
parsed = JSON.parse(raw);
|
|
18070
|
+
} catch {
|
|
18071
|
+
return null;
|
|
18072
|
+
}
|
|
18073
|
+
const hooks = parsed?.hooks;
|
|
18074
|
+
if (hooks == null || typeof hooks !== "object")
|
|
18075
|
+
return null;
|
|
18076
|
+
const matchers = hooks[MANAGED_HOOK_EVENT];
|
|
18077
|
+
if (!Array.isArray(matchers))
|
|
18078
|
+
return null;
|
|
18079
|
+
for (const matcher of matchers) {
|
|
18080
|
+
const inner = matcher?.hooks;
|
|
18081
|
+
if (!Array.isArray(inner))
|
|
18082
|
+
continue;
|
|
18083
|
+
for (const hook of inner) {
|
|
18084
|
+
const h = hook;
|
|
18085
|
+
if (h == null || typeof h.command !== "string")
|
|
18086
|
+
continue;
|
|
18087
|
+
if (!h.command.includes(RECALL_HOOK_COMMAND_MARKER))
|
|
18088
|
+
continue;
|
|
18089
|
+
return typeof h.timeout === "number" ? h.timeout : null;
|
|
18090
|
+
}
|
|
18091
|
+
}
|
|
18092
|
+
return null;
|
|
18093
|
+
}
|
|
18094
|
+
var DEFAULT_RECALL_HOOK_TIMEOUT_SECONDS = 12, RECALL_DEADLINE_HEADROOM_SECONDS = 2, MIN_RECALL_HOOK_TIMEOUT_SECONDS, DEFAULT_RECALL_REQUEST_TIMEOUT_SECONDS = 12, MANAGED_HOOK_EVENT = "UserPromptSubmit", RECALL_HOOK_COMMAND_MARKER = "recall.py";
|
|
18095
|
+
var init_hindsight_recall_tunables = __esm(() => {
|
|
18096
|
+
MIN_RECALL_HOOK_TIMEOUT_SECONDS = RECALL_DEADLINE_HEADROOM_SECONDS + 1;
|
|
18097
|
+
});
|
|
18098
|
+
|
|
17995
18099
|
// src/scheduler/cron-routing.ts
|
|
17996
18100
|
function isKnownCheapModel(model) {
|
|
17997
18101
|
return model !== undefined && CHEAP_MODEL_RE.test(model);
|
|
@@ -27805,7 +27909,7 @@ function writeFileSyncIfChanged(filePath, content, mode) {
|
|
|
27805
27909
|
writeFileSync6(filePath, content, mode !== undefined ? { encoding: "utf-8", mode } : "utf-8");
|
|
27806
27910
|
return true;
|
|
27807
27911
|
}
|
|
27808
|
-
function dirContentEquals(src, dest,
|
|
27912
|
+
function dirContentEquals(src, dest, contentOverrides, relBase = "") {
|
|
27809
27913
|
if (!existsSync18(src) || !existsSync18(dest))
|
|
27810
27914
|
return false;
|
|
27811
27915
|
let srcEntries;
|
|
@@ -27833,16 +27937,17 @@ function dirContentEquals(src, dest, topLevelOverrides) {
|
|
|
27833
27937
|
} catch {
|
|
27834
27938
|
return false;
|
|
27835
27939
|
}
|
|
27940
|
+
const rel = relBase === "" ? entry : `${relBase}/${entry}`;
|
|
27836
27941
|
if (sStat.isDirectory() !== dStat.isDirectory())
|
|
27837
27942
|
return false;
|
|
27838
27943
|
if (sStat.isDirectory()) {
|
|
27839
|
-
if (!dirContentEquals(s, d))
|
|
27944
|
+
if (!dirContentEquals(s, d, contentOverrides, rel))
|
|
27840
27945
|
return false;
|
|
27841
27946
|
} else {
|
|
27842
27947
|
if ((sStat.mode & 64) !== (dStat.mode & 64))
|
|
27843
27948
|
return false;
|
|
27844
27949
|
try {
|
|
27845
|
-
const expected =
|
|
27950
|
+
const expected = contentOverrides?.[rel];
|
|
27846
27951
|
if (expected !== undefined) {
|
|
27847
27952
|
if (readFileSync15(d, "utf-8") !== expected)
|
|
27848
27953
|
return false;
|
|
@@ -28010,7 +28115,7 @@ Thumbs.db
|
|
|
28010
28115
|
function resolveHindsightVendorPath() {
|
|
28011
28116
|
return resolve11(import.meta.dirname, "../../vendor/hindsight-memory");
|
|
28012
28117
|
}
|
|
28013
|
-
function installHindsightPlugin(agentName, agentDir, switchroomConfig) {
|
|
28118
|
+
function installHindsightPlugin(agentName, agentDir, switchroomConfig, resolvedAgentConfig) {
|
|
28014
28119
|
if (!switchroomConfig)
|
|
28015
28120
|
return null;
|
|
28016
28121
|
const memory = switchroomConfig.memory;
|
|
@@ -28030,21 +28135,31 @@ function installHindsightPlugin(agentName, agentDir, switchroomConfig) {
|
|
|
28030
28135
|
const destPath = join12(agentDir, ".claude", "plugins", "hindsight-memory");
|
|
28031
28136
|
const additionalBanks = resolveUsers(switchroomConfig, agentName).additionalBanks;
|
|
28032
28137
|
const retainConfig = resolveHindsightRetainConfig(switchroomConfig, agentName);
|
|
28033
|
-
|
|
28138
|
+
const recallTunables = resolveHindsightRecallTunables(resolveHindsightRecallConfig(switchroomConfig, agentName, resolvedAgentConfig));
|
|
28139
|
+
const contentOverrides = {};
|
|
28034
28140
|
try {
|
|
28035
28141
|
const vendorSettingsPath = join12(sourcePath, "settings.json");
|
|
28036
28142
|
if (existsSync18(vendorSettingsPath)) {
|
|
28037
|
-
expectedSettings = renderHindsightSettingsOverrides(readFileSync15(vendorSettingsPath, "utf-8"), additionalBanks, retainConfig);
|
|
28143
|
+
const expectedSettings = renderHindsightSettingsOverrides(readFileSync15(vendorSettingsPath, "utf-8"), additionalBanks, retainConfig, recallTunables);
|
|
28144
|
+
if (expectedSettings != null)
|
|
28145
|
+
contentOverrides["settings.json"] = expectedSettings;
|
|
28146
|
+
}
|
|
28147
|
+
const vendorHooksPath = join12(sourcePath, "hooks", "hooks.json");
|
|
28148
|
+
if (existsSync18(vendorHooksPath)) {
|
|
28149
|
+
const expectedHooks = renderHindsightHooksOverrides(readFileSync15(vendorHooksPath, "utf-8"), recallTunables);
|
|
28150
|
+
if (expectedHooks != null)
|
|
28151
|
+
contentOverrides["hooks/hooks.json"] = expectedHooks;
|
|
28038
28152
|
}
|
|
28039
28153
|
} catch {}
|
|
28040
|
-
const upToDate = dirContentEquals(sourcePath, destPath,
|
|
28154
|
+
const upToDate = dirContentEquals(sourcePath, destPath, contentOverrides);
|
|
28041
28155
|
if (!upToDate) {
|
|
28042
28156
|
if (existsSync18(destPath)) {
|
|
28043
28157
|
rmSync4(destPath, { recursive: true, force: true });
|
|
28044
28158
|
}
|
|
28045
28159
|
copyDirRecursive2(sourcePath, destPath);
|
|
28046
28160
|
}
|
|
28047
|
-
applyHindsightSettingsOverrides(destPath, additionalBanks, retainConfig);
|
|
28161
|
+
applyHindsightSettingsOverrides(destPath, additionalBanks, retainConfig, recallTunables);
|
|
28162
|
+
applyHindsightHooksOverrides(destPath, recallTunables);
|
|
28048
28163
|
const bankId = agentMemory?.collection ?? agentName;
|
|
28049
28164
|
const mcpUrl = memory.config?.url ?? HINDSIGHT_DEFAULT_MCP_URL;
|
|
28050
28165
|
const apiBaseUrl = mcpUrl.replace(/\/mcp\/?$/, "").replace(/\/$/, "");
|
|
@@ -28058,7 +28173,31 @@ function resolveHindsightRetainConfig(switchroomConfig, agentName) {
|
|
|
28058
28173
|
overlapTurns: retain?.overlap_turns ?? HINDSIGHT_DEFAULT_RETAIN_OVERLAP_TURNS
|
|
28059
28174
|
};
|
|
28060
28175
|
}
|
|
28061
|
-
function
|
|
28176
|
+
function resolveHindsightRecallConfig(switchroomConfig, agentName, resolvedAgentConfig) {
|
|
28177
|
+
if (resolvedAgentConfig) {
|
|
28178
|
+
return resolvedAgentConfig.memory?.recall;
|
|
28179
|
+
}
|
|
28180
|
+
if (!switchroomConfig)
|
|
28181
|
+
return;
|
|
28182
|
+
const resolved = resolveAgentConfig(switchroomConfig.defaults, switchroomConfig.profiles, switchroomConfig.agents[agentName] ?? {});
|
|
28183
|
+
return resolved?.memory?.recall;
|
|
28184
|
+
}
|
|
28185
|
+
function applyHindsightHooksOverrides(pluginDestPath, tunables) {
|
|
28186
|
+
const hooksPath = join12(pluginDestPath, "hooks", "hooks.json");
|
|
28187
|
+
if (!existsSync18(hooksPath))
|
|
28188
|
+
return;
|
|
28189
|
+
let raw;
|
|
28190
|
+
try {
|
|
28191
|
+
raw = readFileSync15(hooksPath, "utf-8");
|
|
28192
|
+
} catch {
|
|
28193
|
+
return;
|
|
28194
|
+
}
|
|
28195
|
+
const next = renderHindsightHooksOverrides(raw, tunables);
|
|
28196
|
+
if (next == null)
|
|
28197
|
+
return;
|
|
28198
|
+
writeFileSyncIfChanged(hooksPath, next);
|
|
28199
|
+
}
|
|
28200
|
+
function applyHindsightSettingsOverrides(pluginDestPath, additionalBanks, retainConfig, recallTunables) {
|
|
28062
28201
|
const settingsPath = join12(pluginDestPath, "settings.json");
|
|
28063
28202
|
if (!existsSync18(settingsPath))
|
|
28064
28203
|
return;
|
|
@@ -28068,12 +28207,12 @@ function applyHindsightSettingsOverrides(pluginDestPath, additionalBanks, retain
|
|
|
28068
28207
|
} catch {
|
|
28069
28208
|
return;
|
|
28070
28209
|
}
|
|
28071
|
-
const next = renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig);
|
|
28210
|
+
const next = renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig, recallTunables);
|
|
28072
28211
|
if (next == null)
|
|
28073
28212
|
return;
|
|
28074
28213
|
writeFileSyncIfChanged(settingsPath, next);
|
|
28075
28214
|
}
|
|
28076
|
-
function renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig) {
|
|
28215
|
+
function renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig, recallTunables) {
|
|
28077
28216
|
let settings;
|
|
28078
28217
|
try {
|
|
28079
28218
|
settings = JSON.parse(raw);
|
|
@@ -28084,6 +28223,8 @@ function renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig) {
|
|
|
28084
28223
|
settings.retainMode = "chunked";
|
|
28085
28224
|
settings.retainOverlapTurns = retainConfig.overlapTurns;
|
|
28086
28225
|
settings.recallMaxMemories = 8;
|
|
28226
|
+
settings.recallParallelDeadlineSeconds = recallTunables.parallelDeadlineSeconds;
|
|
28227
|
+
settings.recallRequestTimeoutSeconds = recallTunables.requestTimeoutSeconds;
|
|
28087
28228
|
settings.recallTypes = ["world", "experience", "observation"];
|
|
28088
28229
|
settings.recallOwnBankMinSlots = HINDSIGHT_OWN_BANK_MIN_SLOTS_DEFAULT;
|
|
28089
28230
|
settings.recallAdditionalBankMinSlots = HINDSIGHT_ADDITIONAL_BANK_MIN_SLOTS_DEFAULT;
|
|
@@ -28122,6 +28263,7 @@ function buildWorkspaceContext(args) {
|
|
|
28122
28263
|
hindsightApiBaseUrl,
|
|
28123
28264
|
hindsightRecallMaxMemories,
|
|
28124
28265
|
hindsightRecallCacheTtlSecs,
|
|
28266
|
+
hindsightRecallParallelDeadlineSeconds,
|
|
28125
28267
|
hindsightRecallQueryMaxTokens,
|
|
28126
28268
|
hindsightRecallQueryStopTermsJson,
|
|
28127
28269
|
hindsightRecallRequestTimeoutSeconds,
|
|
@@ -28171,6 +28313,7 @@ function buildWorkspaceContext(args) {
|
|
|
28171
28313
|
hindsightApiBaseUrlQ: shellSingleQuote(hindsightApiBaseUrl),
|
|
28172
28314
|
hindsightRecallMaxMemories,
|
|
28173
28315
|
hindsightRecallCacheTtlSecs,
|
|
28316
|
+
hindsightRecallParallelDeadlineSeconds,
|
|
28174
28317
|
hindsightRecallQueryMaxTokens,
|
|
28175
28318
|
hindsightRecallQueryStopTermsJson,
|
|
28176
28319
|
hindsightRecallRequestTimeoutSeconds,
|
|
@@ -28429,10 +28572,12 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
|
|
|
28429
28572
|
const hindsightApiBaseUrl = switchroomConfig?.memory?.config?.url ? switchroomConfig.memory.config.url.replace(/\/mcp\/?$/, "").replace(/\/$/, "") : HINDSIGHT_DEFAULT_API_BASE_URL;
|
|
28430
28573
|
const hindsightRecallMaxMemories = agentConfig.memory?.recall?.max_memories;
|
|
28431
28574
|
const hindsightRecallCacheTtlSecs = agentConfig.memory?.recall?.cache_ttl_secs;
|
|
28575
|
+
const hindsightRecallTunables = resolveHindsightRecallTunables(agentConfig.memory?.recall);
|
|
28576
|
+
const hindsightRecallParallelDeadlineSeconds = hindsightRecallTunables.parallelDeadlineSeconds;
|
|
28577
|
+
const hindsightRecallRequestTimeoutSeconds = hindsightRecallTunables.requestTimeoutSeconds;
|
|
28432
28578
|
const hindsightRecallQueryMaxTokens = agentConfig.memory?.recall?.query_max_tokens ?? HINDSIGHT_RECALL_QUERY_MAX_TOKENS_DEFAULT;
|
|
28433
28579
|
const rawRecallQueryStopTerms = agentConfig.memory?.recall?.query_stop_terms;
|
|
28434
28580
|
const hindsightRecallQueryStopTermsJson = JSON.stringify(rawRecallQueryStopTerms ?? []);
|
|
28435
|
-
const hindsightRecallRequestTimeoutSeconds = agentConfig.memory?.recall?.request_timeout_seconds ?? HINDSIGHT_RECALL_REQUEST_TIMEOUT_SECONDS_DEFAULT;
|
|
28436
28581
|
const hindsightRecallOwnBankMinSlots = agentConfig.memory?.recall?.own_bank_min_slots ?? HINDSIGHT_OWN_BANK_MIN_SLOTS_DEFAULT;
|
|
28437
28582
|
const hindsightRecallAdditionalBankMinSlots = agentConfig.memory?.recall?.additional_bank_min_slots ?? HINDSIGHT_ADDITIONAL_BANK_MIN_SLOTS_DEFAULT;
|
|
28438
28583
|
const hindsightRecallTypes = agentConfig.memory?.recall?.types?.length ? agentConfig.memory.recall.types.join(",") : undefined;
|
|
@@ -28461,6 +28606,7 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
|
|
|
28461
28606
|
hindsightApiBaseUrl,
|
|
28462
28607
|
hindsightRecallMaxMemories,
|
|
28463
28608
|
hindsightRecallCacheTtlSecs,
|
|
28609
|
+
hindsightRecallParallelDeadlineSeconds,
|
|
28464
28610
|
hindsightRecallQueryMaxTokens,
|
|
28465
28611
|
hindsightRecallQueryStopTermsJson,
|
|
28466
28612
|
hindsightRecallRequestTimeoutSeconds,
|
|
@@ -28547,7 +28693,7 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
|
|
|
28547
28693
|
}
|
|
28548
28694
|
retractStaleIntegrationKeys(integration, new Set(entries.map((e) => e.key)), settings.mcpServers, site1Protected);
|
|
28549
28695
|
}
|
|
28550
|
-
installHindsightPlugin(name, agentDir, switchroomConfig);
|
|
28696
|
+
installHindsightPlugin(name, agentDir, switchroomConfig, agentConfig);
|
|
28551
28697
|
const hindsightOn = isHindsightEnabled(switchroomConfig) && switchroomConfig.agents[name]?.memory?.auto_recall !== false;
|
|
28552
28698
|
if (hindsightOn) {
|
|
28553
28699
|
settings.autoMemoryEnabled = false;
|
|
@@ -29394,10 +29540,12 @@ function reconcileAgentInner(name, agentConfigRaw, agentsDir, telegramConfig, sw
|
|
|
29394
29540
|
const hindsightApiBaseUrl = switchroomConfig.memory?.config?.url ? switchroomConfig.memory.config.url.replace(/\/mcp\/?$/, "").replace(/\/$/, "") : HINDSIGHT_DEFAULT_API_BASE_URL;
|
|
29395
29541
|
const hindsightRecallMaxMemories = agentConfig.memory?.recall?.max_memories;
|
|
29396
29542
|
const hindsightRecallCacheTtlSecs = agentConfig.memory?.recall?.cache_ttl_secs;
|
|
29543
|
+
const hindsightRecallTunables = resolveHindsightRecallTunables(agentConfig.memory?.recall);
|
|
29544
|
+
const hindsightRecallParallelDeadlineSeconds = hindsightRecallTunables.parallelDeadlineSeconds;
|
|
29545
|
+
const hindsightRecallRequestTimeoutSeconds = hindsightRecallTunables.requestTimeoutSeconds;
|
|
29397
29546
|
const hindsightRecallQueryMaxTokens = agentConfig.memory?.recall?.query_max_tokens ?? HINDSIGHT_RECALL_QUERY_MAX_TOKENS_DEFAULT;
|
|
29398
29547
|
const rawRecallQueryStopTerms = agentConfig.memory?.recall?.query_stop_terms;
|
|
29399
29548
|
const hindsightRecallQueryStopTermsJson = JSON.stringify(rawRecallQueryStopTerms ?? []);
|
|
29400
|
-
const hindsightRecallRequestTimeoutSeconds = agentConfig.memory?.recall?.request_timeout_seconds ?? HINDSIGHT_RECALL_REQUEST_TIMEOUT_SECONDS_DEFAULT;
|
|
29401
29549
|
const hindsightRecallOwnBankMinSlots = agentConfig.memory?.recall?.own_bank_min_slots ?? HINDSIGHT_OWN_BANK_MIN_SLOTS_DEFAULT;
|
|
29402
29550
|
const hindsightRecallAdditionalBankMinSlots = agentConfig.memory?.recall?.additional_bank_min_slots ?? HINDSIGHT_ADDITIONAL_BANK_MIN_SLOTS_DEFAULT;
|
|
29403
29551
|
const hindsightRecallTypes = agentConfig.memory?.recall?.types?.length ? agentConfig.memory.recall.types.join(",") : undefined;
|
|
@@ -29439,6 +29587,7 @@ function reconcileAgentInner(name, agentConfigRaw, agentsDir, telegramConfig, sw
|
|
|
29439
29587
|
hindsightApiBaseUrlQ: shellSingleQuote(hindsightApiBaseUrl),
|
|
29440
29588
|
hindsightRecallMaxMemories,
|
|
29441
29589
|
hindsightRecallCacheTtlSecs,
|
|
29590
|
+
hindsightRecallParallelDeadlineSeconds,
|
|
29442
29591
|
hindsightRecallQueryMaxTokens,
|
|
29443
29592
|
hindsightRecallQueryStopTermsJson,
|
|
29444
29593
|
hindsightRecallRequestTimeoutSeconds,
|
|
@@ -29633,7 +29782,7 @@ ${baseAppend}` : TELEGRAM_FORMATTING_FLOOR_CARD;
|
|
|
29633
29782
|
}
|
|
29634
29783
|
settings.mcpServers = mcpServers;
|
|
29635
29784
|
if (!options.skipProfileTemplates) {
|
|
29636
|
-
installHindsightPlugin(name, agentDir, switchroomConfig);
|
|
29785
|
+
installHindsightPlugin(name, agentDir, switchroomConfig, agentConfig);
|
|
29637
29786
|
}
|
|
29638
29787
|
if (hindsightEnabled) {
|
|
29639
29788
|
settings.autoMemoryEnabled = false;
|
|
@@ -29857,6 +30006,7 @@ ${body}
|
|
|
29857
30006
|
hindsightApiBaseUrl,
|
|
29858
30007
|
hindsightRecallMaxMemories,
|
|
29859
30008
|
hindsightRecallCacheTtlSecs,
|
|
30009
|
+
hindsightRecallParallelDeadlineSeconds,
|
|
29860
30010
|
hindsightRecallQueryMaxTokens,
|
|
29861
30011
|
hindsightRecallQueryStopTermsJson,
|
|
29862
30012
|
hindsightRecallRequestTimeoutSeconds,
|
|
@@ -30191,7 +30341,7 @@ function buildAccessJson2(agentConfig, telegramConfig, resolvedTopicId, userId)
|
|
|
30191
30341
|
return JSON.stringify(access, null, 2) + `
|
|
30192
30342
|
`;
|
|
30193
30343
|
}
|
|
30194
|
-
var REPO_ROOT, HINDSIGHT_RECALL_QUERY_MAX_TOKENS_DEFAULT = 24,
|
|
30344
|
+
var REPO_ROOT, HINDSIGHT_RECALL_QUERY_MAX_TOKENS_DEFAULT = 24, SANDBOX_GUIDANCE = `## Sandbox: you're running in a switchroom container
|
|
30195
30345
|
|
|
30196
30346
|
Your container has \`read_only: true\` rootfs. Most paths are read-only.
|
|
30197
30347
|
|
|
@@ -30556,6 +30706,7 @@ var init_scaffold = __esm(() => {
|
|
|
30556
30706
|
init_atomic();
|
|
30557
30707
|
init_agent_owned_tree();
|
|
30558
30708
|
init_generation_stamp();
|
|
30709
|
+
init_hindsight_recall_tunables();
|
|
30559
30710
|
init_schema();
|
|
30560
30711
|
init_cron_routing();
|
|
30561
30712
|
init_tier_selector();
|
|
@@ -30575,6 +30726,7 @@ var init_scaffold = __esm(() => {
|
|
|
30575
30726
|
init_vault();
|
|
30576
30727
|
init_onboarding();
|
|
30577
30728
|
init_hindsight();
|
|
30729
|
+
init_hindsight_recall_tunables();
|
|
30578
30730
|
init_bare_clone();
|
|
30579
30731
|
init_agent_worktree();
|
|
30580
30732
|
REPO_ROOT = resolve11(import.meta.dirname, "../..");
|
|
@@ -43778,25 +43930,34 @@ var init_doctor_status = __esm(() => {
|
|
|
43778
43930
|
});
|
|
43779
43931
|
|
|
43780
43932
|
// src/config/thinking-effort-risk.ts
|
|
43933
|
+
function isRiskyThinkingEffort(effort) {
|
|
43934
|
+
if (!effort)
|
|
43935
|
+
return false;
|
|
43936
|
+
return RISKY_EFFORTS.has(effort.trim().toLowerCase());
|
|
43937
|
+
}
|
|
43938
|
+
function emitsAdaptiveThinking(model) {
|
|
43939
|
+
if (!model)
|
|
43940
|
+
return false;
|
|
43941
|
+
const m = model.trim().toLowerCase();
|
|
43942
|
+
return m === "opus" || m.startsWith("claude-opus-");
|
|
43943
|
+
}
|
|
43781
43944
|
function isAdaptiveThinkingOpus(model) {
|
|
43782
43945
|
if (!model)
|
|
43783
43946
|
return false;
|
|
43784
43947
|
const m = model.trim().toLowerCase();
|
|
43785
|
-
return m
|
|
43948
|
+
return m.startsWith("claude-opus-4");
|
|
43786
43949
|
}
|
|
43787
43950
|
function assessThinkingEffortRisk(model, effort) {
|
|
43788
|
-
if (!effort)
|
|
43789
|
-
return { risky: false };
|
|
43790
|
-
if (!RISKY_EFFORTS.has(effort.trim().toLowerCase()))
|
|
43951
|
+
if (!isRiskyThinkingEffort(effort))
|
|
43791
43952
|
return { risky: false };
|
|
43792
43953
|
if (!isAdaptiveThinkingOpus(model))
|
|
43793
43954
|
return { risky: false };
|
|
43794
43955
|
return {
|
|
43795
43956
|
risky: true,
|
|
43796
|
-
reason: `thinking_effort '${effort}' on
|
|
43957
|
+
reason: `thinking_effort '${effort}' on pinned Opus 4.x model '${model}' could trigger ` + `'400 thinking/redacted_thinking blocks cannot be modified' errors when work runs ` + `through concurrent sub-agents (issue #1978). The upstream claude-CLI fix shipped in ` + `${CLAUDE_CLI_THINKING_MERGE_FIX_VERSION}, but the Opus 4.x reproduction has not been ` + `re-tested since, so pin 'thinking_effort: low' or move the agent to a current Opus model.`
|
|
43797
43958
|
};
|
|
43798
43959
|
}
|
|
43799
|
-
var RISKY_EFFORTS;
|
|
43960
|
+
var CLAUDE_CLI_THINKING_MERGE_FIX_VERSION = "2.1.156", RISKY_EFFORTS;
|
|
43800
43961
|
var init_thinking_effort_risk = __esm(() => {
|
|
43801
43962
|
RISKY_EFFORTS = new Set(["medium", "high", "xhigh", "max"]);
|
|
43802
43963
|
});
|
|
@@ -49401,6 +49562,83 @@ function detectHookScriptDrift(name, opts = {}) {
|
|
|
49401
49562
|
}
|
|
49402
49563
|
];
|
|
49403
49564
|
}
|
|
49565
|
+
function detectHindsightRecallTunableDrift(name, agentConfig, agentDir, config) {
|
|
49566
|
+
if (!isHindsightEnabled(config))
|
|
49567
|
+
return [];
|
|
49568
|
+
const resolved = resolveAgentConfig(config.defaults, config.profiles, agentConfig);
|
|
49569
|
+
if (resolved.memory?.auto_recall === false)
|
|
49570
|
+
return [];
|
|
49571
|
+
const pluginDir = join67(agentDir, ".claude", "plugins", "hindsight-memory");
|
|
49572
|
+
if (!existsSync72(pluginDir))
|
|
49573
|
+
return [];
|
|
49574
|
+
const expected = resolveHindsightRecallTunables(resolved.memory?.recall);
|
|
49575
|
+
const findings = [];
|
|
49576
|
+
const mismatches = [];
|
|
49577
|
+
const hooksPath = join67(pluginDir, "hooks", "hooks.json");
|
|
49578
|
+
if (existsSync72(hooksPath)) {
|
|
49579
|
+
let installedHookTimeout = null;
|
|
49580
|
+
try {
|
|
49581
|
+
installedHookTimeout = readHooksRecallTimeout(readFileSync66(hooksPath, "utf-8"));
|
|
49582
|
+
} catch {
|
|
49583
|
+
installedHookTimeout = null;
|
|
49584
|
+
}
|
|
49585
|
+
if (installedHookTimeout === null) {
|
|
49586
|
+
mismatches.push("hooks/hooks.json has no readable UserPromptSubmit recall-hook timeout");
|
|
49587
|
+
} else if (installedHookTimeout !== expected.hookTimeoutSeconds) {
|
|
49588
|
+
mismatches.push(`hook ceiling is ${installedHookTimeout}s, expected ` + `${expected.hookTimeoutSeconds}s (memory.recall.hook_timeout_seconds)`);
|
|
49589
|
+
}
|
|
49590
|
+
}
|
|
49591
|
+
const settingsPath = join67(pluginDir, "settings.json");
|
|
49592
|
+
if (existsSync72(settingsPath)) {
|
|
49593
|
+
let settings = null;
|
|
49594
|
+
try {
|
|
49595
|
+
settings = JSON.parse(readFileSync66(settingsPath, "utf-8"));
|
|
49596
|
+
} catch {
|
|
49597
|
+
settings = null;
|
|
49598
|
+
}
|
|
49599
|
+
if (settings === null) {
|
|
49600
|
+
mismatches.push("settings.json is unreadable or malformed");
|
|
49601
|
+
} else {
|
|
49602
|
+
const checks = [
|
|
49603
|
+
[
|
|
49604
|
+
"recallParallelDeadlineSeconds",
|
|
49605
|
+
"memory.recall.parallel_deadline_seconds",
|
|
49606
|
+
expected.parallelDeadlineSeconds
|
|
49607
|
+
],
|
|
49608
|
+
[
|
|
49609
|
+
"recallRequestTimeoutSeconds",
|
|
49610
|
+
"memory.recall.request_timeout_seconds",
|
|
49611
|
+
expected.requestTimeoutSeconds
|
|
49612
|
+
]
|
|
49613
|
+
];
|
|
49614
|
+
for (const [key, yamlKey, want] of checks) {
|
|
49615
|
+
const got = settings[key];
|
|
49616
|
+
if (got === undefined) {
|
|
49617
|
+
mismatches.push(`settings.json is missing \`${key}\` (expected ${want})`);
|
|
49618
|
+
} else if (got !== want) {
|
|
49619
|
+
mismatches.push(`\`${key}\` is ${JSON.stringify(got)}, expected ${want} (${yamlKey})`);
|
|
49620
|
+
}
|
|
49621
|
+
}
|
|
49622
|
+
}
|
|
49623
|
+
}
|
|
49624
|
+
if (mismatches.length > 0) {
|
|
49625
|
+
findings.push({
|
|
49626
|
+
surface: "memory-tunables",
|
|
49627
|
+
agent: name,
|
|
49628
|
+
detail: `installed hindsight plugin disagrees with switchroom.yaml: ` + mismatches.join("; "),
|
|
49629
|
+
fix: "Run `switchroom apply` to re-stamp the plugin, then restart the agent " + "(`switchroom agent restart <name>`). If it recurs immediately after an " + "apply, the stamping in installHindsightPlugin has regressed \u2014 these " + "values have silently reverted three times before, which is why this " + "check exists."
|
|
49630
|
+
});
|
|
49631
|
+
}
|
|
49632
|
+
if (expected.clamps.length > 0) {
|
|
49633
|
+
findings.push({
|
|
49634
|
+
surface: "memory-tunables",
|
|
49635
|
+
agent: name,
|
|
49636
|
+
detail: `recall tunable clamped: ${expected.clamps.join("; ")}`,
|
|
49637
|
+
fix: "Adjust the offending `memory.recall.*` value in switchroom.yaml so the " + "nested deadlines hold (per-bank timeout <= parallel deadline <= hook " + "ceiling), or raise the outer bound."
|
|
49638
|
+
});
|
|
49639
|
+
}
|
|
49640
|
+
return findings;
|
|
49641
|
+
}
|
|
49404
49642
|
function writeDriftReport(agentDir, findings) {
|
|
49405
49643
|
try {
|
|
49406
49644
|
const report = {
|
|
@@ -49423,6 +49661,7 @@ function detectAgentDrift(name, agentConfigRaw, agentsDir, config, configPath, o
|
|
|
49423
49661
|
currentVersion: opts.currentVersion
|
|
49424
49662
|
}));
|
|
49425
49663
|
findings.push(...detectSkillsDrift(name, agentDir));
|
|
49664
|
+
findings.push(...detectHindsightRecallTunableDrift(name, agentConfig, agentDir, config));
|
|
49426
49665
|
if (!opts.skipContainerProbes) {
|
|
49427
49666
|
findings.push(...detectHookScriptDrift(name, {
|
|
49428
49667
|
binDir: opts.binDir,
|
|
@@ -49439,6 +49678,7 @@ var BUNDLED_POOL_SEGMENT = "/.switchroom/skills/_bundled/", defaultExec = (cmd,
|
|
|
49439
49678
|
var init_drift = __esm(() => {
|
|
49440
49679
|
init_merge();
|
|
49441
49680
|
init_hindsight2();
|
|
49681
|
+
init_hindsight_recall_tunables();
|
|
49442
49682
|
init_scaffold();
|
|
49443
49683
|
init_lifecycle();
|
|
49444
49684
|
init_generation_stamp();
|
|
@@ -51279,6 +51519,142 @@ var init_doctor_fix_session_model = __esm(() => {
|
|
|
51279
51519
|
init_loader();
|
|
51280
51520
|
});
|
|
51281
51521
|
|
|
51522
|
+
// src/cli/doctor-claude-cli.ts
|
|
51523
|
+
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
51524
|
+
import { existsSync as existsSync77, readFileSync as readFileSync68 } from "node:fs";
|
|
51525
|
+
import { dirname as dirname29, join as join78 } from "node:path";
|
|
51526
|
+
function parseClaudeCliVersion(raw) {
|
|
51527
|
+
const m = raw.trim().match(/^v?(\d+)\.(\d+)\.(\d+)/);
|
|
51528
|
+
if (!m)
|
|
51529
|
+
return null;
|
|
51530
|
+
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
51531
|
+
}
|
|
51532
|
+
function claudeCliMeetsFloor(raw, floor) {
|
|
51533
|
+
const got = parseClaudeCliVersion(raw);
|
|
51534
|
+
const want = parseClaudeCliVersion(floor);
|
|
51535
|
+
if (!got || !want)
|
|
51536
|
+
return null;
|
|
51537
|
+
for (let i = 0;i < 3; i++) {
|
|
51538
|
+
if (got[i] > want[i])
|
|
51539
|
+
return true;
|
|
51540
|
+
if (got[i] < want[i])
|
|
51541
|
+
return false;
|
|
51542
|
+
}
|
|
51543
|
+
return true;
|
|
51544
|
+
}
|
|
51545
|
+
function locateRepoFile(relative4) {
|
|
51546
|
+
let dir = import.meta.dirname;
|
|
51547
|
+
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
51548
|
+
const candidate = join78(dir, relative4);
|
|
51549
|
+
if (existsSync77(candidate))
|
|
51550
|
+
return candidate;
|
|
51551
|
+
dir = dirname29(dir);
|
|
51552
|
+
}
|
|
51553
|
+
return null;
|
|
51554
|
+
}
|
|
51555
|
+
function readPinnedClaudeCliVersion(dockerfilePath) {
|
|
51556
|
+
const path7 = dockerfilePath === undefined ? locateRepoFile(join78("docker", "Dockerfile.base")) : dockerfilePath;
|
|
51557
|
+
if (!path7)
|
|
51558
|
+
return null;
|
|
51559
|
+
let text;
|
|
51560
|
+
try {
|
|
51561
|
+
text = readFileSync68(path7, "utf-8");
|
|
51562
|
+
} catch {
|
|
51563
|
+
return null;
|
|
51564
|
+
}
|
|
51565
|
+
const m = text.match(/^\s*ARG\s+CLAUDE_CODE_VERSION=([0-9][0-9.]*)\s*$/m);
|
|
51566
|
+
return m ? m[1] : null;
|
|
51567
|
+
}
|
|
51568
|
+
function buildClaudeCliProbeScript() {
|
|
51569
|
+
return 'P="$HOME/.local/bin:$HOME/bin:$HOME/.npm-global/bin:$PATH"; ' + 'B=$(PATH="$P" command -v claude 2>/dev/null); ' + 'if [ -z "$B" ]; then echo "BIN= VER="; exit 0; fi; ' + 'V=$("$B" --version 2>/dev/null | head -n1 | cut -d" " -f1); ' + 'echo "BIN=$B VER=$V"';
|
|
51570
|
+
}
|
|
51571
|
+
function parseClaudeCliProbeOutput(r) {
|
|
51572
|
+
if (r.error || r.status !== 0) {
|
|
51573
|
+
const stderr = (r.stderr?.toString() ?? "").trim();
|
|
51574
|
+
const msg = r.error?.message ?? (stderr !== "" ? stderr : `docker exec exited ${r.status ?? "on timeout"}`);
|
|
51575
|
+
return { state: "unreachable", msg };
|
|
51576
|
+
}
|
|
51577
|
+
const out = (r.stdout?.toString() ?? "").trim();
|
|
51578
|
+
const bin = out.match(/BIN=(\S*)/)?.[1] ?? "";
|
|
51579
|
+
const raw = out.match(/VER=(\S*)/)?.[1] ?? "";
|
|
51580
|
+
if (bin === "")
|
|
51581
|
+
return { state: "missing" };
|
|
51582
|
+
if (raw === "") {
|
|
51583
|
+
return { state: "unreachable", msg: `\`${bin} --version\` produced no output` };
|
|
51584
|
+
}
|
|
51585
|
+
return { state: "read", bin, raw };
|
|
51586
|
+
}
|
|
51587
|
+
function probeAgentClaudeCli(agentName) {
|
|
51588
|
+
const r = spawnSync12("docker", ["exec", `switchroom-${agentName}`, "sh", "-c", buildClaudeCliProbeScript()], { stdio: "pipe", timeout: 3000 });
|
|
51589
|
+
return parseClaudeCliProbeOutput(r);
|
|
51590
|
+
}
|
|
51591
|
+
function assessClaudeCliFloor(input) {
|
|
51592
|
+
const { agentName, probe: probe2, exposed, pinned } = input;
|
|
51593
|
+
const floor = input.floor ?? CLAUDE_CLI_THINKING_MERGE_FIX_VERSION;
|
|
51594
|
+
const name = `${agentName}: claude CLI floor`;
|
|
51595
|
+
const restart = `Restart the agent onto the current image: \`switchroom update\` then ` + `\`switchroom agent restart ${agentName} --force\`` + (pinned ? ` (docker/Dockerfile.base pins ${pinned})` : "") + `.`;
|
|
51596
|
+
if (probe2.state === "unreachable") {
|
|
51597
|
+
return {
|
|
51598
|
+
name,
|
|
51599
|
+
status: "skip",
|
|
51600
|
+
detail: `could not read the running claude CLI in switchroom-${agentName}: ${probe2.msg}`
|
|
51601
|
+
};
|
|
51602
|
+
}
|
|
51603
|
+
if (probe2.state === "missing") {
|
|
51604
|
+
return {
|
|
51605
|
+
name,
|
|
51606
|
+
status: "warn",
|
|
51607
|
+
detail: `no \`claude\` on the session PATH inside switchroom-${agentName}`,
|
|
51608
|
+
fix: restart
|
|
51609
|
+
};
|
|
51610
|
+
}
|
|
51611
|
+
const meets = claudeCliMeetsFloor(probe2.raw, floor);
|
|
51612
|
+
if (meets === null) {
|
|
51613
|
+
return {
|
|
51614
|
+
name,
|
|
51615
|
+
status: "skip",
|
|
51616
|
+
detail: `unparseable version from ${probe2.bin}: ${probe2.raw}`
|
|
51617
|
+
};
|
|
51618
|
+
}
|
|
51619
|
+
if (meets) {
|
|
51620
|
+
return {
|
|
51621
|
+
name,
|
|
51622
|
+
status: "ok",
|
|
51623
|
+
detail: `${probe2.raw} at ${probe2.bin} (>= ${floor} floor)`
|
|
51624
|
+
};
|
|
51625
|
+
}
|
|
51626
|
+
const exposure = exposed ? ` This agent's model and thinking_effort are in the #1978 exposure shape, ` + `which the config-time guard no longer covers on the assumption of a ` + `>= ${floor} CLI.` : ` This agent's model and thinking_effort are not in the #1978 exposure shape.`;
|
|
51627
|
+
return {
|
|
51628
|
+
name,
|
|
51629
|
+
status: "warn",
|
|
51630
|
+
detail: `${probe2.raw} at ${probe2.bin} is BELOW the ${floor} floor \u2014 ` + `the claude-code build that fixed the #1978 thinking-block merge 400.` + exposure,
|
|
51631
|
+
fix: exposed ? `${restart} Until then set \`thinking_effort: low\` for ${agentName}; ` + `on a pre-${floor} CLI, medium+ effort on an Opus model can 400 with ` + `'thinking blocks cannot be modified' when work runs through concurrent sub-agents.` : restart
|
|
51632
|
+
};
|
|
51633
|
+
}
|
|
51634
|
+
function runClaudeCliVersionChecks(config, deps = {}) {
|
|
51635
|
+
if (deps.fast)
|
|
51636
|
+
return [];
|
|
51637
|
+
const agents = Object.entries(config.agents ?? {});
|
|
51638
|
+
if (agents.length === 0)
|
|
51639
|
+
return [];
|
|
51640
|
+
const pinned = readPinnedClaudeCliVersion(deps.dockerfilePath);
|
|
51641
|
+
const probe2 = deps.probe ?? probeAgentClaudeCli;
|
|
51642
|
+
return agents.map(([agentName, agentConfig]) => {
|
|
51643
|
+
const resolved = resolveAgentConfig(config.defaults, config.profiles, agentConfig);
|
|
51644
|
+
const exposed = emitsAdaptiveThinking(resolved.model) && isRiskyThinkingEffort(resolved.thinking_effort);
|
|
51645
|
+
return assessClaudeCliFloor({
|
|
51646
|
+
agentName,
|
|
51647
|
+
probe: probe2(agentName),
|
|
51648
|
+
exposed,
|
|
51649
|
+
pinned
|
|
51650
|
+
});
|
|
51651
|
+
});
|
|
51652
|
+
}
|
|
51653
|
+
var init_doctor_claude_cli = __esm(() => {
|
|
51654
|
+
init_merge();
|
|
51655
|
+
init_thinking_effort_risk();
|
|
51656
|
+
});
|
|
51657
|
+
|
|
51282
51658
|
// src/cli/doctor.ts
|
|
51283
51659
|
var exports_doctor = {};
|
|
51284
51660
|
__export(exports_doctor, {
|
|
@@ -51330,28 +51706,28 @@ __export(exports_doctor, {
|
|
|
51330
51706
|
PENDING_RETAINS_EVICTION_WINDOW_DAYS: () => PENDING_RETAINS_EVICTION_WINDOW_DAYS,
|
|
51331
51707
|
MFF_VAULT_KEY: () => MFF_VAULT_KEY
|
|
51332
51708
|
});
|
|
51333
|
-
import { spawnSync as
|
|
51709
|
+
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
51334
51710
|
import { Socket as Socket3 } from "node:net";
|
|
51335
51711
|
import {
|
|
51336
51712
|
accessSync as accessSync3,
|
|
51337
51713
|
constants as fsConstants6,
|
|
51338
|
-
existsSync as
|
|
51714
|
+
existsSync as existsSync78,
|
|
51339
51715
|
lstatSync as lstatSync11,
|
|
51340
51716
|
mkdirSync as mkdirSync41,
|
|
51341
|
-
readFileSync as
|
|
51717
|
+
readFileSync as readFileSync69,
|
|
51342
51718
|
readdirSync as readdirSync26,
|
|
51343
51719
|
statSync as statSync43
|
|
51344
51720
|
} from "node:fs";
|
|
51345
|
-
import { dirname as
|
|
51721
|
+
import { dirname as dirname30, join as join79, resolve as resolve44 } from "node:path";
|
|
51346
51722
|
import { createPublicKey, createPrivateKey } from "node:crypto";
|
|
51347
51723
|
function findInNvm(bin) {
|
|
51348
|
-
const nvmRoot =
|
|
51349
|
-
if (!
|
|
51724
|
+
const nvmRoot = join79(process.env.HOME ?? "", ".nvm", "versions", "node");
|
|
51725
|
+
if (!existsSync78(nvmRoot))
|
|
51350
51726
|
return null;
|
|
51351
51727
|
try {
|
|
51352
51728
|
const versions = readdirSync26(nvmRoot).sort().reverse();
|
|
51353
51729
|
for (const v of versions) {
|
|
51354
|
-
const candidate =
|
|
51730
|
+
const candidate = join79(nvmRoot, v, "bin", bin);
|
|
51355
51731
|
try {
|
|
51356
51732
|
const s = statSync43(candidate);
|
|
51357
51733
|
if (s.isFile() || s.isSymbolicLink()) {
|
|
@@ -51378,7 +51754,7 @@ function whichOnPath(bin) {
|
|
|
51378
51754
|
for (const dir of pathEnv.split(":")) {
|
|
51379
51755
|
if (!dir)
|
|
51380
51756
|
continue;
|
|
51381
|
-
const candidate =
|
|
51757
|
+
const candidate = join79(dir, bin);
|
|
51382
51758
|
if (isX(candidate))
|
|
51383
51759
|
return candidate;
|
|
51384
51760
|
}
|
|
@@ -51462,7 +51838,7 @@ function readVersion(bin, parser) {
|
|
|
51462
51838
|
const path7 = which(bin);
|
|
51463
51839
|
if (!path7)
|
|
51464
51840
|
return null;
|
|
51465
|
-
const proc =
|
|
51841
|
+
const proc = spawnSync13(path7, ["--version"], {
|
|
51466
51842
|
stdio: ["ignore", "pipe", "pipe"]
|
|
51467
51843
|
});
|
|
51468
51844
|
if (proc.error || proc.status !== 0)
|
|
@@ -51544,21 +51920,21 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
|
|
|
51544
51920
|
if (envBrowsersPath && envBrowsersPath.length > 0) {
|
|
51545
51921
|
cacheLocations.push(envBrowsersPath);
|
|
51546
51922
|
}
|
|
51547
|
-
cacheLocations.push(
|
|
51923
|
+
cacheLocations.push(join79(homeDir, ".cache", "ms-playwright"));
|
|
51548
51924
|
for (const cacheDir of cacheLocations) {
|
|
51549
|
-
if (!
|
|
51925
|
+
if (!existsSync78(cacheDir))
|
|
51550
51926
|
continue;
|
|
51551
51927
|
try {
|
|
51552
51928
|
const entries = readdirSync26(cacheDir).filter((e) => e.startsWith("chromium"));
|
|
51553
51929
|
for (const entry of entries) {
|
|
51554
51930
|
const candidates2 = [
|
|
51555
|
-
|
|
51556
|
-
|
|
51557
|
-
|
|
51558
|
-
|
|
51931
|
+
join79(cacheDir, entry, "chrome-linux64", "chrome"),
|
|
51932
|
+
join79(cacheDir, entry, "chrome-linux", "chrome"),
|
|
51933
|
+
join79(cacheDir, entry, "chrome-linux64", "headless_shell"),
|
|
51934
|
+
join79(cacheDir, entry, "chrome-linux", "headless_shell")
|
|
51559
51935
|
];
|
|
51560
51936
|
for (const path7 of candidates2) {
|
|
51561
|
-
if (
|
|
51937
|
+
if (existsSync78(path7))
|
|
51562
51938
|
return path7;
|
|
51563
51939
|
}
|
|
51564
51940
|
}
|
|
@@ -51643,8 +52019,8 @@ function checkConfig(config, configPath) {
|
|
|
51643
52019
|
results.push({
|
|
51644
52020
|
name: "thinking_effort \u00d7 adaptive model",
|
|
51645
52021
|
status: effortRisks.length > 0 ? "warn" : "ok",
|
|
51646
|
-
detail: effortRisks.length > 0 ? `${effortRisks.length} agent(s) on Opus
|
|
51647
|
-
fix: effortRisks.length > 0 ? "Pin `thinking_effort: low` for Opus 4.x
|
|
52022
|
+
detail: effortRisks.length > 0 ? `${effortRisks.length} agent(s) on pinned Opus 4.x with thinking_effort > low: ${effortRisks.join(", ")}` : "no risky model/effort combos",
|
|
52023
|
+
fix: effortRisks.length > 0 ? "Pin `thinking_effort: low` for pinned Opus 4.x agents, or move them to a current Opus model. medium+ could 400 on 'thinking blocks cannot be modified' with concurrent sub-agents (issue #1978); the upstream fix shipped in claude-code " + `${CLAUDE_CLI_THINKING_MERGE_FIX_VERSION} but the Opus 4.x reproduction was never re-tested. Removing the field is NOT a fix (Opus defaults effort=high when unset).` : undefined
|
|
51648
52024
|
});
|
|
51649
52025
|
return results;
|
|
51650
52026
|
}
|
|
@@ -51687,7 +52063,7 @@ function checkDeployMounts(opts) {
|
|
|
51687
52063
|
const home2 = opts?.home ?? process.env.HOME ?? "/root";
|
|
51688
52064
|
const { pathKind } = opts?.deps ?? DEFAULT_DEPLOY_MOUNTS_DEPS;
|
|
51689
52065
|
const results = [];
|
|
51690
|
-
const dockerComposePlugin =
|
|
52066
|
+
const dockerComposePlugin = join79(home2, ".docker", "cli-plugins", "docker-compose");
|
|
51691
52067
|
const pluginKind = pathKind(dockerComposePlugin);
|
|
51692
52068
|
if (pluginKind === "dir") {
|
|
51693
52069
|
results.push({
|
|
@@ -51725,8 +52101,8 @@ function checkDeployMounts(opts) {
|
|
|
51725
52101
|
function checkLegacyState() {
|
|
51726
52102
|
const results = [];
|
|
51727
52103
|
const h = process.env.HOME ?? "/root";
|
|
51728
|
-
const clerkDir =
|
|
51729
|
-
const clerkPresent =
|
|
52104
|
+
const clerkDir = join79(h, LEGACY_STATE_DIR);
|
|
52105
|
+
const clerkPresent = existsSync78(clerkDir);
|
|
51730
52106
|
results.push({
|
|
51731
52107
|
name: "legacy ~/.clerk state",
|
|
51732
52108
|
status: clerkPresent ? "warn" : "ok",
|
|
@@ -51735,7 +52111,7 @@ function checkLegacyState() {
|
|
|
51735
52111
|
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."
|
|
51736
52112
|
} : {}
|
|
51737
52113
|
});
|
|
51738
|
-
const legacySock =
|
|
52114
|
+
const legacySock = join79(h, ".switchroom", "vault-broker.sock");
|
|
51739
52115
|
let sockStat = null;
|
|
51740
52116
|
try {
|
|
51741
52117
|
sockStat = lstatSync11(legacySock);
|
|
@@ -51754,7 +52130,7 @@ function probeVaultBrokerSocketPair(agentName) {
|
|
|
51754
52130
|
const dataPath = `/run/switchroom/broker/${agentName}/sock`;
|
|
51755
52131
|
const unlockPath = `/run/switchroom/broker/${agentName}/unlock`;
|
|
51756
52132
|
const script = `D=0; U=0; ` + `test -S '${dataPath}' && D=1; ` + `test -S '${unlockPath}' && U=1; ` + `echo "D=$D U=$U"`;
|
|
51757
|
-
const r =
|
|
52133
|
+
const r = spawnSync13("docker", ["exec", "switchroom-vault-broker", "sh", "-c", script], { stdio: "pipe", timeout: 3000 });
|
|
51758
52134
|
if (r.error || r.status === null)
|
|
51759
52135
|
return "unreachable";
|
|
51760
52136
|
if (r.status !== 0) {
|
|
@@ -51856,7 +52232,7 @@ function checkVault(config) {
|
|
|
51856
52232
|
detail: "Approval auth: passphrase (two-factor)"
|
|
51857
52233
|
};
|
|
51858
52234
|
const pairsResult = checkVaultBrokerSocketPairs(config);
|
|
51859
|
-
if (!
|
|
52235
|
+
if (!existsSync78(vaultPath)) {
|
|
51860
52236
|
return [
|
|
51861
52237
|
postureResult,
|
|
51862
52238
|
{
|
|
@@ -51969,7 +52345,7 @@ function checkHindsightConsumer(config, opts) {
|
|
|
51969
52345
|
}
|
|
51970
52346
|
function probeAuthBrokerSocket(consumerName) {
|
|
51971
52347
|
const containerPath = `/run/switchroom/auth-broker/${consumerName}/sock`;
|
|
51972
|
-
const r =
|
|
52348
|
+
const r = spawnSync13("docker", ["exec", "switchroom-auth-broker", "test", "-S", containerPath], { stdio: "pipe", timeout: 3000 });
|
|
51973
52349
|
if (r.error || r.status === null)
|
|
51974
52350
|
return "unreachable";
|
|
51975
52351
|
if (r.status === 0)
|
|
@@ -52164,7 +52540,7 @@ async function checkHindsight(config) {
|
|
|
52164
52540
|
function probePendingRetainsQueue(agentName) {
|
|
52165
52541
|
const base = "/state/agent/home/.hindsight";
|
|
52166
52542
|
const script = buildPendingRetainsProbeScript(base);
|
|
52167
|
-
const r =
|
|
52543
|
+
const r = spawnSync13("docker", ["exec", `switchroom-${agentName}`, "sh", "-c", script], { stdio: "pipe", timeout: 3000 });
|
|
52168
52544
|
return parsePendingRetainsProbeOutput(r);
|
|
52169
52545
|
}
|
|
52170
52546
|
function buildPendingRetainsProbeScript(base, now = Date.now()) {
|
|
@@ -52312,7 +52688,7 @@ function classifyReadError(err) {
|
|
|
52312
52688
|
}
|
|
52313
52689
|
function tryReadHostFile(path7) {
|
|
52314
52690
|
try {
|
|
52315
|
-
return { kind: "ok", content:
|
|
52691
|
+
return { kind: "ok", content: readFileSync69(path7, "utf-8") };
|
|
52316
52692
|
} catch (err) {
|
|
52317
52693
|
const kind = classifyReadError(err);
|
|
52318
52694
|
const error = err?.message ?? String(err);
|
|
@@ -52324,11 +52700,11 @@ function tryReadHostFile(path7) {
|
|
|
52324
52700
|
}
|
|
52325
52701
|
}
|
|
52326
52702
|
function parseEnvFile(path7) {
|
|
52327
|
-
if (!
|
|
52703
|
+
if (!existsSync78(path7))
|
|
52328
52704
|
return {};
|
|
52329
52705
|
let content;
|
|
52330
52706
|
try {
|
|
52331
|
-
content =
|
|
52707
|
+
content = readFileSync69(path7, "utf-8");
|
|
52332
52708
|
} catch {
|
|
52333
52709
|
return {};
|
|
52334
52710
|
}
|
|
@@ -52383,7 +52759,7 @@ async function checkTelegram(config) {
|
|
|
52383
52759
|
const plugin = agentConfig.channels?.telegram?.plugin ?? "switchroom";
|
|
52384
52760
|
if (plugin !== "switchroom")
|
|
52385
52761
|
continue;
|
|
52386
|
-
const envPath =
|
|
52762
|
+
const envPath = join79(agentsDir, name, "telegram", ".env");
|
|
52387
52763
|
const read = tryReadHostFile(envPath);
|
|
52388
52764
|
if (read.kind === "eacces") {
|
|
52389
52765
|
results.push({
|
|
@@ -52435,7 +52811,7 @@ async function checkTelegram(config) {
|
|
|
52435
52811
|
}
|
|
52436
52812
|
function checkStartShStale(agentName, startShPath) {
|
|
52437
52813
|
const label = `${agentName}: start.sh scheduler block`;
|
|
52438
|
-
if (!
|
|
52814
|
+
if (!existsSync78(startShPath)) {
|
|
52439
52815
|
return {
|
|
52440
52816
|
name: label,
|
|
52441
52817
|
status: "warn",
|
|
@@ -52445,7 +52821,7 @@ function checkStartShStale(agentName, startShPath) {
|
|
|
52445
52821
|
}
|
|
52446
52822
|
let content;
|
|
52447
52823
|
try {
|
|
52448
|
-
content =
|
|
52824
|
+
content = readFileSync69(startShPath, "utf-8");
|
|
52449
52825
|
} catch (err) {
|
|
52450
52826
|
return {
|
|
52451
52827
|
name: label,
|
|
@@ -52466,7 +52842,7 @@ function checkStartShStale(agentName, startShPath) {
|
|
|
52466
52842
|
}
|
|
52467
52843
|
function checkStartShSessionModelCarrier(agentName, startShPath) {
|
|
52468
52844
|
const label = `${agentName}: start.sh /model session carrier`;
|
|
52469
|
-
if (!
|
|
52845
|
+
if (!existsSync78(startShPath)) {
|
|
52470
52846
|
return {
|
|
52471
52847
|
name: label,
|
|
52472
52848
|
status: "warn",
|
|
@@ -52476,7 +52852,7 @@ function checkStartShSessionModelCarrier(agentName, startShPath) {
|
|
|
52476
52852
|
}
|
|
52477
52853
|
let content;
|
|
52478
52854
|
try {
|
|
52479
|
-
content =
|
|
52855
|
+
content = readFileSync69(startShPath, "utf-8");
|
|
52480
52856
|
} catch (err) {
|
|
52481
52857
|
return {
|
|
52482
52858
|
name: label,
|
|
@@ -52513,7 +52889,7 @@ function checkStartShSessionModelCarrier(agentName, startShPath) {
|
|
|
52513
52889
|
}
|
|
52514
52890
|
function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
52515
52891
|
const label = `${agentName}: $HOME/.switchroom symlink (#910)`;
|
|
52516
|
-
const path7 =
|
|
52892
|
+
const path7 = join79(agentDir, "home", ".switchroom");
|
|
52517
52893
|
let stats;
|
|
52518
52894
|
try {
|
|
52519
52895
|
stats = lstatSync11(path7);
|
|
@@ -52550,8 +52926,8 @@ function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
|
52550
52926
|
}
|
|
52551
52927
|
function checkRepoHygiene(repoRoot) {
|
|
52552
52928
|
const results = [];
|
|
52553
|
-
const exportDir =
|
|
52554
|
-
if (
|
|
52929
|
+
const exportDir = join79(repoRoot, "clerk-export");
|
|
52930
|
+
if (existsSync78(exportDir)) {
|
|
52555
52931
|
results.push({
|
|
52556
52932
|
name: "repo hygiene: clerk-export/ on disk (#1072)",
|
|
52557
52933
|
status: "warn",
|
|
@@ -52559,8 +52935,8 @@ function checkRepoHygiene(repoRoot) {
|
|
|
52559
52935
|
fix: `Run scripts/migrate-clerk-export-to-vault.sh to move the bundle ` + `into the vault, then delete the on-disk copy.`
|
|
52560
52936
|
});
|
|
52561
52937
|
}
|
|
52562
|
-
const knownTarball =
|
|
52563
|
-
if (
|
|
52938
|
+
const knownTarball = join79(repoRoot, "clerk-export-with-secrets.tar.gz");
|
|
52939
|
+
if (existsSync78(knownTarball)) {
|
|
52564
52940
|
results.push({
|
|
52565
52941
|
name: "repo hygiene: clerk-export-with-secrets.tar.gz on disk (#1072)",
|
|
52566
52942
|
status: "warn",
|
|
@@ -52577,7 +52953,7 @@ function checkRepoHygiene(repoRoot) {
|
|
|
52577
52953
|
results.push({
|
|
52578
52954
|
name: `repo hygiene: ${name} on disk (#1072)`,
|
|
52579
52955
|
status: "warn",
|
|
52580
|
-
detail: `${
|
|
52956
|
+
detail: `${join79(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
|
|
52581
52957
|
fix: `Inspect, migrate any secrets into the vault, then delete the ` + `archive.`
|
|
52582
52958
|
});
|
|
52583
52959
|
}
|
|
@@ -52600,12 +52976,12 @@ function checkRepoHygiene(repoRoot) {
|
|
|
52600
52976
|
}
|
|
52601
52977
|
function isSwitchroomCheckout(dir) {
|
|
52602
52978
|
try {
|
|
52603
|
-
if (!
|
|
52979
|
+
if (!existsSync78(join79(dir, ".git")))
|
|
52604
52980
|
return false;
|
|
52605
|
-
const pkgPath =
|
|
52606
|
-
if (!
|
|
52981
|
+
const pkgPath = join79(dir, "package.json");
|
|
52982
|
+
if (!existsSync78(pkgPath))
|
|
52607
52983
|
return false;
|
|
52608
|
-
const pkg = JSON.parse(
|
|
52984
|
+
const pkg = JSON.parse(readFileSync69(pkgPath, "utf-8"));
|
|
52609
52985
|
return pkg.name === "switchroom";
|
|
52610
52986
|
} catch {
|
|
52611
52987
|
return false;
|
|
@@ -52618,7 +52994,7 @@ function checkAgents(config, configPath) {
|
|
|
52618
52994
|
const authStatuses = getAllAuthStatuses(config);
|
|
52619
52995
|
for (const [name, agentConfig] of Object.entries(config.agents)) {
|
|
52620
52996
|
const agentDir = resolve44(agentsDir, name);
|
|
52621
|
-
if (!
|
|
52997
|
+
if (!existsSync78(agentDir)) {
|
|
52622
52998
|
results.push({
|
|
52623
52999
|
name: `${name}: scaffold`,
|
|
52624
53000
|
status: "fail",
|
|
@@ -52639,8 +53015,8 @@ function checkAgents(config, configPath) {
|
|
|
52639
53015
|
fix: `Rotate the bot token (e.g. via \`switchroom vault\`), then run ` + `\`switchroom agent unquarantine ${name}\` and \`switchroom agent restart ${name}\``
|
|
52640
53016
|
});
|
|
52641
53017
|
}
|
|
52642
|
-
results.push(checkStartShStale(name,
|
|
52643
|
-
results.push(checkStartShSessionModelCarrier(name,
|
|
53018
|
+
results.push(checkStartShStale(name, join79(agentDir, "start.sh")));
|
|
53019
|
+
results.push(checkStartShSessionModelCarrier(name, join79(agentDir, "start.sh")));
|
|
52644
53020
|
results.push(checkLeakedHomeSwitchroom(name, agentDir));
|
|
52645
53021
|
const status = statuses[name];
|
|
52646
53022
|
const active = status?.active ?? "unknown";
|
|
@@ -52717,8 +53093,8 @@ function checkAgents(config, configPath) {
|
|
|
52717
53093
|
}
|
|
52718
53094
|
}
|
|
52719
53095
|
if (agentConfig.channels?.telegram?.plugin === "switchroom") {
|
|
52720
|
-
const mcpJsonPath =
|
|
52721
|
-
if (!
|
|
53096
|
+
const mcpJsonPath = join79(agentDir, ".mcp.json");
|
|
53097
|
+
if (!existsSync78(mcpJsonPath)) {
|
|
52722
53098
|
results.push({
|
|
52723
53099
|
name: `${name}: .mcp.json`,
|
|
52724
53100
|
status: "fail",
|
|
@@ -52727,7 +53103,7 @@ function checkAgents(config, configPath) {
|
|
|
52727
53103
|
});
|
|
52728
53104
|
} else {
|
|
52729
53105
|
try {
|
|
52730
|
-
const mcp = JSON.parse(
|
|
53106
|
+
const mcp = JSON.parse(readFileSync69(mcpJsonPath, "utf-8"));
|
|
52731
53107
|
const hasSwitchroomTelegram = !!mcp.mcpServers?.["switchroom-telegram"];
|
|
52732
53108
|
const memoryEnabled = isHindsightEnabled(config);
|
|
52733
53109
|
const hasHindsight = !!mcp.mcpServers?.hindsight;
|
|
@@ -52804,7 +53180,7 @@ function mffEnvPath(config) {
|
|
|
52804
53180
|
return agent ? resolve44(home2, ".switchroom/credentials", agent, "my-family-finance/.env") : resolve44(home2, ".switchroom/credentials/my-family-finance/.env");
|
|
52805
53181
|
}
|
|
52806
53182
|
function mffEnvState(envPath) {
|
|
52807
|
-
if (!
|
|
53183
|
+
if (!existsSync78(envPath))
|
|
52808
53184
|
return "absent";
|
|
52809
53185
|
try {
|
|
52810
53186
|
accessSync3(envPath, fsConstants6.R_OK);
|
|
@@ -52822,7 +53198,7 @@ function checkMffVaultKeyPresent(passphrase, vaultPath) {
|
|
|
52822
53198
|
fix: "Export SWITCHROOM_VAULT_PASSPHRASE to enable MFF vault probes"
|
|
52823
53199
|
};
|
|
52824
53200
|
}
|
|
52825
|
-
if (!
|
|
53201
|
+
if (!existsSync78(vaultPath)) {
|
|
52826
53202
|
return {
|
|
52827
53203
|
name: "mff: vault key present",
|
|
52828
53204
|
status: "fail",
|
|
@@ -52875,7 +53251,7 @@ function deriveEd25519PublicKeyBytes(keyMaterial) {
|
|
|
52875
53251
|
}
|
|
52876
53252
|
}
|
|
52877
53253
|
function checkMffVaultKeyFormat(passphrase, vaultPath) {
|
|
52878
|
-
if (!passphrase || !
|
|
53254
|
+
if (!passphrase || !existsSync78(vaultPath)) {
|
|
52879
53255
|
return {
|
|
52880
53256
|
name: "mff: vault key format",
|
|
52881
53257
|
status: "warn",
|
|
@@ -53018,9 +53394,9 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
|
|
|
53018
53394
|
detail: "skipped (MFF_API_URL not set)"
|
|
53019
53395
|
};
|
|
53020
53396
|
}
|
|
53021
|
-
const credDir =
|
|
53022
|
-
const authScript =
|
|
53023
|
-
if (!
|
|
53397
|
+
const credDir = dirname30(envPath);
|
|
53398
|
+
const authScript = join79(credDir, "claude-auth.py");
|
|
53399
|
+
if (!existsSync78(authScript)) {
|
|
53024
53400
|
return {
|
|
53025
53401
|
name: "mff: auth flow",
|
|
53026
53402
|
status: "warn",
|
|
@@ -53031,7 +53407,7 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
|
|
|
53031
53407
|
const python3 = which("python3") ?? "python3";
|
|
53032
53408
|
let token;
|
|
53033
53409
|
try {
|
|
53034
|
-
const result =
|
|
53410
|
+
const result = spawnSync13(python3, [authScript, "--quiet"], {
|
|
53035
53411
|
timeout: timeoutMs,
|
|
53036
53412
|
encoding: "utf-8",
|
|
53037
53413
|
env: { ...process.env, ...env2 }
|
|
@@ -53222,11 +53598,11 @@ function runDockerSection(config) {
|
|
|
53222
53598
|
let composeYaml;
|
|
53223
53599
|
let dockerfileAgent;
|
|
53224
53600
|
try {
|
|
53225
|
-
composeYaml =
|
|
53601
|
+
composeYaml = readFileSync69(composePath, "utf8");
|
|
53226
53602
|
} catch {}
|
|
53227
53603
|
const dockerfilePath = resolve44(process.env.HOME ?? "", ".switchroom", "docker", "Dockerfile.agent");
|
|
53228
53604
|
try {
|
|
53229
|
-
dockerfileAgent =
|
|
53605
|
+
dockerfileAgent = readFileSync69(dockerfilePath, "utf8");
|
|
53230
53606
|
} catch {}
|
|
53231
53607
|
return runDockerChecks({
|
|
53232
53608
|
config,
|
|
@@ -53346,7 +53722,7 @@ function registerDoctorCommand(program3) {
|
|
|
53346
53722
|
resolveSecret: (ref) => {
|
|
53347
53723
|
if (!isVaultReference(ref))
|
|
53348
53724
|
return ref;
|
|
53349
|
-
if (!passphrase || !
|
|
53725
|
+
if (!passphrase || !existsSync78(vaultPath))
|
|
53350
53726
|
return null;
|
|
53351
53727
|
try {
|
|
53352
53728
|
return getStringSecret(passphrase, vaultPath, parseVaultReference(ref));
|
|
@@ -53362,7 +53738,7 @@ function registerDoctorCommand(program3) {
|
|
|
53362
53738
|
resolveSecret: (ref) => {
|
|
53363
53739
|
if (!isVaultReference(ref))
|
|
53364
53740
|
return ref;
|
|
53365
|
-
if (!passphrase || !
|
|
53741
|
+
if (!passphrase || !existsSync78(vaultPath))
|
|
53366
53742
|
return null;
|
|
53367
53743
|
try {
|
|
53368
53744
|
return getStringSecret(passphrase, vaultPath, parseVaultReference(ref));
|
|
@@ -53393,6 +53769,10 @@ function registerDoctorCommand(program3) {
|
|
|
53393
53769
|
title: "Agent liveness (in-agent via hostd)",
|
|
53394
53770
|
results: await runAgentSmokeChecks(config, { fast: opts.fast })
|
|
53395
53771
|
},
|
|
53772
|
+
{
|
|
53773
|
+
title: "Claude CLI floor (#1978)",
|
|
53774
|
+
results: runClaudeCliVersionChecks(config, { fast: opts.fast })
|
|
53775
|
+
},
|
|
53396
53776
|
{
|
|
53397
53777
|
title: "Google Drive",
|
|
53398
53778
|
results: [
|
|
@@ -53528,6 +53908,7 @@ var init_doctor = __esm(() => {
|
|
|
53528
53908
|
init_doctor_vault_broker_durability();
|
|
53529
53909
|
init_doctor_timezone();
|
|
53530
53910
|
init_doctor_fix_session_model();
|
|
53911
|
+
init_doctor_claude_cli();
|
|
53531
53912
|
DEFAULT_DEPLOY_MOUNTS_DEPS = { pathKind: defaultPathKind };
|
|
53532
53913
|
MANIFEST_WARN_ONLY = new Set([
|
|
53533
53914
|
"@playwright/mcp",
|
|
@@ -66463,9 +66844,9 @@ __export(exports_server, {
|
|
|
66463
66844
|
dispatchTool: () => dispatchTool,
|
|
66464
66845
|
TOOLS: () => TOOLS
|
|
66465
66846
|
});
|
|
66466
|
-
import { spawnSync as
|
|
66847
|
+
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
66467
66848
|
function execCli(args, stdin) {
|
|
66468
|
-
const r =
|
|
66849
|
+
const r = spawnSync20(CLI_BIN, args, {
|
|
66469
66850
|
encoding: "utf-8",
|
|
66470
66851
|
env: process.env,
|
|
66471
66852
|
timeout: 15000,
|
|
@@ -66974,7 +67355,7 @@ __export(exports_server2, {
|
|
|
66974
67355
|
TOOLS: () => TOOLS2
|
|
66975
67356
|
});
|
|
66976
67357
|
import { randomBytes as randomBytes16 } from "node:crypto";
|
|
66977
|
-
import { existsSync as
|
|
67358
|
+
import { existsSync as existsSync105 } from "node:fs";
|
|
66978
67359
|
function selfSocketPath() {
|
|
66979
67360
|
return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
|
|
66980
67361
|
}
|
|
@@ -67001,7 +67382,7 @@ async function dispatchTool2(name, args) {
|
|
|
67001
67382
|
return errorText2("hostd MCP: SWITCHROOM_AGENT_NAME env var is not set \u2014 cannot " + "determine which per-agent socket to talk to.");
|
|
67002
67383
|
}
|
|
67003
67384
|
const sockPath = selfSocketPath();
|
|
67004
|
-
if (!
|
|
67385
|
+
if (!existsSync105(sockPath)) {
|
|
67005
67386
|
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.`);
|
|
67006
67387
|
}
|
|
67007
67388
|
let req;
|
|
@@ -67252,13 +67633,13 @@ function resolveAuditLogPath() {
|
|
|
67252
67633
|
if (process.env.HOSTD_AUDIT_LOG_PATH)
|
|
67253
67634
|
return process.env.HOSTD_AUDIT_LOG_PATH;
|
|
67254
67635
|
const bindMounted = "/host-home/.switchroom/host-control-audit.log";
|
|
67255
|
-
if (
|
|
67636
|
+
if (existsSync105(bindMounted))
|
|
67256
67637
|
return bindMounted;
|
|
67257
67638
|
return defaultAuditLogPath2();
|
|
67258
67639
|
}
|
|
67259
67640
|
function getLastUpdateApplyStatus() {
|
|
67260
67641
|
const path10 = resolveAuditLogPath();
|
|
67261
|
-
if (!
|
|
67642
|
+
if (!existsSync105(path10)) {
|
|
67262
67643
|
return errorText2(`get_status: audit log not found at ${path10}. No update_apply has run yet?`);
|
|
67263
67644
|
}
|
|
67264
67645
|
let raw;
|
|
@@ -67977,11 +68358,11 @@ var init_test_agents = __esm(() => {
|
|
|
67977
68358
|
});
|
|
67978
68359
|
|
|
67979
68360
|
// src/litellm/header-passthrough-guard.ts
|
|
67980
|
-
import { existsSync as
|
|
68361
|
+
import { existsSync as existsSync108, readdirSync as readdirSync41 } from "node:fs";
|
|
67981
68362
|
function discoverLiveLitellmConfigPath(opts) {
|
|
67982
68363
|
const servicesDir = opts?.servicesDir ?? COOLIFY_SERVICES_DIR;
|
|
67983
68364
|
const readdir2 = opts?.readdirFn ?? ((p) => readdirSync41(p));
|
|
67984
|
-
const exists = opts?.existsFn ??
|
|
68365
|
+
const exists = opts?.existsFn ?? existsSync108;
|
|
67985
68366
|
let entries;
|
|
67986
68367
|
try {
|
|
67987
68368
|
entries = readdir2(servicesDir);
|
|
@@ -68071,7 +68452,7 @@ var init_header_passthrough_guard = __esm(() => {
|
|
|
68071
68452
|
});
|
|
68072
68453
|
|
|
68073
68454
|
// src/fleet-health/litellm-config-sensor.ts
|
|
68074
|
-
import { readFileSync as
|
|
68455
|
+
import { readFileSync as readFileSync94, existsSync as existsSync109 } from "node:fs";
|
|
68075
68456
|
function resolveLitellmConfigPath(explicit, discoverFn = () => discoverLiveLitellmConfigPath()) {
|
|
68076
68457
|
if (explicit)
|
|
68077
68458
|
return explicit;
|
|
@@ -68082,8 +68463,8 @@ function resolveLitellmConfigPath(explicit, discoverFn = () => discoverLiveLitel
|
|
|
68082
68463
|
}
|
|
68083
68464
|
function scanLitellmConfig(opts = {}) {
|
|
68084
68465
|
const path10 = resolveLitellmConfigPath(opts.path, opts.discoverFn);
|
|
68085
|
-
const exists = opts.existsFn ??
|
|
68086
|
-
const read = opts.readFn ?? ((p) =>
|
|
68466
|
+
const exists = opts.existsFn ?? existsSync109;
|
|
68467
|
+
const read = opts.readFn ?? ((p) => readFileSync94(p, "utf-8"));
|
|
68087
68468
|
const log = opts.log ?? (() => {});
|
|
68088
68469
|
const nowIso = opts.nowIso ?? new Date().toISOString();
|
|
68089
68470
|
if (path10 === null) {
|
|
@@ -68158,20 +68539,20 @@ __export(exports_scan, {
|
|
|
68158
68539
|
ledgerPathForBase: () => ledgerPathForBase
|
|
68159
68540
|
});
|
|
68160
68541
|
import {
|
|
68161
|
-
readFileSync as
|
|
68542
|
+
readFileSync as readFileSync95,
|
|
68162
68543
|
readdirSync as readdirSync42,
|
|
68163
|
-
existsSync as
|
|
68544
|
+
existsSync as existsSync110,
|
|
68164
68545
|
mkdirSync as mkdirSync61,
|
|
68165
68546
|
writeFileSync as writeFileSync45
|
|
68166
68547
|
} from "node:fs";
|
|
68167
|
-
import { resolve as resolve61, dirname as
|
|
68548
|
+
import { resolve as resolve61, dirname as dirname40 } from "node:path";
|
|
68168
68549
|
import { homedir as homedir60 } from "node:os";
|
|
68169
68550
|
function resolveSwitchroomBase(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir60()) {
|
|
68170
68551
|
return resolve61(home2, ".switchroom");
|
|
68171
68552
|
}
|
|
68172
68553
|
function listAgents(base) {
|
|
68173
68554
|
const dir = resolve61(base, "agents");
|
|
68174
|
-
if (!
|
|
68555
|
+
if (!existsSync110(dir))
|
|
68175
68556
|
return [];
|
|
68176
68557
|
try {
|
|
68177
68558
|
return readdirSync42(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
|
|
@@ -68200,16 +68581,16 @@ function runScan(opts = {}) {
|
|
|
68200
68581
|
let gwText = "";
|
|
68201
68582
|
let sawArtifact = false;
|
|
68202
68583
|
try {
|
|
68203
|
-
if (
|
|
68204
|
-
turnsText =
|
|
68584
|
+
if (existsSync110(turnsPath)) {
|
|
68585
|
+
turnsText = readFileSync95(turnsPath, "utf-8");
|
|
68205
68586
|
sawArtifact = true;
|
|
68206
68587
|
}
|
|
68207
68588
|
} catch (e) {
|
|
68208
68589
|
log(`fleet-health: WARN skipping ${agent} turns.jsonl unreadable: ${String(e)}`);
|
|
68209
68590
|
}
|
|
68210
68591
|
try {
|
|
68211
|
-
if (
|
|
68212
|
-
gwText =
|
|
68592
|
+
if (existsSync110(gwPath)) {
|
|
68593
|
+
gwText = readFileSync95(gwPath, "utf-8");
|
|
68213
68594
|
sawArtifact = true;
|
|
68214
68595
|
}
|
|
68215
68596
|
} catch (e) {
|
|
@@ -68251,19 +68632,19 @@ function runScan(opts = {}) {
|
|
|
68251
68632
|
function readLedgerIfPresent(base) {
|
|
68252
68633
|
const path10 = ledgerPathForBase(base);
|
|
68253
68634
|
try {
|
|
68254
|
-
if (!
|
|
68635
|
+
if (!existsSync110(path10))
|
|
68255
68636
|
return null;
|
|
68256
|
-
return JSON.parse(
|
|
68637
|
+
return JSON.parse(readFileSync95(path10, "utf-8"));
|
|
68257
68638
|
} catch {
|
|
68258
68639
|
return null;
|
|
68259
68640
|
}
|
|
68260
68641
|
}
|
|
68261
68642
|
function ledgerPathForBase(base) {
|
|
68262
|
-
return fleetHealthLedgerPath(
|
|
68643
|
+
return fleetHealthLedgerPath(dirname40(base));
|
|
68263
68644
|
}
|
|
68264
68645
|
function writeLedger(base, ledger) {
|
|
68265
68646
|
const path10 = ledgerPathForBase(base);
|
|
68266
|
-
mkdirSync61(
|
|
68647
|
+
mkdirSync61(dirname40(path10), { recursive: true });
|
|
68267
68648
|
writeFileSync45(path10, JSON.stringify(ledger, null, 2) + `
|
|
68268
68649
|
`, "utf-8");
|
|
68269
68650
|
return path10;
|
|
@@ -88798,9 +89179,9 @@ init_source();
|
|
|
88798
89179
|
init_loader();
|
|
88799
89180
|
init_lifecycle();
|
|
88800
89181
|
init_compose_env();
|
|
88801
|
-
import { existsSync as
|
|
88802
|
-
import { spawnSync as
|
|
88803
|
-
import { join as
|
|
89182
|
+
import { existsSync as existsSync80, mkdirSync as mkdirSync43, readFileSync as readFileSync71, realpathSync as realpathSync6, statSync as statSync45, chownSync as chownSync8 } from "node:fs";
|
|
89183
|
+
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
89184
|
+
import { join as join81, dirname as dirname31, resolve as resolve45 } from "node:path";
|
|
88804
89185
|
import { homedir as homedir43 } from "node:os";
|
|
88805
89186
|
|
|
88806
89187
|
// src/cli/release-yaml.ts
|
|
@@ -88931,27 +89312,27 @@ init_scaffold_integration();
|
|
|
88931
89312
|
// src/cli/sync-bundled-skills.ts
|
|
88932
89313
|
import {
|
|
88933
89314
|
cpSync as cpSync2,
|
|
88934
|
-
existsSync as
|
|
89315
|
+
existsSync as existsSync79,
|
|
88935
89316
|
mkdirSync as mkdirSync42,
|
|
88936
|
-
readFileSync as
|
|
89317
|
+
readFileSync as readFileSync70,
|
|
88937
89318
|
readdirSync as readdirSync27,
|
|
88938
89319
|
renameSync as renameSync18,
|
|
88939
89320
|
rmSync as rmSync13,
|
|
88940
89321
|
writeFileSync as writeFileSync29
|
|
88941
89322
|
} from "node:fs";
|
|
88942
|
-
import { join as
|
|
89323
|
+
import { join as join80 } from "node:path";
|
|
88943
89324
|
var BUNDLED_SKILL_MANIFEST_NAME = ".switchroom-manifest.json";
|
|
88944
89325
|
function listSkillDirs(dir) {
|
|
88945
|
-
if (!
|
|
89326
|
+
if (!existsSync79(dir))
|
|
88946
89327
|
return [];
|
|
88947
89328
|
return readdirSync27(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
88948
89329
|
}
|
|
88949
89330
|
function readBundledSkillManifest(poolDir) {
|
|
88950
|
-
const path7 =
|
|
88951
|
-
if (!
|
|
89331
|
+
const path7 = join80(poolDir, BUNDLED_SKILL_MANIFEST_NAME);
|
|
89332
|
+
if (!existsSync79(path7))
|
|
88952
89333
|
return { firstRun: true };
|
|
88953
89334
|
try {
|
|
88954
|
-
const parsed = JSON.parse(
|
|
89335
|
+
const parsed = JSON.parse(readFileSync70(path7, "utf8"));
|
|
88955
89336
|
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.skills) || !parsed.skills.every((s) => typeof s === "string")) {
|
|
88956
89337
|
return { corrupt: true };
|
|
88957
89338
|
}
|
|
@@ -88962,7 +89343,7 @@ function readBundledSkillManifest(poolDir) {
|
|
|
88962
89343
|
}
|
|
88963
89344
|
}
|
|
88964
89345
|
function stageAndSwap(srcSkill, destSkill, poolDir, name) {
|
|
88965
|
-
const staging =
|
|
89346
|
+
const staging = join80(poolDir, `.tmp-${name}-${process.pid}-${Date.now()}`);
|
|
88966
89347
|
try {
|
|
88967
89348
|
rmSync13(staging, { recursive: true, force: true });
|
|
88968
89349
|
cpSync2(srcSkill, staging, { recursive: true, dereference: false });
|
|
@@ -88991,11 +89372,11 @@ function syncBundledSkills(opts) {
|
|
|
88991
89372
|
const shipped = listSkillDirs(source).sort();
|
|
88992
89373
|
const shippedSet = new Set(shipped);
|
|
88993
89374
|
for (const name of shipped) {
|
|
88994
|
-
const destSkill =
|
|
88995
|
-
const existed =
|
|
89375
|
+
const destSkill = join80(dest, name);
|
|
89376
|
+
const existed = existsSync79(destSkill);
|
|
88996
89377
|
let transferred = false;
|
|
88997
89378
|
if (existed && !priorSkills.has(name) && !result.firstRun) {
|
|
88998
|
-
const backup =
|
|
89379
|
+
const backup = join80(dest, `${name}.operator-backup-${process.pid}-${Date.now()}`);
|
|
88999
89380
|
try {
|
|
89000
89381
|
renameSync18(destSkill, backup);
|
|
89001
89382
|
transferred = true;
|
|
@@ -89009,7 +89390,7 @@ function syncBundledSkills(opts) {
|
|
|
89009
89390
|
continue;
|
|
89010
89391
|
}
|
|
89011
89392
|
}
|
|
89012
|
-
stageAndSwap(
|
|
89393
|
+
stageAndSwap(join80(source, name), destSkill, dest, name);
|
|
89013
89394
|
if (!transferred && (priorSkills.has(name) || existed))
|
|
89014
89395
|
result.updated.push(name);
|
|
89015
89396
|
else
|
|
@@ -89019,8 +89400,8 @@ function syncBundledSkills(opts) {
|
|
|
89019
89400
|
for (const name of priorSkills) {
|
|
89020
89401
|
if (shippedSet.has(name))
|
|
89021
89402
|
continue;
|
|
89022
|
-
const target =
|
|
89023
|
-
if (
|
|
89403
|
+
const target = join80(dest, name);
|
|
89404
|
+
if (existsSync79(target)) {
|
|
89024
89405
|
rmSync13(target, { recursive: true, force: true });
|
|
89025
89406
|
result.removed.push(name);
|
|
89026
89407
|
}
|
|
@@ -89038,8 +89419,8 @@ function syncBundledSkills(opts) {
|
|
|
89038
89419
|
skills: shipped,
|
|
89039
89420
|
updatedAt: new Date().toISOString()
|
|
89040
89421
|
};
|
|
89041
|
-
const manifestPath =
|
|
89042
|
-
const manifestTmp =
|
|
89422
|
+
const manifestPath = join80(dest, BUNDLED_SKILL_MANIFEST_NAME);
|
|
89423
|
+
const manifestTmp = join80(dest, `${BUNDLED_SKILL_MANIFEST_NAME}.tmp-${process.pid}-${Date.now()}`);
|
|
89043
89424
|
writeFileSync29(manifestTmp, JSON.stringify(manifest, null, 2) + `
|
|
89044
89425
|
`, "utf8");
|
|
89045
89426
|
renameSync18(manifestTmp, manifestPath);
|
|
@@ -89051,7 +89432,7 @@ init_resolve_version();
|
|
|
89051
89432
|
function defaultPersistPin(configPath) {
|
|
89052
89433
|
return (pin) => {
|
|
89053
89434
|
const path7 = configPath ?? findConfigFile();
|
|
89054
|
-
const before =
|
|
89435
|
+
const before = readFileSync71(path7, "utf8");
|
|
89055
89436
|
const after = setReleasePinInConfig(before, pin);
|
|
89056
89437
|
if (after === before)
|
|
89057
89438
|
return;
|
|
@@ -89065,18 +89446,18 @@ function defaultPersistPin(configPath) {
|
|
|
89065
89446
|
} catch {}
|
|
89066
89447
|
};
|
|
89067
89448
|
}
|
|
89068
|
-
var DEFAULT_COMPOSE_PATH2 =
|
|
89449
|
+
var DEFAULT_COMPOSE_PATH2 = join81(homedir43(), ".switchroom", "compose", "docker-compose.yml");
|
|
89069
89450
|
function runningFromSwitchroomCheckout(scriptPath) {
|
|
89070
|
-
let dir =
|
|
89451
|
+
let dir = dirname31(scriptPath);
|
|
89071
89452
|
for (let i = 0;i < 12; i++) {
|
|
89072
|
-
if (
|
|
89453
|
+
if (existsSync80(join81(dir, ".git"))) {
|
|
89073
89454
|
try {
|
|
89074
|
-
const pkg = JSON.parse(
|
|
89455
|
+
const pkg = JSON.parse(readFileSync71(join81(dir, "package.json"), "utf-8"));
|
|
89075
89456
|
if (pkg.name === "switchroom")
|
|
89076
89457
|
return true;
|
|
89077
89458
|
} catch {}
|
|
89078
89459
|
}
|
|
89079
|
-
const parent =
|
|
89460
|
+
const parent = dirname31(dir);
|
|
89080
89461
|
if (parent === dir)
|
|
89081
89462
|
break;
|
|
89082
89463
|
dir = parent;
|
|
@@ -89156,7 +89537,7 @@ function planUpdate(opts) {
|
|
|
89156
89537
|
steps.push({
|
|
89157
89538
|
name: "pull-images",
|
|
89158
89539
|
description: "Pull broker / kernel / agent images from GHCR",
|
|
89159
|
-
skipReason: opts.skipImages ? "--skip-images flag set" : !
|
|
89540
|
+
skipReason: opts.skipImages ? "--skip-images flag set" : !existsSync80(composePath) ? `compose file not found at ${composePath} (run \`switchroom apply --compose-only\` first)` : undefined,
|
|
89160
89541
|
run: () => {
|
|
89161
89542
|
const r = runner("docker", [
|
|
89162
89543
|
"compose",
|
|
@@ -89282,14 +89663,14 @@ function planUpdate(opts) {
|
|
|
89282
89663
|
return;
|
|
89283
89664
|
}
|
|
89284
89665
|
const source = resolve45(import.meta.dirname, "../../skills");
|
|
89285
|
-
const dest =
|
|
89286
|
-
if (!
|
|
89666
|
+
const dest = join81(homedir43(), ".switchroom", "skills", "_bundled");
|
|
89667
|
+
if (!existsSync80(source)) {
|
|
89287
89668
|
process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
|
|
89288
89669
|
`);
|
|
89289
89670
|
return;
|
|
89290
89671
|
}
|
|
89291
89672
|
try {
|
|
89292
|
-
mkdirSync43(
|
|
89673
|
+
mkdirSync43(dirname31(dest), { recursive: true });
|
|
89293
89674
|
const r = syncBundledSkills({
|
|
89294
89675
|
source,
|
|
89295
89676
|
dest,
|
|
@@ -89314,11 +89695,11 @@ function planUpdate(opts) {
|
|
|
89314
89695
|
run: () => {
|
|
89315
89696
|
if (opts.syncBundledSkillsFn)
|
|
89316
89697
|
return;
|
|
89317
|
-
const dest =
|
|
89318
|
-
if (!
|
|
89698
|
+
const dest = join81(homedir43(), ".switchroom", "skills", "_bundled");
|
|
89699
|
+
if (!existsSync80(dest)) {
|
|
89319
89700
|
return;
|
|
89320
89701
|
}
|
|
89321
|
-
const missing = getBuiltinDefaultSkillEntries().map((e) => e.key).filter((key) => !
|
|
89702
|
+
const missing = getBuiltinDefaultSkillEntries().map((e) => e.key).filter((key) => !existsSync80(join81(dest, key)));
|
|
89322
89703
|
if (missing.length > 0) {
|
|
89323
89704
|
throw new Error(`verify-bundled-skills: builtin default skill(s) missing from the pool after sync: ` + `${missing.join(", ")}. These ship in the CLI package and must exist in ${dest}. ` + `This is a broken sync or a packaging regression \u2014 the pool is not converged.`);
|
|
89324
89705
|
}
|
|
@@ -89353,7 +89734,7 @@ function planUpdate(opts) {
|
|
|
89353
89734
|
description: "docker compose up -d --remove-orphans (recreates services with new images / compose)",
|
|
89354
89735
|
run: () => {
|
|
89355
89736
|
try {
|
|
89356
|
-
const composeText =
|
|
89737
|
+
const composeText = readFileSync71(composePath, "utf8");
|
|
89357
89738
|
const pf = validateBindSources(composeText);
|
|
89358
89739
|
if (!pf.ok)
|
|
89359
89740
|
throw new Error(formatPreflightError(pf));
|
|
@@ -89387,7 +89768,7 @@ function planUpdate(opts) {
|
|
|
89387
89768
|
return steps;
|
|
89388
89769
|
}
|
|
89389
89770
|
function defaultRunner2(cmd, args) {
|
|
89390
|
-
const r =
|
|
89771
|
+
const r = spawnSync14(cmd, args, { stdio: "inherit" });
|
|
89391
89772
|
return { status: r.status ?? 1 };
|
|
89392
89773
|
}
|
|
89393
89774
|
function writeMarkerInPreferredLocation(agent, reason, runner) {
|
|
@@ -89430,12 +89811,12 @@ function defaultStatusProbe(composePath) {
|
|
|
89430
89811
|
try {
|
|
89431
89812
|
cliBuiltAt = new Date(statSync45(scriptPath).mtimeMs).toISOString();
|
|
89432
89813
|
} catch {}
|
|
89433
|
-
let dir =
|
|
89814
|
+
let dir = dirname31(scriptPath);
|
|
89434
89815
|
for (let i = 0;i < 8; i++) {
|
|
89435
|
-
const pkgPath =
|
|
89436
|
-
if (
|
|
89816
|
+
const pkgPath = join81(dir, "package.json");
|
|
89817
|
+
if (existsSync80(pkgPath)) {
|
|
89437
89818
|
try {
|
|
89438
|
-
const pkg = JSON.parse(
|
|
89819
|
+
const pkg = JSON.parse(readFileSync71(pkgPath, "utf-8"));
|
|
89439
89820
|
if (typeof pkg.version === "string")
|
|
89440
89821
|
cliVersion = pkg.version;
|
|
89441
89822
|
} catch (err) {
|
|
@@ -89443,7 +89824,7 @@ function defaultStatusProbe(composePath) {
|
|
|
89443
89824
|
}
|
|
89444
89825
|
break;
|
|
89445
89826
|
}
|
|
89446
|
-
const parent =
|
|
89827
|
+
const parent = dirname31(dir);
|
|
89447
89828
|
if (parent === dir)
|
|
89448
89829
|
break;
|
|
89449
89830
|
dir = parent;
|
|
@@ -89456,13 +89837,13 @@ function defaultStatusProbe(composePath) {
|
|
|
89456
89837
|
warnings.push("could not resolve CLI version (no package.json found above the resolved script path)");
|
|
89457
89838
|
}
|
|
89458
89839
|
const services = [];
|
|
89459
|
-
if (!
|
|
89840
|
+
if (!existsSync80(composePath)) {
|
|
89460
89841
|
warnings.push(`compose file not found at ${composePath}; service status unknown`);
|
|
89461
89842
|
return { cliVersion, cliBuiltAt, services, warnings };
|
|
89462
89843
|
}
|
|
89463
89844
|
let serviceList = [];
|
|
89464
89845
|
try {
|
|
89465
|
-
const r =
|
|
89846
|
+
const r = spawnSync14("docker", ["compose", "-p", "switchroom", "-f", composePath, "config", "--services"], { encoding: "utf-8", timeout: 1e4 });
|
|
89466
89847
|
if (r.status !== 0) {
|
|
89467
89848
|
warnings.push(`docker compose config --services failed: ${r.stderr?.trim() ?? r.error?.message ?? "unknown"}`);
|
|
89468
89849
|
return { cliVersion, cliBuiltAt, services, warnings };
|
|
@@ -89479,7 +89860,7 @@ function defaultStatusProbe(composePath) {
|
|
|
89479
89860
|
let containerCreatedAt = null;
|
|
89480
89861
|
let status = "<unknown>";
|
|
89481
89862
|
try {
|
|
89482
|
-
const r =
|
|
89863
|
+
const r = spawnSync14("docker", ["inspect", "-f", "{{.Config.Image}}|{{.Created}}|{{.State.Status}}", containerName2], { encoding: "utf-8", timeout: 5000 });
|
|
89483
89864
|
if (r.status === 0) {
|
|
89484
89865
|
const [img, created, st] = r.stdout.trim().split("|");
|
|
89485
89866
|
image = img ?? null;
|
|
@@ -89495,7 +89876,7 @@ function defaultStatusProbe(composePath) {
|
|
|
89495
89876
|
let imagePulledAt = null;
|
|
89496
89877
|
if (image) {
|
|
89497
89878
|
try {
|
|
89498
|
-
const r =
|
|
89879
|
+
const r = spawnSync14("docker", ["image", "inspect", "-f", "{{.Id}}|{{.Created}}|{{.Metadata.LastTagTime}}", image], { encoding: "utf-8", timeout: 5000 });
|
|
89499
89880
|
if (r.status === 0) {
|
|
89500
89881
|
const [id, created, lastTag] = r.stdout.trim().split("|");
|
|
89501
89882
|
imageDigestShort = id?.replace(/^sha256:/, "").slice(0, 12) ?? null;
|
|
@@ -89651,14 +90032,14 @@ function registerUpdateCommand(program3) {
|
|
|
89651
90032
|
|
|
89652
90033
|
// src/cli/rollout.ts
|
|
89653
90034
|
init_helpers();
|
|
89654
|
-
import { spawnSync as
|
|
89655
|
-
import { readFileSync as
|
|
90035
|
+
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
90036
|
+
import { readFileSync as readFileSync73, chownSync as chownSync9, statSync as statSync47 } from "node:fs";
|
|
89656
90037
|
import { homedir as homedir45 } from "node:os";
|
|
89657
90038
|
|
|
89658
90039
|
// src/cli/rollout-pin-journal.ts
|
|
89659
90040
|
import {
|
|
89660
|
-
existsSync as
|
|
89661
|
-
readFileSync as
|
|
90041
|
+
existsSync as existsSync81,
|
|
90042
|
+
readFileSync as readFileSync72,
|
|
89662
90043
|
writeFileSync as writeFileSync30,
|
|
89663
90044
|
renameSync as renameSync19,
|
|
89664
90045
|
unlinkSync as unlinkSync14,
|
|
@@ -89667,7 +90048,7 @@ import {
|
|
|
89667
90048
|
} from "node:fs";
|
|
89668
90049
|
import { homedir as homedir44 } from "node:os";
|
|
89669
90050
|
import { createHash as createHash16 } from "node:crypto";
|
|
89670
|
-
import { join as
|
|
90051
|
+
import { join as join82, basename as basename9, resolve as resolve46, dirname as dirname32 } from "node:path";
|
|
89671
90052
|
init_flock();
|
|
89672
90053
|
var PIN_JOURNAL_MAX_AGE_MS = 15 * 60 * 1000;
|
|
89673
90054
|
var STATE_DIR_NAME = ".switchroom";
|
|
@@ -89676,16 +90057,16 @@ function pinJournalDir(configPath) {
|
|
|
89676
90057
|
if (override && override.trim().length > 0)
|
|
89677
90058
|
return override.trim();
|
|
89678
90059
|
if (configPath) {
|
|
89679
|
-
const dir =
|
|
90060
|
+
const dir = dirname32(resolve46(configPath));
|
|
89680
90061
|
if (basename9(dir) === STATE_DIR_NAME)
|
|
89681
90062
|
return dir;
|
|
89682
90063
|
}
|
|
89683
|
-
return
|
|
90064
|
+
return join82(homedir44(), STATE_DIR_NAME);
|
|
89684
90065
|
}
|
|
89685
90066
|
function pinJournalPath(configPath) {
|
|
89686
90067
|
const abs = resolve46(configPath);
|
|
89687
90068
|
const key = createHash16("sha256").update(abs).digest("hex").slice(0, 12);
|
|
89688
|
-
return
|
|
90069
|
+
return join82(pinJournalDir(abs), `.rollout-pin-journal.${basename9(abs)}.${key}.json`);
|
|
89689
90070
|
}
|
|
89690
90071
|
function isPidAlive(pid) {
|
|
89691
90072
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
@@ -89719,9 +90100,9 @@ function readPinJournal(configPath, warn = (m) => process.stderr.write(m)) {
|
|
|
89719
90100
|
const p = pinJournalPath(configPath);
|
|
89720
90101
|
let raw;
|
|
89721
90102
|
try {
|
|
89722
|
-
raw =
|
|
90103
|
+
raw = readFileSync72(p, "utf8");
|
|
89723
90104
|
} catch (e) {
|
|
89724
|
-
if (
|
|
90105
|
+
if (existsSync81(p)) {
|
|
89725
90106
|
warn(`\u26a0\ufe0f rollout pin journal: ${p} exists but could not be read ` + `(${e.message}). A provisional \`release.pin\` may be ` + `uncommitted \u2014 verify it host-side before the next reconcile.
|
|
89726
90107
|
`);
|
|
89727
90108
|
}
|
|
@@ -89772,7 +90153,7 @@ function beginPinPersist(configPath, pin, opts = {}) {
|
|
|
89772
90153
|
warn(`\u26a0\ufe0f rollout pin journal: overwriting an ABANDONED journal at ${p} ` + `(pid ${existing.pid} recorded ${existing.at}, provisional pin ` + `${existing.pin}). Its roll never committed or reverted; \`release.pin\` ` + `in ${configPath} may still name that unproven build \u2014 verify it.
|
|
89773
90154
|
`);
|
|
89774
90155
|
}
|
|
89775
|
-
const priorPin = getReleasePinFromConfig(
|
|
90156
|
+
const priorPin = getReleasePinFromConfig(readFileSync72(configPath, "utf8"));
|
|
89776
90157
|
const journal = {
|
|
89777
90158
|
v: 1,
|
|
89778
90159
|
configPath,
|
|
@@ -89781,7 +90162,7 @@ function beginPinPersist(configPath, pin, opts = {}) {
|
|
|
89781
90162
|
pid: process.pid,
|
|
89782
90163
|
at: new Date().toISOString()
|
|
89783
90164
|
};
|
|
89784
|
-
mkdirSync44(
|
|
90165
|
+
mkdirSync44(dirname32(p), { recursive: true });
|
|
89785
90166
|
const tmp = `${p}.${process.pid}.tmp`;
|
|
89786
90167
|
writeFileSync30(tmp, JSON.stringify(journal), { encoding: "utf8", mode: 384 });
|
|
89787
90168
|
renameSync19(tmp, p);
|
|
@@ -89793,7 +90174,7 @@ function commitPinPersist(configPath) {
|
|
|
89793
90174
|
unlinkSync14(p);
|
|
89794
90175
|
return null;
|
|
89795
90176
|
} catch (e) {
|
|
89796
|
-
if (!
|
|
90177
|
+
if (!existsSync81(p))
|
|
89797
90178
|
return null;
|
|
89798
90179
|
return `rollout pin journal: FAILED to clear ${p} after a SUCCESSFUL roll ` + `(${e.message}). Delete it host-side \u2014 while it exists, ` + `recovery may revert a proven \`release.pin\`.`;
|
|
89799
90180
|
}
|
|
@@ -89815,7 +90196,7 @@ function rollbackPinPersist(configPath, opts = {}) {
|
|
|
89815
90196
|
}
|
|
89816
90197
|
}
|
|
89817
90198
|
try {
|
|
89818
|
-
const current =
|
|
90199
|
+
const current = readFileSync72(configPath, "utf8");
|
|
89819
90200
|
const next = journal.priorPin ? setReleasePinInConfig(current, journal.priorPin) : deleteReleasePinInConfig(current);
|
|
89820
90201
|
let mode = 384;
|
|
89821
90202
|
try {
|
|
@@ -89871,10 +90252,10 @@ init_audit_reader();
|
|
|
89871
90252
|
init_hindsight();
|
|
89872
90253
|
|
|
89873
90254
|
// src/cli/deploy-version-guard.ts
|
|
89874
|
-
import { spawnSync as
|
|
90255
|
+
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
89875
90256
|
var DOCKER_INSPECT_TIMEOUT_MS = 60 * 1000;
|
|
89876
90257
|
var defaultRunner3 = (args) => {
|
|
89877
|
-
const r =
|
|
90258
|
+
const r = spawnSync15("docker", args, {
|
|
89878
90259
|
encoding: "utf8",
|
|
89879
90260
|
timeout: DOCKER_INSPECT_TIMEOUT_MS,
|
|
89880
90261
|
killSignal: "SIGKILL"
|
|
@@ -90248,7 +90629,7 @@ function isSpawnTimeout(r, killSignal) {
|
|
|
90248
90629
|
var ROLLOUT_KILL_SIGNAL = "SIGKILL";
|
|
90249
90630
|
function createRolloutDeps(params) {
|
|
90250
90631
|
const { configPath, scriptPath, hostdCtx } = params;
|
|
90251
|
-
const spawn6 = params.spawn ??
|
|
90632
|
+
const spawn6 = params.spawn ?? spawnSync16;
|
|
90252
90633
|
const warn = params.warn ?? ((line) => process.stderr.write(line));
|
|
90253
90634
|
const dockerRun = (args) => {
|
|
90254
90635
|
let r;
|
|
@@ -90323,7 +90704,7 @@ function createRolloutDeps(params) {
|
|
|
90323
90704
|
return r.ok ? r.stdout : null;
|
|
90324
90705
|
}),
|
|
90325
90706
|
persistPin: (pin) => {
|
|
90326
|
-
const before =
|
|
90707
|
+
const before = readFileSync73(configPath, "utf8");
|
|
90327
90708
|
const after = setReleasePinInConfig(before, pin);
|
|
90328
90709
|
if (after === before)
|
|
90329
90710
|
return false;
|
|
@@ -90502,8 +90883,8 @@ init_helpers();
|
|
|
90502
90883
|
init_lifecycle();
|
|
90503
90884
|
init_resolve_version();
|
|
90504
90885
|
import { execSync as execSync3 } from "node:child_process";
|
|
90505
|
-
import { existsSync as
|
|
90506
|
-
import { dirname as
|
|
90886
|
+
import { existsSync as existsSync82, readFileSync as readFileSync74 } from "node:fs";
|
|
90887
|
+
import { dirname as dirname33, join as join83 } from "node:path";
|
|
90507
90888
|
function getClaudeCodeVersion() {
|
|
90508
90889
|
try {
|
|
90509
90890
|
const out = execSync3("claude --version 2>/dev/null", {
|
|
@@ -90553,16 +90934,16 @@ function formatUptime3(timestamp) {
|
|
|
90553
90934
|
function locateSwitchroomInstallDir() {
|
|
90554
90935
|
let dir = import.meta.dirname;
|
|
90555
90936
|
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
90556
|
-
const pkgPath =
|
|
90557
|
-
if (
|
|
90937
|
+
const pkgPath = join83(dir, "package.json");
|
|
90938
|
+
if (existsSync82(pkgPath)) {
|
|
90558
90939
|
try {
|
|
90559
|
-
const pkg = JSON.parse(
|
|
90560
|
-
if (pkg.name === "switchroom" &&
|
|
90940
|
+
const pkg = JSON.parse(readFileSync74(pkgPath, "utf-8"));
|
|
90941
|
+
if (pkg.name === "switchroom" && existsSync82(join83(dir, ".git"))) {
|
|
90561
90942
|
return dir;
|
|
90562
90943
|
}
|
|
90563
90944
|
} catch {}
|
|
90564
90945
|
}
|
|
90565
|
-
dir =
|
|
90946
|
+
dir = dirname33(dir);
|
|
90566
90947
|
}
|
|
90567
90948
|
return null;
|
|
90568
90949
|
}
|
|
@@ -90735,18 +91116,18 @@ import { resolve as resolve48 } from "node:path";
|
|
|
90735
91116
|
|
|
90736
91117
|
// src/agents/session-retention.ts
|
|
90737
91118
|
import {
|
|
90738
|
-
existsSync as
|
|
91119
|
+
existsSync as existsSync83,
|
|
90739
91120
|
readdirSync as readdirSync28,
|
|
90740
91121
|
statSync as statSync48,
|
|
90741
91122
|
unlinkSync as unlinkSync15
|
|
90742
91123
|
} from "node:fs";
|
|
90743
|
-
import { join as
|
|
91124
|
+
import { join as join84 } from "node:path";
|
|
90744
91125
|
var DEFAULT_SESSION_RETENTION_MAX_COUNT = 20;
|
|
90745
91126
|
var DEFAULT_SESSION_RETENTION_MAX_AGE_DAYS = 30;
|
|
90746
91127
|
var MIN_KEEP = 2;
|
|
90747
91128
|
function collectSessionJsonl(claudeConfigDir) {
|
|
90748
|
-
const projects =
|
|
90749
|
-
if (!
|
|
91129
|
+
const projects = join84(claudeConfigDir, "projects");
|
|
91130
|
+
if (!existsSync83(projects))
|
|
90750
91131
|
return [];
|
|
90751
91132
|
const found = [];
|
|
90752
91133
|
const walk2 = (dir) => {
|
|
@@ -90757,7 +91138,7 @@ function collectSessionJsonl(claudeConfigDir) {
|
|
|
90757
91138
|
return;
|
|
90758
91139
|
}
|
|
90759
91140
|
for (const name of entries) {
|
|
90760
|
-
const full =
|
|
91141
|
+
const full = join84(dir, name);
|
|
90761
91142
|
let st;
|
|
90762
91143
|
try {
|
|
90763
91144
|
st = statSync48(full);
|
|
@@ -90877,18 +91258,18 @@ function registerHandoffCommand(program3) {
|
|
|
90877
91258
|
// src/issues/store.ts
|
|
90878
91259
|
import {
|
|
90879
91260
|
closeSync as closeSync15,
|
|
90880
|
-
existsSync as
|
|
91261
|
+
existsSync as existsSync84,
|
|
90881
91262
|
mkdirSync as mkdirSync45,
|
|
90882
91263
|
openSync as openSync15,
|
|
90883
91264
|
readdirSync as readdirSync29,
|
|
90884
|
-
readFileSync as
|
|
91265
|
+
readFileSync as readFileSync75,
|
|
90885
91266
|
renameSync as renameSync20,
|
|
90886
91267
|
statSync as statSync49,
|
|
90887
91268
|
unlinkSync as unlinkSync16,
|
|
90888
91269
|
writeFileSync as writeFileSync31,
|
|
90889
91270
|
writeSync as writeSync9
|
|
90890
91271
|
} from "node:fs";
|
|
90891
|
-
import { join as
|
|
91272
|
+
import { join as join85 } from "node:path";
|
|
90892
91273
|
import { randomBytes as randomBytes13 } from "node:crypto";
|
|
90893
91274
|
import { execSync as execSync4 } from "node:child_process";
|
|
90894
91275
|
|
|
@@ -91319,12 +91700,12 @@ function redactedMarker(ruleId) {
|
|
|
91319
91700
|
var ISSUES_FILE = "issues.jsonl";
|
|
91320
91701
|
var ISSUES_LOCK = "issues.lock";
|
|
91321
91702
|
function readAll(stateDir) {
|
|
91322
|
-
const path7 =
|
|
91323
|
-
if (!
|
|
91703
|
+
const path7 = join85(stateDir, ISSUES_FILE);
|
|
91704
|
+
if (!existsSync84(path7))
|
|
91324
91705
|
return [];
|
|
91325
91706
|
let raw;
|
|
91326
91707
|
try {
|
|
91327
|
-
raw =
|
|
91708
|
+
raw = readFileSync75(path7, "utf-8");
|
|
91328
91709
|
} catch {
|
|
91329
91710
|
return [];
|
|
91330
91711
|
}
|
|
@@ -91397,7 +91778,7 @@ function record(stateDir, input, nowFn = Date.now) {
|
|
|
91397
91778
|
});
|
|
91398
91779
|
}
|
|
91399
91780
|
function resolve49(stateDir, fingerprint, nowFn = Date.now) {
|
|
91400
|
-
if (!
|
|
91781
|
+
if (!existsSync84(join85(stateDir, ISSUES_FILE)))
|
|
91401
91782
|
return 0;
|
|
91402
91783
|
return withLock(stateDir, () => {
|
|
91403
91784
|
const all = readAll(stateDir);
|
|
@@ -91415,7 +91796,7 @@ function resolve49(stateDir, fingerprint, nowFn = Date.now) {
|
|
|
91415
91796
|
});
|
|
91416
91797
|
}
|
|
91417
91798
|
function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
91418
|
-
if (!
|
|
91799
|
+
if (!existsSync84(join85(stateDir, ISSUES_FILE)))
|
|
91419
91800
|
return 0;
|
|
91420
91801
|
return withLock(stateDir, () => {
|
|
91421
91802
|
const all = readAll(stateDir);
|
|
@@ -91433,7 +91814,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
|
91433
91814
|
});
|
|
91434
91815
|
}
|
|
91435
91816
|
function prune(stateDir, opts = {}) {
|
|
91436
|
-
if (!
|
|
91817
|
+
if (!existsSync84(join85(stateDir, ISSUES_FILE)))
|
|
91437
91818
|
return 0;
|
|
91438
91819
|
return withLock(stateDir, () => {
|
|
91439
91820
|
const all = readAll(stateDir);
|
|
@@ -91466,7 +91847,7 @@ function ensureDir(stateDir) {
|
|
|
91466
91847
|
mkdirSync45(stateDir, { recursive: true });
|
|
91467
91848
|
}
|
|
91468
91849
|
function writeAll(stateDir, events) {
|
|
91469
|
-
const path7 =
|
|
91850
|
+
const path7 = join85(stateDir, ISSUES_FILE);
|
|
91470
91851
|
sweepOrphanTmpFiles(stateDir);
|
|
91471
91852
|
const tmp = `${path7}.tmp-${process.pid}-${randomBytes13(4).toString("hex")}`;
|
|
91472
91853
|
const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
|
|
@@ -91488,7 +91869,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
91488
91869
|
for (const entry of entries) {
|
|
91489
91870
|
if (!entry.startsWith(TMP_PREFIX))
|
|
91490
91871
|
continue;
|
|
91491
|
-
const tmpPath =
|
|
91872
|
+
const tmpPath = join85(stateDir, entry);
|
|
91492
91873
|
try {
|
|
91493
91874
|
const stat = statSync49(tmpPath);
|
|
91494
91875
|
if (stat.mtimeMs < cutoff) {
|
|
@@ -91500,7 +91881,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
91500
91881
|
var LOCK_RETRY_MS = 25;
|
|
91501
91882
|
var LOCK_TIMEOUT_MS = 1e4;
|
|
91502
91883
|
function withLock(stateDir, fn) {
|
|
91503
|
-
const lockPath =
|
|
91884
|
+
const lockPath = join85(stateDir, ISSUES_LOCK);
|
|
91504
91885
|
const startedAt = Date.now();
|
|
91505
91886
|
let fd = null;
|
|
91506
91887
|
while (fd === null) {
|
|
@@ -91535,7 +91916,7 @@ function withLock(stateDir, fn) {
|
|
|
91535
91916
|
function tryStealStaleLock(lockPath) {
|
|
91536
91917
|
let pidStr;
|
|
91537
91918
|
try {
|
|
91538
|
-
pidStr =
|
|
91919
|
+
pidStr = readFileSync75(lockPath, "utf-8").trim();
|
|
91539
91920
|
} catch {
|
|
91540
91921
|
return true;
|
|
91541
91922
|
}
|
|
@@ -91783,20 +92164,20 @@ function relTime(deltaMs) {
|
|
|
91783
92164
|
|
|
91784
92165
|
// src/cli/deps.ts
|
|
91785
92166
|
init_source();
|
|
91786
|
-
import { existsSync as
|
|
92167
|
+
import { existsSync as existsSync87 } from "node:fs";
|
|
91787
92168
|
import { homedir as homedir48 } from "node:os";
|
|
91788
|
-
import { join as
|
|
92169
|
+
import { join as join88, resolve as resolve50 } from "node:path";
|
|
91789
92170
|
|
|
91790
92171
|
// src/deps/python.ts
|
|
91791
92172
|
import { createHash as createHash17 } from "node:crypto";
|
|
91792
92173
|
import {
|
|
91793
|
-
existsSync as
|
|
92174
|
+
existsSync as existsSync85,
|
|
91794
92175
|
mkdirSync as mkdirSync46,
|
|
91795
|
-
readFileSync as
|
|
92176
|
+
readFileSync as readFileSync76,
|
|
91796
92177
|
rmSync as rmSync14,
|
|
91797
92178
|
writeFileSync as writeFileSync32
|
|
91798
92179
|
} from "node:fs";
|
|
91799
|
-
import { dirname as
|
|
92180
|
+
import { dirname as dirname34, join as join86 } from "node:path";
|
|
91800
92181
|
import { homedir as homedir46 } from "node:os";
|
|
91801
92182
|
import { execFileSync as execFileSync24 } from "node:child_process";
|
|
91802
92183
|
|
|
@@ -91809,26 +92190,26 @@ class PythonEnvError extends Error {
|
|
|
91809
92190
|
}
|
|
91810
92191
|
}
|
|
91811
92192
|
function defaultPythonCacheRoot() {
|
|
91812
|
-
return
|
|
92193
|
+
return join86(homedir46(), ".switchroom", "deps", "python");
|
|
91813
92194
|
}
|
|
91814
92195
|
function hashFile(path7) {
|
|
91815
|
-
return createHash17("sha256").update(
|
|
92196
|
+
return createHash17("sha256").update(readFileSync76(path7)).digest("hex");
|
|
91816
92197
|
}
|
|
91817
92198
|
function ensurePythonEnv(opts) {
|
|
91818
92199
|
const { skillName, requirementsPath, force = false } = opts;
|
|
91819
92200
|
const cacheRoot = opts.cacheRoot ?? defaultPythonCacheRoot();
|
|
91820
92201
|
const hostPython = opts.pythonBin ?? "python3";
|
|
91821
|
-
if (!
|
|
92202
|
+
if (!existsSync85(requirementsPath)) {
|
|
91822
92203
|
throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
|
|
91823
92204
|
}
|
|
91824
|
-
const venvDir =
|
|
91825
|
-
const stampPath =
|
|
91826
|
-
const binDir =
|
|
91827
|
-
const pythonBin =
|
|
91828
|
-
const pipBin =
|
|
92205
|
+
const venvDir = join86(cacheRoot, skillName);
|
|
92206
|
+
const stampPath = join86(venvDir, ".requirements.sha256");
|
|
92207
|
+
const binDir = join86(venvDir, "bin");
|
|
92208
|
+
const pythonBin = join86(binDir, "python");
|
|
92209
|
+
const pipBin = join86(binDir, "pip");
|
|
91829
92210
|
const targetHash = hashFile(requirementsPath);
|
|
91830
|
-
if (!force &&
|
|
91831
|
-
const existingHash =
|
|
92211
|
+
if (!force && existsSync85(stampPath) && existsSync85(pythonBin)) {
|
|
92212
|
+
const existingHash = readFileSync76(stampPath, "utf8").trim();
|
|
91832
92213
|
if (existingHash === targetHash) {
|
|
91833
92214
|
return {
|
|
91834
92215
|
skillName,
|
|
@@ -91840,10 +92221,10 @@ function ensurePythonEnv(opts) {
|
|
|
91840
92221
|
};
|
|
91841
92222
|
}
|
|
91842
92223
|
}
|
|
91843
|
-
if (
|
|
92224
|
+
if (existsSync85(venvDir)) {
|
|
91844
92225
|
rmSync14(venvDir, { recursive: true, force: true });
|
|
91845
92226
|
}
|
|
91846
|
-
mkdirSync46(
|
|
92227
|
+
mkdirSync46(dirname34(venvDir), { recursive: true });
|
|
91847
92228
|
try {
|
|
91848
92229
|
execFileSync24(hostPython, ["-m", "venv", venvDir], { stdio: "pipe" });
|
|
91849
92230
|
} catch (err) {
|
|
@@ -91878,13 +92259,13 @@ function ensurePythonEnv(opts) {
|
|
|
91878
92259
|
import { createHash as createHash18 } from "node:crypto";
|
|
91879
92260
|
import {
|
|
91880
92261
|
copyFileSync as copyFileSync11,
|
|
91881
|
-
existsSync as
|
|
92262
|
+
existsSync as existsSync86,
|
|
91882
92263
|
mkdirSync as mkdirSync47,
|
|
91883
|
-
readFileSync as
|
|
92264
|
+
readFileSync as readFileSync77,
|
|
91884
92265
|
rmSync as rmSync15,
|
|
91885
92266
|
writeFileSync as writeFileSync33
|
|
91886
92267
|
} from "node:fs";
|
|
91887
|
-
import { dirname as
|
|
92268
|
+
import { dirname as dirname35, join as join87 } from "node:path";
|
|
91888
92269
|
import { homedir as homedir47 } from "node:os";
|
|
91889
92270
|
import { execFileSync as execFileSync25 } from "node:child_process";
|
|
91890
92271
|
|
|
@@ -91908,23 +92289,23 @@ var LOCKFILES_FOR = {
|
|
|
91908
92289
|
npm: ["package-lock.json"]
|
|
91909
92290
|
};
|
|
91910
92291
|
function defaultNodeCacheRoot() {
|
|
91911
|
-
return
|
|
92292
|
+
return join87(homedir47(), ".switchroom", "deps", "node");
|
|
91912
92293
|
}
|
|
91913
92294
|
function hashDepInputs(packageJsonPath) {
|
|
91914
|
-
const sourceDir =
|
|
92295
|
+
const sourceDir = dirname35(packageJsonPath);
|
|
91915
92296
|
const hasher = createHash18("sha256");
|
|
91916
92297
|
hasher.update(`package.json
|
|
91917
92298
|
`);
|
|
91918
|
-
hasher.update(
|
|
92299
|
+
hasher.update(readFileSync77(packageJsonPath));
|
|
91919
92300
|
for (const lockName of ALL_LOCKFILES) {
|
|
91920
|
-
const lockPath =
|
|
91921
|
-
if (
|
|
92301
|
+
const lockPath = join87(sourceDir, lockName);
|
|
92302
|
+
if (existsSync86(lockPath)) {
|
|
91922
92303
|
hasher.update(`
|
|
91923
92304
|
`);
|
|
91924
92305
|
hasher.update(lockName);
|
|
91925
92306
|
hasher.update(`
|
|
91926
92307
|
`);
|
|
91927
|
-
hasher.update(
|
|
92308
|
+
hasher.update(readFileSync77(lockPath));
|
|
91928
92309
|
}
|
|
91929
92310
|
}
|
|
91930
92311
|
return hasher.digest("hex");
|
|
@@ -91933,17 +92314,17 @@ function ensureNodeEnv(opts) {
|
|
|
91933
92314
|
const { skillName, packageJsonPath, force = false } = opts;
|
|
91934
92315
|
const cacheRoot = opts.cacheRoot ?? defaultNodeCacheRoot();
|
|
91935
92316
|
const installer = opts.installer ?? "bun";
|
|
91936
|
-
if (!
|
|
92317
|
+
if (!existsSync86(packageJsonPath)) {
|
|
91937
92318
|
throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
|
|
91938
92319
|
}
|
|
91939
|
-
const sourceDir =
|
|
91940
|
-
const envDir =
|
|
91941
|
-
const stampPath =
|
|
91942
|
-
const nodeModulesDir =
|
|
91943
|
-
const binDir =
|
|
92320
|
+
const sourceDir = dirname35(packageJsonPath);
|
|
92321
|
+
const envDir = join87(cacheRoot, skillName);
|
|
92322
|
+
const stampPath = join87(envDir, ".package.sha256");
|
|
92323
|
+
const nodeModulesDir = join87(envDir, "node_modules");
|
|
92324
|
+
const binDir = join87(nodeModulesDir, ".bin");
|
|
91944
92325
|
const targetHash = hashDepInputs(packageJsonPath);
|
|
91945
|
-
if (!force &&
|
|
91946
|
-
const existingHash =
|
|
92326
|
+
if (!force && existsSync86(stampPath) && existsSync86(nodeModulesDir)) {
|
|
92327
|
+
const existingHash = readFileSync77(stampPath, "utf8").trim();
|
|
91947
92328
|
if (existingHash === targetHash) {
|
|
91948
92329
|
return {
|
|
91949
92330
|
skillName,
|
|
@@ -91954,16 +92335,16 @@ function ensureNodeEnv(opts) {
|
|
|
91954
92335
|
};
|
|
91955
92336
|
}
|
|
91956
92337
|
}
|
|
91957
|
-
if (
|
|
92338
|
+
if (existsSync86(envDir)) {
|
|
91958
92339
|
rmSync15(envDir, { recursive: true, force: true });
|
|
91959
92340
|
}
|
|
91960
92341
|
mkdirSync47(envDir, { recursive: true });
|
|
91961
|
-
copyFileSync11(packageJsonPath,
|
|
92342
|
+
copyFileSync11(packageJsonPath, join87(envDir, "package.json"));
|
|
91962
92343
|
let copiedLockfile = false;
|
|
91963
92344
|
for (const lockName of LOCKFILES_FOR[installer]) {
|
|
91964
|
-
const lockPath =
|
|
91965
|
-
if (
|
|
91966
|
-
copyFileSync11(lockPath,
|
|
92345
|
+
const lockPath = join87(sourceDir, lockName);
|
|
92346
|
+
if (existsSync86(lockPath)) {
|
|
92347
|
+
copyFileSync11(lockPath, join87(envDir, lockName));
|
|
91967
92348
|
copiedLockfile = true;
|
|
91968
92349
|
}
|
|
91969
92350
|
}
|
|
@@ -91998,22 +92379,22 @@ function registerDepsCommand(program3) {
|
|
|
91998
92379
|
const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
|
|
91999
92380
|
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) => {
|
|
92000
92381
|
const skillsRoot = builtinSkillsRoot();
|
|
92001
|
-
if (!
|
|
92382
|
+
if (!existsSync87(skillsRoot)) {
|
|
92002
92383
|
console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
|
|
92003
92384
|
process.exit(1);
|
|
92004
92385
|
}
|
|
92005
|
-
const skillDir =
|
|
92006
|
-
if (!
|
|
92386
|
+
const skillDir = join88(skillsRoot, skill);
|
|
92387
|
+
if (!existsSync87(skillDir)) {
|
|
92007
92388
|
console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
|
|
92008
92389
|
process.exit(1);
|
|
92009
92390
|
}
|
|
92010
|
-
const requirementsPath =
|
|
92011
|
-
const packageJsonPath =
|
|
92012
|
-
const wantPython = opts.python ?? (!opts.python && !opts.node &&
|
|
92013
|
-
const wantNode = opts.node ?? (!opts.python && !opts.node &&
|
|
92391
|
+
const requirementsPath = join88(skillDir, "requirements.txt");
|
|
92392
|
+
const packageJsonPath = join88(skillDir, "package.json");
|
|
92393
|
+
const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync87(requirementsPath));
|
|
92394
|
+
const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync87(packageJsonPath));
|
|
92014
92395
|
let did = 0;
|
|
92015
92396
|
if (wantPython) {
|
|
92016
|
-
if (!
|
|
92397
|
+
if (!existsSync87(requirementsPath)) {
|
|
92017
92398
|
console.error(source_default.red(`Skill "${skill}" has no requirements.txt at ${requirementsPath}`));
|
|
92018
92399
|
process.exit(1);
|
|
92019
92400
|
}
|
|
@@ -92037,7 +92418,7 @@ function registerDepsCommand(program3) {
|
|
|
92037
92418
|
}
|
|
92038
92419
|
}
|
|
92039
92420
|
if (wantNode) {
|
|
92040
|
-
if (!
|
|
92421
|
+
if (!existsSync87(packageJsonPath)) {
|
|
92041
92422
|
console.error(source_default.red(`Skill "${skill}" has no package.json at ${packageJsonPath}`));
|
|
92042
92423
|
process.exit(1);
|
|
92043
92424
|
}
|
|
@@ -92070,9 +92451,9 @@ function registerDepsCommand(program3) {
|
|
|
92070
92451
|
// src/cli/workspace.ts
|
|
92071
92452
|
init_helpers();
|
|
92072
92453
|
init_loader();
|
|
92073
|
-
import { existsSync as
|
|
92454
|
+
import { existsSync as existsSync88 } from "node:fs";
|
|
92074
92455
|
import { resolve as resolve51, sep as sep4 } from "node:path";
|
|
92075
|
-
import { spawnSync as
|
|
92456
|
+
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
92076
92457
|
|
|
92077
92458
|
// src/agents/workspace.ts
|
|
92078
92459
|
import { readFile as readFile2, stat } from "node:fs/promises";
|
|
@@ -92785,7 +93166,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
92785
93166
|
process.exit(1);
|
|
92786
93167
|
}
|
|
92787
93168
|
const editor = process.env["EDITOR"] ?? process.env["VISUAL"] ?? "vi";
|
|
92788
|
-
const child =
|
|
93169
|
+
const child = spawnSync17(editor, [target], { stdio: "inherit" });
|
|
92789
93170
|
if (child.status !== 0 && child.status !== null) {
|
|
92790
93171
|
process.exit(child.status);
|
|
92791
93172
|
}
|
|
@@ -92847,12 +93228,12 @@ function registerWorkspaceCommand(program3) {
|
|
|
92847
93228
|
if (!dir)
|
|
92848
93229
|
return;
|
|
92849
93230
|
const gitDir = resolve51(dir, ".git");
|
|
92850
|
-
if (!
|
|
93231
|
+
if (!existsSync88(gitDir)) {
|
|
92851
93232
|
process.stdout.write(`Workspace is not a git repository. Re-run \`switchroom agent create ${agentName}\` ` + `or manually \`git init\` in ${dir} to enable versioning.
|
|
92852
93233
|
`);
|
|
92853
93234
|
return;
|
|
92854
93235
|
}
|
|
92855
|
-
const statusResult =
|
|
93236
|
+
const statusResult = spawnSync17("git", ["status", "--short"], {
|
|
92856
93237
|
cwd: dir,
|
|
92857
93238
|
encoding: "utf-8"
|
|
92858
93239
|
});
|
|
@@ -92867,7 +93248,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
92867
93248
|
return;
|
|
92868
93249
|
}
|
|
92869
93250
|
const message = opts.message || `checkpoint: ${new Date().toISOString()}`;
|
|
92870
|
-
const addResult =
|
|
93251
|
+
const addResult = spawnSync17("git", ["add", "-A"], {
|
|
92871
93252
|
cwd: dir,
|
|
92872
93253
|
encoding: "utf-8"
|
|
92873
93254
|
});
|
|
@@ -92876,7 +93257,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
92876
93257
|
`);
|
|
92877
93258
|
process.exit(1);
|
|
92878
93259
|
}
|
|
92879
|
-
const commitResult =
|
|
93260
|
+
const commitResult = spawnSync17("git", ["commit", "-m", message], {
|
|
92880
93261
|
cwd: dir,
|
|
92881
93262
|
encoding: "utf-8"
|
|
92882
93263
|
});
|
|
@@ -92885,7 +93266,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
92885
93266
|
`);
|
|
92886
93267
|
process.exit(1);
|
|
92887
93268
|
}
|
|
92888
|
-
const shaResult =
|
|
93269
|
+
const shaResult = spawnSync17("git", ["rev-parse", "--short", "HEAD"], {
|
|
92889
93270
|
cwd: dir,
|
|
92890
93271
|
encoding: "utf-8"
|
|
92891
93272
|
});
|
|
@@ -92901,12 +93282,12 @@ function registerWorkspaceCommand(program3) {
|
|
|
92901
93282
|
if (!dir)
|
|
92902
93283
|
return;
|
|
92903
93284
|
const gitDir = resolve51(dir, ".git");
|
|
92904
|
-
if (!
|
|
93285
|
+
if (!existsSync88(gitDir)) {
|
|
92905
93286
|
process.stdout.write(`Workspace is not a git repository.
|
|
92906
93287
|
`);
|
|
92907
93288
|
return;
|
|
92908
93289
|
}
|
|
92909
|
-
const child =
|
|
93290
|
+
const child = spawnSync17("git", ["status", "--short"], {
|
|
92910
93291
|
cwd: dir,
|
|
92911
93292
|
stdio: "inherit"
|
|
92912
93293
|
});
|
|
@@ -92926,7 +93307,7 @@ function resolveAgentWorkspaceDirOrExit(program3, agentName) {
|
|
|
92926
93307
|
const agentsDir = resolveAgentsDir(config);
|
|
92927
93308
|
const agentDir = resolve51(agentsDir, agentName);
|
|
92928
93309
|
const dir = resolveAgentWorkspaceDir(agentDir);
|
|
92929
|
-
if (!
|
|
93310
|
+
if (!existsSync88(dir)) {
|
|
92930
93311
|
process.stderr.write(`workspace: ${dir} does not exist yet. Run \`switchroom setup\` or \`switchroom agent scaffold ${agentName}\` to seed it.
|
|
92931
93312
|
`);
|
|
92932
93313
|
return;
|
|
@@ -92962,8 +93343,8 @@ function safeParseInt(value, fallback) {
|
|
|
92962
93343
|
init_helpers();
|
|
92963
93344
|
init_loader();
|
|
92964
93345
|
init_merge();
|
|
92965
|
-
import { copyFileSync as copyFileSync12, existsSync as
|
|
92966
|
-
import { join as
|
|
93346
|
+
import { copyFileSync as copyFileSync12, existsSync as existsSync89, readFileSync as readFileSync78, writeFileSync as writeFileSync34 } from "node:fs";
|
|
93347
|
+
import { join as join89, resolve as resolve52 } from "node:path";
|
|
92967
93348
|
init_scaffold();
|
|
92968
93349
|
init_profiles();
|
|
92969
93350
|
init_schema();
|
|
@@ -92980,7 +93361,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
92980
93361
|
const agentsDir = resolveAgentsDir(config);
|
|
92981
93362
|
const agentDir = resolve52(agentsDir, agentName);
|
|
92982
93363
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
92983
|
-
if (!
|
|
93364
|
+
if (!existsSync89(workspaceDir)) {
|
|
92984
93365
|
console.error(`soul: ${workspaceDir} does not exist yet. Run \`switchroom setup\` ` + `or \`switchroom agent scaffold ${agentName}\` to seed it.`);
|
|
92985
93366
|
process.exit(1);
|
|
92986
93367
|
}
|
|
@@ -92989,7 +93370,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
92989
93370
|
profileName,
|
|
92990
93371
|
profilePath,
|
|
92991
93372
|
workspaceDir,
|
|
92992
|
-
soulPath:
|
|
93373
|
+
soulPath: join89(workspaceDir, "SOUL.md"),
|
|
92993
93374
|
soul: merged.soul
|
|
92994
93375
|
};
|
|
92995
93376
|
}
|
|
@@ -93006,11 +93387,11 @@ function registerSoulCommand(program3) {
|
|
|
93006
93387
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
93007
93388
|
if (!t)
|
|
93008
93389
|
return;
|
|
93009
|
-
if (!
|
|
93390
|
+
if (!existsSync89(t.soulPath)) {
|
|
93010
93391
|
console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
|
|
93011
93392
|
process.exit(1);
|
|
93012
93393
|
}
|
|
93013
|
-
process.stdout.write(
|
|
93394
|
+
process.stdout.write(readFileSync78(t.soulPath, "utf-8"));
|
|
93014
93395
|
}));
|
|
93015
93396
|
cmd.command("reset <agent>").description("Re-seed SOUL.md from the agent's current profile " + "(backs the existing file up to SOUL.md.bak first)").option("-y, --yes", "Skip the confirmation prompt").action(withConfigError(async (agentName, opts) => {
|
|
93016
93397
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
@@ -93021,7 +93402,7 @@ function registerSoulCommand(program3) {
|
|
|
93021
93402
|
console.error(`soul: profile "${t.profileName}" ships no SOUL.md.hbs \u2014 ` + `nothing to re-seed from.`);
|
|
93022
93403
|
process.exit(1);
|
|
93023
93404
|
}
|
|
93024
|
-
const exists =
|
|
93405
|
+
const exists = existsSync89(t.soulPath);
|
|
93025
93406
|
if (exists && !opts.yes) {
|
|
93026
93407
|
if (!isInteractive()) {
|
|
93027
93408
|
console.error(`soul: ${t.soulPath} already exists. Re-run with --yes to ` + `replace it (the current file is backed up to SOUL.md.bak).`);
|
|
@@ -93036,7 +93417,7 @@ function registerSoulCommand(program3) {
|
|
|
93036
93417
|
let backupPath;
|
|
93037
93418
|
if (exists) {
|
|
93038
93419
|
backupPath = `${t.soulPath}.bak`;
|
|
93039
|
-
if (
|
|
93420
|
+
if (existsSync89(backupPath)) {
|
|
93040
93421
|
backupPath = `${t.soulPath}.bak.${Date.now()}`;
|
|
93041
93422
|
}
|
|
93042
93423
|
copyFileSync12(t.soulPath, backupPath);
|
|
@@ -93055,8 +93436,8 @@ function registerSoulCommand(program3) {
|
|
|
93055
93436
|
// src/cli/debug.ts
|
|
93056
93437
|
init_helpers();
|
|
93057
93438
|
init_loader();
|
|
93058
|
-
import { existsSync as
|
|
93059
|
-
import { resolve as resolve53, join as
|
|
93439
|
+
import { existsSync as existsSync90, readFileSync as readFileSync79, readdirSync as readdirSync30, statSync as statSync50 } from "node:fs";
|
|
93440
|
+
import { resolve as resolve53, join as join90 } from "node:path";
|
|
93060
93441
|
import { createHash as createHash19 } from "node:crypto";
|
|
93061
93442
|
init_merge();
|
|
93062
93443
|
init_hindsight2();
|
|
@@ -93067,11 +93448,11 @@ function estimateTokens(bytes) {
|
|
|
93067
93448
|
return Math.round(bytes / 3.7);
|
|
93068
93449
|
}
|
|
93069
93450
|
function readMcpServerNames(agentDir) {
|
|
93070
|
-
const mcpPath =
|
|
93071
|
-
if (!
|
|
93451
|
+
const mcpPath = join90(agentDir, ".mcp.json");
|
|
93452
|
+
if (!existsSync90(mcpPath))
|
|
93072
93453
|
return [];
|
|
93073
93454
|
try {
|
|
93074
|
-
const parsed = JSON.parse(
|
|
93455
|
+
const parsed = JSON.parse(readFileSync79(mcpPath, "utf-8"));
|
|
93075
93456
|
return Object.keys(parsed.mcpServers ?? {});
|
|
93076
93457
|
} catch {
|
|
93077
93458
|
return null;
|
|
@@ -93081,8 +93462,8 @@ function sha256(content) {
|
|
|
93081
93462
|
return createHash19("sha256").update(content).digest("hex").slice(0, 16);
|
|
93082
93463
|
}
|
|
93083
93464
|
function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
93084
|
-
const projectsDir =
|
|
93085
|
-
if (!
|
|
93465
|
+
const projectsDir = join90(claudeConfigDir, "projects");
|
|
93466
|
+
if (!existsSync90(projectsDir))
|
|
93086
93467
|
return;
|
|
93087
93468
|
try {
|
|
93088
93469
|
const entries = readdirSync30(projectsDir, { withFileTypes: true });
|
|
@@ -93090,9 +93471,9 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
93090
93471
|
for (const entry of entries) {
|
|
93091
93472
|
if (!entry.isDirectory())
|
|
93092
93473
|
continue;
|
|
93093
|
-
const projectPath =
|
|
93094
|
-
const transcriptPath =
|
|
93095
|
-
if (!
|
|
93474
|
+
const projectPath = join90(projectsDir, entry.name);
|
|
93475
|
+
const transcriptPath = join90(projectPath, "transcript.jsonl");
|
|
93476
|
+
if (!existsSync90(transcriptPath))
|
|
93096
93477
|
continue;
|
|
93097
93478
|
const stat3 = statSync50(transcriptPath);
|
|
93098
93479
|
if (!latest || stat3.mtimeMs > latest.mtime) {
|
|
@@ -93106,7 +93487,7 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
93106
93487
|
}
|
|
93107
93488
|
function extractLatestUserMessage(transcriptPath) {
|
|
93108
93489
|
try {
|
|
93109
|
-
const content =
|
|
93490
|
+
const content = readFileSync79(transcriptPath, "utf-8");
|
|
93110
93491
|
const lines = content.trim().split(`
|
|
93111
93492
|
`).filter(Boolean);
|
|
93112
93493
|
for (let i = lines.length - 1;i >= 0; i--) {
|
|
@@ -93155,16 +93536,16 @@ function registerDebugCommand(program3) {
|
|
|
93155
93536
|
}
|
|
93156
93537
|
const agentsDir = resolveAgentsDir(config);
|
|
93157
93538
|
const agentDir = resolve53(agentsDir, agentName);
|
|
93158
|
-
if (!
|
|
93539
|
+
if (!existsSync90(agentDir)) {
|
|
93159
93540
|
console.error(`Agent directory not found: ${agentDir}`);
|
|
93160
93541
|
process.exit(1);
|
|
93161
93542
|
}
|
|
93162
93543
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
93163
|
-
const claudeConfigDir =
|
|
93164
|
-
const claudeMdPath =
|
|
93165
|
-
const soulMdPath =
|
|
93166
|
-
const workspaceSoulMdPath =
|
|
93167
|
-
const handoffPath =
|
|
93544
|
+
const claudeConfigDir = join90(agentDir, ".claude");
|
|
93545
|
+
const claudeMdPath = join90(agentDir, "CLAUDE.md");
|
|
93546
|
+
const soulMdPath = join90(agentDir, "SOUL.md");
|
|
93547
|
+
const workspaceSoulMdPath = join90(workspaceDir, "SOUL.md");
|
|
93548
|
+
const handoffPath = join90(agentDir, ".handoff.md");
|
|
93168
93549
|
const lastN = parseInt(opts.last, 10);
|
|
93169
93550
|
if (isNaN(lastN) || lastN < 1) {
|
|
93170
93551
|
console.error("--last must be a positive integer");
|
|
@@ -93210,7 +93591,7 @@ function registerDebugCommand(program3) {
|
|
|
93210
93591
|
}
|
|
93211
93592
|
console.log(`=== Append System Prompt (per-session) ===
|
|
93212
93593
|
`);
|
|
93213
|
-
const handoffContent =
|
|
93594
|
+
const handoffContent = existsSync90(handoffPath) ? readFileSync79(handoffPath, "utf-8") : "";
|
|
93214
93595
|
if (handoffContent.trim().length > 0) {
|
|
93215
93596
|
console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
|
|
93216
93597
|
console.log(handoffContent);
|
|
@@ -93221,7 +93602,7 @@ function registerDebugCommand(program3) {
|
|
|
93221
93602
|
}
|
|
93222
93603
|
console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
|
|
93223
93604
|
`);
|
|
93224
|
-
const claudeMdContent =
|
|
93605
|
+
const claudeMdContent = existsSync90(claudeMdPath) ? readFileSync79(claudeMdPath, "utf-8") : "";
|
|
93225
93606
|
if (claudeMdContent.trim().length > 0) {
|
|
93226
93607
|
console.log(`(${formatBytes(claudeMdContent.length)})`);
|
|
93227
93608
|
console.log(claudeMdContent);
|
|
@@ -93232,7 +93613,7 @@ function registerDebugCommand(program3) {
|
|
|
93232
93613
|
}
|
|
93233
93614
|
console.log(`=== Persona (SOUL.md) ===
|
|
93234
93615
|
`);
|
|
93235
|
-
const soulMdContent =
|
|
93616
|
+
const soulMdContent = existsSync90(soulMdPath) ? readFileSync79(soulMdPath, "utf-8") : existsSync90(workspaceSoulMdPath) ? readFileSync79(workspaceSoulMdPath, "utf-8") : "";
|
|
93236
93617
|
if (soulMdContent.trim().length > 0) {
|
|
93237
93618
|
console.log(`(${formatBytes(soulMdContent.length)})`);
|
|
93238
93619
|
console.log(soulMdContent);
|
|
@@ -93293,11 +93674,11 @@ function registerDebugCommand(program3) {
|
|
|
93293
93674
|
const soulMdBytes = soulMdContent.length;
|
|
93294
93675
|
const perTurnBytes = dynamicResult.concatenated.length;
|
|
93295
93676
|
const userBytes = userMessage?.text.length ?? 0;
|
|
93296
|
-
const fleetDir =
|
|
93297
|
-
const fleetInvPath =
|
|
93298
|
-
const fleetClaudePath =
|
|
93299
|
-
const fleetInvBytes =
|
|
93300
|
-
const fleetClaudeBytes =
|
|
93677
|
+
const fleetDir = join90(agentsDir, "..", "fleet");
|
|
93678
|
+
const fleetInvPath = join90(fleetDir, "switchroom-invariants.md");
|
|
93679
|
+
const fleetClaudePath = join90(fleetDir, "CLAUDE.md");
|
|
93680
|
+
const fleetInvBytes = existsSync90(fleetInvPath) ? readFileSync79(fleetInvPath, "utf-8").length : 0;
|
|
93681
|
+
const fleetClaudeBytes = existsSync90(fleetClaudePath) ? readFileSync79(fleetClaudePath, "utf-8").length : 0;
|
|
93301
93682
|
const fleetBytes = fleetInvBytes + fleetClaudeBytes;
|
|
93302
93683
|
const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
|
|
93303
93684
|
console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
|
|
@@ -93330,8 +93711,8 @@ init_source();
|
|
|
93330
93711
|
|
|
93331
93712
|
// src/worktree/claim.ts
|
|
93332
93713
|
import { execFileSync as execFileSync26 } from "node:child_process";
|
|
93333
|
-
import { closeSync as closeSync16, mkdirSync as mkdirSync49, openSync as openSync16, existsSync as
|
|
93334
|
-
import { join as
|
|
93714
|
+
import { closeSync as closeSync16, mkdirSync as mkdirSync49, openSync as openSync16, existsSync as existsSync92, unlinkSync as unlinkSync18 } from "node:fs";
|
|
93715
|
+
import { join as join92, resolve as resolve55 } from "node:path";
|
|
93335
93716
|
import { homedir as homedir50 } from "node:os";
|
|
93336
93717
|
import { randomBytes as randomBytes14 } from "node:crypto";
|
|
93337
93718
|
|
|
@@ -93339,19 +93720,19 @@ import { randomBytes as randomBytes14 } from "node:crypto";
|
|
|
93339
93720
|
import {
|
|
93340
93721
|
mkdirSync as mkdirSync48,
|
|
93341
93722
|
writeFileSync as writeFileSync35,
|
|
93342
|
-
readFileSync as
|
|
93723
|
+
readFileSync as readFileSync80,
|
|
93343
93724
|
readdirSync as readdirSync31,
|
|
93344
93725
|
unlinkSync as unlinkSync17,
|
|
93345
|
-
existsSync as
|
|
93726
|
+
existsSync as existsSync91,
|
|
93346
93727
|
renameSync as renameSync21
|
|
93347
93728
|
} from "node:fs";
|
|
93348
|
-
import { join as
|
|
93729
|
+
import { join as join91, resolve as resolve54 } from "node:path";
|
|
93349
93730
|
import { homedir as homedir49 } from "node:os";
|
|
93350
93731
|
function registryDir() {
|
|
93351
|
-
return resolve54(process.env.SWITCHROOM_WORKTREE_DIR ??
|
|
93732
|
+
return resolve54(process.env.SWITCHROOM_WORKTREE_DIR ?? join91(homedir49(), ".switchroom", "worktrees"));
|
|
93352
93733
|
}
|
|
93353
93734
|
function recordPath(id) {
|
|
93354
|
-
return
|
|
93735
|
+
return join91(registryDir(), `${id}.json`);
|
|
93355
93736
|
}
|
|
93356
93737
|
function ensureDir2() {
|
|
93357
93738
|
mkdirSync48(registryDir(), { recursive: true });
|
|
@@ -93367,7 +93748,7 @@ function writeRecord(record2) {
|
|
|
93367
93748
|
function readRecord(id) {
|
|
93368
93749
|
const path10 = recordPath(id);
|
|
93369
93750
|
try {
|
|
93370
|
-
const raw =
|
|
93751
|
+
const raw = readFileSync80(path10, "utf8");
|
|
93371
93752
|
return JSON.parse(raw);
|
|
93372
93753
|
} catch {
|
|
93373
93754
|
return null;
|
|
@@ -93402,7 +93783,7 @@ function acquireRepoLock(repoPath) {
|
|
|
93402
93783
|
const lockDir = registryDir();
|
|
93403
93784
|
mkdirSync49(lockDir, { recursive: true });
|
|
93404
93785
|
const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
|
|
93405
|
-
const lockPath =
|
|
93786
|
+
const lockPath = join92(lockDir, `.lock-${lockName}`);
|
|
93406
93787
|
const deadline = Date.now() + 5000;
|
|
93407
93788
|
let fd = null;
|
|
93408
93789
|
while (fd === null) {
|
|
@@ -93429,7 +93810,7 @@ function acquireRepoLock(repoPath) {
|
|
|
93429
93810
|
}
|
|
93430
93811
|
var DEFAULT_CONCURRENCY = 5;
|
|
93431
93812
|
function worktreesBaseDir() {
|
|
93432
|
-
return resolve55(process.env.SWITCHROOM_WORKTREE_BASE ??
|
|
93813
|
+
return resolve55(process.env.SWITCHROOM_WORKTREE_BASE ?? join92(homedir50(), ".switchroom", "worktree-checkouts"));
|
|
93433
93814
|
}
|
|
93434
93815
|
function shortId() {
|
|
93435
93816
|
return randomBytes14(4).toString("hex");
|
|
@@ -93451,12 +93832,12 @@ function resolveRepoPath(repo, codeRepos) {
|
|
|
93451
93832
|
}
|
|
93452
93833
|
function expandHome(p) {
|
|
93453
93834
|
if (p.startsWith("~/"))
|
|
93454
|
-
return
|
|
93835
|
+
return join92(homedir50(), p.slice(2));
|
|
93455
93836
|
return p;
|
|
93456
93837
|
}
|
|
93457
93838
|
async function claimWorktree(input, codeRepos) {
|
|
93458
93839
|
const repoPath = resolveRepoPath(input.repo, codeRepos);
|
|
93459
|
-
if (!
|
|
93840
|
+
if (!existsSync92(repoPath)) {
|
|
93460
93841
|
throw new Error(`Repository path does not exist: ${repoPath}`);
|
|
93461
93842
|
}
|
|
93462
93843
|
let concurrencyCap = DEFAULT_CONCURRENCY;
|
|
@@ -93479,7 +93860,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
93479
93860
|
branch = `task/${taskSuffix}-${id}`;
|
|
93480
93861
|
const baseDir = worktreesBaseDir();
|
|
93481
93862
|
mkdirSync49(baseDir, { recursive: true });
|
|
93482
|
-
worktreePath =
|
|
93863
|
+
worktreePath = join92(baseDir, `${id}-${taskSuffix}`);
|
|
93483
93864
|
const ambientOwner = process.env.SWITCHROOM_AGENT_NAME;
|
|
93484
93865
|
const ownerAgent = input.ownerAgent ?? (ambientOwner != null && ambientOwner !== "" ? ambientOwner : undefined);
|
|
93485
93866
|
const now = new Date().toISOString();
|
|
@@ -93512,7 +93893,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
93512
93893
|
|
|
93513
93894
|
// src/worktree/release.ts
|
|
93514
93895
|
import { execFileSync as execFileSync27 } from "node:child_process";
|
|
93515
|
-
import { existsSync as
|
|
93896
|
+
import { existsSync as existsSync93 } from "node:fs";
|
|
93516
93897
|
function releaseWorktree(input) {
|
|
93517
93898
|
const { id } = input;
|
|
93518
93899
|
const record2 = readRecord(id);
|
|
@@ -93520,7 +93901,7 @@ function releaseWorktree(input) {
|
|
|
93520
93901
|
return { released: true };
|
|
93521
93902
|
}
|
|
93522
93903
|
let gitSuccess = true;
|
|
93523
|
-
if (
|
|
93904
|
+
if (existsSync93(record2.path)) {
|
|
93524
93905
|
try {
|
|
93525
93906
|
execFileSync27("git", ["worktree", "remove", "--force", record2.path], {
|
|
93526
93907
|
cwd: record2.repo,
|
|
@@ -93559,7 +93940,7 @@ function listWorktrees() {
|
|
|
93559
93940
|
|
|
93560
93941
|
// src/worktree/reaper.ts
|
|
93561
93942
|
import { execFileSync as execFileSync28 } from "node:child_process";
|
|
93562
|
-
import { existsSync as
|
|
93943
|
+
import { existsSync as existsSync94 } from "node:fs";
|
|
93563
93944
|
var STALE_THRESHOLD_MS = 10 * 60 * 1000;
|
|
93564
93945
|
function reapSkipReasonText(action) {
|
|
93565
93946
|
switch (action) {
|
|
@@ -93620,7 +94001,7 @@ function planReaper(nowMs, deps = {}) {
|
|
|
93620
94001
|
const plan = [];
|
|
93621
94002
|
for (const record2 of listRecords()) {
|
|
93622
94003
|
const heartbeatAge = now - new Date(record2.heartbeatAt).getTime();
|
|
93623
|
-
const worktreeExists =
|
|
94004
|
+
const worktreeExists = existsSync94(record2.path);
|
|
93624
94005
|
if (!worktreeExists) {
|
|
93625
94006
|
plan.push({
|
|
93626
94007
|
record: record2,
|
|
@@ -93701,8 +94082,8 @@ function runReaper(nowMs, deps = {}) {
|
|
|
93701
94082
|
// src/worktree/gc.ts
|
|
93702
94083
|
import { execFileSync as execFileSync29 } from "node:child_process";
|
|
93703
94084
|
import {
|
|
93704
|
-
existsSync as
|
|
93705
|
-
readFileSync as
|
|
94085
|
+
existsSync as existsSync95,
|
|
94086
|
+
readFileSync as readFileSync81,
|
|
93706
94087
|
readdirSync as readdirSync32,
|
|
93707
94088
|
statSync as statSync51,
|
|
93708
94089
|
renameSync as renameSync22,
|
|
@@ -93710,7 +94091,7 @@ import {
|
|
|
93710
94091
|
rmSync as rmSync16
|
|
93711
94092
|
} from "node:fs";
|
|
93712
94093
|
import { homedir as homedir51 } from "node:os";
|
|
93713
|
-
import { join as
|
|
94094
|
+
import { join as join93, resolve as resolve56 } from "node:path";
|
|
93714
94095
|
function parseGitdirPointer(dotGitFileContents) {
|
|
93715
94096
|
const m = /^gitdir:\s*(.+?)\s*$/m.exec(dotGitFileContents);
|
|
93716
94097
|
return m ? m[1] : null;
|
|
@@ -93825,17 +94206,17 @@ function defaultPrSignal(repo, branch, exec) {
|
|
|
93825
94206
|
}
|
|
93826
94207
|
}
|
|
93827
94208
|
function trashRoot() {
|
|
93828
|
-
return resolve56(process.env.SWITCHROOM_WORKTREE_TRASH ??
|
|
94209
|
+
return resolve56(process.env.SWITCHROOM_WORKTREE_TRASH ?? join93(homedir51(), ".switchroom", "worktree-gc-trash"));
|
|
93829
94210
|
}
|
|
93830
94211
|
function planGc(roots, deps = {}) {
|
|
93831
|
-
const exists = deps.existsSync ??
|
|
94212
|
+
const exists = deps.existsSync ?? existsSync95;
|
|
93832
94213
|
const readDir = deps.readDir ?? ((p) => readdirSync32(p));
|
|
93833
|
-
const readFile4 = deps.readFile ?? ((p) =>
|
|
94214
|
+
const readFile4 = deps.readFile ?? ((p) => readFileSync81(p, "utf8"));
|
|
93834
94215
|
const stat3 = deps.stat ?? ((p) => statSync51(p));
|
|
93835
94216
|
const exec = deps.exec ?? defaultExec2;
|
|
93836
94217
|
const prSignal = deps.prSignal ?? ((repo, branch) => defaultPrSignal(repo, branch, exec));
|
|
93837
94218
|
const stamp = deps.dateStamp ?? "undated";
|
|
93838
|
-
const trash =
|
|
94219
|
+
const trash = join93(trashRoot(), stamp);
|
|
93839
94220
|
let claimed;
|
|
93840
94221
|
try {
|
|
93841
94222
|
claimed = new Set(listRecords().map((r) => resolve56(r.path)));
|
|
@@ -93871,10 +94252,10 @@ function planGc(roots, deps = {}) {
|
|
|
93871
94252
|
continue;
|
|
93872
94253
|
}
|
|
93873
94254
|
for (const name of entries) {
|
|
93874
|
-
const dir =
|
|
94255
|
+
const dir = join93(root, name);
|
|
93875
94256
|
if (isEphemeralPath(dir))
|
|
93876
94257
|
continue;
|
|
93877
|
-
const dotGit =
|
|
94258
|
+
const dotGit = join93(dir, ".git");
|
|
93878
94259
|
if (!exists(dotGit))
|
|
93879
94260
|
continue;
|
|
93880
94261
|
let st;
|
|
@@ -93903,7 +94284,7 @@ function planGc(roots, deps = {}) {
|
|
|
93903
94284
|
ownerRepos.add(repoRoot);
|
|
93904
94285
|
if (exists(ptr))
|
|
93905
94286
|
continue;
|
|
93906
|
-
orphans.push({ dir, owner: repoRoot, dest:
|
|
94287
|
+
orphans.push({ dir, owner: repoRoot, dest: join93(trash, name) });
|
|
93907
94288
|
}
|
|
93908
94289
|
}
|
|
93909
94290
|
const registered = [];
|
|
@@ -94007,14 +94388,14 @@ function selectPurgeTargets(entries, olderThanDays) {
|
|
|
94007
94388
|
return entries.filter((e) => e.ageDays >= olderThanDays).map((e) => e.path);
|
|
94008
94389
|
}
|
|
94009
94390
|
function listTrashEntries(nowMs, deps = {}) {
|
|
94010
|
-
const exists = deps.existsSync ??
|
|
94391
|
+
const exists = deps.existsSync ?? existsSync95;
|
|
94011
94392
|
const readDir = deps.readDir ?? ((p) => readdirSync32(p));
|
|
94012
94393
|
const root = trashRoot();
|
|
94013
94394
|
if (!exists(root))
|
|
94014
94395
|
return [];
|
|
94015
94396
|
const out = [];
|
|
94016
94397
|
for (const stamp of readDir(root)) {
|
|
94017
|
-
const stampDir =
|
|
94398
|
+
const stampDir = join93(root, stamp);
|
|
94018
94399
|
let names;
|
|
94019
94400
|
try {
|
|
94020
94401
|
names = readDir(stampDir);
|
|
@@ -94022,7 +94403,7 @@ function listTrashEntries(nowMs, deps = {}) {
|
|
|
94022
94403
|
continue;
|
|
94023
94404
|
}
|
|
94024
94405
|
for (const name of names) {
|
|
94025
|
-
const p =
|
|
94406
|
+
const p = join93(stampDir, name);
|
|
94026
94407
|
let mtimeMs = nowMs;
|
|
94027
94408
|
try {
|
|
94028
94409
|
mtimeMs = statSync51(p).mtimeMs;
|
|
@@ -94046,7 +94427,7 @@ function purgeTrash(paths) {
|
|
|
94046
94427
|
return { deleted, errors: errors2 };
|
|
94047
94428
|
}
|
|
94048
94429
|
function defaultRoots() {
|
|
94049
|
-
return [
|
|
94430
|
+
return [join93(homedir51(), "code")];
|
|
94050
94431
|
}
|
|
94051
94432
|
|
|
94052
94433
|
// src/cli/worktree.ts
|
|
@@ -94274,7 +94655,7 @@ import {
|
|
|
94274
94655
|
rmSync as rmSync17,
|
|
94275
94656
|
writeFileSync as writeFileSync36
|
|
94276
94657
|
} from "node:fs";
|
|
94277
|
-
import { join as
|
|
94658
|
+
import { join as join94 } from "node:path";
|
|
94278
94659
|
function encodeCredentialsFilename(email) {
|
|
94279
94660
|
const SAFE = new Set([
|
|
94280
94661
|
..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
|
@@ -94464,16 +94845,16 @@ function resolveCredentialsDir(env2) {
|
|
|
94464
94845
|
if (explicit && explicit.length > 0)
|
|
94465
94846
|
return explicit;
|
|
94466
94847
|
const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
|
|
94467
|
-
return
|
|
94848
|
+
return join94(stateBase, "google-workspace-mcp", "credentials");
|
|
94468
94849
|
}
|
|
94469
94850
|
function writeSeedFile(dir, email, seed) {
|
|
94470
94851
|
mkdirSync51(dir, { recursive: true, mode: 448 });
|
|
94471
94852
|
chmodSync14(dir, 448);
|
|
94472
94853
|
for (const name of readdirSync33(dir)) {
|
|
94473
|
-
rmSync17(
|
|
94854
|
+
rmSync17(join94(dir, name), { force: true, recursive: true });
|
|
94474
94855
|
}
|
|
94475
94856
|
const filename = encodeCredentialsFilename(email);
|
|
94476
|
-
const filePath =
|
|
94857
|
+
const filePath = join94(dir, filename);
|
|
94477
94858
|
writeFileSync36(filePath, JSON.stringify(seed), { mode: 384 });
|
|
94478
94859
|
chmodSync14(filePath, 384);
|
|
94479
94860
|
return filePath;
|
|
@@ -94633,7 +95014,7 @@ function registerDriveMcpLauncherCommand(program3) {
|
|
|
94633
95014
|
init_scaffold_integration();
|
|
94634
95015
|
import { spawn as spawn6 } from "node:child_process";
|
|
94635
95016
|
import { writeFileSync as writeFileSync37, mkdirSync as mkdirSync52 } from "node:fs";
|
|
94636
|
-
import { dirname as
|
|
95017
|
+
import { dirname as dirname36, join as join95 } from "node:path";
|
|
94637
95018
|
var SOFTERIA_TOKEN_ENV = "MS365_MCP_OAUTH_TOKEN";
|
|
94638
95019
|
var DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
|
|
94639
95020
|
var MAX_REFRESH_INTERVAL_MS = 60 * 60 * 1000;
|
|
@@ -94669,7 +95050,7 @@ function computeRefreshDelayMs(expiresAt, now, leadMs = DEFAULT_REFRESH_LEAD_MS)
|
|
|
94669
95050
|
function writeRefreshHeartbeat(agentName, data, account) {
|
|
94670
95051
|
const path10 = heartbeatPath(agentName, account);
|
|
94671
95052
|
try {
|
|
94672
|
-
mkdirSync52(
|
|
95053
|
+
mkdirSync52(dirname36(path10), { recursive: true });
|
|
94673
95054
|
writeFileSync37(path10, JSON.stringify(data, null, 2), { mode: 420 });
|
|
94674
95055
|
} catch {}
|
|
94675
95056
|
}
|
|
@@ -94678,7 +95059,7 @@ function heartbeatPath(agentName, account) {
|
|
|
94678
95059
|
const override = process.env.SWITCHROOM_M365_HEARTBEAT_DIR;
|
|
94679
95060
|
if (override) {
|
|
94680
95061
|
const base = slug ? `m365-launcher-${agentName}-${slug}` : `m365-launcher-${agentName}`;
|
|
94681
|
-
return
|
|
95062
|
+
return join95(override, `${base}.heartbeat.json`);
|
|
94682
95063
|
}
|
|
94683
95064
|
return slug ? `/state/agent/m365-launcher-${slug}.heartbeat.json` : "/state/agent/m365-launcher.heartbeat.json";
|
|
94684
95065
|
}
|
|
@@ -94910,8 +95291,8 @@ function registerM365McpLauncherCommand(program3) {
|
|
|
94910
95291
|
// src/cli/notion-mcp-launcher.ts
|
|
94911
95292
|
init_scaffold_integration();
|
|
94912
95293
|
import { spawn as spawn7 } from "node:child_process";
|
|
94913
|
-
import { existsSync as
|
|
94914
|
-
import { dirname as
|
|
95294
|
+
import { existsSync as existsSync96, mkdirSync as mkdirSync53, writeFileSync as writeFileSync38 } from "node:fs";
|
|
95295
|
+
import { dirname as dirname37 } from "node:path";
|
|
94915
95296
|
var HEARTBEAT_WRITE_INTERVAL_MS = 30 * 1000;
|
|
94916
95297
|
var DEFAULT_HEARTBEAT_PATH = "/state/agent/notion-launcher.heartbeat.json";
|
|
94917
95298
|
var DEFAULT_VAULT_KEY = "notion/integration-token";
|
|
@@ -94921,8 +95302,8 @@ function buildNotionMcpArgs(opts) {
|
|
|
94921
95302
|
}
|
|
94922
95303
|
function defaultWriteHeartbeat(path10, contents) {
|
|
94923
95304
|
try {
|
|
94924
|
-
const dir =
|
|
94925
|
-
if (!
|
|
95305
|
+
const dir = dirname37(path10);
|
|
95306
|
+
if (!existsSync96(dir))
|
|
94926
95307
|
mkdirSync53(dir, { recursive: true });
|
|
94927
95308
|
writeFileSync38(path10, contents);
|
|
94928
95309
|
} catch {}
|
|
@@ -95034,9 +95415,9 @@ function registerNotionMcpLauncherCommand(program3) {
|
|
|
95034
95415
|
|
|
95035
95416
|
// src/cli/hindsight-mcp-shim.ts
|
|
95036
95417
|
init_hindsight();
|
|
95037
|
-
import { mkdirSync as mkdirSync54, readFileSync as
|
|
95418
|
+
import { mkdirSync as mkdirSync54, readFileSync as readFileSync82, renameSync as renameSync23, writeFileSync as writeFileSync39 } from "node:fs";
|
|
95038
95419
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
95039
|
-
import { join as
|
|
95420
|
+
import { join as join96 } from "node:path";
|
|
95040
95421
|
import { createInterface as createInterface6 } from "node:readline";
|
|
95041
95422
|
var SHIM_SUPPORTED_PROTOCOL_VERSIONS = [
|
|
95042
95423
|
"2025-06-18",
|
|
@@ -95262,12 +95643,12 @@ class HindsightShim {
|
|
|
95262
95643
|
`));
|
|
95263
95644
|
}
|
|
95264
95645
|
get cachePath() {
|
|
95265
|
-
return
|
|
95646
|
+
return join96(this.opts.cacheDir, TOOLS_CACHE_FILENAME);
|
|
95266
95647
|
}
|
|
95267
95648
|
writeCache(result) {
|
|
95268
95649
|
try {
|
|
95269
95650
|
mkdirSync54(this.opts.cacheDir, { recursive: true });
|
|
95270
|
-
const tmp =
|
|
95651
|
+
const tmp = join96(this.opts.cacheDir, `.${TOOLS_CACHE_FILENAME}.${process.pid}.tmp`);
|
|
95271
95652
|
writeFileSync39(tmp, JSON.stringify(result, null, 2) + `
|
|
95272
95653
|
`);
|
|
95273
95654
|
renameSync23(tmp, this.cachePath);
|
|
@@ -95277,7 +95658,7 @@ class HindsightShim {
|
|
|
95277
95658
|
}
|
|
95278
95659
|
readCache() {
|
|
95279
95660
|
try {
|
|
95280
|
-
const parsed = JSON.parse(
|
|
95661
|
+
const parsed = JSON.parse(readFileSync82(this.cachePath, "utf-8"));
|
|
95281
95662
|
if (Array.isArray(parsed.tools))
|
|
95282
95663
|
return parsed;
|
|
95283
95664
|
return null;
|
|
@@ -95440,7 +95821,7 @@ function resolveShimOptionsFromEnv(env2) {
|
|
|
95440
95821
|
return {
|
|
95441
95822
|
url: env2.HINDSIGHT_MCP_URL || HINDSIGHT_DEFAULT_MCP_URL,
|
|
95442
95823
|
bankId: env2.HINDSIGHT_BANK_ID || "",
|
|
95443
|
-
cacheDir: env2.HINDSIGHT_SHIM_CACHE_DIR ||
|
|
95824
|
+
cacheDir: env2.HINDSIGHT_SHIM_CACHE_DIR || join96(home2, ".hindsight-shim")
|
|
95444
95825
|
};
|
|
95445
95826
|
}
|
|
95446
95827
|
function registerHindsightMcpShimCommand(program3) {
|
|
@@ -95453,7 +95834,7 @@ function registerHindsightMcpShimCommand(program3) {
|
|
|
95453
95834
|
|
|
95454
95835
|
// src/cli/deliver-file.ts
|
|
95455
95836
|
init_client2();
|
|
95456
|
-
import { readFileSync as
|
|
95837
|
+
import { readFileSync as readFileSync83, statSync as statSync52 } from "node:fs";
|
|
95457
95838
|
import { basename as basename13 } from "node:path";
|
|
95458
95839
|
|
|
95459
95840
|
// src/delivery/onedrive.ts
|
|
@@ -95793,7 +96174,7 @@ async function defaultResolveProvider() {
|
|
|
95793
96174
|
async function runDeliverFile(localPath, deps = {}) {
|
|
95794
96175
|
const agentName = safeAgentName(deps.agentName ?? process.env.SWITCHROOM_AGENT_NAME);
|
|
95795
96176
|
const sizeOf = deps.fileSize ?? ((p) => statSync52(p).size);
|
|
95796
|
-
const read = deps.readFile ?? ((p) => new Uint8Array(
|
|
96177
|
+
const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync83(p)));
|
|
95797
96178
|
const resolveProvider = deps.resolveProvider ?? defaultResolveProvider;
|
|
95798
96179
|
let size;
|
|
95799
96180
|
try {
|
|
@@ -96114,8 +96495,8 @@ function runRedactStdin() {
|
|
|
96114
96495
|
}
|
|
96115
96496
|
|
|
96116
96497
|
// src/cli/status-ask.ts
|
|
96117
|
-
import { readFileSync as
|
|
96118
|
-
import { join as
|
|
96498
|
+
import { readFileSync as readFileSync84, existsSync as existsSync97, readdirSync as readdirSync34 } from "node:fs";
|
|
96499
|
+
import { join as join97 } from "node:path";
|
|
96119
96500
|
import { homedir as homedir52 } from "node:os";
|
|
96120
96501
|
|
|
96121
96502
|
// src/status-ask/report.ts
|
|
@@ -96390,7 +96771,7 @@ function runReport(opts) {
|
|
|
96390
96771
|
for (const src of sources) {
|
|
96391
96772
|
let content;
|
|
96392
96773
|
try {
|
|
96393
|
-
content =
|
|
96774
|
+
content = readFileSync84(src.path, "utf-8");
|
|
96394
96775
|
} catch (err) {
|
|
96395
96776
|
process.stderr.write(`status-ask report: cannot read ${src.path}: ${err instanceof Error ? err.message : String(err)}
|
|
96396
96777
|
`);
|
|
@@ -96437,7 +96818,7 @@ function runReport(opts) {
|
|
|
96437
96818
|
function resolveSources(explicitPath) {
|
|
96438
96819
|
if (explicitPath != null && explicitPath.trim() !== "") {
|
|
96439
96820
|
const trimmed = explicitPath.trim();
|
|
96440
|
-
if (!
|
|
96821
|
+
if (!existsSync97(trimmed)) {
|
|
96441
96822
|
process.stderr.write(`status-ask report: ${trimmed}: file not found
|
|
96442
96823
|
`);
|
|
96443
96824
|
process.exit(1);
|
|
@@ -96451,9 +96832,9 @@ function resolveSources(explicitPath) {
|
|
|
96451
96832
|
const config = loadConfig();
|
|
96452
96833
|
agentsDir = resolveAgentsDir(config);
|
|
96453
96834
|
} catch {
|
|
96454
|
-
agentsDir =
|
|
96835
|
+
agentsDir = join97(homedir52(), ".switchroom", "agents");
|
|
96455
96836
|
}
|
|
96456
|
-
if (!
|
|
96837
|
+
if (!existsSync97(agentsDir))
|
|
96457
96838
|
return [];
|
|
96458
96839
|
const sources = [];
|
|
96459
96840
|
let entries;
|
|
@@ -96463,8 +96844,8 @@ function resolveSources(explicitPath) {
|
|
|
96463
96844
|
return [];
|
|
96464
96845
|
}
|
|
96465
96846
|
for (const name of entries) {
|
|
96466
|
-
const path10 =
|
|
96467
|
-
if (
|
|
96847
|
+
const path10 = join97(agentsDir, name, "runtime-metrics.jsonl");
|
|
96848
|
+
if (existsSync97(path10)) {
|
|
96468
96849
|
sources.push({ path: path10, agent: name });
|
|
96469
96850
|
}
|
|
96470
96851
|
}
|
|
@@ -96494,32 +96875,32 @@ var import_yaml21 = __toESM(require_dist(), 1);
|
|
|
96494
96875
|
init_paths();
|
|
96495
96876
|
import {
|
|
96496
96877
|
closeSync as closeSync17,
|
|
96497
|
-
existsSync as
|
|
96878
|
+
existsSync as existsSync98,
|
|
96498
96879
|
fsyncSync as fsyncSync8,
|
|
96499
96880
|
mkdirSync as mkdirSync55,
|
|
96500
96881
|
openSync as openSync17,
|
|
96501
96882
|
readdirSync as readdirSync35,
|
|
96502
|
-
readFileSync as
|
|
96883
|
+
readFileSync as readFileSync85,
|
|
96503
96884
|
renameSync as renameSync24,
|
|
96504
96885
|
statSync as statSync53,
|
|
96505
96886
|
unlinkSync as unlinkSync19,
|
|
96506
96887
|
writeSync as writeSync10
|
|
96507
96888
|
} from "node:fs";
|
|
96508
|
-
import { join as
|
|
96889
|
+
import { join as join98, resolve as resolve57 } from "node:path";
|
|
96509
96890
|
var STAGING_SUBDIR = ".staging";
|
|
96510
96891
|
function overlayPathsFor(agent, opts = {}) {
|
|
96511
96892
|
const base = opts.root ? resolve57(opts.root, agent) : resolve57(resolveDualPath(`~/.switchroom/agents/${agent}`));
|
|
96512
|
-
const scheduleDir =
|
|
96513
|
-
const scheduleStagingDir =
|
|
96514
|
-
const skillsDir =
|
|
96515
|
-
const skillsStagingDir =
|
|
96893
|
+
const scheduleDir = join98(base, "schedule.d");
|
|
96894
|
+
const scheduleStagingDir = join98(scheduleDir, STAGING_SUBDIR);
|
|
96895
|
+
const skillsDir = join98(base, "skills.d");
|
|
96896
|
+
const skillsStagingDir = join98(skillsDir, STAGING_SUBDIR);
|
|
96516
96897
|
return {
|
|
96517
96898
|
agentRoot: base,
|
|
96518
96899
|
scheduleDir,
|
|
96519
96900
|
scheduleStagingDir,
|
|
96520
96901
|
skillsDir,
|
|
96521
96902
|
skillsStagingDir,
|
|
96522
|
-
lockPath:
|
|
96903
|
+
lockPath: join98(base, ".lock"),
|
|
96523
96904
|
stagingDir: scheduleStagingDir
|
|
96524
96905
|
};
|
|
96525
96906
|
}
|
|
@@ -96573,8 +96954,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
96573
96954
|
const paths = overlayPathsFor(agent, opts);
|
|
96574
96955
|
return withAgentLock(paths, () => {
|
|
96575
96956
|
ensureDirs(paths);
|
|
96576
|
-
const stagingPath =
|
|
96577
|
-
const finalPath =
|
|
96957
|
+
const stagingPath = join98(paths.scheduleStagingDir, `${slug}.yaml`);
|
|
96958
|
+
const finalPath = join98(paths.scheduleDir, `${slug}.yaml`);
|
|
96578
96959
|
const fd = openSync17(stagingPath, "w", 384);
|
|
96579
96960
|
try {
|
|
96580
96961
|
writeSync10(fd, yamlText);
|
|
@@ -96590,8 +96971,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
96590
96971
|
const paths = overlayPathsFor(agent, opts);
|
|
96591
96972
|
return withAgentLock(paths, () => {
|
|
96592
96973
|
ensureSkillsDirs(paths);
|
|
96593
|
-
const stagingPath =
|
|
96594
|
-
const finalPath =
|
|
96974
|
+
const stagingPath = join98(paths.skillsStagingDir, `${slug}.yaml`);
|
|
96975
|
+
const finalPath = join98(paths.skillsDir, `${slug}.yaml`);
|
|
96595
96976
|
const fd = openSync17(stagingPath, "w", 384);
|
|
96596
96977
|
try {
|
|
96597
96978
|
writeSync10(fd, yamlText);
|
|
@@ -96606,8 +96987,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
96606
96987
|
function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
96607
96988
|
const paths = overlayPathsFor(agent, opts);
|
|
96608
96989
|
return withAgentLock(paths, () => {
|
|
96609
|
-
const finalPath =
|
|
96610
|
-
if (!
|
|
96990
|
+
const finalPath = join98(paths.skillsDir, `${slug}.yaml`);
|
|
96991
|
+
if (!existsSync98(finalPath))
|
|
96611
96992
|
return false;
|
|
96612
96993
|
unlinkSync19(finalPath);
|
|
96613
96994
|
return true;
|
|
@@ -96615,15 +96996,15 @@ function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
|
96615
96996
|
}
|
|
96616
96997
|
function listSkillsOverlayEntries(agent, opts = {}) {
|
|
96617
96998
|
const paths = overlayPathsFor(agent, opts);
|
|
96618
|
-
if (!
|
|
96999
|
+
if (!existsSync98(paths.skillsDir))
|
|
96619
97000
|
return [];
|
|
96620
97001
|
const out = [];
|
|
96621
97002
|
for (const name of readdirSync35(paths.skillsDir)) {
|
|
96622
97003
|
if (!/\.ya?ml$/i.test(name))
|
|
96623
97004
|
continue;
|
|
96624
|
-
const full =
|
|
97005
|
+
const full = join98(paths.skillsDir, name);
|
|
96625
97006
|
try {
|
|
96626
|
-
const raw =
|
|
97007
|
+
const raw = readFileSync85(full, "utf-8");
|
|
96627
97008
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
96628
97009
|
out.push({ slug, path: full, raw });
|
|
96629
97010
|
} catch {}
|
|
@@ -96633,8 +97014,8 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
96633
97014
|
function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
96634
97015
|
const paths = overlayPathsFor(agent, opts);
|
|
96635
97016
|
return withAgentLock(paths, () => {
|
|
96636
|
-
const finalPath =
|
|
96637
|
-
if (!
|
|
97017
|
+
const finalPath = join98(paths.scheduleDir, `${slug}.yaml`);
|
|
97018
|
+
if (!existsSync98(finalPath))
|
|
96638
97019
|
return false;
|
|
96639
97020
|
unlinkSync19(finalPath);
|
|
96640
97021
|
return true;
|
|
@@ -96642,15 +97023,15 @@ function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
|
96642
97023
|
}
|
|
96643
97024
|
function listOverlayEntries(agent, opts = {}) {
|
|
96644
97025
|
const paths = overlayPathsFor(agent, opts);
|
|
96645
|
-
if (!
|
|
97026
|
+
if (!existsSync98(paths.scheduleDir))
|
|
96646
97027
|
return [];
|
|
96647
97028
|
const out = [];
|
|
96648
97029
|
for (const name of readdirSync35(paths.scheduleDir)) {
|
|
96649
97030
|
if (!/\.ya?ml$/i.test(name))
|
|
96650
97031
|
continue;
|
|
96651
|
-
const full =
|
|
97032
|
+
const full = join98(paths.scheduleDir, name);
|
|
96652
97033
|
try {
|
|
96653
|
-
const raw =
|
|
97034
|
+
const raw = readFileSync85(full, "utf-8");
|
|
96654
97035
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
96655
97036
|
out.push({ slug, path: full, raw });
|
|
96656
97037
|
} catch {}
|
|
@@ -96870,23 +97251,23 @@ function reconcileAgentCronOnly(agent) {
|
|
|
96870
97251
|
// src/cli/agent-config-pending.ts
|
|
96871
97252
|
import {
|
|
96872
97253
|
closeSync as closeSync18,
|
|
96873
|
-
existsSync as
|
|
97254
|
+
existsSync as existsSync99,
|
|
96874
97255
|
fsyncSync as fsyncSync9,
|
|
96875
97256
|
mkdirSync as mkdirSync56,
|
|
96876
97257
|
openSync as openSync18,
|
|
96877
97258
|
readdirSync as readdirSync36,
|
|
96878
|
-
readFileSync as
|
|
97259
|
+
readFileSync as readFileSync86,
|
|
96879
97260
|
renameSync as renameSync25,
|
|
96880
97261
|
unlinkSync as unlinkSync20,
|
|
96881
97262
|
writeFileSync as writeFileSync40,
|
|
96882
97263
|
writeSync as writeSync11
|
|
96883
97264
|
} from "node:fs";
|
|
96884
|
-
import { join as
|
|
97265
|
+
import { join as join99 } from "node:path";
|
|
96885
97266
|
import { randomBytes as randomBytes15 } from "node:crypto";
|
|
96886
97267
|
var STAGE_ID_PREFIX = "cap_";
|
|
96887
97268
|
function pendingDir(agent, opts = {}) {
|
|
96888
97269
|
const paths = overlayPathsFor(agent, opts);
|
|
96889
|
-
return
|
|
97270
|
+
return join99(paths.scheduleDir, ".pending");
|
|
96890
97271
|
}
|
|
96891
97272
|
function ensurePendingDir(agent, opts = {}) {
|
|
96892
97273
|
const dir = pendingDir(agent, opts);
|
|
@@ -96899,8 +97280,8 @@ function newStageId() {
|
|
|
96899
97280
|
function stagePendingScheduleEntry(opts) {
|
|
96900
97281
|
const dir = ensurePendingDir(opts.agent, { root: opts.root });
|
|
96901
97282
|
const stageId = opts.stageId ?? newStageId();
|
|
96902
|
-
const yamlPath =
|
|
96903
|
-
const metaPath =
|
|
97283
|
+
const yamlPath = join99(dir, `${stageId}.yaml`);
|
|
97284
|
+
const metaPath = join99(dir, `${stageId}.meta.json`);
|
|
96904
97285
|
const meta = {
|
|
96905
97286
|
v: 1,
|
|
96906
97287
|
stage_id: stageId,
|
|
@@ -96927,19 +97308,19 @@ function stagePendingScheduleEntry(opts) {
|
|
|
96927
97308
|
}
|
|
96928
97309
|
function listPendingScheduleEntries(agent, opts = {}) {
|
|
96929
97310
|
const dir = pendingDir(agent, opts);
|
|
96930
|
-
if (!
|
|
97311
|
+
if (!existsSync99(dir))
|
|
96931
97312
|
return [];
|
|
96932
97313
|
const out = [];
|
|
96933
97314
|
for (const name of readdirSync36(dir).sort()) {
|
|
96934
97315
|
if (!name.endsWith(".meta.json"))
|
|
96935
97316
|
continue;
|
|
96936
97317
|
const stageId = name.slice(0, -".meta.json".length);
|
|
96937
|
-
const metaPath =
|
|
96938
|
-
const yamlPath =
|
|
96939
|
-
if (!
|
|
97318
|
+
const metaPath = join99(dir, name);
|
|
97319
|
+
const yamlPath = join99(dir, `${stageId}.yaml`);
|
|
97320
|
+
if (!existsSync99(yamlPath))
|
|
96940
97321
|
continue;
|
|
96941
97322
|
try {
|
|
96942
|
-
const meta = JSON.parse(
|
|
97323
|
+
const meta = JSON.parse(readFileSync86(metaPath, "utf-8"));
|
|
96943
97324
|
if (meta?.v !== 1 || typeof meta.stage_id !== "string")
|
|
96944
97325
|
continue;
|
|
96945
97326
|
out.push({ stageId: meta.stage_id, agent: meta.agent, yamlPath, metaPath, meta });
|
|
@@ -96954,8 +97335,8 @@ function commitPendingScheduleEntry(opts) {
|
|
|
96954
97335
|
return { committed: false, reason: "not_found" };
|
|
96955
97336
|
const slug = match.meta.entry.name ?? match.stageId;
|
|
96956
97337
|
const paths = overlayPathsFor(opts.agent, { root: opts.root });
|
|
96957
|
-
const finalPath =
|
|
96958
|
-
if (
|
|
97338
|
+
const finalPath = join99(paths.scheduleDir, `${slug}.yaml`);
|
|
97339
|
+
if (existsSync99(finalPath)) {
|
|
96959
97340
|
return { committed: false, reason: "slug_collision" };
|
|
96960
97341
|
}
|
|
96961
97342
|
renameSync25(match.yamlPath, finalPath);
|
|
@@ -96977,7 +97358,7 @@ function denyPendingScheduleEntry(opts) {
|
|
|
96977
97358
|
}
|
|
96978
97359
|
|
|
96979
97360
|
// src/cli/agent-config-write.ts
|
|
96980
|
-
import { existsSync as
|
|
97361
|
+
import { existsSync as existsSync100, readFileSync as readFileSync87 } from "node:fs";
|
|
96981
97362
|
import { execFileSync as execFileSync30 } from "node:child_process";
|
|
96982
97363
|
|
|
96983
97364
|
// src/scheduler/schedule-report.ts
|
|
@@ -97367,8 +97748,8 @@ function scheduleRemove(opts) {
|
|
|
97367
97748
|
}
|
|
97368
97749
|
let priorContent = null;
|
|
97369
97750
|
try {
|
|
97370
|
-
if (
|
|
97371
|
-
priorContent =
|
|
97751
|
+
if (existsSync100(match.path))
|
|
97752
|
+
priorContent = readFileSync87(match.path, "utf-8");
|
|
97372
97753
|
} catch {}
|
|
97373
97754
|
deleteOverlayEntry(agent, match.slug, { root: opts.root });
|
|
97374
97755
|
const reconcileFn = opts.reconcile === undefined ? opts.root ? null : reconcileAgentCronOnly : opts.reconcile;
|
|
@@ -97571,7 +97952,7 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
97571
97952
|
}
|
|
97572
97953
|
let blob;
|
|
97573
97954
|
if (opts.jsonl) {
|
|
97574
|
-
blob =
|
|
97955
|
+
blob = existsSync100(opts.jsonl) ? readFileSync87(opts.jsonl, "utf-8") : "";
|
|
97575
97956
|
} else {
|
|
97576
97957
|
try {
|
|
97577
97958
|
blob = execFileSync30("docker", ["exec", `switchroom-${agent}`, "cat", "/state/agent/scheduler.jsonl"], {
|
|
@@ -97603,11 +97984,11 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
97603
97984
|
|
|
97604
97985
|
// src/cli/agent-config-skill-write.ts
|
|
97605
97986
|
var import_yaml22 = __toESM(require_dist(), 1);
|
|
97606
|
-
import { existsSync as
|
|
97987
|
+
import { existsSync as existsSync101 } from "node:fs";
|
|
97607
97988
|
init_reconcile_default_skills();
|
|
97608
97989
|
init_agent_config();
|
|
97609
97990
|
var import_yaml23 = __toESM(require_dist(), 1);
|
|
97610
|
-
import { join as
|
|
97991
|
+
import { join as join100 } from "node:path";
|
|
97611
97992
|
var MAX_SKILLS_PER_AGENT = 20;
|
|
97612
97993
|
var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
|
|
97613
97994
|
function exitCodeFor2(code) {
|
|
@@ -97682,8 +98063,8 @@ function skillInstall(opts) {
|
|
|
97682
98063
|
return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
|
|
97683
98064
|
}
|
|
97684
98065
|
const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
|
|
97685
|
-
const skillPath =
|
|
97686
|
-
if (!
|
|
98066
|
+
const skillPath = join100(poolDir, skillName);
|
|
98067
|
+
if (!existsSync101(skillPath)) {
|
|
97687
98068
|
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.`);
|
|
97688
98069
|
}
|
|
97689
98070
|
const yamlText = import_yaml22.stringify({ skills: [skillName] });
|
|
@@ -97847,12 +98228,12 @@ function registerAgentConfigSkillWriteCommands(program3) {
|
|
|
97847
98228
|
// src/cli/skill.ts
|
|
97848
98229
|
import {
|
|
97849
98230
|
closeSync as closeSync19,
|
|
97850
|
-
existsSync as
|
|
98231
|
+
existsSync as existsSync102,
|
|
97851
98232
|
lstatSync as lstatSync12,
|
|
97852
98233
|
mkdirSync as mkdirSync57,
|
|
97853
98234
|
mkdtempSync as mkdtempSync5,
|
|
97854
98235
|
openSync as openSync19,
|
|
97855
|
-
readFileSync as
|
|
98236
|
+
readFileSync as readFileSync88,
|
|
97856
98237
|
readdirSync as readdirSync37,
|
|
97857
98238
|
realpathSync as realpathSync7,
|
|
97858
98239
|
renameSync as renameSync26,
|
|
@@ -97861,8 +98242,8 @@ import {
|
|
|
97861
98242
|
writeFileSync as writeFileSync41
|
|
97862
98243
|
} from "node:fs";
|
|
97863
98244
|
import { tmpdir as tmpdir6, homedir as homedir53 } from "node:os";
|
|
97864
|
-
import { dirname as
|
|
97865
|
-
import { spawnSync as
|
|
98245
|
+
import { dirname as dirname38, join as join101, relative as relative4, resolve as resolve58 } from "node:path";
|
|
98246
|
+
import { spawnSync as spawnSync18 } from "node:child_process";
|
|
97866
98247
|
|
|
97867
98248
|
// src/cli/skill-common.ts
|
|
97868
98249
|
var import_yaml24 = __toESM(require_dist(), 1);
|
|
@@ -98095,7 +98476,7 @@ function scanForClaudeP2(content) {
|
|
|
98095
98476
|
function resolveSkillsPoolDir2(override) {
|
|
98096
98477
|
const raw = override ?? "~/.switchroom/skills";
|
|
98097
98478
|
if (raw.startsWith("~/")) {
|
|
98098
|
-
return
|
|
98479
|
+
return join101(homedir53(), raw.slice(2));
|
|
98099
98480
|
}
|
|
98100
98481
|
if (raw === "~")
|
|
98101
98482
|
return homedir53();
|
|
@@ -98134,7 +98515,7 @@ function loadFromDir(dir) {
|
|
|
98134
98515
|
const walk2 = (sub) => {
|
|
98135
98516
|
const entries = readdirSync37(sub, { withFileTypes: true });
|
|
98136
98517
|
for (const ent of entries) {
|
|
98137
|
-
const full =
|
|
98518
|
+
const full = join101(sub, ent.name);
|
|
98138
98519
|
const rel = relative4(abs, full);
|
|
98139
98520
|
if (ent.isSymbolicLink()) {
|
|
98140
98521
|
fail3(`refusing to read symlink inside --from dir: ${rel}`);
|
|
@@ -98144,7 +98525,7 @@ function loadFromDir(dir) {
|
|
|
98144
98525
|
continue;
|
|
98145
98526
|
}
|
|
98146
98527
|
if (ent.isFile()) {
|
|
98147
|
-
const buf =
|
|
98528
|
+
const buf = readFileSync88(full);
|
|
98148
98529
|
files[rel.replace(/\\/g, "/")] = buf.toString("utf-8");
|
|
98149
98530
|
}
|
|
98150
98531
|
}
|
|
@@ -98155,7 +98536,7 @@ function loadFromDir(dir) {
|
|
|
98155
98536
|
function loadFromTarball(tarPath) {
|
|
98156
98537
|
const isGz = tarPath.endsWith(".gz") || tarPath.endsWith(".tgz");
|
|
98157
98538
|
const listFlags = isGz ? ["-tzf"] : ["-tf"];
|
|
98158
|
-
const list2 =
|
|
98539
|
+
const list2 = spawnSync18("tar", [...listFlags, tarPath], {
|
|
98159
98540
|
encoding: "utf-8",
|
|
98160
98541
|
stdio: ["ignore", "pipe", "pipe"]
|
|
98161
98542
|
});
|
|
@@ -98169,10 +98550,10 @@ function loadFromTarball(tarPath) {
|
|
|
98169
98550
|
fail3(`tarball contains disallowed path: ${JSON.stringify(entry)} \u2014 ` + `refusing to extract before any file is written`);
|
|
98170
98551
|
}
|
|
98171
98552
|
}
|
|
98172
|
-
const staging = mkdtempSync5(
|
|
98553
|
+
const staging = mkdtempSync5(join101(tmpdir6(), "skill-apply-extract-"));
|
|
98173
98554
|
try {
|
|
98174
98555
|
const flags = isGz ? ["-xzf"] : ["-xf"];
|
|
98175
|
-
const r =
|
|
98556
|
+
const r = spawnSync18("tar", [
|
|
98176
98557
|
...flags,
|
|
98177
98558
|
tarPath,
|
|
98178
98559
|
"-C",
|
|
@@ -98193,7 +98574,7 @@ function loadFromTarball(tarPath) {
|
|
|
98193
98574
|
}
|
|
98194
98575
|
}
|
|
98195
98576
|
function loadSingleFile(filePath) {
|
|
98196
|
-
const content =
|
|
98577
|
+
const content = readFileSync88(filePath, "utf-8");
|
|
98197
98578
|
return { "SKILL.md": content };
|
|
98198
98579
|
}
|
|
98199
98580
|
function loadFromStdin() {
|
|
@@ -98247,7 +98628,7 @@ function validatePayload(name, files) {
|
|
|
98247
98628
|
if (errors2.length === 0) {
|
|
98248
98629
|
for (const [path10, content] of Object.entries(files)) {
|
|
98249
98630
|
if (SH_SCRIPT_RE2.test(path10)) {
|
|
98250
|
-
const r =
|
|
98631
|
+
const r = spawnSync18("bash", ["-n"], {
|
|
98251
98632
|
input: content,
|
|
98252
98633
|
encoding: "utf-8"
|
|
98253
98634
|
});
|
|
@@ -98255,11 +98636,11 @@ function validatePayload(name, files) {
|
|
|
98255
98636
|
errors2.push(`${path10} fails \`bash -n\` syntax check: ${(r.stderr ?? "").trim()}`);
|
|
98256
98637
|
}
|
|
98257
98638
|
} else if (PY_SCRIPT_RE2.test(path10)) {
|
|
98258
|
-
const tmp = mkdtempSync5(
|
|
98259
|
-
const tmpPy =
|
|
98639
|
+
const tmp = mkdtempSync5(join101(tmpdir6(), "skill-apply-py-"));
|
|
98640
|
+
const tmpPy = join101(tmp, "check.py");
|
|
98260
98641
|
try {
|
|
98261
98642
|
writeFileSync41(tmpPy, content);
|
|
98262
|
-
const r =
|
|
98643
|
+
const r = spawnSync18("python3", ["-m", "py_compile", tmpPy], {
|
|
98263
98644
|
encoding: "utf-8"
|
|
98264
98645
|
});
|
|
98265
98646
|
if (r.status !== 0) {
|
|
@@ -98276,15 +98657,15 @@ function validatePayload(name, files) {
|
|
|
98276
98657
|
function diffSummary(currentDir, files) {
|
|
98277
98658
|
const lines = [];
|
|
98278
98659
|
const currentFiles = {};
|
|
98279
|
-
if (
|
|
98660
|
+
if (existsSync102(currentDir)) {
|
|
98280
98661
|
const walk2 = (sub) => {
|
|
98281
98662
|
for (const ent of readdirSync37(sub, { withFileTypes: true })) {
|
|
98282
|
-
const full =
|
|
98663
|
+
const full = join101(sub, ent.name);
|
|
98283
98664
|
const rel = relative4(currentDir, full);
|
|
98284
98665
|
if (ent.isDirectory()) {
|
|
98285
98666
|
walk2(full);
|
|
98286
98667
|
} else if (ent.isFile()) {
|
|
98287
|
-
currentFiles[rel.replace(/\\/g, "/")] =
|
|
98668
|
+
currentFiles[rel.replace(/\\/g, "/")] = readFileSync88(full, "utf-8");
|
|
98288
98669
|
}
|
|
98289
98670
|
}
|
|
98290
98671
|
};
|
|
@@ -98312,10 +98693,10 @@ function diffSummary(currentDir, files) {
|
|
|
98312
98693
|
`);
|
|
98313
98694
|
}
|
|
98314
98695
|
function writePayload(poolDir, name, files) {
|
|
98315
|
-
if (!
|
|
98696
|
+
if (!existsSync102(poolDir)) {
|
|
98316
98697
|
mkdirSync57(poolDir, { recursive: true, mode: 493 });
|
|
98317
98698
|
}
|
|
98318
|
-
const target =
|
|
98699
|
+
const target = join101(poolDir, name);
|
|
98319
98700
|
let targetIsSymlink = false;
|
|
98320
98701
|
try {
|
|
98321
98702
|
const st = lstatSync12(target);
|
|
@@ -98326,12 +98707,12 @@ function writePayload(poolDir, name, files) {
|
|
|
98326
98707
|
if (targetIsSymlink) {
|
|
98327
98708
|
fail3(`refusing to overwrite symlink at ${target}; investigate manually`);
|
|
98328
98709
|
}
|
|
98329
|
-
const staging = mkdtempSync5(
|
|
98710
|
+
const staging = mkdtempSync5(join101(poolDir, `.skill-apply-stage-${name}-`));
|
|
98330
98711
|
let oldRename = null;
|
|
98331
98712
|
try {
|
|
98332
98713
|
for (const [path10, content] of Object.entries(files)) {
|
|
98333
|
-
const full =
|
|
98334
|
-
mkdirSync57(
|
|
98714
|
+
const full = join101(staging, path10);
|
|
98715
|
+
mkdirSync57(dirname38(full), { recursive: true, mode: 493 });
|
|
98335
98716
|
const fd = openSync19(full, "wx");
|
|
98336
98717
|
try {
|
|
98337
98718
|
writeFileSync41(fd, content);
|
|
@@ -98361,9 +98742,9 @@ function writePayload(poolDir, name, files) {
|
|
|
98361
98742
|
try {
|
|
98362
98743
|
rmSync18(staging, { recursive: true, force: true });
|
|
98363
98744
|
} catch {}
|
|
98364
|
-
if (oldRename &&
|
|
98745
|
+
if (oldRename && existsSync102(oldRename)) {
|
|
98365
98746
|
try {
|
|
98366
|
-
if (
|
|
98747
|
+
if (existsSync102(target)) {
|
|
98367
98748
|
rmSync18(target, { recursive: true, force: true });
|
|
98368
98749
|
}
|
|
98369
98750
|
renameSync26(oldRename, target);
|
|
@@ -98384,7 +98765,7 @@ function registerSkillCommand(program3) {
|
|
|
98384
98765
|
files = loadFromStdin();
|
|
98385
98766
|
} else {
|
|
98386
98767
|
const fromPath = resolve58(opts.from);
|
|
98387
|
-
if (!
|
|
98768
|
+
if (!existsSync102(fromPath)) {
|
|
98388
98769
|
fail3(`--from path does not exist: ${opts.from}`);
|
|
98389
98770
|
}
|
|
98390
98771
|
const st = statSync54(fromPath);
|
|
@@ -98408,7 +98789,7 @@ function registerSkillCommand(program3) {
|
|
|
98408
98789
|
}
|
|
98409
98790
|
const config = loadConfig();
|
|
98410
98791
|
const poolDir = resolveSkillsPoolDir2(config.switchroom?.skills_dir);
|
|
98411
|
-
const currentDir =
|
|
98792
|
+
const currentDir = join101(poolDir, name);
|
|
98412
98793
|
console.log(source_default.bold(`Skill: ${name}`) + source_default.gray(` (${Object.keys(files).length} files, ${sumBytes(files)} bytes)`));
|
|
98413
98794
|
console.log(source_default.bold("Diff vs current pool content:"));
|
|
98414
98795
|
console.log(diffSummary(currentDir, files));
|
|
@@ -98422,7 +98803,7 @@ function registerSkillCommand(program3) {
|
|
|
98422
98803
|
\u2713 Wrote ${name} to ${currentDir}`));
|
|
98423
98804
|
const applyBin = process.argv[1] ?? "switchroom";
|
|
98424
98805
|
console.log(source_default.gray(`Running \`switchroom apply --non-interactive\`...`));
|
|
98425
|
-
const r =
|
|
98806
|
+
const r = spawnSync18(process.argv0, [applyBin, "apply", "--non-interactive"], { stdio: "inherit" });
|
|
98426
98807
|
if (r.status !== 0) {
|
|
98427
98808
|
console.error(source_default.yellow(`(warning: \`switchroom apply\` exited ${r.status} \u2014 skill is ` + `in the pool but symlinks may not be refreshed. Re-run manually.)`));
|
|
98428
98809
|
}
|
|
@@ -98440,12 +98821,12 @@ function sumBytes(files) {
|
|
|
98440
98821
|
init_esm();
|
|
98441
98822
|
import {
|
|
98442
98823
|
closeSync as closeSync20,
|
|
98443
|
-
existsSync as
|
|
98824
|
+
existsSync as existsSync103,
|
|
98444
98825
|
lstatSync as lstatSync13,
|
|
98445
98826
|
mkdirSync as mkdirSync58,
|
|
98446
98827
|
mkdtempSync as mkdtempSync6,
|
|
98447
98828
|
openSync as openSync20,
|
|
98448
|
-
readFileSync as
|
|
98829
|
+
readFileSync as readFileSync89,
|
|
98449
98830
|
readdirSync as readdirSync38,
|
|
98450
98831
|
renameSync as renameSync27,
|
|
98451
98832
|
rmSync as rmSync19,
|
|
@@ -98453,9 +98834,9 @@ import {
|
|
|
98453
98834
|
utimesSync,
|
|
98454
98835
|
writeFileSync as writeFileSync42
|
|
98455
98836
|
} from "node:fs";
|
|
98456
|
-
import { dirname as
|
|
98837
|
+
import { dirname as dirname39, join as join102, relative as relative5, resolve as resolve59 } from "node:path";
|
|
98457
98838
|
import { homedir as homedir54, tmpdir as tmpdir7 } from "node:os";
|
|
98458
|
-
import { spawnSync as
|
|
98839
|
+
import { spawnSync as spawnSync19 } from "node:child_process";
|
|
98459
98840
|
init_helpers();
|
|
98460
98841
|
init_agent_config();
|
|
98461
98842
|
init_source();
|
|
@@ -98465,15 +98846,15 @@ var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
|
|
|
98465
98846
|
var PERSONAL_SKILLS_SUBPATH = "personal-skills";
|
|
98466
98847
|
function resolveConfigSkillsDir(agent) {
|
|
98467
98848
|
const override = process.env.SWITCHROOM_CONFIG_DIR;
|
|
98468
|
-
const candidate = override ? resolve59(override) :
|
|
98469
|
-
if (!
|
|
98849
|
+
const candidate = override ? resolve59(override) : join102(homedir54(), ".switchroom-config");
|
|
98850
|
+
if (!existsSync103(candidate))
|
|
98470
98851
|
return null;
|
|
98471
|
-
return
|
|
98852
|
+
return join102(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
|
|
98472
98853
|
}
|
|
98473
98854
|
var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
|
|
98474
98855
|
function sweepMirrorPriors(configSkillsRoot) {
|
|
98475
98856
|
try {
|
|
98476
|
-
if (!
|
|
98857
|
+
if (!existsSync103(configSkillsRoot))
|
|
98477
98858
|
return;
|
|
98478
98859
|
const now = Date.now();
|
|
98479
98860
|
for (const ent of readdirSync38(configSkillsRoot)) {
|
|
@@ -98486,7 +98867,7 @@ function sweepMirrorPriors(configSkillsRoot) {
|
|
|
98486
98867
|
if (now - ts < MIRROR_PRIOR_TTL_MS)
|
|
98487
98868
|
continue;
|
|
98488
98869
|
try {
|
|
98489
|
-
rmSync19(
|
|
98870
|
+
rmSync19(join102(configSkillsRoot, ent), { recursive: true, force: true });
|
|
98490
98871
|
} catch {}
|
|
98491
98872
|
}
|
|
98492
98873
|
} catch {}
|
|
@@ -98495,7 +98876,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
98495
98876
|
const configSkillsRoot = resolveConfigSkillsDir(agent);
|
|
98496
98877
|
if (!configSkillsRoot)
|
|
98497
98878
|
return;
|
|
98498
|
-
const dest =
|
|
98879
|
+
const dest = join102(configSkillsRoot, name);
|
|
98499
98880
|
try {
|
|
98500
98881
|
if (liveSkillDir !== null) {
|
|
98501
98882
|
try {
|
|
@@ -98509,32 +98890,32 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
98509
98890
|
}
|
|
98510
98891
|
if (liveSkillDir === null) {
|
|
98511
98892
|
sweepMirrorPriors(configSkillsRoot);
|
|
98512
|
-
if (
|
|
98513
|
-
const trash =
|
|
98893
|
+
if (existsSync103(dest)) {
|
|
98894
|
+
const trash = join102(configSkillsRoot, `.${name}-trash-${Date.now()}`);
|
|
98514
98895
|
renameSync27(dest, trash);
|
|
98515
98896
|
}
|
|
98516
98897
|
return;
|
|
98517
98898
|
}
|
|
98518
98899
|
mkdirSync58(configSkillsRoot, { recursive: true, mode: 493 });
|
|
98519
98900
|
sweepMirrorPriors(configSkillsRoot);
|
|
98520
|
-
const staging = mkdtempSync6(
|
|
98901
|
+
const staging = mkdtempSync6(join102(configSkillsRoot, `.${name}-staging-`));
|
|
98521
98902
|
const walk2 = (src, dst) => {
|
|
98522
98903
|
mkdirSync58(dst, { recursive: true, mode: 493 });
|
|
98523
98904
|
for (const ent of readdirSync38(src, { withFileTypes: true })) {
|
|
98524
|
-
const s =
|
|
98525
|
-
const d =
|
|
98905
|
+
const s = join102(src, ent.name);
|
|
98906
|
+
const d = join102(dst, ent.name);
|
|
98526
98907
|
if (ent.isSymbolicLink())
|
|
98527
98908
|
continue;
|
|
98528
98909
|
if (ent.isDirectory())
|
|
98529
98910
|
walk2(s, d);
|
|
98530
98911
|
else if (ent.isFile()) {
|
|
98531
|
-
writeFileSync42(d,
|
|
98912
|
+
writeFileSync42(d, readFileSync89(s));
|
|
98532
98913
|
}
|
|
98533
98914
|
}
|
|
98534
98915
|
};
|
|
98535
98916
|
walk2(liveSkillDir, staging);
|
|
98536
|
-
if (
|
|
98537
|
-
const prior =
|
|
98917
|
+
if (existsSync103(dest)) {
|
|
98918
|
+
const prior = join102(configSkillsRoot, `.${name}-prior-${Date.now()}`);
|
|
98538
98919
|
renameSync27(dest, prior);
|
|
98539
98920
|
}
|
|
98540
98921
|
renameSync27(staging, dest);
|
|
@@ -98564,17 +98945,17 @@ function resolveAgent(opts) {
|
|
|
98564
98945
|
function resolveAgentsRoot(opts) {
|
|
98565
98946
|
if (opts.root)
|
|
98566
98947
|
return resolve59(opts.root);
|
|
98567
|
-
return
|
|
98948
|
+
return join102(homedir54(), ".switchroom", "agents");
|
|
98568
98949
|
}
|
|
98569
98950
|
function personalSkillDir(agentsRoot, agent, name) {
|
|
98570
|
-
return
|
|
98951
|
+
return join102(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
|
|
98571
98952
|
}
|
|
98572
98953
|
function trashDir(agentsRoot, agent) {
|
|
98573
|
-
return
|
|
98954
|
+
return join102(agentsRoot, agent, ".claude", TRASH_DIRNAME);
|
|
98574
98955
|
}
|
|
98575
98956
|
function countPersonalSkills(agentsRoot, agent) {
|
|
98576
|
-
const skillsDir =
|
|
98577
|
-
if (!
|
|
98957
|
+
const skillsDir = join102(agentsRoot, agent, ".claude", "skills");
|
|
98958
|
+
if (!existsSync103(skillsDir))
|
|
98578
98959
|
return 0;
|
|
98579
98960
|
let n = 0;
|
|
98580
98961
|
for (const ent of readdirSync38(skillsDir, { withFileTypes: true })) {
|
|
@@ -98611,7 +98992,7 @@ function loadFromDir2(dir) {
|
|
|
98611
98992
|
const files = {};
|
|
98612
98993
|
const walk2 = (sub) => {
|
|
98613
98994
|
for (const ent of readdirSync38(sub, { withFileTypes: true })) {
|
|
98614
|
-
const full =
|
|
98995
|
+
const full = join102(sub, ent.name);
|
|
98615
98996
|
if (ent.isSymbolicLink()) {
|
|
98616
98997
|
fail4(`refusing to read symlink in --from dir: ${relative5(abs, full)}`);
|
|
98617
98998
|
}
|
|
@@ -98621,7 +99002,7 @@ function loadFromDir2(dir) {
|
|
|
98621
99002
|
}
|
|
98622
99003
|
if (ent.isFile()) {
|
|
98623
99004
|
const rel = relative5(abs, full).replace(/\\/g, "/");
|
|
98624
|
-
files[rel] =
|
|
99005
|
+
files[rel] = readFileSync89(full, "utf-8");
|
|
98625
99006
|
}
|
|
98626
99007
|
}
|
|
98627
99008
|
};
|
|
@@ -98659,16 +99040,16 @@ function behavioralValidate(files) {
|
|
|
98659
99040
|
const errors2 = [];
|
|
98660
99041
|
for (const [path10, content] of Object.entries(files)) {
|
|
98661
99042
|
if (SH_SCRIPT_RE.test(path10)) {
|
|
98662
|
-
const r =
|
|
99043
|
+
const r = spawnSync19("bash", ["-n"], { input: content, encoding: "utf-8" });
|
|
98663
99044
|
if (r.status !== 0) {
|
|
98664
99045
|
errors2.push(`${path10} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
|
|
98665
99046
|
}
|
|
98666
99047
|
} else if (PY_SCRIPT_RE.test(path10)) {
|
|
98667
|
-
const tmp = mkdtempSync6(
|
|
98668
|
-
const tmpPy =
|
|
99048
|
+
const tmp = mkdtempSync6(join102(tmpdir7(), "skill-personal-py-"));
|
|
99049
|
+
const tmpPy = join102(tmp, "check.py");
|
|
98669
99050
|
try {
|
|
98670
99051
|
writeFileSync42(tmpPy, content);
|
|
98671
|
-
const r =
|
|
99052
|
+
const r = spawnSync19("python3", ["-m", "py_compile", tmpPy], {
|
|
98672
99053
|
encoding: "utf-8"
|
|
98673
99054
|
});
|
|
98674
99055
|
if (r.status !== 0) {
|
|
@@ -98683,13 +99064,13 @@ function behavioralValidate(files) {
|
|
|
98683
99064
|
}
|
|
98684
99065
|
function sweepTrash(agentsRoot, agent) {
|
|
98685
99066
|
const trash = trashDir(agentsRoot, agent);
|
|
98686
|
-
if (!
|
|
99067
|
+
if (!existsSync103(trash))
|
|
98687
99068
|
return;
|
|
98688
99069
|
const now = Date.now();
|
|
98689
99070
|
for (const ent of readdirSync38(trash, { withFileTypes: true })) {
|
|
98690
99071
|
if (!ent.isDirectory())
|
|
98691
99072
|
continue;
|
|
98692
|
-
const entPath =
|
|
99073
|
+
const entPath = join102(trash, ent.name);
|
|
98693
99074
|
try {
|
|
98694
99075
|
const st = statSync55(entPath);
|
|
98695
99076
|
if (now - st.mtimeMs > TRASH_TTL_MS) {
|
|
@@ -98709,13 +99090,13 @@ function writePersonalSkill(targetDir, files) {
|
|
|
98709
99090
|
if (targetIsSymlink) {
|
|
98710
99091
|
fail4(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
|
|
98711
99092
|
}
|
|
98712
|
-
mkdirSync58(
|
|
98713
|
-
const staging = mkdtempSync6(
|
|
99093
|
+
mkdirSync58(dirname39(targetDir), { recursive: true, mode: 493 });
|
|
99094
|
+
const staging = mkdtempSync6(join102(dirname39(targetDir), `.skill-personal-stage-`));
|
|
98714
99095
|
let oldRename = null;
|
|
98715
99096
|
try {
|
|
98716
99097
|
for (const [path10, content] of Object.entries(files)) {
|
|
98717
|
-
const full =
|
|
98718
|
-
mkdirSync58(
|
|
99098
|
+
const full = join102(staging, path10);
|
|
99099
|
+
mkdirSync58(dirname39(full), { recursive: true, mode: 493 });
|
|
98719
99100
|
const fd = openSync20(full, "wx");
|
|
98720
99101
|
try {
|
|
98721
99102
|
writeFileSync42(fd, content);
|
|
@@ -98745,9 +99126,9 @@ function writePersonalSkill(targetDir, files) {
|
|
|
98745
99126
|
try {
|
|
98746
99127
|
rmSync19(staging, { recursive: true, force: true });
|
|
98747
99128
|
} catch {}
|
|
98748
|
-
if (oldRename &&
|
|
99129
|
+
if (oldRename && existsSync103(oldRename)) {
|
|
98749
99130
|
try {
|
|
98750
|
-
if (
|
|
99131
|
+
if (existsSync103(targetDir)) {
|
|
98751
99132
|
rmSync19(targetDir, { recursive: true, force: true });
|
|
98752
99133
|
}
|
|
98753
99134
|
renameSync27(oldRename, targetDir);
|
|
@@ -98813,7 +99194,7 @@ function loadFiles(opts) {
|
|
|
98813
99194
|
return loadFromStdin2();
|
|
98814
99195
|
}
|
|
98815
99196
|
const p = resolve59(opts.from);
|
|
98816
|
-
if (!
|
|
99197
|
+
if (!existsSync103(p)) {
|
|
98817
99198
|
fail4(`--from path does not exist: ${opts.from}`);
|
|
98818
99199
|
}
|
|
98819
99200
|
const st = statSync55(p);
|
|
@@ -98821,7 +99202,7 @@ function loadFiles(opts) {
|
|
|
98821
99202
|
return loadFromDir2(p);
|
|
98822
99203
|
}
|
|
98823
99204
|
if (p.endsWith(".md")) {
|
|
98824
|
-
return { "SKILL.md":
|
|
99205
|
+
return { "SKILL.md": readFileSync89(p, "utf-8") };
|
|
98825
99206
|
}
|
|
98826
99207
|
fail4(`--from must be a directory or a .md file. Got: ${opts.from}`);
|
|
98827
99208
|
}
|
|
@@ -98861,10 +99242,10 @@ function editPersonalAction(name, opts) {
|
|
|
98861
99242
|
}
|
|
98862
99243
|
var CLONE_SOURCE_RE = /^(shared|bundled):([a-z0-9][a-z0-9_-]{0,62})$/;
|
|
98863
99244
|
function defaultSharedRoot() {
|
|
98864
|
-
return
|
|
99245
|
+
return join102(homedir54(), ".switchroom", "skills");
|
|
98865
99246
|
}
|
|
98866
99247
|
function defaultBundledRoot() {
|
|
98867
|
-
return
|
|
99248
|
+
return join102(homedir54(), ".switchroom", "skills", "_bundled");
|
|
98868
99249
|
}
|
|
98869
99250
|
function resolveCloneSource(source, opts) {
|
|
98870
99251
|
const m = CLONE_SOURCE_RE.exec(source);
|
|
@@ -98874,8 +99255,8 @@ function resolveCloneSource(source, opts) {
|
|
|
98874
99255
|
const tier = m[1];
|
|
98875
99256
|
const slug = m[2];
|
|
98876
99257
|
const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
|
|
98877
|
-
const dir =
|
|
98878
|
-
if (!
|
|
99258
|
+
const dir = join102(root, slug);
|
|
99259
|
+
if (!existsSync103(dir)) {
|
|
98879
99260
|
fail4(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
|
|
98880
99261
|
}
|
|
98881
99262
|
const st = lstatSync13(dir);
|
|
@@ -98890,7 +99271,7 @@ function readSourceFiles(dir) {
|
|
|
98890
99271
|
const skipped = [];
|
|
98891
99272
|
const walk2 = (sub) => {
|
|
98892
99273
|
for (const ent of readdirSync38(sub, { withFileTypes: true })) {
|
|
98893
|
-
const full =
|
|
99274
|
+
const full = join102(sub, ent.name);
|
|
98894
99275
|
if (ent.isSymbolicLink()) {
|
|
98895
99276
|
continue;
|
|
98896
99277
|
}
|
|
@@ -98910,7 +99291,7 @@ function readSourceFiles(dir) {
|
|
|
98910
99291
|
fail4(`clone source has oversized file ${rel} (${st.size} bytes > ${CLONE_MAX_FILE_BYTES}); ` + `refuse to read`, 3);
|
|
98911
99292
|
}
|
|
98912
99293
|
} catch {}
|
|
98913
|
-
files[rel] =
|
|
99294
|
+
files[rel] = readFileSync89(full, "utf-8");
|
|
98914
99295
|
}
|
|
98915
99296
|
}
|
|
98916
99297
|
};
|
|
@@ -99001,7 +99382,7 @@ function removePersonalAction(name, opts) {
|
|
|
99001
99382
|
const trashRoot2 = trashDir(agentsRoot, agent);
|
|
99002
99383
|
mkdirSync58(trashRoot2, { recursive: true, mode: 493 });
|
|
99003
99384
|
const ts = Date.now();
|
|
99004
|
-
const trashTarget =
|
|
99385
|
+
const trashTarget = join102(trashRoot2, `${name}-${ts}`);
|
|
99005
99386
|
renameSync27(target, trashTarget);
|
|
99006
99387
|
const now = new Date(ts);
|
|
99007
99388
|
utimesSync(trashTarget, now, now);
|
|
@@ -99020,16 +99401,16 @@ function listPersonalAction(opts) {
|
|
|
99020
99401
|
const agent = resolveAgent(opts);
|
|
99021
99402
|
const agentsRoot = resolveAgentsRoot(opts);
|
|
99022
99403
|
sweepTrash(agentsRoot, agent);
|
|
99023
|
-
const skillsDir =
|
|
99404
|
+
const skillsDir = join102(agentsRoot, agent, ".claude", "skills");
|
|
99024
99405
|
const personal = [];
|
|
99025
|
-
if (
|
|
99406
|
+
if (existsSync103(skillsDir)) {
|
|
99026
99407
|
for (const ent of readdirSync38(skillsDir, { withFileTypes: true })) {
|
|
99027
99408
|
if (!ent.isDirectory())
|
|
99028
99409
|
continue;
|
|
99029
99410
|
if (!ent.name.startsWith(PERSONAL_PREFIX))
|
|
99030
99411
|
continue;
|
|
99031
99412
|
const skillName = ent.name.slice(PERSONAL_PREFIX.length);
|
|
99032
|
-
const skillPath =
|
|
99413
|
+
const skillPath = join102(skillsDir, ent.name);
|
|
99033
99414
|
let fileCount = 0;
|
|
99034
99415
|
let totalBytes = 0;
|
|
99035
99416
|
const walk2 = (sub) => {
|
|
@@ -99037,10 +99418,10 @@ function listPersonalAction(opts) {
|
|
|
99037
99418
|
if (e.isFile()) {
|
|
99038
99419
|
fileCount += 1;
|
|
99039
99420
|
try {
|
|
99040
|
-
totalBytes += statSync55(
|
|
99421
|
+
totalBytes += statSync55(join102(sub, e.name)).size;
|
|
99041
99422
|
} catch {}
|
|
99042
99423
|
} else if (e.isDirectory()) {
|
|
99043
|
-
walk2(
|
|
99424
|
+
walk2(join102(sub, e.name));
|
|
99044
99425
|
}
|
|
99045
99426
|
}
|
|
99046
99427
|
};
|
|
@@ -99079,11 +99460,11 @@ function registerSkillPersonalCommands(program3) {
|
|
|
99079
99460
|
// src/cli/self-improve-propose-skill.ts
|
|
99080
99461
|
import { createConnection as createConnection4 } from "node:net";
|
|
99081
99462
|
import { homedir as homedir55 } from "node:os";
|
|
99082
|
-
import { join as
|
|
99083
|
-
import { readFileSync as
|
|
99463
|
+
import { join as join103 } from "node:path";
|
|
99464
|
+
import { readFileSync as readFileSync90 } from "node:fs";
|
|
99084
99465
|
var IPC_CONNECT_TIMEOUT_MS = 5000;
|
|
99085
99466
|
function gatewaySocketPath() {
|
|
99086
|
-
return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ?
|
|
99467
|
+
return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ? join103(process.env.TELEGRAM_STATE_DIR, "gateway.sock") : join103(homedir55(), ".claude", "channels", "telegram", "gateway.sock"));
|
|
99087
99468
|
}
|
|
99088
99469
|
function fail5(msg, code = 1) {
|
|
99089
99470
|
console.error(msg);
|
|
@@ -99118,7 +99499,7 @@ function registerSelfImproveProposeSkillCommand(program3) {
|
|
|
99118
99499
|
fail5("agent name required (--agent or $SWITCHROOM_AGENT_NAME)");
|
|
99119
99500
|
let draft;
|
|
99120
99501
|
try {
|
|
99121
|
-
draft = JSON.parse(
|
|
99502
|
+
draft = JSON.parse(readFileSync90(opts.draft, "utf-8"));
|
|
99122
99503
|
} catch (e) {
|
|
99123
99504
|
fail5(`failed to read/parse --draft: ${e.message}`);
|
|
99124
99505
|
}
|
|
@@ -99155,9 +99536,9 @@ function registerSelfImproveProposeSkillCommand(program3) {
|
|
|
99155
99536
|
init_esm();
|
|
99156
99537
|
init_helpers();
|
|
99157
99538
|
var import_yaml25 = __toESM(require_dist(), 1);
|
|
99158
|
-
import { existsSync as
|
|
99539
|
+
import { existsSync as existsSync104, readdirSync as readdirSync39, readFileSync as readFileSync91, statSync as statSync56 } from "node:fs";
|
|
99159
99540
|
import { homedir as homedir56 } from "node:os";
|
|
99160
|
-
import { join as
|
|
99541
|
+
import { join as join104, resolve as resolve60 } from "node:path";
|
|
99161
99542
|
var PERSONAL_PREFIX2 = "personal-";
|
|
99162
99543
|
var BUNDLED_SUBDIR = "_bundled";
|
|
99163
99544
|
var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
|
|
@@ -99171,12 +99552,12 @@ function defaultBundledRoot2() {
|
|
|
99171
99552
|
return resolve60(homedir56(), ".switchroom/skills/_bundled");
|
|
99172
99553
|
}
|
|
99173
99554
|
function readSkillFrontmatter(skillDir) {
|
|
99174
|
-
const mdPath =
|
|
99175
|
-
if (!
|
|
99555
|
+
const mdPath = join104(skillDir, "SKILL.md");
|
|
99556
|
+
if (!existsSync104(mdPath))
|
|
99176
99557
|
return null;
|
|
99177
99558
|
let content;
|
|
99178
99559
|
try {
|
|
99179
|
-
content =
|
|
99560
|
+
content = readFileSync91(mdPath, "utf-8");
|
|
99180
99561
|
} catch {
|
|
99181
99562
|
return null;
|
|
99182
99563
|
}
|
|
@@ -99204,7 +99585,7 @@ function readSkillFrontmatter(skillDir) {
|
|
|
99204
99585
|
return { fm: parsed };
|
|
99205
99586
|
}
|
|
99206
99587
|
function statSkillMd(skillDir) {
|
|
99207
|
-
const mdPath =
|
|
99588
|
+
const mdPath = join104(skillDir, "SKILL.md");
|
|
99208
99589
|
try {
|
|
99209
99590
|
const st = statSync56(mdPath);
|
|
99210
99591
|
return { size: st.size, mtime: st.mtime.toISOString() };
|
|
@@ -99215,8 +99596,8 @@ function statSkillMd(skillDir) {
|
|
|
99215
99596
|
function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
99216
99597
|
if (!AGENT_NAME_RE3.test(agent))
|
|
99217
99598
|
return [];
|
|
99218
|
-
const skillsDir =
|
|
99219
|
-
if (!
|
|
99599
|
+
const skillsDir = join104(agentsRoot, agent, ".claude/skills");
|
|
99600
|
+
if (!existsSync104(skillsDir))
|
|
99220
99601
|
return [];
|
|
99221
99602
|
const out = [];
|
|
99222
99603
|
let entries;
|
|
@@ -99228,7 +99609,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
99228
99609
|
for (const ent of entries) {
|
|
99229
99610
|
if (!ent.startsWith(PERSONAL_PREFIX2))
|
|
99230
99611
|
continue;
|
|
99231
|
-
const dirPath =
|
|
99612
|
+
const dirPath = join104(skillsDir, ent);
|
|
99232
99613
|
try {
|
|
99233
99614
|
if (!statSync56(dirPath).isDirectory())
|
|
99234
99615
|
continue;
|
|
@@ -99254,7 +99635,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
99254
99635
|
return out;
|
|
99255
99636
|
}
|
|
99256
99637
|
function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
99257
|
-
if (!
|
|
99638
|
+
if (!existsSync104(sharedRoot))
|
|
99258
99639
|
return [];
|
|
99259
99640
|
const out = [];
|
|
99260
99641
|
let entries;
|
|
@@ -99268,7 +99649,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
|
99268
99649
|
continue;
|
|
99269
99650
|
if (ent.startsWith("."))
|
|
99270
99651
|
continue;
|
|
99271
|
-
const dirPath =
|
|
99652
|
+
const dirPath = join104(sharedRoot, ent);
|
|
99272
99653
|
try {
|
|
99273
99654
|
if (!statSync56(dirPath).isDirectory())
|
|
99274
99655
|
continue;
|
|
@@ -99292,7 +99673,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
|
99292
99673
|
return out;
|
|
99293
99674
|
}
|
|
99294
99675
|
function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
99295
|
-
if (!
|
|
99676
|
+
if (!existsSync104(bundledRoot))
|
|
99296
99677
|
return [];
|
|
99297
99678
|
const out = [];
|
|
99298
99679
|
let entries;
|
|
@@ -99304,7 +99685,7 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
|
99304
99685
|
for (const ent of entries) {
|
|
99305
99686
|
if (ent.startsWith("."))
|
|
99306
99687
|
continue;
|
|
99307
|
-
const dirPath =
|
|
99688
|
+
const dirPath = join104(bundledRoot, ent);
|
|
99308
99689
|
try {
|
|
99309
99690
|
if (!statSync56(dirPath).isDirectory())
|
|
99310
99691
|
continue;
|
|
@@ -99450,7 +99831,7 @@ init_source();
|
|
|
99450
99831
|
init_helpers();
|
|
99451
99832
|
init_operator_uid();
|
|
99452
99833
|
import {
|
|
99453
|
-
existsSync as
|
|
99834
|
+
existsSync as existsSync106,
|
|
99454
99835
|
mkdirSync as mkdirSync59,
|
|
99455
99836
|
readdirSync as readdirSync40,
|
|
99456
99837
|
writeFileSync as writeFileSync43,
|
|
@@ -99460,14 +99841,14 @@ import {
|
|
|
99460
99841
|
copyFileSync as copyFileSync13
|
|
99461
99842
|
} from "node:fs";
|
|
99462
99843
|
import { homedir as homedir57 } from "node:os";
|
|
99463
|
-
import { join as
|
|
99464
|
-
import { spawnSync as
|
|
99844
|
+
import { join as join105 } from "node:path";
|
|
99845
|
+
import { spawnSync as spawnSync22 } from "node:child_process";
|
|
99465
99846
|
|
|
99466
99847
|
// src/cli/singleton-stale-cleanup.ts
|
|
99467
|
-
import { spawnSync as
|
|
99848
|
+
import { spawnSync as spawnSync21 } from "node:child_process";
|
|
99468
99849
|
function makeDockerRunner() {
|
|
99469
99850
|
return (args) => {
|
|
99470
|
-
const r =
|
|
99851
|
+
const r = spawnSync21("docker", args, { encoding: "utf8" });
|
|
99471
99852
|
return {
|
|
99472
99853
|
ok: r.status === 0,
|
|
99473
99854
|
stdout: r.stdout ?? "",
|
|
@@ -99809,7 +100190,7 @@ function resolveHostdHostHome(env2 = process.env, home2 = homedir57()) {
|
|
|
99809
100190
|
return resolved;
|
|
99810
100191
|
}
|
|
99811
100192
|
function resolveHostdSkillsTarget(hostHome) {
|
|
99812
|
-
const skillsPath =
|
|
100193
|
+
const skillsPath = join105(hostHome, ".switchroom", "skills");
|
|
99813
100194
|
let st;
|
|
99814
100195
|
try {
|
|
99815
100196
|
st = lstatSync14(skillsPath);
|
|
@@ -99826,21 +100207,21 @@ function resolveHostdSkillsTarget(hostHome) {
|
|
|
99826
100207
|
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.`);
|
|
99827
100208
|
return;
|
|
99828
100209
|
}
|
|
99829
|
-
if (!
|
|
100210
|
+
if (!existsSync106(target)) {
|
|
99830
100211
|
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.`);
|
|
99831
100212
|
return;
|
|
99832
100213
|
}
|
|
99833
100214
|
return target;
|
|
99834
100215
|
}
|
|
99835
100216
|
function hostdDir() {
|
|
99836
|
-
return
|
|
100217
|
+
return join105(homedir57(), ".switchroom", "hostd");
|
|
99837
100218
|
}
|
|
99838
100219
|
function hostdComposePath() {
|
|
99839
|
-
return
|
|
100220
|
+
return join105(hostdDir(), "docker-compose.yml");
|
|
99840
100221
|
}
|
|
99841
100222
|
function backupExistingCompose() {
|
|
99842
100223
|
const p = hostdComposePath();
|
|
99843
|
-
if (!
|
|
100224
|
+
if (!existsSync106(p))
|
|
99844
100225
|
return null;
|
|
99845
100226
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
99846
100227
|
const bak = `${p}.bak-${ts}`;
|
|
@@ -99848,7 +100229,7 @@ function backupExistingCompose() {
|
|
|
99848
100229
|
return bak;
|
|
99849
100230
|
}
|
|
99850
100231
|
function runDocker(args) {
|
|
99851
|
-
const r =
|
|
100232
|
+
const r = spawnSync22("docker", args, { encoding: "utf8" });
|
|
99852
100233
|
return {
|
|
99853
100234
|
ok: r.status === 0,
|
|
99854
100235
|
stdout: r.stdout ?? "",
|
|
@@ -99938,7 +100319,7 @@ function doStatus() {
|
|
|
99938
100319
|
const composeYml = hostdComposePath();
|
|
99939
100320
|
console.log(source_default.bold("switchroom-hostd"));
|
|
99940
100321
|
console.log("");
|
|
99941
|
-
if (!
|
|
100322
|
+
if (!existsSync106(composeYml)) {
|
|
99942
100323
|
console.log(source_default.yellow(" compose: not installed"));
|
|
99943
100324
|
console.log(source_default.dim(" run `switchroom hostd install` to set up."));
|
|
99944
100325
|
return;
|
|
@@ -99959,14 +100340,14 @@ function doStatus() {
|
|
|
99959
100340
|
} else {
|
|
99960
100341
|
console.log(source_default.green(` container: ${ps.stdout.trim()}`));
|
|
99961
100342
|
}
|
|
99962
|
-
if (
|
|
100343
|
+
if (existsSync106(dir)) {
|
|
99963
100344
|
const entries = [];
|
|
99964
100345
|
try {
|
|
99965
100346
|
for (const name of readdirSync40(dir)) {
|
|
99966
100347
|
if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
|
|
99967
100348
|
continue;
|
|
99968
|
-
const sockPath =
|
|
99969
|
-
if (
|
|
100349
|
+
const sockPath = join105(dir, name, "sock");
|
|
100350
|
+
if (existsSync106(sockPath)) {
|
|
99970
100351
|
const st = statSync57(sockPath);
|
|
99971
100352
|
if ((st.mode & 61440) === 49152) {
|
|
99972
100353
|
entries.push(`${name} \u2192 ${sockPath}`);
|
|
@@ -99985,7 +100366,7 @@ function doStatus() {
|
|
|
99985
100366
|
}
|
|
99986
100367
|
function doUninstall() {
|
|
99987
100368
|
const composeYml = hostdComposePath();
|
|
99988
|
-
if (!
|
|
100369
|
+
if (!existsSync106(composeYml)) {
|
|
99989
100370
|
console.log(source_default.yellow(" No hostd install detected (no compose file at this path)."));
|
|
99990
100371
|
return;
|
|
99991
100372
|
}
|
|
@@ -100009,7 +100390,7 @@ function registerHostdCommand(program3) {
|
|
|
100009
100390
|
hostd.command("uninstall").description("Stop the hostd container. Leaves the compose file in place for re-install.").action(() => doUninstall());
|
|
100010
100391
|
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) => {
|
|
100011
100392
|
const logPath = opts.path ?? defaultAuditLogPath2();
|
|
100012
|
-
if (!
|
|
100393
|
+
if (!existsSync106(logPath)) {
|
|
100013
100394
|
console.error(source_default.yellow(`Audit log not found at ${logPath}.`) + source_default.gray(`
|
|
100014
100395
|
The log is created when hostd handles its first privileged-verb request.`));
|
|
100015
100396
|
return;
|
|
@@ -100057,10 +100438,10 @@ The log is created when hostd handles its first privileged-verb request.`));
|
|
|
100057
100438
|
init_source();
|
|
100058
100439
|
init_helpers();
|
|
100059
100440
|
init_operator_uid();
|
|
100060
|
-
import { chownSync as chownSync10, existsSync as
|
|
100441
|
+
import { chownSync as chownSync10, existsSync as existsSync107, mkdirSync as mkdirSync60, writeFileSync as writeFileSync44, copyFileSync as copyFileSync14 } from "node:fs";
|
|
100061
100442
|
import { homedir as homedir58 } from "node:os";
|
|
100062
|
-
import { join as
|
|
100063
|
-
import { spawnSync as
|
|
100443
|
+
import { join as join106 } from "node:path";
|
|
100444
|
+
import { spawnSync as spawnSync23 } from "node:child_process";
|
|
100064
100445
|
function resolveWebImageTag(explicitTag, release) {
|
|
100065
100446
|
if (explicitTag)
|
|
100066
100447
|
return explicitTag;
|
|
@@ -100159,14 +100540,14 @@ services:
|
|
|
100159
100540
|
`;
|
|
100160
100541
|
}
|
|
100161
100542
|
function webdDir() {
|
|
100162
|
-
return
|
|
100543
|
+
return join106(homedir58(), ".switchroom", "web");
|
|
100163
100544
|
}
|
|
100164
100545
|
function webdComposePath() {
|
|
100165
|
-
return
|
|
100546
|
+
return join106(webdDir(), "docker-compose.yml");
|
|
100166
100547
|
}
|
|
100167
100548
|
function backupExistingCompose2() {
|
|
100168
100549
|
const p = webdComposePath();
|
|
100169
|
-
if (!
|
|
100550
|
+
if (!existsSync107(p))
|
|
100170
100551
|
return null;
|
|
100171
100552
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
100172
100553
|
const bak = `${p}.bak-${ts}`;
|
|
@@ -100174,7 +100555,7 @@ function backupExistingCompose2() {
|
|
|
100174
100555
|
return bak;
|
|
100175
100556
|
}
|
|
100176
100557
|
function runDocker2(args) {
|
|
100177
|
-
const r =
|
|
100558
|
+
const r = spawnSync23("docker", args, { encoding: "utf8" });
|
|
100178
100559
|
return {
|
|
100179
100560
|
ok: r.status === 0,
|
|
100180
100561
|
stdout: r.stdout ?? "",
|
|
@@ -100268,7 +100649,7 @@ function doStatus2() {
|
|
|
100268
100649
|
const composeYml = webdComposePath();
|
|
100269
100650
|
console.log(source_default.bold("switchroom-web"));
|
|
100270
100651
|
console.log("");
|
|
100271
|
-
if (!
|
|
100652
|
+
if (!existsSync107(composeYml)) {
|
|
100272
100653
|
console.log(source_default.yellow(" compose: not installed"));
|
|
100273
100654
|
console.log(source_default.dim(" run `switchroom webd install` to set up."));
|
|
100274
100655
|
return;
|
|
@@ -100292,7 +100673,7 @@ function doStatus2() {
|
|
|
100292
100673
|
}
|
|
100293
100674
|
function doUninstall2() {
|
|
100294
100675
|
const composeYml = webdComposePath();
|
|
100295
|
-
if (!
|
|
100676
|
+
if (!existsSync107(composeYml)) {
|
|
100296
100677
|
console.log(source_default.yellow(" No web-service install detected (no compose file at this path)."));
|
|
100297
100678
|
return;
|
|
100298
100679
|
}
|
|
@@ -100322,9 +100703,9 @@ function registerWebdCommand(program3) {
|
|
|
100322
100703
|
// src/cli/host-repair.ts
|
|
100323
100704
|
init_source();
|
|
100324
100705
|
import { homedir as homedir59 } from "node:os";
|
|
100325
|
-
import { join as
|
|
100706
|
+
import { join as join107 } from "node:path";
|
|
100326
100707
|
var ARTIFACT_ALLOWLIST = {
|
|
100327
|
-
dockerComposePluginDir: (home2) =>
|
|
100708
|
+
dockerComposePluginDir: (home2) => join107(home2, ".docker", "cli-plugins", "docker-compose"),
|
|
100328
100709
|
stateSentinel: "/state"
|
|
100329
100710
|
};
|
|
100330
100711
|
function isStateBogusAutoDir(probe2) {
|
|
@@ -100567,13 +100948,13 @@ init_helpers();
|
|
|
100567
100948
|
init_scan();
|
|
100568
100949
|
|
|
100569
100950
|
// src/fleet-health/gh-sync.ts
|
|
100570
|
-
import { spawnSync as
|
|
100951
|
+
import { spawnSync as spawnSync24 } from "node:child_process";
|
|
100571
100952
|
var LABEL = "fleet-health";
|
|
100572
100953
|
function defaultGhDeps(log) {
|
|
100573
100954
|
return {
|
|
100574
100955
|
log,
|
|
100575
100956
|
run: (args) => {
|
|
100576
|
-
const r =
|
|
100957
|
+
const r = spawnSync24("gh", args, { encoding: "utf-8" });
|
|
100577
100958
|
return {
|
|
100578
100959
|
ok: r.status === 0,
|
|
100579
100960
|
stdout: (r.stdout ?? "").trim(),
|
|
@@ -100778,9 +101159,9 @@ function printDeepDiveBrief(targets) {
|
|
|
100778
101159
|
|
|
100779
101160
|
// src/cli/hindsight-watch.ts
|
|
100780
101161
|
init_source();
|
|
100781
|
-
import { existsSync as
|
|
101162
|
+
import { existsSync as existsSync112, readdirSync as readdirSync45 } from "node:fs";
|
|
100782
101163
|
import { homedir as homedir62 } from "node:os";
|
|
100783
|
-
import { join as
|
|
101164
|
+
import { join as join109, resolve as resolve63 } from "node:path";
|
|
100784
101165
|
|
|
100785
101166
|
// src/host-control/config-degraded.ts
|
|
100786
101167
|
import { connect as connect3 } from "node:net";
|
|
@@ -100848,8 +101229,8 @@ function postOperatorNoticeViaGateways(candidates, text, log, connectTimeoutMs =
|
|
|
100848
101229
|
init_install_cron();
|
|
100849
101230
|
|
|
100850
101231
|
// src/hindsight-watch/probe.ts
|
|
100851
|
-
import { spawnSync as
|
|
100852
|
-
import { readFileSync as
|
|
101232
|
+
import { spawnSync as spawnSync25 } from "node:child_process";
|
|
101233
|
+
import { readFileSync as readFileSync96, readdirSync as readdirSync44, statSync as statSync59 } from "node:fs";
|
|
100853
101234
|
import { homedir as homedir61 } from "node:os";
|
|
100854
101235
|
import { resolve as resolve62 } from "node:path";
|
|
100855
101236
|
|
|
@@ -100977,16 +101358,16 @@ function readLlmSignals(series) {
|
|
|
100977
101358
|
}
|
|
100978
101359
|
|
|
100979
101360
|
// src/hindsight-watch/recall-log.ts
|
|
100980
|
-
import { closeSync as closeSync21, existsSync as
|
|
100981
|
-
import { join as
|
|
101361
|
+
import { closeSync as closeSync21, existsSync as existsSync111, openSync as openSync21, readdirSync as readdirSync43, readSync as readSync5, statSync as statSync58 } from "node:fs";
|
|
101362
|
+
import { join as join108 } from "node:path";
|
|
100982
101363
|
var RECALL_WINDOW_ROWS = 200;
|
|
100983
101364
|
var RECALL_MAX_TAIL_BYTES = 1024 * 1024;
|
|
100984
101365
|
var RECALL_MAX_ROW_AGE_MS = 24 * 60 * 60 * 1000;
|
|
100985
101366
|
function recallLogPath2(agentDir) {
|
|
100986
|
-
return
|
|
101367
|
+
return join108(agentDir, ".claude", "plugins", "data", "hindsight-memory-inline", "state", "recall_log.jsonl");
|
|
100987
101368
|
}
|
|
100988
101369
|
function readRecallLogTail2(path10, windowRows = RECALL_WINDOW_ROWS) {
|
|
100989
|
-
if (!
|
|
101370
|
+
if (!existsSync111(path10))
|
|
100990
101371
|
return [];
|
|
100991
101372
|
let text;
|
|
100992
101373
|
let truncatedHead = false;
|
|
@@ -101131,7 +101512,7 @@ function probeRecallLogs(agentsDir, now, windowRows = RECALL_WINDOW_ROWS, maxAge
|
|
|
101131
101512
|
const all = [];
|
|
101132
101513
|
let agents = 0;
|
|
101133
101514
|
for (const name of names) {
|
|
101134
|
-
const rows = withinWindow(readRecallLogTail2(recallLogPath2(
|
|
101515
|
+
const rows = withinWindow(readRecallLogTail2(recallLogPath2(join108(agentsDir, name)), windowRows), now, maxAgeMs);
|
|
101135
101516
|
if (rows.length > 0)
|
|
101136
101517
|
agents++;
|
|
101137
101518
|
all.push(...rows);
|
|
@@ -101198,7 +101579,7 @@ async function probeMetrics(url = DEFAULT_METRICS_URL, fetchImpl = fetch) {
|
|
|
101198
101579
|
}
|
|
101199
101580
|
}
|
|
101200
101581
|
var defaultRunner4 = (cmd, args) => {
|
|
101201
|
-
const r =
|
|
101582
|
+
const r = spawnSync25(cmd, args, { stdio: "pipe", timeout: 1e4, encoding: "utf8" });
|
|
101202
101583
|
if (r.error)
|
|
101203
101584
|
return { status: null, stdout: "", stderr: r.error.message };
|
|
101204
101585
|
return { status: r.status, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
@@ -101226,7 +101607,7 @@ function readDropCount(hindsightDir) {
|
|
|
101226
101607
|
try {
|
|
101227
101608
|
if (statSync59(path10).size > DROPS_LEDGER_MAX_BYTES)
|
|
101228
101609
|
return 0;
|
|
101229
|
-
parsed = JSON.parse(
|
|
101610
|
+
parsed = JSON.parse(readFileSync96(path10, "utf8"));
|
|
101230
101611
|
} catch {
|
|
101231
101612
|
return 0;
|
|
101232
101613
|
}
|
|
@@ -101926,7 +102307,7 @@ function registerHindsightWatchCommand(program3) {
|
|
|
101926
102307
|
process.exitCode = runInstallCron(opts.cronUser);
|
|
101927
102308
|
return;
|
|
101928
102309
|
}
|
|
101929
|
-
const agentsDir = opts.agentsDir ?? process.env.SWITCHROOM_AGENTS_DIR ??
|
|
102310
|
+
const agentsDir = opts.agentsDir ?? process.env.SWITCHROOM_AGENTS_DIR ?? join109(process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir62(), ".switchroom", "agents");
|
|
101930
102311
|
const statePath = opts.state ?? defaultStatePath2();
|
|
101931
102312
|
const log = (m) => {
|
|
101932
102313
|
process.stderr.write(`${m}
|
|
@@ -102018,7 +102399,7 @@ async function notifyOperator(agentsDir, text, log) {
|
|
|
102018
102399
|
try {
|
|
102019
102400
|
for (const name of readdirSync45(agentsDir).sort()) {
|
|
102020
102401
|
const sock = resolve63(agentsDir, name, "telegram", "gateway.sock");
|
|
102021
|
-
if (
|
|
102402
|
+
if (existsSync112(sock))
|
|
102022
102403
|
candidates.push({ agent: name, sock });
|
|
102023
102404
|
}
|
|
102024
102405
|
} catch (e) {
|