switchroom 0.19.0 → 0.19.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/agent-scheduler/index.js +29 -1
  2. package/dist/auth-broker/index.js +552 -48
  3. package/dist/cli/autoaccept-poll.js +29 -1
  4. package/dist/cli/drive-write-pretool.mjs +30 -2
  5. package/dist/cli/ms-365-write-pretool.mjs +30 -2
  6. package/dist/cli/switchroom.js +751 -36
  7. package/dist/host-control/main.js +3 -3
  8. package/dist/vault/approvals/kernel-server.js +2 -2
  9. package/dist/vault/broker/server.js +2 -2
  10. package/package.json +3 -2
  11. package/profiles/_base/start.sh.hbs +1 -0
  12. package/skills/switchroom-cli/SKILL.md +25 -0
  13. package/telegram-plugin/auth-snapshot-format.ts +39 -0
  14. package/telegram-plugin/dist/gateway/gateway.js +363 -25
  15. package/telegram-plugin/external-spend.ts +135 -0
  16. package/telegram-plugin/gateway/gateway.ts +83 -67
  17. package/telegram-plugin/gateway/model-command.ts +106 -0
  18. package/telegram-plugin/gateway/narrative-lane.ts +23 -9
  19. package/telegram-plugin/gateway/status-pin-store.ts +64 -4
  20. package/telegram-plugin/gateway/usage-mask.ts +29 -0
  21. package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +19 -2
  22. package/telegram-plugin/quota-bar-format.ts +18 -0
  23. package/telegram-plugin/quota-check.ts +17 -2
  24. package/telegram-plugin/tests/activity-card-wiring.test.ts +47 -0
  25. package/telegram-plugin/tests/external-spend.test.ts +168 -0
  26. package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +57 -23
  27. package/telegram-plugin/tests/quota-bar-format.test.ts +43 -0
  28. package/telegram-plugin/tests/quota-check.test.ts +57 -0
  29. package/telegram-plugin/tests/status-pin-store.test.ts +198 -0
  30. package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +50 -0
  31. package/telegram-plugin/tests/usage-footer-freshness.test.ts +141 -0
  32. package/telegram-plugin/tests/usage-mask.test.ts +35 -0
  33. package/telegram-plugin/tests/worker-feed-dispatch.test.ts +27 -0
  34. package/telegram-plugin/tests/worker-feed-pin-persistence.test.ts +131 -1
@@ -22057,6 +22057,7 @@ __export(exports_auth_snapshot_format, {
22057
22057
  formatRelative: () => formatRelative,
22058
22058
  formatAbsolute: () => formatAbsolute,
22059
22059
  fmtPct: () => fmtPct,
22060
+ deriveUsageFooterFreshness: () => deriveUsageFooterFreshness,
22060
22061
  classifyHealth: () => classifyHealth,
22061
22062
  buildSnapshotsFromState: () => buildSnapshotsFromState,
22062
22063
  buildSnapshotsFromCachedState: () => buildSnapshotsFromCachedState,
@@ -22467,6 +22468,13 @@ function zipProbeResults(labels, results) {
22467
22468
  });
22468
22469
  return staleCachedAtMs != null ? { quotas, staleCachedAtMs } : { quotas };
22469
22470
  }
22471
+ function deriveUsageFooterFreshness(results, staleCachedAtMs, liveProbedAtMs) {
22472
+ if (staleCachedAtMs != null)
22473
+ return { staleCachedAtMs };
22474
+ if (results.some((r) => r.result.ok))
22475
+ return { liveProbedAtMs };
22476
+ return { probeFailed: true };
22477
+ }
22470
22478
  function buildSnapshotsFromState(state, quotas) {
22471
22479
  const out = [];
22472
22480
  for (let i = 0;i < state.accounts.length; i++) {
@@ -22549,7 +22557,7 @@ function decodeResponse2(line) {
22549
22557
  }
22550
22558
  return ResponseSchema2.parse(parsed);
22551
22559
  }
22552
- var MAX_FRAME_BYTES2, PROTOCOL_VERSION = 1, ProviderNameSchema, GetCredentialsRequestSchema, ListStateRequestSchema, SetActiveRequestSchema, MarkExhaustedRequestSchema, MarkThrottledRequestSchema, RefreshAccountRequestSchema, AnthropicCredentialsSchema, GoogleCredentialsSchema, MicrosoftCredentialsSchema, ProviderCredentialsSchema, AddAccountRequestSchema, RmAccountRequestSchema, SetOverrideRequestSchema, ListGoogleAccountsRequestSchema, ListMicrosoftAccountsRequestSchema, ProbeQuotaRequestSchema, ClaimNotificationRequestSchema, RequestSchema2, GetCredentialsDataSchema, AccountStateSchema, AgentStateSchema, ConsumerStateSchema, ListStateDataSchema, SetActiveDataSchema, MarkExhaustedDataSchema, MarkThrottledDataSchema, RefreshAccountDataSchema, AddAccountDataSchema, RmAccountDataSchema, SetOverrideDataSchema, ClaimNotificationDataSchema, GoogleAccountStateSchema, ListGoogleAccountsDataSchema, MicrosoftAccountStateSchema, ListMicrosoftAccountsDataSchema, ErrorBodySchema, SuccessResponseSchema, ErrorResponseSchema2, ResponseSchema2;
22560
+ var MAX_FRAME_BYTES2, PROTOCOL_VERSION = 1, ProviderNameSchema, GetCredentialsRequestSchema, ListStateRequestSchema, SetActiveRequestSchema, MarkExhaustedRequestSchema, MarkThrottledRequestSchema, RefreshAccountRequestSchema, AnthropicCredentialsSchema, GoogleCredentialsSchema, MicrosoftCredentialsSchema, ProviderCredentialsSchema, AddAccountRequestSchema, RmAccountRequestSchema, SetOverrideRequestSchema, ListGoogleAccountsRequestSchema, ListMicrosoftAccountsRequestSchema, ProbeQuotaRequestSchema, ClaimNotificationRequestSchema, GetExternalSpendRequestSchema, RequestSchema2, GetCredentialsDataSchema, AccountStateSchema, AgentStateSchema, ConsumerStateSchema, ListStateDataSchema, SetActiveDataSchema, MarkExhaustedDataSchema, MarkThrottledDataSchema, RefreshAccountDataSchema, AddAccountDataSchema, RmAccountDataSchema, SetOverrideDataSchema, ClaimNotificationDataSchema, GetExternalSpendDataSchema, GoogleAccountStateSchema, ListGoogleAccountsDataSchema, MicrosoftAccountStateSchema, ListMicrosoftAccountsDataSchema, ErrorBodySchema, SuccessResponseSchema, ErrorResponseSchema2, ResponseSchema2;
22553
22561
  var init_protocol2 = __esm(() => {
22554
22562
  init_zod();
22555
22563
  MAX_FRAME_BYTES2 = 64 * 1024;
@@ -22680,6 +22688,12 @@ var init_protocol2 = __esm(() => {
22680
22688
  key: exports_external.string().min(1).max(512),
22681
22689
  windowMs: exports_external.number().int().positive().max(86400000)
22682
22690
  });
22691
+ GetExternalSpendRequestSchema = exports_external.object({
22692
+ v: exports_external.literal(PROTOCOL_VERSION),
22693
+ op: exports_external.literal("get-external-spend"),
22694
+ id: exports_external.string().min(1),
22695
+ forceLive: exports_external.boolean().optional()
22696
+ });
22683
22697
  RequestSchema2 = exports_external.discriminatedUnion("op", [
22684
22698
  GetCredentialsRequestSchema,
22685
22699
  ListStateRequestSchema,
@@ -22693,7 +22707,8 @@ var init_protocol2 = __esm(() => {
22693
22707
  ListGoogleAccountsRequestSchema,
22694
22708
  ListMicrosoftAccountsRequestSchema,
22695
22709
  ProbeQuotaRequestSchema,
22696
- ClaimNotificationRequestSchema
22710
+ ClaimNotificationRequestSchema,
22711
+ GetExternalSpendRequestSchema
22697
22712
  ]);
22698
22713
  GetCredentialsDataSchema = exports_external.object({
22699
22714
  account: exports_external.string(),
@@ -22760,6 +22775,18 @@ var init_protocol2 = __esm(() => {
22760
22775
  ClaimNotificationDataSchema = exports_external.object({
22761
22776
  granted: exports_external.boolean()
22762
22777
  });
22778
+ GetExternalSpendDataSchema = exports_external.object({
22779
+ available: exports_external.boolean(),
22780
+ day24hUsd: exports_external.number().optional(),
22781
+ day7dUsd: exports_external.number().optional(),
22782
+ top: exports_external.array(exports_external.object({
22783
+ label: exports_external.string(),
22784
+ usd: exports_external.number()
22785
+ })).optional(),
22786
+ capturedAtMs: exports_external.number().int().nonnegative().optional(),
22787
+ served: exports_external.enum(["live", "cache"]).optional(),
22788
+ reason: exports_external.string().optional()
22789
+ });
22763
22790
  GoogleAccountStateSchema = exports_external.object({
22764
22791
  account: exports_external.string(),
22765
22792
  expiresAt: exports_external.number(),
@@ -22930,6 +22957,15 @@ class AuthBrokerClient {
22930
22957
  }
22931
22958
  return parsed;
22932
22959
  }
22960
+ async getExternalSpend(forceLive) {
22961
+ const data = await this.send({
22962
+ v: PROTOCOL_VERSION,
22963
+ id: randomUUID(),
22964
+ op: "get-external-spend",
22965
+ ...forceLive ? { forceLive: true } : {}
22966
+ });
22967
+ return data;
22968
+ }
22933
22969
  async setActive(account) {
22934
22970
  const data = await this.send({
22935
22971
  v: PROTOCOL_VERSION,
@@ -29337,7 +29373,7 @@ var FLUSH_SUPPRESSION_WINDOW_MS = 2000;
29337
29373
  var init_turn_flush_suppression = () => {};
29338
29374
 
29339
29375
  // ../src/util/atomic.ts
29340
- import { closeSync as closeSync6, constants as constants2, fsyncSync as fsyncSync2, openSync as openSync6, renameSync as renameSync11, rmSync as rmSync5, writeSync as writeSync4 } from "node:fs";
29376
+ import { closeSync as closeSync6, constants as constants2, fchmodSync, fchownSync, fsyncSync as fsyncSync2, openSync as openSync6, renameSync as renameSync11, rmSync as rmSync5, writeSync as writeSync4 } from "node:fs";
29341
29377
  var TMP_OPEN_FLAGS;
29342
29378
  var init_atomic = __esm(() => {
29343
29379
  TMP_OPEN_FLAGS = constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | (constants2.O_NOFOLLOW ?? 0);
@@ -36742,6 +36778,7 @@ __export(exports_auth_snapshot_format2, {
36742
36778
  formatRelative: () => formatRelative2,
36743
36779
  formatAbsolute: () => formatAbsolute2,
36744
36780
  fmtPct: () => fmtPct2,
36781
+ deriveUsageFooterFreshness: () => deriveUsageFooterFreshness2,
36745
36782
  classifyHealth: () => classifyHealth2,
36746
36783
  buildSnapshotsFromState: () => buildSnapshotsFromState2,
36747
36784
  buildSnapshotsFromCachedState: () => buildSnapshotsFromCachedState2,
@@ -37152,6 +37189,13 @@ function zipProbeResults2(labels, results) {
37152
37189
  });
37153
37190
  return staleCachedAtMs != null ? { quotas, staleCachedAtMs } : { quotas };
37154
37191
  }
37192
+ function deriveUsageFooterFreshness2(results, staleCachedAtMs, liveProbedAtMs) {
37193
+ if (staleCachedAtMs != null)
37194
+ return { staleCachedAtMs };
37195
+ if (results.some((r) => r.result.ok))
37196
+ return { liveProbedAtMs };
37197
+ return { probeFailed: true };
37198
+ }
37155
37199
  function buildSnapshotsFromState2(state7, quotas) {
37156
37200
  const out = [];
37157
37201
  for (let i = 0;i < state7.accounts.length; i++) {
@@ -37626,6 +37670,140 @@ var init_config_approval_handler = __esm(() => {
37626
37670
  pending = new Map;
37627
37671
  });
37628
37672
 
37673
+ // ../src/litellm/external-spend.ts
37674
+ function isExternalModel(model) {
37675
+ const m = model.trim().toLowerCase();
37676
+ if (!m)
37677
+ return false;
37678
+ if (m.startsWith("claude"))
37679
+ return false;
37680
+ if (m.includes("anthropic/claude"))
37681
+ return false;
37682
+ if (m.startsWith("openrouter/"))
37683
+ return true;
37684
+ if (m.startsWith("sr-"))
37685
+ return true;
37686
+ for (const needle of BARE_EXTERNAL_NEEDLES) {
37687
+ if (m.includes(needle))
37688
+ return true;
37689
+ }
37690
+ return false;
37691
+ }
37692
+ function shortModelLabel(model) {
37693
+ let m = model.trim();
37694
+ if (m.toLowerCase().startsWith("openrouter/")) {
37695
+ m = m.slice("openrouter/".length);
37696
+ }
37697
+ const parts = m.split("/").filter(Boolean);
37698
+ if (parts.length >= 2) {
37699
+ m = parts[parts.length - 1];
37700
+ }
37701
+ if (m.toLowerCase().startsWith("sr-")) {
37702
+ m = m.slice(3);
37703
+ }
37704
+ return m || model.trim();
37705
+ }
37706
+ function formatUsd(n) {
37707
+ if (!Number.isFinite(n) || n < 0)
37708
+ return "$0.00";
37709
+ return `$${n.toFixed(2)}`;
37710
+ }
37711
+ function utcDateString(d) {
37712
+ return d.toISOString().slice(0, 10);
37713
+ }
37714
+ function addUtcDays(dateStr, days) {
37715
+ const [y, m, d] = dateStr.split("-").map(Number);
37716
+ const dt = new Date(Date.UTC(y, m - 1, d));
37717
+ dt.setUTCDate(dt.getUTCDate() + days);
37718
+ return utcDateString(dt);
37719
+ }
37720
+ function rowDay(row) {
37721
+ const st = row.startTime;
37722
+ if (!st || typeof st !== "string")
37723
+ return null;
37724
+ return st.length >= 10 ? st.slice(0, 10) : null;
37725
+ }
37726
+ function externalModelsFromRow(row) {
37727
+ const out = {};
37728
+ const models = row.models ?? {};
37729
+ for (const [name, raw] of Object.entries(models)) {
37730
+ if (!isExternalModel(name))
37731
+ continue;
37732
+ const n = typeof raw === "number" ? raw : Number(raw);
37733
+ if (!Number.isFinite(n) || n === 0)
37734
+ continue;
37735
+ out[name] = (out[name] ?? 0) + n;
37736
+ }
37737
+ return out;
37738
+ }
37739
+ function summarizeExternalSpend(days, now = new Date) {
37740
+ const today = utcDateString(now);
37741
+ const start7 = addUtcDays(today, -6);
37742
+ let day24hUsd = 0;
37743
+ let day7dUsd = 0;
37744
+ const byModel = {};
37745
+ for (const row of days) {
37746
+ const day = rowDay(row);
37747
+ if (!day)
37748
+ continue;
37749
+ if (day < start7 || day > today)
37750
+ continue;
37751
+ const ext = externalModelsFromRow(row);
37752
+ let rowSum = 0;
37753
+ for (const [name, usd] of Object.entries(ext)) {
37754
+ rowSum += usd;
37755
+ byModel[name] = (byModel[name] ?? 0) + usd;
37756
+ }
37757
+ day7dUsd += rowSum;
37758
+ if (day === today)
37759
+ day24hUsd += rowSum;
37760
+ }
37761
+ const top = Object.entries(byModel).filter(([, usd]) => usd > 0).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, EXTERNAL_SPEND_TOP_N).map(([name, usd]) => ({ label: shortModelLabel(name), usd }));
37762
+ return { day24hUsd, day7dUsd, top };
37763
+ }
37764
+ function normalizeSpendLogRows(body) {
37765
+ if (Array.isArray(body))
37766
+ return body;
37767
+ if (body && typeof body === "object") {
37768
+ const data = body.data;
37769
+ if (Array.isArray(data))
37770
+ return data;
37771
+ }
37772
+ return [];
37773
+ }
37774
+ var EXTERNAL_SPEND_TOP_N = 3, EXTERNAL_SPEND_CACHE_TTL_MS = 90000, BARE_EXTERNAL_NEEDLES;
37775
+ var init_external_spend = __esm(() => {
37776
+ BARE_EXTERNAL_NEEDLES = [
37777
+ "gpt-oss",
37778
+ "grok",
37779
+ "gemini",
37780
+ "deepseek",
37781
+ "kimi",
37782
+ "glm-",
37783
+ "qwen",
37784
+ "llama"
37785
+ ];
37786
+ });
37787
+
37788
+ // external-spend.ts
37789
+ function formatExternalSpendBlock(summary) {
37790
+ if (summary == null)
37791
+ return [];
37792
+ const lines = [
37793
+ "- \uD83D\uDCB8 External",
37794
+ `- 24h \`${formatUsd(summary.day24hUsd)}\` \u00b7 7d \`${formatUsd(summary.day7dUsd)}\``
37795
+ ];
37796
+ if (summary.top.length > 0) {
37797
+ const topParts = summary.top.map((t) => `\`${t.label} ${formatUsd(t.usd)}\``);
37798
+ lines.push(`- top ${topParts.join(" \u00b7 ")}`);
37799
+ }
37800
+ return lines;
37801
+ }
37802
+ var init_external_spend2 = __esm(() => {
37803
+ init_external_spend();
37804
+ init_external_spend();
37805
+ });
37806
+
37629
37807
  // quota-bar-format.ts
37630
37808
  var exports_quota_bar_format = {};
37631
37809
  __export(exports_quota_bar_format, {
@@ -37747,6 +37925,9 @@ function renderUsageCard(snapshots, exhaustedByLabel, opts = {}) {
37747
37925
  const bar = renderQuotaBarBlock(snapshots, exhaustedByLabel, { now, demo });
37748
37926
  const lines = [bar];
37749
37927
  lines.push(`_${recommendation(snapshots, now, demo)}_`);
37928
+ if (opts.externalSpend != null) {
37929
+ lines.push(...formatExternalSpendBlock(opts.externalSpend));
37930
+ }
37750
37931
  if (opts.staleCachedAtMs != null) {
37751
37932
  lines.push(`_\u26a0 cached ${formatAgeStamp3(opts.staleCachedAtMs, now)}_`);
37752
37933
  } else if (opts.liveProbedAtMs != null) {
@@ -37764,10 +37945,85 @@ var init_quota_bar_format = __esm(() => {
37764
37945
  init_auth_snapshot_format();
37765
37946
  init_card_format();
37766
37947
  init_demo_mask();
37948
+ init_external_spend2();
37767
37949
  FIVE_HOUR_MS = 5 * 60 * 60 * 1000;
37768
37950
  SEVEN_DAY_MS = 7 * 24 * 60 * 60 * 1000;
37769
37951
  });
37770
37952
 
37953
+ // external-spend.ts
37954
+ var exports_external_spend = {};
37955
+ __export(exports_external_spend, {
37956
+ utcDateString: () => utcDateString,
37957
+ summarizeExternalSpend: () => summarizeExternalSpend,
37958
+ shortModelLabel: () => shortModelLabel,
37959
+ normalizeSpendLogRows: () => normalizeSpendLogRows,
37960
+ isExternalModel: () => isExternalModel,
37961
+ formatUsd: () => formatUsd,
37962
+ formatExternalSpendBlock: () => formatExternalSpendBlock2,
37963
+ fetchExternalSpendSummary: () => fetchExternalSpendSummary,
37964
+ clearExternalSpendCache: () => clearExternalSpendCache,
37965
+ addUtcDays: () => addUtcDays,
37966
+ EXTERNAL_SPEND_CACHE_TTL_MS: () => EXTERNAL_SPEND_CACHE_TTL_MS
37967
+ });
37968
+ function clearExternalSpendCache() {
37969
+ clientCache = null;
37970
+ }
37971
+ function formatExternalSpendBlock2(summary) {
37972
+ if (summary == null)
37973
+ return [];
37974
+ const lines = [
37975
+ "- \uD83D\uDCB8 External",
37976
+ `- 24h \`${formatUsd(summary.day24hUsd)}\` \u00b7 7d \`${formatUsd(summary.day7dUsd)}\``
37977
+ ];
37978
+ if (summary.top.length > 0) {
37979
+ const topParts = summary.top.map((t) => `\`${t.label} ${formatUsd(t.usd)}\``);
37980
+ lines.push(`- top ${topParts.join(" \u00b7 ")}`);
37981
+ }
37982
+ return lines;
37983
+ }
37984
+ async function fetchExternalSpendSummary(nowOrDeps = {}) {
37985
+ const deps = nowOrDeps instanceof Date ? { now: nowOrDeps } : nowOrDeps;
37986
+ const ttl = deps.cacheTtlMs ?? CLIENT_CACHE_TTL_MS;
37987
+ if (!deps.bypassCache && clientCache && Date.now() - clientCache.atMs < ttl) {
37988
+ return clientCache.summary;
37989
+ }
37990
+ try {
37991
+ let data;
37992
+ if (deps.getExternalSpend) {
37993
+ data = await deps.getExternalSpend(deps.forceLive);
37994
+ } else {
37995
+ const { AuthBrokerClient: AuthBrokerClient3 } = await Promise.resolve().then(() => (init_client2(), exports_client));
37996
+ const client3 = new AuthBrokerClient3;
37997
+ try {
37998
+ data = await client3.getExternalSpend(deps.forceLive);
37999
+ } finally {
38000
+ try {
38001
+ await client3.close();
38002
+ } catch {}
38003
+ }
38004
+ }
38005
+ if (!data?.available)
38006
+ return null;
38007
+ if (typeof data.day24hUsd !== "number" || typeof data.day7dUsd !== "number" || !Array.isArray(data.top)) {
38008
+ return null;
38009
+ }
38010
+ const summary = {
38011
+ day24hUsd: data.day24hUsd,
38012
+ day7dUsd: data.day7dUsd,
38013
+ top: data.top.map((t) => ({ label: String(t.label), usd: Number(t.usd) }))
38014
+ };
38015
+ clientCache = { atMs: Date.now(), summary };
38016
+ return summary;
38017
+ } catch {
38018
+ return null;
38019
+ }
38020
+ }
38021
+ var CLIENT_CACHE_TTL_MS = 30000, clientCache = null;
38022
+ var init_external_spend3 = __esm(() => {
38023
+ init_external_spend();
38024
+ init_external_spend();
38025
+ });
38026
+
37771
38027
  // ../src/vault/approvals/client.ts
37772
38028
  function resolveKernelSocketPath2(opts) {
37773
38029
  if (opts?.socket)
@@ -38411,6 +38667,16 @@ function buildStopReply(turnInFlight, queuedSessionCmds) {
38411
38667
  `) };
38412
38668
  }
38413
38669
 
38670
+ // gateway/usage-mask.ts
38671
+ function shouldMaskUsageLabels(chatType, groupAllowFrom) {
38672
+ if (chatType === "private")
38673
+ return false;
38674
+ if (chatType === "group" || chatType === "supergroup") {
38675
+ return (groupAllowFrom ?? []).length === 0;
38676
+ }
38677
+ return true;
38678
+ }
38679
+
38414
38680
  // gateway/busy-ack.ts
38415
38681
  var BUSY_ACK_STEP_AGE_THRESHOLD_MS = 12000;
38416
38682
  function shouldPostBusyAck(input) {
@@ -42809,6 +43075,7 @@ function pinnedMessageIsOurs(tracked, chatId, pinnedMessageId) {
42809
43075
  return false;
42810
43076
  }
42811
43077
  var storeLockTails = new Map;
43078
+ var pinReconcileTails = new Map;
42812
43079
 
42813
43080
  // gateway/pinned-message-handler.ts
42814
43081
  async function handlePinnedMessage(ctx, deps) {
@@ -69626,6 +69893,15 @@ class AuthBrokerClient2 {
69626
69893
  }
69627
69894
  return parsed;
69628
69895
  }
69896
+ async getExternalSpend(forceLive) {
69897
+ const data = await this.send({
69898
+ v: PROTOCOL_VERSION,
69899
+ id: randomUUID4(),
69900
+ op: "get-external-spend",
69901
+ ...forceLive ? { forceLive: true } : {}
69902
+ });
69903
+ return data;
69904
+ }
69629
69905
  async setActive(account) {
69630
69906
  const data = await this.send({
69631
69907
  v: PROTOCOL_VERSION,
@@ -78479,8 +78755,10 @@ function formatQuotaBlock(q, now = new Date) {
78479
78755
  const lines = [];
78480
78756
  lines.push("**Claude plan quota**");
78481
78757
  lines.push("");
78482
- lines.push(`**5h window** \`${Math.round(q.fiveHourUtilizationPct)}%\` \u00b7 \`${formatResetRelative2(q.fiveHourResetAt, now)}\``);
78483
- lines.push(`**7d window** \`${Math.round(q.sevenDayUtilizationPct)}%\` \u00b7 \`${formatResetRelative2(q.sevenDayResetAt, now)}\``);
78758
+ const fiveHour = q.fiveHourUtilPresent === false ? "no data" : `\`${Math.round(q.fiveHourUtilizationPct)}%\``;
78759
+ const sevenDay = q.sevenDayUtilPresent === false ? "no data" : `\`${Math.round(q.sevenDayUtilizationPct)}%\``;
78760
+ lines.push(`**5h window** ${fiveHour} \u00b7 \`${formatResetRelative2(q.fiveHourResetAt, now)}\``);
78761
+ lines.push(`**7d window** ${sevenDay} \u00b7 \`${formatResetRelative2(q.sevenDayResetAt, now)}\``);
78484
78762
  if (q.representativeClaim) {
78485
78763
  lines.push("");
78486
78764
  lines.push(`_Binding window: ${q.representativeClaim.replace(/_/g, " ")}_`);
@@ -79172,6 +79450,37 @@ function classifyModelSwitchConfirmation(input) {
79172
79450
  }
79173
79451
  return { kind: "default", launched: revertedTo };
79174
79452
  }
79453
+ function formatModelRelaunchDiagLog(input) {
79454
+ const { agent, launched, configured, confirmation, isApplyBoot } = input;
79455
+ const L = launched || "(none)";
79456
+ if (confirmation == null) {
79457
+ return "telegram gateway: gw /model relaunch applied agent=" + agent + " launched=" + L + " configured=" + configured + " override=" + (isApplyBoot ? "set" : "cleared") + `
79458
+ `;
79459
+ }
79460
+ if (confirmation.kind === "not-applied") {
79461
+ return "telegram gateway: gw /model relaunch NOT-APPLIED agent=" + agent + " target=" + confirmation.target + " launched=" + L + " configured=" + configured + " revertedTo=" + confirmation.revertedTo + `
79462
+ `;
79463
+ }
79464
+ if (confirmation.kind === "applied") {
79465
+ return "telegram gateway: gw /model relaunch applied agent=" + agent + " launched=" + L + " configured=" + configured + ` override=set outcome=applied
79466
+ `;
79467
+ }
79468
+ return "telegram gateway: gw /model relaunch applied agent=" + agent + " launched=" + L + " configured=" + configured + ` override=cleared outcome=default
79469
+ `;
79470
+ }
79471
+ function formatModelSwitchConfirmationBody(confirmation) {
79472
+ if (confirmation.kind === "applied") {
79473
+ return "\u2705 Now running `" + confirmation.launched + "` \u2014 session-only, reverts to the configured model on the next restart. Fresh session; memory and the handoff briefing carry the context.";
79474
+ }
79475
+ if (confirmation.kind === "not-applied") {
79476
+ return "\u26a0\ufe0f Your switch to `" + confirmation.target + "` didn't apply \u2014 the agent reverted to `" + confirmation.revertedTo + "` (the apply-boot didn't complete). Re-issue `/model " + confirmation.target + "` to try again.";
79477
+ }
79478
+ return "\u2705 Now running `" + confirmation.launched + "` (the configured default) \u2014 fresh session; memory and the handoff briefing carry the context.";
79479
+ }
79480
+ function formatModelRelaunchSuppressNotAppliedLog(input) {
79481
+ return "telegram gateway: gw /model relaunch NOT-APPLIED \u2014 suppressing not-applied confirmation (a .session-model-alert is present and will be relayed) agent=" + input.agent + " target=" + input.target + `
79482
+ `;
79483
+ }
79175
79484
  function resolveStaleAwareBusy(input) {
79176
79485
  const turnStale = input.currentTurnActive && input.turnAgeMs !== null && input.turnAgeMs > input.hardTtlMs;
79177
79486
  const approvalLive = input.oldestPendingApprovalAgeMs !== null && input.oldestPendingApprovalAgeMs <= input.hardTtlMs;
@@ -80073,7 +80382,7 @@ var HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.
80073
80382
  var HINDSIGHT_HEALTHCHECK_CMD = `python3 -c '${HINDSIGHT_HEALTHCHECK_PY}'`;
80074
80383
 
80075
80384
  // ../src/memory/hindsight.ts
80076
- var DEFAULT_RETAIN_MISSION = "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise.";
80385
+ var DEFAULT_RETAIN_MISSION = "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise, " + "including in-flight workflow/process narration (a sub-task started, " + "paused, or is still running) \u2014 only retain the outcome once a task " + "actually completes or a decision is made.";
80077
80386
  var PROFILE_MEMORY_DEFAULTS = {
80078
80387
  "health-coach": {
80079
80388
  disposition: { skepticism: 2, literalism: 2, empathy: 5 },
@@ -84201,6 +84510,17 @@ function withStoreLock(path2, fn) {
84201
84510
  }));
84202
84511
  return run3;
84203
84512
  }
84513
+ var pinReconcileTails2 = new Map;
84514
+ function withPinReconcileLock(pinKey, fn) {
84515
+ const prev = pinReconcileTails2.get(pinKey) ?? Promise.resolve();
84516
+ const run3 = prev.then(fn, fn);
84517
+ pinReconcileTails2.set(pinKey, run3.then(() => {
84518
+ return;
84519
+ }, () => {
84520
+ return;
84521
+ }));
84522
+ return run3;
84523
+ }
84204
84524
  function applyStatusPinRow(path2, fs2, pinKey, row, log) {
84205
84525
  const current = loadStatusPins(path2, fs2);
84206
84526
  const others = current.filter((p) => p.pinKey !== pinKey);
@@ -84227,7 +84547,11 @@ function reconcileAndPersistStatusPin(args) {
84227
84547
  return next2;
84228
84548
  }
84229
84549
  const next = await args.applyPin();
84230
- applyStatusPinRow(path2, fs2, pinKey, null, log);
84550
+ if (next == null) {
84551
+ applyStatusPinRow(path2, fs2, pinKey, null, log);
84552
+ } else {
84553
+ applyStatusPinRow(path2, fs2, pinKey, { pinKey, chatId, messageId: next.messageId }, log);
84554
+ }
84231
84555
  return next;
84232
84556
  });
84233
84557
  }
@@ -92377,10 +92701,10 @@ function effectiveTurnAgeMs(markerAgeMs, turnStartedAt, now) {
92377
92701
  }
92378
92702
 
92379
92703
  // ../src/build-info.ts
92380
- var VERSION = "0.19.0";
92381
- var COMMIT_SHA = "9709a540";
92382
- var COMMIT_DATE = "2026-07-19T03:15:55Z";
92383
- var LATEST_PR = 3404;
92704
+ var VERSION = "0.19.2";
92705
+ var COMMIT_SHA = "1fa69736";
92706
+ var COMMIT_DATE = "2026-07-19T09:11:44Z";
92707
+ var LATEST_PR = 3425;
92384
92708
  var COMMITS_AHEAD_OF_TAG = 0;
92385
92709
 
92386
92710
  // gateway/boot-version.ts
@@ -97645,7 +97969,7 @@ var midSessionCardReaper = isGatewayMain ? setInterval(() => {
97645
97969
  midSessionCardReaper?.unref();
97646
97970
  async function reconcileStatusPin(pinKey, chatId, desired) {
97647
97971
  try {
97648
- await reconcileStatusPinInner(pinKey, chatId, desired);
97972
+ await withPinReconcileLock(pinKey, () => reconcileStatusPinInner(pinKey, chatId, desired));
97649
97973
  } catch (err) {
97650
97974
  const msg = err instanceof Error ? err.message : String(err);
97651
97975
  process.stderr.write(`telegram gateway: status-pin reconcile absorbed error (key=${pinKey} chat=${chatId}): ${msg}
@@ -100546,6 +100870,10 @@ function isAuthorizedSender(ctx) {
100546
100870
  }
100547
100871
  return false;
100548
100872
  }
100873
+ function shouldMaskAccountLabels(ctx) {
100874
+ const groupAllowFrom = ctx.chat?.type === "group" || ctx.chat?.type === "supergroup" ? loadAccess().groups[String(ctx.chat.id)]?.allowFrom : undefined;
100875
+ return shouldMaskUsageLabels(ctx.chat?.type, groupAllowFrom);
100876
+ }
100549
100877
  var __inboundRouterTestSeam = {
100550
100878
  activeStatusReactions,
100551
100879
  activeTurnStartedAt,
@@ -104403,7 +104731,7 @@ Send \`/auth ${parsed.provider} cancel\` to abort.`, { html: true });
104403
104731
  bot2.command("usage", async (ctx) => {
104404
104732
  if (!isAuthorizedSender(ctx))
104405
104733
  return;
104406
- const demo = hasDemoFlag(getCommandArgs(ctx));
104734
+ const demo = hasDemoFlag(getCommandArgs(ctx)) || shouldMaskAccountLabels(ctx);
104407
104735
  const currentAgent = getMyAgentName();
104408
104736
  try {
104409
104737
  const client3 = await getAuthBrokerClient2(currentAgent);
@@ -104416,11 +104744,14 @@ Send \`/auth ${parsed.provider} cancel\` to abort.`, { html: true });
104416
104744
  const { buildSnapshotsFromState: buildSnapshotsFromState4, buildSnapshotKeyboard: buildSnapshotKeyboard3 } = await Promise.resolve().then(() => (init_auth_snapshot_format2(), exports_auth_snapshot_format2));
104417
104745
  const { renderUsageCard: renderUsageCard2 } = await Promise.resolve().then(() => (init_quota_bar_format(), exports_quota_bar_format));
104418
104746
  const snapshots = buildSnapshotsFromState4(state7, quotas);
104747
+ const { fetchExternalSpendSummary: fetchExternalSpendSummary2 } = await Promise.resolve().then(() => (init_external_spend3(), exports_external_spend));
104748
+ const externalSpend = await fetchExternalSpendSummary2(renderNow).catch(() => null);
104419
104749
  const exhaustedByLabel = new Map(state7.accounts.map((a) => [a.label, a.exhausted]));
104420
104750
  const text5 = renderUsageCard2(snapshots, exhaustedByLabel, {
104421
104751
  now: renderNow,
104422
104752
  demo,
104423
- ...staleCachedAtMs != null ? { staleCachedAtMs } : probeResp.results.length > 0 ? { liveProbedAtMs: renderNow.getTime() } : { probeFailed: true }
104753
+ externalSpend,
104754
+ ...deriveUsageFooterFreshness2(probeResp.results, staleCachedAtMs, renderNow.getTime())
104424
104755
  });
104425
104756
  let kbRows = buildSnapshotKeyboard3(snapshots, { now: new Date, demo });
104426
104757
  if (ctx.chat?.type !== "private") {
@@ -105750,21 +106081,28 @@ async function startGateway() {
105750
106081
  })();
105751
106082
  const isApplyBoot = launched.length > 0 && launched !== configured;
105752
106083
  sessionModelSource.setOverride(isApplyBoot ? launched : null);
105753
- process.stderr.write(`telegram gateway: gw /model relaunch applied agent=${getMyAgentName()} launched=${launched || "(none)"} configured=${configured} override=${isApplyBoot ? "set" : "cleared"}
105754
- `);
105755
- if (modelSwitchReason != null && modelSwitchMarkerChat) {
106084
+ const confirmation = modelSwitchReason != null ? classifyModelSwitchConfirmation({
106085
+ reason: modelSwitchReason,
106086
+ launched,
106087
+ configured
106088
+ }) : null;
106089
+ process.stderr.write(formatModelRelaunchDiagLog({
106090
+ agent: getMyAgentName(),
106091
+ launched,
106092
+ configured,
106093
+ confirmation,
106094
+ isApplyBoot
106095
+ }));
106096
+ if (confirmation != null && modelSwitchMarkerChat) {
105756
106097
  const chat = modelSwitchMarkerChat;
105757
- const confirmation = classifyModelSwitchConfirmation({
105758
- reason: modelSwitchReason,
105759
- launched,
105760
- configured
105761
- });
105762
106098
  const hasSessionModelAlert = existsSync54(join59(smAgentDir, ".session-model-alert"));
105763
106099
  if (confirmation.kind === "not-applied" && hasSessionModelAlert) {
105764
- process.stderr.write(`telegram gateway: gw /model relaunch applied \u2014 suppressing not-applied confirmation (a .session-model-alert is present and will be relayed) agent=${getMyAgentName()} target=${confirmation.target}
105765
- `);
106100
+ process.stderr.write(formatModelRelaunchSuppressNotAppliedLog({
106101
+ agent: getMyAgentName(),
106102
+ target: confirmation.target
106103
+ }));
105766
106104
  } else {
105767
- const body = confirmation.kind === "applied" ? `\u2705 Now running \`${confirmation.launched}\` \u2014 session-only, reverts to the configured model on the next restart. Fresh session; memory and the handoff briefing carry the context.` : confirmation.kind === "not-applied" ? `\u26A0\uFE0F Your switch to \`${confirmation.target}\` didn't apply \u2014 the agent reverted to \`${confirmation.revertedTo}\` (the apply-boot didn't complete). Re-issue \`/model ${confirmation.target}\` to try again.` : `\u2705 Now running \`${confirmation.launched}\` (the configured default) \u2014 fresh session; memory and the handoff briefing carry the context.`;
106105
+ const body = formatModelSwitchConfirmationBody(confirmation);
105768
106106
  lockedBot.api.sendMessage(chat.chatId, body, {
105769
106107
  parse_mode: "Markdown",
105770
106108
  ...chat.threadId != null ? { message_thread_id: chat.threadId } : {}