@integrity-labs/agt-cli 0.28.457 → 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"
@@ -35704,6 +35858,22 @@ var FLAG_REGISTRY = [
35704
35858
  // registry-only (ADR-0022). NOT `public`: it is resolved server-side only, so it
35705
35859
  // must never be serialized into the browser map.
35706
35860
  defaultValue: false
35861
+ },
35862
+ {
35863
+ key: "ninjafy-brand",
35864
+ description: 'Present the product under the Ninjafy brand instead of Augmented Team (ENG-8250). This is the UMBRELLA brand gate, not a one-off nav toggle: every subsequent rebrand surface (page titles, email templates, marketing-facing copy) reads THIS key rather than adding its own flag, so the whole rebrand keeps a single kill switch. First surface is the left-hand nav wordmark \u2014 ON replaces the human+robot mark and the "augmented.team" text with italic lowercase "ninjafy"; OFF renders exactly what shipped before. Scope is USER-FACING BRAND TEXT ONLY: it must never gate a code identifier, package name, env var or CLI name, which stay `Augmented`/`agt` per the CLAUDE.md naming contract (the deep code rename is workstream C of docs/runbooks/rebrand-ninjafy-migration.md and is out of scope here). Set the stage-wide default to flip a whole environment, or add a feature_flag_overrides row to pilot one organization while every other org still sees Augmented. Ships dark.',
35865
+ flagType: "boolean",
35866
+ // Declared safe value is `false`: the pre-rebrand brand. `false` is also the
35867
+ // fail-closed direction — if the flag DB is unreachable we must show the brand
35868
+ // that is currently live and contractually correct, never leak an unannounced
35869
+ // rebrand to every customer at once.
35870
+ defaultValue: false,
35871
+ // Read CLIENT-SIDE: sidebar.tsx is a "use client" component and resolves this
35872
+ // via usePublicBooleanFlag, so the key must be in the browser-exposed public
35873
+ // map. Unlike onboarding-msteams-channel above there is no wrong-org hazard —
35874
+ // the sidebar renders inside the active-org cookie's scope, which is exactly
35875
+ // the org whose brand should be shown.
35876
+ public: true
35707
35877
  }
35708
35878
  ];
35709
35879
  var REGISTRY_BY_KEY = new Map(FLAG_REGISTRY.map((definition) => [definition.key, definition]));
@@ -37480,6 +37650,149 @@ async function watchForRateLimitRefusal(opts) {
37480
37650
  }
37481
37651
  }
37482
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
+
37483
37796
  // src/usage-limit-reactive-decision.ts
37484
37797
  import { createHash } from "crypto";
37485
37798
  function shouldReadPredictiveMarker(mode) {
@@ -37506,7 +37819,7 @@ function describeUnparsedRefusal(text) {
37506
37819
  import { execFile as execFile2 } from "child_process";
37507
37820
  import { existsSync as existsSync5, mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
37508
37821
  import { homedir as homedir6 } from "os";
37509
- import { join as join10 } from "path";
37822
+ import { join as join11 } from "path";
37510
37823
  var DEFAULT_CLAUDE_EVAL_MODEL = "claude-haiku-4-5-20251001";
37511
37824
  var DEFAULT_ANTHROPIC_MESSAGES_URL = "https://api.anthropic.com/v1/messages";
37512
37825
  var ANTHROPIC_API_VERSION = "2023-06-01";
@@ -37605,12 +37918,12 @@ async function runAnthropicMessages(prompt, opts) {
37605
37918
  var emptyMcpConfigPath = null;
37606
37919
  function ensureEmptyMcpConfig() {
37607
37920
  if (emptyMcpConfigPath && existsSync5(emptyMcpConfigPath)) return emptyMcpConfigPath;
37608
- const dir = join10(homedir6(), ".augmented");
37921
+ const dir = join11(homedir6(), ".augmented");
37609
37922
  try {
37610
37923
  mkdirSync6(dir, { recursive: true });
37611
37924
  } catch {
37612
37925
  }
37613
- const p2 = join10(dir, ".reply-intent-empty-mcp.json");
37926
+ const p2 = join11(dir, ".reply-intent-empty-mcp.json");
37614
37927
  writeFileSync7(p2, JSON.stringify({ mcpServers: {} }));
37615
37928
  emptyMcpConfigPath = p2;
37616
37929
  return p2;
@@ -37757,17 +38070,17 @@ function emitTransientApiErrorTelemetry(channel, match, original) {
37757
38070
  }
37758
38071
 
37759
38072
  // src/telegram-pending-inbound-cleanup.ts
37760
- import { readdirSync as readdirSync2, readFileSync as readFileSync12, statSync as statSync2 } from "fs";
37761
- 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";
37762
38075
  function markerArrivalMs(fullPath) {
37763
38076
  try {
37764
- const received = JSON.parse(readFileSync12(fullPath, "utf-8")).received_at;
38077
+ const received = JSON.parse(readFileSync13(fullPath, "utf-8")).received_at;
37765
38078
  const parsed = received ? Date.parse(received) : Number.NaN;
37766
38079
  if (Number.isFinite(parsed)) return parsed;
37767
38080
  } catch {
37768
38081
  }
37769
38082
  try {
37770
- return statSync2(fullPath).mtimeMs;
38083
+ return statSync3(fullPath).mtimeMs;
37771
38084
  } catch {
37772
38085
  return Number.POSITIVE_INFINITY;
37773
38086
  }
@@ -37783,7 +38096,7 @@ function applyToChatMarkers(pendingDir, chatId, op, cutoffMs) {
37783
38096
  const bounded = Number.isFinite(cutoffMs);
37784
38097
  let filenames;
37785
38098
  try {
37786
- filenames = readdirSync2(pendingDir);
38099
+ filenames = readdirSync3(pendingDir);
37787
38100
  } catch {
37788
38101
  return 0;
37789
38102
  }
@@ -37791,7 +38104,7 @@ function applyToChatMarkers(pendingDir, chatId, op, cutoffMs) {
37791
38104
  for (const filename of filenames) {
37792
38105
  if (!filename.startsWith(prefix)) continue;
37793
38106
  if (!filename.endsWith(".json")) continue;
37794
- const fullPath = join11(pendingDir, filename);
38107
+ const fullPath = join12(pendingDir, filename);
37795
38108
  if (bounded && markerArrivalMs(fullPath) > cutoffMs) continue;
37796
38109
  op(fullPath);
37797
38110
  applied++;
@@ -37801,12 +38114,12 @@ function applyToChatMarkers(pendingDir, chatId, op, cutoffMs) {
37801
38114
 
37802
38115
  // src/recovery-ledger.ts
37803
38116
  import { existsSync as existsSync6, unlinkSync as unlinkSync6 } from "fs";
37804
- import { join as join12 } from "path";
38117
+ import { join as join13 } from "path";
37805
38118
  function recoveryLedgerEntryExists(ledgerDir, markerName, exists = (p2) => existsSync6(p2)) {
37806
38119
  if (!ledgerDir || !markerName) return false;
37807
38120
  if (markerName.includes("/") || markerName.includes("\\") || markerName.includes("..")) return false;
37808
38121
  try {
37809
- return exists(join12(ledgerDir, markerName));
38122
+ return exists(join13(ledgerDir, markerName));
37810
38123
  } catch {
37811
38124
  return false;
37812
38125
  }
@@ -37817,14 +38130,14 @@ function removeRecoveryLedgerEntry(ledgerDir, markerName, unlink = (p2) => {
37817
38130
  if (!ledgerDir || !markerName) return;
37818
38131
  if (markerName.includes("/") || markerName.includes("\\") || markerName.includes("..")) return;
37819
38132
  try {
37820
- unlink(join12(ledgerDir, markerName));
38133
+ unlink(join13(ledgerDir, markerName));
37821
38134
  } catch {
37822
38135
  }
37823
38136
  }
37824
38137
 
37825
38138
  // src/inbound-delivery-ledger.ts
37826
- import { existsSync as existsSync7, mkdirSync as mkdirSync7, readdirSync as readdirSync3, readFileSync as readFileSync13, renameSync as renameSync5, writeFileSync as writeFileSync8 } from "fs";
37827
- 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";
37828
38141
  function safeInboundId(inboundId) {
37829
38142
  return inboundId.replace(/[^A-Za-z0-9_-]/g, "_");
37830
38143
  }
@@ -37832,8 +38145,8 @@ var defaultDeps = {
37832
38145
  mkdir: (dir) => mkdirSync7(dir, { recursive: true }),
37833
38146
  writeFile: (path, data) => writeFileSync8(path, data, "utf8"),
37834
38147
  rename: (from, to) => renameSync5(from, to),
37835
- readdir: (dir) => readdirSync3(dir),
37836
- readFile: (path) => readFileSync13(path, "utf8"),
38148
+ readdir: (dir) => readdirSync4(dir),
38149
+ readFile: (path) => readFileSync14(path, "utf8"),
37837
38150
  exists: (path) => existsSync7(path)
37838
38151
  };
37839
38152
  function writeInboundDeliveryLedgerEntry(dir, record2, deps = defaultDeps) {
@@ -37842,7 +38155,7 @@ function writeInboundDeliveryLedgerEntry(dir, record2, deps = defaultDeps) {
37842
38155
  if (!safe || safe.includes("/") || safe.includes("\\") || safe.includes("..")) return;
37843
38156
  try {
37844
38157
  deps.mkdir(dir);
37845
- const final = join13(dir, `${safe}.json`);
38158
+ const final = join14(dir, `${safe}.json`);
37846
38159
  const tmp = `${final}.tmp`;
37847
38160
  deps.writeFile(tmp, JSON.stringify(record2));
37848
38161
  deps.rename(tmp, final);
@@ -38047,14 +38360,14 @@ function createKanbanCardActiveClient(args) {
38047
38360
  import {
38048
38361
  existsSync as existsSync8,
38049
38362
  mkdirSync as mkdirSync8,
38050
- readFileSync as readFileSync14,
38363
+ readFileSync as readFileSync15,
38051
38364
  renameSync as renameSync6,
38052
- statSync as statSync3,
38365
+ statSync as statSync4,
38053
38366
  unlinkSync as unlinkSync7,
38054
38367
  utimesSync,
38055
38368
  writeFileSync as writeFileSync9
38056
38369
  } from "fs";
38057
- import { join as join14 } from "path";
38370
+ import { join as join15 } from "path";
38058
38371
  var STALE_LOCK_MS = 9e4;
38059
38372
  var HEARTBEAT_INTERVAL_MS = 3e4;
38060
38373
  function defaultIsPidAlive(pid) {
@@ -38077,7 +38390,7 @@ function acquireMcpSpawnLock(args) {
38077
38390
  const nowMs = options.nowMs ?? (() => Date.now());
38078
38391
  const lockMtimeMs = options.lockMtimeMs ?? defaultLockMtimeMs;
38079
38392
  const staleMs = options.staleMs ?? STALE_LOCK_MS;
38080
- const path = join14(agentDir, basename2);
38393
+ const path = join15(agentDir, basename2);
38081
38394
  const existing = readLockHolder(path);
38082
38395
  if (existing) {
38083
38396
  if (existing.pid === selfPid) {
@@ -38136,7 +38449,7 @@ function startMcpSpawnLockHeartbeat(lockPath, opts = {}) {
38136
38449
  }
38137
38450
  function defaultLockMtimeMs(path) {
38138
38451
  try {
38139
- return statSync3(path).mtimeMs;
38452
+ return statSync4(path).mtimeMs;
38140
38453
  } catch {
38141
38454
  return null;
38142
38455
  }
@@ -38144,7 +38457,7 @@ function defaultLockMtimeMs(path) {
38144
38457
  function readLockHolder(path) {
38145
38458
  if (!existsSync8(path)) return null;
38146
38459
  try {
38147
- const raw = readFileSync14(path, "utf8");
38460
+ const raw = readFileSync15(path, "utf8");
38148
38461
  const parsed = JSON.parse(raw);
38149
38462
  const pid = typeof parsed.pid === "number" ? parsed.pid : Number(parsed.pid);
38150
38463
  if (!Number.isFinite(pid) || pid <= 0) return null;
@@ -38156,8 +38469,8 @@ function readLockHolder(path) {
38156
38469
  }
38157
38470
 
38158
38471
  // src/ack-reaction.ts
38159
- import { readdirSync as readdirSync4, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "fs";
38160
- 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";
38161
38474
  var REPLY_WEDGED_THRESHOLD_MS = 5 * 60 * 1e3;
38162
38475
  var ACK_STARTUP_GRACE_MS = 6e4;
38163
38476
  var ACK_PANE_FRESH_THRESHOLD_MS = 6e4;
@@ -38230,7 +38543,7 @@ var GIVE_UP_SIGNAL_MAX_AGE_MS = 30 * 60 * 1e3;
38230
38543
  function readGiveUpSignal(path, now = Date.now()) {
38231
38544
  if (!path) return null;
38232
38545
  try {
38233
- const raw = JSON.parse(readFileSync15(path, "utf8"));
38546
+ const raw = JSON.parse(readFileSync16(path, "utf8"));
38234
38547
  if (typeof raw.gave_up_at !== "string") return null;
38235
38548
  const t = Date.parse(raw.gave_up_at);
38236
38549
  if (!Number.isFinite(t) || t > now) return null;
@@ -38252,11 +38565,17 @@ function giveUpNoticeText(reason = null) {
38252
38565
  }
38253
38566
  return "\u26A0\uFE0F I couldn't read your last message \u2014 please resend it.";
38254
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
+ }
38255
38574
  function oldestPendingMarkerAgeMs(dir, now = Date.now(), opts) {
38256
38575
  if (!dir) return null;
38257
38576
  let names;
38258
38577
  try {
38259
- names = readdirSync4(dir);
38578
+ names = readdirSync5(dir);
38260
38579
  } catch {
38261
38580
  return null;
38262
38581
  }
@@ -38265,7 +38584,7 @@ function oldestPendingMarkerAgeMs(dir, now = Date.now(), opts) {
38265
38584
  if (!name.endsWith(".json")) continue;
38266
38585
  let receivedAt;
38267
38586
  try {
38268
- const raw = JSON.parse(readFileSync15(join15(dir, name), "utf-8"));
38587
+ const raw = JSON.parse(readFileSync16(join16(dir, name), "utf-8"));
38269
38588
  if (raw.discretionary === true) continue;
38270
38589
  if (!opts?.includeSeen && typeof raw.seen_at === "string" && raw.seen_at) continue;
38271
38590
  receivedAt = raw.received_at;
@@ -38342,14 +38661,14 @@ function isMarkerGenuinelyAged(receivedAt, nowMs, thresholdMs) {
38342
38661
  }
38343
38662
  var DEFLECTION_COUNTER_SUFFIX = "-deflections.json";
38344
38663
  function deflectionCounterPath(agentDir, channel) {
38345
- return join15(agentDir, `${channel}${DEFLECTION_COUNTER_SUFFIX}`);
38664
+ return join16(agentDir, `${channel}${DEFLECTION_COUNTER_SUFFIX}`);
38346
38665
  }
38347
38666
  function recordChannelDeflection(agentDir, channel, cause) {
38348
38667
  if (!agentDir) return;
38349
38668
  const path = deflectionCounterPath(agentDir, channel);
38350
38669
  let counts = {};
38351
38670
  try {
38352
- const parsed = JSON.parse(readFileSync15(path, "utf-8"));
38671
+ const parsed = JSON.parse(readFileSync16(path, "utf-8"));
38353
38672
  if (parsed && typeof parsed === "object") counts = parsed;
38354
38673
  } catch {
38355
38674
  }
@@ -38430,7 +38749,7 @@ function redactId(id) {
38430
38749
  }
38431
38750
  var BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
38432
38751
  var AGENT_CODE_NAME = process.env.AGT_AGENT_CODE_NAME ?? "unknown";
38433
- 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;
38434
38753
  var AGT_HOST = process.env.AGT_HOST ?? null;
38435
38754
  var AGT_API_KEY = process.env.AGT_API_KEY ?? null;
38436
38755
  var AGT_AGENT_ID = process.env.AGT_AGENT_ID ?? null;
@@ -38533,11 +38852,11 @@ var peerRateLimiter = peerRateApiClient && parsePeerAgentModeEnv(process.env.TEL
38533
38852
  log: (line) => process.stderr.write(`telegram-channel(${AGENT_CODE_NAME}): ${line}
38534
38853
  `)
38535
38854
  }) : createPeerRateLimiter();
38536
- 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;
38537
38856
  function readPeerPresenceState() {
38538
38857
  if (!PEER_PRESENCE_STATE_FILE) return null;
38539
38858
  try {
38540
- const parsed = JSON.parse(readFileSync16(PEER_PRESENCE_STATE_FILE, "utf-8"));
38859
+ const parsed = JSON.parse(readFileSync17(PEER_PRESENCE_STATE_FILE, "utf-8"));
38541
38860
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
38542
38861
  return parsed;
38543
38862
  } catch {
@@ -38572,9 +38891,9 @@ if (!BOT_TOKEN) {
38572
38891
  var stderrLogStream = null;
38573
38892
  if (AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown") {
38574
38893
  try {
38575
- const logDir = join16(homedir7(), ".augmented", AGENT_CODE_NAME);
38894
+ const logDir = join17(homedir7(), ".augmented", AGENT_CODE_NAME);
38576
38895
  mkdirSync9(logDir, { recursive: true });
38577
- stderrLogStream = createWriteStream(join16(logDir, "telegram-channel-stderr.log"), {
38896
+ stderrLogStream = createWriteStream(join17(logDir, "telegram-channel-stderr.log"), {
38578
38897
  flags: "a",
38579
38898
  mode: 384
38580
38899
  });
@@ -39011,7 +39330,7 @@ function scheduleBusyAck(chatId, messageId, arrivedWhileBusy) {
39011
39330
  let paneLogFreshAgeMs = null;
39012
39331
  if (AGENT_DIR) {
39013
39332
  try {
39014
- const paneMtimeMs = statSync4(join16(AGENT_DIR, "pane.log")).mtimeMs;
39333
+ const paneMtimeMs = statSync5(join17(AGENT_DIR, "pane.log")).mtimeMs;
39015
39334
  paneLogFreshAgeMs = Math.max(0, Date.now() - paneMtimeMs);
39016
39335
  } catch {
39017
39336
  }
@@ -39041,7 +39360,7 @@ function scheduleBusyAck(chatId, messageId, arrivedWhileBusy) {
39041
39360
  function __resetBusyAckNoticeThrottle() {
39042
39361
  lastBusyAckNoticeAt.clear();
39043
39362
  }
39044
- var RESTART_FLAGS_DIR = join16(homedir7(), ".augmented", "restart-flags");
39363
+ var RESTART_FLAGS_DIR = join17(homedir7(), ".augmented", "restart-flags");
39045
39364
  function actuateHostRestartTelegram() {
39046
39365
  return actuateHostRestart({
39047
39366
  agtHost: AGT_HOST,
@@ -39426,7 +39745,7 @@ async function handleRestartCommand(opts) {
39426
39745
  if (!existsSync9(RESTART_FLAGS_DIR)) {
39427
39746
  mkdirSync9(RESTART_FLAGS_DIR, { recursive: true });
39428
39747
  }
39429
- const flagPath = join16(RESTART_FLAGS_DIR, `${AGENT_CODE_NAME}.flag`);
39748
+ const flagPath = join17(RESTART_FLAGS_DIR, `${AGENT_CODE_NAME}.flag`);
39430
39749
  const flag = {
39431
39750
  codeName: AGENT_CODE_NAME,
39432
39751
  source: "telegram",
@@ -39832,16 +40151,16 @@ async function classifyRestartCommand(text) {
39832
40151
  if (!ours) return "verification_failed";
39833
40152
  return target === ours ? "act" : "ignore";
39834
40153
  }
39835
- var AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join16(homedir7(), ".augmented", AGENT_CODE_NAME) : null;
39836
- var PENDING_INBOUND_DIR = AGENT_DIR ? join16(AGENT_DIR, "telegram-pending-inbound") : null;
39837
- var RECOVERY_OUTBOX_DIR = AGENT_DIR ? join16(AGENT_DIR, "telegram-recovery-outbox") : null;
39838
- var RECOVERY_LEDGER_DIR = AGENT_DIR ? join16(AGENT_DIR, ".agt-telegram-recovery-ledger") : null;
39839
- var DELIVERY_LEDGER_DIR = AGENT_DIR ? join16(AGENT_DIR, ".agt-inbound-delivery-ledger") : null;
39840
- 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;
39841
40160
  var TELEGRAM_PROCESS_BOOT_MS = Date.now();
39842
- var TELEGRAM_RECENT_DMS_FILE = AGENT_DIR ? join16(AGENT_DIR, "telegram-recent-dms.json") : null;
39843
- var TELEGRAM_CHANNEL_ADD_RESTART_FILE = AGENT_DIR ? join16(AGENT_DIR, "telegram-channel-add-restart.json") : null;
39844
- 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;
39845
40164
  var recentDms = /* @__PURE__ */ new Map();
39846
40165
  var recentDmPersister = TELEGRAM_RECENT_DMS_FILE ? createRecentDmPersister({
39847
40166
  filePath: TELEGRAM_RECENT_DMS_FILE,
@@ -39896,7 +40215,7 @@ function safeMarkerName(chatId, messageId) {
39896
40215
  }
39897
40216
  function pendingInboundPath(chatId, messageId) {
39898
40217
  if (!PENDING_INBOUND_DIR) return null;
39899
- return join16(PENDING_INBOUND_DIR, safeMarkerName(chatId, messageId));
40218
+ return join17(PENDING_INBOUND_DIR, safeMarkerName(chatId, messageId));
39900
40219
  }
39901
40220
  function writePendingInboundMarker(chatId, messageId, chatType, undeliverable = false, payload) {
39902
40221
  const path = pendingInboundPath(chatId, messageId);
@@ -39944,7 +40263,7 @@ function rewriteTelegramMarkerInPlace(path, marker) {
39944
40263
  function clearTelegramMarkerFileWithHeal(fullPath) {
39945
40264
  let marker = null;
39946
40265
  try {
39947
- marker = JSON.parse(readFileSync16(fullPath, "utf-8"));
40266
+ marker = JSON.parse(readFileSync17(fullPath, "utf-8"));
39948
40267
  } catch {
39949
40268
  }
39950
40269
  if (marker && decideRecoveryHeal({
@@ -39961,7 +40280,7 @@ function clearTelegramMarkerFileWithHeal(fullPath) {
39961
40280
  function markTelegramMarkerSeenInPlace(fullPath) {
39962
40281
  let marker;
39963
40282
  try {
39964
- marker = JSON.parse(readFileSync16(fullPath, "utf-8"));
40283
+ marker = JSON.parse(readFileSync17(fullPath, "utf-8"));
39965
40284
  } catch {
39966
40285
  return;
39967
40286
  }
@@ -39973,7 +40292,7 @@ function markTelegramMarkerSeenInPlace(fullPath) {
39973
40292
  function markTelegramMarkerSeenWithHeal(fullPath) {
39974
40293
  let marker = null;
39975
40294
  try {
39976
- marker = JSON.parse(readFileSync16(fullPath, "utf-8"));
40295
+ marker = JSON.parse(readFileSync17(fullPath, "utf-8"));
39977
40296
  } catch {
39978
40297
  return;
39979
40298
  }
@@ -39989,7 +40308,7 @@ function readPendingInboundMarker(chatId, messageId) {
39989
40308
  const path = pendingInboundPath(chatId, messageId);
39990
40309
  if (!path || !existsSync9(path)) return null;
39991
40310
  try {
39992
- return JSON.parse(readFileSync16(path, "utf-8"));
40311
+ return JSON.parse(readFileSync17(path, "utf-8"));
39993
40312
  } catch {
39994
40313
  return null;
39995
40314
  }
@@ -40009,10 +40328,10 @@ function nextRetryName(filename) {
40009
40328
  async function processRecoveryOutboxFile(filename) {
40010
40329
  if (!RECOVERY_OUTBOX_DIR) return;
40011
40330
  if (filename.endsWith(".poison.json") || filename.endsWith(".tmp")) return;
40012
- const fullPath = join16(RECOVERY_OUTBOX_DIR, filename);
40331
+ const fullPath = join17(RECOVERY_OUTBOX_DIR, filename);
40013
40332
  let payload;
40014
40333
  try {
40015
- const raw = readFileSync16(fullPath, "utf-8");
40334
+ const raw = readFileSync17(fullPath, "utf-8");
40016
40335
  payload = JSON.parse(raw);
40017
40336
  } catch (err) {
40018
40337
  process.stderr.write(
@@ -40106,7 +40425,7 @@ async function processRecoveryOutboxFile(filename) {
40106
40425
  const next = nextRetryName(filename);
40107
40426
  if (next) {
40108
40427
  try {
40109
- renameSync7(fullPath, join16(RECOVERY_OUTBOX_DIR, next.next));
40428
+ renameSync7(fullPath, join17(RECOVERY_OUTBOX_DIR, next.next));
40110
40429
  if (next.attempt >= MAX_RECOVERY_ATTEMPTS) {
40111
40430
  process.stderr.write(
40112
40431
  `telegram-channel(${AGENT_CODE_NAME}): ghost-reply recovery exhausted retries \u2014 moved to ${next.next}
@@ -40138,7 +40457,7 @@ function scanRecoveryRetries() {
40138
40457
  if (!RECOVERY_OUTBOX_DIR) return;
40139
40458
  let entries;
40140
40459
  try {
40141
- entries = readdirSync5(RECOVERY_OUTBOX_DIR);
40460
+ entries = readdirSync6(RECOVERY_OUTBOX_DIR);
40142
40461
  } catch {
40143
40462
  return;
40144
40463
  }
@@ -40147,7 +40466,7 @@ function scanRecoveryRetries() {
40147
40466
  if (!f.includes(".retry-") || f.endsWith(".poison.json")) continue;
40148
40467
  let mtimeMs;
40149
40468
  try {
40150
- mtimeMs = statSync4(join16(RECOVERY_OUTBOX_DIR, f)).mtimeMs;
40469
+ mtimeMs = statSync5(join17(RECOVERY_OUTBOX_DIR, f)).mtimeMs;
40151
40470
  } catch {
40152
40471
  continue;
40153
40472
  }
@@ -40168,7 +40487,7 @@ function startRecoveryOutboxWatcher() {
40168
40487
  return;
40169
40488
  }
40170
40489
  try {
40171
- for (const f of readdirSync5(RECOVERY_OUTBOX_DIR)) {
40490
+ for (const f of readdirSync6(RECOVERY_OUTBOX_DIR)) {
40172
40491
  if (isFirstAttemptOutboxFile(f)) void processRecoveryOutboxFile(f);
40173
40492
  }
40174
40493
  } catch {
@@ -40177,7 +40496,7 @@ function startRecoveryOutboxWatcher() {
40177
40496
  const watcher = watch(RECOVERY_OUTBOX_DIR, (event, filename) => {
40178
40497
  if (event !== "rename" || !filename) return;
40179
40498
  if (!isFirstAttemptOutboxFile(filename)) return;
40180
- if (existsSync9(join16(RECOVERY_OUTBOX_DIR, filename))) {
40499
+ if (existsSync9(join17(RECOVERY_OUTBOX_DIR, filename))) {
40181
40500
  void processRecoveryOutboxFile(filename);
40182
40501
  }
40183
40502
  });
@@ -40192,7 +40511,7 @@ function startRecoveryOutboxWatcher() {
40192
40511
  retryTimer.unref?.();
40193
40512
  }
40194
40513
  startRecoveryOutboxWatcher();
40195
- 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;
40196
40515
  var NOTICE_MAX_AGE_MS = 9e4;
40197
40516
  var NOTICE_INFLIGHT = /* @__PURE__ */ new Set();
40198
40517
  async function processNoticeOutboxFile(filename) {
@@ -40200,11 +40519,11 @@ async function processNoticeOutboxFile(filename) {
40200
40519
  if (filename.startsWith(".") || filename.endsWith(".tmp") || !filename.endsWith(".json")) return;
40201
40520
  if (NOTICE_INFLIGHT.has(filename)) return;
40202
40521
  NOTICE_INFLIGHT.add(filename);
40203
- const fullPath = join16(NOTICE_OUTBOX_DIR, filename);
40522
+ const fullPath = join17(NOTICE_OUTBOX_DIR, filename);
40204
40523
  try {
40205
40524
  let mtimeMs;
40206
40525
  try {
40207
- mtimeMs = statSync4(fullPath).mtimeMs;
40526
+ mtimeMs = statSync5(fullPath).mtimeMs;
40208
40527
  } catch {
40209
40528
  return;
40210
40529
  }
@@ -40217,7 +40536,7 @@ async function processNoticeOutboxFile(filename) {
40217
40536
  }
40218
40537
  let payload;
40219
40538
  try {
40220
- const parsed = JSON.parse(readFileSync16(fullPath, "utf-8"));
40539
+ const parsed = JSON.parse(readFileSync17(fullPath, "utf-8"));
40221
40540
  if (!parsed || typeof parsed !== "object") throw new Error("not an object");
40222
40541
  payload = parsed;
40223
40542
  } catch {
@@ -40288,13 +40607,13 @@ function startNoticeOutboxWatcher() {
40288
40607
  return;
40289
40608
  }
40290
40609
  try {
40291
- for (const f of readdirSync5(NOTICE_OUTBOX_DIR)) void processNoticeOutboxFile(f);
40610
+ for (const f of readdirSync6(NOTICE_OUTBOX_DIR)) void processNoticeOutboxFile(f);
40292
40611
  } catch {
40293
40612
  }
40294
40613
  try {
40295
40614
  const watcher = watch(NOTICE_OUTBOX_DIR, (event, filename) => {
40296
40615
  if (event !== "rename" || !filename) return;
40297
- if (existsSync9(join16(NOTICE_OUTBOX_DIR, filename))) void processNoticeOutboxFile(filename);
40616
+ if (existsSync9(join17(NOTICE_OUTBOX_DIR, filename))) void processNoticeOutboxFile(filename);
40298
40617
  });
40299
40618
  watcher.unref?.();
40300
40619
  } catch (err) {
@@ -40314,7 +40633,7 @@ function sweepTelegramStaleMarkers(thresholdMs) {
40314
40633
  if (!existsSync9(PENDING_INBOUND_DIR)) return;
40315
40634
  let filenames;
40316
40635
  try {
40317
- filenames = readdirSync5(PENDING_INBOUND_DIR);
40636
+ filenames = readdirSync6(PENDING_INBOUND_DIR);
40318
40637
  } catch (err) {
40319
40638
  process.stderr.write(
40320
40639
  `telegram-channel(${AGENT_CODE_NAME}): stale-marker readdir failed: ${err.message}
@@ -40328,10 +40647,10 @@ function sweepTelegramStaleMarkers(thresholdMs) {
40328
40647
  for (const filename of filenames) {
40329
40648
  if (!filename.endsWith(".json")) continue;
40330
40649
  if (filename.endsWith(".tmp")) continue;
40331
- const fullPath = join16(PENDING_INBOUND_DIR, filename);
40650
+ const fullPath = join17(PENDING_INBOUND_DIR, filename);
40332
40651
  let marker;
40333
40652
  try {
40334
- marker = JSON.parse(readFileSync16(fullPath, "utf-8"));
40653
+ marker = JSON.parse(readFileSync17(fullPath, "utf-8"));
40335
40654
  } catch (err) {
40336
40655
  process.stderr.write(
40337
40656
  `telegram-channel(${AGENT_CODE_NAME}): stale-marker parse failed for ${redactId(filename)}: ${err.message}
@@ -40384,13 +40703,13 @@ var orphanSweepTimer = setInterval(() => {
40384
40703
  checkWatchdogGiveUpNotice();
40385
40704
  }, orphanSweepIntervalMs());
40386
40705
  orphanSweepTimer.unref?.();
40387
- 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;
40388
40707
  var telegramTrackedProgress = null;
40389
40708
  var telegramProgressTickRunning = false;
40390
40709
  function readTelegramProgressHeartbeat() {
40391
40710
  if (!TELEGRAM_PROGRESS_HEARTBEAT_PATH || !existsSync9(TELEGRAM_PROGRESS_HEARTBEAT_PATH)) return null;
40392
40711
  try {
40393
- return parseProgressHeartbeat(readFileSync16(TELEGRAM_PROGRESS_HEARTBEAT_PATH, "utf-8"));
40712
+ return parseProgressHeartbeat(readFileSync17(TELEGRAM_PROGRESS_HEARTBEAT_PATH, "utf-8"));
40394
40713
  } catch {
40395
40714
  return null;
40396
40715
  }
@@ -40413,11 +40732,11 @@ function findTelegramProgressTarget() {
40413
40732
  let best = null;
40414
40733
  let bestMs = Infinity;
40415
40734
  try {
40416
- for (const name of readdirSync5(PENDING_INBOUND_DIR)) {
40735
+ for (const name of readdirSync6(PENDING_INBOUND_DIR)) {
40417
40736
  if (!name.endsWith(".json")) continue;
40418
40737
  let m;
40419
40738
  try {
40420
- m = JSON.parse(readFileSync16(join16(PENDING_INBOUND_DIR, name), "utf-8"));
40739
+ m = JSON.parse(readFileSync17(join17(PENDING_INBOUND_DIR, name), "utf-8"));
40421
40740
  } catch {
40422
40741
  continue;
40423
40742
  }
@@ -40544,11 +40863,11 @@ function listPendingInboundChatIds() {
40544
40863
  if (!PENDING_INBOUND_DIR || !existsSync9(PENDING_INBOUND_DIR)) return [];
40545
40864
  const chats = /* @__PURE__ */ new Set();
40546
40865
  try {
40547
- for (const name of readdirSync5(PENDING_INBOUND_DIR)) {
40866
+ for (const name of readdirSync6(PENDING_INBOUND_DIR)) {
40548
40867
  if (!name.endsWith(".json")) continue;
40549
40868
  try {
40550
40869
  const marker = JSON.parse(
40551
- readFileSync16(join16(PENDING_INBOUND_DIR, name), "utf8")
40870
+ readFileSync17(join17(PENDING_INBOUND_DIR, name), "utf8")
40552
40871
  );
40553
40872
  if (typeof marker.seen_at === "string" && marker.seen_at) continue;
40554
40873
  if (typeof marker.chat_id === "string" && marker.chat_id) chats.add(marker.chat_id);
@@ -40560,6 +40879,84 @@ function listPendingInboundChatIds() {
40560
40879
  }
40561
40880
  return [...chats];
40562
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
+ }
40563
40960
  async function notifyWatchdogGiveUp(chatId, reason) {
40564
40961
  const now = Date.now();
40565
40962
  if (!shouldPostUndeliverableNotice(lastUndeliverableNoticeAt.get(chatId), now)) return;
@@ -40590,7 +40987,7 @@ async function notifyWatchdogGiveUp(chatId, reason) {
40590
40987
  }
40591
40988
  function checkWatchdogGiveUpNotice() {
40592
40989
  if (!AGENT_DIR) return;
40593
- const signal = readGiveUpSignal(join16(AGENT_DIR, GIVE_UP_SIGNAL_FILENAME));
40990
+ const signal = readGiveUpSignal(join17(AGENT_DIR, GIVE_UP_SIGNAL_FILENAME));
40594
40991
  const signalAtMs = signal?.atMs ?? null;
40595
40992
  const act = decideGiveUpNotice({
40596
40993
  signalAtMs,
@@ -40932,7 +41329,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
40932
41329
  let bytes;
40933
41330
  let size;
40934
41331
  try {
40935
- const st = statSync4(realPath);
41332
+ const st = statSync5(realPath);
40936
41333
  if (!st.isFile()) {
40937
41334
  return { content: [{ type: "text", text: `Upload refused: ${resolvedPath} is not a regular file.` }], isError: true };
40938
41335
  }
@@ -40946,7 +41343,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
40946
41343
  isError: true
40947
41344
  };
40948
41345
  }
40949
- bytes = readFileSync16(realPath);
41346
+ bytes = readFileSync17(realPath);
40950
41347
  } catch (err) {
40951
41348
  return { content: [{ type: "text", text: `Failed to read file: ${err.message}` }], isError: true };
40952
41349
  }
@@ -41500,7 +41897,7 @@ async function replayPendingTelegramMarkers() {
41500
41897
  if (!sessionAlive) return;
41501
41898
  let filenames;
41502
41899
  try {
41503
- filenames = readdirSync5(PENDING_INBOUND_DIR);
41900
+ filenames = readdirSync6(PENDING_INBOUND_DIR);
41504
41901
  } catch {
41505
41902
  return;
41506
41903
  }
@@ -41508,7 +41905,7 @@ async function replayPendingTelegramMarkers() {
41508
41905
  let paneFreshAgeMs = null;
41509
41906
  if (AGENT_DIR) {
41510
41907
  try {
41511
- 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);
41512
41909
  } catch {
41513
41910
  }
41514
41911
  }
@@ -41516,10 +41913,10 @@ async function replayPendingTelegramMarkers() {
41516
41913
  const entries = [];
41517
41914
  for (const name of filenames) {
41518
41915
  if (!name.endsWith(".json") || name.endsWith(".tmp")) continue;
41519
- const fullPath = join16(PENDING_INBOUND_DIR, name);
41916
+ const fullPath = join17(PENDING_INBOUND_DIR, name);
41520
41917
  let marker;
41521
41918
  try {
41522
- marker = JSON.parse(readFileSync16(fullPath, "utf-8"));
41919
+ marker = JSON.parse(readFileSync17(fullPath, "utf-8"));
41523
41920
  } catch {
41524
41921
  continue;
41525
41922
  }
@@ -41687,7 +42084,7 @@ async function pollLoop() {
41687
42084
  `telegram-channel(${AGENT_CODE_NAME}): getUpdates failed: ${resp.description ?? "unknown"}
41688
42085
  `
41689
42086
  );
41690
- await sleep3(5e3);
42087
+ await sleep4(5e3);
41691
42088
  continue;
41692
42089
  }
41693
42090
  const offsetBeforeBatch = nextOffset;
@@ -42070,7 +42467,7 @@ async function pollLoop() {
42070
42467
  let paneLogFreshAgeMs = null;
42071
42468
  if (AGENT_DIR) {
42072
42469
  try {
42073
- const paneMtimeMs = statSync4(join16(AGENT_DIR, "pane.log")).mtimeMs;
42470
+ const paneMtimeMs = statSync5(join17(AGENT_DIR, "pane.log")).mtimeMs;
42074
42471
  paneLogFreshAgeMs = Math.max(0, Date.now() - paneMtimeMs);
42075
42472
  } catch {
42076
42473
  }
@@ -42190,6 +42587,13 @@ async function pollLoop() {
42190
42587
  sinceMs: dispatchedAtMs
42191
42588
  });
42192
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
+ }
42193
42597
  seedTelegramProgressHeartbeat();
42194
42598
  conversationIngestClient?.ingest({
42195
42599
  channel: "telegram",
@@ -42235,11 +42639,11 @@ async function pollLoop() {
42235
42639
  `telegram-channel(${AGENT_CODE_NAME}): poll error: ${err.message}
42236
42640
  `
42237
42641
  );
42238
- await sleep3(5e3);
42642
+ await sleep4(5e3);
42239
42643
  }
42240
42644
  }
42241
42645
  }
42242
- function sleep3(ms) {
42646
+ function sleep4(ms) {
42243
42647
  return new Promise((resolve3) => setTimeout(resolve3, ms).unref());
42244
42648
  }
42245
42649
  function shutdown(reason) {