switchroom 0.13.12 → 0.13.14

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";
@@ -37383,6 +37521,19 @@ function recordSilentTurnEnd(args, deps) {
37383
37521
  writeSilentEndState(args, deps);
37384
37522
  return { exhausted: false };
37385
37523
  }
37524
+ var recordUndeliveredTurnEnd = recordSilentTurnEnd;
37525
+
37526
+ // final-answer-detect.ts
37527
+ var FINAL_ANSWER_MIN_CHARS = 200;
37528
+ function isFinalAnswerReply(input) {
37529
+ if (input.done === true)
37530
+ return true;
37531
+ if (!input.disableNotification)
37532
+ return true;
37533
+ if (input.text.length >= FINAL_ANSWER_MIN_CHARS)
37534
+ return true;
37535
+ return false;
37536
+ }
37386
37537
 
37387
37538
  // turn-flush-safety.ts
37388
37539
  var SILENT_MARKERS = new Set(["NO_REPLY", "HEARTBEAT_OK"]);
@@ -38943,7 +39094,7 @@ class AuthBrokerClient2 {
38943
39094
  const id = req.id;
38944
39095
  const frame = encodeRequest(req);
38945
39096
  return new Promise((resolve2, reject) => {
38946
- const timer2 = setTimeout(() => {
39097
+ const timer3 = setTimeout(() => {
38947
39098
  this.pending.delete(id);
38948
39099
  reject(new AuthBrokerUnreachableError2(`request ${req.op} timed out after ${this.timeoutMs}ms`, this.socketPath));
38949
39100
  }, this.timeoutMs);
@@ -38956,7 +39107,7 @@ class AuthBrokerClient2 {
38956
39107
  }
38957
39108
  },
38958
39109
  reject,
38959
- timer: timer2
39110
+ timer: timer3
38960
39111
  });
38961
39112
  sock.write(frame, (err) => {
38962
39113
  if (err) {
@@ -39109,7 +39260,7 @@ async function startAccountAuthSession(label, opts = {}) {
39109
39260
  cleanup();
39110
39261
  reject(new Error(`claude setup-token did not print an OAuth URL within ${urlTimeoutMs}ms`));
39111
39262
  }, urlTimeoutMs);
39112
- const tick2 = setInterval(() => {
39263
+ const tick3 = setInterval(() => {
39113
39264
  const url = parseSetupTokenUrl(buffer);
39114
39265
  if (url) {
39115
39266
  cleanup();
@@ -39123,7 +39274,7 @@ async function startAccountAuthSession(label, opts = {}) {
39123
39274
  child.once("exit", onExit);
39124
39275
  function cleanup() {
39125
39276
  clearTimeout(deadline);
39126
- clearInterval(tick2);
39277
+ clearInterval(tick3);
39127
39278
  child.removeListener("exit", onExit);
39128
39279
  }
39129
39280
  }).catch((err) => {
@@ -39781,7 +39932,7 @@ function startRestartWatchdog(config) {
39781
39932
  }
39782
39933
  const unit = `switchroom-${agentName3}`;
39783
39934
  let snapshot = null;
39784
- function tick2() {
39935
+ function tick3() {
39785
39936
  let raw;
39786
39937
  try {
39787
39938
  raw = execShow(unit);
@@ -39811,14 +39962,14 @@ function startRestartWatchdog(config) {
39811
39962
  log?.(`restart-watchdog: tick agent=${agentName3} ${decision.detail}`);
39812
39963
  }
39813
39964
  }
39814
- tick2();
39815
- const timer2 = setInterval(tick2, pollIntervalMs);
39816
- if (typeof timer2.unref === "function")
39817
- timer2.unref();
39965
+ tick3();
39966
+ const timer3 = setInterval(tick3, pollIntervalMs);
39967
+ if (typeof timer3.unref === "function")
39968
+ timer3.unref();
39818
39969
  log?.(`restart-watchdog: started agent=${agentName3} unit=${unit} interval=${pollIntervalMs}ms`);
39819
39970
  return {
39820
39971
  stop() {
39821
- clearInterval(timer2);
39972
+ clearInterval(timer3);
39822
39973
  }
39823
39974
  };
39824
39975
  }
@@ -41173,11 +41324,11 @@ class BrokerCredentialsExpiredError extends Error {
41173
41324
  account;
41174
41325
  expiresAt;
41175
41326
  nowMs;
41176
- constructor(account, expiresAt, nowMs) {
41177
- 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.`);
41178
41329
  this.account = account;
41179
41330
  this.expiresAt = expiresAt;
41180
- this.nowMs = nowMs;
41331
+ this.nowMs = nowMs2;
41181
41332
  this.name = "BrokerCredentialsExpiredError";
41182
41333
  }
41183
41334
  }
@@ -41885,7 +42036,7 @@ class InjectError extends Error {
41885
42036
  this.code = code;
41886
42037
  }
41887
42038
  }
41888
- var POLL_INTERVAL_MS = 150;
42039
+ var POLL_INTERVAL_MS2 = 150;
41889
42040
  var OUTPUT_BYTE_CAP = 3000;
41890
42041
  function validateInjectCommand(command) {
41891
42042
  if (typeof command !== "string" || command.trim().length === 0) {
@@ -42054,12 +42205,12 @@ async function injectSlashCommandWith(runner, args) {
42054
42205
  let last = before;
42055
42206
  let stableSince = null;
42056
42207
  while (Date.now() - start < timeoutMs) {
42057
- await sleep(POLL_INTERVAL_MS);
42208
+ await sleep(POLL_INTERVAL_MS2);
42058
42209
  const cur = runner.capture(socket, session) ?? "";
42059
42210
  if (cur === last && cur !== before) {
42060
42211
  if (stableSince === null) {
42061
42212
  stableSince = Date.now();
42062
- } else if (Date.now() - stableSince >= POLL_INTERVAL_MS) {
42213
+ } else if (Date.now() - stableSince >= POLL_INTERVAL_MS2) {
42063
42214
  last = cur;
42064
42215
  break;
42065
42216
  }
@@ -43008,7 +43159,7 @@ async function hostdRequest(opts, req) {
43008
43159
  const socket = connect(opts.socketPath);
43009
43160
  let buf = "";
43010
43161
  let settled = false;
43011
- const timer2 = setTimeout(() => {
43162
+ const timer3 = setTimeout(() => {
43012
43163
  if (settled)
43013
43164
  return;
43014
43165
  settled = true;
@@ -43022,7 +43173,7 @@ async function hostdRequest(opts, req) {
43022
43173
  if (settled)
43023
43174
  return;
43024
43175
  settled = true;
43025
- clearTimeout(timer2);
43176
+ clearTimeout(timer3);
43026
43177
  socket.destroy();
43027
43178
  reject(err);
43028
43179
  }
@@ -43033,7 +43184,7 @@ async function hostdRequest(opts, req) {
43033
43184
  if (settled)
43034
43185
  return;
43035
43186
  settled = true;
43036
- clearTimeout(timer2);
43187
+ clearTimeout(timer3);
43037
43188
  socket.destroy();
43038
43189
  reject(new Error("hostd: response exceeded frame budget"));
43039
43190
  return;
@@ -43048,14 +43199,14 @@ async function hostdRequest(opts, req) {
43048
43199
  if (settled)
43049
43200
  return;
43050
43201
  settled = true;
43051
- clearTimeout(timer2);
43202
+ clearTimeout(timer3);
43052
43203
  socket.end();
43053
43204
  resolve6(resp);
43054
43205
  } catch (err) {
43055
43206
  if (settled)
43056
43207
  return;
43057
43208
  settled = true;
43058
- clearTimeout(timer2);
43209
+ clearTimeout(timer3);
43059
43210
  socket.destroy();
43060
43211
  reject(new Error(`hostd: bad response frame: ${err.message}`, { cause: err }));
43061
43212
  }
@@ -43064,14 +43215,14 @@ async function hostdRequest(opts, req) {
43064
43215
  if (settled)
43065
43216
  return;
43066
43217
  settled = true;
43067
- clearTimeout(timer2);
43218
+ clearTimeout(timer3);
43068
43219
  reject(err);
43069
43220
  });
43070
43221
  socket.on("close", () => {
43071
43222
  if (settled)
43072
43223
  return;
43073
43224
  settled = true;
43074
- clearTimeout(timer2);
43225
+ clearTimeout(timer3);
43075
43226
  reject(new Error("hostd: connection closed before response"));
43076
43227
  });
43077
43228
  });
@@ -44435,9 +44586,9 @@ function transition(state3, event) {
44435
44586
 
44436
44587
  // gateway/inbound-delivery-machine-shadow.ts
44437
44588
  var state3 = initialState();
44438
- var enabled2 = process.env.SWITCHROOM_DELIVERY_MACHINE_SHADOW !== "0";
44589
+ var enabled3 = process.env.SWITCHROOM_DELIVERY_MACHINE_SHADOW !== "0";
44439
44590
  function shadowEmit(event) {
44440
- if (!enabled2)
44591
+ if (!enabled3)
44441
44592
  return [];
44442
44593
  try {
44443
44594
  const result = transition(state3, event);
@@ -44495,12 +44646,12 @@ function redeliverBufferedInbound2(buffer, agent, send, spool) {
44495
44646
  }
44496
44647
 
44497
44648
  // gateway/inbound-delivery-machine-dispatch.ts
44498
- var enabled3 = process.env.SWITCHROOM_DELIVERY_MACHINE_CUTOVER !== "0";
44649
+ var enabled4 = process.env.SWITCHROOM_DELIVERY_MACHINE_CUTOVER !== "0";
44499
44650
  function isDispatchEnabled() {
44500
- return enabled3;
44651
+ return enabled4;
44501
44652
  }
44502
44653
  function dispatchEffects(effects, ctx) {
44503
- if (!enabled3)
44654
+ if (!enabled4)
44504
44655
  return;
44505
44656
  for (const effect of effects) {
44506
44657
  dispatchOne(effect, ctx);
@@ -44809,10 +44960,10 @@ function createPollHealthCheck(options) {
44809
44960
  setIntervalFn = setInterval,
44810
44961
  clearIntervalFn = clearInterval
44811
44962
  } = options;
44812
- let timer2 = null;
44963
+ let timer3 = null;
44813
44964
  let failures = 0;
44814
44965
  let active = false;
44815
- async function tick2() {
44966
+ async function tick3() {
44816
44967
  try {
44817
44968
  await ping();
44818
44969
  if (failures > 0) {
@@ -44836,9 +44987,9 @@ function createPollHealthCheck(options) {
44836
44987
  }
44837
44988
  function doStop() {
44838
44989
  active = false;
44839
- if (timer2 !== null) {
44840
- clearIntervalFn(timer2);
44841
- timer2 = null;
44990
+ if (timer3 !== null) {
44991
+ clearIntervalFn(timer3);
44992
+ timer3 = null;
44842
44993
  }
44843
44994
  }
44844
44995
  return {
@@ -44847,11 +44998,11 @@ function createPollHealthCheck(options) {
44847
44998
  return;
44848
44999
  active = true;
44849
45000
  failures = 0;
44850
- timer2 = setIntervalFn(() => {
44851
- tick2();
45001
+ timer3 = setIntervalFn(() => {
45002
+ tick3();
44852
45003
  }, intervalMs);
44853
- if (typeof timer2?.unref === "function") {
44854
- timer2.unref();
45004
+ if (typeof timer3?.unref === "function") {
45005
+ timer3.unref();
44855
45006
  }
44856
45007
  log(`telegram gateway: poll.health_check started interval=${intervalMs}ms threshold=${failureThreshold}`);
44857
45008
  },
@@ -47590,7 +47741,7 @@ function startIssuesWatcher(opts) {
47590
47741
  log(`issues-watcher: refresh failed: ${err.message}`);
47591
47742
  }
47592
47743
  }
47593
- async function tick2() {
47744
+ async function tick3() {
47594
47745
  if (stopped)
47595
47746
  return;
47596
47747
  const signature = signatureProvider(path);
@@ -47599,25 +47750,25 @@ function startIssuesWatcher(opts) {
47599
47750
  lastSignature = signature;
47600
47751
  await readAndRefresh();
47601
47752
  }
47602
- tick2().catch((err) => {
47753
+ tick3().catch((err) => {
47603
47754
  log(`issues-watcher: initial tick failed: ${err.message}`);
47604
47755
  });
47605
- const timer2 = setIntervalFn(() => {
47606
- tick2().catch((err) => {
47756
+ const timer3 = setIntervalFn(() => {
47757
+ tick3().catch((err) => {
47607
47758
  log(`issues-watcher: tick failed: ${err.message}`);
47608
47759
  });
47609
47760
  }, intervalMs);
47610
- if (typeof timer2.unref === "function") {
47611
- timer2.unref();
47761
+ if (typeof timer3.unref === "function") {
47762
+ timer3.unref();
47612
47763
  }
47613
47764
  return {
47614
47765
  stop() {
47615
47766
  if (stopped)
47616
47767
  return;
47617
47768
  stopped = true;
47618
- clearIntervalFn(timer2);
47769
+ clearIntervalFn(timer3);
47619
47770
  },
47620
- tick: tick2
47771
+ tick: tick3
47621
47772
  };
47622
47773
  }
47623
47774
  function defaultSignatureProvider(path) {
@@ -48003,11 +48154,11 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
48003
48154
  }
48004
48155
 
48005
48156
  // ../src/build-info.ts
48006
- var VERSION = "0.13.12";
48007
- var COMMIT_SHA = "18363dfb";
48008
- var COMMIT_DATE = "2026-05-22T19:32:19+10:00";
48009
- var LATEST_PR = null;
48010
- var COMMITS_AHEAD_OF_TAG = 5;
48157
+ var VERSION = "0.13.14";
48158
+ var COMMIT_SHA = "0cf961a6";
48159
+ var COMMIT_DATE = "2026-05-23T01:15:10Z";
48160
+ var LATEST_PR = 1670;
48161
+ var COMMITS_AHEAD_OF_TAG = 0;
48011
48162
 
48012
48163
  // gateway/boot-version.ts
48013
48164
  function formatRelativeAgo(iso) {
@@ -48106,12 +48257,12 @@ async function rpc2(req, opts) {
48106
48257
  resolve7(val);
48107
48258
  };
48108
48259
  const client3 = new net4.Socket;
48109
- const timer2 = setTimeout(() => {
48260
+ const timer3 = setTimeout(() => {
48110
48261
  client3.destroy();
48111
48262
  settle({ kind: "unreachable", msg: `broker did not respond within ${timeoutMs}ms` });
48112
48263
  }, timeoutMs);
48113
48264
  client3.on("error", (err) => {
48114
- clearTimeout(timer2);
48265
+ clearTimeout(timer3);
48115
48266
  const code = err.code ?? "ERR";
48116
48267
  let msg;
48117
48268
  if (code === "ENOENT")
@@ -48131,7 +48282,7 @@ async function rpc2(req, opts) {
48131
48282
  `);
48132
48283
  if (newlineIdx !== -1) {
48133
48284
  const line = buffer.slice(0, newlineIdx).trimEnd();
48134
- clearTimeout(timer2);
48285
+ clearTimeout(timer3);
48135
48286
  client3.destroy();
48136
48287
  try {
48137
48288
  const resp = decodeResponse2(line);
@@ -48148,7 +48299,7 @@ async function rpc2(req, opts) {
48148
48299
  try {
48149
48300
  client3.write(encodeRequest2(req));
48150
48301
  } catch (err) {
48151
- clearTimeout(timer2);
48302
+ clearTimeout(timer3);
48152
48303
  client3.destroy();
48153
48304
  settle({
48154
48305
  kind: "unreachable",
@@ -48195,13 +48346,13 @@ async function unlockViaBroker(passphrase, opts) {
48195
48346
  settled = true;
48196
48347
  resolve7(val);
48197
48348
  };
48198
- const timer2 = setTimeout(() => {
48349
+ const timer3 = setTimeout(() => {
48199
48350
  client3.destroy();
48200
48351
  settle({ ok: false, msg: "Timeout waiting for broker" });
48201
48352
  }, timeoutMs);
48202
48353
  const client3 = net4.createConnection({ path: unlockSocketPath });
48203
48354
  client3.on("error", (err) => {
48204
- clearTimeout(timer2);
48355
+ clearTimeout(timer3);
48205
48356
  settle({ ok: false, msg: `Broker unreachable: ${err.message}` });
48206
48357
  });
48207
48358
  let buffer = "";
@@ -48211,7 +48362,7 @@ async function unlockViaBroker(passphrase, opts) {
48211
48362
  `);
48212
48363
  if (newlineIdx !== -1) {
48213
48364
  const line = buffer.slice(0, newlineIdx).trimEnd();
48214
- clearTimeout(timer2);
48365
+ clearTimeout(timer3);
48215
48366
  client3.destroy();
48216
48367
  if (line === "OK") {
48217
48368
  settle({ ok: true });
@@ -49065,17 +49216,17 @@ async function postCompactCard(occ, cap) {
49065
49216
  const messageId = sent?.message_id;
49066
49217
  if (typeof messageId !== "number")
49067
49218
  return;
49068
- const timer2 = setTimeout(() => {
49219
+ const timer3 = setTimeout(() => {
49069
49220
  resolveCompactCard("timeout", null);
49070
49221
  }, COMPACT_CARD_TIMEOUT_MS);
49071
- timer2.unref?.();
49222
+ timer3.unref?.();
49072
49223
  outstandingCompactCard = {
49073
49224
  chatId,
49074
49225
  threadId,
49075
49226
  messageId,
49076
49227
  occAtStart: occ,
49077
49228
  capAtStart: cap,
49078
- timer: timer2
49229
+ timer: timer3
49079
49230
  };
49080
49231
  } catch (err) {
49081
49232
  process.stderr.write(`telegram gateway: proactive-compact start card failed: ${err instanceof Error ? err.message : String(err)}
@@ -49858,6 +50009,7 @@ startTimer({
49858
50009
  lastPtyPreviewByChat.delete(fbKey);
49859
50010
  preambleSuppressor.dropNow();
49860
50011
  endTurn(fbKey);
50012
+ noteTurnEnd(fbKey);
49861
50013
  purgeReactionTracking(fbKey);
49862
50014
  const fbExtraPurge = purgeStaleTurnsForChat(fbChatId, activeTurnStartedAt.keys(), purgeReactionTracking);
49863
50015
  if (turnMatchesFallback && currentTurn === wedgedTurn)
@@ -49871,6 +50023,16 @@ startTimer({
49871
50023
  `);
49872
50024
  }
49873
50025
  });
50026
+ startTimer2({
50027
+ editMessage: async (ctx) => {
50028
+ await swallowingApiCall(() => lockedBot.api.editMessageText(ctx.chatId, ctx.messageId, ctx.newText), {
50029
+ chat_id: ctx.chatId,
50030
+ verb: "pending-progress-edit",
50031
+ ...ctx.threadId != null ? { threadId: ctx.threadId } : {}
50032
+ });
50033
+ },
50034
+ emitMetric: (event) => emitRuntimeMetric(event)
50035
+ });
49874
50036
  var inboundSpool = STATIC ? undefined : createInboundSpool({
49875
50037
  path: join32(STATE_DIR, "inbound-spool.jsonl"),
49876
50038
  fs: {
@@ -49960,18 +50122,18 @@ var ipcServer = createIpcServer({
49960
50122
  process.stderr.write(`telegram gateway: bridge-reconnect: skipping boot card (${dedupeDecision.reason})
49961
50123
  `);
49962
50124
  } else {
49963
- const nowMs = Date.now();
50125
+ const nowMs2 = Date.now();
49964
50126
  const marker = readRestartMarker();
49965
50127
  const cleanMarker = readCleanShutdownMarker(GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH);
49966
50128
  const storedSession = readSessionMarker(GATEWAY_SESSION_MARKER_PATH);
49967
- const markerAgeMs = marker ? nowMs - marker.ts : undefined;
50129
+ const markerAgeMs = marker ? nowMs2 - marker.ts : undefined;
49968
50130
  if (marker) {
49969
50131
  const ageSec = Math.max(1, Math.round((markerAgeMs ?? 0) / 1000));
49970
50132
  process.stderr.write(`telegram gateway: bridge-reconnect: restart-marker present, chat_id=${marker.chat_id} age=${ageSec}s agent=${client3.agentName}
49971
50133
  `);
49972
50134
  clearRestartMarker();
49973
50135
  }
49974
- const reason = determineRestartReason({ marker, cleanMarker, sessionMarker: storedSession, now: nowMs });
50136
+ const reason = determineRestartReason({ marker, cleanMarker, sessionMarker: storedSession, now: nowMs2 });
49975
50137
  const target = resolveBootChatId(marker, markerAgeMs);
49976
50138
  if (target) {
49977
50139
  const { chatId, threadId, ackMsgId } = target;
@@ -50100,6 +50262,10 @@ ${reminder}
50100
50262
  if (ev.toolUseId != null && ev.toolUseId.length > 0 && !isTelegramSurfaceTool(ev.toolName)) {
50101
50263
  const label = toolLabel(ev.toolName, ev.input, undefined, ev.precomputedLabel);
50102
50264
  noteToolStart(key, ev.toolUseId, ev.toolName, label.length > 0 ? label : null, Date.now());
50265
+ const evInput = ev.input;
50266
+ if (ev.toolName === "Agent" || ev.toolName === "Task" || ev.toolName === "Bash" && evInput?.run_in_background === true) {
50267
+ noteAsyncDispatch(key);
50268
+ }
50103
50269
  }
50104
50270
  } else if (ev.kind === "tool_result") {
50105
50271
  if (ev.toolUseId != null && ev.toolUseId.length > 0) {
@@ -50422,6 +50588,7 @@ async function executeUpdateChecklist(args) {
50422
50588
  return { content: [{ type: "text", text: `checklist updated (id: ${message_id})` }] };
50423
50589
  }
50424
50590
  async function executeReply(args) {
50591
+ const turn = currentTurn;
50425
50592
  const chat_id = args.chat_id;
50426
50593
  if (!chat_id)
50427
50594
  throw new Error("reply: chat_id is required");
@@ -50635,6 +50802,15 @@ ${url}`;
50635
50802
  rememberAgentButtonMeta(chat_id, keyboardMsgId, replyButtonMeta);
50636
50803
  }
50637
50804
  }
50805
+ if (sentIds.length === chunks.length && chunks.length > 0) {
50806
+ const anchorMsgId = sentIds[chunks.length - 1];
50807
+ if (typeof anchorMsgId === "number") {
50808
+ noteOutbound3(statusKey(chat_id, threadId), {
50809
+ messageId: anchorMsgId,
50810
+ text: chunks[chunks.length - 1]
50811
+ });
50812
+ }
50813
+ }
50638
50814
  const allPhotos = files.length >= 2 && files.length <= 10 && files.every((f) => PHOTO_EXTS.has(extname(f).toLowerCase()));
50639
50815
  const replyParams = reply_to != null && replyMode !== "off" ? { reply_parameters: { message_id: reply_to } } : {};
50640
50816
  if (allPhotos) {
@@ -50707,6 +50883,9 @@ ${url}`;
50707
50883
  process.stderr.write(`telegram gateway: reply: endStatusReaction hook threw: ${err}
50708
50884
  `);
50709
50885
  }
50886
+ if (turn != null && isFinalAnswerReply({ text: rawText, disableNotification })) {
50887
+ turn.finalAnswerDelivered = true;
50888
+ }
50710
50889
  }
50711
50890
  process.stderr.write(`telegram channel: reply: finalized chatId=${chat_id} messageIds=[${sentIds.join(",")}] chunks=${chunks.length}
50712
50891
  `);
@@ -50716,6 +50895,7 @@ ${url}`;
50716
50895
  return { content: [{ type: "text", text: result }] };
50717
50896
  }
50718
50897
  async function executeStreamReply(args) {
50898
+ const turn = currentTurn;
50719
50899
  if (!args.chat_id)
50720
50900
  throw new Error("stream_reply: chat_id is required");
50721
50901
  if (args.text == null || args.text === "")
@@ -50817,6 +50997,17 @@ async function executeStreamReply(args) {
50817
50997
  const sChatId = args.chat_id;
50818
50998
  const sThreadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
50819
50999
  outboundDedup.record(sChatId, sThreadId, args.text, Date.now());
51000
+ noteOutbound3(statusKey(sChatId, sThreadId), {
51001
+ messageId: result.messageId,
51002
+ text: args.text
51003
+ });
51004
+ }
51005
+ if (turn != null && isFinalAnswerReply({
51006
+ text: args.text ?? "",
51007
+ disableNotification: args.disable_notification === true,
51008
+ done: args.done === true
51009
+ })) {
51010
+ turn.finalAnswerDelivered = true;
50820
51011
  }
50821
51012
  return { content: [{ type: "text", text: `${result.status} (id: ${result.messageId ?? "pending"})` }] };
50822
51013
  }
@@ -50971,7 +51162,7 @@ async function executeAskUser(rawArgs) {
50971
51162
  return { content: [{ type: "text", text: JSON.stringify({ kind: "cancelled", reason: `send-failed: ${msg}` }) }] };
50972
51163
  }
50973
51164
  return new Promise((resolveTool) => {
50974
- const timer2 = setTimeout(() => {
51165
+ const timer3 = setTimeout(() => {
50975
51166
  const entry = pendingAskUser.get(askId);
50976
51167
  pendingAskUser.delete(askId);
50977
51168
  if (!entry)
@@ -50991,7 +51182,7 @@ async function executeAskUser(rawArgs) {
50991
51182
  threadId,
50992
51183
  messageId,
50993
51184
  startedAt: Date.now(),
50994
- timer: timer2,
51185
+ timer: timer3,
50995
51186
  resolve: (outcome) => {
50996
51187
  resolveTool({ content: [{ type: "text", text: JSON.stringify(outcome) }] });
50997
51188
  }
@@ -51555,6 +51746,10 @@ function handleSessionEvent(ev) {
51555
51746
  switch (ev.kind) {
51556
51747
  case "enqueue": {
51557
51748
  typingWrapper.drainAll();
51749
+ if (ev.chatId) {
51750
+ const enqThreadId = ev.threadId != null ? Number(ev.threadId) : undefined;
51751
+ clearPending(statusKey(ev.chatId, enqThreadId), "handback");
51752
+ }
51558
51753
  if (ev.chatId) {
51559
51754
  const prior = currentTurn;
51560
51755
  if (prior?.answerStream != null) {
@@ -51569,6 +51764,7 @@ function handleSessionEvent(ev) {
51569
51764
  startedAt,
51570
51765
  gatewayReceiveAt: startedAt,
51571
51766
  replyCalled: false,
51767
+ finalAnswerDelivered: false,
51572
51768
  capturedText: [],
51573
51769
  orphanedReplyTimeoutId: null,
51574
51770
  registryKey: null,
@@ -51743,6 +51939,7 @@ function handleSessionEvent(ev) {
51743
51939
  ctrl.setError();
51744
51940
  purgeReactionTracking(ceKey);
51745
51941
  endTurn(ceKey);
51942
+ noteTurnEnd(ceKey);
51746
51943
  if (turn.answerStream != null) {
51747
51944
  turn.answerStream.stop();
51748
51945
  turn.answerStream = null;
@@ -51855,6 +52052,7 @@ function handleSessionEvent(ev) {
51855
52052
  } catch {}
51856
52053
  clear(tKey);
51857
52054
  endTurn(tKey);
52055
+ noteTurnEnd(tKey);
51858
52056
  }
51859
52057
  lastPtyPreviewByChat.delete(statusKey(chatId, threadId));
51860
52058
  pendingPtyPartial = null;
@@ -51868,6 +52066,7 @@ function handleSessionEvent(ev) {
51868
52066
  const backstopChatId = chatId;
51869
52067
  const backstopThreadId = threadId;
51870
52068
  const backstopCtrl = ctrl;
52069
+ turn.finalAnswerDelivered = true;
51871
52070
  const cardTakeover = progressDriver?.takeOverCard({
51872
52071
  chatId: backstopChatId,
51873
52072
  threadId: backstopThreadId != null ? String(backstopThreadId) : undefined
@@ -51880,6 +52079,7 @@ function handleSessionEvent(ev) {
51880
52079
  const tKey = statusKey(chatId, threadId);
51881
52080
  clear(tKey);
51882
52081
  endTurn(tKey);
52082
+ noteTurnEnd(tKey);
51883
52083
  }
51884
52084
  (async () => {
51885
52085
  await new Promise((resolve8) => setTimeout(resolve8, 500));
@@ -52006,8 +52206,8 @@ function handleSessionEvent(ev) {
52006
52206
  longest_silent_gap_ms: outboundMetrics.longestOutboundGapMs,
52007
52207
  ended_via: outboundMetrics.outboundCount > 0 ? "reply" : "silent"
52008
52208
  });
52009
- if (outboundMetrics.outboundCount === 0) {
52010
- const silentEnd = recordSilentTurnEnd({
52209
+ if (turn.finalAnswerDelivered === false) {
52210
+ const silentEnd = recordUndeliveredTurnEnd({
52011
52211
  chatId,
52012
52212
  threadId: threadId ?? null,
52013
52213
  turnKey: tKey
@@ -52023,6 +52223,7 @@ function handleSessionEvent(ev) {
52023
52223
  }
52024
52224
  clear(tKey);
52025
52225
  endTurn(tKey);
52226
+ noteTurnEnd(tKey);
52026
52227
  }
52027
52228
  lastPtyPreviewByChat.delete(statusKey(chatId, threadId));
52028
52229
  pendingPtyPartial = null;
@@ -52722,6 +52923,7 @@ ${preBlock(write.output)}`;
52722
52923
  logStreamingEvent({ kind: "inbound_ack", chatId: chat_id, messageId: msgId, ackDelayMs: Date.now() - inboundReceivedAt });
52723
52924
  reset(statusKey(chat_id, messageThreadId), Date.now());
52724
52925
  startTurn(statusKey(chat_id, messageThreadId), Date.now());
52926
+ clearPending(statusKey(chat_id, messageThreadId), "inbound");
52725
52927
  startTurnTypingLoop(chat_id);
52726
52928
  emitRuntimeMetric({
52727
52929
  kind: "turn_started",
@@ -57115,21 +57317,21 @@ var didOneTimeSetup = false;
57115
57317
  }
57116
57318
  } catch {}
57117
57319
  try {
57118
- const nowMs = Date.now();
57320
+ const nowMs2 = Date.now();
57119
57321
  const marker = readRestartMarker();
57120
57322
  const cleanMarker = readCleanShutdownMarker(GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH);
57121
57323
  const currentSession = { pid: process.pid, startedAtMs: GATEWAY_STARTED_AT_MS };
57122
57324
  const storedSession = readSessionMarker(GATEWAY_SESSION_MARKER_PATH);
57123
57325
  const isRealRestart = shouldFireRestartBanner({ stored: storedSession, current: currentSession });
57124
57326
  if (cleanMarker) {
57125
- const ageSec = Math.max(0, Math.round((nowMs - cleanMarker.ts) / 1000));
57327
+ const ageSec = Math.max(0, Math.round((nowMs2 - cleanMarker.ts) / 1000));
57126
57328
  const reasonTag = cleanMarker.reason ? ` reason=${JSON.stringify(cleanMarker.reason)}` : "";
57127
- const cleanFresh = shouldSuppressRecoveryBanner(cleanMarker, nowMs, DEFAULT_MAX_AGE_MS);
57329
+ const cleanFresh = shouldSuppressRecoveryBanner(cleanMarker, nowMs2, DEFAULT_MAX_AGE_MS);
57128
57330
  const isOperatorMarker = typeof cleanMarker.reason === "string" && cleanMarker.reason.startsWith("operator:");
57129
57331
  if (cleanFresh) {
57130
57332
  process.stderr.write(`telegram gateway: boot.clean_shutdown_detected age=${ageSec}s signal=${cleanMarker.signal}${reasonTag}
57131
57333
  `);
57132
- } else if (isOperatorMarker && nowMs - cleanMarker.ts < 300000) {
57334
+ } else if (isOperatorMarker && nowMs2 - cleanMarker.ts < 300000) {
57133
57335
  process.stderr.write(`telegram gateway: boot.clean_shutdown_marker_fresh_extended age=${ageSec}s signal=${cleanMarker.signal}${reasonTag} window=5min
57134
57336
  `);
57135
57337
  } else {
@@ -57138,7 +57340,7 @@ var didOneTimeSetup = false;
57138
57340
  }
57139
57341
  }
57140
57342
  if (marker) {
57141
- const ageMs = nowMs - marker.ts;
57343
+ const ageMs = nowMs2 - marker.ts;
57142
57344
  const ageSec = Math.max(1, Math.round(ageMs / 1000));
57143
57345
  process.stderr.write(`telegram gateway: boot: restart-marker present, chat_id=${marker.chat_id} age=${ageSec}s within5min=${ageMs < 300000}
57144
57346
  `);
@@ -57148,11 +57350,11 @@ var didOneTimeSetup = false;
57148
57350
  process.stderr.write(`telegram gateway: boot: suppressed boot card \u2014 session marker matches current process (pid=${process.pid} startedAt=${GATEWAY_STARTED_AT_MS})
57149
57351
  `);
57150
57352
  } else {
57151
- const markerAgeMs = marker ? nowMs - marker.ts : undefined;
57152
- const reason = determineRestartReason({ marker, cleanMarker, sessionMarker: storedSession, now: nowMs });
57353
+ const markerAgeMs = marker ? nowMs2 - marker.ts : undefined;
57354
+ const reason = determineRestartReason({ marker, cleanMarker, sessionMarker: storedSession, now: nowMs2 });
57153
57355
  const target = resolveBootChatId(marker, markerAgeMs);
57154
57356
  if (reason === "crash") {
57155
- const cleanMarkerStale = cleanMarker ? !shouldSuppressRecoveryBanner(cleanMarker, nowMs, DEFAULT_MAX_AGE_MS) : false;
57357
+ const cleanMarkerStale = cleanMarker ? !shouldSuppressRecoveryBanner(cleanMarker, nowMs2, DEFAULT_MAX_AGE_MS) : false;
57156
57358
  const supervisor = process.env.SWITCHROOM_RUNTIME === "docker" ? "docker compose" : "systemd";
57157
57359
  const detailParts = [`gateway crashed and was auto-restarted by ${supervisor}`];
57158
57360
  if (cleanMarker?.signal)