@integrity-labs/agt-cli 0.27.7-test.7 → 0.27.8-test.9

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.
@@ -14293,13 +14293,13 @@ var StdioServerTransport = class {
14293
14293
 
14294
14294
  // src/telegram-channel.ts
14295
14295
  import https from "https";
14296
- import { createHash, randomUUID as randomUUID2 } from "crypto";
14296
+ import { createHash, randomUUID } from "crypto";
14297
14297
  import {
14298
14298
  createWriteStream,
14299
14299
  existsSync as existsSync2,
14300
14300
  mkdirSync as mkdirSync3,
14301
- readFileSync as readFileSync2,
14302
- readdirSync,
14301
+ readFileSync as readFileSync3,
14302
+ readdirSync as readdirSync2,
14303
14303
  renameSync as renameSync3,
14304
14304
  statSync,
14305
14305
  unlinkSync as unlinkSync3,
@@ -14307,7 +14307,7 @@ import {
14307
14307
  writeFileSync as writeFileSync3
14308
14308
  } from "fs";
14309
14309
  import { homedir as homedir2 } from "os";
14310
- import { join as join3 } from "path";
14310
+ import { join as join4 } from "path";
14311
14311
 
14312
14312
  // src/channel-attachments.ts
14313
14313
  import { homedir } from "os";
@@ -15386,430 +15386,6 @@ function createInboundContextClient(args) {
15386
15386
  };
15387
15387
  }
15388
15388
 
15389
- // src/progress-tools.ts
15390
- import { randomUUID } from "crypto";
15391
-
15392
- // src/progress-handle.ts
15393
- async function startProgressHandle(opts) {
15394
- const now = opts.now ?? (() => Date.now());
15395
- const setTimer = opts.setTimer ?? ((cb, ms) => setTimeout(cb, ms));
15396
- const clearTimer = opts.clearTimer ?? ((id) => clearTimeout(id));
15397
- const debounceMs = opts.debounceMs ?? 1e3;
15398
- const onPostTerminalUpdate = opts.onPostTerminalUpdate ?? defaultPostTerminalWarn;
15399
- const state = {
15400
- initialLabel: opts.initialLabel,
15401
- mode: opts.initialMode ?? "working",
15402
- lastChangeAt: now()
15403
- };
15404
- let lastFlushAt = 0;
15405
- let pendingTimer = null;
15406
- let inFlight = null;
15407
- let terminal = false;
15408
- async function doFlush() {
15409
- while (inFlight) await inFlight;
15410
- if (pendingTimer != null) {
15411
- clearTimer(pendingTimer);
15412
- pendingTimer = null;
15413
- }
15414
- lastFlushAt = now();
15415
- const promise = opts.flush(snapshotState());
15416
- inFlight = promise.then(
15417
- () => {
15418
- inFlight = null;
15419
- },
15420
- (err) => {
15421
- inFlight = null;
15422
- throw err;
15423
- }
15424
- );
15425
- await inFlight;
15426
- }
15427
- function snapshotState() {
15428
- return {
15429
- initialLabel: state.initialLabel,
15430
- mode: state.mode,
15431
- stepLabel: state.stepLabel,
15432
- stepNumber: state.stepNumber ? { ...state.stepNumber } : void 0,
15433
- detail: state.detail,
15434
- lastChangeAt: state.lastChangeAt,
15435
- terminal: state.terminal ? { ...state.terminal } : void 0
15436
- };
15437
- }
15438
- function applyUpdate(next) {
15439
- if (next.mode !== void 0) state.mode = next.mode;
15440
- if (next.stepLabel !== void 0) state.stepLabel = next.stepLabel;
15441
- if (next.stepNumber !== void 0) state.stepNumber = { ...next.stepNumber };
15442
- if (next.detail !== void 0) state.detail = next.detail;
15443
- state.lastChangeAt = now();
15444
- }
15445
- async function scheduleOrFlush() {
15446
- const elapsed = now() - lastFlushAt;
15447
- if (elapsed >= debounceMs) {
15448
- await doFlush();
15449
- return;
15450
- }
15451
- if (pendingTimer != null) return;
15452
- const remaining = debounceMs - elapsed;
15453
- pendingTimer = setTimer(() => {
15454
- pendingTimer = null;
15455
- void doFlush().catch(() => {
15456
- });
15457
- }, remaining);
15458
- }
15459
- lastFlushAt = now();
15460
- await opts.flush(snapshotState());
15461
- return {
15462
- snapshot: snapshotState,
15463
- async update(next) {
15464
- if (terminal) {
15465
- const peeked = snapshotState();
15466
- Object.assign(peeked, {
15467
- mode: next.mode ?? peeked.mode,
15468
- stepLabel: next.stepLabel ?? peeked.stepLabel,
15469
- stepNumber: next.stepNumber ?? peeked.stepNumber,
15470
- detail: next.detail ?? peeked.detail
15471
- });
15472
- try {
15473
- onPostTerminalUpdate(peeked);
15474
- } catch (err) {
15475
- console.warn(
15476
- "[progress-handle] onPostTerminalUpdate threw \u2014 swallowed to keep update() non-throwing:",
15477
- err
15478
- );
15479
- }
15480
- return;
15481
- }
15482
- applyUpdate(next);
15483
- await scheduleOrFlush();
15484
- },
15485
- async complete(summary) {
15486
- if (terminal) return;
15487
- terminal = true;
15488
- state.terminal = { kind: "completed", message: summary };
15489
- state.lastChangeAt = now();
15490
- await doFlush();
15491
- },
15492
- async fail(reason) {
15493
- if (terminal) return;
15494
- terminal = true;
15495
- state.terminal = { kind: "failed", message: reason };
15496
- state.lastChangeAt = now();
15497
- await doFlush();
15498
- },
15499
- isTerminal: () => terminal
15500
- };
15501
- }
15502
- function defaultPostTerminalWarn(state) {
15503
- console.warn(
15504
- `[progress-handle] update() called after terminal transition (${state.terminal?.kind}) \u2014 ignored.`
15505
- );
15506
- }
15507
-
15508
- // src/progress-tools.ts
15509
- var ProgressToolRegistry = class {
15510
- constructor(opts) {
15511
- this.opts = opts;
15512
- this.toolNames = {
15513
- start: `${opts.prefix}_progress_start`,
15514
- update: `${opts.prefix}_progress_update`,
15515
- complete: `${opts.prefix}_progress_complete`,
15516
- fail: `${opts.prefix}_progress_fail`
15517
- };
15518
- }
15519
- handles = /* @__PURE__ */ new Map();
15520
- toolNames;
15521
- /** Tool definitions to splice into the MCP ListToolsRequest response. */
15522
- getDefinitions() {
15523
- const { surfaceDescription, startArgsSchema } = this.opts;
15524
- const debounceMs = this.opts.debounceMs ?? 1e3;
15525
- const debounceText = debounceMs === 1e3 ? "per second" : `every ${debounceMs}ms`;
15526
- return [
15527
- {
15528
- name: this.toolNames.start,
15529
- description: `Post a "still working" anchor to ${surfaceDescription} and return a progress_id. The anchor message updates in place as you call ${this.toolNames.update} \u2014 operators see one message that ticks forward instead of a flood of new ones. Always end with ${this.toolNames.complete} or ${this.toolNames.fail}; otherwise the anchor lingers as "working" until the stale-state sweep flips it. Opt-in \u2014 only call this for tasks that will take more than a few seconds.`,
15530
- inputSchema: {
15531
- type: "object",
15532
- properties: {
15533
- label: {
15534
- type: "string",
15535
- description: 'Short top-line label for the task (e.g. "diagnose vigil"). Stays constant for the lifetime of this progress anchor.'
15536
- },
15537
- initial_mode: {
15538
- type: "string",
15539
- enum: ["thinking", "working", "waiting"],
15540
- description: 'Initial mode emoji \u2014 defaults to "working".'
15541
- },
15542
- ...startArgsSchema.properties
15543
- },
15544
- required: ["label", ...startArgsSchema.required]
15545
- }
15546
- },
15547
- {
15548
- name: this.toolNames.update,
15549
- description: `Update the progress anchor in place. The state machine debounces calls to one ${surfaceDescription} API call ${debounceText}, so spamming this every step is safe \u2014 only the latest pending state will paint. Any subset of fields can be passed; omitted ones retain their previous value.`,
15550
- inputSchema: {
15551
- type: "object",
15552
- properties: {
15553
- progress_id: { type: "string", description: "The progress_id returned from start." },
15554
- step_label: { type: "string", description: 'Short label for the current step (e.g. "Querying CloudWatch logs").' },
15555
- step_current: { type: "number", description: "1-based step counter \u2014 current." },
15556
- step_total: { type: "number", description: "Total steps if known. Omit for open-ended progress." },
15557
- mode: { type: "string", enum: ["thinking", "working", "waiting"] },
15558
- detail: { type: "string", description: "Free-text detail line under the step label." }
15559
- },
15560
- required: ["progress_id"]
15561
- }
15562
- },
15563
- {
15564
- name: this.toolNames.complete,
15565
- description: `Mark the progress anchor as \u2705 completed. Flushes any pending debounced state before painting the terminal banner \u2014 the operator never sees a stale "Step N" beside the \u2705. After this, the handle is dead; further updates are no-ops.`,
15566
- inputSchema: {
15567
- type: "object",
15568
- properties: {
15569
- progress_id: { type: "string" },
15570
- summary: { type: "string", description: "Optional one-line summary of what was achieved." }
15571
- },
15572
- required: ["progress_id"]
15573
- }
15574
- },
15575
- {
15576
- name: this.toolNames.fail,
15577
- description: `Mark the progress anchor as \u274C failed. Always include a one-sentence reason \u2014 the operator can't tell *why* from emoji alone. Same flush-then-paint behaviour as complete.`,
15578
- inputSchema: {
15579
- type: "object",
15580
- properties: {
15581
- progress_id: { type: "string" },
15582
- reason: { type: "string", description: "One-sentence failure reason." }
15583
- },
15584
- required: ["progress_id", "reason"]
15585
- }
15586
- }
15587
- ];
15588
- }
15589
- /**
15590
- * Dispatch a CallToolRequest. Returns `undefined` if the tool name
15591
- * isn't one of ours (so the caller can keep walking its dispatch
15592
- * chain). Returns a tool result otherwise.
15593
- */
15594
- async handle(name, args) {
15595
- if (name === this.toolNames.start) return this.handleStart(args);
15596
- if (name === this.toolNames.update) return this.handleUpdate(args);
15597
- if (name === this.toolNames.complete) return this.handleComplete(args);
15598
- if (name === this.toolNames.fail) return this.handleFail(args);
15599
- return void 0;
15600
- }
15601
- /** Live count of in-flight handles. Exposed for tests + diagnostics. */
15602
- size() {
15603
- return this.handles.size;
15604
- }
15605
- async handleStart(args) {
15606
- const label = typeof args.label === "string" ? args.label : null;
15607
- if (!label) return errResult(`${this.toolNames.start}: 'label' must be a non-empty string`);
15608
- if ("initial_mode" in args && !isMode(args.initial_mode)) {
15609
- return errResult(
15610
- `${this.toolNames.start}: 'initial_mode' must be one of 'thinking', 'working', or 'waiting'`
15611
- );
15612
- }
15613
- for (const k of this.opts.startArgsSchema.required) {
15614
- if (typeof args[k] !== "string" || args[k].length === 0) {
15615
- return errResult(`${this.toolNames.start}: '${k}' must be a non-empty string`);
15616
- }
15617
- }
15618
- const initialMode = isMode(args.initial_mode) ? args.initial_mode : void 0;
15619
- const progressId = randomUUID();
15620
- try {
15621
- const flush = this.opts.createFlush(args);
15622
- const handle = await startProgressHandle({
15623
- initialLabel: label,
15624
- initialMode,
15625
- flush,
15626
- debounceMs: this.opts.debounceMs ?? 1e3
15627
- });
15628
- this.handles.set(progressId, handle);
15629
- return okResult(JSON.stringify({ progress_id: progressId }));
15630
- } catch (err) {
15631
- return errResult(`${this.toolNames.start} failed: ${err.message ?? String(err)}`);
15632
- }
15633
- }
15634
- async handleUpdate(args) {
15635
- const id = typeof args.progress_id === "string" ? args.progress_id : null;
15636
- if (!id) return errResult(`${this.toolNames.update}: 'progress_id' must be a string`);
15637
- const handle = this.handles.get(id);
15638
- if (!handle) return errResult(`${this.toolNames.update}: unknown progress_id (already terminated, or never started)`);
15639
- if ("mode" in args && !isMode(args.mode)) {
15640
- return errResult(
15641
- `${this.toolNames.update}: 'mode' must be one of 'thinking', 'working', or 'waiting'`
15642
- );
15643
- }
15644
- if (typeof args.step_total === "number" && typeof args.step_current !== "number") {
15645
- return errResult(
15646
- `${this.toolNames.update}: 'step_total' requires 'step_current'`
15647
- );
15648
- }
15649
- const next = {};
15650
- if (typeof args.step_label === "string") next.stepLabel = args.step_label;
15651
- if (typeof args.step_current === "number") {
15652
- next.stepNumber = {
15653
- current: args.step_current,
15654
- ...typeof args.step_total === "number" ? { total: args.step_total } : {}
15655
- };
15656
- }
15657
- if (isMode(args.mode)) next.mode = args.mode;
15658
- if (typeof args.detail === "string") next.detail = args.detail;
15659
- try {
15660
- await handle.update(next);
15661
- return okResult("ok");
15662
- } catch (err) {
15663
- return errResult(`${this.toolNames.update} failed: ${err.message ?? String(err)}`);
15664
- }
15665
- }
15666
- async handleComplete(args) {
15667
- const id = typeof args.progress_id === "string" ? args.progress_id : null;
15668
- if (!id) return errResult(`${this.toolNames.complete}: 'progress_id' must be a string`);
15669
- const handle = this.handles.get(id);
15670
- if (!handle) return errResult(`${this.toolNames.complete}: unknown progress_id`);
15671
- const summary = typeof args.summary === "string" ? args.summary : void 0;
15672
- try {
15673
- await handle.complete(summary);
15674
- this.handles.delete(id);
15675
- return okResult("ok");
15676
- } catch (err) {
15677
- return errResult(`${this.toolNames.complete} failed: ${err.message ?? String(err)}`);
15678
- }
15679
- }
15680
- async handleFail(args) {
15681
- const id = typeof args.progress_id === "string" ? args.progress_id : null;
15682
- if (!id) return errResult(`${this.toolNames.fail}: 'progress_id' must be a string`);
15683
- const reason = typeof args.reason === "string" ? args.reason : null;
15684
- if (!reason) return errResult(`${this.toolNames.fail}: 'reason' must be a non-empty string`);
15685
- const handle = this.handles.get(id);
15686
- if (!handle) return errResult(`${this.toolNames.fail}: unknown progress_id`);
15687
- try {
15688
- await handle.fail(reason);
15689
- this.handles.delete(id);
15690
- return okResult("ok");
15691
- } catch (err) {
15692
- return errResult(`${this.toolNames.fail} failed: ${err.message ?? String(err)}`);
15693
- }
15694
- }
15695
- };
15696
- function okResult(text) {
15697
- return { content: [{ type: "text", text }] };
15698
- }
15699
- function errResult(text) {
15700
- return { content: [{ type: "text", text }], isError: true };
15701
- }
15702
- function isMode(value) {
15703
- return value === "thinking" || value === "working" || value === "waiting";
15704
- }
15705
-
15706
- // src/telegram-progress.ts
15707
- var MODE_EMOJI = {
15708
- thinking: "\u{1F4AD}",
15709
- working: "\u{1F6E0}\uFE0F",
15710
- waiting: "\u23F3"
15711
- };
15712
- var TERMINAL_EMOJI = {
15713
- completed: "\u2705",
15714
- failed: "\u274C"
15715
- };
15716
- function formatWallClock(ms, timeZone) {
15717
- const fmt = new Intl.DateTimeFormat("en-GB", {
15718
- hour: "2-digit",
15719
- minute: "2-digit",
15720
- second: "2-digit",
15721
- hour12: false,
15722
- timeZone,
15723
- timeZoneName: "short"
15724
- });
15725
- const parts = fmt.formatToParts(new Date(ms));
15726
- const get = (t) => parts.find((p) => p.type === t)?.value ?? "";
15727
- return `${get("hour")}:${get("minute")}:${get("second")} ${get("timeZoneName")}`;
15728
- }
15729
- function escapeHtml(s) {
15730
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
15731
- }
15732
- function renderProgressHtml(state) {
15733
- const lines = [];
15734
- if (state.terminal) {
15735
- const banner = state.terminal.kind === "completed" ? "Done" : "Failed";
15736
- lines.push(`${TERMINAL_EMOJI[state.terminal.kind]} <b>${banner}</b> \u2014 ${escapeHtml(state.initialLabel)}`);
15737
- if (state.terminal.message) lines.push(escapeHtml(state.terminal.message));
15738
- const verb = state.terminal.kind === "completed" ? "completed" : "failed";
15739
- lines.push(`<i>${verb}: ${formatWallClock(state.lastChangeAt)}</i>`);
15740
- return lines.join("\n");
15741
- }
15742
- lines.push(`${MODE_EMOJI[state.mode]} <b>Working on:</b> ${escapeHtml(state.initialLabel)}`);
15743
- if (state.stepNumber || state.stepLabel) {
15744
- const parts = [];
15745
- if (state.stepNumber) {
15746
- if (state.stepNumber.total != null) {
15747
- parts.push(`<b>Step ${state.stepNumber.current} of ${state.stepNumber.total}</b>`);
15748
- } else {
15749
- parts.push(`<b>Step ${state.stepNumber.current}</b>`);
15750
- }
15751
- }
15752
- if (state.stepLabel) parts.push(escapeHtml(state.stepLabel));
15753
- lines.push(parts.join(": "));
15754
- }
15755
- if (state.detail) lines.push(`<i>${escapeHtml(state.detail)}</i>`);
15756
- lines.push(`<i>last update: ${formatWallClock(state.lastChangeAt)}</i>`);
15757
- return lines.join("\n");
15758
- }
15759
- var TelegramApiError = class extends Error {
15760
- constructor(endpoint, apiError) {
15761
- super(`Telegram ${endpoint} failed: ${apiError}`);
15762
- this.endpoint = endpoint;
15763
- this.apiError = apiError;
15764
- this.name = "TelegramApiError";
15765
- }
15766
- };
15767
- function isMessageNotModified(description) {
15768
- if (!description) return false;
15769
- return /message is not modified/i.test(description);
15770
- }
15771
- function createTelegramProgressFlush(opts) {
15772
- let anchorMessageId = null;
15773
- return async function flush(state) {
15774
- const text = renderProgressHtml(state);
15775
- if (anchorMessageId == null) {
15776
- const body = {
15777
- chat_id: opts.chatId,
15778
- text,
15779
- parse_mode: "HTML",
15780
- disable_notification: true
15781
- };
15782
- if (opts.messageThreadId != null) body.message_thread_id = opts.messageThreadId;
15783
- if (opts.replyToMessageId != null) body.reply_to_message_id = opts.replyToMessageId;
15784
- const res = await opts.callImpl("sendMessage", body);
15785
- if (!res.ok || !res.result?.message_id) {
15786
- throw new TelegramApiError("sendMessage", res.description ?? "missing message_id");
15787
- }
15788
- anchorMessageId = res.result.message_id;
15789
- opts.onAnchorPosted?.(res.result.message_id);
15790
- return;
15791
- }
15792
- try {
15793
- const res = await opts.callImpl("editMessageText", {
15794
- chat_id: opts.chatId,
15795
- message_id: anchorMessageId,
15796
- text,
15797
- parse_mode: "HTML"
15798
- });
15799
- if (!res.ok) {
15800
- if (isMessageNotModified(res.description)) return;
15801
- throw new TelegramApiError("editMessageText", res.description ?? "unknown");
15802
- }
15803
- } catch (err) {
15804
- if (opts.onApiError && err instanceof TelegramApiError) {
15805
- await opts.onApiError(err);
15806
- return;
15807
- }
15808
- throw err;
15809
- }
15810
- };
15811
- }
15812
-
15813
15389
  // src/impersonation.ts
15814
15390
  var ENV_VAR = "AGT_ACT_AS_AGENT_ID";
15815
15391
  var OVERRIDE_ENV_VAR = "ENABLE_IMPERSONATION_CHANNELS";
@@ -15925,13 +15501,112 @@ function readLockHolder(path) {
15925
15501
  }
15926
15502
  }
15927
15503
 
15504
+ // src/ack-reaction.ts
15505
+ import { readdirSync, readFileSync as readFileSync2 } from "fs";
15506
+ import { join as join3 } from "path";
15507
+ var REPLY_WEDGED_THRESHOLD_MS = 5 * 60 * 1e3;
15508
+ var ACK_STARTUP_GRACE_MS = 6e4;
15509
+ function decideAckReaction(i) {
15510
+ if (!i.hasTarget) return "none";
15511
+ if (!i.integrationReady) return "undeliverable";
15512
+ if (i.tmux === "dead") return "undeliverable";
15513
+ if (!i.withinStartupGrace && i.claude === "dead") return "undeliverable";
15514
+ const threshold = i.pendingStaleThresholdMs ?? REPLY_WEDGED_THRESHOLD_MS;
15515
+ if (i.oldestPendingAgeMs != null && i.oldestPendingAgeMs > threshold) {
15516
+ return "undeliverable";
15517
+ }
15518
+ return "ack";
15519
+ }
15520
+ function decideRecoveryHeal(i) {
15521
+ return i.wasUndeliverable && i.hasTarget ? "heal" : "none";
15522
+ }
15523
+ function oldestPendingMarkerAgeMs(dir, now = Date.now()) {
15524
+ if (!dir) return null;
15525
+ let names;
15526
+ try {
15527
+ names = readdirSync(dir);
15528
+ } catch {
15529
+ return null;
15530
+ }
15531
+ let oldest = null;
15532
+ for (const name of names) {
15533
+ if (!name.endsWith(".json")) continue;
15534
+ let receivedAt;
15535
+ try {
15536
+ const raw = JSON.parse(readFileSync2(join3(dir, name), "utf-8"));
15537
+ receivedAt = raw.received_at;
15538
+ } catch {
15539
+ continue;
15540
+ }
15541
+ if (!receivedAt) continue;
15542
+ const t = Date.parse(receivedAt);
15543
+ if (Number.isNaN(t)) continue;
15544
+ const age = now - t;
15545
+ if (age < 0) continue;
15546
+ if (oldest == null || age > oldest) oldest = age;
15547
+ }
15548
+ return oldest;
15549
+ }
15550
+
15551
+ // src/session-probe-runtime.ts
15552
+ import { execFileSync } from "child_process";
15553
+ function agentTmuxSessionName(codeName) {
15554
+ return `agt-${codeName}`;
15555
+ }
15556
+ function escapePgrepRegex(value) {
15557
+ return value.replace(/[.[\]{}()*+?^$|\\]/g, "\\$&");
15558
+ }
15559
+ function probeClaudeProcessInTmux(tmuxSession) {
15560
+ const escapedSession = escapePgrepRegex(tmuxSession);
15561
+ const pattern = `(^|[[:space:]])--name ${escapedSession}([[:space:]]|$)`;
15562
+ try {
15563
+ const out = execFileSync("pgrep", ["-f", "--", pattern], {
15564
+ encoding: "utf-8",
15565
+ timeout: 3e3
15566
+ }).trim();
15567
+ return out.length > 0 ? "alive" : "dead";
15568
+ } catch (err) {
15569
+ const e = err;
15570
+ if (e?.code === "ENOENT") return "unknown";
15571
+ return e?.status === 1 ? "dead" : "unknown";
15572
+ }
15573
+ }
15574
+ function probeTmuxSession(tmuxSession) {
15575
+ try {
15576
+ execFileSync("tmux", ["has-session", "-t", tmuxSession], {
15577
+ stdio: "ignore",
15578
+ timeout: 3e3
15579
+ });
15580
+ return "alive";
15581
+ } catch (err) {
15582
+ const e = err;
15583
+ if (e?.code === "ENOENT") return "unknown";
15584
+ return "dead";
15585
+ }
15586
+ }
15587
+ function probeAgentSession(codeName) {
15588
+ const session = agentTmuxSessionName(codeName);
15589
+ const tmux = probeTmuxSession(session);
15590
+ const claude = tmux === "alive" ? probeClaudeProcessInTmux(session) : tmux;
15591
+ return { tmux, claude };
15592
+ }
15593
+ var probeCache = /* @__PURE__ */ new Map();
15594
+ var SESSION_PROBE_TTL_MS = 15e3;
15595
+ function probeAgentSessionCached(codeName, ttlMs = SESSION_PROBE_TTL_MS, now = Date.now()) {
15596
+ const cached2 = probeCache.get(codeName);
15597
+ if (cached2 && now - cached2.at < ttlMs) return cached2.value;
15598
+ const value = probeAgentSession(codeName);
15599
+ probeCache.set(codeName, { at: now, value });
15600
+ return value;
15601
+ }
15602
+
15928
15603
  // src/telegram-channel.ts
15929
15604
  function redactId(id) {
15930
15605
  return createHash("sha256").update(String(id)).digest("hex").slice(0, 8);
15931
15606
  }
15932
15607
  var BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
15933
15608
  var AGENT_CODE_NAME = process.env.AGT_AGENT_CODE_NAME ?? "unknown";
15934
- var TELEGRAM_AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join3(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
15609
+ var TELEGRAM_AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join4(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
15935
15610
  var AGT_HOST = process.env.AGT_HOST ?? null;
15936
15611
  var AGT_API_KEY = process.env.AGT_API_KEY ?? null;
15937
15612
  var AGT_AGENT_ID = process.env.AGT_AGENT_ID ?? null;
@@ -16015,9 +15690,9 @@ if (!BOT_TOKEN) {
16015
15690
  var stderrLogStream = null;
16016
15691
  if (AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown") {
16017
15692
  try {
16018
- const logDir = join3(homedir2(), ".augmented", AGENT_CODE_NAME);
15693
+ const logDir = join4(homedir2(), ".augmented", AGENT_CODE_NAME);
16019
15694
  mkdirSync3(logDir, { recursive: true });
16020
- stderrLogStream = createWriteStream(join3(logDir, "telegram-channel-stderr.log"), {
15695
+ stderrLogStream = createWriteStream(join4(logDir, "telegram-channel-stderr.log"), {
16021
15696
  flags: "a",
16022
15697
  mode: 384
16023
15698
  });
@@ -16098,7 +15773,76 @@ async function setMessageReaction(chatId, messageId, emoji2) {
16098
15773
  );
16099
15774
  }
16100
15775
  }
16101
- var RESTART_FLAGS_DIR = join3(homedir2(), ".augmented", "restart-flags");
15776
+ var UNDELIVERABLE_NOTICE_THROTTLE_MS = 5 * 60 * 1e3;
15777
+ var lastUndeliverableNoticeAt = /* @__PURE__ */ new Map();
15778
+ async function notifyUndeliverable(chatId, messageId) {
15779
+ const key2 = String(chatId);
15780
+ const now = Date.now();
15781
+ const last = lastUndeliverableNoticeAt.get(key2);
15782
+ if (last != null && now - last < UNDELIVERABLE_NOTICE_THROTTLE_MS) return;
15783
+ lastUndeliverableNoticeAt.set(key2, now);
15784
+ try {
15785
+ const resp = await telegramApiCall(
15786
+ "sendMessage",
15787
+ {
15788
+ chat_id: chatId,
15789
+ text: "\u26A0\uFE0F I can't respond right now \u2014 I'll follow up once I'm back.",
15790
+ reply_to_message_id: Number(messageId),
15791
+ allow_sending_without_reply: true
15792
+ },
15793
+ 1e4
15794
+ );
15795
+ if (!resp.ok) {
15796
+ process.stderr.write(
15797
+ `telegram-channel(${AGENT_CODE_NAME}): undeliverable notice failed (chat=${redactId(chatId)}): ${resp.description ?? "unknown"}
15798
+ `
15799
+ );
15800
+ }
15801
+ } catch (err) {
15802
+ process.stderr.write(
15803
+ `telegram-channel(${AGENT_CODE_NAME}): undeliverable notice error: ${err.message}
15804
+ `
15805
+ );
15806
+ }
15807
+ }
15808
+ function __resetUndeliverableNoticeThrottle() {
15809
+ lastUndeliverableNoticeAt.clear();
15810
+ }
15811
+ var lastBackOnlineNoticeAt = /* @__PURE__ */ new Map();
15812
+ function notifyBackOnline(chatId) {
15813
+ const key2 = String(chatId);
15814
+ const now = Date.now();
15815
+ const last = lastBackOnlineNoticeAt.get(key2);
15816
+ if (last != null && now - last < UNDELIVERABLE_NOTICE_THROTTLE_MS) return;
15817
+ lastBackOnlineNoticeAt.set(key2, now);
15818
+ void (async () => {
15819
+ try {
15820
+ const resp = await telegramApiCall(
15821
+ "sendMessage",
15822
+ {
15823
+ chat_id: chatId,
15824
+ text: "\u2705 Back online \u2014 catching up on what I missed."
15825
+ },
15826
+ 1e4
15827
+ );
15828
+ if (!resp.ok) {
15829
+ process.stderr.write(
15830
+ `telegram-channel(${AGENT_CODE_NAME}): back-online notice failed (chat=${redactId(chatId)}): ${resp.description ?? "unknown"}
15831
+ `
15832
+ );
15833
+ }
15834
+ } catch (err) {
15835
+ process.stderr.write(
15836
+ `telegram-channel(${AGENT_CODE_NAME}): back-online notice error: ${err.message}
15837
+ `
15838
+ );
15839
+ }
15840
+ })();
15841
+ }
15842
+ function __resetBackOnlineNoticeThrottle() {
15843
+ lastBackOnlineNoticeAt.clear();
15844
+ }
15845
+ var RESTART_FLAGS_DIR = join4(homedir2(), ".augmented", "restart-flags");
16102
15846
  function buildTelegramHelpMessage(codeName) {
16103
15847
  return [
16104
15848
  `\u{1F916} *Available commands for \`${codeName}\`*`,
@@ -16138,14 +15882,14 @@ async function handleRestartCommand(opts) {
16138
15882
  if (!existsSync2(RESTART_FLAGS_DIR)) {
16139
15883
  mkdirSync3(RESTART_FLAGS_DIR, { recursive: true });
16140
15884
  }
16141
- const flagPath = join3(RESTART_FLAGS_DIR, `${AGENT_CODE_NAME}.flag`);
15885
+ const flagPath = join4(RESTART_FLAGS_DIR, `${AGENT_CODE_NAME}.flag`);
16142
15886
  const flag = {
16143
15887
  codeName: AGENT_CODE_NAME,
16144
15888
  source: "telegram",
16145
15889
  ts: Date.now(),
16146
15890
  reply: { chat_id: opts.chatId, message_id: opts.messageId }
16147
15891
  };
16148
- const tmpPath = `${flagPath}.${process.pid}.${randomUUID2()}.tmp`;
15892
+ const tmpPath = `${flagPath}.${process.pid}.${randomUUID()}.tmp`;
16149
15893
  writeFileSync3(tmpPath, JSON.stringify(flag) + "\n", "utf8");
16150
15894
  renameSync3(tmpPath, flagPath);
16151
15895
  process.stderr.write(
@@ -16254,25 +15998,27 @@ async function classifyRestartCommand(text) {
16254
15998
  if (!ours) return "verification_failed";
16255
15999
  return target === ours ? "act" : "ignore";
16256
16000
  }
16257
- var AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join3(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
16258
- var PENDING_INBOUND_DIR = AGENT_DIR ? join3(AGENT_DIR, "telegram-pending-inbound") : null;
16259
- var RECOVERY_OUTBOX_DIR = AGENT_DIR ? join3(AGENT_DIR, "telegram-recovery-outbox") : null;
16001
+ var AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join4(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
16002
+ var PENDING_INBOUND_DIR = AGENT_DIR ? join4(AGENT_DIR, "telegram-pending-inbound") : null;
16003
+ var RECOVERY_OUTBOX_DIR = AGENT_DIR ? join4(AGENT_DIR, "telegram-recovery-outbox") : null;
16260
16004
  function safeMarkerName(chatId, messageId) {
16261
16005
  const safe = (s) => s.replace(/[^A-Za-z0-9_-]/g, "_");
16262
16006
  return `${safe(chatId)}__${safe(messageId)}.json`;
16263
16007
  }
16264
16008
  function pendingInboundPath(chatId, messageId) {
16265
16009
  if (!PENDING_INBOUND_DIR) return null;
16266
- return join3(PENDING_INBOUND_DIR, safeMarkerName(chatId, messageId));
16010
+ return join4(PENDING_INBOUND_DIR, safeMarkerName(chatId, messageId));
16267
16011
  }
16268
- function writePendingInboundMarker(chatId, messageId, chatType) {
16012
+ function writePendingInboundMarker(chatId, messageId, chatType, undeliverable = false) {
16269
16013
  const path = pendingInboundPath(chatId, messageId);
16270
16014
  if (!path || !PENDING_INBOUND_DIR) return;
16271
16015
  const marker = {
16272
16016
  chat_id: chatId,
16273
16017
  message_id: messageId,
16274
16018
  chat_type: chatType,
16275
- received_at: (/* @__PURE__ */ new Date()).toISOString()
16019
+ received_at: (/* @__PURE__ */ new Date()).toISOString(),
16020
+ // Only persist the flag when set — keeps healthy-path markers byte-identical.
16021
+ ...undeliverable ? { undeliverable: true } : {}
16276
16022
  };
16277
16023
  try {
16278
16024
  mkdirSync3(PENDING_INBOUND_DIR, { recursive: true, mode: 448 });
@@ -16284,14 +16030,28 @@ function writePendingInboundMarker(chatId, messageId, chatType) {
16284
16030
  );
16285
16031
  }
16286
16032
  }
16287
- function clearPendingInboundMarker(chatId, messageId) {
16288
- const path = pendingInboundPath(chatId, messageId);
16289
- if (!path) return;
16033
+ function clearTelegramMarkerFileWithHeal(fullPath) {
16034
+ let marker = null;
16035
+ try {
16036
+ marker = JSON.parse(readFileSync3(fullPath, "utf-8"));
16037
+ } catch {
16038
+ }
16039
+ if (marker && decideRecoveryHeal({
16040
+ wasUndeliverable: marker.undeliverable === true,
16041
+ hasTarget: Boolean(marker.chat_id)
16042
+ }) === "heal") {
16043
+ notifyBackOnline(marker.chat_id);
16044
+ }
16290
16045
  try {
16291
- if (existsSync2(path)) unlinkSync3(path);
16046
+ if (existsSync2(fullPath)) unlinkSync3(fullPath);
16292
16047
  } catch {
16293
16048
  }
16294
16049
  }
16050
+ function clearPendingInboundMarker(chatId, messageId) {
16051
+ const path = pendingInboundPath(chatId, messageId);
16052
+ if (!path) return;
16053
+ clearTelegramMarkerFileWithHeal(path);
16054
+ }
16295
16055
  var MAX_RECOVERY_ATTEMPTS = 3;
16296
16056
  function nextRetryName(filename) {
16297
16057
  const match = filename.match(/^(.*?)(?:\.retry-(\d+))?\.json$/);
@@ -16307,10 +16067,10 @@ function nextRetryName(filename) {
16307
16067
  async function processRecoveryOutboxFile(filename) {
16308
16068
  if (!RECOVERY_OUTBOX_DIR) return;
16309
16069
  if (filename.endsWith(".poison.json") || filename.endsWith(".tmp")) return;
16310
- const fullPath = join3(RECOVERY_OUTBOX_DIR, filename);
16070
+ const fullPath = join4(RECOVERY_OUTBOX_DIR, filename);
16311
16071
  let payload;
16312
16072
  try {
16313
- const raw = readFileSync2(fullPath, "utf-8");
16073
+ const raw = readFileSync3(fullPath, "utf-8");
16314
16074
  payload = JSON.parse(raw);
16315
16075
  } catch (err) {
16316
16076
  process.stderr.write(
@@ -16373,7 +16133,7 @@ async function processRecoveryOutboxFile(filename) {
16373
16133
  const next = nextRetryName(filename);
16374
16134
  if (next) {
16375
16135
  try {
16376
- renameSync3(fullPath, join3(RECOVERY_OUTBOX_DIR, next.next));
16136
+ renameSync3(fullPath, join4(RECOVERY_OUTBOX_DIR, next.next));
16377
16137
  if (next.attempt >= MAX_RECOVERY_ATTEMPTS) {
16378
16138
  process.stderr.write(
16379
16139
  `telegram-channel(${AGENT_CODE_NAME}): ghost-reply recovery exhausted retries \u2014 moved to ${next.next}
@@ -16404,7 +16164,7 @@ function scanRecoveryRetries() {
16404
16164
  if (!RECOVERY_OUTBOX_DIR) return;
16405
16165
  let entries;
16406
16166
  try {
16407
- entries = readdirSync(RECOVERY_OUTBOX_DIR);
16167
+ entries = readdirSync2(RECOVERY_OUTBOX_DIR);
16408
16168
  } catch {
16409
16169
  return;
16410
16170
  }
@@ -16413,7 +16173,7 @@ function scanRecoveryRetries() {
16413
16173
  if (!f.includes(".retry-") || f.endsWith(".poison.json")) continue;
16414
16174
  let mtimeMs;
16415
16175
  try {
16416
- mtimeMs = statSync(join3(RECOVERY_OUTBOX_DIR, f)).mtimeMs;
16176
+ mtimeMs = statSync(join4(RECOVERY_OUTBOX_DIR, f)).mtimeMs;
16417
16177
  } catch {
16418
16178
  continue;
16419
16179
  }
@@ -16434,7 +16194,7 @@ function startRecoveryOutboxWatcher() {
16434
16194
  return;
16435
16195
  }
16436
16196
  try {
16437
- for (const f of readdirSync(RECOVERY_OUTBOX_DIR)) {
16197
+ for (const f of readdirSync2(RECOVERY_OUTBOX_DIR)) {
16438
16198
  if (isFirstAttemptOutboxFile(f)) void processRecoveryOutboxFile(f);
16439
16199
  }
16440
16200
  } catch {
@@ -16443,7 +16203,7 @@ function startRecoveryOutboxWatcher() {
16443
16203
  const watcher = watch(RECOVERY_OUTBOX_DIR, (event, filename) => {
16444
16204
  if (event !== "rename" || !filename) return;
16445
16205
  if (!isFirstAttemptOutboxFile(filename)) return;
16446
- if (existsSync2(join3(RECOVERY_OUTBOX_DIR, filename))) {
16206
+ if (existsSync2(join4(RECOVERY_OUTBOX_DIR, filename))) {
16447
16207
  void processRecoveryOutboxFile(filename);
16448
16208
  }
16449
16209
  });
@@ -16459,15 +16219,15 @@ function startRecoveryOutboxWatcher() {
16459
16219
  }
16460
16220
  startRecoveryOutboxWatcher();
16461
16221
  var STALE_MARKER_MS = 24 * 60 * 60 * 1e3;
16462
- function trackPendingMessage(chatId, messageId, chatType) {
16463
- writePendingInboundMarker(chatId, messageId, chatType);
16222
+ function trackPendingMessage(chatId, messageId, chatType, undeliverable = false) {
16223
+ writePendingInboundMarker(chatId, messageId, chatType, undeliverable);
16464
16224
  }
16465
16225
  function sweepTelegramStaleMarkersOnBoot() {
16466
16226
  if (!PENDING_INBOUND_DIR) return;
16467
16227
  if (!existsSync2(PENDING_INBOUND_DIR)) return;
16468
16228
  let filenames;
16469
16229
  try {
16470
- filenames = readdirSync(PENDING_INBOUND_DIR);
16230
+ filenames = readdirSync2(PENDING_INBOUND_DIR);
16471
16231
  } catch (err) {
16472
16232
  process.stderr.write(
16473
16233
  `telegram-channel(${AGENT_CODE_NAME}): stale-marker readdir failed: ${err.message}
@@ -16480,10 +16240,10 @@ function sweepTelegramStaleMarkersOnBoot() {
16480
16240
  for (const filename of filenames) {
16481
16241
  if (!filename.endsWith(".json")) continue;
16482
16242
  if (filename.endsWith(".tmp")) continue;
16483
- const fullPath = join3(PENDING_INBOUND_DIR, filename);
16243
+ const fullPath = join4(PENDING_INBOUND_DIR, filename);
16484
16244
  let marker;
16485
16245
  try {
16486
- marker = JSON.parse(readFileSync2(fullPath, "utf-8"));
16246
+ marker = JSON.parse(readFileSync3(fullPath, "utf-8"));
16487
16247
  } catch (err) {
16488
16248
  process.stderr.write(
16489
16249
  `telegram-channel(${AGENT_CODE_NAME}): stale-marker parse failed for ${redactId(filename)}: ${err.message}
@@ -16532,17 +16292,14 @@ function clearPendingMessage(chatId, messageId) {
16532
16292
  const prefix = `${safeChatId}__`;
16533
16293
  let filenames;
16534
16294
  try {
16535
- filenames = readdirSync(PENDING_INBOUND_DIR);
16295
+ filenames = readdirSync2(PENDING_INBOUND_DIR);
16536
16296
  } catch {
16537
16297
  return;
16538
16298
  }
16539
16299
  for (const filename of filenames) {
16540
16300
  if (!filename.startsWith(prefix)) continue;
16541
16301
  if (!filename.endsWith(".json")) continue;
16542
- try {
16543
- unlinkSync3(join3(PENDING_INBOUND_DIR, filename));
16544
- } catch {
16545
- }
16302
+ clearTelegramMarkerFileWithHeal(join4(PENDING_INBOUND_DIR, filename));
16546
16303
  }
16547
16304
  }
16548
16305
  function noteThreadActivity(chatId, messageId) {
@@ -16567,46 +16324,12 @@ var mcp = new Server(
16567
16324
  "Inbound attachments: <channel> `files` is a JSON-serialised array \u2014 JSON.parse it. If an entry has `path`, the image is already downloaded \u2014 Read it directly, do NOT call telegram.download_attachment. Use that tool only for entries with `file_id` but NO `path` (PDF, docx, voice, audio, video, animations): pass file_id + chat_id verbatim, then Read the returned path. Single-image messages also get a top-level `image_path`. Caption arrives as channel content. Don't surface internal file-handling errors that don't affect the answer.",
16568
16325
  'For work >30s follow CLAUDE.md kanban flow: kanban_add \u2192 reply "On it \u2014 tracking here: <kanban URL>" \u2192 move to in_progress \u2192 do the work \u2192 reply with the result. Simple lookups skip kanban but still reply.',
16569
16326
  "Address users by user_name; user is the numeric Telegram ID. Resolve ambiguous times against your own Timezone from CLAUDE.md \u2014 do not ask.",
16570
- "Reaction taxonomy (use telegram.react sparingly \u2014 prefer telegram.reply): \u{1F440} = ack (already auto-added on inbound, do not duplicate); \u{1F44D} or \u{1F389} = success. NEVER react to signal failure. On failure, telegram.reply with one sentence explaining what went wrong. Free-tier emoji: \u{1F44D} \u{1F44E} \u2764 \u{1F525} \u{1F389} \u{1F914} \u{1F92F} \u{1F64F} \u{1F44C} \u{1F440} \u{1F4AF} \u270D \u{1FAE1} \u{1F192} \u{1F973} \u{1F494}."
16327
+ `Reaction taxonomy (use telegram.react sparingly \u2014 prefer telegram.reply): \u{1F440} = ack (already auto-added on inbound, do not duplicate); \u{1F44D} or \u{1F389} = success. NEVER react to signal failure of YOUR work. On failure, telegram.reply with one sentence explaining what went wrong. Free-tier emoji: \u{1F44D} \u{1F44E} \u2764 \u{1F525} \u{1F389} \u{1F914} \u{1F92F} \u{1F64F} \u{1F44C} \u{1F440} \u{1F4AF} \u270D \u{1FAE1} \u{1F192} \u{1F973} \u{1F494}. (If a message arrived while you were offline you may see a system-posted "can't respond right now" notice \u2014 that's the runtime, not you; don't repeat or apologise for it.)`
16571
16328
  ].join(" ")
16572
16329
  }
16573
16330
  );
16574
- var TELEGRAM_PROGRESS_TIMEOUT_MS = 5e3;
16575
- var progressRegistry = new ProgressToolRegistry({
16576
- prefix: "telegram",
16577
- surfaceDescription: "a Telegram chat",
16578
- startArgsSchema: {
16579
- properties: {
16580
- chat_id: {
16581
- type: "string",
16582
- description: "Telegram chat id (from the `chat_id` attribute on the inbound <channel> tag). Numeric ids are passed as strings; the API accepts both."
16583
- },
16584
- message_thread_id: {
16585
- type: "string",
16586
- description: "Forum topic id (`message_thread_id`) \u2014 pass this if the inbound arrived in a forum topic so the anchor lands in the same topic. Omit for normal chats."
16587
- },
16588
- reply_to_message_id: {
16589
- type: "string",
16590
- description: "Optional message id to thread the anchor under (typically the inbound message_id from the <channel> tag)."
16591
- }
16592
- },
16593
- required: ["chat_id"]
16594
- },
16595
- createFlush: (args) => {
16596
- const chatId = args.chat_id;
16597
- const threadId = typeof args.message_thread_id === "string" && args.message_thread_id.length > 0 ? Number(args.message_thread_id) : void 0;
16598
- const replyTo = typeof args.reply_to_message_id === "string" && args.reply_to_message_id.length > 0 ? Number(args.reply_to_message_id) : void 0;
16599
- return createTelegramProgressFlush({
16600
- chatId,
16601
- messageThreadId: Number.isFinite(threadId) ? threadId : void 0,
16602
- replyToMessageId: Number.isFinite(replyTo) ? replyTo : void 0,
16603
- callImpl: (method, body) => telegramApiCall(method, body, TELEGRAM_PROGRESS_TIMEOUT_MS)
16604
- });
16605
- }
16606
- });
16607
16331
  mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
16608
16332
  tools: [
16609
- ...progressRegistry.getDefinitions(),
16610
16333
  {
16611
16334
  name: "channel_request_input",
16612
16335
  description: "Generic Yes/No or multi-choice picker. Renders as a Telegram inline keyboard; the tool blocks until the user taps an option (or the timeout elapses) and returns the resolved value. Use this when you need a structured pick rather than freeform text \u2014 e.g. confirming a draft action or disambiguating an ambiguous fork.",
@@ -16698,7 +16421,7 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
16698
16421
  },
16699
16422
  {
16700
16423
  name: "telegram.react",
16701
- description: "Add an emoji reaction to a Telegram message. Use sparingly \u2014 prefer a text reply via telegram.reply. Reaction taxonomy: \u{1F44D} or \u{1F389} = action completed successfully. NEVER react to signal failure \u2014 users can't tell why it failed from a reaction. On failure, call telegram.reply with one sentence explaining what went wrong instead. \u{1F440} is already auto-applied on inbound; do not duplicate. Only free-tier emoji reactions are available to bots (Premium-only emoji fail silently). Pass an empty string or omit emoji to clear the bot's reaction on that message.",
16424
+ description: `Add an emoji reaction to a Telegram message. Use sparingly \u2014 prefer a text reply via telegram.reply. Reaction taxonomy: \u{1F44D} or \u{1F389} = action completed successfully. NEVER react to signal failure of your work \u2014 users can't tell why it failed from a reaction. On failure, call telegram.reply with one sentence explaining what went wrong instead. \u{1F440} is already auto-applied on inbound; do not duplicate. Only free-tier emoji reactions are available to bots (Premium-only emoji fail silently). Pass an empty string or omit emoji to clear the bot's reaction on that message. (Note: when a message arrives while the agent is offline/wedged, the runtime \u2014 not you \u2014 posts a brief "can't respond right now" notice; you never need to add a failure reaction for that.)`,
16702
16425
  inputSchema: {
16703
16426
  type: "object",
16704
16427
  properties: {
@@ -16725,8 +16448,6 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
16725
16448
  if (isImpersonating() && !channelsEnabledOverride() && TELEGRAM_EGRESS_TOOLS.has(name)) {
16726
16449
  return buildImpersonationRefusal(name);
16727
16450
  }
16728
- const progressResult = await progressRegistry.handle(name, args ?? {});
16729
- if (progressResult !== void 0) return progressResult;
16730
16451
  if (name === "channel_request_input") {
16731
16452
  return handleChannelRequestInput(args ?? {});
16732
16453
  }
@@ -16926,12 +16647,12 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
16926
16647
  function channelRequestInputAvailable() {
16927
16648
  return Boolean(AGT_HOST && AGT_API_KEY && AGT_AGENT_ID);
16928
16649
  }
16929
- function errResult2(text) {
16650
+ function errResult(text) {
16930
16651
  return { content: [{ type: "text", text }], isError: true };
16931
16652
  }
16932
16653
  async function handleChannelRequestInput(args) {
16933
16654
  if (!channelRequestInputAvailable()) {
16934
- return errResult2(
16655
+ return errResult(
16935
16656
  "channel_request_input is disabled \u2014 missing AGT_HOST / AGT_API_KEY / AGT_AGENT_ID env wiring."
16936
16657
  );
16937
16658
  }
@@ -16939,12 +16660,12 @@ async function handleChannelRequestInput(args) {
16939
16660
  const { apiCall: apiCall2, waitForResolution: waitForResolution2, validateAskUserOptions: validateAskUserOptions2 } = runtime;
16940
16661
  const { thread_id, prompt_text, options, schema, request_id, timeout_seconds } = args;
16941
16662
  if (typeof thread_id !== "string" || !thread_id) {
16942
- return errResult2(
16663
+ return errResult(
16943
16664
  "thread_id is required (Telegram shape: `<chat_id>` or `<chat_id>:<reply_to_message_id>`)"
16944
16665
  );
16945
16666
  }
16946
16667
  if (typeof prompt_text !== "string" || !prompt_text) {
16947
- return errResult2("prompt_text is required");
16668
+ return errResult("prompt_text is required");
16948
16669
  }
16949
16670
  const optsValidation = validateAskUserOptions2(options, {
16950
16671
  maxOptions: 4,
@@ -16952,27 +16673,27 @@ async function handleChannelRequestInput(args) {
16952
16673
  maxValueChars: 2e3
16953
16674
  });
16954
16675
  if (!optsValidation.ok) {
16955
- return errResult2(
16676
+ return errResult(
16956
16677
  `Invalid options: ${optsValidation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`
16957
16678
  );
16958
16679
  }
16959
16680
  const optionsArr = options;
16960
16681
  if (optionsArr.length < 2) {
16961
- return errResult2(
16682
+ return errResult(
16962
16683
  "options must contain at least 2 entries (this is a picker, not a notification)"
16963
16684
  );
16964
16685
  }
16965
16686
  if (!schema || typeof schema.type !== "string") {
16966
- return errResult2("schema is required with shape { type: 'yes_no' | 'choice' }");
16687
+ return errResult("schema is required with shape { type: 'yes_no' | 'choice' }");
16967
16688
  }
16968
16689
  if (schema.type !== "yes_no" && schema.type !== "choice") {
16969
- return errResult2("schema.type must be 'yes_no' or 'choice'");
16690
+ return errResult("schema.type must be 'yes_no' or 'choice'");
16970
16691
  }
16971
16692
  if (schema.type === "yes_no" && optionsArr.length !== 2) {
16972
- return errResult2("schema.type='yes_no' requires exactly 2 options");
16693
+ return errResult("schema.type='yes_no' requires exactly 2 options");
16973
16694
  }
16974
16695
  if (request_id !== void 0 && (typeof request_id !== "string" || !request_id)) {
16975
- return errResult2("request_id must be a non-empty string when provided");
16696
+ return errResult("request_id must be a non-empty string when provided");
16976
16697
  }
16977
16698
  const requestedTimeout = typeof timeout_seconds === "number" ? timeout_seconds : 300;
16978
16699
  const timeoutSec = Math.max(10, Math.min(3600, requestedTimeout));
@@ -16994,10 +16715,10 @@ async function handleChannelRequestInput(args) {
16994
16715
  dispatchPayload = await res.json().catch(() => ({}));
16995
16716
  if (!res.ok || !dispatchPayload.ok || !dispatchPayload.callback_id) {
16996
16717
  const reason = dispatchPayload.reason ?? `http_${res.status}`;
16997
- return errResult2(`channel_request_input dispatch failed: ${reason}`);
16718
+ return errResult(`channel_request_input dispatch failed: ${reason}`);
16998
16719
  }
16999
16720
  } catch (err) {
17000
- return errResult2(
16721
+ return errResult(
17001
16722
  `channel_request_input dispatch failed: ${err.message}`
17002
16723
  );
17003
16724
  }
@@ -17419,8 +17140,21 @@ async function pollLoop() {
17419
17140
  }
17420
17141
  }
17421
17142
  const messageId = String(msg.message_id);
17422
- void setMessageReaction(chatId, messageId, ACK_EMOJI);
17423
- trackPendingMessage(chatId, messageId, msg.chat.type);
17143
+ const ackProbe = process.env.TMUX && AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? probeAgentSessionCached(AGENT_CODE_NAME) : { tmux: "unknown", claude: "unknown" };
17144
+ const ackDecision = decideAckReaction({
17145
+ hasTarget: Boolean(chatId && messageId),
17146
+ integrationReady: Boolean(BOT_TOKEN),
17147
+ tmux: ackProbe.tmux,
17148
+ claude: ackProbe.claude,
17149
+ withinStartupGrace: process.uptime() * 1e3 < ACK_STARTUP_GRACE_MS,
17150
+ oldestPendingAgeMs: oldestPendingMarkerAgeMs(PENDING_INBOUND_DIR)
17151
+ });
17152
+ if (ackDecision === "ack") {
17153
+ void setMessageReaction(chatId, messageId, ACK_EMOJI);
17154
+ } else if (ackDecision === "undeliverable") {
17155
+ void notifyUndeliverable(chatId, messageId);
17156
+ }
17157
+ trackPendingMessage(chatId, messageId, msg.chat.type, ackDecision === "undeliverable");
17424
17158
  const fileMeta = [];
17425
17159
  for (const attachment of classifiedAttachments) {
17426
17160
  if (attachment.kind === "image") {
@@ -17546,3 +17280,7 @@ pollLoop().catch((err) => {
17546
17280
  );
17547
17281
  process.exit(1);
17548
17282
  });
17283
+ export {
17284
+ __resetBackOnlineNoticeThrottle,
17285
+ __resetUndeliverableNoticeThrottle
17286
+ };