switchroom 0.20.6 → 0.20.8
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 +106 -12
- package/dist/auth-broker/index.js +71 -9
- package/dist/cli/notion-write-pretool.mjs +68 -7
- package/dist/cli/switchroom.js +387 -30
- package/dist/host-control/main.js +72 -10
- package/dist/vault/approvals/kernel-server.js +71 -9
- package/dist/vault/broker/server.js +71 -9
- package/package.json +1 -1
- package/telegram-plugin/bridge/ipc-client.ts +17 -1
- package/telegram-plugin/dist/bridge/bridge.js +5 -2
- package/telegram-plugin/dist/gateway/gateway.js +342 -120
- package/telegram-plugin/dist/server.js +5 -2
- package/telegram-plugin/gateway/boot-reason.ts +61 -0
- package/telegram-plugin/gateway/cron-session.ts +66 -0
- package/telegram-plugin/gateway/gateway.ts +50 -54
- package/telegram-plugin/gateway/narrative-lane.ts +21 -1
- package/telegram-plugin/gateway/obligation-ledger.ts +9 -0
- package/telegram-plugin/gateway/obligation-wiring.ts +19 -4
- package/telegram-plugin/gateway/pending-inbound-buffer.ts +43 -3
- package/telegram-plugin/gateway/represent-delivery-guard.ts +188 -0
- package/telegram-plugin/gateway/stream-render.ts +24 -2
- package/telegram-plugin/tests/boot-card-reason.test.ts +88 -0
- package/telegram-plugin/tests/cron-bridge-drain-spool-ack.test.ts +150 -0
- package/telegram-plugin/tests/ipc-client-reconnect-rejection.test.ts +70 -0
- package/telegram-plugin/tests/narrative-lane-golden.test.ts +86 -0
- package/telegram-plugin/tests/pending-inbound-buffer.test.ts +145 -9
- package/telegram-plugin/tests/queued-card-surface.test.ts +66 -0
- package/telegram-plugin/tests/represent-guard.test.ts +295 -0
- package/telegram-plugin/tests/turn-flush-safety.test.ts +83 -0
- package/telegram-plugin/turn-flush-safety.ts +79 -0
|
@@ -11419,7 +11419,7 @@ var HindsightConfigSchema = exports_external.object({
|
|
|
11419
11419
|
reflect: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `reflect` LLM op (synthesis / mental-model " + "refresh). Emits `HINDSIGHT_API_REFLECT_LLM_*`. Absent → uses global."),
|
|
11420
11420
|
consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent → global.")
|
|
11421
11421
|
}).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."),
|
|
11422
|
-
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 — 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 — a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP — 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 — 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 — 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 — 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 — 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 — 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 — 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 — 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 — 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_TEMPORAL_LANGUAGES — the language set dateparser is " + "restricted to during temporal query analysis, made live by switchroom's " + "temporal-language image patch (which ended a 200+-locale auto-detection " + "pass that blocked the shared asyncio loop on every recall); " + "comma-separated, unset means the image's baked `en`, set e.g. `en,es` to " + "restore i18n parsing), 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 — 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 — a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
|
|
11422
|
+
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 — 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 — a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP — 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 — 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 — 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 — 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 — 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 — 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 — 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 — 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 — 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_TEMPORAL_LANGUAGES — the language set dateparser is " + "restricted to during temporal query analysis, made live by switchroom's " + "temporal-language image patch (which ended a 200+-locale auto-detection " + "pass that blocked the shared asyncio loop on every recall); " + "comma-separated, unset means the image's baked `en`, set e.g. `en,es` to " + "restore i18n parsing; and " + "HINDSIGHT_API_TEMPORAL_MAX_QUERY_CHARS — 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), 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 — 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 — a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
|
|
11423
11423
|
});
|
|
11424
11424
|
var MicrosoftWorkspaceConfigSchema = exports_external.object({
|
|
11425
11425
|
microsoft_client_id: exports_external.string().min(1).optional().describe("Microsoft OAuth application (client) ID from Entra portal " + "(literal string or vault reference e.g. " + "'vault:microsoft-oauth-client-id'). OPTIONAL — omit it to use " + "switchroom's shipped default Microsoft app (zero-config). " + "Set it only to bring your own Entra app (BYO)."),
|
|
@@ -12004,6 +12004,28 @@ var OverlayDocSchema = exports_external.object({
|
|
|
12004
12004
|
// src/config/overlay-loader.ts
|
|
12005
12005
|
var OVERLAY_SOURCE = Symbol.for("switchroom.config.overlay-source");
|
|
12006
12006
|
var OVERLAY_TITLE = Symbol.for("switchroom.config.overlay-title");
|
|
12007
|
+
var OVERLAY_READ_FAILURES = Symbol.for("switchroom.config.overlay-read-failures");
|
|
12008
|
+
function recordReadFailure(agentCfg, failure) {
|
|
12009
|
+
const node = agentCfg;
|
|
12010
|
+
const existing = node[OVERLAY_READ_FAILURES];
|
|
12011
|
+
if (Array.isArray(existing)) {
|
|
12012
|
+
existing.push(failure);
|
|
12013
|
+
return;
|
|
12014
|
+
}
|
|
12015
|
+
Object.defineProperty(agentCfg, OVERLAY_READ_FAILURES, {
|
|
12016
|
+
value: [failure],
|
|
12017
|
+
enumerable: false,
|
|
12018
|
+
configurable: true,
|
|
12019
|
+
writable: false
|
|
12020
|
+
});
|
|
12021
|
+
}
|
|
12022
|
+
function overlayReadFailures(config, agent) {
|
|
12023
|
+
const agentCfg = config.agents?.[agent];
|
|
12024
|
+
if (!agentCfg)
|
|
12025
|
+
return [];
|
|
12026
|
+
const list = agentCfg[OVERLAY_READ_FAILURES];
|
|
12027
|
+
return Array.isArray(list) ? list : [];
|
|
12028
|
+
}
|
|
12007
12029
|
function deriveOverlayTitle(raw, fileName) {
|
|
12008
12030
|
const titleFromComment = raw.match(/^#[^\S\n]*name:[^\S\n]*(\S.*?)[^\S\n]*$/m)?.[1];
|
|
12009
12031
|
if (titleFromComment)
|
|
@@ -12013,17 +12035,39 @@ function deriveOverlayTitle(raw, fileName) {
|
|
|
12013
12035
|
return;
|
|
12014
12036
|
return base.length > 0 ? base : undefined;
|
|
12015
12037
|
}
|
|
12038
|
+
function readOverlayFile(agentName, file, agentCfg, warnings) {
|
|
12039
|
+
try {
|
|
12040
|
+
return readFileSync(file, "utf-8");
|
|
12041
|
+
} catch (err) {
|
|
12042
|
+
const code = err.code;
|
|
12043
|
+
if (code === "ENOENT")
|
|
12044
|
+
return;
|
|
12045
|
+
const w = {
|
|
12046
|
+
agent: agentName,
|
|
12047
|
+
file,
|
|
12048
|
+
reason: `read error: ${err.message}`,
|
|
12049
|
+
code: code ?? "EUNKNOWN"
|
|
12050
|
+
};
|
|
12051
|
+
recordReadFailure(agentCfg, { file, code: w.code });
|
|
12052
|
+
warnings.push(w);
|
|
12053
|
+
console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${file}': ${w.reason}`);
|
|
12054
|
+
return;
|
|
12055
|
+
}
|
|
12056
|
+
}
|
|
12016
12057
|
function overlayDirFor(agentName, subdir) {
|
|
12017
12058
|
const base = resolveDualPath(`~/.switchroom/agents/${agentName}/${subdir}`);
|
|
12018
12059
|
return resolve2(base);
|
|
12019
12060
|
}
|
|
12020
|
-
function listYamlFiles(dir) {
|
|
12061
|
+
function listYamlFiles(dir, onUnreadableDir) {
|
|
12021
12062
|
if (!existsSync2(dir))
|
|
12022
12063
|
return [];
|
|
12023
12064
|
let entries;
|
|
12024
12065
|
try {
|
|
12025
12066
|
entries = readdirSync(dir);
|
|
12026
|
-
} catch {
|
|
12067
|
+
} catch (err) {
|
|
12068
|
+
const code = err.code;
|
|
12069
|
+
if (code !== "ENOENT")
|
|
12070
|
+
onUnreadableDir?.(code ?? "EUNKNOWN");
|
|
12027
12071
|
return [];
|
|
12028
12072
|
}
|
|
12029
12073
|
const out = [];
|
|
@@ -12061,12 +12105,24 @@ function applyAgentOverlays(config) {
|
|
|
12061
12105
|
for (const [agentName, agentCfg] of Object.entries(agents)) {
|
|
12062
12106
|
try {
|
|
12063
12107
|
const scheduleDir = overlayDirFor(agentName, "schedule.d");
|
|
12064
|
-
const files = listYamlFiles(scheduleDir)
|
|
12108
|
+
const files = listYamlFiles(scheduleDir, (code) => {
|
|
12109
|
+
const w = {
|
|
12110
|
+
agent: agentName,
|
|
12111
|
+
file: scheduleDir,
|
|
12112
|
+
reason: `read error: cannot list overlay directory (${code})`,
|
|
12113
|
+
code
|
|
12114
|
+
};
|
|
12115
|
+
recordReadFailure(agentCfg, { file: scheduleDir, code });
|
|
12116
|
+
warnings.push(w);
|
|
12117
|
+
console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${scheduleDir}': ${w.reason}`);
|
|
12118
|
+
});
|
|
12065
12119
|
if (files.length > 0) {
|
|
12066
12120
|
const merged = [...agentCfg.schedule ?? []];
|
|
12067
12121
|
for (const file of files) {
|
|
12122
|
+
const raw = readOverlayFile(agentName, file, agentCfg, warnings);
|
|
12123
|
+
if (raw === undefined)
|
|
12124
|
+
continue;
|
|
12068
12125
|
try {
|
|
12069
|
-
const raw = readFileSync(file, "utf-8");
|
|
12070
12126
|
const parsed = $parse(raw);
|
|
12071
12127
|
const doc = OverlayDocSchema.parse(parsed);
|
|
12072
12128
|
const title = deriveOverlayTitle(raw, basename(file));
|
|
@@ -12101,13 +12157,25 @@ function applyAgentOverlays(config) {
|
|
|
12101
12157
|
}
|
|
12102
12158
|
try {
|
|
12103
12159
|
const skillsDir = overlayDirFor(agentName, "skills.d");
|
|
12104
|
-
const skillFiles = listYamlFiles(skillsDir)
|
|
12160
|
+
const skillFiles = listYamlFiles(skillsDir, (code) => {
|
|
12161
|
+
const w = {
|
|
12162
|
+
agent: agentName,
|
|
12163
|
+
file: skillsDir,
|
|
12164
|
+
reason: `read error: cannot list overlay directory (${code})`,
|
|
12165
|
+
code
|
|
12166
|
+
};
|
|
12167
|
+
recordReadFailure(agentCfg, { file: skillsDir, code });
|
|
12168
|
+
warnings.push(w);
|
|
12169
|
+
console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${skillsDir}': ${w.reason}`);
|
|
12170
|
+
});
|
|
12105
12171
|
if (skillFiles.length === 0) {} else {
|
|
12106
12172
|
const merged = [...agentCfg.skills ?? []];
|
|
12107
12173
|
const seen = new Set(merged);
|
|
12108
12174
|
for (const file of skillFiles) {
|
|
12175
|
+
const raw = readOverlayFile(agentName, file, agentCfg, warnings);
|
|
12176
|
+
if (raw === undefined)
|
|
12177
|
+
continue;
|
|
12109
12178
|
try {
|
|
12110
|
-
const raw = readFileSync(file, "utf-8");
|
|
12111
12179
|
const parsed = $parse(raw);
|
|
12112
12180
|
const doc = OverlayDocSchema.parse(parsed);
|
|
12113
12181
|
for (const skillName of doc.skills ?? []) {
|
|
@@ -15264,6 +15332,17 @@ function createScheduleReloader(opts) {
|
|
|
15264
15332
|
}
|
|
15265
15333
|
};
|
|
15266
15334
|
}
|
|
15335
|
+
function formatReadFailures(failures) {
|
|
15336
|
+
return failures.map((f) => `${f.file} (${f.code})`).join(", ") + " — fix ownership/mode so the agent uid can read the file " + "(a root-euid writer leaves cron overlays root-owned 0600; " + "`switchroom apply` / a reconcile ownership sweep heals this)";
|
|
15337
|
+
}
|
|
15338
|
+
function loadAgentEntriesStrict(configPath, agentName) {
|
|
15339
|
+
const config = loadConfig(configPath);
|
|
15340
|
+
const failures = overlayReadFailures(config, agentName);
|
|
15341
|
+
if (failures.length > 0) {
|
|
15342
|
+
throw new Error(`schedule.d overlay unreadable — refusing a reload that may be ` + `missing entries: ${formatReadFailures(failures)}`);
|
|
15343
|
+
}
|
|
15344
|
+
return collectScheduleEntries(config).filter((e) => e.agent === agentName);
|
|
15345
|
+
}
|
|
15267
15346
|
function resolveReloadPollMs(env) {
|
|
15268
15347
|
const raw = Number.parseInt(env.SWITCHROOM_SCHEDULER_RELOAD_POLL_MS ?? "", 10);
|
|
15269
15348
|
return Number.isFinite(raw) && raw >= 1000 ? raw : 30000;
|
|
@@ -15303,6 +15382,11 @@ async function main() {
|
|
|
15303
15382
|
const config = loadConfig(configPath);
|
|
15304
15383
|
const allEntries = collectScheduleEntries(config);
|
|
15305
15384
|
const entries = allEntries.filter((e) => e.agent === agentName);
|
|
15385
|
+
const bootReadFailures = overlayReadFailures(config, agentName);
|
|
15386
|
+
if (bootReadFailures.length > 0) {
|
|
15387
|
+
process.stderr.write(`agent-scheduler: ${agentName} WARNING: ${bootReadFailures.length} ` + `schedule.d overlay file(s) unreadable at boot — their cron entries ` + `are NOT registered: ${formatReadFailures(bootReadFailures)}
|
|
15388
|
+
`);
|
|
15389
|
+
}
|
|
15306
15390
|
const channel = resolveChannelTarget(config, agentName);
|
|
15307
15391
|
if (channel === null) {
|
|
15308
15392
|
process.stderr.write(`agent-scheduler: ${agentName} has no resolvable chat target ` + `(missing telegram.forum_chat_id) — exiting
|
|
@@ -15519,15 +15603,24 @@ Briefly and plainly tell the user these scheduled runs did not ` + "happen so th
|
|
|
15519
15603
|
let reloader;
|
|
15520
15604
|
let reloadTimer;
|
|
15521
15605
|
if (isHotReloadEnabled(process.env)) {
|
|
15606
|
+
let lastReloadError;
|
|
15522
15607
|
reloader = createScheduleReloader({
|
|
15523
|
-
loadEntries: () =>
|
|
15608
|
+
loadEntries: () => loadAgentEntriesStrict(configPath, agentName),
|
|
15524
15609
|
register: registerForEntries,
|
|
15525
15610
|
initialTasks: tasks,
|
|
15526
15611
|
initialEntries: entries,
|
|
15527
|
-
log: (m) =>
|
|
15528
|
-
|
|
15529
|
-
|
|
15530
|
-
`)
|
|
15612
|
+
log: (m) => {
|
|
15613
|
+
lastReloadError = undefined;
|
|
15614
|
+
process.stdout.write(`agent-scheduler: ${agentName} ${m}
|
|
15615
|
+
`);
|
|
15616
|
+
},
|
|
15617
|
+
onError: (e) => {
|
|
15618
|
+
if (e.message === lastReloadError)
|
|
15619
|
+
return;
|
|
15620
|
+
lastReloadError = e.message;
|
|
15621
|
+
process.stderr.write(`agent-scheduler: ${agentName} reload skipped (config error, keeping current schedule): ${e.message}
|
|
15622
|
+
`);
|
|
15623
|
+
}
|
|
15531
15624
|
});
|
|
15532
15625
|
reloadTimer = setInterval(() => reloader.tick(), resolveReloadPollMs(process.env));
|
|
15533
15626
|
}
|
|
@@ -15560,6 +15653,7 @@ export {
|
|
|
15560
15653
|
registerAgentSchedule,
|
|
15561
15654
|
recoverPendingEscalations,
|
|
15562
15655
|
main,
|
|
15656
|
+
loadAgentEntriesStrict,
|
|
15563
15657
|
isHotReloadEnabled,
|
|
15564
15658
|
ipcDispatcher,
|
|
15565
15659
|
createScheduleReloader
|
|
@@ -11444,7 +11444,7 @@ var init_schema = __esm(() => {
|
|
|
11444
11444
|
reflect: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `reflect` LLM op (synthesis / mental-model " + "refresh). Emits `HINDSIGHT_API_REFLECT_LLM_*`. Absent → uses global."),
|
|
11445
11445
|
consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent → global.")
|
|
11446
11446
|
}).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."),
|
|
11447
|
-
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 — 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 — a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP — 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 — 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 — 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 — 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 — 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 — 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 — 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 — 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 — 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_TEMPORAL_LANGUAGES — the language set dateparser is " + "restricted to during temporal query analysis, made live by switchroom's " + "temporal-language image patch (which ended a 200+-locale auto-detection " + "pass that blocked the shared asyncio loop on every recall); " + "comma-separated, unset means the image's baked `en`, set e.g. `en,es` to " + "restore i18n parsing), 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 — 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 — a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
|
|
11447
|
+
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 — 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 — a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP — 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 — 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 — 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 — 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 — 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 — 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 — 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 — 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 — 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_TEMPORAL_LANGUAGES — the language set dateparser is " + "restricted to during temporal query analysis, made live by switchroom's " + "temporal-language image patch (which ended a 200+-locale auto-detection " + "pass that blocked the shared asyncio loop on every recall); " + "comma-separated, unset means the image's baked `en`, set e.g. `en,es` to " + "restore i18n parsing; and " + "HINDSIGHT_API_TEMPORAL_MAX_QUERY_CHARS — 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), 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 — 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 — a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
|
|
11448
11448
|
});
|
|
11449
11449
|
MicrosoftWorkspaceConfigSchema = exports_external.object({
|
|
11450
11450
|
microsoft_client_id: exports_external.string().min(1).optional().describe("Microsoft OAuth application (client) ID from Entra portal " + "(literal string or vault reference e.g. " + "'vault:microsoft-oauth-client-id'). OPTIONAL — omit it to use " + "switchroom's shipped default Microsoft app (zero-config). " + "Set it only to bring your own Entra app (BYO)."),
|
|
@@ -12030,6 +12030,20 @@ var init_overlay_schema = __esm(() => {
|
|
|
12030
12030
|
// src/config/overlay-loader.ts
|
|
12031
12031
|
import { existsSync as existsSync2, readFileSync, readdirSync, statSync } from "node:fs";
|
|
12032
12032
|
import { basename, resolve as resolve2 } from "node:path";
|
|
12033
|
+
function recordReadFailure(agentCfg, failure) {
|
|
12034
|
+
const node = agentCfg;
|
|
12035
|
+
const existing = node[OVERLAY_READ_FAILURES];
|
|
12036
|
+
if (Array.isArray(existing)) {
|
|
12037
|
+
existing.push(failure);
|
|
12038
|
+
return;
|
|
12039
|
+
}
|
|
12040
|
+
Object.defineProperty(agentCfg, OVERLAY_READ_FAILURES, {
|
|
12041
|
+
value: [failure],
|
|
12042
|
+
enumerable: false,
|
|
12043
|
+
configurable: true,
|
|
12044
|
+
writable: false
|
|
12045
|
+
});
|
|
12046
|
+
}
|
|
12033
12047
|
function deriveOverlayTitle(raw, fileName) {
|
|
12034
12048
|
const titleFromComment = raw.match(/^#[^\S\n]*name:[^\S\n]*(\S.*?)[^\S\n]*$/m)?.[1];
|
|
12035
12049
|
if (titleFromComment)
|
|
@@ -12039,17 +12053,39 @@ function deriveOverlayTitle(raw, fileName) {
|
|
|
12039
12053
|
return;
|
|
12040
12054
|
return base.length > 0 ? base : undefined;
|
|
12041
12055
|
}
|
|
12056
|
+
function readOverlayFile(agentName, file, agentCfg, warnings) {
|
|
12057
|
+
try {
|
|
12058
|
+
return readFileSync(file, "utf-8");
|
|
12059
|
+
} catch (err) {
|
|
12060
|
+
const code = err.code;
|
|
12061
|
+
if (code === "ENOENT")
|
|
12062
|
+
return;
|
|
12063
|
+
const w = {
|
|
12064
|
+
agent: agentName,
|
|
12065
|
+
file,
|
|
12066
|
+
reason: `read error: ${err.message}`,
|
|
12067
|
+
code: code ?? "EUNKNOWN"
|
|
12068
|
+
};
|
|
12069
|
+
recordReadFailure(agentCfg, { file, code: w.code });
|
|
12070
|
+
warnings.push(w);
|
|
12071
|
+
console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${file}': ${w.reason}`);
|
|
12072
|
+
return;
|
|
12073
|
+
}
|
|
12074
|
+
}
|
|
12042
12075
|
function overlayDirFor(agentName, subdir) {
|
|
12043
12076
|
const base = resolveDualPath(`~/.switchroom/agents/${agentName}/${subdir}`);
|
|
12044
12077
|
return resolve2(base);
|
|
12045
12078
|
}
|
|
12046
|
-
function listYamlFiles(dir) {
|
|
12079
|
+
function listYamlFiles(dir, onUnreadableDir) {
|
|
12047
12080
|
if (!existsSync2(dir))
|
|
12048
12081
|
return [];
|
|
12049
12082
|
let entries;
|
|
12050
12083
|
try {
|
|
12051
12084
|
entries = readdirSync(dir);
|
|
12052
|
-
} catch {
|
|
12085
|
+
} catch (err) {
|
|
12086
|
+
const code = err.code;
|
|
12087
|
+
if (code !== "ENOENT")
|
|
12088
|
+
onUnreadableDir?.(code ?? "EUNKNOWN");
|
|
12053
12089
|
return [];
|
|
12054
12090
|
}
|
|
12055
12091
|
const out = [];
|
|
@@ -12087,12 +12123,24 @@ function applyAgentOverlays(config) {
|
|
|
12087
12123
|
for (const [agentName, agentCfg] of Object.entries(agents)) {
|
|
12088
12124
|
try {
|
|
12089
12125
|
const scheduleDir = overlayDirFor(agentName, "schedule.d");
|
|
12090
|
-
const files = listYamlFiles(scheduleDir)
|
|
12126
|
+
const files = listYamlFiles(scheduleDir, (code) => {
|
|
12127
|
+
const w = {
|
|
12128
|
+
agent: agentName,
|
|
12129
|
+
file: scheduleDir,
|
|
12130
|
+
reason: `read error: cannot list overlay directory (${code})`,
|
|
12131
|
+
code
|
|
12132
|
+
};
|
|
12133
|
+
recordReadFailure(agentCfg, { file: scheduleDir, code });
|
|
12134
|
+
warnings.push(w);
|
|
12135
|
+
console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${scheduleDir}': ${w.reason}`);
|
|
12136
|
+
});
|
|
12091
12137
|
if (files.length > 0) {
|
|
12092
12138
|
const merged = [...agentCfg.schedule ?? []];
|
|
12093
12139
|
for (const file of files) {
|
|
12140
|
+
const raw = readOverlayFile(agentName, file, agentCfg, warnings);
|
|
12141
|
+
if (raw === undefined)
|
|
12142
|
+
continue;
|
|
12094
12143
|
try {
|
|
12095
|
-
const raw = readFileSync(file, "utf-8");
|
|
12096
12144
|
const parsed = import_yaml.parse(raw);
|
|
12097
12145
|
const doc = OverlayDocSchema.parse(parsed);
|
|
12098
12146
|
const title = deriveOverlayTitle(raw, basename(file));
|
|
@@ -12127,13 +12175,25 @@ function applyAgentOverlays(config) {
|
|
|
12127
12175
|
}
|
|
12128
12176
|
try {
|
|
12129
12177
|
const skillsDir = overlayDirFor(agentName, "skills.d");
|
|
12130
|
-
const skillFiles = listYamlFiles(skillsDir)
|
|
12178
|
+
const skillFiles = listYamlFiles(skillsDir, (code) => {
|
|
12179
|
+
const w = {
|
|
12180
|
+
agent: agentName,
|
|
12181
|
+
file: skillsDir,
|
|
12182
|
+
reason: `read error: cannot list overlay directory (${code})`,
|
|
12183
|
+
code
|
|
12184
|
+
};
|
|
12185
|
+
recordReadFailure(agentCfg, { file: skillsDir, code });
|
|
12186
|
+
warnings.push(w);
|
|
12187
|
+
console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${skillsDir}': ${w.reason}`);
|
|
12188
|
+
});
|
|
12131
12189
|
if (skillFiles.length === 0) {} else {
|
|
12132
12190
|
const merged = [...agentCfg.skills ?? []];
|
|
12133
12191
|
const seen = new Set(merged);
|
|
12134
12192
|
for (const file of skillFiles) {
|
|
12193
|
+
const raw = readOverlayFile(agentName, file, agentCfg, warnings);
|
|
12194
|
+
if (raw === undefined)
|
|
12195
|
+
continue;
|
|
12135
12196
|
try {
|
|
12136
|
-
const raw = readFileSync(file, "utf-8");
|
|
12137
12197
|
const parsed = import_yaml.parse(raw);
|
|
12138
12198
|
const doc = OverlayDocSchema.parse(parsed);
|
|
12139
12199
|
for (const skillName of doc.skills ?? []) {
|
|
@@ -12161,7 +12221,7 @@ function applyAgentOverlays(config) {
|
|
|
12161
12221
|
}
|
|
12162
12222
|
return { config, warnings };
|
|
12163
12223
|
}
|
|
12164
|
-
var import_yaml, OVERLAY_SOURCE, OVERLAY_TITLE;
|
|
12224
|
+
var import_yaml, OVERLAY_SOURCE, OVERLAY_TITLE, OVERLAY_READ_FAILURES;
|
|
12165
12225
|
var init_overlay_loader = __esm(() => {
|
|
12166
12226
|
init_zod();
|
|
12167
12227
|
init_overlay_schema();
|
|
@@ -12169,6 +12229,7 @@ var init_overlay_loader = __esm(() => {
|
|
|
12169
12229
|
import_yaml = __toESM(require_dist(), 1);
|
|
12170
12230
|
OVERLAY_SOURCE = Symbol.for("switchroom.config.overlay-source");
|
|
12171
12231
|
OVERLAY_TITLE = Symbol.for("switchroom.config.overlay-title");
|
|
12232
|
+
OVERLAY_READ_FAILURES = Symbol.for("switchroom.config.overlay-read-failures");
|
|
12172
12233
|
});
|
|
12173
12234
|
|
|
12174
12235
|
// src/config/merge.ts
|
|
@@ -19170,7 +19231,8 @@ var HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS = new Set([
|
|
|
19170
19231
|
"HINDSIGHT_API_RETAIN_WALL_TIMEOUT",
|
|
19171
19232
|
"HINDSIGHT_API_CONSOLIDATION_RECALL_MAX_CONCURRENT",
|
|
19172
19233
|
"HINDSIGHT_MM_REFRESH_MIN_INTERVAL_S",
|
|
19173
|
-
"HINDSIGHT_API_TEMPORAL_LANGUAGES"
|
|
19234
|
+
"HINDSIGHT_API_TEMPORAL_LANGUAGES",
|
|
19235
|
+
"HINDSIGHT_API_TEMPORAL_MAX_QUERY_CHARS"
|
|
19174
19236
|
]);
|
|
19175
19237
|
var HINDSIGHT_WORKER_SLOT_TYPES = [
|
|
19176
19238
|
"consolidation",
|
|
@@ -12180,7 +12180,7 @@ var HindsightConfigSchema = exports_external.object({
|
|
|
12180
12180
|
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."),
|
|
12181
12181
|
consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent \u2192 global.")
|
|
12182
12182
|
}).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."),
|
|
12183
|
-
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_TEMPORAL_LANGUAGES \u2014 the language set dateparser is " + "restricted to during temporal query analysis, made live by switchroom's " + "temporal-language image patch (which ended a 200+-locale auto-detection " + "pass that blocked the shared asyncio loop on every recall); " + "comma-separated, unset means the image's baked `en`, set e.g. `en,es` to " + "restore i18n parsing), 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).")
|
|
12183
|
+
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_TEMPORAL_LANGUAGES \u2014 the language set dateparser is " + "restricted to during temporal query analysis, made live by switchroom's " + "temporal-language image patch (which ended a 200+-locale auto-detection " + "pass that blocked the shared asyncio loop on every recall); " + "comma-separated, unset means the image's baked `en`, set e.g. `en,es` to " + "restore 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), 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).")
|
|
12184
12184
|
});
|
|
12185
12185
|
var MicrosoftWorkspaceConfigSchema = exports_external.object({
|
|
12186
12186
|
microsoft_client_id: exports_external.string().min(1).optional().describe("Microsoft OAuth application (client) ID from Entra portal " + "(literal string or vault reference e.g. " + "'vault:microsoft-oauth-client-id'). OPTIONAL \u2014 omit it to use " + "switchroom's shipped default Microsoft app (zero-config). " + "Set it only to bring your own Entra app (BYO)."),
|
|
@@ -12767,6 +12767,21 @@ var OverlayDocSchema = exports_external.object({
|
|
|
12767
12767
|
// src/config/overlay-loader.ts
|
|
12768
12768
|
var OVERLAY_SOURCE = Symbol.for("switchroom.config.overlay-source");
|
|
12769
12769
|
var OVERLAY_TITLE = Symbol.for("switchroom.config.overlay-title");
|
|
12770
|
+
var OVERLAY_READ_FAILURES = Symbol.for("switchroom.config.overlay-read-failures");
|
|
12771
|
+
function recordReadFailure(agentCfg, failure) {
|
|
12772
|
+
const node = agentCfg;
|
|
12773
|
+
const existing = node[OVERLAY_READ_FAILURES];
|
|
12774
|
+
if (Array.isArray(existing)) {
|
|
12775
|
+
existing.push(failure);
|
|
12776
|
+
return;
|
|
12777
|
+
}
|
|
12778
|
+
Object.defineProperty(agentCfg, OVERLAY_READ_FAILURES, {
|
|
12779
|
+
value: [failure],
|
|
12780
|
+
enumerable: false,
|
|
12781
|
+
configurable: true,
|
|
12782
|
+
writable: false
|
|
12783
|
+
});
|
|
12784
|
+
}
|
|
12770
12785
|
function deriveOverlayTitle(raw, fileName) {
|
|
12771
12786
|
const titleFromComment = raw.match(/^#[^\S\n]*name:[^\S\n]*(\S.*?)[^\S\n]*$/m)?.[1];
|
|
12772
12787
|
if (titleFromComment)
|
|
@@ -12776,17 +12791,39 @@ function deriveOverlayTitle(raw, fileName) {
|
|
|
12776
12791
|
return;
|
|
12777
12792
|
return base.length > 0 ? base : undefined;
|
|
12778
12793
|
}
|
|
12794
|
+
function readOverlayFile(agentName, file, agentCfg, warnings) {
|
|
12795
|
+
try {
|
|
12796
|
+
return readFileSync(file, "utf-8");
|
|
12797
|
+
} catch (err) {
|
|
12798
|
+
const code = err.code;
|
|
12799
|
+
if (code === "ENOENT")
|
|
12800
|
+
return;
|
|
12801
|
+
const w = {
|
|
12802
|
+
agent: agentName,
|
|
12803
|
+
file,
|
|
12804
|
+
reason: `read error: ${err.message}`,
|
|
12805
|
+
code: code ?? "EUNKNOWN"
|
|
12806
|
+
};
|
|
12807
|
+
recordReadFailure(agentCfg, { file, code: w.code });
|
|
12808
|
+
warnings.push(w);
|
|
12809
|
+
console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${file}': ${w.reason}`);
|
|
12810
|
+
return;
|
|
12811
|
+
}
|
|
12812
|
+
}
|
|
12779
12813
|
function overlayDirFor(agentName, subdir) {
|
|
12780
12814
|
const base = resolveDualPath(`~/.switchroom/agents/${agentName}/${subdir}`);
|
|
12781
12815
|
return resolve2(base);
|
|
12782
12816
|
}
|
|
12783
|
-
function listYamlFiles(dir) {
|
|
12817
|
+
function listYamlFiles(dir, onUnreadableDir) {
|
|
12784
12818
|
if (!existsSync2(dir))
|
|
12785
12819
|
return [];
|
|
12786
12820
|
let entries;
|
|
12787
12821
|
try {
|
|
12788
12822
|
entries = readdirSync(dir);
|
|
12789
|
-
} catch {
|
|
12823
|
+
} catch (err) {
|
|
12824
|
+
const code = err.code;
|
|
12825
|
+
if (code !== "ENOENT")
|
|
12826
|
+
onUnreadableDir?.(code ?? "EUNKNOWN");
|
|
12790
12827
|
return [];
|
|
12791
12828
|
}
|
|
12792
12829
|
const out = [];
|
|
@@ -12824,12 +12861,24 @@ function applyAgentOverlays(config) {
|
|
|
12824
12861
|
for (const [agentName, agentCfg] of Object.entries(agents)) {
|
|
12825
12862
|
try {
|
|
12826
12863
|
const scheduleDir = overlayDirFor(agentName, "schedule.d");
|
|
12827
|
-
const files = listYamlFiles(scheduleDir)
|
|
12864
|
+
const files = listYamlFiles(scheduleDir, (code) => {
|
|
12865
|
+
const w = {
|
|
12866
|
+
agent: agentName,
|
|
12867
|
+
file: scheduleDir,
|
|
12868
|
+
reason: `read error: cannot list overlay directory (${code})`,
|
|
12869
|
+
code
|
|
12870
|
+
};
|
|
12871
|
+
recordReadFailure(agentCfg, { file: scheduleDir, code });
|
|
12872
|
+
warnings.push(w);
|
|
12873
|
+
console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${scheduleDir}': ${w.reason}`);
|
|
12874
|
+
});
|
|
12828
12875
|
if (files.length > 0) {
|
|
12829
12876
|
const merged = [...agentCfg.schedule ?? []];
|
|
12830
12877
|
for (const file of files) {
|
|
12878
|
+
const raw = readOverlayFile(agentName, file, agentCfg, warnings);
|
|
12879
|
+
if (raw === undefined)
|
|
12880
|
+
continue;
|
|
12831
12881
|
try {
|
|
12832
|
-
const raw = readFileSync(file, "utf-8");
|
|
12833
12882
|
const parsed = $parse(raw);
|
|
12834
12883
|
const doc = OverlayDocSchema.parse(parsed);
|
|
12835
12884
|
const title = deriveOverlayTitle(raw, basename(file));
|
|
@@ -12864,13 +12913,25 @@ function applyAgentOverlays(config) {
|
|
|
12864
12913
|
}
|
|
12865
12914
|
try {
|
|
12866
12915
|
const skillsDir = overlayDirFor(agentName, "skills.d");
|
|
12867
|
-
const skillFiles = listYamlFiles(skillsDir)
|
|
12916
|
+
const skillFiles = listYamlFiles(skillsDir, (code) => {
|
|
12917
|
+
const w = {
|
|
12918
|
+
agent: agentName,
|
|
12919
|
+
file: skillsDir,
|
|
12920
|
+
reason: `read error: cannot list overlay directory (${code})`,
|
|
12921
|
+
code
|
|
12922
|
+
};
|
|
12923
|
+
recordReadFailure(agentCfg, { file: skillsDir, code });
|
|
12924
|
+
warnings.push(w);
|
|
12925
|
+
console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${skillsDir}': ${w.reason}`);
|
|
12926
|
+
});
|
|
12868
12927
|
if (skillFiles.length === 0) {} else {
|
|
12869
12928
|
const merged = [...agentCfg.skills ?? []];
|
|
12870
12929
|
const seen = new Set(merged);
|
|
12871
12930
|
for (const file of skillFiles) {
|
|
12931
|
+
const raw = readOverlayFile(agentName, file, agentCfg, warnings);
|
|
12932
|
+
if (raw === undefined)
|
|
12933
|
+
continue;
|
|
12872
12934
|
try {
|
|
12873
|
-
const raw = readFileSync(file, "utf-8");
|
|
12874
12935
|
const parsed = $parse(raw);
|
|
12875
12936
|
const doc = OverlayDocSchema.parse(parsed);
|
|
12876
12937
|
for (const skillName of doc.skills ?? []) {
|