switchroom 0.16.22 → 0.16.24

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.
Files changed (118) hide show
  1. package/dist/cli/self-improve-apply-guard-pretool.mjs +6 -0
  2. package/dist/cli/skill-validate-pretool.mjs +2865 -48
  3. package/dist/cli/switchroom.js +338 -92
  4. package/dist/host-control/main.js +1 -1
  5. package/package.json +5 -5
  6. package/telegram-plugin/answer-stream.ts +13 -43
  7. package/telegram-plugin/auth-snapshot-format.ts +27 -30
  8. package/telegram-plugin/auto-fallback-fleet.ts +6 -11
  9. package/telegram-plugin/card-format.ts +28 -25
  10. package/telegram-plugin/credits-watch.ts +5 -10
  11. package/telegram-plugin/dist/gateway/gateway.js +1670 -1892
  12. package/telegram-plugin/draft-stream.ts +4 -4
  13. package/telegram-plugin/format.ts +101 -662
  14. package/telegram-plugin/gateway/approval-callback.ts +2 -3
  15. package/telegram-plugin/gateway/approval-card.test.ts +17 -4
  16. package/telegram-plugin/gateway/approval-card.ts +16 -6
  17. package/telegram-plugin/gateway/approvals-commands.ts +18 -24
  18. package/telegram-plugin/gateway/auth-command.ts +74 -74
  19. package/telegram-plugin/gateway/auth-line.ts +5 -15
  20. package/telegram-plugin/gateway/boot-card.ts +20 -22
  21. package/telegram-plugin/gateway/boot-version.ts +3 -2
  22. package/telegram-plugin/gateway/config-approval-handler.test.ts +35 -33
  23. package/telegram-plugin/gateway/config-approval-handler.ts +24 -24
  24. package/telegram-plugin/gateway/config-snapshot.ts +9 -9
  25. package/telegram-plugin/gateway/diff-preview-card.test.ts +8 -8
  26. package/telegram-plugin/gateway/diff-preview-card.ts +2 -5
  27. package/telegram-plugin/gateway/disconnect-flush.ts +0 -4
  28. package/telegram-plugin/gateway/effort-command.ts +17 -17
  29. package/telegram-plugin/gateway/folder-picker-handler.test.ts +8 -2
  30. package/telegram-plugin/gateway/folder-picker-handler.ts +3 -4
  31. package/telegram-plugin/gateway/gateway.ts +751 -624
  32. package/telegram-plugin/gateway/inject-handler.test.ts +15 -13
  33. package/telegram-plugin/gateway/inject-handler.ts +5 -5
  34. package/telegram-plugin/gateway/ipc-protocol.ts +33 -1
  35. package/telegram-plugin/gateway/ipc-server.ts +28 -0
  36. package/telegram-plugin/gateway/linear-activity.ts +16 -14
  37. package/telegram-plugin/gateway/linear-setup.ts +1 -1
  38. package/telegram-plugin/gateway/model-command.ts +25 -25
  39. package/telegram-plugin/gateway/oversize-card-body.ts +6 -7
  40. package/telegram-plugin/gateway/skill-proposal-card.ts +167 -0
  41. package/telegram-plugin/inline-keyboard-callbacks.ts +19 -13
  42. package/telegram-plugin/issues-card.ts +6 -7
  43. package/telegram-plugin/model-unavailable.ts +8 -12
  44. package/telegram-plugin/operator-events-history.ts +1 -1
  45. package/telegram-plugin/operator-events.ts +24 -28
  46. package/telegram-plugin/package.json +1 -1
  47. package/telegram-plugin/pending-work-progress.ts +36 -36
  48. package/telegram-plugin/permission-title.ts +36 -20
  49. package/telegram-plugin/pty-partial-handler.ts +5 -13
  50. package/telegram-plugin/quota-check.ts +5 -5
  51. package/telegram-plugin/quota-watch.ts +13 -18
  52. package/telegram-plugin/recent-outbound-dedup.ts +5 -5
  53. package/telegram-plugin/registry/turns-schema.ts +44 -2
  54. package/telegram-plugin/retry-api-call.ts +15 -7
  55. package/telegram-plugin/rich-send.ts +57 -0
  56. package/telegram-plugin/secret-detect/vault-error.test.ts +6 -6
  57. package/telegram-plugin/secret-detect/vault-error.ts +29 -22
  58. package/telegram-plugin/shared/bot-runtime.ts +23 -6
  59. package/telegram-plugin/silence-poke.ts +26 -69
  60. package/telegram-plugin/slot-banner-driver.ts +9 -6
  61. package/telegram-plugin/slot-banner.ts +5 -8
  62. package/telegram-plugin/steering.ts +0 -4
  63. package/telegram-plugin/stream-controller.ts +59 -62
  64. package/telegram-plugin/stream-reply-handler.ts +40 -97
  65. package/telegram-plugin/subagent-watcher.ts +2 -2
  66. package/telegram-plugin/tests/answer-stream-silent-markers.test.ts +5 -2
  67. package/telegram-plugin/tests/answer-stream.test.ts +54 -63
  68. package/telegram-plugin/tests/auth-command-format2.test.ts +4 -4
  69. package/telegram-plugin/tests/auth-command-vernacular.test.ts +3 -2
  70. package/telegram-plugin/tests/auth-snapshot-format.test.ts +19 -18
  71. package/telegram-plugin/tests/auto-fallback-fleet.test.ts +15 -13
  72. package/telegram-plugin/tests/boot-card-reason-to-render.test.ts +27 -12
  73. package/telegram-plugin/tests/boot-card-render.test.ts +59 -48
  74. package/telegram-plugin/tests/boot-version-string.test.ts +0 -0
  75. package/telegram-plugin/tests/bot-api.harness.ts +23 -1
  76. package/telegram-plugin/tests/bot-runtime.test.ts +23 -18
  77. package/telegram-plugin/tests/card-format.test.ts +6 -4
  78. package/telegram-plugin/tests/config-snapshot.test.ts +1 -1
  79. package/telegram-plugin/tests/credits-watch.test.ts +5 -5
  80. package/telegram-plugin/tests/fake-bot-api.ts +58 -4
  81. package/telegram-plugin/tests/finalize-callback.test.ts +11 -9
  82. package/telegram-plugin/tests/foreground-nesting.test.ts +1 -1
  83. package/telegram-plugin/tests/gateway-disconnect-flush.test.ts +3 -13
  84. package/telegram-plugin/tests/issues-card.test.ts +15 -12
  85. package/telegram-plugin/tests/linear-agent-activity.test.ts +8 -5
  86. package/telegram-plugin/tests/model-command.test.ts +2 -2
  87. package/telegram-plugin/tests/model-unavailable.test.ts +13 -13
  88. package/telegram-plugin/tests/multi-turn-continuity.test.ts +6 -10
  89. package/telegram-plugin/tests/operator-events.test.ts +7 -9
  90. package/telegram-plugin/tests/pending-work-progress.test.ts +20 -21
  91. package/telegram-plugin/tests/permission-title.test.ts +45 -41
  92. package/telegram-plugin/tests/pty-partial-handler.test.ts +8 -8
  93. package/telegram-plugin/tests/quota-check.test.ts +3 -3
  94. package/telegram-plugin/tests/quota-watch.test.ts +8 -4
  95. package/telegram-plugin/tests/secret-detect-delete-must-surface-failures.test.ts +4 -3
  96. package/telegram-plugin/tests/silence-poke.test.ts +75 -112
  97. package/telegram-plugin/tests/single-mode-stream-reply.test.ts +137 -0
  98. package/telegram-plugin/tests/skill-proposal-card.test.ts +103 -0
  99. package/telegram-plugin/tests/slot-banner-driver.e2e.test.ts +36 -24
  100. package/telegram-plugin/tests/slot-banner.test.ts +9 -6
  101. package/telegram-plugin/tests/status-accent.test.ts +29 -32
  102. package/telegram-plugin/tests/{stream-controller-html-fallback.test.ts → stream-controller-parse-fallback.test.ts} +40 -42
  103. package/telegram-plugin/tests/stream-controller.test.ts +63 -52
  104. package/telegram-plugin/tests/stream-reply-error-paths.test.ts +43 -38
  105. package/telegram-plugin/tests/stream-reply-handler.test.ts +122 -249
  106. package/telegram-plugin/tests/streaming-e2e.test.ts +35 -30
  107. package/telegram-plugin/tests/streaming-orchestration.test.ts +29 -28
  108. package/telegram-plugin/tests/telegram-format.test.ts +120 -1083
  109. package/telegram-plugin/tests/tool-activity-summary.test.ts +144 -145
  110. package/telegram-plugin/tests/welcome-text.test.ts +72 -65
  111. package/telegram-plugin/tests/worker-activity-feed.test.ts +119 -137
  112. package/telegram-plugin/text-voice-scrub.ts +8 -11
  113. package/telegram-plugin/tool-activity-summary.ts +29 -29
  114. package/telegram-plugin/welcome-text.ts +82 -83
  115. package/telegram-plugin/worker-activity-feed.ts +2 -3
  116. package/telegram-plugin/html-sanitize.ts +0 -244
  117. package/telegram-plugin/tests/html-sanitize.test.ts +0 -146
  118. package/telegram-plugin/tests/parse-mode-rotation.test.ts +0 -162
@@ -51218,7 +51218,7 @@ __export(exports_server2, {
51218
51218
  TOOLS: () => TOOLS2
51219
51219
  });
51220
51220
  import { randomBytes as randomBytes15 } from "node:crypto";
51221
- import { existsSync as existsSync86, readFileSync as readFileSync74 } from "node:fs";
51221
+ import { existsSync as existsSync86, readFileSync as readFileSync75 } from "node:fs";
51222
51222
  function selfSocketPath() {
51223
51223
  return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
51224
51224
  }
@@ -51422,7 +51422,7 @@ function getLastUpdateApplyStatus() {
51422
51422
  }
51423
51423
  let raw;
51424
51424
  try {
51425
- raw = readFileSync74(path8, "utf-8");
51425
+ raw = readFileSync75(path8, "utf-8");
51426
51426
  } catch (err2) {
51427
51427
  return errorText2(`get_status: failed to read audit log at ${path8}: ${err2.message}`);
51428
51428
  }
@@ -51692,8 +51692,8 @@ import { existsSync, readFileSync } from "node:fs";
51692
51692
  import { dirname, join } from "node:path";
51693
51693
 
51694
51694
  // src/build-info.ts
51695
- var VERSION = "0.16.22";
51696
- var COMMIT_SHA = "ea287bf4";
51695
+ var VERSION = "0.16.23";
51696
+ var COMMIT_SHA = "3c17304c";
51697
51697
 
51698
51698
  // src/cli/resolve-version.ts
51699
51699
  function readPackageVersion() {
@@ -75714,7 +75714,12 @@ function openTurnsDb(agentDir) {
75714
75714
  const db = new Database2(path4, { create: true });
75715
75715
  applySchema(db);
75716
75716
  try {
75717
- chmodSync8(path4, 384);
75717
+ chmodSync8(path4, 420);
75718
+ for (const suffix of ["-shm", "-wal"]) {
75719
+ try {
75720
+ chmodSync8(path4 + suffix, 420);
75721
+ } catch {}
75722
+ }
75718
75723
  } catch {}
75719
75724
  return db;
75720
75725
  }
@@ -75737,8 +75742,34 @@ function mapRow(row) {
75737
75742
  updated_at: row.updated_at
75738
75743
  };
75739
75744
  }
75745
+ function listDistinctThreadIds(db, chatId) {
75746
+ const rows = db.prepare(`
75747
+ SELECT DISTINCT thread_id FROM turns
75748
+ WHERE chat_id = ?
75749
+ ORDER BY thread_id ASC
75750
+ `).all(chatId);
75751
+ return rows.map((r) => r.thread_id);
75752
+ }
75740
75753
  function listTurnsForAgent(db, opts = {}) {
75741
75754
  const limit = Math.min(Math.max(1, opts.limit ?? 20), 200);
75755
+ if (opts.chatId && "threadId" in opts) {
75756
+ const rows2 = db.prepare(`
75757
+ SELECT * FROM turns
75758
+ WHERE chat_id = ? AND thread_id IS ?
75759
+ ORDER BY started_at DESC
75760
+ LIMIT ?
75761
+ `).all(opts.chatId, opts.threadId ?? null, limit);
75762
+ return rows2.map(mapRow);
75763
+ }
75764
+ if (opts.chatId) {
75765
+ const rows2 = db.prepare(`
75766
+ SELECT * FROM turns
75767
+ WHERE chat_id = ?
75768
+ ORDER BY started_at DESC
75769
+ LIMIT ?
75770
+ `).all(opts.chatId, limit);
75771
+ return rows2.map(mapRow);
75772
+ }
75742
75773
  const rows = db.prepare(`
75743
75774
  SELECT * FROM turns
75744
75775
  ORDER BY started_at DESC
@@ -75985,13 +76016,24 @@ async function handleGetLogs(name, lines = 50, deps = {}) {
75985
76016
  const fetchLogs = deps.fetchLogs ?? fetchAgentLogsViaHostd;
75986
76017
  return await fetchLogs(name, lines);
75987
76018
  }
75988
- function handleGetTurns(config, agentName, limit) {
76019
+ function parseHermesSessionId(sessionId) {
76020
+ const tilde = sessionId.indexOf("~");
76021
+ if (tilde === -1)
76022
+ return { agentName: sessionId };
76023
+ return { agentName: sessionId.slice(0, tilde), threadId: sessionId.slice(tilde + 1) || null };
76024
+ }
76025
+ function handleGetTurns(config, sessionId, limit) {
75989
76026
  try {
76027
+ const parsed = parseHermesSessionId(sessionId);
76028
+ const agentName = parsed.agentName;
76029
+ const hasThread = "threadId" in parsed;
75990
76030
  const agentsDir = resolveAgentsDir(config);
75991
76031
  const agentDir = resolve28(agentsDir, agentName);
76032
+ const chatId = config.agents?.[agentName]?.channels?.telegram?.chat_id ?? undefined;
75992
76033
  const db = openTurnsDb(agentDir);
75993
76034
  try {
75994
- const turns = listTurnsForAgent(db, { limit });
76035
+ const opts = chatId && hasThread ? { limit, chatId, threadId: parsed.threadId } : chatId ? { limit, chatId } : { limit };
76036
+ const turns = listTurnsForAgent(db, opts);
75995
76037
  return { ok: true, turns };
75996
76038
  } finally {
75997
76039
  db.close();
@@ -76003,6 +76045,22 @@ function handleGetTurns(config, agentName, limit) {
76003
76045
  };
76004
76046
  }
76005
76047
  }
76048
+ function handleListThreadIds(config, agentName) {
76049
+ try {
76050
+ const chatId = config.agents?.[agentName]?.channels?.telegram?.chat_id;
76051
+ if (!chatId)
76052
+ return [];
76053
+ const agentsDir = resolveAgentsDir(config);
76054
+ const db = openTurnsDb(resolve28(agentsDir, agentName));
76055
+ try {
76056
+ return listDistinctThreadIds(db, chatId);
76057
+ } finally {
76058
+ db.close();
76059
+ }
76060
+ } catch {
76061
+ return [];
76062
+ }
76063
+ }
76006
76064
  function handleGetSubagents(config, agentName, status) {
76007
76065
  try {
76008
76066
  const agentsDir = resolveAgentsDir(config);
@@ -77519,12 +77577,28 @@ function agentLiveness(config, agentName) {
77519
77577
  return "idle";
77520
77578
  return "offline";
77521
77579
  }
77522
- function toHermesSession(agent, liveness) {
77580
+ function sessionTitle(config, sessionId) {
77581
+ const { agentName, threadId } = parseHermesSessionId(sessionId);
77582
+ if (threadId === undefined)
77583
+ return agentName;
77584
+ const aliases = config.agents?.[agentName]?.channels?.telegram?.topic_aliases ?? {};
77585
+ const byId = Object.fromEntries(Object.entries(aliases).map(([name2, id]) => [String(id), name2]));
77586
+ if (threadId === null) {
77587
+ return `${agentName} \u00b7 ${byId["1"] ?? "General"}`;
77588
+ }
77589
+ const name = byId[threadId];
77590
+ return name ? `${agentName} \u00b7 ${name}` : `${agentName} \u00b7 ${threadId}`;
77591
+ }
77592
+ function isKnownSession(config, sessionId) {
77593
+ const { agentName } = parseHermesSessionId(sessionId);
77594
+ return !!config.agents?.[agentName];
77595
+ }
77596
+ function toHermesSession(sessionId, agent, liveness, config) {
77523
77597
  const nowSec = Math.floor(Date.now() / 1000);
77524
77598
  return {
77525
- id: agent.name,
77526
- name: agent.name,
77527
- title: agent.name,
77599
+ id: sessionId,
77600
+ name: sessionId,
77601
+ title: sessionTitle(config, sessionId),
77528
77602
  status: liveness,
77529
77603
  model: "claude",
77530
77604
  is_active: liveness === "active",
@@ -77542,10 +77616,35 @@ function toHermesSession(agent, liveness) {
77542
77616
  quota: agent.primaryAccount ? { used_pct: null, slot: agent.primaryAccount } : null
77543
77617
  };
77544
77618
  }
77545
- function resolveAgentChat(config, agentName, agentsDir) {
77619
+ async function buildAllSessions(config) {
77620
+ const agents = await handleGetAgents(config);
77621
+ const sessions = [];
77622
+ for (const agent of agents) {
77623
+ const liveness = agentLiveness(config, agent.name);
77624
+ const chatId = config.agents?.[agent.name]?.channels?.telegram?.chat_id;
77625
+ if (chatId) {
77626
+ const threadIds = handleListThreadIds(config, agent.name);
77627
+ if (threadIds.length === 0) {
77628
+ sessions.push(toHermesSession(agent.name, agent, liveness, config));
77629
+ } else {
77630
+ for (const threadId of threadIds) {
77631
+ const sessionId = threadId === null ? agent.name : `${agent.name}~${threadId}`;
77632
+ sessions.push(toHermesSession(sessionId, agent, liveness, config));
77633
+ }
77634
+ }
77635
+ } else {
77636
+ sessions.push(toHermesSession(agent.name, agent, liveness, config));
77637
+ }
77638
+ }
77639
+ return sessions;
77640
+ }
77641
+ function resolveAgentChat(config, sessionId, agentsDir) {
77642
+ const { agentName, threadId: topicId } = parseHermesSessionId(sessionId);
77546
77643
  const channel = resolveChannelTarget(config, agentName);
77547
- if (channel)
77548
- return { chatId: channel.chatId, threadId: channel.threadId };
77644
+ if (channel) {
77645
+ const threadId = topicId !== undefined ? topicId === null ? undefined : parseInt(topicId, 10) || undefined : channel.threadId;
77646
+ return { chatId: channel.chatId, threadId };
77647
+ }
77549
77648
  const accessPath = resolve29(agentsDir, agentName, "telegram", "access.json");
77550
77649
  if (!existsSync51(accessPath))
77551
77650
  return null;
@@ -77637,15 +77736,26 @@ function cronJobRuns(schedule, jobId) {
77637
77736
  const agent = jobId.slice(0, tilde);
77638
77737
  const index = parseInt(jobId.slice(tilde + 1), 10);
77639
77738
  const fires = schedule.recentByAgent[agent] ?? [];
77640
- return fires.filter((f) => f.scheduleIndex === index).map((f) => ({
77641
- id: `${jobId}/${f.startedAt}`,
77642
- job_id: jobId,
77643
- started_at: new Date(f.startedAt).toISOString(),
77644
- finished_at: new Date(f.finishedAt).toISOString(),
77645
- exit_code: f.exitCode,
77646
- output: f.outputSummary,
77647
- status: f.exitCode === 0 ? "success" : "error"
77648
- }));
77739
+ return fires.filter((f) => f.scheduleIndex === index).map((f) => {
77740
+ const d = new Date(f.startedAt);
77741
+ const name = d.toLocaleString("en-US", {
77742
+ month: "short",
77743
+ day: "numeric",
77744
+ hour: "2-digit",
77745
+ minute: "2-digit",
77746
+ hour12: false
77747
+ });
77748
+ return {
77749
+ id: `${jobId}/${f.startedAt}`,
77750
+ job_id: jobId,
77751
+ name,
77752
+ started_at: new Date(f.startedAt).toISOString(),
77753
+ finished_at: new Date(f.finishedAt).toISOString(),
77754
+ exit_code: f.exitCode,
77755
+ output: f.outputSummary,
77756
+ status: f.exitCode === 0 ? "success" : "error"
77757
+ };
77758
+ });
77649
77759
  }
77650
77760
  function turnsToMessages(turns) {
77651
77761
  const messages = [];
@@ -77730,8 +77840,7 @@ function switchroomCommandsCatalog() {
77730
77840
  }
77731
77841
  async function handleHermesRest(method, pathname, config, search = "") {
77732
77842
  if (method === "GET" && (pathname === "/api/sessions" || pathname === "/api/profiles/sessions")) {
77733
- const agents = await handleGetAgents(config);
77734
- const sessions = agents.map((a) => toHermesSession(a, agentLiveness(config, a.name)));
77843
+ const sessions = await buildAllSessions(config);
77735
77844
  return {
77736
77845
  status: 200,
77737
77846
  body: {
@@ -77746,22 +77855,21 @@ async function handleHermesRest(method, pathname, config, search = "") {
77746
77855
  const sessionMatch = pathname.match(/^\/api\/sessions\/([^/]+)$/);
77747
77856
  if (method === "GET" && sessionMatch) {
77748
77857
  const id = decodeURIComponent(sessionMatch[1]);
77749
- if (!config.agents?.[id])
77858
+ if (!isKnownSession(config, id))
77750
77859
  return { status: 404, body: { error: "Unknown session" } };
77860
+ const { agentName } = parseHermesSessionId(id);
77751
77861
  const agents = await handleGetAgents(config);
77752
- const agent = agents.find((a) => a.name === id);
77862
+ const agent = agents.find((a) => a.name === agentName);
77753
77863
  if (!agent)
77754
77864
  return { status: 404, body: { error: "Unknown session" } };
77755
- return { status: 200, body: { session: toHermesSession(agent, agentLiveness(config, id)) } };
77865
+ return { status: 200, body: { session: toHermesSession(id, agent, agentLiveness(config, agentName), config) } };
77756
77866
  }
77757
77867
  const messagesMatch = pathname.match(/^\/api\/sessions\/([^/]+)\/messages$/);
77758
77868
  if (method === "GET" && messagesMatch) {
77759
77869
  const id = decodeURIComponent(messagesMatch[1]);
77760
- if (!config.agents?.[id])
77870
+ if (!isKnownSession(config, id))
77761
77871
  return { status: 404, body: { error: "Unknown session" } };
77762
77872
  const result = handleGetTurns(config, id, 100);
77763
- if (!result.ok)
77764
- return { status: 500, body: { error: result.error } };
77765
77873
  const messages = turnsToMessages(result.turns ?? []);
77766
77874
  return { status: 200, body: { session_id: id, messages } };
77767
77875
  }
@@ -77929,52 +78037,48 @@ async function onHermesMessage(ctx, raw) {
77929
78037
  const config = ctx.config;
77930
78038
  switch (req.method) {
77931
78039
  case "session.list": {
77932
- const agents = await handleGetAgents(config);
77933
- const sessions = agents.map((a) => toHermesSession(a, agentLiveness(config, a.name)));
78040
+ const sessions = await buildAllSessions(config);
77934
78041
  sendResponse(ctx, rpcOk(id, { sessions }));
77935
78042
  break;
77936
78043
  }
77937
78044
  case "session.most_recent": {
77938
- const agents = await handleGetAgents(config);
77939
- if (agents.length === 0) {
77940
- sendResponse(ctx, rpcOk(id, { session: null }));
77941
- break;
77942
- }
77943
- const a = agents[0];
77944
- sendResponse(ctx, rpcOk(id, { session: toHermesSession(a, agentLiveness(config, a.name)) }));
78045
+ const sessions = await buildAllSessions(config);
78046
+ sendResponse(ctx, rpcOk(id, { session: sessions[0] ?? null }));
77945
78047
  break;
77946
78048
  }
77947
78049
  case "session.status": {
77948
78050
  const sessionId = String(params.session_id ?? "");
77949
- if (!config.agents?.[sessionId]) {
78051
+ if (!isKnownSession(config, sessionId)) {
77950
78052
  sendResponse(ctx, rpcErr(id, -32602, `Unknown session: ${sessionId}`));
77951
78053
  break;
77952
78054
  }
78055
+ const { agentName } = parseHermesSessionId(sessionId);
77953
78056
  const agents = await handleGetAgents(config);
77954
- const agent = agents.find((a) => a.name === sessionId);
78057
+ const agent = agents.find((a) => a.name === agentName);
77955
78058
  if (!agent) {
77956
78059
  sendResponse(ctx, rpcErr(id, -32602, `Unknown session: ${sessionId}`));
77957
78060
  break;
77958
78061
  }
77959
- sendResponse(ctx, rpcOk(id, { session: toHermesSession(agent, agentLiveness(config, sessionId)) }));
78062
+ sendResponse(ctx, rpcOk(id, { session: toHermesSession(sessionId, agent, agentLiveness(config, agentName), config) }));
77960
78063
  break;
77961
78064
  }
77962
78065
  case "session.create":
77963
78066
  case "session.resume":
77964
78067
  case "session.activate": {
77965
78068
  const sessionId = String(params.session_id ?? params.name ?? "");
77966
- if (!config.agents?.[sessionId]) {
78069
+ if (!isKnownSession(config, sessionId)) {
77967
78070
  sendResponse(ctx, rpcErr(id, -32602, `Unknown session: ${sessionId}`));
77968
78071
  break;
77969
78072
  }
77970
78073
  ctx.activeSessionId = sessionId;
78074
+ const { agentName } = parseHermesSessionId(sessionId);
77971
78075
  const agents = await handleGetAgents(config);
77972
- const agent = agents.find((a) => a.name === sessionId);
78076
+ const agent = agents.find((a) => a.name === agentName);
77973
78077
  if (!agent) {
77974
78078
  sendResponse(ctx, rpcErr(id, -32602, `Unknown session: ${sessionId}`));
77975
78079
  break;
77976
78080
  }
77977
- const session = toHermesSession(agent, agentLiveness(config, sessionId));
78081
+ const session = toHermesSession(sessionId, agent, agentLiveness(config, agentName), config);
77978
78082
  sendResponse(ctx, rpcOk(id, { session }));
77979
78083
  sendEvent(ctx, "session.info", sessionId, session);
77980
78084
  break;
@@ -77986,27 +78090,24 @@ async function onHermesMessage(ctx, raw) {
77986
78090
  }
77987
78091
  case "session.history": {
77988
78092
  const sessionId = String(params.session_id ?? ctx.activeSessionId ?? "");
77989
- if (!config.agents?.[sessionId]) {
78093
+ if (!isKnownSession(config, sessionId)) {
77990
78094
  sendResponse(ctx, rpcErr(id, -32602, `Unknown session: ${sessionId}`));
77991
78095
  break;
77992
78096
  }
77993
78097
  const limit = typeof params.limit === "number" ? params.limit : 50;
77994
78098
  const result = handleGetTurns(config, sessionId, Math.min(limit, 200));
77995
- if (!result.ok) {
77996
- sendResponse(ctx, rpcErr(id, -32603, result.error ?? "Failed to read history"));
77997
- break;
77998
- }
77999
78099
  sendResponse(ctx, rpcOk(id, { session_id: sessionId, messages: turnsToMessages(result.turns ?? []) }));
78000
78100
  break;
78001
78101
  }
78002
78102
  case "session.usage": {
78003
78103
  const sessionId = String(params.session_id ?? ctx.activeSessionId ?? "");
78004
- if (!config.agents?.[sessionId]) {
78104
+ if (!isKnownSession(config, sessionId)) {
78005
78105
  sendResponse(ctx, rpcErr(id, -32602, `Unknown session: ${sessionId}`));
78006
78106
  break;
78007
78107
  }
78108
+ const { agentName: usageAgentName } = parseHermesSessionId(sessionId);
78008
78109
  const agents = await handleGetAgents(config);
78009
- const agent = agents.find((a) => a.name === sessionId);
78110
+ const agent = agents.find((a) => a.name === usageAgentName);
78010
78111
  sendResponse(ctx, rpcOk(id, {
78011
78112
  session_id: sessionId,
78012
78113
  quota: agent?.primaryAccount ? { slot: agent.primaryAccount, state: agent.auth.subscriptionType ?? null } : null
@@ -78017,7 +78118,7 @@ async function onHermesMessage(ctx, raw) {
78017
78118
  case "prompt.background": {
78018
78119
  const sessionId = String(params.session_id ?? ctx.activeSessionId ?? "");
78019
78120
  const content = String(params.content ?? params.text ?? "");
78020
- if (!sessionId || !config.agents?.[sessionId]) {
78121
+ if (!sessionId || !isKnownSession(config, sessionId)) {
78021
78122
  sendResponse(ctx, rpcErr(id, -32602, `Unknown session: ${sessionId}`));
78022
78123
  break;
78023
78124
  }
@@ -78025,6 +78126,7 @@ async function onHermesMessage(ctx, raw) {
78025
78126
  sendResponse(ctx, rpcErr(id, -32602, "content is required"));
78026
78127
  break;
78027
78128
  }
78129
+ const { agentName: submitAgent } = parseHermesSessionId(sessionId);
78028
78130
  const agentsDir = resolveAgentsDir(config);
78029
78131
  const chat = resolveAgentChat(config, sessionId, agentsDir);
78030
78132
  if (!chat) {
@@ -78033,7 +78135,7 @@ async function onHermesMessage(ctx, raw) {
78033
78135
  }
78034
78136
  const promptKey = createHash10("sha256").update(`${sessionId}:${Date.now()}:${content}`).digest("hex").slice(0, 12);
78035
78137
  sendEvent(ctx, "message.start", sessionId, { prompt_key: promptKey });
78036
- const result = await injectInbound(agentsDir, sessionId, chat.chatId, chat.threadId, content, promptKey);
78138
+ const result = await injectInbound(agentsDir, submitAgent, chat.chatId, chat.threadId, content, promptKey);
78037
78139
  if (!result.ok) {
78038
78140
  sendEvent(ctx, "error", sessionId, { message: result.error, prompt_key: promptKey });
78039
78141
  sendResponse(ctx, rpcErr(id, -32603, result.error ?? "inject failed"));
@@ -78052,10 +78154,11 @@ async function onHermesMessage(ctx, raw) {
78052
78154
  }
78053
78155
  case "session.interrupt": {
78054
78156
  const sessionId = String(params.session_id ?? ctx.activeSessionId ?? "");
78055
- if (!sessionId || !config.agents?.[sessionId]) {
78157
+ if (!sessionId || !isKnownSession(config, sessionId)) {
78056
78158
  sendResponse(ctx, rpcErr(id, -32602, `Unknown session: ${sessionId}`));
78057
78159
  break;
78058
78160
  }
78161
+ const { agentName: intAgent } = parseHermesSessionId(sessionId);
78059
78162
  const agentsDir = resolveAgentsDir(config);
78060
78163
  const chat = resolveAgentChat(config, sessionId, agentsDir);
78061
78164
  if (!chat) {
@@ -78063,7 +78166,7 @@ async function onHermesMessage(ctx, raw) {
78063
78166
  break;
78064
78167
  }
78065
78168
  const promptKey = `interrupt-${Date.now()}`;
78066
- const intResult = await injectInbound(agentsDir, sessionId, chat.chatId, chat.threadId, "! ", promptKey);
78169
+ const intResult = await injectInbound(agentsDir, intAgent, chat.chatId, chat.threadId, "! ", promptKey);
78067
78170
  if (!intResult.ok) {
78068
78171
  sendResponse(ctx, rpcErr(id, -32603, intResult.error ?? "interrupt inject failed"));
78069
78172
  break;
@@ -78091,14 +78194,15 @@ async function onHermesMessage(ctx, raw) {
78091
78194
  const sessionId = String(params.session_id ?? ctx.activeSessionId ?? "");
78092
78195
  const key = String(params.key ?? "");
78093
78196
  const value = String(params.value ?? "");
78094
- if (key === "model" && sessionId && config.agents?.[sessionId]) {
78197
+ if (key === "model" && sessionId && isKnownSession(config, sessionId)) {
78095
78198
  const modelName = value.replace(/\s+--provider\s+\S+$/, "").trim();
78096
78199
  if (modelName) {
78097
78200
  const agentsDir = resolveAgentsDir(config);
78098
78201
  const chat = resolveAgentChat(config, sessionId, agentsDir);
78099
78202
  if (chat) {
78100
78203
  const promptKey = `model-switch-${Date.now()}`;
78101
- injectInbound(agentsDir, sessionId, chat.chatId, chat.threadId, `/model ${modelName}`, promptKey);
78204
+ const { agentName: modelAgent } = parseHermesSessionId(sessionId);
78205
+ injectInbound(agentsDir, modelAgent, chat.chatId, chat.threadId, `/model ${modelName}`, promptKey);
78102
78206
  }
78103
78207
  }
78104
78208
  }
@@ -78113,7 +78217,7 @@ async function onHermesMessage(ctx, raw) {
78113
78217
  const sessionId = String(params.session_id ?? ctx.activeSessionId ?? "");
78114
78218
  const bare = String(params.command ?? "").replace(/^\/+/, "");
78115
78219
  const arg = params.arg != null ? String(params.arg) : "";
78116
- if (!sessionId || !config.agents?.[sessionId]) {
78220
+ if (!sessionId || !isKnownSession(config, sessionId)) {
78117
78221
  sendResponse(ctx, rpcErr(id, -32602, `Unknown session: ${sessionId}`));
78118
78222
  break;
78119
78223
  }
@@ -78121,6 +78225,7 @@ async function onHermesMessage(ctx, raw) {
78121
78225
  sendResponse(ctx, rpcErr(id, -32602, "command is required"));
78122
78226
  break;
78123
78227
  }
78228
+ const { agentName: slashAgent } = parseHermesSessionId(sessionId);
78124
78229
  const fullCommand = arg ? `/${bare} ${arg}` : `/${bare}`;
78125
78230
  const verb = `/${bare}`;
78126
78231
  if (INJECT_BLOCKLIST.has(verb)) {
@@ -78130,7 +78235,7 @@ async function onHermesMessage(ctx, raw) {
78130
78235
  if (INJECT_ALLOWLIST.has(verb)) {
78131
78236
  let injectResult;
78132
78237
  try {
78133
- injectResult = await injectSlashCommand(sessionId, fullCommand);
78238
+ injectResult = await injectSlashCommand(slashAgent, fullCommand);
78134
78239
  } catch (err) {
78135
78240
  const msg = err instanceof Error ? err.message : String(err);
78136
78241
  sendResponse(ctx, rpcErr(id, -32603, msg));
@@ -78147,7 +78252,7 @@ async function onHermesMessage(ctx, raw) {
78147
78252
  break;
78148
78253
  }
78149
78254
  const promptKey = `slash-${bare}-${Date.now()}`;
78150
- const result = await injectInbound(agentsDir, sessionId, chat.chatId, chat.threadId, fullCommand, promptKey);
78255
+ const result = await injectInbound(agentsDir, slashAgent, chat.chatId, chat.threadId, fullCommand, promptKey);
78151
78256
  if (!result.ok) {
78152
78257
  sendResponse(ctx, rpcErr(id, -32603, result.error ?? "inject failed"));
78153
78258
  break;
@@ -89080,6 +89185,7 @@ import { spawnSync as spawnSync14 } from "node:child_process";
89080
89185
 
89081
89186
  // src/cli/skill-common.ts
89082
89187
  var import_yaml24 = __toESM(require_dist(), 1);
89188
+ var MAX_SKILLS_PER_AGENT2 = 20;
89083
89189
  var MAX_FILE_BYTES = 256 * 1024;
89084
89190
  var MAX_SKILL_BYTES = 2 * 1024 * 1024;
89085
89191
  var MAX_FILES_PER_SKILL = 50;
@@ -89252,6 +89358,45 @@ function validateSkillBundle(name, files) {
89252
89358
  }
89253
89359
  return { ok: errors2.length === 0, errors: errors2 };
89254
89360
  }
89361
+ var SECRET_RULE_LABELS = {
89362
+ anthropic_api_key: "Anthropic API key (sk-ant-\u2026)",
89363
+ anthropic_oauth_code: "Anthropic OAuth code",
89364
+ openai_api_key: "API key (sk-\u2026)",
89365
+ github_pat_classic: "GitHub personal access token (ghp_\u2026)",
89366
+ github_pat_fine_grained: "GitHub fine-grained PAT (github_pat_\u2026)",
89367
+ slack_token: "Slack token (xox\u2026)",
89368
+ slack_app_token: "Slack app token (xapp-\u2026)",
89369
+ google_api_key: "Google API key (AIza\u2026)",
89370
+ aws_access_key: "AWS access key (AKIA\u2026)",
89371
+ aws_temp_access_key: "AWS temporary access key (ASIA\u2026)",
89372
+ jwt: "JWT",
89373
+ pem_private_key: "PEM private-key block",
89374
+ env_key_value: "KEY=value secret assignment",
89375
+ json_secret_field: "JSON secret field",
89376
+ bearer_auth_header: "Authorization: Bearer token",
89377
+ bearer_loose: "Bearer token",
89378
+ cli_flag: "secret CLI flag",
89379
+ laravel_sanctum_token: "personal access token (id|token)",
89380
+ telegram_bot_token: "Telegram bot token",
89381
+ telegram_bot_token_prefixed: "Telegram bot token",
89382
+ generic_high_entropy: "high-entropy string (likely a credential)"
89383
+ };
89384
+ function scanBundleForSecrets(files) {
89385
+ const findings = [];
89386
+ for (const [file, content] of Object.entries(files)) {
89387
+ if (typeof content !== "string" || content.length === 0)
89388
+ continue;
89389
+ for (const d of detectSecrets(content)) {
89390
+ findings.push({
89391
+ file,
89392
+ pattern: SECRET_RULE_LABELS[d.rule_id] ?? d.rule_id,
89393
+ rule_id: d.rule_id,
89394
+ offset: d.start
89395
+ });
89396
+ }
89397
+ }
89398
+ return findings;
89399
+ }
89255
89400
 
89256
89401
  // src/cli/skill.ts
89257
89402
  init_loader();
@@ -89741,6 +89886,17 @@ function personalSkillDir(agentsRoot, agent, name) {
89741
89886
  function trashDir(agentsRoot, agent) {
89742
89887
  return join85(agentsRoot, agent, ".claude", TRASH_DIRNAME);
89743
89888
  }
89889
+ function countPersonalSkills(agentsRoot, agent) {
89890
+ const skillsDir = join85(agentsRoot, agent, ".claude", "skills");
89891
+ if (!existsSync84(skillsDir))
89892
+ return 0;
89893
+ let n = 0;
89894
+ for (const ent of readdirSync32(skillsDir, { withFileTypes: true })) {
89895
+ if (ent.isDirectory() && ent.name.startsWith(PERSONAL_PREFIX))
89896
+ n += 1;
89897
+ }
89898
+ return n;
89899
+ }
89744
89900
  function readStdinSync2() {
89745
89901
  const chunks = [];
89746
89902
  const buf = Buffer.alloc(64 * 1024);
@@ -89934,6 +90090,12 @@ function loadValidateWrite(agentsRoot, agent, name, files, ensureNew) {
89934
90090
  if (!ensureNew && !exists) {
89935
90091
  fail4(`personal skill ${JSON.stringify(name)} does not exist for agent ${JSON.stringify(agent)}. ` + `Use \`skill init-personal\` to create it.`, 9);
89936
90092
  }
90093
+ if (ensureNew) {
90094
+ const existingCount = countPersonalSkills(agentsRoot, agent);
90095
+ if (existingCount >= MAX_SKILLS_PER_AGENT2) {
90096
+ fail4(`agent ${JSON.stringify(agent)} already has ${existingCount} personal skills ` + `(cap is ${MAX_SKILLS_PER_AGENT2}). Remove one with \`skill remove-personal\` ` + `before adding a new skill, or edit an existing skill instead.`, 15);
90097
+ }
90098
+ }
89937
90099
  const v = validateSkillBundle(name, files);
89938
90100
  if (!v.ok) {
89939
90101
  console.error(source_default.red("Validation failed:"));
@@ -89950,6 +90112,14 @@ function loadValidateWrite(agentsRoot, agent, name, files, ensureNew) {
89950
90112
  }
89951
90113
  process.exit(3);
89952
90114
  }
90115
+ const secretFindings = scanBundleForSecrets(files);
90116
+ if (secretFindings.length > 0) {
90117
+ console.error(source_default.red("Secret scan failed \u2014 refusing to write skill:"));
90118
+ for (const f of secretFindings) {
90119
+ console.error(source_default.red(` - ${f.file}: ${f.pattern} detected (at byte offset ${f.offset}). ` + `Remove the secret from the skill content and use the vault instead.`));
90120
+ }
90121
+ process.exit(3);
90122
+ }
89953
90123
  writePersonalSkill(target, files);
89954
90124
  }
89955
90125
  function loadFiles(opts) {
@@ -90220,31 +90390,106 @@ function registerSkillPersonalCommands(program3) {
90220
90390
  }));
90221
90391
  }
90222
90392
 
90393
+ // src/cli/self-improve-propose-skill.ts
90394
+ import { createConnection as createConnection4 } from "node:net";
90395
+ import { homedir as homedir50 } from "node:os";
90396
+ import { join as join86 } from "node:path";
90397
+ import { readFileSync as readFileSync73 } from "node:fs";
90398
+ var IPC_CONNECT_TIMEOUT_MS = 5000;
90399
+ function gatewaySocketPath() {
90400
+ return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ? join86(process.env.TELEGRAM_STATE_DIR, "gateway.sock") : join86(homedir50(), ".claude", "channels", "telegram", "gateway.sock"));
90401
+ }
90402
+ function fail5(msg, code = 1) {
90403
+ console.error(msg);
90404
+ process.exit(code);
90405
+ }
90406
+ function sendProposal(payload) {
90407
+ return new Promise((resolve52, reject) => {
90408
+ const sock = createConnection4({ path: gatewaySocketPath() });
90409
+ const t = setTimeout(() => {
90410
+ sock.destroy();
90411
+ reject(new Error("gateway IPC connect timeout"));
90412
+ }, IPC_CONNECT_TIMEOUT_MS);
90413
+ sock.on("connect", () => {
90414
+ clearTimeout(t);
90415
+ sock.write(JSON.stringify(payload) + `
90416
+ `, () => {
90417
+ sock.end();
90418
+ resolve52();
90419
+ });
90420
+ });
90421
+ sock.on("error", (err2) => {
90422
+ clearTimeout(t);
90423
+ reject(new Error(`gateway IPC error: ${err2.message}`));
90424
+ });
90425
+ });
90426
+ }
90427
+ function registerSelfImproveProposeSkillCommand(program3) {
90428
+ const parent = program3.commands.find((c) => c.name() === "self-improve") ?? program3.command("self-improve").description("Agent self-improvement ops");
90429
+ parent.command("propose-skill").description("Surface a drafted personal skill as a one-tap Telegram approval card").requiredOption("--slug <slug>", "target personal-skill slug").requiredOption("--lesson <text>", "one-line lesson the skill captures").option("--evidence <text>", "evidence line (e.g. 'seen across 3 sessions')", "").option("--edit", "this is an edit to an existing personal skill (not new)").requiredOption("--draft <path>", "path to a JSON file mapping skill-relative paths to file contents").requiredOption("--chat <id>", "agent's own chat id to post the card to").option("--thread <id>", "forum topic thread id").option("--agent <name>", "agent name (defaults to $SWITCHROOM_AGENT_NAME)").action((opts) => {
90430
+ const agent = opts.agent ?? process.env.SWITCHROOM_AGENT_NAME;
90431
+ if (!agent)
90432
+ fail5("agent name required (--agent or $SWITCHROOM_AGENT_NAME)");
90433
+ let draft;
90434
+ try {
90435
+ draft = JSON.parse(readFileSync73(opts.draft, "utf-8"));
90436
+ } catch (e) {
90437
+ fail5(`failed to read/parse --draft: ${e.message}`);
90438
+ }
90439
+ if (typeof draft !== "object" || draft === null || Array.isArray(draft)) {
90440
+ fail5("--draft must be a JSON object of { path: content }");
90441
+ }
90442
+ if (typeof draft["SKILL.md"] !== "string" || draft["SKILL.md"].length === 0) {
90443
+ fail5("--draft must contain a non-empty SKILL.md");
90444
+ }
90445
+ const payload = {
90446
+ type: "post_skill_proposal",
90447
+ agentName: agent,
90448
+ chatId: opts.chat,
90449
+ skillSlug: opts.slug,
90450
+ isNew: opts.edit !== true,
90451
+ lesson: opts.lesson,
90452
+ evidence: opts.evidence ?? "",
90453
+ draft
90454
+ };
90455
+ if (opts.thread != null) {
90456
+ const n = Number.parseInt(opts.thread, 10);
90457
+ if (Number.isInteger(n))
90458
+ payload.threadId = n;
90459
+ }
90460
+ sendProposal(payload).then(() => {
90461
+ console.log(JSON.stringify({ ok: true, action: "propose-skill", slug: opts.slug, agent }));
90462
+ }).catch((err2) => {
90463
+ fail5(`propose-skill failed: ${err2.message}`);
90464
+ });
90465
+ });
90466
+ }
90467
+
90223
90468
  // src/cli/skill-search.ts
90224
90469
  init_helpers();
90225
90470
  var import_yaml25 = __toESM(require_dist(), 1);
90226
- import { existsSync as existsSync85, readdirSync as readdirSync33, readFileSync as readFileSync73, statSync as statSync36 } from "node:fs";
90227
- import { homedir as homedir50 } from "node:os";
90228
- import { join as join86, resolve as resolve52 } from "node:path";
90471
+ import { existsSync as existsSync85, readdirSync as readdirSync33, readFileSync as readFileSync74, statSync as statSync36 } from "node:fs";
90472
+ import { homedir as homedir51 } from "node:os";
90473
+ import { join as join87, resolve as resolve52 } from "node:path";
90229
90474
  var PERSONAL_PREFIX2 = "personal-";
90230
90475
  var BUNDLED_SUBDIR = "_bundled";
90231
90476
  var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
90232
90477
  function defaultAgentsRoot() {
90233
- return resolve52(homedir50(), ".switchroom/agents");
90478
+ return resolve52(homedir51(), ".switchroom/agents");
90234
90479
  }
90235
90480
  function defaultSharedRoot2() {
90236
- return resolve52(homedir50(), ".switchroom/skills");
90481
+ return resolve52(homedir51(), ".switchroom/skills");
90237
90482
  }
90238
90483
  function defaultBundledRoot2() {
90239
- return resolve52(homedir50(), ".switchroom/skills/_bundled");
90484
+ return resolve52(homedir51(), ".switchroom/skills/_bundled");
90240
90485
  }
90241
90486
  function readSkillFrontmatter(skillDir) {
90242
- const mdPath = join86(skillDir, "SKILL.md");
90487
+ const mdPath = join87(skillDir, "SKILL.md");
90243
90488
  if (!existsSync85(mdPath))
90244
90489
  return null;
90245
90490
  let content;
90246
90491
  try {
90247
- content = readFileSync73(mdPath, "utf-8");
90492
+ content = readFileSync74(mdPath, "utf-8");
90248
90493
  } catch {
90249
90494
  return null;
90250
90495
  }
@@ -90272,7 +90517,7 @@ function readSkillFrontmatter(skillDir) {
90272
90517
  return { fm: parsed };
90273
90518
  }
90274
90519
  function statSkillMd(skillDir) {
90275
- const mdPath = join86(skillDir, "SKILL.md");
90520
+ const mdPath = join87(skillDir, "SKILL.md");
90276
90521
  try {
90277
90522
  const st = statSync36(mdPath);
90278
90523
  return { size: st.size, mtime: st.mtime.toISOString() };
@@ -90283,7 +90528,7 @@ function statSkillMd(skillDir) {
90283
90528
  function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
90284
90529
  if (!AGENT_NAME_RE3.test(agent))
90285
90530
  return [];
90286
- const skillsDir = join86(agentsRoot, agent, ".claude/skills");
90531
+ const skillsDir = join87(agentsRoot, agent, ".claude/skills");
90287
90532
  if (!existsSync85(skillsDir))
90288
90533
  return [];
90289
90534
  const out = [];
@@ -90296,7 +90541,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
90296
90541
  for (const ent of entries) {
90297
90542
  if (!ent.startsWith(PERSONAL_PREFIX2))
90298
90543
  continue;
90299
- const dirPath = join86(skillsDir, ent);
90544
+ const dirPath = join87(skillsDir, ent);
90300
90545
  try {
90301
90546
  if (!statSync36(dirPath).isDirectory())
90302
90547
  continue;
@@ -90336,7 +90581,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
90336
90581
  continue;
90337
90582
  if (ent.startsWith("."))
90338
90583
  continue;
90339
- const dirPath = join86(sharedRoot, ent);
90584
+ const dirPath = join87(sharedRoot, ent);
90340
90585
  try {
90341
90586
  if (!statSync36(dirPath).isDirectory())
90342
90587
  continue;
@@ -90372,7 +90617,7 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
90372
90617
  for (const ent of entries) {
90373
90618
  if (ent.startsWith("."))
90374
90619
  continue;
90375
- const dirPath = join86(bundledRoot, ent);
90620
+ const dirPath = join87(bundledRoot, ent);
90376
90621
  try {
90377
90622
  if (!statSync36(dirPath).isDirectory())
90378
90623
  continue;
@@ -90516,9 +90761,9 @@ function registerHostdMcpCommand(program3) {
90516
90761
  // src/cli/hostd.ts
90517
90762
  init_source();
90518
90763
  init_helpers();
90519
- import { existsSync as existsSync87, mkdirSync as mkdirSync49, readdirSync as readdirSync34, readFileSync as readFileSync75, writeFileSync as writeFileSync42, statSync as statSync37, copyFileSync as copyFileSync12 } from "node:fs";
90520
- import { homedir as homedir51 } from "node:os";
90521
- import { join as join87 } from "node:path";
90764
+ import { existsSync as existsSync87, mkdirSync as mkdirSync49, readdirSync as readdirSync34, readFileSync as readFileSync76, writeFileSync as writeFileSync42, statSync as statSync37, copyFileSync as copyFileSync12 } from "node:fs";
90765
+ import { homedir as homedir52 } from "node:os";
90766
+ import { join as join88 } from "node:path";
90522
90767
  import { spawnSync as spawnSync19 } from "node:child_process";
90523
90768
 
90524
90769
  // src/cli/deploy-version-guard.ts
@@ -90721,7 +90966,7 @@ networks:
90721
90966
  # operator surface; the daemon's stderr lands in \`docker logs switchroom-hostd\`.
90722
90967
  `;
90723
90968
  }
90724
- function resolveHostdHostHome(env2 = process.env, home2 = homedir51()) {
90969
+ function resolveHostdHostHome(env2 = process.env, home2 = homedir52()) {
90725
90970
  const fromEnv = env2.SWITCHROOM_HOST_HOME?.trim();
90726
90971
  const resolved = fromEnv && fromEnv.length > 0 ? fromEnv : home2;
90727
90972
  if (resolved === "/host-home" || resolved.startsWith("/host-home/")) {
@@ -90732,10 +90977,10 @@ function resolveHostdHostHome(env2 = process.env, home2 = homedir51()) {
90732
90977
  return resolved;
90733
90978
  }
90734
90979
  function hostdDir() {
90735
- return join87(homedir51(), ".switchroom", "hostd");
90980
+ return join88(homedir52(), ".switchroom", "hostd");
90736
90981
  }
90737
90982
  function hostdComposePath() {
90738
- return join87(hostdDir(), "docker-compose.yml");
90983
+ return join88(hostdDir(), "docker-compose.yml");
90739
90984
  }
90740
90985
  function backupExistingCompose() {
90741
90986
  const p = hostdComposePath();
@@ -90862,7 +91107,7 @@ function doStatus() {
90862
91107
  for (const name of readdirSync34(dir)) {
90863
91108
  if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
90864
91109
  continue;
90865
- const sockPath = join87(dir, name, "sock");
91110
+ const sockPath = join88(dir, name, "sock");
90866
91111
  if (existsSync87(sockPath)) {
90867
91112
  const st = statSync37(sockPath);
90868
91113
  if ((st.mode & 61440) === 49152) {
@@ -90911,7 +91156,7 @@ function registerHostdCommand(program3) {
90911
91156
  The log is created when hostd handles its first privileged-verb request.`));
90912
91157
  return;
90913
91158
  }
90914
- const raw = readFileSync75(logPath, "utf-8");
91159
+ const raw = readFileSync76(logPath, "utf-8");
90915
91160
  const limit = Math.max(1, parseInt(opts.tail ?? "50", 10) || 50);
90916
91161
  const filters = {
90917
91162
  agent: opts.agent,
@@ -90954,8 +91199,8 @@ The log is created when hostd handles its first privileged-verb request.`));
90954
91199
  init_source();
90955
91200
  init_helpers();
90956
91201
  import { existsSync as existsSync88, mkdirSync as mkdirSync50, writeFileSync as writeFileSync43, copyFileSync as copyFileSync13 } from "node:fs";
90957
- import { homedir as homedir52 } from "node:os";
90958
- import { join as join88 } from "node:path";
91202
+ import { homedir as homedir53 } from "node:os";
91203
+ import { join as join89 } from "node:path";
90959
91204
  import { spawnSync as spawnSync20 } from "node:child_process";
90960
91205
  function resolveWebImageTag(explicitTag, release) {
90961
91206
  if (explicitTag)
@@ -91040,10 +91285,10 @@ services:
91040
91285
  `;
91041
91286
  }
91042
91287
  function webdDir() {
91043
- return join88(homedir52(), ".switchroom", "web");
91288
+ return join89(homedir53(), ".switchroom", "web");
91044
91289
  }
91045
91290
  function webdComposePath() {
91046
- return join88(webdDir(), "docker-compose.yml");
91291
+ return join89(webdDir(), "docker-compose.yml");
91047
91292
  }
91048
91293
  function backupExistingCompose2() {
91049
91294
  const p = webdComposePath();
@@ -91085,7 +91330,7 @@ async function doInstall2(opts, program3) {
91085
91330
  return;
91086
91331
  }
91087
91332
  const yaml = renderWebComposeFile({
91088
- hostHome: homedir52(),
91333
+ hostHome: homedir53(),
91089
91334
  imageTag,
91090
91335
  operatorUid
91091
91336
  });
@@ -91184,10 +91429,10 @@ function registerWebdCommand(program3) {
91184
91429
 
91185
91430
  // src/cli/host-repair.ts
91186
91431
  init_source();
91187
- import { homedir as homedir53 } from "node:os";
91188
- import { join as join89 } from "node:path";
91432
+ import { homedir as homedir54 } from "node:os";
91433
+ import { join as join90 } from "node:path";
91189
91434
  var ARTIFACT_ALLOWLIST = {
91190
- dockerComposePluginDir: (home2) => join89(home2, ".docker", "cli-plugins", "docker-compose"),
91435
+ dockerComposePluginDir: (home2) => join90(home2, ".docker", "cli-plugins", "docker-compose"),
91191
91436
  stateSentinel: "/state"
91192
91437
  };
91193
91438
  function isStateBogusAutoDir(probe2) {
@@ -91236,7 +91481,7 @@ function isStateBogusAutoDir(probe2) {
91236
91481
  return true;
91237
91482
  }
91238
91483
  function planMountRepairs(probe2, opts = {}) {
91239
- const home2 = opts.hostHome ?? homedir53();
91484
+ const home2 = opts.hostHome ?? homedir54();
91240
91485
  const items = [];
91241
91486
  const dockerComposePath = ARTIFACT_ALLOWLIST.dockerComposePluginDir(home2);
91242
91487
  const dockerComposeSt = probe2.lstat(dockerComposePath);
@@ -91472,6 +91717,7 @@ registerAgentConfigWriteCommands(program3);
91472
91717
  registerAgentConfigSkillWriteCommands(program3);
91473
91718
  registerSkillCommand(program3);
91474
91719
  registerSkillPersonalCommands(program3);
91720
+ registerSelfImproveProposeSkillCommand(program3);
91475
91721
  registerSkillSearchCommand(program3);
91476
91722
  registerAgentConfigMcpCommand(program3);
91477
91723
  registerHostdMcpCommand(program3);