switchroom 0.20.8 → 0.20.10

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 (43) hide show
  1. package/bin/handoff-briefing.sh +57 -5
  2. package/bin/working-state-reload-hook.sh +262 -0
  3. package/dist/agent-scheduler/index.js +16 -13
  4. package/dist/auth-broker/index.js +70 -30
  5. package/dist/cli/autoaccept-poll.js +5 -3
  6. package/dist/cli/drive-write-pretool.mjs +5 -3
  7. package/dist/cli/ms-365-write-pretool.mjs +5 -3
  8. package/dist/cli/notion-write-pretool.mjs +6 -6
  9. package/dist/cli/switchroom.js +42 -13
  10. package/dist/host-control/main.js +7 -7
  11. package/dist/vault/approvals/kernel-server.js +6 -6
  12. package/dist/vault/broker/server.js +6 -6
  13. package/package.json +1 -1
  14. package/profiles/_base/start.sh.hbs +49 -0
  15. package/profiles/default/CLAUDE.md.hbs +12 -13
  16. package/telegram-plugin/ask-user.ts +6 -7
  17. package/telegram-plugin/dist/gateway/gateway.js +192 -66
  18. package/telegram-plugin/gateway/auth-broker-client.ts +1 -1
  19. package/telegram-plugin/gateway/auth-command.ts +4 -2
  20. package/telegram-plugin/gateway/checklist-fallback.ts +8 -1
  21. package/telegram-plugin/gateway/gateway.ts +8 -4
  22. package/telegram-plugin/gateway/outbound-send-path.ts +9 -1
  23. package/telegram-plugin/gateway/subagent-handback-inbound-builder.ts +21 -1
  24. package/telegram-plugin/gateway/subagent-handback-marker.ts +94 -0
  25. package/telegram-plugin/gateway/throttle-tier-wiring.ts +93 -18
  26. package/telegram-plugin/render/emphasis-guard.ts +92 -12
  27. package/telegram-plugin/render/line-start-guard.ts +27 -2
  28. package/telegram-plugin/sticker-aliases.ts +12 -14
  29. package/telegram-plugin/tests/ask-user.test.ts +15 -0
  30. package/telegram-plugin/tests/checklist-fallback.test.ts +21 -0
  31. package/telegram-plugin/tests/handback-tasknotif-dedup.test.ts +248 -0
  32. package/telegram-plugin/tests/render/emphasis-guard.test.ts +105 -6
  33. package/telegram-plugin/tests/render/heading-guard-blockquote-glued-hash.test.ts +123 -36
  34. package/telegram-plugin/tests/reply-quote-wire.test.ts +47 -0
  35. package/telegram-plugin/tests/sticker-aliases.test.ts +43 -0
  36. package/telegram-plugin/tests/throttle-tier-probe-only.test.ts +216 -0
  37. package/telegram-plugin/tests/throttle-tier-route-429-wiring.test.ts +92 -0
  38. package/telegram-plugin/tests/throttle-tier-route-429.test.ts +71 -0
  39. package/telegram-plugin/throttle-tier.ts +59 -0
  40. package/vendor/hindsight-memory/CHANGELOG.md +31 -0
  41. package/vendor/hindsight-memory/hooks/hooks.json +2 -1
  42. package/vendor/hindsight-memory/scripts/session_start.py +35 -8
  43. package/vendor/hindsight-memory/scripts/tests/test_session_start_durability.py +107 -0
@@ -12053,7 +12053,7 @@ function deriveOverlayTitle(raw, fileName) {
12053
12053
  return;
12054
12054
  return base.length > 0 ? base : undefined;
12055
12055
  }
12056
- function readOverlayFile(agentName, file, agentCfg, warnings) {
12056
+ function readOverlayFile(agentName, file, agentCfg, warnings, source) {
12057
12057
  try {
12058
12058
  return readFileSync(file, "utf-8");
12059
12059
  } catch (err) {
@@ -12066,7 +12066,7 @@ function readOverlayFile(agentName, file, agentCfg, warnings) {
12066
12066
  reason: `read error: ${err.message}`,
12067
12067
  code: code ?? "EUNKNOWN"
12068
12068
  };
12069
- recordReadFailure(agentCfg, { file, code: w.code });
12069
+ recordReadFailure(agentCfg, { file, code: w.code, source });
12070
12070
  warnings.push(w);
12071
12071
  console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${file}': ${w.reason}`);
12072
12072
  return;
@@ -12130,14 +12130,14 @@ function applyAgentOverlays(config) {
12130
12130
  reason: `read error: cannot list overlay directory (${code})`,
12131
12131
  code
12132
12132
  };
12133
- recordReadFailure(agentCfg, { file: scheduleDir, code });
12133
+ recordReadFailure(agentCfg, { file: scheduleDir, code, source: "schedule" });
12134
12134
  warnings.push(w);
12135
12135
  console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${scheduleDir}': ${w.reason}`);
12136
12136
  });
12137
12137
  if (files.length > 0) {
12138
12138
  const merged = [...agentCfg.schedule ?? []];
12139
12139
  for (const file of files) {
12140
- const raw = readOverlayFile(agentName, file, agentCfg, warnings);
12140
+ const raw = readOverlayFile(agentName, file, agentCfg, warnings, "schedule");
12141
12141
  if (raw === undefined)
12142
12142
  continue;
12143
12143
  try {
@@ -12182,7 +12182,7 @@ function applyAgentOverlays(config) {
12182
12182
  reason: `read error: cannot list overlay directory (${code})`,
12183
12183
  code
12184
12184
  };
12185
- recordReadFailure(agentCfg, { file: skillsDir, code });
12185
+ recordReadFailure(agentCfg, { file: skillsDir, code, source: "skills" });
12186
12186
  warnings.push(w);
12187
12187
  console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${skillsDir}': ${w.reason}`);
12188
12188
  });
@@ -12190,7 +12190,7 @@ function applyAgentOverlays(config) {
12190
12190
  const merged = [...agentCfg.skills ?? []];
12191
12191
  const seen = new Set(merged);
12192
12192
  for (const file of skillFiles) {
12193
- const raw = readOverlayFile(agentName, file, agentCfg, warnings);
12193
+ const raw = readOverlayFile(agentName, file, agentCfg, warnings, "skills");
12194
12194
  if (raw === undefined)
12195
12195
  continue;
12196
12196
  try {
@@ -19820,6 +19820,23 @@ function extractApiErrorMessage(bodyText) {
19820
19820
  } catch {}
19821
19821
  return bodyText.trim().slice(0, 500);
19822
19822
  }
19823
+ function extractApiErrorCode(bodyText) {
19824
+ if (!bodyText || bodyText.trim().length === 0)
19825
+ return null;
19826
+ try {
19827
+ const parsed = JSON.parse(bodyText);
19828
+ const code = parsed?.error?.details?.error_code;
19829
+ if (typeof code === "string" && code.trim().length > 0)
19830
+ return code.trim();
19831
+ } catch {}
19832
+ return null;
19833
+ }
19834
+ var ENTITLEMENT_BLOCKED_ERROR_CODES = new Set([
19835
+ "oauth_not_allowed_for_organization"
19836
+ ]);
19837
+ function isEntitlementBlockedErrorCode(code) {
19838
+ return code != null && ENTITLEMENT_BLOCKED_ERROR_CODES.has(code);
19839
+ }
19823
19840
  function isEntitlementDisabledMessage(message) {
19824
19841
  if (!message)
19825
19842
  return false;
@@ -19877,7 +19894,8 @@ async function fetchQuota(opts) {
19877
19894
  } catch {}
19878
19895
  clearTimeout(timeout);
19879
19896
  const apiErrorMessage = extractApiErrorMessage(bodyText);
19880
- const entitlement = resp.status === 403 && isEntitlementDisabledMessage(apiErrorMessage);
19897
+ const apiErrorCode = extractApiErrorCode(bodyText);
19898
+ const entitlement = resp.status === 403 && (isEntitlementBlockedErrorCode(apiErrorCode) || isEntitlementDisabledMessage(apiErrorMessage));
19881
19899
  return {
19882
19900
  ok: false,
19883
19901
  reason: `HTTP ${resp.status} from Anthropic (${parsed.reason})`,
@@ -21286,7 +21304,8 @@ var MarkThrottledRequestSchema = exports_external.object({
21286
21304
  v: exports_external.literal(PROTOCOL_VERSION),
21287
21305
  op: exports_external.literal("mark-throttled"),
21288
21306
  id: exports_external.string().min(1),
21289
- until: exports_external.number().int().positive()
21307
+ until: exports_external.number().int().positive(),
21308
+ probeOnly: exports_external.boolean().optional()
21290
21309
  });
21291
21310
  var RefreshAccountRequestSchema = exports_external.object({
21292
21311
  v: exports_external.literal(PROTOCOL_VERSION),
@@ -21568,8 +21587,8 @@ var AUTH_BROKER_ROOT2 = "/run/switchroom/auth-broker";
21568
21587
  var REFRESH_TICK_INTERVAL_MS = 60 * 1000;
21569
21588
  var MARK_EXHAUSTED_DEFAULT_MS = 5 * 60 * 60 * 1000;
21570
21589
  var MARK_THROTTLED_MAX_MS = 30 * 60 * 1000;
21571
- var THROTTLE_ESCALATION_HITS = 3;
21572
21590
  var THROTTLE_ESCALATION_WINDOW_MS = 10 * 60 * 1000;
21591
+ var THROTTLE_ESCALATION_PROBE_MIN_INTERVAL_MS = 60 * 1000;
21573
21592
  var MARK_THROTTLED_MIN_INTERVAL_MS = 5 * 1000;
21574
21593
  var MARK_CORROBORATION_PROBE_DELAY_MS = 90 * 1000;
21575
21594
  var AUDIT_ROTATE_BYTES = 10 * 1024 * 1024;
@@ -21663,6 +21682,7 @@ class AuthBroker {
21663
21682
  externalSpendCache = null;
21664
21683
  externalSpendInFlight = null;
21665
21684
  probeInFlight = new Map;
21685
+ lastEscalationProbeAt = {};
21666
21686
  shaIndex = {};
21667
21687
  thresholdViolations = {};
21668
21688
  notificationClaims = {};
@@ -22047,7 +22067,7 @@ class AuthBroker {
22047
22067
  await this.opMarkExhausted(socket, reqId, identity, req.until);
22048
22068
  break;
22049
22069
  case "mark-throttled":
22050
- await this.opMarkThrottled(socket, reqId, identity, req.until);
22070
+ await this.opMarkThrottled(socket, reqId, identity, req.until, req.probeOnly);
22051
22071
  break;
22052
22072
  case "refresh-account": {
22053
22073
  const provider = req.provider ?? "anthropic";
@@ -22879,7 +22899,7 @@ class AuthBroker {
22879
22899
  this.audit({ op: "mark-exhausted", identity, account, accountKind: "claude", ok: true });
22880
22900
  socket.write(encodeSuccess(id, { account, rolled, rolledTo }));
22881
22901
  }
22882
- async opMarkThrottled(socket, id, identity, until) {
22902
+ async opMarkThrottled(socket, id, identity, until, probeOnly = false) {
22883
22903
  const account = this.callerAccount(identity);
22884
22904
  if (!account) {
22885
22905
  this.audit({ op: "mark-throttled", identity, accountKind: "claude", ok: false, error: "no-active-account" });
@@ -22887,6 +22907,17 @@ class AuthBroker {
22887
22907
  return;
22888
22908
  }
22889
22909
  const now = this.now();
22910
+ if (probeOnly) {
22911
+ const { escalated: escalated2, rolledTo: rolledTo2 } = await this.maybeEscalateThrottle(account, identity, now);
22912
+ this.audit({ op: "mark-throttled", identity, account, accountKind: "claude", ok: true });
22913
+ socket.write(encodeSuccess(id, {
22914
+ account,
22915
+ throttled_until: this.quota[account]?.throttled_until ?? 0,
22916
+ escalated: escalated2,
22917
+ rolledTo: escalated2 ? rolledTo2 : null
22918
+ }));
22919
+ return;
22920
+ }
22890
22921
  const throttledUntil = Math.min(Math.max(until, now + 1000), now + MARK_THROTTLED_MAX_MS);
22891
22922
  const entry = this.quota[account] ?? {};
22892
22923
  const priorHits = entry.throttle_hits ?? [];
@@ -22908,31 +22939,19 @@ class AuthBroker {
22908
22939
  }
22909
22940
  const hits = priorHits.filter((t) => now - t < THROTTLE_ESCALATION_WINDOW_MS);
22910
22941
  hits.push(now);
22911
- const escalate = hits.length >= THROTTLE_ESCALATION_HITS;
22912
22942
  this.quota[account] = {
22913
22943
  ...entry,
22914
22944
  throttled_until: throttledUntil,
22915
- throttle_hits: escalate ? [] : hits
22945
+ throttle_hits: hits
22916
22946
  };
22917
22947
  this.persistQuota();
22918
22948
  this.audit({ op: "mark-throttled", identity, account, accountKind: "claude", ok: true });
22919
- process.stdout.write(`auth-broker: mark-throttled ${account} until ${new Date(throttledUntil).toISOString()} ` + `(hit ${hits.length}/${THROTTLE_ESCALATION_HITS} in window)
22920
- `);
22921
- let escalated = false;
22922
- let rolledTo = null;
22923
- if (escalate) {
22924
- const probe = await this.probeThrottleEscalation(account);
22925
- if (probe.exhausted) {
22926
- escalated = true;
22927
- process.stdout.write(`auth-broker: throttle-escalation probe corroborates wall on ${account} — mark-exhausted + roll
22928
- `);
22929
- this.audit({ op: "mark-exhausted", identity, account, accountKind: "claude", ok: true, reason: "throttle-escalation" });
22930
- const roll = await this.markExhaustedAndRoll(account, probe.until ?? undefined, identity);
22931
- rolledTo = roll.rolledTo;
22932
- } else {
22933
- process.stdout.write(`auth-broker: throttle-escalation probe on ${account} did NOT corroborate a wall — staying throttled
22949
+ process.stdout.write(`auth-broker: mark-throttled ${account} until ${new Date(throttledUntil).toISOString()} ` + `(hit ${hits.length} in window)
22934
22950
  `);
22935
- }
22951
+ const { escalated, rolledTo } = await this.maybeEscalateThrottle(account, identity, now);
22952
+ if (escalated) {
22953
+ this.quota[account] = { ...this.quota[account], throttle_hits: [] };
22954
+ this.persistQuota();
22936
22955
  }
22937
22956
  socket.write(encodeSuccess(id, {
22938
22957
  account,
@@ -22941,6 +22960,27 @@ class AuthBroker {
22941
22960
  rolledTo: escalated ? rolledTo : null
22942
22961
  }));
22943
22962
  }
22963
+ async maybeEscalateThrottle(account, identity, now) {
22964
+ const lastProbeAt = this.lastEscalationProbeAt[account];
22965
+ const probeAllowed = lastProbeAt === undefined || now - lastProbeAt >= THROTTLE_ESCALATION_PROBE_MIN_INTERVAL_MS;
22966
+ if (!probeAllowed) {
22967
+ process.stdout.write(`auth-broker: throttle-escalation probe on ${account} rate-bounded — skipped
22968
+ `);
22969
+ return { escalated: false, rolledTo: null };
22970
+ }
22971
+ this.lastEscalationProbeAt[account] = now;
22972
+ const probe = await this.probeThrottleEscalation(account);
22973
+ if (!probe.exhausted) {
22974
+ process.stdout.write(`auth-broker: throttle-escalation probe on ${account} did NOT corroborate a wall — staying put
22975
+ `);
22976
+ return { escalated: false, rolledTo: null };
22977
+ }
22978
+ process.stdout.write(`auth-broker: throttle-escalation probe corroborates wall on ${account} — mark-exhausted + roll
22979
+ `);
22980
+ this.audit({ op: "mark-exhausted", identity, account, accountKind: "claude", ok: true, reason: "throttle-escalation" });
22981
+ const roll = await this.markExhaustedAndRoll(account, probe.until ?? undefined, identity);
22982
+ return { escalated: true, rolledTo: roll.rolledTo };
22983
+ }
22944
22984
  async probeThrottleEscalation(account) {
22945
22985
  const creds = readAccountCredentials(account, this.home);
22946
22986
  const token = creds?.claudeAiOauth?.accessToken;
@@ -22948,7 +22988,7 @@ class AuthBroker {
22948
22988
  return { exhausted: false, until: null };
22949
22989
  let result;
22950
22990
  try {
22951
- result = await this.fetchQuotaImpl({ accessToken: token });
22991
+ result = await this.probeQuotaSingleFlight(account, token);
22952
22992
  } catch (err) {
22953
22993
  this.logErr(`throttle-escalation probe ${account}: ${err.message}`);
22954
22994
  return { exhausted: false, until: null };
@@ -4958,7 +4958,8 @@ var MarkThrottledRequestSchema = exports_external.object({
4958
4958
  v: exports_external.literal(PROTOCOL_VERSION),
4959
4959
  op: exports_external.literal("mark-throttled"),
4960
4960
  id: exports_external.string().min(1),
4961
- until: exports_external.number().int().positive()
4961
+ until: exports_external.number().int().positive(),
4962
+ probeOnly: exports_external.boolean().optional()
4962
4963
  });
4963
4964
  var RefreshAccountRequestSchema = exports_external.object({
4964
4965
  v: exports_external.literal(PROTOCOL_VERSION),
@@ -5375,12 +5376,13 @@ class AuthBrokerClient {
5375
5376
  const data = await this.send(req);
5376
5377
  return data;
5377
5378
  }
5378
- async markThrottled(until) {
5379
+ async markThrottled(until, probeOnly = false) {
5379
5380
  const data = await this.send({
5380
5381
  v: PROTOCOL_VERSION,
5381
5382
  id: randomUUID2(),
5382
5383
  op: "mark-throttled",
5383
- until
5384
+ until,
5385
+ ...probeOnly ? { probeOnly: true } : {}
5384
5386
  });
5385
5387
  return data;
5386
5388
  }
@@ -4034,7 +4034,8 @@ var init_protocol = __esm(() => {
4034
4034
  v: exports_external.literal(PROTOCOL_VERSION),
4035
4035
  op: exports_external.literal("mark-throttled"),
4036
4036
  id: exports_external.string().min(1),
4037
- until: exports_external.number().int().positive()
4037
+ until: exports_external.number().int().positive(),
4038
+ probeOnly: exports_external.boolean().optional()
4038
4039
  });
4039
4040
  RefreshAccountRequestSchema = exports_external.object({
4040
4041
  v: exports_external.literal(PROTOCOL_VERSION),
@@ -4430,12 +4431,13 @@ class AuthBrokerClient {
4430
4431
  const data = await this.send(req);
4431
4432
  return data;
4432
4433
  }
4433
- async markThrottled(until) {
4434
+ async markThrottled(until, probeOnly = false) {
4434
4435
  const data = await this.send({
4435
4436
  v: PROTOCOL_VERSION,
4436
4437
  id: randomUUID(),
4437
4438
  op: "mark-throttled",
4438
- until
4439
+ until,
4440
+ ...probeOnly ? { probeOnly: true } : {}
4439
4441
  });
4440
4442
  return data;
4441
4443
  }
@@ -4036,7 +4036,8 @@ var init_protocol = __esm(() => {
4036
4036
  v: exports_external.literal(PROTOCOL_VERSION),
4037
4037
  op: exports_external.literal("mark-throttled"),
4038
4038
  id: exports_external.string().min(1),
4039
- until: exports_external.number().int().positive()
4039
+ until: exports_external.number().int().positive(),
4040
+ probeOnly: exports_external.boolean().optional()
4040
4041
  });
4041
4042
  RefreshAccountRequestSchema = exports_external.object({
4042
4043
  v: exports_external.literal(PROTOCOL_VERSION),
@@ -4432,12 +4433,13 @@ class AuthBrokerClient {
4432
4433
  const data = await this.send(req);
4433
4434
  return data;
4434
4435
  }
4435
- async markThrottled(until) {
4436
+ async markThrottled(until, probeOnly = false) {
4436
4437
  const data = await this.send({
4437
4438
  v: PROTOCOL_VERSION,
4438
4439
  id: randomUUID(),
4439
4440
  op: "mark-throttled",
4440
- until
4441
+ until,
4442
+ ...probeOnly ? { probeOnly: true } : {}
4441
4443
  });
4442
4444
  return data;
4443
4445
  }
@@ -12791,7 +12791,7 @@ function deriveOverlayTitle(raw, fileName) {
12791
12791
  return;
12792
12792
  return base.length > 0 ? base : undefined;
12793
12793
  }
12794
- function readOverlayFile(agentName, file, agentCfg, warnings) {
12794
+ function readOverlayFile(agentName, file, agentCfg, warnings, source) {
12795
12795
  try {
12796
12796
  return readFileSync(file, "utf-8");
12797
12797
  } catch (err) {
@@ -12804,7 +12804,7 @@ function readOverlayFile(agentName, file, agentCfg, warnings) {
12804
12804
  reason: `read error: ${err.message}`,
12805
12805
  code: code ?? "EUNKNOWN"
12806
12806
  };
12807
- recordReadFailure(agentCfg, { file, code: w.code });
12807
+ recordReadFailure(agentCfg, { file, code: w.code, source });
12808
12808
  warnings.push(w);
12809
12809
  console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${file}': ${w.reason}`);
12810
12810
  return;
@@ -12868,14 +12868,14 @@ function applyAgentOverlays(config) {
12868
12868
  reason: `read error: cannot list overlay directory (${code})`,
12869
12869
  code
12870
12870
  };
12871
- recordReadFailure(agentCfg, { file: scheduleDir, code });
12871
+ recordReadFailure(agentCfg, { file: scheduleDir, code, source: "schedule" });
12872
12872
  warnings.push(w);
12873
12873
  console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${scheduleDir}': ${w.reason}`);
12874
12874
  });
12875
12875
  if (files.length > 0) {
12876
12876
  const merged = [...agentCfg.schedule ?? []];
12877
12877
  for (const file of files) {
12878
- const raw = readOverlayFile(agentName, file, agentCfg, warnings);
12878
+ const raw = readOverlayFile(agentName, file, agentCfg, warnings, "schedule");
12879
12879
  if (raw === undefined)
12880
12880
  continue;
12881
12881
  try {
@@ -12920,7 +12920,7 @@ function applyAgentOverlays(config) {
12920
12920
  reason: `read error: cannot list overlay directory (${code})`,
12921
12921
  code
12922
12922
  };
12923
- recordReadFailure(agentCfg, { file: skillsDir, code });
12923
+ recordReadFailure(agentCfg, { file: skillsDir, code, source: "skills" });
12924
12924
  warnings.push(w);
12925
12925
  console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${skillsDir}': ${w.reason}`);
12926
12926
  });
@@ -12928,7 +12928,7 @@ function applyAgentOverlays(config) {
12928
12928
  const merged = [...agentCfg.skills ?? []];
12929
12929
  const seen = new Set(merged);
12930
12930
  for (const file of skillFiles) {
12931
- const raw = readOverlayFile(agentName, file, agentCfg, warnings);
12931
+ const raw = readOverlayFile(agentName, file, agentCfg, warnings, "skills");
12932
12932
  if (raw === undefined)
12933
12933
  continue;
12934
12934
  try {
@@ -2120,7 +2120,7 @@ var init_esm = __esm(() => {
2120
2120
  });
2121
2121
 
2122
2122
  // src/build-info.ts
2123
- var VERSION = "0.20.8", COMMIT_SHA = "a6efc10f";
2123
+ var VERSION = "0.20.10", COMMIT_SHA = "ebb54738";
2124
2124
 
2125
2125
  // src/cli/resolve-version.ts
2126
2126
  import { existsSync, readFileSync } from "node:fs";
@@ -14664,7 +14664,7 @@ function deriveOverlayTitle(raw, fileName) {
14664
14664
  return;
14665
14665
  return base.length > 0 ? base : undefined;
14666
14666
  }
14667
- function readOverlayFile(agentName, file, agentCfg, warnings) {
14667
+ function readOverlayFile(agentName, file, agentCfg, warnings, source) {
14668
14668
  try {
14669
14669
  return readFileSync2(file, "utf-8");
14670
14670
  } catch (err) {
@@ -14677,7 +14677,7 @@ function readOverlayFile(agentName, file, agentCfg, warnings) {
14677
14677
  reason: `read error: ${err.message}`,
14678
14678
  code: code ?? "EUNKNOWN"
14679
14679
  };
14680
- recordReadFailure(agentCfg, { file, code: w.code });
14680
+ recordReadFailure(agentCfg, { file, code: w.code, source });
14681
14681
  warnings.push(w);
14682
14682
  console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${file}': ${w.reason}`);
14683
14683
  return;
@@ -14741,14 +14741,14 @@ function applyAgentOverlays(config) {
14741
14741
  reason: `read error: cannot list overlay directory (${code})`,
14742
14742
  code
14743
14743
  };
14744
- recordReadFailure(agentCfg, { file: scheduleDir, code });
14744
+ recordReadFailure(agentCfg, { file: scheduleDir, code, source: "schedule" });
14745
14745
  warnings.push(w);
14746
14746
  console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${scheduleDir}': ${w.reason}`);
14747
14747
  });
14748
14748
  if (files.length > 0) {
14749
14749
  const merged = [...agentCfg.schedule ?? []];
14750
14750
  for (const file of files) {
14751
- const raw = readOverlayFile(agentName, file, agentCfg, warnings);
14751
+ const raw = readOverlayFile(agentName, file, agentCfg, warnings, "schedule");
14752
14752
  if (raw === undefined)
14753
14753
  continue;
14754
14754
  try {
@@ -14793,7 +14793,7 @@ function applyAgentOverlays(config) {
14793
14793
  reason: `read error: cannot list overlay directory (${code})`,
14794
14794
  code
14795
14795
  };
14796
- recordReadFailure(agentCfg, { file: skillsDir, code });
14796
+ recordReadFailure(agentCfg, { file: skillsDir, code, source: "skills" });
14797
14797
  warnings.push(w);
14798
14798
  console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${skillsDir}': ${w.reason}`);
14799
14799
  });
@@ -14801,7 +14801,7 @@ function applyAgentOverlays(config) {
14801
14801
  const merged = [...agentCfg.skills ?? []];
14802
14802
  const seen = new Set(merged);
14803
14803
  for (const file of skillFiles) {
14804
- const raw = readOverlayFile(agentName, file, agentCfg, warnings);
14804
+ const raw = readOverlayFile(agentName, file, agentCfg, warnings, "skills");
14805
14805
  if (raw === undefined)
14806
14806
  continue;
14807
14807
  try {
@@ -18815,7 +18815,9 @@ function hindsightContainerEnvPairs(opts) {
18815
18815
  ...hindsightLlmBudgetEnv(llm),
18816
18816
  ["HINDSIGHT_API_WORKER_ID", HINDSIGHT_DEFAULT_WORKER_ID],
18817
18817
  ...hindsightPerfEnvPairs(llm, litellm, gpu, perf),
18818
- ...hindsightPgEnvPairs(perf)
18818
+ ...hindsightPgEnvPairs(perf),
18819
+ ["SWITCHROOM_HINDSIGHT_REQUEUE_DEAD_LETTERS", HINDSIGHT_DEFAULT_REQUEUE_DEAD_LETTERS],
18820
+ ["SWITCHROOM_HINDSIGHT_REQUEUE_MAX", HINDSIGHT_DEFAULT_REQUEUE_MAX]
18819
18821
  ];
18820
18822
  if (llmProvider === "claude-code") {
18821
18823
  pairs.push(["ANTHROPIC_MODEL", llmModel]);
@@ -19086,6 +19088,8 @@ function generateHindsightComposeSnippet(llm, mirrorDir, litellm, gpu, perf, cpA
19086
19088
  ` - HINDSIGHT_API_WORKER_ID=${HINDSIGHT_DEFAULT_WORKER_ID}`,
19087
19089
  ...hindsightPerfEnvPairs(llm, litellm, gpu, perf).map(([k, v]) => ` - ${k}=${v}`),
19088
19090
  ...hindsightPgEnvPairs(perf).map(([k, v]) => ` - ${k}=${v}`),
19091
+ ` - SWITCHROOM_HINDSIGHT_REQUEUE_DEAD_LETTERS=${HINDSIGHT_DEFAULT_REQUEUE_DEAD_LETTERS}`,
19092
+ ` - SWITCHROOM_HINDSIGHT_REQUEUE_MAX=${HINDSIGHT_DEFAULT_REQUEUE_MAX}`,
19089
19093
  ` mem_limit: ${HINDSIGHT_DEFAULT_MEM_LIMIT}`,
19090
19094
  ` mem_reservation: ${HINDSIGHT_DEFAULT_MEM_RESERVATION}`,
19091
19095
  ` pids_limit: ${HINDSIGHT_DEFAULT_PIDS_LIMIT}`,
@@ -19184,7 +19188,7 @@ async function ensureHindsightConsumer(configPath, account, uid = HINDSIGHT_DEFA
19184
19188
  atomicWriteFileSync3(configPath, tail, mode);
19185
19189
  return { added: true, reason: "added" };
19186
19190
  }
19187
- var HINDSIGHT_DEFAULT_API_PORT = 18888, HINDSIGHT_DEFAULT_UI_PORT = 9999, HINDSIGHT_DEFAULT_MCP_URL, HINDSIGHT_DEFAULT_API_BASE_URL, HINDSIGHT_CONSUMER_NAME = "hindsight", HINDSIGHT_DEFAULT_WORKER_ID = "switchroom-hindsight", HINDSIGHT_DATA_VOLUME = "switchroom-hindsight-data", HINDSIGHT_DEFAULT_UID = 11000, HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight", HINDSIGHT_IMAGE, HINDSIGHT_DEFAULT_MODEL = "claude-sonnet-5", HINDSIGHT_DEFAULT_LITELLM_MODEL = "openrouter/google/gemini-3.1-flash-lite", HINDSIGHT_DEFAULT_MCP_STATELESS = true, HINDSIGHT_BROKER_SOCK_VOLUME, HINDSIGHT_CREDS_MIRROR_VOLUME, HINDSIGHT_CRED_DIR = "/run/claude-creds", HINDSIGHT_RETAIN_CHUNK_SIZE = 3000, HINDSIGHT_DEFAULT_RETAIN_MAX_COMPLETION_TOKENS = 16384, HINDSIGHT_RETAIN_MIN_TOKENS_PER_SECOND = 80.6, HINDSIGHT_RETAIN_CLIENT_DEADLINE_S, HINDSIGHT_DEFAULT_MEM_LIMIT = "16g", HINDSIGHT_DEFAULT_MEM_RESERVATION = "4g", HINDSIGHT_DEFAULT_PIDS_LIMIT = 1000, HINDSIGHT_DEFAULT_SHM_SIZE = "2g", HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)', HINDSIGHT_HEALTHCHECK_CMD, DOCKER_PROBE_TIMEOUT_MS, defaultDockerProbe = (args) => {
19191
+ var HINDSIGHT_DEFAULT_API_PORT = 18888, HINDSIGHT_DEFAULT_UI_PORT = 9999, HINDSIGHT_DEFAULT_MCP_URL, HINDSIGHT_DEFAULT_API_BASE_URL, HINDSIGHT_CONSUMER_NAME = "hindsight", HINDSIGHT_DEFAULT_WORKER_ID = "switchroom-hindsight", HINDSIGHT_DATA_VOLUME = "switchroom-hindsight-data", HINDSIGHT_DEFAULT_UID = 11000, HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight", HINDSIGHT_IMAGE, HINDSIGHT_DEFAULT_MODEL = "claude-sonnet-5", HINDSIGHT_DEFAULT_LITELLM_MODEL = "openrouter/google/gemini-3.1-flash-lite", HINDSIGHT_DEFAULT_MCP_STATELESS = true, HINDSIGHT_DEFAULT_REQUEUE_DEAD_LETTERS = "1", HINDSIGHT_DEFAULT_REQUEUE_MAX = "25", HINDSIGHT_BROKER_SOCK_VOLUME, HINDSIGHT_CREDS_MIRROR_VOLUME, HINDSIGHT_CRED_DIR = "/run/claude-creds", HINDSIGHT_RETAIN_CHUNK_SIZE = 3000, HINDSIGHT_DEFAULT_RETAIN_MAX_COMPLETION_TOKENS = 16384, HINDSIGHT_RETAIN_MIN_TOKENS_PER_SECOND = 80.6, HINDSIGHT_RETAIN_CLIENT_DEADLINE_S, HINDSIGHT_DEFAULT_MEM_LIMIT = "16g", HINDSIGHT_DEFAULT_MEM_RESERVATION = "4g", HINDSIGHT_DEFAULT_PIDS_LIMIT = 1000, HINDSIGHT_DEFAULT_SHM_SIZE = "2g", HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)', HINDSIGHT_HEALTHCHECK_CMD, DOCKER_PROBE_TIMEOUT_MS, defaultDockerProbe = (args) => {
19188
19192
  try {
19189
19193
  return execFileSync2("docker", args, {
19190
19194
  stdio: "pipe",
@@ -31105,6 +31109,18 @@ function buildSettingsHooksBlock(p) {
31105
31109
  ]
31106
31110
  }
31107
31111
  ];
31112
+ const switchroomSessionStart = [
31113
+ {
31114
+ matcher: "compact",
31115
+ hooks: [
31116
+ {
31117
+ type: "command",
31118
+ command: wrap("hook:working-state-reload", `bash "${join12(DOCKER_BIN_PATH, "working-state-reload-hook.sh")}"`),
31119
+ timeout: 8
31120
+ }
31121
+ ]
31122
+ }
31123
+ ];
31108
31124
  if (userHooks) {
31109
31125
  return {
31110
31126
  ...userHooks,
@@ -31112,6 +31128,10 @@ function buildSettingsHooksBlock(p) {
31112
31128
  ...userHooks.UserPromptSubmit ?? [],
31113
31129
  ...switchroomUserPromptSubmit
31114
31130
  ],
31131
+ SessionStart: [
31132
+ ...userHooks.SessionStart ?? [],
31133
+ ...switchroomSessionStart
31134
+ ],
31115
31135
  ...switchroomPreToolUse.length > 0 ? {
31116
31136
  PreToolUse: [
31117
31137
  ...userHooks.PreToolUse ?? [],
@@ -31134,6 +31154,7 @@ function buildSettingsHooksBlock(p) {
31134
31154
  }
31135
31155
  return {
31136
31156
  UserPromptSubmit: switchroomUserPromptSubmit,
31157
+ SessionStart: switchroomSessionStart,
31137
31158
  ...switchroomPreToolUse.length > 0 ? { PreToolUse: switchroomPreToolUse } : {},
31138
31159
  ...switchroomPostToolUse.length > 0 ? { PostToolUse: switchroomPostToolUse } : {},
31139
31160
  ...switchroomStop.length > 0 ? { Stop: switchroomStop } : {}
@@ -36014,7 +36035,8 @@ var init_protocol2 = __esm(() => {
36014
36035
  v: exports_external.literal(PROTOCOL_VERSION),
36015
36036
  op: exports_external.literal("mark-throttled"),
36016
36037
  id: exports_external.string().min(1),
36017
- until: exports_external.number().int().positive()
36038
+ until: exports_external.number().int().positive(),
36039
+ probeOnly: exports_external.boolean().optional()
36018
36040
  });
36019
36041
  RefreshAccountRequestSchema = exports_external.object({
36020
36042
  v: exports_external.literal(PROTOCOL_VERSION),
@@ -36410,12 +36432,13 @@ class AuthBrokerClient {
36410
36432
  const data = await this.send(req);
36411
36433
  return data;
36412
36434
  }
36413
- async markThrottled(until) {
36435
+ async markThrottled(until, probeOnly = false) {
36414
36436
  const data = await this.send({
36415
36437
  v: PROTOCOL_VERSION,
36416
36438
  id: randomUUID(),
36417
36439
  op: "mark-throttled",
36418
- until
36440
+ until,
36441
+ ...probeOnly ? { probeOnly: true } : {}
36419
36442
  });
36420
36443
  return data;
36421
36444
  }
@@ -63669,7 +63692,7 @@ function checkNodeVersion() {
63669
63692
  }
63670
63693
  return { name: "Node 18+", status: "ok", detail: label };
63671
63694
  }
63672
- function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = process.env.PLAYWRIGHT_BROWSERS_PATH) {
63695
+ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = process.env.PLAYWRIGHT_BROWSERS_PATH, bakedBrowsersPath = "/opt/playwright/browsers") {
63673
63696
  const candidates = [
63674
63697
  "chromium",
63675
63698
  "chromium-browser",
@@ -63686,6 +63709,9 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
63686
63709
  cacheLocations.push(envBrowsersPath);
63687
63710
  }
63688
63711
  cacheLocations.push(join74(homeDir, ".cache", "ms-playwright"));
63712
+ if (bakedBrowsersPath && bakedBrowsersPath.length > 0) {
63713
+ cacheLocations.push(bakedBrowsersPath);
63714
+ }
63689
63715
  for (const cacheDir of cacheLocations) {
63690
63716
  if (!existsSync69(cacheDir))
63691
63717
  continue;
@@ -88253,6 +88279,9 @@ function refillNormalizedUtils(q, now) {
88253
88279
  sevenDayRefilled
88254
88280
  };
88255
88281
  }
88282
+ var ENTITLEMENT_BLOCKED_ERROR_CODES = new Set([
88283
+ "oauth_not_allowed_for_organization"
88284
+ ]);
88256
88285
 
88257
88286
  // src/cli/auth-schedule.ts
88258
88287
  function overageReasonBlocks(reason) {
@@ -12063,7 +12063,7 @@ function deriveOverlayTitle(raw, fileName) {
12063
12063
  return;
12064
12064
  return base.length > 0 ? base : undefined;
12065
12065
  }
12066
- function readOverlayFile(agentName, file, agentCfg, warnings) {
12066
+ function readOverlayFile(agentName, file, agentCfg, warnings, source) {
12067
12067
  try {
12068
12068
  return readFileSync(file, "utf-8");
12069
12069
  } catch (err) {
@@ -12076,7 +12076,7 @@ function readOverlayFile(agentName, file, agentCfg, warnings) {
12076
12076
  reason: `read error: ${err.message}`,
12077
12077
  code: code ?? "EUNKNOWN"
12078
12078
  };
12079
- recordReadFailure(agentCfg, { file, code: w.code });
12079
+ recordReadFailure(agentCfg, { file, code: w.code, source });
12080
12080
  warnings.push(w);
12081
12081
  console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${file}': ${w.reason}`);
12082
12082
  return;
@@ -12140,14 +12140,14 @@ function applyAgentOverlays(config) {
12140
12140
  reason: `read error: cannot list overlay directory (${code})`,
12141
12141
  code
12142
12142
  };
12143
- recordReadFailure(agentCfg, { file: scheduleDir, code });
12143
+ recordReadFailure(agentCfg, { file: scheduleDir, code, source: "schedule" });
12144
12144
  warnings.push(w);
12145
12145
  console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${scheduleDir}': ${w.reason}`);
12146
12146
  });
12147
12147
  if (files.length > 0) {
12148
12148
  const merged = [...agentCfg.schedule ?? []];
12149
12149
  for (const file of files) {
12150
- const raw = readOverlayFile(agentName, file, agentCfg, warnings);
12150
+ const raw = readOverlayFile(agentName, file, agentCfg, warnings, "schedule");
12151
12151
  if (raw === undefined)
12152
12152
  continue;
12153
12153
  try {
@@ -12192,7 +12192,7 @@ function applyAgentOverlays(config) {
12192
12192
  reason: `read error: cannot list overlay directory (${code})`,
12193
12193
  code
12194
12194
  };
12195
- recordReadFailure(agentCfg, { file: skillsDir, code });
12195
+ recordReadFailure(agentCfg, { file: skillsDir, code, source: "skills" });
12196
12196
  warnings.push(w);
12197
12197
  console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${skillsDir}': ${w.reason}`);
12198
12198
  });
@@ -12200,7 +12200,7 @@ function applyAgentOverlays(config) {
12200
12200
  const merged = [...agentCfg.skills ?? []];
12201
12201
  const seen = new Set(merged);
12202
12202
  for (const file of skillFiles) {
12203
- const raw = readOverlayFile(agentName, file, agentCfg, warnings);
12203
+ const raw = readOverlayFile(agentName, file, agentCfg, warnings, "skills");
12204
12204
  if (raw === undefined)
12205
12205
  continue;
12206
12206
  try {
@@ -21493,7 +21493,7 @@ function allocateAgentUid(name) {
21493
21493
  }
21494
21494
 
21495
21495
  // src/build-info.ts
21496
- var VERSION = "0.20.8";
21496
+ var VERSION = "0.20.10";
21497
21497
 
21498
21498
  // src/setup/hindsight-recall-tunables.ts
21499
21499
  var RECALL_DEADLINE_HEADROOM_SECONDS = 2;
@@ -12867,7 +12867,7 @@ function deriveOverlayTitle(raw, fileName) {
12867
12867
  return;
12868
12868
  return base.length > 0 ? base : undefined;
12869
12869
  }
12870
- function readOverlayFile(agentName, file, agentCfg, warnings) {
12870
+ function readOverlayFile(agentName, file, agentCfg, warnings, source) {
12871
12871
  try {
12872
12872
  return readFileSync2(file, "utf-8");
12873
12873
  } catch (err) {
@@ -12880,7 +12880,7 @@ function readOverlayFile(agentName, file, agentCfg, warnings) {
12880
12880
  reason: `read error: ${err.message}`,
12881
12881
  code: code ?? "EUNKNOWN"
12882
12882
  };
12883
- recordReadFailure(agentCfg, { file, code: w.code });
12883
+ recordReadFailure(agentCfg, { file, code: w.code, source });
12884
12884
  warnings.push(w);
12885
12885
  console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${file}': ${w.reason}`);
12886
12886
  return;
@@ -12944,14 +12944,14 @@ function applyAgentOverlays(config) {
12944
12944
  reason: `read error: cannot list overlay directory (${code})`,
12945
12945
  code
12946
12946
  };
12947
- recordReadFailure(agentCfg, { file: scheduleDir, code });
12947
+ recordReadFailure(agentCfg, { file: scheduleDir, code, source: "schedule" });
12948
12948
  warnings.push(w);
12949
12949
  console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${scheduleDir}': ${w.reason}`);
12950
12950
  });
12951
12951
  if (files.length > 0) {
12952
12952
  const merged = [...agentCfg.schedule ?? []];
12953
12953
  for (const file of files) {
12954
- const raw = readOverlayFile(agentName, file, agentCfg, warnings);
12954
+ const raw = readOverlayFile(agentName, file, agentCfg, warnings, "schedule");
12955
12955
  if (raw === undefined)
12956
12956
  continue;
12957
12957
  try {
@@ -12996,7 +12996,7 @@ function applyAgentOverlays(config) {
12996
12996
  reason: `read error: cannot list overlay directory (${code})`,
12997
12997
  code
12998
12998
  };
12999
- recordReadFailure(agentCfg, { file: skillsDir, code });
12999
+ recordReadFailure(agentCfg, { file: skillsDir, code, source: "skills" });
13000
13000
  warnings.push(w);
13001
13001
  console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${skillsDir}': ${w.reason}`);
13002
13002
  });
@@ -13004,7 +13004,7 @@ function applyAgentOverlays(config) {
13004
13004
  const merged = [...agentCfg.skills ?? []];
13005
13005
  const seen = new Set(merged);
13006
13006
  for (const file of skillFiles) {
13007
- const raw = readOverlayFile(agentName, file, agentCfg, warnings);
13007
+ const raw = readOverlayFile(agentName, file, agentCfg, warnings, "skills");
13008
13008
  if (raw === undefined)
13009
13009
  continue;
13010
13010
  try {