switchroom 0.20.7 → 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 +105 -11
- package/dist/auth-broker/index.js +68 -7
- package/dist/cli/notion-write-pretool.mjs +67 -6
- package/dist/cli/switchroom.js +384 -28
- package/dist/host-control/main.js +69 -8
- package/dist/vault/approvals/kernel-server.js +68 -7
- package/dist/vault/broker/server.js +68 -7
- 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 +237 -122
- 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 +28 -30
- package/telegram-plugin/gateway/narrative-lane.ts +21 -1
- package/telegram-plugin/gateway/represent-delivery-guard.ts +33 -2
- package/telegram-plugin/gateway/stream-render.ts +11 -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/queued-card-surface.test.ts +66 -0
- package/telegram-plugin/tests/represent-guard.test.ts +45 -0
- package/telegram-plugin/tests/turn-flush-safety.test.ts +83 -0
- package/telegram-plugin/turn-flush-safety.ts +79 -0
|
@@ -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
|
|
@@ -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
|
|
@@ -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 ?? []) {
|