switchroom 0.20.17 → 0.20.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-scheduler/index.js +7 -1
- package/dist/auth-broker/index.js +42 -5
- package/dist/cli/notion-write-pretool.mjs +7 -1
- package/dist/cli/switchroom.js +1684 -437
- package/dist/host-control/main.js +43 -6
- package/dist/vault/approvals/kernel-server.js +42 -5
- package/dist/vault/broker/server.js +42 -5
- package/package.json +1 -1
- package/profiles/_shared/dev-protocol.md.hbs +1 -1
- package/profiles/coding/CLAUDE.md.hbs +2 -2
- package/profiles/default/CLAUDE.md.hbs +5 -5
- package/profiles/executive-assistant/CLAUDE.md.hbs +2 -2
- package/profiles/health-coach/CLAUDE.md.hbs +2 -2
- package/telegram-plugin/dist/gateway/gateway.js +57 -13
- package/telegram-plugin/telegraph.ts +23 -5
- package/telegram-plugin/tests/telegraph.test.ts +34 -0
- package/telegram-plugin/tests/welcome-text.test.ts +5 -4
- package/telegram-plugin/welcome-text.ts +7 -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.20.
|
|
2123
|
+
var VERSION = "0.20.19", COMMIT_SHA = "552a2d21";
|
|
2124
2124
|
|
|
2125
2125
|
// src/cli/resolve-version.ts
|
|
2126
2126
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -14046,7 +14046,13 @@ var init_schema = __esm(() => {
|
|
|
14046
14046
|
reflect: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `reflect` LLM op (synthesis / mental-model " + "refresh). Emits `HINDSIGHT_API_REFLECT_LLM_*`. Absent \u2192 uses global."),
|
|
14047
14047
|
consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent \u2192 global.")
|
|
14048
14048
|
}).optional().describe("LLM knob for the hindsight container. The flat `provider`/`model` set " + "the global default (backward-compatible); optional `retain`/`reflect`/" + "`consolidation` blocks override individual ops. All fields optional; " + "unset fields fall back to the hard-coded defaults."),
|
|
14049
|
-
|
|
14049
|
+
recall_pool: exports_external.object({
|
|
14050
|
+
enabled: exports_external.boolean().optional().describe("Turn on the recall/background WORKER SPLIT: a second container, " + "`switchroom-hindsight-recall`, serves recall/reflect only with N " + "uvicorn workers, while the existing `switchroom-hindsight` " + "container keeps pg0, the single background WorkerPoller, the " + "healthcheck and all consolidation/retain processing but moves off " + "the public port onto public+1. The public port (memory.config.url) " + "does not change \u2014 the pool binds it instead. Absent/false (the " + "default) \u2192 the single-container topology, unchanged. Opt-in only. " + "Raises recall throughput ~2.2x on the fleet (2.6\u21925.6 rps)."),
|
|
14051
|
+
workers: exports_external.number().int().positive().optional().describe("uvicorn `--workers` for the recall pool (HINDSIGHT_API_WORKERS). " + "Default 4. Each worker holds its own GPU cross-encoder reranker, " + "so past ~4 workers contend for VRAM rather than adding throughput " + "on a single GPU \u2014 raise only on a bigger GPU or a CPU reranker. " + "The per-worker DB pools are auto-sized down from the pg0 " + "connection budget so N*(write+read)+60 background stays under " + "max_connections=250; a worker count that would overflow that " + "budget is rejected at launch."),
|
|
14052
|
+
db_pool_max_size: exports_external.number().int().positive().optional().describe("Override the per-worker WRITE pool cap " + "(HINDSIGHT_API_DB_POOL_MAX_SIZE). Absent \u2192 auto-sized from the " + "worker count. Set this only to override the auto-sizing; the " + "connection-budget check still applies and rejects an overflow."),
|
|
14053
|
+
read_db_pool_max_size: exports_external.number().int().positive().optional().describe("Override the per-worker READ pool cap " + "(HINDSIGHT_API_READ_DB_POOL_MAX_SIZE). Absent \u2192 auto-sized from " + "the worker count. Set this only to override the auto-sizing; the " + "connection-budget check still applies and rejects an overflow.")
|
|
14054
|
+
}).optional().describe("Optional recall/background container split for the hindsight " + "singleton. Off by default; see `enabled`. Provisioned by " + "`switchroom memory setup` (src/setup/hindsight-recall-pool.ts) so it " + "survives `switchroom apply` \u2014 the hand-applied topology it replaces " + "did not."),
|
|
14055
|
+
env: exports_external.record(exports_external.union([exports_external.string(), exports_external.number(), exports_external.boolean()])).optional().describe("Operator overrides for switchroom's capability-gated Hindsight " + "performance defaults. Only the keys switchroom actually manages are " + "honoured (`HINDSIGHT_PERF_ENV_KEYS` in " + "src/setup/hindsight-perf-defaults.ts: RERANKER_LOCAL_FP16, " + "RERANKER_LOCAL_BATCH_SIZE, LLM_MAX_CONCURRENT, " + "RETAIN/CONSOLIDATION_LLM_MAX_CONCURRENT, LLM_STRICT_SCHEMA, " + "LLM_MAX_RETRIES, CONSOLIDATION_LLM_PARALLELISM, " + "MAX_OBSERVATIONS_PER_SCOPE, " + "RECALL_MAX_CANDIDATES_PER_SOURCE, LINK_EXPANSION_PER_ENTITY_LIMIT, " + "LINK_EXPANSION_TIMEOUT, LLM_REASONING_EFFORT, " + "RERANKER_LOCAL_BUCKET_BATCHING, RERANKER_MAX_CANDIDATES, " + "RERANKER_LOCAL_MAX_CONCURRENT, RECALL_MAX_CONCURRENT, " + "REFLECT_WALL_TIMEOUT, WORKER_CONSOLIDATION_RESERVED_SLOTS, " + "WORKER_CONSOLIDATION_SLOT_LIMIT, " + "CONSOLIDATION_MAX_MEMORIES_PER_ROUND, GRAPH_SEED_MIN_SIMILARITY, " + "LLM_SUPPORTS_MAX_ITEMS, RECENCY_DECAY_FUNCTION, " + "RECENCY_DECAY_HALFLIFE_DAYS \u2014 switchroom defaults recall's recency " + "curve to `exponential` with a 30-day half-life so a fact retained " + "today outranks a stale one, instead of upstream's near-flat " + "linear/365-day window), the override-only keys " + "switchroom manages but ships NO default for " + "(`HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS`: " + "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY \u2014 a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP \u2014 the rollback knob for " + "switchroom's CE-saturation damping patch, a float; >= ~0.65 backs the " + "damping out entirely, unset means the patch's own derived gap; and " + "HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS \u2014 only read when the " + "decay function is `linear`, so switchroom ships no default for it but " + "still honours an operator who flips the function back; and " + "HINDSIGHT_API_WORKER_MAX_SLOTS \u2014 the worker poller's TOTAL in-flight " + "task budget, the pool WORKER_CONSOLIDATION_RESERVED_SLOTS reserves out of; " + "unset means upstream's own default; and " + "HINDSIGHT_API_WORKER_RETAIN_RESERVED_SLOTS \u2014 the reserved slot FLOOR for the " + "retain (memory write) lane, carved from that same total; unset means " + "upstream's own 0, i.e. no floor and retain competes for the shared " + "pool; and " + "the pre-0.8.6 name HINDSIGHT_API_WORKER_<TYPE>_MAX_SLOTS is still " + "accepted for every operation type and normalised to " + "..._RESERVED_SLOTS on the way in, because setting both names for " + "one type is a hard boot failure in the engine and switchroom " + "therefore emits only the canonical name; and " + "HINDSIGHT_API_SEMANTIC_MIN_SIMILARITY \u2014 the cosine-similarity floor a " + "candidate must reach to be returned by the semantic retrieval arm at " + "all (0..1); unset means upstream's own 0.3, and where it should sit " + "depends on the bank's embedding model and phrasing diversity, so " + "switchroom ships no opinion; and " + "HINDSIGHT_MCP_RECALL_BUDGET_MODE \u2014 the rollback knob for switchroom's " + "mcp-recall-token-budget image patch; `legacy` restores upstream's " + "exact pre-patch recall returns, anything else or unset is the " + "honest-envelope mode; and " + "HINDSIGHT_API_LLM_TEMPERATURE_REFLECT \u2014 upstream's own per-op reflect " + "temperature knob, made live by switchroom's reflect-temperature image " + "patch; a float, or `none` to omit the kwarg (provider default, " + "upstream's accidental pre-patch behaviour); unset means the image's " + "baked default 0.1; and " + "HINDSIGHT_API_CONSOLIDATION_RECALL_MAX_CONCURRENT \u2014 the background " + "half of switchroom's recall-admission split (#3660): of the " + "RECALL_MAX_CONCURRENT admission slots, at most this many may be held " + "by background consolidation recalls at once, so foreground per-turn " + "recall always keeps the remainder; must be >= 1 and strictly less " + "than RECALL_MAX_CONCURRENT or the engine refuses to boot; unset " + "means the image's derived default min(2, RECALL_MAX_CONCURRENT - 1); " + "1 biases hard toward the interactive lane while a consolidation " + "backlog drains; and " + "HINDSIGHT_MM_REFRESH_MIN_INTERVAL_S \u2014 the rollback/tuning knob for " + "switchroom's MM-refresh-debounce image patch: the minimum seconds " + "between consolidation-triggered refreshes of one mental model; unset " + "means the image's baked 3600, 0 restores upstream's " + "refresh-every-round behaviour; explicit and cron-scheduled refreshes " + "are never debounced; and " + "HINDSIGHT_API_QUERY_ANALYZER_LANGUAGES \u2014 the language set dateparser is " + "restricted to during temporal query analysis (upstream #3154, which " + "superseded switchroom's own temporal-language image patch at v0.9.0); " + "comma-separated; switchroom EMITS `en` on every host because upstream's " + "unset default auto-detects across 200+ locales inline on the shared " + "asyncio loop (~165x slower per call), set e.g. `en,es` for i18n parsing; " + "and " + "HINDSIGHT_API_TEMPORAL_MAX_QUERY_CHARS \u2014 the max chars of query text " + "dateparser.search_dates() is handed during temporal analysis (0 = " + "unlimited), made live by switchroom's temporal-offload image patch " + "(which moved the synchronous extraction off the shared asyncio loop onto " + "a single-worker thread and bounds its input, ending multi-second loop " + "stalls on multi-KB consolidation queries); unset means the image's baked " + "2000, set 0 to restore an unbounded full scan); and " + "HINDSIGHT_API_TEXT_SEARCH_EXTENSION_NATIVE_LANGUAGE \u2014 the Postgres " + "text-search regconfig the native BM25 arms and the search_vector index " + "write run to_tsvector/to_tsquery against; unset means upstream's " + "always-safe `english`, set `hindsight_english` to use switchroom's " + "junk-stopping config (dict + stopword file provisioned durably by the " + "image entrypoint, recall degrading to semantic-only if it is ever " + "missing); and " + "HINDSIGHT_API_BM25_MAX_QUERY_TERMS \u2014 the max number of query tokens the " + "keyword (BM25) recall arm searches for; unset means upstream's 0 = " + "unbounded, which makes a multi-KB consolidation query a disjunction " + "over hundreds of terms; set e.g. 64 to cap it, at the cost of " + "silently dropping the tail of a long query; and " + "HINDSIGHT_API_EMBEDDINGS_PROVIDER with the four " + "HINDSIGHT_API_EMBEDDINGS_ONNX_{MODEL_ID,POOLING,QUERY_PREFIX," + "PASSAGE_PREFIX} keys \u2014 the ONNX-embeddings cutover: unset keeps " + "upstream's slower `local` (PyTorch) backend, set `onnx` plus the model " + "id/pooling to run the SAME model on ONNX-CPU at a fraction of the " + "latency; the two _PREFIX keys are the query/passage affixes and are " + "set to the empty string for bge parity (vectors are byte-identical " + "between the two backends, so no re-embedding is needed) \u2014 unusually " + "for this block an EMPTY value here is honoured, not dropped, because an " + "affix-free prefix is the intended value; and the v0.9.0 knobs " + "switchroom allowlists for reach but emits no value for \u2014 " + "HINDSIGHT_API_RERANKER_MAX_CANDIDATES_{LOW,MID,HIGH} (per-recall-budget " + "rerank caps; unset/0 falls back to the global " + "HINDSIGHT_API_RERANKER_MAX_CANDIDATES switchroom does pin), " + "HINDSIGHT_API_EMBEDDINGS_MAX_INPUT_TOKENS (embedding-input truncation " + "ceiling; unset means the model's own limit), " + "HINDSIGHT_API_ENTITY_TRGM_SIMILARITY_THRESHOLD (entity-resolution fuzzy " + "match floor; unset means upstream's 0.15) and " + "HINDSIGHT_API_ENABLE_{TEMPORAL_RETRIEVAL,GRAPH_RETRIEVAL,RERANKING} " + "(global per-stage kill switches, all default true \u2014 the emergency lever " + "if one retrieval stage is implicated in a latency incident, at the cost " + "of recall quality); plus the " + "embedded-PostgreSQL (pg0) sizing keys switchroom manages in " + "src/setup/hindsight-pg-defaults.ts (`HINDSIGHT_PG_ENV_KEYS`: " + "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE, " + "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS \u2014 a postgres size string such " + "as `4GB`, or the sentinel `off` to leave pg0's own default for that " + "one knob). A value set here " + "REPLACES switchroom's default and is emitted even when the gating " + "capability is absent, so an operator can always force a knob. Other " + "`HINDSIGHT_API_*` keys are deliberately IGNORED \u2014 a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
|
|
14050
14056
|
});
|
|
14051
14057
|
MicrosoftWorkspaceConfigSchema = exports_external.object({
|
|
14052
14058
|
microsoft_client_id: exports_external.string().min(1).optional().describe("Microsoft OAuth application (client) ID from Entra portal " + "(literal string or vault reference e.g. " + "'vault:microsoft-oauth-client-id'). OPTIONAL \u2014 omit it to use " + "switchroom's shipped default Microsoft app (zero-config). " + "Set it only to bring your own Entra app (BYO)."),
|
|
@@ -16136,7 +16142,13 @@ function resolveHindsightPerfOverrides(configEnv, processEnv = process.env) {
|
|
|
16136
16142
|
const seenProcess = new Set;
|
|
16137
16143
|
for (const key of HINDSIGHT_PERF_ENV_KEYS) {
|
|
16138
16144
|
const fromProcess = processEnv[key];
|
|
16139
|
-
if (typeof fromProcess
|
|
16145
|
+
if (typeof fromProcess !== "string")
|
|
16146
|
+
continue;
|
|
16147
|
+
if (HINDSIGHT_PERF_ALLOW_EMPTY_KEYS.has(key)) {
|
|
16148
|
+
set(out, seenProcess, key, fromProcess);
|
|
16149
|
+
continue;
|
|
16150
|
+
}
|
|
16151
|
+
if (fromProcess.trim() !== "") {
|
|
16140
16152
|
set(out, seenProcess, key, fromProcess.trim());
|
|
16141
16153
|
}
|
|
16142
16154
|
}
|
|
@@ -16145,6 +16157,10 @@ function resolveHindsightPerfOverrides(configEnv, processEnv = process.env) {
|
|
|
16145
16157
|
for (const [key, raw] of configEntries) {
|
|
16146
16158
|
if (!HINDSIGHT_PERF_ENV_KEYS.has(key))
|
|
16147
16159
|
continue;
|
|
16160
|
+
if (HINDSIGHT_PERF_ALLOW_EMPTY_KEYS.has(key)) {
|
|
16161
|
+
set(out, seenConfig, key, String(raw));
|
|
16162
|
+
continue;
|
|
16163
|
+
}
|
|
16148
16164
|
const value = String(raw).trim();
|
|
16149
16165
|
if (value === "")
|
|
16150
16166
|
continue;
|
|
@@ -16190,7 +16206,7 @@ function hindsightPerfEnv(caps, overrides = new Map) {
|
|
|
16190
16206
|
function findUnmanagedHindsightEnvKeys(configEnv) {
|
|
16191
16207
|
return Object.keys(configEnv ?? {}).filter((key) => !HINDSIGHT_PERF_ENV_KEYS.has(key) && !HINDSIGHT_PG_ENV_KEYS.has(key)).sort();
|
|
16192
16208
|
}
|
|
16193
|
-
var HINDSIGHT_RERANKER_MAX_CANDIDATES_FOR_DERIVATION = 150, HINDSIGHT_DEFAULT_RECALL_MAX_CANDIDATES_PER_SOURCE, HINDSIGHT_DEFAULT_LINK_EXPANSION_PER_ENTITY_LIMIT = 50, HINDSIGHT_DEFAULT_LINK_EXPANSION_TIMEOUT_S = 2, HINDSIGHT_DEFAULT_LLM_REASONING_EFFORT = "low", HINDSIGHT_DEFAULT_LLM_MAX_CONCURRENT = 4, HINDSIGHT_DEFAULT_RETAIN_LLM_MAX_CONCURRENT = 1, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT, HINDSIGHT_DEFAULT_RERANKER_LOCAL_FP16 = "true", HINDSIGHT_DEFAULT_RERANKER_LOCAL_BATCH_SIZE = 128, HINDSIGHT_DEFAULT_LLM_STRICT_SCHEMA = "true", HINDSIGHT_DEFAULT_LLM_MAX_RETRIES = 2, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM = 2, HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE = 1000, HINDSIGHT_DEFAULT_RERANKER_BUCKET_BATCHING = "true", HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES = 150, HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT = 4, HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8, HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 250, HINDSIGHT_DEFAULT_CONSOLIDATION_SLOT_LIMIT = 6, HINDSIGHT_DEFAULT_CONSOLIDATION_RESERVED_SLOTS = 1, HINDSIGHT_DEFAULT_GRAPH_SEED_MIN_SIMILARITY = 0.3, HINDSIGHT_DEFAULT_LLM_SUPPORTS_MAX_ITEMS = "false", HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential", HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS = 30, HINDSIGHT_DEFAULT_TEXT_SEARCH_EXTENSION = "pg_search", HINDSIGHT_PERF_DEFAULTS_UNGATED, HINDSIGHT_PERF_DEFAULTS_GPU, HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM, HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS, HINDSIGHT_WORKER_SLOT_TYPES, HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES, HINDSIGHT_PERF_ENV_KEYS;
|
|
16209
|
+
var HINDSIGHT_RERANKER_MAX_CANDIDATES_FOR_DERIVATION = 150, HINDSIGHT_DEFAULT_RECALL_MAX_CANDIDATES_PER_SOURCE, HINDSIGHT_DEFAULT_LINK_EXPANSION_PER_ENTITY_LIMIT = 50, HINDSIGHT_DEFAULT_LINK_EXPANSION_TIMEOUT_S = 2, HINDSIGHT_DEFAULT_LLM_REASONING_EFFORT = "low", HINDSIGHT_DEFAULT_LLM_MAX_CONCURRENT = 4, HINDSIGHT_DEFAULT_RETAIN_LLM_MAX_CONCURRENT = 1, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT, HINDSIGHT_DEFAULT_RERANKER_LOCAL_FP16 = "true", HINDSIGHT_DEFAULT_RERANKER_LOCAL_BATCH_SIZE = 128, HINDSIGHT_DEFAULT_LLM_STRICT_SCHEMA = "true", HINDSIGHT_DEFAULT_LLM_MAX_RETRIES = 2, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM = 2, HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE = 1000, HINDSIGHT_DEFAULT_RERANKER_BUCKET_BATCHING = "true", HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES = 150, HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT = 4, HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8, HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 250, HINDSIGHT_DEFAULT_CONSOLIDATION_SLOT_LIMIT = 6, HINDSIGHT_DEFAULT_CONSOLIDATION_RESERVED_SLOTS = 1, HINDSIGHT_DEFAULT_GRAPH_SEED_MIN_SIMILARITY = 0.3, HINDSIGHT_DEFAULT_LLM_SUPPORTS_MAX_ITEMS = "false", HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential", HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS = 30, HINDSIGHT_DEFAULT_TEXT_SEARCH_EXTENSION = "pg_search", HINDSIGHT_DEFAULT_QUERY_ANALYZER_LANGUAGES = "en", HINDSIGHT_PERF_DEFAULTS_UNGATED, HINDSIGHT_PERF_DEFAULTS_GPU, HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM, HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS, HINDSIGHT_PERF_ALLOW_EMPTY_KEYS, HINDSIGHT_WORKER_SLOT_TYPES, HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES, HINDSIGHT_RECALL_POOL_ENV_KEYS, HINDSIGHT_PERF_ENV_KEYS;
|
|
16194
16210
|
var init_hindsight_perf_defaults = __esm(() => {
|
|
16195
16211
|
init_hindsight_pg_defaults();
|
|
16196
16212
|
HINDSIGHT_DEFAULT_RECALL_MAX_CANDIDATES_PER_SOURCE = Math.ceil(HINDSIGHT_RERANKER_MAX_CANDIDATES_FOR_DERIVATION * 0.4);
|
|
@@ -16261,7 +16277,11 @@ var init_hindsight_perf_defaults = __esm(() => {
|
|
|
16261
16277
|
"HINDSIGHT_API_GRAPH_SEED_MIN_SIMILARITY",
|
|
16262
16278
|
String(HINDSIGHT_DEFAULT_GRAPH_SEED_MIN_SIMILARITY)
|
|
16263
16279
|
],
|
|
16264
|
-
["HINDSIGHT_API_TEXT_SEARCH_EXTENSION", HINDSIGHT_DEFAULT_TEXT_SEARCH_EXTENSION]
|
|
16280
|
+
["HINDSIGHT_API_TEXT_SEARCH_EXTENSION", HINDSIGHT_DEFAULT_TEXT_SEARCH_EXTENSION],
|
|
16281
|
+
[
|
|
16282
|
+
"HINDSIGHT_API_QUERY_ANALYZER_LANGUAGES",
|
|
16283
|
+
HINDSIGHT_DEFAULT_QUERY_ANALYZER_LANGUAGES
|
|
16284
|
+
]
|
|
16265
16285
|
];
|
|
16266
16286
|
HINDSIGHT_PERF_DEFAULTS_GPU = [
|
|
16267
16287
|
["HINDSIGHT_API_RERANKER_LOCAL_FP16", HINDSIGHT_DEFAULT_RERANKER_LOCAL_FP16],
|
|
@@ -16296,10 +16316,26 @@ var init_hindsight_perf_defaults = __esm(() => {
|
|
|
16296
16316
|
"HINDSIGHT_API_RETAIN_WALL_TIMEOUT",
|
|
16297
16317
|
"HINDSIGHT_API_CONSOLIDATION_RECALL_MAX_CONCURRENT",
|
|
16298
16318
|
"HINDSIGHT_MM_REFRESH_MIN_INTERVAL_S",
|
|
16299
|
-
"HINDSIGHT_API_TEMPORAL_LANGUAGES",
|
|
16300
16319
|
"HINDSIGHT_API_TEMPORAL_MAX_QUERY_CHARS",
|
|
16301
16320
|
"HINDSIGHT_API_TEXT_SEARCH_EXTENSION_NATIVE_LANGUAGE",
|
|
16302
|
-
"HINDSIGHT_API_BM25_MAX_QUERY_TERMS"
|
|
16321
|
+
"HINDSIGHT_API_BM25_MAX_QUERY_TERMS",
|
|
16322
|
+
"HINDSIGHT_API_EMBEDDINGS_PROVIDER",
|
|
16323
|
+
"HINDSIGHT_API_EMBEDDINGS_ONNX_MODEL_ID",
|
|
16324
|
+
"HINDSIGHT_API_EMBEDDINGS_ONNX_POOLING",
|
|
16325
|
+
"HINDSIGHT_API_EMBEDDINGS_ONNX_QUERY_PREFIX",
|
|
16326
|
+
"HINDSIGHT_API_EMBEDDINGS_ONNX_PASSAGE_PREFIX",
|
|
16327
|
+
"HINDSIGHT_API_RERANKER_MAX_CANDIDATES_LOW",
|
|
16328
|
+
"HINDSIGHT_API_RERANKER_MAX_CANDIDATES_MID",
|
|
16329
|
+
"HINDSIGHT_API_RERANKER_MAX_CANDIDATES_HIGH",
|
|
16330
|
+
"HINDSIGHT_API_EMBEDDINGS_MAX_INPUT_TOKENS",
|
|
16331
|
+
"HINDSIGHT_API_ENTITY_TRGM_SIMILARITY_THRESHOLD",
|
|
16332
|
+
"HINDSIGHT_API_ENABLE_TEMPORAL_RETRIEVAL",
|
|
16333
|
+
"HINDSIGHT_API_ENABLE_GRAPH_RETRIEVAL",
|
|
16334
|
+
"HINDSIGHT_API_ENABLE_RERANKING"
|
|
16335
|
+
]);
|
|
16336
|
+
HINDSIGHT_PERF_ALLOW_EMPTY_KEYS = new Set([
|
|
16337
|
+
"HINDSIGHT_API_EMBEDDINGS_ONNX_QUERY_PREFIX",
|
|
16338
|
+
"HINDSIGHT_API_EMBEDDINGS_ONNX_PASSAGE_PREFIX"
|
|
16303
16339
|
]);
|
|
16304
16340
|
HINDSIGHT_WORKER_SLOT_TYPES = [
|
|
16305
16341
|
"consolidation",
|
|
@@ -16313,6 +16349,15 @@ var init_hindsight_perf_defaults = __esm(() => {
|
|
|
16313
16349
|
`HINDSIGHT_API_WORKER_${type.toUpperCase()}_MAX_SLOTS`,
|
|
16314
16350
|
`HINDSIGHT_API_WORKER_${type.toUpperCase()}_RESERVED_SLOTS`
|
|
16315
16351
|
]));
|
|
16352
|
+
HINDSIGHT_RECALL_POOL_ENV_KEYS = [
|
|
16353
|
+
"HINDSIGHT_API_WORKERS",
|
|
16354
|
+
"HINDSIGHT_API_WORKER_ENABLED",
|
|
16355
|
+
"HINDSIGHT_API_DATABASE_URL",
|
|
16356
|
+
"HINDSIGHT_API_DB_URL",
|
|
16357
|
+
"HINDSIGHT_API_DB_POOL_MAX_SIZE",
|
|
16358
|
+
"HINDSIGHT_API_READ_DB_POOL_MAX_SIZE",
|
|
16359
|
+
"HINDSIGHT_API_RUN_MIGRATIONS_ON_STARTUP"
|
|
16360
|
+
];
|
|
16316
16361
|
HINDSIGHT_PERF_ENV_KEYS = new Set([
|
|
16317
16362
|
...[
|
|
16318
16363
|
...HINDSIGHT_PERF_DEFAULTS_UNGATED,
|
|
@@ -16321,7 +16366,8 @@ var init_hindsight_perf_defaults = __esm(() => {
|
|
|
16321
16366
|
].map(([k]) => k),
|
|
16322
16367
|
...HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS,
|
|
16323
16368
|
...HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES.keys(),
|
|
16324
|
-
...HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES.values()
|
|
16369
|
+
...HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES.values(),
|
|
16370
|
+
...HINDSIGHT_RECALL_POOL_ENV_KEYS
|
|
16325
16371
|
]);
|
|
16326
16372
|
});
|
|
16327
16373
|
|
|
@@ -16491,7 +16537,7 @@ function resolveCheckedHindsightContextBudget(llm) {
|
|
|
16491
16537
|
assertHindsightContextBudgetFits(budget);
|
|
16492
16538
|
return budget;
|
|
16493
16539
|
}
|
|
16494
|
-
var HINDSIGHT_CLAUDE_CONTEXT_WINDOW = 200000, HINDSIGHT_CONSERVATIVE_CONTEXT_WINDOW = 32768, HINDSIGHT_CONTEXT_SAFETY_FRACTION = 0.2, HINDSIGHT_CONSOLIDATION_PROMPT_OVERHEAD_TOKENS = 2270, HINDSIGHT_CONSOLIDATION_TOKENS_PER_FACT =
|
|
16540
|
+
var HINDSIGHT_CLAUDE_CONTEXT_WINDOW = 200000, HINDSIGHT_CONSERVATIVE_CONTEXT_WINDOW = 32768, HINDSIGHT_CONTEXT_SAFETY_FRACTION = 0.2, HINDSIGHT_CONSOLIDATION_PROMPT_OVERHEAD_TOKENS = 2270, HINDSIGHT_CONSOLIDATION_TOKENS_PER_FACT = 5000, HINDSIGHT_RETAIN_PROMPT_ESTIMATE_TOKENS = 8000, HINDSIGHT_CONSOLIDATION_BATCH_SIZE_CEILING = 12, HINDSIGHT_CONSOLIDATION_BATCH_SIZE_FLOOR = 1, HINDSIGHT_CONSOLIDATION_MAX_COMPLETION_CEILING = 16384, HINDSIGHT_RETAIN_MAX_COMPLETION_CEILING = 16384, HINDSIGHT_UPSTREAM_RETAIN_CHUNK_SIZE = 3000, HINDSIGHT_CONSOLIDATION_MAX_COMPLETION_FLOOR = 1024, HINDSIGHT_RETAIN_MAX_COMPLETION_FLOOR, HINDSIGHT_REFLECT_MAX_CONTEXT_FLOOR = 2048, HindsightContextBudgetError;
|
|
16495
16541
|
var init_hindsight_context_budget = __esm(() => {
|
|
16496
16542
|
HINDSIGHT_RETAIN_MAX_COMPLETION_FLOOR = HINDSIGHT_UPSTREAM_RETAIN_CHUNK_SIZE + 72;
|
|
16497
16543
|
HindsightContextBudgetError = class HindsightContextBudgetError extends Error {
|
|
@@ -18697,20 +18743,16 @@ function probeDockerAvailability(probe = defaultDockerProbe) {
|
|
|
18697
18743
|
function isDockerAvailable(probe = defaultDockerProbe) {
|
|
18698
18744
|
return probeDockerAvailability(probe) === "ok";
|
|
18699
18745
|
}
|
|
18746
|
+
function dockerNameMatchesExactly(probe, args, want) {
|
|
18747
|
+
const out = probe(args);
|
|
18748
|
+
return (out ?? "").split(`
|
|
18749
|
+
`).map((n) => n.trim()).some((n) => n === want);
|
|
18750
|
+
}
|
|
18700
18751
|
function isHindsightRunning(probe = defaultDockerProbe) {
|
|
18701
|
-
|
|
18702
|
-
return (out ?? "").trim().length > 0;
|
|
18752
|
+
return dockerNameMatchesExactly(probe, ["ps", "--filter", "name=switchroom-hindsight", "--format", "{{.Names}}"], HINDSIGHT_CONTAINER_NAME);
|
|
18703
18753
|
}
|
|
18704
18754
|
function isHindsightContainerExists(probe = defaultDockerProbe) {
|
|
18705
|
-
|
|
18706
|
-
"ps",
|
|
18707
|
-
"-a",
|
|
18708
|
-
"--filter",
|
|
18709
|
-
"name=switchroom-hindsight",
|
|
18710
|
-
"--format",
|
|
18711
|
-
"{{.Names}}"
|
|
18712
|
-
]);
|
|
18713
|
-
return (out ?? "").trim().length > 0;
|
|
18755
|
+
return dockerNameMatchesExactly(probe, ["ps", "-a", "--filter", "name=switchroom-hindsight", "--format", "{{.Names}}"], HINDSIGHT_CONTAINER_NAME);
|
|
18714
18756
|
}
|
|
18715
18757
|
function resolveHindsightPerOpLlm(llm) {
|
|
18716
18758
|
const out = [];
|
|
@@ -19243,11 +19285,28 @@ function hindsightResolvedCapabilities(llm, litellm, gpu, perf) {
|
|
|
19243
19285
|
localLlm: hindsightLocalLlmEnabled(llm, litellm, perf?.localLlm)
|
|
19244
19286
|
};
|
|
19245
19287
|
}
|
|
19246
|
-
function getHindsightStatus() {
|
|
19288
|
+
function getHindsightStatus(exec = (cmd, args) => execFileSync2(cmd, args, { stdio: "pipe", encoding: "utf-8" })) {
|
|
19247
19289
|
try {
|
|
19248
|
-
const output =
|
|
19249
|
-
|
|
19250
|
-
|
|
19290
|
+
const output = exec("docker", [
|
|
19291
|
+
"ps",
|
|
19292
|
+
"-a",
|
|
19293
|
+
"--filter",
|
|
19294
|
+
"name=switchroom-hindsight",
|
|
19295
|
+
"--format",
|
|
19296
|
+
"{{.Names}}\t{{.Status}}"
|
|
19297
|
+
]);
|
|
19298
|
+
for (const line of output.split(`
|
|
19299
|
+
`)) {
|
|
19300
|
+
const tab = line.indexOf("\t");
|
|
19301
|
+
if (tab === -1)
|
|
19302
|
+
continue;
|
|
19303
|
+
if (line.slice(0, tab).trim() !== HINDSIGHT_CONTAINER_NAME)
|
|
19304
|
+
continue;
|
|
19305
|
+
const status = line.slice(tab + 1).trim();
|
|
19306
|
+
if (status.length > 0)
|
|
19307
|
+
return status;
|
|
19308
|
+
}
|
|
19309
|
+
return null;
|
|
19251
19310
|
} catch {
|
|
19252
19311
|
return null;
|
|
19253
19312
|
}
|
|
@@ -19409,7 +19468,7 @@ async function ensureHindsightConsumer(configPath, account, uid = HINDSIGHT_DEFA
|
|
|
19409
19468
|
atomicWriteFileSync3(configPath, tail, mode);
|
|
19410
19469
|
return { added: true, reason: "added" };
|
|
19411
19470
|
}
|
|
19412
|
-
var HINDSIGHT_DEFAULT_API_PORT = 18888, HINDSIGHT_DEFAULT_UI_PORT = 9999, HINDSIGHT_DEFAULT_MCP_URL, HINDSIGHT_DEFAULT_API_BASE_URL, HINDSIGHT_CONSUMER_NAME = "hindsight", HINDSIGHT_DEFAULT_WORKER_ID = "switchroom-hindsight", HINDSIGHT_DATA_VOLUME = "switchroom-hindsight-data", HINDSIGHT_DEFAULT_UID = 11000, HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight", HINDSIGHT_IMAGE, HINDSIGHT_DEFAULT_MODEL = "claude-sonnet-5", HINDSIGHT_DEFAULT_LITELLM_MODEL = "openrouter/google/gemini-3.1-flash-lite", HINDSIGHT_DEFAULT_MCP_STATELESS = true, HINDSIGHT_DEFAULT_REQUEUE_DEAD_LETTERS = "1", HINDSIGHT_DEFAULT_REQUEUE_MAX = "25", HINDSIGHT_BROKER_SOCK_VOLUME, HINDSIGHT_CREDS_MIRROR_VOLUME, HINDSIGHT_CRED_DIR = "/run/claude-creds", HINDSIGHT_RETAIN_CHUNK_SIZE = 3000, HINDSIGHT_DEFAULT_RETAIN_MAX_COMPLETION_TOKENS = 16384, HINDSIGHT_RETAIN_MIN_TOKENS_PER_SECOND = 80.6, HINDSIGHT_RETAIN_CLIENT_DEADLINE_S, HINDSIGHT_DEFAULT_MEM_LIMIT = "16g", HINDSIGHT_DEFAULT_MEM_RESERVATION = "4g", HINDSIGHT_DEFAULT_PIDS_LIMIT = 1000, HINDSIGHT_DEFAULT_SHM_SIZE = "2g", HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)', HINDSIGHT_HEALTHCHECK_CMD, DOCKER_PROBE_TIMEOUT_MS, defaultDockerProbe = (args) => {
|
|
19471
|
+
var HINDSIGHT_DEFAULT_API_PORT = 18888, HINDSIGHT_DEFAULT_UI_PORT = 9999, HINDSIGHT_DEFAULT_MCP_URL, HINDSIGHT_DEFAULT_API_BASE_URL, HINDSIGHT_CONSUMER_NAME = "hindsight", HINDSIGHT_DEFAULT_WORKER_ID = "switchroom-hindsight", HINDSIGHT_CONTAINER_NAME = "switchroom-hindsight", HINDSIGHT_DATA_VOLUME = "switchroom-hindsight-data", HINDSIGHT_DEFAULT_UID = 11000, HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight", HINDSIGHT_IMAGE, HINDSIGHT_DEFAULT_MODEL = "claude-sonnet-5", HINDSIGHT_DEFAULT_LITELLM_MODEL = "openrouter/google/gemini-3.1-flash-lite", HINDSIGHT_DEFAULT_MCP_STATELESS = true, HINDSIGHT_DEFAULT_REQUEUE_DEAD_LETTERS = "1", HINDSIGHT_DEFAULT_REQUEUE_MAX = "25", HINDSIGHT_BROKER_SOCK_VOLUME, HINDSIGHT_CREDS_MIRROR_VOLUME, HINDSIGHT_CRED_DIR = "/run/claude-creds", HINDSIGHT_RETAIN_CHUNK_SIZE = 3000, HINDSIGHT_DEFAULT_RETAIN_MAX_COMPLETION_TOKENS = 16384, HINDSIGHT_RETAIN_MIN_TOKENS_PER_SECOND = 80.6, HINDSIGHT_RETAIN_CLIENT_DEADLINE_S, HINDSIGHT_DEFAULT_MEM_LIMIT = "16g", HINDSIGHT_DEFAULT_MEM_RESERVATION = "4g", HINDSIGHT_DEFAULT_PIDS_LIMIT = 1000, HINDSIGHT_DEFAULT_SHM_SIZE = "2g", HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)', HINDSIGHT_HEALTHCHECK_CMD, DOCKER_PROBE_TIMEOUT_MS, defaultDockerProbe = (args) => {
|
|
19413
19472
|
try {
|
|
19414
19473
|
return execFileSync2("docker", args, {
|
|
19415
19474
|
stdio: "pipe",
|
|
@@ -32775,6 +32834,9 @@ var init_scaffold = __esm(() => {
|
|
|
32775
32834
|
"mcp__hindsight__delete_document",
|
|
32776
32835
|
"mcp__hindsight__get_mental_model",
|
|
32777
32836
|
"mcp__hindsight__list_mental_models",
|
|
32837
|
+
"mcp__hindsight__search_knowledge_pages",
|
|
32838
|
+
"mcp__hindsight__get_knowledge_page",
|
|
32839
|
+
"mcp__hindsight__get_knowledge_tree",
|
|
32778
32840
|
"mcp__hindsight__list_tags",
|
|
32779
32841
|
"mcp__hindsight__get_operation",
|
|
32780
32842
|
"mcp__hindsight__list_operations",
|
|
@@ -47545,7 +47607,7 @@ function recentUnextracted(docs, withinDays = 30, now = new Date, minTextLength
|
|
|
47545
47607
|
var DOCUMENTS_PAGE_LIMIT = 500;
|
|
47546
47608
|
|
|
47547
47609
|
// src/memory/hindsight-tools.ts
|
|
47548
|
-
var HINDSIGHT_MIN_API_VERSION = "0.
|
|
47610
|
+
var HINDSIGHT_MIN_API_VERSION = "0.9.0", HINDSIGHT_REPAIR_MIN_API_VERSION = "0.8.5", EXPECTED_HINDSIGHT_TOOLS;
|
|
47549
47611
|
var init_hindsight_tools = __esm(() => {
|
|
47550
47612
|
EXPECTED_HINDSIGHT_TOOLS = {
|
|
47551
47613
|
recall: { required: ["query"] },
|
|
@@ -47588,7 +47650,7 @@ function buildRepairBankArgv(opts) {
|
|
|
47588
47650
|
const script = `if command -v hindsight-admin >/dev/null 2>&1; then exec hindsight-admin "$@"; fi; ` + `exec ${HINDSIGHT_ADMIN_BIN} "$@"`;
|
|
47589
47651
|
return [
|
|
47590
47652
|
"exec",
|
|
47591
|
-
opts.container ??
|
|
47653
|
+
opts.container ?? HINDSIGHT_CONTAINER_NAME2,
|
|
47592
47654
|
"sh",
|
|
47593
47655
|
"-c",
|
|
47594
47656
|
script,
|
|
@@ -47701,7 +47763,7 @@ function classifyRepairOutcome(run) {
|
|
|
47701
47763
|
detail: run.dryRun ? `Coverage complete: ${run.summary.alreadyPresent} index(es) present across ` + `${run.summary.banksScanned} bank(s), nothing to create.` : `${run.summary.created} created, ${run.summary.alreadyPresent} already present ` + `across ${run.summary.banksScanned} bank(s). Re-run \`switchroom doctor\` to confirm.`
|
|
47702
47764
|
};
|
|
47703
47765
|
}
|
|
47704
|
-
var
|
|
47766
|
+
var HINDSIGHT_CONTAINER_NAME2 = "switchroom-hindsight", HINDSIGHT_ADMIN_BIN = "/app/api/.venv/bin/hindsight-admin", REPAIR_FAILED_EXIT = 1, REPAIR_COVERAGE_MISSING_EXIT = 3, REPAIR_UNVERIFIED_EXIT = 4;
|
|
47705
47767
|
var init_hindsight_repair = __esm(() => {
|
|
47706
47768
|
init_hindsight_tools();
|
|
47707
47769
|
});
|
|
@@ -83039,7 +83101,7 @@ __export(exports_server2, {
|
|
|
83039
83101
|
TOOLS: () => TOOLS2
|
|
83040
83102
|
});
|
|
83041
83103
|
import { randomBytes as randomBytes18 } from "node:crypto";
|
|
83042
|
-
import { existsSync as
|
|
83104
|
+
import { existsSync as existsSync119 } from "node:fs";
|
|
83043
83105
|
function selfSocketPath() {
|
|
83044
83106
|
return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
|
|
83045
83107
|
}
|
|
@@ -83066,7 +83128,7 @@ async function dispatchTool2(name, args) {
|
|
|
83066
83128
|
return errorText2("hostd MCP: SWITCHROOM_AGENT_NAME env var is not set \u2014 cannot " + "determine which per-agent socket to talk to.");
|
|
83067
83129
|
}
|
|
83068
83130
|
const sockPath = selfSocketPath();
|
|
83069
|
-
if (!
|
|
83131
|
+
if (!existsSync119(sockPath)) {
|
|
83070
83132
|
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.`);
|
|
83071
83133
|
}
|
|
83072
83134
|
let req;
|
|
@@ -83317,13 +83379,13 @@ function resolveAuditLogPath() {
|
|
|
83317
83379
|
if (process.env.HOSTD_AUDIT_LOG_PATH)
|
|
83318
83380
|
return process.env.HOSTD_AUDIT_LOG_PATH;
|
|
83319
83381
|
const bindMounted = "/host-home/.switchroom/host-control-audit.log";
|
|
83320
|
-
if (
|
|
83382
|
+
if (existsSync119(bindMounted))
|
|
83321
83383
|
return bindMounted;
|
|
83322
83384
|
return defaultAuditLogPath2();
|
|
83323
83385
|
}
|
|
83324
83386
|
function getLastUpdateApplyStatus() {
|
|
83325
83387
|
const path10 = resolveAuditLogPath();
|
|
83326
|
-
if (!
|
|
83388
|
+
if (!existsSync119(path10)) {
|
|
83327
83389
|
return errorText2(`get_status: audit log not found at ${path10}. No update_apply has run yet?`);
|
|
83328
83390
|
}
|
|
83329
83391
|
let raw;
|
|
@@ -83877,6 +83939,18 @@ var init_mapping = __esm(() => {
|
|
|
83877
83939
|
job_spec: "keep-my-subscription-honest",
|
|
83878
83940
|
signature: "litellm-header-passthrough:oauth-leak-scope"
|
|
83879
83941
|
},
|
|
83942
|
+
"litellm-callback-mount-missing": {
|
|
83943
|
+
failure_mode: "constraint-violation",
|
|
83944
|
+
severity: 3,
|
|
83945
|
+
job_spec: "fleet-stays-healthy",
|
|
83946
|
+
signature: "litellm-callback-mount:missing-bind"
|
|
83947
|
+
},
|
|
83948
|
+
"litellm-passthrough-mount-stale": {
|
|
83949
|
+
failure_mode: "constraint-violation",
|
|
83950
|
+
severity: 3,
|
|
83951
|
+
job_spec: "fleet-stays-healthy",
|
|
83952
|
+
signature: "litellm-passthrough-mount:stale-python-version"
|
|
83953
|
+
},
|
|
83880
83954
|
"litellm-timeout-budget-drift": {
|
|
83881
83955
|
failure_mode: "drift",
|
|
83882
83956
|
severity: 3,
|
|
@@ -84079,11 +84153,11 @@ var init_test_agents = __esm(() => {
|
|
|
84079
84153
|
});
|
|
84080
84154
|
|
|
84081
84155
|
// src/litellm/header-passthrough-guard.ts
|
|
84082
|
-
import { existsSync as
|
|
84156
|
+
import { existsSync as existsSync122, readdirSync as readdirSync46 } from "node:fs";
|
|
84083
84157
|
function discoverLiveLitellmConfigPath(opts) {
|
|
84084
84158
|
const servicesDir = opts?.servicesDir ?? COOLIFY_SERVICES_DIR;
|
|
84085
84159
|
const readdir2 = opts?.readdirFn ?? ((p) => readdirSync46(p));
|
|
84086
|
-
const exists = opts?.existsFn ??
|
|
84160
|
+
const exists = opts?.existsFn ?? existsSync122;
|
|
84087
84161
|
let entries;
|
|
84088
84162
|
try {
|
|
84089
84163
|
entries = readdir2(servicesDir);
|
|
@@ -84172,8 +84246,159 @@ var init_header_passthrough_guard = __esm(() => {
|
|
|
84172
84246
|
]);
|
|
84173
84247
|
});
|
|
84174
84248
|
|
|
84249
|
+
// src/litellm/callback-mount-guard.ts
|
|
84250
|
+
function canReadComposeMounts(composeText) {
|
|
84251
|
+
if (composeText == null)
|
|
84252
|
+
return false;
|
|
84253
|
+
return extractComposeBindTargets(composeText) !== null;
|
|
84254
|
+
}
|
|
84255
|
+
function extractCustomCallbackModules(parsed) {
|
|
84256
|
+
const settings = parsed?.litellm_settings;
|
|
84257
|
+
if (settings == null || typeof settings !== "object")
|
|
84258
|
+
return [];
|
|
84259
|
+
const out = [];
|
|
84260
|
+
const seen = new Set;
|
|
84261
|
+
for (const key of CALLBACK_KEYS) {
|
|
84262
|
+
const raw = settings[key];
|
|
84263
|
+
if (!Array.isArray(raw))
|
|
84264
|
+
continue;
|
|
84265
|
+
for (const entry of raw) {
|
|
84266
|
+
if (typeof entry !== "string")
|
|
84267
|
+
continue;
|
|
84268
|
+
const reference = entry.trim();
|
|
84269
|
+
const parts = reference.split(".");
|
|
84270
|
+
if (parts.length !== 2)
|
|
84271
|
+
continue;
|
|
84272
|
+
const [module, attr] = parts;
|
|
84273
|
+
if (!module || !attr)
|
|
84274
|
+
continue;
|
|
84275
|
+
if (seen.has(module))
|
|
84276
|
+
continue;
|
|
84277
|
+
seen.add(module);
|
|
84278
|
+
out.push({ module, reference });
|
|
84279
|
+
}
|
|
84280
|
+
}
|
|
84281
|
+
return out;
|
|
84282
|
+
}
|
|
84283
|
+
function extractComposeBindTargets(composeText) {
|
|
84284
|
+
const targets = new Set;
|
|
84285
|
+
let doc2;
|
|
84286
|
+
try {
|
|
84287
|
+
doc2 = import_yaml28.parse(composeText);
|
|
84288
|
+
} catch {
|
|
84289
|
+
return null;
|
|
84290
|
+
}
|
|
84291
|
+
const services = doc2?.services;
|
|
84292
|
+
if (services == null || typeof services !== "object")
|
|
84293
|
+
return null;
|
|
84294
|
+
for (const svc of Object.values(services)) {
|
|
84295
|
+
const volumes = svc?.volumes;
|
|
84296
|
+
if (!Array.isArray(volumes))
|
|
84297
|
+
continue;
|
|
84298
|
+
for (const v of volumes) {
|
|
84299
|
+
if (typeof v === "string") {
|
|
84300
|
+
const parts = v.split(":");
|
|
84301
|
+
if (parts.length >= 2 && parts[1])
|
|
84302
|
+
targets.add(parts[1].trim());
|
|
84303
|
+
} else if (v != null && typeof v === "object") {
|
|
84304
|
+
const t = v.target;
|
|
84305
|
+
if (typeof t === "string" && t)
|
|
84306
|
+
targets.add(t.trim());
|
|
84307
|
+
}
|
|
84308
|
+
}
|
|
84309
|
+
}
|
|
84310
|
+
return targets;
|
|
84311
|
+
}
|
|
84312
|
+
function detectMissingCallbackMounts(parsedConfig, composeText) {
|
|
84313
|
+
const modules = extractCustomCallbackModules(parsedConfig);
|
|
84314
|
+
if (modules.length === 0 || composeText == null)
|
|
84315
|
+
return [];
|
|
84316
|
+
const targets = extractComposeBindTargets(composeText);
|
|
84317
|
+
if (targets === null)
|
|
84318
|
+
return [];
|
|
84319
|
+
const violations = [];
|
|
84320
|
+
for (const { module, reference } of modules) {
|
|
84321
|
+
const expectedTarget = `${CONTAINER_APP_DIR}/${module}.py`;
|
|
84322
|
+
if (targets.has(expectedTarget))
|
|
84323
|
+
continue;
|
|
84324
|
+
violations.push({
|
|
84325
|
+
module,
|
|
84326
|
+
reference,
|
|
84327
|
+
expectedTarget,
|
|
84328
|
+
detail: `litellm_settings names custom callback '${reference}' but no bind mount targets ` + `${expectedTarget} \u2014 the proxy will abort startup with ` + `ModuleNotFoundError: No module named '${module}'. Coolify regenerates this compose ` + `from services.docker_compose_raw in its database, so the mount must be restored THERE ` + `(a hand edit to the generated file is wiped on the next deploy).`
|
|
84329
|
+
});
|
|
84330
|
+
}
|
|
84331
|
+
return violations;
|
|
84332
|
+
}
|
|
84333
|
+
var import_yaml28, CONTAINER_APP_DIR = "/app", LIVE_COMPOSE_BASENAME = "docker-compose.yml", CALLBACK_KEYS;
|
|
84334
|
+
var init_callback_mount_guard = __esm(() => {
|
|
84335
|
+
import_yaml28 = __toESM(require_dist(), 1);
|
|
84336
|
+
CALLBACK_KEYS = ["callbacks", "success_callback", "failure_callback"];
|
|
84337
|
+
});
|
|
84338
|
+
|
|
84339
|
+
// src/litellm/passthrough-mount-guard.ts
|
|
84340
|
+
function extractVersionedSitePackagesMounts(composeText) {
|
|
84341
|
+
const targets = extractComposeBindTargets(composeText);
|
|
84342
|
+
if (targets === null)
|
|
84343
|
+
return null;
|
|
84344
|
+
const out = [];
|
|
84345
|
+
for (const target of targets) {
|
|
84346
|
+
const m = VERSIONED_SITE_PACKAGES_RE.exec(target);
|
|
84347
|
+
if (m)
|
|
84348
|
+
out.push({ target, declaredPythonVersion: m[1] });
|
|
84349
|
+
}
|
|
84350
|
+
return out;
|
|
84351
|
+
}
|
|
84352
|
+
function detectStalePassthroughMounts(composeText, actualPythonVersion) {
|
|
84353
|
+
if (composeText == null || actualPythonVersion == null)
|
|
84354
|
+
return [];
|
|
84355
|
+
const mounts = extractVersionedSitePackagesMounts(composeText);
|
|
84356
|
+
if (mounts === null)
|
|
84357
|
+
return [];
|
|
84358
|
+
const violations = [];
|
|
84359
|
+
for (const { target, declaredPythonVersion } of mounts) {
|
|
84360
|
+
if (declaredPythonVersion === actualPythonVersion)
|
|
84361
|
+
continue;
|
|
84362
|
+
violations.push({
|
|
84363
|
+
target,
|
|
84364
|
+
declaredPythonVersion,
|
|
84365
|
+
actualPythonVersion,
|
|
84366
|
+
detail: `bind mount targets ${target}, hard-coding CPython ${declaredPythonVersion}, but the live ` + `litellm image ships CPython ${actualPythonVersion} \u2014 its site-packages live under ` + `python${actualPythonVersion}/, so this mount lands at an inert path and the patch is ` + `SILENTLY dropped (no crash, no log; the proxy comes up "healthy" on unpatched code). ` + `Update the mount target to python${actualPythonVersion}/ in services.docker_compose_raw ` + `(a hand edit to the generated compose is wiped on the next Coolify deploy), then redeploy.`
|
|
84367
|
+
});
|
|
84368
|
+
}
|
|
84369
|
+
return violations;
|
|
84370
|
+
}
|
|
84371
|
+
var VERSIONED_SITE_PACKAGES_RE;
|
|
84372
|
+
var init_passthrough_mount_guard = __esm(() => {
|
|
84373
|
+
init_callback_mount_guard();
|
|
84374
|
+
VERSIONED_SITE_PACKAGES_RE = /\/python(\d+\.\d+)\/site-packages\//;
|
|
84375
|
+
});
|
|
84376
|
+
|
|
84175
84377
|
// src/fleet-health/litellm-config-sensor.ts
|
|
84176
|
-
import {
|
|
84378
|
+
import { execFileSync as execFileSync35 } from "node:child_process";
|
|
84379
|
+
import { readFileSync as readFileSync110, existsSync as existsSync123 } from "node:fs";
|
|
84380
|
+
import { dirname as dirname50, join as join124 } from "node:path";
|
|
84381
|
+
function resolveLiveLitellmPythonVersion() {
|
|
84382
|
+
try {
|
|
84383
|
+
const ps = execFileSync35("docker", ["ps", "--format", "{{.Names}}\t{{.Image}}"], {
|
|
84384
|
+
encoding: "utf-8",
|
|
84385
|
+
timeout: 5000,
|
|
84386
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
84387
|
+
});
|
|
84388
|
+
const line = ps.split(`
|
|
84389
|
+
`).find((l) => /litellm-database|(^|\s)litellm/i.test(l) && l.trim().length > 0);
|
|
84390
|
+
if (!line)
|
|
84391
|
+
return null;
|
|
84392
|
+
const name = line.split("\t")[0]?.trim();
|
|
84393
|
+
if (!name)
|
|
84394
|
+
return null;
|
|
84395
|
+
const lsOut = execFileSync35("docker", ["exec", name, "sh", "-c", "ls -d /app/.venv/lib/python*/ 2>/dev/null"], { encoding: "utf-8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
|
|
84396
|
+
const m = lsOut.match(/python(\d+\.\d+)/);
|
|
84397
|
+
return m ? m[1] : null;
|
|
84398
|
+
} catch {
|
|
84399
|
+
return null;
|
|
84400
|
+
}
|
|
84401
|
+
}
|
|
84177
84402
|
function resolveLitellmConfigPath(explicit, discoverFn = () => discoverLiveLitellmConfigPath()) {
|
|
84178
84403
|
if (explicit)
|
|
84179
84404
|
return explicit;
|
|
@@ -84184,7 +84409,7 @@ function resolveLitellmConfigPath(explicit, discoverFn = () => discoverLiveLitel
|
|
|
84184
84409
|
}
|
|
84185
84410
|
function scanLitellmConfig(opts = {}) {
|
|
84186
84411
|
const path10 = resolveLitellmConfigPath(opts.path, opts.discoverFn);
|
|
84187
|
-
const exists = opts.existsFn ??
|
|
84412
|
+
const exists = opts.existsFn ?? existsSync123;
|
|
84188
84413
|
const read = opts.readFn ?? ((p) => readFileSync110(p, "utf-8"));
|
|
84189
84414
|
const log = opts.log ?? (() => {});
|
|
84190
84415
|
const nowIso = opts.nowIso ?? new Date().toISOString();
|
|
@@ -84236,6 +84461,52 @@ function scanLitellmConfig(opts = {}) {
|
|
|
84236
84461
|
ts: nowIso
|
|
84237
84462
|
});
|
|
84238
84463
|
}
|
|
84464
|
+
const composePath = join124(dirname50(path10), LIVE_COMPOSE_BASENAME);
|
|
84465
|
+
let composeText = null;
|
|
84466
|
+
if (exists(composePath)) {
|
|
84467
|
+
try {
|
|
84468
|
+
composeText = read(composePath);
|
|
84469
|
+
} catch (e) {
|
|
84470
|
+
log(`fleet-health: litellm-config sensor \u2014 callback-mount check SKIPPED, ` + `${composePath} unreadable: ${String(e)}`);
|
|
84471
|
+
}
|
|
84472
|
+
} else {
|
|
84473
|
+
log(`fleet-health: litellm-config sensor \u2014 callback-mount check SKIPPED, ` + `no ${LIVE_COMPOSE_BASENAME} beside ${path10}`);
|
|
84474
|
+
}
|
|
84475
|
+
const missingMounts = detectMissingCallbackMounts(parsed, composeText);
|
|
84476
|
+
if (composeText !== null && !canReadComposeMounts(composeText)) {
|
|
84477
|
+
log(`fleet-health: litellm-config sensor \u2014 callback-mount check SKIPPED, ` + `${composePath} has no readable services/volumes mapping`);
|
|
84478
|
+
} else if (composeText !== null && missingMounts.length === 0) {
|
|
84479
|
+
log(`fleet-health: litellm-config sensor OK \u2014 every custom callback module is ` + `bind-mounted by ${composePath}`);
|
|
84480
|
+
}
|
|
84481
|
+
for (const m of missingMounts) {
|
|
84482
|
+
findings.push({
|
|
84483
|
+
signal: "litellm-callback-mount-missing",
|
|
84484
|
+
agent: LITELLM_PROXY_PSEUDO_AGENT,
|
|
84485
|
+
turn_id: `litellm-callback-mount:${m.module}`,
|
|
84486
|
+
log_pointer: `${composePath}: ${m.detail}`,
|
|
84487
|
+
ts: nowIso
|
|
84488
|
+
});
|
|
84489
|
+
}
|
|
84490
|
+
const versionedMounts = composeText !== null ? extractVersionedSitePackagesMounts(composeText) : null;
|
|
84491
|
+
const hasVersionedMounts = versionedMounts !== null && versionedMounts.length > 0;
|
|
84492
|
+
const pythonVersionFn = opts.pythonVersionFn ?? resolveLiveLitellmPythonVersion;
|
|
84493
|
+
const actualPythonVersion = hasVersionedMounts ? pythonVersionFn() : null;
|
|
84494
|
+
if (hasVersionedMounts && actualPythonVersion === null) {
|
|
84495
|
+
log(`fleet-health: litellm-config sensor \u2014 passthrough-mount check SKIPPED, ` + `could not resolve the live litellm image's CPython version (docker down, no ` + `container, or off-host CI/dev)`);
|
|
84496
|
+
}
|
|
84497
|
+
const staleMounts = detectStalePassthroughMounts(composeText, actualPythonVersion);
|
|
84498
|
+
if (hasVersionedMounts && actualPythonVersion !== null && staleMounts.length === 0) {
|
|
84499
|
+
log(`fleet-health: litellm-config sensor OK \u2014 every versioned site-packages shadow ` + `mount in ${composePath} matches the live image's CPython ${actualPythonVersion}`);
|
|
84500
|
+
}
|
|
84501
|
+
for (const s of staleMounts) {
|
|
84502
|
+
findings.push({
|
|
84503
|
+
signal: "litellm-passthrough-mount-stale",
|
|
84504
|
+
agent: LITELLM_PROXY_PSEUDO_AGENT,
|
|
84505
|
+
turn_id: `litellm-passthrough-mount:${s.declaredPythonVersion}->${s.actualPythonVersion}`,
|
|
84506
|
+
log_pointer: `${composePath}: ${s.detail}`,
|
|
84507
|
+
ts: nowIso
|
|
84508
|
+
});
|
|
84509
|
+
}
|
|
84239
84510
|
if (findings.length === 0)
|
|
84240
84511
|
return { status: "ok", path: path10, findings: [] };
|
|
84241
84512
|
for (const f of findings) {
|
|
@@ -84247,6 +84518,8 @@ var LITELLM_PROXY_PSEUDO_AGENT = "litellm-proxy";
|
|
|
84247
84518
|
var init_litellm_config_sensor = __esm(() => {
|
|
84248
84519
|
init_header_passthrough_guard();
|
|
84249
84520
|
init_timeout_budget();
|
|
84521
|
+
init_callback_mount_guard();
|
|
84522
|
+
init_passthrough_mount_guard();
|
|
84250
84523
|
});
|
|
84251
84524
|
|
|
84252
84525
|
// src/fleet-health/hindsight-gpu-drift-sensor.ts
|
|
@@ -84293,18 +84566,18 @@ __export(exports_scan, {
|
|
|
84293
84566
|
import {
|
|
84294
84567
|
readFileSync as readFileSync111,
|
|
84295
84568
|
readdirSync as readdirSync47,
|
|
84296
|
-
existsSync as
|
|
84569
|
+
existsSync as existsSync124,
|
|
84297
84570
|
mkdirSync as mkdirSync70,
|
|
84298
84571
|
writeFileSync as writeFileSync51
|
|
84299
84572
|
} from "node:fs";
|
|
84300
|
-
import { resolve as resolve64, dirname as
|
|
84573
|
+
import { resolve as resolve64, dirname as dirname51 } from "node:path";
|
|
84301
84574
|
import { homedir as homedir67 } from "node:os";
|
|
84302
84575
|
function resolveSwitchroomBase(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir67()) {
|
|
84303
84576
|
return resolve64(home2, ".switchroom");
|
|
84304
84577
|
}
|
|
84305
84578
|
function listAgents(base) {
|
|
84306
84579
|
const dir = resolve64(base, "agents");
|
|
84307
|
-
if (!
|
|
84580
|
+
if (!existsSync124(dir))
|
|
84308
84581
|
return [];
|
|
84309
84582
|
try {
|
|
84310
84583
|
return readdirSync47(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
|
|
@@ -84333,7 +84606,7 @@ function runScan(opts = {}) {
|
|
|
84333
84606
|
let gwText = "";
|
|
84334
84607
|
let sawArtifact = false;
|
|
84335
84608
|
try {
|
|
84336
|
-
if (
|
|
84609
|
+
if (existsSync124(turnsPath)) {
|
|
84337
84610
|
turnsText = readFileSync111(turnsPath, "utf-8");
|
|
84338
84611
|
sawArtifact = true;
|
|
84339
84612
|
}
|
|
@@ -84341,7 +84614,7 @@ function runScan(opts = {}) {
|
|
|
84341
84614
|
log(`fleet-health: WARN skipping ${agent} turns.jsonl unreadable: ${String(e)}`);
|
|
84342
84615
|
}
|
|
84343
84616
|
try {
|
|
84344
|
-
if (
|
|
84617
|
+
if (existsSync124(gwPath)) {
|
|
84345
84618
|
gwText = readFileSync111(gwPath, "utf-8");
|
|
84346
84619
|
sawArtifact = true;
|
|
84347
84620
|
}
|
|
@@ -84386,7 +84659,7 @@ function runScan(opts = {}) {
|
|
|
84386
84659
|
function readLedgerIfPresent(base) {
|
|
84387
84660
|
const path10 = ledgerPathForBase(base);
|
|
84388
84661
|
try {
|
|
84389
|
-
if (!
|
|
84662
|
+
if (!existsSync124(path10))
|
|
84390
84663
|
return null;
|
|
84391
84664
|
return JSON.parse(readFileSync111(path10, "utf-8"));
|
|
84392
84665
|
} catch {
|
|
@@ -84394,11 +84667,11 @@ function readLedgerIfPresent(base) {
|
|
|
84394
84667
|
}
|
|
84395
84668
|
}
|
|
84396
84669
|
function ledgerPathForBase(base) {
|
|
84397
|
-
return fleetHealthLedgerPath(
|
|
84670
|
+
return fleetHealthLedgerPath(dirname51(base));
|
|
84398
84671
|
}
|
|
84399
84672
|
function writeLedger(base, ledger) {
|
|
84400
84673
|
const path10 = ledgerPathForBase(base);
|
|
84401
|
-
mkdirSync70(
|
|
84674
|
+
mkdirSync70(dirname51(path10), { recursive: true });
|
|
84402
84675
|
writeFileSync51(path10, JSON.stringify(ledger, null, 2) + `
|
|
84403
84676
|
`, "utf-8");
|
|
84404
84677
|
return path10;
|
|
@@ -106709,6 +106982,323 @@ function redactMemoryWriteBody(body) {
|
|
|
106709
106982
|
init_helpers();
|
|
106710
106983
|
init_hindsight();
|
|
106711
106984
|
|
|
106985
|
+
// src/setup/hindsight-recall-pool.ts
|
|
106986
|
+
init_hindsight();
|
|
106987
|
+
init_hindsight_perf_defaults();
|
|
106988
|
+
import { execFileSync as execFileSync26 } from "node:child_process";
|
|
106989
|
+
var HINDSIGHT_RECALL_POOL_CONTAINER = "switchroom-hindsight-recall";
|
|
106990
|
+
var HINDSIGHT_RECALL_POOL_WORKER_ID = "switchroom-hindsight-recall";
|
|
106991
|
+
var HINDSIGHT_PG_MAX_CONNECTIONS = 250;
|
|
106992
|
+
var HINDSIGHT_BACKGROUND_DB_POOL_MAX_SIZE = 40;
|
|
106993
|
+
var HINDSIGHT_BACKGROUND_READ_DB_POOL_MAX_SIZE = 20;
|
|
106994
|
+
var HINDSIGHT_BACKGROUND_CONNECTION_BUDGET = HINDSIGHT_BACKGROUND_DB_POOL_MAX_SIZE + HINDSIGHT_BACKGROUND_READ_DB_POOL_MAX_SIZE;
|
|
106995
|
+
var HINDSIGHT_PG_SUPERUSER_RESERVED_CONNECTIONS = 3;
|
|
106996
|
+
var HINDSIGHT_RECALL_POOL_HEADROOM = 10;
|
|
106997
|
+
if (HINDSIGHT_RECALL_POOL_HEADROOM < HINDSIGHT_PG_SUPERUSER_RESERVED_CONNECTIONS) {
|
|
106998
|
+
throw new Error(`HINDSIGHT_RECALL_POOL_HEADROOM (${HINDSIGHT_RECALL_POOL_HEADROOM}) must be \u2265 ` + `HINDSIGHT_PG_SUPERUSER_RESERVED_CONNECTIONS ` + `(${HINDSIGHT_PG_SUPERUSER_RESERVED_CONNECTIONS}).`);
|
|
106999
|
+
}
|
|
107000
|
+
var HINDSIGHT_RECALL_POOL_MAX_TOTAL_CONNECTIONS = HINDSIGHT_PG_MAX_CONNECTIONS - HINDSIGHT_RECALL_POOL_HEADROOM;
|
|
107001
|
+
var HINDSIGHT_RECALL_POOL_CONNECTION_BUDGET = HINDSIGHT_RECALL_POOL_MAX_TOTAL_CONNECTIONS - HINDSIGHT_BACKGROUND_CONNECTION_BUDGET;
|
|
107002
|
+
var HINDSIGHT_RECALL_POOL_DEFAULT_WORKERS = 4;
|
|
107003
|
+
var HINDSIGHT_RECALL_POOL_PG_DSN = "postgresql://hindsight:hindsight@127.0.0.1:5432/hindsight";
|
|
107004
|
+
function hindsightBackgroundApiPort(publicApiPort) {
|
|
107005
|
+
return publicApiPort + 1;
|
|
107006
|
+
}
|
|
107007
|
+
function resolveHindsightLaunchPorts(input) {
|
|
107008
|
+
const publicApiPort = input.configUrlPort ?? input.reuseApiPort ?? input.defaultApiPort;
|
|
107009
|
+
const apiPort = input.recallEnabled ? hindsightBackgroundApiPort(publicApiPort) : publicApiPort;
|
|
107010
|
+
return { publicApiPort, basePorts: { apiPort, uiPort: input.uiPort } };
|
|
107011
|
+
}
|
|
107012
|
+
function publicPortIsUnserved(input) {
|
|
107013
|
+
if (input.configUrlPort == null || input.authorityApiPort == null)
|
|
107014
|
+
return false;
|
|
107015
|
+
if (input.poolRunning)
|
|
107016
|
+
return false;
|
|
107017
|
+
return input.authorityApiPort !== input.configUrlPort;
|
|
107018
|
+
}
|
|
107019
|
+
function planStaleRecallPoolConvergence(input) {
|
|
107020
|
+
if (input.recallEnabled)
|
|
107021
|
+
return { action: "none" };
|
|
107022
|
+
if (!input.poolRunning)
|
|
107023
|
+
return { action: "none" };
|
|
107024
|
+
if (input.configUrlPort == null || input.authorityApiPort == null) {
|
|
107025
|
+
return { action: "none" };
|
|
107026
|
+
}
|
|
107027
|
+
if (input.authorityApiPort === input.configUrlPort)
|
|
107028
|
+
return { action: "stop-pool" };
|
|
107029
|
+
return { action: "relaunch-single-on-public", publicApiPort: input.configUrlPort };
|
|
107030
|
+
}
|
|
107031
|
+
function recallPoolDbPoolSizing(workers) {
|
|
107032
|
+
const perWorker = Math.floor(HINDSIGHT_RECALL_POOL_CONNECTION_BUDGET / workers);
|
|
107033
|
+
const dbPoolMaxSize = Math.max(1, Math.floor(perWorker * 0.4));
|
|
107034
|
+
const readDbPoolMaxSize = Math.max(1, perWorker - dbPoolMaxSize);
|
|
107035
|
+
return { dbPoolMaxSize, readDbPoolMaxSize };
|
|
107036
|
+
}
|
|
107037
|
+
function assertRecallPoolConnectionBudget(cfg) {
|
|
107038
|
+
const poolDemand = cfg.workers * (cfg.dbPoolMaxSize + cfg.readDbPoolMaxSize);
|
|
107039
|
+
const total = poolDemand + HINDSIGHT_BACKGROUND_CONNECTION_BUDGET;
|
|
107040
|
+
if (total > HINDSIGHT_RECALL_POOL_MAX_TOTAL_CONNECTIONS) {
|
|
107041
|
+
throw new Error(`hindsight.recall_pool connection budget overflow: ${cfg.workers} workers \u00d7 ` + `(${cfg.dbPoolMaxSize} write + ${cfg.readDbPoolMaxSize} read) = ${poolDemand}, ` + `plus ${HINDSIGHT_BACKGROUND_CONNECTION_BUDGET} background = ${total} > the ` + `${HINDSIGHT_RECALL_POOL_MAX_TOTAL_CONNECTIONS}-connection ceiling ` + `(pg0 max_connections=${HINDSIGHT_PG_MAX_CONNECTIONS} \u2212 ` + `${HINDSIGHT_RECALL_POOL_HEADROOM} headroom, which reserves PG's ` + `${HINDSIGHT_PG_SUPERUSER_RESERVED_CONNECTIONS} superuser slots plus ` + `maintenance). Lower hindsight.recall_pool.workers or its ` + `db_pool_max_size/read_db_pool_max_size.`);
|
|
107042
|
+
}
|
|
107043
|
+
}
|
|
107044
|
+
function resolveRecallPoolConfig(input) {
|
|
107045
|
+
if (!input || input.enabled !== true)
|
|
107046
|
+
return null;
|
|
107047
|
+
const workers = input.workers && input.workers > 0 ? Math.floor(input.workers) : HINDSIGHT_RECALL_POOL_DEFAULT_WORKERS;
|
|
107048
|
+
const autoSized = recallPoolDbPoolSizing(workers);
|
|
107049
|
+
const cfg = {
|
|
107050
|
+
workers,
|
|
107051
|
+
dbPoolMaxSize: input.db_pool_max_size && input.db_pool_max_size > 0 ? Math.floor(input.db_pool_max_size) : autoSized.dbPoolMaxSize,
|
|
107052
|
+
readDbPoolMaxSize: input.read_db_pool_max_size && input.read_db_pool_max_size > 0 ? Math.floor(input.read_db_pool_max_size) : autoSized.readDbPoolMaxSize
|
|
107053
|
+
};
|
|
107054
|
+
assertRecallPoolConnectionBudget(cfg);
|
|
107055
|
+
return cfg;
|
|
107056
|
+
}
|
|
107057
|
+
function backgroundContainerPoolEnv() {
|
|
107058
|
+
return {
|
|
107059
|
+
HINDSIGHT_API_DB_POOL_MAX_SIZE: String(HINDSIGHT_BACKGROUND_DB_POOL_MAX_SIZE),
|
|
107060
|
+
HINDSIGHT_API_READ_DB_POOL_MAX_SIZE: String(HINDSIGHT_BACKGROUND_READ_DB_POOL_MAX_SIZE)
|
|
107061
|
+
};
|
|
107062
|
+
}
|
|
107063
|
+
var HINDSIGHT_SPLIT_MANAGED_DB_POOL_KEYS = [
|
|
107064
|
+
"HINDSIGHT_API_DB_POOL_MAX_SIZE",
|
|
107065
|
+
"HINDSIGHT_API_READ_DB_POOL_MAX_SIZE"
|
|
107066
|
+
];
|
|
107067
|
+
function splitDbPoolEnvConflicts(operatorEnv, cfg) {
|
|
107068
|
+
if (!operatorEnv)
|
|
107069
|
+
return [];
|
|
107070
|
+
const authority = backgroundContainerPoolEnv();
|
|
107071
|
+
const pool = {
|
|
107072
|
+
HINDSIGHT_API_DB_POOL_MAX_SIZE: String(cfg.dbPoolMaxSize),
|
|
107073
|
+
HINDSIGHT_API_READ_DB_POOL_MAX_SIZE: String(cfg.readDbPoolMaxSize)
|
|
107074
|
+
};
|
|
107075
|
+
const out = [];
|
|
107076
|
+
for (const key of HINDSIGHT_SPLIT_MANAGED_DB_POOL_KEYS) {
|
|
107077
|
+
const operatorValue = operatorEnv[key];
|
|
107078
|
+
if (operatorValue === undefined)
|
|
107079
|
+
continue;
|
|
107080
|
+
out.push({
|
|
107081
|
+
key,
|
|
107082
|
+
operatorValue: String(operatorValue),
|
|
107083
|
+
authorityValue: authority[key],
|
|
107084
|
+
poolValue: pool[key]
|
|
107085
|
+
});
|
|
107086
|
+
}
|
|
107087
|
+
return out;
|
|
107088
|
+
}
|
|
107089
|
+
function splitDbPoolEnvConflictWarning(c) {
|
|
107090
|
+
const knob = c.key === "HINDSIGHT_API_DB_POOL_MAX_SIZE" ? "hindsight.recall_pool.db_pool_max_size" : "hindsight.recall_pool.read_db_pool_max_size";
|
|
107091
|
+
return `hindsight.env.${c.key}=${c.operatorValue} is IGNORED while ` + `hindsight.recall_pool.enabled is true: the split computes this cap for both ` + `containers (authority ${c.authorityValue}, recall pool ${c.poolValue}) so the ` + `pg0 connection budget stays provable. Set \`${knob}\` instead \u2014 it is budget-checked \u2014 ` + `or set hindsight.recall_pool.enabled: false to have hindsight.env win again.`;
|
|
107092
|
+
}
|
|
107093
|
+
function splitContainerPerfEnv(operatorEnv) {
|
|
107094
|
+
return { ...operatorEnv ?? {}, ...backgroundContainerPoolEnv() };
|
|
107095
|
+
}
|
|
107096
|
+
function recallPoolEnvOverrides(cfg, poolPort) {
|
|
107097
|
+
return [
|
|
107098
|
+
["HINDSIGHT_API_HOST", HINDSIGHT_HOST_NETWORK_BIND_ADDR],
|
|
107099
|
+
["HINDSIGHT_API_PORT", String(poolPort)],
|
|
107100
|
+
["HINDSIGHT_API_DATABASE_URL", HINDSIGHT_RECALL_POOL_PG_DSN],
|
|
107101
|
+
["HINDSIGHT_API_DB_URL", HINDSIGHT_RECALL_POOL_PG_DSN],
|
|
107102
|
+
["HINDSIGHT_API_WORKERS", String(cfg.workers)],
|
|
107103
|
+
["HINDSIGHT_API_WORKER_ENABLED", "false"],
|
|
107104
|
+
["HINDSIGHT_API_RUN_MIGRATIONS_ON_STARTUP", "false"],
|
|
107105
|
+
["HINDSIGHT_ENABLE_CP", "false"],
|
|
107106
|
+
["HINDSIGHT_API_WORKER_ID", HINDSIGHT_RECALL_POOL_WORKER_ID],
|
|
107107
|
+
["HINDSIGHT_API_DB_POOL_MAX_SIZE", String(cfg.dbPoolMaxSize)],
|
|
107108
|
+
["HINDSIGHT_API_READ_DB_POOL_MAX_SIZE", String(cfg.readDbPoolMaxSize)]
|
|
107109
|
+
];
|
|
107110
|
+
}
|
|
107111
|
+
function applyRecallPoolEnvOverrides(base, overrides) {
|
|
107112
|
+
const overrideMap = new Map(overrides);
|
|
107113
|
+
const seen = new Set;
|
|
107114
|
+
const out = [];
|
|
107115
|
+
for (const [k, v] of base) {
|
|
107116
|
+
if (overrideMap.has(k)) {
|
|
107117
|
+
out.push([k, overrideMap.get(k)]);
|
|
107118
|
+
seen.add(k);
|
|
107119
|
+
} else {
|
|
107120
|
+
out.push([k, v]);
|
|
107121
|
+
}
|
|
107122
|
+
}
|
|
107123
|
+
for (const [k, v] of overrides) {
|
|
107124
|
+
if (!seen.has(k))
|
|
107125
|
+
out.push([k, v]);
|
|
107126
|
+
}
|
|
107127
|
+
return out;
|
|
107128
|
+
}
|
|
107129
|
+
function hindsightRecallPoolEnvPairs(opts) {
|
|
107130
|
+
const { cfg, poolPort, litellm, llm, gpu, perf, cpAccessKey } = opts;
|
|
107131
|
+
const base = hindsightContainerEnvPairs({
|
|
107132
|
+
apiPort: poolPort,
|
|
107133
|
+
litellm,
|
|
107134
|
+
llm,
|
|
107135
|
+
gpu,
|
|
107136
|
+
perf,
|
|
107137
|
+
cpAccessKey
|
|
107138
|
+
});
|
|
107139
|
+
return applyRecallPoolEnvOverrides(base, recallPoolEnvOverrides(cfg, poolPort));
|
|
107140
|
+
}
|
|
107141
|
+
function startHindsightRecallPool(opts) {
|
|
107142
|
+
const {
|
|
107143
|
+
cfg,
|
|
107144
|
+
poolPort,
|
|
107145
|
+
imageTag,
|
|
107146
|
+
litellm,
|
|
107147
|
+
llm,
|
|
107148
|
+
gpu,
|
|
107149
|
+
perf,
|
|
107150
|
+
cpAccessKey,
|
|
107151
|
+
mirrorDir,
|
|
107152
|
+
exec = (cmd, args2) => {
|
|
107153
|
+
execFileSync26(cmd, args2, { stdio: "pipe" });
|
|
107154
|
+
}
|
|
107155
|
+
} = opts;
|
|
107156
|
+
assertRecallPoolConnectionBudget(cfg);
|
|
107157
|
+
const envArgs = hindsightRecallPoolEnvPairs({
|
|
107158
|
+
cfg,
|
|
107159
|
+
poolPort,
|
|
107160
|
+
litellm,
|
|
107161
|
+
llm,
|
|
107162
|
+
gpu,
|
|
107163
|
+
perf,
|
|
107164
|
+
cpAccessKey
|
|
107165
|
+
}).flatMap(([k, v]) => ["-e", `${k}=${v}`]);
|
|
107166
|
+
const probe2 = pickHindsightLiteLlmProbeUrl(llm, litellm);
|
|
107167
|
+
const healthCmd = probe2 ? buildLiteLlmAwareHealthCmd(poolPort, probe2) : HINDSIGHT_HEALTHCHECK_CMD.replace("localhost:8888", `localhost:${poolPort}`);
|
|
107168
|
+
const args = [
|
|
107169
|
+
"run",
|
|
107170
|
+
"-d",
|
|
107171
|
+
"--name",
|
|
107172
|
+
HINDSIGHT_RECALL_POOL_CONTAINER,
|
|
107173
|
+
"--restart",
|
|
107174
|
+
"always",
|
|
107175
|
+
"--shm-size",
|
|
107176
|
+
HINDSIGHT_DEFAULT_SHM_SIZE,
|
|
107177
|
+
...hindsightGpuEnabled(gpu) ? ["--gpus", "all"] : [],
|
|
107178
|
+
"--network",
|
|
107179
|
+
"host",
|
|
107180
|
+
"--user",
|
|
107181
|
+
"hindsight",
|
|
107182
|
+
"--health-cmd",
|
|
107183
|
+
healthCmd,
|
|
107184
|
+
"--health-interval",
|
|
107185
|
+
"30s",
|
|
107186
|
+
"--health-timeout",
|
|
107187
|
+
"5s",
|
|
107188
|
+
"--health-retries",
|
|
107189
|
+
"3",
|
|
107190
|
+
"--health-start-period",
|
|
107191
|
+
"60s",
|
|
107192
|
+
...mirrorDir ? ["-v", `${HINDSIGHT_CREDS_MIRROR_VOLUME}:${HINDSIGHT_CRED_DIR}`] : ["--tmpfs", `${HINDSIGHT_CRED_DIR}:rw,mode=0700,uid=${HINDSIGHT_DEFAULT_UID},gid=${HINDSIGHT_DEFAULT_UID}`],
|
|
107193
|
+
"-v",
|
|
107194
|
+
`${HINDSIGHT_BROKER_SOCK_VOLUME}:/run/switchroom/auth-broker`,
|
|
107195
|
+
...envArgs,
|
|
107196
|
+
hindsightImageRef(imageTag)
|
|
107197
|
+
];
|
|
107198
|
+
exec("docker", args);
|
|
107199
|
+
}
|
|
107200
|
+
function stopHindsightRecallPool(exec = (cmd, args) => {
|
|
107201
|
+
execFileSync26(cmd, args, { stdio: "pipe" });
|
|
107202
|
+
}) {
|
|
107203
|
+
try {
|
|
107204
|
+
exec("docker", ["update", "--restart=no", HINDSIGHT_RECALL_POOL_CONTAINER]);
|
|
107205
|
+
} catch {}
|
|
107206
|
+
try {
|
|
107207
|
+
exec("docker", ["stop", HINDSIGHT_RECALL_POOL_CONTAINER]);
|
|
107208
|
+
} catch {}
|
|
107209
|
+
try {
|
|
107210
|
+
exec("docker", ["rm", "-f", HINDSIGHT_RECALL_POOL_CONTAINER]);
|
|
107211
|
+
} catch {}
|
|
107212
|
+
}
|
|
107213
|
+
function isHindsightRecallPoolRunning(probe2 = defaultDockerProbe) {
|
|
107214
|
+
return dockerNameMatchesExactly(probe2, ["ps", "--filter", `name=${HINDSIGHT_RECALL_POOL_CONTAINER}`, "--format", "{{.Names}}"], HINDSIGHT_RECALL_POOL_CONTAINER);
|
|
107215
|
+
}
|
|
107216
|
+
var HINDSIGHT_HEALTH_MIN_TIMEOUT_MS = 150000;
|
|
107217
|
+
var HINDSIGHT_HEALTH_PHASE_BUDGET_MS = {
|
|
107218
|
+
apiStartup: 60000,
|
|
107219
|
+
pgFirstBoot: 90000,
|
|
107220
|
+
migrations: 90000,
|
|
107221
|
+
perExtraWorker: 15000
|
|
107222
|
+
};
|
|
107223
|
+
function hindsightHealthTimeoutMs(input) {
|
|
107224
|
+
const p = HINDSIGHT_HEALTH_PHASE_BUDGET_MS;
|
|
107225
|
+
let budget;
|
|
107226
|
+
switch (input.role) {
|
|
107227
|
+
case "authority":
|
|
107228
|
+
case "single-container":
|
|
107229
|
+
budget = p.apiStartup + p.pgFirstBoot + p.migrations;
|
|
107230
|
+
break;
|
|
107231
|
+
case "recall-pool": {
|
|
107232
|
+
const workers = input.workers && input.workers > 0 ? input.workers : 1;
|
|
107233
|
+
budget = p.apiStartup + (workers - 1) * p.perExtraWorker;
|
|
107234
|
+
break;
|
|
107235
|
+
}
|
|
107236
|
+
}
|
|
107237
|
+
return Math.max(HINDSIGHT_HEALTH_MIN_TIMEOUT_MS, budget);
|
|
107238
|
+
}
|
|
107239
|
+
async function waitForHindsightHealthy(port, opts = {}) {
|
|
107240
|
+
const {
|
|
107241
|
+
timeoutMs = opts.role ? hindsightHealthTimeoutMs({ role: opts.role, workers: opts.workers }) : HINDSIGHT_HEALTH_MIN_TIMEOUT_MS,
|
|
107242
|
+
intervalMs = 3000,
|
|
107243
|
+
fetchImpl = fetch,
|
|
107244
|
+
sleep: sleep4 = (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
107245
|
+
now: now2 = () => Date.now()
|
|
107246
|
+
} = opts;
|
|
107247
|
+
const deadline = now2() + timeoutMs;
|
|
107248
|
+
for (;; ) {
|
|
107249
|
+
try {
|
|
107250
|
+
const res = await fetchImpl(`http://127.0.0.1:${port}/health`, {
|
|
107251
|
+
signal: AbortSignal.timeout(4000)
|
|
107252
|
+
});
|
|
107253
|
+
if (res.ok)
|
|
107254
|
+
return true;
|
|
107255
|
+
} catch {}
|
|
107256
|
+
if (now2() >= deadline)
|
|
107257
|
+
return false;
|
|
107258
|
+
await sleep4(intervalMs);
|
|
107259
|
+
}
|
|
107260
|
+
}
|
|
107261
|
+
async function launchRecallPoolHealthGated(opts) {
|
|
107262
|
+
const log = opts.log ?? (() => {});
|
|
107263
|
+
log(` Waiting for the authority container to become healthy on ` + `${opts.authorityApiPort} (pg0 startup)...`);
|
|
107264
|
+
if (!await opts.waitForHealthy(opts.authorityApiPort, "authority")) {
|
|
107265
|
+
return { outcome: "authority-unhealthy" };
|
|
107266
|
+
}
|
|
107267
|
+
opts.stopPool();
|
|
107268
|
+
log(` Starting recall pool (${opts.workers} workers) on ${opts.publicApiPort}...`);
|
|
107269
|
+
opts.startPool();
|
|
107270
|
+
if (await opts.waitForHealthy(opts.publicApiPort, "recall-pool")) {
|
|
107271
|
+
return { outcome: "split" };
|
|
107272
|
+
}
|
|
107273
|
+
const reason = `the recall pool did not become healthy on ${opts.publicApiPort}`;
|
|
107274
|
+
log(` Recall pool unhealthy on ${opts.publicApiPort}; degrading to a single ` + `container on the public port so memory.config.url stays served...`);
|
|
107275
|
+
opts.degradeToSingleContainer();
|
|
107276
|
+
if (await opts.waitForHealthy(opts.publicApiPort, "single-container")) {
|
|
107277
|
+
return { outcome: "degraded-single-container", reason };
|
|
107278
|
+
}
|
|
107279
|
+
return { outcome: "degrade-failed", reason };
|
|
107280
|
+
}
|
|
107281
|
+
async function convergeStaleRecallPool(opts) {
|
|
107282
|
+
const log = opts.log ?? (() => {});
|
|
107283
|
+
switch (opts.plan.action) {
|
|
107284
|
+
case "none":
|
|
107285
|
+
return { outcome: "nothing-to-do" };
|
|
107286
|
+
case "stop-pool":
|
|
107287
|
+
log(" hindsight.recall_pool is disabled but a recall pool container is running; " + "the authority already serves the public port, so removing the orphan pool...");
|
|
107288
|
+
opts.stopPool();
|
|
107289
|
+
return { outcome: "pool-stopped" };
|
|
107290
|
+
case "relaunch-single-on-public": {
|
|
107291
|
+
const publicApiPort = opts.plan.publicApiPort;
|
|
107292
|
+
log(` hindsight.recall_pool is disabled but a recall pool container still holds the ` + `public port ${publicApiPort}; relaunching a single container on it to match the ` + `declared topology...`);
|
|
107293
|
+
opts.relaunchSingleOnPublic(publicApiPort);
|
|
107294
|
+
if (await opts.waitForHealthy(publicApiPort, "single-container")) {
|
|
107295
|
+
return { outcome: "relaunched-single", publicApiPort };
|
|
107296
|
+
}
|
|
107297
|
+
return { outcome: "relaunch-failed", publicApiPort };
|
|
107298
|
+
}
|
|
107299
|
+
}
|
|
107300
|
+
}
|
|
107301
|
+
|
|
106712
107302
|
// src/setup/hindsight-gpu-guard.ts
|
|
106713
107303
|
init_hindsight();
|
|
106714
107304
|
function assessHindsightGpuDrop(args) {
|
|
@@ -107086,6 +107676,7 @@ Cross-agent reflection plan
|
|
|
107086
107676
|
return;
|
|
107087
107677
|
}
|
|
107088
107678
|
console.log(source_default.gray(" Stopping switchroom-hindsight..."));
|
|
107679
|
+
stopHindsightRecallPool();
|
|
107089
107680
|
stopHindsight();
|
|
107090
107681
|
console.log(source_default.green(" Hindsight container stopped and removed."));
|
|
107091
107682
|
return;
|
|
@@ -107141,6 +107732,182 @@ Cross-agent reflection plan
|
|
|
107141
107732
|
}
|
|
107142
107733
|
const reusePorts = recreate ? getRunningHindsightPorts() : null;
|
|
107143
107734
|
if (isHindsightRunning() && !recreate) {
|
|
107735
|
+
let noRecreatePoolCfg = null;
|
|
107736
|
+
try {
|
|
107737
|
+
noRecreatePoolCfg = resolveRecallPoolConfig(getConfig(program3).hindsight?.recall_pool);
|
|
107738
|
+
} catch {
|
|
107739
|
+
noRecreatePoolCfg = null;
|
|
107740
|
+
}
|
|
107741
|
+
if (noRecreatePoolCfg && !isHindsightRecallPoolRunning()) {
|
|
107742
|
+
console.log(source_default.gray(`
|
|
107743
|
+
Authority container is up but the recall pool is not \u2014 restoring the pool...`));
|
|
107744
|
+
const repairConfig = getConfig(program3);
|
|
107745
|
+
const repairGpu = hindsightGpuDecision(resolveHindsightGpuOverride({ flag: opts.gpu, config: repairConfig.hindsight?.gpu }));
|
|
107746
|
+
const repairConfigUrlPort = (() => {
|
|
107747
|
+
try {
|
|
107748
|
+
const p = Number(new URL(repairConfig.memory?.config?.url ?? "").port);
|
|
107749
|
+
return Number.isInteger(p) && p > 0 ? p : undefined;
|
|
107750
|
+
} catch {
|
|
107751
|
+
return;
|
|
107752
|
+
}
|
|
107753
|
+
})();
|
|
107754
|
+
const { publicApiPort: repairPublicPort, basePorts: repairBasePorts } = resolveHindsightLaunchPorts({
|
|
107755
|
+
recallEnabled: true,
|
|
107756
|
+
configUrlPort: repairConfigUrlPort,
|
|
107757
|
+
reuseApiPort: undefined,
|
|
107758
|
+
uiPort: 0,
|
|
107759
|
+
defaultApiPort: HINDSIGHT_DEFAULT_API_PORT
|
|
107760
|
+
});
|
|
107761
|
+
try {
|
|
107762
|
+
const { litellm: repairLitellm } = await resolveLiteLLMForHindsight(repairConfig);
|
|
107763
|
+
const repairLlm = await getResolvedLlm();
|
|
107764
|
+
const repairCpAccessKey = await resolveHindsightCpAccessKey(repairConfig);
|
|
107765
|
+
for (const conflict of splitDbPoolEnvConflicts(repairConfig.hindsight?.env, noRecreatePoolCfg)) {
|
|
107766
|
+
console.log(source_default.yellow(` ! ${splitDbPoolEnvConflictWarning(conflict)}`));
|
|
107767
|
+
}
|
|
107768
|
+
const repairAuthorityEnv = splitContainerPerfEnv(repairConfig.hindsight?.env);
|
|
107769
|
+
const repairResult = await launchRecallPoolHealthGated({
|
|
107770
|
+
publicApiPort: repairPublicPort,
|
|
107771
|
+
authorityApiPort: repairBasePorts.apiPort,
|
|
107772
|
+
workers: noRecreatePoolCfg.workers,
|
|
107773
|
+
waitForHealthy: (p, role) => waitForHindsightHealthy(p, { role, workers: noRecreatePoolCfg.workers }),
|
|
107774
|
+
stopPool: () => stopHindsightRecallPool(),
|
|
107775
|
+
startPool: () => startHindsightRecallPool({
|
|
107776
|
+
cfg: noRecreatePoolCfg,
|
|
107777
|
+
poolPort: repairPublicPort,
|
|
107778
|
+
imageTag: effectiveTag,
|
|
107779
|
+
litellm: repairLitellm,
|
|
107780
|
+
llm: repairLlm,
|
|
107781
|
+
gpu: repairGpu.enabled,
|
|
107782
|
+
perf: {
|
|
107783
|
+
env: repairAuthorityEnv,
|
|
107784
|
+
memLimit: repairConfig.hindsight?.mem_limit
|
|
107785
|
+
},
|
|
107786
|
+
cpAccessKey: repairCpAccessKey,
|
|
107787
|
+
mirrorDir: hindsightConsumerMirrorDir(repairConfig)
|
|
107788
|
+
}),
|
|
107789
|
+
degradeToSingleContainer: () => {
|
|
107790
|
+
const uiPort2 = getRunningHindsightPorts()?.uiPort ?? HINDSIGHT_DEFAULT_UI_PORT;
|
|
107791
|
+
stopHindsightRecallPool();
|
|
107792
|
+
stopHindsight();
|
|
107793
|
+
startHindsight({ apiPort: repairPublicPort, uiPort: uiPort2 }, repairLitellm, effectiveTag, repairLlm, hindsightConsumerMirrorDir(repairConfig), repairGpu.enabled, { env: repairConfig.hindsight?.env, memLimit: repairConfig.hindsight?.mem_limit }, repairCpAccessKey);
|
|
107794
|
+
},
|
|
107795
|
+
log: (m) => console.log(source_default.gray(m))
|
|
107796
|
+
});
|
|
107797
|
+
switch (repairResult.outcome) {
|
|
107798
|
+
case "split":
|
|
107799
|
+
console.log(source_default.green(`
|
|
107800
|
+
Recall pool restored on ${repairPublicPort} ` + `(authority on ${repairBasePorts.apiPort}).
|
|
107801
|
+
`));
|
|
107802
|
+
break;
|
|
107803
|
+
case "degraded-single-container":
|
|
107804
|
+
console.log(source_default.yellow(`
|
|
107805
|
+
! Recall pool failed (${repairResult.reason}); DEGRADED to a single ` + `container on the public port ${repairPublicPort}. memory.config.url IS ` + `served, but the recall split is OFF \u2014 inspect \`docker logs ` + `${HINDSIGHT_RECALL_POOL_CONTAINER}\` and re-run \`switchroom memory setup ` + `--recreate\` once fixed, or set hindsight.recall_pool.enabled: false.
|
|
107806
|
+
`));
|
|
107807
|
+
break;
|
|
107808
|
+
case "authority-unhealthy":
|
|
107809
|
+
console.error(source_default.red(`
|
|
107810
|
+
The running authority container is not answering /health on ` + `${repairBasePorts.apiPort}; cannot restore the pool. Inspect ` + `\`docker logs switchroom-hindsight\` and re-run \`switchroom memory setup ` + `--recreate\`.
|
|
107811
|
+
`));
|
|
107812
|
+
process.exit(1);
|
|
107813
|
+
break;
|
|
107814
|
+
case "degrade-failed":
|
|
107815
|
+
console.error(source_default.red(`
|
|
107816
|
+
Recall pool restore failed AND the single-container fallback could not ` + `bind the public port ${repairPublicPort}. Inspect \`docker logs ` + `switchroom-hindsight\` and \`docker logs ${HINDSIGHT_RECALL_POOL_CONTAINER}\`, ` + `then re-run \`switchroom memory setup --recreate\`.
|
|
107817
|
+
`));
|
|
107818
|
+
process.exit(1);
|
|
107819
|
+
break;
|
|
107820
|
+
}
|
|
107821
|
+
} catch (err) {
|
|
107822
|
+
console.error(source_default.red(`
|
|
107823
|
+
Failed to restore the recall pool: ${err.message}
|
|
107824
|
+
`));
|
|
107825
|
+
process.exit(1);
|
|
107826
|
+
}
|
|
107827
|
+
const repairUrl = `http://127.0.0.1:${repairPublicPort}/mcp/`;
|
|
107828
|
+
try {
|
|
107829
|
+
if (persistMemoryConfigUrl(getConfigPath(program3), "claude-code", repairUrl)) {
|
|
107830
|
+
console.log(source_default.gray(` Updated ${getConfigPath(program3)} with memory.config.url = ${repairUrl}`));
|
|
107831
|
+
}
|
|
107832
|
+
} catch (err) {
|
|
107833
|
+
console.error(source_default.yellow(` Note: could not auto-update switchroom.yaml: ${err.message}`));
|
|
107834
|
+
}
|
|
107835
|
+
return;
|
|
107836
|
+
}
|
|
107837
|
+
const livePorts = getRunningHindsightPorts();
|
|
107838
|
+
const liveApiPort = livePorts?.apiPort;
|
|
107839
|
+
const liveConfigUrlPort = (() => {
|
|
107840
|
+
try {
|
|
107841
|
+
const p = Number(new URL(getConfig(program3).memory?.config?.url ?? "").port);
|
|
107842
|
+
return Number.isInteger(p) && p > 0 ? p : undefined;
|
|
107843
|
+
} catch {
|
|
107844
|
+
return;
|
|
107845
|
+
}
|
|
107846
|
+
})();
|
|
107847
|
+
const stalePlan = planStaleRecallPoolConvergence({
|
|
107848
|
+
recallEnabled: noRecreatePoolCfg !== null,
|
|
107849
|
+
poolRunning: isHindsightRecallPoolRunning(),
|
|
107850
|
+
authorityApiPort: liveApiPort,
|
|
107851
|
+
configUrlPort: liveConfigUrlPort
|
|
107852
|
+
});
|
|
107853
|
+
if (stalePlan.action !== "none") {
|
|
107854
|
+
const staleConfig = getConfig(program3);
|
|
107855
|
+
const staleGpu = hindsightGpuDecision(resolveHindsightGpuOverride({ flag: opts.gpu, config: staleConfig.hindsight?.gpu }));
|
|
107856
|
+
try {
|
|
107857
|
+
const needsSecrets = stalePlan.action === "relaunch-single-on-public";
|
|
107858
|
+
const staleLitellm = needsSecrets ? (await resolveLiteLLMForHindsight(staleConfig)).litellm : undefined;
|
|
107859
|
+
const staleLlm = needsSecrets ? await getResolvedLlm() : undefined;
|
|
107860
|
+
const staleCpAccessKey = needsSecrets ? await resolveHindsightCpAccessKey(staleConfig) : undefined;
|
|
107861
|
+
const staleResult = await convergeStaleRecallPool({
|
|
107862
|
+
plan: stalePlan,
|
|
107863
|
+
stopPool: () => stopHindsightRecallPool(),
|
|
107864
|
+
relaunchSingleOnPublic: (publicApiPort2) => {
|
|
107865
|
+
stopHindsightRecallPool();
|
|
107866
|
+
stopHindsight();
|
|
107867
|
+
startHindsight({ apiPort: publicApiPort2, uiPort: livePorts?.uiPort ?? HINDSIGHT_DEFAULT_UI_PORT }, staleLitellm, effectiveTag, staleLlm, hindsightConsumerMirrorDir(staleConfig), staleGpu.enabled, { env: staleConfig.hindsight?.env, memLimit: staleConfig.hindsight?.mem_limit }, staleCpAccessKey);
|
|
107868
|
+
},
|
|
107869
|
+
waitForHealthy: (p, role) => waitForHindsightHealthy(p, { role }),
|
|
107870
|
+
log: (m) => console.log(source_default.gray(m))
|
|
107871
|
+
});
|
|
107872
|
+
switch (staleResult.outcome) {
|
|
107873
|
+
case "pool-stopped":
|
|
107874
|
+
console.log(source_default.green(`
|
|
107875
|
+
Removed the orphan recall pool (hindsight.recall_pool is disabled).
|
|
107876
|
+
`));
|
|
107877
|
+
break;
|
|
107878
|
+
case "relaunched-single":
|
|
107879
|
+
console.log(source_default.green(`
|
|
107880
|
+
Converged to the declared single-container topology on ` + `${staleResult.publicApiPort}.
|
|
107881
|
+
`));
|
|
107882
|
+
break;
|
|
107883
|
+
case "relaunch-failed":
|
|
107884
|
+
console.error(source_default.red(`
|
|
107885
|
+
hindsight.recall_pool is disabled, but the single container relaunched ` + `on the public port ${staleResult.publicApiPort} did not become healthy \u2014 ` + `nothing is serving fleet memory. Inspect \`docker logs ` + `switchroom-hindsight --tail 50\`, then re-run \`switchroom memory setup ` + `--recreate\`.
|
|
107886
|
+
`));
|
|
107887
|
+
process.exit(1);
|
|
107888
|
+
break;
|
|
107889
|
+
case "nothing-to-do":
|
|
107890
|
+
break;
|
|
107891
|
+
}
|
|
107892
|
+
} catch (err) {
|
|
107893
|
+
console.error(source_default.red(`
|
|
107894
|
+
Failed to converge the stale recall pool: ${err.message}
|
|
107895
|
+
`));
|
|
107896
|
+
process.exit(1);
|
|
107897
|
+
}
|
|
107898
|
+
return;
|
|
107899
|
+
}
|
|
107900
|
+
if (publicPortIsUnserved({
|
|
107901
|
+
configUrlPort: liveConfigUrlPort,
|
|
107902
|
+
authorityApiPort: liveApiPort,
|
|
107903
|
+
poolRunning: isHindsightRecallPoolRunning()
|
|
107904
|
+
})) {
|
|
107905
|
+
console.error(source_default.red(`
|
|
107906
|
+
switchroom-hindsight is running on port ${liveApiPort}, but ` + `memory.config.url points at ${liveConfigUrlPort} and nothing is serving ` + `it \u2014 fleet memory is DOWN.
|
|
107907
|
+
` + ` Run \`switchroom memory setup --recreate\` to rebind it to ` + `${liveConfigUrlPort}, or set hindsight.recall_pool.enabled: true to restore ` + `the recall pool on the public port.
|
|
107908
|
+
`));
|
|
107909
|
+
process.exit(1);
|
|
107910
|
+
}
|
|
107144
107911
|
console.log(source_default.green(`
|
|
107145
107912
|
Hindsight container is already running (switchroom-hindsight).
|
|
107146
107913
|
`));
|
|
@@ -107223,31 +107990,24 @@ Cross-agent reflection plan
|
|
|
107223
107990
|
process.exit(1);
|
|
107224
107991
|
}
|
|
107225
107992
|
}
|
|
107993
|
+
stopHindsightRecallPool();
|
|
107226
107994
|
if (isHindsightContainerExists()) {
|
|
107227
107995
|
console.log(source_default.gray(" Removing existing switchroom-hindsight container..."));
|
|
107228
107996
|
stopHindsight();
|
|
107229
107997
|
}
|
|
107230
|
-
let
|
|
107998
|
+
let recallPoolCfg;
|
|
107999
|
+
try {
|
|
108000
|
+
recallPoolCfg = resolveRecallPoolConfig(getConfig(program3).hindsight?.recall_pool);
|
|
108001
|
+
} catch (err) {
|
|
108002
|
+
console.log(source_default.yellow(` ! Ignoring hindsight.recall_pool (${err.message}); ` + `starting the single-container topology.`));
|
|
108003
|
+
recallPoolCfg = null;
|
|
108004
|
+
}
|
|
108005
|
+
let uiPort;
|
|
107231
108006
|
if (reusePorts) {
|
|
107232
|
-
|
|
107233
|
-
if (reusePorts.apiPort !== HINDSIGHT_DEFAULT_API_PORT) {
|
|
107234
|
-
let explicitUrl;
|
|
107235
|
-
try {
|
|
107236
|
-
explicitUrl = getConfig(program3).memory?.config?.url;
|
|
107237
|
-
} catch {
|
|
107238
|
-
explicitUrl = undefined;
|
|
107239
|
-
}
|
|
107240
|
-
if (!explicitUrl) {
|
|
107241
|
-
console.log(source_default.yellow(`
|
|
107242
|
-
\u26a0 MIGRATION HAZARD: reusing stale host port ${reusePorts.apiPort} ` + `for switchroom-hindsight, but scaffolding now defaults to ` + `${HINDSIGHT_DEFAULT_API_PORT} and no explicit memory.config.url is set.`));
|
|
107243
|
-
console.log(source_default.yellow(` Agents may be repointed to a dead URL (http://127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}/mcp/) ` + `while the container listens on ${reusePorts.apiPort}.`));
|
|
107244
|
-
console.log(source_default.yellow(` Fix: set memory.config.url: http://127.0.0.1:${reusePorts.apiPort}/mcp/ explicitly, ` + `or migrate the container to port ${HINDSIGHT_DEFAULT_API_PORT}.
|
|
107245
|
-
`));
|
|
107246
|
-
}
|
|
107247
|
-
}
|
|
108007
|
+
uiPort = reusePorts.uiPort;
|
|
107248
108008
|
} else {
|
|
107249
108009
|
try {
|
|
107250
|
-
|
|
108010
|
+
uiPort = (await pickHindsightPorts()).uiPort;
|
|
107251
108011
|
} catch (err) {
|
|
107252
108012
|
console.error(source_default.red(`
|
|
107253
108013
|
${err.message}
|
|
@@ -107255,31 +108015,39 @@ Cross-agent reflection plan
|
|
|
107255
108015
|
process.exit(1);
|
|
107256
108016
|
}
|
|
107257
108017
|
}
|
|
107258
|
-
|
|
107259
|
-
console.log(source_default.yellow(` Port ${HINDSIGHT_DEFAULT_API_PORT} is already in use; ` + `using ${ports.apiPort}/${ports.uiPort} instead.`));
|
|
107260
|
-
}
|
|
107261
|
-
let conflict = await preflightHindsightPorts(ports);
|
|
107262
|
-
if (conflict) {
|
|
107263
|
-
const heldBy = conflict.holder ? ` (held by ${conflict.holder})` : "";
|
|
107264
|
-
console.log(source_default.yellow(`
|
|
107265
|
-
Chosen Hindsight port ${conflict.port} is occupied${heldBy}; ` + `selecting a free port instead of crash-looping.`));
|
|
108018
|
+
const configUrlPort = (() => {
|
|
107266
108019
|
try {
|
|
107267
|
-
|
|
107268
|
-
|
|
107269
|
-
|
|
107270
|
-
|
|
107271
|
-
`));
|
|
107272
|
-
process.exit(1);
|
|
108020
|
+
const p = Number(new URL(getConfig(program3).memory?.config?.url ?? "").port);
|
|
108021
|
+
return Number.isInteger(p) && p > 0 ? p : undefined;
|
|
108022
|
+
} catch {
|
|
108023
|
+
return;
|
|
107273
108024
|
}
|
|
107274
|
-
|
|
108025
|
+
})();
|
|
108026
|
+
const { publicApiPort, basePorts } = resolveHindsightLaunchPorts({
|
|
108027
|
+
recallEnabled: recallPoolCfg != null,
|
|
108028
|
+
configUrlPort,
|
|
108029
|
+
reuseApiPort: reusePorts?.apiPort,
|
|
108030
|
+
uiPort,
|
|
108031
|
+
defaultApiPort: HINDSIGHT_DEFAULT_API_PORT
|
|
108032
|
+
});
|
|
108033
|
+
if (!recallPoolCfg && configUrlPort == null && reusePorts && reusePorts.apiPort !== HINDSIGHT_DEFAULT_API_PORT) {
|
|
108034
|
+
console.log(source_default.yellow(`
|
|
108035
|
+
Note: reusing host port ${reusePorts.apiPort} for switchroom-hindsight ` + `(scaffolding default is ${HINDSIGHT_DEFAULT_API_PORT}); memory.config.url will be ` + `pinned to ${reusePorts.apiPort} so agents follow the live port. Migrate deliberately ` + `if you want ${HINDSIGHT_DEFAULT_API_PORT}.
|
|
108036
|
+
`));
|
|
108037
|
+
}
|
|
108038
|
+
if (recallPoolCfg) {
|
|
108039
|
+
console.log(source_default.gray(` recall_pool enabled: authority (pg0 + background) on ${basePorts.apiPort}, ` + `${recallPoolCfg.workers}-worker recall pool on ${publicApiPort}.`));
|
|
108040
|
+
}
|
|
108041
|
+
const portsToBind = recallPoolCfg ? [publicApiPort, basePorts.apiPort, basePorts.uiPort] : [basePorts.apiPort, basePorts.uiPort];
|
|
108042
|
+
for (const p of portsToBind) {
|
|
108043
|
+
const conflict = await preflightHindsightPorts({ apiPort: p, uiPort: p });
|
|
107275
108044
|
if (conflict) {
|
|
107276
|
-
const
|
|
108045
|
+
const heldBy = conflict.holder ? ` (held by ${conflict.holder})` : "";
|
|
107277
108046
|
console.error(source_default.red(`
|
|
107278
|
-
Refusing to start Hindsight: port ${conflict.port} is
|
|
108047
|
+
Refusing to start Hindsight: port ${conflict.port} is occupied${heldBy}. ` + `Free it and retry \`switchroom memory setup --recreate\`.
|
|
107279
108048
|
`));
|
|
107280
108049
|
process.exit(1);
|
|
107281
108050
|
}
|
|
107282
|
-
console.log(source_default.green(` Reassigned Hindsight to port ${ports.apiPort}/${ports.uiPort}.`));
|
|
107283
108051
|
}
|
|
107284
108052
|
if (opts.provider && opts.provider !== "claude-code") {
|
|
107285
108053
|
console.log(source_default.gray(` Note: --provider=${opts.provider} ignored. switchroom-hindsight is pinned to the ` + "`claude-code` provider (subscription-honest). The flag is kept for back-compat."));
|
|
@@ -107299,23 +108067,83 @@ Cross-agent reflection plan
|
|
|
107299
108067
|
for (const drop of await diffDroppedHindsightLlmVaultKeys(hindsightConfig.hindsight?.llm, resolvedLlm)) {
|
|
107300
108068
|
console.log(source_default.yellow(` ! ${hindsightLlmDroppedKeyWarning(drop)}`));
|
|
107301
108069
|
}
|
|
107302
|
-
|
|
107303
|
-
|
|
108070
|
+
if (recallPoolCfg) {
|
|
108071
|
+
for (const conflict of splitDbPoolEnvConflicts(hindsightConfig.hindsight?.env, recallPoolCfg)) {
|
|
108072
|
+
console.log(source_default.yellow(` ! ${splitDbPoolEnvConflictWarning(conflict)}`));
|
|
108073
|
+
}
|
|
108074
|
+
}
|
|
108075
|
+
const authorityEnv = recallPoolCfg ? splitContainerPerfEnv(hindsightConfig.hindsight?.env) : hindsightConfig.hindsight?.env;
|
|
108076
|
+
startHindsight(basePorts, litellmCfg, effectiveTag, resolvedLlm, hindsightConsumerMirrorDir(hindsightConfig), gpuDecision.enabled, {
|
|
108077
|
+
env: authorityEnv,
|
|
107304
108078
|
memLimit: hindsightConfig.hindsight?.mem_limit
|
|
107305
108079
|
}, cpAccessKey);
|
|
107306
108080
|
if (litellmCfg) {
|
|
107307
108081
|
console.log(source_default.gray(" LiteLLM routing enabled for hindsight (--network host)."));
|
|
107308
108082
|
}
|
|
107309
|
-
|
|
107310
|
-
|
|
108083
|
+
if (recallPoolCfg) {
|
|
108084
|
+
const launchResult = await launchRecallPoolHealthGated({
|
|
108085
|
+
publicApiPort,
|
|
108086
|
+
authorityApiPort: basePorts.apiPort,
|
|
108087
|
+
workers: recallPoolCfg.workers,
|
|
108088
|
+
waitForHealthy: (p, role) => waitForHindsightHealthy(p, { role, workers: recallPoolCfg.workers }),
|
|
108089
|
+
stopPool: () => stopHindsightRecallPool(),
|
|
108090
|
+
startPool: () => startHindsightRecallPool({
|
|
108091
|
+
cfg: recallPoolCfg,
|
|
108092
|
+
poolPort: publicApiPort,
|
|
108093
|
+
imageTag: effectiveTag,
|
|
108094
|
+
litellm: litellmCfg,
|
|
108095
|
+
llm: resolvedLlm,
|
|
108096
|
+
gpu: gpuDecision.enabled,
|
|
108097
|
+
perf: {
|
|
108098
|
+
env: authorityEnv,
|
|
108099
|
+
memLimit: hindsightConfig.hindsight?.mem_limit
|
|
108100
|
+
},
|
|
108101
|
+
cpAccessKey,
|
|
108102
|
+
mirrorDir: hindsightConsumerMirrorDir(hindsightConfig)
|
|
108103
|
+
}),
|
|
108104
|
+
degradeToSingleContainer: () => {
|
|
108105
|
+
stopHindsightRecallPool();
|
|
108106
|
+
stopHindsight();
|
|
108107
|
+
startHindsight({ apiPort: publicApiPort, uiPort: basePorts.uiPort }, litellmCfg, effectiveTag, resolvedLlm, hindsightConsumerMirrorDir(hindsightConfig), gpuDecision.enabled, { env: hindsightConfig.hindsight?.env, memLimit: hindsightConfig.hindsight?.mem_limit }, cpAccessKey);
|
|
108108
|
+
},
|
|
108109
|
+
log: (m) => console.log(source_default.gray(m))
|
|
108110
|
+
});
|
|
108111
|
+
switch (launchResult.outcome) {
|
|
108112
|
+
case "split":
|
|
108113
|
+
console.log(source_default.green(`
|
|
108114
|
+
Hindsight recall split started: authority (pg0 + background) on ` + `${basePorts.apiPort}, ${recallPoolCfg.workers}-worker recall pool on ${publicApiPort}.
|
|
108115
|
+
`));
|
|
108116
|
+
break;
|
|
108117
|
+
case "degraded-single-container":
|
|
108118
|
+
console.log(source_default.yellow(`
|
|
108119
|
+
! Recall pool failed (${launchResult.reason}); DEGRADED to a single ` + `container on the public port ${publicApiPort}. memory.config.url IS served, ` + `but the recall split is OFF \u2014 inspect \`docker logs ` + `${HINDSIGHT_RECALL_POOL_CONTAINER}\` and re-run \`switchroom memory setup ` + `--recreate\` once fixed, or set hindsight.recall_pool.enabled: false.
|
|
108120
|
+
`));
|
|
108121
|
+
break;
|
|
108122
|
+
case "authority-unhealthy":
|
|
108123
|
+
console.error(source_default.red(`
|
|
108124
|
+
Authority container did not become healthy on ${basePorts.apiPort}. ` + `The recall pool was NOT started and no single container could share its ` + `pg0. Investigate \`docker logs switchroom-hindsight\` and re-run ` + `\`switchroom memory setup --recreate\`.
|
|
108125
|
+
`));
|
|
108126
|
+
process.exit(1);
|
|
108127
|
+
break;
|
|
108128
|
+
case "degrade-failed":
|
|
108129
|
+
console.error(source_default.red(`
|
|
108130
|
+
Recall pool did not become healthy on ${publicApiPort} AND the ` + `single-container fallback also failed to bind the public port. Inspect ` + `\`docker logs switchroom-hindsight\` and \`docker logs ` + `${HINDSIGHT_RECALL_POOL_CONTAINER}\`, then re-run \`switchroom memory setup ` + `--recreate\`.
|
|
108131
|
+
`));
|
|
108132
|
+
process.exit(1);
|
|
108133
|
+
break;
|
|
108134
|
+
}
|
|
108135
|
+
} else {
|
|
108136
|
+
console.log(source_default.green(`
|
|
108137
|
+
Hindsight container started (switchroom-hindsight) on port ${basePorts.apiPort}.
|
|
107311
108138
|
`));
|
|
108139
|
+
}
|
|
107312
108140
|
} catch (err) {
|
|
107313
108141
|
console.error(source_default.red(`
|
|
107314
108142
|
Failed to start Hindsight: ${err.message}
|
|
107315
108143
|
`));
|
|
107316
108144
|
process.exit(1);
|
|
107317
108145
|
}
|
|
107318
|
-
const url = `http://127.0.0.1:${
|
|
108146
|
+
const url = `http://127.0.0.1:${publicApiPort}/mcp/`;
|
|
107319
108147
|
const configPath = getConfigPath(program3);
|
|
107320
108148
|
try {
|
|
107321
108149
|
if (persistMemoryConfigUrl(configPath, provider, url)) {
|
|
@@ -107371,7 +108199,7 @@ Cross-agent reflection plan
|
|
|
107371
108199
|
}
|
|
107372
108200
|
console.log();
|
|
107373
108201
|
}));
|
|
107374
|
-
memory.command("repair").description("Verify and rebuild a bank's per-(bank, fact_type) vector index coverage (hindsight >= 0.8.5) \u2014 the fix for a restored/upgraded bank whose recall silently under-returns").option("--bank <id>", "Bank id to repair (mutually exclusive with --all)").option("--agent <name>", "Repair the bank belonging to this agent (resolves the bank id for you)").option("--all", "Repair every bank in the base schema and all discovered tenant schemas").option("--schema <name>", "Limit to a single schema (default: base + discovered tenant schemas)").option("--dry-run", "Report what would be repaired without creating or dropping any index").option("--container <name>", `Hindsight container name (default: ${
|
|
108202
|
+
memory.command("repair").description("Verify and rebuild a bank's per-(bank, fact_type) vector index coverage (hindsight >= 0.8.5) \u2014 the fix for a restored/upgraded bank whose recall silently under-returns").option("--bank <id>", "Bank id to repair (mutually exclusive with --all)").option("--agent <name>", "Repair the bank belonging to this agent (resolves the bank id for you)").option("--all", "Repair every bank in the base schema and all discovered tenant schemas").option("--schema <name>", "Limit to a single schema (default: base + discovered tenant schemas)").option("--dry-run", "Report what would be repaired without creating or dropping any index").option("--container <name>", `Hindsight container name (default: ${HINDSIGHT_CONTAINER_NAME2})`).action(withConfigError(async (opts) => {
|
|
107375
108203
|
const config = getConfig(program3);
|
|
107376
108204
|
if (opts.agent && opts.bank) {
|
|
107377
108205
|
console.error(source_default.red("Pass either --agent or --bank, not both."));
|
|
@@ -112013,8 +112841,8 @@ init_embedded_examples();
|
|
|
112013
112841
|
init_atomic();
|
|
112014
112842
|
init_loader();
|
|
112015
112843
|
init_scaffold();
|
|
112016
|
-
import { existsSync as
|
|
112017
|
-
import { execFileSync as
|
|
112844
|
+
import { existsSync as existsSync86, readFileSync as readFileSync79, mkdirSync as mkdirSync47, statSync as statSync47, writeFileSync as writeFileSync33 } from "node:fs";
|
|
112845
|
+
import { execFileSync as execFileSync27 } from "node:child_process";
|
|
112018
112846
|
import { resolve as resolve46, dirname as dirname36 } from "node:path";
|
|
112019
112847
|
init_state();
|
|
112020
112848
|
init_vault();
|
|
@@ -112022,7 +112850,6 @@ init_manager();
|
|
|
112022
112850
|
init_onboarding();
|
|
112023
112851
|
init_gpu_detect();
|
|
112024
112852
|
init_host_capabilities();
|
|
112025
|
-
init_hindsight();
|
|
112026
112853
|
init_posthog();
|
|
112027
112854
|
|
|
112028
112855
|
// src/cli/setup-posture-rewrite.ts
|
|
@@ -112413,6 +113240,9 @@ class SetupVerificationError extends Error {
|
|
|
112413
113240
|
init_lifecycle();
|
|
112414
113241
|
init_timezone();
|
|
112415
113242
|
init_schema();
|
|
113243
|
+
|
|
113244
|
+
// src/cli/setup-step-ui.ts
|
|
113245
|
+
init_source();
|
|
112416
113246
|
var STEP_PENDING = source_default.gray("\u25cb");
|
|
112417
113247
|
var STEP_ACTIVE = source_default.blue("->");
|
|
112418
113248
|
var STEP_DONE = source_default.green("OK");
|
|
@@ -112423,6 +113253,315 @@ function stepHeaderTo(log, num3, title, status) {
|
|
|
112423
113253
|
log(`
|
|
112424
113254
|
${status} ${source_default.bold(`Step ${num3}:`)} ${title}`);
|
|
112425
113255
|
}
|
|
113256
|
+
|
|
113257
|
+
// src/cli/setup-memory-backend.ts
|
|
113258
|
+
init_source();
|
|
113259
|
+
init_loader();
|
|
113260
|
+
init_hindsight();
|
|
113261
|
+
import { existsSync as existsSync85 } from "node:fs";
|
|
113262
|
+
var HINDSIGHT_READY_RETRIES = 5;
|
|
113263
|
+
var HINDSIGHT_READY_INTERVAL_MS = 1000;
|
|
113264
|
+
async function resolveLiteLLMForHindsight2(config, deps = {}) {
|
|
113265
|
+
return resolveHindsightLiteLlm(config, deps);
|
|
113266
|
+
}
|
|
113267
|
+
function isMemoryBackendDisabled(config) {
|
|
113268
|
+
const memoryBackend = config.memory?.backend ?? "hindsight";
|
|
113269
|
+
return process.env.SWITCHROOM_MEMORY_BACKEND === "none" || memoryBackend === "none";
|
|
113270
|
+
}
|
|
113271
|
+
async function stepMemoryBackend(config, nonInteractive, switchroomConfigPath, deps = {}) {
|
|
113272
|
+
stepHeader(6, "Memory backend", STEP_ACTIVE);
|
|
113273
|
+
const sleep4 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
113274
|
+
const pickPorts = deps.pickPorts ?? pickHindsightPorts;
|
|
113275
|
+
const preflightPorts = deps.preflightPorts ?? preflightHindsightPorts;
|
|
113276
|
+
if (isMemoryBackendDisabled(config)) {
|
|
113277
|
+
console.log(source_default.gray(" Memory backend disabled (set to 'none')."));
|
|
113278
|
+
console.log(source_default.green(` ${STEP_DONE} Skipped`));
|
|
113279
|
+
return { hindsightExpected: false, optedOut: true };
|
|
113280
|
+
}
|
|
113281
|
+
let setupHindsight = true;
|
|
113282
|
+
if (!nonInteractive) {
|
|
113283
|
+
console.log(source_default.gray(" Hindsight will use Anthropic OAuth via the auth-broker. The fleet's"));
|
|
113284
|
+
console.log(source_default.gray(" active account (auth.active) is shared \u2014 no OpenAI API key is needed."));
|
|
113285
|
+
setupHindsight = await askYesNo(" Set up Hindsight memory? (recommended)", true);
|
|
113286
|
+
}
|
|
113287
|
+
if (!setupHindsight) {
|
|
113288
|
+
console.log(source_default.gray(" Skipping Hindsight setup."));
|
|
113289
|
+
console.log(source_default.green(` ${STEP_DONE} Skipped`));
|
|
113290
|
+
return { hindsightExpected: false, optedOut: false };
|
|
113291
|
+
}
|
|
113292
|
+
if (process.env.HINDSIGHT_API_LLM_API_KEY) {
|
|
113293
|
+
console.log(source_default.gray(" Note: HINDSIGHT_API_LLM_API_KEY is set in your env but is no longer used. " + "You can remove it."));
|
|
113294
|
+
}
|
|
113295
|
+
try {
|
|
113296
|
+
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
113297
|
+
const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
113298
|
+
if (passphrase && existsSync85(vaultPath)) {
|
|
113299
|
+
const { getStringSecret: getStringSecret2 } = await Promise.resolve().then(() => (init_vault(), exports_vault));
|
|
113300
|
+
const existing = getStringSecret2(passphrase, vaultPath, "hindsight-api-key");
|
|
113301
|
+
if (existing) {
|
|
113302
|
+
console.log(source_default.gray(" Note: legacy 'hindsight-api-key' is in your vault but is no longer used. You can remove it with `switchroom vault rm hindsight-api-key`."));
|
|
113303
|
+
}
|
|
113304
|
+
}
|
|
113305
|
+
} catch {}
|
|
113306
|
+
{
|
|
113307
|
+
try {
|
|
113308
|
+
const result = await ensureHindsightConsumer(switchroomConfigPath);
|
|
113309
|
+
if (result.added) {
|
|
113310
|
+
console.log(source_default.green(` ${STEP_DONE} Registered auth.consumers[${HINDSIGHT_CONSUMER_NAME}] (follows the fleet active account)`));
|
|
113311
|
+
} else {
|
|
113312
|
+
console.log(source_default.gray(` auth.consumers[${HINDSIGHT_CONSUMER_NAME}] already present.`));
|
|
113313
|
+
}
|
|
113314
|
+
} catch (err) {
|
|
113315
|
+
console.log(source_default.yellow(` Warning: could not write auth.consumers entry: ${err.message}`));
|
|
113316
|
+
}
|
|
113317
|
+
}
|
|
113318
|
+
const dockerState = probeDockerAvailability(deps.dockerProbe);
|
|
113319
|
+
if (dockerState !== "ok") {
|
|
113320
|
+
const noCli = dockerState === "no-cli";
|
|
113321
|
+
const headline = noCli ? "Docker is not installed (no `docker` command on PATH)." : "Docker is installed but its daemon is not responding.";
|
|
113322
|
+
const remedy = noCli ? "Install Docker (https://docs.docker.com/get-docker/), then re-run `switchroom setup`" : "Start Docker (on macOS: open Docker Desktop; on Linux: `sudo systemctl start docker`), then re-run `switchroom setup`";
|
|
113323
|
+
console.log(source_default.red(` x ${headline}`));
|
|
113324
|
+
console.log(source_default.gray(" Hindsight memory runs as a Docker container (and so does every agent)."));
|
|
113325
|
+
console.log(source_default.gray(` ${remedy} \u2014 or re-run with SWITCHROOM_MEMORY_BACKEND=none to skip memory setup.`));
|
|
113326
|
+
throw new Error(`Memory backend setup failed: ${headline} ${remedy}, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.`);
|
|
113327
|
+
}
|
|
113328
|
+
let recallPoolCfg;
|
|
113329
|
+
try {
|
|
113330
|
+
recallPoolCfg = resolveRecallPoolConfig(config.hindsight?.recall_pool);
|
|
113331
|
+
} catch (err) {
|
|
113332
|
+
console.log(source_default.yellow(` ! Ignoring hindsight.recall_pool (${err.message}); starting the single-container topology.`));
|
|
113333
|
+
recallPoolCfg = null;
|
|
113334
|
+
}
|
|
113335
|
+
if (recallPoolCfg) {
|
|
113336
|
+
for (const conflict2 of splitDbPoolEnvConflicts(config.hindsight?.env, recallPoolCfg)) {
|
|
113337
|
+
console.log(source_default.yellow(` ! ${splitDbPoolEnvConflictWarning(conflict2)}`));
|
|
113338
|
+
}
|
|
113339
|
+
}
|
|
113340
|
+
const startPool = deps.startRecallPool ?? startHindsightRecallPool;
|
|
113341
|
+
const waitHealthy = deps.waitForHealthy ?? waitForHindsightHealthy;
|
|
113342
|
+
const waitHealthyFor = (port, role) => waitHealthy(port, { role, workers: recallPoolCfg?.workers });
|
|
113343
|
+
const poolIsRunning = deps.recallPoolRunningProbe ?? isHindsightRecallPoolRunning;
|
|
113344
|
+
const stopPool = deps.stopRecallPool ?? stopHindsightRecallPool;
|
|
113345
|
+
const parseUrlPort = (u) => {
|
|
113346
|
+
try {
|
|
113347
|
+
const p = Number(new URL(u ?? "").port);
|
|
113348
|
+
return Number.isInteger(p) && p > 0 ? p : undefined;
|
|
113349
|
+
} catch {
|
|
113350
|
+
return;
|
|
113351
|
+
}
|
|
113352
|
+
};
|
|
113353
|
+
const resolveLaunchSecrets = async () => {
|
|
113354
|
+
const { litellm: litellmCfg, droppedRef: litellmDroppedRef } = await resolveLiteLLMForHindsight2(config, deps.getViaBrokerStructured ? { getViaBrokerStructured: deps.getViaBrokerStructured } : {});
|
|
113355
|
+
if (litellmDroppedRef) {
|
|
113356
|
+
console.log(source_default.yellow(` ! ${hindsightLiteLlmDroppedKeyWarning(litellmDroppedRef)}`));
|
|
113357
|
+
}
|
|
113358
|
+
const cpAccessKey = await resolveHindsightCpAccessKey(config);
|
|
113359
|
+
if (!cpAccessKey) {
|
|
113360
|
+
console.log(source_default.yellow(` ! ${HINDSIGHT_CP_NO_ACCESS_KEY_WARNING}`));
|
|
113361
|
+
}
|
|
113362
|
+
const resolvedLlm = await resolveHindsightLlmSecrets(config.hindsight?.llm, deps.getViaBrokerStructured ? { getViaBrokerStructured: deps.getViaBrokerStructured } : {});
|
|
113363
|
+
for (const drop of await diffDroppedHindsightLlmVaultKeys(config.hindsight?.llm, resolvedLlm)) {
|
|
113364
|
+
console.log(source_default.yellow(` ! ${hindsightLlmDroppedKeyWarning(drop)}`));
|
|
113365
|
+
}
|
|
113366
|
+
return { litellmCfg, cpAccessKey, resolvedLlm };
|
|
113367
|
+
};
|
|
113368
|
+
const launchRecallPool = async (cfg, publicApiPort2, authorityApiPort, fallbackUiPort, secrets) => {
|
|
113369
|
+
const startContainer = deps.startContainer ?? startHindsight;
|
|
113370
|
+
const result = await launchRecallPoolHealthGated({
|
|
113371
|
+
publicApiPort: publicApiPort2,
|
|
113372
|
+
authorityApiPort,
|
|
113373
|
+
workers: cfg.workers,
|
|
113374
|
+
waitForHealthy: waitHealthyFor,
|
|
113375
|
+
stopPool,
|
|
113376
|
+
startPool: () => startPool({
|
|
113377
|
+
cfg,
|
|
113378
|
+
poolPort: publicApiPort2,
|
|
113379
|
+
litellm: secrets.litellmCfg,
|
|
113380
|
+
llm: secrets.resolvedLlm,
|
|
113381
|
+
perf: {
|
|
113382
|
+
env: splitContainerPerfEnv(config.hindsight?.env),
|
|
113383
|
+
memLimit: config.hindsight?.mem_limit
|
|
113384
|
+
},
|
|
113385
|
+
cpAccessKey: secrets.cpAccessKey,
|
|
113386
|
+
mirrorDir: hindsightConsumerMirrorDir(config)
|
|
113387
|
+
}),
|
|
113388
|
+
degradeToSingleContainer: () => {
|
|
113389
|
+
stopPool();
|
|
113390
|
+
(deps.stopContainer ?? stopHindsight)();
|
|
113391
|
+
startContainer({ apiPort: publicApiPort2, uiPort: fallbackUiPort }, secrets.litellmCfg, undefined, secrets.resolvedLlm, hindsightConsumerMirrorDir(config), undefined, { env: config.hindsight?.env, memLimit: config.hindsight?.mem_limit }, secrets.cpAccessKey);
|
|
113392
|
+
},
|
|
113393
|
+
log: (m) => console.log(source_default.gray(m))
|
|
113394
|
+
});
|
|
113395
|
+
switch (result.outcome) {
|
|
113396
|
+
case "split":
|
|
113397
|
+
return "split";
|
|
113398
|
+
case "degraded-single-container":
|
|
113399
|
+
console.log(source_default.yellow(` ! Recall pool failed (${result.reason}); DEGRADED to a single container on the public port ${publicApiPort2}. memory.config.url IS served, but the recall split is ` + `OFF \u2014 inspect \`docker logs ${HINDSIGHT_RECALL_POOL_CONTAINER}\` and re-run \`switchroom setup\` once fixed, or set hindsight.recall_pool.enabled: false.`));
|
|
113400
|
+
return "degraded";
|
|
113401
|
+
case "authority-unhealthy":
|
|
113402
|
+
throw new Error(`Memory backend setup failed: the authority container did not become healthy on ${authorityApiPort}. Check \`docker logs switchroom-hindsight --tail 50\`, then re-run \`switchroom setup\`, or set hindsight.recall_pool.enabled: false to skip the split.`);
|
|
113403
|
+
case "degrade-failed":
|
|
113404
|
+
throw new Error(`Memory backend setup failed: the recall pool did not become healthy on ${publicApiPort2} AND the single-container fallback also failed to bind the public port. Inspect \`docker logs switchroom-hindsight --tail 50\` and \`docker logs ${HINDSIGHT_RECALL_POOL_CONTAINER}\`, then re-run \`switchroom setup\`, or set hindsight.recall_pool.enabled: false to skip the split.`);
|
|
113405
|
+
}
|
|
113406
|
+
};
|
|
113407
|
+
if (isHindsightRunning(deps.dockerProbe)) {
|
|
113408
|
+
if (recallPoolCfg && !poolIsRunning(deps.dockerProbe)) {
|
|
113409
|
+
console.log(source_default.gray(" Authority container is up but the recall pool is not \u2014 restoring the pool..."));
|
|
113410
|
+
const secrets = await resolveLaunchSecrets();
|
|
113411
|
+
const { publicApiPort: publicApiPort2, basePorts: basePorts2 } = resolveHindsightLaunchPorts({
|
|
113412
|
+
recallEnabled: true,
|
|
113413
|
+
configUrlPort: parseUrlPort(config.memory?.config?.url),
|
|
113414
|
+
reuseApiPort: undefined,
|
|
113415
|
+
uiPort: 0,
|
|
113416
|
+
defaultApiPort: HINDSIGHT_DEFAULT_API_PORT
|
|
113417
|
+
});
|
|
113418
|
+
const topo = await launchRecallPool(recallPoolCfg, publicApiPort2, basePorts2.apiPort, HINDSIGHT_DEFAULT_UI_PORT, secrets);
|
|
113419
|
+
if (topo === "split") {
|
|
113420
|
+
console.log(source_default.green(` ${STEP_DONE} Recall pool restored on ${publicApiPort2} (authority on ${basePorts2.apiPort})`));
|
|
113421
|
+
}
|
|
113422
|
+
return { hindsightExpected: true, optedOut: false };
|
|
113423
|
+
}
|
|
113424
|
+
const staleRunningPorts = (deps.runningPortsProbe ?? getRunningHindsightPorts)();
|
|
113425
|
+
const stalePlan = planStaleRecallPoolConvergence({
|
|
113426
|
+
recallEnabled: recallPoolCfg !== null,
|
|
113427
|
+
poolRunning: poolIsRunning(deps.dockerProbe),
|
|
113428
|
+
authorityApiPort: staleRunningPorts?.apiPort,
|
|
113429
|
+
configUrlPort: parseUrlPort(config.memory?.config?.url)
|
|
113430
|
+
});
|
|
113431
|
+
if (stalePlan.action !== "none") {
|
|
113432
|
+
const staleSecrets = stalePlan.action === "relaunch-single-on-public" ? await resolveLaunchSecrets() : undefined;
|
|
113433
|
+
const startContainer = deps.startContainer ?? startHindsight;
|
|
113434
|
+
const staleResult = await convergeStaleRecallPool({
|
|
113435
|
+
plan: stalePlan,
|
|
113436
|
+
stopPool,
|
|
113437
|
+
relaunchSingleOnPublic: (publicApiPort2) => {
|
|
113438
|
+
stopPool();
|
|
113439
|
+
(deps.stopContainer ?? stopHindsight)();
|
|
113440
|
+
startContainer({
|
|
113441
|
+
apiPort: publicApiPort2,
|
|
113442
|
+
uiPort: staleRunningPorts?.uiPort ?? HINDSIGHT_DEFAULT_UI_PORT
|
|
113443
|
+
}, staleSecrets.litellmCfg, undefined, staleSecrets.resolvedLlm, hindsightConsumerMirrorDir(config), undefined, { env: config.hindsight?.env, memLimit: config.hindsight?.mem_limit }, staleSecrets.cpAccessKey);
|
|
113444
|
+
},
|
|
113445
|
+
waitForHealthy: waitHealthyFor,
|
|
113446
|
+
log: (m) => console.log(source_default.gray(m))
|
|
113447
|
+
});
|
|
113448
|
+
if (staleResult.outcome === "relaunch-failed") {
|
|
113449
|
+
const msg = `hindsight.recall_pool is disabled, but the single container relaunched on the public port ${staleResult.publicApiPort} did not become healthy \u2014 nothing is ` + `serving fleet memory.`;
|
|
113450
|
+
console.log(source_default.red(` x ${msg}`));
|
|
113451
|
+
throw new Error(`Memory backend setup failed: ${msg} Inspect \`docker logs switchroom-hindsight --tail 50\`, then re-run \`switchroom setup\`.`);
|
|
113452
|
+
}
|
|
113453
|
+
if (staleResult.outcome === "pool-stopped") {
|
|
113454
|
+
console.log(source_default.green(` ${STEP_DONE} Removed the orphan recall pool (hindsight.recall_pool is disabled)`));
|
|
113455
|
+
} else if (staleResult.outcome === "relaunched-single") {
|
|
113456
|
+
console.log(source_default.green(` ${STEP_DONE} Converged to the declared single-container topology on ${staleResult.publicApiPort}`));
|
|
113457
|
+
}
|
|
113458
|
+
return { hindsightExpected: true, optedOut: false };
|
|
113459
|
+
}
|
|
113460
|
+
const runningPorts = staleRunningPorts;
|
|
113461
|
+
if (publicPortIsUnserved({
|
|
113462
|
+
configUrlPort: parseUrlPort(config.memory?.config?.url),
|
|
113463
|
+
authorityApiPort: runningPorts?.apiPort,
|
|
113464
|
+
poolRunning: poolIsRunning(deps.dockerProbe)
|
|
113465
|
+
})) {
|
|
113466
|
+
const publicPort = parseUrlPort(config.memory?.config?.url);
|
|
113467
|
+
const msg = `switchroom-hindsight is running on port ${runningPorts?.apiPort}, but memory.config.url points at ${publicPort} and nothing is serving it \u2014 ` + `fleet memory is DOWN.`;
|
|
113468
|
+
console.log(source_default.red(` x ${msg}`));
|
|
113469
|
+
throw new Error(`Memory backend setup failed: ${msg} Run \`switchroom memory setup --recreate\` to rebind it to ${publicPort}, or set hindsight.recall_pool.enabled: true to restore the recall pool on the public port.`);
|
|
113470
|
+
}
|
|
113471
|
+
console.log(source_default.green(` ${STEP_DONE} Hindsight container already running (switchroom-hindsight)`));
|
|
113472
|
+
return { hindsightExpected: true, optedOut: false };
|
|
113473
|
+
}
|
|
113474
|
+
if (isHindsightContainerExists(deps.dockerProbe)) {
|
|
113475
|
+
console.log(source_default.gray(" Found stopped switchroom-hindsight container, removing..."));
|
|
113476
|
+
stopHindsight();
|
|
113477
|
+
}
|
|
113478
|
+
stopPool();
|
|
113479
|
+
let ports;
|
|
113480
|
+
try {
|
|
113481
|
+
ports = await pickPorts();
|
|
113482
|
+
} catch (err) {
|
|
113483
|
+
console.log(source_default.red(` ${err.message}`));
|
|
113484
|
+
throw new Error(`Memory backend setup failed: could not allocate Hindsight ports: ${err.message}. Free a port and re-run \`switchroom setup\`, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.`);
|
|
113485
|
+
}
|
|
113486
|
+
if (ports.apiPort !== HINDSIGHT_DEFAULT_API_PORT) {
|
|
113487
|
+
console.log(source_default.yellow(` Port ${HINDSIGHT_DEFAULT_API_PORT} is already in use; using ${ports.apiPort}/${ports.uiPort} instead.`));
|
|
113488
|
+
}
|
|
113489
|
+
let conflict = await preflightPorts(ports);
|
|
113490
|
+
if (conflict) {
|
|
113491
|
+
const heldBy = conflict.holder ? ` (held by ${conflict.holder})` : "";
|
|
113492
|
+
console.log(source_default.yellow(` Chosen Hindsight port ${conflict.port} is occupied${heldBy}; selecting a free port instead of crash-looping.`));
|
|
113493
|
+
try {
|
|
113494
|
+
ports = await pickPorts();
|
|
113495
|
+
} catch (err) {
|
|
113496
|
+
console.log(source_default.red(` ${err.message}`));
|
|
113497
|
+
throw new Error(`Memory backend setup failed: could not allocate Hindsight ports after reassignment: ${err.message}. Free a port and re-run \`switchroom setup\`, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.`);
|
|
113498
|
+
}
|
|
113499
|
+
conflict = await preflightPorts(ports);
|
|
113500
|
+
if (conflict) {
|
|
113501
|
+
const stillHeldBy = conflict.holder ? ` (held by ${conflict.holder})` : "";
|
|
113502
|
+
const msg = `Refusing to start Hindsight: port ${conflict.port} is still occupied${stillHeldBy} after reassignment.`;
|
|
113503
|
+
console.log(source_default.red(` ${msg}`));
|
|
113504
|
+
throw new Error(`Memory backend setup failed: ${msg} Free it and re-run \`switchroom setup\`, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.`);
|
|
113505
|
+
}
|
|
113506
|
+
console.log(source_default.green(` Reassigned Hindsight to port ${ports.apiPort}/${ports.uiPort}.`));
|
|
113507
|
+
}
|
|
113508
|
+
const { publicApiPort, basePorts } = resolveHindsightLaunchPorts({
|
|
113509
|
+
recallEnabled: recallPoolCfg != null,
|
|
113510
|
+
configUrlPort: parseUrlPort(config.memory?.config?.url),
|
|
113511
|
+
reuseApiPort: ports.apiPort,
|
|
113512
|
+
uiPort: ports.uiPort,
|
|
113513
|
+
defaultApiPort: HINDSIGHT_DEFAULT_API_PORT
|
|
113514
|
+
});
|
|
113515
|
+
if (recallPoolCfg) {
|
|
113516
|
+
const bgConflict = await preflightPorts({
|
|
113517
|
+
apiPort: basePorts.apiPort,
|
|
113518
|
+
uiPort: basePorts.apiPort
|
|
113519
|
+
});
|
|
113520
|
+
if (bgConflict) {
|
|
113521
|
+
const heldBy = bgConflict.holder ? ` (held by ${bgConflict.holder})` : "";
|
|
113522
|
+
const msg = `Refusing to start the hindsight recall split: authority port ${bgConflict.port} is occupied${heldBy}.`;
|
|
113523
|
+
console.log(source_default.red(` ${msg}`));
|
|
113524
|
+
throw new Error(`Memory backend setup failed: ${msg} Free it and re-run \`switchroom setup\`, or set hindsight.recall_pool.enabled: false to skip the split.`);
|
|
113525
|
+
}
|
|
113526
|
+
}
|
|
113527
|
+
const spin = spinner("Starting Hindsight Docker container...");
|
|
113528
|
+
let launchSecrets;
|
|
113529
|
+
try {
|
|
113530
|
+
launchSecrets = await resolveLaunchSecrets();
|
|
113531
|
+
const authorityEnv = recallPoolCfg ? splitContainerPerfEnv(config.hindsight?.env) : config.hindsight?.env;
|
|
113532
|
+
const startContainer = deps.startContainer ?? startHindsight;
|
|
113533
|
+
startContainer(basePorts, launchSecrets.litellmCfg, undefined, launchSecrets.resolvedLlm, hindsightConsumerMirrorDir(config), undefined, { env: authorityEnv, memLimit: config.hindsight?.mem_limit }, launchSecrets.cpAccessKey);
|
|
113534
|
+
if (launchSecrets.litellmCfg) {
|
|
113535
|
+
console.log(source_default.gray(" LiteLLM routing enabled (--network host, ANTHROPIC_BASE_URL set)."));
|
|
113536
|
+
}
|
|
113537
|
+
} catch (err) {
|
|
113538
|
+
spin.stop(source_default.red(`Failed to start Hindsight: ${err.message}`));
|
|
113539
|
+
console.log(source_default.gray(` Make sure \`switchroom apply\` has run so the auth-broker consumer socket volume (auth-broker-${HINDSIGHT_CONSUMER_NAME}-sock) exists.`));
|
|
113540
|
+
throw new Error(`Memory backend setup failed: ${err.message}. Fix the cause and re-run \`switchroom setup\`, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.`);
|
|
113541
|
+
}
|
|
113542
|
+
let running = isHindsightRunning(deps.dockerProbe);
|
|
113543
|
+
const retries = deps.readyRetries ?? HINDSIGHT_READY_RETRIES;
|
|
113544
|
+
for (let i2 = 0;!running && i2 < retries; i2++) {
|
|
113545
|
+
await sleep4(HINDSIGHT_READY_INTERVAL_MS);
|
|
113546
|
+
running = isHindsightRunning(deps.dockerProbe);
|
|
113547
|
+
}
|
|
113548
|
+
if (!running) {
|
|
113549
|
+
spin.stop(source_default.red("Hindsight container did not stay running after start"));
|
|
113550
|
+
throw new Error("Memory backend setup failed: switchroom-hindsight did not stay running after start. Check `docker logs switchroom-hindsight --tail 50`, then re-run `switchroom setup`, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.");
|
|
113551
|
+
}
|
|
113552
|
+
spin.stop(source_default.green(`${STEP_DONE} Hindsight container started (switchroom-hindsight)`));
|
|
113553
|
+
if (recallPoolCfg) {
|
|
113554
|
+
const topo = await launchRecallPool(recallPoolCfg, publicApiPort, basePorts.apiPort, basePorts.uiPort, launchSecrets);
|
|
113555
|
+
if (topo === "split") {
|
|
113556
|
+
console.log(source_default.green(` ${STEP_DONE} Hindsight recall split started: authority (pg0 + background) on ${basePorts.apiPort}, ${recallPoolCfg.workers}-worker recall pool on ${publicApiPort}`));
|
|
113557
|
+
}
|
|
113558
|
+
}
|
|
113559
|
+
console.log(source_default.gray(` API: http://localhost:${publicApiPort}/mcp`));
|
|
113560
|
+
console.log(source_default.gray(` UI: http://localhost:${basePorts.uiPort}`));
|
|
113561
|
+
return { hindsightExpected: true, optedOut: false };
|
|
113562
|
+
}
|
|
113563
|
+
|
|
113564
|
+
// src/cli/setup.ts
|
|
112426
113565
|
function writeSwitchroomYaml(configPath, text) {
|
|
112427
113566
|
let mode = 420;
|
|
112428
113567
|
try {
|
|
@@ -112525,7 +113664,7 @@ async function stepConfigFile(configPath, nonInteractive) {
|
|
|
112525
113664
|
existingConfig = null;
|
|
112526
113665
|
}
|
|
112527
113666
|
}
|
|
112528
|
-
if (existingConfig &&
|
|
113667
|
+
if (existingConfig && existsSync86(existingConfig)) {
|
|
112529
113668
|
if (!nonInteractive) {
|
|
112530
113669
|
const useExisting = await askYesNo(` Found ${source_default.cyan(existingConfig)}. Use it?`, true);
|
|
112531
113670
|
if (!useExisting) {
|
|
@@ -112671,7 +113810,7 @@ async function resolveOrPromptToken(rawToken, label, config, nonInteractive) {
|
|
|
112671
113810
|
try {
|
|
112672
113811
|
const { openVault: openVault2 } = await Promise.resolve().then(() => (init_vault(), exports_vault));
|
|
112673
113812
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
112674
|
-
if (
|
|
113813
|
+
if (existsSync86(vaultPath)) {
|
|
112675
113814
|
const secrets = openVault2(passphrase, vaultPath);
|
|
112676
113815
|
const key = rawToken.replace("vault:", "");
|
|
112677
113816
|
const entry = secrets[key];
|
|
@@ -112699,7 +113838,7 @@ async function resolveOrPromptToken(rawToken, label, config, nonInteractive) {
|
|
|
112699
113838
|
async function storeTokenInVault(config, vaultRef, token) {
|
|
112700
113839
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
112701
113840
|
const key = vaultRef.replace("vault:", "");
|
|
112702
|
-
if (!
|
|
113841
|
+
if (!existsSync86(vaultPath)) {
|
|
112703
113842
|
console.log(source_default.gray(" Creating encrypted vault..."));
|
|
112704
113843
|
let passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
112705
113844
|
if (!passphrase) {
|
|
@@ -112846,145 +113985,6 @@ async function stepCreateTopics(config, botToken, nonInteractive) {
|
|
|
112846
113985
|
}
|
|
112847
113986
|
}
|
|
112848
113987
|
}
|
|
112849
|
-
async function resolveLiteLLMForHindsight2(config, deps = {}) {
|
|
112850
|
-
return resolveHindsightLiteLlm(config, deps);
|
|
112851
|
-
}
|
|
112852
|
-
function isMemoryBackendDisabled(config) {
|
|
112853
|
-
const memoryBackend = config.memory?.backend ?? "hindsight";
|
|
112854
|
-
return process.env.SWITCHROOM_MEMORY_BACKEND === "none" || memoryBackend === "none";
|
|
112855
|
-
}
|
|
112856
|
-
async function stepMemoryBackend(config, nonInteractive, switchroomConfigPath, deps = {}) {
|
|
112857
|
-
stepHeader(6, "Memory backend", STEP_ACTIVE);
|
|
112858
|
-
const sleep4 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
112859
|
-
if (isMemoryBackendDisabled(config)) {
|
|
112860
|
-
console.log(source_default.gray(" Memory backend disabled (set to 'none')."));
|
|
112861
|
-
console.log(source_default.green(` ${STEP_DONE} Skipped`));
|
|
112862
|
-
return { hindsightExpected: false, optedOut: true };
|
|
112863
|
-
}
|
|
112864
|
-
let setupHindsight = true;
|
|
112865
|
-
if (!nonInteractive) {
|
|
112866
|
-
console.log(source_default.gray(" Hindsight will use Anthropic OAuth via the auth-broker. The fleet's"));
|
|
112867
|
-
console.log(source_default.gray(" active account (auth.active) is shared \u2014 no OpenAI API key is needed."));
|
|
112868
|
-
setupHindsight = await askYesNo(" Set up Hindsight memory? (recommended)", true);
|
|
112869
|
-
}
|
|
112870
|
-
if (!setupHindsight) {
|
|
112871
|
-
console.log(source_default.gray(" Skipping Hindsight setup."));
|
|
112872
|
-
console.log(source_default.green(` ${STEP_DONE} Skipped`));
|
|
112873
|
-
return { hindsightExpected: false, optedOut: false };
|
|
112874
|
-
}
|
|
112875
|
-
if (process.env.HINDSIGHT_API_LLM_API_KEY) {
|
|
112876
|
-
console.log(source_default.gray(" Note: HINDSIGHT_API_LLM_API_KEY is set in your env but is no longer used. You can remove it."));
|
|
112877
|
-
}
|
|
112878
|
-
try {
|
|
112879
|
-
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
112880
|
-
const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
112881
|
-
if (passphrase && existsSync85(vaultPath)) {
|
|
112882
|
-
const existing = getStringSecret(passphrase, vaultPath, "hindsight-api-key");
|
|
112883
|
-
if (existing) {
|
|
112884
|
-
console.log(source_default.gray(" Note: legacy 'hindsight-api-key' is in your vault but is no longer used. You can remove it with `switchroom vault rm hindsight-api-key`."));
|
|
112885
|
-
}
|
|
112886
|
-
}
|
|
112887
|
-
} catch {}
|
|
112888
|
-
{
|
|
112889
|
-
try {
|
|
112890
|
-
const result = await ensureHindsightConsumer(switchroomConfigPath);
|
|
112891
|
-
if (result.added) {
|
|
112892
|
-
console.log(source_default.green(` ${STEP_DONE} Registered auth.consumers[${HINDSIGHT_CONSUMER_NAME}] (follows the fleet active account)`));
|
|
112893
|
-
} else {
|
|
112894
|
-
console.log(source_default.gray(` auth.consumers[${HINDSIGHT_CONSUMER_NAME}] already present.`));
|
|
112895
|
-
}
|
|
112896
|
-
} catch (err) {
|
|
112897
|
-
console.log(source_default.yellow(` Warning: could not write auth.consumers entry: ${err.message}`));
|
|
112898
|
-
}
|
|
112899
|
-
}
|
|
112900
|
-
const dockerState = probeDockerAvailability(deps.dockerProbe);
|
|
112901
|
-
if (dockerState !== "ok") {
|
|
112902
|
-
const noCli = dockerState === "no-cli";
|
|
112903
|
-
const headline = noCli ? "Docker is not installed (no `docker` command on PATH)." : "Docker is installed but its daemon is not responding.";
|
|
112904
|
-
const remedy = noCli ? "Install Docker (https://docs.docker.com/get-docker/), then re-run `switchroom setup`" : "Start Docker (on macOS: open Docker Desktop; on Linux: `sudo systemctl start docker`), then re-run `switchroom setup`";
|
|
112905
|
-
console.log(source_default.red(` x ${headline}`));
|
|
112906
|
-
console.log(source_default.gray(" Hindsight memory runs as a Docker container (and so does every agent)."));
|
|
112907
|
-
console.log(source_default.gray(` ${remedy} \u2014 or re-run with SWITCHROOM_MEMORY_BACKEND=none to skip memory setup.`));
|
|
112908
|
-
throw new Error(`Memory backend setup failed: ${headline} ${remedy}, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.`);
|
|
112909
|
-
}
|
|
112910
|
-
if (isHindsightRunning(deps.dockerProbe)) {
|
|
112911
|
-
console.log(source_default.green(` ${STEP_DONE} Hindsight container already running (switchroom-hindsight)`));
|
|
112912
|
-
return { hindsightExpected: true, optedOut: false };
|
|
112913
|
-
}
|
|
112914
|
-
if (isHindsightContainerExists(deps.dockerProbe)) {
|
|
112915
|
-
console.log(source_default.gray(" Found stopped switchroom-hindsight container, removing..."));
|
|
112916
|
-
stopHindsight();
|
|
112917
|
-
}
|
|
112918
|
-
let ports;
|
|
112919
|
-
try {
|
|
112920
|
-
ports = await pickHindsightPorts();
|
|
112921
|
-
} catch (err) {
|
|
112922
|
-
console.log(source_default.red(` ${err.message}`));
|
|
112923
|
-
throw new Error(`Memory backend setup failed: could not allocate Hindsight ports: ${err.message}. Free a port and re-run \`switchroom setup\`, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.`);
|
|
112924
|
-
}
|
|
112925
|
-
if (ports.apiPort !== HINDSIGHT_DEFAULT_API_PORT) {
|
|
112926
|
-
console.log(source_default.yellow(` Port ${HINDSIGHT_DEFAULT_API_PORT} is already in use; using ${ports.apiPort}/${ports.uiPort} instead.`));
|
|
112927
|
-
}
|
|
112928
|
-
let conflict = await preflightHindsightPorts(ports);
|
|
112929
|
-
if (conflict) {
|
|
112930
|
-
const heldBy = conflict.holder ? ` (held by ${conflict.holder})` : "";
|
|
112931
|
-
console.log(source_default.yellow(` Chosen Hindsight port ${conflict.port} is occupied${heldBy}; selecting a free port instead of crash-looping.`));
|
|
112932
|
-
try {
|
|
112933
|
-
ports = await pickHindsightPorts();
|
|
112934
|
-
} catch (err) {
|
|
112935
|
-
console.log(source_default.red(` ${err.message}`));
|
|
112936
|
-
throw new Error(`Memory backend setup failed: could not allocate Hindsight ports after reassignment: ${err.message}. Free a port and re-run \`switchroom setup\`, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.`);
|
|
112937
|
-
}
|
|
112938
|
-
conflict = await preflightHindsightPorts(ports);
|
|
112939
|
-
if (conflict) {
|
|
112940
|
-
const stillHeldBy = conflict.holder ? ` (held by ${conflict.holder})` : "";
|
|
112941
|
-
const msg = `Refusing to start Hindsight: port ${conflict.port} is still occupied${stillHeldBy} after reassignment.`;
|
|
112942
|
-
console.log(source_default.red(` ${msg}`));
|
|
112943
|
-
throw new Error(`Memory backend setup failed: ${msg} Free it and re-run \`switchroom setup\`, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.`);
|
|
112944
|
-
}
|
|
112945
|
-
console.log(source_default.green(` Reassigned Hindsight to port ${ports.apiPort}/${ports.uiPort}.`));
|
|
112946
|
-
}
|
|
112947
|
-
const spin = spinner("Starting Hindsight Docker container...");
|
|
112948
|
-
try {
|
|
112949
|
-
const { litellm: litellmCfg, droppedRef: litellmDroppedRef } = await resolveLiteLLMForHindsight2(config, deps.getViaBrokerStructured ? { getViaBrokerStructured: deps.getViaBrokerStructured } : {});
|
|
112950
|
-
if (litellmDroppedRef) {
|
|
112951
|
-
console.log(source_default.yellow(` ! ${hindsightLiteLlmDroppedKeyWarning(litellmDroppedRef)}`));
|
|
112952
|
-
}
|
|
112953
|
-
const cpAccessKey = await resolveHindsightCpAccessKey(config);
|
|
112954
|
-
if (!cpAccessKey) {
|
|
112955
|
-
console.log(source_default.yellow(` ! ${HINDSIGHT_CP_NO_ACCESS_KEY_WARNING}`));
|
|
112956
|
-
}
|
|
112957
|
-
const resolvedLlm = await resolveHindsightLlmSecrets(config.hindsight?.llm, deps.getViaBrokerStructured ? { getViaBrokerStructured: deps.getViaBrokerStructured } : {});
|
|
112958
|
-
for (const drop of await diffDroppedHindsightLlmVaultKeys(config.hindsight?.llm, resolvedLlm)) {
|
|
112959
|
-
console.log(source_default.yellow(` ! ${hindsightLlmDroppedKeyWarning(drop)}`));
|
|
112960
|
-
}
|
|
112961
|
-
const startContainer = deps.startContainer ?? startHindsight;
|
|
112962
|
-
startContainer(ports, litellmCfg, undefined, resolvedLlm, hindsightConsumerMirrorDir(config), undefined, { env: config.hindsight?.env, memLimit: config.hindsight?.mem_limit }, cpAccessKey);
|
|
112963
|
-
if (litellmCfg) {
|
|
112964
|
-
console.log(source_default.gray(" LiteLLM routing enabled (--network host, ANTHROPIC_BASE_URL set)."));
|
|
112965
|
-
}
|
|
112966
|
-
} catch (err) {
|
|
112967
|
-
spin.stop(source_default.red(`Failed to start Hindsight: ${err.message}`));
|
|
112968
|
-
console.log(source_default.gray(` Make sure \`switchroom apply\` has run so the auth-broker consumer socket volume (auth-broker-${HINDSIGHT_CONSUMER_NAME}-sock) exists.`));
|
|
112969
|
-
throw new Error(`Memory backend setup failed: ${err.message}. Fix the cause and re-run \`switchroom setup\`, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.`);
|
|
112970
|
-
}
|
|
112971
|
-
let running = isHindsightRunning(deps.dockerProbe);
|
|
112972
|
-
const retries = deps.readyRetries ?? HINDSIGHT_READY_RETRIES;
|
|
112973
|
-
for (let i2 = 0;!running && i2 < retries; i2++) {
|
|
112974
|
-
await sleep4(HINDSIGHT_READY_INTERVAL_MS);
|
|
112975
|
-
running = isHindsightRunning(deps.dockerProbe);
|
|
112976
|
-
}
|
|
112977
|
-
if (!running) {
|
|
112978
|
-
spin.stop(source_default.red("Hindsight container did not stay running after start"));
|
|
112979
|
-
throw new Error("Memory backend setup failed: switchroom-hindsight did not stay running after start. Check `docker logs switchroom-hindsight --tail 50`, then re-run `switchroom setup`, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.");
|
|
112980
|
-
}
|
|
112981
|
-
spin.stop(source_default.green(`${STEP_DONE} Hindsight container started (switchroom-hindsight)`));
|
|
112982
|
-
console.log(source_default.gray(` API: http://localhost:${ports.apiPort}/mcp`));
|
|
112983
|
-
console.log(source_default.gray(` UI: http://localhost:${ports.uiPort}`));
|
|
112984
|
-
return { hindsightExpected: true, optedOut: false };
|
|
112985
|
-
}
|
|
112986
|
-
var HINDSIGHT_READY_RETRIES = 5;
|
|
112987
|
-
var HINDSIGHT_READY_INTERVAL_MS = 1000;
|
|
112988
113988
|
function stepVoiceEngine() {
|
|
112989
113989
|
stepHeader(7, "Voice engine", STEP_ACTIVE);
|
|
112990
113990
|
let caps;
|
|
@@ -113121,13 +114121,13 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
|
|
|
113121
114121
|
return;
|
|
113122
114122
|
}
|
|
113123
114123
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
113124
|
-
if (!
|
|
114124
|
+
if (!existsSync86(vaultPath)) {
|
|
113125
114125
|
console.log(source_default.gray(" Skipping (vault not created yet)."));
|
|
113126
114126
|
return;
|
|
113127
114127
|
}
|
|
113128
114128
|
const credPathRaw = config.vault?.broker?.autoUnlockCredentialPath ?? "~/.switchroom/vault-auto-unlock";
|
|
113129
114129
|
const credPath = resolvePath(credPathRaw);
|
|
113130
|
-
if (config.vault?.broker?.autoUnlock === true &&
|
|
114130
|
+
if (config.vault?.broker?.autoUnlock === true && existsSync86(credPath)) {
|
|
113131
114131
|
console.log(source_default.green(` ${STEP_DONE} Already configured (${credPath})`));
|
|
113132
114132
|
return;
|
|
113133
114133
|
}
|
|
@@ -113205,7 +114205,7 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
|
|
|
113205
114205
|
}
|
|
113206
114206
|
}
|
|
113207
114207
|
function persistApprovalAuthTelegramId(configPath) {
|
|
113208
|
-
if (!
|
|
114208
|
+
if (!existsSync86(configPath)) {
|
|
113209
114209
|
throw new ConfigError(`Cannot persist vault.broker.approvalAuth: config not found at ${configPath}`);
|
|
113210
114210
|
}
|
|
113211
114211
|
const content = readFileSync79(configPath, "utf-8");
|
|
@@ -113216,7 +114216,7 @@ function persistApprovalAuthTelegramId(configPath) {
|
|
|
113216
114216
|
return result.kind;
|
|
113217
114217
|
}
|
|
113218
114218
|
function persistDangerousMode(config, configPath) {
|
|
113219
|
-
if (!
|
|
114219
|
+
if (!existsSync86(configPath)) {
|
|
113220
114220
|
throw new ConfigError(`Cannot persist dangerous_mode: config not found at ${configPath}`);
|
|
113221
114221
|
}
|
|
113222
114222
|
let content = readFileSync79(configPath, "utf-8");
|
|
@@ -113327,7 +114327,7 @@ var START_OUTPUT_TAIL_LINES = 12;
|
|
|
113327
114327
|
function defaultStartAgent(name) {
|
|
113328
114328
|
let out = "";
|
|
113329
114329
|
try {
|
|
113330
|
-
out =
|
|
114330
|
+
out = execFileSync27("switchroom", ["agent", "start", name], {
|
|
113331
114331
|
stdio: ["ignore", "pipe", "pipe"],
|
|
113332
114332
|
encoding: "utf-8"
|
|
113333
114333
|
});
|
|
@@ -113355,7 +114355,7 @@ async function stepVerification(config, nonInteractive, deps = {}, expect = {})
|
|
|
113355
114355
|
const findings = [];
|
|
113356
114356
|
let applyPending = false;
|
|
113357
114357
|
if (!nonInteractive && agentNames.length > 0) {
|
|
113358
|
-
const composeExists = (deps.composeFileExists ?? (() =>
|
|
114358
|
+
const composeExists = (deps.composeFileExists ?? (() => existsSync86(composeFilePath())))();
|
|
113359
114359
|
if (!composeExists) {
|
|
113360
114360
|
applyPending = true;
|
|
113361
114361
|
log(source_default.gray(" Not started: `switchroom apply` has not generated the compose file yet."));
|
|
@@ -113452,7 +114452,7 @@ init_source();
|
|
|
113452
114452
|
init_loader();
|
|
113453
114453
|
init_lifecycle();
|
|
113454
114454
|
init_compose_env();
|
|
113455
|
-
import { existsSync as
|
|
114455
|
+
import { existsSync as existsSync89, mkdirSync as mkdirSync50, readFileSync as readFileSync82, realpathSync as realpathSync8, statSync as statSync49, chownSync as chownSync10 } from "node:fs";
|
|
113456
114456
|
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
113457
114457
|
import { join as join92, dirname as dirname39 } from "node:path";
|
|
113458
114458
|
import { homedir as homedir48 } from "node:os";
|
|
@@ -113899,7 +114899,7 @@ import {
|
|
|
113899
114899
|
closeSync as closeSync15,
|
|
113900
114900
|
copyFileSync as copyFileSync12,
|
|
113901
114901
|
createWriteStream,
|
|
113902
|
-
existsSync as
|
|
114902
|
+
existsSync as existsSync87,
|
|
113903
114903
|
lstatSync as lstatSync13,
|
|
113904
114904
|
mkdirSync as mkdirSync48,
|
|
113905
114905
|
openSync as openSync15,
|
|
@@ -113982,7 +114982,7 @@ function defaultSelfUpdateIO() {
|
|
|
113982
114982
|
rmSync15(path7, { force: true });
|
|
113983
114983
|
},
|
|
113984
114984
|
exists(path7) {
|
|
113985
|
-
return
|
|
114985
|
+
return existsSync87(path7);
|
|
113986
114986
|
},
|
|
113987
114987
|
dirname(path7) {
|
|
113988
114988
|
return dirname37(path7);
|
|
@@ -114037,7 +115037,7 @@ init_install_cron2();
|
|
|
114037
115037
|
|
|
114038
115038
|
// src/host-cli-self-heal/install-timer.ts
|
|
114039
115039
|
import { spawnSync as spawnSync19 } from "node:child_process";
|
|
114040
|
-
import { existsSync as
|
|
115040
|
+
import { existsSync as existsSync88, mkdirSync as mkdirSync49, readFileSync as readFileSync81, renameSync as renameSync22, writeFileSync as writeFileSync34 } from "node:fs";
|
|
114041
115041
|
import { dirname as dirname38 } from "node:path";
|
|
114042
115042
|
var SERVICE_PATH = "/etc/systemd/system/switchroom-self-heal.service";
|
|
114043
115043
|
var TIMER_PATH = "/etc/systemd/system/switchroom-self-heal.timer";
|
|
@@ -114084,7 +115084,7 @@ function renderTimer() {
|
|
|
114084
115084
|
`;
|
|
114085
115085
|
}
|
|
114086
115086
|
function writeUnitIdempotent(path7, content) {
|
|
114087
|
-
if (
|
|
115087
|
+
if (existsSync88(path7)) {
|
|
114088
115088
|
try {
|
|
114089
115089
|
if (readFileSync81(path7, "utf8") === content)
|
|
114090
115090
|
return "unchanged";
|
|
@@ -114115,7 +115115,7 @@ function resolveOperatorUser(env2) {
|
|
|
114115
115115
|
}
|
|
114116
115116
|
function installSelfHealTimer(deps = {}) {
|
|
114117
115117
|
const env2 = deps.env ?? process.env;
|
|
114118
|
-
const systemdBooted = deps.systemdBooted ?? (() =>
|
|
115118
|
+
const systemdBooted = deps.systemdBooted ?? (() => existsSync88(SYSTEMD_MARKER));
|
|
114119
115119
|
const geteuid = deps.geteuid ?? (() => typeof process.geteuid === "function" ? process.geteuid() : undefined);
|
|
114120
115120
|
const homeForUser = deps.homeForUser ?? homeFromPasswd;
|
|
114121
115121
|
const servicePath = deps.servicePath ?? SERVICE_PATH;
|
|
@@ -114200,7 +115200,7 @@ var PACKAGED_INSTALL_KINDS = new Set([
|
|
|
114200
115200
|
function runningFromSwitchroomCheckout(scriptPath) {
|
|
114201
115201
|
let dir = dirname39(scriptPath);
|
|
114202
115202
|
for (let i2 = 0;i2 < 12; i2++) {
|
|
114203
|
-
if (
|
|
115203
|
+
if (existsSync89(join92(dir, ".git"))) {
|
|
114204
115204
|
try {
|
|
114205
115205
|
const pkg = JSON.parse(readFileSync82(join92(dir, "package.json"), "utf-8"));
|
|
114206
115206
|
if (pkg.name === "switchroom")
|
|
@@ -114269,7 +115269,7 @@ function planUpdate(opts) {
|
|
|
114269
115269
|
bundleDir: import.meta.dirname,
|
|
114270
115270
|
execPath: process.execPath,
|
|
114271
115271
|
scriptPath,
|
|
114272
|
-
inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" ||
|
|
115272
|
+
inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" || existsSync89("/.dockerenv")
|
|
114273
115273
|
});
|
|
114274
115274
|
if (detection.kind !== "static-binary") {
|
|
114275
115275
|
say(source_default.gray(` host CLI not self-updated: ${detection.reason}
|
|
@@ -114352,7 +115352,7 @@ function planUpdate(opts) {
|
|
|
114352
115352
|
steps.push({
|
|
114353
115353
|
name: "pull-images",
|
|
114354
115354
|
description: "Pull broker / kernel / agent images from GHCR",
|
|
114355
|
-
skipReason: opts.skipImages ? "--skip-images flag set" : !
|
|
115355
|
+
skipReason: opts.skipImages ? "--skip-images flag set" : !existsSync89(composePath) ? `compose file not found at ${composePath} (run \`switchroom apply --compose-only\` first)` : undefined,
|
|
114356
115356
|
run: () => {
|
|
114357
115357
|
const r = runner("docker", [
|
|
114358
115358
|
"compose",
|
|
@@ -114587,7 +115587,7 @@ function planUpdate(opts) {
|
|
|
114587
115587
|
bundleDir: import.meta.dirname,
|
|
114588
115588
|
execPath: process.execPath,
|
|
114589
115589
|
scriptPath,
|
|
114590
|
-
inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" ||
|
|
115590
|
+
inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" || existsSync89("/.dockerenv")
|
|
114591
115591
|
});
|
|
114592
115592
|
const detail = `no shipped skills/ payload found for this install ` + `(${detection.kind}; ${describeShippedAssetSearch(resolution)})`;
|
|
114593
115593
|
if (PACKAGED_INSTALL_KINDS.has(detection.kind)) {
|
|
@@ -114627,10 +115627,10 @@ function planUpdate(opts) {
|
|
|
114627
115627
|
if (opts.syncBundledSkillsFn)
|
|
114628
115628
|
return;
|
|
114629
115629
|
const dest = join92(homedir48(), ".switchroom", "skills", "_bundled");
|
|
114630
|
-
if (!
|
|
115630
|
+
if (!existsSync89(dest)) {
|
|
114631
115631
|
return;
|
|
114632
115632
|
}
|
|
114633
|
-
const missing = getBuiltinDefaultSkillEntries().map((e) => e.key).filter((key) => !
|
|
115633
|
+
const missing = getBuiltinDefaultSkillEntries().map((e) => e.key).filter((key) => !existsSync89(join92(dest, key)));
|
|
114634
115634
|
if (missing.length > 0) {
|
|
114635
115635
|
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.`);
|
|
114636
115636
|
}
|
|
@@ -114745,7 +115745,7 @@ function defaultStatusProbe(composePath) {
|
|
|
114745
115745
|
let dir = dirname39(scriptPath);
|
|
114746
115746
|
for (let i2 = 0;i2 < 8; i2++) {
|
|
114747
115747
|
const pkgPath = join92(dir, "package.json");
|
|
114748
|
-
if (
|
|
115748
|
+
if (existsSync89(pkgPath)) {
|
|
114749
115749
|
try {
|
|
114750
115750
|
const pkg = JSON.parse(readFileSync82(pkgPath, "utf-8"));
|
|
114751
115751
|
if (typeof pkg.version === "string")
|
|
@@ -114768,7 +115768,7 @@ function defaultStatusProbe(composePath) {
|
|
|
114768
115768
|
warnings.push("could not resolve CLI version (no package.json found above the resolved script path)");
|
|
114769
115769
|
}
|
|
114770
115770
|
const services = [];
|
|
114771
|
-
if (!
|
|
115771
|
+
if (!existsSync89(composePath)) {
|
|
114772
115772
|
warnings.push(`compose file not found at ${composePath}; service status unknown`);
|
|
114773
115773
|
return { cliVersion, cliBuiltAt, services, warnings };
|
|
114774
115774
|
}
|
|
@@ -115036,7 +116036,7 @@ import { homedir as homedir50 } from "node:os";
|
|
|
115036
116036
|
|
|
115037
116037
|
// src/cli/rollout-pin-journal.ts
|
|
115038
116038
|
import {
|
|
115039
|
-
existsSync as
|
|
116039
|
+
existsSync as existsSync90,
|
|
115040
116040
|
readFileSync as readFileSync83,
|
|
115041
116041
|
writeFileSync as writeFileSync35,
|
|
115042
116042
|
renameSync as renameSync23,
|
|
@@ -115100,7 +116100,7 @@ function readPinJournal(configPath, warn = (m) => process.stderr.write(m)) {
|
|
|
115100
116100
|
try {
|
|
115101
116101
|
raw = readFileSync83(p, "utf8");
|
|
115102
116102
|
} catch (e) {
|
|
115103
|
-
if (
|
|
116103
|
+
if (existsSync90(p)) {
|
|
115104
116104
|
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.
|
|
115105
116105
|
`);
|
|
115106
116106
|
}
|
|
@@ -115172,7 +116172,7 @@ function commitPinPersist(configPath) {
|
|
|
115172
116172
|
unlinkSync17(p);
|
|
115173
116173
|
return null;
|
|
115174
116174
|
} catch (e) {
|
|
115175
|
-
if (!
|
|
116175
|
+
if (!existsSync90(p))
|
|
115176
116176
|
return null;
|
|
115177
116177
|
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\`.`;
|
|
115178
116178
|
}
|
|
@@ -116100,7 +117100,7 @@ init_helpers();
|
|
|
116100
117100
|
init_lifecycle();
|
|
116101
117101
|
init_resolve_version();
|
|
116102
117102
|
import { execSync as execSync3 } from "node:child_process";
|
|
116103
|
-
import { existsSync as
|
|
117103
|
+
import { existsSync as existsSync91, readFileSync as readFileSync85 } from "node:fs";
|
|
116104
117104
|
import { dirname as dirname41, join as join94 } from "node:path";
|
|
116105
117105
|
function getClaudeCodeVersion() {
|
|
116106
117106
|
try {
|
|
@@ -116152,10 +117152,10 @@ function locateSwitchroomInstallDir() {
|
|
|
116152
117152
|
let dir = import.meta.dirname;
|
|
116153
117153
|
for (let i2 = 0;i2 < 10 && dir && dir !== "/"; i2++) {
|
|
116154
117154
|
const pkgPath = join94(dir, "package.json");
|
|
116155
|
-
if (
|
|
117155
|
+
if (existsSync91(pkgPath)) {
|
|
116156
117156
|
try {
|
|
116157
117157
|
const pkg = JSON.parse(readFileSync85(pkgPath, "utf-8"));
|
|
116158
|
-
if (pkg.name === "switchroom" &&
|
|
117158
|
+
if (pkg.name === "switchroom" && existsSync91(join94(dir, ".git"))) {
|
|
116159
117159
|
return dir;
|
|
116160
117160
|
}
|
|
116161
117161
|
} catch {}
|
|
@@ -116336,7 +117336,7 @@ init_merge();
|
|
|
116336
117336
|
|
|
116337
117337
|
// src/agents/session-retention.ts
|
|
116338
117338
|
import {
|
|
116339
|
-
existsSync as
|
|
117339
|
+
existsSync as existsSync92,
|
|
116340
117340
|
readdirSync as readdirSync31,
|
|
116341
117341
|
statSync as statSync52,
|
|
116342
117342
|
unlinkSync as unlinkSync18
|
|
@@ -116347,7 +117347,7 @@ var DEFAULT_SESSION_RETENTION_MAX_AGE_DAYS = 30;
|
|
|
116347
117347
|
var MIN_KEEP = 2;
|
|
116348
117348
|
function collectSessionJsonl(claudeConfigDir) {
|
|
116349
117349
|
const projects = join95(claudeConfigDir, "projects");
|
|
116350
|
-
if (!
|
|
117350
|
+
if (!existsSync92(projects))
|
|
116351
117351
|
return [];
|
|
116352
117352
|
const found = [];
|
|
116353
117353
|
const walk2 = (dir) => {
|
|
@@ -116479,7 +117479,7 @@ function registerHandoffCommand(program3) {
|
|
|
116479
117479
|
// src/issues/store.ts
|
|
116480
117480
|
import {
|
|
116481
117481
|
closeSync as closeSync16,
|
|
116482
|
-
existsSync as
|
|
117482
|
+
existsSync as existsSync93,
|
|
116483
117483
|
mkdirSync as mkdirSync52,
|
|
116484
117484
|
openSync as openSync16,
|
|
116485
117485
|
readdirSync as readdirSync32,
|
|
@@ -116518,7 +117518,7 @@ var ISSUES_FILE = "issues.jsonl";
|
|
|
116518
117518
|
var ISSUES_LOCK = "issues.lock";
|
|
116519
117519
|
function readAll(stateDir) {
|
|
116520
117520
|
const path7 = join96(stateDir, ISSUES_FILE);
|
|
116521
|
-
if (!
|
|
117521
|
+
if (!existsSync93(path7))
|
|
116522
117522
|
return [];
|
|
116523
117523
|
let raw;
|
|
116524
117524
|
try {
|
|
@@ -116595,7 +117595,7 @@ function record(stateDir, input, nowFn = Date.now) {
|
|
|
116595
117595
|
});
|
|
116596
117596
|
}
|
|
116597
117597
|
function resolve51(stateDir, fingerprint, nowFn = Date.now) {
|
|
116598
|
-
if (!
|
|
117598
|
+
if (!existsSync93(join96(stateDir, ISSUES_FILE)))
|
|
116599
117599
|
return 0;
|
|
116600
117600
|
return withLock(stateDir, () => {
|
|
116601
117601
|
const all = readAll(stateDir);
|
|
@@ -116613,7 +117613,7 @@ function resolve51(stateDir, fingerprint, nowFn = Date.now) {
|
|
|
116613
117613
|
});
|
|
116614
117614
|
}
|
|
116615
117615
|
function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
116616
|
-
if (!
|
|
117616
|
+
if (!existsSync93(join96(stateDir, ISSUES_FILE)))
|
|
116617
117617
|
return 0;
|
|
116618
117618
|
return withLock(stateDir, () => {
|
|
116619
117619
|
const all = readAll(stateDir);
|
|
@@ -116631,7 +117631,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
|
116631
117631
|
});
|
|
116632
117632
|
}
|
|
116633
117633
|
function prune(stateDir, opts = {}) {
|
|
116634
|
-
if (!
|
|
117634
|
+
if (!existsSync93(join96(stateDir, ISSUES_FILE)))
|
|
116635
117635
|
return 0;
|
|
116636
117636
|
return withLock(stateDir, () => {
|
|
116637
117637
|
const all = readAll(stateDir);
|
|
@@ -116981,14 +117981,14 @@ function relTime(deltaMs) {
|
|
|
116981
117981
|
|
|
116982
117982
|
// src/cli/deps.ts
|
|
116983
117983
|
init_source();
|
|
116984
|
-
import { existsSync as
|
|
117984
|
+
import { existsSync as existsSync96 } from "node:fs";
|
|
116985
117985
|
import { homedir as homedir53 } from "node:os";
|
|
116986
117986
|
import { join as join99, resolve as resolve52 } from "node:path";
|
|
116987
117987
|
|
|
116988
117988
|
// src/deps/python.ts
|
|
116989
117989
|
import { createHash as createHash18 } from "node:crypto";
|
|
116990
117990
|
import {
|
|
116991
|
-
existsSync as
|
|
117991
|
+
existsSync as existsSync94,
|
|
116992
117992
|
mkdirSync as mkdirSync53,
|
|
116993
117993
|
readFileSync as readFileSync87,
|
|
116994
117994
|
rmSync as rmSync16,
|
|
@@ -116996,7 +117996,7 @@ import {
|
|
|
116996
117996
|
} from "node:fs";
|
|
116997
117997
|
import { dirname as dirname42, join as join97 } from "node:path";
|
|
116998
117998
|
import { homedir as homedir51 } from "node:os";
|
|
116999
|
-
import { execFileSync as
|
|
117999
|
+
import { execFileSync as execFileSync28 } from "node:child_process";
|
|
117000
118000
|
|
|
117001
118001
|
class PythonEnvError extends Error {
|
|
117002
118002
|
stderr;
|
|
@@ -117016,7 +118016,7 @@ function ensurePythonEnv(opts) {
|
|
|
117016
118016
|
const { skillName, requirementsPath, force = false } = opts;
|
|
117017
118017
|
const cacheRoot = opts.cacheRoot ?? defaultPythonCacheRoot();
|
|
117018
118018
|
const hostPython = opts.pythonBin ?? "python3";
|
|
117019
|
-
if (!
|
|
118019
|
+
if (!existsSync94(requirementsPath)) {
|
|
117020
118020
|
throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
|
|
117021
118021
|
}
|
|
117022
118022
|
const venvDir = join97(cacheRoot, skillName);
|
|
@@ -117025,7 +118025,7 @@ function ensurePythonEnv(opts) {
|
|
|
117025
118025
|
const pythonBin = join97(binDir, "python");
|
|
117026
118026
|
const pipBin = join97(binDir, "pip");
|
|
117027
118027
|
const targetHash = hashFile(requirementsPath);
|
|
117028
|
-
if (!force &&
|
|
118028
|
+
if (!force && existsSync94(stampPath) && existsSync94(pythonBin)) {
|
|
117029
118029
|
const existingHash = readFileSync87(stampPath, "utf8").trim();
|
|
117030
118030
|
if (existingHash === targetHash) {
|
|
117031
118031
|
return {
|
|
@@ -117038,12 +118038,12 @@ function ensurePythonEnv(opts) {
|
|
|
117038
118038
|
};
|
|
117039
118039
|
}
|
|
117040
118040
|
}
|
|
117041
|
-
if (
|
|
118041
|
+
if (existsSync94(venvDir)) {
|
|
117042
118042
|
rmSync16(venvDir, { recursive: true, force: true });
|
|
117043
118043
|
}
|
|
117044
118044
|
mkdirSync53(dirname42(venvDir), { recursive: true });
|
|
117045
118045
|
try {
|
|
117046
|
-
|
|
118046
|
+
execFileSync28(hostPython, ["-m", "venv", venvDir], { stdio: "pipe" });
|
|
117047
118047
|
} catch (err) {
|
|
117048
118048
|
const e = err;
|
|
117049
118049
|
throw new PythonEnvError(`Failed to create venv for skill "${skillName}" with ${hostPython}: ${e.message}`, e.stderr?.toString());
|
|
@@ -117055,7 +118055,7 @@ function ensurePythonEnv(opts) {
|
|
|
117055
118055
|
delete childEnv.PIP_TARGET;
|
|
117056
118056
|
delete childEnv.PIP_PREFIX;
|
|
117057
118057
|
delete childEnv.PYTHONUSERBASE;
|
|
117058
|
-
|
|
118058
|
+
execFileSync28(pipBin, ["install", "--disable-pip-version-check", "-r", requirementsPath], { stdio: "pipe", env: childEnv });
|
|
117059
118059
|
} catch (err) {
|
|
117060
118060
|
const e = err;
|
|
117061
118061
|
throw new PythonEnvError(`Failed to install requirements for skill "${skillName}": ${e.message}`, e.stderr?.toString());
|
|
@@ -117076,7 +118076,7 @@ function ensurePythonEnv(opts) {
|
|
|
117076
118076
|
import { createHash as createHash19 } from "node:crypto";
|
|
117077
118077
|
import {
|
|
117078
118078
|
copyFileSync as copyFileSync13,
|
|
117079
|
-
existsSync as
|
|
118079
|
+
existsSync as existsSync95,
|
|
117080
118080
|
mkdirSync as mkdirSync54,
|
|
117081
118081
|
readFileSync as readFileSync88,
|
|
117082
118082
|
rmSync as rmSync17,
|
|
@@ -117084,7 +118084,7 @@ import {
|
|
|
117084
118084
|
} from "node:fs";
|
|
117085
118085
|
import { dirname as dirname43, join as join98 } from "node:path";
|
|
117086
118086
|
import { homedir as homedir52 } from "node:os";
|
|
117087
|
-
import { execFileSync as
|
|
118087
|
+
import { execFileSync as execFileSync29 } from "node:child_process";
|
|
117088
118088
|
|
|
117089
118089
|
class NodeEnvError extends Error {
|
|
117090
118090
|
stderr;
|
|
@@ -117116,7 +118116,7 @@ function hashDepInputs(packageJsonPath) {
|
|
|
117116
118116
|
hasher.update(readFileSync88(packageJsonPath));
|
|
117117
118117
|
for (const lockName of ALL_LOCKFILES) {
|
|
117118
118118
|
const lockPath = join98(sourceDir, lockName);
|
|
117119
|
-
if (
|
|
118119
|
+
if (existsSync95(lockPath)) {
|
|
117120
118120
|
hasher.update(`
|
|
117121
118121
|
`);
|
|
117122
118122
|
hasher.update(lockName);
|
|
@@ -117131,7 +118131,7 @@ function ensureNodeEnv(opts) {
|
|
|
117131
118131
|
const { skillName, packageJsonPath, force = false } = opts;
|
|
117132
118132
|
const cacheRoot = opts.cacheRoot ?? defaultNodeCacheRoot();
|
|
117133
118133
|
const installer = opts.installer ?? "bun";
|
|
117134
|
-
if (!
|
|
118134
|
+
if (!existsSync95(packageJsonPath)) {
|
|
117135
118135
|
throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
|
|
117136
118136
|
}
|
|
117137
118137
|
const sourceDir = dirname43(packageJsonPath);
|
|
@@ -117140,7 +118140,7 @@ function ensureNodeEnv(opts) {
|
|
|
117140
118140
|
const nodeModulesDir = join98(envDir, "node_modules");
|
|
117141
118141
|
const binDir = join98(nodeModulesDir, ".bin");
|
|
117142
118142
|
const targetHash = hashDepInputs(packageJsonPath);
|
|
117143
|
-
if (!force &&
|
|
118143
|
+
if (!force && existsSync95(stampPath) && existsSync95(nodeModulesDir)) {
|
|
117144
118144
|
const existingHash = readFileSync88(stampPath, "utf8").trim();
|
|
117145
118145
|
if (existingHash === targetHash) {
|
|
117146
118146
|
return {
|
|
@@ -117152,7 +118152,7 @@ function ensureNodeEnv(opts) {
|
|
|
117152
118152
|
};
|
|
117153
118153
|
}
|
|
117154
118154
|
}
|
|
117155
|
-
if (
|
|
118155
|
+
if (existsSync95(envDir)) {
|
|
117156
118156
|
rmSync17(envDir, { recursive: true, force: true });
|
|
117157
118157
|
}
|
|
117158
118158
|
mkdirSync54(envDir, { recursive: true });
|
|
@@ -117160,7 +118160,7 @@ function ensureNodeEnv(opts) {
|
|
|
117160
118160
|
let copiedLockfile = false;
|
|
117161
118161
|
for (const lockName of LOCKFILES_FOR[installer]) {
|
|
117162
118162
|
const lockPath = join98(sourceDir, lockName);
|
|
117163
|
-
if (
|
|
118163
|
+
if (existsSync95(lockPath)) {
|
|
117164
118164
|
copyFileSync13(lockPath, join98(envDir, lockName));
|
|
117165
118165
|
copiedLockfile = true;
|
|
117166
118166
|
}
|
|
@@ -117168,10 +118168,10 @@ function ensureNodeEnv(opts) {
|
|
|
117168
118168
|
try {
|
|
117169
118169
|
if (installer === "bun") {
|
|
117170
118170
|
const args = copiedLockfile ? ["install", "--frozen-lockfile"] : ["install"];
|
|
117171
|
-
|
|
118171
|
+
execFileSync29("bun", args, { cwd: envDir, stdio: "pipe" });
|
|
117172
118172
|
} else {
|
|
117173
118173
|
const args = copiedLockfile ? ["ci"] : ["install"];
|
|
117174
|
-
|
|
118174
|
+
execFileSync29("npm", args, { cwd: envDir, stdio: "pipe" });
|
|
117175
118175
|
}
|
|
117176
118176
|
} catch (err) {
|
|
117177
118177
|
const e = err;
|
|
@@ -117196,22 +118196,22 @@ function registerDepsCommand(program3) {
|
|
|
117196
118196
|
const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
|
|
117197
118197
|
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) => {
|
|
117198
118198
|
const skillsRoot = builtinSkillsRoot();
|
|
117199
|
-
if (!
|
|
118199
|
+
if (!existsSync96(skillsRoot)) {
|
|
117200
118200
|
console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
|
|
117201
118201
|
process.exit(1);
|
|
117202
118202
|
}
|
|
117203
118203
|
const skillDir = join99(skillsRoot, skill);
|
|
117204
|
-
if (!
|
|
118204
|
+
if (!existsSync96(skillDir)) {
|
|
117205
118205
|
console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
|
|
117206
118206
|
process.exit(1);
|
|
117207
118207
|
}
|
|
117208
118208
|
const requirementsPath = join99(skillDir, "requirements.txt");
|
|
117209
118209
|
const packageJsonPath = join99(skillDir, "package.json");
|
|
117210
|
-
const wantPython = opts.python ?? (!opts.python && !opts.node &&
|
|
117211
|
-
const wantNode = opts.node ?? (!opts.python && !opts.node &&
|
|
118210
|
+
const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync96(requirementsPath));
|
|
118211
|
+
const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync96(packageJsonPath));
|
|
117212
118212
|
let did = 0;
|
|
117213
118213
|
if (wantPython) {
|
|
117214
|
-
if (!
|
|
118214
|
+
if (!existsSync96(requirementsPath)) {
|
|
117215
118215
|
console.error(source_default.red(`Skill "${skill}" has no requirements.txt at ${requirementsPath}`));
|
|
117216
118216
|
process.exit(1);
|
|
117217
118217
|
}
|
|
@@ -117235,7 +118235,7 @@ function registerDepsCommand(program3) {
|
|
|
117235
118235
|
}
|
|
117236
118236
|
}
|
|
117237
118237
|
if (wantNode) {
|
|
117238
|
-
if (!
|
|
118238
|
+
if (!existsSync96(packageJsonPath)) {
|
|
117239
118239
|
console.error(source_default.red(`Skill "${skill}" has no package.json at ${packageJsonPath}`));
|
|
117240
118240
|
process.exit(1);
|
|
117241
118241
|
}
|
|
@@ -117268,7 +118268,7 @@ function registerDepsCommand(program3) {
|
|
|
117268
118268
|
// src/cli/workspace.ts
|
|
117269
118269
|
init_helpers();
|
|
117270
118270
|
init_loader();
|
|
117271
|
-
import { existsSync as
|
|
118271
|
+
import { existsSync as existsSync97 } from "node:fs";
|
|
117272
118272
|
import { resolve as resolve53, sep as sep5 } from "node:path";
|
|
117273
118273
|
import { spawnSync as spawnSync23 } from "node:child_process";
|
|
117274
118274
|
|
|
@@ -118045,7 +119045,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
118045
119045
|
if (!dir)
|
|
118046
119046
|
return;
|
|
118047
119047
|
const gitDir = resolve53(dir, ".git");
|
|
118048
|
-
if (!
|
|
119048
|
+
if (!existsSync97(gitDir)) {
|
|
118049
119049
|
process.stdout.write(`Workspace is not a git repository. Re-run \`switchroom agent create ${agentName}\` ` + `or manually \`git init\` in ${dir} to enable versioning.
|
|
118050
119050
|
`);
|
|
118051
119051
|
return;
|
|
@@ -118099,7 +119099,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
118099
119099
|
if (!dir)
|
|
118100
119100
|
return;
|
|
118101
119101
|
const gitDir = resolve53(dir, ".git");
|
|
118102
|
-
if (!
|
|
119102
|
+
if (!existsSync97(gitDir)) {
|
|
118103
119103
|
process.stdout.write(`Workspace is not a git repository.
|
|
118104
119104
|
`);
|
|
118105
119105
|
return;
|
|
@@ -118124,7 +119124,7 @@ function resolveAgentWorkspaceDirOrExit(program3, agentName) {
|
|
|
118124
119124
|
const agentsDir = resolveAgentsDir(config);
|
|
118125
119125
|
const agentDir = resolve53(agentsDir, agentName);
|
|
118126
119126
|
const dir = resolveAgentWorkspaceDir(agentDir);
|
|
118127
|
-
if (!
|
|
119127
|
+
if (!existsSync97(dir)) {
|
|
118128
119128
|
process.stderr.write(`workspace: ${dir} does not exist yet. Run \`switchroom setup\` or \`switchroom agent scaffold ${agentName}\` to seed it.
|
|
118129
119129
|
`);
|
|
118130
119130
|
return;
|
|
@@ -118160,7 +119160,7 @@ function safeParseInt(value, fallback) {
|
|
|
118160
119160
|
init_helpers();
|
|
118161
119161
|
init_loader();
|
|
118162
119162
|
init_merge();
|
|
118163
|
-
import { copyFileSync as copyFileSync14, existsSync as
|
|
119163
|
+
import { copyFileSync as copyFileSync14, existsSync as existsSync98, readFileSync as readFileSync89, writeFileSync as writeFileSync39 } from "node:fs";
|
|
118164
119164
|
import { join as join100, resolve as resolve54 } from "node:path";
|
|
118165
119165
|
init_scaffold();
|
|
118166
119166
|
init_profiles();
|
|
@@ -118178,7 +119178,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
118178
119178
|
const agentsDir = resolveAgentsDir(config);
|
|
118179
119179
|
const agentDir = resolve54(agentsDir, agentName);
|
|
118180
119180
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
118181
|
-
if (!
|
|
119181
|
+
if (!existsSync98(workspaceDir)) {
|
|
118182
119182
|
console.error(`soul: ${workspaceDir} does not exist yet. Run \`switchroom setup\` ` + `or \`switchroom agent scaffold ${agentName}\` to seed it.`);
|
|
118183
119183
|
process.exit(1);
|
|
118184
119184
|
}
|
|
@@ -118204,7 +119204,7 @@ function registerSoulCommand(program3) {
|
|
|
118204
119204
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
118205
119205
|
if (!t)
|
|
118206
119206
|
return;
|
|
118207
|
-
if (!
|
|
119207
|
+
if (!existsSync98(t.soulPath)) {
|
|
118208
119208
|
console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
|
|
118209
119209
|
process.exit(1);
|
|
118210
119210
|
}
|
|
@@ -118219,7 +119219,7 @@ function registerSoulCommand(program3) {
|
|
|
118219
119219
|
console.error(`soul: profile "${t.profileName}" ships no SOUL.md.hbs \u2014 ` + `nothing to re-seed from.`);
|
|
118220
119220
|
process.exit(1);
|
|
118221
119221
|
}
|
|
118222
|
-
const exists =
|
|
119222
|
+
const exists = existsSync98(t.soulPath);
|
|
118223
119223
|
if (exists && !opts.yes) {
|
|
118224
119224
|
if (!isInteractive()) {
|
|
118225
119225
|
console.error(`soul: ${t.soulPath} already exists. Re-run with --yes to ` + `replace it (the current file is backed up to SOUL.md.bak).`);
|
|
@@ -118234,7 +119234,7 @@ function registerSoulCommand(program3) {
|
|
|
118234
119234
|
let backupPath;
|
|
118235
119235
|
if (exists) {
|
|
118236
119236
|
backupPath = `${t.soulPath}.bak`;
|
|
118237
|
-
if (
|
|
119237
|
+
if (existsSync98(backupPath)) {
|
|
118238
119238
|
backupPath = `${t.soulPath}.bak.${Date.now()}`;
|
|
118239
119239
|
}
|
|
118240
119240
|
copyFileSync14(t.soulPath, backupPath);
|
|
@@ -118253,7 +119253,7 @@ function registerSoulCommand(program3) {
|
|
|
118253
119253
|
// src/cli/debug.ts
|
|
118254
119254
|
init_helpers();
|
|
118255
119255
|
init_loader();
|
|
118256
|
-
import { existsSync as
|
|
119256
|
+
import { existsSync as existsSync99, readFileSync as readFileSync90, readdirSync as readdirSync33, statSync as statSync54 } from "node:fs";
|
|
118257
119257
|
import { resolve as resolve55, join as join101 } from "node:path";
|
|
118258
119258
|
import { createHash as createHash20 } from "node:crypto";
|
|
118259
119259
|
init_merge();
|
|
@@ -118266,7 +119266,7 @@ function estimateTokens(bytes) {
|
|
|
118266
119266
|
}
|
|
118267
119267
|
function readMcpServerNames(agentDir) {
|
|
118268
119268
|
const mcpPath = join101(agentDir, ".mcp.json");
|
|
118269
|
-
if (!
|
|
119269
|
+
if (!existsSync99(mcpPath))
|
|
118270
119270
|
return [];
|
|
118271
119271
|
try {
|
|
118272
119272
|
const parsed = JSON.parse(readFileSync90(mcpPath, "utf-8"));
|
|
@@ -118280,7 +119280,7 @@ function sha2562(content) {
|
|
|
118280
119280
|
}
|
|
118281
119281
|
function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
118282
119282
|
const projectsDir = join101(claudeConfigDir, "projects");
|
|
118283
|
-
if (!
|
|
119283
|
+
if (!existsSync99(projectsDir))
|
|
118284
119284
|
return;
|
|
118285
119285
|
try {
|
|
118286
119286
|
const entries = readdirSync33(projectsDir, { withFileTypes: true });
|
|
@@ -118290,7 +119290,7 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
118290
119290
|
continue;
|
|
118291
119291
|
const projectPath = join101(projectsDir, entry.name);
|
|
118292
119292
|
const transcriptPath = join101(projectPath, "transcript.jsonl");
|
|
118293
|
-
if (!
|
|
119293
|
+
if (!existsSync99(transcriptPath))
|
|
118294
119294
|
continue;
|
|
118295
119295
|
const stat3 = statSync54(transcriptPath);
|
|
118296
119296
|
if (!latest || stat3.mtimeMs > latest.mtime) {
|
|
@@ -118353,7 +119353,7 @@ function registerDebugCommand(program3) {
|
|
|
118353
119353
|
}
|
|
118354
119354
|
const agentsDir = resolveAgentsDir(config);
|
|
118355
119355
|
const agentDir = resolve55(agentsDir, agentName);
|
|
118356
|
-
if (!
|
|
119356
|
+
if (!existsSync99(agentDir)) {
|
|
118357
119357
|
console.error(`Agent directory not found: ${agentDir}`);
|
|
118358
119358
|
process.exit(1);
|
|
118359
119359
|
}
|
|
@@ -118408,7 +119408,7 @@ function registerDebugCommand(program3) {
|
|
|
118408
119408
|
}
|
|
118409
119409
|
console.log(`=== Append System Prompt (per-session) ===
|
|
118410
119410
|
`);
|
|
118411
|
-
const handoffContent =
|
|
119411
|
+
const handoffContent = existsSync99(handoffPath) ? readFileSync90(handoffPath, "utf-8") : "";
|
|
118412
119412
|
if (handoffContent.trim().length > 0) {
|
|
118413
119413
|
console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
|
|
118414
119414
|
console.log(handoffContent);
|
|
@@ -118419,7 +119419,7 @@ function registerDebugCommand(program3) {
|
|
|
118419
119419
|
}
|
|
118420
119420
|
console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
|
|
118421
119421
|
`);
|
|
118422
|
-
const claudeMdContent =
|
|
119422
|
+
const claudeMdContent = existsSync99(claudeMdPath) ? readFileSync90(claudeMdPath, "utf-8") : "";
|
|
118423
119423
|
if (claudeMdContent.trim().length > 0) {
|
|
118424
119424
|
console.log(`(${formatBytes(claudeMdContent.length)})`);
|
|
118425
119425
|
console.log(claudeMdContent);
|
|
@@ -118430,7 +119430,7 @@ function registerDebugCommand(program3) {
|
|
|
118430
119430
|
}
|
|
118431
119431
|
console.log(`=== Persona (SOUL.md) ===
|
|
118432
119432
|
`);
|
|
118433
|
-
const soulMdContent =
|
|
119433
|
+
const soulMdContent = existsSync99(soulMdPath) ? readFileSync90(soulMdPath, "utf-8") : existsSync99(workspaceSoulMdPath) ? readFileSync90(workspaceSoulMdPath, "utf-8") : "";
|
|
118434
119434
|
if (soulMdContent.trim().length > 0) {
|
|
118435
119435
|
console.log(`(${formatBytes(soulMdContent.length)})`);
|
|
118436
119436
|
console.log(soulMdContent);
|
|
@@ -118494,8 +119494,8 @@ function registerDebugCommand(program3) {
|
|
|
118494
119494
|
const fleetDir = join101(agentsDir, "..", "fleet");
|
|
118495
119495
|
const fleetInvPath = join101(fleetDir, "switchroom-invariants.md");
|
|
118496
119496
|
const fleetClaudePath = join101(fleetDir, "CLAUDE.md");
|
|
118497
|
-
const fleetInvBytes =
|
|
118498
|
-
const fleetClaudeBytes =
|
|
119497
|
+
const fleetInvBytes = existsSync99(fleetInvPath) ? readFileSync90(fleetInvPath, "utf-8").length : 0;
|
|
119498
|
+
const fleetClaudeBytes = existsSync99(fleetClaudePath) ? readFileSync90(fleetClaudePath, "utf-8").length : 0;
|
|
118499
119499
|
const fleetBytes = fleetInvBytes + fleetClaudeBytes;
|
|
118500
119500
|
const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
|
|
118501
119501
|
console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
|
|
@@ -118527,8 +119527,8 @@ function registerDebugCommand(program3) {
|
|
|
118527
119527
|
init_source();
|
|
118528
119528
|
|
|
118529
119529
|
// src/worktree/claim.ts
|
|
118530
|
-
import { execFileSync as
|
|
118531
|
-
import { closeSync as closeSync17, mkdirSync as mkdirSync56, openSync as openSync17, existsSync as
|
|
119530
|
+
import { execFileSync as execFileSync30 } from "node:child_process";
|
|
119531
|
+
import { closeSync as closeSync17, mkdirSync as mkdirSync56, openSync as openSync17, existsSync as existsSync101, unlinkSync as unlinkSync21 } from "node:fs";
|
|
118532
119532
|
import { join as join103, resolve as resolve57 } from "node:path";
|
|
118533
119533
|
import { homedir as homedir55 } from "node:os";
|
|
118534
119534
|
import { randomBytes as randomBytes16 } from "node:crypto";
|
|
@@ -118540,7 +119540,7 @@ import {
|
|
|
118540
119540
|
readFileSync as readFileSync91,
|
|
118541
119541
|
readdirSync as readdirSync34,
|
|
118542
119542
|
unlinkSync as unlinkSync20,
|
|
118543
|
-
existsSync as
|
|
119543
|
+
existsSync as existsSync100,
|
|
118544
119544
|
renameSync as renameSync25
|
|
118545
119545
|
} from "node:fs";
|
|
118546
119546
|
import { join as join102, resolve as resolve56 } from "node:path";
|
|
@@ -118654,7 +119654,7 @@ function expandHome(p) {
|
|
|
118654
119654
|
}
|
|
118655
119655
|
async function claimWorktree(input, codeRepos) {
|
|
118656
119656
|
const repoPath = resolveRepoPath(input.repo, codeRepos);
|
|
118657
|
-
if (!
|
|
119657
|
+
if (!existsSync101(repoPath)) {
|
|
118658
119658
|
throw new Error(`Repository path does not exist: ${repoPath}`);
|
|
118659
119659
|
}
|
|
118660
119660
|
let concurrencyCap = DEFAULT_CONCURRENCY;
|
|
@@ -118696,7 +119696,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
118696
119696
|
releaseLock();
|
|
118697
119697
|
}
|
|
118698
119698
|
try {
|
|
118699
|
-
|
|
119699
|
+
execFileSync30("git", ["worktree", "add", "-b", branch, worktreePath], {
|
|
118700
119700
|
cwd: repoPath,
|
|
118701
119701
|
stdio: "pipe"
|
|
118702
119702
|
});
|
|
@@ -118709,8 +119709,8 @@ async function claimWorktree(input, codeRepos) {
|
|
|
118709
119709
|
}
|
|
118710
119710
|
|
|
118711
119711
|
// src/worktree/release.ts
|
|
118712
|
-
import { execFileSync as
|
|
118713
|
-
import { existsSync as
|
|
119712
|
+
import { execFileSync as execFileSync31 } from "node:child_process";
|
|
119713
|
+
import { existsSync as existsSync102 } from "node:fs";
|
|
118714
119714
|
function releaseWorktree(input) {
|
|
118715
119715
|
const { id } = input;
|
|
118716
119716
|
const record2 = readRecord(id);
|
|
@@ -118718,9 +119718,9 @@ function releaseWorktree(input) {
|
|
|
118718
119718
|
return { released: true };
|
|
118719
119719
|
}
|
|
118720
119720
|
let gitSuccess = true;
|
|
118721
|
-
if (
|
|
119721
|
+
if (existsSync102(record2.path)) {
|
|
118722
119722
|
try {
|
|
118723
|
-
|
|
119723
|
+
execFileSync31("git", ["worktree", "remove", "--force", record2.path], {
|
|
118724
119724
|
cwd: record2.repo,
|
|
118725
119725
|
stdio: "pipe"
|
|
118726
119726
|
});
|
|
@@ -118756,8 +119756,8 @@ function listWorktrees() {
|
|
|
118756
119756
|
}
|
|
118757
119757
|
|
|
118758
119758
|
// src/worktree/reaper.ts
|
|
118759
|
-
import { execFileSync as
|
|
118760
|
-
import { existsSync as
|
|
119759
|
+
import { execFileSync as execFileSync32 } from "node:child_process";
|
|
119760
|
+
import { existsSync as existsSync103 } from "node:fs";
|
|
118761
119761
|
var STALE_THRESHOLD_MS = 10 * 60 * 1000;
|
|
118762
119762
|
function reapSkipReasonText(action) {
|
|
118763
119763
|
switch (action) {
|
|
@@ -118774,7 +119774,7 @@ function reapSkipReasonText(action) {
|
|
|
118774
119774
|
function probePathInUse(path10) {
|
|
118775
119775
|
let probeRan = false;
|
|
118776
119776
|
try {
|
|
118777
|
-
|
|
119777
|
+
execFileSync32("fuser", [path10], { stdio: "pipe" });
|
|
118778
119778
|
return "in-use";
|
|
118779
119779
|
} catch (err) {
|
|
118780
119780
|
if (err.code !== "ENOENT") {
|
|
@@ -118782,7 +119782,7 @@ function probePathInUse(path10) {
|
|
|
118782
119782
|
}
|
|
118783
119783
|
}
|
|
118784
119784
|
try {
|
|
118785
|
-
const out =
|
|
119785
|
+
const out = execFileSync32("lsof", ["-t", path10], {
|
|
118786
119786
|
stdio: ["ignore", "pipe", "ignore"]
|
|
118787
119787
|
}).toString().trim();
|
|
118788
119788
|
probeRan = true;
|
|
@@ -118797,7 +119797,7 @@ function probePathInUse(path10) {
|
|
|
118797
119797
|
}
|
|
118798
119798
|
function hasUncommittedChanges(_repoPath, worktreePath) {
|
|
118799
119799
|
try {
|
|
118800
|
-
const out =
|
|
119800
|
+
const out = execFileSync32("git", ["-C", worktreePath, "status", "--porcelain"], { stdio: "pipe" }).toString();
|
|
118801
119801
|
return out.trim().length > 0;
|
|
118802
119802
|
} catch {
|
|
118803
119803
|
return true;
|
|
@@ -118805,7 +119805,7 @@ function hasUncommittedChanges(_repoPath, worktreePath) {
|
|
|
118805
119805
|
}
|
|
118806
119806
|
function defaultRemoveWorktree(repoPath, worktreePath) {
|
|
118807
119807
|
try {
|
|
118808
|
-
|
|
119808
|
+
execFileSync32("git", ["worktree", "remove", "--force", worktreePath], {
|
|
118809
119809
|
cwd: repoPath,
|
|
118810
119810
|
stdio: "pipe"
|
|
118811
119811
|
});
|
|
@@ -118818,7 +119818,7 @@ function planReaper(nowMs, deps = {}) {
|
|
|
118818
119818
|
const plan = [];
|
|
118819
119819
|
for (const record2 of listRecords()) {
|
|
118820
119820
|
const heartbeatAge = now2 - new Date(record2.heartbeatAt).getTime();
|
|
118821
|
-
const worktreeExists =
|
|
119821
|
+
const worktreeExists = existsSync103(record2.path);
|
|
118822
119822
|
if (!worktreeExists) {
|
|
118823
119823
|
plan.push({
|
|
118824
119824
|
record: record2,
|
|
@@ -118897,9 +119897,9 @@ function runReaper(nowMs, deps = {}) {
|
|
|
118897
119897
|
}
|
|
118898
119898
|
|
|
118899
119899
|
// src/worktree/gc.ts
|
|
118900
|
-
import { execFileSync as
|
|
119900
|
+
import { execFileSync as execFileSync33 } from "node:child_process";
|
|
118901
119901
|
import {
|
|
118902
|
-
existsSync as
|
|
119902
|
+
existsSync as existsSync104,
|
|
118903
119903
|
readFileSync as readFileSync92,
|
|
118904
119904
|
readdirSync as readdirSync35,
|
|
118905
119905
|
statSync as statSync55,
|
|
@@ -118920,7 +119920,7 @@ function repoRootFromWorktreeGitdir(gitdir) {
|
|
|
118920
119920
|
return gitdir.slice(0, idx);
|
|
118921
119921
|
}
|
|
118922
119922
|
function isSwitchroomRemote(originUrl) {
|
|
118923
|
-
return /[:/]switchroom\/switchroom(\.git)?\/?$/.test(originUrl.trim());
|
|
119923
|
+
return /[:/](?:switchroom|mekenthompson)\/switchroom(\.git)?\/?$/.test(originUrl.trim());
|
|
118924
119924
|
}
|
|
118925
119925
|
function parseWorktreeList(porcelain) {
|
|
118926
119926
|
const entries = [];
|
|
@@ -119024,7 +120024,7 @@ function classifyTaskTree(i2) {
|
|
|
119024
120024
|
return "skip-unmerged";
|
|
119025
120025
|
return "reap";
|
|
119026
120026
|
}
|
|
119027
|
-
var defaultExec3 = (file, args, cwd) =>
|
|
120027
|
+
var defaultExec3 = (file, args, cwd) => execFileSync33(file, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).toString();
|
|
119028
120028
|
function defaultPrSignal(repo, branch, exec) {
|
|
119029
120029
|
try {
|
|
119030
120030
|
const out = exec("gh", [
|
|
@@ -119078,7 +120078,7 @@ function trashRoot() {
|
|
|
119078
120078
|
return resolve58(process.env.SWITCHROOM_WORKTREE_TRASH ?? join104(homedir56(), ".switchroom", "worktree-gc-trash"));
|
|
119079
120079
|
}
|
|
119080
120080
|
function planGc(roots, deps = {}, taskTreeRoots = []) {
|
|
119081
|
-
const exists = deps.existsSync ??
|
|
120081
|
+
const exists = deps.existsSync ?? existsSync104;
|
|
119082
120082
|
const readDir = deps.readDir ?? ((p) => readdirSync35(p));
|
|
119083
120083
|
const readFile4 = deps.readFile ?? ((p) => readFileSync92(p, "utf8"));
|
|
119084
120084
|
const stat3 = deps.stat ?? ((p) => statSync55(p));
|
|
@@ -119397,7 +120397,7 @@ function selectPurgeTargets(entries, olderThanDays) {
|
|
|
119397
120397
|
return entries.filter((e) => e.ageDays >= olderThanDays).map((e) => e.path);
|
|
119398
120398
|
}
|
|
119399
120399
|
function listTrashEntries(nowMs, deps = {}) {
|
|
119400
|
-
const exists = deps.existsSync ??
|
|
120400
|
+
const exists = deps.existsSync ?? existsSync104;
|
|
119401
120401
|
const readDir = deps.readDir ?? ((p) => readdirSync35(p));
|
|
119402
120402
|
const root = trashRoot();
|
|
119403
120403
|
if (!exists(root))
|
|
@@ -119450,7 +120450,7 @@ function defaultTaskTreeRoots() {
|
|
|
119450
120450
|
for (const name of names.sort()) {
|
|
119451
120451
|
for (const sub of ["home/work", "home/workspace"]) {
|
|
119452
120452
|
const r = join104(agentsDir, name, sub);
|
|
119453
|
-
if (
|
|
120453
|
+
if (existsSync104(r))
|
|
119454
120454
|
roots.push(r);
|
|
119455
120455
|
}
|
|
119456
120456
|
}
|
|
@@ -120461,7 +121461,7 @@ function registerM365McpLauncherCommand(program3) {
|
|
|
120461
121461
|
// src/cli/notion-mcp-launcher.ts
|
|
120462
121462
|
init_scaffold_integration();
|
|
120463
121463
|
import { spawn as spawn7 } from "node:child_process";
|
|
120464
|
-
import { existsSync as
|
|
121464
|
+
import { existsSync as existsSync105, mkdirSync as mkdirSync60, writeFileSync as writeFileSync43 } from "node:fs";
|
|
120465
121465
|
import { dirname as dirname45 } from "node:path";
|
|
120466
121466
|
var HEARTBEAT_WRITE_INTERVAL_MS = 30 * 1000;
|
|
120467
121467
|
var DEFAULT_HEARTBEAT_PATH = "/state/agent/notion-launcher.heartbeat.json";
|
|
@@ -120473,7 +121473,7 @@ function buildNotionMcpArgs(opts) {
|
|
|
120473
121473
|
function defaultWriteHeartbeat(path10, contents) {
|
|
120474
121474
|
try {
|
|
120475
121475
|
const dir = dirname45(path10);
|
|
120476
|
-
if (!
|
|
121476
|
+
if (!existsSync105(dir))
|
|
120477
121477
|
mkdirSync60(dir, { recursive: true });
|
|
120478
121478
|
writeFileSync43(path10, contents);
|
|
120479
121479
|
} catch {}
|
|
@@ -120589,6 +121589,92 @@ import { mkdirSync as mkdirSync61, readFileSync as readFileSync93, renameSync as
|
|
|
120589
121589
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
120590
121590
|
import { join as join107 } from "node:path";
|
|
120591
121591
|
import { createInterface as createInterface7 } from "node:readline";
|
|
121592
|
+
|
|
121593
|
+
// src/memory/hindsight-knowledge-admin.ts
|
|
121594
|
+
var KNOWLEDGE_SEARCH_LIMIT_MIN = 1;
|
|
121595
|
+
var KNOWLEDGE_SEARCH_LIMIT_MAX = 50;
|
|
121596
|
+
var KNOWLEDGE_SEARCH_LIMIT_DEFAULT = 10;
|
|
121597
|
+
function clampKnowledgeSearchLimit(limit) {
|
|
121598
|
+
if (limit === undefined || !Number.isFinite(limit)) {
|
|
121599
|
+
return KNOWLEDGE_SEARCH_LIMIT_DEFAULT;
|
|
121600
|
+
}
|
|
121601
|
+
return Math.min(KNOWLEDGE_SEARCH_LIMIT_MAX, Math.max(KNOWLEDGE_SEARCH_LIMIT_MIN, Math.trunc(limit)));
|
|
121602
|
+
}
|
|
121603
|
+
var KNOWLEDGE_ADMIN_TIMEOUT_MS = 15000;
|
|
121604
|
+
var KNOWLEDGE_PAGE_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
121605
|
+
|
|
121606
|
+
class KnowledgeAdmin {
|
|
121607
|
+
opts;
|
|
121608
|
+
constructor(opts) {
|
|
121609
|
+
this.opts = opts;
|
|
121610
|
+
}
|
|
121611
|
+
get fetchImpl() {
|
|
121612
|
+
return this.opts.fetchImpl ?? fetch;
|
|
121613
|
+
}
|
|
121614
|
+
knowledgeBasePath() {
|
|
121615
|
+
const base = this.opts.apiBaseUrl.replace(/\/+$/, "");
|
|
121616
|
+
return `${base}/v1/default/banks/${encodeURIComponent(this.opts.bankId)}/knowledge-base`;
|
|
121617
|
+
}
|
|
121618
|
+
async get(url) {
|
|
121619
|
+
const ctl = new AbortController;
|
|
121620
|
+
const timer = setTimeout(() => ctl.abort(), this.opts.timeoutMs ?? KNOWLEDGE_ADMIN_TIMEOUT_MS);
|
|
121621
|
+
try {
|
|
121622
|
+
return await this.fetchImpl(url, {
|
|
121623
|
+
method: "GET",
|
|
121624
|
+
headers: {
|
|
121625
|
+
accept: "application/json",
|
|
121626
|
+
...this.opts.bankId ? { "X-Bank-Id": this.opts.bankId } : {}
|
|
121627
|
+
},
|
|
121628
|
+
signal: ctl.signal
|
|
121629
|
+
});
|
|
121630
|
+
} finally {
|
|
121631
|
+
clearTimeout(timer);
|
|
121632
|
+
}
|
|
121633
|
+
}
|
|
121634
|
+
async search(args) {
|
|
121635
|
+
const params = new URLSearchParams({
|
|
121636
|
+
q: args.query,
|
|
121637
|
+
limit: String(clampKnowledgeSearchLimit(args.limit))
|
|
121638
|
+
});
|
|
121639
|
+
const res = await this.get(`${this.knowledgeBasePath()}/search?${params}`);
|
|
121640
|
+
if (!res.ok) {
|
|
121641
|
+
throw new Error(`searching knowledge pages in bank '${this.opts.bankId}' failed: HTTP ${res.status}`);
|
|
121642
|
+
}
|
|
121643
|
+
const parsed = await res.json();
|
|
121644
|
+
const results = Array.isArray(parsed.results) ? parsed.results : [];
|
|
121645
|
+
return {
|
|
121646
|
+
results,
|
|
121647
|
+
total: typeof parsed.total === "number" ? parsed.total : results.length
|
|
121648
|
+
};
|
|
121649
|
+
}
|
|
121650
|
+
async getPage(args) {
|
|
121651
|
+
if (!KNOWLEDGE_PAGE_ID_PATTERN.test(args.page_id)) {
|
|
121652
|
+
throw new Error(`'${args.page_id}' is not a knowledge page id. Ids look like ` + `'kp-<hex>' (letters, digits, '-' and '_' only) and come from ` + `search_knowledge_pages or get_knowledge_tree.`);
|
|
121653
|
+
}
|
|
121654
|
+
const res = await this.get(`${this.knowledgeBasePath()}/pages/${encodeURIComponent(args.page_id)}`);
|
|
121655
|
+
if (res.status === 404) {
|
|
121656
|
+
throw new Error(`no knowledge page '${args.page_id}' in bank '${this.opts.bankId}'. ` + `Page ids come from search_knowledge_pages or get_knowledge_tree.`);
|
|
121657
|
+
}
|
|
121658
|
+
if (!res.ok) {
|
|
121659
|
+
throw new Error(`reading knowledge page '${args.page_id}' in bank ` + `'${this.opts.bankId}' failed: HTTP ${res.status}`);
|
|
121660
|
+
}
|
|
121661
|
+
const page = await res.json();
|
|
121662
|
+
if (typeof page.markdown !== "string") {
|
|
121663
|
+
throw new Error(`knowledge page '${args.page_id}' in bank '${this.opts.bankId}' came ` + `back with no markdown body \u2014 the response had no string 'markdown' ` + `field, so there is nothing to read.`);
|
|
121664
|
+
}
|
|
121665
|
+
return page;
|
|
121666
|
+
}
|
|
121667
|
+
async tree() {
|
|
121668
|
+
const res = await this.get(`${this.knowledgeBasePath()}/tree`);
|
|
121669
|
+
if (!res.ok) {
|
|
121670
|
+
throw new Error(`listing the knowledge tree in bank '${this.opts.bankId}' failed: HTTP ${res.status}`);
|
|
121671
|
+
}
|
|
121672
|
+
const parsed = await res.json();
|
|
121673
|
+
return { roots: Array.isArray(parsed.roots) ? parsed.roots : [] };
|
|
121674
|
+
}
|
|
121675
|
+
}
|
|
121676
|
+
|
|
121677
|
+
// src/cli/hindsight-mcp-shim.ts
|
|
120592
121678
|
init_hindsight();
|
|
120593
121679
|
var SHIM_SUPPORTED_PROTOCOL_VERSIONS = [
|
|
120594
121680
|
"2025-06-18",
|
|
@@ -120657,9 +121743,80 @@ var SYNTHESIZED_TOOL_TABLE = {
|
|
|
120657
121743
|
description: "Exact name of the directive to reactivate."
|
|
120658
121744
|
}
|
|
120659
121745
|
}
|
|
121746
|
+
},
|
|
121747
|
+
search_knowledge_pages: {
|
|
121748
|
+
description: "Search YOUR OWN knowledge pages \u2014 the curated, continuously-refreshed " + "summaries of what this bank knows \u2014 with hybrid full-text + semantic " + "search. Reach for this before re-deriving a standing answer from raw " + "recall: a page is already synthesized, where recall returns fragments. " + "Returns ranked hits with a relevance snippet and a page id; read the " + "whole page with get_knowledge_page. Read-only, and only ever your own " + "memory bank. An empty knowledge base returns no results, not an error.",
|
|
121749
|
+
required: ["query"],
|
|
121750
|
+
props: {
|
|
121751
|
+
query: {
|
|
121752
|
+
type: "string",
|
|
121753
|
+
description: "What to look for, in natural language."
|
|
121754
|
+
},
|
|
121755
|
+
limit: {
|
|
121756
|
+
type: "integer",
|
|
121757
|
+
minimum: KNOWLEDGE_SEARCH_LIMIT_MIN,
|
|
121758
|
+
maximum: KNOWLEDGE_SEARCH_LIMIT_MAX,
|
|
121759
|
+
description: `Maximum hits to return (${KNOWLEDGE_SEARCH_LIMIT_MIN}-` + `${KNOWLEDGE_SEARCH_LIMIT_MAX}, default ${KNOWLEDGE_SEARCH_LIMIT_DEFAULT}).`
|
|
121760
|
+
}
|
|
121761
|
+
}
|
|
121762
|
+
},
|
|
121763
|
+
get_knowledge_page: {
|
|
121764
|
+
description: "Read one of YOUR OWN knowledge pages in full, by the page id returned " + "by search_knowledge_pages or get_knowledge_tree. Returns the complete " + "markdown document (YAML frontmatter + synthesized body). Prefer this " + "over rebuilding the same understanding from scratch. Read-only: it " + "returns the page as it currently stands and never triggers a refresh.",
|
|
121765
|
+
required: ["page_id"],
|
|
121766
|
+
props: {
|
|
121767
|
+
page_id: {
|
|
121768
|
+
type: "string",
|
|
121769
|
+
description: "Id of the page to read, from search_knowledge_pages or " + "get_knowledge_tree."
|
|
121770
|
+
}
|
|
121771
|
+
}
|
|
121772
|
+
},
|
|
121773
|
+
get_knowledge_tree: {
|
|
121774
|
+
description: "List YOUR OWN knowledge base as a folder/page tree \u2014 every page's id, " + "name, source query, tags and staleness flag. Call this to see what " + "this bank already knows before reading anything else; is_stale=true " + "means the page MAY be behind newer memories, not that it is wrong. " + "Read-only, and only ever your own memory bank.",
|
|
121775
|
+
required: [],
|
|
121776
|
+
props: {}
|
|
120660
121777
|
}
|
|
120661
121778
|
};
|
|
120662
121779
|
var SYNTHESIZED_TOOL_NAMES = Object.keys(SYNTHESIZED_TOOL_TABLE);
|
|
121780
|
+
var INTEGER_STRING_PATTERN = /^-?(?:0|[1-9]\d*)$/;
|
|
121781
|
+
function coerceSynthesizedArg(toolName, key, propSchema, value) {
|
|
121782
|
+
const schema = propSchema ?? {};
|
|
121783
|
+
if (schema.type !== "integer") {
|
|
121784
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
121785
|
+
return {
|
|
121786
|
+
ok: false,
|
|
121787
|
+
text: `${toolName}: '${key}' must be a non-empty string.`
|
|
121788
|
+
};
|
|
121789
|
+
}
|
|
121790
|
+
return { ok: true, value };
|
|
121791
|
+
}
|
|
121792
|
+
const min = schema.minimum;
|
|
121793
|
+
const max = schema.maximum;
|
|
121794
|
+
const range = min !== undefined && max !== undefined ? ` between ${min} and ${max}` : "";
|
|
121795
|
+
let n;
|
|
121796
|
+
if (typeof value === "number") {
|
|
121797
|
+
n = value;
|
|
121798
|
+
} else if (typeof value === "string" && INTEGER_STRING_PATTERN.test(value)) {
|
|
121799
|
+
n = Number(value);
|
|
121800
|
+
} else {
|
|
121801
|
+
return {
|
|
121802
|
+
ok: false,
|
|
121803
|
+
text: `${toolName}: '${key}' must be an integer${range}.`
|
|
121804
|
+
};
|
|
121805
|
+
}
|
|
121806
|
+
if (!Number.isSafeInteger(n)) {
|
|
121807
|
+
return {
|
|
121808
|
+
ok: false,
|
|
121809
|
+
text: `${toolName}: '${key}' must be an integer${range}.`
|
|
121810
|
+
};
|
|
121811
|
+
}
|
|
121812
|
+
if (min !== undefined && n < min || max !== undefined && n > max) {
|
|
121813
|
+
return {
|
|
121814
|
+
ok: false,
|
|
121815
|
+
text: `${toolName}: '${key}' must be an integer${range} \u2014 got ${n}. ` + `Re-issue the call with a value in range.`
|
|
121816
|
+
};
|
|
121817
|
+
}
|
|
121818
|
+
return { ok: true, value: n };
|
|
121819
|
+
}
|
|
120663
121820
|
function buildSynthesizedToolsList() {
|
|
120664
121821
|
return Object.entries(SYNTHESIZED_TOOL_TABLE).map(([name, { description, required, props }]) => ({
|
|
120665
121822
|
name,
|
|
@@ -120738,6 +121895,52 @@ var DEFAULT_RECALL_MAX_TOKENS = 1024;
|
|
|
120738
121895
|
var DEFAULT_REFLECT_MAX_TOKENS = 1024;
|
|
120739
121896
|
var DEFAULT_RECALL_BUDGET = "low";
|
|
120740
121897
|
var DEFAULT_REFLECT_BUDGET = "mid";
|
|
121898
|
+
var KNOWLEDGE_RESPONSE_MAX_CHARS = 16384;
|
|
121899
|
+
var KNOWLEDGE_TREE_MARKER_RESERVE = 256;
|
|
121900
|
+
function capKnowledgeResponse(text) {
|
|
121901
|
+
if (text.length <= KNOWLEDGE_RESPONSE_MAX_CHARS)
|
|
121902
|
+
return text;
|
|
121903
|
+
const omitted = text.length - KNOWLEDGE_RESPONSE_MAX_CHARS;
|
|
121904
|
+
return text.slice(0, KNOWLEDGE_RESPONSE_MAX_CHARS) + `
|
|
121905
|
+
|
|
121906
|
+
\u2026truncated at ${KNOWLEDGE_RESPONSE_MAX_CHARS} chars \u2014 ${omitted} ` + `chars omitted. This is a PARTIAL read; narrow it with ` + `search_knowledge_pages rather than treating the above as the whole.`;
|
|
121907
|
+
}
|
|
121908
|
+
function countKnowledgeNodes(nodes) {
|
|
121909
|
+
let n = 0;
|
|
121910
|
+
for (const node of nodes)
|
|
121911
|
+
n += 1 + countKnowledgeNodes(node.children ?? []);
|
|
121912
|
+
return n;
|
|
121913
|
+
}
|
|
121914
|
+
function takeKnowledgeNodes(nodes, budget) {
|
|
121915
|
+
const out = [];
|
|
121916
|
+
for (const node of nodes) {
|
|
121917
|
+
if (budget.left <= 0)
|
|
121918
|
+
break;
|
|
121919
|
+
budget.left -= 1;
|
|
121920
|
+
out.push(node.children === undefined ? node : { ...node, children: takeKnowledgeNodes(node.children, budget) });
|
|
121921
|
+
}
|
|
121922
|
+
return out;
|
|
121923
|
+
}
|
|
121924
|
+
function renderKnowledgeTree(roots) {
|
|
121925
|
+
const total = countKnowledgeNodes(roots);
|
|
121926
|
+
const whole = JSON.stringify(roots);
|
|
121927
|
+
if (whole.length <= KNOWLEDGE_RESPONSE_MAX_CHARS)
|
|
121928
|
+
return whole;
|
|
121929
|
+
const charBudget = KNOWLEDGE_RESPONSE_MAX_CHARS - KNOWLEDGE_TREE_MARKER_RESERVE;
|
|
121930
|
+
let allowed = total;
|
|
121931
|
+
let kept = takeKnowledgeNodes(roots, { left: allowed });
|
|
121932
|
+
let json = JSON.stringify(kept);
|
|
121933
|
+
while (json.length > charBudget && allowed > 0) {
|
|
121934
|
+
allowed = Math.max(0, Math.min(allowed - 1, Math.floor(allowed * (charBudget / json.length))));
|
|
121935
|
+
kept = takeKnowledgeNodes(roots, { left: allowed });
|
|
121936
|
+
json = JSON.stringify(kept);
|
|
121937
|
+
}
|
|
121938
|
+
const shown = countKnowledgeNodes(kept);
|
|
121939
|
+
if (shown === total)
|
|
121940
|
+
return json;
|
|
121941
|
+
return json + `
|
|
121942
|
+
\u2026${total - shown} of ${total} nodes omitted \u2014 the knowledge tree was ` + `truncated to fit the response budget. This is a PARTIAL listing; use ` + `search_knowledge_pages to find a specific page.`;
|
|
121943
|
+
}
|
|
120741
121944
|
var VALID_BUDGETS = new Set(["low", "mid", "high"]);
|
|
120742
121945
|
function resolveClampMaxTokens(envVal, fallback) {
|
|
120743
121946
|
if (envVal === undefined || envVal === "")
|
|
@@ -121072,6 +122275,17 @@ class HindsightShim {
|
|
|
121072
122275
|
}
|
|
121073
122276
|
return this.directiveAdminCache;
|
|
121074
122277
|
}
|
|
122278
|
+
knowledgeAdminCache = null;
|
|
122279
|
+
get knowledgeAdmin() {
|
|
122280
|
+
if (!this.knowledgeAdminCache) {
|
|
122281
|
+
this.knowledgeAdminCache = new KnowledgeAdmin({
|
|
122282
|
+
apiBaseUrl: this.opts.apiBaseUrl ?? this.opts.url.replace(/\/mcp\/?$/, ""),
|
|
122283
|
+
bankId: this.opts.bankId,
|
|
122284
|
+
...this.opts.fetchImpl ? { fetchImpl: this.opts.fetchImpl } : {}
|
|
122285
|
+
});
|
|
122286
|
+
}
|
|
122287
|
+
return this.knowledgeAdminCache;
|
|
122288
|
+
}
|
|
121075
122289
|
async synthesizedCall(name, rawArgs) {
|
|
121076
122290
|
const fail4 = (text) => ({
|
|
121077
122291
|
content: [{ type: "text", text }],
|
|
@@ -121084,26 +122298,23 @@ class HindsightShim {
|
|
|
121084
122298
|
if (unknown.length > 0) {
|
|
121085
122299
|
return fail4(`${name} does not accept ${unknown.map((u) => `'${u}'`).join(", ")}. ` + `Accepted arguments: ${[...allowed].join(", ")}. ` + (unknown.includes("bank_id") ? "This tool always operates on your own memory bank; there is no " + "way to target another agent's bank through it." : ""));
|
|
121086
122300
|
}
|
|
122301
|
+
const clean3 = {};
|
|
121087
122302
|
for (const key of Object.keys(args)) {
|
|
121088
|
-
|
|
121089
|
-
|
|
121090
|
-
|
|
122303
|
+
const coerced = coerceSynthesizedArg(name, key, spec.props[key], args[key]);
|
|
122304
|
+
if (!coerced.ok)
|
|
122305
|
+
return fail4(coerced.text);
|
|
122306
|
+
clean3[key] = coerced.value;
|
|
121091
122307
|
}
|
|
121092
122308
|
for (const req of spec.required) {
|
|
121093
|
-
if (
|
|
122309
|
+
if (clean3[req] === undefined) {
|
|
121094
122310
|
return fail4(`${name} requires '${req}'.`);
|
|
121095
122311
|
}
|
|
121096
122312
|
}
|
|
121097
122313
|
if (!this.opts.bankId) {
|
|
121098
|
-
return fail4(`${name} is unavailable: this agent has no HINDSIGHT_BANK_ID, so ` + "there is no bank to pin
|
|
122314
|
+
return fail4(`${name} is unavailable: this agent has no HINDSIGHT_BANK_ID, so ` + "there is no bank to pin it to.");
|
|
121099
122315
|
}
|
|
121100
122316
|
try {
|
|
121101
|
-
const text =
|
|
121102
|
-
name: args.name,
|
|
121103
|
-
...typeof args.superseded_by === "string" ? { supersededBy: args.superseded_by } : {}
|
|
121104
|
-
}) : await this.directiveAdmin.reactivate({
|
|
121105
|
-
name: args.name
|
|
121106
|
-
});
|
|
122317
|
+
const text = await this.runSynthesized(name, clean3);
|
|
121107
122318
|
return { content: [{ type: "text", text }], isError: false };
|
|
121108
122319
|
} catch (err) {
|
|
121109
122320
|
if (err instanceof DirectivePairInconsistentError) {
|
|
@@ -121112,6 +122323,42 @@ class HindsightShim {
|
|
|
121112
122323
|
return fail4(`${name} failed: ${String(err)}`);
|
|
121113
122324
|
}
|
|
121114
122325
|
}
|
|
122326
|
+
async runSynthesized(name, args) {
|
|
122327
|
+
switch (name) {
|
|
122328
|
+
case "deactivate_directive":
|
|
122329
|
+
return this.directiveAdmin.deactivate({
|
|
122330
|
+
name: args.name,
|
|
122331
|
+
...typeof args.superseded_by === "string" ? { supersededBy: args.superseded_by } : {}
|
|
122332
|
+
});
|
|
122333
|
+
case "reactivate_directive":
|
|
122334
|
+
return this.directiveAdmin.reactivate({ name: args.name });
|
|
122335
|
+
case "search_knowledge_pages": {
|
|
122336
|
+
const res = await this.knowledgeAdmin.search({
|
|
122337
|
+
query: args.query,
|
|
122338
|
+
...typeof args.limit === "number" ? { limit: args.limit } : {}
|
|
122339
|
+
});
|
|
122340
|
+
if (res.results.length === 0) {
|
|
122341
|
+
return "No knowledge pages matched that query in your memory bank. " + "The bank may have no pages yet \u2014 get_knowledge_tree lists what " + "exists.";
|
|
122342
|
+
}
|
|
122343
|
+
return capKnowledgeResponse(JSON.stringify({ total: res.total, results: res.results }, null, 2));
|
|
122344
|
+
}
|
|
122345
|
+
case "get_knowledge_page": {
|
|
122346
|
+
const page = await this.knowledgeAdmin.getPage({
|
|
122347
|
+
page_id: args.page_id
|
|
122348
|
+
});
|
|
122349
|
+
return capKnowledgeResponse(page.markdown);
|
|
122350
|
+
}
|
|
122351
|
+
case "get_knowledge_tree": {
|
|
122352
|
+
const tree = await this.knowledgeAdmin.tree();
|
|
122353
|
+
if (tree.roots.length === 0) {
|
|
122354
|
+
return "Your knowledge base has no pages yet. Pages are synthesized from " + "mental models \u2014 propose one to start it.";
|
|
122355
|
+
}
|
|
122356
|
+
return renderKnowledgeTree(tree.roots);
|
|
122357
|
+
}
|
|
122358
|
+
default:
|
|
122359
|
+
throw new Error(`${name} is advertised as a synthesized tool but has no dispatch arm`);
|
|
122360
|
+
}
|
|
122361
|
+
}
|
|
121115
122362
|
async toolsCall(params) {
|
|
121116
122363
|
const called = params;
|
|
121117
122364
|
if (called?.name && SYNTHESIZED_TOOL_NAMES.includes(called.name)) {
|
|
@@ -121891,7 +123138,7 @@ function runRedactStdin() {
|
|
|
121891
123138
|
}
|
|
121892
123139
|
|
|
121893
123140
|
// src/cli/status-ask.ts
|
|
121894
|
-
import { readFileSync as readFileSync95, existsSync as
|
|
123141
|
+
import { readFileSync as readFileSync95, existsSync as existsSync106, readdirSync as readdirSync37 } from "node:fs";
|
|
121895
123142
|
import { join as join108 } from "node:path";
|
|
121896
123143
|
import { homedir as homedir57 } from "node:os";
|
|
121897
123144
|
|
|
@@ -122214,7 +123461,7 @@ function runReport(opts) {
|
|
|
122214
123461
|
function resolveSources(explicitPath) {
|
|
122215
123462
|
if (explicitPath != null && explicitPath.trim() !== "") {
|
|
122216
123463
|
const trimmed = explicitPath.trim();
|
|
122217
|
-
if (!
|
|
123464
|
+
if (!existsSync106(trimmed)) {
|
|
122218
123465
|
process.stderr.write(`status-ask report: ${trimmed}: file not found
|
|
122219
123466
|
`);
|
|
122220
123467
|
process.exit(1);
|
|
@@ -122230,7 +123477,7 @@ function resolveSources(explicitPath) {
|
|
|
122230
123477
|
} catch {
|
|
122231
123478
|
agentsDir = join108(homedir57(), ".switchroom", "agents");
|
|
122232
123479
|
}
|
|
122233
|
-
if (!
|
|
123480
|
+
if (!existsSync106(agentsDir))
|
|
122234
123481
|
return [];
|
|
122235
123482
|
const sources = [];
|
|
122236
123483
|
let entries;
|
|
@@ -122241,7 +123488,7 @@ function resolveSources(explicitPath) {
|
|
|
122241
123488
|
}
|
|
122242
123489
|
for (const name of entries) {
|
|
122243
123490
|
const path10 = join108(agentsDir, name, "runtime-metrics.jsonl");
|
|
122244
|
-
if (
|
|
123491
|
+
if (existsSync106(path10)) {
|
|
122245
123492
|
sources.push({ path: path10, agent: name });
|
|
122246
123493
|
}
|
|
122247
123494
|
}
|
|
@@ -122272,7 +123519,7 @@ init_paths();
|
|
|
122272
123519
|
import {
|
|
122273
123520
|
chownSync as chownSync12,
|
|
122274
123521
|
closeSync as closeSync18,
|
|
122275
|
-
existsSync as
|
|
123522
|
+
existsSync as existsSync107,
|
|
122276
123523
|
fsyncSync as fsyncSync8,
|
|
122277
123524
|
mkdirSync as mkdirSync62,
|
|
122278
123525
|
openSync as openSync18,
|
|
@@ -122402,7 +123649,7 @@ function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
|
122402
123649
|
const paths = overlayPathsFor(agent, opts);
|
|
122403
123650
|
return withAgentLock(paths, () => {
|
|
122404
123651
|
const finalPath = join109(paths.skillsDir, `${slug}.yaml`);
|
|
122405
|
-
if (!
|
|
123652
|
+
if (!existsSync107(finalPath))
|
|
122406
123653
|
return false;
|
|
122407
123654
|
unlinkSync22(finalPath);
|
|
122408
123655
|
return true;
|
|
@@ -122410,7 +123657,7 @@ function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
|
122410
123657
|
}
|
|
122411
123658
|
function listSkillsOverlayEntries(agent, opts = {}) {
|
|
122412
123659
|
const paths = overlayPathsFor(agent, opts);
|
|
122413
|
-
if (!
|
|
123660
|
+
if (!existsSync107(paths.skillsDir))
|
|
122414
123661
|
return [];
|
|
122415
123662
|
const out = [];
|
|
122416
123663
|
for (const name of readdirSync38(paths.skillsDir)) {
|
|
@@ -122429,7 +123676,7 @@ function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
|
122429
123676
|
const paths = overlayPathsFor(agent, opts);
|
|
122430
123677
|
return withAgentLock(paths, () => {
|
|
122431
123678
|
const finalPath = join109(paths.scheduleDir, `${slug}.yaml`);
|
|
122432
|
-
if (!
|
|
123679
|
+
if (!existsSync107(finalPath))
|
|
122433
123680
|
return false;
|
|
122434
123681
|
unlinkSync22(finalPath);
|
|
122435
123682
|
return true;
|
|
@@ -122437,7 +123684,7 @@ function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
|
122437
123684
|
}
|
|
122438
123685
|
function listOverlayEntries(agent, opts = {}) {
|
|
122439
123686
|
const paths = overlayPathsFor(agent, opts);
|
|
122440
|
-
if (!
|
|
123687
|
+
if (!existsSync107(paths.scheduleDir))
|
|
122441
123688
|
return [];
|
|
122442
123689
|
const out = [];
|
|
122443
123690
|
for (const name of readdirSync38(paths.scheduleDir)) {
|
|
@@ -122665,7 +123912,7 @@ function reconcileAgentCronOnly(agent) {
|
|
|
122665
123912
|
// src/cli/agent-config-pending.ts
|
|
122666
123913
|
import {
|
|
122667
123914
|
closeSync as closeSync19,
|
|
122668
|
-
existsSync as
|
|
123915
|
+
existsSync as existsSync108,
|
|
122669
123916
|
fsyncSync as fsyncSync9,
|
|
122670
123917
|
mkdirSync as mkdirSync63,
|
|
122671
123918
|
openSync as openSync19,
|
|
@@ -122722,7 +123969,7 @@ function stagePendingScheduleEntry(opts) {
|
|
|
122722
123969
|
}
|
|
122723
123970
|
function listPendingScheduleEntries(agent, opts = {}) {
|
|
122724
123971
|
const dir = pendingDir(agent, opts);
|
|
122725
|
-
if (!
|
|
123972
|
+
if (!existsSync108(dir))
|
|
122726
123973
|
return [];
|
|
122727
123974
|
const out = [];
|
|
122728
123975
|
for (const name of readdirSync39(dir).sort()) {
|
|
@@ -122731,7 +123978,7 @@ function listPendingScheduleEntries(agent, opts = {}) {
|
|
|
122731
123978
|
const stageId = name.slice(0, -".meta.json".length);
|
|
122732
123979
|
const metaPath = join110(dir, name);
|
|
122733
123980
|
const yamlPath = join110(dir, `${stageId}.yaml`);
|
|
122734
|
-
if (!
|
|
123981
|
+
if (!existsSync108(yamlPath))
|
|
122735
123982
|
continue;
|
|
122736
123983
|
try {
|
|
122737
123984
|
const meta = JSON.parse(readFileSync97(metaPath, "utf-8"));
|
|
@@ -122750,7 +123997,7 @@ function commitPendingScheduleEntry(opts) {
|
|
|
122750
123997
|
const slug = match.meta.entry.name ?? match.stageId;
|
|
122751
123998
|
const paths = overlayPathsFor(opts.agent, { root: opts.root });
|
|
122752
123999
|
const finalPath = join110(paths.scheduleDir, `${slug}.yaml`);
|
|
122753
|
-
if (
|
|
124000
|
+
if (existsSync108(finalPath)) {
|
|
122754
124001
|
return { committed: false, reason: "slug_collision" };
|
|
122755
124002
|
}
|
|
122756
124003
|
renameSync29(match.yamlPath, finalPath);
|
|
@@ -122772,8 +124019,8 @@ function denyPendingScheduleEntry(opts) {
|
|
|
122772
124019
|
}
|
|
122773
124020
|
|
|
122774
124021
|
// src/cli/agent-config-write.ts
|
|
122775
|
-
import { existsSync as
|
|
122776
|
-
import { execFileSync as
|
|
124022
|
+
import { existsSync as existsSync109, readFileSync as readFileSync98 } from "node:fs";
|
|
124023
|
+
import { execFileSync as execFileSync34 } from "node:child_process";
|
|
122777
124024
|
|
|
122778
124025
|
// src/scheduler/schedule-report.ts
|
|
122779
124026
|
var TIER_WEIGHT = { poll: 0, action: 0, cheap: 1, main: 5 };
|
|
@@ -123162,7 +124409,7 @@ function scheduleRemove(opts) {
|
|
|
123162
124409
|
}
|
|
123163
124410
|
let priorContent = null;
|
|
123164
124411
|
try {
|
|
123165
|
-
if (
|
|
124412
|
+
if (existsSync109(match.path))
|
|
123166
124413
|
priorContent = readFileSync98(match.path, "utf-8");
|
|
123167
124414
|
} catch {}
|
|
123168
124415
|
deleteOverlayEntry(agent, match.slug, { root: opts.root });
|
|
@@ -123366,10 +124613,10 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
123366
124613
|
}
|
|
123367
124614
|
let blob;
|
|
123368
124615
|
if (opts.jsonl) {
|
|
123369
|
-
blob =
|
|
124616
|
+
blob = existsSync109(opts.jsonl) ? readFileSync98(opts.jsonl, "utf-8") : "";
|
|
123370
124617
|
} else {
|
|
123371
124618
|
try {
|
|
123372
|
-
blob =
|
|
124619
|
+
blob = execFileSync34("docker", ["exec", `switchroom-${agent}`, "cat", "/state/agent/scheduler.jsonl"], {
|
|
123373
124620
|
encoding: "utf-8",
|
|
123374
124621
|
stdio: ["ignore", "pipe", "ignore"]
|
|
123375
124622
|
});
|
|
@@ -123398,7 +124645,7 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
123398
124645
|
|
|
123399
124646
|
// src/cli/agent-config-skill-write.ts
|
|
123400
124647
|
var import_yaml24 = __toESM(require_dist(), 1);
|
|
123401
|
-
import { existsSync as
|
|
124648
|
+
import { existsSync as existsSync110 } from "node:fs";
|
|
123402
124649
|
init_reconcile_default_skills();
|
|
123403
124650
|
init_agent_config();
|
|
123404
124651
|
var import_yaml25 = __toESM(require_dist(), 1);
|
|
@@ -123478,7 +124725,7 @@ function skillInstall(opts) {
|
|
|
123478
124725
|
}
|
|
123479
124726
|
const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
|
|
123480
124727
|
const skillPath = join111(poolDir, skillName);
|
|
123481
|
-
if (!
|
|
124728
|
+
if (!existsSync110(skillPath)) {
|
|
123482
124729
|
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.`);
|
|
123483
124730
|
}
|
|
123484
124731
|
const yamlText = import_yaml24.stringify({ skills: [skillName] });
|
|
@@ -123648,7 +124895,7 @@ init_source();
|
|
|
123648
124895
|
import {
|
|
123649
124896
|
chmodSync as chmodSync18,
|
|
123650
124897
|
closeSync as closeSync20,
|
|
123651
|
-
existsSync as
|
|
124898
|
+
existsSync as existsSync111,
|
|
123652
124899
|
lstatSync as lstatSync14,
|
|
123653
124900
|
mkdirSync as mkdirSync64,
|
|
123654
124901
|
mkdtempSync as mkdtempSync5,
|
|
@@ -123857,7 +125104,7 @@ function validatePayload(name, files) {
|
|
|
123857
125104
|
function diffSummary(currentDir, files) {
|
|
123858
125105
|
const lines = [];
|
|
123859
125106
|
const currentFiles = {};
|
|
123860
|
-
if (
|
|
125107
|
+
if (existsSync111(currentDir)) {
|
|
123861
125108
|
const walk2 = (sub) => {
|
|
123862
125109
|
for (const ent of readdirSync40(sub, { withFileTypes: true })) {
|
|
123863
125110
|
const full = join112(sub, ent.name);
|
|
@@ -123893,7 +125140,7 @@ function diffSummary(currentDir, files) {
|
|
|
123893
125140
|
`);
|
|
123894
125141
|
}
|
|
123895
125142
|
function writePayload(poolDir, name, files) {
|
|
123896
|
-
if (!
|
|
125143
|
+
if (!existsSync111(poolDir)) {
|
|
123897
125144
|
mkdirSync64(poolDir, { recursive: true, mode: 493 });
|
|
123898
125145
|
}
|
|
123899
125146
|
const target = join112(poolDir, name);
|
|
@@ -123943,9 +125190,9 @@ function writePayload(poolDir, name, files) {
|
|
|
123943
125190
|
try {
|
|
123944
125191
|
rmSync20(staging, { recursive: true, force: true });
|
|
123945
125192
|
} catch {}
|
|
123946
|
-
if (oldRename &&
|
|
125193
|
+
if (oldRename && existsSync111(oldRename)) {
|
|
123947
125194
|
try {
|
|
123948
|
-
if (
|
|
125195
|
+
if (existsSync111(target)) {
|
|
123949
125196
|
rmSync20(target, { recursive: true, force: true });
|
|
123950
125197
|
}
|
|
123951
125198
|
renameSync30(oldRename, target);
|
|
@@ -123966,7 +125213,7 @@ function registerSkillCommand(program3) {
|
|
|
123966
125213
|
files = loadFromStdin();
|
|
123967
125214
|
} else {
|
|
123968
125215
|
const fromPath = resolve60(opts.from);
|
|
123969
|
-
if (!
|
|
125216
|
+
if (!existsSync111(fromPath)) {
|
|
123970
125217
|
fail4(`--from path does not exist: ${opts.from}`);
|
|
123971
125218
|
}
|
|
123972
125219
|
const st = statSync58(fromPath);
|
|
@@ -124027,7 +125274,7 @@ init_source();
|
|
|
124027
125274
|
import {
|
|
124028
125275
|
chmodSync as chmodSync19,
|
|
124029
125276
|
closeSync as closeSync21,
|
|
124030
|
-
existsSync as
|
|
125277
|
+
existsSync as existsSync112,
|
|
124031
125278
|
lstatSync as lstatSync15,
|
|
124032
125279
|
mkdirSync as mkdirSync65,
|
|
124033
125280
|
mkdtempSync as mkdtempSync6,
|
|
@@ -124050,14 +125297,14 @@ var PERSONAL_SKILLS_SUBPATH2 = "personal-skills";
|
|
|
124050
125297
|
function resolveConfigSkillsDir(agent) {
|
|
124051
125298
|
const override = process.env.SWITCHROOM_CONFIG_DIR;
|
|
124052
125299
|
const candidate = override ? resolve61(override) : join113(homedir59(), ".switchroom-config");
|
|
124053
|
-
if (!
|
|
125300
|
+
if (!existsSync112(candidate))
|
|
124054
125301
|
return null;
|
|
124055
125302
|
return join113(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH2);
|
|
124056
125303
|
}
|
|
124057
125304
|
var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
|
|
124058
125305
|
function sweepMirrorPriors(configSkillsRoot) {
|
|
124059
125306
|
try {
|
|
124060
|
-
if (!
|
|
125307
|
+
if (!existsSync112(configSkillsRoot))
|
|
124061
125308
|
return;
|
|
124062
125309
|
const now2 = Date.now();
|
|
124063
125310
|
for (const ent of readdirSync41(configSkillsRoot)) {
|
|
@@ -124093,7 +125340,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
124093
125340
|
}
|
|
124094
125341
|
if (liveSkillDir === null) {
|
|
124095
125342
|
sweepMirrorPriors(configSkillsRoot);
|
|
124096
|
-
if (
|
|
125343
|
+
if (existsSync112(dest)) {
|
|
124097
125344
|
const trash = join113(configSkillsRoot, `.${name}-trash-${Date.now()}`);
|
|
124098
125345
|
renameSync31(dest, trash);
|
|
124099
125346
|
}
|
|
@@ -124118,7 +125365,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
124118
125365
|
};
|
|
124119
125366
|
walk2(liveSkillDir, staging);
|
|
124120
125367
|
chmodSync19(staging, 493);
|
|
124121
|
-
if (
|
|
125368
|
+
if (existsSync112(dest)) {
|
|
124122
125369
|
const prior = join113(configSkillsRoot, `.${name}-prior-${Date.now()}`);
|
|
124123
125370
|
renameSync31(dest, prior);
|
|
124124
125371
|
}
|
|
@@ -124159,7 +125406,7 @@ function trashDir(agentsRoot, agent) {
|
|
|
124159
125406
|
}
|
|
124160
125407
|
function countPersonalSkills(agentsRoot, agent) {
|
|
124161
125408
|
const skillsDir = join113(agentsRoot, agent, ".claude", "skills");
|
|
124162
|
-
if (!
|
|
125409
|
+
if (!existsSync112(skillsDir))
|
|
124163
125410
|
return 0;
|
|
124164
125411
|
let n = 0;
|
|
124165
125412
|
for (const ent of readdirSync41(skillsDir, { withFileTypes: true })) {
|
|
@@ -124268,7 +125515,7 @@ function behavioralValidate(files) {
|
|
|
124268
125515
|
}
|
|
124269
125516
|
function sweepTrash(agentsRoot, agent) {
|
|
124270
125517
|
const trash = trashDir(agentsRoot, agent);
|
|
124271
|
-
if (!
|
|
125518
|
+
if (!existsSync112(trash))
|
|
124272
125519
|
return;
|
|
124273
125520
|
const now2 = Date.now();
|
|
124274
125521
|
for (const ent of readdirSync41(trash, { withFileTypes: true })) {
|
|
@@ -124331,9 +125578,9 @@ function writePersonalSkill(targetDir, files) {
|
|
|
124331
125578
|
try {
|
|
124332
125579
|
rmSync21(staging, { recursive: true, force: true });
|
|
124333
125580
|
} catch {}
|
|
124334
|
-
if (oldRename &&
|
|
125581
|
+
if (oldRename && existsSync112(oldRename)) {
|
|
124335
125582
|
try {
|
|
124336
|
-
if (
|
|
125583
|
+
if (existsSync112(targetDir)) {
|
|
124337
125584
|
rmSync21(targetDir, { recursive: true, force: true });
|
|
124338
125585
|
}
|
|
124339
125586
|
renameSync31(oldRename, targetDir);
|
|
@@ -124399,7 +125646,7 @@ function loadFiles(opts) {
|
|
|
124399
125646
|
return loadFromStdin2();
|
|
124400
125647
|
}
|
|
124401
125648
|
const p = resolve61(opts.from);
|
|
124402
|
-
if (!
|
|
125649
|
+
if (!existsSync112(p)) {
|
|
124403
125650
|
fail5(`--from path does not exist: ${opts.from}`);
|
|
124404
125651
|
}
|
|
124405
125652
|
const st = statSync59(p);
|
|
@@ -124461,7 +125708,7 @@ function resolveCloneSource(source, opts) {
|
|
|
124461
125708
|
const slug = m[2];
|
|
124462
125709
|
const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
|
|
124463
125710
|
const dir = join113(root, slug);
|
|
124464
|
-
if (!
|
|
125711
|
+
if (!existsSync112(dir)) {
|
|
124465
125712
|
fail5(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
|
|
124466
125713
|
}
|
|
124467
125714
|
const st = lstatSync15(dir);
|
|
@@ -124608,7 +125855,7 @@ function listPersonalAction(opts) {
|
|
|
124608
125855
|
sweepTrash(agentsRoot, agent);
|
|
124609
125856
|
const skillsDir = join113(agentsRoot, agent, ".claude", "skills");
|
|
124610
125857
|
const personal = [];
|
|
124611
|
-
if (
|
|
125858
|
+
if (existsSync112(skillsDir)) {
|
|
124612
125859
|
for (const ent of readdirSync41(skillsDir, { withFileTypes: true })) {
|
|
124613
125860
|
if (!ent.isDirectory())
|
|
124614
125861
|
continue;
|
|
@@ -124794,10 +126041,10 @@ function registerSelfImproveProposeSkillCommand(program3) {
|
|
|
124794
126041
|
import { createConnection as createConnection5 } from "node:net";
|
|
124795
126042
|
import { homedir as homedir61 } from "node:os";
|
|
124796
126043
|
import { join as join118, resolve as resolve62, sep as sep6 } from "node:path";
|
|
124797
|
-
import { existsSync as
|
|
126044
|
+
import { existsSync as existsSync117, readFileSync as readFileSync106, realpathSync as realpathSync10 } from "node:fs";
|
|
124798
126045
|
|
|
124799
126046
|
// src/self-improve/apply-guard.ts
|
|
124800
|
-
import { existsSync as
|
|
126047
|
+
import { existsSync as existsSync115, lstatSync as lstatSync16, readFileSync as readFileSync104, statSync as statSync61 } from "node:fs";
|
|
124801
126048
|
|
|
124802
126049
|
// src/self-improve/config.ts
|
|
124803
126050
|
function intEnv(name, def) {
|
|
@@ -124818,7 +126065,7 @@ function resolveSelfImproveConfig() {
|
|
|
124818
126065
|
}
|
|
124819
126066
|
|
|
124820
126067
|
// src/self-improve/eval-gate.ts
|
|
124821
|
-
import { existsSync as
|
|
126068
|
+
import { existsSync as existsSync113, readFileSync as readFileSync102, readdirSync as readdirSync42, statSync as statSync60 } from "node:fs";
|
|
124822
126069
|
import { join as join115 } from "node:path";
|
|
124823
126070
|
import { spawnSync as spawnSync26 } from "node:child_process";
|
|
124824
126071
|
function evalsJsonPath(skillDir) {
|
|
@@ -124826,7 +126073,7 @@ function evalsJsonPath(skillDir) {
|
|
|
124826
126073
|
}
|
|
124827
126074
|
function skillHasEvals(skillDir) {
|
|
124828
126075
|
const p = evalsJsonPath(skillDir);
|
|
124829
|
-
if (!
|
|
126076
|
+
if (!existsSync113(p))
|
|
124830
126077
|
return false;
|
|
124831
126078
|
try {
|
|
124832
126079
|
const parsed = JSON.parse(readFileSync102(p, "utf-8"));
|
|
@@ -124837,7 +126084,7 @@ function skillHasEvals(skillDir) {
|
|
|
124837
126084
|
}
|
|
124838
126085
|
function aggregateBenchmark(benchmarkDir, repoRoot, pythonBin = process.env.SWITCHROOM_PYTHON ?? "python3") {
|
|
124839
126086
|
const script = join115(repoRoot, "skills", "skill-creator", "scripts", "aggregate_benchmark.py");
|
|
124840
|
-
if (!
|
|
126087
|
+
if (!existsSync113(script)) {
|
|
124841
126088
|
return { ok: false, error: `aggregate_benchmark.py not found at ${script}` };
|
|
124842
126089
|
}
|
|
124843
126090
|
const r = spawnSync26(pythonBin, [script, benchmarkDir], {
|
|
@@ -124869,7 +126116,7 @@ function isBenchmarkDir(dir) {
|
|
|
124869
126116
|
if (hasEvalDirs(dir))
|
|
124870
126117
|
return true;
|
|
124871
126118
|
const runs = join115(dir, "runs");
|
|
124872
|
-
return
|
|
126119
|
+
return existsSync113(runs) && hasEvalDirs(runs);
|
|
124873
126120
|
}
|
|
124874
126121
|
function resolveBenchmarkDir(baseDir, explicit) {
|
|
124875
126122
|
if (explicit != null && explicit.length > 0) {
|
|
@@ -124948,7 +126195,7 @@ function floatEnv(name, def) {
|
|
|
124948
126195
|
import {
|
|
124949
126196
|
closeSync as closeSync22,
|
|
124950
126197
|
copyFileSync as copyFileSync15,
|
|
124951
|
-
existsSync as
|
|
126198
|
+
existsSync as existsSync114,
|
|
124952
126199
|
mkdirSync as mkdirSync66,
|
|
124953
126200
|
openSync as openSync22,
|
|
124954
126201
|
readFileSync as readFileSync103,
|
|
@@ -125095,7 +126342,7 @@ function caseFingerprint(prompt) {
|
|
|
125095
126342
|
}
|
|
125096
126343
|
function readEvalsDoc(skillDir) {
|
|
125097
126344
|
const p = evalsJsonPath(skillDir);
|
|
125098
|
-
if (!
|
|
126345
|
+
if (!existsSync114(p))
|
|
125099
126346
|
return null;
|
|
125100
126347
|
try {
|
|
125101
126348
|
const parsed = JSON.parse(readFileSync103(p, "utf-8"));
|
|
@@ -125119,7 +126366,7 @@ function appendEvalCase(skillDir, slug, input) {
|
|
|
125119
126366
|
return { ok: false, reason: parsed.error };
|
|
125120
126367
|
const ec = parsed.case;
|
|
125121
126368
|
const existingDoc = readEvalsDoc(skillDir);
|
|
125122
|
-
if (existingDoc === null &&
|
|
126369
|
+
if (existingDoc === null && existsSync114(evalsJsonPath(skillDir))) {
|
|
125123
126370
|
return {
|
|
125124
126371
|
ok: false,
|
|
125125
126372
|
reason: "evals.json exists but is unparseable or malformed \u2014 refusing to " + "overwrite it (fix or remove the file first)"
|
|
@@ -125151,7 +126398,7 @@ function heldOutPath(stateDir) {
|
|
|
125151
126398
|
}
|
|
125152
126399
|
function heldOutHasFingerprint(stateDir, slug, fp) {
|
|
125153
126400
|
const path10 = heldOutPath(stateDir);
|
|
125154
|
-
if (!
|
|
126401
|
+
if (!existsSync114(path10))
|
|
125155
126402
|
return false;
|
|
125156
126403
|
let text;
|
|
125157
126404
|
try {
|
|
@@ -125203,7 +126450,7 @@ function baselineEvalsPath(stateDir, slug) {
|
|
|
125203
126450
|
}
|
|
125204
126451
|
function evalsSha256(skillDir) {
|
|
125205
126452
|
const p = evalsJsonPath(skillDir);
|
|
125206
|
-
if (!
|
|
126453
|
+
if (!existsSync114(p))
|
|
125207
126454
|
return null;
|
|
125208
126455
|
try {
|
|
125209
126456
|
return createHash21("sha256").update(readFileSync103(p)).digest("hex");
|
|
@@ -125213,7 +126460,7 @@ function evalsSha256(skillDir) {
|
|
|
125213
126460
|
}
|
|
125214
126461
|
function readManifest(stateDir) {
|
|
125215
126462
|
const p = manifestPath(stateDir);
|
|
125216
|
-
if (!
|
|
126463
|
+
if (!existsSync114(p))
|
|
125217
126464
|
return {};
|
|
125218
126465
|
try {
|
|
125219
126466
|
const parsed = JSON.parse(readFileSync103(p, "utf-8"));
|
|
@@ -125255,7 +126502,7 @@ function ownsSkill(skillDir) {
|
|
|
125255
126502
|
// src/self-improve/eval-case-proposals.ts
|
|
125256
126503
|
import {
|
|
125257
126504
|
closeSync as closeSync23,
|
|
125258
|
-
existsSync as
|
|
126505
|
+
existsSync as existsSync116,
|
|
125259
126506
|
mkdirSync as mkdirSync67,
|
|
125260
126507
|
openSync as openSync23,
|
|
125261
126508
|
readFileSync as readFileSync105,
|
|
@@ -125271,7 +126518,7 @@ function isRecord2(v) {
|
|
|
125271
126518
|
}
|
|
125272
126519
|
function readEvalCaseProposals(stateDir) {
|
|
125273
126520
|
const p = proposalsPath(stateDir);
|
|
125274
|
-
if (!
|
|
126521
|
+
if (!existsSync116(p))
|
|
125275
126522
|
return [];
|
|
125276
126523
|
let raw;
|
|
125277
126524
|
try {
|
|
@@ -125468,7 +126715,7 @@ function registerApply(parent) {
|
|
|
125468
126715
|
}));
|
|
125469
126716
|
process.exit(0);
|
|
125470
126717
|
}
|
|
125471
|
-
if (!
|
|
126718
|
+
if (!existsSync117(proposal.skill_dir)) {
|
|
125472
126719
|
fail7(`skill dir gone: ${proposal.skill_dir}`);
|
|
125473
126720
|
}
|
|
125474
126721
|
const { dir, resolvedDir } = authorizeApplyTarget(proposal.skill_dir, proposal.skill_slug);
|
|
@@ -125581,7 +126828,7 @@ function registerSelfImproveBenchCommand(program3) {
|
|
|
125581
126828
|
init_esm();
|
|
125582
126829
|
init_helpers();
|
|
125583
126830
|
var import_yaml26 = __toESM(require_dist(), 1);
|
|
125584
|
-
import { existsSync as
|
|
126831
|
+
import { existsSync as existsSync118, readdirSync as readdirSync44, readFileSync as readFileSync107, statSync as statSync62 } from "node:fs";
|
|
125585
126832
|
import { homedir as homedir63 } from "node:os";
|
|
125586
126833
|
import { join as join120, resolve as resolve63 } from "node:path";
|
|
125587
126834
|
var PERSONAL_PREFIX2 = "personal-";
|
|
@@ -125598,7 +126845,7 @@ function defaultBundledRoot2() {
|
|
|
125598
126845
|
}
|
|
125599
126846
|
function readSkillFrontmatter(skillDir) {
|
|
125600
126847
|
const mdPath = join120(skillDir, "SKILL.md");
|
|
125601
|
-
if (!
|
|
126848
|
+
if (!existsSync118(mdPath))
|
|
125602
126849
|
return null;
|
|
125603
126850
|
let content;
|
|
125604
126851
|
try {
|
|
@@ -125642,7 +126889,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
125642
126889
|
if (!AGENT_NAME_RE3.test(agent))
|
|
125643
126890
|
return [];
|
|
125644
126891
|
const skillsDir = join120(agentsRoot, agent, ".claude/skills");
|
|
125645
|
-
if (!
|
|
126892
|
+
if (!existsSync118(skillsDir))
|
|
125646
126893
|
return [];
|
|
125647
126894
|
const out = [];
|
|
125648
126895
|
let entries;
|
|
@@ -125680,7 +126927,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
125680
126927
|
return out;
|
|
125681
126928
|
}
|
|
125682
126929
|
function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
125683
|
-
if (!
|
|
126930
|
+
if (!existsSync118(sharedRoot))
|
|
125684
126931
|
return [];
|
|
125685
126932
|
const out = [];
|
|
125686
126933
|
let entries;
|
|
@@ -125718,7 +126965,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
|
125718
126965
|
return out;
|
|
125719
126966
|
}
|
|
125720
126967
|
function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
125721
|
-
if (!
|
|
126968
|
+
if (!existsSync118(bundledRoot))
|
|
125722
126969
|
return [];
|
|
125723
126970
|
const out = [];
|
|
125724
126971
|
let entries;
|
|
@@ -125877,7 +127124,7 @@ init_helpers();
|
|
|
125877
127124
|
init_docker_socket();
|
|
125878
127125
|
init_operator_uid();
|
|
125879
127126
|
import {
|
|
125880
|
-
existsSync as
|
|
127127
|
+
existsSync as existsSync120,
|
|
125881
127128
|
mkdirSync as mkdirSync68,
|
|
125882
127129
|
readdirSync as readdirSync45,
|
|
125883
127130
|
writeFileSync as writeFileSync49,
|
|
@@ -126260,7 +127507,7 @@ function resolveHostdSkillsTarget(hostHome) {
|
|
|
126260
127507
|
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.`);
|
|
126261
127508
|
return;
|
|
126262
127509
|
}
|
|
126263
|
-
if (!
|
|
127510
|
+
if (!existsSync120(target)) {
|
|
126264
127511
|
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.`);
|
|
126265
127512
|
return;
|
|
126266
127513
|
}
|
|
@@ -126274,7 +127521,7 @@ function hostdComposePath() {
|
|
|
126274
127521
|
}
|
|
126275
127522
|
function backupExistingCompose() {
|
|
126276
127523
|
const p = hostdComposePath();
|
|
126277
|
-
if (!
|
|
127524
|
+
if (!existsSync120(p))
|
|
126278
127525
|
return null;
|
|
126279
127526
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
126280
127527
|
const bak = `${p}.bak-${ts}`;
|
|
@@ -126373,7 +127620,7 @@ function doStatus() {
|
|
|
126373
127620
|
const composeYml = hostdComposePath();
|
|
126374
127621
|
console.log(source_default.bold("switchroom-hostd"));
|
|
126375
127622
|
console.log("");
|
|
126376
|
-
if (!
|
|
127623
|
+
if (!existsSync120(composeYml)) {
|
|
126377
127624
|
console.log(source_default.yellow(" compose: not installed"));
|
|
126378
127625
|
console.log(source_default.dim(" run `switchroom hostd install` to set up."));
|
|
126379
127626
|
return;
|
|
@@ -126394,14 +127641,14 @@ function doStatus() {
|
|
|
126394
127641
|
} else {
|
|
126395
127642
|
console.log(source_default.green(` container: ${ps.stdout.trim()}`));
|
|
126396
127643
|
}
|
|
126397
|
-
if (
|
|
127644
|
+
if (existsSync120(dir)) {
|
|
126398
127645
|
const entries = [];
|
|
126399
127646
|
try {
|
|
126400
127647
|
for (const name of readdirSync45(dir)) {
|
|
126401
127648
|
if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
|
|
126402
127649
|
continue;
|
|
126403
127650
|
const sockPath = join121(dir, name, "sock");
|
|
126404
|
-
if (
|
|
127651
|
+
if (existsSync120(sockPath)) {
|
|
126405
127652
|
const st = statSync63(sockPath);
|
|
126406
127653
|
if ((st.mode & 61440) === 49152) {
|
|
126407
127654
|
entries.push(`${name} \u2192 ${sockPath}`);
|
|
@@ -126420,7 +127667,7 @@ function doStatus() {
|
|
|
126420
127667
|
}
|
|
126421
127668
|
function doUninstall() {
|
|
126422
127669
|
const composeYml = hostdComposePath();
|
|
126423
|
-
if (!
|
|
127670
|
+
if (!existsSync120(composeYml)) {
|
|
126424
127671
|
console.log(source_default.yellow(" No hostd install detected (no compose file at this path)."));
|
|
126425
127672
|
return;
|
|
126426
127673
|
}
|
|
@@ -126444,7 +127691,7 @@ function registerHostdCommand(program3) {
|
|
|
126444
127691
|
hostd.command("uninstall").description("Stop the hostd container. Leaves the compose file in place for re-install.").action(() => doUninstall());
|
|
126445
127692
|
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) => {
|
|
126446
127693
|
const logPath = opts.path ?? defaultAuditLogPath2();
|
|
126447
|
-
if (!
|
|
127694
|
+
if (!existsSync120(logPath)) {
|
|
126448
127695
|
console.error(source_default.yellow(`Audit log not found at ${logPath}.`) + source_default.gray(`
|
|
126449
127696
|
The log is created when hostd handles its first privileged-verb request.`));
|
|
126450
127697
|
return;
|
|
@@ -126493,7 +127740,7 @@ init_source();
|
|
|
126493
127740
|
init_helpers();
|
|
126494
127741
|
init_operator_uid();
|
|
126495
127742
|
init_compose();
|
|
126496
|
-
import { chownSync as chownSync13, existsSync as
|
|
127743
|
+
import { chownSync as chownSync13, existsSync as existsSync121, mkdirSync as mkdirSync69, writeFileSync as writeFileSync50, copyFileSync as copyFileSync17 } from "node:fs";
|
|
126497
127744
|
import { homedir as homedir65 } from "node:os";
|
|
126498
127745
|
import { join as join122 } from "node:path";
|
|
126499
127746
|
import { spawnSync as spawnSync30 } from "node:child_process";
|
|
@@ -126604,7 +127851,7 @@ function webdComposePath() {
|
|
|
126604
127851
|
}
|
|
126605
127852
|
function backupExistingCompose2() {
|
|
126606
127853
|
const p = webdComposePath();
|
|
126607
|
-
if (!
|
|
127854
|
+
if (!existsSync121(p))
|
|
126608
127855
|
return null;
|
|
126609
127856
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
126610
127857
|
const bak = `${p}.bak-${ts}`;
|
|
@@ -126712,7 +127959,7 @@ function doStatus2() {
|
|
|
126712
127959
|
const composeYml = webdComposePath();
|
|
126713
127960
|
console.log(source_default.bold("switchroom-web"));
|
|
126714
127961
|
console.log("");
|
|
126715
|
-
if (!
|
|
127962
|
+
if (!existsSync121(composeYml)) {
|
|
126716
127963
|
console.log(source_default.yellow(" compose: not installed"));
|
|
126717
127964
|
console.log(source_default.dim(" run `switchroom webd install` to set up."));
|
|
126718
127965
|
return;
|
|
@@ -126736,7 +127983,7 @@ function doStatus2() {
|
|
|
126736
127983
|
}
|
|
126737
127984
|
function doUninstall2() {
|
|
126738
127985
|
const composeYml = webdComposePath();
|
|
126739
|
-
if (!
|
|
127986
|
+
if (!existsSync121(composeYml)) {
|
|
126740
127987
|
console.log(source_default.yellow(" No web-service install detected (no compose file at this path)."));
|
|
126741
127988
|
return;
|
|
126742
127989
|
}
|
|
@@ -127222,9 +128469,9 @@ function printDeepDiveBrief(targets) {
|
|
|
127222
128469
|
|
|
127223
128470
|
// src/cli/hindsight-watch.ts
|
|
127224
128471
|
init_source();
|
|
127225
|
-
import { existsSync as
|
|
128472
|
+
import { existsSync as existsSync126, readdirSync as readdirSync50 } from "node:fs";
|
|
127226
128473
|
import { homedir as homedir69 } from "node:os";
|
|
127227
|
-
import { join as
|
|
128474
|
+
import { join as join126, resolve as resolve66 } from "node:path";
|
|
127228
128475
|
|
|
127229
128476
|
// src/host-control/config-degraded.ts
|
|
127230
128477
|
import { connect as connect3 } from "node:net";
|
|
@@ -127421,16 +128668,16 @@ function readLlmSignals(series) {
|
|
|
127421
128668
|
}
|
|
127422
128669
|
|
|
127423
128670
|
// src/hindsight-watch/recall-log.ts
|
|
127424
|
-
import { closeSync as closeSync24, existsSync as
|
|
127425
|
-
import { join as
|
|
128671
|
+
import { closeSync as closeSync24, existsSync as existsSync125, openSync as openSync24, readdirSync as readdirSync48, readSync as readSync6, statSync as statSync64 } from "node:fs";
|
|
128672
|
+
import { join as join125 } from "node:path";
|
|
127426
128673
|
var RECALL_WINDOW_ROWS = 200;
|
|
127427
128674
|
var RECALL_MAX_TAIL_BYTES = 1024 * 1024;
|
|
127428
128675
|
var RECALL_MAX_ROW_AGE_MS = 24 * 60 * 60 * 1000;
|
|
127429
128676
|
function recallLogPath2(agentDir) {
|
|
127430
|
-
return
|
|
128677
|
+
return join125(agentDir, ".claude", "plugins", "data", "hindsight-memory-inline", "state", "recall_log.jsonl");
|
|
127431
128678
|
}
|
|
127432
128679
|
function readRecallLogTail2(path10, windowRows = RECALL_WINDOW_ROWS) {
|
|
127433
|
-
if (!
|
|
128680
|
+
if (!existsSync125(path10))
|
|
127434
128681
|
return [];
|
|
127435
128682
|
let text;
|
|
127436
128683
|
let truncatedHead = false;
|
|
@@ -127575,7 +128822,7 @@ function probeRecallLogs(agentsDir, now2, windowRows = RECALL_WINDOW_ROWS, maxAg
|
|
|
127575
128822
|
const all = [];
|
|
127576
128823
|
let agents = 0;
|
|
127577
128824
|
for (const name of names) {
|
|
127578
|
-
const rows = withinWindow(readRecallLogTail2(recallLogPath2(
|
|
128825
|
+
const rows = withinWindow(readRecallLogTail2(recallLogPath2(join125(agentsDir, name)), windowRows), now2, maxAgeMs);
|
|
127579
128826
|
if (rows.length > 0)
|
|
127580
128827
|
agents++;
|
|
127581
128828
|
all.push(...rows);
|
|
@@ -128633,7 +129880,7 @@ function registerHindsightWatchCommand(program3) {
|
|
|
128633
129880
|
process.exitCode = runInstallCron(opts.cronUser);
|
|
128634
129881
|
return;
|
|
128635
129882
|
}
|
|
128636
|
-
const agentsDir = opts.agentsDir ?? process.env.SWITCHROOM_AGENTS_DIR ??
|
|
129883
|
+
const agentsDir = opts.agentsDir ?? process.env.SWITCHROOM_AGENTS_DIR ?? join126(process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir69(), ".switchroom", "agents");
|
|
128637
129884
|
const statePath = opts.state ?? defaultStatePath2();
|
|
128638
129885
|
const log = (m) => {
|
|
128639
129886
|
process.stderr.write(`${m}
|
|
@@ -128749,7 +129996,7 @@ function collectRelayCandidates(agentsDir) {
|
|
|
128749
129996
|
const candidates = [];
|
|
128750
129997
|
for (const name of readdirSync50(agentsDir).sort()) {
|
|
128751
129998
|
const sock = resolve66(agentsDir, name, "telegram", "gateway.sock");
|
|
128752
|
-
if (
|
|
129999
|
+
if (existsSync126(sock))
|
|
128753
130000
|
candidates.push({ agent: name, sock });
|
|
128754
130001
|
}
|
|
128755
130002
|
return orderRelayCandidates(candidates);
|
|
@@ -128772,7 +130019,7 @@ async function notifyOperator(agentsDir, text, log) {
|
|
|
128772
130019
|
// src/cli/hindsight-bench.ts
|
|
128773
130020
|
init_source();
|
|
128774
130021
|
import { mkdirSync as mkdirSync71, readFileSync as readFileSync113, writeFileSync as writeFileSync52 } from "node:fs";
|
|
128775
|
-
import { dirname as
|
|
130022
|
+
import { dirname as dirname52, resolve as resolve67 } from "node:path";
|
|
128776
130023
|
|
|
128777
130024
|
// src/hindsight-bench/anonymise.ts
|
|
128778
130025
|
function pseudonym(n, width) {
|
|
@@ -129900,7 +131147,7 @@ function readResult(path10) {
|
|
|
129900
131147
|
}
|
|
129901
131148
|
function writeOut(path10, body) {
|
|
129902
131149
|
const abs = resolve67(path10);
|
|
129903
|
-
mkdirSync71(
|
|
131150
|
+
mkdirSync71(dirname52(abs), { recursive: true });
|
|
129904
131151
|
writeFileSync52(abs, body);
|
|
129905
131152
|
process.stderr.write(`${source_default.green("\u2713")} wrote ${abs}
|
|
129906
131153
|
`);
|
|
@@ -130105,13 +131352,13 @@ async function runMeasureMode(opts) {
|
|
|
130105
131352
|
|
|
130106
131353
|
// src/cli/openrouter-watch.ts
|
|
130107
131354
|
init_source();
|
|
130108
|
-
import { existsSync as
|
|
131355
|
+
import { existsSync as existsSync128, readdirSync as readdirSync51 } from "node:fs";
|
|
130109
131356
|
import { homedir as homedir71 } from "node:os";
|
|
130110
|
-
import { join as
|
|
131357
|
+
import { join as join127, resolve as resolve69 } from "node:path";
|
|
130111
131358
|
|
|
130112
131359
|
// src/openrouter/install-cron.ts
|
|
130113
|
-
import { existsSync as
|
|
130114
|
-
import { dirname as
|
|
131360
|
+
import { existsSync as existsSync127, mkdirSync as mkdirSync72, readFileSync as readFileSync114, renameSync as renameSync33, writeFileSync as writeFileSync53 } from "node:fs";
|
|
131361
|
+
import { dirname as dirname53 } from "node:path";
|
|
130115
131362
|
var CRON_PATH3 = "/etc/cron.d/openrouter-watch";
|
|
130116
131363
|
var CRON_SCHEDULE2 = "17 * * * *";
|
|
130117
131364
|
var CRON_LOG_PATH3 = "/var/log/openrouter-watch.log";
|
|
@@ -130128,14 +131375,14 @@ function renderCron3(opts) {
|
|
|
130128
131375
|
function installCron3(opts) {
|
|
130129
131376
|
const path10 = opts.path ?? CRON_PATH3;
|
|
130130
131377
|
const content = renderCron3(opts);
|
|
130131
|
-
if (
|
|
131378
|
+
if (existsSync127(path10)) {
|
|
130132
131379
|
try {
|
|
130133
131380
|
if (readFileSync114(path10, "utf8") === content) {
|
|
130134
131381
|
return { status: "unchanged", path: path10, content };
|
|
130135
131382
|
}
|
|
130136
131383
|
} catch {}
|
|
130137
131384
|
}
|
|
130138
|
-
mkdirSync72(
|
|
131385
|
+
mkdirSync72(dirname53(path10), { recursive: true });
|
|
130139
131386
|
const tmp = `${path10}.${process.pid}.tmp`;
|
|
130140
131387
|
writeFileSync53(tmp, content, { mode: 420 });
|
|
130141
131388
|
renameSync33(tmp, path10);
|
|
@@ -130144,7 +131391,7 @@ function installCron3(opts) {
|
|
|
130144
131391
|
// src/openrouter/state.ts
|
|
130145
131392
|
import { mkdirSync as mkdirSync73, readFileSync as readFileSync115, renameSync as renameSync34, writeFileSync as writeFileSync54 } from "node:fs";
|
|
130146
131393
|
import { homedir as homedir70 } from "node:os";
|
|
130147
|
-
import { dirname as
|
|
131394
|
+
import { dirname as dirname54, resolve as resolve68 } from "node:path";
|
|
130148
131395
|
function defaultStatePath3(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir70()) {
|
|
130149
131396
|
return resolve68(home2, ".switchroom", "openrouter-watch", "state.json");
|
|
130150
131397
|
}
|
|
@@ -130171,7 +131418,7 @@ function loadState2(path10) {
|
|
|
130171
131418
|
return out;
|
|
130172
131419
|
}
|
|
130173
131420
|
function saveState2(path10, state) {
|
|
130174
|
-
mkdirSync73(
|
|
131421
|
+
mkdirSync73(dirname54(path10), { recursive: true, mode: 448 });
|
|
130175
131422
|
const tmp = `${path10}.${process.pid}.tmp`;
|
|
130176
131423
|
const payload = { v: 1, ...state };
|
|
130177
131424
|
writeFileSync54(tmp, JSON.stringify(payload, null, 2) + `
|
|
@@ -130262,7 +131509,7 @@ function registerOpenRouterWatchCommand(program3) {
|
|
|
130262
131509
|
process.exitCode = runInstallCron2(opts.cronUser);
|
|
130263
131510
|
return;
|
|
130264
131511
|
}
|
|
130265
|
-
const agentsDir = opts.agentsDir ?? process.env.SWITCHROOM_AGENTS_DIR ??
|
|
131512
|
+
const agentsDir = opts.agentsDir ?? process.env.SWITCHROOM_AGENTS_DIR ?? join127(process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir71(), ".switchroom", "agents");
|
|
130266
131513
|
const statePath = opts.state ?? defaultStatePath3();
|
|
130267
131514
|
const log = (m) => {
|
|
130268
131515
|
process.stderr.write(`${m}
|
|
@@ -130361,7 +131608,7 @@ async function notifyOperator2(agentsDir, text, log) {
|
|
|
130361
131608
|
try {
|
|
130362
131609
|
for (const name of readdirSync51(agentsDir).sort()) {
|
|
130363
131610
|
const sock = resolve69(agentsDir, name, "telegram", "gateway.sock");
|
|
130364
|
-
if (
|
|
131611
|
+
if (existsSync128(sock))
|
|
130365
131612
|
candidates.push({ agent: name, sock });
|
|
130366
131613
|
}
|
|
130367
131614
|
} catch (e) {
|
|
@@ -130382,7 +131629,7 @@ init_sync();
|
|
|
130382
131629
|
init_install_cron2();
|
|
130383
131630
|
init_flock();
|
|
130384
131631
|
import { homedir as homedir72 } from "node:os";
|
|
130385
|
-
import { join as
|
|
131632
|
+
import { join as join128 } from "node:path";
|
|
130386
131633
|
function switchroomHome() {
|
|
130387
131634
|
return process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir72();
|
|
130388
131635
|
}
|
|
@@ -130394,9 +131641,9 @@ function resolveSyncOptions(configPath, opts) {
|
|
|
130394
131641
|
cfg = loaded.config_repo;
|
|
130395
131642
|
} catch {}
|
|
130396
131643
|
const home2 = switchroomHome();
|
|
130397
|
-
const repoPath = opts.path ? resolvePath(opts.path) : cfg.path ? resolvePath(cfg.path) :
|
|
130398
|
-
const livePath = process.env.SWITCHROOM_CONFIG ??
|
|
130399
|
-
const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ??
|
|
131644
|
+
const repoPath = opts.path ? resolvePath(opts.path) : cfg.path ? resolvePath(cfg.path) : join128(home2, ".switchroom-config");
|
|
131645
|
+
const livePath = process.env.SWITCHROOM_CONFIG ?? join128(home2, ".switchroom", "switchroom.yaml");
|
|
131646
|
+
const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ?? join128(home2, ".switchroom", "agents");
|
|
130400
131647
|
const push = opts.push === false ? false : cfg.push ?? true;
|
|
130401
131648
|
const remote = opts.remote ?? cfg.remote ?? "origin";
|
|
130402
131649
|
const requirePrivate = opts.requirePrivate ?? cfg.require_private ?? true;
|