switchroom 0.15.27 → 0.15.29

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.
@@ -11617,7 +11617,7 @@ function resolveDualPath(pathStr) {
11617
11617
 
11618
11618
  // src/config/overlay-loader.ts
11619
11619
  import { existsSync as existsSync2, readFileSync, readdirSync, statSync } from "node:fs";
11620
- import { resolve as resolve2 } from "node:path";
11620
+ import { basename, resolve as resolve2 } from "node:path";
11621
11621
 
11622
11622
  // src/config/overlay-schema.ts
11623
11623
  var OverlayDocSchema = exports_external.object({
@@ -11627,6 +11627,16 @@ var OverlayDocSchema = exports_external.object({
11627
11627
 
11628
11628
  // src/config/overlay-loader.ts
11629
11629
  var OVERLAY_SOURCE = Symbol.for("switchroom.config.overlay-source");
11630
+ var OVERLAY_TITLE = Symbol.for("switchroom.config.overlay-title");
11631
+ function deriveOverlayTitle(raw, fileName) {
11632
+ const titleFromComment = raw.match(/^#[^\S\n]*name:[^\S\n]*(\S.*?)[^\S\n]*$/m)?.[1];
11633
+ if (titleFromComment)
11634
+ return titleFromComment;
11635
+ const base = fileName.replace(/\.ya?ml$/i, "");
11636
+ if (/^cron-[0-9a-f]{6,}$/i.test(base))
11637
+ return;
11638
+ return base.length > 0 ? base : undefined;
11639
+ }
11630
11640
  function overlayDirFor(agentName, subdir) {
11631
11641
  const base = resolveDualPath(`~/.switchroom/agents/${agentName}/${subdir}`);
11632
11642
  return resolve2(base);
@@ -11652,13 +11662,21 @@ function listYamlFiles(dir) {
11652
11662
  }
11653
11663
  return out.sort();
11654
11664
  }
11655
- function stampOverlay(entry) {
11665
+ function stampOverlay(entry, title) {
11656
11666
  Object.defineProperty(entry, OVERLAY_SOURCE, {
11657
11667
  value: true,
11658
11668
  enumerable: false,
11659
11669
  configurable: false,
11660
11670
  writable: false
11661
11671
  });
11672
+ if (title !== undefined) {
11673
+ Object.defineProperty(entry, OVERLAY_TITLE, {
11674
+ value: title,
11675
+ enumerable: false,
11676
+ configurable: false,
11677
+ writable: false
11678
+ });
11679
+ }
11662
11680
  return entry;
11663
11681
  }
11664
11682
  function applyAgentOverlays(config) {
@@ -11675,6 +11693,7 @@ function applyAgentOverlays(config) {
11675
11693
  const raw = readFileSync(file, "utf-8");
11676
11694
  const parsed = $parse(raw);
11677
11695
  const doc = OverlayDocSchema.parse(parsed);
11696
+ const title = deriveOverlayTitle(raw, basename(file));
11678
11697
  for (const entry of doc.schedule ?? []) {
11679
11698
  if (entry.secrets && entry.secrets.length > 0) {
11680
11699
  const w = {
@@ -11686,7 +11705,7 @@ function applyAgentOverlays(config) {
11686
11705
  console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${file}': ${w.reason}`);
11687
11706
  continue;
11688
11707
  }
11689
- merged.push(stampOverlay(entry));
11708
+ merged.push(stampOverlay(entry, title));
11690
11709
  }
11691
11710
  } catch (err) {
11692
11711
  const reason = err instanceof ZodError ? `schema rejection: ${err.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ")}` : `parse error: ${err.message}`;
@@ -12283,10 +12302,12 @@ function collectScheduleEntries(config) {
12283
12302
  for (let i = 0;i < schedule.length; i++) {
12284
12303
  const entry = schedule[i];
12285
12304
  const auditMaterial = entry.prompt ?? `action:${JSON.stringify(entry.action ?? {})}`;
12305
+ const title = entry[OVERLAY_TITLE];
12286
12306
  out.push({
12287
12307
  agent,
12288
12308
  scheduleIndex: i,
12289
12309
  cron: entry.cron,
12310
+ ...typeof title === "string" && title.length > 0 ? { name: title } : {},
12290
12311
  ...entry.prompt !== undefined ? { prompt: entry.prompt } : {},
12291
12312
  promptKey: createHash("sha256").update(auditMaterial).digest("hex").slice(0, 12),
12292
12313
  ...entry.topic !== undefined ? { topic: entry.topic } : {},
@@ -11617,7 +11617,7 @@ function resolveDualPath(pathStr) {
11617
11617
 
11618
11618
  // src/config/overlay-loader.ts
11619
11619
  import { existsSync as existsSync2, readFileSync, readdirSync, statSync } from "node:fs";
11620
- import { resolve as resolve2 } from "node:path";
11620
+ import { basename, resolve as resolve2 } from "node:path";
11621
11621
 
11622
11622
  // src/config/overlay-schema.ts
11623
11623
  var OverlayDocSchema = exports_external.object({
@@ -11627,6 +11627,16 @@ var OverlayDocSchema = exports_external.object({
11627
11627
 
11628
11628
  // src/config/overlay-loader.ts
11629
11629
  var OVERLAY_SOURCE = Symbol.for("switchroom.config.overlay-source");
11630
+ var OVERLAY_TITLE = Symbol.for("switchroom.config.overlay-title");
11631
+ function deriveOverlayTitle(raw, fileName) {
11632
+ const titleFromComment = raw.match(/^#[^\S\n]*name:[^\S\n]*(\S.*?)[^\S\n]*$/m)?.[1];
11633
+ if (titleFromComment)
11634
+ return titleFromComment;
11635
+ const base = fileName.replace(/\.ya?ml$/i, "");
11636
+ if (/^cron-[0-9a-f]{6,}$/i.test(base))
11637
+ return;
11638
+ return base.length > 0 ? base : undefined;
11639
+ }
11630
11640
  function overlayDirFor(agentName, subdir) {
11631
11641
  const base = resolveDualPath(`~/.switchroom/agents/${agentName}/${subdir}`);
11632
11642
  return resolve2(base);
@@ -11652,13 +11662,21 @@ function listYamlFiles(dir) {
11652
11662
  }
11653
11663
  return out.sort();
11654
11664
  }
11655
- function stampOverlay(entry) {
11665
+ function stampOverlay(entry, title) {
11656
11666
  Object.defineProperty(entry, OVERLAY_SOURCE, {
11657
11667
  value: true,
11658
11668
  enumerable: false,
11659
11669
  configurable: false,
11660
11670
  writable: false
11661
11671
  });
11672
+ if (title !== undefined) {
11673
+ Object.defineProperty(entry, OVERLAY_TITLE, {
11674
+ value: title,
11675
+ enumerable: false,
11676
+ configurable: false,
11677
+ writable: false
11678
+ });
11679
+ }
11662
11680
  return entry;
11663
11681
  }
11664
11682
  function applyAgentOverlays(config) {
@@ -11675,6 +11693,7 @@ function applyAgentOverlays(config) {
11675
11693
  const raw = readFileSync(file, "utf-8");
11676
11694
  const parsed = $parse(raw);
11677
11695
  const doc = OverlayDocSchema.parse(parsed);
11696
+ const title = deriveOverlayTitle(raw, basename(file));
11678
11697
  for (const entry of doc.schedule ?? []) {
11679
11698
  if (entry.secrets && entry.secrets.length > 0) {
11680
11699
  const w = {
@@ -11686,7 +11705,7 @@ function applyAgentOverlays(config) {
11686
11705
  console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${file}': ${w.reason}`);
11687
11706
  continue;
11688
11707
  }
11689
- merged.push(stampOverlay(entry));
11708
+ merged.push(stampOverlay(entry, title));
11690
11709
  }
11691
11710
  } catch (err) {
11692
11711
  const reason = err instanceof ZodError ? `schema rejection: ${err.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ")}` : `parse error: ${err.message}`;
@@ -12365,7 +12365,7 @@ function resolveDualPath(pathStr) {
12365
12365
 
12366
12366
  // src/config/overlay-loader.ts
12367
12367
  import { existsSync as existsSync2, readFileSync, readdirSync, statSync } from "node:fs";
12368
- import { resolve as resolve2 } from "node:path";
12368
+ import { basename, resolve as resolve2 } from "node:path";
12369
12369
  init_zod();
12370
12370
 
12371
12371
  // src/config/overlay-schema.ts
@@ -12377,6 +12377,16 @@ var OverlayDocSchema = exports_external.object({
12377
12377
 
12378
12378
  // src/config/overlay-loader.ts
12379
12379
  var OVERLAY_SOURCE = Symbol.for("switchroom.config.overlay-source");
12380
+ var OVERLAY_TITLE = Symbol.for("switchroom.config.overlay-title");
12381
+ function deriveOverlayTitle(raw, fileName) {
12382
+ const titleFromComment = raw.match(/^#[^\S\n]*name:[^\S\n]*(\S.*?)[^\S\n]*$/m)?.[1];
12383
+ if (titleFromComment)
12384
+ return titleFromComment;
12385
+ const base = fileName.replace(/\.ya?ml$/i, "");
12386
+ if (/^cron-[0-9a-f]{6,}$/i.test(base))
12387
+ return;
12388
+ return base.length > 0 ? base : undefined;
12389
+ }
12380
12390
  function overlayDirFor(agentName, subdir) {
12381
12391
  const base = resolveDualPath(`~/.switchroom/agents/${agentName}/${subdir}`);
12382
12392
  return resolve2(base);
@@ -12402,13 +12412,21 @@ function listYamlFiles(dir) {
12402
12412
  }
12403
12413
  return out.sort();
12404
12414
  }
12405
- function stampOverlay(entry) {
12415
+ function stampOverlay(entry, title) {
12406
12416
  Object.defineProperty(entry, OVERLAY_SOURCE, {
12407
12417
  value: true,
12408
12418
  enumerable: false,
12409
12419
  configurable: false,
12410
12420
  writable: false
12411
12421
  });
12422
+ if (title !== undefined) {
12423
+ Object.defineProperty(entry, OVERLAY_TITLE, {
12424
+ value: title,
12425
+ enumerable: false,
12426
+ configurable: false,
12427
+ writable: false
12428
+ });
12429
+ }
12412
12430
  return entry;
12413
12431
  }
12414
12432
  function applyAgentOverlays(config) {
@@ -12425,6 +12443,7 @@ function applyAgentOverlays(config) {
12425
12443
  const raw = readFileSync(file, "utf-8");
12426
12444
  const parsed = $parse(raw);
12427
12445
  const doc = OverlayDocSchema.parse(parsed);
12446
+ const title = deriveOverlayTitle(raw, basename(file));
12428
12447
  for (const entry of doc.schedule ?? []) {
12429
12448
  if (entry.secrets && entry.secrets.length > 0) {
12430
12449
  const w = {
@@ -12436,7 +12455,7 @@ function applyAgentOverlays(config) {
12436
12455
  console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${file}': ${w.reason}`);
12437
12456
  continue;
12438
12457
  }
12439
- merged.push(stampOverlay(entry));
12458
+ merged.push(stampOverlay(entry, title));
12440
12459
  }
12441
12460
  } catch (err) {
12442
12461
  const reason = err instanceof ZodError ? `schema rejection: ${err.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ")}` : `parse error: ${err.message}`;
@@ -14202,7 +14202,16 @@ var init_overlay_schema = __esm(() => {
14202
14202
 
14203
14203
  // src/config/overlay-loader.ts
14204
14204
  import { existsSync as existsSync2, readFileSync, readdirSync, statSync as statSync2 } from "node:fs";
14205
- import { resolve as resolve2 } from "node:path";
14205
+ import { basename, resolve as resolve2 } from "node:path";
14206
+ function deriveOverlayTitle(raw, fileName) {
14207
+ const titleFromComment = raw.match(/^#[^\S\n]*name:[^\S\n]*(\S.*?)[^\S\n]*$/m)?.[1];
14208
+ if (titleFromComment)
14209
+ return titleFromComment;
14210
+ const base = fileName.replace(/\.ya?ml$/i, "");
14211
+ if (/^cron-[0-9a-f]{6,}$/i.test(base))
14212
+ return;
14213
+ return base.length > 0 ? base : undefined;
14214
+ }
14206
14215
  function overlayDirFor(agentName, subdir) {
14207
14216
  const base = resolveDualPath(`~/.switchroom/agents/${agentName}/${subdir}`);
14208
14217
  return resolve2(base);
@@ -14228,13 +14237,21 @@ function listYamlFiles(dir) {
14228
14237
  }
14229
14238
  return out.sort();
14230
14239
  }
14231
- function stampOverlay(entry) {
14240
+ function stampOverlay(entry, title) {
14232
14241
  Object.defineProperty(entry, OVERLAY_SOURCE, {
14233
14242
  value: true,
14234
14243
  enumerable: false,
14235
14244
  configurable: false,
14236
14245
  writable: false
14237
14246
  });
14247
+ if (title !== undefined) {
14248
+ Object.defineProperty(entry, OVERLAY_TITLE, {
14249
+ value: title,
14250
+ enumerable: false,
14251
+ configurable: false,
14252
+ writable: false
14253
+ });
14254
+ }
14238
14255
  return entry;
14239
14256
  }
14240
14257
  function applyAgentOverlays(config) {
@@ -14251,6 +14268,7 @@ function applyAgentOverlays(config) {
14251
14268
  const raw = readFileSync(file, "utf-8");
14252
14269
  const parsed = import_yaml.parse(raw);
14253
14270
  const doc = OverlayDocSchema.parse(parsed);
14271
+ const title = deriveOverlayTitle(raw, basename(file));
14254
14272
  for (const entry of doc.schedule ?? []) {
14255
14273
  if (entry.secrets && entry.secrets.length > 0) {
14256
14274
  const w = {
@@ -14262,7 +14280,7 @@ function applyAgentOverlays(config) {
14262
14280
  console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${file}': ${w.reason}`);
14263
14281
  continue;
14264
14282
  }
14265
- merged.push(stampOverlay(entry));
14283
+ merged.push(stampOverlay(entry, title));
14266
14284
  }
14267
14285
  } catch (err) {
14268
14286
  const reason = err instanceof ZodError ? `schema rejection: ${err.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ")}` : `parse error: ${err.message}`;
@@ -14316,13 +14334,14 @@ function applyAgentOverlays(config) {
14316
14334
  }
14317
14335
  return { config, warnings };
14318
14336
  }
14319
- var import_yaml, OVERLAY_SOURCE;
14337
+ var import_yaml, OVERLAY_SOURCE, OVERLAY_TITLE;
14320
14338
  var init_overlay_loader = __esm(() => {
14321
14339
  init_zod();
14322
14340
  init_overlay_schema();
14323
14341
  init_paths();
14324
14342
  import_yaml = __toESM(require_dist(), 1);
14325
14343
  OVERLAY_SOURCE = Symbol.for("switchroom.config.overlay-source");
14344
+ OVERLAY_TITLE = Symbol.for("switchroom.config.overlay-title");
14326
14345
  });
14327
14346
 
14328
14347
  // src/config/merge.ts
@@ -21871,7 +21890,7 @@ import {
21871
21890
  lstatSync as lstatSync2,
21872
21891
  realpathSync as realpathSync2
21873
21892
  } from "node:fs";
21874
- import { dirname as dirname2, basename as basename2, resolve as resolve7 } from "node:path";
21893
+ import { dirname as dirname2, basename as basename3, resolve as resolve7 } from "node:path";
21875
21894
  function atomicWriteFileSync(path, data, mode) {
21876
21895
  let effectivePath = path;
21877
21896
  try {
@@ -21880,7 +21899,7 @@ function atomicWriteFileSync(path, data, mode) {
21880
21899
  }
21881
21900
  } catch {}
21882
21901
  const dir = dirname2(resolve7(effectivePath));
21883
- const tmp = resolve7(dir, `.${basename2(effectivePath)}.${process.pid}.${Date.now()}.tmp`);
21902
+ const tmp = resolve7(dir, `.${basename3(effectivePath)}.${process.pid}.${Date.now()}.tmp`);
21884
21903
  try {
21885
21904
  writeFileSync3(tmp, data, { encoding: "utf8", mode });
21886
21905
  renameSync2(tmp, effectivePath);
@@ -50494,8 +50513,8 @@ var {
50494
50513
  } = import__.default;
50495
50514
 
50496
50515
  // src/build-info.ts
50497
- var VERSION = "0.15.27";
50498
- var COMMIT_SHA = "97160057";
50516
+ var VERSION = "0.15.29";
50517
+ var COMMIT_SHA = "eddde22d";
50499
50518
 
50500
50519
  // src/cli/agent.ts
50501
50520
  init_source();
@@ -51153,7 +51172,7 @@ import {
51153
51172
  copyFileSync as copyFileSync2,
51154
51173
  unlinkSync
51155
51174
  } from "node:fs";
51156
- import { basename, dirname, resolve as resolve6 } from "node:path";
51175
+ import { basename as basename2, dirname, resolve as resolve6 } from "node:path";
51157
51176
  function defaultStatePath() {
51158
51177
  return resolveStatePath("topics.json");
51159
51178
  }
@@ -51185,7 +51204,7 @@ function saveTopicState(state, statePath) {
51185
51204
  mkdirSync4(dir, { recursive: true });
51186
51205
  }
51187
51206
  const absDir = resolve6(dir);
51188
- const tmp = resolve6(absDir, `.${basename(path)}.${process.pid}.${Date.now()}.tmp`);
51207
+ const tmp = resolve6(absDir, `.${basename2(path)}.${process.pid}.${Date.now()}.tmp`);
51189
51208
  try {
51190
51209
  writeFileSync2(tmp, JSON.stringify(state, null, 2) + `
51191
51210
  `, "utf-8");
@@ -54443,7 +54462,7 @@ init_compose();
54443
54462
  import { chownSync as chownSync2 } from "node:fs";
54444
54463
  import { mkdir, readFile, writeFile } from "node:fs/promises";
54445
54464
  import { homedir as homedir6 } from "node:os";
54446
- import { basename as basename3, dirname as dirname3, join as join12 } from "node:path";
54465
+ import { basename as basename4, dirname as dirname3, join as join12 } from "node:path";
54447
54466
 
54448
54467
  // src/config/release-resolve.ts
54449
54468
  function resolveImageTag(release) {
@@ -54571,7 +54590,7 @@ function resolveHostSwitchroomConfigPath(rawPath) {
54571
54590
 
54572
54591
  ` + `Recovery: run \`switchroom apply\` once from the HOST (not from inside the container). ` + `That will regenerate the compose file with SWITCHROOM_HOST_HOME set, after which ` + `in-container apply will work correctly.`);
54573
54592
  }
54574
- const filename = basename3(rawPath);
54593
+ const filename = basename4(rawPath);
54575
54594
  return join12(hostHome, ".switchroom", filename);
54576
54595
  }
54577
54596
  async function writeComposeFile(opts) {
@@ -61148,7 +61167,7 @@ init_compose();
61148
61167
  init_vault();
61149
61168
  import * as net3 from "node:net";
61150
61169
  import { mkdirSync as mkdirSync23, chmodSync as chmodSync7, chownSync as chownSync4, existsSync as existsSync37, readFileSync as readFileSync32, readdirSync as readdirSync17, statSync as statSync20, unlinkSync as unlinkSync8, writeFileSync as writeFileSync19, renameSync as renameSync10 } from "node:fs";
61151
- import { dirname as dirname7, resolve as resolve26, basename as basename5 } from "node:path";
61170
+ import { dirname as dirname7, resolve as resolve26, basename as basename6 } from "node:path";
61152
61171
  import * as os4 from "node:os";
61153
61172
  import * as path3 from "node:path";
61154
61173
 
@@ -61170,7 +61189,7 @@ import {
61170
61189
  unlinkSync as unlinkSync7
61171
61190
  } from "node:fs";
61172
61191
  import { createHash as createHash5 } from "node:crypto";
61173
- import { basename as basename4, dirname as dirname4, join as join28 } from "node:path";
61192
+ import { basename as basename5, dirname as dirname4, join as join28 } from "node:path";
61174
61193
  function vaultLayoutPaths(home2) {
61175
61194
  const switchroomRoot = join28(home2, ".switchroom");
61176
61195
  return {
@@ -61323,7 +61342,7 @@ function sha256File(path) {
61323
61342
  return createHash5("sha256").update(data).digest("hex");
61324
61343
  }
61325
61344
  function atomicReplaceWithSymlink(target, linkTarget) {
61326
- const tmp = join28(dirname4(target), `.${basename4(target)}.symlink-tmp`);
61345
+ const tmp = join28(dirname4(target), `.${basename5(target)}.symlink-tmp`);
61327
61346
  if (existsSync35(tmp)) {
61328
61347
  try {
61329
61348
  unlinkSync7(tmp);
@@ -65350,12 +65369,12 @@ class VaultBroker {
65350
65369
  }
65351
65370
  function detectVaultLayoutDrift(vaultPath) {
65352
65371
  const dir = dirname7(vaultPath);
65353
- if (basename5(dir) !== "vault")
65372
+ if (basename6(dir) !== "vault")
65354
65373
  return;
65355
- if (basename5(vaultPath) !== "vault.enc")
65374
+ if (basename6(vaultPath) !== "vault.enc")
65356
65375
  return;
65357
65376
  const switchroomDir = dirname7(dir);
65358
- if (basename5(switchroomDir) !== ".switchroom")
65377
+ if (basename6(switchroomDir) !== ".switchroom")
65359
65378
  return;
65360
65379
  const home2 = dirname7(switchroomDir);
65361
65380
  const result = inspectVaultLayout(home2);
@@ -68980,6 +68999,7 @@ init_audit_reader();
68980
68999
 
68981
69000
  // src/scheduler/dispatch.ts
68982
69001
  init_merge();
69002
+ init_overlay_loader();
68983
69003
  import { createHash as createHash9 } from "node:crypto";
68984
69004
  function collectScheduleEntries(config) {
68985
69005
  const out = [];
@@ -68993,10 +69013,12 @@ function collectScheduleEntries(config) {
68993
69013
  for (let i = 0;i < schedule.length; i++) {
68994
69014
  const entry = schedule[i];
68995
69015
  const auditMaterial = entry.prompt ?? `action:${JSON.stringify(entry.action ?? {})}`;
69016
+ const title = entry[OVERLAY_TITLE];
68996
69017
  out.push({
68997
69018
  agent,
68998
69019
  scheduleIndex: i,
68999
69020
  cron: entry.cron,
69021
+ ...typeof title === "string" && title.length > 0 ? { name: title } : {},
69000
69022
  ...entry.prompt !== undefined ? { prompt: entry.prompt } : {},
69001
69023
  promptKey: createHash9("sha256").update(auditMaterial).digest("hex").slice(0, 12),
69002
69024
  ...entry.topic !== undefined ? { topic: entry.topic } : {},
@@ -81891,10 +81913,10 @@ function registerNotionMcpLauncherCommand(program3) {
81891
81913
  // src/cli/deliver-file.ts
81892
81914
  init_client2();
81893
81915
  import { readFileSync as readFileSync61, statSync as statSync30 } from "node:fs";
81894
- import { basename as basename8 } from "node:path";
81916
+ import { basename as basename9 } from "node:path";
81895
81917
 
81896
81918
  // src/delivery/onedrive.ts
81897
- import { basename as basename6 } from "node:path";
81919
+ import { basename as basename7 } from "node:path";
81898
81920
  var GRAPH = "https://graph.microsoft.com/v1.0";
81899
81921
  var ONEDRIVE_INLINE_MAX_BYTES = 4 * 1024 * 1024;
81900
81922
  function authHeaders(token) {
@@ -82017,14 +82039,14 @@ async function createShareLink(deps, item, scopes = ["anonymous", "organization"
82017
82039
  async function deliverToOneDrive(args) {
82018
82040
  const deps = { accessToken: args.accessToken, fetchImpl: args.fetchImpl };
82019
82041
  const folder = await ensureSwitchroomFolder(deps, args.agentName);
82020
- const filename = basename6(args.localPath);
82042
+ const filename = basename7(args.localPath);
82021
82043
  const item = await uploadFile(deps, folder.id, filename, args.bytes);
82022
82044
  const link = await createShareLink(deps, item, args.linkScopes);
82023
82045
  return { itemId: item.id, link, folderPath: `Switchroom/${args.agentName}` };
82024
82046
  }
82025
82047
 
82026
82048
  // src/delivery/gdrive.ts
82027
- import { basename as basename7 } from "node:path";
82049
+ import { basename as basename8 } from "node:path";
82028
82050
  var DRIVE = "https://www.googleapis.com/drive/v3";
82029
82051
  var UPLOAD = "https://www.googleapis.com/upload/drive/v3";
82030
82052
  var FOLDER_MIME = "application/vnd.google-apps.folder";
@@ -82172,7 +82194,7 @@ async function createShareLink2(deps, file, scopes = ["anyone"]) {
82172
82194
  async function deliverToGoogleDrive(args) {
82173
82195
  const deps = { accessToken: args.accessToken, fetchImpl: args.fetchImpl };
82174
82196
  const folder = await ensureSwitchroomFolder2(deps, args.agentName);
82175
- const filename = basename7(args.localPath);
82197
+ const filename = basename8(args.localPath);
82176
82198
  const file = await uploadFile2(deps, folder.id, filename, args.bytes);
82177
82199
  const link = await createShareLink2(deps, file, args.linkScopes);
82178
82200
  return { itemId: file.id, link, folderPath: `Switchroom/${args.agentName}` };
@@ -82251,7 +82273,7 @@ async function runDeliverFile(localPath, deps = {}) {
82251
82273
  try {
82252
82274
  const bytes = read(localPath);
82253
82275
  const out = await provider.deliver({ agentName, localPath, bytes });
82254
- return { ok: true, provider: provider.name, link: out.link, folderPath: out.folderPath, filename: basename8(localPath) };
82276
+ return { ok: true, provider: provider.name, link: out.link, folderPath: out.folderPath, filename: basename9(localPath) };
82255
82277
  } catch (err) {
82256
82278
  return { ok: false, provider: provider.name, error: `upload failed: ${err.message}` };
82257
82279
  }
Binary file
Binary file
Binary file
@@ -4,6 +4,9 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Switchroom Fleet</title>
7
+ <link rel="icon" href="/favicon.ico" sizes="any">
8
+ <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png">
9
+ <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
7
10
  <style>
8
11
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
9
12
 
@@ -1916,37 +1919,81 @@
1916
1919
  return;
1917
1920
  }
1918
1921
  const dim = (s) => `<span style="color:var(--text-dim)">${escapeHtml(s)}</span>`;
1919
- // Group entries by agent.
1922
+ // Group entries by agent. Agents sorted by name; within an agent the
1923
+ // entries keep their cascade-given order (defaults → profile → agent →
1924
+ // overlays), which is the order the operator/agent declared them.
1920
1925
  const byAgent = {};
1921
1926
  for (const e of entries) (byAgent[e.agent] ||= []).push(e);
1922
- const cards = Object.keys(byAgent).sort().map(agent => {
1923
- const rows = byAgent[agent].map(e => `
1924
- <tr>
1925
- <td><code>${escapeHtml(e.cron)}</code></td>
1926
- <td title="${escapeHtml(e.prompt)}">${escapeHtml(e.prompt.length > 70 ? e.prompt.slice(0, 70) + '…' : e.prompt)}</td>
1927
- </tr>`).join('');
1928
- const fires = (recent[agent] || []).slice().reverse();
1929
- const fireRows = fires.length === 0
1930
- ? `<tr><td colspan="3">${dim('no recorded fires yet')}</td></tr>`
1927
+
1928
+ // One self-contained block per cron: title (or cron expr as fallback
1929
+ // heading) + badges, the prompt, then THIS cron's recent fires right
1930
+ // below it. Fires are matched to a cron by promptKey — both the entry
1931
+ // and each DispatchResult carry the same stable SHA. (Edge: two
1932
+ // entries with an identical prompt share a promptKey, so they'd share
1933
+ // the same fire list — acceptable; identical prompts are
1934
+ // indistinguishable in the audit ledger by design.)
1935
+ const renderCron = (e, agentFires) => {
1936
+ const hasTitle = typeof e.name === 'string' && e.name.length > 0;
1937
+ const heading = hasTitle
1938
+ ? `<span style="font-weight:600;color:var(--text)">${escapeHtml(e.name)}</span>`
1939
+ : `<span style="color:var(--text-dim)">${escapeHtml(e.cron)}</span>`;
1940
+ const badges = []
1941
+ .concat(e.kind ? [e.kind] : [])
1942
+ .concat(e.model ? [e.model] : [])
1943
+ .concat(e.context ? [e.context] : [])
1944
+ .map(b => `<span class="chip">${escapeHtml(String(b))}</span>`)
1945
+ .join('');
1946
+ // Prompt: kind:action entries have no prompt (the action is
1947
+ // model-free), so show a muted marker rather than a blank line.
1948
+ const promptText = typeof e.prompt === 'string' ? e.prompt : '';
1949
+ const promptBlock = promptText
1950
+ ? `<div style="margin:.4rem 0 .55rem;color:var(--text);font-size:.84rem" title="${escapeHtml(promptText)}">${escapeHtml(promptText.length > 140 ? promptText.slice(0, 140) + '…' : promptText)}</div>`
1951
+ : `<div style="margin:.4rem 0 .55rem">${dim(e.kind === 'action' ? 'model-free action' : '—')}</div>`;
1952
+ // Recent fires for THIS cron only — filter the agent's fire list by
1953
+ // matching promptKey, newest-first, last 8.
1954
+ const fires = agentFires
1955
+ .filter(f => f.promptKey === e.promptKey)
1956
+ .slice()
1957
+ .reverse()
1958
+ .slice(0, 8);
1959
+ const fireList = fires.length === 0
1960
+ ? `<div style="font-size:.78rem">${dim('no recorded fires yet')}</div>`
1931
1961
  : fires.map(f => {
1932
1962
  const ok = f.exitCode === 0;
1933
- return `<tr>
1934
- <td><span class="status-dot ${ok ? 'active' : 'inactive'}" style="display:inline-block;vertical-align:middle"></span> ${escapeHtml(shortTs(new Date(f.startedAt).toISOString()))}</td>
1935
- <td>${escapeHtml(String(f.outputSummary || '').slice(0, 80))}</td>
1936
- <td>${ok ? 'ok' : 'exit ' + escapeHtml(String(f.exitCode))}</td>
1937
- </tr>`;
1963
+ const when = escapeHtml(shortTs(new Date(f.startedAt).toISOString()));
1964
+ const summary = String(f.outputSummary || '');
1965
+ const summaryHtml = summary
1966
+ ? ' · <span style="color:var(--text-dim)">' + escapeHtml(summary.length > 70 ? summary.slice(0, 70) + '…' : summary) + '</span>'
1967
+ : '';
1968
+ return `<div style="font-size:.78rem;line-height:1.55">
1969
+ <span class="status-dot ${ok ? 'active' : 'inactive'}" style="display:inline-block;vertical-align:middle;margin-right:.35rem"></span>${when} · ${ok ? 'ok' : 'exit ' + escapeHtml(String(f.exitCode))}${summaryHtml}
1970
+ </div>`;
1938
1971
  }).join('');
1972
+ return `
1973
+ <div style="border:1px solid var(--border);border-radius:8px;padding:.7rem .85rem;margin-bottom:.7rem;background:var(--surface-hover)">
1974
+ <div style="display:flex;align-items:center;gap:.5rem;flex-wrap:wrap">
1975
+ ${heading}
1976
+ <code style="font-size:.78rem">${escapeHtml(e.cron)}</code>
1977
+ ${badges}
1978
+ </div>
1979
+ ${promptBlock}
1980
+ <div style="border-top:1px solid var(--border);padding-top:.5rem">
1981
+ <div style="font-size:.72rem;color:var(--text-dim);margin-bottom:.3rem">Recent fires</div>
1982
+ ${fireList}
1983
+ </div>
1984
+ </div>`;
1985
+ };
1986
+
1987
+ const cards = Object.keys(byAgent).sort().map(agent => {
1988
+ const agentFires = recent[agent] || [];
1989
+ const list = byAgent[agent].map(e => renderCron(e, agentFires)).join('');
1939
1990
  return `
1940
1991
  <div class="agent-card" style="margin-bottom:1rem">
1941
1992
  <div class="card-header" style="cursor:default">
1942
1993
  <span class="agent-name">${escapeHtml(agent)}</span>
1943
1994
  <span class="agent-topic">${byAgent[agent].length} schedule entr${byAgent[agent].length === 1 ? 'y' : 'ies'}</span>
1944
1995
  </div>
1945
- <div style="padding:0 1.25rem 1rem">
1946
- <table class="accounts-table"><thead><tr><th>Cron</th><th>Prompt</th></tr></thead><tbody>${rows}</tbody></table>
1947
- <div style="margin-top:0.6rem;font-size:0.8rem;color:var(--text-dim)">Recent fires (from scheduler.jsonl)</div>
1948
- <table class="accounts-table"><thead><tr><th>When</th><th>Result</th><th>Exit</th></tr></thead><tbody>${fireRows}</tbody></table>
1949
- </div>
1996
+ <div style="padding:.5rem 1.25rem 1rem">${list}</div>
1950
1997
  </div>`;
1951
1998
  }).join('');
1952
1999
  container.innerHTML = degradedBanner + truncatedNote + cards;
@@ -14362,7 +14362,7 @@ function resolveDualPath(pathStr) {
14362
14362
 
14363
14363
  // src/config/overlay-loader.ts
14364
14364
  import { existsSync as existsSync2, readFileSync, readdirSync, statSync } from "node:fs";
14365
- import { resolve as resolve2 } from "node:path";
14365
+ import { basename, resolve as resolve2 } from "node:path";
14366
14366
 
14367
14367
  // src/config/overlay-schema.ts
14368
14368
  var OverlayDocSchema = exports_external.object({
@@ -14372,6 +14372,16 @@ var OverlayDocSchema = exports_external.object({
14372
14372
 
14373
14373
  // src/config/overlay-loader.ts
14374
14374
  var OVERLAY_SOURCE = Symbol.for("switchroom.config.overlay-source");
14375
+ var OVERLAY_TITLE = Symbol.for("switchroom.config.overlay-title");
14376
+ function deriveOverlayTitle(raw, fileName) {
14377
+ const titleFromComment = raw.match(/^#[^\S\n]*name:[^\S\n]*(\S.*?)[^\S\n]*$/m)?.[1];
14378
+ if (titleFromComment)
14379
+ return titleFromComment;
14380
+ const base = fileName.replace(/\.ya?ml$/i, "");
14381
+ if (/^cron-[0-9a-f]{6,}$/i.test(base))
14382
+ return;
14383
+ return base.length > 0 ? base : undefined;
14384
+ }
14375
14385
  function overlayDirFor(agentName, subdir) {
14376
14386
  const base = resolveDualPath(`~/.switchroom/agents/${agentName}/${subdir}`);
14377
14387
  return resolve2(base);
@@ -14397,13 +14407,21 @@ function listYamlFiles(dir) {
14397
14407
  }
14398
14408
  return out.sort();
14399
14409
  }
14400
- function stampOverlay(entry) {
14410
+ function stampOverlay(entry, title) {
14401
14411
  Object.defineProperty(entry, OVERLAY_SOURCE, {
14402
14412
  value: true,
14403
14413
  enumerable: false,
14404
14414
  configurable: false,
14405
14415
  writable: false
14406
14416
  });
14417
+ if (title !== undefined) {
14418
+ Object.defineProperty(entry, OVERLAY_TITLE, {
14419
+ value: title,
14420
+ enumerable: false,
14421
+ configurable: false,
14422
+ writable: false
14423
+ });
14424
+ }
14407
14425
  return entry;
14408
14426
  }
14409
14427
  function applyAgentOverlays(config) {
@@ -14420,6 +14438,7 @@ function applyAgentOverlays(config) {
14420
14438
  const raw = readFileSync(file, "utf-8");
14421
14439
  const parsed = $parse(raw);
14422
14440
  const doc = OverlayDocSchema.parse(parsed);
14441
+ const title = deriveOverlayTitle(raw, basename(file));
14423
14442
  for (const entry of doc.schedule ?? []) {
14424
14443
  if (entry.secrets && entry.secrets.length > 0) {
14425
14444
  const w = {
@@ -14431,7 +14450,7 @@ function applyAgentOverlays(config) {
14431
14450
  console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${file}': ${w.reason}`);
14432
14451
  continue;
14433
14452
  }
14434
- merged.push(stampOverlay(entry));
14453
+ merged.push(stampOverlay(entry, title));
14435
14454
  }
14436
14455
  } catch (err) {
14437
14456
  const reason = err instanceof ZodError ? `schema rejection: ${err.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ")}` : `parse error: ${err.message}`;
@@ -20535,8 +20554,8 @@ function applyPatch(unifiedDiff, configPath, gitBin) {
20535
20554
  const liveContent = readFileSync4(configPath, "utf8");
20536
20555
  const scratchDir = mkdtempSync(join2(tmpdir(), "config-propose-edit-"));
20537
20556
  try {
20538
- const basename = configPath.split("/").pop() ?? "switchroom.yaml";
20539
- const scratchFile = join2(scratchDir, basename);
20557
+ const basename2 = configPath.split("/").pop() ?? "switchroom.yaml";
20558
+ const scratchFile = join2(scratchDir, basename2);
20540
20559
  writeFileSync2(scratchFile, liveContent);
20541
20560
  const patchFile = join2(scratchDir, "proposal.patch");
20542
20561
  writeFileSync2(patchFile, unifiedDiff);
@@ -21013,10 +21032,12 @@ function collectScheduleEntries(config) {
21013
21032
  for (let i = 0;i < schedule.length; i++) {
21014
21033
  const entry = schedule[i];
21015
21034
  const auditMaterial = entry.prompt ?? `action:${JSON.stringify(entry.action ?? {})}`;
21035
+ const title = entry[OVERLAY_TITLE];
21016
21036
  out.push({
21017
21037
  agent,
21018
21038
  scheduleIndex: i,
21019
21039
  cron: entry.cron,
21040
+ ...typeof title === "string" && title.length > 0 ? { name: title } : {},
21020
21041
  ...entry.prompt !== undefined ? { prompt: entry.prompt } : {},
21021
21042
  promptKey: createHash4("sha256").update(auditMaterial).digest("hex").slice(0, 12),
21022
21043
  ...entry.topic !== undefined ? { topic: entry.topic } : {},
@@ -22148,7 +22169,7 @@ function tail(s, bytes = TAIL_BYTES) {
22148
22169
  }
22149
22170
  var SCHEDULE_PROMPT_MAX_CHARS = 160;
22150
22171
  var SCHEDULE_OUTPUT_SUMMARY_MAX_CHARS = 100;
22151
- var SCHEDULE_MAX_FIRES_PER_AGENT = 8;
22172
+ var SCHEDULE_MAX_FIRES_PER_CRON = 8;
22152
22173
  var SCHEDULE_FRAME_BUDGET_BYTES = MAX_FRAME_BYTES - 2048;
22153
22174
  function slimScheduleEntry(e) {
22154
22175
  const slim = {
@@ -22157,6 +22178,8 @@ function slimScheduleEntry(e) {
22157
22178
  cron: e.cron,
22158
22179
  promptKey: e.promptKey
22159
22180
  };
22181
+ if (e.name !== undefined)
22182
+ slim.name = e.name;
22160
22183
  if (e.prompt !== undefined) {
22161
22184
  slim.prompt = e.prompt.length > SCHEDULE_PROMPT_MAX_CHARS ? e.prompt.slice(0, SCHEDULE_PROMPT_MAX_CHARS) : e.prompt;
22162
22185
  }
@@ -22175,9 +22198,23 @@ function scheduleFrameBytes(view) {
22175
22198
  }
22176
22199
  function boundScheduleView(entries, recentByAgent) {
22177
22200
  const slimEntries = entries.map(slimScheduleEntry);
22201
+ const truncSummary = (r) => typeof r.outputSummary === "string" && r.outputSummary.length > SCHEDULE_OUTPUT_SUMMARY_MAX_CHARS ? { ...r, outputSummary: r.outputSummary.slice(0, SCHEDULE_OUTPUT_SUMMARY_MAX_CHARS) } : { ...r };
22178
22202
  const boundedRecent = {};
22179
22203
  for (const [agent, rows] of Object.entries(recentByAgent)) {
22180
- boundedRecent[agent] = rows.slice(-SCHEDULE_MAX_FIRES_PER_AGENT).map((r) => typeof r.outputSummary === "string" && r.outputSummary.length > SCHEDULE_OUTPUT_SUMMARY_MAX_CHARS ? { ...r, outputSummary: r.outputSummary.slice(0, SCHEDULE_OUTPUT_SUMMARY_MAX_CHARS) } : { ...r });
22204
+ const byKey = new Map;
22205
+ for (const r of rows) {
22206
+ const arr = byKey.get(r.promptKey);
22207
+ if (arr)
22208
+ arr.push(r);
22209
+ else
22210
+ byKey.set(r.promptKey, [r]);
22211
+ }
22212
+ const kept = [];
22213
+ for (const arr of byKey.values()) {
22214
+ for (const r of arr.slice(-SCHEDULE_MAX_FIRES_PER_CRON))
22215
+ kept.push(truncSummary(r));
22216
+ }
22217
+ boundedRecent[agent] = kept;
22181
22218
  }
22182
22219
  let view = { entries: slimEntries, recentByAgent: boundedRecent };
22183
22220
  if (scheduleFrameBytes(view) <= SCHEDULE_FRAME_BUDGET_BYTES)
@@ -11971,7 +11971,16 @@ var init_overlay_schema = __esm(() => {
11971
11971
 
11972
11972
  // src/config/overlay-loader.ts
11973
11973
  import { existsSync as existsSync2, readFileSync, readdirSync, statSync } from "node:fs";
11974
- import { resolve as resolve2 } from "node:path";
11974
+ import { basename, resolve as resolve2 } from "node:path";
11975
+ function deriveOverlayTitle(raw, fileName) {
11976
+ const titleFromComment = raw.match(/^#[^\S\n]*name:[^\S\n]*(\S.*?)[^\S\n]*$/m)?.[1];
11977
+ if (titleFromComment)
11978
+ return titleFromComment;
11979
+ const base = fileName.replace(/\.ya?ml$/i, "");
11980
+ if (/^cron-[0-9a-f]{6,}$/i.test(base))
11981
+ return;
11982
+ return base.length > 0 ? base : undefined;
11983
+ }
11975
11984
  function overlayDirFor(agentName, subdir) {
11976
11985
  const base = resolveDualPath(`~/.switchroom/agents/${agentName}/${subdir}`);
11977
11986
  return resolve2(base);
@@ -11997,13 +12006,21 @@ function listYamlFiles(dir) {
11997
12006
  }
11998
12007
  return out.sort();
11999
12008
  }
12000
- function stampOverlay(entry) {
12009
+ function stampOverlay(entry, title) {
12001
12010
  Object.defineProperty(entry, OVERLAY_SOURCE, {
12002
12011
  value: true,
12003
12012
  enumerable: false,
12004
12013
  configurable: false,
12005
12014
  writable: false
12006
12015
  });
12016
+ if (title !== undefined) {
12017
+ Object.defineProperty(entry, OVERLAY_TITLE, {
12018
+ value: title,
12019
+ enumerable: false,
12020
+ configurable: false,
12021
+ writable: false
12022
+ });
12023
+ }
12007
12024
  return entry;
12008
12025
  }
12009
12026
  function applyAgentOverlays(config) {
@@ -12020,6 +12037,7 @@ function applyAgentOverlays(config) {
12020
12037
  const raw = readFileSync(file, "utf-8");
12021
12038
  const parsed = $parse(raw);
12022
12039
  const doc = OverlayDocSchema.parse(parsed);
12040
+ const title = deriveOverlayTitle(raw, basename(file));
12023
12041
  for (const entry of doc.schedule ?? []) {
12024
12042
  if (entry.secrets && entry.secrets.length > 0) {
12025
12043
  const w = {
@@ -12031,7 +12049,7 @@ function applyAgentOverlays(config) {
12031
12049
  console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${file}': ${w.reason}`);
12032
12050
  continue;
12033
12051
  }
12034
- merged.push(stampOverlay(entry));
12052
+ merged.push(stampOverlay(entry, title));
12035
12053
  }
12036
12054
  } catch (err) {
12037
12055
  const reason = err instanceof ZodError ? `schema rejection: ${err.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ")}` : `parse error: ${err.message}`;
@@ -12085,13 +12103,14 @@ function applyAgentOverlays(config) {
12085
12103
  }
12086
12104
  return { config, warnings };
12087
12105
  }
12088
- var OVERLAY_SOURCE;
12106
+ var OVERLAY_SOURCE, OVERLAY_TITLE;
12089
12107
  var init_overlay_loader = __esm(() => {
12090
12108
  init_dist();
12091
12109
  init_zod();
12092
12110
  init_overlay_schema();
12093
12111
  init_paths();
12094
12112
  OVERLAY_SOURCE = Symbol.for("switchroom.config.overlay-source");
12113
+ OVERLAY_TITLE = Symbol.for("switchroom.config.overlay-title");
12095
12114
  });
12096
12115
 
12097
12116
  // src/config/notion-workspace-acl.ts
@@ -12306,7 +12325,7 @@ var init_loader = __esm(() => {
12306
12325
  // src/vault/approvals/kernel-server.ts
12307
12326
  import * as net from "node:net";
12308
12327
  import { mkdirSync, chmodSync, chownSync, existsSync as existsSync4, unlinkSync, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
12309
- import { dirname, resolve as resolve4, basename } from "node:path";
12328
+ import { dirname, resolve as resolve4, basename as basename2 } from "node:path";
12310
12329
  import { Database } from "bun:sqlite";
12311
12330
 
12312
12331
  // src/vault/broker/protocol.ts
@@ -13507,7 +13526,7 @@ async function main() {
13507
13526
  }
13508
13527
  }
13509
13528
  if (agents.length === 0) {
13510
- const fallbackName = basename(socketEnv).replace(/\.sock$/, "");
13529
+ const fallbackName = basename2(socketEnv).replace(/\.sock$/, "");
13511
13530
  process.umask(63);
13512
13531
  mkdirSync(socketParent, { recursive: true, mode: 493 });
13513
13532
  try {
@@ -11971,7 +11971,16 @@ var init_overlay_schema = __esm(() => {
11971
11971
 
11972
11972
  // src/config/overlay-loader.ts
11973
11973
  import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync3 } from "node:fs";
11974
- import { resolve as resolve3 } from "node:path";
11974
+ import { basename as basename3, resolve as resolve3 } from "node:path";
11975
+ function deriveOverlayTitle(raw, fileName) {
11976
+ const titleFromComment = raw.match(/^#[^\S\n]*name:[^\S\n]*(\S.*?)[^\S\n]*$/m)?.[1];
11977
+ if (titleFromComment)
11978
+ return titleFromComment;
11979
+ const base = fileName.replace(/\.ya?ml$/i, "");
11980
+ if (/^cron-[0-9a-f]{6,}$/i.test(base))
11981
+ return;
11982
+ return base.length > 0 ? base : undefined;
11983
+ }
11975
11984
  function overlayDirFor(agentName, subdir) {
11976
11985
  const base = resolveDualPath(`~/.switchroom/agents/${agentName}/${subdir}`);
11977
11986
  return resolve3(base);
@@ -11997,13 +12006,21 @@ function listYamlFiles(dir) {
11997
12006
  }
11998
12007
  return out.sort();
11999
12008
  }
12000
- function stampOverlay(entry) {
12009
+ function stampOverlay(entry, title) {
12001
12010
  Object.defineProperty(entry, OVERLAY_SOURCE, {
12002
12011
  value: true,
12003
12012
  enumerable: false,
12004
12013
  configurable: false,
12005
12014
  writable: false
12006
12015
  });
12016
+ if (title !== undefined) {
12017
+ Object.defineProperty(entry, OVERLAY_TITLE, {
12018
+ value: title,
12019
+ enumerable: false,
12020
+ configurable: false,
12021
+ writable: false
12022
+ });
12023
+ }
12007
12024
  return entry;
12008
12025
  }
12009
12026
  function applyAgentOverlays(config) {
@@ -12020,6 +12037,7 @@ function applyAgentOverlays(config) {
12020
12037
  const raw = readFileSync5(file, "utf-8");
12021
12038
  const parsed = $parse(raw);
12022
12039
  const doc = OverlayDocSchema.parse(parsed);
12040
+ const title = deriveOverlayTitle(raw, basename3(file));
12023
12041
  for (const entry of doc.schedule ?? []) {
12024
12042
  if (entry.secrets && entry.secrets.length > 0) {
12025
12043
  const w = {
@@ -12031,7 +12049,7 @@ function applyAgentOverlays(config) {
12031
12049
  console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${file}': ${w.reason}`);
12032
12050
  continue;
12033
12051
  }
12034
- merged.push(stampOverlay(entry));
12052
+ merged.push(stampOverlay(entry, title));
12035
12053
  }
12036
12054
  } catch (err) {
12037
12055
  const reason = err instanceof ZodError ? `schema rejection: ${err.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ")}` : `parse error: ${err.message}`;
@@ -12085,13 +12103,14 @@ function applyAgentOverlays(config) {
12085
12103
  }
12086
12104
  return { config, warnings };
12087
12105
  }
12088
- var OVERLAY_SOURCE;
12106
+ var OVERLAY_SOURCE, OVERLAY_TITLE;
12089
12107
  var init_overlay_loader = __esm(() => {
12090
12108
  init_dist();
12091
12109
  init_zod();
12092
12110
  init_overlay_schema();
12093
12111
  init_paths();
12094
12112
  OVERLAY_SOURCE = Symbol.for("switchroom.config.overlay-source");
12113
+ OVERLAY_TITLE = Symbol.for("switchroom.config.overlay-title");
12095
12114
  });
12096
12115
 
12097
12116
  // src/config/notion-workspace-acl.ts
@@ -12640,7 +12659,7 @@ function allocateAgentUid(name) {
12640
12659
  var BIND_MOUNT_EXACT_SOURCE_DENY = new Set(["/var/run/docker.sock"]);
12641
12660
 
12642
12661
  // src/vault/broker/server.ts
12643
- import { dirname as dirname4, resolve as resolve6, basename as basename3 } from "node:path";
12662
+ import { dirname as dirname4, resolve as resolve6, basename as basename4 } from "node:path";
12644
12663
  import * as os3 from "node:os";
12645
12664
  import * as path3 from "node:path";
12646
12665
 
@@ -17778,12 +17797,12 @@ class VaultBroker {
17778
17797
  }
17779
17798
  function detectVaultLayoutDrift(vaultPath) {
17780
17799
  const dir = dirname4(vaultPath);
17781
- if (basename3(dir) !== "vault")
17800
+ if (basename4(dir) !== "vault")
17782
17801
  return;
17783
- if (basename3(vaultPath) !== "vault.enc")
17802
+ if (basename4(vaultPath) !== "vault.enc")
17784
17803
  return;
17785
17804
  const switchroomDir = dirname4(dir);
17786
- if (basename3(switchroomDir) !== ".switchroom")
17805
+ if (basename4(switchroomDir) !== ".switchroom")
17787
17806
  return;
17788
17807
  const home2 = dirname4(switchroomDir);
17789
17808
  const result = inspectVaultLayout(home2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchroom",
3
- "version": "0.15.27",
3
+ "version": "0.15.29",
4
4
  "description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24474,7 +24474,16 @@ var init_overlay_schema = __esm(() => {
24474
24474
 
24475
24475
  // ../src/config/overlay-loader.ts
24476
24476
  import { existsSync as existsSync9, readFileSync as readFileSync5, readdirSync, statSync as statSync2 } from "node:fs";
24477
- import { resolve as resolve3 } from "node:path";
24477
+ import { basename as basename3, resolve as resolve3 } from "node:path";
24478
+ function deriveOverlayTitle(raw, fileName) {
24479
+ const titleFromComment = raw.match(/^#[^\S\n]*name:[^\S\n]*(\S.*?)[^\S\n]*$/m)?.[1];
24480
+ if (titleFromComment)
24481
+ return titleFromComment;
24482
+ const base = fileName.replace(/\.ya?ml$/i, "");
24483
+ if (/^cron-[0-9a-f]{6,}$/i.test(base))
24484
+ return;
24485
+ return base.length > 0 ? base : undefined;
24486
+ }
24478
24487
  function overlayDirFor(agentName3, subdir) {
24479
24488
  const base = resolveDualPath(`~/.switchroom/agents/${agentName3}/${subdir}`);
24480
24489
  return resolve3(base);
@@ -24500,13 +24509,21 @@ function listYamlFiles(dir) {
24500
24509
  }
24501
24510
  return out.sort();
24502
24511
  }
24503
- function stampOverlay(entry) {
24512
+ function stampOverlay(entry, title) {
24504
24513
  Object.defineProperty(entry, OVERLAY_SOURCE, {
24505
24514
  value: true,
24506
24515
  enumerable: false,
24507
24516
  configurable: false,
24508
24517
  writable: false
24509
24518
  });
24519
+ if (title !== undefined) {
24520
+ Object.defineProperty(entry, OVERLAY_TITLE, {
24521
+ value: title,
24522
+ enumerable: false,
24523
+ configurable: false,
24524
+ writable: false
24525
+ });
24526
+ }
24510
24527
  return entry;
24511
24528
  }
24512
24529
  function applyAgentOverlays(config) {
@@ -24523,6 +24540,7 @@ function applyAgentOverlays(config) {
24523
24540
  const raw = readFileSync5(file, "utf-8");
24524
24541
  const parsed = $parse(raw);
24525
24542
  const doc = OverlayDocSchema.parse(parsed);
24543
+ const title = deriveOverlayTitle(raw, basename3(file));
24526
24544
  for (const entry of doc.schedule ?? []) {
24527
24545
  if (entry.secrets && entry.secrets.length > 0) {
24528
24546
  const w = {
@@ -24534,7 +24552,7 @@ function applyAgentOverlays(config) {
24534
24552
  console.warn(`[switchroom] overlay-loader: agent='${agentName3}' file='${file}': ${w.reason}`);
24535
24553
  continue;
24536
24554
  }
24537
- merged.push(stampOverlay(entry));
24555
+ merged.push(stampOverlay(entry, title));
24538
24556
  }
24539
24557
  } catch (err) {
24540
24558
  const reason = err instanceof ZodError ? `schema rejection: ${err.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ")}` : `parse error: ${err.message}`;
@@ -24588,13 +24606,14 @@ function applyAgentOverlays(config) {
24588
24606
  }
24589
24607
  return { config, warnings };
24590
24608
  }
24591
- var OVERLAY_SOURCE;
24609
+ var OVERLAY_SOURCE, OVERLAY_TITLE;
24592
24610
  var init_overlay_loader = __esm(() => {
24593
24611
  init_dist();
24594
24612
  init_zod();
24595
24613
  init_overlay_schema();
24596
24614
  init_paths();
24597
24615
  OVERLAY_SOURCE = Symbol.for("switchroom.config.overlay-source");
24616
+ OVERLAY_TITLE = Symbol.for("switchroom.config.overlay-title");
24598
24617
  });
24599
24618
 
24600
24619
  // ../src/config/merge.ts
@@ -31522,7 +31541,7 @@ import {
31522
31541
  appendFileSync as appendFileSync5
31523
31542
  } from "fs";
31524
31543
  import { homedir as homedir14 } from "os";
31525
- import { join as join35, extname, sep as sep3, basename as basename9 } from "path";
31544
+ import { join as join35, extname, sep as sep3, basename as basename10 } from "path";
31526
31545
 
31527
31546
  // plugin-logger.ts
31528
31547
  import { appendFileSync, mkdirSync, renameSync, statSync, existsSync } from "fs";
@@ -51508,7 +51527,7 @@ function sanitiseToolArg(name, raw) {
51508
51527
  case "NotebookEdit": {
51509
51528
  const fp = raw.file_path;
51510
51529
  if (typeof fp === "string" && fp.length > 0)
51511
- out = basename3(fp);
51530
+ out = basename4(fp);
51512
51531
  break;
51513
51532
  }
51514
51533
  case "Bash": {
@@ -51544,7 +51563,7 @@ function sanitiseToolArg(name, raw) {
51544
51563
  out = out.slice(0, SANITISE_MAX_LEN - 1) + "\u2026";
51545
51564
  return out;
51546
51565
  }
51547
- function basename3(p) {
51566
+ function basename4(p) {
51548
51567
  const idx = p.lastIndexOf("/");
51549
51568
  return idx === -1 ? p : p.slice(idx + 1);
51550
51569
  }
@@ -53139,10 +53158,10 @@ function defaultReadEvents(stateDir) {
53139
53158
  return readAll(stateDir);
53140
53159
  }
53141
53160
  // permission-title.ts
53142
- import { basename as basename6 } from "node:path";
53161
+ import { basename as basename7 } from "node:path";
53143
53162
 
53144
53163
  // permission-rule.ts
53145
- import { basename as basename5 } from "node:path";
53164
+ import { basename as basename6 } from "node:path";
53146
53165
  var FILE_TOOLS = new Set([
53147
53166
  "Edit",
53148
53167
  "Write",
@@ -53203,7 +53222,7 @@ function skillBasenameFromPath(input) {
53203
53222
  if (!path)
53204
53223
  return null;
53205
53224
  const trimmed = path.replace(/\/SKILL\.md$/i, "").replace(/\/$/, "");
53206
- return basename5(trimmed) || null;
53225
+ return basename6(trimmed) || null;
53207
53226
  }
53208
53227
  function matchesAllowRule(rule, toolName, inputPreview) {
53209
53228
  if (!rule || !toolName)
@@ -53432,11 +53451,11 @@ function describeGrant(toolName, inputPreview, option) {
53432
53451
  return m ? `run ${m[1]} commands` : "run that command";
53433
53452
  }
53434
53453
  if (t === "Edit" || t === "MultiEdit" || t === "NotebookEdit")
53435
- return `edit ${basename6(arg)}`;
53454
+ return `edit ${basename7(arg)}`;
53436
53455
  if (t === "Write")
53437
- return `write ${basename6(arg)}`;
53456
+ return `write ${basename7(arg)}`;
53438
53457
  if (t === "Read")
53439
- return `read ${basename6(arg)}`;
53458
+ return `read ${basename7(arg)}`;
53440
53459
  return naturalAction(toolName, inputPreview);
53441
53460
  }
53442
53461
  switch (rule) {
@@ -53475,7 +53494,7 @@ function fileBase(input) {
53475
53494
  if (!input)
53476
53495
  return null;
53477
53496
  const p = readString2(input, "file_path") ?? readString2(input, "notebook_path");
53478
- return p ? basename6(p) : null;
53497
+ return p ? basename7(p) : null;
53479
53498
  }
53480
53499
  function lowerFirst(text) {
53481
53500
  return text.length > 0 ? text.charAt(0).toLowerCase() + text.slice(1) : text;
@@ -53521,7 +53540,7 @@ function truncate6(text, max) {
53521
53540
  }
53522
53541
 
53523
53542
  // permission-rule.ts
53524
- import { basename as basename7 } from "node:path";
53543
+ import { basename as basename8 } from "node:path";
53525
53544
  var FILE_TOOLS2 = new Set([
53526
53545
  "Edit",
53527
53546
  "Write",
@@ -53636,14 +53655,14 @@ function skillBasenameFromPath3(input) {
53636
53655
  if (!path)
53637
53656
  return null;
53638
53657
  const trimmed = path.replace(/\/SKILL\.md$/i, "").replace(/\/$/, "");
53639
- return basename7(trimmed) || null;
53658
+ return basename8(trimmed) || null;
53640
53659
  }
53641
53660
  function isRulePersisted(resolvedAllow, ruleRule) {
53642
53661
  return resolvedAllow.includes(ruleRule);
53643
53662
  }
53644
53663
 
53645
53664
  // scoped-approval.ts
53646
- import { basename as basename8 } from "node:path";
53665
+ import { basename as basename9 } from "node:path";
53647
53666
  var SCOPED_APPROVAL_DEFAULT_TTL_MS = 30 * 60 * 1000;
53648
53667
  function scopedApprovalTtlMs(env = process.env) {
53649
53668
  const raw = env.SWITCHROOM_SCOPED_APPROVAL_TTL_MS;
@@ -53668,7 +53687,7 @@ function resolveTimeBox(toolName, inputPreview, choices) {
53668
53687
  const fileMatch = FILE_RULE.exec(rule);
53669
53688
  if (fileMatch) {
53670
53689
  const verb = fileMatch[1] === "Read" ? "reads of" : "edits to";
53671
- return { rule, breadth: `${verb} ${basename8(fileMatch[2])}` };
53690
+ return { rule, breadth: `${verb} ${basename9(fileMatch[2])}` };
53672
53691
  }
53673
53692
  const bashMatch = BASH_FAMILY_RULE.exec(rule);
53674
53693
  if (bashMatch) {
@@ -54441,10 +54460,10 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
54441
54460
  }
54442
54461
 
54443
54462
  // ../src/build-info.ts
54444
- var VERSION = "0.15.27";
54445
- var COMMIT_SHA = "97160057";
54446
- var COMMIT_DATE = "2026-06-15T06:43:23Z";
54447
- var LATEST_PR = 2372;
54463
+ var VERSION = "0.15.29";
54464
+ var COMMIT_SHA = "eddde22d";
54465
+ var COMMIT_DATE = "2026-06-15T09:43:58Z";
54466
+ var LATEST_PR = 2376;
54448
54467
  var COMMITS_AHEAD_OF_TAG = 0;
54449
54468
 
54450
54469
  // gateway/boot-version.ts
@@ -61794,7 +61813,7 @@ function getMyAgentName() {
61794
61813
  const fromEnv = process.env.SWITCHROOM_AGENT_NAME;
61795
61814
  if (fromEnv && fromEnv.trim().length > 0)
61796
61815
  return fromEnv.trim();
61797
- return basename9(process.cwd());
61816
+ return basename10(process.cwd());
61798
61817
  }
61799
61818
  function isSelfTargetingCommand(name) {
61800
61819
  if (name === "all")