switchroom 0.20.18 → 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 +6 -0
- package/dist/auth-broker/index.js +17 -1
- package/dist/cli/notion-write-pretool.mjs +6 -0
- package/dist/cli/switchroom.js +1647 -430
- package/dist/host-control/main.js +18 -2
- package/dist/vault/approvals/kernel-server.js +17 -1
- package/dist/vault/broker/server.js +17 -1
- package/package.json +1 -1
- package/telegram-plugin/dist/gateway/gateway.js +26 -9
- package/telegram-plugin/telegraph.ts +23 -5
- package/telegram-plugin/tests/telegraph.test.ts +34 -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,6 +14046,12 @@ 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
|
+
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."),
|
|
14049
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({
|
|
@@ -16200,7 +16206,7 @@ function hindsightPerfEnv(caps, overrides = new Map) {
|
|
|
16200
16206
|
function findUnmanagedHindsightEnvKeys(configEnv) {
|
|
16201
16207
|
return Object.keys(configEnv ?? {}).filter((key) => !HINDSIGHT_PERF_ENV_KEYS.has(key) && !HINDSIGHT_PG_ENV_KEYS.has(key)).sort();
|
|
16202
16208
|
}
|
|
16203
|
-
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_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;
|
|
16204
16210
|
var init_hindsight_perf_defaults = __esm(() => {
|
|
16205
16211
|
init_hindsight_pg_defaults();
|
|
16206
16212
|
HINDSIGHT_DEFAULT_RECALL_MAX_CANDIDATES_PER_SOURCE = Math.ceil(HINDSIGHT_RERANKER_MAX_CANDIDATES_FOR_DERIVATION * 0.4);
|
|
@@ -16343,6 +16349,15 @@ var init_hindsight_perf_defaults = __esm(() => {
|
|
|
16343
16349
|
`HINDSIGHT_API_WORKER_${type.toUpperCase()}_MAX_SLOTS`,
|
|
16344
16350
|
`HINDSIGHT_API_WORKER_${type.toUpperCase()}_RESERVED_SLOTS`
|
|
16345
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
|
+
];
|
|
16346
16361
|
HINDSIGHT_PERF_ENV_KEYS = new Set([
|
|
16347
16362
|
...[
|
|
16348
16363
|
...HINDSIGHT_PERF_DEFAULTS_UNGATED,
|
|
@@ -16351,7 +16366,8 @@ var init_hindsight_perf_defaults = __esm(() => {
|
|
|
16351
16366
|
].map(([k]) => k),
|
|
16352
16367
|
...HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS,
|
|
16353
16368
|
...HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES.keys(),
|
|
16354
|
-
...HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES.values()
|
|
16369
|
+
...HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES.values(),
|
|
16370
|
+
...HINDSIGHT_RECALL_POOL_ENV_KEYS
|
|
16355
16371
|
]);
|
|
16356
16372
|
});
|
|
16357
16373
|
|
|
@@ -16521,7 +16537,7 @@ function resolveCheckedHindsightContextBudget(llm) {
|
|
|
16521
16537
|
assertHindsightContextBudgetFits(budget);
|
|
16522
16538
|
return budget;
|
|
16523
16539
|
}
|
|
16524
|
-
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;
|
|
16525
16541
|
var init_hindsight_context_budget = __esm(() => {
|
|
16526
16542
|
HINDSIGHT_RETAIN_MAX_COMPLETION_FLOOR = HINDSIGHT_UPSTREAM_RETAIN_CHUNK_SIZE + 72;
|
|
16527
16543
|
HindsightContextBudgetError = class HindsightContextBudgetError extends Error {
|
|
@@ -18727,20 +18743,16 @@ function probeDockerAvailability(probe = defaultDockerProbe) {
|
|
|
18727
18743
|
function isDockerAvailable(probe = defaultDockerProbe) {
|
|
18728
18744
|
return probeDockerAvailability(probe) === "ok";
|
|
18729
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
|
+
}
|
|
18730
18751
|
function isHindsightRunning(probe = defaultDockerProbe) {
|
|
18731
|
-
|
|
18732
|
-
return (out ?? "").trim().length > 0;
|
|
18752
|
+
return dockerNameMatchesExactly(probe, ["ps", "--filter", "name=switchroom-hindsight", "--format", "{{.Names}}"], HINDSIGHT_CONTAINER_NAME);
|
|
18733
18753
|
}
|
|
18734
18754
|
function isHindsightContainerExists(probe = defaultDockerProbe) {
|
|
18735
|
-
|
|
18736
|
-
"ps",
|
|
18737
|
-
"-a",
|
|
18738
|
-
"--filter",
|
|
18739
|
-
"name=switchroom-hindsight",
|
|
18740
|
-
"--format",
|
|
18741
|
-
"{{.Names}}"
|
|
18742
|
-
]);
|
|
18743
|
-
return (out ?? "").trim().length > 0;
|
|
18755
|
+
return dockerNameMatchesExactly(probe, ["ps", "-a", "--filter", "name=switchroom-hindsight", "--format", "{{.Names}}"], HINDSIGHT_CONTAINER_NAME);
|
|
18744
18756
|
}
|
|
18745
18757
|
function resolveHindsightPerOpLlm(llm) {
|
|
18746
18758
|
const out = [];
|
|
@@ -19273,11 +19285,28 @@ function hindsightResolvedCapabilities(llm, litellm, gpu, perf) {
|
|
|
19273
19285
|
localLlm: hindsightLocalLlmEnabled(llm, litellm, perf?.localLlm)
|
|
19274
19286
|
};
|
|
19275
19287
|
}
|
|
19276
|
-
function getHindsightStatus() {
|
|
19288
|
+
function getHindsightStatus(exec = (cmd, args) => execFileSync2(cmd, args, { stdio: "pipe", encoding: "utf-8" })) {
|
|
19277
19289
|
try {
|
|
19278
|
-
const output =
|
|
19279
|
-
|
|
19280
|
-
|
|
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;
|
|
19281
19310
|
} catch {
|
|
19282
19311
|
return null;
|
|
19283
19312
|
}
|
|
@@ -19439,7 +19468,7 @@ async function ensureHindsightConsumer(configPath, account, uid = HINDSIGHT_DEFA
|
|
|
19439
19468
|
atomicWriteFileSync3(configPath, tail, mode);
|
|
19440
19469
|
return { added: true, reason: "added" };
|
|
19441
19470
|
}
|
|
19442
|
-
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) => {
|
|
19443
19472
|
try {
|
|
19444
19473
|
return execFileSync2("docker", args, {
|
|
19445
19474
|
stdio: "pipe",
|
|
@@ -32805,6 +32834,9 @@ var init_scaffold = __esm(() => {
|
|
|
32805
32834
|
"mcp__hindsight__delete_document",
|
|
32806
32835
|
"mcp__hindsight__get_mental_model",
|
|
32807
32836
|
"mcp__hindsight__list_mental_models",
|
|
32837
|
+
"mcp__hindsight__search_knowledge_pages",
|
|
32838
|
+
"mcp__hindsight__get_knowledge_page",
|
|
32839
|
+
"mcp__hindsight__get_knowledge_tree",
|
|
32808
32840
|
"mcp__hindsight__list_tags",
|
|
32809
32841
|
"mcp__hindsight__get_operation",
|
|
32810
32842
|
"mcp__hindsight__list_operations",
|
|
@@ -47618,7 +47650,7 @@ function buildRepairBankArgv(opts) {
|
|
|
47618
47650
|
const script = `if command -v hindsight-admin >/dev/null 2>&1; then exec hindsight-admin "$@"; fi; ` + `exec ${HINDSIGHT_ADMIN_BIN} "$@"`;
|
|
47619
47651
|
return [
|
|
47620
47652
|
"exec",
|
|
47621
|
-
opts.container ??
|
|
47653
|
+
opts.container ?? HINDSIGHT_CONTAINER_NAME2,
|
|
47622
47654
|
"sh",
|
|
47623
47655
|
"-c",
|
|
47624
47656
|
script,
|
|
@@ -47731,7 +47763,7 @@ function classifyRepairOutcome(run) {
|
|
|
47731
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.`
|
|
47732
47764
|
};
|
|
47733
47765
|
}
|
|
47734
|
-
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;
|
|
47735
47767
|
var init_hindsight_repair = __esm(() => {
|
|
47736
47768
|
init_hindsight_tools();
|
|
47737
47769
|
});
|
|
@@ -83069,7 +83101,7 @@ __export(exports_server2, {
|
|
|
83069
83101
|
TOOLS: () => TOOLS2
|
|
83070
83102
|
});
|
|
83071
83103
|
import { randomBytes as randomBytes18 } from "node:crypto";
|
|
83072
|
-
import { existsSync as
|
|
83104
|
+
import { existsSync as existsSync119 } from "node:fs";
|
|
83073
83105
|
function selfSocketPath() {
|
|
83074
83106
|
return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
|
|
83075
83107
|
}
|
|
@@ -83096,7 +83128,7 @@ async function dispatchTool2(name, args) {
|
|
|
83096
83128
|
return errorText2("hostd MCP: SWITCHROOM_AGENT_NAME env var is not set \u2014 cannot " + "determine which per-agent socket to talk to.");
|
|
83097
83129
|
}
|
|
83098
83130
|
const sockPath = selfSocketPath();
|
|
83099
|
-
if (!
|
|
83131
|
+
if (!existsSync119(sockPath)) {
|
|
83100
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.`);
|
|
83101
83133
|
}
|
|
83102
83134
|
let req;
|
|
@@ -83347,13 +83379,13 @@ function resolveAuditLogPath() {
|
|
|
83347
83379
|
if (process.env.HOSTD_AUDIT_LOG_PATH)
|
|
83348
83380
|
return process.env.HOSTD_AUDIT_LOG_PATH;
|
|
83349
83381
|
const bindMounted = "/host-home/.switchroom/host-control-audit.log";
|
|
83350
|
-
if (
|
|
83382
|
+
if (existsSync119(bindMounted))
|
|
83351
83383
|
return bindMounted;
|
|
83352
83384
|
return defaultAuditLogPath2();
|
|
83353
83385
|
}
|
|
83354
83386
|
function getLastUpdateApplyStatus() {
|
|
83355
83387
|
const path10 = resolveAuditLogPath();
|
|
83356
|
-
if (!
|
|
83388
|
+
if (!existsSync119(path10)) {
|
|
83357
83389
|
return errorText2(`get_status: audit log not found at ${path10}. No update_apply has run yet?`);
|
|
83358
83390
|
}
|
|
83359
83391
|
let raw;
|
|
@@ -83907,6 +83939,18 @@ var init_mapping = __esm(() => {
|
|
|
83907
83939
|
job_spec: "keep-my-subscription-honest",
|
|
83908
83940
|
signature: "litellm-header-passthrough:oauth-leak-scope"
|
|
83909
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
|
+
},
|
|
83910
83954
|
"litellm-timeout-budget-drift": {
|
|
83911
83955
|
failure_mode: "drift",
|
|
83912
83956
|
severity: 3,
|
|
@@ -84109,11 +84153,11 @@ var init_test_agents = __esm(() => {
|
|
|
84109
84153
|
});
|
|
84110
84154
|
|
|
84111
84155
|
// src/litellm/header-passthrough-guard.ts
|
|
84112
|
-
import { existsSync as
|
|
84156
|
+
import { existsSync as existsSync122, readdirSync as readdirSync46 } from "node:fs";
|
|
84113
84157
|
function discoverLiveLitellmConfigPath(opts) {
|
|
84114
84158
|
const servicesDir = opts?.servicesDir ?? COOLIFY_SERVICES_DIR;
|
|
84115
84159
|
const readdir2 = opts?.readdirFn ?? ((p) => readdirSync46(p));
|
|
84116
|
-
const exists = opts?.existsFn ??
|
|
84160
|
+
const exists = opts?.existsFn ?? existsSync122;
|
|
84117
84161
|
let entries;
|
|
84118
84162
|
try {
|
|
84119
84163
|
entries = readdir2(servicesDir);
|
|
@@ -84202,8 +84246,159 @@ var init_header_passthrough_guard = __esm(() => {
|
|
|
84202
84246
|
]);
|
|
84203
84247
|
});
|
|
84204
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
|
+
|
|
84205
84377
|
// src/fleet-health/litellm-config-sensor.ts
|
|
84206
|
-
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
|
+
}
|
|
84207
84402
|
function resolveLitellmConfigPath(explicit, discoverFn = () => discoverLiveLitellmConfigPath()) {
|
|
84208
84403
|
if (explicit)
|
|
84209
84404
|
return explicit;
|
|
@@ -84214,7 +84409,7 @@ function resolveLitellmConfigPath(explicit, discoverFn = () => discoverLiveLitel
|
|
|
84214
84409
|
}
|
|
84215
84410
|
function scanLitellmConfig(opts = {}) {
|
|
84216
84411
|
const path10 = resolveLitellmConfigPath(opts.path, opts.discoverFn);
|
|
84217
|
-
const exists = opts.existsFn ??
|
|
84412
|
+
const exists = opts.existsFn ?? existsSync123;
|
|
84218
84413
|
const read = opts.readFn ?? ((p) => readFileSync110(p, "utf-8"));
|
|
84219
84414
|
const log = opts.log ?? (() => {});
|
|
84220
84415
|
const nowIso = opts.nowIso ?? new Date().toISOString();
|
|
@@ -84266,6 +84461,52 @@ function scanLitellmConfig(opts = {}) {
|
|
|
84266
84461
|
ts: nowIso
|
|
84267
84462
|
});
|
|
84268
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
|
+
}
|
|
84269
84510
|
if (findings.length === 0)
|
|
84270
84511
|
return { status: "ok", path: path10, findings: [] };
|
|
84271
84512
|
for (const f of findings) {
|
|
@@ -84277,6 +84518,8 @@ var LITELLM_PROXY_PSEUDO_AGENT = "litellm-proxy";
|
|
|
84277
84518
|
var init_litellm_config_sensor = __esm(() => {
|
|
84278
84519
|
init_header_passthrough_guard();
|
|
84279
84520
|
init_timeout_budget();
|
|
84521
|
+
init_callback_mount_guard();
|
|
84522
|
+
init_passthrough_mount_guard();
|
|
84280
84523
|
});
|
|
84281
84524
|
|
|
84282
84525
|
// src/fleet-health/hindsight-gpu-drift-sensor.ts
|
|
@@ -84323,18 +84566,18 @@ __export(exports_scan, {
|
|
|
84323
84566
|
import {
|
|
84324
84567
|
readFileSync as readFileSync111,
|
|
84325
84568
|
readdirSync as readdirSync47,
|
|
84326
|
-
existsSync as
|
|
84569
|
+
existsSync as existsSync124,
|
|
84327
84570
|
mkdirSync as mkdirSync70,
|
|
84328
84571
|
writeFileSync as writeFileSync51
|
|
84329
84572
|
} from "node:fs";
|
|
84330
|
-
import { resolve as resolve64, dirname as
|
|
84573
|
+
import { resolve as resolve64, dirname as dirname51 } from "node:path";
|
|
84331
84574
|
import { homedir as homedir67 } from "node:os";
|
|
84332
84575
|
function resolveSwitchroomBase(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir67()) {
|
|
84333
84576
|
return resolve64(home2, ".switchroom");
|
|
84334
84577
|
}
|
|
84335
84578
|
function listAgents(base) {
|
|
84336
84579
|
const dir = resolve64(base, "agents");
|
|
84337
|
-
if (!
|
|
84580
|
+
if (!existsSync124(dir))
|
|
84338
84581
|
return [];
|
|
84339
84582
|
try {
|
|
84340
84583
|
return readdirSync47(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
|
|
@@ -84363,7 +84606,7 @@ function runScan(opts = {}) {
|
|
|
84363
84606
|
let gwText = "";
|
|
84364
84607
|
let sawArtifact = false;
|
|
84365
84608
|
try {
|
|
84366
|
-
if (
|
|
84609
|
+
if (existsSync124(turnsPath)) {
|
|
84367
84610
|
turnsText = readFileSync111(turnsPath, "utf-8");
|
|
84368
84611
|
sawArtifact = true;
|
|
84369
84612
|
}
|
|
@@ -84371,7 +84614,7 @@ function runScan(opts = {}) {
|
|
|
84371
84614
|
log(`fleet-health: WARN skipping ${agent} turns.jsonl unreadable: ${String(e)}`);
|
|
84372
84615
|
}
|
|
84373
84616
|
try {
|
|
84374
|
-
if (
|
|
84617
|
+
if (existsSync124(gwPath)) {
|
|
84375
84618
|
gwText = readFileSync111(gwPath, "utf-8");
|
|
84376
84619
|
sawArtifact = true;
|
|
84377
84620
|
}
|
|
@@ -84416,7 +84659,7 @@ function runScan(opts = {}) {
|
|
|
84416
84659
|
function readLedgerIfPresent(base) {
|
|
84417
84660
|
const path10 = ledgerPathForBase(base);
|
|
84418
84661
|
try {
|
|
84419
|
-
if (!
|
|
84662
|
+
if (!existsSync124(path10))
|
|
84420
84663
|
return null;
|
|
84421
84664
|
return JSON.parse(readFileSync111(path10, "utf-8"));
|
|
84422
84665
|
} catch {
|
|
@@ -84424,11 +84667,11 @@ function readLedgerIfPresent(base) {
|
|
|
84424
84667
|
}
|
|
84425
84668
|
}
|
|
84426
84669
|
function ledgerPathForBase(base) {
|
|
84427
|
-
return fleetHealthLedgerPath(
|
|
84670
|
+
return fleetHealthLedgerPath(dirname51(base));
|
|
84428
84671
|
}
|
|
84429
84672
|
function writeLedger(base, ledger) {
|
|
84430
84673
|
const path10 = ledgerPathForBase(base);
|
|
84431
|
-
mkdirSync70(
|
|
84674
|
+
mkdirSync70(dirname51(path10), { recursive: true });
|
|
84432
84675
|
writeFileSync51(path10, JSON.stringify(ledger, null, 2) + `
|
|
84433
84676
|
`, "utf-8");
|
|
84434
84677
|
return path10;
|
|
@@ -106739,6 +106982,323 @@ function redactMemoryWriteBody(body) {
|
|
|
106739
106982
|
init_helpers();
|
|
106740
106983
|
init_hindsight();
|
|
106741
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
|
+
|
|
106742
107302
|
// src/setup/hindsight-gpu-guard.ts
|
|
106743
107303
|
init_hindsight();
|
|
106744
107304
|
function assessHindsightGpuDrop(args) {
|
|
@@ -107116,6 +107676,7 @@ Cross-agent reflection plan
|
|
|
107116
107676
|
return;
|
|
107117
107677
|
}
|
|
107118
107678
|
console.log(source_default.gray(" Stopping switchroom-hindsight..."));
|
|
107679
|
+
stopHindsightRecallPool();
|
|
107119
107680
|
stopHindsight();
|
|
107120
107681
|
console.log(source_default.green(" Hindsight container stopped and removed."));
|
|
107121
107682
|
return;
|
|
@@ -107171,6 +107732,182 @@ Cross-agent reflection plan
|
|
|
107171
107732
|
}
|
|
107172
107733
|
const reusePorts = recreate ? getRunningHindsightPorts() : null;
|
|
107173
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
|
+
}
|
|
107174
107911
|
console.log(source_default.green(`
|
|
107175
107912
|
Hindsight container is already running (switchroom-hindsight).
|
|
107176
107913
|
`));
|
|
@@ -107253,31 +107990,24 @@ Cross-agent reflection plan
|
|
|
107253
107990
|
process.exit(1);
|
|
107254
107991
|
}
|
|
107255
107992
|
}
|
|
107993
|
+
stopHindsightRecallPool();
|
|
107256
107994
|
if (isHindsightContainerExists()) {
|
|
107257
107995
|
console.log(source_default.gray(" Removing existing switchroom-hindsight container..."));
|
|
107258
107996
|
stopHindsight();
|
|
107259
107997
|
}
|
|
107260
|
-
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;
|
|
107261
108006
|
if (reusePorts) {
|
|
107262
|
-
|
|
107263
|
-
if (reusePorts.apiPort !== HINDSIGHT_DEFAULT_API_PORT) {
|
|
107264
|
-
let explicitUrl;
|
|
107265
|
-
try {
|
|
107266
|
-
explicitUrl = getConfig(program3).memory?.config?.url;
|
|
107267
|
-
} catch {
|
|
107268
|
-
explicitUrl = undefined;
|
|
107269
|
-
}
|
|
107270
|
-
if (!explicitUrl) {
|
|
107271
|
-
console.log(source_default.yellow(`
|
|
107272
|
-
\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.`));
|
|
107273
|
-
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}.`));
|
|
107274
|
-
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}.
|
|
107275
|
-
`));
|
|
107276
|
-
}
|
|
107277
|
-
}
|
|
108007
|
+
uiPort = reusePorts.uiPort;
|
|
107278
108008
|
} else {
|
|
107279
108009
|
try {
|
|
107280
|
-
|
|
108010
|
+
uiPort = (await pickHindsightPorts()).uiPort;
|
|
107281
108011
|
} catch (err) {
|
|
107282
108012
|
console.error(source_default.red(`
|
|
107283
108013
|
${err.message}
|
|
@@ -107285,31 +108015,39 @@ Cross-agent reflection plan
|
|
|
107285
108015
|
process.exit(1);
|
|
107286
108016
|
}
|
|
107287
108017
|
}
|
|
107288
|
-
|
|
107289
|
-
console.log(source_default.yellow(` Port ${HINDSIGHT_DEFAULT_API_PORT} is already in use; ` + `using ${ports.apiPort}/${ports.uiPort} instead.`));
|
|
107290
|
-
}
|
|
107291
|
-
let conflict = await preflightHindsightPorts(ports);
|
|
107292
|
-
if (conflict) {
|
|
107293
|
-
const heldBy = conflict.holder ? ` (held by ${conflict.holder})` : "";
|
|
107294
|
-
console.log(source_default.yellow(`
|
|
107295
|
-
Chosen Hindsight port ${conflict.port} is occupied${heldBy}; ` + `selecting a free port instead of crash-looping.`));
|
|
108018
|
+
const configUrlPort = (() => {
|
|
107296
108019
|
try {
|
|
107297
|
-
|
|
107298
|
-
|
|
107299
|
-
|
|
107300
|
-
|
|
107301
|
-
`));
|
|
107302
|
-
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;
|
|
107303
108024
|
}
|
|
107304
|
-
|
|
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 });
|
|
107305
108044
|
if (conflict) {
|
|
107306
|
-
const
|
|
108045
|
+
const heldBy = conflict.holder ? ` (held by ${conflict.holder})` : "";
|
|
107307
108046
|
console.error(source_default.red(`
|
|
107308
|
-
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\`.
|
|
107309
108048
|
`));
|
|
107310
108049
|
process.exit(1);
|
|
107311
108050
|
}
|
|
107312
|
-
console.log(source_default.green(` Reassigned Hindsight to port ${ports.apiPort}/${ports.uiPort}.`));
|
|
107313
108051
|
}
|
|
107314
108052
|
if (opts.provider && opts.provider !== "claude-code") {
|
|
107315
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."));
|
|
@@ -107329,23 +108067,83 @@ Cross-agent reflection plan
|
|
|
107329
108067
|
for (const drop of await diffDroppedHindsightLlmVaultKeys(hindsightConfig.hindsight?.llm, resolvedLlm)) {
|
|
107330
108068
|
console.log(source_default.yellow(` ! ${hindsightLlmDroppedKeyWarning(drop)}`));
|
|
107331
108069
|
}
|
|
107332
|
-
|
|
107333
|
-
|
|
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,
|
|
107334
108078
|
memLimit: hindsightConfig.hindsight?.mem_limit
|
|
107335
108079
|
}, cpAccessKey);
|
|
107336
108080
|
if (litellmCfg) {
|
|
107337
108081
|
console.log(source_default.gray(" LiteLLM routing enabled for hindsight (--network host)."));
|
|
107338
108082
|
}
|
|
107339
|
-
|
|
107340
|
-
|
|
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\`.
|
|
107341
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}.
|
|
108138
|
+
`));
|
|
108139
|
+
}
|
|
107342
108140
|
} catch (err) {
|
|
107343
108141
|
console.error(source_default.red(`
|
|
107344
108142
|
Failed to start Hindsight: ${err.message}
|
|
107345
108143
|
`));
|
|
107346
108144
|
process.exit(1);
|
|
107347
108145
|
}
|
|
107348
|
-
const url = `http://127.0.0.1:${
|
|
108146
|
+
const url = `http://127.0.0.1:${publicApiPort}/mcp/`;
|
|
107349
108147
|
const configPath = getConfigPath(program3);
|
|
107350
108148
|
try {
|
|
107351
108149
|
if (persistMemoryConfigUrl(configPath, provider, url)) {
|
|
@@ -107401,7 +108199,7 @@ Cross-agent reflection plan
|
|
|
107401
108199
|
}
|
|
107402
108200
|
console.log();
|
|
107403
108201
|
}));
|
|
107404
|
-
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) => {
|
|
107405
108203
|
const config = getConfig(program3);
|
|
107406
108204
|
if (opts.agent && opts.bank) {
|
|
107407
108205
|
console.error(source_default.red("Pass either --agent or --bank, not both."));
|
|
@@ -112043,8 +112841,8 @@ init_embedded_examples();
|
|
|
112043
112841
|
init_atomic();
|
|
112044
112842
|
init_loader();
|
|
112045
112843
|
init_scaffold();
|
|
112046
|
-
import { existsSync as
|
|
112047
|
-
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";
|
|
112048
112846
|
import { resolve as resolve46, dirname as dirname36 } from "node:path";
|
|
112049
112847
|
init_state();
|
|
112050
112848
|
init_vault();
|
|
@@ -112052,7 +112850,6 @@ init_manager();
|
|
|
112052
112850
|
init_onboarding();
|
|
112053
112851
|
init_gpu_detect();
|
|
112054
112852
|
init_host_capabilities();
|
|
112055
|
-
init_hindsight();
|
|
112056
112853
|
init_posthog();
|
|
112057
112854
|
|
|
112058
112855
|
// src/cli/setup-posture-rewrite.ts
|
|
@@ -112443,6 +113240,9 @@ class SetupVerificationError extends Error {
|
|
|
112443
113240
|
init_lifecycle();
|
|
112444
113241
|
init_timezone();
|
|
112445
113242
|
init_schema();
|
|
113243
|
+
|
|
113244
|
+
// src/cli/setup-step-ui.ts
|
|
113245
|
+
init_source();
|
|
112446
113246
|
var STEP_PENDING = source_default.gray("\u25cb");
|
|
112447
113247
|
var STEP_ACTIVE = source_default.blue("->");
|
|
112448
113248
|
var STEP_DONE = source_default.green("OK");
|
|
@@ -112453,6 +113253,315 @@ function stepHeaderTo(log, num3, title, status) {
|
|
|
112453
113253
|
log(`
|
|
112454
113254
|
${status} ${source_default.bold(`Step ${num3}:`)} ${title}`);
|
|
112455
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
|
|
112456
113565
|
function writeSwitchroomYaml(configPath, text) {
|
|
112457
113566
|
let mode = 420;
|
|
112458
113567
|
try {
|
|
@@ -112555,7 +113664,7 @@ async function stepConfigFile(configPath, nonInteractive) {
|
|
|
112555
113664
|
existingConfig = null;
|
|
112556
113665
|
}
|
|
112557
113666
|
}
|
|
112558
|
-
if (existingConfig &&
|
|
113667
|
+
if (existingConfig && existsSync86(existingConfig)) {
|
|
112559
113668
|
if (!nonInteractive) {
|
|
112560
113669
|
const useExisting = await askYesNo(` Found ${source_default.cyan(existingConfig)}. Use it?`, true);
|
|
112561
113670
|
if (!useExisting) {
|
|
@@ -112701,7 +113810,7 @@ async function resolveOrPromptToken(rawToken, label, config, nonInteractive) {
|
|
|
112701
113810
|
try {
|
|
112702
113811
|
const { openVault: openVault2 } = await Promise.resolve().then(() => (init_vault(), exports_vault));
|
|
112703
113812
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
112704
|
-
if (
|
|
113813
|
+
if (existsSync86(vaultPath)) {
|
|
112705
113814
|
const secrets = openVault2(passphrase, vaultPath);
|
|
112706
113815
|
const key = rawToken.replace("vault:", "");
|
|
112707
113816
|
const entry = secrets[key];
|
|
@@ -112729,7 +113838,7 @@ async function resolveOrPromptToken(rawToken, label, config, nonInteractive) {
|
|
|
112729
113838
|
async function storeTokenInVault(config, vaultRef, token) {
|
|
112730
113839
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
112731
113840
|
const key = vaultRef.replace("vault:", "");
|
|
112732
|
-
if (!
|
|
113841
|
+
if (!existsSync86(vaultPath)) {
|
|
112733
113842
|
console.log(source_default.gray(" Creating encrypted vault..."));
|
|
112734
113843
|
let passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
112735
113844
|
if (!passphrase) {
|
|
@@ -112876,145 +113985,6 @@ async function stepCreateTopics(config, botToken, nonInteractive) {
|
|
|
112876
113985
|
}
|
|
112877
113986
|
}
|
|
112878
113987
|
}
|
|
112879
|
-
async function resolveLiteLLMForHindsight2(config, deps = {}) {
|
|
112880
|
-
return resolveHindsightLiteLlm(config, deps);
|
|
112881
|
-
}
|
|
112882
|
-
function isMemoryBackendDisabled(config) {
|
|
112883
|
-
const memoryBackend = config.memory?.backend ?? "hindsight";
|
|
112884
|
-
return process.env.SWITCHROOM_MEMORY_BACKEND === "none" || memoryBackend === "none";
|
|
112885
|
-
}
|
|
112886
|
-
async function stepMemoryBackend(config, nonInteractive, switchroomConfigPath, deps = {}) {
|
|
112887
|
-
stepHeader(6, "Memory backend", STEP_ACTIVE);
|
|
112888
|
-
const sleep4 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
112889
|
-
if (isMemoryBackendDisabled(config)) {
|
|
112890
|
-
console.log(source_default.gray(" Memory backend disabled (set to 'none')."));
|
|
112891
|
-
console.log(source_default.green(` ${STEP_DONE} Skipped`));
|
|
112892
|
-
return { hindsightExpected: false, optedOut: true };
|
|
112893
|
-
}
|
|
112894
|
-
let setupHindsight = true;
|
|
112895
|
-
if (!nonInteractive) {
|
|
112896
|
-
console.log(source_default.gray(" Hindsight will use Anthropic OAuth via the auth-broker. The fleet's"));
|
|
112897
|
-
console.log(source_default.gray(" active account (auth.active) is shared \u2014 no OpenAI API key is needed."));
|
|
112898
|
-
setupHindsight = await askYesNo(" Set up Hindsight memory? (recommended)", true);
|
|
112899
|
-
}
|
|
112900
|
-
if (!setupHindsight) {
|
|
112901
|
-
console.log(source_default.gray(" Skipping Hindsight setup."));
|
|
112902
|
-
console.log(source_default.green(` ${STEP_DONE} Skipped`));
|
|
112903
|
-
return { hindsightExpected: false, optedOut: false };
|
|
112904
|
-
}
|
|
112905
|
-
if (process.env.HINDSIGHT_API_LLM_API_KEY) {
|
|
112906
|
-
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."));
|
|
112907
|
-
}
|
|
112908
|
-
try {
|
|
112909
|
-
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
112910
|
-
const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
112911
|
-
if (passphrase && existsSync85(vaultPath)) {
|
|
112912
|
-
const existing = getStringSecret(passphrase, vaultPath, "hindsight-api-key");
|
|
112913
|
-
if (existing) {
|
|
112914
|
-
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`."));
|
|
112915
|
-
}
|
|
112916
|
-
}
|
|
112917
|
-
} catch {}
|
|
112918
|
-
{
|
|
112919
|
-
try {
|
|
112920
|
-
const result = await ensureHindsightConsumer(switchroomConfigPath);
|
|
112921
|
-
if (result.added) {
|
|
112922
|
-
console.log(source_default.green(` ${STEP_DONE} Registered auth.consumers[${HINDSIGHT_CONSUMER_NAME}] (follows the fleet active account)`));
|
|
112923
|
-
} else {
|
|
112924
|
-
console.log(source_default.gray(` auth.consumers[${HINDSIGHT_CONSUMER_NAME}] already present.`));
|
|
112925
|
-
}
|
|
112926
|
-
} catch (err) {
|
|
112927
|
-
console.log(source_default.yellow(` Warning: could not write auth.consumers entry: ${err.message}`));
|
|
112928
|
-
}
|
|
112929
|
-
}
|
|
112930
|
-
const dockerState = probeDockerAvailability(deps.dockerProbe);
|
|
112931
|
-
if (dockerState !== "ok") {
|
|
112932
|
-
const noCli = dockerState === "no-cli";
|
|
112933
|
-
const headline = noCli ? "Docker is not installed (no `docker` command on PATH)." : "Docker is installed but its daemon is not responding.";
|
|
112934
|
-
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`";
|
|
112935
|
-
console.log(source_default.red(` x ${headline}`));
|
|
112936
|
-
console.log(source_default.gray(" Hindsight memory runs as a Docker container (and so does every agent)."));
|
|
112937
|
-
console.log(source_default.gray(` ${remedy} \u2014 or re-run with SWITCHROOM_MEMORY_BACKEND=none to skip memory setup.`));
|
|
112938
|
-
throw new Error(`Memory backend setup failed: ${headline} ${remedy}, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.`);
|
|
112939
|
-
}
|
|
112940
|
-
if (isHindsightRunning(deps.dockerProbe)) {
|
|
112941
|
-
console.log(source_default.green(` ${STEP_DONE} Hindsight container already running (switchroom-hindsight)`));
|
|
112942
|
-
return { hindsightExpected: true, optedOut: false };
|
|
112943
|
-
}
|
|
112944
|
-
if (isHindsightContainerExists(deps.dockerProbe)) {
|
|
112945
|
-
console.log(source_default.gray(" Found stopped switchroom-hindsight container, removing..."));
|
|
112946
|
-
stopHindsight();
|
|
112947
|
-
}
|
|
112948
|
-
let ports;
|
|
112949
|
-
try {
|
|
112950
|
-
ports = await pickHindsightPorts();
|
|
112951
|
-
} catch (err) {
|
|
112952
|
-
console.log(source_default.red(` ${err.message}`));
|
|
112953
|
-
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.`);
|
|
112954
|
-
}
|
|
112955
|
-
if (ports.apiPort !== HINDSIGHT_DEFAULT_API_PORT) {
|
|
112956
|
-
console.log(source_default.yellow(` Port ${HINDSIGHT_DEFAULT_API_PORT} is already in use; using ${ports.apiPort}/${ports.uiPort} instead.`));
|
|
112957
|
-
}
|
|
112958
|
-
let conflict = await preflightHindsightPorts(ports);
|
|
112959
|
-
if (conflict) {
|
|
112960
|
-
const heldBy = conflict.holder ? ` (held by ${conflict.holder})` : "";
|
|
112961
|
-
console.log(source_default.yellow(` Chosen Hindsight port ${conflict.port} is occupied${heldBy}; selecting a free port instead of crash-looping.`));
|
|
112962
|
-
try {
|
|
112963
|
-
ports = await pickHindsightPorts();
|
|
112964
|
-
} catch (err) {
|
|
112965
|
-
console.log(source_default.red(` ${err.message}`));
|
|
112966
|
-
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.`);
|
|
112967
|
-
}
|
|
112968
|
-
conflict = await preflightHindsightPorts(ports);
|
|
112969
|
-
if (conflict) {
|
|
112970
|
-
const stillHeldBy = conflict.holder ? ` (held by ${conflict.holder})` : "";
|
|
112971
|
-
const msg = `Refusing to start Hindsight: port ${conflict.port} is still occupied${stillHeldBy} after reassignment.`;
|
|
112972
|
-
console.log(source_default.red(` ${msg}`));
|
|
112973
|
-
throw new Error(`Memory backend setup failed: ${msg} Free it and re-run \`switchroom setup\`, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.`);
|
|
112974
|
-
}
|
|
112975
|
-
console.log(source_default.green(` Reassigned Hindsight to port ${ports.apiPort}/${ports.uiPort}.`));
|
|
112976
|
-
}
|
|
112977
|
-
const spin = spinner("Starting Hindsight Docker container...");
|
|
112978
|
-
try {
|
|
112979
|
-
const { litellm: litellmCfg, droppedRef: litellmDroppedRef } = await resolveLiteLLMForHindsight2(config, deps.getViaBrokerStructured ? { getViaBrokerStructured: deps.getViaBrokerStructured } : {});
|
|
112980
|
-
if (litellmDroppedRef) {
|
|
112981
|
-
console.log(source_default.yellow(` ! ${hindsightLiteLlmDroppedKeyWarning(litellmDroppedRef)}`));
|
|
112982
|
-
}
|
|
112983
|
-
const cpAccessKey = await resolveHindsightCpAccessKey(config);
|
|
112984
|
-
if (!cpAccessKey) {
|
|
112985
|
-
console.log(source_default.yellow(` ! ${HINDSIGHT_CP_NO_ACCESS_KEY_WARNING}`));
|
|
112986
|
-
}
|
|
112987
|
-
const resolvedLlm = await resolveHindsightLlmSecrets(config.hindsight?.llm, deps.getViaBrokerStructured ? { getViaBrokerStructured: deps.getViaBrokerStructured } : {});
|
|
112988
|
-
for (const drop of await diffDroppedHindsightLlmVaultKeys(config.hindsight?.llm, resolvedLlm)) {
|
|
112989
|
-
console.log(source_default.yellow(` ! ${hindsightLlmDroppedKeyWarning(drop)}`));
|
|
112990
|
-
}
|
|
112991
|
-
const startContainer = deps.startContainer ?? startHindsight;
|
|
112992
|
-
startContainer(ports, litellmCfg, undefined, resolvedLlm, hindsightConsumerMirrorDir(config), undefined, { env: config.hindsight?.env, memLimit: config.hindsight?.mem_limit }, cpAccessKey);
|
|
112993
|
-
if (litellmCfg) {
|
|
112994
|
-
console.log(source_default.gray(" LiteLLM routing enabled (--network host, ANTHROPIC_BASE_URL set)."));
|
|
112995
|
-
}
|
|
112996
|
-
} catch (err) {
|
|
112997
|
-
spin.stop(source_default.red(`Failed to start Hindsight: ${err.message}`));
|
|
112998
|
-
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.`));
|
|
112999
|
-
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.`);
|
|
113000
|
-
}
|
|
113001
|
-
let running = isHindsightRunning(deps.dockerProbe);
|
|
113002
|
-
const retries = deps.readyRetries ?? HINDSIGHT_READY_RETRIES;
|
|
113003
|
-
for (let i2 = 0;!running && i2 < retries; i2++) {
|
|
113004
|
-
await sleep4(HINDSIGHT_READY_INTERVAL_MS);
|
|
113005
|
-
running = isHindsightRunning(deps.dockerProbe);
|
|
113006
|
-
}
|
|
113007
|
-
if (!running) {
|
|
113008
|
-
spin.stop(source_default.red("Hindsight container did not stay running after start"));
|
|
113009
|
-
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.");
|
|
113010
|
-
}
|
|
113011
|
-
spin.stop(source_default.green(`${STEP_DONE} Hindsight container started (switchroom-hindsight)`));
|
|
113012
|
-
console.log(source_default.gray(` API: http://localhost:${ports.apiPort}/mcp`));
|
|
113013
|
-
console.log(source_default.gray(` UI: http://localhost:${ports.uiPort}`));
|
|
113014
|
-
return { hindsightExpected: true, optedOut: false };
|
|
113015
|
-
}
|
|
113016
|
-
var HINDSIGHT_READY_RETRIES = 5;
|
|
113017
|
-
var HINDSIGHT_READY_INTERVAL_MS = 1000;
|
|
113018
113988
|
function stepVoiceEngine() {
|
|
113019
113989
|
stepHeader(7, "Voice engine", STEP_ACTIVE);
|
|
113020
113990
|
let caps;
|
|
@@ -113151,13 +114121,13 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
|
|
|
113151
114121
|
return;
|
|
113152
114122
|
}
|
|
113153
114123
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
113154
|
-
if (!
|
|
114124
|
+
if (!existsSync86(vaultPath)) {
|
|
113155
114125
|
console.log(source_default.gray(" Skipping (vault not created yet)."));
|
|
113156
114126
|
return;
|
|
113157
114127
|
}
|
|
113158
114128
|
const credPathRaw = config.vault?.broker?.autoUnlockCredentialPath ?? "~/.switchroom/vault-auto-unlock";
|
|
113159
114129
|
const credPath = resolvePath(credPathRaw);
|
|
113160
|
-
if (config.vault?.broker?.autoUnlock === true &&
|
|
114130
|
+
if (config.vault?.broker?.autoUnlock === true && existsSync86(credPath)) {
|
|
113161
114131
|
console.log(source_default.green(` ${STEP_DONE} Already configured (${credPath})`));
|
|
113162
114132
|
return;
|
|
113163
114133
|
}
|
|
@@ -113235,7 +114205,7 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
|
|
|
113235
114205
|
}
|
|
113236
114206
|
}
|
|
113237
114207
|
function persistApprovalAuthTelegramId(configPath) {
|
|
113238
|
-
if (!
|
|
114208
|
+
if (!existsSync86(configPath)) {
|
|
113239
114209
|
throw new ConfigError(`Cannot persist vault.broker.approvalAuth: config not found at ${configPath}`);
|
|
113240
114210
|
}
|
|
113241
114211
|
const content = readFileSync79(configPath, "utf-8");
|
|
@@ -113246,7 +114216,7 @@ function persistApprovalAuthTelegramId(configPath) {
|
|
|
113246
114216
|
return result.kind;
|
|
113247
114217
|
}
|
|
113248
114218
|
function persistDangerousMode(config, configPath) {
|
|
113249
|
-
if (!
|
|
114219
|
+
if (!existsSync86(configPath)) {
|
|
113250
114220
|
throw new ConfigError(`Cannot persist dangerous_mode: config not found at ${configPath}`);
|
|
113251
114221
|
}
|
|
113252
114222
|
let content = readFileSync79(configPath, "utf-8");
|
|
@@ -113357,7 +114327,7 @@ var START_OUTPUT_TAIL_LINES = 12;
|
|
|
113357
114327
|
function defaultStartAgent(name) {
|
|
113358
114328
|
let out = "";
|
|
113359
114329
|
try {
|
|
113360
|
-
out =
|
|
114330
|
+
out = execFileSync27("switchroom", ["agent", "start", name], {
|
|
113361
114331
|
stdio: ["ignore", "pipe", "pipe"],
|
|
113362
114332
|
encoding: "utf-8"
|
|
113363
114333
|
});
|
|
@@ -113385,7 +114355,7 @@ async function stepVerification(config, nonInteractive, deps = {}, expect = {})
|
|
|
113385
114355
|
const findings = [];
|
|
113386
114356
|
let applyPending = false;
|
|
113387
114357
|
if (!nonInteractive && agentNames.length > 0) {
|
|
113388
|
-
const composeExists = (deps.composeFileExists ?? (() =>
|
|
114358
|
+
const composeExists = (deps.composeFileExists ?? (() => existsSync86(composeFilePath())))();
|
|
113389
114359
|
if (!composeExists) {
|
|
113390
114360
|
applyPending = true;
|
|
113391
114361
|
log(source_default.gray(" Not started: `switchroom apply` has not generated the compose file yet."));
|
|
@@ -113482,7 +114452,7 @@ init_source();
|
|
|
113482
114452
|
init_loader();
|
|
113483
114453
|
init_lifecycle();
|
|
113484
114454
|
init_compose_env();
|
|
113485
|
-
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";
|
|
113486
114456
|
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
113487
114457
|
import { join as join92, dirname as dirname39 } from "node:path";
|
|
113488
114458
|
import { homedir as homedir48 } from "node:os";
|
|
@@ -113929,7 +114899,7 @@ import {
|
|
|
113929
114899
|
closeSync as closeSync15,
|
|
113930
114900
|
copyFileSync as copyFileSync12,
|
|
113931
114901
|
createWriteStream,
|
|
113932
|
-
existsSync as
|
|
114902
|
+
existsSync as existsSync87,
|
|
113933
114903
|
lstatSync as lstatSync13,
|
|
113934
114904
|
mkdirSync as mkdirSync48,
|
|
113935
114905
|
openSync as openSync15,
|
|
@@ -114012,7 +114982,7 @@ function defaultSelfUpdateIO() {
|
|
|
114012
114982
|
rmSync15(path7, { force: true });
|
|
114013
114983
|
},
|
|
114014
114984
|
exists(path7) {
|
|
114015
|
-
return
|
|
114985
|
+
return existsSync87(path7);
|
|
114016
114986
|
},
|
|
114017
114987
|
dirname(path7) {
|
|
114018
114988
|
return dirname37(path7);
|
|
@@ -114067,7 +115037,7 @@ init_install_cron2();
|
|
|
114067
115037
|
|
|
114068
115038
|
// src/host-cli-self-heal/install-timer.ts
|
|
114069
115039
|
import { spawnSync as spawnSync19 } from "node:child_process";
|
|
114070
|
-
import { existsSync as
|
|
115040
|
+
import { existsSync as existsSync88, mkdirSync as mkdirSync49, readFileSync as readFileSync81, renameSync as renameSync22, writeFileSync as writeFileSync34 } from "node:fs";
|
|
114071
115041
|
import { dirname as dirname38 } from "node:path";
|
|
114072
115042
|
var SERVICE_PATH = "/etc/systemd/system/switchroom-self-heal.service";
|
|
114073
115043
|
var TIMER_PATH = "/etc/systemd/system/switchroom-self-heal.timer";
|
|
@@ -114114,7 +115084,7 @@ function renderTimer() {
|
|
|
114114
115084
|
`;
|
|
114115
115085
|
}
|
|
114116
115086
|
function writeUnitIdempotent(path7, content) {
|
|
114117
|
-
if (
|
|
115087
|
+
if (existsSync88(path7)) {
|
|
114118
115088
|
try {
|
|
114119
115089
|
if (readFileSync81(path7, "utf8") === content)
|
|
114120
115090
|
return "unchanged";
|
|
@@ -114145,7 +115115,7 @@ function resolveOperatorUser(env2) {
|
|
|
114145
115115
|
}
|
|
114146
115116
|
function installSelfHealTimer(deps = {}) {
|
|
114147
115117
|
const env2 = deps.env ?? process.env;
|
|
114148
|
-
const systemdBooted = deps.systemdBooted ?? (() =>
|
|
115118
|
+
const systemdBooted = deps.systemdBooted ?? (() => existsSync88(SYSTEMD_MARKER));
|
|
114149
115119
|
const geteuid = deps.geteuid ?? (() => typeof process.geteuid === "function" ? process.geteuid() : undefined);
|
|
114150
115120
|
const homeForUser = deps.homeForUser ?? homeFromPasswd;
|
|
114151
115121
|
const servicePath = deps.servicePath ?? SERVICE_PATH;
|
|
@@ -114230,7 +115200,7 @@ var PACKAGED_INSTALL_KINDS = new Set([
|
|
|
114230
115200
|
function runningFromSwitchroomCheckout(scriptPath) {
|
|
114231
115201
|
let dir = dirname39(scriptPath);
|
|
114232
115202
|
for (let i2 = 0;i2 < 12; i2++) {
|
|
114233
|
-
if (
|
|
115203
|
+
if (existsSync89(join92(dir, ".git"))) {
|
|
114234
115204
|
try {
|
|
114235
115205
|
const pkg = JSON.parse(readFileSync82(join92(dir, "package.json"), "utf-8"));
|
|
114236
115206
|
if (pkg.name === "switchroom")
|
|
@@ -114299,7 +115269,7 @@ function planUpdate(opts) {
|
|
|
114299
115269
|
bundleDir: import.meta.dirname,
|
|
114300
115270
|
execPath: process.execPath,
|
|
114301
115271
|
scriptPath,
|
|
114302
|
-
inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" ||
|
|
115272
|
+
inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" || existsSync89("/.dockerenv")
|
|
114303
115273
|
});
|
|
114304
115274
|
if (detection.kind !== "static-binary") {
|
|
114305
115275
|
say(source_default.gray(` host CLI not self-updated: ${detection.reason}
|
|
@@ -114382,7 +115352,7 @@ function planUpdate(opts) {
|
|
|
114382
115352
|
steps.push({
|
|
114383
115353
|
name: "pull-images",
|
|
114384
115354
|
description: "Pull broker / kernel / agent images from GHCR",
|
|
114385
|
-
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,
|
|
114386
115356
|
run: () => {
|
|
114387
115357
|
const r = runner("docker", [
|
|
114388
115358
|
"compose",
|
|
@@ -114617,7 +115587,7 @@ function planUpdate(opts) {
|
|
|
114617
115587
|
bundleDir: import.meta.dirname,
|
|
114618
115588
|
execPath: process.execPath,
|
|
114619
115589
|
scriptPath,
|
|
114620
|
-
inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" ||
|
|
115590
|
+
inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" || existsSync89("/.dockerenv")
|
|
114621
115591
|
});
|
|
114622
115592
|
const detail = `no shipped skills/ payload found for this install ` + `(${detection.kind}; ${describeShippedAssetSearch(resolution)})`;
|
|
114623
115593
|
if (PACKAGED_INSTALL_KINDS.has(detection.kind)) {
|
|
@@ -114657,10 +115627,10 @@ function planUpdate(opts) {
|
|
|
114657
115627
|
if (opts.syncBundledSkillsFn)
|
|
114658
115628
|
return;
|
|
114659
115629
|
const dest = join92(homedir48(), ".switchroom", "skills", "_bundled");
|
|
114660
|
-
if (!
|
|
115630
|
+
if (!existsSync89(dest)) {
|
|
114661
115631
|
return;
|
|
114662
115632
|
}
|
|
114663
|
-
const missing = getBuiltinDefaultSkillEntries().map((e) => e.key).filter((key) => !
|
|
115633
|
+
const missing = getBuiltinDefaultSkillEntries().map((e) => e.key).filter((key) => !existsSync89(join92(dest, key)));
|
|
114664
115634
|
if (missing.length > 0) {
|
|
114665
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.`);
|
|
114666
115636
|
}
|
|
@@ -114775,7 +115745,7 @@ function defaultStatusProbe(composePath) {
|
|
|
114775
115745
|
let dir = dirname39(scriptPath);
|
|
114776
115746
|
for (let i2 = 0;i2 < 8; i2++) {
|
|
114777
115747
|
const pkgPath = join92(dir, "package.json");
|
|
114778
|
-
if (
|
|
115748
|
+
if (existsSync89(pkgPath)) {
|
|
114779
115749
|
try {
|
|
114780
115750
|
const pkg = JSON.parse(readFileSync82(pkgPath, "utf-8"));
|
|
114781
115751
|
if (typeof pkg.version === "string")
|
|
@@ -114798,7 +115768,7 @@ function defaultStatusProbe(composePath) {
|
|
|
114798
115768
|
warnings.push("could not resolve CLI version (no package.json found above the resolved script path)");
|
|
114799
115769
|
}
|
|
114800
115770
|
const services = [];
|
|
114801
|
-
if (!
|
|
115771
|
+
if (!existsSync89(composePath)) {
|
|
114802
115772
|
warnings.push(`compose file not found at ${composePath}; service status unknown`);
|
|
114803
115773
|
return { cliVersion, cliBuiltAt, services, warnings };
|
|
114804
115774
|
}
|
|
@@ -115066,7 +116036,7 @@ import { homedir as homedir50 } from "node:os";
|
|
|
115066
116036
|
|
|
115067
116037
|
// src/cli/rollout-pin-journal.ts
|
|
115068
116038
|
import {
|
|
115069
|
-
existsSync as
|
|
116039
|
+
existsSync as existsSync90,
|
|
115070
116040
|
readFileSync as readFileSync83,
|
|
115071
116041
|
writeFileSync as writeFileSync35,
|
|
115072
116042
|
renameSync as renameSync23,
|
|
@@ -115130,7 +116100,7 @@ function readPinJournal(configPath, warn = (m) => process.stderr.write(m)) {
|
|
|
115130
116100
|
try {
|
|
115131
116101
|
raw = readFileSync83(p, "utf8");
|
|
115132
116102
|
} catch (e) {
|
|
115133
|
-
if (
|
|
116103
|
+
if (existsSync90(p)) {
|
|
115134
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.
|
|
115135
116105
|
`);
|
|
115136
116106
|
}
|
|
@@ -115202,7 +116172,7 @@ function commitPinPersist(configPath) {
|
|
|
115202
116172
|
unlinkSync17(p);
|
|
115203
116173
|
return null;
|
|
115204
116174
|
} catch (e) {
|
|
115205
|
-
if (!
|
|
116175
|
+
if (!existsSync90(p))
|
|
115206
116176
|
return null;
|
|
115207
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\`.`;
|
|
115208
116178
|
}
|
|
@@ -116130,7 +117100,7 @@ init_helpers();
|
|
|
116130
117100
|
init_lifecycle();
|
|
116131
117101
|
init_resolve_version();
|
|
116132
117102
|
import { execSync as execSync3 } from "node:child_process";
|
|
116133
|
-
import { existsSync as
|
|
117103
|
+
import { existsSync as existsSync91, readFileSync as readFileSync85 } from "node:fs";
|
|
116134
117104
|
import { dirname as dirname41, join as join94 } from "node:path";
|
|
116135
117105
|
function getClaudeCodeVersion() {
|
|
116136
117106
|
try {
|
|
@@ -116182,10 +117152,10 @@ function locateSwitchroomInstallDir() {
|
|
|
116182
117152
|
let dir = import.meta.dirname;
|
|
116183
117153
|
for (let i2 = 0;i2 < 10 && dir && dir !== "/"; i2++) {
|
|
116184
117154
|
const pkgPath = join94(dir, "package.json");
|
|
116185
|
-
if (
|
|
117155
|
+
if (existsSync91(pkgPath)) {
|
|
116186
117156
|
try {
|
|
116187
117157
|
const pkg = JSON.parse(readFileSync85(pkgPath, "utf-8"));
|
|
116188
|
-
if (pkg.name === "switchroom" &&
|
|
117158
|
+
if (pkg.name === "switchroom" && existsSync91(join94(dir, ".git"))) {
|
|
116189
117159
|
return dir;
|
|
116190
117160
|
}
|
|
116191
117161
|
} catch {}
|
|
@@ -116366,7 +117336,7 @@ init_merge();
|
|
|
116366
117336
|
|
|
116367
117337
|
// src/agents/session-retention.ts
|
|
116368
117338
|
import {
|
|
116369
|
-
existsSync as
|
|
117339
|
+
existsSync as existsSync92,
|
|
116370
117340
|
readdirSync as readdirSync31,
|
|
116371
117341
|
statSync as statSync52,
|
|
116372
117342
|
unlinkSync as unlinkSync18
|
|
@@ -116377,7 +117347,7 @@ var DEFAULT_SESSION_RETENTION_MAX_AGE_DAYS = 30;
|
|
|
116377
117347
|
var MIN_KEEP = 2;
|
|
116378
117348
|
function collectSessionJsonl(claudeConfigDir) {
|
|
116379
117349
|
const projects = join95(claudeConfigDir, "projects");
|
|
116380
|
-
if (!
|
|
117350
|
+
if (!existsSync92(projects))
|
|
116381
117351
|
return [];
|
|
116382
117352
|
const found = [];
|
|
116383
117353
|
const walk2 = (dir) => {
|
|
@@ -116509,7 +117479,7 @@ function registerHandoffCommand(program3) {
|
|
|
116509
117479
|
// src/issues/store.ts
|
|
116510
117480
|
import {
|
|
116511
117481
|
closeSync as closeSync16,
|
|
116512
|
-
existsSync as
|
|
117482
|
+
existsSync as existsSync93,
|
|
116513
117483
|
mkdirSync as mkdirSync52,
|
|
116514
117484
|
openSync as openSync16,
|
|
116515
117485
|
readdirSync as readdirSync32,
|
|
@@ -116548,7 +117518,7 @@ var ISSUES_FILE = "issues.jsonl";
|
|
|
116548
117518
|
var ISSUES_LOCK = "issues.lock";
|
|
116549
117519
|
function readAll(stateDir) {
|
|
116550
117520
|
const path7 = join96(stateDir, ISSUES_FILE);
|
|
116551
|
-
if (!
|
|
117521
|
+
if (!existsSync93(path7))
|
|
116552
117522
|
return [];
|
|
116553
117523
|
let raw;
|
|
116554
117524
|
try {
|
|
@@ -116625,7 +117595,7 @@ function record(stateDir, input, nowFn = Date.now) {
|
|
|
116625
117595
|
});
|
|
116626
117596
|
}
|
|
116627
117597
|
function resolve51(stateDir, fingerprint, nowFn = Date.now) {
|
|
116628
|
-
if (!
|
|
117598
|
+
if (!existsSync93(join96(stateDir, ISSUES_FILE)))
|
|
116629
117599
|
return 0;
|
|
116630
117600
|
return withLock(stateDir, () => {
|
|
116631
117601
|
const all = readAll(stateDir);
|
|
@@ -116643,7 +117613,7 @@ function resolve51(stateDir, fingerprint, nowFn = Date.now) {
|
|
|
116643
117613
|
});
|
|
116644
117614
|
}
|
|
116645
117615
|
function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
116646
|
-
if (!
|
|
117616
|
+
if (!existsSync93(join96(stateDir, ISSUES_FILE)))
|
|
116647
117617
|
return 0;
|
|
116648
117618
|
return withLock(stateDir, () => {
|
|
116649
117619
|
const all = readAll(stateDir);
|
|
@@ -116661,7 +117631,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
|
116661
117631
|
});
|
|
116662
117632
|
}
|
|
116663
117633
|
function prune(stateDir, opts = {}) {
|
|
116664
|
-
if (!
|
|
117634
|
+
if (!existsSync93(join96(stateDir, ISSUES_FILE)))
|
|
116665
117635
|
return 0;
|
|
116666
117636
|
return withLock(stateDir, () => {
|
|
116667
117637
|
const all = readAll(stateDir);
|
|
@@ -117011,14 +117981,14 @@ function relTime(deltaMs) {
|
|
|
117011
117981
|
|
|
117012
117982
|
// src/cli/deps.ts
|
|
117013
117983
|
init_source();
|
|
117014
|
-
import { existsSync as
|
|
117984
|
+
import { existsSync as existsSync96 } from "node:fs";
|
|
117015
117985
|
import { homedir as homedir53 } from "node:os";
|
|
117016
117986
|
import { join as join99, resolve as resolve52 } from "node:path";
|
|
117017
117987
|
|
|
117018
117988
|
// src/deps/python.ts
|
|
117019
117989
|
import { createHash as createHash18 } from "node:crypto";
|
|
117020
117990
|
import {
|
|
117021
|
-
existsSync as
|
|
117991
|
+
existsSync as existsSync94,
|
|
117022
117992
|
mkdirSync as mkdirSync53,
|
|
117023
117993
|
readFileSync as readFileSync87,
|
|
117024
117994
|
rmSync as rmSync16,
|
|
@@ -117026,7 +117996,7 @@ import {
|
|
|
117026
117996
|
} from "node:fs";
|
|
117027
117997
|
import { dirname as dirname42, join as join97 } from "node:path";
|
|
117028
117998
|
import { homedir as homedir51 } from "node:os";
|
|
117029
|
-
import { execFileSync as
|
|
117999
|
+
import { execFileSync as execFileSync28 } from "node:child_process";
|
|
117030
118000
|
|
|
117031
118001
|
class PythonEnvError extends Error {
|
|
117032
118002
|
stderr;
|
|
@@ -117046,7 +118016,7 @@ function ensurePythonEnv(opts) {
|
|
|
117046
118016
|
const { skillName, requirementsPath, force = false } = opts;
|
|
117047
118017
|
const cacheRoot = opts.cacheRoot ?? defaultPythonCacheRoot();
|
|
117048
118018
|
const hostPython = opts.pythonBin ?? "python3";
|
|
117049
|
-
if (!
|
|
118019
|
+
if (!existsSync94(requirementsPath)) {
|
|
117050
118020
|
throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
|
|
117051
118021
|
}
|
|
117052
118022
|
const venvDir = join97(cacheRoot, skillName);
|
|
@@ -117055,7 +118025,7 @@ function ensurePythonEnv(opts) {
|
|
|
117055
118025
|
const pythonBin = join97(binDir, "python");
|
|
117056
118026
|
const pipBin = join97(binDir, "pip");
|
|
117057
118027
|
const targetHash = hashFile(requirementsPath);
|
|
117058
|
-
if (!force &&
|
|
118028
|
+
if (!force && existsSync94(stampPath) && existsSync94(pythonBin)) {
|
|
117059
118029
|
const existingHash = readFileSync87(stampPath, "utf8").trim();
|
|
117060
118030
|
if (existingHash === targetHash) {
|
|
117061
118031
|
return {
|
|
@@ -117068,12 +118038,12 @@ function ensurePythonEnv(opts) {
|
|
|
117068
118038
|
};
|
|
117069
118039
|
}
|
|
117070
118040
|
}
|
|
117071
|
-
if (
|
|
118041
|
+
if (existsSync94(venvDir)) {
|
|
117072
118042
|
rmSync16(venvDir, { recursive: true, force: true });
|
|
117073
118043
|
}
|
|
117074
118044
|
mkdirSync53(dirname42(venvDir), { recursive: true });
|
|
117075
118045
|
try {
|
|
117076
|
-
|
|
118046
|
+
execFileSync28(hostPython, ["-m", "venv", venvDir], { stdio: "pipe" });
|
|
117077
118047
|
} catch (err) {
|
|
117078
118048
|
const e = err;
|
|
117079
118049
|
throw new PythonEnvError(`Failed to create venv for skill "${skillName}" with ${hostPython}: ${e.message}`, e.stderr?.toString());
|
|
@@ -117085,7 +118055,7 @@ function ensurePythonEnv(opts) {
|
|
|
117085
118055
|
delete childEnv.PIP_TARGET;
|
|
117086
118056
|
delete childEnv.PIP_PREFIX;
|
|
117087
118057
|
delete childEnv.PYTHONUSERBASE;
|
|
117088
|
-
|
|
118058
|
+
execFileSync28(pipBin, ["install", "--disable-pip-version-check", "-r", requirementsPath], { stdio: "pipe", env: childEnv });
|
|
117089
118059
|
} catch (err) {
|
|
117090
118060
|
const e = err;
|
|
117091
118061
|
throw new PythonEnvError(`Failed to install requirements for skill "${skillName}": ${e.message}`, e.stderr?.toString());
|
|
@@ -117106,7 +118076,7 @@ function ensurePythonEnv(opts) {
|
|
|
117106
118076
|
import { createHash as createHash19 } from "node:crypto";
|
|
117107
118077
|
import {
|
|
117108
118078
|
copyFileSync as copyFileSync13,
|
|
117109
|
-
existsSync as
|
|
118079
|
+
existsSync as existsSync95,
|
|
117110
118080
|
mkdirSync as mkdirSync54,
|
|
117111
118081
|
readFileSync as readFileSync88,
|
|
117112
118082
|
rmSync as rmSync17,
|
|
@@ -117114,7 +118084,7 @@ import {
|
|
|
117114
118084
|
} from "node:fs";
|
|
117115
118085
|
import { dirname as dirname43, join as join98 } from "node:path";
|
|
117116
118086
|
import { homedir as homedir52 } from "node:os";
|
|
117117
|
-
import { execFileSync as
|
|
118087
|
+
import { execFileSync as execFileSync29 } from "node:child_process";
|
|
117118
118088
|
|
|
117119
118089
|
class NodeEnvError extends Error {
|
|
117120
118090
|
stderr;
|
|
@@ -117146,7 +118116,7 @@ function hashDepInputs(packageJsonPath) {
|
|
|
117146
118116
|
hasher.update(readFileSync88(packageJsonPath));
|
|
117147
118117
|
for (const lockName of ALL_LOCKFILES) {
|
|
117148
118118
|
const lockPath = join98(sourceDir, lockName);
|
|
117149
|
-
if (
|
|
118119
|
+
if (existsSync95(lockPath)) {
|
|
117150
118120
|
hasher.update(`
|
|
117151
118121
|
`);
|
|
117152
118122
|
hasher.update(lockName);
|
|
@@ -117161,7 +118131,7 @@ function ensureNodeEnv(opts) {
|
|
|
117161
118131
|
const { skillName, packageJsonPath, force = false } = opts;
|
|
117162
118132
|
const cacheRoot = opts.cacheRoot ?? defaultNodeCacheRoot();
|
|
117163
118133
|
const installer = opts.installer ?? "bun";
|
|
117164
|
-
if (!
|
|
118134
|
+
if (!existsSync95(packageJsonPath)) {
|
|
117165
118135
|
throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
|
|
117166
118136
|
}
|
|
117167
118137
|
const sourceDir = dirname43(packageJsonPath);
|
|
@@ -117170,7 +118140,7 @@ function ensureNodeEnv(opts) {
|
|
|
117170
118140
|
const nodeModulesDir = join98(envDir, "node_modules");
|
|
117171
118141
|
const binDir = join98(nodeModulesDir, ".bin");
|
|
117172
118142
|
const targetHash = hashDepInputs(packageJsonPath);
|
|
117173
|
-
if (!force &&
|
|
118143
|
+
if (!force && existsSync95(stampPath) && existsSync95(nodeModulesDir)) {
|
|
117174
118144
|
const existingHash = readFileSync88(stampPath, "utf8").trim();
|
|
117175
118145
|
if (existingHash === targetHash) {
|
|
117176
118146
|
return {
|
|
@@ -117182,7 +118152,7 @@ function ensureNodeEnv(opts) {
|
|
|
117182
118152
|
};
|
|
117183
118153
|
}
|
|
117184
118154
|
}
|
|
117185
|
-
if (
|
|
118155
|
+
if (existsSync95(envDir)) {
|
|
117186
118156
|
rmSync17(envDir, { recursive: true, force: true });
|
|
117187
118157
|
}
|
|
117188
118158
|
mkdirSync54(envDir, { recursive: true });
|
|
@@ -117190,7 +118160,7 @@ function ensureNodeEnv(opts) {
|
|
|
117190
118160
|
let copiedLockfile = false;
|
|
117191
118161
|
for (const lockName of LOCKFILES_FOR[installer]) {
|
|
117192
118162
|
const lockPath = join98(sourceDir, lockName);
|
|
117193
|
-
if (
|
|
118163
|
+
if (existsSync95(lockPath)) {
|
|
117194
118164
|
copyFileSync13(lockPath, join98(envDir, lockName));
|
|
117195
118165
|
copiedLockfile = true;
|
|
117196
118166
|
}
|
|
@@ -117198,10 +118168,10 @@ function ensureNodeEnv(opts) {
|
|
|
117198
118168
|
try {
|
|
117199
118169
|
if (installer === "bun") {
|
|
117200
118170
|
const args = copiedLockfile ? ["install", "--frozen-lockfile"] : ["install"];
|
|
117201
|
-
|
|
118171
|
+
execFileSync29("bun", args, { cwd: envDir, stdio: "pipe" });
|
|
117202
118172
|
} else {
|
|
117203
118173
|
const args = copiedLockfile ? ["ci"] : ["install"];
|
|
117204
|
-
|
|
118174
|
+
execFileSync29("npm", args, { cwd: envDir, stdio: "pipe" });
|
|
117205
118175
|
}
|
|
117206
118176
|
} catch (err) {
|
|
117207
118177
|
const e = err;
|
|
@@ -117226,22 +118196,22 @@ function registerDepsCommand(program3) {
|
|
|
117226
118196
|
const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
|
|
117227
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) => {
|
|
117228
118198
|
const skillsRoot = builtinSkillsRoot();
|
|
117229
|
-
if (!
|
|
118199
|
+
if (!existsSync96(skillsRoot)) {
|
|
117230
118200
|
console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
|
|
117231
118201
|
process.exit(1);
|
|
117232
118202
|
}
|
|
117233
118203
|
const skillDir = join99(skillsRoot, skill);
|
|
117234
|
-
if (!
|
|
118204
|
+
if (!existsSync96(skillDir)) {
|
|
117235
118205
|
console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
|
|
117236
118206
|
process.exit(1);
|
|
117237
118207
|
}
|
|
117238
118208
|
const requirementsPath = join99(skillDir, "requirements.txt");
|
|
117239
118209
|
const packageJsonPath = join99(skillDir, "package.json");
|
|
117240
|
-
const wantPython = opts.python ?? (!opts.python && !opts.node &&
|
|
117241
|
-
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));
|
|
117242
118212
|
let did = 0;
|
|
117243
118213
|
if (wantPython) {
|
|
117244
|
-
if (!
|
|
118214
|
+
if (!existsSync96(requirementsPath)) {
|
|
117245
118215
|
console.error(source_default.red(`Skill "${skill}" has no requirements.txt at ${requirementsPath}`));
|
|
117246
118216
|
process.exit(1);
|
|
117247
118217
|
}
|
|
@@ -117265,7 +118235,7 @@ function registerDepsCommand(program3) {
|
|
|
117265
118235
|
}
|
|
117266
118236
|
}
|
|
117267
118237
|
if (wantNode) {
|
|
117268
|
-
if (!
|
|
118238
|
+
if (!existsSync96(packageJsonPath)) {
|
|
117269
118239
|
console.error(source_default.red(`Skill "${skill}" has no package.json at ${packageJsonPath}`));
|
|
117270
118240
|
process.exit(1);
|
|
117271
118241
|
}
|
|
@@ -117298,7 +118268,7 @@ function registerDepsCommand(program3) {
|
|
|
117298
118268
|
// src/cli/workspace.ts
|
|
117299
118269
|
init_helpers();
|
|
117300
118270
|
init_loader();
|
|
117301
|
-
import { existsSync as
|
|
118271
|
+
import { existsSync as existsSync97 } from "node:fs";
|
|
117302
118272
|
import { resolve as resolve53, sep as sep5 } from "node:path";
|
|
117303
118273
|
import { spawnSync as spawnSync23 } from "node:child_process";
|
|
117304
118274
|
|
|
@@ -118075,7 +119045,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
118075
119045
|
if (!dir)
|
|
118076
119046
|
return;
|
|
118077
119047
|
const gitDir = resolve53(dir, ".git");
|
|
118078
|
-
if (!
|
|
119048
|
+
if (!existsSync97(gitDir)) {
|
|
118079
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.
|
|
118080
119050
|
`);
|
|
118081
119051
|
return;
|
|
@@ -118129,7 +119099,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
118129
119099
|
if (!dir)
|
|
118130
119100
|
return;
|
|
118131
119101
|
const gitDir = resolve53(dir, ".git");
|
|
118132
|
-
if (!
|
|
119102
|
+
if (!existsSync97(gitDir)) {
|
|
118133
119103
|
process.stdout.write(`Workspace is not a git repository.
|
|
118134
119104
|
`);
|
|
118135
119105
|
return;
|
|
@@ -118154,7 +119124,7 @@ function resolveAgentWorkspaceDirOrExit(program3, agentName) {
|
|
|
118154
119124
|
const agentsDir = resolveAgentsDir(config);
|
|
118155
119125
|
const agentDir = resolve53(agentsDir, agentName);
|
|
118156
119126
|
const dir = resolveAgentWorkspaceDir(agentDir);
|
|
118157
|
-
if (!
|
|
119127
|
+
if (!existsSync97(dir)) {
|
|
118158
119128
|
process.stderr.write(`workspace: ${dir} does not exist yet. Run \`switchroom setup\` or \`switchroom agent scaffold ${agentName}\` to seed it.
|
|
118159
119129
|
`);
|
|
118160
119130
|
return;
|
|
@@ -118190,7 +119160,7 @@ function safeParseInt(value, fallback) {
|
|
|
118190
119160
|
init_helpers();
|
|
118191
119161
|
init_loader();
|
|
118192
119162
|
init_merge();
|
|
118193
|
-
import { copyFileSync as copyFileSync14, existsSync as
|
|
119163
|
+
import { copyFileSync as copyFileSync14, existsSync as existsSync98, readFileSync as readFileSync89, writeFileSync as writeFileSync39 } from "node:fs";
|
|
118194
119164
|
import { join as join100, resolve as resolve54 } from "node:path";
|
|
118195
119165
|
init_scaffold();
|
|
118196
119166
|
init_profiles();
|
|
@@ -118208,7 +119178,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
118208
119178
|
const agentsDir = resolveAgentsDir(config);
|
|
118209
119179
|
const agentDir = resolve54(agentsDir, agentName);
|
|
118210
119180
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
118211
|
-
if (!
|
|
119181
|
+
if (!existsSync98(workspaceDir)) {
|
|
118212
119182
|
console.error(`soul: ${workspaceDir} does not exist yet. Run \`switchroom setup\` ` + `or \`switchroom agent scaffold ${agentName}\` to seed it.`);
|
|
118213
119183
|
process.exit(1);
|
|
118214
119184
|
}
|
|
@@ -118234,7 +119204,7 @@ function registerSoulCommand(program3) {
|
|
|
118234
119204
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
118235
119205
|
if (!t)
|
|
118236
119206
|
return;
|
|
118237
|
-
if (!
|
|
119207
|
+
if (!existsSync98(t.soulPath)) {
|
|
118238
119208
|
console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
|
|
118239
119209
|
process.exit(1);
|
|
118240
119210
|
}
|
|
@@ -118249,7 +119219,7 @@ function registerSoulCommand(program3) {
|
|
|
118249
119219
|
console.error(`soul: profile "${t.profileName}" ships no SOUL.md.hbs \u2014 ` + `nothing to re-seed from.`);
|
|
118250
119220
|
process.exit(1);
|
|
118251
119221
|
}
|
|
118252
|
-
const exists =
|
|
119222
|
+
const exists = existsSync98(t.soulPath);
|
|
118253
119223
|
if (exists && !opts.yes) {
|
|
118254
119224
|
if (!isInteractive()) {
|
|
118255
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).`);
|
|
@@ -118264,7 +119234,7 @@ function registerSoulCommand(program3) {
|
|
|
118264
119234
|
let backupPath;
|
|
118265
119235
|
if (exists) {
|
|
118266
119236
|
backupPath = `${t.soulPath}.bak`;
|
|
118267
|
-
if (
|
|
119237
|
+
if (existsSync98(backupPath)) {
|
|
118268
119238
|
backupPath = `${t.soulPath}.bak.${Date.now()}`;
|
|
118269
119239
|
}
|
|
118270
119240
|
copyFileSync14(t.soulPath, backupPath);
|
|
@@ -118283,7 +119253,7 @@ function registerSoulCommand(program3) {
|
|
|
118283
119253
|
// src/cli/debug.ts
|
|
118284
119254
|
init_helpers();
|
|
118285
119255
|
init_loader();
|
|
118286
|
-
import { existsSync as
|
|
119256
|
+
import { existsSync as existsSync99, readFileSync as readFileSync90, readdirSync as readdirSync33, statSync as statSync54 } from "node:fs";
|
|
118287
119257
|
import { resolve as resolve55, join as join101 } from "node:path";
|
|
118288
119258
|
import { createHash as createHash20 } from "node:crypto";
|
|
118289
119259
|
init_merge();
|
|
@@ -118296,7 +119266,7 @@ function estimateTokens(bytes) {
|
|
|
118296
119266
|
}
|
|
118297
119267
|
function readMcpServerNames(agentDir) {
|
|
118298
119268
|
const mcpPath = join101(agentDir, ".mcp.json");
|
|
118299
|
-
if (!
|
|
119269
|
+
if (!existsSync99(mcpPath))
|
|
118300
119270
|
return [];
|
|
118301
119271
|
try {
|
|
118302
119272
|
const parsed = JSON.parse(readFileSync90(mcpPath, "utf-8"));
|
|
@@ -118310,7 +119280,7 @@ function sha2562(content) {
|
|
|
118310
119280
|
}
|
|
118311
119281
|
function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
118312
119282
|
const projectsDir = join101(claudeConfigDir, "projects");
|
|
118313
|
-
if (!
|
|
119283
|
+
if (!existsSync99(projectsDir))
|
|
118314
119284
|
return;
|
|
118315
119285
|
try {
|
|
118316
119286
|
const entries = readdirSync33(projectsDir, { withFileTypes: true });
|
|
@@ -118320,7 +119290,7 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
118320
119290
|
continue;
|
|
118321
119291
|
const projectPath = join101(projectsDir, entry.name);
|
|
118322
119292
|
const transcriptPath = join101(projectPath, "transcript.jsonl");
|
|
118323
|
-
if (!
|
|
119293
|
+
if (!existsSync99(transcriptPath))
|
|
118324
119294
|
continue;
|
|
118325
119295
|
const stat3 = statSync54(transcriptPath);
|
|
118326
119296
|
if (!latest || stat3.mtimeMs > latest.mtime) {
|
|
@@ -118383,7 +119353,7 @@ function registerDebugCommand(program3) {
|
|
|
118383
119353
|
}
|
|
118384
119354
|
const agentsDir = resolveAgentsDir(config);
|
|
118385
119355
|
const agentDir = resolve55(agentsDir, agentName);
|
|
118386
|
-
if (!
|
|
119356
|
+
if (!existsSync99(agentDir)) {
|
|
118387
119357
|
console.error(`Agent directory not found: ${agentDir}`);
|
|
118388
119358
|
process.exit(1);
|
|
118389
119359
|
}
|
|
@@ -118438,7 +119408,7 @@ function registerDebugCommand(program3) {
|
|
|
118438
119408
|
}
|
|
118439
119409
|
console.log(`=== Append System Prompt (per-session) ===
|
|
118440
119410
|
`);
|
|
118441
|
-
const handoffContent =
|
|
119411
|
+
const handoffContent = existsSync99(handoffPath) ? readFileSync90(handoffPath, "utf-8") : "";
|
|
118442
119412
|
if (handoffContent.trim().length > 0) {
|
|
118443
119413
|
console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
|
|
118444
119414
|
console.log(handoffContent);
|
|
@@ -118449,7 +119419,7 @@ function registerDebugCommand(program3) {
|
|
|
118449
119419
|
}
|
|
118450
119420
|
console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
|
|
118451
119421
|
`);
|
|
118452
|
-
const claudeMdContent =
|
|
119422
|
+
const claudeMdContent = existsSync99(claudeMdPath) ? readFileSync90(claudeMdPath, "utf-8") : "";
|
|
118453
119423
|
if (claudeMdContent.trim().length > 0) {
|
|
118454
119424
|
console.log(`(${formatBytes(claudeMdContent.length)})`);
|
|
118455
119425
|
console.log(claudeMdContent);
|
|
@@ -118460,7 +119430,7 @@ function registerDebugCommand(program3) {
|
|
|
118460
119430
|
}
|
|
118461
119431
|
console.log(`=== Persona (SOUL.md) ===
|
|
118462
119432
|
`);
|
|
118463
|
-
const soulMdContent =
|
|
119433
|
+
const soulMdContent = existsSync99(soulMdPath) ? readFileSync90(soulMdPath, "utf-8") : existsSync99(workspaceSoulMdPath) ? readFileSync90(workspaceSoulMdPath, "utf-8") : "";
|
|
118464
119434
|
if (soulMdContent.trim().length > 0) {
|
|
118465
119435
|
console.log(`(${formatBytes(soulMdContent.length)})`);
|
|
118466
119436
|
console.log(soulMdContent);
|
|
@@ -118524,8 +119494,8 @@ function registerDebugCommand(program3) {
|
|
|
118524
119494
|
const fleetDir = join101(agentsDir, "..", "fleet");
|
|
118525
119495
|
const fleetInvPath = join101(fleetDir, "switchroom-invariants.md");
|
|
118526
119496
|
const fleetClaudePath = join101(fleetDir, "CLAUDE.md");
|
|
118527
|
-
const fleetInvBytes =
|
|
118528
|
-
const fleetClaudeBytes =
|
|
119497
|
+
const fleetInvBytes = existsSync99(fleetInvPath) ? readFileSync90(fleetInvPath, "utf-8").length : 0;
|
|
119498
|
+
const fleetClaudeBytes = existsSync99(fleetClaudePath) ? readFileSync90(fleetClaudePath, "utf-8").length : 0;
|
|
118529
119499
|
const fleetBytes = fleetInvBytes + fleetClaudeBytes;
|
|
118530
119500
|
const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
|
|
118531
119501
|
console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
|
|
@@ -118557,8 +119527,8 @@ function registerDebugCommand(program3) {
|
|
|
118557
119527
|
init_source();
|
|
118558
119528
|
|
|
118559
119529
|
// src/worktree/claim.ts
|
|
118560
|
-
import { execFileSync as
|
|
118561
|
-
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";
|
|
118562
119532
|
import { join as join103, resolve as resolve57 } from "node:path";
|
|
118563
119533
|
import { homedir as homedir55 } from "node:os";
|
|
118564
119534
|
import { randomBytes as randomBytes16 } from "node:crypto";
|
|
@@ -118570,7 +119540,7 @@ import {
|
|
|
118570
119540
|
readFileSync as readFileSync91,
|
|
118571
119541
|
readdirSync as readdirSync34,
|
|
118572
119542
|
unlinkSync as unlinkSync20,
|
|
118573
|
-
existsSync as
|
|
119543
|
+
existsSync as existsSync100,
|
|
118574
119544
|
renameSync as renameSync25
|
|
118575
119545
|
} from "node:fs";
|
|
118576
119546
|
import { join as join102, resolve as resolve56 } from "node:path";
|
|
@@ -118684,7 +119654,7 @@ function expandHome(p) {
|
|
|
118684
119654
|
}
|
|
118685
119655
|
async function claimWorktree(input, codeRepos) {
|
|
118686
119656
|
const repoPath = resolveRepoPath(input.repo, codeRepos);
|
|
118687
|
-
if (!
|
|
119657
|
+
if (!existsSync101(repoPath)) {
|
|
118688
119658
|
throw new Error(`Repository path does not exist: ${repoPath}`);
|
|
118689
119659
|
}
|
|
118690
119660
|
let concurrencyCap = DEFAULT_CONCURRENCY;
|
|
@@ -118726,7 +119696,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
118726
119696
|
releaseLock();
|
|
118727
119697
|
}
|
|
118728
119698
|
try {
|
|
118729
|
-
|
|
119699
|
+
execFileSync30("git", ["worktree", "add", "-b", branch, worktreePath], {
|
|
118730
119700
|
cwd: repoPath,
|
|
118731
119701
|
stdio: "pipe"
|
|
118732
119702
|
});
|
|
@@ -118739,8 +119709,8 @@ async function claimWorktree(input, codeRepos) {
|
|
|
118739
119709
|
}
|
|
118740
119710
|
|
|
118741
119711
|
// src/worktree/release.ts
|
|
118742
|
-
import { execFileSync as
|
|
118743
|
-
import { existsSync as
|
|
119712
|
+
import { execFileSync as execFileSync31 } from "node:child_process";
|
|
119713
|
+
import { existsSync as existsSync102 } from "node:fs";
|
|
118744
119714
|
function releaseWorktree(input) {
|
|
118745
119715
|
const { id } = input;
|
|
118746
119716
|
const record2 = readRecord(id);
|
|
@@ -118748,9 +119718,9 @@ function releaseWorktree(input) {
|
|
|
118748
119718
|
return { released: true };
|
|
118749
119719
|
}
|
|
118750
119720
|
let gitSuccess = true;
|
|
118751
|
-
if (
|
|
119721
|
+
if (existsSync102(record2.path)) {
|
|
118752
119722
|
try {
|
|
118753
|
-
|
|
119723
|
+
execFileSync31("git", ["worktree", "remove", "--force", record2.path], {
|
|
118754
119724
|
cwd: record2.repo,
|
|
118755
119725
|
stdio: "pipe"
|
|
118756
119726
|
});
|
|
@@ -118786,8 +119756,8 @@ function listWorktrees() {
|
|
|
118786
119756
|
}
|
|
118787
119757
|
|
|
118788
119758
|
// src/worktree/reaper.ts
|
|
118789
|
-
import { execFileSync as
|
|
118790
|
-
import { existsSync as
|
|
119759
|
+
import { execFileSync as execFileSync32 } from "node:child_process";
|
|
119760
|
+
import { existsSync as existsSync103 } from "node:fs";
|
|
118791
119761
|
var STALE_THRESHOLD_MS = 10 * 60 * 1000;
|
|
118792
119762
|
function reapSkipReasonText(action) {
|
|
118793
119763
|
switch (action) {
|
|
@@ -118804,7 +119774,7 @@ function reapSkipReasonText(action) {
|
|
|
118804
119774
|
function probePathInUse(path10) {
|
|
118805
119775
|
let probeRan = false;
|
|
118806
119776
|
try {
|
|
118807
|
-
|
|
119777
|
+
execFileSync32("fuser", [path10], { stdio: "pipe" });
|
|
118808
119778
|
return "in-use";
|
|
118809
119779
|
} catch (err) {
|
|
118810
119780
|
if (err.code !== "ENOENT") {
|
|
@@ -118812,7 +119782,7 @@ function probePathInUse(path10) {
|
|
|
118812
119782
|
}
|
|
118813
119783
|
}
|
|
118814
119784
|
try {
|
|
118815
|
-
const out =
|
|
119785
|
+
const out = execFileSync32("lsof", ["-t", path10], {
|
|
118816
119786
|
stdio: ["ignore", "pipe", "ignore"]
|
|
118817
119787
|
}).toString().trim();
|
|
118818
119788
|
probeRan = true;
|
|
@@ -118827,7 +119797,7 @@ function probePathInUse(path10) {
|
|
|
118827
119797
|
}
|
|
118828
119798
|
function hasUncommittedChanges(_repoPath, worktreePath) {
|
|
118829
119799
|
try {
|
|
118830
|
-
const out =
|
|
119800
|
+
const out = execFileSync32("git", ["-C", worktreePath, "status", "--porcelain"], { stdio: "pipe" }).toString();
|
|
118831
119801
|
return out.trim().length > 0;
|
|
118832
119802
|
} catch {
|
|
118833
119803
|
return true;
|
|
@@ -118835,7 +119805,7 @@ function hasUncommittedChanges(_repoPath, worktreePath) {
|
|
|
118835
119805
|
}
|
|
118836
119806
|
function defaultRemoveWorktree(repoPath, worktreePath) {
|
|
118837
119807
|
try {
|
|
118838
|
-
|
|
119808
|
+
execFileSync32("git", ["worktree", "remove", "--force", worktreePath], {
|
|
118839
119809
|
cwd: repoPath,
|
|
118840
119810
|
stdio: "pipe"
|
|
118841
119811
|
});
|
|
@@ -118848,7 +119818,7 @@ function planReaper(nowMs, deps = {}) {
|
|
|
118848
119818
|
const plan = [];
|
|
118849
119819
|
for (const record2 of listRecords()) {
|
|
118850
119820
|
const heartbeatAge = now2 - new Date(record2.heartbeatAt).getTime();
|
|
118851
|
-
const worktreeExists =
|
|
119821
|
+
const worktreeExists = existsSync103(record2.path);
|
|
118852
119822
|
if (!worktreeExists) {
|
|
118853
119823
|
plan.push({
|
|
118854
119824
|
record: record2,
|
|
@@ -118927,9 +119897,9 @@ function runReaper(nowMs, deps = {}) {
|
|
|
118927
119897
|
}
|
|
118928
119898
|
|
|
118929
119899
|
// src/worktree/gc.ts
|
|
118930
|
-
import { execFileSync as
|
|
119900
|
+
import { execFileSync as execFileSync33 } from "node:child_process";
|
|
118931
119901
|
import {
|
|
118932
|
-
existsSync as
|
|
119902
|
+
existsSync as existsSync104,
|
|
118933
119903
|
readFileSync as readFileSync92,
|
|
118934
119904
|
readdirSync as readdirSync35,
|
|
118935
119905
|
statSync as statSync55,
|
|
@@ -119054,7 +120024,7 @@ function classifyTaskTree(i2) {
|
|
|
119054
120024
|
return "skip-unmerged";
|
|
119055
120025
|
return "reap";
|
|
119056
120026
|
}
|
|
119057
|
-
var defaultExec3 = (file, args, cwd) =>
|
|
120027
|
+
var defaultExec3 = (file, args, cwd) => execFileSync33(file, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).toString();
|
|
119058
120028
|
function defaultPrSignal(repo, branch, exec) {
|
|
119059
120029
|
try {
|
|
119060
120030
|
const out = exec("gh", [
|
|
@@ -119108,7 +120078,7 @@ function trashRoot() {
|
|
|
119108
120078
|
return resolve58(process.env.SWITCHROOM_WORKTREE_TRASH ?? join104(homedir56(), ".switchroom", "worktree-gc-trash"));
|
|
119109
120079
|
}
|
|
119110
120080
|
function planGc(roots, deps = {}, taskTreeRoots = []) {
|
|
119111
|
-
const exists = deps.existsSync ??
|
|
120081
|
+
const exists = deps.existsSync ?? existsSync104;
|
|
119112
120082
|
const readDir = deps.readDir ?? ((p) => readdirSync35(p));
|
|
119113
120083
|
const readFile4 = deps.readFile ?? ((p) => readFileSync92(p, "utf8"));
|
|
119114
120084
|
const stat3 = deps.stat ?? ((p) => statSync55(p));
|
|
@@ -119427,7 +120397,7 @@ function selectPurgeTargets(entries, olderThanDays) {
|
|
|
119427
120397
|
return entries.filter((e) => e.ageDays >= olderThanDays).map((e) => e.path);
|
|
119428
120398
|
}
|
|
119429
120399
|
function listTrashEntries(nowMs, deps = {}) {
|
|
119430
|
-
const exists = deps.existsSync ??
|
|
120400
|
+
const exists = deps.existsSync ?? existsSync104;
|
|
119431
120401
|
const readDir = deps.readDir ?? ((p) => readdirSync35(p));
|
|
119432
120402
|
const root = trashRoot();
|
|
119433
120403
|
if (!exists(root))
|
|
@@ -119480,7 +120450,7 @@ function defaultTaskTreeRoots() {
|
|
|
119480
120450
|
for (const name of names.sort()) {
|
|
119481
120451
|
for (const sub of ["home/work", "home/workspace"]) {
|
|
119482
120452
|
const r = join104(agentsDir, name, sub);
|
|
119483
|
-
if (
|
|
120453
|
+
if (existsSync104(r))
|
|
119484
120454
|
roots.push(r);
|
|
119485
120455
|
}
|
|
119486
120456
|
}
|
|
@@ -120491,7 +121461,7 @@ function registerM365McpLauncherCommand(program3) {
|
|
|
120491
121461
|
// src/cli/notion-mcp-launcher.ts
|
|
120492
121462
|
init_scaffold_integration();
|
|
120493
121463
|
import { spawn as spawn7 } from "node:child_process";
|
|
120494
|
-
import { existsSync as
|
|
121464
|
+
import { existsSync as existsSync105, mkdirSync as mkdirSync60, writeFileSync as writeFileSync43 } from "node:fs";
|
|
120495
121465
|
import { dirname as dirname45 } from "node:path";
|
|
120496
121466
|
var HEARTBEAT_WRITE_INTERVAL_MS = 30 * 1000;
|
|
120497
121467
|
var DEFAULT_HEARTBEAT_PATH = "/state/agent/notion-launcher.heartbeat.json";
|
|
@@ -120503,7 +121473,7 @@ function buildNotionMcpArgs(opts) {
|
|
|
120503
121473
|
function defaultWriteHeartbeat(path10, contents) {
|
|
120504
121474
|
try {
|
|
120505
121475
|
const dir = dirname45(path10);
|
|
120506
|
-
if (!
|
|
121476
|
+
if (!existsSync105(dir))
|
|
120507
121477
|
mkdirSync60(dir, { recursive: true });
|
|
120508
121478
|
writeFileSync43(path10, contents);
|
|
120509
121479
|
} catch {}
|
|
@@ -120619,6 +121589,92 @@ import { mkdirSync as mkdirSync61, readFileSync as readFileSync93, renameSync as
|
|
|
120619
121589
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
120620
121590
|
import { join as join107 } from "node:path";
|
|
120621
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
|
|
120622
121678
|
init_hindsight();
|
|
120623
121679
|
var SHIM_SUPPORTED_PROTOCOL_VERSIONS = [
|
|
120624
121680
|
"2025-06-18",
|
|
@@ -120687,9 +121743,80 @@ var SYNTHESIZED_TOOL_TABLE = {
|
|
|
120687
121743
|
description: "Exact name of the directive to reactivate."
|
|
120688
121744
|
}
|
|
120689
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: {}
|
|
120690
121777
|
}
|
|
120691
121778
|
};
|
|
120692
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
|
+
}
|
|
120693
121820
|
function buildSynthesizedToolsList() {
|
|
120694
121821
|
return Object.entries(SYNTHESIZED_TOOL_TABLE).map(([name, { description, required, props }]) => ({
|
|
120695
121822
|
name,
|
|
@@ -120768,6 +121895,52 @@ var DEFAULT_RECALL_MAX_TOKENS = 1024;
|
|
|
120768
121895
|
var DEFAULT_REFLECT_MAX_TOKENS = 1024;
|
|
120769
121896
|
var DEFAULT_RECALL_BUDGET = "low";
|
|
120770
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
|
+
}
|
|
120771
121944
|
var VALID_BUDGETS = new Set(["low", "mid", "high"]);
|
|
120772
121945
|
function resolveClampMaxTokens(envVal, fallback) {
|
|
120773
121946
|
if (envVal === undefined || envVal === "")
|
|
@@ -121102,6 +122275,17 @@ class HindsightShim {
|
|
|
121102
122275
|
}
|
|
121103
122276
|
return this.directiveAdminCache;
|
|
121104
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
|
+
}
|
|
121105
122289
|
async synthesizedCall(name, rawArgs) {
|
|
121106
122290
|
const fail4 = (text) => ({
|
|
121107
122291
|
content: [{ type: "text", text }],
|
|
@@ -121114,26 +122298,23 @@ class HindsightShim {
|
|
|
121114
122298
|
if (unknown.length > 0) {
|
|
121115
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." : ""));
|
|
121116
122300
|
}
|
|
122301
|
+
const clean3 = {};
|
|
121117
122302
|
for (const key of Object.keys(args)) {
|
|
121118
|
-
|
|
121119
|
-
|
|
121120
|
-
|
|
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;
|
|
121121
122307
|
}
|
|
121122
122308
|
for (const req of spec.required) {
|
|
121123
|
-
if (
|
|
122309
|
+
if (clean3[req] === undefined) {
|
|
121124
122310
|
return fail4(`${name} requires '${req}'.`);
|
|
121125
122311
|
}
|
|
121126
122312
|
}
|
|
121127
122313
|
if (!this.opts.bankId) {
|
|
121128
|
-
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.");
|
|
121129
122315
|
}
|
|
121130
122316
|
try {
|
|
121131
|
-
const text =
|
|
121132
|
-
name: args.name,
|
|
121133
|
-
...typeof args.superseded_by === "string" ? { supersededBy: args.superseded_by } : {}
|
|
121134
|
-
}) : await this.directiveAdmin.reactivate({
|
|
121135
|
-
name: args.name
|
|
121136
|
-
});
|
|
122317
|
+
const text = await this.runSynthesized(name, clean3);
|
|
121137
122318
|
return { content: [{ type: "text", text }], isError: false };
|
|
121138
122319
|
} catch (err) {
|
|
121139
122320
|
if (err instanceof DirectivePairInconsistentError) {
|
|
@@ -121142,6 +122323,42 @@ class HindsightShim {
|
|
|
121142
122323
|
return fail4(`${name} failed: ${String(err)}`);
|
|
121143
122324
|
}
|
|
121144
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
|
+
}
|
|
121145
122362
|
async toolsCall(params) {
|
|
121146
122363
|
const called = params;
|
|
121147
122364
|
if (called?.name && SYNTHESIZED_TOOL_NAMES.includes(called.name)) {
|
|
@@ -121921,7 +123138,7 @@ function runRedactStdin() {
|
|
|
121921
123138
|
}
|
|
121922
123139
|
|
|
121923
123140
|
// src/cli/status-ask.ts
|
|
121924
|
-
import { readFileSync as readFileSync95, existsSync as
|
|
123141
|
+
import { readFileSync as readFileSync95, existsSync as existsSync106, readdirSync as readdirSync37 } from "node:fs";
|
|
121925
123142
|
import { join as join108 } from "node:path";
|
|
121926
123143
|
import { homedir as homedir57 } from "node:os";
|
|
121927
123144
|
|
|
@@ -122244,7 +123461,7 @@ function runReport(opts) {
|
|
|
122244
123461
|
function resolveSources(explicitPath) {
|
|
122245
123462
|
if (explicitPath != null && explicitPath.trim() !== "") {
|
|
122246
123463
|
const trimmed = explicitPath.trim();
|
|
122247
|
-
if (!
|
|
123464
|
+
if (!existsSync106(trimmed)) {
|
|
122248
123465
|
process.stderr.write(`status-ask report: ${trimmed}: file not found
|
|
122249
123466
|
`);
|
|
122250
123467
|
process.exit(1);
|
|
@@ -122260,7 +123477,7 @@ function resolveSources(explicitPath) {
|
|
|
122260
123477
|
} catch {
|
|
122261
123478
|
agentsDir = join108(homedir57(), ".switchroom", "agents");
|
|
122262
123479
|
}
|
|
122263
|
-
if (!
|
|
123480
|
+
if (!existsSync106(agentsDir))
|
|
122264
123481
|
return [];
|
|
122265
123482
|
const sources = [];
|
|
122266
123483
|
let entries;
|
|
@@ -122271,7 +123488,7 @@ function resolveSources(explicitPath) {
|
|
|
122271
123488
|
}
|
|
122272
123489
|
for (const name of entries) {
|
|
122273
123490
|
const path10 = join108(agentsDir, name, "runtime-metrics.jsonl");
|
|
122274
|
-
if (
|
|
123491
|
+
if (existsSync106(path10)) {
|
|
122275
123492
|
sources.push({ path: path10, agent: name });
|
|
122276
123493
|
}
|
|
122277
123494
|
}
|
|
@@ -122302,7 +123519,7 @@ init_paths();
|
|
|
122302
123519
|
import {
|
|
122303
123520
|
chownSync as chownSync12,
|
|
122304
123521
|
closeSync as closeSync18,
|
|
122305
|
-
existsSync as
|
|
123522
|
+
existsSync as existsSync107,
|
|
122306
123523
|
fsyncSync as fsyncSync8,
|
|
122307
123524
|
mkdirSync as mkdirSync62,
|
|
122308
123525
|
openSync as openSync18,
|
|
@@ -122432,7 +123649,7 @@ function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
|
122432
123649
|
const paths = overlayPathsFor(agent, opts);
|
|
122433
123650
|
return withAgentLock(paths, () => {
|
|
122434
123651
|
const finalPath = join109(paths.skillsDir, `${slug}.yaml`);
|
|
122435
|
-
if (!
|
|
123652
|
+
if (!existsSync107(finalPath))
|
|
122436
123653
|
return false;
|
|
122437
123654
|
unlinkSync22(finalPath);
|
|
122438
123655
|
return true;
|
|
@@ -122440,7 +123657,7 @@ function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
|
122440
123657
|
}
|
|
122441
123658
|
function listSkillsOverlayEntries(agent, opts = {}) {
|
|
122442
123659
|
const paths = overlayPathsFor(agent, opts);
|
|
122443
|
-
if (!
|
|
123660
|
+
if (!existsSync107(paths.skillsDir))
|
|
122444
123661
|
return [];
|
|
122445
123662
|
const out = [];
|
|
122446
123663
|
for (const name of readdirSync38(paths.skillsDir)) {
|
|
@@ -122459,7 +123676,7 @@ function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
|
122459
123676
|
const paths = overlayPathsFor(agent, opts);
|
|
122460
123677
|
return withAgentLock(paths, () => {
|
|
122461
123678
|
const finalPath = join109(paths.scheduleDir, `${slug}.yaml`);
|
|
122462
|
-
if (!
|
|
123679
|
+
if (!existsSync107(finalPath))
|
|
122463
123680
|
return false;
|
|
122464
123681
|
unlinkSync22(finalPath);
|
|
122465
123682
|
return true;
|
|
@@ -122467,7 +123684,7 @@ function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
|
122467
123684
|
}
|
|
122468
123685
|
function listOverlayEntries(agent, opts = {}) {
|
|
122469
123686
|
const paths = overlayPathsFor(agent, opts);
|
|
122470
|
-
if (!
|
|
123687
|
+
if (!existsSync107(paths.scheduleDir))
|
|
122471
123688
|
return [];
|
|
122472
123689
|
const out = [];
|
|
122473
123690
|
for (const name of readdirSync38(paths.scheduleDir)) {
|
|
@@ -122695,7 +123912,7 @@ function reconcileAgentCronOnly(agent) {
|
|
|
122695
123912
|
// src/cli/agent-config-pending.ts
|
|
122696
123913
|
import {
|
|
122697
123914
|
closeSync as closeSync19,
|
|
122698
|
-
existsSync as
|
|
123915
|
+
existsSync as existsSync108,
|
|
122699
123916
|
fsyncSync as fsyncSync9,
|
|
122700
123917
|
mkdirSync as mkdirSync63,
|
|
122701
123918
|
openSync as openSync19,
|
|
@@ -122752,7 +123969,7 @@ function stagePendingScheduleEntry(opts) {
|
|
|
122752
123969
|
}
|
|
122753
123970
|
function listPendingScheduleEntries(agent, opts = {}) {
|
|
122754
123971
|
const dir = pendingDir(agent, opts);
|
|
122755
|
-
if (!
|
|
123972
|
+
if (!existsSync108(dir))
|
|
122756
123973
|
return [];
|
|
122757
123974
|
const out = [];
|
|
122758
123975
|
for (const name of readdirSync39(dir).sort()) {
|
|
@@ -122761,7 +123978,7 @@ function listPendingScheduleEntries(agent, opts = {}) {
|
|
|
122761
123978
|
const stageId = name.slice(0, -".meta.json".length);
|
|
122762
123979
|
const metaPath = join110(dir, name);
|
|
122763
123980
|
const yamlPath = join110(dir, `${stageId}.yaml`);
|
|
122764
|
-
if (!
|
|
123981
|
+
if (!existsSync108(yamlPath))
|
|
122765
123982
|
continue;
|
|
122766
123983
|
try {
|
|
122767
123984
|
const meta = JSON.parse(readFileSync97(metaPath, "utf-8"));
|
|
@@ -122780,7 +123997,7 @@ function commitPendingScheduleEntry(opts) {
|
|
|
122780
123997
|
const slug = match.meta.entry.name ?? match.stageId;
|
|
122781
123998
|
const paths = overlayPathsFor(opts.agent, { root: opts.root });
|
|
122782
123999
|
const finalPath = join110(paths.scheduleDir, `${slug}.yaml`);
|
|
122783
|
-
if (
|
|
124000
|
+
if (existsSync108(finalPath)) {
|
|
122784
124001
|
return { committed: false, reason: "slug_collision" };
|
|
122785
124002
|
}
|
|
122786
124003
|
renameSync29(match.yamlPath, finalPath);
|
|
@@ -122802,8 +124019,8 @@ function denyPendingScheduleEntry(opts) {
|
|
|
122802
124019
|
}
|
|
122803
124020
|
|
|
122804
124021
|
// src/cli/agent-config-write.ts
|
|
122805
|
-
import { existsSync as
|
|
122806
|
-
import { execFileSync as
|
|
124022
|
+
import { existsSync as existsSync109, readFileSync as readFileSync98 } from "node:fs";
|
|
124023
|
+
import { execFileSync as execFileSync34 } from "node:child_process";
|
|
122807
124024
|
|
|
122808
124025
|
// src/scheduler/schedule-report.ts
|
|
122809
124026
|
var TIER_WEIGHT = { poll: 0, action: 0, cheap: 1, main: 5 };
|
|
@@ -123192,7 +124409,7 @@ function scheduleRemove(opts) {
|
|
|
123192
124409
|
}
|
|
123193
124410
|
let priorContent = null;
|
|
123194
124411
|
try {
|
|
123195
|
-
if (
|
|
124412
|
+
if (existsSync109(match.path))
|
|
123196
124413
|
priorContent = readFileSync98(match.path, "utf-8");
|
|
123197
124414
|
} catch {}
|
|
123198
124415
|
deleteOverlayEntry(agent, match.slug, { root: opts.root });
|
|
@@ -123396,10 +124613,10 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
123396
124613
|
}
|
|
123397
124614
|
let blob;
|
|
123398
124615
|
if (opts.jsonl) {
|
|
123399
|
-
blob =
|
|
124616
|
+
blob = existsSync109(opts.jsonl) ? readFileSync98(opts.jsonl, "utf-8") : "";
|
|
123400
124617
|
} else {
|
|
123401
124618
|
try {
|
|
123402
|
-
blob =
|
|
124619
|
+
blob = execFileSync34("docker", ["exec", `switchroom-${agent}`, "cat", "/state/agent/scheduler.jsonl"], {
|
|
123403
124620
|
encoding: "utf-8",
|
|
123404
124621
|
stdio: ["ignore", "pipe", "ignore"]
|
|
123405
124622
|
});
|
|
@@ -123428,7 +124645,7 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
123428
124645
|
|
|
123429
124646
|
// src/cli/agent-config-skill-write.ts
|
|
123430
124647
|
var import_yaml24 = __toESM(require_dist(), 1);
|
|
123431
|
-
import { existsSync as
|
|
124648
|
+
import { existsSync as existsSync110 } from "node:fs";
|
|
123432
124649
|
init_reconcile_default_skills();
|
|
123433
124650
|
init_agent_config();
|
|
123434
124651
|
var import_yaml25 = __toESM(require_dist(), 1);
|
|
@@ -123508,7 +124725,7 @@ function skillInstall(opts) {
|
|
|
123508
124725
|
}
|
|
123509
124726
|
const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
|
|
123510
124727
|
const skillPath = join111(poolDir, skillName);
|
|
123511
|
-
if (!
|
|
124728
|
+
if (!existsSync110(skillPath)) {
|
|
123512
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.`);
|
|
123513
124730
|
}
|
|
123514
124731
|
const yamlText = import_yaml24.stringify({ skills: [skillName] });
|
|
@@ -123678,7 +124895,7 @@ init_source();
|
|
|
123678
124895
|
import {
|
|
123679
124896
|
chmodSync as chmodSync18,
|
|
123680
124897
|
closeSync as closeSync20,
|
|
123681
|
-
existsSync as
|
|
124898
|
+
existsSync as existsSync111,
|
|
123682
124899
|
lstatSync as lstatSync14,
|
|
123683
124900
|
mkdirSync as mkdirSync64,
|
|
123684
124901
|
mkdtempSync as mkdtempSync5,
|
|
@@ -123887,7 +125104,7 @@ function validatePayload(name, files) {
|
|
|
123887
125104
|
function diffSummary(currentDir, files) {
|
|
123888
125105
|
const lines = [];
|
|
123889
125106
|
const currentFiles = {};
|
|
123890
|
-
if (
|
|
125107
|
+
if (existsSync111(currentDir)) {
|
|
123891
125108
|
const walk2 = (sub) => {
|
|
123892
125109
|
for (const ent of readdirSync40(sub, { withFileTypes: true })) {
|
|
123893
125110
|
const full = join112(sub, ent.name);
|
|
@@ -123923,7 +125140,7 @@ function diffSummary(currentDir, files) {
|
|
|
123923
125140
|
`);
|
|
123924
125141
|
}
|
|
123925
125142
|
function writePayload(poolDir, name, files) {
|
|
123926
|
-
if (!
|
|
125143
|
+
if (!existsSync111(poolDir)) {
|
|
123927
125144
|
mkdirSync64(poolDir, { recursive: true, mode: 493 });
|
|
123928
125145
|
}
|
|
123929
125146
|
const target = join112(poolDir, name);
|
|
@@ -123973,9 +125190,9 @@ function writePayload(poolDir, name, files) {
|
|
|
123973
125190
|
try {
|
|
123974
125191
|
rmSync20(staging, { recursive: true, force: true });
|
|
123975
125192
|
} catch {}
|
|
123976
|
-
if (oldRename &&
|
|
125193
|
+
if (oldRename && existsSync111(oldRename)) {
|
|
123977
125194
|
try {
|
|
123978
|
-
if (
|
|
125195
|
+
if (existsSync111(target)) {
|
|
123979
125196
|
rmSync20(target, { recursive: true, force: true });
|
|
123980
125197
|
}
|
|
123981
125198
|
renameSync30(oldRename, target);
|
|
@@ -123996,7 +125213,7 @@ function registerSkillCommand(program3) {
|
|
|
123996
125213
|
files = loadFromStdin();
|
|
123997
125214
|
} else {
|
|
123998
125215
|
const fromPath = resolve60(opts.from);
|
|
123999
|
-
if (!
|
|
125216
|
+
if (!existsSync111(fromPath)) {
|
|
124000
125217
|
fail4(`--from path does not exist: ${opts.from}`);
|
|
124001
125218
|
}
|
|
124002
125219
|
const st = statSync58(fromPath);
|
|
@@ -124057,7 +125274,7 @@ init_source();
|
|
|
124057
125274
|
import {
|
|
124058
125275
|
chmodSync as chmodSync19,
|
|
124059
125276
|
closeSync as closeSync21,
|
|
124060
|
-
existsSync as
|
|
125277
|
+
existsSync as existsSync112,
|
|
124061
125278
|
lstatSync as lstatSync15,
|
|
124062
125279
|
mkdirSync as mkdirSync65,
|
|
124063
125280
|
mkdtempSync as mkdtempSync6,
|
|
@@ -124080,14 +125297,14 @@ var PERSONAL_SKILLS_SUBPATH2 = "personal-skills";
|
|
|
124080
125297
|
function resolveConfigSkillsDir(agent) {
|
|
124081
125298
|
const override = process.env.SWITCHROOM_CONFIG_DIR;
|
|
124082
125299
|
const candidate = override ? resolve61(override) : join113(homedir59(), ".switchroom-config");
|
|
124083
|
-
if (!
|
|
125300
|
+
if (!existsSync112(candidate))
|
|
124084
125301
|
return null;
|
|
124085
125302
|
return join113(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH2);
|
|
124086
125303
|
}
|
|
124087
125304
|
var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
|
|
124088
125305
|
function sweepMirrorPriors(configSkillsRoot) {
|
|
124089
125306
|
try {
|
|
124090
|
-
if (!
|
|
125307
|
+
if (!existsSync112(configSkillsRoot))
|
|
124091
125308
|
return;
|
|
124092
125309
|
const now2 = Date.now();
|
|
124093
125310
|
for (const ent of readdirSync41(configSkillsRoot)) {
|
|
@@ -124123,7 +125340,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
124123
125340
|
}
|
|
124124
125341
|
if (liveSkillDir === null) {
|
|
124125
125342
|
sweepMirrorPriors(configSkillsRoot);
|
|
124126
|
-
if (
|
|
125343
|
+
if (existsSync112(dest)) {
|
|
124127
125344
|
const trash = join113(configSkillsRoot, `.${name}-trash-${Date.now()}`);
|
|
124128
125345
|
renameSync31(dest, trash);
|
|
124129
125346
|
}
|
|
@@ -124148,7 +125365,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
124148
125365
|
};
|
|
124149
125366
|
walk2(liveSkillDir, staging);
|
|
124150
125367
|
chmodSync19(staging, 493);
|
|
124151
|
-
if (
|
|
125368
|
+
if (existsSync112(dest)) {
|
|
124152
125369
|
const prior = join113(configSkillsRoot, `.${name}-prior-${Date.now()}`);
|
|
124153
125370
|
renameSync31(dest, prior);
|
|
124154
125371
|
}
|
|
@@ -124189,7 +125406,7 @@ function trashDir(agentsRoot, agent) {
|
|
|
124189
125406
|
}
|
|
124190
125407
|
function countPersonalSkills(agentsRoot, agent) {
|
|
124191
125408
|
const skillsDir = join113(agentsRoot, agent, ".claude", "skills");
|
|
124192
|
-
if (!
|
|
125409
|
+
if (!existsSync112(skillsDir))
|
|
124193
125410
|
return 0;
|
|
124194
125411
|
let n = 0;
|
|
124195
125412
|
for (const ent of readdirSync41(skillsDir, { withFileTypes: true })) {
|
|
@@ -124298,7 +125515,7 @@ function behavioralValidate(files) {
|
|
|
124298
125515
|
}
|
|
124299
125516
|
function sweepTrash(agentsRoot, agent) {
|
|
124300
125517
|
const trash = trashDir(agentsRoot, agent);
|
|
124301
|
-
if (!
|
|
125518
|
+
if (!existsSync112(trash))
|
|
124302
125519
|
return;
|
|
124303
125520
|
const now2 = Date.now();
|
|
124304
125521
|
for (const ent of readdirSync41(trash, { withFileTypes: true })) {
|
|
@@ -124361,9 +125578,9 @@ function writePersonalSkill(targetDir, files) {
|
|
|
124361
125578
|
try {
|
|
124362
125579
|
rmSync21(staging, { recursive: true, force: true });
|
|
124363
125580
|
} catch {}
|
|
124364
|
-
if (oldRename &&
|
|
125581
|
+
if (oldRename && existsSync112(oldRename)) {
|
|
124365
125582
|
try {
|
|
124366
|
-
if (
|
|
125583
|
+
if (existsSync112(targetDir)) {
|
|
124367
125584
|
rmSync21(targetDir, { recursive: true, force: true });
|
|
124368
125585
|
}
|
|
124369
125586
|
renameSync31(oldRename, targetDir);
|
|
@@ -124429,7 +125646,7 @@ function loadFiles(opts) {
|
|
|
124429
125646
|
return loadFromStdin2();
|
|
124430
125647
|
}
|
|
124431
125648
|
const p = resolve61(opts.from);
|
|
124432
|
-
if (!
|
|
125649
|
+
if (!existsSync112(p)) {
|
|
124433
125650
|
fail5(`--from path does not exist: ${opts.from}`);
|
|
124434
125651
|
}
|
|
124435
125652
|
const st = statSync59(p);
|
|
@@ -124491,7 +125708,7 @@ function resolveCloneSource(source, opts) {
|
|
|
124491
125708
|
const slug = m[2];
|
|
124492
125709
|
const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
|
|
124493
125710
|
const dir = join113(root, slug);
|
|
124494
|
-
if (!
|
|
125711
|
+
if (!existsSync112(dir)) {
|
|
124495
125712
|
fail5(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
|
|
124496
125713
|
}
|
|
124497
125714
|
const st = lstatSync15(dir);
|
|
@@ -124638,7 +125855,7 @@ function listPersonalAction(opts) {
|
|
|
124638
125855
|
sweepTrash(agentsRoot, agent);
|
|
124639
125856
|
const skillsDir = join113(agentsRoot, agent, ".claude", "skills");
|
|
124640
125857
|
const personal = [];
|
|
124641
|
-
if (
|
|
125858
|
+
if (existsSync112(skillsDir)) {
|
|
124642
125859
|
for (const ent of readdirSync41(skillsDir, { withFileTypes: true })) {
|
|
124643
125860
|
if (!ent.isDirectory())
|
|
124644
125861
|
continue;
|
|
@@ -124824,10 +126041,10 @@ function registerSelfImproveProposeSkillCommand(program3) {
|
|
|
124824
126041
|
import { createConnection as createConnection5 } from "node:net";
|
|
124825
126042
|
import { homedir as homedir61 } from "node:os";
|
|
124826
126043
|
import { join as join118, resolve as resolve62, sep as sep6 } from "node:path";
|
|
124827
|
-
import { existsSync as
|
|
126044
|
+
import { existsSync as existsSync117, readFileSync as readFileSync106, realpathSync as realpathSync10 } from "node:fs";
|
|
124828
126045
|
|
|
124829
126046
|
// src/self-improve/apply-guard.ts
|
|
124830
|
-
import { existsSync as
|
|
126047
|
+
import { existsSync as existsSync115, lstatSync as lstatSync16, readFileSync as readFileSync104, statSync as statSync61 } from "node:fs";
|
|
124831
126048
|
|
|
124832
126049
|
// src/self-improve/config.ts
|
|
124833
126050
|
function intEnv(name, def) {
|
|
@@ -124848,7 +126065,7 @@ function resolveSelfImproveConfig() {
|
|
|
124848
126065
|
}
|
|
124849
126066
|
|
|
124850
126067
|
// src/self-improve/eval-gate.ts
|
|
124851
|
-
import { existsSync as
|
|
126068
|
+
import { existsSync as existsSync113, readFileSync as readFileSync102, readdirSync as readdirSync42, statSync as statSync60 } from "node:fs";
|
|
124852
126069
|
import { join as join115 } from "node:path";
|
|
124853
126070
|
import { spawnSync as spawnSync26 } from "node:child_process";
|
|
124854
126071
|
function evalsJsonPath(skillDir) {
|
|
@@ -124856,7 +126073,7 @@ function evalsJsonPath(skillDir) {
|
|
|
124856
126073
|
}
|
|
124857
126074
|
function skillHasEvals(skillDir) {
|
|
124858
126075
|
const p = evalsJsonPath(skillDir);
|
|
124859
|
-
if (!
|
|
126076
|
+
if (!existsSync113(p))
|
|
124860
126077
|
return false;
|
|
124861
126078
|
try {
|
|
124862
126079
|
const parsed = JSON.parse(readFileSync102(p, "utf-8"));
|
|
@@ -124867,7 +126084,7 @@ function skillHasEvals(skillDir) {
|
|
|
124867
126084
|
}
|
|
124868
126085
|
function aggregateBenchmark(benchmarkDir, repoRoot, pythonBin = process.env.SWITCHROOM_PYTHON ?? "python3") {
|
|
124869
126086
|
const script = join115(repoRoot, "skills", "skill-creator", "scripts", "aggregate_benchmark.py");
|
|
124870
|
-
if (!
|
|
126087
|
+
if (!existsSync113(script)) {
|
|
124871
126088
|
return { ok: false, error: `aggregate_benchmark.py not found at ${script}` };
|
|
124872
126089
|
}
|
|
124873
126090
|
const r = spawnSync26(pythonBin, [script, benchmarkDir], {
|
|
@@ -124899,7 +126116,7 @@ function isBenchmarkDir(dir) {
|
|
|
124899
126116
|
if (hasEvalDirs(dir))
|
|
124900
126117
|
return true;
|
|
124901
126118
|
const runs = join115(dir, "runs");
|
|
124902
|
-
return
|
|
126119
|
+
return existsSync113(runs) && hasEvalDirs(runs);
|
|
124903
126120
|
}
|
|
124904
126121
|
function resolveBenchmarkDir(baseDir, explicit) {
|
|
124905
126122
|
if (explicit != null && explicit.length > 0) {
|
|
@@ -124978,7 +126195,7 @@ function floatEnv(name, def) {
|
|
|
124978
126195
|
import {
|
|
124979
126196
|
closeSync as closeSync22,
|
|
124980
126197
|
copyFileSync as copyFileSync15,
|
|
124981
|
-
existsSync as
|
|
126198
|
+
existsSync as existsSync114,
|
|
124982
126199
|
mkdirSync as mkdirSync66,
|
|
124983
126200
|
openSync as openSync22,
|
|
124984
126201
|
readFileSync as readFileSync103,
|
|
@@ -125125,7 +126342,7 @@ function caseFingerprint(prompt) {
|
|
|
125125
126342
|
}
|
|
125126
126343
|
function readEvalsDoc(skillDir) {
|
|
125127
126344
|
const p = evalsJsonPath(skillDir);
|
|
125128
|
-
if (!
|
|
126345
|
+
if (!existsSync114(p))
|
|
125129
126346
|
return null;
|
|
125130
126347
|
try {
|
|
125131
126348
|
const parsed = JSON.parse(readFileSync103(p, "utf-8"));
|
|
@@ -125149,7 +126366,7 @@ function appendEvalCase(skillDir, slug, input) {
|
|
|
125149
126366
|
return { ok: false, reason: parsed.error };
|
|
125150
126367
|
const ec = parsed.case;
|
|
125151
126368
|
const existingDoc = readEvalsDoc(skillDir);
|
|
125152
|
-
if (existingDoc === null &&
|
|
126369
|
+
if (existingDoc === null && existsSync114(evalsJsonPath(skillDir))) {
|
|
125153
126370
|
return {
|
|
125154
126371
|
ok: false,
|
|
125155
126372
|
reason: "evals.json exists but is unparseable or malformed \u2014 refusing to " + "overwrite it (fix or remove the file first)"
|
|
@@ -125181,7 +126398,7 @@ function heldOutPath(stateDir) {
|
|
|
125181
126398
|
}
|
|
125182
126399
|
function heldOutHasFingerprint(stateDir, slug, fp) {
|
|
125183
126400
|
const path10 = heldOutPath(stateDir);
|
|
125184
|
-
if (!
|
|
126401
|
+
if (!existsSync114(path10))
|
|
125185
126402
|
return false;
|
|
125186
126403
|
let text;
|
|
125187
126404
|
try {
|
|
@@ -125233,7 +126450,7 @@ function baselineEvalsPath(stateDir, slug) {
|
|
|
125233
126450
|
}
|
|
125234
126451
|
function evalsSha256(skillDir) {
|
|
125235
126452
|
const p = evalsJsonPath(skillDir);
|
|
125236
|
-
if (!
|
|
126453
|
+
if (!existsSync114(p))
|
|
125237
126454
|
return null;
|
|
125238
126455
|
try {
|
|
125239
126456
|
return createHash21("sha256").update(readFileSync103(p)).digest("hex");
|
|
@@ -125243,7 +126460,7 @@ function evalsSha256(skillDir) {
|
|
|
125243
126460
|
}
|
|
125244
126461
|
function readManifest(stateDir) {
|
|
125245
126462
|
const p = manifestPath(stateDir);
|
|
125246
|
-
if (!
|
|
126463
|
+
if (!existsSync114(p))
|
|
125247
126464
|
return {};
|
|
125248
126465
|
try {
|
|
125249
126466
|
const parsed = JSON.parse(readFileSync103(p, "utf-8"));
|
|
@@ -125285,7 +126502,7 @@ function ownsSkill(skillDir) {
|
|
|
125285
126502
|
// src/self-improve/eval-case-proposals.ts
|
|
125286
126503
|
import {
|
|
125287
126504
|
closeSync as closeSync23,
|
|
125288
|
-
existsSync as
|
|
126505
|
+
existsSync as existsSync116,
|
|
125289
126506
|
mkdirSync as mkdirSync67,
|
|
125290
126507
|
openSync as openSync23,
|
|
125291
126508
|
readFileSync as readFileSync105,
|
|
@@ -125301,7 +126518,7 @@ function isRecord2(v) {
|
|
|
125301
126518
|
}
|
|
125302
126519
|
function readEvalCaseProposals(stateDir) {
|
|
125303
126520
|
const p = proposalsPath(stateDir);
|
|
125304
|
-
if (!
|
|
126521
|
+
if (!existsSync116(p))
|
|
125305
126522
|
return [];
|
|
125306
126523
|
let raw;
|
|
125307
126524
|
try {
|
|
@@ -125498,7 +126715,7 @@ function registerApply(parent) {
|
|
|
125498
126715
|
}));
|
|
125499
126716
|
process.exit(0);
|
|
125500
126717
|
}
|
|
125501
|
-
if (!
|
|
126718
|
+
if (!existsSync117(proposal.skill_dir)) {
|
|
125502
126719
|
fail7(`skill dir gone: ${proposal.skill_dir}`);
|
|
125503
126720
|
}
|
|
125504
126721
|
const { dir, resolvedDir } = authorizeApplyTarget(proposal.skill_dir, proposal.skill_slug);
|
|
@@ -125611,7 +126828,7 @@ function registerSelfImproveBenchCommand(program3) {
|
|
|
125611
126828
|
init_esm();
|
|
125612
126829
|
init_helpers();
|
|
125613
126830
|
var import_yaml26 = __toESM(require_dist(), 1);
|
|
125614
|
-
import { existsSync as
|
|
126831
|
+
import { existsSync as existsSync118, readdirSync as readdirSync44, readFileSync as readFileSync107, statSync as statSync62 } from "node:fs";
|
|
125615
126832
|
import { homedir as homedir63 } from "node:os";
|
|
125616
126833
|
import { join as join120, resolve as resolve63 } from "node:path";
|
|
125617
126834
|
var PERSONAL_PREFIX2 = "personal-";
|
|
@@ -125628,7 +126845,7 @@ function defaultBundledRoot2() {
|
|
|
125628
126845
|
}
|
|
125629
126846
|
function readSkillFrontmatter(skillDir) {
|
|
125630
126847
|
const mdPath = join120(skillDir, "SKILL.md");
|
|
125631
|
-
if (!
|
|
126848
|
+
if (!existsSync118(mdPath))
|
|
125632
126849
|
return null;
|
|
125633
126850
|
let content;
|
|
125634
126851
|
try {
|
|
@@ -125672,7 +126889,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
125672
126889
|
if (!AGENT_NAME_RE3.test(agent))
|
|
125673
126890
|
return [];
|
|
125674
126891
|
const skillsDir = join120(agentsRoot, agent, ".claude/skills");
|
|
125675
|
-
if (!
|
|
126892
|
+
if (!existsSync118(skillsDir))
|
|
125676
126893
|
return [];
|
|
125677
126894
|
const out = [];
|
|
125678
126895
|
let entries;
|
|
@@ -125710,7 +126927,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
125710
126927
|
return out;
|
|
125711
126928
|
}
|
|
125712
126929
|
function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
125713
|
-
if (!
|
|
126930
|
+
if (!existsSync118(sharedRoot))
|
|
125714
126931
|
return [];
|
|
125715
126932
|
const out = [];
|
|
125716
126933
|
let entries;
|
|
@@ -125748,7 +126965,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
|
125748
126965
|
return out;
|
|
125749
126966
|
}
|
|
125750
126967
|
function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
125751
|
-
if (!
|
|
126968
|
+
if (!existsSync118(bundledRoot))
|
|
125752
126969
|
return [];
|
|
125753
126970
|
const out = [];
|
|
125754
126971
|
let entries;
|
|
@@ -125907,7 +127124,7 @@ init_helpers();
|
|
|
125907
127124
|
init_docker_socket();
|
|
125908
127125
|
init_operator_uid();
|
|
125909
127126
|
import {
|
|
125910
|
-
existsSync as
|
|
127127
|
+
existsSync as existsSync120,
|
|
125911
127128
|
mkdirSync as mkdirSync68,
|
|
125912
127129
|
readdirSync as readdirSync45,
|
|
125913
127130
|
writeFileSync as writeFileSync49,
|
|
@@ -126290,7 +127507,7 @@ function resolveHostdSkillsTarget(hostHome) {
|
|
|
126290
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.`);
|
|
126291
127508
|
return;
|
|
126292
127509
|
}
|
|
126293
|
-
if (!
|
|
127510
|
+
if (!existsSync120(target)) {
|
|
126294
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.`);
|
|
126295
127512
|
return;
|
|
126296
127513
|
}
|
|
@@ -126304,7 +127521,7 @@ function hostdComposePath() {
|
|
|
126304
127521
|
}
|
|
126305
127522
|
function backupExistingCompose() {
|
|
126306
127523
|
const p = hostdComposePath();
|
|
126307
|
-
if (!
|
|
127524
|
+
if (!existsSync120(p))
|
|
126308
127525
|
return null;
|
|
126309
127526
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
126310
127527
|
const bak = `${p}.bak-${ts}`;
|
|
@@ -126403,7 +127620,7 @@ function doStatus() {
|
|
|
126403
127620
|
const composeYml = hostdComposePath();
|
|
126404
127621
|
console.log(source_default.bold("switchroom-hostd"));
|
|
126405
127622
|
console.log("");
|
|
126406
|
-
if (!
|
|
127623
|
+
if (!existsSync120(composeYml)) {
|
|
126407
127624
|
console.log(source_default.yellow(" compose: not installed"));
|
|
126408
127625
|
console.log(source_default.dim(" run `switchroom hostd install` to set up."));
|
|
126409
127626
|
return;
|
|
@@ -126424,14 +127641,14 @@ function doStatus() {
|
|
|
126424
127641
|
} else {
|
|
126425
127642
|
console.log(source_default.green(` container: ${ps.stdout.trim()}`));
|
|
126426
127643
|
}
|
|
126427
|
-
if (
|
|
127644
|
+
if (existsSync120(dir)) {
|
|
126428
127645
|
const entries = [];
|
|
126429
127646
|
try {
|
|
126430
127647
|
for (const name of readdirSync45(dir)) {
|
|
126431
127648
|
if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
|
|
126432
127649
|
continue;
|
|
126433
127650
|
const sockPath = join121(dir, name, "sock");
|
|
126434
|
-
if (
|
|
127651
|
+
if (existsSync120(sockPath)) {
|
|
126435
127652
|
const st = statSync63(sockPath);
|
|
126436
127653
|
if ((st.mode & 61440) === 49152) {
|
|
126437
127654
|
entries.push(`${name} \u2192 ${sockPath}`);
|
|
@@ -126450,7 +127667,7 @@ function doStatus() {
|
|
|
126450
127667
|
}
|
|
126451
127668
|
function doUninstall() {
|
|
126452
127669
|
const composeYml = hostdComposePath();
|
|
126453
|
-
if (!
|
|
127670
|
+
if (!existsSync120(composeYml)) {
|
|
126454
127671
|
console.log(source_default.yellow(" No hostd install detected (no compose file at this path)."));
|
|
126455
127672
|
return;
|
|
126456
127673
|
}
|
|
@@ -126474,7 +127691,7 @@ function registerHostdCommand(program3) {
|
|
|
126474
127691
|
hostd.command("uninstall").description("Stop the hostd container. Leaves the compose file in place for re-install.").action(() => doUninstall());
|
|
126475
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) => {
|
|
126476
127693
|
const logPath = opts.path ?? defaultAuditLogPath2();
|
|
126477
|
-
if (!
|
|
127694
|
+
if (!existsSync120(logPath)) {
|
|
126478
127695
|
console.error(source_default.yellow(`Audit log not found at ${logPath}.`) + source_default.gray(`
|
|
126479
127696
|
The log is created when hostd handles its first privileged-verb request.`));
|
|
126480
127697
|
return;
|
|
@@ -126523,7 +127740,7 @@ init_source();
|
|
|
126523
127740
|
init_helpers();
|
|
126524
127741
|
init_operator_uid();
|
|
126525
127742
|
init_compose();
|
|
126526
|
-
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";
|
|
126527
127744
|
import { homedir as homedir65 } from "node:os";
|
|
126528
127745
|
import { join as join122 } from "node:path";
|
|
126529
127746
|
import { spawnSync as spawnSync30 } from "node:child_process";
|
|
@@ -126634,7 +127851,7 @@ function webdComposePath() {
|
|
|
126634
127851
|
}
|
|
126635
127852
|
function backupExistingCompose2() {
|
|
126636
127853
|
const p = webdComposePath();
|
|
126637
|
-
if (!
|
|
127854
|
+
if (!existsSync121(p))
|
|
126638
127855
|
return null;
|
|
126639
127856
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
126640
127857
|
const bak = `${p}.bak-${ts}`;
|
|
@@ -126742,7 +127959,7 @@ function doStatus2() {
|
|
|
126742
127959
|
const composeYml = webdComposePath();
|
|
126743
127960
|
console.log(source_default.bold("switchroom-web"));
|
|
126744
127961
|
console.log("");
|
|
126745
|
-
if (!
|
|
127962
|
+
if (!existsSync121(composeYml)) {
|
|
126746
127963
|
console.log(source_default.yellow(" compose: not installed"));
|
|
126747
127964
|
console.log(source_default.dim(" run `switchroom webd install` to set up."));
|
|
126748
127965
|
return;
|
|
@@ -126766,7 +127983,7 @@ function doStatus2() {
|
|
|
126766
127983
|
}
|
|
126767
127984
|
function doUninstall2() {
|
|
126768
127985
|
const composeYml = webdComposePath();
|
|
126769
|
-
if (!
|
|
127986
|
+
if (!existsSync121(composeYml)) {
|
|
126770
127987
|
console.log(source_default.yellow(" No web-service install detected (no compose file at this path)."));
|
|
126771
127988
|
return;
|
|
126772
127989
|
}
|
|
@@ -127252,9 +128469,9 @@ function printDeepDiveBrief(targets) {
|
|
|
127252
128469
|
|
|
127253
128470
|
// src/cli/hindsight-watch.ts
|
|
127254
128471
|
init_source();
|
|
127255
|
-
import { existsSync as
|
|
128472
|
+
import { existsSync as existsSync126, readdirSync as readdirSync50 } from "node:fs";
|
|
127256
128473
|
import { homedir as homedir69 } from "node:os";
|
|
127257
|
-
import { join as
|
|
128474
|
+
import { join as join126, resolve as resolve66 } from "node:path";
|
|
127258
128475
|
|
|
127259
128476
|
// src/host-control/config-degraded.ts
|
|
127260
128477
|
import { connect as connect3 } from "node:net";
|
|
@@ -127451,16 +128668,16 @@ function readLlmSignals(series) {
|
|
|
127451
128668
|
}
|
|
127452
128669
|
|
|
127453
128670
|
// src/hindsight-watch/recall-log.ts
|
|
127454
|
-
import { closeSync as closeSync24, existsSync as
|
|
127455
|
-
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";
|
|
127456
128673
|
var RECALL_WINDOW_ROWS = 200;
|
|
127457
128674
|
var RECALL_MAX_TAIL_BYTES = 1024 * 1024;
|
|
127458
128675
|
var RECALL_MAX_ROW_AGE_MS = 24 * 60 * 60 * 1000;
|
|
127459
128676
|
function recallLogPath2(agentDir) {
|
|
127460
|
-
return
|
|
128677
|
+
return join125(agentDir, ".claude", "plugins", "data", "hindsight-memory-inline", "state", "recall_log.jsonl");
|
|
127461
128678
|
}
|
|
127462
128679
|
function readRecallLogTail2(path10, windowRows = RECALL_WINDOW_ROWS) {
|
|
127463
|
-
if (!
|
|
128680
|
+
if (!existsSync125(path10))
|
|
127464
128681
|
return [];
|
|
127465
128682
|
let text;
|
|
127466
128683
|
let truncatedHead = false;
|
|
@@ -127605,7 +128822,7 @@ function probeRecallLogs(agentsDir, now2, windowRows = RECALL_WINDOW_ROWS, maxAg
|
|
|
127605
128822
|
const all = [];
|
|
127606
128823
|
let agents = 0;
|
|
127607
128824
|
for (const name of names) {
|
|
127608
|
-
const rows = withinWindow(readRecallLogTail2(recallLogPath2(
|
|
128825
|
+
const rows = withinWindow(readRecallLogTail2(recallLogPath2(join125(agentsDir, name)), windowRows), now2, maxAgeMs);
|
|
127609
128826
|
if (rows.length > 0)
|
|
127610
128827
|
agents++;
|
|
127611
128828
|
all.push(...rows);
|
|
@@ -128663,7 +129880,7 @@ function registerHindsightWatchCommand(program3) {
|
|
|
128663
129880
|
process.exitCode = runInstallCron(opts.cronUser);
|
|
128664
129881
|
return;
|
|
128665
129882
|
}
|
|
128666
|
-
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");
|
|
128667
129884
|
const statePath = opts.state ?? defaultStatePath2();
|
|
128668
129885
|
const log = (m) => {
|
|
128669
129886
|
process.stderr.write(`${m}
|
|
@@ -128779,7 +129996,7 @@ function collectRelayCandidates(agentsDir) {
|
|
|
128779
129996
|
const candidates = [];
|
|
128780
129997
|
for (const name of readdirSync50(agentsDir).sort()) {
|
|
128781
129998
|
const sock = resolve66(agentsDir, name, "telegram", "gateway.sock");
|
|
128782
|
-
if (
|
|
129999
|
+
if (existsSync126(sock))
|
|
128783
130000
|
candidates.push({ agent: name, sock });
|
|
128784
130001
|
}
|
|
128785
130002
|
return orderRelayCandidates(candidates);
|
|
@@ -128802,7 +130019,7 @@ async function notifyOperator(agentsDir, text, log) {
|
|
|
128802
130019
|
// src/cli/hindsight-bench.ts
|
|
128803
130020
|
init_source();
|
|
128804
130021
|
import { mkdirSync as mkdirSync71, readFileSync as readFileSync113, writeFileSync as writeFileSync52 } from "node:fs";
|
|
128805
|
-
import { dirname as
|
|
130022
|
+
import { dirname as dirname52, resolve as resolve67 } from "node:path";
|
|
128806
130023
|
|
|
128807
130024
|
// src/hindsight-bench/anonymise.ts
|
|
128808
130025
|
function pseudonym(n, width) {
|
|
@@ -129930,7 +131147,7 @@ function readResult(path10) {
|
|
|
129930
131147
|
}
|
|
129931
131148
|
function writeOut(path10, body) {
|
|
129932
131149
|
const abs = resolve67(path10);
|
|
129933
|
-
mkdirSync71(
|
|
131150
|
+
mkdirSync71(dirname52(abs), { recursive: true });
|
|
129934
131151
|
writeFileSync52(abs, body);
|
|
129935
131152
|
process.stderr.write(`${source_default.green("\u2713")} wrote ${abs}
|
|
129936
131153
|
`);
|
|
@@ -130135,13 +131352,13 @@ async function runMeasureMode(opts) {
|
|
|
130135
131352
|
|
|
130136
131353
|
// src/cli/openrouter-watch.ts
|
|
130137
131354
|
init_source();
|
|
130138
|
-
import { existsSync as
|
|
131355
|
+
import { existsSync as existsSync128, readdirSync as readdirSync51 } from "node:fs";
|
|
130139
131356
|
import { homedir as homedir71 } from "node:os";
|
|
130140
|
-
import { join as
|
|
131357
|
+
import { join as join127, resolve as resolve69 } from "node:path";
|
|
130141
131358
|
|
|
130142
131359
|
// src/openrouter/install-cron.ts
|
|
130143
|
-
import { existsSync as
|
|
130144
|
-
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";
|
|
130145
131362
|
var CRON_PATH3 = "/etc/cron.d/openrouter-watch";
|
|
130146
131363
|
var CRON_SCHEDULE2 = "17 * * * *";
|
|
130147
131364
|
var CRON_LOG_PATH3 = "/var/log/openrouter-watch.log";
|
|
@@ -130158,14 +131375,14 @@ function renderCron3(opts) {
|
|
|
130158
131375
|
function installCron3(opts) {
|
|
130159
131376
|
const path10 = opts.path ?? CRON_PATH3;
|
|
130160
131377
|
const content = renderCron3(opts);
|
|
130161
|
-
if (
|
|
131378
|
+
if (existsSync127(path10)) {
|
|
130162
131379
|
try {
|
|
130163
131380
|
if (readFileSync114(path10, "utf8") === content) {
|
|
130164
131381
|
return { status: "unchanged", path: path10, content };
|
|
130165
131382
|
}
|
|
130166
131383
|
} catch {}
|
|
130167
131384
|
}
|
|
130168
|
-
mkdirSync72(
|
|
131385
|
+
mkdirSync72(dirname53(path10), { recursive: true });
|
|
130169
131386
|
const tmp = `${path10}.${process.pid}.tmp`;
|
|
130170
131387
|
writeFileSync53(tmp, content, { mode: 420 });
|
|
130171
131388
|
renameSync33(tmp, path10);
|
|
@@ -130174,7 +131391,7 @@ function installCron3(opts) {
|
|
|
130174
131391
|
// src/openrouter/state.ts
|
|
130175
131392
|
import { mkdirSync as mkdirSync73, readFileSync as readFileSync115, renameSync as renameSync34, writeFileSync as writeFileSync54 } from "node:fs";
|
|
130176
131393
|
import { homedir as homedir70 } from "node:os";
|
|
130177
|
-
import { dirname as
|
|
131394
|
+
import { dirname as dirname54, resolve as resolve68 } from "node:path";
|
|
130178
131395
|
function defaultStatePath3(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir70()) {
|
|
130179
131396
|
return resolve68(home2, ".switchroom", "openrouter-watch", "state.json");
|
|
130180
131397
|
}
|
|
@@ -130201,7 +131418,7 @@ function loadState2(path10) {
|
|
|
130201
131418
|
return out;
|
|
130202
131419
|
}
|
|
130203
131420
|
function saveState2(path10, state) {
|
|
130204
|
-
mkdirSync73(
|
|
131421
|
+
mkdirSync73(dirname54(path10), { recursive: true, mode: 448 });
|
|
130205
131422
|
const tmp = `${path10}.${process.pid}.tmp`;
|
|
130206
131423
|
const payload = { v: 1, ...state };
|
|
130207
131424
|
writeFileSync54(tmp, JSON.stringify(payload, null, 2) + `
|
|
@@ -130292,7 +131509,7 @@ function registerOpenRouterWatchCommand(program3) {
|
|
|
130292
131509
|
process.exitCode = runInstallCron2(opts.cronUser);
|
|
130293
131510
|
return;
|
|
130294
131511
|
}
|
|
130295
|
-
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");
|
|
130296
131513
|
const statePath = opts.state ?? defaultStatePath3();
|
|
130297
131514
|
const log = (m) => {
|
|
130298
131515
|
process.stderr.write(`${m}
|
|
@@ -130391,7 +131608,7 @@ async function notifyOperator2(agentsDir, text, log) {
|
|
|
130391
131608
|
try {
|
|
130392
131609
|
for (const name of readdirSync51(agentsDir).sort()) {
|
|
130393
131610
|
const sock = resolve69(agentsDir, name, "telegram", "gateway.sock");
|
|
130394
|
-
if (
|
|
131611
|
+
if (existsSync128(sock))
|
|
130395
131612
|
candidates.push({ agent: name, sock });
|
|
130396
131613
|
}
|
|
130397
131614
|
} catch (e) {
|
|
@@ -130412,7 +131629,7 @@ init_sync();
|
|
|
130412
131629
|
init_install_cron2();
|
|
130413
131630
|
init_flock();
|
|
130414
131631
|
import { homedir as homedir72 } from "node:os";
|
|
130415
|
-
import { join as
|
|
131632
|
+
import { join as join128 } from "node:path";
|
|
130416
131633
|
function switchroomHome() {
|
|
130417
131634
|
return process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir72();
|
|
130418
131635
|
}
|
|
@@ -130424,9 +131641,9 @@ function resolveSyncOptions(configPath, opts) {
|
|
|
130424
131641
|
cfg = loaded.config_repo;
|
|
130425
131642
|
} catch {}
|
|
130426
131643
|
const home2 = switchroomHome();
|
|
130427
|
-
const repoPath = opts.path ? resolvePath(opts.path) : cfg.path ? resolvePath(cfg.path) :
|
|
130428
|
-
const livePath = process.env.SWITCHROOM_CONFIG ??
|
|
130429
|
-
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");
|
|
130430
131647
|
const push = opts.push === false ? false : cfg.push ?? true;
|
|
130431
131648
|
const remote = opts.remote ?? cfg.remote ?? "origin";
|
|
130432
131649
|
const requirePrivate = opts.requirePrivate ?? cfg.require_private ?? true;
|