@integrity-labs/agt-cli 0.21.8 → 0.22.0

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.
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
8
11
  var __commonJS = (cb, mod) => function __require() {
9
12
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
13
  };
@@ -6798,6 +6801,433 @@ var require_dist = __commonJS({
6798
6801
  }
6799
6802
  });
6800
6803
 
6804
+ // src/ask-user-runtime.ts
6805
+ var ask_user_runtime_exports = {};
6806
+ __export(ask_user_runtime_exports, {
6807
+ DEFAULT_ASK_USER_LIMITS: () => DEFAULT_ASK_USER_LIMITS,
6808
+ apiCall: () => apiCall,
6809
+ createPendingInteraction: () => createPendingInteraction,
6810
+ generateOptionToken: () => generateOptionToken,
6811
+ updatePendingInteractionMessageTs: () => updatePendingInteractionMessageTs,
6812
+ validateAskUserOptions: () => validateAskUserOptions,
6813
+ waitForResolution: () => waitForResolution
6814
+ });
6815
+ async function getAuthToken(cfg) {
6816
+ if (auth.token && Date.now() < auth.expiresAtEpochMs) return auth.token;
6817
+ const res = await fetch(`${cfg.apiHost}/host/exchange`, {
6818
+ method: "POST",
6819
+ headers: { "Content-Type": "application/json" },
6820
+ body: JSON.stringify({ host_key: cfg.apiKey }),
6821
+ signal: AbortSignal.timeout(API_FETCH_TIMEOUT_MS)
6822
+ });
6823
+ if (!res.ok) {
6824
+ const body = await res.text().catch(() => "");
6825
+ throw new Error(`AGT auth exchange failed (${res.status}): ${body.slice(0, 200)}`);
6826
+ }
6827
+ const data = await res.json();
6828
+ auth.token = data.token;
6829
+ auth.expiresAtEpochMs = data.expires_at ? new Date(data.expires_at).getTime() - 12e4 : Date.now() + 55 * 6e4;
6830
+ return auth.token;
6831
+ }
6832
+ async function apiCall(cfg, method, path, body) {
6833
+ const token = await getAuthToken(cfg);
6834
+ const buildInit = (authToken) => ({
6835
+ method,
6836
+ headers: {
6837
+ "Content-Type": "application/json",
6838
+ Authorization: `Bearer ${authToken}`
6839
+ },
6840
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {},
6841
+ signal: AbortSignal.timeout(API_FETCH_TIMEOUT_MS)
6842
+ });
6843
+ const res = await fetch(`${cfg.apiHost}${path}`, buildInit(token));
6844
+ if (res.status !== 401) return res;
6845
+ auth.token = null;
6846
+ auth.expiresAtEpochMs = 0;
6847
+ const fresh = await getAuthToken(cfg);
6848
+ return fetch(`${cfg.apiHost}${path}`, buildInit(fresh));
6849
+ }
6850
+ async function createPendingInteraction(cfg, input) {
6851
+ const res = await apiCall(cfg, "POST", "/host/pending-interactions", {
6852
+ agent_id: cfg.agentId,
6853
+ callback_id: input.callbackId,
6854
+ channel_id: input.channelId,
6855
+ message_ts: input.messageTs ?? "",
6856
+ thread_ts: input.threadTs,
6857
+ options: input.options,
6858
+ expires_at: input.expiresAt.toISOString(),
6859
+ kind: input.kind,
6860
+ kind_data: input.kindData
6861
+ });
6862
+ if (!res.ok) {
6863
+ const body = await res.text().catch(() => "");
6864
+ throw new Error(`createPendingInteraction failed (${res.status}): ${body.slice(0, 200)}`);
6865
+ }
6866
+ }
6867
+ async function updatePendingInteractionMessageTs(cfg, callbackId, messageTs) {
6868
+ const res = await apiCall(
6869
+ cfg,
6870
+ "PATCH",
6871
+ `/host/pending-interactions/${encodeURIComponent(callbackId)}`,
6872
+ { agent_id: cfg.agentId, message_ts: messageTs }
6873
+ );
6874
+ if (!res.ok) {
6875
+ const body = await res.text().catch(() => "");
6876
+ throw new Error(
6877
+ `updatePendingInteractionMessageTs failed (${res.status}): ${body.slice(0, 200)}`
6878
+ );
6879
+ }
6880
+ }
6881
+ async function waitForResolution(cfg, callbackId, opts) {
6882
+ const interval = opts.pollIntervalMs ?? 500;
6883
+ const deadlineMs = opts.deadline.getTime();
6884
+ let firstPass = true;
6885
+ while (firstPass || Date.now() < deadlineMs) {
6886
+ firstPass = false;
6887
+ try {
6888
+ const res = await apiCall(
6889
+ cfg,
6890
+ "GET",
6891
+ `/host/pending-interactions/${encodeURIComponent(callbackId)}?agent_id=${encodeURIComponent(cfg.agentId)}`
6892
+ );
6893
+ if (res.ok) {
6894
+ const row = await res.json();
6895
+ if (row.status === "resolved") {
6896
+ return {
6897
+ kind: "resolved",
6898
+ value: row.value ?? "",
6899
+ respondedByUser: row.responded_by_user,
6900
+ respondedAt: row.responded_at ?? (/* @__PURE__ */ new Date()).toISOString()
6901
+ };
6902
+ }
6903
+ if (row.status === "timeout") {
6904
+ return { kind: "timeout" };
6905
+ }
6906
+ }
6907
+ } catch {
6908
+ }
6909
+ await sleep(interval);
6910
+ }
6911
+ return { kind: "timeout" };
6912
+ }
6913
+ function sleep(ms) {
6914
+ return new Promise((r) => setTimeout(r, ms));
6915
+ }
6916
+ function generateOptionToken() {
6917
+ const bytes = new Uint8Array(12);
6918
+ const webCrypto = globalThis.crypto;
6919
+ if (webCrypto && typeof webCrypto.getRandomValues === "function") {
6920
+ webCrypto.getRandomValues(bytes);
6921
+ } else {
6922
+ throw new Error(
6923
+ "generateOptionToken: no crypto.getRandomValues implementation available. Need Node 20+ or a Web Crypto polyfill."
6924
+ );
6925
+ }
6926
+ const out = Buffer.from(bytes).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
6927
+ return out;
6928
+ }
6929
+ function validateAskUserOptions(options, limits = DEFAULT_ASK_USER_LIMITS) {
6930
+ const errors = [];
6931
+ if (!Array.isArray(options)) {
6932
+ return { ok: false, errors: [{ path: "options", message: "must be an array" }] };
6933
+ }
6934
+ if (options.length === 0) {
6935
+ errors.push({ path: "options", message: "must contain at least one option" });
6936
+ }
6937
+ if (options.length > limits.maxOptions) {
6938
+ errors.push({
6939
+ path: "options",
6940
+ message: `max ${limits.maxOptions} options`
6941
+ });
6942
+ }
6943
+ const seenLabels = /* @__PURE__ */ new Set();
6944
+ const seenValues = /* @__PURE__ */ new Set();
6945
+ options.forEach((opt, i) => {
6946
+ const o = opt;
6947
+ const path = `options[${i}]`;
6948
+ if (typeof o?.label !== "string" || o.label.length === 0) {
6949
+ errors.push({ path: `${path}.label`, message: "required string" });
6950
+ } else {
6951
+ if (o.label.length > limits.maxLabelChars) {
6952
+ errors.push({ path: `${path}.label`, message: `max ${limits.maxLabelChars} chars` });
6953
+ }
6954
+ if (seenLabels.has(o.label)) errors.push({ path: `${path}.label`, message: "duplicate label" });
6955
+ seenLabels.add(o.label);
6956
+ }
6957
+ if (typeof o?.value !== "string" || o.value.length === 0) {
6958
+ errors.push({ path: `${path}.value`, message: "required string" });
6959
+ } else {
6960
+ if (o.value.length > limits.maxValueChars) {
6961
+ errors.push({ path: `${path}.value`, message: `max ${limits.maxValueChars} chars` });
6962
+ }
6963
+ if (seenValues.has(o.value)) errors.push({ path: `${path}.value`, message: "duplicate value" });
6964
+ seenValues.add(o.value);
6965
+ }
6966
+ });
6967
+ return { ok: errors.length === 0, errors };
6968
+ }
6969
+ var auth, API_FETCH_TIMEOUT_MS, DEFAULT_ASK_USER_LIMITS;
6970
+ var init_ask_user_runtime = __esm({
6971
+ "src/ask-user-runtime.ts"() {
6972
+ "use strict";
6973
+ auth = { token: null, expiresAtEpochMs: 0 };
6974
+ API_FETCH_TIMEOUT_MS = 1e4;
6975
+ DEFAULT_ASK_USER_LIMITS = {
6976
+ maxOptions: 5,
6977
+ maxLabelChars: 75,
6978
+ maxValueChars: 2e3
6979
+ };
6980
+ }
6981
+ });
6982
+
6983
+ // src/slack-block-kit-runtime.ts
6984
+ var slack_block_kit_runtime_exports = {};
6985
+ __export(slack_block_kit_runtime_exports, {
6986
+ DEFAULT_ASK_USER_LIMITS: () => DEFAULT_ASK_USER_LIMITS,
6987
+ apiCall: () => apiCall,
6988
+ buildAskUserBlocks: () => buildAskUserBlocks,
6989
+ buildRatingActionsBlock: () => buildRatingActionsBlock,
6990
+ createPendingInteraction: () => createPendingInteraction,
6991
+ decodeActionId: () => decodeActionId,
6992
+ defaultRatingLabels: () => defaultRatingLabels,
6993
+ encodeActionId: () => encodeActionId,
6994
+ generateOptionToken: () => generateOptionToken,
6995
+ ratingValuesForScale: () => ratingValuesForScale,
6996
+ recordSlackDelivery: () => recordSlackDelivery,
6997
+ resolveInteractive: () => resolveInteractive,
6998
+ updatePendingInteractionMessageTs: () => updatePendingInteractionMessageTs,
6999
+ validateAskUserOptions: () => validateAskUserOptions,
7000
+ validateSlackBlocks: () => validateSlackBlocks,
7001
+ waitForResolution: () => waitForResolution
7002
+ });
7003
+ function validateSlackBlocks(blocks) {
7004
+ const errors = [];
7005
+ const cap = 10;
7006
+ const push = (path, message) => {
7007
+ if (errors.length < cap) errors.push({ path, message });
7008
+ };
7009
+ if (!Array.isArray(blocks)) {
7010
+ return { ok: false, errors: [{ path: "blocks", message: "must be an array" }] };
7011
+ }
7012
+ if (blocks.length === 0) {
7013
+ return { ok: false, errors: [{ path: "blocks", message: "must contain at least one block" }] };
7014
+ }
7015
+ if (blocks.length > SLACK_LIMITS.blocksPerMessage) {
7016
+ push("blocks", `max ${SLACK_LIMITS.blocksPerMessage} blocks per message (got ${blocks.length})`);
7017
+ }
7018
+ for (let i = 0; i < blocks.length; i++) {
7019
+ const block = blocks[i];
7020
+ const path = `blocks[${i}]`;
7021
+ const type = block?.type;
7022
+ if (typeof type !== "string") {
7023
+ push(path, "`type` is required and must be a string");
7024
+ continue;
7025
+ }
7026
+ if (!SUPPORTED_BLOCK_TYPES.includes(type)) {
7027
+ push(path, `unsupported block type "${type}" \u2014 supported: ${SUPPORTED_BLOCK_TYPES.join(", ")}`);
7028
+ continue;
7029
+ }
7030
+ if ("block_id" in block && typeof block.block_id === "string" && block.block_id.length > SLACK_LIMITS.identifierChars) {
7031
+ push(`${path}.block_id`, `max ${SLACK_LIMITS.identifierChars} chars`);
7032
+ }
7033
+ if (type === "header") validateHeaderBlock(block, path, push);
7034
+ else if (type === "section") validateSectionBlock(block, path, push);
7035
+ else if (type === "context") validateContextBlock(block, path, push);
7036
+ else if (type === "actions") validateActionsBlock(block, path, push);
7037
+ }
7038
+ return { ok: errors.length === 0, errors };
7039
+ }
7040
+ function validateHeaderBlock(b, path, push) {
7041
+ const text = b.text;
7042
+ if (!text) {
7043
+ push(`${path}.text`, "header requires a text object");
7044
+ return;
7045
+ }
7046
+ validateTextObject(text, `${path}.text`, SLACK_LIMITS.headerTextChars, ["plain_text"], push);
7047
+ }
7048
+ function validateSectionBlock(b, path, push) {
7049
+ const text = b.text;
7050
+ const fields = b.fields;
7051
+ if (!text && !fields) {
7052
+ push(path, "section must have at least one of `text` or `fields`");
7053
+ return;
7054
+ }
7055
+ if (text) {
7056
+ validateTextObject(text, `${path}.text`, SLACK_LIMITS.sectionTextChars, ["plain_text", "mrkdwn"], push);
7057
+ }
7058
+ if (fields) {
7059
+ if (!Array.isArray(fields)) {
7060
+ push(`${path}.fields`, "`fields` must be an array");
7061
+ } else if (fields.length > SLACK_LIMITS.sectionFields) {
7062
+ push(`${path}.fields`, `max ${SLACK_LIMITS.sectionFields} fields`);
7063
+ } else {
7064
+ fields.forEach((f, i) => {
7065
+ validateTextObject(f, `${path}.fields[${i}]`, SLACK_LIMITS.sectionFieldChars, ["plain_text", "mrkdwn"], push);
7066
+ });
7067
+ }
7068
+ }
7069
+ }
7070
+ function validateContextBlock(b, path, push) {
7071
+ const elements = b.elements;
7072
+ if (!Array.isArray(elements) || elements.length === 0) {
7073
+ push(`${path}.elements`, "context requires non-empty `elements` array");
7074
+ return;
7075
+ }
7076
+ elements.forEach((el, i) => {
7077
+ validateTextObject(el, `${path}.elements[${i}]`, SLACK_LIMITS.sectionTextChars, ["plain_text", "mrkdwn"], push);
7078
+ });
7079
+ }
7080
+ function validateActionsBlock(b, path, push) {
7081
+ const elements = b.elements;
7082
+ if (!Array.isArray(elements) || elements.length === 0) {
7083
+ push(`${path}.elements`, "actions requires non-empty `elements` array");
7084
+ return;
7085
+ }
7086
+ if (elements.length > SLACK_LIMITS.actionElements) {
7087
+ push(`${path}.elements`, `max ${SLACK_LIMITS.actionElements} elements`);
7088
+ }
7089
+ elements.forEach((el, i) => {
7090
+ const type = el?.type;
7091
+ if (typeof type !== "string") {
7092
+ push(`${path}.elements[${i}]`, "each element must have a `type`");
7093
+ return;
7094
+ }
7095
+ if (!SUPPORTED_ACTION_ELEMENTS.includes(type)) {
7096
+ push(`${path}.elements[${i}]`, `unsupported action element "${type}" \u2014 supported: ${SUPPORTED_ACTION_ELEMENTS.join(", ")}`);
7097
+ return;
7098
+ }
7099
+ if (type === "button") {
7100
+ const text = el.text;
7101
+ if (!text) {
7102
+ push(`${path}.elements[${i}].text`, "button requires a text object");
7103
+ } else {
7104
+ validateTextObject(text, `${path}.elements[${i}].text`, SLACK_LIMITS.buttonLabelChars, ["plain_text"], push);
7105
+ }
7106
+ const actionId = el.action_id;
7107
+ if (typeof actionId !== "string" || actionId.length === 0) {
7108
+ push(`${path}.elements[${i}].action_id`, "button requires a non-empty `action_id`");
7109
+ } else if (actionId.length > SLACK_LIMITS.identifierChars) {
7110
+ push(`${path}.elements[${i}].action_id`, `max ${SLACK_LIMITS.identifierChars} chars`);
7111
+ }
7112
+ const value = el.value;
7113
+ if (value !== void 0) {
7114
+ if (typeof value !== "string") {
7115
+ push(`${path}.elements[${i}].value`, "`value` must be a string");
7116
+ } else if (value.length > SLACK_LIMITS.optionValueChars) {
7117
+ push(`${path}.elements[${i}].value`, `max ${SLACK_LIMITS.optionValueChars} chars`);
7118
+ }
7119
+ }
7120
+ }
7121
+ });
7122
+ }
7123
+ function validateTextObject(text, path, maxChars, allowedTypes, push) {
7124
+ if (!text || typeof text !== "object") {
7125
+ push(path, "must be an object with `type` and `text`");
7126
+ return;
7127
+ }
7128
+ const t = text;
7129
+ if (typeof t.type !== "string" || !allowedTypes.includes(t.type)) {
7130
+ push(`${path}.type`, `must be one of: ${allowedTypes.join(", ")}`);
7131
+ }
7132
+ if (typeof t.text !== "string" || t.text.length === 0) {
7133
+ push(`${path}.text`, "required non-empty string");
7134
+ } else if (t.text.length > maxChars) {
7135
+ push(`${path}.text`, `max ${maxChars} chars`);
7136
+ }
7137
+ }
7138
+ function encodeActionId(callbackId, token) {
7139
+ return `aug:${callbackId}:${token}`;
7140
+ }
7141
+ function decodeActionId(actionId) {
7142
+ const m = ACTION_ID_RE.exec(actionId);
7143
+ if (!m) return null;
7144
+ return { callbackId: m[1], token: m[2] };
7145
+ }
7146
+ function buildAskUserBlocks(opts) {
7147
+ return [
7148
+ {
7149
+ type: "section",
7150
+ text: { type: "mrkdwn", text: opts.question }
7151
+ },
7152
+ {
7153
+ type: "actions",
7154
+ elements: opts.options.map(({ label, token }) => ({
7155
+ type: "button",
7156
+ text: { type: "plain_text", text: label },
7157
+ action_id: encodeActionId(opts.callbackId, token)
7158
+ }))
7159
+ }
7160
+ ];
7161
+ }
7162
+ function ratingValuesForScale(scale) {
7163
+ return scale === "1-5" ? [1, 2, 3, 4, 5] : [-1, 0, 1];
7164
+ }
7165
+ function defaultRatingLabels(scale) {
7166
+ return scale === "1-5" ? ["1", "2", "3", "4", "5"] : ["\u{1F44E}", "\u{1F914}", "\u{1F44D}"];
7167
+ }
7168
+ function buildRatingActionsBlock(opts) {
7169
+ return {
7170
+ type: "actions",
7171
+ elements: opts.options.map(({ label, token }) => ({
7172
+ type: "button",
7173
+ text: { type: "plain_text", text: label },
7174
+ action_id: encodeActionId(opts.callbackId, token)
7175
+ }))
7176
+ };
7177
+ }
7178
+ async function resolveInteractive(cfg, input) {
7179
+ const res = await apiCall(cfg, "POST", "/host/interactive/resolve", {
7180
+ agent_id: cfg.agentId,
7181
+ callback_id: input.callbackId,
7182
+ token: input.token,
7183
+ responded_by_user: input.respondedByUser,
7184
+ response_url: input.responseUrl,
7185
+ original_blocks: input.originalBlocks
7186
+ });
7187
+ if (!res.ok) {
7188
+ const body = await res.text().catch(() => "");
7189
+ throw new Error(`resolveInteractive failed (${res.status}): ${body.slice(0, 200)}`);
7190
+ }
7191
+ }
7192
+ async function recordSlackDelivery(cfg, input) {
7193
+ const res = await apiCall(
7194
+ cfg,
7195
+ "POST",
7196
+ `/host/agents/${encodeURIComponent(cfg.agentId)}/slack-delivery`,
7197
+ {
7198
+ channel: input.channel,
7199
+ message_ts: input.messageTs,
7200
+ thread_ts: input.threadTs
7201
+ }
7202
+ );
7203
+ if (!res.ok) {
7204
+ const body = await res.text().catch(() => "");
7205
+ throw new Error(`recordSlackDelivery failed (${res.status}): ${body.slice(0, 200)}`);
7206
+ }
7207
+ }
7208
+ var SLACK_LIMITS, SUPPORTED_BLOCK_TYPES, SUPPORTED_ACTION_ELEMENTS, ACTION_ID_RE;
7209
+ var init_slack_block_kit_runtime = __esm({
7210
+ "src/slack-block-kit-runtime.ts"() {
7211
+ "use strict";
7212
+ init_ask_user_runtime();
7213
+ init_ask_user_runtime();
7214
+ SLACK_LIMITS = {
7215
+ blocksPerMessage: 50,
7216
+ sectionTextChars: 3e3,
7217
+ sectionFields: 10,
7218
+ sectionFieldChars: 2e3,
7219
+ actionElements: 5,
7220
+ buttonLabelChars: 75,
7221
+ identifierChars: 255,
7222
+ optionValueChars: 2e3,
7223
+ headerTextChars: 150
7224
+ };
7225
+ SUPPORTED_BLOCK_TYPES = ["header", "section", "divider", "context", "actions"];
7226
+ SUPPORTED_ACTION_ELEMENTS = ["button"];
7227
+ ACTION_ID_RE = /^aug:([0-9a-f-]{36}):([A-Za-z0-9_-]{1,200})$/;
7228
+ }
7229
+ });
7230
+
6801
7231
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/core.js
6802
7232
  var NEVER = Object.freeze({
6803
7233
  status: "aborted"
@@ -15276,6 +15706,7 @@ var BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
15276
15706
  var AGENT_CODE_NAME = process.env.AGT_AGENT_CODE_NAME ?? "unknown";
15277
15707
  var AGT_HOST = process.env.AGT_HOST ?? null;
15278
15708
  var AGT_API_KEY = process.env.AGT_API_KEY ?? null;
15709
+ var AGT_AGENT_ID = process.env.AGT_AGENT_ID ?? null;
15279
15710
  var ALLOWED_CHATS = new Set(
15280
15711
  (process.env.TELEGRAM_ALLOWED_CHATS ?? "").split(",").map((s) => s.trim()).filter(Boolean)
15281
15712
  );
@@ -16081,7 +16512,7 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
16081
16512
  ...progressRegistry.getDefinitions(),
16082
16513
  {
16083
16514
  name: "channel_request_input",
16084
- description: "Generic Yes/No or multi-choice picker. NOT YET IMPLEMENTED for Telegram \u2014 call returns NotImplemented. Sibling follow-up to ENG-5392 will land the Telegram inline-keyboard renderer (parallel to the existing ENG-5021 ask_user work). Today the tool exists only so agents register a stable, channel-agnostic signature in their prompt and degrade gracefully.",
16515
+ 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.",
16085
16516
  inputSchema: {
16086
16517
  type: "object",
16087
16518
  properties: {
@@ -16197,15 +16628,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
16197
16628
  const progressResult = await progressRegistry.handle(name, args ?? {});
16198
16629
  if (progressResult !== void 0) return progressResult;
16199
16630
  if (name === "channel_request_input") {
16200
- return {
16201
- content: [
16202
- {
16203
- type: "text",
16204
- text: "NotImplemented: channel_request_input is not yet available on Telegram. Sibling follow-up to ENG-5392 will add the Telegram inline-keyboard renderer. For now, send a freeform telegram.reply asking the question and parse the reply text."
16205
- }
16206
- ],
16207
- isError: true
16208
- };
16631
+ return handleChannelRequestInput(args ?? {});
16209
16632
  }
16210
16633
  if (name === "telegram.reply" || name === "telegram.send_message") {
16211
16634
  const { chat_id, text, reply_to_message_id } = args;
@@ -16400,6 +16823,228 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
16400
16823
  }
16401
16824
  throw new Error(`Unknown tool: ${name}`);
16402
16825
  });
16826
+ function channelRequestInputAvailable() {
16827
+ return Boolean(AGT_HOST && AGT_API_KEY && AGT_AGENT_ID);
16828
+ }
16829
+ function errResult2(text) {
16830
+ return { content: [{ type: "text", text }], isError: true };
16831
+ }
16832
+ async function handleChannelRequestInput(args) {
16833
+ if (!channelRequestInputAvailable()) {
16834
+ return errResult2(
16835
+ "channel_request_input is disabled \u2014 missing AGT_HOST / AGT_API_KEY / AGT_AGENT_ID env wiring."
16836
+ );
16837
+ }
16838
+ const runtime = await Promise.resolve().then(() => (init_ask_user_runtime(), ask_user_runtime_exports));
16839
+ const { apiCall: apiCall2, waitForResolution: waitForResolution2, validateAskUserOptions: validateAskUserOptions2 } = runtime;
16840
+ const { thread_id, prompt_text, options, schema, request_id, timeout_seconds } = args;
16841
+ if (typeof thread_id !== "string" || !thread_id) {
16842
+ return errResult2(
16843
+ "thread_id is required (Telegram shape: `<chat_id>` or `<chat_id>:<reply_to_message_id>`)"
16844
+ );
16845
+ }
16846
+ if (typeof prompt_text !== "string" || !prompt_text) {
16847
+ return errResult2("prompt_text is required");
16848
+ }
16849
+ const optsValidation = validateAskUserOptions2(options, {
16850
+ maxOptions: 4,
16851
+ maxLabelChars: 75,
16852
+ maxValueChars: 2e3
16853
+ });
16854
+ if (!optsValidation.ok) {
16855
+ return errResult2(
16856
+ `Invalid options: ${optsValidation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`
16857
+ );
16858
+ }
16859
+ const optionsArr = options;
16860
+ if (optionsArr.length < 2) {
16861
+ return errResult2(
16862
+ "options must contain at least 2 entries (this is a picker, not a notification)"
16863
+ );
16864
+ }
16865
+ if (!schema || typeof schema.type !== "string") {
16866
+ return errResult2("schema is required with shape { type: 'yes_no' | 'choice' }");
16867
+ }
16868
+ if (schema.type !== "yes_no" && schema.type !== "choice") {
16869
+ return errResult2("schema.type must be 'yes_no' or 'choice'");
16870
+ }
16871
+ if (schema.type === "yes_no" && optionsArr.length !== 2) {
16872
+ return errResult2("schema.type='yes_no' requires exactly 2 options");
16873
+ }
16874
+ if (request_id !== void 0 && (typeof request_id !== "string" || !request_id)) {
16875
+ return errResult2("request_id must be a non-empty string when provided");
16876
+ }
16877
+ const requestedTimeout = typeof timeout_seconds === "number" ? timeout_seconds : 300;
16878
+ const timeoutSec = Math.max(10, Math.min(3600, requestedTimeout));
16879
+ const cfg = {
16880
+ apiHost: AGT_HOST,
16881
+ apiKey: AGT_API_KEY,
16882
+ agentId: AGT_AGENT_ID
16883
+ };
16884
+ let dispatchPayload;
16885
+ try {
16886
+ const res = await apiCall2(cfg, "POST", "/host/channel/request-input", {
16887
+ agent_id: cfg.agentId,
16888
+ thread_id,
16889
+ prompt_text,
16890
+ options: optionsArr,
16891
+ schema,
16892
+ ...request_id ? { request_id } : {}
16893
+ });
16894
+ dispatchPayload = await res.json().catch(() => ({}));
16895
+ if (!res.ok || !dispatchPayload.ok || !dispatchPayload.callback_id) {
16896
+ const reason = dispatchPayload.reason ?? `http_${res.status}`;
16897
+ return errResult2(`channel_request_input dispatch failed: ${reason}`);
16898
+ }
16899
+ } catch (err) {
16900
+ return errResult2(
16901
+ `channel_request_input dispatch failed: ${err.message}`
16902
+ );
16903
+ }
16904
+ const callbackId = dispatchPayload.callback_id;
16905
+ const deadline = new Date(Date.now() + timeoutSec * 1e3);
16906
+ const resolution = await waitForResolution2(cfg, callbackId, { deadline });
16907
+ if (resolution.kind === "timeout") {
16908
+ return {
16909
+ content: [
16910
+ {
16911
+ type: "text",
16912
+ text: JSON.stringify({
16913
+ value: null,
16914
+ clicked_by_user: null,
16915
+ timed_out: true,
16916
+ responded_at: null,
16917
+ callback_id: callbackId
16918
+ })
16919
+ }
16920
+ ]
16921
+ };
16922
+ }
16923
+ return {
16924
+ content: [
16925
+ {
16926
+ type: "text",
16927
+ text: JSON.stringify({
16928
+ value: resolution.value,
16929
+ clicked_by_user: resolution.respondedByUser,
16930
+ timed_out: false,
16931
+ responded_at: resolution.respondedAt,
16932
+ callback_id: callbackId
16933
+ })
16934
+ }
16935
+ ]
16936
+ };
16937
+ }
16938
+ async function handleCallbackQuery(cb) {
16939
+ if (!channelRequestInputAvailable()) {
16940
+ await ackCallbackQuery(cb.id, "Not configured").catch(() => {
16941
+ });
16942
+ process.stderr.write(
16943
+ `telegram-channel(${AGENT_CODE_NAME}): callback_query received but host wiring missing \u2014 dropping
16944
+ `
16945
+ );
16946
+ return;
16947
+ }
16948
+ if (typeof cb.data !== "string" || !cb.data) {
16949
+ await ackCallbackQuery(cb.id).catch(() => {
16950
+ });
16951
+ return;
16952
+ }
16953
+ const runtime = await Promise.resolve().then(() => (init_slack_block_kit_runtime(), slack_block_kit_runtime_exports));
16954
+ const decoded = runtime.decodeActionId(cb.data);
16955
+ if (!decoded) {
16956
+ await ackCallbackQuery(cb.id).catch(() => {
16957
+ });
16958
+ return;
16959
+ }
16960
+ await ackCallbackQuery(cb.id, "Got it").catch(() => {
16961
+ });
16962
+ const apiCallRuntime = await Promise.resolve().then(() => (init_ask_user_runtime(), ask_user_runtime_exports));
16963
+ const cfg = {
16964
+ apiHost: AGT_HOST,
16965
+ apiKey: AGT_API_KEY,
16966
+ agentId: AGT_AGENT_ID
16967
+ };
16968
+ let resolveResult;
16969
+ try {
16970
+ const res = await apiCallRuntime.apiCall(cfg, "POST", "/host/interactive/resolve", {
16971
+ agent_id: cfg.agentId,
16972
+ callback_id: decoded.callbackId,
16973
+ token: decoded.token,
16974
+ responded_by_user: String(cb.from.id)
16975
+ });
16976
+ resolveResult = await res.json().catch(() => ({}));
16977
+ if (!res.ok || !resolveResult.ok) {
16978
+ process.stderr.write(
16979
+ `telegram-channel(${AGENT_CODE_NAME}): /host/interactive/resolve returned non-ok (callback_id=${decoded.callbackId}, http=${res.status}, reason=${resolveResult.reason ?? "unknown"})
16980
+ `
16981
+ );
16982
+ await ackCallbackQuery(cb.id, "Could not record your choice").catch(() => {
16983
+ });
16984
+ return;
16985
+ }
16986
+ } catch (err) {
16987
+ process.stderr.write(
16988
+ `telegram-channel(${AGENT_CODE_NAME}): /host/interactive/resolve threw (callback_id=${decoded.callbackId}): ${err.message}
16989
+ `
16990
+ );
16991
+ await ackCallbackQuery(cb.id, "Could not record your choice").catch(() => {
16992
+ });
16993
+ return;
16994
+ }
16995
+ const confirmationLabel = "\u2713";
16996
+ if (cb.message) {
16997
+ try {
16998
+ const resp = await telegramApiCall(
16999
+ "editMessageReplyMarkup",
17000
+ {
17001
+ chat_id: cb.message.chat.id,
17002
+ message_id: cb.message.message_id,
17003
+ // Empty inline_keyboard removes the buttons but leaves the
17004
+ // original prompt text intact. Less destructive than
17005
+ // editMessageText, and the prompt remains scrollable history.
17006
+ reply_markup: { inline_keyboard: [] }
17007
+ },
17008
+ 5e3
17009
+ );
17010
+ if (!resp.ok) {
17011
+ process.stderr.write(
17012
+ `telegram-channel(${AGENT_CODE_NAME}): editMessageReplyMarkup failed (callback_id=${decoded.callbackId}): ${resp.description ?? "unknown"}
17013
+ `
17014
+ );
17015
+ }
17016
+ } catch (err) {
17017
+ process.stderr.write(
17018
+ `telegram-channel(${AGENT_CODE_NAME}): editMessageReplyMarkup threw (callback_id=${decoded.callbackId}): ${err.message}
17019
+ `
17020
+ );
17021
+ }
17022
+ void confirmationLabel;
17023
+ }
17024
+ }
17025
+ async function ackCallbackQuery(callbackQueryId, text) {
17026
+ try {
17027
+ const resp = await telegramApiCall(
17028
+ "answerCallbackQuery",
17029
+ {
17030
+ callback_query_id: callbackQueryId,
17031
+ ...text ? { text } : {}
17032
+ },
17033
+ 5e3
17034
+ );
17035
+ if (!resp.ok) {
17036
+ process.stderr.write(
17037
+ `telegram-channel(${AGENT_CODE_NAME}): answerCallbackQuery rejected (id=${callbackQueryId}): ${resp.description ?? "unknown"}
17038
+ `
17039
+ );
17040
+ }
17041
+ } catch (err) {
17042
+ process.stderr.write(
17043
+ `telegram-channel(${AGENT_CODE_NAME}): answerCallbackQuery failed (id=${callbackQueryId}): ${err.message}
17044
+ `
17045
+ );
17046
+ }
17047
+ }
16403
17048
  await mcp.connect(new StdioServerTransport());
16404
17049
  var LONG_POLL_SECONDS = 50;
16405
17050
  var POLL_HTTP_TIMEOUT_MS = (LONG_POLL_SECONDS + 10) * 1e3;
@@ -16493,7 +17138,11 @@ async function pollLoop() {
16493
17138
  {
16494
17139
  offset: nextOffset,
16495
17140
  timeout: LONG_POLL_SECONDS,
16496
- allowed_updates: ["message"]
17141
+ // ENG-5398: `callback_query` carries inline-keyboard taps from
17142
+ // `channel_request_input`. Without it the buttons render fine
17143
+ // but Telegram silently drops the tap and the agent's polling
17144
+ // loop times out as if no-one ever pressed anything.
17145
+ allowed_updates: ["message", "callback_query"]
16497
17146
  },
16498
17147
  POLL_HTTP_TIMEOUT_MS
16499
17148
  );
@@ -16503,11 +17152,20 @@ async function pollLoop() {
16503
17152
  `telegram-channel(${AGENT_CODE_NAME}): getUpdates failed: ${resp.description ?? "unknown"}
16504
17153
  `
16505
17154
  );
16506
- await sleep(5e3);
17155
+ await sleep2(5e3);
16507
17156
  continue;
16508
17157
  }
16509
17158
  for (const update of resp.result ?? []) {
16510
17159
  nextOffset = Math.max(nextOffset, update.update_id + 1);
17160
+ if (update.callback_query) {
17161
+ await handleCallbackQuery(update.callback_query).catch((err) => {
17162
+ process.stderr.write(
17163
+ `telegram-channel(${AGENT_CODE_NAME}): callback_query handler crashed: ${err.message}
17164
+ `
17165
+ );
17166
+ });
17167
+ continue;
17168
+ }
16511
17169
  const msg = update.message;
16512
17170
  if (!msg) continue;
16513
17171
  const classifiedAttachments = classifyTelegramAttachments(msg);
@@ -16721,11 +17379,11 @@ async function pollLoop() {
16721
17379
  `telegram-channel(${AGENT_CODE_NAME}): poll error: ${err.message}
16722
17380
  `
16723
17381
  );
16724
- await sleep(5e3);
17382
+ await sleep2(5e3);
16725
17383
  }
16726
17384
  }
16727
17385
  }
16728
- function sleep(ms) {
17386
+ function sleep2(ms) {
16729
17387
  return new Promise((resolve2) => setTimeout(resolve2, ms).unref());
16730
17388
  }
16731
17389
  function shutdown(reason) {