switchroom 0.20.8 → 0.20.9

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 (36) hide show
  1. package/dist/agent-scheduler/index.js +16 -13
  2. package/dist/auth-broker/index.js +51 -29
  3. package/dist/cli/autoaccept-poll.js +5 -3
  4. package/dist/cli/drive-write-pretool.mjs +5 -3
  5. package/dist/cli/ms-365-write-pretool.mjs +5 -3
  6. package/dist/cli/notion-write-pretool.mjs +6 -6
  7. package/dist/cli/switchroom.js +12 -10
  8. package/dist/host-control/main.js +7 -7
  9. package/dist/vault/approvals/kernel-server.js +6 -6
  10. package/dist/vault/broker/server.js +6 -6
  11. package/package.json +1 -1
  12. package/profiles/default/CLAUDE.md.hbs +12 -13
  13. package/telegram-plugin/ask-user.ts +6 -7
  14. package/telegram-plugin/dist/gateway/gateway.js +183 -66
  15. package/telegram-plugin/gateway/auth-broker-client.ts +1 -1
  16. package/telegram-plugin/gateway/auth-command.ts +4 -2
  17. package/telegram-plugin/gateway/checklist-fallback.ts +8 -1
  18. package/telegram-plugin/gateway/gateway.ts +8 -4
  19. package/telegram-plugin/gateway/outbound-send-path.ts +9 -1
  20. package/telegram-plugin/gateway/subagent-handback-inbound-builder.ts +21 -1
  21. package/telegram-plugin/gateway/subagent-handback-marker.ts +94 -0
  22. package/telegram-plugin/gateway/throttle-tier-wiring.ts +93 -18
  23. package/telegram-plugin/render/emphasis-guard.ts +92 -12
  24. package/telegram-plugin/render/line-start-guard.ts +27 -2
  25. package/telegram-plugin/sticker-aliases.ts +12 -14
  26. package/telegram-plugin/tests/ask-user.test.ts +15 -0
  27. package/telegram-plugin/tests/checklist-fallback.test.ts +21 -0
  28. package/telegram-plugin/tests/handback-tasknotif-dedup.test.ts +248 -0
  29. package/telegram-plugin/tests/render/emphasis-guard.test.ts +105 -6
  30. package/telegram-plugin/tests/render/heading-guard-blockquote-glued-hash.test.ts +123 -36
  31. package/telegram-plugin/tests/reply-quote-wire.test.ts +47 -0
  32. package/telegram-plugin/tests/sticker-aliases.test.ts +43 -0
  33. package/telegram-plugin/tests/throttle-tier-probe-only.test.ts +216 -0
  34. package/telegram-plugin/tests/throttle-tier-route-429-wiring.test.ts +92 -0
  35. package/telegram-plugin/tests/throttle-tier-route-429.test.ts +71 -0
  36. package/telegram-plugin/throttle-tier.ts +59 -0
@@ -12019,12 +12019,13 @@ function recordReadFailure(agentCfg, failure) {
12019
12019
  writable: false
12020
12020
  });
12021
12021
  }
12022
- function overlayReadFailures(config, agent) {
12022
+ function overlayReadFailures(config, agent, source) {
12023
12023
  const agentCfg = config.agents?.[agent];
12024
12024
  if (!agentCfg)
12025
12025
  return [];
12026
12026
  const list = agentCfg[OVERLAY_READ_FAILURES];
12027
- return Array.isArray(list) ? list : [];
12027
+ const all = Array.isArray(list) ? list : [];
12028
+ return source ? all.filter((f) => f.source === source) : all;
12028
12029
  }
12029
12030
  function deriveOverlayTitle(raw, fileName) {
12030
12031
  const titleFromComment = raw.match(/^#[^\S\n]*name:[^\S\n]*(\S.*?)[^\S\n]*$/m)?.[1];
@@ -12035,7 +12036,7 @@ function deriveOverlayTitle(raw, fileName) {
12035
12036
  return;
12036
12037
  return base.length > 0 ? base : undefined;
12037
12038
  }
12038
- function readOverlayFile(agentName, file, agentCfg, warnings) {
12039
+ function readOverlayFile(agentName, file, agentCfg, warnings, source) {
12039
12040
  try {
12040
12041
  return readFileSync(file, "utf-8");
12041
12042
  } catch (err) {
@@ -12048,7 +12049,7 @@ function readOverlayFile(agentName, file, agentCfg, warnings) {
12048
12049
  reason: `read error: ${err.message}`,
12049
12050
  code: code ?? "EUNKNOWN"
12050
12051
  };
12051
- recordReadFailure(agentCfg, { file, code: w.code });
12052
+ recordReadFailure(agentCfg, { file, code: w.code, source });
12052
12053
  warnings.push(w);
12053
12054
  console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${file}': ${w.reason}`);
12054
12055
  return;
@@ -12112,14 +12113,14 @@ function applyAgentOverlays(config) {
12112
12113
  reason: `read error: cannot list overlay directory (${code})`,
12113
12114
  code
12114
12115
  };
12115
- recordReadFailure(agentCfg, { file: scheduleDir, code });
12116
+ recordReadFailure(agentCfg, { file: scheduleDir, code, source: "schedule" });
12116
12117
  warnings.push(w);
12117
12118
  console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${scheduleDir}': ${w.reason}`);
12118
12119
  });
12119
12120
  if (files.length > 0) {
12120
12121
  const merged = [...agentCfg.schedule ?? []];
12121
12122
  for (const file of files) {
12122
- const raw = readOverlayFile(agentName, file, agentCfg, warnings);
12123
+ const raw = readOverlayFile(agentName, file, agentCfg, warnings, "schedule");
12123
12124
  if (raw === undefined)
12124
12125
  continue;
12125
12126
  try {
@@ -12164,7 +12165,7 @@ function applyAgentOverlays(config) {
12164
12165
  reason: `read error: cannot list overlay directory (${code})`,
12165
12166
  code
12166
12167
  };
12167
- recordReadFailure(agentCfg, { file: skillsDir, code });
12168
+ recordReadFailure(agentCfg, { file: skillsDir, code, source: "skills" });
12168
12169
  warnings.push(w);
12169
12170
  console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${skillsDir}': ${w.reason}`);
12170
12171
  });
@@ -12172,7 +12173,7 @@ function applyAgentOverlays(config) {
12172
12173
  const merged = [...agentCfg.skills ?? []];
12173
12174
  const seen = new Set(merged);
12174
12175
  for (const file of skillFiles) {
12175
- const raw = readOverlayFile(agentName, file, agentCfg, warnings);
12176
+ const raw = readOverlayFile(agentName, file, agentCfg, warnings, "skills");
12176
12177
  if (raw === undefined)
12177
12178
  continue;
12178
12179
  try {
@@ -14043,7 +14044,8 @@ var MarkThrottledRequestSchema = exports_external.object({
14043
14044
  v: exports_external.literal(PROTOCOL_VERSION),
14044
14045
  op: exports_external.literal("mark-throttled"),
14045
14046
  id: exports_external.string().min(1),
14046
- until: exports_external.number().int().positive()
14047
+ until: exports_external.number().int().positive(),
14048
+ probeOnly: exports_external.boolean().optional()
14047
14049
  });
14048
14050
  var RefreshAccountRequestSchema = exports_external.object({
14049
14051
  v: exports_external.literal(PROTOCOL_VERSION),
@@ -14460,12 +14462,13 @@ class AuthBrokerClient {
14460
14462
  const data = await this.send(req);
14461
14463
  return data;
14462
14464
  }
14463
- async markThrottled(until) {
14465
+ async markThrottled(until, probeOnly = false) {
14464
14466
  const data = await this.send({
14465
14467
  v: PROTOCOL_VERSION,
14466
14468
  id: randomUUID(),
14467
14469
  op: "mark-throttled",
14468
- until
14470
+ until,
14471
+ ...probeOnly ? { probeOnly: true } : {}
14469
14472
  });
14470
14473
  return data;
14471
14474
  }
@@ -15337,7 +15340,7 @@ function formatReadFailures(failures) {
15337
15340
  }
15338
15341
  function loadAgentEntriesStrict(configPath, agentName) {
15339
15342
  const config = loadConfig(configPath);
15340
- const failures = overlayReadFailures(config, agentName);
15343
+ const failures = overlayReadFailures(config, agentName, "schedule");
15341
15344
  if (failures.length > 0) {
15342
15345
  throw new Error(`schedule.d overlay unreadable — refusing a reload that may be ` + `missing entries: ${formatReadFailures(failures)}`);
15343
15346
  }
@@ -15382,7 +15385,7 @@ async function main() {
15382
15385
  const config = loadConfig(configPath);
15383
15386
  const allEntries = collectScheduleEntries(config);
15384
15387
  const entries = allEntries.filter((e) => e.agent === agentName);
15385
- const bootReadFailures = overlayReadFailures(config, agentName);
15388
+ const bootReadFailures = overlayReadFailures(config, agentName, "schedule");
15386
15389
  if (bootReadFailures.length > 0) {
15387
15390
  process.stderr.write(`agent-scheduler: ${agentName} WARNING: ${bootReadFailures.length} ` + `schedule.d overlay file(s) unreadable at boot — their cron entries ` + `are NOT registered: ${formatReadFailures(bootReadFailures)}
15388
15391
  `);
@@ -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 {
@@ -21286,7 +21286,8 @@ var MarkThrottledRequestSchema = exports_external.object({
21286
21286
  v: exports_external.literal(PROTOCOL_VERSION),
21287
21287
  op: exports_external.literal("mark-throttled"),
21288
21288
  id: exports_external.string().min(1),
21289
- until: exports_external.number().int().positive()
21289
+ until: exports_external.number().int().positive(),
21290
+ probeOnly: exports_external.boolean().optional()
21290
21291
  });
21291
21292
  var RefreshAccountRequestSchema = exports_external.object({
21292
21293
  v: exports_external.literal(PROTOCOL_VERSION),
@@ -21568,8 +21569,8 @@ var AUTH_BROKER_ROOT2 = "/run/switchroom/auth-broker";
21568
21569
  var REFRESH_TICK_INTERVAL_MS = 60 * 1000;
21569
21570
  var MARK_EXHAUSTED_DEFAULT_MS = 5 * 60 * 60 * 1000;
21570
21571
  var MARK_THROTTLED_MAX_MS = 30 * 60 * 1000;
21571
- var THROTTLE_ESCALATION_HITS = 3;
21572
21572
  var THROTTLE_ESCALATION_WINDOW_MS = 10 * 60 * 1000;
21573
+ var THROTTLE_ESCALATION_PROBE_MIN_INTERVAL_MS = 60 * 1000;
21573
21574
  var MARK_THROTTLED_MIN_INTERVAL_MS = 5 * 1000;
21574
21575
  var MARK_CORROBORATION_PROBE_DELAY_MS = 90 * 1000;
21575
21576
  var AUDIT_ROTATE_BYTES = 10 * 1024 * 1024;
@@ -21663,6 +21664,7 @@ class AuthBroker {
21663
21664
  externalSpendCache = null;
21664
21665
  externalSpendInFlight = null;
21665
21666
  probeInFlight = new Map;
21667
+ lastEscalationProbeAt = {};
21666
21668
  shaIndex = {};
21667
21669
  thresholdViolations = {};
21668
21670
  notificationClaims = {};
@@ -22047,7 +22049,7 @@ class AuthBroker {
22047
22049
  await this.opMarkExhausted(socket, reqId, identity, req.until);
22048
22050
  break;
22049
22051
  case "mark-throttled":
22050
- await this.opMarkThrottled(socket, reqId, identity, req.until);
22052
+ await this.opMarkThrottled(socket, reqId, identity, req.until, req.probeOnly);
22051
22053
  break;
22052
22054
  case "refresh-account": {
22053
22055
  const provider = req.provider ?? "anthropic";
@@ -22879,7 +22881,7 @@ class AuthBroker {
22879
22881
  this.audit({ op: "mark-exhausted", identity, account, accountKind: "claude", ok: true });
22880
22882
  socket.write(encodeSuccess(id, { account, rolled, rolledTo }));
22881
22883
  }
22882
- async opMarkThrottled(socket, id, identity, until) {
22884
+ async opMarkThrottled(socket, id, identity, until, probeOnly = false) {
22883
22885
  const account = this.callerAccount(identity);
22884
22886
  if (!account) {
22885
22887
  this.audit({ op: "mark-throttled", identity, accountKind: "claude", ok: false, error: "no-active-account" });
@@ -22887,6 +22889,17 @@ class AuthBroker {
22887
22889
  return;
22888
22890
  }
22889
22891
  const now = this.now();
22892
+ if (probeOnly) {
22893
+ const { escalated: escalated2, rolledTo: rolledTo2 } = await this.maybeEscalateThrottle(account, identity, now);
22894
+ this.audit({ op: "mark-throttled", identity, account, accountKind: "claude", ok: true });
22895
+ socket.write(encodeSuccess(id, {
22896
+ account,
22897
+ throttled_until: this.quota[account]?.throttled_until ?? 0,
22898
+ escalated: escalated2,
22899
+ rolledTo: escalated2 ? rolledTo2 : null
22900
+ }));
22901
+ return;
22902
+ }
22890
22903
  const throttledUntil = Math.min(Math.max(until, now + 1000), now + MARK_THROTTLED_MAX_MS);
22891
22904
  const entry = this.quota[account] ?? {};
22892
22905
  const priorHits = entry.throttle_hits ?? [];
@@ -22908,31 +22921,19 @@ class AuthBroker {
22908
22921
  }
22909
22922
  const hits = priorHits.filter((t) => now - t < THROTTLE_ESCALATION_WINDOW_MS);
22910
22923
  hits.push(now);
22911
- const escalate = hits.length >= THROTTLE_ESCALATION_HITS;
22912
22924
  this.quota[account] = {
22913
22925
  ...entry,
22914
22926
  throttled_until: throttledUntil,
22915
- throttle_hits: escalate ? [] : hits
22927
+ throttle_hits: hits
22916
22928
  };
22917
22929
  this.persistQuota();
22918
22930
  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
22931
+ process.stdout.write(`auth-broker: mark-throttled ${account} until ${new Date(throttledUntil).toISOString()} ` + `(hit ${hits.length} in window)
22934
22932
  `);
22935
- }
22933
+ const { escalated, rolledTo } = await this.maybeEscalateThrottle(account, identity, now);
22934
+ if (escalated) {
22935
+ this.quota[account] = { ...this.quota[account], throttle_hits: [] };
22936
+ this.persistQuota();
22936
22937
  }
22937
22938
  socket.write(encodeSuccess(id, {
22938
22939
  account,
@@ -22941,6 +22942,27 @@ class AuthBroker {
22941
22942
  rolledTo: escalated ? rolledTo : null
22942
22943
  }));
22943
22944
  }
22945
+ async maybeEscalateThrottle(account, identity, now) {
22946
+ const lastProbeAt = this.lastEscalationProbeAt[account];
22947
+ const probeAllowed = lastProbeAt === undefined || now - lastProbeAt >= THROTTLE_ESCALATION_PROBE_MIN_INTERVAL_MS;
22948
+ if (!probeAllowed) {
22949
+ process.stdout.write(`auth-broker: throttle-escalation probe on ${account} rate-bounded — skipped
22950
+ `);
22951
+ return { escalated: false, rolledTo: null };
22952
+ }
22953
+ this.lastEscalationProbeAt[account] = now;
22954
+ const probe = await this.probeThrottleEscalation(account);
22955
+ if (!probe.exhausted) {
22956
+ process.stdout.write(`auth-broker: throttle-escalation probe on ${account} did NOT corroborate a wall — staying put
22957
+ `);
22958
+ return { escalated: false, rolledTo: null };
22959
+ }
22960
+ process.stdout.write(`auth-broker: throttle-escalation probe corroborates wall on ${account} — mark-exhausted + roll
22961
+ `);
22962
+ this.audit({ op: "mark-exhausted", identity, account, accountKind: "claude", ok: true, reason: "throttle-escalation" });
22963
+ const roll = await this.markExhaustedAndRoll(account, probe.until ?? undefined, identity);
22964
+ return { escalated: true, rolledTo: roll.rolledTo };
22965
+ }
22944
22966
  async probeThrottleEscalation(account) {
22945
22967
  const creds = readAccountCredentials(account, this.home);
22946
22968
  const token = creds?.claudeAiOauth?.accessToken;
@@ -22948,7 +22970,7 @@ class AuthBroker {
22948
22970
  return { exhausted: false, until: null };
22949
22971
  let result;
22950
22972
  try {
22951
- result = await this.fetchQuotaImpl({ accessToken: token });
22973
+ result = await this.probeQuotaSingleFlight(account, token);
22952
22974
  } catch (err) {
22953
22975
  this.logErr(`throttle-escalation probe ${account}: ${err.message}`);
22954
22976
  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.9", COMMIT_SHA = "63c4c44b";
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 {
@@ -36014,7 +36014,8 @@ var init_protocol2 = __esm(() => {
36014
36014
  v: exports_external.literal(PROTOCOL_VERSION),
36015
36015
  op: exports_external.literal("mark-throttled"),
36016
36016
  id: exports_external.string().min(1),
36017
- until: exports_external.number().int().positive()
36017
+ until: exports_external.number().int().positive(),
36018
+ probeOnly: exports_external.boolean().optional()
36018
36019
  });
36019
36020
  RefreshAccountRequestSchema = exports_external.object({
36020
36021
  v: exports_external.literal(PROTOCOL_VERSION),
@@ -36410,12 +36411,13 @@ class AuthBrokerClient {
36410
36411
  const data = await this.send(req);
36411
36412
  return data;
36412
36413
  }
36413
- async markThrottled(until) {
36414
+ async markThrottled(until, probeOnly = false) {
36414
36415
  const data = await this.send({
36415
36416
  v: PROTOCOL_VERSION,
36416
36417
  id: randomUUID(),
36417
36418
  op: "mark-throttled",
36418
- until
36419
+ until,
36420
+ ...probeOnly ? { probeOnly: true } : {}
36419
36421
  });
36420
36422
  return data;
36421
36423
  }
@@ -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.9";
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 {