@integrity-labs/agt-cli 0.28.45 → 0.28.47

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.
@@ -21106,6 +21106,10 @@ var AdminDebugClient = class _AdminDebugClient {
21106
21106
  probeIntegration(args) {
21107
21107
  return this.get("/admin/debug/probe-integration", _AdminDebugClient.cleanQuery(args));
21108
21108
  }
21109
+ /** List an agent's dead-lettered / quarantined inbound (ENG-6443, read). */
21110
+ inspectDeadLetters(args) {
21111
+ return this.get("/admin/debug/dead-letters", _AdminDebugClient.cleanQuery(args));
21112
+ }
21109
21113
  queryAuditLog(args) {
21110
21114
  return this.get("/admin/debug/audit-log", _AdminDebugClient.cleanQuery(args));
21111
21115
  }
@@ -21195,6 +21199,21 @@ var AdminDebugClient = class _AdminDebugClient {
21195
21199
  run_id: this.runId
21196
21200
  });
21197
21201
  }
21202
+ /** Request a HITL-gated dead-letter replay (re-inject one parked inbound) (ENG-6443). */
21203
+ requestReplayDeadLetter(args) {
21204
+ if (!this.agentId) {
21205
+ throw makeError(400, "AdminDebugClient.requestReplayDeadLetter requires agentId (set AGT_AGENT_ID)");
21206
+ }
21207
+ return this.post("/admin/debug/actions/replay-dead-letter", {
21208
+ target_agent_id: args.target_agent_id,
21209
+ channel: args.channel,
21210
+ store: args.store,
21211
+ marker_name: args.marker_name,
21212
+ reason: args.reason,
21213
+ agent_id: this.agentId,
21214
+ run_id: this.runId
21215
+ });
21216
+ }
21198
21217
  /** Poll a remedial action's HITL decision. */
21199
21218
  getActionStatus(requestId) {
21200
21219
  return this.get(`/admin/debug/actions/${encodeURIComponent(requestId)}`);
@@ -21245,6 +21264,21 @@ var probeIntegrationSchema = external_exports.object({
21245
21264
  "The integration slug (its definition code_name, e.g. `gmail`, `slack`, `here-now`) to probe on that agent."
21246
21265
  )
21247
21266
  });
21267
+ var inspectDeadLettersSchema = external_exports.object({
21268
+ agent_id: external_exports.string().min(1).max(64).describe(
21269
+ "UUID of the agent whose dead-lettered inbound to list. Fails closed (uniform not-found) if you are not authorized for its org."
21270
+ )
21271
+ });
21272
+ var replayDeadLetterSchema = external_exports.object({
21273
+ target_agent_id: external_exports.string().min(1).max(64).describe("UUID of the customer agent whose dead letter to replay."),
21274
+ channel: external_exports.enum(["slack", "telegram"]).describe("Which inbound channel the dead letter belongs to (from the inspect result)."),
21275
+ store: external_exports.string().min(1).max(128).regex(
21276
+ /^(stale|cleared:pending-inbound-cleared-[A-Za-z0-9._-]+)$/,
21277
+ 'store must be "stale" or "cleared:pending-inbound-cleared-<stamp>" (copy from the inspect result)'
21278
+ ).describe("The dead-letter store the marker is in \u2014 copy the `store` value verbatim from debug_inspect_dead_letters."),
21279
+ marker_name: external_exports.string().trim().min(1).max(256).describe("The exact `marker_name` from the inspect result identifying which parked message to re-inject."),
21280
+ reason: external_exports.string().min(1).max(2e3).describe("Why this message should be re-injected \u2014 shown verbatim to the human approver. Be specific.")
21281
+ });
21248
21282
  var listAlertsSchema = external_exports.object({
21249
21283
  severity: external_exports.string().max(16).optional().describe("Filter by severity (critical | warning | info)."),
21250
21284
  open: external_exports.boolean().optional().describe("When true, only currently-open (unclosed) alerts."),
@@ -21455,6 +21489,19 @@ server.tool(
21455
21489
  }
21456
21490
  }
21457
21491
  );
21492
+ server.tool(
21493
+ "debug_inspect_dead_letters",
21494
+ "List an agent's DEAD-LETTERED / quarantined inbound \u2014 customer messages that were parked on the host instead of delivered (so the agent never saw them), rather than silently lost. Use when a user says \"the agent never replied\" but nothing shows in the normal flow, or to audit what fell into the dead-letter path. Resolves the agent's current host and reads two on-disk stores over SSM: `stale` (markers moved aside on a wedge-respawn because the agent couldn't receive) and `cleared:<stamp>` (markers the clear_pending_inbound remedial action moved aside). Returns { agent_id, code_name, host, markers[], total, ssm_status } where each marker is { index, store, channel (slack|telegram), marker_name, received_at, undeliverable, discretionary, replay_count, conversation, message_ref } \u2014 routing metadata only, NEVER the message body. To re-inject one, pass its exact { channel, store, marker_name } to debug_replay_dead_letter. Fails closed for an unauthorized org; every call is audited as a cross-org host access. Pass { agent_id }.",
21495
+ inspectDeadLettersSchema.shape,
21496
+ async (args) => {
21497
+ try {
21498
+ const result = await client.inspectDeadLetters(args);
21499
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
21500
+ } catch (err) {
21501
+ return { content: [{ type: "text", text: formatError2(err) }], isError: true };
21502
+ }
21503
+ }
21504
+ );
21458
21505
  server.tool(
21459
21506
  "debug_query_audit_log",
21460
21507
  'Read recent audit events to troubleshoot why/when agents bounced \u2014 primarily restart events. Defaults to the "agent.restart" action: each event returns { created_at, source, reason, actor_kind, agent_id, code_name }, where `source` is the restart trigger (agent-self-request, channel-command, maintenance-window, stale-mcp, hot-reload-mcp, mcp-presence-reaper, day-rollover, auth-tuple-change, \u2026) and `reason` is the free-text rationale when one was given (e.g. the agent\'s own words for a request_restart). Two modes: (1) pass { agent_id } to scope to ONE agent (fails closed if you\'re not authorized for its org) \u2014 use when a specific agent went quiet. (2) OMIT agent_id for a CROSS-AGENT sweep across every org you\'re authorized for (optionally narrow with { org_id }) \u2014 use to spot fleet-wide restart storms or compare agents. Filter the action with `action` (pass "all" to drop the action filter and read every event type), and bound with { since_hours?, limit? }. Cross-agent results are org-walled in SQL; unauthorized orgs are invisible.',
@@ -21535,9 +21582,28 @@ server.tool(
21535
21582
  }
21536
21583
  }
21537
21584
  );
21585
+ server.tool(
21586
+ "debug_replay_dead_letter",
21587
+ "Request a HITL-gated REPLAY of ONE dead-lettered inbound \u2014 re-inject a customer message that was parked on the host (listed by debug_inspect_dead_letters) back into the agent's live inbound so it is reprocessed. This is the inverse of clear_pending_inbound: use it to recover a message a wedged agent missed. A HUMAN must approve in Slack before anything happens, and only if writes are armed (shadow mode runs the approval but executes nothing). The marker is matched on the host by the exact (channel, store, marker_name) you pass \u2014 copy them verbatim from the inspect result \u2014 so a stale or wrong selector safely no-ops (`replayed:false, reason:not_found`) rather than moving the wrong message. Async: returns { request_id, status, write_mode, notification_status }; after approval, poll check_action_status (result_payload carries { replayed, moved_to }). Pass { target_agent_id, channel, store, marker_name, reason }.",
21588
+ replayDeadLetterSchema.shape,
21589
+ async (args) => {
21590
+ try {
21591
+ const result = await client.requestReplayDeadLetter({
21592
+ target_agent_id: args.target_agent_id,
21593
+ channel: args.channel,
21594
+ store: args.store,
21595
+ marker_name: args.marker_name,
21596
+ reason: args.reason
21597
+ });
21598
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
21599
+ } catch (err) {
21600
+ return { content: [{ type: "text", text: formatError2(err) }], isError: true };
21601
+ }
21602
+ }
21603
+ );
21538
21604
  server.tool(
21539
21605
  "check_action_status",
21540
- "Poll the decision on a remedial action you requested (the approval result is also pushed to you via direct-chat). Returns { status, result_payload, denial_reason, resolved_at } \u2014 for an approved request_ssm_run, result_payload carries the command output. Pass { request_id }.",
21606
+ "Poll the decision on a remedial action you requested (the approval result is also pushed to you via direct-chat). Returns { status, result_payload, denial_reason, resolved_at } \u2014 for an approved request_ssm_run, result_payload carries the command output; for debug_replay_dead_letter, result_payload carries { replayed, moved_to }. Pass { request_id }.",
21541
21607
  checkActionStatusSchema.shape,
21542
21608
  async (args) => {
21543
21609
  try {
@@ -14547,6 +14547,13 @@ function channelBusyAckThresholdMs() {
14547
14547
  const raw = parseInt(process.env.AGT_CHANNEL_BUSY_ACK_THRESHOLD_MS ?? "", 10);
14548
14548
  return Number.isFinite(raw) && raw > 0 ? raw : BUSY_ACK_THRESHOLD_MS;
14549
14549
  }
14550
+ function channelSkipReactionEnabled() {
14551
+ return resolveHostBooleanFlag({
14552
+ key: "channel-skip-reaction",
14553
+ envVar: "AGT_CHANNEL_SKIP_REACTION_ENABLED",
14554
+ defaultValue: false
14555
+ });
14556
+ }
14550
14557
  function decideBusyAck(i) {
14551
14558
  if (!i.hasTarget) return false;
14552
14559
  if (!i.stillPending) return false;
@@ -16607,6 +16614,8 @@ var BLOCK_KIT_ASK_USER_ENABLED = process.env.SLACK_BLOCK_KIT_ASK_USER_ENABLED ==
16607
16614
  var BLOCK_KIT_DISABLED = process.env.SLACK_BLOCK_KIT_DISABLED === "true";
16608
16615
  var ALLOWED_USERS = parseAllowedUsersCsv(process.env.SLACK_ALLOWED_USERS);
16609
16616
  var THREAD_AUTO_FOLLOW = process.env.SLACK_THREAD_AUTO_FOLLOW ?? "off";
16617
+ var SLACK_SKIP_REACTION = (process.env.SLACK_SKIP_REACTION ?? "").trim();
16618
+ var SLACK_ACK_REACTION = (process.env.SLACK_ACK_REACTION ?? "").trim() || "eyes";
16610
16619
  var CHANNEL_RESPONSE_MODE = parseResponseMode(process.env.SLACK_CHANNEL_RESPONSE_MODE);
16611
16620
  var SLACK_PEER_DISABLED_MODE = (() => {
16612
16621
  const raw = (process.env.PEER_DISABLED ?? "").trim().toLowerCase();
@@ -16773,7 +16782,7 @@ function healSlackUndeliverable(channel, messageTs) {
16773
16782
  fetch("https://slack.com/api/reactions.add", {
16774
16783
  method: "POST",
16775
16784
  headers,
16776
- body: JSON.stringify({ channel, timestamp: messageTs, name: "eyes" })
16785
+ body: JSON.stringify({ channel, timestamp: messageTs, name: SLACK_ACK_REACTION })
16777
16786
  }).catch(() => {
16778
16787
  });
16779
16788
  });
@@ -18145,6 +18154,7 @@ void resolveBotUserIdOrThrow().then((id) => {
18145
18154
  );
18146
18155
  if (authFailed) slackBotUserIdClient?.reportAuthHealth(false);
18147
18156
  });
18157
+ var SKIP_REACTION_ON = channelSkipReactionEnabled() && SLACK_SKIP_REACTION.length > 0;
18148
18158
  var mcp = new Server(
18149
18159
  { name: "slack", version: "0.1.0" },
18150
18160
  {
@@ -18163,8 +18173,8 @@ var mcp = new Server(
18163
18173
  "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 slack.download_attachment. Use that tool only for entries with `file_id` but NO `path` (PDF, docx, csv): pass file_id + channel verbatim, then Read the returned path. Single-image messages also get a top-level `image_path`. Don't surface internal file-handling errors that don't affect the answer.",
18164
18174
  "Address users by user_name, never by raw user ID. In multi-participant threads the CURRENT speaker is the one on the latest <channel> tag.",
18165
18175
  'Mentioned in a channel \u2192 respond in that thread. DM \u2192 respond directly. auto_followed="true" \u2192 only reply if useful, OR if your own bot user is @-mentioned (counts even in auto_followed).',
18166
- "Reaction taxonomy (use slack.react sparingly \u2014 prefer a reply): \u{1F440} = ack (already auto-added on inbound, do not duplicate); \u2705 = success. NEVER react to signal failure of YOUR work \u2014 users can't tell why something failed from an emoji. On failure, slack.reply with one sentence explaining what went wrong (no stack traces, no secrets). (The \u23F3 you may see on an inbound is applied by the system, not you \u2014 it marks a message that arrived while the agent couldn't reply; never add \u23F3 or \u274C yourself.)",
18167
- `When a thread message is NOT addressed to you (different @-mention, side conversation, auto_followed catch-up): SILENTLY SKIP \u2014 no reaction, no reply, no "this wasn't for me" message.`,
18176
+ `Reaction taxonomy (use slack.react sparingly \u2014 prefer a reply): :${SLACK_ACK_REACTION}: = ack (already auto-added on inbound, do not duplicate); \u2705 = success.` + (SKIP_REACTION_ON ? ` "${SLACK_SKIP_REACTION}" = seen but not replying (see below).` : "") + " NEVER react to signal failure of YOUR work \u2014 users can't tell why something failed from an emoji. On failure, slack.reply with one sentence explaining what went wrong (no stack traces, no secrets). (The \u23F3 you may see on an inbound is applied by the system, not you \u2014 it marks a message that arrived while the agent couldn't reply; never add \u23F3 or \u274C yourself.)",
18177
+ SKIP_REACTION_ON ? `When you read a DM or thread message and decide NOT to reply (different @-mention, side conversation, auto_followed catch-up, or simply nothing useful to add): call slack.react with emoji "${SLACK_SKIP_REACTION}" so the sender knows you saw it \u2014 never post a "this wasn't for me" message. Top-level channel messages (not a DM, not a thread reply) you may still skip silently.` : `When a thread message is NOT addressed to you (different @-mention, side conversation, auto_followed catch-up): SILENTLY SKIP \u2014 no reaction, no reply, no "this wasn't for me" message.`,
18168
18178
  "To deliver a file: save under your project dir, call slack.upload_file with path + channel + thread_ts."
18169
18179
  ].join(" ")
18170
18180
  }
@@ -19777,7 +19787,7 @@ async function connectSocketMode() {
19777
19787
  );
19778
19788
  }
19779
19789
  if (ackDecision !== "none") {
19780
- const reactionName = ackDecision === "undeliverable" ? SLACK_UNDELIVERABLE_REACTION : "eyes";
19790
+ const reactionName = ackDecision === "undeliverable" ? SLACK_UNDELIVERABLE_REACTION : SLACK_ACK_REACTION;
19781
19791
  fetch("https://slack.com/api/reactions.add", {
19782
19792
  method: "POST",
19783
19793
  headers: {
@@ -16198,6 +16198,13 @@ function channelBusyAckThresholdMs() {
16198
16198
  const raw = parseInt(process.env.AGT_CHANNEL_BUSY_ACK_THRESHOLD_MS ?? "", 10);
16199
16199
  return Number.isFinite(raw) && raw > 0 ? raw : BUSY_ACK_THRESHOLD_MS;
16200
16200
  }
16201
+ function channelSkipReactionEnabled() {
16202
+ return resolveHostBooleanFlag({
16203
+ key: "channel-skip-reaction",
16204
+ envVar: "AGT_CHANNEL_SKIP_REACTION_ENABLED",
16205
+ defaultValue: false
16206
+ });
16207
+ }
16201
16208
  function decideBusyAck(i) {
16202
16209
  if (!i.hasTarget) return false;
16203
16210
  if (!i.stillPending) return false;
@@ -16506,7 +16513,8 @@ function telegramApiCall(method, body, timeoutMs) {
16506
16513
  req.end();
16507
16514
  });
16508
16515
  }
16509
- var ACK_EMOJI = "\u{1F440}";
16516
+ var ACK_EMOJI = (process.env.TELEGRAM_ACK_REACTION ?? "").trim() || "\u{1F440}";
16517
+ var TELEGRAM_SKIP_REACTION = (process.env.TELEGRAM_SKIP_REACTION ?? "").trim();
16510
16518
  async function setMessageReaction(chatId, messageId, emoji2) {
16511
16519
  try {
16512
16520
  const resp = await telegramApiCall(
@@ -17698,6 +17706,7 @@ function noteThreadActivity(chatId, cutoffMs = Number.POSITIVE_INFINITY) {
17698
17706
  if (!chatId) return;
17699
17707
  clearPendingMessage(chatId, cutoffMs);
17700
17708
  }
17709
+ var SKIP_REACTION_ON = channelSkipReactionEnabled() && TELEGRAM_SKIP_REACTION.length > 0;
17701
17710
  var mcp = new Server(
17702
17711
  { name: "telegram", version: "0.1.0" },
17703
17712
  {
@@ -17716,8 +17725,9 @@ var mcp = new Server(
17716
17725
  "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.",
17717
17726
  '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.',
17718
17727
  "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.",
17719
- `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.)`
17720
- ].join(" ")
17728
+ "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." + (SKIP_REACTION_ON ? ` ${TELEGRAM_SKIP_REACTION} = seen but not replying (see below).` : "") + ` 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.)`,
17729
+ SKIP_REACTION_ON ? `When you read a message you decide NOT to reply to (nothing useful to add): call telegram.react with emoji "${TELEGRAM_SKIP_REACTION}" so the sender knows you saw it \u2014 never post a "this wasn't for me" message.` : ""
17730
+ ].filter(Boolean).join(" ")
17721
17731
  }
17722
17732
  );
17723
17733
  mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
@@ -25,8 +25,8 @@ import {
25
25
  takeZombieDetection,
26
26
  writeDirectChatSessionState,
27
27
  writePersistentClaudeWrapper
28
- } from "./chunk-2BRF2FDJ.js";
29
- import "./chunk-SJUD2BWU.js";
28
+ } from "./chunk-UVE6VSU3.js";
29
+ import "./chunk-ONG7KSRP.js";
30
30
  import "./chunk-XWVM4KPK.js";
31
31
  export {
32
32
  SEND_KEYS_ENTER_DELAY_MS,
@@ -56,4 +56,4 @@ export {
56
56
  writeDirectChatSessionState,
57
57
  writePersistentClaudeWrapper
58
58
  };
59
- //# sourceMappingURL=persistent-session-PNY26VBX.js.map
59
+ //# sourceMappingURL=persistent-session-Q73X7PPM.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  paneLogPath
3
- } from "./chunk-2BRF2FDJ.js";
4
- import "./chunk-SJUD2BWU.js";
3
+ } from "./chunk-UVE6VSU3.js";
4
+ import "./chunk-ONG7KSRP.js";
5
5
  import "./chunk-XWVM4KPK.js";
6
6
 
7
7
  // src/lib/responsiveness-probe.ts
@@ -250,4 +250,4 @@ export {
250
250
  parkPendingInbound,
251
251
  readAndResetChannelDeflections
252
252
  };
253
- //# sourceMappingURL=responsiveness-probe-4YMZ6LXO.js.map
253
+ //# sourceMappingURL=responsiveness-probe-R7WZXP2Y.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@integrity-labs/agt-cli",
3
- "version": "0.28.45",
3
+ "version": "0.28.47",
4
4
  "description": "Augmented Team CLI — agent provisioning and management",
5
5
  "type": "module",
6
6
  "engines": {