switchroom 0.15.0 → 0.15.2

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.
@@ -11348,6 +11348,7 @@ var AgentSchema = exports_external.object({
11348
11348
  dangerous_mode: exports_external.boolean().optional().describe("If true, include --dangerously-skip-permissions in start.sh"),
11349
11349
  network_isolation: NetworkIsolationSchema,
11350
11350
  admin: exports_external.boolean().optional().describe("If true, the agent's Telegram gateway intercepts admin slash commands " + "(/agents, /logs, /restart, /delete, /update, /auth, /reconcile, etc.) " + "locally before forwarding to Claude. Commands are handled silently — " + "Claude never sees them. Requires the agent to use the switchroom-telegram " + "plugin. When false or absent, all messages pass through to Claude unchanged."),
11351
+ root: exports_external.boolean().optional().describe("If true, this is a ROOT-tier debugging agent: a root-privileged " + "container (runs as uid 0, mounts /var/run/docker.sock, the whole " + "~/.switchroom tree, and the host root filesystem at /host) so you " + "can DM it to debug the whole fleet — read any agent's logs, " + "docker exec into peers, edit host files — instead of SSHing into " + "the host as root. Implies admin: true (all admin slash commands). " + "Standing root power, audited via the agent's own session transcript " + "and shell history; there is no per-action approval tap. Per-agent " + "only (never set at defaults/profile layers). Grant to exactly one " + "trusted operator-private agent — it ingests other agents' output, " + "which is attacker-influenced text. See docs/root-agent.md."),
11351
11352
  settings_raw: exports_external.record(exports_external.string(), exports_external.unknown()).optional().describe("Escape hatch: raw object deep-merged into the generated " + "settings.json as the final step. Use for Claude Code settings " + "keys switchroom doesn't wrap directly (e.g. effort, apiKeyHelper). " + "Power-user-only — prefer the typed fields when they exist."),
11352
11353
  claude_md_raw: exports_external.string().optional().describe("Escape hatch: markdown text appended verbatim to CLAUDE.md on " + "initial scaffold. Not re-applied on reconcile (CLAUDE.md is " + "user-protected). Use for one-off persona tuning that isn't " + "worth a template."),
11353
11354
  cli_args: exports_external.array(exports_external.string()).optional().describe("Escape hatch: extra arguments appended to the `exec claude` " + "invocation in start.sh. Use for Claude Code CLI flags switchroom " + "doesn't expose directly (e.g. --effort high, " + "--exclude-dynamic-system-prompt-sections)."),
@@ -13283,6 +13284,13 @@ var ProbeQuotaRequestSchema = exports_external.object({
13283
13284
  accounts: exports_external.array(exports_external.string().min(1)).min(1).max(32),
13284
13285
  timeoutMs: exports_external.number().int().positive().max(60000).optional()
13285
13286
  });
13287
+ var ClaimNotificationRequestSchema = exports_external.object({
13288
+ v: exports_external.literal(PROTOCOL_VERSION),
13289
+ op: exports_external.literal("claim-notification"),
13290
+ id: exports_external.string().min(1),
13291
+ key: exports_external.string().min(1).max(512),
13292
+ windowMs: exports_external.number().int().positive().max(86400000)
13293
+ });
13286
13294
  var RequestSchema2 = exports_external.discriminatedUnion("op", [
13287
13295
  GetCredentialsRequestSchema,
13288
13296
  ListStateRequestSchema,
@@ -13294,7 +13302,8 @@ var RequestSchema2 = exports_external.discriminatedUnion("op", [
13294
13302
  SetOverrideRequestSchema,
13295
13303
  ListGoogleAccountsRequestSchema,
13296
13304
  ListMicrosoftAccountsRequestSchema,
13297
- ProbeQuotaRequestSchema
13305
+ ProbeQuotaRequestSchema,
13306
+ ClaimNotificationRequestSchema
13298
13307
  ]);
13299
13308
  var GetCredentialsDataSchema = exports_external.object({
13300
13309
  account: exports_external.string(),
@@ -13350,6 +13359,9 @@ var SetOverrideDataSchema = exports_external.object({
13350
13359
  agent: exports_external.string(),
13351
13360
  account: exports_external.string().nullable()
13352
13361
  });
13362
+ var ClaimNotificationDataSchema = exports_external.object({
13363
+ granted: exports_external.boolean()
13364
+ });
13353
13365
  var GoogleAccountStateSchema = exports_external.object({
13354
13366
  account: exports_external.string(),
13355
13367
  expiresAt: exports_external.number(),
@@ -13556,6 +13568,16 @@ class AuthBrokerClient {
13556
13568
  const data = await this.send(req);
13557
13569
  return data;
13558
13570
  }
13571
+ async claimNotification(key, windowMs) {
13572
+ const data = await this.send({
13573
+ v: PROTOCOL_VERSION,
13574
+ id: randomUUID(),
13575
+ op: "claim-notification",
13576
+ key,
13577
+ windowMs
13578
+ });
13579
+ return data;
13580
+ }
13559
13581
  async refreshAccount(account) {
13560
13582
  const data = await this.send({
13561
13583
  v: PROTOCOL_VERSION,
@@ -11348,6 +11348,7 @@ var AgentSchema = exports_external.object({
11348
11348
  dangerous_mode: exports_external.boolean().optional().describe("If true, include --dangerously-skip-permissions in start.sh"),
11349
11349
  network_isolation: NetworkIsolationSchema,
11350
11350
  admin: exports_external.boolean().optional().describe("If true, the agent's Telegram gateway intercepts admin slash commands " + "(/agents, /logs, /restart, /delete, /update, /auth, /reconcile, etc.) " + "locally before forwarding to Claude. Commands are handled silently — " + "Claude never sees them. Requires the agent to use the switchroom-telegram " + "plugin. When false or absent, all messages pass through to Claude unchanged."),
11351
+ root: exports_external.boolean().optional().describe("If true, this is a ROOT-tier debugging agent: a root-privileged " + "container (runs as uid 0, mounts /var/run/docker.sock, the whole " + "~/.switchroom tree, and the host root filesystem at /host) so you " + "can DM it to debug the whole fleet — read any agent's logs, " + "docker exec into peers, edit host files — instead of SSHing into " + "the host as root. Implies admin: true (all admin slash commands). " + "Standing root power, audited via the agent's own session transcript " + "and shell history; there is no per-action approval tap. Per-agent " + "only (never set at defaults/profile layers). Grant to exactly one " + "trusted operator-private agent — it ingests other agents' output, " + "which is attacker-influenced text. See docs/root-agent.md."),
11351
11352
  settings_raw: exports_external.record(exports_external.string(), exports_external.unknown()).optional().describe("Escape hatch: raw object deep-merged into the generated " + "settings.json as the final step. Use for Claude Code settings " + "keys switchroom doesn't wrap directly (e.g. effort, apiKeyHelper). " + "Power-user-only — prefer the typed fields when they exist."),
11352
11353
  claude_md_raw: exports_external.string().optional().describe("Escape hatch: markdown text appended verbatim to CLAUDE.md on " + "initial scaffold. Not re-applied on reconcile (CLAUDE.md is " + "user-protected). Use for one-off persona tuning that isn't " + "worth a template."),
11353
11354
  cli_args: exports_external.array(exports_external.string()).optional().describe("Escape hatch: extra arguments appended to the `exec claude` " + "invocation in start.sh. Use for Claude Code CLI flags switchroom " + "doesn't expose directly (e.g. --effort high, " + "--exclude-dynamic-system-prompt-sections)."),
@@ -13367,6 +13368,13 @@ var ProbeQuotaRequestSchema = exports_external.object({
13367
13368
  accounts: exports_external.array(exports_external.string().min(1)).min(1).max(32),
13368
13369
  timeoutMs: exports_external.number().int().positive().max(60000).optional()
13369
13370
  });
13371
+ var ClaimNotificationRequestSchema = exports_external.object({
13372
+ v: exports_external.literal(PROTOCOL_VERSION),
13373
+ op: exports_external.literal("claim-notification"),
13374
+ id: exports_external.string().min(1),
13375
+ key: exports_external.string().min(1).max(512),
13376
+ windowMs: exports_external.number().int().positive().max(86400000)
13377
+ });
13370
13378
  var RequestSchema = exports_external.discriminatedUnion("op", [
13371
13379
  GetCredentialsRequestSchema,
13372
13380
  ListStateRequestSchema,
@@ -13378,7 +13386,8 @@ var RequestSchema = exports_external.discriminatedUnion("op", [
13378
13386
  SetOverrideRequestSchema,
13379
13387
  ListGoogleAccountsRequestSchema,
13380
13388
  ListMicrosoftAccountsRequestSchema,
13381
- ProbeQuotaRequestSchema
13389
+ ProbeQuotaRequestSchema,
13390
+ ClaimNotificationRequestSchema
13382
13391
  ]);
13383
13392
  var GetCredentialsDataSchema = exports_external.object({
13384
13393
  account: exports_external.string(),
@@ -13434,6 +13443,9 @@ var SetOverrideDataSchema = exports_external.object({
13434
13443
  agent: exports_external.string(),
13435
13444
  account: exports_external.string().nullable()
13436
13445
  });
13446
+ var ClaimNotificationDataSchema = exports_external.object({
13447
+ granted: exports_external.boolean()
13448
+ });
13437
13449
  var GoogleAccountStateSchema = exports_external.object({
13438
13450
  account: exports_external.string(),
13439
13451
  expiresAt: exports_external.number(),
@@ -13520,6 +13532,7 @@ var MARK_EXHAUSTED_DEFAULT_MS = 5 * 60 * 60 * 1000;
13520
13532
  var AUDIT_ROTATE_BYTES = 10 * 1024 * 1024;
13521
13533
  var AUDIT_KEEP = 5;
13522
13534
  var AUDIT_LINE_MAX = 4000;
13535
+ var NOTIFICATION_CLAIM_MAX_AGE_MS = 86400000;
13523
13536
  function sha256Hex(content) {
13524
13537
  return createHash2("sha256").update(content).digest("hex");
13525
13538
  }
@@ -13546,7 +13559,10 @@ function enrichMirrorContent(sourceJson) {
13546
13559
  function configToShape(cfg) {
13547
13560
  const auth = cfg.auth ?? {};
13548
13561
  const agentsMap = cfg.agents ?? {};
13549
- const adminAgents = Object.entries(agentsMap).filter(([, a]) => a.admin === true).map(([name]) => name);
13562
+ const adminAgents = Object.entries(agentsMap).filter(([, a]) => {
13563
+ const cfg2 = a;
13564
+ return cfg2.admin === true || cfg2.root === true;
13565
+ }).map(([name]) => name);
13550
13566
  return {
13551
13567
  agents: Object.keys(agentsMap),
13552
13568
  consumers: (auth.consumers ?? []).map((c) => c.name),
@@ -13573,6 +13589,7 @@ class AuthBroker {
13573
13589
  lastQuotaCache = {};
13574
13590
  shaIndex = {};
13575
13591
  thresholdViolations = {};
13592
+ notificationClaims = {};
13576
13593
  lastWrittenExpiresAt = new Map;
13577
13594
  refreshInFlight = new Set;
13578
13595
  consumerLastSeen = {};
@@ -13761,7 +13778,8 @@ class AuthBroker {
13761
13778
  }
13762
13779
  const sockPath = this.agentSocketPath(agentName);
13763
13780
  const uid = allocateAgentUid(agentName);
13764
- const adminFlag = this.config.agents?.[agentName]?.admin === true;
13781
+ const agentCfg = this.config.agents?.[agentName];
13782
+ const adminFlag = agentCfg?.admin === true || agentCfg?.root === true;
13765
13783
  await this.bindListener(sockPath, uid, 432, {
13766
13784
  kind: "agent",
13767
13785
  name: agentName,
@@ -13971,6 +13989,9 @@ class AuthBroker {
13971
13989
  case "probe-quota":
13972
13990
  await this.opProbeQuota(socket, reqId, identity2, req.accounts, req.timeoutMs);
13973
13991
  break;
13992
+ case "claim-notification":
13993
+ this.opClaimNotification(socket, reqId, identity2, req.key, req.windowMs);
13994
+ break;
13974
13995
  }
13975
13996
  } catch (err) {
13976
13997
  socket.write(encodeError(reqId, "INTERNAL", err.message));
@@ -14211,6 +14232,21 @@ class AuthBroker {
14211
14232
  this.audit({ op: "mark-exhausted", identity: identity2, account, ok: true });
14212
14233
  socket.write(encodeSuccess(id, { account, rolled, rolledTo }));
14213
14234
  }
14235
+ opClaimNotification(socket, id, identity2, key, windowMs) {
14236
+ const now = this.now();
14237
+ const prev = this.notificationClaims[key];
14238
+ const granted = prev === undefined || now - prev >= windowMs;
14239
+ if (granted) {
14240
+ this.notificationClaims[key] = now;
14241
+ for (const [k, ts] of Object.entries(this.notificationClaims)) {
14242
+ if (now - ts > NOTIFICATION_CLAIM_MAX_AGE_MS)
14243
+ delete this.notificationClaims[k];
14244
+ }
14245
+ this.persistNotificationClaims();
14246
+ this.audit({ op: "claim-notification", identity: identity2, account: key, ok: true });
14247
+ }
14248
+ socket.write(encodeSuccess(id, { granted }));
14249
+ }
14214
14250
  async opRefreshAccount(socket, id, identity2, account) {
14215
14251
  if (!this.isAdmin(identity2)) {
14216
14252
  this.audit({ op: "refresh-account", identity: identity2, account, ok: false, error: "FORBIDDEN" });
@@ -14813,6 +14849,7 @@ class AuthBroker {
14813
14849
  this.quota = this.readJson("quota.json") ?? {};
14814
14850
  this.shaIndex = this.readJson("sha-index.json") ?? {};
14815
14851
  this.thresholdViolations = this.readJson("threshold-violations.json") ?? {};
14852
+ this.notificationClaims = this.readJson("notification-claims.json") ?? {};
14816
14853
  }
14817
14854
  readJson(name) {
14818
14855
  const p = join4(this.stateDir, name);
@@ -14827,6 +14864,9 @@ class AuthBroker {
14827
14864
  persistQuota() {
14828
14865
  atomicWriteJsonSync(join4(this.stateDir, "quota.json"), this.quota, 384);
14829
14866
  }
14867
+ persistNotificationClaims() {
14868
+ atomicWriteJsonSync(join4(this.stateDir, "notification-claims.json"), this.notificationClaims, 384);
14869
+ }
14830
14870
  persistShaIndex() {
14831
14871
  atomicWriteJsonSync(join4(this.stateDir, "sha-index.json"), this.shaIndex, 384);
14832
14872
  }
@@ -4000,7 +4000,7 @@ function decodeResponse(line) {
4000
4000
  }
4001
4001
  return ResponseSchema.parse(parsed);
4002
4002
  }
4003
- var MAX_FRAME_BYTES, PROTOCOL_VERSION = 1, ProviderNameSchema, GetCredentialsRequestSchema, ListStateRequestSchema, SetActiveRequestSchema, MarkExhaustedRequestSchema, RefreshAccountRequestSchema, AnthropicCredentialsSchema, GoogleCredentialsSchema, MicrosoftCredentialsSchema, ProviderCredentialsSchema, AddAccountRequestSchema, RmAccountRequestSchema, SetOverrideRequestSchema, ListGoogleAccountsRequestSchema, ListMicrosoftAccountsRequestSchema, ProbeQuotaRequestSchema, RequestSchema, GetCredentialsDataSchema, AccountStateSchema, AgentStateSchema, ConsumerStateSchema, ListStateDataSchema, SetActiveDataSchema, MarkExhaustedDataSchema, RefreshAccountDataSchema, AddAccountDataSchema, RmAccountDataSchema, SetOverrideDataSchema, GoogleAccountStateSchema, ListGoogleAccountsDataSchema, MicrosoftAccountStateSchema, ListMicrosoftAccountsDataSchema, ErrorBodySchema, SuccessResponseSchema, ErrorResponseSchema, ResponseSchema;
4003
+ var MAX_FRAME_BYTES, PROTOCOL_VERSION = 1, ProviderNameSchema, GetCredentialsRequestSchema, ListStateRequestSchema, SetActiveRequestSchema, MarkExhaustedRequestSchema, RefreshAccountRequestSchema, AnthropicCredentialsSchema, GoogleCredentialsSchema, MicrosoftCredentialsSchema, ProviderCredentialsSchema, AddAccountRequestSchema, RmAccountRequestSchema, SetOverrideRequestSchema, ListGoogleAccountsRequestSchema, ListMicrosoftAccountsRequestSchema, ProbeQuotaRequestSchema, ClaimNotificationRequestSchema, RequestSchema, GetCredentialsDataSchema, AccountStateSchema, AgentStateSchema, ConsumerStateSchema, ListStateDataSchema, SetActiveDataSchema, MarkExhaustedDataSchema, RefreshAccountDataSchema, AddAccountDataSchema, RmAccountDataSchema, SetOverrideDataSchema, ClaimNotificationDataSchema, GoogleAccountStateSchema, ListGoogleAccountsDataSchema, MicrosoftAccountStateSchema, ListMicrosoftAccountsDataSchema, ErrorBodySchema, SuccessResponseSchema, ErrorResponseSchema, ResponseSchema;
4004
4004
  var init_protocol = __esm(() => {
4005
4005
  init_zod();
4006
4006
  MAX_FRAME_BYTES = 64 * 1024;
@@ -4116,6 +4116,13 @@ var init_protocol = __esm(() => {
4116
4116
  accounts: exports_external.array(exports_external.string().min(1)).min(1).max(32),
4117
4117
  timeoutMs: exports_external.number().int().positive().max(60000).optional()
4118
4118
  });
4119
+ ClaimNotificationRequestSchema = exports_external.object({
4120
+ v: exports_external.literal(PROTOCOL_VERSION),
4121
+ op: exports_external.literal("claim-notification"),
4122
+ id: exports_external.string().min(1),
4123
+ key: exports_external.string().min(1).max(512),
4124
+ windowMs: exports_external.number().int().positive().max(86400000)
4125
+ });
4119
4126
  RequestSchema = exports_external.discriminatedUnion("op", [
4120
4127
  GetCredentialsRequestSchema,
4121
4128
  ListStateRequestSchema,
@@ -4127,7 +4134,8 @@ var init_protocol = __esm(() => {
4127
4134
  SetOverrideRequestSchema,
4128
4135
  ListGoogleAccountsRequestSchema,
4129
4136
  ListMicrosoftAccountsRequestSchema,
4130
- ProbeQuotaRequestSchema
4137
+ ProbeQuotaRequestSchema,
4138
+ ClaimNotificationRequestSchema
4131
4139
  ]);
4132
4140
  GetCredentialsDataSchema = exports_external.object({
4133
4141
  account: exports_external.string(),
@@ -4183,6 +4191,9 @@ var init_protocol = __esm(() => {
4183
4191
  agent: exports_external.string(),
4184
4192
  account: exports_external.string().nullable()
4185
4193
  });
4194
+ ClaimNotificationDataSchema = exports_external.object({
4195
+ granted: exports_external.boolean()
4196
+ });
4186
4197
  GoogleAccountStateSchema = exports_external.object({
4187
4198
  account: exports_external.string(),
4188
4199
  expiresAt: exports_external.number(),
@@ -4364,6 +4375,16 @@ class AuthBrokerClient {
4364
4375
  const data = await this.send(req);
4365
4376
  return data;
4366
4377
  }
4378
+ async claimNotification(key, windowMs) {
4379
+ const data = await this.send({
4380
+ v: PROTOCOL_VERSION,
4381
+ id: randomUUID(),
4382
+ op: "claim-notification",
4383
+ key,
4384
+ windowMs
4385
+ });
4386
+ return data;
4387
+ }
4367
4388
  async refreshAccount(account) {
4368
4389
  const data = await this.send({
4369
4390
  v: PROTOCOL_VERSION,
@@ -12096,6 +12096,7 @@ var AgentSchema = exports_external.object({
12096
12096
  dangerous_mode: exports_external.boolean().optional().describe("If true, include --dangerously-skip-permissions in start.sh"),
12097
12097
  network_isolation: NetworkIsolationSchema,
12098
12098
  admin: exports_external.boolean().optional().describe("If true, the agent's Telegram gateway intercepts admin slash commands " + "(/agents, /logs, /restart, /delete, /update, /auth, /reconcile, etc.) " + "locally before forwarding to Claude. Commands are handled silently \u2014 " + "Claude never sees them. Requires the agent to use the switchroom-telegram " + "plugin. When false or absent, all messages pass through to Claude unchanged."),
12099
+ root: exports_external.boolean().optional().describe("If true, this is a ROOT-tier debugging agent: a root-privileged " + "container (runs as uid 0, mounts /var/run/docker.sock, the whole " + "~/.switchroom tree, and the host root filesystem at /host) so you " + "can DM it to debug the whole fleet \u2014 read any agent's logs, " + "docker exec into peers, edit host files \u2014 instead of SSHing into " + "the host as root. Implies admin: true (all admin slash commands). " + "Standing root power, audited via the agent's own session transcript " + "and shell history; there is no per-action approval tap. Per-agent " + "only (never set at defaults/profile layers). Grant to exactly one " + "trusted operator-private agent \u2014 it ingests other agents' output, " + "which is attacker-influenced text. See docs/root-agent.md."),
12099
12100
  settings_raw: exports_external.record(exports_external.string(), exports_external.unknown()).optional().describe("Escape hatch: raw object deep-merged into the generated " + "settings.json as the final step. Use for Claude Code settings " + "keys switchroom doesn't wrap directly (e.g. effort, apiKeyHelper). " + "Power-user-only \u2014 prefer the typed fields when they exist."),
12100
12101
  claude_md_raw: exports_external.string().optional().describe("Escape hatch: markdown text appended verbatim to CLAUDE.md on " + "initial scaffold. Not re-applied on reconcile (CLAUDE.md is " + "user-protected). Use for one-off persona tuning that isn't " + "worth a template."),
12101
12102
  cli_args: exports_external.array(exports_external.string()).optional().describe("Escape hatch: extra arguments appended to the `exec claude` " + "invocation in start.sh. Use for Claude Code CLI flags switchroom " + "doesn't expose directly (e.g. --effort high, " + "--exclude-dynamic-system-prompt-sections)."),