@integrity-labs/agt-cli 0.28.974 → 0.28.976

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.
Files changed (25) hide show
  1. package/dist/bin/agt.js +5 -5
  2. package/dist/{chunk-3PYNSD4I.js → chunk-CIC25QXS.js} +14 -14
  3. package/dist/{chunk-3PYNSD4I.js.map → chunk-CIC25QXS.js.map} +1 -1
  4. package/dist/{chunk-PHPOHMSK.js → chunk-KTFN2AIB.js} +2 -2
  5. package/dist/{chunk-HRHDATT5.js → chunk-VF5BT7BP.js} +42 -21
  6. package/dist/{chunk-HRHDATT5.js.map → chunk-VF5BT7BP.js.map} +1 -1
  7. package/dist/{claude-pair-runtime-U6GBR7KM.js → claude-pair-runtime-XOIAF3ZU.js} +2 -2
  8. package/dist/lib/manager-worker.js +14 -14
  9. package/dist/lib/manager-worker.js.map +1 -1
  10. package/dist/mcp/direct-chat-channel.js +71 -24
  11. package/dist/mcp/index.js +38 -17
  12. package/dist/mcp/origami.js +34 -13
  13. package/dist/mcp/slack-channel.js +56 -35
  14. package/dist/mcp/teams-channel.js +5 -5
  15. package/dist/mcp/telegram-channel.js +49 -28
  16. package/dist/mcp/whatsapp-channel.js +9 -9
  17. package/dist/{persistent-session-YJJXZSUE.js → persistent-session-26W47NVS.js} +3 -3
  18. package/dist/{responsiveness-probe-SQZGSNVI.js → responsiveness-probe-KMHYGGYP.js} +3 -3
  19. package/dist/{session-auth-dead-7OLNDHJC.js → session-auth-dead-CRG2ESD2.js} +2 -2
  20. package/package.json +1 -1
  21. /package/dist/{chunk-PHPOHMSK.js.map → chunk-KTFN2AIB.js.map} +0 -0
  22. /package/dist/{claude-pair-runtime-U6GBR7KM.js.map → claude-pair-runtime-XOIAF3ZU.js.map} +0 -0
  23. /package/dist/{persistent-session-YJJXZSUE.js.map → persistent-session-26W47NVS.js.map} +0 -0
  24. /package/dist/{responsiveness-probe-SQZGSNVI.js.map → responsiveness-probe-KMHYGGYP.js.map} +0 -0
  25. /package/dist/{session-auth-dead-7OLNDHJC.js.map → session-auth-dead-CRG2ESD2.js.map} +0 -0
@@ -39177,11 +39177,18 @@ var FLAG_REGISTRY = [
39177
39177
  },
39178
39178
  {
39179
39179
  key: "ip-allowlist-enforce-admin-debug",
39180
- description: "ENG-10594 - whether the IP allowlist on the admin-debug surface (/admin/debug/*, including the deliberately unauthenticated POST /admin/debug/token that exchanges a tlk_ key) REFUSES, or merely observes. OFF (default) = shadow: every request is still evaluated and logged with verdict=admit or verdict=would-refuse, and nothing is ever rejected. ON = a caller whose platform-attested source address is outside AGT_IP_ALLOWLIST_ADMIN_DEBUG gets a bare 403. THE SSM PARAMETER IS A SECOND LEVER AND BOTH ARE NEEDED: with the list unset this flag does nothing, because an unconfigured allowlist admits everyone by design. Arm in that order - populate the list, read the shadow logs until the would-refuse set is understood, then flip this. Deliberately global (evaluated with an empty context): the middleware runs BEFORE authentication, so there is no verified org to scope by, and taking one from an unverified request would let a caller choose its own enforcement posture. Separate from the mcp flag on purpose - see that entry.",
39180
+ description: "ENG-10594 - whether the IP allowlist on the admin-debug surface (/admin/debug/*, including the deliberately unauthenticated POST /admin/debug/token that exchanges a tlk_ key) REFUSES, or merely observes. OFF (default) = shadow: every request is still evaluated and logged with verdict=admit or verdict=would-refuse, and nothing is ever rejected. ON = a caller whose platform-attested source address is outside AGT_IP_ALLOWLIST_ADMIN_DEBUG gets a bare 403. THE SSM PARAMETER IS A SECOND LEVER AND BOTH ARE NEEDED: with the list unset this flag does nothing, because an unconfigured allowlist admits everyone by design. Arm in that order - populate the list, read the shadow logs until the would-refuse set is understood, then flip this. Deliberately global (evaluated with an empty context): the middleware runs BEFORE authentication, so there is no verified org to scope by, and taking one from an unverified request would let a caller choose its own enforcement posture. There is deliberately NO counterpart flag for the per-agent MCP surface: that one is shadow-only structurally (its ENFORCE_FLAG entry is null), so arming this one does not and cannot arm that one. See the comment below this entry for why.",
39181
39181
  flagType: "boolean",
39182
- // Shadow. The measurement comes first: nobody yet knows the real set of
39183
- // staff addresses, and arming against a guess locks you out of the very
39184
- // tool you would use to diagnose the lockout.
39182
+ // Shadow. The measurement comes first - and ENG-10664 corrected what the
39183
+ // measurement is OF. This comment used to say "nobody yet knows the real set
39184
+ // of staff addresses": there is no such set. Measured over 48h, all 315
39185
+ // requests to this surface came from ONE address, the agt-hosts AZ-2a NAT
39186
+ // EIP - an AGENT calling the debug_* tools, not a person. The decided policy
39187
+ // is that it stays that way (admin-debug is only ever reached from our own
39188
+ // NAT gateways). So the pre-arm check is not "does my laptop appear" but
39189
+ // "is every NAT EIP in the list", because an AZ whose NAT has not yet
39190
+ // carried a debug_* call is absent from the log and would be refused the
39191
+ // instant this flips. See docs/runbooks/ip-allowlist-rollout.md step 3.
39185
39192
  defaultValue: false,
39186
39193
  // Arming it changes who can reach production diagnostics. Worth one
39187
39194
  // confirmation.
@@ -39196,20 +39203,34 @@ var FLAG_REGISTRY = [
39196
39203
  // ENFORCE_FLAG (packages/api/src/middleware/ip-allowlist.ts) is null, so no
39197
39204
  // flag is consulted and no flag flip can arm it.
39198
39205
  //
39199
- // Why it is not simply a second flag defaulting false: most of that surface's
39200
- // callers have no allowlistable address at all. Per ADR-0032 only hosts in the
39201
- // agt-hosts account egress through stable NAT EIPs; a host with
39202
- // provision_source `self` or `customer-ec2`, or any BYO account, egresses from
39203
- // an address the control plane cannot know. Arming against that population
39204
- // black-holes a customer's whole fleet, and per
39205
- // docs/runbooks/composio-allowlist-rollout.md this class of change has no
39206
+ // Why it is not simply a second flag defaulting false (CORRECTED, ENG-10664):
39207
+ //
39208
+ // This comment used to say the callers are agent HOSTS, most of which have no
39209
+ // allowlistable address per ADR-0032. That is the wrong caller. Agent hosts do
39210
+ // not call this surface at all: its handler's first act is
39211
+ // validateMcpAccessToken() against `mcp_oauth_tokens` (routes/mcp.ts:87, token
39212
+ // check at :91), so a host JWT is rejected outright - routes/host-runtime.ts
39213
+ // :33421 says so in as many words, which is why the ENG-4724 /host/* twins
39214
+ // exist. The caller set is remote MCP OAuth clients, structurally.
39215
+ //
39216
+ // The conclusion is unchanged and ONLY the reason has moved. Allowlisting this
39217
+ // surface would mean pinning a THIRD PARTY's egress ranges rather than our own
39218
+ // NAT EIPs - a different control, a different owner, a different failure mode:
39219
+ // they move a range and agent MCP access breaks with no signal on our side.
39220
+ // Per docs/runbooks/composio-allowlist-rollout.md this class of change has no
39206
39221
  // fleet-level undo. A flag would leave that one toggle away during an
39207
39222
  // incident, from someone who had just armed the admin-debug surface and
39208
39223
  // reasonably assumed the pair moved together. Requiring a code change puts
39209
39224
  // review in front of an irreversible action, which is the right price.
39210
39225
  //
39211
- // If the shadow data ever shows the population IS allowlistable, adding the
39212
- // flag here and a key in ENFORCE_FLAG is a small, deliberate diff.
39226
+ // Keeping the false rationale would have been worse than having none: a reader
39227
+ // who checked it would find it does not hold and might "fix" it by adding the
39228
+ // flag. That is the failure this rewrite exists to prevent.
39229
+ //
39230
+ // If we ever DECIDE to take on that third-party dependency deliberately,
39231
+ // adding the flag here and a key in ENFORCE_FLAG is a small diff - but it is a
39232
+ // decision to own someone else's egress, not a discovery that the population
39233
+ // was allowlistable all along.
39213
39234
  ];
39214
39235
  var REGISTRY_BY_KEY = new Map(FLAG_REGISTRY.map((definition) => [definition.key, definition]));
39215
39236
 
@@ -40436,7 +40457,7 @@ function formatThreadMessages(messages, nameById) {
40436
40457
  }
40437
40458
  function capThreadContext(formatted, maxChars) {
40438
40459
  if (formatted.length <= maxChars) return formatted;
40439
- const marker = "[\u2026earlier thread messages omitted \u2014 slack.read_thread for full history\u2026]\n";
40460
+ const marker = "[\u2026earlier thread messages omitted \u2014 slack_read_thread for full history\u2026]\n";
40440
40461
  if (maxChars <= marker.length) return marker.slice(0, maxChars);
40441
40462
  const tailBudget = maxChars - marker.length;
40442
40463
  const tail = formatted.slice(formatted.length - tailBudget);
@@ -43136,13 +43157,13 @@ function classifyByCoords(mode, args, registry2, now, unknownInboundId, knownThr
43136
43157
  function buildSlackMcpInstructions(opts) {
43137
43158
  const { ackReaction, skipReactionOn, skipReaction } = opts;
43138
43159
  return [
43139
- "CRITICAL: every response to a Slack <channel> tag MUST go through slack.reply - typed text never reaches the user. Slow work: interim ack (slack.reply interim:true), then your FINAL answer as a separate slack.reply (interim omitted). An ack is not the answer.",
43140
- "Each inbound tag carries inbound_id and message_ts, and sometimes thread_context. Reply with slack.reply passing inbound_id (copy it verbatim from the tag you are answering) + text; the server routes it to the right thread. Multiple <channel> tags = separate conversations: NEVER answer one user in another's inbound_id/thread. thread_context = pre-loaded thread; ground replies ONLY in it. For a self-initiated message (not a reply) set proactive:true.",
43141
- "Attachments: <channel> `files` is a JSON string - JSON.parse it. Entry with `path` (or a top-level `image_path`) is already downloaded - Read it; do NOT call slack.download_attachment. Only entries with `file_id` and no `path` (PDF/docx/csv) need slack.download_attachment (pass file_id + channel), then Read the returned path. Don't surface file-handling errors that don't affect the answer.",
43160
+ "CRITICAL: every response to a Slack <channel> tag MUST go through slack_reply - typed text never reaches the user. Slow work: interim ack (slack_reply interim:true), then your FINAL answer as a separate slack_reply (interim omitted). An ack is not the answer.",
43161
+ "Each inbound tag carries inbound_id and message_ts, and sometimes thread_context. Reply with slack_reply passing inbound_id (copy it verbatim from the tag you are answering) + text; the server routes it to the right thread. Multiple <channel> tags = separate conversations: NEVER answer one user in another's inbound_id/thread. thread_context = pre-loaded thread; ground replies ONLY in it. For a self-initiated message (not a reply) set proactive:true.",
43162
+ "Attachments: <channel> `files` is a JSON string - JSON.parse it. Entry with `path` (or a top-level `image_path`) is already downloaded - Read it; do NOT call slack_download_attachment. Only entries with `file_id` and no `path` (PDF/docx/csv) need slack_download_attachment (pass file_id + channel), then Read the returned path. Don't surface file-handling errors that don't affect the answer.",
43142
43163
  "Address users by user_name, never raw user ID. In multi-participant threads the CURRENT speaker is on the latest <channel> tag.",
43143
- `Reactions (slack.react sparingly - prefer a reply): :${ackReaction}: = ack (auto-added on inbound, don't duplicate); \u2705 = success.` + (skipReactionOn ? ` "${skipReaction}" = seen, not replying.` : "") + " NEVER react to signal failure of your work - on failure slack.reply one sentence explaining it (no stack traces/secrets). \u23F3 and \u274C are system-only; never add them.",
43144
- skipReactionOn ? `Decided NOT to reply to a DM or thread message (different @-mention, side chat, auto_followed catch-up, nothing useful)? slack.react with "${skipReaction}" so the sender knows you saw it - never post "this wasn't for me". Top-level channel messages you may skip silently.` : "A thread message NOT addressed to you (different @-mention, side chat, auto_followed catch-up): SILENTLY SKIP - no reaction, no reply.",
43145
- 'auto_followed="true" \u2192 reply only if useful (self-mentions auto-cleared server-side). own_bot_user_id=your Slack user ID. File: slack.upload_file path+channel+inbound_id.'
43164
+ `Reactions (slack_react sparingly - prefer a reply): :${ackReaction}: = ack (auto-added on inbound, don't duplicate); \u2705 = success.` + (skipReactionOn ? ` "${skipReaction}" = seen, not replying.` : "") + " NEVER react to signal failure of your work - on failure slack_reply one sentence explaining it (no stack traces/secrets). \u23F3 and \u274C are system-only; never add them.",
43165
+ skipReactionOn ? `Decided NOT to reply to a DM or thread message (different @-mention, side chat, auto_followed catch-up, nothing useful)? slack_react with "${skipReaction}" so the sender knows you saw it - never post "this wasn't for me". Top-level channel messages you may skip silently.` : "A thread message NOT addressed to you (different @-mention, side chat, auto_followed catch-up): SILENTLY SKIP - no reaction, no reply.",
43166
+ 'auto_followed="true" \u2192 reply only if useful (self-mentions auto-cleared server-side). own_bot_user_id=your Slack user ID. File: slack_upload_file path+channel+inbound_id.'
43146
43167
  ].join(" ");
43147
43168
  }
43148
43169
 
@@ -43150,7 +43171,7 @@ function buildSlackMcpInstructions(opts) {
43150
43171
  function buildSlackReactToolDescription(opts) {
43151
43172
  const { skipReactionOn, skipReaction } = opts;
43152
43173
  const skip = skipReactionOn ? skipReaction.trim() : "";
43153
- return "Add an emoji reaction to a Slack message. Use sparingly \u2014 prefer a text reply. Reaction taxonomy: \u2705 = action completed successfully. " + (skip ? `":${skip}:" = seen, deliberately NOT replying \u2014 use it on a DM or thread message you correctly chose not to answer (different @-mention, side chat, auto_followed catch-up, nothing useful to add). This one is not cosmetic: it CLOSES the pending-inbound marker, so the message stops being re-delivered to you and the reply-recovery net will not publish your end-of-turn text into a thread you stayed out of. Skipping silently leaves that marker open. (Top-level channel messages you may skip silently.) ` : "") + "\u{1F4DD} (:memo:) and \u26A0\uFE0F (:warning:) are blessed for explicit multi-state STATUS pipelines only \u2014 e.g. marking a run/parent \u2705 done / \u{1F4DD} waiting / \u26A0\uFE0F failed for at-a-glance status. That exception is narrow: never use a reaction to tell a user that YOUR reply failed \u2014 NEVER react to signal failure of your own work; on failure call slack.reply with one sentence explaining what went wrong instead (no stack traces, no secrets). \u{1F440} (eyes) is already auto-applied on inbound; do not duplicate. \u23F3 (:hourglass_flowing_sand:) is reserved for the system to mark an inbound that arrived while the agent couldn't reply \u2014 never add \u23F3 or \u274C yourself. (A \u{1F512} you may see on a thread is the server's reply-throttle marker, not a coerced reaction \u2014 slack.react passes your emoji straight through to Slack and never rewrites it to \u{1F512}.)";
43174
+ return "Add an emoji reaction to a Slack message. Use sparingly \u2014 prefer a text reply. Reaction taxonomy: \u2705 = action completed successfully. " + (skip ? `":${skip}:" = seen, deliberately NOT replying \u2014 use it on a DM or thread message you correctly chose not to answer (different @-mention, side chat, auto_followed catch-up, nothing useful to add). This one is not cosmetic: it CLOSES the pending-inbound marker, so the message stops being re-delivered to you and the reply-recovery net will not publish your end-of-turn text into a thread you stayed out of. Skipping silently leaves that marker open. (Top-level channel messages you may skip silently.) ` : "") + "\u{1F4DD} (:memo:) and \u26A0\uFE0F (:warning:) are blessed for explicit multi-state STATUS pipelines only \u2014 e.g. marking a run/parent \u2705 done / \u{1F4DD} waiting / \u26A0\uFE0F failed for at-a-glance status. That exception is narrow: never use a reaction to tell a user that YOUR reply failed \u2014 NEVER react to signal failure of your own work; on failure call slack_reply with one sentence explaining what went wrong instead (no stack traces, no secrets). \u{1F440} (eyes) is already auto-applied on inbound; do not duplicate. \u23F3 (:hourglass_flowing_sand:) is reserved for the system to mark an inbound that arrived while the agent couldn't reply \u2014 never add \u23F3 or \u274C yourself. (A \u{1F512} you may see on a thread is the server's reply-throttle marker, not a coerced reaction \u2014 slack_react passes your emoji straight through to Slack and never rewrites it to \u{1F512}.)";
43154
43175
  }
43155
43176
 
43156
43177
  // src/slack-routed-to-note.ts
@@ -47366,7 +47387,7 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
47366
47387
  // whether the agent still owes a substantive answer.
47367
47388
  interim: {
47368
47389
  type: "boolean",
47369
- description: 'Set true ONLY when this is an interim acknowledgement (e.g. "On it \u2014 checking now\u2026") and the real answer is still coming. The system keeps tracking this conversation as awaiting your final reply. Your eventual substantive answer MUST be a separate slack.reply with interim omitted/false \u2014 that is what marks the request complete. Omit (default false) for any reply that fully answers the user.'
47390
+ description: 'Set true ONLY when this is an interim acknowledgement (e.g. "On it \u2014 checking now\u2026") and the real answer is still coming. The system keeps tracking this conversation as awaiting your final reply. Your eventual substantive answer MUST be a separate slack_reply with interim omitted/false \u2014 that is what marks the request complete. Omit (default false) for any reply that fully answers the user.'
47370
47391
  },
47371
47392
  // WS2 (cross-thread reply routing): the preferred way to target a reply.
47372
47393
  inbound_id: {
@@ -47404,14 +47425,14 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
47404
47425
  properties: {
47405
47426
  channel: { type: "string", description: "Slack channel ID" },
47406
47427
  timestamp: { type: "string", description: "Message timestamp to react to" },
47407
- emoji: { type: "string", description: 'Emoji name without colons (e.g. "white_check_mark", "memo", "warning"). Do NOT use "x" (\u274C) or "hourglass_flowing_sand" (\u23F3) \u2014 those are system-only. To signal that your own reply failed, send a slack.reply explaining it rather than a reaction. See the tool description for the full taxonomy.' }
47428
+ emoji: { type: "string", description: 'Emoji name without colons (e.g. "white_check_mark", "memo", "warning"). Do NOT use "x" (\u274C) or "hourglass_flowing_sand" (\u23F3) \u2014 those are system-only. To signal that your own reply failed, send a slack_reply explaining it rather than a reaction. See the tool description for the full taxonomy.' }
47408
47429
  },
47409
47430
  required: ["channel", "timestamp", "emoji"]
47410
47431
  }
47411
47432
  },
47412
47433
  {
47413
47434
  name: "slack.update",
47414
- description: 'Edit the text of a Slack message YOU already sent (Slack chat.update). Pass the channel and the message\'s ts (slack.reply returns it as `(ts=\u2026)` on a successful send) plus the new text. You can only edit your OWN messages. This REPLACES the message text - any block layout on the original is cleared - so use it to correct a typo or update a message in place instead of posting a new one. NOTE: editing an interim "On it\u2026" into the final answer does NOT clear the pending-reply obligation - your final answer must still be a separate slack.reply (interim omitted). Common Slack errors: message_not_found (wrong ts/channel), cant_update_message (not your message), edit_window_closed.',
47435
+ description: 'Edit the text of a Slack message YOU already sent (Slack chat.update). Pass the channel and the message\'s ts (slack_reply returns it as `(ts=\u2026)` on a successful send) plus the new text. You can only edit your OWN messages. This REPLACES the message text - any block layout on the original is cleared - so use it to correct a typo or update a message in place instead of posting a new one. NOTE: editing an interim "On it\u2026" into the final answer does NOT clear the pending-reply obligation - your final answer must still be a separate slack_reply (interim omitted). Common Slack errors: message_not_found (wrong ts/channel), cant_update_message (not your message), edit_window_closed.',
47415
47436
  inputSchema: {
47416
47437
  type: "object",
47417
47438
  properties: {
@@ -47421,7 +47442,7 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
47421
47442
  },
47422
47443
  ts: {
47423
47444
  type: "string",
47424
- description: 'Timestamp of the message to edit - the `ts` value slack.reply returned when you sent it (e.g. "1783902864.258519"). Must be a message the bot itself sent.'
47445
+ description: 'Timestamp of the message to edit - the `ts` value slack_reply returned when you sent it (e.g. "1783902864.258519"). Must be a message the bot itself sent.'
47425
47446
  },
47426
47447
  text: { type: "string", description: "The new message text. Replaces the existing text." }
47427
47448
  },
@@ -47452,7 +47473,7 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
47452
47473
  },
47453
47474
  {
47454
47475
  name: "slack.read_history",
47455
- description: `Read recent top-level messages in a Slack channel the bot is a member of (via conversations.history). Use this to catch up on what was said in a channel \u2014 e.g. an auto-followed channel you have not been @-mentioned in, or to gather context before posting. This reads CHANNEL history; for one thread's replies use slack.read_thread instead. Optional oldest/latest bound the time window (Slack ts values). Returns messages oldest\u2192newest. The bot only sees channels it has been invited to \u2014 a "not_in_channel" error means someone must /invite the bot first.`,
47476
+ description: `Read recent top-level messages in a Slack channel the bot is a member of (via conversations.history). Use this to catch up on what was said in a channel \u2014 e.g. an auto-followed channel you have not been @-mentioned in, or to gather context before posting. This reads CHANNEL history; for one thread's replies use slack_read_thread instead. Optional oldest/latest bound the time window (Slack ts values). Returns messages oldest\u2192newest. The bot only sees channels it has been invited to \u2014 a "not_in_channel" error means someone must /invite the bot first.`,
47456
47477
  inputSchema: {
47457
47478
  type: "object",
47458
47479
  properties: {
@@ -47501,7 +47522,7 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
47501
47522
  ...blockKitToolsAvailable() ? [
47502
47523
  {
47503
47524
  name: "slack.send_structured",
47504
- description: "Send a Slack message built from Block Kit blocks (structured layout: headers, sections, dividers, context, action buttons). Use when plain text isn't enough \u2014 status reports, confirmations with multiple fields, calls-to-action with buttons. Pass a `text` fallback for push notifications. Returns { message_ts, permalink }. Does NOT block waiting for user interactions; for that use slack.ask_user.",
47525
+ description: "Send a Slack message built from Block Kit blocks (structured layout: headers, sections, dividers, context, action buttons). Use when plain text isn't enough \u2014 status reports, confirmations with multiple fields, calls-to-action with buttons. Pass a `text` fallback for push notifications. Returns { message_ts, permalink }. Does NOT block waiting for user interactions; for that use slack_ask_user.",
47505
47526
  inputSchema: {
47506
47527
  type: "object",
47507
47528
  properties: {
@@ -47596,7 +47617,7 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
47596
47617
  },
47597
47618
  {
47598
47619
  name: "slack.ask_user",
47599
- description: "Post a question to Slack with multiple-choice buttons and BLOCK until the user taps one (or the timeout fires). Returns the user's choice as { value, label, responded_by, timed_out, responded_at }. Use sparingly \u2014 this stops the agent for up to 5 minutes by default. Prefer plain slack.reply for asking questions where you don't need a structured answer back.",
47620
+ description: "Post a question to Slack with multiple-choice buttons and BLOCK until the user taps one (or the timeout fires). Returns the user's choice as { value, label, responded_by, timed_out, responded_at }. Use sparingly \u2014 this stops the agent for up to 5 minutes by default. Prefer plain slack_reply for asking questions where you don't need a structured answer back.",
47600
47621
  inputSchema: {
47601
47622
  type: "object",
47602
47623
  properties: {
@@ -47652,7 +47673,7 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
47652
47673
  },
47653
47674
  {
47654
47675
  name: "slack.channel_info",
47655
- description: 'Verify the bot can post to a specific Slack channel, OR resolve who is on the other side of a DM. Use this before relying on a channel ID for delivery (e.g. before submitting an AWS access request that will notify an approval channel) to avoid silent posting failures, and to confirm the human behind a DM channel id before claiming a message reached a named person. Works for public AND private channels (unlike slack.list_channels, which is public-only) and for DM (`D0...`) channels. Returns `{ ok, channel: { id, name, is_private, is_archived, is_member, kind }, bot_user_handle, reason? }`. For a DM, `kind` is "dm" and the result also carries `channel.user` (the partner\'s Slack user id) plus best-effort `channel.user_handle` and `channel.user_name` (the human\'s handle and display name); use these to VERIFY a DM\'s recipient rather than assuming whose DM it is. When `ok=false`, branch on `reason`: "channel_not_found" or "not_in_channel" \u2192 tell the user to run `/invite @<bot_user_handle>` in that channel (the configured ID exists but your bot isn\'t a member, or the ID is from a workspace the bot isn\'t installed in); "archived" \u2192 the channel exists but is archived, ask an admin to point the integration at a non-archived channel; "auth_failed" \u2192 bot token is invalid, escalate to an operator (do not surface to the end user). Pass the raw C0.../G0.../D0... channel ID, not a human name (use slack.list_channels for name \u2192 ID).',
47676
+ description: 'Verify the bot can post to a specific Slack channel, OR resolve who is on the other side of a DM. Use this before relying on a channel ID for delivery (e.g. before submitting an AWS access request that will notify an approval channel) to avoid silent posting failures, and to confirm the human behind a DM channel id before claiming a message reached a named person. Works for public AND private channels (unlike slack_list_channels, which is public-only) and for DM (`D0...`) channels. Returns `{ ok, channel: { id, name, is_private, is_archived, is_member, kind }, bot_user_handle, reason? }`. For a DM, `kind` is "dm" and the result also carries `channel.user` (the partner\'s Slack user id) plus best-effort `channel.user_handle` and `channel.user_name` (the human\'s handle and display name); use these to VERIFY a DM\'s recipient rather than assuming whose DM it is. When `ok=false`, branch on `reason`: "channel_not_found" or "not_in_channel" \u2192 tell the user to run `/invite @<bot_user_handle>` in that channel (the configured ID exists but your bot isn\'t a member, or the ID is from a workspace the bot isn\'t installed in); "archived" \u2192 the channel exists but is archived, ask an admin to point the integration at a non-archived channel; "auth_failed" \u2192 bot token is invalid, escalate to an operator (do not surface to the end user). Pass the raw C0.../G0.../D0... channel ID, not a human name (use slack_list_channels for name \u2192 ID).',
47656
47677
  inputSchema: {
47657
47678
  type: "object",
47658
47679
  properties: {
@@ -48029,7 +48050,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
48029
48050
  });
48030
48051
  if (redirectNote) base += `. ${redirectNote}`;
48031
48052
  if (interim) {
48032
- const promiseNote = describePromiseStampOutcome(slackPromiseStampOutcome, "slack.reply");
48053
+ const promiseNote = describePromiseStampOutcome(slackPromiseStampOutcome, "slack_reply");
48033
48054
  if (promiseNote) base += `. ${promiseNote}`;
48034
48055
  if (slackPromiseStampOutcome.attempted > slackPromiseStampOutcome.stamped) {
48035
48056
  process.stderr.write(
@@ -48110,7 +48131,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
48110
48131
  const { channel, ts, text } = args;
48111
48132
  if (!channel || !ts || typeof text !== "string") {
48112
48133
  return {
48113
- content: [{ type: "text", text: "slack.update requires channel, ts and text" }],
48134
+ content: [{ type: "text", text: "slack_update requires channel, ts and text" }],
48114
48135
  isError: true
48115
48136
  };
48116
48137
  }
@@ -48317,13 +48338,13 @@ ${result.formatted}` : "No messages in range, or the bot is not a member of this
48317
48338
  const { file_id, channel } = args;
48318
48339
  if (typeof file_id !== "string" || !file_id.trim()) {
48319
48340
  return {
48320
- content: [{ type: "text", text: "slack.download_attachment requires a string `file_id`." }],
48341
+ content: [{ type: "text", text: "slack_download_attachment requires a string `file_id`." }],
48321
48342
  isError: true
48322
48343
  };
48323
48344
  }
48324
48345
  if (typeof channel !== "string" || !channel.trim()) {
48325
48346
  return {
48326
- content: [{ type: "text", text: "slack.download_attachment requires a string `channel` (from the inbound <channel> tag)." }],
48347
+ content: [{ type: "text", text: "slack_download_attachment requires a string `channel` (from the inbound <channel> tag)." }],
48327
48348
  isError: true
48328
48349
  };
48329
48350
  }
@@ -48586,7 +48607,7 @@ async function getPermalink(channel, ts) {
48586
48607
  async function handleSendStructured(args) {
48587
48608
  if (!blockKitToolsAvailable()) {
48588
48609
  return {
48589
- content: [{ type: "text", text: "slack.send_structured is disabled by the SLACK_BLOCK_KIT_DISABLED fleet kill switch." }],
48610
+ content: [{ type: "text", text: "slack_send_structured is disabled by the SLACK_BLOCK_KIT_DISABLED fleet kill switch." }],
48590
48611
  isError: true
48591
48612
  };
48592
48613
  }
@@ -48681,7 +48702,7 @@ async function handleSendStructured(args) {
48681
48702
  }
48682
48703
  async function handleAskUser(args) {
48683
48704
  if (!askUserToolAvailable()) {
48684
- return errResult("slack.ask_user is disabled or the host MCP is missing AGT_HOST/AGT_API_KEY/AGT_AGENT_ID env wiring.");
48705
+ return errResult("slack_ask_user is disabled or the host MCP is missing AGT_HOST/AGT_API_KEY/AGT_AGENT_ID env wiring.");
48685
48706
  }
48686
48707
  const { randomUUID: rndUUID } = await import("crypto");
48687
48708
  const runtime = await Promise.resolve().then(() => (init_slack_block_kit_runtime(), slack_block_kit_runtime_exports));
@@ -15741,12 +15741,12 @@ var mcp = new Server(
15741
15741
  instructions: [
15742
15742
  // High-priority lines first — Claude Code truncates instructions at
15743
15743
  // 2048 chars, so anything appended late silently disappears.
15744
- "CRITICAL: every response to an MS Teams <channel> tag MUST go through teams.reply. Text in your session without a teams.reply call never reaches the user.",
15745
- "Messages from Teams arrive as a channel tag carrying user (an AAD id), user_name, conversation_id, service_url and reply_to_id. Pass conversation_id + service_url + reply_to_id from the tag to teams.reply verbatim; threaded replies must include reply_to_id.",
15744
+ "CRITICAL: every response to an MS Teams <channel> tag MUST go through teams_reply. Text in your session without a teams_reply call never reaches the user.",
15745
+ "Messages from Teams arrive as a channel tag carrying user (an AAD id), user_name, conversation_id, service_url and reply_to_id. Pass conversation_id + service_url + reply_to_id from the tag to teams_reply verbatim; threaded replies must include reply_to_id.",
15746
15746
  "Address users by user_name, never by raw user id (AAD object ids leak directory information).",
15747
15747
  "service_url is region-specific and arrives on every inbound \u2014 NEVER hardcode it, always echo the inbound value.",
15748
15748
  'Channel message + auto_followed="true": only reply if you have something useful to add; otherwise silently skip.',
15749
- "Reaction taxonomy (use teams.react sparingly \u2014 prefer a reply): like = ack; heart = success. NEVER react to signal failure \u2014 users can't tell why something failed from a reaction. On failure, teams.reply with one sentence explaining what went wrong."
15749
+ "Reaction taxonomy (use teams_react sparingly \u2014 prefer a reply): like = ack; heart = success. NEVER react to signal failure \u2014 users can't tell why something failed from a reaction. On failure, teams_reply with one sentence explaining what went wrong."
15750
15750
  ].join(" ")
15751
15751
  }
15752
15752
  );
@@ -16195,7 +16195,7 @@ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
16195
16195
  async function handleSendStructured(args) {
16196
16196
  if (!ADAPTIVE_CARDS_ENABLED) {
16197
16197
  return errResult(
16198
- "teams.send_structured is disabled. Set MSTEAMS_ADAPTIVE_CARDS_ENABLED=true to enable Adaptive Cards."
16198
+ "teams_send_structured is disabled. Set MSTEAMS_ADAPTIVE_CARDS_ENABLED=true to enable Adaptive Cards."
16199
16199
  );
16200
16200
  }
16201
16201
  const conversationId = stringArg(args, "conversation_id");
@@ -16223,7 +16223,7 @@ async function handleSendStructured(args) {
16223
16223
  async function handleAskUser(args) {
16224
16224
  if (!ADAPTIVE_CARDS_ENABLED || !ADAPTIVE_CARDS_ASK_USER_ENABLED) {
16225
16225
  return errResult(
16226
- "teams.ask_user is disabled. Set MSTEAMS_ADAPTIVE_CARDS_ENABLED=true AND MSTEAMS_ADAPTIVE_CARDS_ASK_USER_ENABLED=true to enable."
16226
+ "teams_ask_user is disabled. Set MSTEAMS_ADAPTIVE_CARDS_ENABLED=true AND MSTEAMS_ADAPTIVE_CARDS_ASK_USER_ENABLED=true to enable."
16227
16227
  );
16228
16228
  }
16229
16229
  if (!PENDING_INTERACTIONS_DIR) {
@@ -39191,11 +39191,18 @@ var FLAG_REGISTRY = [
39191
39191
  },
39192
39192
  {
39193
39193
  key: "ip-allowlist-enforce-admin-debug",
39194
- description: "ENG-10594 - whether the IP allowlist on the admin-debug surface (/admin/debug/*, including the deliberately unauthenticated POST /admin/debug/token that exchanges a tlk_ key) REFUSES, or merely observes. OFF (default) = shadow: every request is still evaluated and logged with verdict=admit or verdict=would-refuse, and nothing is ever rejected. ON = a caller whose platform-attested source address is outside AGT_IP_ALLOWLIST_ADMIN_DEBUG gets a bare 403. THE SSM PARAMETER IS A SECOND LEVER AND BOTH ARE NEEDED: with the list unset this flag does nothing, because an unconfigured allowlist admits everyone by design. Arm in that order - populate the list, read the shadow logs until the would-refuse set is understood, then flip this. Deliberately global (evaluated with an empty context): the middleware runs BEFORE authentication, so there is no verified org to scope by, and taking one from an unverified request would let a caller choose its own enforcement posture. Separate from the mcp flag on purpose - see that entry.",
39194
+ description: "ENG-10594 - whether the IP allowlist on the admin-debug surface (/admin/debug/*, including the deliberately unauthenticated POST /admin/debug/token that exchanges a tlk_ key) REFUSES, or merely observes. OFF (default) = shadow: every request is still evaluated and logged with verdict=admit or verdict=would-refuse, and nothing is ever rejected. ON = a caller whose platform-attested source address is outside AGT_IP_ALLOWLIST_ADMIN_DEBUG gets a bare 403. THE SSM PARAMETER IS A SECOND LEVER AND BOTH ARE NEEDED: with the list unset this flag does nothing, because an unconfigured allowlist admits everyone by design. Arm in that order - populate the list, read the shadow logs until the would-refuse set is understood, then flip this. Deliberately global (evaluated with an empty context): the middleware runs BEFORE authentication, so there is no verified org to scope by, and taking one from an unverified request would let a caller choose its own enforcement posture. There is deliberately NO counterpart flag for the per-agent MCP surface: that one is shadow-only structurally (its ENFORCE_FLAG entry is null), so arming this one does not and cannot arm that one. See the comment below this entry for why.",
39195
39195
  flagType: "boolean",
39196
- // Shadow. The measurement comes first: nobody yet knows the real set of
39197
- // staff addresses, and arming against a guess locks you out of the very
39198
- // tool you would use to diagnose the lockout.
39196
+ // Shadow. The measurement comes first - and ENG-10664 corrected what the
39197
+ // measurement is OF. This comment used to say "nobody yet knows the real set
39198
+ // of staff addresses": there is no such set. Measured over 48h, all 315
39199
+ // requests to this surface came from ONE address, the agt-hosts AZ-2a NAT
39200
+ // EIP - an AGENT calling the debug_* tools, not a person. The decided policy
39201
+ // is that it stays that way (admin-debug is only ever reached from our own
39202
+ // NAT gateways). So the pre-arm check is not "does my laptop appear" but
39203
+ // "is every NAT EIP in the list", because an AZ whose NAT has not yet
39204
+ // carried a debug_* call is absent from the log and would be refused the
39205
+ // instant this flips. See docs/runbooks/ip-allowlist-rollout.md step 3.
39199
39206
  defaultValue: false,
39200
39207
  // Arming it changes who can reach production diagnostics. Worth one
39201
39208
  // confirmation.
@@ -39210,20 +39217,34 @@ var FLAG_REGISTRY = [
39210
39217
  // ENFORCE_FLAG (packages/api/src/middleware/ip-allowlist.ts) is null, so no
39211
39218
  // flag is consulted and no flag flip can arm it.
39212
39219
  //
39213
- // Why it is not simply a second flag defaulting false: most of that surface's
39214
- // callers have no allowlistable address at all. Per ADR-0032 only hosts in the
39215
- // agt-hosts account egress through stable NAT EIPs; a host with
39216
- // provision_source `self` or `customer-ec2`, or any BYO account, egresses from
39217
- // an address the control plane cannot know. Arming against that population
39218
- // black-holes a customer's whole fleet, and per
39219
- // docs/runbooks/composio-allowlist-rollout.md this class of change has no
39220
+ // Why it is not simply a second flag defaulting false (CORRECTED, ENG-10664):
39221
+ //
39222
+ // This comment used to say the callers are agent HOSTS, most of which have no
39223
+ // allowlistable address per ADR-0032. That is the wrong caller. Agent hosts do
39224
+ // not call this surface at all: its handler's first act is
39225
+ // validateMcpAccessToken() against `mcp_oauth_tokens` (routes/mcp.ts:87, token
39226
+ // check at :91), so a host JWT is rejected outright - routes/host-runtime.ts
39227
+ // :33421 says so in as many words, which is why the ENG-4724 /host/* twins
39228
+ // exist. The caller set is remote MCP OAuth clients, structurally.
39229
+ //
39230
+ // The conclusion is unchanged and ONLY the reason has moved. Allowlisting this
39231
+ // surface would mean pinning a THIRD PARTY's egress ranges rather than our own
39232
+ // NAT EIPs - a different control, a different owner, a different failure mode:
39233
+ // they move a range and agent MCP access breaks with no signal on our side.
39234
+ // Per docs/runbooks/composio-allowlist-rollout.md this class of change has no
39220
39235
  // fleet-level undo. A flag would leave that one toggle away during an
39221
39236
  // incident, from someone who had just armed the admin-debug surface and
39222
39237
  // reasonably assumed the pair moved together. Requiring a code change puts
39223
39238
  // review in front of an irreversible action, which is the right price.
39224
39239
  //
39225
- // If the shadow data ever shows the population IS allowlistable, adding the
39226
- // flag here and a key in ENFORCE_FLAG is a small, deliberate diff.
39240
+ // Keeping the false rationale would have been worse than having none: a reader
39241
+ // who checked it would find it does not hold and might "fix" it by adding the
39242
+ // flag. That is the failure this rewrite exists to prevent.
39243
+ //
39244
+ // If we ever DECIDE to take on that third-party dependency deliberately,
39245
+ // adding the flag here and a key in ENFORCE_FLAG is a small diff - but it is a
39246
+ // decision to own someone else's egress, not a discovery that the population
39247
+ // was allowlistable all along.
39227
39248
  ];
39228
39249
  var REGISTRY_BY_KEY = new Map(FLAG_REGISTRY.map((definition) => [definition.key, definition]));
39229
39250
 
@@ -40796,13 +40817,13 @@ function validateLeaveChatArgs(args) {
40796
40817
  if (typeof chat_id !== "string" || !chat_id.trim()) {
40797
40818
  return {
40798
40819
  ok: false,
40799
- message: "telegram.leave_chat requires chat_id (from the <channel> tag)."
40820
+ message: "telegram_leave_chat requires chat_id (from the <channel> tag)."
40800
40821
  };
40801
40822
  }
40802
40823
  if (typeof reason !== "string" || !reason.trim()) {
40803
40824
  return {
40804
40825
  ok: false,
40805
- message: "telegram.leave_chat requires a reason (a non-empty string). It is logged, not sent to the chat."
40826
+ message: "telegram_leave_chat requires a reason (a non-empty string). It is logged, not sent to the chat."
40806
40827
  };
40807
40828
  }
40808
40829
  return { ok: true, chatId: chat_id.trim(), reason: reason.trim() };
@@ -42555,19 +42576,19 @@ async function actuateHostRestart(opts) {
42555
42576
 
42556
42577
  // src/telegram-mcp-instructions.ts
42557
42578
  var MAX_SKIP_REACTION_UNITS = 8;
42558
- var REACT_POLICY = `Reacting is egress and does NOT settle anything \u2014 telegram.consume is what settles. Never react in a chat you are barred from, and never post a "this wasn't for me" message.`;
42579
+ var REACT_POLICY = `Reacting is egress and does NOT settle anything \u2014 telegram_consume is what settles. Never react in a chat you are barred from, and never post a "this wasn't for me" message.`;
42559
42580
  function buildTelegramMcpInstructions(opts) {
42560
42581
  const emoji2 = opts.skipReaction ?? "";
42561
42582
  const skipReactionOn = opts.skipReactionOn && emoji2.length > 0 && emoji2.length <= MAX_SKIP_REACTION_UNITS;
42562
42583
  return [
42563
- "CRITICAL: every response to a Telegram <channel> tag MUST go through telegram.reply with the chat_id from the tag \u2014 typed text never reaches the user. Slow work: interim ack (telegram.reply interim:true), then your FINAL answer as a separate telegram.reply (interim omitted). An ack is not the answer.",
42584
+ "CRITICAL: every response to a Telegram <channel> tag MUST go through telegram_reply with the chat_id from the tag \u2014 typed text never reaches the user. Slow work: interim ack (telegram_reply interim:true), then your FINAL answer as a separate telegram_reply (interim omitted). An ack is not the answer.",
42564
42585
  "Messages from Telegram arrive as a channel tag carrying chat_id, user, user_name and message_id. Pass reply_to_message_id from the tag so the response lands as a quote-reply in busy chats.",
42565
- "Deliberately NOT replying (a standing rule bars you from that chat, or the message only changed state)? Call telegram.consume with the chat_id and a short reason: it settles the message without posting anything. Without a reply or a consume it counts as undelivered and re-delivers to you forever.",
42566
- "Inbound attachments: `files` on the tag is a JSON string \u2014 JSON.parse it. An entry with `path` (or top-level `image_path`) is already downloaded \u2014 Read it. Entries with `file_id` and no `path` need telegram.download_attachment (file_id + chat_id), then Read the returned path.",
42586
+ "Deliberately NOT replying (a standing rule bars you from that chat, or the message only changed state)? Call telegram_consume with the chat_id and a short reason: it settles the message without posting anything. Without a reply or a consume it counts as undelivered and re-delivers to you forever.",
42587
+ "Inbound attachments: `files` on the tag is a JSON string \u2014 JSON.parse it. An entry with `path` (or top-level `image_path`) is already downloaded \u2014 Read it. Entries with `file_id` and no `path` need telegram_download_attachment (file_id + chat_id), then Read the returned path.",
42567
42588
  'For work >30s follow the CLAUDE.md kanban flow: kanban_add \u2192 reply "On it \u2014 tracking here: <kanban URL>" \u2192 in_progress \u2192 reply the result. Simple lookups skip kanban but still reply.',
42568
42589
  "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.",
42569
- "Reactions (telegram.react sparingly \u2014 prefer a reply): \u{1F440} = ack (auto-added, do not duplicate); \u{1F44D} or \u{1F389} = success." + (skipReactionOn ? ` ${emoji2} = seen, not replying.` : "") + " NEVER react to signal failure of YOUR work \u2014 telegram.reply one sentence explaining it. Free-tier emoji only: \u{1F44D} \u{1F44E} \u2764 \u{1F525} \u{1F389} \u{1F64F} \u{1F44C} \u{1F440} \u{1F4AF} \u{1FAE1}.",
42570
- skipReactionOn ? `${REACT_POLICY} Where engaging IS permitted you may also telegram.react "${emoji2}" after consuming.` : REACT_POLICY
42590
+ "Reactions (telegram_react sparingly \u2014 prefer a reply): \u{1F440} = ack (auto-added, do not duplicate); \u{1F44D} or \u{1F389} = success." + (skipReactionOn ? ` ${emoji2} = seen, not replying.` : "") + " NEVER react to signal failure of YOUR work \u2014 telegram_reply one sentence explaining it. Free-tier emoji only: \u{1F44D} \u{1F44E} \u2764 \u{1F525} \u{1F389} \u{1F64F} \u{1F44C} \u{1F440} \u{1F4AF} \u{1FAE1}.",
42591
+ skipReactionOn ? `${REACT_POLICY} Where engaging IS permitted you may also telegram_react "${emoji2}" after consuming.` : REACT_POLICY
42571
42592
  ].filter(Boolean).join(" ");
42572
42593
  }
42573
42594
 
@@ -45100,7 +45121,7 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
45100
45121
  },
45101
45122
  interim: {
45102
45123
  type: "boolean",
45103
- description: 'Set true ONLY when this is an interim acknowledgement (e.g. "On it \u2014 checking now\u2026") and the real answer is still coming. The system keeps tracking this chat as awaiting your final reply. Your eventual substantive answer MUST be a separate telegram.reply with interim omitted/false \u2014 that is what marks the request complete. Omit (default false) for any reply that fully answers the user.'
45124
+ description: 'Set true ONLY when this is an interim acknowledgement (e.g. "On it \u2014 checking now\u2026") and the real answer is still coming. The system keeps tracking this chat as awaiting your final reply. Your eventual substantive answer MUST be a separate telegram_reply with interim omitted/false \u2014 that is what marks the request complete. Omit (default false) for any reply that fully answers the user.'
45104
45125
  }
45105
45126
  },
45106
45127
  required: ["chat_id", "text"]
@@ -45108,7 +45129,7 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
45108
45129
  },
45109
45130
  {
45110
45131
  name: "telegram.consume",
45111
- description: "Acknowledge Telegram message(s) you handled WITHOUT sending a reply \u2014 a message you deliberately did not answer because a standing rule forbids engaging in that chat, or one that only updated state. Pass the chat_id from the <channel> tag. If you send a telegram.reply you do NOT also need to consume: the reply already acknowledges the message. Without one of the two, a silently-handled message is treated as undelivered and is re-delivered to you indefinitely. Clears the chat, not one message \u2014 that is Telegram's conversation grain \u2014 and only messages received up to the one you are answering, so an inbound that arrives while you are deciding is never discarded unseen.",
45132
+ description: "Acknowledge Telegram message(s) you handled WITHOUT sending a reply \u2014 a message you deliberately did not answer because a standing rule forbids engaging in that chat, or one that only updated state. Pass the chat_id from the <channel> tag. If you send a telegram_reply you do NOT also need to consume: the reply already acknowledges the message. Without one of the two, a silently-handled message is treated as undelivered and is re-delivered to you indefinitely. Clears the chat, not one message \u2014 that is Telegram's conversation grain \u2014 and only messages received up to the one you are answering, so an inbound that arrives while you are deciding is never discarded unseen.",
45112
45133
  inputSchema: {
45113
45134
  type: "object",
45114
45135
  properties: {
@@ -45211,7 +45232,7 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
45211
45232
  },
45212
45233
  {
45213
45234
  name: "telegram.react",
45214
- 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.)`,
45235
+ 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.)`,
45215
45236
  inputSchema: {
45216
45237
  type: "object",
45217
45238
  properties: {
@@ -45427,7 +45448,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
45427
45448
  const { chat_id, reason } = args;
45428
45449
  if (!chat_id) {
45429
45450
  return {
45430
- content: [{ type: "text", text: "telegram.consume requires chat_id (from the <channel> tag)." }],
45451
+ content: [{ type: "text", text: "telegram_consume requires chat_id (from the <channel> tag)." }],
45431
45452
  isError: true
45432
45453
  };
45433
45454
  }
@@ -45563,7 +45584,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
45563
45584
  if (name === "telegram.reply") {
45564
45585
  if (interim) {
45565
45586
  markPromisedPendingMessage(chat_id, drainCutoffMs);
45566
- promiseNote = describePromiseStampOutcome(telegramPromiseStampOutcome, "telegram.reply");
45587
+ promiseNote = describePromiseStampOutcome(telegramPromiseStampOutcome, "telegram_reply");
45567
45588
  } else {
45568
45589
  clearPendingMessage(chat_id, drainCutoffMs);
45569
45590
  }
@@ -45623,13 +45644,13 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
45623
45644
  const { file_id, chat_id } = args;
45624
45645
  if (typeof file_id !== "string" || !file_id.trim()) {
45625
45646
  return {
45626
- content: [{ type: "text", text: "telegram.download_attachment requires a string `file_id`." }],
45647
+ content: [{ type: "text", text: "telegram_download_attachment requires a string `file_id`." }],
45627
45648
  isError: true
45628
45649
  };
45629
45650
  }
45630
45651
  if (typeof chat_id !== "string" || !chat_id.trim()) {
45631
45652
  return {
45632
- content: [{ type: "text", text: "telegram.download_attachment requires a string `chat_id` (from the inbound <channel> tag)." }],
45653
+ content: [{ type: "text", text: "telegram_download_attachment requires a string `chat_id` (from the inbound <channel> tag)." }],
45633
45654
  isError: true
45634
45655
  };
45635
45656
  }
@@ -156049,19 +156049,19 @@ var mcp = new Server(
156049
156049
  tools: {}
156050
156050
  },
156051
156051
  instructions: (IS_BAILEYS ? [
156052
- "CRITICAL: every response to a WhatsApp <channel> tag MUST go through whatsapp.reply. Text in your session without a tool call never reaches the user.",
156053
- "Messages arrive as a channel tag carrying sender (a JID), conversation_id and message_id. Pass the `sender` value verbatim as the `to` argument to whatsapp.reply.",
156054
- "whatsapp.reply sends free-form text with no restrictions (this is a linked personal number, not a business template flow)."
156052
+ "CRITICAL: every response to a WhatsApp <channel> tag MUST go through whatsapp_reply. Text in your session without a tool call never reaches the user.",
156053
+ "Messages arrive as a channel tag carrying sender (a JID), conversation_id and message_id. Pass the `sender` value verbatim as the `to` argument to whatsapp_reply.",
156054
+ "whatsapp_reply sends free-form text with no restrictions (this is a linked personal number, not a business template flow)."
156055
156055
  ] : [
156056
- "CRITICAL: every response to a WhatsApp <channel> tag MUST go through whatsapp.reply (or whatsapp.send_template). Text in your session without a tool call never reaches the user.",
156057
- "Messages arrive as a channel tag carrying sender, conversation_id and message_id. Pass the `sender` value verbatim as the `to` argument to whatsapp.reply.",
156058
- "whatsapp.reply sends free-form text and only works inside the 24h customer-service window (24h since the user last messaged). If it reports the window is closed, use whatsapp.send_template with a pre-approved template instead."
156056
+ "CRITICAL: every response to a WhatsApp <channel> tag MUST go through whatsapp_reply (or whatsapp_send_template). Text in your session without a tool call never reaches the user.",
156057
+ "Messages arrive as a channel tag carrying sender, conversation_id and message_id. Pass the `sender` value verbatim as the `to` argument to whatsapp_reply.",
156058
+ "whatsapp_reply sends free-form text and only works inside the 24h customer-service window (24h since the user last messaged). If it reports the window is closed, use whatsapp_send_template with a pre-approved template instead."
156059
156059
  ]).join(" ")
156060
156060
  }
156061
156061
  );
156062
156062
  var replyTool = {
156063
156063
  name: "whatsapp.reply",
156064
- description: IS_BAILEYS ? "Send a WhatsApp text reply. Pass the inbound `sender` (a JID) value as `to`. Free-form, no restrictions." : "Send a free-form WhatsApp text reply. Pass the inbound `sender` value as `to`. Only deliverable inside the 24h customer-service window; outside it, use whatsapp.send_template.",
156064
+ description: IS_BAILEYS ? "Send a WhatsApp text reply. Pass the inbound `sender` (a JID) value as `to`. Free-form, no restrictions." : "Send a free-form WhatsApp text reply. Pass the inbound `sender` value as `to`. Only deliverable inside the 24h customer-service window; outside it, use whatsapp_send_template.",
156065
156065
  inputSchema: {
156066
156066
  type: "object",
156067
156067
  properties: {
@@ -156123,7 +156123,7 @@ async function handleReply(a) {
156123
156123
  const lastInbound = lastInboundBySender.get(to) ?? null;
156124
156124
  if (chooseOutboundMode(lastInbound, Date.now()) === "template") {
156125
156125
  return errResult(
156126
- "The 24h customer-service window for this recipient is closed (no inbound within 24h). Free-form text cannot be delivered \u2014 use whatsapp.send_template with a pre-approved template."
156126
+ "The 24h customer-service window for this recipient is closed (no inbound within 24h). Free-form text cannot be delivered \u2014 use whatsapp_send_template with a pre-approved template."
156127
156127
  );
156128
156128
  }
156129
156129
  const res = await kapso.sendText(to, text);
@@ -156131,7 +156131,7 @@ async function handleReply(a) {
156131
156131
  return { content: [{ type: "text", text: `sent (message_id=${res.messageId ?? "unknown"})` }] };
156132
156132
  }
156133
156133
  async function handleSendTemplate(a) {
156134
- if (IS_BAILEYS) return errResult("Templates are not used in QR-link mode \u2014 use whatsapp.reply to send freely.");
156134
+ if (IS_BAILEYS) return errResult("Templates are not used in QR-link mode \u2014 use whatsapp_reply to send freely.");
156135
156135
  if (!kapso) return errResult("WhatsApp outbound is not configured (missing WHATSAPP_PROJECT_API_KEY / WHATSAPP_PHONE_NUMBER_ID).");
156136
156136
  const to = stringArg(a, "to");
156137
156137
  const name = stringArg(a, "template");
@@ -62,8 +62,8 @@ import {
62
62
  writeDirectChatSessionState,
63
63
  writeEgressAllowlist,
64
64
  writePersistentClaudeWrapper
65
- } from "./chunk-PHPOHMSK.js";
66
- import "./chunk-HRHDATT5.js";
65
+ } from "./chunk-KTFN2AIB.js";
66
+ import "./chunk-VF5BT7BP.js";
67
67
  import "./chunk-DHWNVVX4.js";
68
68
  import "./chunk-XWVM4KPK.js";
69
69
  export {
@@ -131,4 +131,4 @@ export {
131
131
  writeEgressAllowlist,
132
132
  writePersistentClaudeWrapper
133
133
  };
134
- //# sourceMappingURL=persistent-session-YJJXZSUE.js.map
134
+ //# sourceMappingURL=persistent-session-26W47NVS.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  paneLogPath
3
- } from "./chunk-PHPOHMSK.js";
4
- import "./chunk-HRHDATT5.js";
3
+ } from "./chunk-KTFN2AIB.js";
4
+ import "./chunk-VF5BT7BP.js";
5
5
  import "./chunk-DHWNVVX4.js";
6
6
  import "./chunk-XWVM4KPK.js";
7
7
 
@@ -800,4 +800,4 @@ export {
800
800
  readAndResetSlackReplyBindingClassifications,
801
801
  readAndResetSlackReplyTargetClassifications
802
802
  };
803
- //# sourceMappingURL=responsiveness-probe-SQZGSNVI.js.map
803
+ //# sourceMappingURL=responsiveness-probe-KMHYGGYP.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  sessionTranscriptDir
3
- } from "./chunk-HRHDATT5.js";
3
+ } from "./chunk-VF5BT7BP.js";
4
4
 
5
5
  // src/lib/session-auth-dead.ts
6
6
  import { closeSync, openSync, readSync, readdirSync, statSync } from "fs";
@@ -203,4 +203,4 @@ export {
203
203
  decideSessionAuthState,
204
204
  probeSessionAuth
205
205
  };
206
- //# sourceMappingURL=session-auth-dead-7OLNDHJC.js.map
206
+ //# sourceMappingURL=session-auth-dead-CRG2ESD2.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@integrity-labs/agt-cli",
3
- "version": "0.28.974",
3
+ "version": "0.28.976",
4
4
  "description": "Augmented Team CLI — agent provisioning and management",
5
5
  "type": "module",
6
6
  "engines": {