@integrity-labs/agt-cli 0.28.458 → 0.28.459

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.
@@ -18906,7 +18906,7 @@ var require_filters = __commonJS({
18906
18906
  return r.copySafeness(str, res);
18907
18907
  }
18908
18908
  _exports.indent = indent;
18909
- function join17(arr, del, attr) {
18909
+ function join18(arr, del, attr) {
18910
18910
  del = del || "";
18911
18911
  if (attr) {
18912
18912
  arr = lib.map(arr, function(v) {
@@ -18915,7 +18915,7 @@ var require_filters = __commonJS({
18915
18915
  }
18916
18916
  return arr.join(del);
18917
18917
  }
18918
- _exports.join = join17;
18918
+ _exports.join = join18;
18919
18919
  function last(arr) {
18920
18920
  return arr[arr.length - 1];
18921
18921
  }
@@ -22516,11 +22516,11 @@ async function waitForResolution(cfg, callbackId, opts) {
22516
22516
  }
22517
22517
  } catch {
22518
22518
  }
22519
- await sleep2(interval);
22519
+ await sleep3(interval);
22520
22520
  }
22521
22521
  return { kind: "timeout" };
22522
22522
  }
22523
- function sleep2(ms) {
22523
+ function sleep3(ms) {
22524
22524
  return new Promise((r) => setTimeout(r, ms));
22525
22525
  }
22526
22526
  function generateOptionToken() {
@@ -30132,18 +30132,18 @@ import {
30132
30132
  ftruncateSync,
30133
30133
  mkdirSync as mkdirSync9,
30134
30134
  openSync,
30135
- readFileSync as readFileSync16,
30136
- readdirSync as readdirSync5,
30135
+ readFileSync as readFileSync17,
30136
+ readdirSync as readdirSync6,
30137
30137
  realpathSync,
30138
30138
  renameSync as renameSync7,
30139
- statSync as statSync4,
30139
+ statSync as statSync5,
30140
30140
  unlinkSync as unlinkSync8,
30141
30141
  watch,
30142
30142
  writeFileSync as writeFileSync11,
30143
30143
  writeSync
30144
30144
  } from "fs";
30145
30145
  import { homedir as homedir7 } from "os";
30146
- import { basename, extname, join as join16, resolve as resolve2 } from "path";
30146
+ import { basename, extname, join as join17, resolve as resolve2 } from "path";
30147
30147
 
30148
30148
  // src/channel-attachments.ts
30149
30149
  import { homedir } from "os";
@@ -34354,6 +34354,160 @@ function classifyTranscriptRateLimit(jsonl, startMs, endMs, now) {
34354
34354
  return newest;
34355
34355
  }
34356
34356
 
34357
+ // ../core/dist/claude-code-usage/turn-failure-classifier.js
34358
+ var UNKNOWN_TURN_FAILURE = Object.freeze({
34359
+ outcome: "unknown",
34360
+ atMs: null,
34361
+ failureClass: null,
34362
+ httpStatus: null,
34363
+ attempt: null,
34364
+ maxAttempts: null
34365
+ });
34366
+ var EXCLUDED_STATUSES = /* @__PURE__ */ new Set([429]);
34367
+ function classifyTransientStatus(status) {
34368
+ if (!Number.isFinite(status))
34369
+ return null;
34370
+ if (EXCLUDED_STATUSES.has(status))
34371
+ return null;
34372
+ if (status === 529)
34373
+ return "overloaded";
34374
+ if (status >= 500 && status <= 599)
34375
+ return "server_error";
34376
+ return null;
34377
+ }
34378
+ function numberOrNull(value) {
34379
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
34380
+ }
34381
+ function isErrorShaped(record2) {
34382
+ if (record2.isApiErrorMessage === true)
34383
+ return true;
34384
+ if (record2.type === "system" && record2.level === "error")
34385
+ return true;
34386
+ if (record2.type === "assistant" && typeof record2.error === "string" && record2.error)
34387
+ return true;
34388
+ return false;
34389
+ }
34390
+ function classifyRecord(record2, tsMs) {
34391
+ if (record2.isSidechain === true)
34392
+ return null;
34393
+ if (record2.type === "system" && record2.subtype === "api_error") {
34394
+ const error2 = record2.error;
34395
+ const status = typeof error2 === "object" && error2 !== null ? numberOrNull(error2.status) : null;
34396
+ if (status === null)
34397
+ return null;
34398
+ const failureClass = classifyTransientStatus(status);
34399
+ if (!failureClass)
34400
+ return null;
34401
+ return {
34402
+ outcome: "retrying",
34403
+ atMs: tsMs,
34404
+ failureClass,
34405
+ httpStatus: status,
34406
+ attempt: numberOrNull(record2.retryAttempt),
34407
+ maxAttempts: numberOrNull(record2.maxRetries)
34408
+ };
34409
+ }
34410
+ if (record2.type !== "assistant")
34411
+ return null;
34412
+ if (record2.isApiErrorMessage === true) {
34413
+ const status = numberOrNull(record2.apiErrorStatus);
34414
+ const failureClass = status === null ? null : classifyTransientStatus(status);
34415
+ if (!failureClass)
34416
+ return null;
34417
+ return {
34418
+ outcome: "failed",
34419
+ atMs: tsMs,
34420
+ failureClass,
34421
+ httpStatus: status,
34422
+ attempt: null,
34423
+ maxAttempts: null
34424
+ };
34425
+ }
34426
+ const message = record2.message;
34427
+ if (typeof message !== "object" || message === null)
34428
+ return null;
34429
+ const msg = message;
34430
+ if (msg.model === "<synthetic>")
34431
+ return null;
34432
+ const usage = msg.usage;
34433
+ if (typeof usage !== "object" || usage === null)
34434
+ return null;
34435
+ const u = usage;
34436
+ const spent = Number(u.input_tokens ?? 0) + Number(u.output_tokens ?? 0) + Number(u.cache_creation_input_tokens ?? 0) + Number(u.cache_read_input_tokens ?? 0);
34437
+ if (!Number.isFinite(spent) || spent <= 0)
34438
+ return null;
34439
+ return {
34440
+ outcome: "served",
34441
+ atMs: tsMs,
34442
+ failureClass: null,
34443
+ httpStatus: null,
34444
+ attempt: null,
34445
+ maxAttempts: null
34446
+ };
34447
+ }
34448
+ function pickNewerTurnFailure(current, next) {
34449
+ if (next.outcome === "unknown")
34450
+ return current;
34451
+ if (current.outcome === "unknown")
34452
+ return next;
34453
+ return (next.atMs ?? 0) >= (current.atMs ?? 0) ? next : current;
34454
+ }
34455
+ function isShapeRecognised(record2) {
34456
+ if (record2.type === "system" && record2.subtype === "api_error") {
34457
+ const error2 = record2.error;
34458
+ return typeof error2 === "object" && error2 !== null && numberOrNull(error2.status) !== null;
34459
+ }
34460
+ if (record2.type === "assistant" && record2.isApiErrorMessage === true) {
34461
+ return numberOrNull(record2.apiErrorStatus) !== null;
34462
+ }
34463
+ return false;
34464
+ }
34465
+ function analyzeTranscriptTurnFailure(jsonl, startMs, endMs, opts = {}) {
34466
+ const maxKeys = opts.maxKeys ?? 40;
34467
+ let newest = UNKNOWN_TURN_FAILURE;
34468
+ let coarse = 0;
34469
+ let fine = 0;
34470
+ const keys = /* @__PURE__ */ new Set();
34471
+ for (const line of jsonl.split("\n")) {
34472
+ const trimmed = line.trim();
34473
+ if (!trimmed)
34474
+ continue;
34475
+ let obj;
34476
+ try {
34477
+ obj = JSON.parse(trimmed);
34478
+ } catch {
34479
+ continue;
34480
+ }
34481
+ if (typeof obj !== "object" || obj === null)
34482
+ continue;
34483
+ const record2 = obj;
34484
+ const ts = record2.timestamp;
34485
+ if (typeof ts !== "string" || !ts)
34486
+ continue;
34487
+ const tsMs = new Date(ts).getTime();
34488
+ if (!Number.isFinite(tsMs) || tsMs < startMs || tsMs > endMs)
34489
+ continue;
34490
+ const classified = classifyRecord(record2, tsMs);
34491
+ if (classified)
34492
+ newest = pickNewerTurnFailure(newest, classified);
34493
+ if (!isErrorShaped(record2) || record2.isSidechain === true)
34494
+ continue;
34495
+ coarse++;
34496
+ if (isShapeRecognised(record2)) {
34497
+ fine++;
34498
+ continue;
34499
+ }
34500
+ if (keys.size < maxKeys) {
34501
+ for (const key2 of Object.keys(record2)) {
34502
+ if (keys.size >= maxKeys)
34503
+ break;
34504
+ keys.add(key2);
34505
+ }
34506
+ }
34507
+ }
34508
+ return { result: newest, coarse, fine, unrecognisedKeys: [...keys].sort() };
34509
+ }
34510
+
34357
34511
  // ../core/dist/claude-code-usage/transcript-location.js
34358
34512
  function encodeClaudeProjectPath(projectDir) {
34359
34513
  return "-" + projectDir.replace(/^\//, "").replace(/[/.]/g, "-");
@@ -35210,7 +35364,7 @@ var FLAG_REGISTRY = [
35210
35364
  },
35211
35365
  {
35212
35366
  key: "wedge-transient-notice",
35213
- description: 'When a wedge-respawn was preceded by a transient LLM-API error (529/429/503/500 that exhausted its retries and wedged the turn), the manager writes the ENG-6058 give-up signal tagged reason=transient_overload so the channel sweeps post a friendly "I hit a brief overload \u2014 please resend" notice instead of leaving the user in silence (ENG-7360, extends ENG-6861 to the retry-exhaustion + wedge path). Boolean gate; ships dark \u2014 channel-visible copy soaks per host before going wide.',
35367
+ description: `Tell the person waiting when their turn dies on a transient LLM-API failure (529 overloaded / 5xx). TWO consumers now share this gate. (1) ENG-7360: a wedge-respawn preceded by such an error writes the ENG-6058 give-up signal tagged reason=transient_overload so the channel sweeps post a "please resend" notice. (2) ENG-8269: the channel MCPs watch the dispatched turn in Claude Code's own transcript and notify the conversation that was actually waiting \u2014 Slack, Telegram AND direct chat \u2014 plus a "still working on this" notice after ~3min on Slack/Telegram only (direct chat already shows a client-side one at 90s). NOTE: (2) fires on a MUCH larger population than (1) \u2014 any dispatched turn that dies, not only one that also wedged the session \u2014 and (1) has never actually been able to fire, because its pane.log detector cannot match the banner Claude Code renders today. So flipping this on is in practice enabling (2) for the first time. Boolean gate; ships dark \u2014 channel-visible copy soaks per host before going wide. Materialized into the channel-MCP spawn env: a Docker-isolated agent never mounts the host flags-cache, so a central flip reaches it only that way.`,
35214
35368
  flagType: "boolean",
35215
35369
  defaultValue: false,
35216
35370
  envVar: "AGT_WEDGE_TRANSIENT_NOTICE_ENABLED"
@@ -37496,6 +37650,149 @@ async function watchForRateLimitRefusal(opts) {
37496
37650
  }
37497
37651
  }
37498
37652
 
37653
+ // src/turn-failure-watch.ts
37654
+ import { readFileSync as readFileSync12, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
37655
+ import { join as join10 } from "path";
37656
+ function turnFailureNoticeEnabled(env2) {
37657
+ return resolveHostBooleanFlag({
37658
+ key: "wedge-transient-notice",
37659
+ envVar: "AGT_WEDGE_TRANSIENT_NOTICE_ENABLED",
37660
+ defaultValue: false,
37661
+ ...env2 ? { env: env2 } : {}
37662
+ });
37663
+ }
37664
+ var DEFAULT_FAILURE_WATCH_MS = 5 * 6e4;
37665
+ var FAST_POLL_MS = 500;
37666
+ var SLOW_POLL_MS = 3e3;
37667
+ var FAST_PHASE_MS = 1e4;
37668
+ var RETRYING_NOTICE_AFTER_MS = 3 * 6e4;
37669
+ var sleep2 = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
37670
+ function classifyTurnFailureSince(opts) {
37671
+ const dir = opts.transcriptDir ?? agentTranscriptDir({ cwd: opts.cwd, home: opts.home });
37672
+ let entries;
37673
+ try {
37674
+ entries = readdirSync2(dir);
37675
+ } catch {
37676
+ return { result: UNKNOWN_TURN_FAILURE, coarse: 0, fine: 0, unrecognisedKeys: [] };
37677
+ }
37678
+ let newest = UNKNOWN_TURN_FAILURE;
37679
+ let coarse = 0;
37680
+ let fine = 0;
37681
+ const unrecognised = /* @__PURE__ */ new Set();
37682
+ for (const name of entries) {
37683
+ if (!name.endsWith(".jsonl")) continue;
37684
+ const path = join10(dir, name);
37685
+ let fingerprint;
37686
+ try {
37687
+ const st = statSync2(path);
37688
+ if (!st.isFile() || st.mtimeMs < opts.sinceMs) continue;
37689
+ fingerprint = `${st.mtimeMs}:${st.size}`;
37690
+ } catch {
37691
+ continue;
37692
+ }
37693
+ const cached2 = opts.cache?.get(path);
37694
+ let scan;
37695
+ if (cached2 && cached2.fingerprint === fingerprint) {
37696
+ scan = cached2.scan;
37697
+ } else {
37698
+ let content;
37699
+ try {
37700
+ content = readFileSync12(path, "utf-8");
37701
+ } catch {
37702
+ continue;
37703
+ }
37704
+ const analysis = analyzeTranscriptTurnFailure(content, opts.sinceMs, opts.nowMs);
37705
+ scan = {
37706
+ result: analysis.result,
37707
+ coarse: analysis.coarse,
37708
+ fine: analysis.fine,
37709
+ unrecognisedKeys: analysis.unrecognisedKeys
37710
+ };
37711
+ opts.cache?.set(path, { fingerprint, scan });
37712
+ }
37713
+ newest = pickNewerTurnFailure(newest, scan.result);
37714
+ coarse += scan.coarse;
37715
+ fine += scan.fine;
37716
+ for (const key2 of scan.unrecognisedKeys) unrecognised.add(key2);
37717
+ }
37718
+ return { result: newest, coarse, fine, unrecognisedKeys: [...unrecognised].sort() };
37719
+ }
37720
+ function emitDriftTelemetryIfBlind(args) {
37721
+ if (args.coarse <= 0 || args.fine > 0) return;
37722
+ try {
37723
+ process.stderr.write(
37724
+ `agt.transcript.api_error.unclassified ${JSON.stringify({
37725
+ channel: args.channel,
37726
+ agent_code: process.env.AGT_AGENT_CODE_NAME ?? "unknown",
37727
+ coarse: args.coarse,
37728
+ keys: args.unrecognisedKeys.slice(0, 20)
37729
+ })}
37730
+ `
37731
+ );
37732
+ } catch {
37733
+ }
37734
+ }
37735
+ async function watchForTurnFailure(opts) {
37736
+ const now = opts.now ?? (() => Date.now());
37737
+ const wait = opts.wait ?? sleep2;
37738
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_FAILURE_WATCH_MS;
37739
+ const fastPollMs = opts.fastPollMs ?? FAST_POLL_MS;
37740
+ const slowPollMs = opts.slowPollMs ?? SLOW_POLL_MS;
37741
+ const fastPhaseMs = opts.fastPhaseMs ?? FAST_PHASE_MS;
37742
+ const retryingAfterMs = opts.retryingNoticeAfterMs ?? RETRYING_NOTICE_AFTER_MS;
37743
+ const startedAt = now();
37744
+ const deadline = startedAt + timeoutMs;
37745
+ let longRetryFired = false;
37746
+ let driftEmitted = false;
37747
+ const cache = /* @__PURE__ */ new Map();
37748
+ for (; ; ) {
37749
+ let scan;
37750
+ try {
37751
+ scan = classifyTurnFailureSince({
37752
+ sinceMs: opts.sinceMs,
37753
+ // No upper bound, deliberately. Two reasons, and the second is a
37754
+ // correctness requirement of the cache above:
37755
+ // 1. For a post-dispatch watch the question is "has anything since the
37756
+ // dispatch killed this turn?" — a future-dated entry is still an
37757
+ // answer, and rejecting it on clock skew would drop the notice.
37758
+ // 2. A window that widens each poll makes a cached scan unsound: an
37759
+ // entry stamped a hair ahead of our clock would be parsed as
37760
+ // out-of-window, cached as "not seen", and — because a DEAD turn
37761
+ // produces no further appends to invalidate the entry — never looked
37762
+ // at again. That is exactly the silence this feature exists to end.
37763
+ nowMs: Number.POSITIVE_INFINITY,
37764
+ transcriptDir: opts.transcriptDir,
37765
+ cwd: opts.cwd,
37766
+ home: opts.home,
37767
+ cache
37768
+ });
37769
+ } catch {
37770
+ return null;
37771
+ }
37772
+ if (!driftEmitted && scan.coarse > 0 && scan.fine === 0) {
37773
+ driftEmitted = true;
37774
+ emitDriftTelemetryIfBlind({
37775
+ channel: opts.channel ?? "unknown",
37776
+ coarse: scan.coarse,
37777
+ fine: scan.fine,
37778
+ unrecognisedKeys: scan.unrecognisedKeys
37779
+ });
37780
+ }
37781
+ const { result } = scan;
37782
+ if (result.outcome === "failed") return result;
37783
+ if (result.outcome === "served") return null;
37784
+ if (result.outcome === "retrying" && !longRetryFired && opts.onLongRetry && now() - startedAt >= retryingAfterMs) {
37785
+ longRetryFired = true;
37786
+ try {
37787
+ await opts.onLongRetry(result);
37788
+ } catch {
37789
+ }
37790
+ }
37791
+ if (now() >= deadline) return null;
37792
+ await wait(now() - startedAt < fastPhaseMs ? fastPollMs : slowPollMs);
37793
+ }
37794
+ }
37795
+
37499
37796
  // src/usage-limit-reactive-decision.ts
37500
37797
  import { createHash } from "crypto";
37501
37798
  function shouldReadPredictiveMarker(mode) {
@@ -37522,7 +37819,7 @@ function describeUnparsedRefusal(text) {
37522
37819
  import { execFile as execFile2 } from "child_process";
37523
37820
  import { existsSync as existsSync5, mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
37524
37821
  import { homedir as homedir6 } from "os";
37525
- import { join as join10 } from "path";
37822
+ import { join as join11 } from "path";
37526
37823
  var DEFAULT_CLAUDE_EVAL_MODEL = "claude-haiku-4-5-20251001";
37527
37824
  var DEFAULT_ANTHROPIC_MESSAGES_URL = "https://api.anthropic.com/v1/messages";
37528
37825
  var ANTHROPIC_API_VERSION = "2023-06-01";
@@ -37621,12 +37918,12 @@ async function runAnthropicMessages(prompt, opts) {
37621
37918
  var emptyMcpConfigPath = null;
37622
37919
  function ensureEmptyMcpConfig() {
37623
37920
  if (emptyMcpConfigPath && existsSync5(emptyMcpConfigPath)) return emptyMcpConfigPath;
37624
- const dir = join10(homedir6(), ".augmented");
37921
+ const dir = join11(homedir6(), ".augmented");
37625
37922
  try {
37626
37923
  mkdirSync6(dir, { recursive: true });
37627
37924
  } catch {
37628
37925
  }
37629
- const p2 = join10(dir, ".reply-intent-empty-mcp.json");
37926
+ const p2 = join11(dir, ".reply-intent-empty-mcp.json");
37630
37927
  writeFileSync7(p2, JSON.stringify({ mcpServers: {} }));
37631
37928
  emptyMcpConfigPath = p2;
37632
37929
  return p2;
@@ -37773,17 +38070,17 @@ function emitTransientApiErrorTelemetry(channel, match, original) {
37773
38070
  }
37774
38071
 
37775
38072
  // src/telegram-pending-inbound-cleanup.ts
37776
- import { readdirSync as readdirSync2, readFileSync as readFileSync12, statSync as statSync2 } from "fs";
37777
- import { join as join11 } from "path";
38073
+ import { readdirSync as readdirSync3, readFileSync as readFileSync13, statSync as statSync3 } from "fs";
38074
+ import { join as join12 } from "path";
37778
38075
  function markerArrivalMs(fullPath) {
37779
38076
  try {
37780
- const received = JSON.parse(readFileSync12(fullPath, "utf-8")).received_at;
38077
+ const received = JSON.parse(readFileSync13(fullPath, "utf-8")).received_at;
37781
38078
  const parsed = received ? Date.parse(received) : Number.NaN;
37782
38079
  if (Number.isFinite(parsed)) return parsed;
37783
38080
  } catch {
37784
38081
  }
37785
38082
  try {
37786
- return statSync2(fullPath).mtimeMs;
38083
+ return statSync3(fullPath).mtimeMs;
37787
38084
  } catch {
37788
38085
  return Number.POSITIVE_INFINITY;
37789
38086
  }
@@ -37799,7 +38096,7 @@ function applyToChatMarkers(pendingDir, chatId, op, cutoffMs) {
37799
38096
  const bounded = Number.isFinite(cutoffMs);
37800
38097
  let filenames;
37801
38098
  try {
37802
- filenames = readdirSync2(pendingDir);
38099
+ filenames = readdirSync3(pendingDir);
37803
38100
  } catch {
37804
38101
  return 0;
37805
38102
  }
@@ -37807,7 +38104,7 @@ function applyToChatMarkers(pendingDir, chatId, op, cutoffMs) {
37807
38104
  for (const filename of filenames) {
37808
38105
  if (!filename.startsWith(prefix)) continue;
37809
38106
  if (!filename.endsWith(".json")) continue;
37810
- const fullPath = join11(pendingDir, filename);
38107
+ const fullPath = join12(pendingDir, filename);
37811
38108
  if (bounded && markerArrivalMs(fullPath) > cutoffMs) continue;
37812
38109
  op(fullPath);
37813
38110
  applied++;
@@ -37817,12 +38114,12 @@ function applyToChatMarkers(pendingDir, chatId, op, cutoffMs) {
37817
38114
 
37818
38115
  // src/recovery-ledger.ts
37819
38116
  import { existsSync as existsSync6, unlinkSync as unlinkSync6 } from "fs";
37820
- import { join as join12 } from "path";
38117
+ import { join as join13 } from "path";
37821
38118
  function recoveryLedgerEntryExists(ledgerDir, markerName, exists = (p2) => existsSync6(p2)) {
37822
38119
  if (!ledgerDir || !markerName) return false;
37823
38120
  if (markerName.includes("/") || markerName.includes("\\") || markerName.includes("..")) return false;
37824
38121
  try {
37825
- return exists(join12(ledgerDir, markerName));
38122
+ return exists(join13(ledgerDir, markerName));
37826
38123
  } catch {
37827
38124
  return false;
37828
38125
  }
@@ -37833,14 +38130,14 @@ function removeRecoveryLedgerEntry(ledgerDir, markerName, unlink = (p2) => {
37833
38130
  if (!ledgerDir || !markerName) return;
37834
38131
  if (markerName.includes("/") || markerName.includes("\\") || markerName.includes("..")) return;
37835
38132
  try {
37836
- unlink(join12(ledgerDir, markerName));
38133
+ unlink(join13(ledgerDir, markerName));
37837
38134
  } catch {
37838
38135
  }
37839
38136
  }
37840
38137
 
37841
38138
  // src/inbound-delivery-ledger.ts
37842
- import { existsSync as existsSync7, mkdirSync as mkdirSync7, readdirSync as readdirSync3, readFileSync as readFileSync13, renameSync as renameSync5, writeFileSync as writeFileSync8 } from "fs";
37843
- import { join as join13 } from "path";
38139
+ import { existsSync as existsSync7, mkdirSync as mkdirSync7, readdirSync as readdirSync4, readFileSync as readFileSync14, renameSync as renameSync5, writeFileSync as writeFileSync8 } from "fs";
38140
+ import { join as join14 } from "path";
37844
38141
  function safeInboundId(inboundId) {
37845
38142
  return inboundId.replace(/[^A-Za-z0-9_-]/g, "_");
37846
38143
  }
@@ -37848,8 +38145,8 @@ var defaultDeps = {
37848
38145
  mkdir: (dir) => mkdirSync7(dir, { recursive: true }),
37849
38146
  writeFile: (path, data) => writeFileSync8(path, data, "utf8"),
37850
38147
  rename: (from, to) => renameSync5(from, to),
37851
- readdir: (dir) => readdirSync3(dir),
37852
- readFile: (path) => readFileSync13(path, "utf8"),
38148
+ readdir: (dir) => readdirSync4(dir),
38149
+ readFile: (path) => readFileSync14(path, "utf8"),
37853
38150
  exists: (path) => existsSync7(path)
37854
38151
  };
37855
38152
  function writeInboundDeliveryLedgerEntry(dir, record2, deps = defaultDeps) {
@@ -37858,7 +38155,7 @@ function writeInboundDeliveryLedgerEntry(dir, record2, deps = defaultDeps) {
37858
38155
  if (!safe || safe.includes("/") || safe.includes("\\") || safe.includes("..")) return;
37859
38156
  try {
37860
38157
  deps.mkdir(dir);
37861
- const final = join13(dir, `${safe}.json`);
38158
+ const final = join14(dir, `${safe}.json`);
37862
38159
  const tmp = `${final}.tmp`;
37863
38160
  deps.writeFile(tmp, JSON.stringify(record2));
37864
38161
  deps.rename(tmp, final);
@@ -38063,14 +38360,14 @@ function createKanbanCardActiveClient(args) {
38063
38360
  import {
38064
38361
  existsSync as existsSync8,
38065
38362
  mkdirSync as mkdirSync8,
38066
- readFileSync as readFileSync14,
38363
+ readFileSync as readFileSync15,
38067
38364
  renameSync as renameSync6,
38068
- statSync as statSync3,
38365
+ statSync as statSync4,
38069
38366
  unlinkSync as unlinkSync7,
38070
38367
  utimesSync,
38071
38368
  writeFileSync as writeFileSync9
38072
38369
  } from "fs";
38073
- import { join as join14 } from "path";
38370
+ import { join as join15 } from "path";
38074
38371
  var STALE_LOCK_MS = 9e4;
38075
38372
  var HEARTBEAT_INTERVAL_MS = 3e4;
38076
38373
  function defaultIsPidAlive(pid) {
@@ -38093,7 +38390,7 @@ function acquireMcpSpawnLock(args) {
38093
38390
  const nowMs = options.nowMs ?? (() => Date.now());
38094
38391
  const lockMtimeMs = options.lockMtimeMs ?? defaultLockMtimeMs;
38095
38392
  const staleMs = options.staleMs ?? STALE_LOCK_MS;
38096
- const path = join14(agentDir, basename2);
38393
+ const path = join15(agentDir, basename2);
38097
38394
  const existing = readLockHolder(path);
38098
38395
  if (existing) {
38099
38396
  if (existing.pid === selfPid) {
@@ -38152,7 +38449,7 @@ function startMcpSpawnLockHeartbeat(lockPath, opts = {}) {
38152
38449
  }
38153
38450
  function defaultLockMtimeMs(path) {
38154
38451
  try {
38155
- return statSync3(path).mtimeMs;
38452
+ return statSync4(path).mtimeMs;
38156
38453
  } catch {
38157
38454
  return null;
38158
38455
  }
@@ -38160,7 +38457,7 @@ function defaultLockMtimeMs(path) {
38160
38457
  function readLockHolder(path) {
38161
38458
  if (!existsSync8(path)) return null;
38162
38459
  try {
38163
- const raw = readFileSync14(path, "utf8");
38460
+ const raw = readFileSync15(path, "utf8");
38164
38461
  const parsed = JSON.parse(raw);
38165
38462
  const pid = typeof parsed.pid === "number" ? parsed.pid : Number(parsed.pid);
38166
38463
  if (!Number.isFinite(pid) || pid <= 0) return null;
@@ -38172,8 +38469,8 @@ function readLockHolder(path) {
38172
38469
  }
38173
38470
 
38174
38471
  // src/ack-reaction.ts
38175
- import { readdirSync as readdirSync4, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "fs";
38176
- import { join as join15 } from "path";
38472
+ import { readdirSync as readdirSync5, readFileSync as readFileSync16, writeFileSync as writeFileSync10 } from "fs";
38473
+ import { join as join16 } from "path";
38177
38474
  var REPLY_WEDGED_THRESHOLD_MS = 5 * 60 * 1e3;
38178
38475
  var ACK_STARTUP_GRACE_MS = 6e4;
38179
38476
  var ACK_PANE_FRESH_THRESHOLD_MS = 6e4;
@@ -38246,7 +38543,7 @@ var GIVE_UP_SIGNAL_MAX_AGE_MS = 30 * 60 * 1e3;
38246
38543
  function readGiveUpSignal(path, now = Date.now()) {
38247
38544
  if (!path) return null;
38248
38545
  try {
38249
- const raw = JSON.parse(readFileSync15(path, "utf8"));
38546
+ const raw = JSON.parse(readFileSync16(path, "utf8"));
38250
38547
  if (typeof raw.gave_up_at !== "string") return null;
38251
38548
  const t = Date.parse(raw.gave_up_at);
38252
38549
  if (!Number.isFinite(t) || t > now) return null;
@@ -38268,11 +38565,17 @@ function giveUpNoticeText(reason = null) {
38268
38565
  }
38269
38566
  return "\u26A0\uFE0F I couldn't read your last message \u2014 please resend it.";
38270
38567
  }
38568
+ function turnFailedNoticeText() {
38569
+ return "\u26A0\uFE0F Something went wrong on my side and I couldn\u2019t finish that. Sorry \u2014 send it again when you get a chance and I\u2019ll pick it straight up.";
38570
+ }
38571
+ function turnRetryingNoticeText() {
38572
+ return "\u23F3 Still working on this \u2014 things are running slower than usual on my side. I\u2019ll reply as soon as I have it.";
38573
+ }
38271
38574
  function oldestPendingMarkerAgeMs(dir, now = Date.now(), opts) {
38272
38575
  if (!dir) return null;
38273
38576
  let names;
38274
38577
  try {
38275
- names = readdirSync4(dir);
38578
+ names = readdirSync5(dir);
38276
38579
  } catch {
38277
38580
  return null;
38278
38581
  }
@@ -38281,7 +38584,7 @@ function oldestPendingMarkerAgeMs(dir, now = Date.now(), opts) {
38281
38584
  if (!name.endsWith(".json")) continue;
38282
38585
  let receivedAt;
38283
38586
  try {
38284
- const raw = JSON.parse(readFileSync15(join15(dir, name), "utf-8"));
38587
+ const raw = JSON.parse(readFileSync16(join16(dir, name), "utf-8"));
38285
38588
  if (raw.discretionary === true) continue;
38286
38589
  if (!opts?.includeSeen && typeof raw.seen_at === "string" && raw.seen_at) continue;
38287
38590
  receivedAt = raw.received_at;
@@ -38358,14 +38661,14 @@ function isMarkerGenuinelyAged(receivedAt, nowMs, thresholdMs) {
38358
38661
  }
38359
38662
  var DEFLECTION_COUNTER_SUFFIX = "-deflections.json";
38360
38663
  function deflectionCounterPath(agentDir, channel) {
38361
- return join15(agentDir, `${channel}${DEFLECTION_COUNTER_SUFFIX}`);
38664
+ return join16(agentDir, `${channel}${DEFLECTION_COUNTER_SUFFIX}`);
38362
38665
  }
38363
38666
  function recordChannelDeflection(agentDir, channel, cause) {
38364
38667
  if (!agentDir) return;
38365
38668
  const path = deflectionCounterPath(agentDir, channel);
38366
38669
  let counts = {};
38367
38670
  try {
38368
- const parsed = JSON.parse(readFileSync15(path, "utf-8"));
38671
+ const parsed = JSON.parse(readFileSync16(path, "utf-8"));
38369
38672
  if (parsed && typeof parsed === "object") counts = parsed;
38370
38673
  } catch {
38371
38674
  }
@@ -38446,7 +38749,7 @@ function redactId(id) {
38446
38749
  }
38447
38750
  var BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
38448
38751
  var AGENT_CODE_NAME = process.env.AGT_AGENT_CODE_NAME ?? "unknown";
38449
- var TELEGRAM_AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join16(homedir7(), ".augmented", AGENT_CODE_NAME) : null;
38752
+ var TELEGRAM_AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join17(homedir7(), ".augmented", AGENT_CODE_NAME) : null;
38450
38753
  var AGT_HOST = process.env.AGT_HOST ?? null;
38451
38754
  var AGT_API_KEY = process.env.AGT_API_KEY ?? null;
38452
38755
  var AGT_AGENT_ID = process.env.AGT_AGENT_ID ?? null;
@@ -38549,11 +38852,11 @@ var peerRateLimiter = peerRateApiClient && parsePeerAgentModeEnv(process.env.TEL
38549
38852
  log: (line) => process.stderr.write(`telegram-channel(${AGENT_CODE_NAME}): ${line}
38550
38853
  `)
38551
38854
  }) : createPeerRateLimiter();
38552
- var PEER_PRESENCE_STATE_FILE = TELEGRAM_AGENT_DIR ? join16(TELEGRAM_AGENT_DIR, "telegram-peer-presence.json") : null;
38855
+ var PEER_PRESENCE_STATE_FILE = TELEGRAM_AGENT_DIR ? join17(TELEGRAM_AGENT_DIR, "telegram-peer-presence.json") : null;
38553
38856
  function readPeerPresenceState() {
38554
38857
  if (!PEER_PRESENCE_STATE_FILE) return null;
38555
38858
  try {
38556
- const parsed = JSON.parse(readFileSync16(PEER_PRESENCE_STATE_FILE, "utf-8"));
38859
+ const parsed = JSON.parse(readFileSync17(PEER_PRESENCE_STATE_FILE, "utf-8"));
38557
38860
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
38558
38861
  return parsed;
38559
38862
  } catch {
@@ -38588,9 +38891,9 @@ if (!BOT_TOKEN) {
38588
38891
  var stderrLogStream = null;
38589
38892
  if (AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown") {
38590
38893
  try {
38591
- const logDir = join16(homedir7(), ".augmented", AGENT_CODE_NAME);
38894
+ const logDir = join17(homedir7(), ".augmented", AGENT_CODE_NAME);
38592
38895
  mkdirSync9(logDir, { recursive: true });
38593
- stderrLogStream = createWriteStream(join16(logDir, "telegram-channel-stderr.log"), {
38896
+ stderrLogStream = createWriteStream(join17(logDir, "telegram-channel-stderr.log"), {
38594
38897
  flags: "a",
38595
38898
  mode: 384
38596
38899
  });
@@ -39027,7 +39330,7 @@ function scheduleBusyAck(chatId, messageId, arrivedWhileBusy) {
39027
39330
  let paneLogFreshAgeMs = null;
39028
39331
  if (AGENT_DIR) {
39029
39332
  try {
39030
- const paneMtimeMs = statSync4(join16(AGENT_DIR, "pane.log")).mtimeMs;
39333
+ const paneMtimeMs = statSync5(join17(AGENT_DIR, "pane.log")).mtimeMs;
39031
39334
  paneLogFreshAgeMs = Math.max(0, Date.now() - paneMtimeMs);
39032
39335
  } catch {
39033
39336
  }
@@ -39057,7 +39360,7 @@ function scheduleBusyAck(chatId, messageId, arrivedWhileBusy) {
39057
39360
  function __resetBusyAckNoticeThrottle() {
39058
39361
  lastBusyAckNoticeAt.clear();
39059
39362
  }
39060
- var RESTART_FLAGS_DIR = join16(homedir7(), ".augmented", "restart-flags");
39363
+ var RESTART_FLAGS_DIR = join17(homedir7(), ".augmented", "restart-flags");
39061
39364
  function actuateHostRestartTelegram() {
39062
39365
  return actuateHostRestart({
39063
39366
  agtHost: AGT_HOST,
@@ -39442,7 +39745,7 @@ async function handleRestartCommand(opts) {
39442
39745
  if (!existsSync9(RESTART_FLAGS_DIR)) {
39443
39746
  mkdirSync9(RESTART_FLAGS_DIR, { recursive: true });
39444
39747
  }
39445
- const flagPath = join16(RESTART_FLAGS_DIR, `${AGENT_CODE_NAME}.flag`);
39748
+ const flagPath = join17(RESTART_FLAGS_DIR, `${AGENT_CODE_NAME}.flag`);
39446
39749
  const flag = {
39447
39750
  codeName: AGENT_CODE_NAME,
39448
39751
  source: "telegram",
@@ -39848,16 +40151,16 @@ async function classifyRestartCommand(text) {
39848
40151
  if (!ours) return "verification_failed";
39849
40152
  return target === ours ? "act" : "ignore";
39850
40153
  }
39851
- var AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join16(homedir7(), ".augmented", AGENT_CODE_NAME) : null;
39852
- var PENDING_INBOUND_DIR = AGENT_DIR ? join16(AGENT_DIR, "telegram-pending-inbound") : null;
39853
- var RECOVERY_OUTBOX_DIR = AGENT_DIR ? join16(AGENT_DIR, "telegram-recovery-outbox") : null;
39854
- var RECOVERY_LEDGER_DIR = AGENT_DIR ? join16(AGENT_DIR, ".agt-telegram-recovery-ledger") : null;
39855
- var DELIVERY_LEDGER_DIR = AGENT_DIR ? join16(AGENT_DIR, ".agt-inbound-delivery-ledger") : null;
39856
- var RESTART_CONFIRM_FILE = AGENT_DIR ? join16(AGENT_DIR, "telegram-restart-confirm.json") : null;
40154
+ var AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join17(homedir7(), ".augmented", AGENT_CODE_NAME) : null;
40155
+ var PENDING_INBOUND_DIR = AGENT_DIR ? join17(AGENT_DIR, "telegram-pending-inbound") : null;
40156
+ var RECOVERY_OUTBOX_DIR = AGENT_DIR ? join17(AGENT_DIR, "telegram-recovery-outbox") : null;
40157
+ var RECOVERY_LEDGER_DIR = AGENT_DIR ? join17(AGENT_DIR, ".agt-telegram-recovery-ledger") : null;
40158
+ var DELIVERY_LEDGER_DIR = AGENT_DIR ? join17(AGENT_DIR, ".agt-inbound-delivery-ledger") : null;
40159
+ var RESTART_CONFIRM_FILE = AGENT_DIR ? join17(AGENT_DIR, "telegram-restart-confirm.json") : null;
39857
40160
  var TELEGRAM_PROCESS_BOOT_MS = Date.now();
39858
- var TELEGRAM_RECENT_DMS_FILE = AGENT_DIR ? join16(AGENT_DIR, "telegram-recent-dms.json") : null;
39859
- var TELEGRAM_CHANNEL_ADD_RESTART_FILE = AGENT_DIR ? join16(AGENT_DIR, "telegram-channel-add-restart.json") : null;
39860
- var TELEGRAM_OFFSET_FILE = AGENT_DIR ? join16(AGENT_DIR, "telegram-getupdates-offset.json") : null;
40161
+ var TELEGRAM_RECENT_DMS_FILE = AGENT_DIR ? join17(AGENT_DIR, "telegram-recent-dms.json") : null;
40162
+ var TELEGRAM_CHANNEL_ADD_RESTART_FILE = AGENT_DIR ? join17(AGENT_DIR, "telegram-channel-add-restart.json") : null;
40163
+ var TELEGRAM_OFFSET_FILE = AGENT_DIR ? join17(AGENT_DIR, "telegram-getupdates-offset.json") : null;
39861
40164
  var recentDms = /* @__PURE__ */ new Map();
39862
40165
  var recentDmPersister = TELEGRAM_RECENT_DMS_FILE ? createRecentDmPersister({
39863
40166
  filePath: TELEGRAM_RECENT_DMS_FILE,
@@ -39912,7 +40215,7 @@ function safeMarkerName(chatId, messageId) {
39912
40215
  }
39913
40216
  function pendingInboundPath(chatId, messageId) {
39914
40217
  if (!PENDING_INBOUND_DIR) return null;
39915
- return join16(PENDING_INBOUND_DIR, safeMarkerName(chatId, messageId));
40218
+ return join17(PENDING_INBOUND_DIR, safeMarkerName(chatId, messageId));
39916
40219
  }
39917
40220
  function writePendingInboundMarker(chatId, messageId, chatType, undeliverable = false, payload) {
39918
40221
  const path = pendingInboundPath(chatId, messageId);
@@ -39960,7 +40263,7 @@ function rewriteTelegramMarkerInPlace(path, marker) {
39960
40263
  function clearTelegramMarkerFileWithHeal(fullPath) {
39961
40264
  let marker = null;
39962
40265
  try {
39963
- marker = JSON.parse(readFileSync16(fullPath, "utf-8"));
40266
+ marker = JSON.parse(readFileSync17(fullPath, "utf-8"));
39964
40267
  } catch {
39965
40268
  }
39966
40269
  if (marker && decideRecoveryHeal({
@@ -39977,7 +40280,7 @@ function clearTelegramMarkerFileWithHeal(fullPath) {
39977
40280
  function markTelegramMarkerSeenInPlace(fullPath) {
39978
40281
  let marker;
39979
40282
  try {
39980
- marker = JSON.parse(readFileSync16(fullPath, "utf-8"));
40283
+ marker = JSON.parse(readFileSync17(fullPath, "utf-8"));
39981
40284
  } catch {
39982
40285
  return;
39983
40286
  }
@@ -39989,7 +40292,7 @@ function markTelegramMarkerSeenInPlace(fullPath) {
39989
40292
  function markTelegramMarkerSeenWithHeal(fullPath) {
39990
40293
  let marker = null;
39991
40294
  try {
39992
- marker = JSON.parse(readFileSync16(fullPath, "utf-8"));
40295
+ marker = JSON.parse(readFileSync17(fullPath, "utf-8"));
39993
40296
  } catch {
39994
40297
  return;
39995
40298
  }
@@ -40005,7 +40308,7 @@ function readPendingInboundMarker(chatId, messageId) {
40005
40308
  const path = pendingInboundPath(chatId, messageId);
40006
40309
  if (!path || !existsSync9(path)) return null;
40007
40310
  try {
40008
- return JSON.parse(readFileSync16(path, "utf-8"));
40311
+ return JSON.parse(readFileSync17(path, "utf-8"));
40009
40312
  } catch {
40010
40313
  return null;
40011
40314
  }
@@ -40025,10 +40328,10 @@ function nextRetryName(filename) {
40025
40328
  async function processRecoveryOutboxFile(filename) {
40026
40329
  if (!RECOVERY_OUTBOX_DIR) return;
40027
40330
  if (filename.endsWith(".poison.json") || filename.endsWith(".tmp")) return;
40028
- const fullPath = join16(RECOVERY_OUTBOX_DIR, filename);
40331
+ const fullPath = join17(RECOVERY_OUTBOX_DIR, filename);
40029
40332
  let payload;
40030
40333
  try {
40031
- const raw = readFileSync16(fullPath, "utf-8");
40334
+ const raw = readFileSync17(fullPath, "utf-8");
40032
40335
  payload = JSON.parse(raw);
40033
40336
  } catch (err) {
40034
40337
  process.stderr.write(
@@ -40122,7 +40425,7 @@ async function processRecoveryOutboxFile(filename) {
40122
40425
  const next = nextRetryName(filename);
40123
40426
  if (next) {
40124
40427
  try {
40125
- renameSync7(fullPath, join16(RECOVERY_OUTBOX_DIR, next.next));
40428
+ renameSync7(fullPath, join17(RECOVERY_OUTBOX_DIR, next.next));
40126
40429
  if (next.attempt >= MAX_RECOVERY_ATTEMPTS) {
40127
40430
  process.stderr.write(
40128
40431
  `telegram-channel(${AGENT_CODE_NAME}): ghost-reply recovery exhausted retries \u2014 moved to ${next.next}
@@ -40154,7 +40457,7 @@ function scanRecoveryRetries() {
40154
40457
  if (!RECOVERY_OUTBOX_DIR) return;
40155
40458
  let entries;
40156
40459
  try {
40157
- entries = readdirSync5(RECOVERY_OUTBOX_DIR);
40460
+ entries = readdirSync6(RECOVERY_OUTBOX_DIR);
40158
40461
  } catch {
40159
40462
  return;
40160
40463
  }
@@ -40163,7 +40466,7 @@ function scanRecoveryRetries() {
40163
40466
  if (!f.includes(".retry-") || f.endsWith(".poison.json")) continue;
40164
40467
  let mtimeMs;
40165
40468
  try {
40166
- mtimeMs = statSync4(join16(RECOVERY_OUTBOX_DIR, f)).mtimeMs;
40469
+ mtimeMs = statSync5(join17(RECOVERY_OUTBOX_DIR, f)).mtimeMs;
40167
40470
  } catch {
40168
40471
  continue;
40169
40472
  }
@@ -40184,7 +40487,7 @@ function startRecoveryOutboxWatcher() {
40184
40487
  return;
40185
40488
  }
40186
40489
  try {
40187
- for (const f of readdirSync5(RECOVERY_OUTBOX_DIR)) {
40490
+ for (const f of readdirSync6(RECOVERY_OUTBOX_DIR)) {
40188
40491
  if (isFirstAttemptOutboxFile(f)) void processRecoveryOutboxFile(f);
40189
40492
  }
40190
40493
  } catch {
@@ -40193,7 +40496,7 @@ function startRecoveryOutboxWatcher() {
40193
40496
  const watcher = watch(RECOVERY_OUTBOX_DIR, (event, filename) => {
40194
40497
  if (event !== "rename" || !filename) return;
40195
40498
  if (!isFirstAttemptOutboxFile(filename)) return;
40196
- if (existsSync9(join16(RECOVERY_OUTBOX_DIR, filename))) {
40499
+ if (existsSync9(join17(RECOVERY_OUTBOX_DIR, filename))) {
40197
40500
  void processRecoveryOutboxFile(filename);
40198
40501
  }
40199
40502
  });
@@ -40208,7 +40511,7 @@ function startRecoveryOutboxWatcher() {
40208
40511
  retryTimer.unref?.();
40209
40512
  }
40210
40513
  startRecoveryOutboxWatcher();
40211
- var NOTICE_OUTBOX_DIR = AGENT_DIR ? join16(AGENT_DIR, "telegram-notice-outbox") : null;
40514
+ var NOTICE_OUTBOX_DIR = AGENT_DIR ? join17(AGENT_DIR, "telegram-notice-outbox") : null;
40212
40515
  var NOTICE_MAX_AGE_MS = 9e4;
40213
40516
  var NOTICE_INFLIGHT = /* @__PURE__ */ new Set();
40214
40517
  async function processNoticeOutboxFile(filename) {
@@ -40216,11 +40519,11 @@ async function processNoticeOutboxFile(filename) {
40216
40519
  if (filename.startsWith(".") || filename.endsWith(".tmp") || !filename.endsWith(".json")) return;
40217
40520
  if (NOTICE_INFLIGHT.has(filename)) return;
40218
40521
  NOTICE_INFLIGHT.add(filename);
40219
- const fullPath = join16(NOTICE_OUTBOX_DIR, filename);
40522
+ const fullPath = join17(NOTICE_OUTBOX_DIR, filename);
40220
40523
  try {
40221
40524
  let mtimeMs;
40222
40525
  try {
40223
- mtimeMs = statSync4(fullPath).mtimeMs;
40526
+ mtimeMs = statSync5(fullPath).mtimeMs;
40224
40527
  } catch {
40225
40528
  return;
40226
40529
  }
@@ -40233,7 +40536,7 @@ async function processNoticeOutboxFile(filename) {
40233
40536
  }
40234
40537
  let payload;
40235
40538
  try {
40236
- const parsed = JSON.parse(readFileSync16(fullPath, "utf-8"));
40539
+ const parsed = JSON.parse(readFileSync17(fullPath, "utf-8"));
40237
40540
  if (!parsed || typeof parsed !== "object") throw new Error("not an object");
40238
40541
  payload = parsed;
40239
40542
  } catch {
@@ -40304,13 +40607,13 @@ function startNoticeOutboxWatcher() {
40304
40607
  return;
40305
40608
  }
40306
40609
  try {
40307
- for (const f of readdirSync5(NOTICE_OUTBOX_DIR)) void processNoticeOutboxFile(f);
40610
+ for (const f of readdirSync6(NOTICE_OUTBOX_DIR)) void processNoticeOutboxFile(f);
40308
40611
  } catch {
40309
40612
  }
40310
40613
  try {
40311
40614
  const watcher = watch(NOTICE_OUTBOX_DIR, (event, filename) => {
40312
40615
  if (event !== "rename" || !filename) return;
40313
- if (existsSync9(join16(NOTICE_OUTBOX_DIR, filename))) void processNoticeOutboxFile(filename);
40616
+ if (existsSync9(join17(NOTICE_OUTBOX_DIR, filename))) void processNoticeOutboxFile(filename);
40314
40617
  });
40315
40618
  watcher.unref?.();
40316
40619
  } catch (err) {
@@ -40330,7 +40633,7 @@ function sweepTelegramStaleMarkers(thresholdMs) {
40330
40633
  if (!existsSync9(PENDING_INBOUND_DIR)) return;
40331
40634
  let filenames;
40332
40635
  try {
40333
- filenames = readdirSync5(PENDING_INBOUND_DIR);
40636
+ filenames = readdirSync6(PENDING_INBOUND_DIR);
40334
40637
  } catch (err) {
40335
40638
  process.stderr.write(
40336
40639
  `telegram-channel(${AGENT_CODE_NAME}): stale-marker readdir failed: ${err.message}
@@ -40344,10 +40647,10 @@ function sweepTelegramStaleMarkers(thresholdMs) {
40344
40647
  for (const filename of filenames) {
40345
40648
  if (!filename.endsWith(".json")) continue;
40346
40649
  if (filename.endsWith(".tmp")) continue;
40347
- const fullPath = join16(PENDING_INBOUND_DIR, filename);
40650
+ const fullPath = join17(PENDING_INBOUND_DIR, filename);
40348
40651
  let marker;
40349
40652
  try {
40350
- marker = JSON.parse(readFileSync16(fullPath, "utf-8"));
40653
+ marker = JSON.parse(readFileSync17(fullPath, "utf-8"));
40351
40654
  } catch (err) {
40352
40655
  process.stderr.write(
40353
40656
  `telegram-channel(${AGENT_CODE_NAME}): stale-marker parse failed for ${redactId(filename)}: ${err.message}
@@ -40400,13 +40703,13 @@ var orphanSweepTimer = setInterval(() => {
40400
40703
  checkWatchdogGiveUpNotice();
40401
40704
  }, orphanSweepIntervalMs());
40402
40705
  orphanSweepTimer.unref?.();
40403
- var TELEGRAM_PROGRESS_HEARTBEAT_PATH = AGENT_DIR ? join16(AGENT_DIR, "channel-progress-heartbeat.json") : null;
40706
+ var TELEGRAM_PROGRESS_HEARTBEAT_PATH = AGENT_DIR ? join17(AGENT_DIR, "channel-progress-heartbeat.json") : null;
40404
40707
  var telegramTrackedProgress = null;
40405
40708
  var telegramProgressTickRunning = false;
40406
40709
  function readTelegramProgressHeartbeat() {
40407
40710
  if (!TELEGRAM_PROGRESS_HEARTBEAT_PATH || !existsSync9(TELEGRAM_PROGRESS_HEARTBEAT_PATH)) return null;
40408
40711
  try {
40409
- return parseProgressHeartbeat(readFileSync16(TELEGRAM_PROGRESS_HEARTBEAT_PATH, "utf-8"));
40712
+ return parseProgressHeartbeat(readFileSync17(TELEGRAM_PROGRESS_HEARTBEAT_PATH, "utf-8"));
40410
40713
  } catch {
40411
40714
  return null;
40412
40715
  }
@@ -40429,11 +40732,11 @@ function findTelegramProgressTarget() {
40429
40732
  let best = null;
40430
40733
  let bestMs = Infinity;
40431
40734
  try {
40432
- for (const name of readdirSync5(PENDING_INBOUND_DIR)) {
40735
+ for (const name of readdirSync6(PENDING_INBOUND_DIR)) {
40433
40736
  if (!name.endsWith(".json")) continue;
40434
40737
  let m;
40435
40738
  try {
40436
- m = JSON.parse(readFileSync16(join16(PENDING_INBOUND_DIR, name), "utf-8"));
40739
+ m = JSON.parse(readFileSync17(join17(PENDING_INBOUND_DIR, name), "utf-8"));
40437
40740
  } catch {
40438
40741
  continue;
40439
40742
  }
@@ -40560,11 +40863,11 @@ function listPendingInboundChatIds() {
40560
40863
  if (!PENDING_INBOUND_DIR || !existsSync9(PENDING_INBOUND_DIR)) return [];
40561
40864
  const chats = /* @__PURE__ */ new Set();
40562
40865
  try {
40563
- for (const name of readdirSync5(PENDING_INBOUND_DIR)) {
40866
+ for (const name of readdirSync6(PENDING_INBOUND_DIR)) {
40564
40867
  if (!name.endsWith(".json")) continue;
40565
40868
  try {
40566
40869
  const marker = JSON.parse(
40567
- readFileSync16(join16(PENDING_INBOUND_DIR, name), "utf8")
40870
+ readFileSync17(join17(PENDING_INBOUND_DIR, name), "utf8")
40568
40871
  );
40569
40872
  if (typeof marker.seen_at === "string" && marker.seen_at) continue;
40570
40873
  if (typeof marker.chat_id === "string" && marker.chat_id) chats.add(marker.chat_id);
@@ -40576,6 +40879,84 @@ function listPendingInboundChatIds() {
40576
40879
  }
40577
40880
  return [...chats];
40578
40881
  }
40882
+ var lastTurnFailedNoticeAt = /* @__PURE__ */ new Map();
40883
+ var lastTurnRetryingNoticeAt = /* @__PURE__ */ new Map();
40884
+ async function postTelegramTurnNotice(args) {
40885
+ try {
40886
+ const resp = await telegramApiCall(
40887
+ "sendMessage",
40888
+ {
40889
+ chat_id: args.chatId,
40890
+ text: args.text,
40891
+ ...args.messageThreadId != null ? { message_thread_id: args.messageThreadId } : {}
40892
+ },
40893
+ 1e4
40894
+ );
40895
+ return resp.ok === true;
40896
+ } catch {
40897
+ return false;
40898
+ }
40899
+ }
40900
+ function armTelegramTurnFailureWatch(args) {
40901
+ const conversationKey = args.messageThreadId != null ? `${args.chatId}:${args.messageThreadId}` : args.chatId;
40902
+ void (async () => {
40903
+ try {
40904
+ const failure = await watchForTurnFailure({
40905
+ sinceMs: args.sinceMs,
40906
+ channel: "telegram",
40907
+ onLongRetry: async () => {
40908
+ const now2 = Date.now();
40909
+ if (!shouldPostUndeliverableNotice(lastTurnRetryingNoticeAt.get(conversationKey), now2)) {
40910
+ return;
40911
+ }
40912
+ lastTurnRetryingNoticeAt.set(conversationKey, now2);
40913
+ const posted2 = await postTelegramTurnNotice({
40914
+ chatId: args.chatId,
40915
+ ...args.messageThreadId != null ? { messageThreadId: args.messageThreadId } : {},
40916
+ text: turnRetryingNoticeText()
40917
+ });
40918
+ if (!posted2) lastTurnRetryingNoticeAt.delete(conversationKey);
40919
+ process.stderr.write(
40920
+ `telegram-channel(${AGENT_CODE_NAME}): [turn-failure] retrying notice ${posted2 ? "posted" : "FAILED"} chat=${redactId(args.chatId)}
40921
+ `
40922
+ );
40923
+ }
40924
+ });
40925
+ if (!failure) return;
40926
+ const now = Date.now();
40927
+ if (!shouldPostUndeliverableNotice(lastTurnFailedNoticeAt.get(conversationKey), now)) {
40928
+ process.stderr.write(
40929
+ `telegram-channel(${AGENT_CODE_NAME}): [turn-failure] suppressed (throttled) chat=${redactId(args.chatId)} class=${failure.failureClass}
40930
+ `
40931
+ );
40932
+ return;
40933
+ }
40934
+ lastTurnFailedNoticeAt.set(conversationKey, now);
40935
+ const posted = await postTelegramTurnNotice({
40936
+ chatId: args.chatId,
40937
+ ...args.messageThreadId != null ? { messageThreadId: args.messageThreadId } : {},
40938
+ text: turnFailedNoticeText()
40939
+ });
40940
+ if (!posted) {
40941
+ lastTurnFailedNoticeAt.delete(conversationKey);
40942
+ process.stderr.write(
40943
+ `telegram-channel(${AGENT_CODE_NAME}): [turn-failure] NOTICE POST FAILED chat=${redactId(args.chatId)} - the user is still waiting on a dead turn
40944
+ `
40945
+ );
40946
+ return;
40947
+ }
40948
+ process.stderr.write(
40949
+ `telegram-channel(${AGENT_CODE_NAME}): [turn-failure] notified chat=${redactId(args.chatId)} class=${failure.failureClass} status=${failure.httpStatus}
40950
+ `
40951
+ );
40952
+ } catch (err) {
40953
+ process.stderr.write(
40954
+ `telegram-channel(${AGENT_CODE_NAME}): [turn-failure] watch error: ${err.message}
40955
+ `
40956
+ );
40957
+ }
40958
+ })();
40959
+ }
40579
40960
  async function notifyWatchdogGiveUp(chatId, reason) {
40580
40961
  const now = Date.now();
40581
40962
  if (!shouldPostUndeliverableNotice(lastUndeliverableNoticeAt.get(chatId), now)) return;
@@ -40606,7 +40987,7 @@ async function notifyWatchdogGiveUp(chatId, reason) {
40606
40987
  }
40607
40988
  function checkWatchdogGiveUpNotice() {
40608
40989
  if (!AGENT_DIR) return;
40609
- const signal = readGiveUpSignal(join16(AGENT_DIR, GIVE_UP_SIGNAL_FILENAME));
40990
+ const signal = readGiveUpSignal(join17(AGENT_DIR, GIVE_UP_SIGNAL_FILENAME));
40610
40991
  const signalAtMs = signal?.atMs ?? null;
40611
40992
  const act = decideGiveUpNotice({
40612
40993
  signalAtMs,
@@ -40948,7 +41329,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
40948
41329
  let bytes;
40949
41330
  let size;
40950
41331
  try {
40951
- const st = statSync4(realPath);
41332
+ const st = statSync5(realPath);
40952
41333
  if (!st.isFile()) {
40953
41334
  return { content: [{ type: "text", text: `Upload refused: ${resolvedPath} is not a regular file.` }], isError: true };
40954
41335
  }
@@ -40962,7 +41343,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
40962
41343
  isError: true
40963
41344
  };
40964
41345
  }
40965
- bytes = readFileSync16(realPath);
41346
+ bytes = readFileSync17(realPath);
40966
41347
  } catch (err) {
40967
41348
  return { content: [{ type: "text", text: `Failed to read file: ${err.message}` }], isError: true };
40968
41349
  }
@@ -41516,7 +41897,7 @@ async function replayPendingTelegramMarkers() {
41516
41897
  if (!sessionAlive) return;
41517
41898
  let filenames;
41518
41899
  try {
41519
- filenames = readdirSync5(PENDING_INBOUND_DIR);
41900
+ filenames = readdirSync6(PENDING_INBOUND_DIR);
41520
41901
  } catch {
41521
41902
  return;
41522
41903
  }
@@ -41524,7 +41905,7 @@ async function replayPendingTelegramMarkers() {
41524
41905
  let paneFreshAgeMs = null;
41525
41906
  if (AGENT_DIR) {
41526
41907
  try {
41527
- paneFreshAgeMs = Math.max(0, now - statSync4(join16(AGENT_DIR, "pane.log")).mtimeMs);
41908
+ paneFreshAgeMs = Math.max(0, now - statSync5(join17(AGENT_DIR, "pane.log")).mtimeMs);
41528
41909
  } catch {
41529
41910
  }
41530
41911
  }
@@ -41532,10 +41913,10 @@ async function replayPendingTelegramMarkers() {
41532
41913
  const entries = [];
41533
41914
  for (const name of filenames) {
41534
41915
  if (!name.endsWith(".json") || name.endsWith(".tmp")) continue;
41535
- const fullPath = join16(PENDING_INBOUND_DIR, name);
41916
+ const fullPath = join17(PENDING_INBOUND_DIR, name);
41536
41917
  let marker;
41537
41918
  try {
41538
- marker = JSON.parse(readFileSync16(fullPath, "utf-8"));
41919
+ marker = JSON.parse(readFileSync17(fullPath, "utf-8"));
41539
41920
  } catch {
41540
41921
  continue;
41541
41922
  }
@@ -41703,7 +42084,7 @@ async function pollLoop() {
41703
42084
  `telegram-channel(${AGENT_CODE_NAME}): getUpdates failed: ${resp.description ?? "unknown"}
41704
42085
  `
41705
42086
  );
41706
- await sleep3(5e3);
42087
+ await sleep4(5e3);
41707
42088
  continue;
41708
42089
  }
41709
42090
  const offsetBeforeBatch = nextOffset;
@@ -42086,7 +42467,7 @@ async function pollLoop() {
42086
42467
  let paneLogFreshAgeMs = null;
42087
42468
  if (AGENT_DIR) {
42088
42469
  try {
42089
- const paneMtimeMs = statSync4(join16(AGENT_DIR, "pane.log")).mtimeMs;
42470
+ const paneMtimeMs = statSync5(join17(AGENT_DIR, "pane.log")).mtimeMs;
42090
42471
  paneLogFreshAgeMs = Math.max(0, Date.now() - paneMtimeMs);
42091
42472
  } catch {
42092
42473
  }
@@ -42206,6 +42587,13 @@ async function pollLoop() {
42206
42587
  sinceMs: dispatchedAtMs
42207
42588
  });
42208
42589
  }
42590
+ if (!isFromBot && !peerAgentMeta && turnFailureNoticeEnabled()) {
42591
+ armTelegramTurnFailureWatch({
42592
+ chatId,
42593
+ ...msg.message_thread_id != null ? { messageThreadId: msg.message_thread_id } : {},
42594
+ sinceMs: dispatchedAtMs
42595
+ });
42596
+ }
42209
42597
  seedTelegramProgressHeartbeat();
42210
42598
  conversationIngestClient?.ingest({
42211
42599
  channel: "telegram",
@@ -42251,11 +42639,11 @@ async function pollLoop() {
42251
42639
  `telegram-channel(${AGENT_CODE_NAME}): poll error: ${err.message}
42252
42640
  `
42253
42641
  );
42254
- await sleep3(5e3);
42642
+ await sleep4(5e3);
42255
42643
  }
42256
42644
  }
42257
42645
  }
42258
- function sleep3(ms) {
42646
+ function sleep4(ms) {
42259
42647
  return new Promise((resolve3) => setTimeout(resolve3, ms).unref());
42260
42648
  }
42261
42649
  function shutdown(reason) {