switchroom 0.18.12 → 0.18.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.
Files changed (49) hide show
  1. package/dist/agent-scheduler/index.js +8 -0
  2. package/dist/auth-broker/index.js +63 -65
  3. package/dist/cli/ms-365-write-pretool.mjs +31 -8
  4. package/dist/cli/notion-write-pretool.mjs +9 -1
  5. package/dist/cli/skill-validate-pretool.mjs +144 -2847
  6. package/dist/cli/switchroom.js +952 -3126
  7. package/dist/host-control/main.js +216 -2862
  8. package/dist/vault/approvals/kernel-server.js +67 -0
  9. package/dist/vault/broker/server.js +98 -44
  10. package/package.json +1 -1
  11. package/telegram-plugin/dist/bridge/bridge.js +49 -3
  12. package/telegram-plugin/dist/gateway/gateway.js +656 -2326
  13. package/telegram-plugin/dist/server.js +65 -3
  14. package/telegram-plugin/format.ts +19 -0
  15. package/telegram-plugin/gateway/approval-hold.ts +21 -2
  16. package/telegram-plugin/gateway/callback-query-handlers.ts +12 -0
  17. package/telegram-plugin/gateway/gateway.ts +221 -73
  18. package/telegram-plugin/history.ts +51 -0
  19. package/telegram-plugin/inline-keyboard-callbacks.ts +94 -0
  20. package/telegram-plugin/model-unavailable.ts +41 -11
  21. package/telegram-plugin/outbound-field-redact.ts +69 -0
  22. package/telegram-plugin/render/render.ts +32 -14
  23. package/telegram-plugin/scoped-approval.ts +11 -2
  24. package/telegram-plugin/secret-detect/chunker.ts +18 -4
  25. package/telegram-plugin/secret-detect/index.ts +12 -56
  26. package/telegram-plugin/send-gate-degraded.test.ts +131 -0
  27. package/telegram-plugin/send-gate.test.ts +25 -6
  28. package/telegram-plugin/send-gate.ts +82 -8
  29. package/telegram-plugin/session-tail.ts +82 -7
  30. package/telegram-plugin/subagent-watcher.ts +71 -16
  31. package/telegram-plugin/tests/approval-hold-outcome.test.ts +36 -5
  32. package/telegram-plugin/tests/callback-query-handlers.test.ts +65 -0
  33. package/telegram-plugin/tests/gateway-outbound-redact.test.ts +57 -0
  34. package/telegram-plugin/tests/history.test.ts +115 -0
  35. package/telegram-plugin/tests/inbound-message-types.test.ts +5 -1
  36. package/telegram-plugin/tests/inline-keyboard-callbacks.test.ts +164 -0
  37. package/telegram-plugin/tests/operator-events-session-tail.test.ts +74 -0
  38. package/telegram-plugin/tests/outbound-field-redact.test.ts +107 -0
  39. package/telegram-plugin/tests/reaction-gate-routing.test.ts +173 -0
  40. package/telegram-plugin/tests/render/render.test.ts +88 -0
  41. package/telegram-plugin/tests/scoped-approval.test.ts +27 -0
  42. package/telegram-plugin/tests/secret-detect-chunk-overlap.test.ts +65 -0
  43. package/telegram-plugin/tests/secret-detect-oauth-code.test.ts +5 -4
  44. package/telegram-plugin/tests/session-tail-sidecar-reap.test.ts +268 -0
  45. package/telegram-plugin/tests/subagent-watcher-fd-leak.test.ts +275 -0
  46. package/telegram-plugin/tests/worktree-watch-cwds.test.ts +215 -1
  47. package/telegram-plugin/worktree-watch-cwds.ts +194 -5
  48. package/telegram-plugin/secret-detect/secretlint-source.ts +0 -95
  49. package/telegram-plugin/tests/secret-detect-secretlint.test.ts +0 -105
@@ -4778,6 +4778,13 @@ var init_schema = __esm(() => {
4778
4778
  });
4779
4779
  });
4780
4780
 
4781
+ // src/util/atomic.ts
4782
+ import { closeSync, constants, fsyncSync, openSync, renameSync, rmSync, writeSync } from "node:fs";
4783
+ var TMP_OPEN_FLAGS;
4784
+ var init_atomic = __esm(() => {
4785
+ TMP_OPEN_FLAGS = constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0);
4786
+ });
4787
+
4781
4788
  // src/config/merge.ts
4782
4789
  function dedupe(items) {
4783
4790
  const seen = new Set;
@@ -18249,6 +18256,13 @@ var ApprovalLookupRequestSchema = exports_external.object({
18249
18256
  action: exports_external.string().min(1),
18250
18257
  current_approver_set: exports_external.array(exports_external.string())
18251
18258
  });
18259
+ var ApprovalLookupByRequestRequestSchema = exports_external.object({
18260
+ v: exports_external.literal(1),
18261
+ op: exports_external.literal("approval_lookup_by_request"),
18262
+ agent_unit: exports_external.string().min(1),
18263
+ request_id: exports_external.string().regex(/^[0-9a-f]{32}$/),
18264
+ current_approver_set: exports_external.array(exports_external.string())
18265
+ });
18252
18266
  var ApprovalConsumeRequestSchema = exports_external.object({
18253
18267
  v: exports_external.literal(1),
18254
18268
  op: exports_external.literal("approval_consume"),
@@ -18303,6 +18317,7 @@ var RequestSchema = exports_external.discriminatedUnion("op", [
18303
18317
  RevokeGrantRequestSchema,
18304
18318
  ApprovalRequestRequestSchema,
18305
18319
  ApprovalLookupRequestSchema,
18320
+ ApprovalLookupByRequestRequestSchema,
18306
18321
  ApprovalConsumeRequestSchema,
18307
18322
  ApprovalRevokeRequestSchema,
18308
18323
  ApprovalListRequestSchema,
@@ -18664,6 +18679,30 @@ function lookupDecision(db, query, opts = {}, now = Date.now()) {
18664
18679
  ORDER BY granted_at DESC LIMIT 1`).get(query.agent_unit, query.scope, query.action);
18665
18680
  if (!row)
18666
18681
  return { state: "no_decision" };
18682
+ return evaluateDecisionRow(db, row, currentCanonical, opts, now);
18683
+ }
18684
+ function lookupDecisionByRequestId(db, query, opts = {}, now = Date.now()) {
18685
+ const currentCanonical = canonicalizeApproverSet(query.current_approver_set);
18686
+ const nonceRow = db.query(`SELECT * FROM approval_nonces WHERE request_id = ?`).get(query.request_id);
18687
+ if (!nonceRow)
18688
+ return { state: "no_decision" };
18689
+ const nonce = nonceFromRow(nonceRow);
18690
+ if (nonce.agent_unit !== query.agent_unit)
18691
+ return { state: "no_decision" };
18692
+ if (nonce.decision_id === null) {
18693
+ if (nonce.expires_at < now)
18694
+ return { state: "expired" };
18695
+ return { state: "pending", request_id: nonce.request_id };
18696
+ }
18697
+ const decRow = db.query(`SELECT * FROM approval_decisions WHERE id = ?`).get(nonce.decision_id);
18698
+ if (!decRow)
18699
+ return { state: "expired" };
18700
+ if (decRow.revoked_at != null) {
18701
+ return { state: "denied", decision: rowToDecision(decRow) };
18702
+ }
18703
+ return evaluateDecisionRow(db, decRow, currentCanonical, opts, now);
18704
+ }
18705
+ function evaluateDecisionRow(db, row, currentCanonical, opts, now) {
18667
18706
  const decision = rowToDecision(row);
18668
18707
  if (decision.decision === "deny_perm") {
18669
18708
  return { state: "denied", decision };
@@ -18860,6 +18899,7 @@ init_schema();
18860
18899
 
18861
18900
  // src/agents/scaffold.ts
18862
18901
  import { join as join4, resolve as resolve5 } from "node:path";
18902
+ init_atomic();
18863
18903
  init_schema();
18864
18904
 
18865
18905
  // src/config/users.ts
@@ -19301,6 +19341,33 @@ function handleRequest(socket, req, agent, db, peerUid, isOperator = false) {
19301
19341
  socket.write(encodeResponse({ ok: true, state: r.state, decision }));
19302
19342
  return;
19303
19343
  }
19344
+ if (req.op === "approval_lookup_by_request") {
19345
+ const acl = checkApprovalAclByAgent(agent, req.agent_unit);
19346
+ if (!acl.allow) {
19347
+ socket.write(encodeResponse(errorResponse("DENIED", acl.reason)));
19348
+ return;
19349
+ }
19350
+ const r = lookupDecisionByRequestId(db, {
19351
+ agent_unit: req.agent_unit,
19352
+ request_id: req.request_id,
19353
+ current_approver_set: req.current_approver_set
19354
+ });
19355
+ const decision = r.state === "granted" || r.state === "denied" ? {
19356
+ id: r.decision.id,
19357
+ agent_unit: r.decision.agent_unit,
19358
+ scope: r.decision.scope,
19359
+ action: r.decision.action,
19360
+ decision: r.decision.decision,
19361
+ granted_at: r.decision.granted_at,
19362
+ granted_by_user_id: r.decision.granted_by_user_id,
19363
+ ttl_expires_at: r.decision.ttl_expires_at,
19364
+ last_used_at: r.decision.last_used_at,
19365
+ revoked_at: r.decision.revoked_at,
19366
+ revoke_reason: r.decision.revoke_reason
19367
+ } : null;
19368
+ socket.write(encodeResponse({ ok: true, state: r.state, decision }));
19369
+ return;
19370
+ }
19304
19371
  if (req.op === "approval_consume") {
19305
19372
  const peek = getNonce(db, req.request_id);
19306
19373
  if (peek !== null) {
@@ -4778,6 +4778,13 @@ var init_schema = __esm(() => {
4778
4778
  });
4779
4779
  });
4780
4780
 
4781
+ // src/util/atomic.ts
4782
+ import { closeSync, constants, fsyncSync, openSync, renameSync, rmSync, writeSync } from "node:fs";
4783
+ var TMP_OPEN_FLAGS;
4784
+ var init_atomic = __esm(() => {
4785
+ TMP_OPEN_FLAGS = constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0);
4786
+ });
4787
+
4781
4788
  // src/config/merge.ts
4782
4789
  function dedupe(items) {
4783
4790
  const seen = new Set;
@@ -18087,7 +18094,7 @@ var require_lib = __commonJS((exports, module) => {
18087
18094
 
18088
18095
  // src/vault/broker/server.ts
18089
18096
  import * as net from "node:net";
18090
- import { mkdirSync as mkdirSync6, chmodSync as chmodSync4, chownSync, existsSync as existsSync11, readFileSync as readFileSync10, readdirSync as readdirSync4, statSync as statSync6, unlinkSync as unlinkSync6, writeFileSync as writeFileSync3, renameSync as renameSync6 } from "node:fs";
18097
+ import { mkdirSync as mkdirSync6, chmodSync as chmodSync4, chownSync, existsSync as existsSync11, readFileSync as readFileSync10, readdirSync as readdirSync4, statSync as statSync6, unlinkSync as unlinkSync6, writeFileSync as writeFileSync3, renameSync as renameSync7 } from "node:fs";
18091
18098
 
18092
18099
  // src/agents/compose.ts
18093
18100
  import { createHash } from "node:crypto";
@@ -18095,6 +18102,7 @@ init_schema();
18095
18102
 
18096
18103
  // src/agents/scaffold.ts
18097
18104
  import { join as join4, resolve as resolve6 } from "node:path";
18105
+ init_atomic();
18098
18106
  init_schema();
18099
18107
 
18100
18108
  // src/config/users.ts
@@ -18424,14 +18432,14 @@ init_loader();
18424
18432
  import { randomBytes, scryptSync, createCipheriv, createDecipheriv } from "node:crypto";
18425
18433
  import {
18426
18434
  readFileSync as readFileSync5,
18427
- writeSync as writeSync2,
18435
+ writeSync as writeSync3,
18428
18436
  existsSync as existsSync6,
18429
- renameSync,
18437
+ renameSync as renameSync2,
18430
18438
  mkdirSync as mkdirSync2,
18431
18439
  unlinkSync as unlinkSync2,
18432
- fsyncSync as fsyncSync2,
18433
- openSync as openSync2,
18434
- closeSync as closeSync2,
18440
+ fsyncSync as fsyncSync3,
18441
+ openSync as openSync3,
18442
+ closeSync as closeSync3,
18435
18443
  lstatSync,
18436
18444
  realpathSync as realpathSync2
18437
18445
  } from "node:fs";
@@ -18440,10 +18448,10 @@ import { dirname, basename as basename2, resolve as resolve5 } from "node:path";
18440
18448
  // src/vault/flock.ts
18441
18449
  import {
18442
18450
  existsSync as existsSync5,
18443
- openSync,
18444
- closeSync,
18445
- writeSync,
18446
- fsyncSync,
18451
+ openSync as openSync2,
18452
+ closeSync as closeSync2,
18453
+ writeSync as writeSync2,
18454
+ fsyncSync as fsyncSync2,
18447
18455
  unlinkSync,
18448
18456
  readFileSync as readFileSync4,
18449
18457
  readdirSync as readdirSync3,
@@ -18568,7 +18576,7 @@ function acquireLock(vaultPath, options = {}) {
18568
18576
  while (true) {
18569
18577
  let fd = null;
18570
18578
  try {
18571
- fd = openSync(lockPath, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL, 384);
18579
+ fd = openSync2(lockPath, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL, 384);
18572
18580
  } catch (err) {
18573
18581
  const code = err?.code ?? "";
18574
18582
  if (code === "EEXIST") {
@@ -18604,11 +18612,11 @@ function acquireLock(vaultPath, options = {}) {
18604
18612
  ${Date.now()}
18605
18613
  ${process.argv[1] ?? ""}
18606
18614
  `;
18607
- writeSync(fd, meta);
18608
- fsyncSync(fd);
18615
+ writeSync2(fd, meta);
18616
+ fsyncSync2(fd);
18609
18617
  } catch {
18610
18618
  try {
18611
- closeSync(fd);
18619
+ closeSync2(fd);
18612
18620
  } catch {}
18613
18621
  try {
18614
18622
  unlinkSync(lockPath);
@@ -18619,7 +18627,7 @@ ${process.argv[1] ?? ""}
18619
18627
  return {
18620
18628
  release: () => {
18621
18629
  try {
18622
- closeSync(ownedFd);
18630
+ closeSync2(ownedFd);
18623
18631
  } catch {}
18624
18632
  try {
18625
18633
  unlinkSync(lockPath);
@@ -18675,20 +18683,20 @@ function atomicWriteFileSync(path, data, mode) {
18675
18683
  const dir = dirname(resolve5(effectivePath));
18676
18684
  const tmp = resolve5(dir, `.${basename2(effectivePath)}.${process.pid}.${Date.now()}.tmp`);
18677
18685
  try {
18678
- const fd = openSync2(tmp, "wx", mode);
18686
+ const fd = openSync3(tmp, "wx", mode);
18679
18687
  try {
18680
- writeSync2(fd, data);
18681
- fsyncSync2(fd);
18688
+ writeSync3(fd, data);
18689
+ fsyncSync3(fd);
18682
18690
  } finally {
18683
- closeSync2(fd);
18691
+ closeSync3(fd);
18684
18692
  }
18685
- renameSync(tmp, effectivePath);
18693
+ renameSync2(tmp, effectivePath);
18686
18694
  try {
18687
- const dirFd = openSync2(dir, "r");
18695
+ const dirFd = openSync3(dir, "r");
18688
18696
  try {
18689
- fsyncSync2(dirFd);
18697
+ fsyncSync3(dirFd);
18690
18698
  } finally {
18691
- closeSync2(dirFd);
18699
+ closeSync3(dirFd);
18692
18700
  }
18693
18701
  } catch {}
18694
18702
  } catch (err) {
@@ -19264,6 +19272,13 @@ var ApprovalLookupRequestSchema = exports_external.object({
19264
19272
  action: exports_external.string().min(1),
19265
19273
  current_approver_set: exports_external.array(exports_external.string())
19266
19274
  });
19275
+ var ApprovalLookupByRequestRequestSchema = exports_external.object({
19276
+ v: exports_external.literal(1),
19277
+ op: exports_external.literal("approval_lookup_by_request"),
19278
+ agent_unit: exports_external.string().min(1),
19279
+ request_id: exports_external.string().regex(/^[0-9a-f]{32}$/),
19280
+ current_approver_set: exports_external.array(exports_external.string())
19281
+ });
19267
19282
  var ApprovalConsumeRequestSchema = exports_external.object({
19268
19283
  v: exports_external.literal(1),
19269
19284
  op: exports_external.literal("approval_consume"),
@@ -19318,6 +19333,7 @@ var RequestSchema = exports_external.discriminatedUnion("op", [
19318
19333
  RevokeGrantRequestSchema,
19319
19334
  ApprovalRequestRequestSchema,
19320
19335
  ApprovalLookupRequestSchema,
19336
+ ApprovalLookupByRequestRequestSchema,
19321
19337
  ApprovalConsumeRequestSchema,
19322
19338
  ApprovalRevokeRequestSchema,
19323
19339
  ApprovalListRequestSchema,
@@ -19570,13 +19586,13 @@ import {
19570
19586
  copyFileSync as copyFileSync2,
19571
19587
  chmodSync,
19572
19588
  existsSync as existsSync7,
19573
- fsyncSync as fsyncSync3,
19589
+ fsyncSync as fsyncSync4,
19574
19590
  lstatSync as lstatSync2,
19575
19591
  mkdirSync as mkdirSync3,
19576
- openSync as openSync3,
19577
- closeSync as closeSync3,
19592
+ openSync as openSync4,
19593
+ closeSync as closeSync4,
19578
19594
  readFileSync as readFileSync7,
19579
- renameSync as renameSync2,
19595
+ renameSync as renameSync3,
19580
19596
  statSync as statSync4,
19581
19597
  symlinkSync,
19582
19598
  unlinkSync as unlinkSync3
@@ -19642,7 +19658,7 @@ function runMigration(home2, opts) {
19642
19658
  copyFileSync2(oldPath, tempNew);
19643
19659
  chmodSync(tempNew, 384);
19644
19660
  fsyncFile(tempNew);
19645
- renameSync2(tempNew, newPath);
19661
+ renameSync3(tempNew, newPath);
19646
19662
  fsyncDir(parent);
19647
19663
  atomicReplaceWithSymlink(oldPath, "vault/vault.enc");
19648
19664
  fsyncDir(switchroomRoot);
@@ -19676,22 +19692,22 @@ function atomicReplaceWithSymlink(target, linkTarget) {
19676
19692
  } catch {}
19677
19693
  }
19678
19694
  symlinkSync(linkTarget, tmp);
19679
- renameSync2(tmp, target);
19695
+ renameSync3(tmp, target);
19680
19696
  }
19681
19697
  function fsyncFile(path) {
19682
- const fd = openSync3(path, "r+");
19698
+ const fd = openSync4(path, "r+");
19683
19699
  try {
19684
- fsyncSync3(fd);
19700
+ fsyncSync4(fd);
19685
19701
  } finally {
19686
- closeSync3(fd);
19702
+ closeSync4(fd);
19687
19703
  }
19688
19704
  }
19689
19705
  function fsyncDir(path) {
19690
- const fd = openSync3(path, "r");
19706
+ const fd = openSync4(path, "r");
19691
19707
  try {
19692
- fsyncSync3(fd);
19708
+ fsyncSync4(fd);
19693
19709
  } finally {
19694
- closeSync3(fd);
19710
+ closeSync4(fd);
19695
19711
  }
19696
19712
  }
19697
19713
 
@@ -19702,15 +19718,15 @@ init_loader();
19702
19718
  import { createHmac, randomBytes as randomBytes2, createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2 } from "node:crypto";
19703
19719
  import {
19704
19720
  chmodSync as chmodSync2,
19705
- closeSync as closeSync4,
19721
+ closeSync as closeSync5,
19706
19722
  existsSync as existsSync8,
19707
- fsyncSync as fsyncSync4,
19723
+ fsyncSync as fsyncSync5,
19708
19724
  mkdirSync as mkdirSync4,
19709
- openSync as openSync4,
19725
+ openSync as openSync5,
19710
19726
  readFileSync as readFileSync8,
19711
- renameSync as renameSync3,
19727
+ renameSync as renameSync4,
19712
19728
  unlinkSync as unlinkSync4,
19713
- writeSync as writeSync3
19729
+ writeSync as writeSync4
19714
19730
  } from "node:fs";
19715
19731
  var FORMAT_VERSION = 1;
19716
19732
  var SALT_LEN = 16;
@@ -19803,7 +19819,7 @@ import * as path from "node:path";
19803
19819
 
19804
19820
  // src/util/audit-hashchain.ts
19805
19821
  import { createHash as createHash4 } from "node:crypto";
19806
- import { openSync as openSync5, readSync, fstatSync as fstatSync2, closeSync as closeSync5 } from "node:fs";
19822
+ import { openSync as openSync6, readSync, fstatSync as fstatSync2, closeSync as closeSync6 } from "node:fs";
19807
19823
  var CHAIN_GENESIS = "GENESIS";
19808
19824
  var SEP = "\x00";
19809
19825
  function rowHash(prev, seq, rowJsonNoChain) {
@@ -19824,7 +19840,7 @@ function chainRow(state, entry) {
19824
19840
  function readTail(path, tailBytes = 64 * 1024) {
19825
19841
  let fd;
19826
19842
  try {
19827
- fd = openSync5(path, "r");
19843
+ fd = openSync6(path, "r");
19828
19844
  } catch {
19829
19845
  return null;
19830
19846
  }
@@ -19840,7 +19856,7 @@ function readTail(path, tailBytes = 64 * 1024) {
19840
19856
  return null;
19841
19857
  } finally {
19842
19858
  try {
19843
- closeSync5(fd);
19859
+ closeSync6(fd);
19844
19860
  } catch {}
19845
19861
  }
19846
19862
  }
@@ -22196,6 +22212,9 @@ function lookupDecision(db, query, opts = {}, now = Date.now()) {
22196
22212
  ORDER BY granted_at DESC LIMIT 1`).get(query.agent_unit, query.scope, query.action);
22197
22213
  if (!row)
22198
22214
  return { state: "no_decision" };
22215
+ return evaluateDecisionRow(db, row, currentCanonical, opts, now);
22216
+ }
22217
+ function evaluateDecisionRow(db, row, currentCanonical, opts, now) {
22199
22218
  const decision = rowToDecision(row);
22200
22219
  if (decision.decision === "deny_perm") {
22201
22220
  return { state: "denied", decision };
@@ -23203,6 +23222,41 @@ class VaultBroker {
23203
23222
  if (req.token !== undefined && req.token !== "") {
23204
23223
  const v = await validateGrantForWrite(this.grantsDb, req.token, req.key);
23205
23224
  if (v.ok) {
23225
+ const writeGrantAgentSlug = v.grant.agent_slug;
23226
+ if (agentName !== null && writeGrantAgentSlug !== agentName) {
23227
+ this.auditLogger.write({
23228
+ ts: new Date().toISOString(),
23229
+ op: "put",
23230
+ key: req.key,
23231
+ caller: auditCaller,
23232
+ pid: auditPid,
23233
+ cgroup: auditCgroup,
23234
+ result: "denied:grant-identity-mismatch",
23235
+ method: "grant",
23236
+ grant_id: v.grant.id
23237
+ });
23238
+ socket.write(encodeResponse(errorResponse("DENIED", "grant-identity-mismatch")));
23239
+ return;
23240
+ }
23241
+ const existingForScope = this.secrets[req.key];
23242
+ if (existingForScope !== undefined) {
23243
+ const writeScope = checkEntryScope(existingForScope.scope, writeGrantAgentSlug);
23244
+ if (!writeScope.allow) {
23245
+ this.auditLogger.write({
23246
+ ts: new Date().toISOString(),
23247
+ op: "put",
23248
+ key: req.key,
23249
+ caller: auditCaller,
23250
+ pid: auditPid,
23251
+ cgroup: auditCgroup,
23252
+ result: `denied:${writeScope.reason}`,
23253
+ method: "grant",
23254
+ grant_id: v.grant.id
23255
+ });
23256
+ socket.write(encodeResponse(errorResponse("DENIED", writeScope.reason)));
23257
+ return;
23258
+ }
23259
+ }
23206
23260
  writeGrantId = v.grant.id;
23207
23261
  } else if (v.reason === "grant-expired" || v.reason === "grant-revoked") {
23208
23262
  this.auditLogger.write({
@@ -23550,7 +23604,7 @@ class VaultBroker {
23550
23604
  const tokenPath = path3.join(tokenDir, ".vault-token");
23551
23605
  const tmpPath = `${tokenPath}.tmp.${process.pid}`;
23552
23606
  writeFileSync3(tmpPath, mintResult.token, { mode: 384 });
23553
- renameSync6(tmpPath, tokenPath);
23607
+ renameSync7(tmpPath, tokenPath);
23554
23608
  try {
23555
23609
  const uid = allocateAgentUid(agent);
23556
23610
  chownSync(tokenPath, uid, uid);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "switchroom",
3
3
  "//version": "NOT the release version — source of truth is the git tag, resolved by scripts/build.mjs:resolveVersion() (see CLAUDE.md > Standard release process). This field is stale by design and only the Layer-4 dev/non-tag fallback for build.mjs + src/cli/resolve-version.ts; do NOT bump it expecting a release to pick it up. npm-pack tarball naming needs a real version — do that as an UNCOMMITTED pack-time bump (see release step 6), never a committed one.",
4
- "version": "0.18.12",
4
+ "version": "0.18.13",
5
5
  "description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
6
6
  "type": "module",
7
7
  "bin": {
@@ -23035,6 +23035,33 @@ function getNestedObj(obj, key) {
23035
23035
  var DEFAULT_OPERATOR_EVENT_COOLDOWN_MS = 5 * 60000;
23036
23036
  var cooldownMap = new Map;
23037
23037
 
23038
+ // text-voice-scrub.ts
23039
+ var NULL = "\x00";
23040
+ var FENCE_PH = `${NULL}VS_FENCE`;
23041
+ var INLINE_PH = `${NULL}VS_INLINE`;
23042
+ var HTML_CODE_PH = `${NULL}VS_HTMLCODE`;
23043
+ var HTML_PRE_PH = `${NULL}VS_HTMLPRE`;
23044
+ var URL_PH = `${NULL}VS_URL`;
23045
+
23046
+ // model-unavailable.ts
23047
+ var transientUpstreamSignals = [
23048
+ "not your usage limit",
23049
+ "not your account",
23050
+ "not your account's",
23051
+ "temporarily limiting requests",
23052
+ "temporarily rate",
23053
+ "server is temporarily",
23054
+ "would exceed your account\u2019s rate limit",
23055
+ "would exceed your account's rate limit"
23056
+ ];
23057
+ function isTransientUpstreamSignal(text) {
23058
+ if (typeof text !== "string" || text.length === 0)
23059
+ return false;
23060
+ const sample = text.length > 16384 ? text.slice(0, 16384) : text;
23061
+ const lower = sample.toLowerCase();
23062
+ return transientUpstreamSignals.some((s) => lower.includes(s));
23063
+ }
23064
+
23038
23065
  // tool-label-sidecar.ts
23039
23066
  import { existsSync as existsSync2, readFileSync, statSync as statSync2 } from "node:fs";
23040
23067
  import { join as join2 } from "node:path";
@@ -23453,7 +23480,8 @@ function detectErrorInTranscriptLine(line) {
23453
23480
  const status = typeof obj.apiErrorStatus === "number" ? obj.apiErrorStatus : null;
23454
23481
  const errStr = typeof obj.error === "string" ? obj.error : "";
23455
23482
  const text = extractAssistantText(obj);
23456
- const kind2 = status === 429 ? "quota-exhausted" : classifyClaudeError({ type: errStr, status, message: text });
23483
+ const kind2 = status === 429 ? isTransientUpstreamSignal(`${text}
23484
+ ${errStr}`) ? "rate-limited" : "quota-exhausted" : classifyClaudeError({ type: errStr, status, message: text });
23457
23485
  return {
23458
23486
  kind: kind2,
23459
23487
  raw: obj,
@@ -23547,6 +23575,7 @@ function startSessionTail(config2) {
23547
23575
  const onOperatorEvent = config2.onOperatorEvent;
23548
23576
  log?.(`session-tail: projectsDir=${projectsDir}`);
23549
23577
  const sidecars = new Map;
23578
+ const createSidecar = config2.createSidecar ?? createToolLabelSidecar;
23550
23579
  const stateDirForSidecar = process.env.TELEGRAM_STATE_DIR ?? null;
23551
23580
  function sessionIdForFile(file) {
23552
23581
  if (!file)
@@ -23561,7 +23590,7 @@ function startSessionTail(config2) {
23561
23590
  if (existing)
23562
23591
  return existing;
23563
23592
  try {
23564
- const s = createToolLabelSidecar({ stateDir: stateDirForSidecar, sessionId });
23593
+ const s = createSidecar({ stateDir: stateDirForSidecar, sessionId });
23565
23594
  sidecars.set(sessionId, s);
23566
23595
  s.onLabel((toolUseId, label, toolName) => {
23567
23596
  rawOnEvent({ kind: "tool_label", toolUseId, label, toolName });
@@ -23572,6 +23601,17 @@ function startSessionTail(config2) {
23572
23601
  return null;
23573
23602
  }
23574
23603
  }
23604
+ function stopSidecar(sessionId) {
23605
+ if (!sessionId)
23606
+ return;
23607
+ const s = sidecars.get(sessionId);
23608
+ if (!s)
23609
+ return;
23610
+ try {
23611
+ s.stop();
23612
+ } catch {}
23613
+ sidecars.delete(sessionId);
23614
+ }
23575
23615
  function decorate(ev, sessionId) {
23576
23616
  if (!sessionId)
23577
23617
  return ev;
@@ -23664,6 +23704,11 @@ function startSessionTail(config2) {
23664
23704
  } catch {}
23665
23705
  watcher = null;
23666
23706
  }
23707
+ const rotatedAwaySid = sessionIdForFile(currentFile);
23708
+ const nextSid = sessionIdForFile(file);
23709
+ if (rotatedAwaySid != null && rotatedAwaySid !== nextSid) {
23710
+ stopSidecar(rotatedAwaySid);
23711
+ }
23667
23712
  currentFile = file;
23668
23713
  const prior = fileCursors.get(file);
23669
23714
  if (prior != null) {
@@ -23696,7 +23741,7 @@ function startSessionTail(config2) {
23696
23741
  const multiAgent = isMultiAgentEnabled();
23697
23742
  const CAP_TOOL_USE_THRESHOLD = 30;
23698
23743
  const subTails = new Map;
23699
- const IDLE_FSWATCH_TTL_MS = 5 * 60 * 1000;
23744
+ const IDLE_FSWATCH_TTL_MS = Math.max(1000, config2.subTailIdleReapMs ?? 5 * 60 * 1000);
23700
23745
  function readSub(t) {
23701
23746
  if (stopped)
23702
23747
  return;
@@ -23803,6 +23848,7 @@ function startSessionTail(config2) {
23803
23848
  } catch {}
23804
23849
  t.watcher = null;
23805
23850
  }
23851
+ stopSidecar(sessionIdForFile(t.file));
23806
23852
  subTails.delete(file);
23807
23853
  log?.(`session-tail: reaped idle sub ${t.agentId} (${file})`);
23808
23854
  }