switchroom 0.13.13 → 0.13.15

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.
@@ -16565,7 +16565,7 @@ class AuthBrokerClient {
16565
16565
  const id = req.id;
16566
16566
  const frame = encodeRequest(req);
16567
16567
  return new Promise((resolve2, reject) => {
16568
- const timer2 = setTimeout(() => {
16568
+ const timer3 = setTimeout(() => {
16569
16569
  this.pending.delete(id);
16570
16570
  reject(new AuthBrokerUnreachableError(`request ${req.op} timed out after ${this.timeoutMs}ms`, this.socketPath));
16571
16571
  }, this.timeoutMs);
@@ -16578,7 +16578,7 @@ class AuthBrokerClient {
16578
16578
  }
16579
16579
  },
16580
16580
  reject,
16581
- timer: timer2
16581
+ timer: timer3
16582
16582
  });
16583
16583
  sock.write(frame, (err) => {
16584
16584
  if (err) {
@@ -25066,12 +25066,12 @@ async function rpc(req, opts) {
25066
25066
  resolve5(val);
25067
25067
  };
25068
25068
  const client3 = new net3.Socket;
25069
- const timer2 = setTimeout(() => {
25069
+ const timer3 = setTimeout(() => {
25070
25070
  client3.destroy();
25071
25071
  settle({ kind: "unreachable", msg: `broker did not respond within ${timeoutMs}ms` });
25072
25072
  }, timeoutMs);
25073
25073
  client3.on("error", (err) => {
25074
- clearTimeout(timer2);
25074
+ clearTimeout(timer3);
25075
25075
  const code = err.code ?? "ERR";
25076
25076
  let msg;
25077
25077
  if (code === "ENOENT")
@@ -25091,7 +25091,7 @@ async function rpc(req, opts) {
25091
25091
  `);
25092
25092
  if (newlineIdx !== -1) {
25093
25093
  const line = buffer.slice(0, newlineIdx).trimEnd();
25094
- clearTimeout(timer2);
25094
+ clearTimeout(timer3);
25095
25095
  client3.destroy();
25096
25096
  try {
25097
25097
  const resp = decodeResponse2(line);
@@ -25108,7 +25108,7 @@ async function rpc(req, opts) {
25108
25108
  try {
25109
25109
  client3.write(encodeRequest2(req));
25110
25110
  } catch (err) {
25111
- clearTimeout(timer2);
25111
+ clearTimeout(timer3);
25112
25112
  client3.destroy();
25113
25113
  settle({
25114
25114
  kind: "unreachable",
@@ -27352,14 +27352,14 @@ import { join as join21 } from "path";
27352
27352
  import { execFile as execFileCb } from "child_process";
27353
27353
  import { promisify as promisify3 } from "util";
27354
27354
  async function withTimeout(label, p, timeoutMs = PROBE_TIMEOUT_MS) {
27355
- let timer2;
27355
+ let timer3;
27356
27356
  const timeout = new Promise((resolve6) => {
27357
- timer2 = setTimeout(() => resolve6({ status: "fail", label, detail: "timed out" }), timeoutMs);
27357
+ timer3 = setTimeout(() => resolve6({ status: "fail", label, detail: "timed out" }), timeoutMs);
27358
27358
  });
27359
27359
  try {
27360
27360
  return await Promise.race([p, timeout]);
27361
27361
  } finally {
27362
- clearTimeout(timer2);
27362
+ clearTimeout(timer3);
27363
27363
  }
27364
27364
  }
27365
27365
  function formatMs(ms) {
@@ -28163,7 +28163,7 @@ function diffProbes(probes, cache, opts = {}) {
28163
28163
  const snoozeBoots = opts.snoozeBoots ?? DEFAULT_SNOOZE_BOOTS;
28164
28164
  const snoozeMs = opts.snoozeMs ?? DEFAULT_SNOOZE_MS;
28165
28165
  const now = opts.now ?? Date.now;
28166
- const nowMs = now();
28166
+ const nowMs2 = now();
28167
28167
  const out = {};
28168
28168
  for (const [key, r] of Object.entries(probes)) {
28169
28169
  if (!r)
@@ -28182,12 +28182,12 @@ function diffProbes(probes, cache, opts = {}) {
28182
28182
  continue;
28183
28183
  }
28184
28184
  let consecutiveBoots = 1;
28185
- let firstSeenMs = nowMs;
28185
+ let firstSeenMs = nowMs2;
28186
28186
  if (prev != null && prev.fingerprint === fp) {
28187
28187
  consecutiveBoots = prev.consecutiveBoots + 1;
28188
28188
  firstSeenMs = prev.firstSeenMs;
28189
28189
  }
28190
- const ageMs = nowMs - firstSeenMs;
28190
+ const ageMs = nowMs2 - firstSeenMs;
28191
28191
  const snoozed = consecutiveBoots > snoozeBoots || ageMs >= snoozeMs;
28192
28192
  out[key] = {
28193
28193
  fingerprint: fp,
@@ -28198,7 +28198,7 @@ function diffProbes(probes, cache, opts = {}) {
28198
28198
  fingerprint: fp,
28199
28199
  consecutiveBoots,
28200
28200
  firstSeenMs,
28201
- lastSeenMs: nowMs
28201
+ lastSeenMs: nowMs2
28202
28202
  }
28203
28203
  };
28204
28204
  }
@@ -37296,6 +37296,144 @@ function startTimer(deps) {
37296
37296
  timer.unref();
37297
37297
  }
37298
37298
 
37299
+ // pending-work-progress.ts
37300
+ var EDIT_INTERVAL_MS = 60000;
37301
+ var POLL_INTERVAL_MS = 5000;
37302
+ var MAX_LIFETIME_MS = 30 * 60000;
37303
+ var TELEGRAM_MSG_CAP = 4000;
37304
+ var SUFFIX_RE = /\n\n\u2014 still working \(\d+m\)$/;
37305
+ var stateByKey = new Map;
37306
+ var timer2 = null;
37307
+ var activeDeps2 = null;
37308
+ function enabled2() {
37309
+ const v = process.env.SWITCHROOM_DISABLE_PENDING_PROGRESS;
37310
+ return !(v === "1" || v === "true");
37311
+ }
37312
+ function nowMs() {
37313
+ return activeDeps2?.nowMs ? activeDeps2.nowMs() : Date.now();
37314
+ }
37315
+ function ensure(key) {
37316
+ let s = stateByKey.get(key);
37317
+ if (!s) {
37318
+ s = {
37319
+ pending: false,
37320
+ anchorMessageId: null,
37321
+ anchorOriginalText: "",
37322
+ activatedAt: null,
37323
+ lastEditAt: null
37324
+ };
37325
+ stateByKey.set(key, s);
37326
+ }
37327
+ return s;
37328
+ }
37329
+ function noteAsyncDispatch(key) {
37330
+ if (!enabled2())
37331
+ return;
37332
+ ensure(key).pending = true;
37333
+ }
37334
+ function noteOutbound3(key, opts) {
37335
+ if (!enabled2())
37336
+ return;
37337
+ const s = ensure(key);
37338
+ s.anchorMessageId = opts.messageId;
37339
+ s.anchorOriginalText = opts.text.replace(SUFFIX_RE, "");
37340
+ }
37341
+ function noteTurnEnd(key) {
37342
+ if (!enabled2())
37343
+ return;
37344
+ const s = stateByKey.get(key);
37345
+ if (s == null)
37346
+ return;
37347
+ if (s.pending && s.anchorMessageId != null) {
37348
+ s.activatedAt = nowMs();
37349
+ s.lastEditAt = s.activatedAt;
37350
+ activeDeps2?.emitMetric?.({
37351
+ kind: "pending_progress_started",
37352
+ chatKey: key
37353
+ });
37354
+ } else {
37355
+ stateByKey.delete(key);
37356
+ }
37357
+ }
37358
+ function clearPending(key, reason) {
37359
+ if (!stateByKey.has(key))
37360
+ return;
37361
+ const s = stateByKey.get(key);
37362
+ const elapsed = s.activatedAt != null ? nowMs() - s.activatedAt : 0;
37363
+ stateByKey.delete(key);
37364
+ activeDeps2?.emitMetric?.({
37365
+ kind: "pending_progress_cleared",
37366
+ chatKey: key,
37367
+ elapsedMs: elapsed,
37368
+ reason
37369
+ });
37370
+ }
37371
+ function startTimer2(deps) {
37372
+ if (!enabled2())
37373
+ return;
37374
+ if (timer2 != null)
37375
+ return;
37376
+ activeDeps2 = deps;
37377
+ const interval = deps.pollIntervalMs ?? POLL_INTERVAL_MS;
37378
+ timer2 = setInterval(() => tick2(nowMs()), interval);
37379
+ if (typeof timer2.unref === "function")
37380
+ timer2.unref();
37381
+ }
37382
+ function parseKey2(key) {
37383
+ const idx = key.indexOf(":");
37384
+ if (idx < 0)
37385
+ return { chatId: key, threadId: null };
37386
+ const chatId = key.slice(0, idx);
37387
+ const tail = key.slice(idx + 1);
37388
+ if (tail === "" || tail === "undefined")
37389
+ return { chatId, threadId: null };
37390
+ const n = Number(tail);
37391
+ return { chatId, threadId: Number.isFinite(n) ? n : null };
37392
+ }
37393
+ function tick2(now) {
37394
+ if (activeDeps2 == null)
37395
+ return;
37396
+ for (const [key, s] of stateByKey.entries()) {
37397
+ if (s.activatedAt == null || s.anchorMessageId == null)
37398
+ continue;
37399
+ const elapsed = now - s.activatedAt;
37400
+ if (elapsed >= MAX_LIFETIME_MS) {
37401
+ clearPending(key, "timeout");
37402
+ continue;
37403
+ }
37404
+ const sinceEdit = s.lastEditAt == null ? 0 : now - s.lastEditAt;
37405
+ if (sinceEdit < EDIT_INTERVAL_MS)
37406
+ continue;
37407
+ const minutes = Math.max(1, Math.round(elapsed / 60000));
37408
+ const suffix = `
37409
+
37410
+ \u2014 still working (${minutes}m)`;
37411
+ const newText = s.anchorOriginalText + suffix;
37412
+ if (newText.length > TELEGRAM_MSG_CAP) {
37413
+ s.lastEditAt = now;
37414
+ continue;
37415
+ }
37416
+ const { chatId, threadId } = parseKey2(key);
37417
+ s.lastEditAt = now;
37418
+ const editCtx = {
37419
+ chatId,
37420
+ threadId,
37421
+ messageId: s.anchorMessageId,
37422
+ newText
37423
+ };
37424
+ Promise.resolve().then(() => activeDeps2.editMessage(editCtx)).then(() => {
37425
+ activeDeps2.emitMetric?.({
37426
+ kind: "pending_progress_edited",
37427
+ chatKey: key,
37428
+ elapsedMs: elapsed
37429
+ });
37430
+ }).catch((err) => {
37431
+ process.stderr.write(`pending-work-progress: edit failed key=${key} ` + `msg=${editCtx.messageId}: ${err.message}
37432
+ `);
37433
+ });
37434
+ }
37435
+ }
37436
+
37299
37437
  // silent-end.ts
37300
37438
  import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync, mkdirSync as mkdirSync5 } from "node:fs";
37301
37439
  import { dirname as dirname6, join as join6 } from "node:path";
@@ -38956,7 +39094,7 @@ class AuthBrokerClient2 {
38956
39094
  const id = req.id;
38957
39095
  const frame = encodeRequest(req);
38958
39096
  return new Promise((resolve2, reject) => {
38959
- const timer2 = setTimeout(() => {
39097
+ const timer3 = setTimeout(() => {
38960
39098
  this.pending.delete(id);
38961
39099
  reject(new AuthBrokerUnreachableError2(`request ${req.op} timed out after ${this.timeoutMs}ms`, this.socketPath));
38962
39100
  }, this.timeoutMs);
@@ -38969,7 +39107,7 @@ class AuthBrokerClient2 {
38969
39107
  }
38970
39108
  },
38971
39109
  reject,
38972
- timer: timer2
39110
+ timer: timer3
38973
39111
  });
38974
39112
  sock.write(frame, (err) => {
38975
39113
  if (err) {
@@ -39122,7 +39260,7 @@ async function startAccountAuthSession(label, opts = {}) {
39122
39260
  cleanup();
39123
39261
  reject(new Error(`claude setup-token did not print an OAuth URL within ${urlTimeoutMs}ms`));
39124
39262
  }, urlTimeoutMs);
39125
- const tick2 = setInterval(() => {
39263
+ const tick3 = setInterval(() => {
39126
39264
  const url = parseSetupTokenUrl(buffer);
39127
39265
  if (url) {
39128
39266
  cleanup();
@@ -39136,7 +39274,7 @@ async function startAccountAuthSession(label, opts = {}) {
39136
39274
  child.once("exit", onExit);
39137
39275
  function cleanup() {
39138
39276
  clearTimeout(deadline);
39139
- clearInterval(tick2);
39277
+ clearInterval(tick3);
39140
39278
  child.removeListener("exit", onExit);
39141
39279
  }
39142
39280
  }).catch((err) => {
@@ -39794,7 +39932,7 @@ function startRestartWatchdog(config) {
39794
39932
  }
39795
39933
  const unit = `switchroom-${agentName3}`;
39796
39934
  let snapshot = null;
39797
- function tick2() {
39935
+ function tick3() {
39798
39936
  let raw;
39799
39937
  try {
39800
39938
  raw = execShow(unit);
@@ -39824,14 +39962,14 @@ function startRestartWatchdog(config) {
39824
39962
  log?.(`restart-watchdog: tick agent=${agentName3} ${decision.detail}`);
39825
39963
  }
39826
39964
  }
39827
- tick2();
39828
- const timer2 = setInterval(tick2, pollIntervalMs);
39829
- if (typeof timer2.unref === "function")
39830
- timer2.unref();
39965
+ tick3();
39966
+ const timer3 = setInterval(tick3, pollIntervalMs);
39967
+ if (typeof timer3.unref === "function")
39968
+ timer3.unref();
39831
39969
  log?.(`restart-watchdog: started agent=${agentName3} unit=${unit} interval=${pollIntervalMs}ms`);
39832
39970
  return {
39833
39971
  stop() {
39834
- clearInterval(timer2);
39972
+ clearInterval(timer3);
39835
39973
  }
39836
39974
  };
39837
39975
  }
@@ -41186,11 +41324,11 @@ class BrokerCredentialsExpiredError extends Error {
41186
41324
  account;
41187
41325
  expiresAt;
41188
41326
  nowMs;
41189
- constructor(account, expiresAt, nowMs) {
41190
- super(`Google credentials for account '${account}' expired at ${new Date(expiresAt).toISOString()} (now ${new Date(nowMs).toISOString()}). Re-run \`switchroom auth google account add ${account}\` to mint fresh tokens.`);
41327
+ constructor(account, expiresAt, nowMs2) {
41328
+ super(`Google credentials for account '${account}' expired at ${new Date(expiresAt).toISOString()} (now ${new Date(nowMs2).toISOString()}). Re-run \`switchroom auth google account add ${account}\` to mint fresh tokens.`);
41191
41329
  this.account = account;
41192
41330
  this.expiresAt = expiresAt;
41193
- this.nowMs = nowMs;
41331
+ this.nowMs = nowMs2;
41194
41332
  this.name = "BrokerCredentialsExpiredError";
41195
41333
  }
41196
41334
  }
@@ -41898,7 +42036,7 @@ class InjectError extends Error {
41898
42036
  this.code = code;
41899
42037
  }
41900
42038
  }
41901
- var POLL_INTERVAL_MS = 150;
42039
+ var POLL_INTERVAL_MS2 = 150;
41902
42040
  var OUTPUT_BYTE_CAP = 3000;
41903
42041
  function validateInjectCommand(command) {
41904
42042
  if (typeof command !== "string" || command.trim().length === 0) {
@@ -42067,12 +42205,12 @@ async function injectSlashCommandWith(runner, args) {
42067
42205
  let last = before;
42068
42206
  let stableSince = null;
42069
42207
  while (Date.now() - start < timeoutMs) {
42070
- await sleep(POLL_INTERVAL_MS);
42208
+ await sleep(POLL_INTERVAL_MS2);
42071
42209
  const cur = runner.capture(socket, session) ?? "";
42072
42210
  if (cur === last && cur !== before) {
42073
42211
  if (stableSince === null) {
42074
42212
  stableSince = Date.now();
42075
- } else if (Date.now() - stableSince >= POLL_INTERVAL_MS) {
42213
+ } else if (Date.now() - stableSince >= POLL_INTERVAL_MS2) {
42076
42214
  last = cur;
42077
42215
  break;
42078
42216
  }
@@ -43021,7 +43159,7 @@ async function hostdRequest(opts, req) {
43021
43159
  const socket = connect(opts.socketPath);
43022
43160
  let buf = "";
43023
43161
  let settled = false;
43024
- const timer2 = setTimeout(() => {
43162
+ const timer3 = setTimeout(() => {
43025
43163
  if (settled)
43026
43164
  return;
43027
43165
  settled = true;
@@ -43035,7 +43173,7 @@ async function hostdRequest(opts, req) {
43035
43173
  if (settled)
43036
43174
  return;
43037
43175
  settled = true;
43038
- clearTimeout(timer2);
43176
+ clearTimeout(timer3);
43039
43177
  socket.destroy();
43040
43178
  reject(err);
43041
43179
  }
@@ -43046,7 +43184,7 @@ async function hostdRequest(opts, req) {
43046
43184
  if (settled)
43047
43185
  return;
43048
43186
  settled = true;
43049
- clearTimeout(timer2);
43187
+ clearTimeout(timer3);
43050
43188
  socket.destroy();
43051
43189
  reject(new Error("hostd: response exceeded frame budget"));
43052
43190
  return;
@@ -43061,14 +43199,14 @@ async function hostdRequest(opts, req) {
43061
43199
  if (settled)
43062
43200
  return;
43063
43201
  settled = true;
43064
- clearTimeout(timer2);
43202
+ clearTimeout(timer3);
43065
43203
  socket.end();
43066
43204
  resolve6(resp);
43067
43205
  } catch (err) {
43068
43206
  if (settled)
43069
43207
  return;
43070
43208
  settled = true;
43071
- clearTimeout(timer2);
43209
+ clearTimeout(timer3);
43072
43210
  socket.destroy();
43073
43211
  reject(new Error(`hostd: bad response frame: ${err.message}`, { cause: err }));
43074
43212
  }
@@ -43077,14 +43215,14 @@ async function hostdRequest(opts, req) {
43077
43215
  if (settled)
43078
43216
  return;
43079
43217
  settled = true;
43080
- clearTimeout(timer2);
43218
+ clearTimeout(timer3);
43081
43219
  reject(err);
43082
43220
  });
43083
43221
  socket.on("close", () => {
43084
43222
  if (settled)
43085
43223
  return;
43086
43224
  settled = true;
43087
- clearTimeout(timer2);
43225
+ clearTimeout(timer3);
43088
43226
  reject(new Error("hostd: connection closed before response"));
43089
43227
  });
43090
43228
  });
@@ -44448,9 +44586,9 @@ function transition(state3, event) {
44448
44586
 
44449
44587
  // gateway/inbound-delivery-machine-shadow.ts
44450
44588
  var state3 = initialState();
44451
- var enabled2 = process.env.SWITCHROOM_DELIVERY_MACHINE_SHADOW !== "0";
44589
+ var enabled3 = process.env.SWITCHROOM_DELIVERY_MACHINE_SHADOW !== "0";
44452
44590
  function shadowEmit(event) {
44453
- if (!enabled2)
44591
+ if (!enabled3)
44454
44592
  return [];
44455
44593
  try {
44456
44594
  const result = transition(state3, event);
@@ -44508,12 +44646,12 @@ function redeliverBufferedInbound2(buffer, agent, send, spool) {
44508
44646
  }
44509
44647
 
44510
44648
  // gateway/inbound-delivery-machine-dispatch.ts
44511
- var enabled3 = process.env.SWITCHROOM_DELIVERY_MACHINE_CUTOVER !== "0";
44649
+ var enabled4 = process.env.SWITCHROOM_DELIVERY_MACHINE_CUTOVER !== "0";
44512
44650
  function isDispatchEnabled() {
44513
- return enabled3;
44651
+ return enabled4;
44514
44652
  }
44515
44653
  function dispatchEffects(effects, ctx) {
44516
- if (!enabled3)
44654
+ if (!enabled4)
44517
44655
  return;
44518
44656
  for (const effect of effects) {
44519
44657
  dispatchOne(effect, ctx);
@@ -44822,10 +44960,10 @@ function createPollHealthCheck(options) {
44822
44960
  setIntervalFn = setInterval,
44823
44961
  clearIntervalFn = clearInterval
44824
44962
  } = options;
44825
- let timer2 = null;
44963
+ let timer3 = null;
44826
44964
  let failures = 0;
44827
44965
  let active = false;
44828
- async function tick2() {
44966
+ async function tick3() {
44829
44967
  try {
44830
44968
  await ping();
44831
44969
  if (failures > 0) {
@@ -44849,9 +44987,9 @@ function createPollHealthCheck(options) {
44849
44987
  }
44850
44988
  function doStop() {
44851
44989
  active = false;
44852
- if (timer2 !== null) {
44853
- clearIntervalFn(timer2);
44854
- timer2 = null;
44990
+ if (timer3 !== null) {
44991
+ clearIntervalFn(timer3);
44992
+ timer3 = null;
44855
44993
  }
44856
44994
  }
44857
44995
  return {
@@ -44860,11 +44998,11 @@ function createPollHealthCheck(options) {
44860
44998
  return;
44861
44999
  active = true;
44862
45000
  failures = 0;
44863
- timer2 = setIntervalFn(() => {
44864
- tick2();
45001
+ timer3 = setIntervalFn(() => {
45002
+ tick3();
44865
45003
  }, intervalMs);
44866
- if (typeof timer2?.unref === "function") {
44867
- timer2.unref();
45004
+ if (typeof timer3?.unref === "function") {
45005
+ timer3.unref();
44868
45006
  }
44869
45007
  log(`telegram gateway: poll.health_check started interval=${intervalMs}ms threshold=${failureThreshold}`);
44870
45008
  },
@@ -47603,7 +47741,7 @@ function startIssuesWatcher(opts) {
47603
47741
  log(`issues-watcher: refresh failed: ${err.message}`);
47604
47742
  }
47605
47743
  }
47606
- async function tick2() {
47744
+ async function tick3() {
47607
47745
  if (stopped)
47608
47746
  return;
47609
47747
  const signature = signatureProvider(path);
@@ -47612,25 +47750,25 @@ function startIssuesWatcher(opts) {
47612
47750
  lastSignature = signature;
47613
47751
  await readAndRefresh();
47614
47752
  }
47615
- tick2().catch((err) => {
47753
+ tick3().catch((err) => {
47616
47754
  log(`issues-watcher: initial tick failed: ${err.message}`);
47617
47755
  });
47618
- const timer2 = setIntervalFn(() => {
47619
- tick2().catch((err) => {
47756
+ const timer3 = setIntervalFn(() => {
47757
+ tick3().catch((err) => {
47620
47758
  log(`issues-watcher: tick failed: ${err.message}`);
47621
47759
  });
47622
47760
  }, intervalMs);
47623
- if (typeof timer2.unref === "function") {
47624
- timer2.unref();
47761
+ if (typeof timer3.unref === "function") {
47762
+ timer3.unref();
47625
47763
  }
47626
47764
  return {
47627
47765
  stop() {
47628
47766
  if (stopped)
47629
47767
  return;
47630
47768
  stopped = true;
47631
- clearIntervalFn(timer2);
47769
+ clearIntervalFn(timer3);
47632
47770
  },
47633
- tick: tick2
47771
+ tick: tick3
47634
47772
  };
47635
47773
  }
47636
47774
  function defaultSignatureProvider(path) {
@@ -48016,11 +48154,11 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
48016
48154
  }
48017
48155
 
48018
48156
  // ../src/build-info.ts
48019
- var VERSION = "0.13.13";
48020
- var COMMIT_SHA = "dc583d57";
48021
- var COMMIT_DATE = "2026-05-22T22:02:14+10:00";
48022
- var LATEST_PR = null;
48023
- var COMMITS_AHEAD_OF_TAG = 3;
48157
+ var VERSION = "0.13.15";
48158
+ var COMMIT_SHA = "bc0b5540";
48159
+ var COMMIT_DATE = "2026-05-23T02:55:43Z";
48160
+ var LATEST_PR = 1673;
48161
+ var COMMITS_AHEAD_OF_TAG = 0;
48024
48162
 
48025
48163
  // gateway/boot-version.ts
48026
48164
  function formatRelativeAgo(iso) {
@@ -48119,12 +48257,12 @@ async function rpc2(req, opts) {
48119
48257
  resolve7(val);
48120
48258
  };
48121
48259
  const client3 = new net4.Socket;
48122
- const timer2 = setTimeout(() => {
48260
+ const timer3 = setTimeout(() => {
48123
48261
  client3.destroy();
48124
48262
  settle({ kind: "unreachable", msg: `broker did not respond within ${timeoutMs}ms` });
48125
48263
  }, timeoutMs);
48126
48264
  client3.on("error", (err) => {
48127
- clearTimeout(timer2);
48265
+ clearTimeout(timer3);
48128
48266
  const code = err.code ?? "ERR";
48129
48267
  let msg;
48130
48268
  if (code === "ENOENT")
@@ -48144,7 +48282,7 @@ async function rpc2(req, opts) {
48144
48282
  `);
48145
48283
  if (newlineIdx !== -1) {
48146
48284
  const line = buffer.slice(0, newlineIdx).trimEnd();
48147
- clearTimeout(timer2);
48285
+ clearTimeout(timer3);
48148
48286
  client3.destroy();
48149
48287
  try {
48150
48288
  const resp = decodeResponse2(line);
@@ -48161,7 +48299,7 @@ async function rpc2(req, opts) {
48161
48299
  try {
48162
48300
  client3.write(encodeRequest2(req));
48163
48301
  } catch (err) {
48164
- clearTimeout(timer2);
48302
+ clearTimeout(timer3);
48165
48303
  client3.destroy();
48166
48304
  settle({
48167
48305
  kind: "unreachable",
@@ -48208,13 +48346,13 @@ async function unlockViaBroker(passphrase, opts) {
48208
48346
  settled = true;
48209
48347
  resolve7(val);
48210
48348
  };
48211
- const timer2 = setTimeout(() => {
48349
+ const timer3 = setTimeout(() => {
48212
48350
  client3.destroy();
48213
48351
  settle({ ok: false, msg: "Timeout waiting for broker" });
48214
48352
  }, timeoutMs);
48215
48353
  const client3 = net4.createConnection({ path: unlockSocketPath });
48216
48354
  client3.on("error", (err) => {
48217
- clearTimeout(timer2);
48355
+ clearTimeout(timer3);
48218
48356
  settle({ ok: false, msg: `Broker unreachable: ${err.message}` });
48219
48357
  });
48220
48358
  let buffer = "";
@@ -48224,7 +48362,7 @@ async function unlockViaBroker(passphrase, opts) {
48224
48362
  `);
48225
48363
  if (newlineIdx !== -1) {
48226
48364
  const line = buffer.slice(0, newlineIdx).trimEnd();
48227
- clearTimeout(timer2);
48365
+ clearTimeout(timer3);
48228
48366
  client3.destroy();
48229
48367
  if (line === "OK") {
48230
48368
  settle({ ok: true });
@@ -49078,17 +49216,17 @@ async function postCompactCard(occ, cap) {
49078
49216
  const messageId = sent?.message_id;
49079
49217
  if (typeof messageId !== "number")
49080
49218
  return;
49081
- const timer2 = setTimeout(() => {
49219
+ const timer3 = setTimeout(() => {
49082
49220
  resolveCompactCard("timeout", null);
49083
49221
  }, COMPACT_CARD_TIMEOUT_MS);
49084
- timer2.unref?.();
49222
+ timer3.unref?.();
49085
49223
  outstandingCompactCard = {
49086
49224
  chatId,
49087
49225
  threadId,
49088
49226
  messageId,
49089
49227
  occAtStart: occ,
49090
49228
  capAtStart: cap,
49091
- timer: timer2
49229
+ timer: timer3
49092
49230
  };
49093
49231
  } catch (err) {
49094
49232
  process.stderr.write(`telegram gateway: proactive-compact start card failed: ${err instanceof Error ? err.message : String(err)}
@@ -49719,6 +49857,7 @@ var STREAM_THROTTLE_MS_OVERRIDE = (() => {
49719
49857
  return Number.isFinite(n) && n >= 0 ? n : undefined;
49720
49858
  })();
49721
49859
  var TURN_FLUSH_SAFETY_ENABLED = isTurnFlushSafetyEnabled();
49860
+ var ANSWER_STREAM_VISIBLE_ENABLED = process.env.SWITCHROOM_VISIBLE_ANSWER_STREAM === "1" || process.env.SWITCHROOM_VISIBLE_ANSWER_STREAM === "true";
49722
49861
  var progressDriver = null;
49723
49862
  var unpinProgressCardForChat = null;
49724
49863
  var getPinnedProgressCardMessageId = null;
@@ -49871,6 +50010,7 @@ startTimer({
49871
50010
  lastPtyPreviewByChat.delete(fbKey);
49872
50011
  preambleSuppressor.dropNow();
49873
50012
  endTurn(fbKey);
50013
+ noteTurnEnd(fbKey);
49874
50014
  purgeReactionTracking(fbKey);
49875
50015
  const fbExtraPurge = purgeStaleTurnsForChat(fbChatId, activeTurnStartedAt.keys(), purgeReactionTracking);
49876
50016
  if (turnMatchesFallback && currentTurn === wedgedTurn)
@@ -49884,6 +50024,16 @@ startTimer({
49884
50024
  `);
49885
50025
  }
49886
50026
  });
50027
+ startTimer2({
50028
+ editMessage: async (ctx) => {
50029
+ await swallowingApiCall(() => lockedBot.api.editMessageText(ctx.chatId, ctx.messageId, ctx.newText), {
50030
+ chat_id: ctx.chatId,
50031
+ verb: "pending-progress-edit",
50032
+ ...ctx.threadId != null ? { threadId: ctx.threadId } : {}
50033
+ });
50034
+ },
50035
+ emitMetric: (event) => emitRuntimeMetric(event)
50036
+ });
49887
50037
  var inboundSpool = STATIC ? undefined : createInboundSpool({
49888
50038
  path: join32(STATE_DIR, "inbound-spool.jsonl"),
49889
50039
  fs: {
@@ -49973,18 +50123,18 @@ var ipcServer = createIpcServer({
49973
50123
  process.stderr.write(`telegram gateway: bridge-reconnect: skipping boot card (${dedupeDecision.reason})
49974
50124
  `);
49975
50125
  } else {
49976
- const nowMs = Date.now();
50126
+ const nowMs2 = Date.now();
49977
50127
  const marker = readRestartMarker();
49978
50128
  const cleanMarker = readCleanShutdownMarker(GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH);
49979
50129
  const storedSession = readSessionMarker(GATEWAY_SESSION_MARKER_PATH);
49980
- const markerAgeMs = marker ? nowMs - marker.ts : undefined;
50130
+ const markerAgeMs = marker ? nowMs2 - marker.ts : undefined;
49981
50131
  if (marker) {
49982
50132
  const ageSec = Math.max(1, Math.round((markerAgeMs ?? 0) / 1000));
49983
50133
  process.stderr.write(`telegram gateway: bridge-reconnect: restart-marker present, chat_id=${marker.chat_id} age=${ageSec}s agent=${client3.agentName}
49984
50134
  `);
49985
50135
  clearRestartMarker();
49986
50136
  }
49987
- const reason = determineRestartReason({ marker, cleanMarker, sessionMarker: storedSession, now: nowMs });
50137
+ const reason = determineRestartReason({ marker, cleanMarker, sessionMarker: storedSession, now: nowMs2 });
49988
50138
  const target = resolveBootChatId(marker, markerAgeMs);
49989
50139
  if (target) {
49990
50140
  const { chatId, threadId, ackMsgId } = target;
@@ -50113,6 +50263,10 @@ ${reminder}
50113
50263
  if (ev.toolUseId != null && ev.toolUseId.length > 0 && !isTelegramSurfaceTool(ev.toolName)) {
50114
50264
  const label = toolLabel(ev.toolName, ev.input, undefined, ev.precomputedLabel);
50115
50265
  noteToolStart(key, ev.toolUseId, ev.toolName, label.length > 0 ? label : null, Date.now());
50266
+ const evInput = ev.input;
50267
+ if (ev.toolName === "Agent" || ev.toolName === "Task" || ev.toolName === "Bash" && evInput?.run_in_background === true) {
50268
+ noteAsyncDispatch(key);
50269
+ }
50116
50270
  }
50117
50271
  } else if (ev.kind === "tool_result") {
50118
50272
  if (ev.toolUseId != null && ev.toolUseId.length > 0) {
@@ -50649,6 +50803,15 @@ ${url}`;
50649
50803
  rememberAgentButtonMeta(chat_id, keyboardMsgId, replyButtonMeta);
50650
50804
  }
50651
50805
  }
50806
+ if (sentIds.length === chunks.length && chunks.length > 0) {
50807
+ const anchorMsgId = sentIds[chunks.length - 1];
50808
+ if (typeof anchorMsgId === "number") {
50809
+ noteOutbound3(statusKey(chat_id, threadId), {
50810
+ messageId: anchorMsgId,
50811
+ text: chunks[chunks.length - 1]
50812
+ });
50813
+ }
50814
+ }
50652
50815
  const allPhotos = files.length >= 2 && files.length <= 10 && files.every((f) => PHOTO_EXTS.has(extname(f).toLowerCase()));
50653
50816
  const replyParams = reply_to != null && replyMode !== "off" ? { reply_parameters: { message_id: reply_to } } : {};
50654
50817
  if (allPhotos) {
@@ -50835,6 +50998,10 @@ async function executeStreamReply(args) {
50835
50998
  const sChatId = args.chat_id;
50836
50999
  const sThreadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
50837
51000
  outboundDedup.record(sChatId, sThreadId, args.text, Date.now());
51001
+ noteOutbound3(statusKey(sChatId, sThreadId), {
51002
+ messageId: result.messageId,
51003
+ text: args.text
51004
+ });
50838
51005
  }
50839
51006
  if (turn != null && isFinalAnswerReply({
50840
51007
  text: args.text ?? "",
@@ -50996,7 +51163,7 @@ async function executeAskUser(rawArgs) {
50996
51163
  return { content: [{ type: "text", text: JSON.stringify({ kind: "cancelled", reason: `send-failed: ${msg}` }) }] };
50997
51164
  }
50998
51165
  return new Promise((resolveTool) => {
50999
- const timer2 = setTimeout(() => {
51166
+ const timer3 = setTimeout(() => {
51000
51167
  const entry = pendingAskUser.get(askId);
51001
51168
  pendingAskUser.delete(askId);
51002
51169
  if (!entry)
@@ -51016,7 +51183,7 @@ async function executeAskUser(rawArgs) {
51016
51183
  threadId,
51017
51184
  messageId,
51018
51185
  startedAt: Date.now(),
51019
- timer: timer2,
51186
+ timer: timer3,
51020
51187
  resolve: (outcome) => {
51021
51188
  resolveTool({ content: [{ type: "text", text: JSON.stringify(outcome) }] });
51022
51189
  }
@@ -51580,6 +51747,10 @@ function handleSessionEvent(ev) {
51580
51747
  switch (ev.kind) {
51581
51748
  case "enqueue": {
51582
51749
  typingWrapper.drainAll();
51750
+ if (ev.chatId) {
51751
+ const enqThreadId = ev.threadId != null ? Number(ev.threadId) : undefined;
51752
+ clearPending(statusKey(ev.chatId, enqThreadId), "handback");
51753
+ }
51583
51754
  if (ev.chatId) {
51584
51755
  const prior = currentTurn;
51585
51756
  if (prior?.answerStream != null) {
@@ -51684,7 +51855,7 @@ function handleSessionEvent(ev) {
51684
51855
  chatId: turn.sessionChatId,
51685
51856
  isPrivateChat: turn.isDm,
51686
51857
  threadId: turn.sessionThreadId,
51687
- sendMessageDraft: sendMessageDraftFn,
51858
+ ...ANSWER_STREAM_VISIBLE_ENABLED ? { minInitialChars: 1 } : { sendMessageDraft: sendMessageDraftFn },
51688
51859
  sendMessage: async (chatId, text, params) => {
51689
51860
  const tid = params?.message_thread_id;
51690
51861
  const msg = await robustApiCall(() => bot.api.sendMessage(chatId, text, {
@@ -51769,6 +51940,7 @@ function handleSessionEvent(ev) {
51769
51940
  ctrl.setError();
51770
51941
  purgeReactionTracking(ceKey);
51771
51942
  endTurn(ceKey);
51943
+ noteTurnEnd(ceKey);
51772
51944
  if (turn.answerStream != null) {
51773
51945
  turn.answerStream.stop();
51774
51946
  turn.answerStream = null;
@@ -51805,20 +51977,45 @@ function handleSessionEvent(ev) {
51805
51977
  turn.orphanedReplyTimeoutId = null;
51806
51978
  }
51807
51979
  preambleSuppressor.flushNow();
51980
+ let streamFinalizedAsAnswer = false;
51808
51981
  if (turn?.answerStream != null) {
51809
51982
  const stream = turn.answerStream;
51810
- turn.answerStream = null;
51811
- stream.retract().catch((err) => {
51812
- process.stderr.write(`telegram gateway: answer-stream retract failed: ${err instanceof Error ? err.message : String(err)}
51983
+ const streamedMsgId = stream.messageId();
51984
+ const streamedFinalText = turn.capturedText.join("").trim();
51985
+ if (ANSWER_STREAM_VISIBLE_ENABLED && !turn.replyCalled && streamedMsgId != null && streamedFinalText.length > 0) {
51986
+ turn.answerStream = null;
51987
+ stream.stop();
51988
+ streamFinalizedAsAnswer = true;
51989
+ turn.finalAnswerDelivered = true;
51990
+ try {
51991
+ outboundDedup.record(turn.sessionChatId, turn.sessionThreadId, streamedFinalText, Date.now());
51992
+ } catch {}
51993
+ if (HISTORY_ENABLED) {
51994
+ try {
51995
+ recordOutbound({
51996
+ chat_id: turn.sessionChatId,
51997
+ thread_id: turn.sessionThreadId ?? null,
51998
+ message_ids: [streamedMsgId],
51999
+ texts: [streamedFinalText]
52000
+ });
52001
+ } catch {}
52002
+ }
52003
+ process.stderr.write(`telegram gateway: answer-stream finalized as answer chat=${turn.sessionChatId} msg=${streamedMsgId} chars=${streamedFinalText.length}
51813
52004
  `);
51814
- });
52005
+ } else {
52006
+ turn.answerStream = null;
52007
+ stream.retract().catch((err) => {
52008
+ process.stderr.write(`telegram gateway: answer-stream retract failed: ${err instanceof Error ? err.message : String(err)}
52009
+ `);
52010
+ });
52011
+ }
51815
52012
  }
51816
52013
  if (turn == null)
51817
52014
  return;
51818
52015
  const chatId = turn.sessionChatId;
51819
52016
  const threadId = turn.sessionThreadId;
51820
52017
  const ctrl = activeStatusReactions.get(statusKey(chatId, threadId));
51821
- const flushDecision = decideTurnFlush({
52018
+ const flushDecision = streamFinalizedAsAnswer ? { kind: "skip", reason: "reply-called" } : decideTurnFlush({
51822
52019
  chatId: turn.sessionChatId,
51823
52020
  replyCalled: turn.replyCalled,
51824
52021
  capturedText: turn.capturedText,
@@ -51881,6 +52078,7 @@ function handleSessionEvent(ev) {
51881
52078
  } catch {}
51882
52079
  clear(tKey);
51883
52080
  endTurn(tKey);
52081
+ noteTurnEnd(tKey);
51884
52082
  }
51885
52083
  lastPtyPreviewByChat.delete(statusKey(chatId, threadId));
51886
52084
  pendingPtyPartial = null;
@@ -51907,6 +52105,7 @@ function handleSessionEvent(ev) {
51907
52105
  const tKey = statusKey(chatId, threadId);
51908
52106
  clear(tKey);
51909
52107
  endTurn(tKey);
52108
+ noteTurnEnd(tKey);
51910
52109
  }
51911
52110
  (async () => {
51912
52111
  await new Promise((resolve8) => setTimeout(resolve8, 500));
@@ -52050,6 +52249,7 @@ function handleSessionEvent(ev) {
52050
52249
  }
52051
52250
  clear(tKey);
52052
52251
  endTurn(tKey);
52252
+ noteTurnEnd(tKey);
52053
52253
  }
52054
52254
  lastPtyPreviewByChat.delete(statusKey(chatId, threadId));
52055
52255
  pendingPtyPartial = null;
@@ -52749,6 +52949,7 @@ ${preBlock(write.output)}`;
52749
52949
  logStreamingEvent({ kind: "inbound_ack", chatId: chat_id, messageId: msgId, ackDelayMs: Date.now() - inboundReceivedAt });
52750
52950
  reset(statusKey(chat_id, messageThreadId), Date.now());
52751
52951
  startTurn(statusKey(chat_id, messageThreadId), Date.now());
52952
+ clearPending(statusKey(chat_id, messageThreadId), "inbound");
52752
52953
  startTurnTypingLoop(chat_id);
52753
52954
  emitRuntimeMetric({
52754
52955
  kind: "turn_started",
@@ -57142,21 +57343,21 @@ var didOneTimeSetup = false;
57142
57343
  }
57143
57344
  } catch {}
57144
57345
  try {
57145
- const nowMs = Date.now();
57346
+ const nowMs2 = Date.now();
57146
57347
  const marker = readRestartMarker();
57147
57348
  const cleanMarker = readCleanShutdownMarker(GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH);
57148
57349
  const currentSession = { pid: process.pid, startedAtMs: GATEWAY_STARTED_AT_MS };
57149
57350
  const storedSession = readSessionMarker(GATEWAY_SESSION_MARKER_PATH);
57150
57351
  const isRealRestart = shouldFireRestartBanner({ stored: storedSession, current: currentSession });
57151
57352
  if (cleanMarker) {
57152
- const ageSec = Math.max(0, Math.round((nowMs - cleanMarker.ts) / 1000));
57353
+ const ageSec = Math.max(0, Math.round((nowMs2 - cleanMarker.ts) / 1000));
57153
57354
  const reasonTag = cleanMarker.reason ? ` reason=${JSON.stringify(cleanMarker.reason)}` : "";
57154
- const cleanFresh = shouldSuppressRecoveryBanner(cleanMarker, nowMs, DEFAULT_MAX_AGE_MS);
57355
+ const cleanFresh = shouldSuppressRecoveryBanner(cleanMarker, nowMs2, DEFAULT_MAX_AGE_MS);
57155
57356
  const isOperatorMarker = typeof cleanMarker.reason === "string" && cleanMarker.reason.startsWith("operator:");
57156
57357
  if (cleanFresh) {
57157
57358
  process.stderr.write(`telegram gateway: boot.clean_shutdown_detected age=${ageSec}s signal=${cleanMarker.signal}${reasonTag}
57158
57359
  `);
57159
- } else if (isOperatorMarker && nowMs - cleanMarker.ts < 300000) {
57360
+ } else if (isOperatorMarker && nowMs2 - cleanMarker.ts < 300000) {
57160
57361
  process.stderr.write(`telegram gateway: boot.clean_shutdown_marker_fresh_extended age=${ageSec}s signal=${cleanMarker.signal}${reasonTag} window=5min
57161
57362
  `);
57162
57363
  } else {
@@ -57165,7 +57366,7 @@ var didOneTimeSetup = false;
57165
57366
  }
57166
57367
  }
57167
57368
  if (marker) {
57168
- const ageMs = nowMs - marker.ts;
57369
+ const ageMs = nowMs2 - marker.ts;
57169
57370
  const ageSec = Math.max(1, Math.round(ageMs / 1000));
57170
57371
  process.stderr.write(`telegram gateway: boot: restart-marker present, chat_id=${marker.chat_id} age=${ageSec}s within5min=${ageMs < 300000}
57171
57372
  `);
@@ -57175,11 +57376,11 @@ var didOneTimeSetup = false;
57175
57376
  process.stderr.write(`telegram gateway: boot: suppressed boot card \u2014 session marker matches current process (pid=${process.pid} startedAt=${GATEWAY_STARTED_AT_MS})
57176
57377
  `);
57177
57378
  } else {
57178
- const markerAgeMs = marker ? nowMs - marker.ts : undefined;
57179
- const reason = determineRestartReason({ marker, cleanMarker, sessionMarker: storedSession, now: nowMs });
57379
+ const markerAgeMs = marker ? nowMs2 - marker.ts : undefined;
57380
+ const reason = determineRestartReason({ marker, cleanMarker, sessionMarker: storedSession, now: nowMs2 });
57180
57381
  const target = resolveBootChatId(marker, markerAgeMs);
57181
57382
  if (reason === "crash") {
57182
- const cleanMarkerStale = cleanMarker ? !shouldSuppressRecoveryBanner(cleanMarker, nowMs, DEFAULT_MAX_AGE_MS) : false;
57383
+ const cleanMarkerStale = cleanMarker ? !shouldSuppressRecoveryBanner(cleanMarker, nowMs2, DEFAULT_MAX_AGE_MS) : false;
57183
57384
  const supervisor = process.env.SWITCHROOM_RUNTIME === "docker" ? "docker compose" : "systemd";
57184
57385
  const detailParts = [`gateway crashed and was auto-restarted by ${supervisor}`];
57185
57386
  if (cleanMarker?.signal)