@integrity-labs/agt-cli 0.28.706 → 0.28.708

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.
@@ -7647,6 +7647,31 @@ var init_snooze = __esm({
7647
7647
  });
7648
7648
 
7649
7649
  // ../core/dist/channels/slack-block-kit.js
7650
+ function isFetchableHttpUrl(value) {
7651
+ let parsed;
7652
+ try {
7653
+ parsed = new URL(value);
7654
+ } catch {
7655
+ return false;
7656
+ }
7657
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
7658
+ return false;
7659
+ return parsed.hostname.length > 0;
7660
+ }
7661
+ function isSvgImageUrl(value) {
7662
+ let parsed;
7663
+ try {
7664
+ parsed = new URL(value);
7665
+ } catch {
7666
+ return false;
7667
+ }
7668
+ let pathname = parsed.pathname;
7669
+ try {
7670
+ pathname = decodeURIComponent(pathname);
7671
+ } catch {
7672
+ }
7673
+ return pathname.toLowerCase().endsWith(".svg");
7674
+ }
7650
7675
  function decodeActionId(actionId) {
7651
7676
  const m = ACTION_ID_RE.exec(actionId);
7652
7677
  if (!m)
@@ -7659,11 +7684,12 @@ function isRoutableActionId(actionId) {
7659
7684
  function isLinkButton(element) {
7660
7685
  return typeof element.url === "string" && element.url.length > 0;
7661
7686
  }
7662
- var ACTION_ID_RE, UNROUTABLE_ACTION_ID_MESSAGE;
7687
+ var SVG_IMAGE_URL_MESSAGE, ACTION_ID_RE, UNROUTABLE_ACTION_ID_MESSAGE;
7663
7688
  var init_slack_block_kit = __esm({
7664
7689
  "../core/dist/channels/slack-block-kit.js"() {
7665
7690
  "use strict";
7666
7691
  init_snooze();
7692
+ SVG_IMAGE_URL_MESSAGE = "Slack cannot render SVG in an image element \u2014 use a PNG, JPG or GIF (our mirrored logos at logos.augmented.team are PNGs)";
7667
7693
  ACTION_ID_RE = /^aug:([0-9a-f-]{36}):([A-Za-z0-9_-]{1,200})$/;
7668
7694
  UNROUTABLE_ACTION_ID_MESSAGE = "interactive buttons must be created with the `request_buttons` tool \u2014 it mints the `aug:<callback-id>:<token>` action_id that makes a tap routable. A hand-written action_id renders fine but its click is silently dropped: the agent is never notified and the buttons are never swapped for the chosen value. For a non-interactive button, set `url` to make it a link button.";
7669
7695
  }
@@ -22683,10 +22709,37 @@ function validateContextBlock(b, path, push) {
22683
22709
  push(`${path}.elements`, "context requires non-empty `elements` array");
22684
22710
  return;
22685
22711
  }
22712
+ if (elements.length > SLACK_LIMITS.contextElements) {
22713
+ push(`${path}.elements`, `max ${SLACK_LIMITS.contextElements} context elements (got ${elements.length})`);
22714
+ }
22686
22715
  elements.forEach((el, i) => {
22687
- validateTextObject(el, `${path}.elements[${i}]`, SLACK_LIMITS.sectionTextChars, ["plain_text", "mrkdwn"], push);
22716
+ const elPath = `${path}.elements[${i}]`;
22717
+ const element = el ?? {};
22718
+ if (element.type === "image") {
22719
+ validateContextImageElement(element, elPath, push);
22720
+ return;
22721
+ }
22722
+ validateTextObject(el, elPath, SLACK_LIMITS.sectionTextChars, ["plain_text", "mrkdwn"], push);
22688
22723
  });
22689
22724
  }
22725
+ function validateContextImageElement(element, elPath, push) {
22726
+ if (typeof element.image_url !== "string" || element.image_url.length === 0) {
22727
+ push(`${elPath}.image_url`, "image element requires a non-empty image_url");
22728
+ } else {
22729
+ if (!isFetchableHttpUrl(element.image_url)) {
22730
+ push(`${elPath}.image_url`, "image_url must be an http(s) url Slack can fetch");
22731
+ }
22732
+ if (element.image_url.length > SLACK_LIMITS.imageUrlChars) {
22733
+ push(`${elPath}.image_url`, `max ${SLACK_LIMITS.imageUrlChars} chars in image_url (got ${element.image_url.length})`);
22734
+ }
22735
+ if (isSvgImageUrl(element.image_url)) {
22736
+ push(`${elPath}.image_url`, SVG_IMAGE_URL_MESSAGE);
22737
+ }
22738
+ }
22739
+ if (typeof element.alt_text !== "string" || element.alt_text.trim().length === 0) {
22740
+ push(`${elPath}.alt_text`, "image element requires non-empty alt_text");
22741
+ }
22742
+ }
22690
22743
  function validateActionsBlock(b, path, push) {
22691
22744
  const elements = b.elements;
22692
22745
  if (!Array.isArray(elements) || elements.length === 0) {
@@ -22860,7 +22913,11 @@ var init_slack_block_kit_runtime = __esm({
22860
22913
  buttonLabelChars: 75,
22861
22914
  identifierChars: 255,
22862
22915
  optionValueChars: 2e3,
22863
- headerTextChars: 150
22916
+ headerTextChars: 150,
22917
+ // ENG-9548: context limits, matching core's SLACK_LIMITS. Pinned equal by
22918
+ // slack-context-image-validation.test.ts so the two cannot drift silently.
22919
+ contextElements: 10,
22920
+ imageUrlChars: 3e3
22864
22921
  };
22865
22922
  SUPPORTED_BLOCK_TYPES = ["header", "section", "divider", "context", "actions"];
22866
22923
  SUPPORTED_ACTION_ELEMENTS = ["button"];
@@ -39133,6 +39190,131 @@ function emitUrlAsteriskStripTelemetry(channel, count = 0) {
39133
39190
  }
39134
39191
  }
39135
39192
 
39193
+ // src/markdown-to-mrkdwn.ts
39194
+ function segmentByCode(text) {
39195
+ const segments = [];
39196
+ let plainStart = 0;
39197
+ let i = 0;
39198
+ const pushPlain = (end) => {
39199
+ if (end > plainStart) segments.push({ text: text.slice(plainStart, end), code: false });
39200
+ };
39201
+ while (i < text.length) {
39202
+ if (text[i] !== "`") {
39203
+ i += 1;
39204
+ continue;
39205
+ }
39206
+ let backslashes = 0;
39207
+ for (let j = i - 1; j >= 0 && text[j] === "\\"; j -= 1) backslashes += 1;
39208
+ if (backslashes % 2 === 1) {
39209
+ i += 1;
39210
+ continue;
39211
+ }
39212
+ let runLen = 1;
39213
+ while (text[i + runLen] === "`") runLen += 1;
39214
+ if (runLen >= 3) {
39215
+ const close = text.indexOf("```", i + runLen);
39216
+ const end2 = close === -1 ? text.length : close + 3;
39217
+ pushPlain(i);
39218
+ segments.push({ text: text.slice(i, end2), code: true });
39219
+ plainStart = end2;
39220
+ i = end2;
39221
+ continue;
39222
+ }
39223
+ const afterOpen = i + runLen;
39224
+ const lineEnd = text.indexOf("\n", afterOpen);
39225
+ const windowEnd = lineEnd === -1 ? text.length : lineEnd;
39226
+ const closeIdx = text.slice(afterOpen, windowEnd).indexOf("`".repeat(runLen));
39227
+ if (closeIdx === -1) {
39228
+ i = afterOpen;
39229
+ continue;
39230
+ }
39231
+ const end = afterOpen + closeIdx + runLen;
39232
+ pushPlain(i);
39233
+ segments.push({ text: text.slice(i, end), code: true });
39234
+ plainStart = end;
39235
+ i = end;
39236
+ }
39237
+ pushPlain(text.length);
39238
+ return segments;
39239
+ }
39240
+ var TARGET = String.raw`(<[^<>]*>|[^()\s]+(?:\([^()\s]*\)[^()\s]*)*)`;
39241
+ var TITLE = String.raw`(?:\s+"[^"]*")?`;
39242
+ var WHOLE_LINE_BLANK_IMAGE = new RegExp(
39243
+ String.raw`^[ \t]*!\[[ \t]*\]\(\s*${TARGET}${TITLE}\s*\)[ \t]*\r?\n?`,
39244
+ "gm"
39245
+ );
39246
+ var INLINE_BLANK_IMAGE = new RegExp(
39247
+ String.raw`!\[[ \t]*\]\(\s*${TARGET}${TITLE}\s*\)[ \t]*`,
39248
+ "g"
39249
+ );
39250
+ var IMAGE = new RegExp(String.raw`!\[([^\]]*)\]\(\s*${TARGET}${TITLE}\s*\)`, "g");
39251
+ var LINK = new RegExp(String.raw`(?<!!)\[([^\]]*)\]\(\s*${TARGET}${TITLE}\s*\)`, "g");
39252
+ function normalizeTarget(raw) {
39253
+ let url = raw.trim();
39254
+ const slackWrapped = /^<(https?:\/\/[^|>\s]+)(?:\|[^>]*)?>$/i.exec(url);
39255
+ if (slackWrapped?.[1]) {
39256
+ url = slackWrapped[1];
39257
+ } else if (url.startsWith("<") && url.endsWith(">")) {
39258
+ url = url.slice(1, -1).trim();
39259
+ }
39260
+ if (!/^(?:https?:\/\/|mailto:)/i.test(url)) return null;
39261
+ if (/[\s<>|]/.test(url)) return null;
39262
+ return url;
39263
+ }
39264
+ function escapeLabel(label) {
39265
+ return label.trim().replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
39266
+ }
39267
+ function convertProse(input, tally) {
39268
+ let out = input;
39269
+ out = out.replace(WHOLE_LINE_BLANK_IMAGE, () => {
39270
+ tally.images += 1;
39271
+ return "";
39272
+ });
39273
+ out = out.replace(INLINE_BLANK_IMAGE, () => {
39274
+ tally.images += 1;
39275
+ return "";
39276
+ });
39277
+ out = out.replace(IMAGE, (match, alt, target) => {
39278
+ const label = escapeLabel(alt);
39279
+ const url = normalizeTarget(target);
39280
+ tally.images += 1;
39281
+ if (!url) return label;
39282
+ return `<${url}|${label}>`;
39283
+ });
39284
+ out = out.replace(LINK, (match, label, target) => {
39285
+ const url = normalizeTarget(target);
39286
+ if (!url) return match;
39287
+ tally.links += 1;
39288
+ const clean = escapeLabel(label);
39289
+ return clean ? `<${url}|${clean}>` : url;
39290
+ });
39291
+ return out;
39292
+ }
39293
+ function markdownToMrkdwn(text) {
39294
+ if (!text || !text.includes("](")) {
39295
+ return { text, converted: false, links: 0, images: 0 };
39296
+ }
39297
+ const tally = { links: 0, images: 0 };
39298
+ const out = segmentByCode(text).map((segment) => segment.code ? segment.text : convertProse(segment.text, tally)).join("");
39299
+ const converted = tally.links > 0 || tally.images > 0;
39300
+ return { text: converted ? out : text, converted, links: tally.links, images: tally.images };
39301
+ }
39302
+ function emitMarkdownToMrkdwnTelemetry(channel, links = 0, images = 0) {
39303
+ const agentCode = process.env.AGT_AGENT_CODE_NAME ?? "unknown";
39304
+ try {
39305
+ process.stderr.write(
39306
+ `agt.egress.markdown_to_mrkdwn_converted ${JSON.stringify({
39307
+ channel,
39308
+ agent_code: agentCode,
39309
+ links,
39310
+ images
39311
+ })}
39312
+ `
39313
+ );
39314
+ } catch {
39315
+ }
39316
+ }
39317
+
39136
39318
  // src/slack-pending-inbound-cleanup.ts
39137
39319
  import { existsSync as existsSync4, readdirSync as readdirSync4, statSync as statSync3, unlinkSync } from "fs";
39138
39320
  import { join as join9 } from "path";
@@ -42739,7 +42921,9 @@ async function processSlackRecoveryOutboxFile(filename) {
42739
42921
  if (recoveredGuarded.redacted) emitToolCallMarkupRedactionTelemetry("slack");
42740
42922
  const apiErr = rewriteTransientApiError(recoveredGuarded.text);
42741
42923
  if (apiErr.rewritten) emitTransientApiErrorTelemetry("slack", apiErr.match, apiErr.original);
42742
- const urlFixed = stripUrlWrappingAsterisks(apiErr.text);
42924
+ const mrkdwn = markdownToMrkdwn(apiErr.text);
42925
+ if (mrkdwn.converted) emitMarkdownToMrkdwnTelemetry("slack", mrkdwn.links, mrkdwn.images);
42926
+ const urlFixed = stripUrlWrappingAsterisks(mrkdwn.text);
42743
42927
  if (urlFixed.stripped) emitUrlAsteriskStripTelemetry("slack", urlFixed.count);
42744
42928
  const text = urlFixed.text;
42745
42929
  const ghostReplyMode = resolveGhostReplyMode();
@@ -44911,7 +45095,7 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
44911
45095
  channel: { type: "string", description: "Slack channel ID" },
44912
45096
  blocks: {
44913
45097
  type: "array",
44914
- description: "Array of Block Kit blocks. Supported block types: header, section, divider, context, actions. Supported interactive elements (inside actions blocks): button. Hard limits: 50 blocks per message, 5 elements per actions block, 3000 chars per section text, 75 chars per button label."
45098
+ description: 'Array of Block Kit blocks. Supported block types: header, section, divider, context, actions. Supported interactive elements (inside actions blocks): button. IMAGES: a context block is the only way to show one \u2014 message text is mrkdwn, which has no inline image syntax, so `![](url)` never renders. Use { type: "image", image_url, alt_text } (alt_text required) alongside an mrkdwn element, one context block per row, since elements flow as a single wrapping line. image_url must be a PNG, JPG or GIF: Slack does not render SVG. For vendor/product logos use our mirror at logos.augmented.team rather than hotlinking the upstream source (upload.wikimedia.org and friends) \u2014 those are the sync scripts\' INPUT, and three had already rotted before the mirror existed. Hard limits: 50 blocks per message, 10 elements per context block, 5 elements per actions block, 3000 chars per section text, 3000 chars per image_url, 75 chars per button label.'
44915
45099
  },
44916
45100
  text: { type: "string", description: "Plain-text fallback for push notifications and unfurls. Required." },
44917
45101
  thread_ts: { type: "string", description: "Thread timestamp for an existing thread reply (from the thread_ts attribute). Safe to omit: for a channel message the server automatically threads your reply off the message you are answering (via message_ts), and a DM reply posts inline." },
@@ -45317,7 +45501,9 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
45317
45501
  if (sanitizedGuarded.redacted) emitToolCallMarkupRedactionTelemetry("slack");
45318
45502
  const apiErr = rewriteTransientApiError(sanitizedGuarded.text);
45319
45503
  if (apiErr.rewritten) emitTransientApiErrorTelemetry("slack", apiErr.match, apiErr.original);
45320
- const urlFixed = stripUrlWrappingAsterisks(apiErr.text);
45504
+ const mrkdwn = markdownToMrkdwn(apiErr.text);
45505
+ if (mrkdwn.converted) emitMarkdownToMrkdwnTelemetry("slack", mrkdwn.links, mrkdwn.images);
45506
+ const urlFixed = stripUrlWrappingAsterisks(mrkdwn.text);
45321
45507
  if (urlFixed.stripped) emitUrlAsteriskStripTelemetry("slack", urlFixed.count);
45322
45508
  try {
45323
45509
  const res = await fetch("https://slack.com/api/chat.postMessage", {
@@ -7647,6 +7647,31 @@ var init_snooze = __esm({
7647
7647
  });
7648
7648
 
7649
7649
  // ../core/dist/channels/slack-block-kit.js
7650
+ function isFetchableHttpUrl(value) {
7651
+ let parsed;
7652
+ try {
7653
+ parsed = new URL(value);
7654
+ } catch {
7655
+ return false;
7656
+ }
7657
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
7658
+ return false;
7659
+ return parsed.hostname.length > 0;
7660
+ }
7661
+ function isSvgImageUrl(value) {
7662
+ let parsed;
7663
+ try {
7664
+ parsed = new URL(value);
7665
+ } catch {
7666
+ return false;
7667
+ }
7668
+ let pathname = parsed.pathname;
7669
+ try {
7670
+ pathname = decodeURIComponent(pathname);
7671
+ } catch {
7672
+ }
7673
+ return pathname.toLowerCase().endsWith(".svg");
7674
+ }
7650
7675
  function decodeActionId(actionId) {
7651
7676
  const m = ACTION_ID_RE.exec(actionId);
7652
7677
  if (!m)
@@ -7659,11 +7684,12 @@ function isRoutableActionId(actionId) {
7659
7684
  function isLinkButton(element) {
7660
7685
  return typeof element.url === "string" && element.url.length > 0;
7661
7686
  }
7662
- var ACTION_ID_RE, UNROUTABLE_ACTION_ID_MESSAGE;
7687
+ var SVG_IMAGE_URL_MESSAGE, ACTION_ID_RE, UNROUTABLE_ACTION_ID_MESSAGE;
7663
7688
  var init_slack_block_kit = __esm({
7664
7689
  "../core/dist/channels/slack-block-kit.js"() {
7665
7690
  "use strict";
7666
7691
  init_snooze();
7692
+ SVG_IMAGE_URL_MESSAGE = "Slack cannot render SVG in an image element \u2014 use a PNG, JPG or GIF (our mirrored logos at logos.augmented.team are PNGs)";
7667
7693
  ACTION_ID_RE = /^aug:([0-9a-f-]{36}):([A-Za-z0-9_-]{1,200})$/;
7668
7694
  UNROUTABLE_ACTION_ID_MESSAGE = "interactive buttons must be created with the `request_buttons` tool \u2014 it mints the `aug:<callback-id>:<token>` action_id that makes a tap routable. A hand-written action_id renders fine but its click is silently dropped: the agent is never notified and the buttons are never swapped for the chosen value. For a non-interactive button, set `url` to make it a link button.";
7669
7695
  }
@@ -22683,10 +22709,37 @@ function validateContextBlock(b, path, push) {
22683
22709
  push(`${path}.elements`, "context requires non-empty `elements` array");
22684
22710
  return;
22685
22711
  }
22712
+ if (elements.length > SLACK_LIMITS.contextElements) {
22713
+ push(`${path}.elements`, `max ${SLACK_LIMITS.contextElements} context elements (got ${elements.length})`);
22714
+ }
22686
22715
  elements.forEach((el, i) => {
22687
- validateTextObject(el, `${path}.elements[${i}]`, SLACK_LIMITS.sectionTextChars, ["plain_text", "mrkdwn"], push);
22716
+ const elPath = `${path}.elements[${i}]`;
22717
+ const element = el ?? {};
22718
+ if (element.type === "image") {
22719
+ validateContextImageElement(element, elPath, push);
22720
+ return;
22721
+ }
22722
+ validateTextObject(el, elPath, SLACK_LIMITS.sectionTextChars, ["plain_text", "mrkdwn"], push);
22688
22723
  });
22689
22724
  }
22725
+ function validateContextImageElement(element, elPath, push) {
22726
+ if (typeof element.image_url !== "string" || element.image_url.length === 0) {
22727
+ push(`${elPath}.image_url`, "image element requires a non-empty image_url");
22728
+ } else {
22729
+ if (!isFetchableHttpUrl(element.image_url)) {
22730
+ push(`${elPath}.image_url`, "image_url must be an http(s) url Slack can fetch");
22731
+ }
22732
+ if (element.image_url.length > SLACK_LIMITS.imageUrlChars) {
22733
+ push(`${elPath}.image_url`, `max ${SLACK_LIMITS.imageUrlChars} chars in image_url (got ${element.image_url.length})`);
22734
+ }
22735
+ if (isSvgImageUrl(element.image_url)) {
22736
+ push(`${elPath}.image_url`, SVG_IMAGE_URL_MESSAGE);
22737
+ }
22738
+ }
22739
+ if (typeof element.alt_text !== "string" || element.alt_text.trim().length === 0) {
22740
+ push(`${elPath}.alt_text`, "image element requires non-empty alt_text");
22741
+ }
22742
+ }
22690
22743
  function validateActionsBlock(b, path, push) {
22691
22744
  const elements = b.elements;
22692
22745
  if (!Array.isArray(elements) || elements.length === 0) {
@@ -22860,7 +22913,11 @@ var init_slack_block_kit_runtime = __esm({
22860
22913
  buttonLabelChars: 75,
22861
22914
  identifierChars: 255,
22862
22915
  optionValueChars: 2e3,
22863
- headerTextChars: 150
22916
+ headerTextChars: 150,
22917
+ // ENG-9548: context limits, matching core's SLACK_LIMITS. Pinned equal by
22918
+ // slack-context-image-validation.test.ts so the two cannot drift silently.
22919
+ contextElements: 10,
22920
+ imageUrlChars: 3e3
22864
22921
  };
22865
22922
  SUPPORTED_BLOCK_TYPES = ["header", "section", "divider", "context", "actions"];
22866
22923
  SUPPORTED_ACTION_ELEMENTS = ["button"];
@@ -43,8 +43,8 @@ import {
43
43
  writeDirectChatSessionState,
44
44
  writeEgressAllowlist,
45
45
  writePersistentClaudeWrapper
46
- } from "./chunk-326G325O.js";
47
- import "./chunk-QRGNDW6C.js";
46
+ } from "./chunk-VYG4CTEQ.js";
47
+ import "./chunk-NOGQSRQM.js";
48
48
  import "./chunk-XWVM4KPK.js";
49
49
  export {
50
50
  EGRESS_BASELINE_DOMAINS,
@@ -92,4 +92,4 @@ export {
92
92
  writeEgressAllowlist,
93
93
  writePersistentClaudeWrapper
94
94
  };
95
- //# sourceMappingURL=persistent-session-TPP35O6H.js.map
95
+ //# sourceMappingURL=persistent-session-GIRGRUMF.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  paneLogPath
3
- } from "./chunk-326G325O.js";
4
- import "./chunk-QRGNDW6C.js";
3
+ } from "./chunk-VYG4CTEQ.js";
4
+ import "./chunk-NOGQSRQM.js";
5
5
  import "./chunk-XWVM4KPK.js";
6
6
 
7
7
  // src/lib/responsiveness-probe.ts
@@ -745,4 +745,4 @@ export {
745
745
  readAndResetSlackReplyBindingClassifications,
746
746
  readAndResetSlackReplyTargetClassifications
747
747
  };
748
- //# sourceMappingURL=responsiveness-probe-3HITHSYU.js.map
748
+ //# sourceMappingURL=responsiveness-probe-PIRS3LTX.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  sessionTranscriptDir
3
- } from "./chunk-QRGNDW6C.js";
3
+ } from "./chunk-NOGQSRQM.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-LY5264IN.js.map
206
+ //# sourceMappingURL=session-auth-dead-PYWTMI43.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@integrity-labs/agt-cli",
3
- "version": "0.28.706",
3
+ "version": "0.28.708",
4
4
  "description": "Augmented Team CLI — agent provisioning and management",
5
5
  "type": "module",
6
6
  "engines": {