switchroom 0.16.20 → 0.16.22

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.
@@ -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.20";
51696
- var COMMIT_SHA = "6ea6e873";
51695
+ var VERSION = "0.16.22";
51696
+ var COMMIT_SHA = "ea287bf4";
51697
51697
 
51698
51698
  // src/cli/resolve-version.ts
51699
51699
  function readPackageVersion() {
@@ -77500,6 +77500,7 @@ function resolveChannelTarget(config, agentName) {
77500
77500
  }
77501
77501
 
77502
77502
  // src/web/hermes-adapter.ts
77503
+ init_inject();
77503
77504
  function rpcOk(id, result) {
77504
77505
  return { jsonrpc: "2.0", id, result };
77505
77506
  }
@@ -77607,7 +77608,127 @@ async function injectInbound(agentsDir, agentName, chatId, threadId, text, promp
77607
77608
  });
77608
77609
  });
77609
77610
  }
77610
- async function handleHermesRest(method, pathname, config) {
77611
+ function cronJobId(agent, index) {
77612
+ return `${agent}~${index}`;
77613
+ }
77614
+ function scheduleToCronJobs(schedule) {
77615
+ return schedule.entries.map((entry) => {
77616
+ const id = cronJobId(entry.agent, entry.scheduleIndex);
77617
+ const fires = schedule.recentByAgent[entry.agent] ?? [];
77618
+ const entryFires = fires.filter((f) => f.scheduleIndex === entry.scheduleIndex);
77619
+ const lastFire = entryFires[entryFires.length - 1];
77620
+ return {
77621
+ id,
77622
+ enabled: true,
77623
+ name: entry.name ?? null,
77624
+ prompt: entry.prompt ?? null,
77625
+ schedule: { expr: entry.cron, display: entry.cron, kind: entry.kind ?? "prompt" },
77626
+ schedule_display: entry.cron,
77627
+ last_run_at: lastFire ? new Date(lastFire.finishedAt).toISOString() : null,
77628
+ last_error: lastFire?.exitCode !== 0 ? lastFire?.outputSummary ?? null : null,
77629
+ state: lastFire ? lastFire.exitCode === 0 ? "success" : "error" : null
77630
+ };
77631
+ });
77632
+ }
77633
+ function cronJobRuns(schedule, jobId) {
77634
+ const tilde = jobId.lastIndexOf("~");
77635
+ if (tilde === -1)
77636
+ return [];
77637
+ const agent = jobId.slice(0, tilde);
77638
+ const index = parseInt(jobId.slice(tilde + 1), 10);
77639
+ 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
+ }));
77649
+ }
77650
+ function turnsToMessages(turns) {
77651
+ const messages = [];
77652
+ for (const t of turns) {
77653
+ if (t.user_prompt_preview) {
77654
+ messages.push({
77655
+ role: "user",
77656
+ content: t.user_prompt_preview,
77657
+ timestamp: Math.floor(t.started_at / 1000)
77658
+ });
77659
+ }
77660
+ if (t.assistant_reply_preview) {
77661
+ messages.push({
77662
+ role: "assistant",
77663
+ content: t.assistant_reply_preview,
77664
+ timestamp: t.ended_at != null ? Math.floor(t.ended_at / 1000) : Math.floor(t.started_at / 1000)
77665
+ });
77666
+ }
77667
+ }
77668
+ return messages;
77669
+ }
77670
+ var SWITCHROOM_MODELS = [
77671
+ "claude-sonnet-4-6",
77672
+ "claude-opus-4-8",
77673
+ "claude-haiku-4-5-20251001",
77674
+ "claude-fable-5"
77675
+ ];
77676
+ function switchroomModelOptions() {
77677
+ return {
77678
+ model: "claude-sonnet-4-6",
77679
+ provider: "switchroom",
77680
+ providers: [
77681
+ {
77682
+ slug: "switchroom",
77683
+ name: "Switchroom (Claude)",
77684
+ is_current: true,
77685
+ authenticated: true,
77686
+ models: SWITCHROOM_MODELS,
77687
+ total_models: SWITCHROOM_MODELS.length
77688
+ }
77689
+ ]
77690
+ };
77691
+ }
77692
+ function switchroomCommandsCatalog() {
77693
+ return {
77694
+ categories: [
77695
+ {
77696
+ name: "Session",
77697
+ pairs: [
77698
+ ["/compact", "Compact context (summarize, keep the thread)"],
77699
+ ["/clear", "Clear context (fresh slate; memory in Hindsight)"],
77700
+ ["/model", "Show or switch the Claude model"],
77701
+ ["/status", "Agent, model, auth status"]
77702
+ ]
77703
+ },
77704
+ {
77705
+ name: "Memory & knowledge",
77706
+ pairs: [
77707
+ ["/memory", "List, search, or clear Hindsight memory"]
77708
+ ]
77709
+ },
77710
+ {
77711
+ name: "Vault & auth",
77712
+ pairs: [
77713
+ ["/vault", "Manage vault secrets + capability grants"],
77714
+ ["/auth", "Auth dashboard \u2014 accounts, quota, reauth, switch primary"],
77715
+ ["/whoami", "This agent's sandbox: tools, MCP, vault key-names"]
77716
+ ]
77717
+ },
77718
+ {
77719
+ name: "Diagnostics",
77720
+ pairs: [
77721
+ ["/doctor", "Health check (deps, services, MCP)"],
77722
+ ["/logs", "Show recent agent logs"],
77723
+ ["/usage", "Pro/Max plan quota (5h + 7d windows)"],
77724
+ ["/version", "Show version + running agent health"],
77725
+ ["/commands", "Full command list"]
77726
+ ]
77727
+ }
77728
+ ]
77729
+ };
77730
+ }
77731
+ async function handleHermesRest(method, pathname, config, search = "") {
77611
77732
  if (method === "GET" && (pathname === "/api/sessions" || pathname === "/api/profiles/sessions")) {
77612
77733
  const agents = await handleGetAgents(config);
77613
77734
  const sessions = agents.map((a) => toHermesSession(a, agentLiveness(config, a.name)));
@@ -77633,15 +77754,16 @@ async function handleHermesRest(method, pathname, config) {
77633
77754
  return { status: 404, body: { error: "Unknown session" } };
77634
77755
  return { status: 200, body: { session: toHermesSession(agent, agentLiveness(config, id)) } };
77635
77756
  }
77636
- const historyMatch = pathname.match(/^\/api\/sessions\/([^/]+)\/history$/);
77637
- if (method === "GET" && historyMatch) {
77638
- const id = decodeURIComponent(historyMatch[1]);
77757
+ const messagesMatch = pathname.match(/^\/api\/sessions\/([^/]+)\/messages$/);
77758
+ if (method === "GET" && messagesMatch) {
77759
+ const id = decodeURIComponent(messagesMatch[1]);
77639
77760
  if (!config.agents?.[id])
77640
77761
  return { status: 404, body: { error: "Unknown session" } };
77641
- const result = handleGetTurns(config, id, 50);
77762
+ const result = handleGetTurns(config, id, 100);
77642
77763
  if (!result.ok)
77643
77764
  return { status: 500, body: { error: result.error } };
77644
- return { status: 200, body: { history: result.turns ?? [] } };
77765
+ const messages = turnsToMessages(result.turns ?? []);
77766
+ return { status: 200, body: { session_id: id, messages } };
77645
77767
  }
77646
77768
  if (method === "GET" && pathname === "/api/status") {
77647
77769
  return {
@@ -77683,7 +77805,7 @@ async function handleHermesRest(method, pathname, config) {
77683
77805
  return {
77684
77806
  status: 200,
77685
77807
  body: {
77686
- model: "claude",
77808
+ model: "claude-sonnet-4-6",
77687
77809
  provider: "switchroom",
77688
77810
  capabilities: {}
77689
77811
  }
@@ -77692,13 +77814,32 @@ async function handleHermesRest(method, pathname, config) {
77692
77814
  if (method === "GET" && pathname.startsWith("/api/logs")) {
77693
77815
  return { status: 200, body: { file: "gateway.log", lines: [] } };
77694
77816
  }
77695
- if (method === "GET" && pathname.startsWith("/api/cron")) {
77696
- if (pathname === "/api/cron/jobs")
77697
- return { status: 200, body: [] };
77698
- if (pathname.includes("/runs"))
77699
- return { status: 200, body: { runs: [] } };
77700
- if (pathname.includes("sessions")) {
77701
- return { status: 200, body: { sessions: [], total: 0, limit: 0, offset: 0 } };
77817
+ if (pathname.startsWith("/api/cron")) {
77818
+ if (method === "GET" && pathname === "/api/cron/jobs") {
77819
+ const schedule = await handleGetSchedule(config);
77820
+ const jobs = scheduleToCronJobs(schedule);
77821
+ return { status: 200, body: jobs };
77822
+ }
77823
+ const jobRunsMatch = pathname.match(/^\/api\/cron\/jobs\/([^/]+)\/runs$/);
77824
+ if (method === "GET" && jobRunsMatch) {
77825
+ const jobId = decodeURIComponent(jobRunsMatch[1]);
77826
+ const schedule = await handleGetSchedule(config);
77827
+ const runs = cronJobRuns(schedule, jobId);
77828
+ const limitParam = new URLSearchParams(search).get("limit");
77829
+ const limit = limitParam ? parseInt(limitParam, 10) : 20;
77830
+ return { status: 200, body: { runs: runs.slice(0, limit) } };
77831
+ }
77832
+ const jobMatch = pathname.match(/^\/api\/cron\/jobs\/([^/]+)$/);
77833
+ if (method === "GET" && jobMatch) {
77834
+ const jobId = decodeURIComponent(jobMatch[1]);
77835
+ const schedule = await handleGetSchedule(config);
77836
+ const job = scheduleToCronJobs(schedule).find((j) => j.id === jobId);
77837
+ if (!job)
77838
+ return { status: 404, body: { error: "Not found" } };
77839
+ return { status: 200, body: job };
77840
+ }
77841
+ if (method === "POST" || method === "PATCH" || method === "DELETE") {
77842
+ return { status: 422, body: { error: "Schedules are managed via switchroom YAML config \u2014 edit the agent config file and apply." } };
77702
77843
  }
77703
77844
  return { status: 200, body: {} };
77704
77845
  }
@@ -77711,6 +77852,12 @@ async function handleHermesRest(method, pathname, config) {
77711
77852
  if (pathname.includes("sessions")) {
77712
77853
  return { status: 200, body: { sessions: [], total: 0, limit: 0, offset: 0 } };
77713
77854
  }
77855
+ if (pathname === "/api/profiles") {
77856
+ return { status: 200, body: { profiles: [] } };
77857
+ }
77858
+ if (pathname.endsWith("/soul")) {
77859
+ return { status: 200, body: { content: "", exists: false } };
77860
+ }
77714
77861
  return { status: 200, body: {} };
77715
77862
  }
77716
77863
  if (method === "GET" && pathname === "/api/memory/providers") {
@@ -77719,6 +77866,21 @@ async function handleHermesRest(method, pathname, config) {
77719
77866
  if (method === "GET" && pathname === "/api/fs/default-cwd") {
77720
77867
  return { status: 200, body: { cwd: null, branch: null } };
77721
77868
  }
77869
+ if (method === "GET" && pathname === "/api/env") {
77870
+ return { status: 200, body: {} };
77871
+ }
77872
+ if (method === "POST" && pathname === "/api/providers/validate") {
77873
+ return { status: 200, body: { ok: true, model: null } };
77874
+ }
77875
+ if (method === "GET" && pathname === "/api/auth/providers") {
77876
+ return { status: 200, body: { providers: [] } };
77877
+ }
77878
+ if (method === "GET" && pathname.startsWith("/api/model/options")) {
77879
+ return { status: 200, body: switchroomModelOptions() };
77880
+ }
77881
+ if (method === "POST" && pathname === "/api/model/set") {
77882
+ return { status: 200, body: { ok: true } };
77883
+ }
77722
77884
  return null;
77723
77885
  }
77724
77886
  function sendEvent(ctx, type, sessionId, payload) {
@@ -77834,7 +77996,7 @@ async function onHermesMessage(ctx, raw) {
77834
77996
  sendResponse(ctx, rpcErr(id, -32603, result.error ?? "Failed to read history"));
77835
77997
  break;
77836
77998
  }
77837
- sendResponse(ctx, rpcOk(id, { history: result.turns ?? [] }));
77999
+ sendResponse(ctx, rpcOk(id, { session_id: sessionId, messages: turnsToMessages(result.turns ?? []) }));
77838
78000
  break;
77839
78001
  }
77840
78002
  case "session.usage": {
@@ -77917,6 +78079,82 @@ async function onHermesMessage(ctx, raw) {
77917
78079
  sendResponse(ctx, rpcOk(id, { ok: true }));
77918
78080
  break;
77919
78081
  }
78082
+ case "model.options": {
78083
+ sendResponse(ctx, rpcOk(id, switchroomModelOptions()));
78084
+ break;
78085
+ }
78086
+ case "model.info": {
78087
+ sendResponse(ctx, rpcOk(id, { model: "claude-sonnet-4-6", provider: "switchroom" }));
78088
+ break;
78089
+ }
78090
+ case "config.set": {
78091
+ const sessionId = String(params.session_id ?? ctx.activeSessionId ?? "");
78092
+ const key = String(params.key ?? "");
78093
+ const value = String(params.value ?? "");
78094
+ if (key === "model" && sessionId && config.agents?.[sessionId]) {
78095
+ const modelName = value.replace(/\s+--provider\s+\S+$/, "").trim();
78096
+ if (modelName) {
78097
+ const agentsDir = resolveAgentsDir(config);
78098
+ const chat = resolveAgentChat(config, sessionId, agentsDir);
78099
+ if (chat) {
78100
+ const promptKey = `model-switch-${Date.now()}`;
78101
+ injectInbound(agentsDir, sessionId, chat.chatId, chat.threadId, `/model ${modelName}`, promptKey);
78102
+ }
78103
+ }
78104
+ }
78105
+ sendResponse(ctx, rpcOk(id, { ok: true }));
78106
+ break;
78107
+ }
78108
+ case "commands.catalog": {
78109
+ sendResponse(ctx, rpcOk(id, switchroomCommandsCatalog()));
78110
+ break;
78111
+ }
78112
+ case "slash.exec": {
78113
+ const sessionId = String(params.session_id ?? ctx.activeSessionId ?? "");
78114
+ const bare = String(params.command ?? "").replace(/^\/+/, "");
78115
+ const arg = params.arg != null ? String(params.arg) : "";
78116
+ if (!sessionId || !config.agents?.[sessionId]) {
78117
+ sendResponse(ctx, rpcErr(id, -32602, `Unknown session: ${sessionId}`));
78118
+ break;
78119
+ }
78120
+ if (!bare) {
78121
+ sendResponse(ctx, rpcErr(id, -32602, "command is required"));
78122
+ break;
78123
+ }
78124
+ const fullCommand = arg ? `/${bare} ${arg}` : `/${bare}`;
78125
+ const verb = `/${bare}`;
78126
+ if (INJECT_BLOCKLIST.has(verb)) {
78127
+ sendResponse(ctx, rpcErr(id, -32602, `/${bare} cannot be executed remotely`));
78128
+ break;
78129
+ }
78130
+ if (INJECT_ALLOWLIST.has(verb)) {
78131
+ let injectResult;
78132
+ try {
78133
+ injectResult = await injectSlashCommand(sessionId, fullCommand);
78134
+ } catch (err) {
78135
+ const msg = err instanceof Error ? err.message : String(err);
78136
+ sendResponse(ctx, rpcErr(id, -32603, msg));
78137
+ break;
78138
+ }
78139
+ const output = injectResult.outcome === "ok" ? injectResult.output ?? "" : `*(${fullCommand} sent)*`;
78140
+ sendResponse(ctx, rpcOk(id, { ok: true, output }));
78141
+ break;
78142
+ }
78143
+ const agentsDir = resolveAgentsDir(config);
78144
+ const chat = resolveAgentChat(config, sessionId, agentsDir);
78145
+ if (!chat) {
78146
+ sendResponse(ctx, rpcErr(id, -32603, `Cannot resolve chat for ${sessionId}`));
78147
+ break;
78148
+ }
78149
+ const promptKey = `slash-${bare}-${Date.now()}`;
78150
+ const result = await injectInbound(agentsDir, sessionId, chat.chatId, chat.threadId, fullCommand, promptKey);
78151
+ if (!result.ok) {
78152
+ sendResponse(ctx, rpcErr(id, -32603, result.error ?? "inject failed"));
78153
+ break;
78154
+ }
78155
+ sendResponse(ctx, rpcOk(id, { ok: true, output: `*(${fullCommand} sent to ${sessionId})*` }));
78156
+ break;
78157
+ }
77920
78158
  case "approval.request":
77921
78159
  case "approval.respond":
77922
78160
  case "sudo.request":
@@ -78542,7 +78780,7 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
78542
78780
  if (authError)
78543
78781
  return authError;
78544
78782
  return (async () => {
78545
- const hermesResult = await handleHermesRest(req.method, pathname, config);
78783
+ const hermesResult = await handleHermesRest(req.method, pathname, config, url.search);
78546
78784
  if (hermesResult !== null) {
78547
78785
  return jsonResponse(hermesResult.body, hermesResult.status);
78548
78786
  }
@@ -22587,7 +22587,7 @@ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "node:f
22587
22587
  import { dirname as dirname4, join as join2 } from "node:path";
22588
22588
 
22589
22589
  // src/build-info.ts
22590
- var VERSION = "0.16.20";
22590
+ var VERSION = "0.16.22";
22591
22591
 
22592
22592
  // src/cli/resolve-version.ts
22593
22593
  function readPackageVersion() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchroom",
3
- "version": "0.16.20",
3
+ "version": "0.16.22",
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": {
@@ -56246,10 +56246,10 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
56246
56246
  }
56247
56247
 
56248
56248
  // ../src/build-info.ts
56249
- var VERSION = "0.16.20";
56250
- var COMMIT_SHA = "6ea6e873";
56251
- var COMMIT_DATE = "2026-06-29T05:16:25Z";
56252
- var LATEST_PR = 2657;
56249
+ var VERSION = "0.16.22";
56250
+ var COMMIT_SHA = "ea287bf4";
56251
+ var COMMIT_DATE = "2026-06-29T08:38:13Z";
56252
+ var LATEST_PR = 2663;
56253
56253
  var COMMITS_AHEAD_OF_TAG = 0;
56254
56254
 
56255
56255
  // gateway/boot-version.ts