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.
- package/dist/cli/switchroom.js +60 -5
- package/package.json +1 -1
- package/telegram-plugin/dist/gateway/gateway.js +290 -88
- package/telegram-plugin/final-answer-detect.ts +83 -0
- package/telegram-plugin/gateway/gateway.ts +213 -11
- package/telegram-plugin/hooks/silent-end-interrupt-stop.mjs +17 -5
- package/telegram-plugin/pending-work-progress.ts +377 -0
- package/telegram-plugin/runtime-metrics.ts +20 -0
- package/telegram-plugin/silent-end.ts +37 -11
- package/telegram-plugin/tests/final-answer-detect.test.ts +89 -0
- package/telegram-plugin/tests/pending-work-progress.test.ts +354 -0
- package/telegram-plugin/tests/silent-end.test.ts +118 -0
- package/telegram-plugin/uat/scenarios/cross-turn-pending-progress-dm.test.ts +237 -0
|
@@ -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
|
|
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:
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
27355
|
+
let timer3;
|
|
27356
27356
|
const timeout = new Promise((resolve6) => {
|
|
27357
|
-
|
|
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(
|
|
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
|
|
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 =
|
|
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 =
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
39815
|
-
const
|
|
39816
|
-
if (typeof
|
|
39817
|
-
|
|
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(
|
|
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,
|
|
41177
|
-
super(`Google credentials for account '${account}' expired at ${new Date(expiresAt).toISOString()} (now ${new Date(
|
|
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 =
|
|
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
|
|
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(
|
|
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 >=
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
44589
|
+
var enabled3 = process.env.SWITCHROOM_DELIVERY_MACHINE_SHADOW !== "0";
|
|
44439
44590
|
function shadowEmit(event) {
|
|
44440
|
-
if (!
|
|
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
|
|
44649
|
+
var enabled4 = process.env.SWITCHROOM_DELIVERY_MACHINE_CUTOVER !== "0";
|
|
44499
44650
|
function isDispatchEnabled() {
|
|
44500
|
-
return
|
|
44651
|
+
return enabled4;
|
|
44501
44652
|
}
|
|
44502
44653
|
function dispatchEffects(effects, ctx) {
|
|
44503
|
-
if (!
|
|
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
|
|
44963
|
+
let timer3 = null;
|
|
44813
44964
|
let failures = 0;
|
|
44814
44965
|
let active = false;
|
|
44815
|
-
async function
|
|
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 (
|
|
44840
|
-
clearIntervalFn(
|
|
44841
|
-
|
|
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
|
-
|
|
44851
|
-
|
|
45001
|
+
timer3 = setIntervalFn(() => {
|
|
45002
|
+
tick3();
|
|
44852
45003
|
}, intervalMs);
|
|
44853
|
-
if (typeof
|
|
44854
|
-
|
|
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
|
|
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
|
-
|
|
47753
|
+
tick3().catch((err) => {
|
|
47603
47754
|
log(`issues-watcher: initial tick failed: ${err.message}`);
|
|
47604
47755
|
});
|
|
47605
|
-
const
|
|
47606
|
-
|
|
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
|
|
47611
|
-
|
|
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(
|
|
47769
|
+
clearIntervalFn(timer3);
|
|
47619
47770
|
},
|
|
47620
|
-
tick:
|
|
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.
|
|
48007
|
-
var COMMIT_SHA = "
|
|
48008
|
-
var COMMIT_DATE = "2026-05-
|
|
48009
|
-
var LATEST_PR =
|
|
48010
|
-
var COMMITS_AHEAD_OF_TAG =
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
49219
|
+
const timer3 = setTimeout(() => {
|
|
49069
49220
|
resolveCompactCard("timeout", null);
|
|
49070
49221
|
}, COMPACT_CARD_TIMEOUT_MS);
|
|
49071
|
-
|
|
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:
|
|
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
|
|
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 ?
|
|
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:
|
|
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
|
|
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:
|
|
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 (
|
|
52010
|
-
const silentEnd =
|
|
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
|
|
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((
|
|
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,
|
|
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 &&
|
|
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 =
|
|
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 ?
|
|
57152
|
-
const reason = determineRestartReason({ marker, cleanMarker, sessionMarker: storedSession, now:
|
|
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,
|
|
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)
|