switchroom 0.19.2 → 0.19.4

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 (60) hide show
  1. package/dist/agent-scheduler/index.js +2 -0
  2. package/dist/auth-broker/index.js +109 -7
  3. package/dist/cli/autoaccept-poll.js +2 -0
  4. package/dist/cli/drive-write-pretool.mjs +2 -0
  5. package/dist/cli/ms-365-write-pretool.mjs +2 -0
  6. package/dist/cli/switchroom.js +404 -245
  7. package/dist/host-control/main.js +1 -1
  8. package/package.json +1 -1
  9. package/profiles/default/CLAUDE.md.hbs +8 -0
  10. package/skills/mental-model-curator/SKILL.md +68 -2
  11. package/telegram-plugin/auth-snapshot-format.ts +104 -12
  12. package/telegram-plugin/dist/bridge/bridge.js +8 -2
  13. package/telegram-plugin/dist/gateway/gateway.js +1194 -794
  14. package/telegram-plugin/dist/server.js +8 -2
  15. package/telegram-plugin/flushed-turn-supersede.ts +117 -13
  16. package/telegram-plugin/gateway/auth-add-flow.ts +215 -6
  17. package/telegram-plugin/gateway/auth-command.ts +138 -5
  18. package/telegram-plugin/gateway/gateway.ts +68 -101
  19. package/telegram-plugin/gateway/inbound-interceptors.ts +13 -3
  20. package/telegram-plugin/gateway/model-command.ts +203 -1
  21. package/telegram-plugin/gateway/outbound-send-path.ts +68 -15
  22. package/telegram-plugin/gateway/session-model-source.ts +90 -10
  23. package/telegram-plugin/gateway/stream-render.ts +22 -5
  24. package/telegram-plugin/quota-bar-format.ts +60 -12
  25. package/telegram-plugin/reply-owner-resolve.ts +76 -11
  26. package/telegram-plugin/session-tail.ts +27 -3
  27. package/telegram-plugin/tests/auth-add-flow.test.ts +367 -5
  28. package/telegram-plugin/tests/auth-snapshot-format.test.ts +41 -0
  29. package/telegram-plugin/tests/flushed-turn-supersede.test.ts +117 -0
  30. package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +185 -29
  31. package/telegram-plugin/tests/model-command.test.ts +220 -0
  32. package/telegram-plugin/tests/reply-owner-resolve.test.ts +257 -13
  33. package/telegram-plugin/tests/send-reply-golden.test.ts +154 -0
  34. package/telegram-plugin/tests/session-model-source.test.ts +142 -0
  35. package/telegram-plugin/tests/session-tail-first-attach.test.ts +115 -2
  36. package/vendor/hindsight-memory/CHANGELOG.md +102 -0
  37. package/vendor/hindsight-memory/README.md +2 -1
  38. package/vendor/hindsight-memory/hooks/hooks.json +12 -0
  39. package/vendor/hindsight-memory/scripts/directive_verify.py +100 -3
  40. package/vendor/hindsight-memory/scripts/lib/config.py +150 -1
  41. package/vendor/hindsight-memory/scripts/lib/content.py +55 -5
  42. package/vendor/hindsight-memory/scripts/lib/directives.py +152 -15
  43. package/vendor/hindsight-memory/scripts/lib/parallel_recall.py +142 -0
  44. package/vendor/hindsight-memory/scripts/lib/state.py +31 -0
  45. package/vendor/hindsight-memory/scripts/recall.py +789 -143
  46. package/vendor/hindsight-memory/scripts/reconcile_tail.py +22 -1
  47. package/vendor/hindsight-memory/scripts/retain.py +71 -2
  48. package/vendor/hindsight-memory/scripts/subagent_retain.py +501 -0
  49. package/vendor/hindsight-memory/scripts/tests/test_directive_verify.py +169 -0
  50. package/vendor/hindsight-memory/scripts/tests/test_directives.py +177 -0
  51. package/vendor/hindsight-memory/scripts/tests/test_lesson_tagging.py +200 -0
  52. package/vendor/hindsight-memory/scripts/tests/test_recall_context_turns_default.py +200 -0
  53. package/vendor/hindsight-memory/scripts/tests/test_recall_envelope_strip_telemetry.py +477 -0
  54. package/vendor/hindsight-memory/scripts/tests/test_recall_integration.py +51 -0
  55. package/vendor/hindsight-memory/scripts/tests/test_recall_parallel_deadline.py +409 -0
  56. package/vendor/hindsight-memory/scripts/tests/test_recall_tag_weights.py +96 -0
  57. package/vendor/hindsight-memory/scripts/tests/test_recall_transcript_fallback.py +413 -0
  58. package/vendor/hindsight-memory/scripts/tests/test_reconcile_durability.py +49 -0
  59. package/vendor/hindsight-memory/scripts/tests/test_subagent_retain.py +439 -0
  60. package/vendor/hindsight-memory/settings.json +3 -1
@@ -13945,6 +13945,8 @@ var AccountStateSchema = exports_external.object({
13945
13945
  label: exports_external.string(),
13946
13946
  expiresAt: exports_external.number().optional(),
13947
13947
  exhausted: exports_external.boolean(),
13948
+ in_service: exports_external.boolean().optional(),
13949
+ entitlement_blocked: exports_external.boolean().optional(),
13948
13950
  exhausted_until: exports_external.number().optional(),
13949
13951
  throttled_until: exports_external.number().optional(),
13950
13952
  threshold_violations: exports_external.number().int().nonnegative().optional(),
@@ -18839,6 +18839,27 @@ function parseQuotaHeaders(headers, now = new Date) {
18839
18839
  }
18840
18840
  };
18841
18841
  }
18842
+ function extractApiErrorMessage(bodyText) {
18843
+ if (!bodyText || bodyText.trim().length === 0)
18844
+ return null;
18845
+ try {
18846
+ const parsed = JSON.parse(bodyText);
18847
+ const msg = parsed?.error?.message;
18848
+ if (typeof msg === "string" && msg.trim().length > 0)
18849
+ return msg.trim();
18850
+ } catch {}
18851
+ return bodyText.trim().slice(0, 500);
18852
+ }
18853
+ function isEntitlementDisabledMessage(message) {
18854
+ if (!message)
18855
+ return false;
18856
+ const m = message.toLowerCase();
18857
+ if (!m.includes("disabled"))
18858
+ return false;
18859
+ const mentionsCode = m.includes("claude code") || m.includes("claude subscription");
18860
+ const mentionsOrgOrSub = m.includes("organization") || m.includes("subscription") || m.includes(" org ");
18861
+ return mentionsCode && mentionsOrgOrSub;
18862
+ }
18842
18863
  async function fetchQuota(opts) {
18843
18864
  const token = opts.accessToken?.trim();
18844
18865
  if (!token || token.length === 0) {
@@ -18874,13 +18895,28 @@ async function fetchQuota(opts) {
18874
18895
  }
18875
18896
  return { ok: false, reason: `quota probe network error: ${msg}` };
18876
18897
  }
18877
- clearTimeout(timeout);
18878
18898
  const parsed = parseQuotaHeaders(resp.headers);
18879
- if (parsed.ok)
18899
+ if (parsed.ok) {
18900
+ clearTimeout(timeout);
18880
18901
  return parsed;
18902
+ }
18881
18903
  if (!resp.ok) {
18882
- return { ok: false, reason: `HTTP ${resp.status} from Anthropic (${parsed.reason})` };
18904
+ let bodyText = "";
18905
+ try {
18906
+ bodyText = await resp.text();
18907
+ } catch {}
18908
+ clearTimeout(timeout);
18909
+ const apiErrorMessage = extractApiErrorMessage(bodyText);
18910
+ const entitlement = resp.status === 403 && isEntitlementDisabledMessage(apiErrorMessage);
18911
+ return {
18912
+ ok: false,
18913
+ reason: `HTTP ${resp.status} from Anthropic (${parsed.reason})`,
18914
+ httpStatus: resp.status,
18915
+ ...apiErrorMessage ? { apiErrorMessage } : {},
18916
+ failureKind: entitlement ? "entitlement_blocked" : "other"
18917
+ };
18883
18918
  }
18919
+ clearTimeout(timeout);
18884
18920
  return parsed;
18885
18921
  }
18886
18922
 
@@ -18912,7 +18948,9 @@ function snapshotClearlyHealthy(s) {
18912
18948
  return s.fiveHourUtilizationPct < HEALTHY_CLEAR_PCT && s.sevenDayUtilizationPct < HEALTHY_CLEAR_PCT;
18913
18949
  }
18914
18950
  function accountEligibility(opts) {
18915
- const { mark, snapshot, now, allowOverage = false } = opts;
18951
+ const { mark, snapshot, now, allowOverage = false, entitlementBlocked = false } = opts;
18952
+ if (entitlementBlocked)
18953
+ return "blocked";
18916
18954
  if (snapshotFresh(snapshot, now) && snapshotWalled(snapshot) && !overageLiftsWall(snapshot, allowOverage)) {
18917
18955
  return "blocked";
18918
18956
  }
@@ -20401,6 +20439,8 @@ var AccountStateSchema = exports_external.object({
20401
20439
  label: exports_external.string(),
20402
20440
  expiresAt: exports_external.number().optional(),
20403
20441
  exhausted: exports_external.boolean(),
20442
+ in_service: exports_external.boolean().optional(),
20443
+ entitlement_blocked: exports_external.boolean().optional(),
20404
20444
  exhausted_until: exports_external.number().optional(),
20405
20445
  throttled_until: exports_external.number().optional(),
20406
20446
  threshold_violations: exports_external.number().int().nonnegative().optional(),
@@ -21168,8 +21208,50 @@ class AuthBroker {
21168
21208
  mark: this.exhaustionMarkOf(account),
21169
21209
  snapshot: this.lastQuotaCache[account],
21170
21210
  now: this.now(),
21171
- allowOverage: this.isOverageAllowed(account)
21211
+ allowOverage: this.isOverageAllowed(account),
21212
+ entitlementBlocked: this.isAccountEntitlementBlocked(account)
21213
+ });
21214
+ }
21215
+ isAccountEntitlementBlocked(account) {
21216
+ return this.quota[account]?.entitlement_blocked === true;
21217
+ }
21218
+ markEntitlementBlocked(label, result) {
21219
+ if (result.ok || result.failureKind !== "entitlement_blocked")
21220
+ return;
21221
+ if (this.quota[label]?.entitlement_blocked === true)
21222
+ return;
21223
+ this.quota[label] = {
21224
+ ...this.quota[label],
21225
+ entitlement_blocked: true,
21226
+ entitlement_blocked_at: this.now(),
21227
+ ...result.apiErrorMessage ? { entitlement_blocked_reason: result.apiErrorMessage } : {}
21228
+ };
21229
+ this.persistQuota();
21230
+ this.audit({
21231
+ op: "mark-exhausted",
21232
+ identity: { kind: "operator" },
21233
+ account: label,
21234
+ accountKind: "claude",
21235
+ ok: true,
21236
+ reason: "entitlement-403"
21172
21237
  });
21238
+ process.stdout.write(`auth-broker: ${label} returned entitlement-403 (Claude Code access disabled) — marked entitlement_blocked${result.apiErrorMessage ? ` (${result.apiErrorMessage})` : ""}
21239
+ `);
21240
+ }
21241
+ clearEntitlementBlocked(label) {
21242
+ const entry = this.quota[label];
21243
+ if (!entry?.entitlement_blocked)
21244
+ return;
21245
+ const {
21246
+ entitlement_blocked,
21247
+ entitlement_blocked_at,
21248
+ entitlement_blocked_reason,
21249
+ ...rest
21250
+ } = entry;
21251
+ this.quota[label] = rest;
21252
+ this.persistQuota();
21253
+ process.stdout.write(`auth-broker: live probe of ${label} succeeded — cleared entitlement_blocked mark
21254
+ `);
21173
21255
  }
21174
21256
  premiumWallMarkOf(account) {
21175
21257
  const q = this.quota[account];
@@ -21327,7 +21409,8 @@ class AuthBroker {
21327
21409
  mark: this.exhaustionMarkOf(account),
21328
21410
  snapshot,
21329
21411
  now,
21330
- allowOverage: true
21412
+ allowOverage: true,
21413
+ entitlementBlocked: this.isAccountEntitlementBlocked(account)
21331
21414
  }) === "eligible";
21332
21415
  }
21333
21416
  accountEligibilityOf(account) {
@@ -21337,7 +21420,8 @@ class AuthBroker {
21337
21420
  mark: this.exhaustionMarkOf(account),
21338
21421
  snapshot,
21339
21422
  now: this.now(),
21340
- allowOverage
21423
+ allowOverage,
21424
+ entitlementBlocked: this.isAccountEntitlementBlocked(account)
21341
21425
  });
21342
21426
  if (verdict === "eligible" && allowOverage && snapshot && (snapshot.fiveHourUtilizationPct >= WALL_PCT || snapshot.sevenDayUtilizationPct >= WALL_PCT)) {
21343
21427
  process.stdout.write(`auth-broker: ${account} is past the utilization wall but eligible via allow_overage — Anthropic overage billing active (5h=${snapshot.fiveHourUtilizationPct.toFixed(1)}%, 7d=${snapshot.sevenDayUtilizationPct.toFixed(1)}%)
@@ -21354,6 +21438,8 @@ class AuthBroker {
21354
21438
  const result = await this.probeQuotaSingleFlight(account, token);
21355
21439
  if (result.ok)
21356
21440
  this.cacheQuotaSnapshot(account, result);
21441
+ else
21442
+ this.markEntitlementBlocked(account, result);
21357
21443
  } catch {}
21358
21444
  }
21359
21445
  async nextHealthyAccountLive(current, order) {
@@ -21441,6 +21527,7 @@ class AuthBroker {
21441
21527
  }
21442
21528
  async opListState(socket, id, identity) {
21443
21529
  const auth = this.config.auth ?? {};
21530
+ const inService = this.inServiceSet();
21444
21531
  const accounts = listAccounts(this.home).map((label) => {
21445
21532
  const creds = readAccountCredentials(label, this.home);
21446
21533
  const meta = readAccountMeta(label, this.home);
@@ -21451,6 +21538,7 @@ class AuthBroker {
21451
21538
  label,
21452
21539
  expiresAt: creds?.claudeAiOauth?.expiresAt,
21453
21540
  exhausted,
21541
+ in_service: inService.has(label),
21454
21542
  exhausted_until: q?.exhausted_until,
21455
21543
  throttled_until: q?.throttled_until,
21456
21544
  threshold_violations: this.thresholdViolations[label] ?? 0,
@@ -21460,6 +21548,7 @@ class AuthBroker {
21460
21548
  premium_walled_until: q?.premium_walled_until,
21461
21549
  premium_wall_bucket: q?.premium_wall_bucket,
21462
21550
  last_tier_quota: this.lastTierQuotaCache[label] ?? null,
21551
+ entitlement_blocked: q?.entitlement_blocked ?? false,
21463
21552
  usage_ledger: summarizeAccountUsage(this.usageLedger, label, this.now())
21464
21553
  };
21465
21554
  });
@@ -21534,6 +21623,7 @@ class AuthBroker {
21534
21623
  this.cacheQuotaSnapshot(label, result);
21535
21624
  return { label, result, served: "live" };
21536
21625
  }
21626
+ this.markEntitlementBlocked(label, result);
21537
21627
  if (cached) {
21538
21628
  return { label, result: cachedSnapshotToResult(cached), served: "cache", capturedAt: cached.capturedAt };
21539
21629
  }
@@ -21555,6 +21645,7 @@ class AuthBroker {
21555
21645
  cacheQuotaSnapshot(label, result) {
21556
21646
  if (!result.ok)
21557
21647
  return;
21648
+ this.clearEntitlementBlocked(label);
21558
21649
  const snapshot = {
21559
21650
  fiveHourUtilizationPct: result.data.fiveHourUtilizationPct,
21560
21651
  sevenDayUtilizationPct: result.data.sevenDayUtilizationPct,
@@ -21584,6 +21675,8 @@ class AuthBroker {
21584
21675
  const canarySet = this.premiumCanarySet();
21585
21676
  const canaryEnabled = process.env.SWITCHROOM_DISABLE_MODEL_TIER_PROBE !== "1";
21586
21677
  for (const label of listAccounts(this.home)) {
21678
+ if (this.isAccountEntitlementBlocked(label))
21679
+ continue;
21587
21680
  const creds = readAccountCredentials(label, this.home);
21588
21681
  const token = creds?.claudeAiOauth?.accessToken;
21589
21682
  if (!token)
@@ -21596,6 +21689,7 @@ class AuthBroker {
21596
21689
  continue;
21597
21690
  }
21598
21691
  this.cacheQuotaSnapshot(label, result);
21692
+ this.markEntitlementBlocked(label, result);
21599
21693
  probed.push({ label, result });
21600
21694
  if (canaryEnabled && canarySet.has(label)) {
21601
21695
  let tierResult;
@@ -21700,6 +21794,14 @@ class AuthBroker {
21700
21794
  set.add(acct);
21701
21795
  return set;
21702
21796
  }
21797
+ inServiceSet() {
21798
+ const set = this.premiumCanarySet();
21799
+ for (const c of this.config.auth?.consumers ?? []) {
21800
+ if (c.account)
21801
+ set.add(c.account);
21802
+ }
21803
+ return set;
21804
+ }
21703
21805
  pinnedAgentAccounts() {
21704
21806
  return new Set(Object.values(this.config.agents ?? {}).map((a) => a.auth?.override).filter((x) => typeof x === "string" && x.length > 0));
21705
21807
  }
@@ -4874,6 +4874,8 @@ var AccountStateSchema = exports_external.object({
4874
4874
  label: exports_external.string(),
4875
4875
  expiresAt: exports_external.number().optional(),
4876
4876
  exhausted: exports_external.boolean(),
4877
+ in_service: exports_external.boolean().optional(),
4878
+ entitlement_blocked: exports_external.boolean().optional(),
4877
4879
  exhausted_until: exports_external.number().optional(),
4878
4880
  throttled_until: exports_external.number().optional(),
4879
4881
  threshold_violations: exports_external.number().int().nonnegative().optional(),
@@ -4162,6 +4162,8 @@ var init_protocol = __esm(() => {
4162
4162
  label: exports_external.string(),
4163
4163
  expiresAt: exports_external.number().optional(),
4164
4164
  exhausted: exports_external.boolean(),
4165
+ in_service: exports_external.boolean().optional(),
4166
+ entitlement_blocked: exports_external.boolean().optional(),
4165
4167
  exhausted_until: exports_external.number().optional(),
4166
4168
  throttled_until: exports_external.number().optional(),
4167
4169
  threshold_violations: exports_external.number().int().nonnegative().optional(),
@@ -4164,6 +4164,8 @@ var init_protocol = __esm(() => {
4164
4164
  label: exports_external.string(),
4165
4165
  expiresAt: exports_external.number().optional(),
4166
4166
  exhausted: exports_external.boolean(),
4167
+ in_service: exports_external.boolean().optional(),
4168
+ entitlement_blocked: exports_external.boolean().optional(),
4167
4169
  exhausted_until: exports_external.number().optional(),
4168
4170
  throttled_until: exports_external.number().optional(),
4169
4171
  threshold_violations: exports_external.number().int().nonnegative().optional(),