switchroom 0.14.11 → 0.14.13

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.
@@ -11271,7 +11271,7 @@ var init_zod = __esm(() => {
11271
11271
  });
11272
11272
 
11273
11273
  // src/config/schema.ts
11274
- var CodeRepoEntrySchema, AgentBindMountSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReleaseBlock, NetworkIsolationSchema, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, HostdConfigSchema, SwitchroomConfigSchema;
11274
+ var CodeRepoEntrySchema, AgentBindMountSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReleaseBlock, NetworkIsolationSchema, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, HostdConfigSchema, SwitchroomConfigSchema;
11275
11275
  var init_schema = __esm(() => {
11276
11276
  init_zod();
11277
11277
  CodeRepoEntrySchema = exports_external.object({
@@ -11721,6 +11721,9 @@ var init_schema = __esm(() => {
11721
11721
  enabled: exports_external.boolean().default(true).describe("Whether the host-control daemon is in use. Default: true (since " + "RFC C Phase 2 default-flip — the gateway's /restart, /new, /reset, " + "and /update apply slash-commands all dispatch through hostd, and " + "without it those verbs fail on docker-mode installs because the " + "agent container has no docker binary/socket). " + "When true, the compose generator emits per-agent bind mounts " + "at `~/.switchroom/hostd/<name>/sock` for every admin-flagged " + "agent. Install the daemon with `switchroom hostd install` — " + "it runs as a docker container in its own compose project " + "(`switchroom-hostd`), separate from the agent fleet's compose " + "project so `up -d --remove-orphans` cycles of the fleet " + "can't recreate the daemon mid-RPC. See RFC C §5.1. " + "Set enabled: false only on legacy systemd-mode installs that " + "still rely on the in-container `spawnSwitchroomDetached` " + "shellout (removal is tracked as RFC C Phase 3)."),
11722
11722
  auto_release_check: AutoReleaseCheckSchema.default({}).describe("Pull-based release-triggered fleet restart (#1743). hostd polls " + "the remote release tag on a fixed interval and applies + " + "restarts the fleet (graceful) when a new release is detected. " + "Opt-in: default enabled=false.")
11723
11723
  });
11724
+ WebServiceConfigSchema = exports_external.object({
11725
+ managed: exports_external.boolean().default(false).describe("Whether `switchroom update` refreshes the web-service container " + "(dashboard + GitHub-webhook receiver) via `switchroom webd " + "install`. Default: false — existing installs run the web server " + "as the legacy `switchroom-web.service` systemd unit and must not " + "be surprised by a container takeover of host loopback 127.0.0.1:" + "8080 mid-update. Set true ONLY after cutting over to the " + "container (stop+disable the systemd unit, `switchroom webd " + "install`). The container runs in its own compose project " + "(`switchroom-web`), separate from the agent fleet, with " + "network_mode: host so it keeps owning loopback:8080 for the " + "cloudflared tunnel + tailscale serve consumers.")
11726
+ });
11724
11727
  HostdConfigSchema = exports_external.object({
11725
11728
  config_edit_enabled: exports_external.boolean().default(false).describe("Opt-in toggle for the `config_propose_edit` hostd verb (RFC " + "admin-agent-config-edit §3). Default false — the verb returns " + "`E_CONFIG_EDIT_DISABLED` until the operator explicitly flips " + "this to true. When true (and once PR 1c lands the apply path), " + "admin agents can propose unified-diff patches against " + "`/state/config/switchroom.yaml`, gated by an operator approval " + "card in the primary chat. Same trust posture as `update_apply` " + "and `agent_restart`: the human-in-the-loop tap is the security " + "boundary, not the agent's judgement."),
11726
11729
  config_edit_rate_per_hour: exports_external.number().int().min(1).max(20).default(3).describe("Per-requesting-agent rate cap for `config_propose_edit` cards " + "(RFC admin-agent-config-edit §5). Default 3 cards/hour; min 1, " + "max 20. Implemented as a sqlite token bucket in PR 1c; the " + "field is wired here in PR 1a so operators can pin it before the " + "limiter is live. Above the cap, the verb returns " + "`E_RATE_LIMITED` without raising a card.")
@@ -11754,6 +11757,7 @@ var init_schema = __esm(() => {
11754
11757
  quota: QuotaConfigSchema.optional().describe("Optional weekly/monthly USD spend budgets rendered in the session " + "greeting. Usage is read from ccusage at runtime; no network calls."),
11755
11758
  host_control: HostControlConfigSchema.default({}).describe("Host-control daemon configuration. Defaults to enabled=true since " + "RFC C Phase 2 (docs/rfcs/host-control-daemon.md). Omit the block " + "to accept defaults; set `enabled: false` only on legacy systemd-" + "mode installs (removal tracked as RFC C Phase 3)."),
11756
11759
  hostd: HostdConfigSchema.default({}).describe("hostd verb-level knobs (RFC admin-agent-config-edit). Distinct " + "from `host_control:` which governs whether the daemon runs at " + "all. Currently scopes the opt-in flag and rate cap for the new " + "`config_propose_edit` verb (PR 1a — disabled by default)."),
11760
+ web_service: WebServiceConfigSchema.default({}).describe("Web-service container (dashboard + GitHub-webhook receiver) config. " + "Defaults to managed=false so existing systemd-mode installs are " + "untouched. Set managed: true after cutting over to the " + "`switchroom-web` container — then `switchroom update` keeps it " + "refreshed. See `switchroom webd install`."),
11757
11761
  google_accounts: exports_external.record(exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
11758
11762
  message: "Account key must be a Google account email like 'alice@example.com' (colons not allowed)"
11759
11763
  }).transform((v) => v.trim().toLowerCase()), exports_external.object({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchroom",
3
- "version": "0.14.11",
3
+ "version": "0.14.13",
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": {
@@ -24446,6 +24446,40 @@ function createIpcClient(options) {
24446
24446
 
24447
24447
  // permission-rule.ts
24448
24448
  import { basename as basename2 } from "node:path";
24449
+ var FILE_TOOLS = new Set([
24450
+ "Edit",
24451
+ "Write",
24452
+ "MultiEdit",
24453
+ "NotebookEdit",
24454
+ "Read"
24455
+ ]);
24456
+ var BROAD_ONLY_TOOLS = new Set([
24457
+ "Glob",
24458
+ "Grep",
24459
+ "WebFetch",
24460
+ "WebSearch",
24461
+ "Task",
24462
+ "Agent",
24463
+ "TodoWrite",
24464
+ "ExitPlanMode"
24465
+ ]);
24466
+ function resolveSkillName(input) {
24467
+ return readString(input, "skill") ?? readString(input, "skill_name") ?? readString(input, "skillName") ?? readString(input, "name") ?? skillBasenameFromPath(input);
24468
+ }
24469
+ function filePathFrom(input) {
24470
+ if (!input)
24471
+ return null;
24472
+ return readString(input, "file_path") ?? readString(input, "notebook_path");
24473
+ }
24474
+ function bashFirstToken(command) {
24475
+ const m = /^\s*([^\s|&;<>()`$]+)/.exec(command);
24476
+ if (!m)
24477
+ return null;
24478
+ const tok = m[1];
24479
+ if (tok.includes(".."))
24480
+ return null;
24481
+ return /^[A-Za-z0-9._\-\/]+$/.test(tok) ? tok : null;
24482
+ }
24449
24483
  function parseInput(raw) {
24450
24484
  if (!raw || typeof raw !== "string")
24451
24485
  return null;
@@ -24474,16 +24508,35 @@ function skillBasenameFromPath(input) {
24474
24508
  function matchesAllowRule(rule, toolName, inputPreview) {
24475
24509
  if (!rule || !toolName)
24476
24510
  return false;
24477
- const skillMatch = /^Skill\(([^)]+)\)$/.exec(rule);
24478
- if (skillMatch) {
24479
- if (toolName !== "Skill")
24511
+ if (rule.endsWith("__*") && rule.startsWith("mcp__")) {
24512
+ const prefix = rule.slice(0, -1);
24513
+ return toolName.startsWith(prefix);
24514
+ }
24515
+ const scoped = /^([A-Za-z]+)\((.+)\)$/.exec(rule);
24516
+ if (scoped) {
24517
+ const ruleTool = scoped[1];
24518
+ const arg = scoped[2];
24519
+ if (ruleTool !== toolName)
24480
24520
  return false;
24481
- const ruleSkill = skillMatch[1];
24482
24521
  const input = parseInput(inputPreview);
24483
- if (!input)
24484
- return false;
24485
- const reqSkill = readString(input, "skill") ?? readString(input, "skill_name") ?? readString(input, "skillName") ?? readString(input, "name") ?? skillBasenameFromPath(input);
24486
- return reqSkill === ruleSkill;
24522
+ if (ruleTool === "Skill") {
24523
+ if (!input)
24524
+ return false;
24525
+ return resolveSkillName(input) === arg;
24526
+ }
24527
+ if (ruleTool === "Bash") {
24528
+ const cmd = input ? readString(input, "command") : null;
24529
+ if (!cmd)
24530
+ return false;
24531
+ const m = /^([^:]+):\*$/.exec(arg);
24532
+ if (!m)
24533
+ return false;
24534
+ return bashFirstToken(cmd) === m[1];
24535
+ }
24536
+ if (FILE_TOOLS.has(ruleTool)) {
24537
+ return filePathFrom(input) === arg;
24538
+ }
24539
+ return false;
24487
24540
  }
24488
24541
  return rule === toolName;
24489
24542
  }