@agentconnect.md/daemon 1.0.0-rc.100 → 1.0.0-rc.101

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.
package/dist/index.js CHANGED
@@ -24896,7 +24896,7 @@ const SLACK_TOOLS = [
24896
24896
  inputSchema: obj({
24897
24897
  text: {
24898
24898
  type: "string",
24899
- description: "Message body, in Slack mrkdwn."
24899
+ description: "Message body, in standard Markdown (CommonMark/GFM). Use `**bold**`, `~~strikethrough~~`, `# headings`, tables, etc. — Slack renders standard Markdown natively (do NOT use single-char Slack mrkdwn like `*bold*` or `~strike~`)."
24900
24900
  },
24901
24901
  channel: {
24902
24902
  type: "string",
@@ -80973,7 +80973,7 @@ var import_dist = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((expor
80973
80973
  }
80974
80974
  });
80975
80975
  })))(), 1);
80976
- const MENTION_RE = /<@([A-Z0-9]+)>/g;
80976
+ const MENTION_RE$1 = /<@([A-Z0-9]+)>/g;
80977
80977
  /** Map a Slack file element to a NormalizedMessage Attachment, dropping any
80978
80978
  * without a usable id + fetch URL (e.g. external/tombstoned files). Tolerates a
80979
80979
  * malformed (null / non-object) element off the wire without throwing. */
@@ -80991,7 +80991,7 @@ function toAttachment(f) {
80991
80991
  }
80992
80992
  function normalizeSlackEvent(event, ctx) {
80993
80993
  const text = event.text ?? "";
80994
- const mentionedBots = [...text.matchAll(MENTION_RE)].map((m) => m[1]);
80994
+ const mentionedBots = [...text.matchAll(MENTION_RE$1)].map((m) => m[1]);
80995
80995
  const attachments = (event.files ?? []).map(toAttachment).filter((a) => a !== null);
80996
80996
  return {
80997
80997
  msgId: `slack:${event.channel}:${event.ts}`,
@@ -81098,6 +81098,21 @@ function consolidate(agents) {
81098
81098
  }
81099
81099
  /** Cap on members enriched per `listChannelMembers` call (bounds users.info fan-out). */
81100
81100
  const MEMBER_ENRICH_CAP = 50;
81101
+ /**
81102
+ * Build a chat.postMessage/update payload that renders the body as a Block Kit
81103
+ * `markdown` block — Slack renders standard CommonMark there natively, so the agent's
81104
+ * markdown is sent verbatim (no markdown→mrkdwn conversion). `text` is kept as the
81105
+ * notification/accessibility fallback. Callers pre-chunk to SLACK_MARKDOWN_BLOCK_LIMIT.
81106
+ */
81107
+ function markdownBlock(text) {
81108
+ return {
81109
+ text,
81110
+ blocks: [{
81111
+ type: "markdown",
81112
+ text
81113
+ }]
81114
+ };
81115
+ }
81101
81116
  var SlackConnection = class {
81102
81117
  deps;
81103
81118
  app;
@@ -81170,8 +81185,8 @@ var SlackConnection = class {
81170
81185
  return this.queue.enqueue(async () => {
81171
81186
  return (await this.app.client.chat.postMessage({
81172
81187
  channel,
81173
- text,
81174
- thread_ts: threadTs
81188
+ thread_ts: threadTs,
81189
+ ...markdownBlock(text)
81175
81190
  }))?.ts;
81176
81191
  });
81177
81192
  }
@@ -81183,7 +81198,7 @@ var SlackConnection = class {
81183
81198
  await this.app.client.chat.update({
81184
81199
  channel,
81185
81200
  ts,
81186
- text
81201
+ ...markdownBlock(text)
81187
81202
  });
81188
81203
  } catch (err) {
81189
81204
  this.deps.log?.debug(`slack: chat.update failed (ch=${channel} ts=${ts}): ${err.message}`);
@@ -81356,6 +81371,31 @@ var SlackConnection = class {
81356
81371
  }
81357
81372
  };
81358
81373
  //#endregion
81374
+ //#region src/slack/mentions.ts
81375
+ /**
81376
+ * Slack `<@U…>` user-mention helpers, shared by the name-resolver (which warms the
81377
+ * display-name cache for mentioned users) and the session reader (which rewrites
81378
+ * mentions to `@name` for read-back). Slack encodes a user mention as `<@U123>` or
81379
+ * `<@U123|label>`; the id is a `U…`/`W…` user id (resolvable via users.info even for
81380
+ * a bot's user id, unlike a bot SENDER's `B…` id).
81381
+ */
81382
+ const MENTION_RE = /<@([UW][A-Z0-9]+)(?:\|[^>]*)?>/g;
81383
+ /** Distinct `U…/W…` user ids mentioned in a message body (empty for none/nullish). */
81384
+ function mentionedUserIds(text) {
81385
+ if (!text) return [];
81386
+ const ids = /* @__PURE__ */ new Set();
81387
+ for (const m of text.matchAll(MENTION_RE)) ids.add(m[1]);
81388
+ return [...ids];
81389
+ }
81390
+ /** Rewrite `<@U123>` / `<@U123|x>` to `@<display name>` when the id is known; an
81391
+ * unknown id is left as the raw token so the web renderer's own fallback applies. */
81392
+ function substituteUserMentions(text, names) {
81393
+ return text.replace(MENTION_RE, (raw, id) => {
81394
+ const name = names.get(id);
81395
+ return name ? `@${name}` : raw;
81396
+ });
81397
+ }
81398
+ //#endregion
81359
81399
  //#region src/slack/name-resolver.ts
81360
81400
  /**
81361
81401
  * Resolves Slack ids (channel "C…", user "U…/W…") to human display names off the
@@ -81385,10 +81425,14 @@ var SlackNameResolver = class {
81385
81425
  this.log = log;
81386
81426
  this.now = now;
81387
81427
  }
81388
- /** Kick off (cached) resolution for a message's channel + human sender. */
81428
+ /** Kick off (cached) resolution for a message's channel, human sender, and any
81429
+ * `<@U…>` user mentions in its body — so session read-back can render mentions
81430
+ * as `@name` (not the raw id). Mention ids use the `U…/W…` user id (resolvable
81431
+ * via users.info even for a bot's user id), unlike a bot SENDER's `B…` id. */
81389
81432
  noteMessage(conn, msg) {
81390
81433
  this.resolveChannel(conn, msg.channel);
81391
81434
  if (!msg.sender.isBot && /^[UW]/.test(msg.sender.id)) this.resolveUser(conn, msg.sender.id);
81435
+ for (const id of mentionedUserIds(msg.text)) this.resolveUser(conn, id);
81392
81436
  }
81393
81437
  claim(id) {
81394
81438
  const at = this.nextAttemptAt.get(id);
@@ -81426,61 +81470,22 @@ var SlackNameResolver = class {
81426
81470
  //#endregion
81427
81471
  //#region src/slack/formatter.ts
81428
81472
  /**
81429
- * Last-mile Slack formatting (§9.1 / §9.3). Two pure helpers:
81430
- * - markdownToMrkdwn: convert the agent's CommonMark-ish output to Slack mrkdwn.
81431
- * - splitIntoSections: break a message into <= maxLen sections (Slack ~3000/section).
81432
- * Both are platform-specific; the upstream convergence (ACP->IR) stays platform-agnostic.
81433
- */
81434
- /** Slack section soft cap (§9.3: Slack splits at ~3000 chars per section). */
81435
- const SLACK_SECTION_LIMIT = 3e3;
81436
- const CODE_SENTINEL = "\0";
81437
- const BOLD = "";
81438
- /** Replace fenced ```code``` and inline `code` with placeholders so inline
81439
- * formatting rules never fire inside them; returns the masked text + restorer. */
81440
- function protectCode(input) {
81441
- const spans = [];
81442
- const masked = input.replace(/```[\s\S]*?```|`[^`\n]*`/g, (m) => {
81443
- return `${CODE_SENTINEL}${spans.push(m) - 1}${CODE_SENTINEL}`;
81444
- });
81445
- const restore = (s) => s.replace(new RegExp(`${CODE_SENTINEL}(\\d+)${CODE_SENTINEL}`, "g"), (_, i) => spans[Number(i)] ?? "");
81446
- return {
81447
- masked,
81448
- restore
81449
- };
81450
- }
81451
- /**
81452
- * Convert common Markdown to Slack mrkdwn. Handled: bold (`**`/`__` -> `*`),
81453
- * italic (`*` -> `_`; bare `_` is already valid mrkdwn italic and left alone to
81454
- * avoid mangling snake_case), strikethrough (`~~` -> `~`), links (`[t](u)` ->
81455
- * `<u|t>`), ATX headings (`# H` -> `*H*`), and `-`/`*`/`+` bullets -> `•`. Code
81456
- * spans and fences are preserved verbatim.
81473
+ * Last-mile Slack formatting (§9.1 / §9.3). One pure helper:
81474
+ * - splitIntoSections: break a message into <= maxLen sections.
81457
81475
  *
81458
- * Best-effort, not a full CommonMark parser: pathological overlapping emphasis
81459
- * (e.g. `***a** b*`, or a lone `*` inside `**bold**`) can still render imperfectly.
81460
- * Such inputs are rare in agent output and only affect cosmetics — never content.
81476
+ * The daemon posts the agent's reply as a Block Kit `markdown` block (which renders
81477
+ * standard CommonMark natively), so NO markdown→mrkdwn conversion happens here — the
81478
+ * agent's text is sent verbatim. We only chunk it: a single `markdown` block caps at
81479
+ * 12000 chars, so a long body is split across blocks/messages on line boundaries.
81461
81480
  */
81462
- function markdownToMrkdwn(input) {
81463
- if (!input) return input;
81464
- const { masked, restore } = protectCode(input);
81465
- let out = masked;
81466
- out = out.replace(/^[ \t]*#{1,6}[ \t]+(.+?)[ \t]*#*$/gm, (_m, h) => `${BOLD}${h.replace(/\*+|__/g, "")}${BOLD}`);
81467
- out = out.replace(/^([ \t]*)[-*+][ \t]+/gm, "$1• ");
81468
- out = out.replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g, "<$2|$1>");
81469
- out = out.replace(/\*\*\*(?!\s)([^\n]+?)(?<!\s)\*\*\*/g, `${BOLD}_$1_${BOLD}`);
81470
- out = out.replace(/___(?!\s)([^\n]+?)(?<!\s)___/g, `${BOLD}_$1_${BOLD}`);
81471
- out = out.replace(/\*\*(?!\s)([^\n]+?)(?<!\s)\*\*/g, `${BOLD}$1${BOLD}`);
81472
- out = out.replace(/__(?!\s)([^\n]+?)(?<!\s)__/g, `${BOLD}$1${BOLD}`);
81473
- out = out.replace(/\*(?!\s)([^*\n]+?)(?<!\s)\*/g, "_$1_");
81474
- out = out.split(BOLD).join("*");
81475
- out = out.replace(/~~([^\n]+?)~~/g, "~$1~");
81476
- return restore(out);
81477
- }
81481
+ /** Slack `markdown` block hard cap (12000 chars per block). */
81482
+ const SLACK_MARKDOWN_BLOCK_LIMIT = 12e3;
81478
81483
  /**
81479
81484
  * Split text into sections no longer than maxLen, preferring line boundaries;
81480
81485
  * a single line longer than maxLen is hard-cut. Whitespace-only input yields no
81481
81486
  * sections. Content is never lost across sections.
81482
81487
  */
81483
- function splitIntoSections(text, maxLen = SLACK_SECTION_LIMIT) {
81488
+ function splitIntoSections(text, maxLen = SLACK_MARKDOWN_BLOCK_LIMIT) {
81484
81489
  if (!text.trim()) return [];
81485
81490
  if (text.length <= maxLen) return [text];
81486
81491
  const sections = [];
@@ -81520,9 +81525,10 @@ function planIcon(status) {
81520
81525
  if (status === "in_progress") return ":hourglass_flowing_sand:";
81521
81526
  return ":white_large_square:";
81522
81527
  }
81523
- /** Render an ACP plan (full entry list, resent on every update) as a compact summary. */
81528
+ /** Render an ACP plan (full entry list, resent on every update) as a compact summary.
81529
+ * Posted as a `markdown` block, so emphasis is CommonMark (`**bold**`), not mrkdwn. */
81524
81530
  function renderPlan(entries) {
81525
- return [":clipboard: *Plan*", ...entries.map((e) => `${planIcon(e.status)} ${clampLabel(e.content ?? "")}`)].join("\n");
81531
+ return [":clipboard: **Plan**", ...entries.map((e) => `${planIcon(e.status)} ${clampLabel(e.content ?? "")}`)].join("\n");
81526
81532
  }
81527
81533
  var OutputConverger = class {
81528
81534
  mode;
@@ -81537,8 +81543,9 @@ var OutputConverger = class {
81537
81543
  hasBuffered() {
81538
81544
  return this.buf.trim().length > 0;
81539
81545
  }
81540
- /** Flush the buffered body: markdown→mrkdwn, then split into ≤3000-char sections,
81541
- * one `post` per section (§9.1/§9.3). Public so the daemon's idle timer can call it. */
81546
+ /** Flush the buffered body: split the agent's verbatim markdown into ≤block-limit
81547
+ * sections, one `post` per section (each posted as a Block Kit `markdown` block, so
81548
+ * no mrkdwn conversion). Public so the daemon's idle timer can call it. */
81542
81549
  flushBuffered() {
81543
81550
  return this.flush();
81544
81551
  }
@@ -81546,7 +81553,7 @@ var OutputConverger = class {
81546
81553
  const text = this.buf;
81547
81554
  this.buf = "";
81548
81555
  if (!text.trim()) return [];
81549
- return splitIntoSections(markdownToMrkdwn(text)).map((t) => ({
81556
+ return splitIntoSections(text).map((t) => ({
81550
81557
  kind: "post",
81551
81558
  text: t
81552
81559
  }));
@@ -81637,7 +81644,7 @@ var OutputConverger = class {
81637
81644
  if (this.mode === "low") return [...this.flush(), clear];
81638
81645
  const footer = link ? [{
81639
81646
  kind: "notice",
81640
- text: `:white_check_mark: done — <${link}|details>`
81647
+ text: `:white_check_mark: done — [details](${link})`
81641
81648
  }] : [];
81642
81649
  return [
81643
81650
  ...this.flush(),
@@ -83293,11 +83300,16 @@ function createSessionReader(store) {
83293
83300
  return {
83294
83301
  list(req) {
83295
83302
  const rows = store.listSessions(req.agentId);
83296
- const names = store.getDisplayNames(rows.flatMap((r) => [r.channel, ...r.triggeredBy ? [r.triggeredBy] : []]));
83303
+ const names = store.getDisplayNames(rows.flatMap((r) => [
83304
+ r.channel,
83305
+ ...r.triggeredBy ? [r.triggeredBy] : [],
83306
+ ...mentionedUserIds(r.title)
83307
+ ]));
83297
83308
  return { sessions: rows.map((r) => {
83298
83309
  const usage = parseUsage(r.usage);
83299
83310
  const channelName = names.get(r.channel);
83300
83311
  const triggeredByName = r.triggeredBy ? names.get(r.triggeredBy) : void 0;
83312
+ const title = r.title ? substituteUserMentions(r.title, names) : void 0;
83301
83313
  return {
83302
83314
  sessionId: r.acpSessionId,
83303
83315
  sessionKey: {
@@ -83309,7 +83321,7 @@ function createSessionReader(store) {
83309
83321
  status: r.state,
83310
83322
  lastActivityAt: new Date(r.updatedAt).toISOString(),
83311
83323
  ...usage ? { usage } : {},
83312
- ...r.title ? { title: r.title } : {},
83324
+ ...title ? { title } : {},
83313
83325
  ...r.triggeredBy ? { triggeredBy: r.triggeredBy } : {},
83314
83326
  ...channelName ? { channelName } : {},
83315
83327
  ...triggeredByName ? { triggeredByName } : {}
@@ -83325,7 +83337,7 @@ function createSessionReader(store) {
83325
83337
  const before = req.cursor != null && Number.isFinite(Number(req.cursor)) ? Number(req.cursor) : null;
83326
83338
  const { rows, hasMore } = store.transcriptPage(rec.channel, rec.thread, before, req.limit);
83327
83339
  const ordered = rows.slice().reverse();
83328
- const names = store.getDisplayNames(ordered.map((r) => r.sender));
83340
+ const names = store.getDisplayNames(ordered.flatMap((r) => [r.sender, ...mentionedUserIds(r.text)]));
83329
83341
  const built = ordered.map((r) => {
83330
83342
  const senderName = names.get(r.sender);
83331
83343
  const base = {
@@ -83334,7 +83346,7 @@ function createSessionReader(store) {
83334
83346
  ...senderName ? { senderName } : {},
83335
83347
  ts: r.ts,
83336
83348
  kind: r.kind,
83337
- text: r.text
83349
+ text: substituteUserMentions(r.text, names)
83338
83350
  };
83339
83351
  if (r.kind !== "tool" || !r.body) return base;
83340
83352
  let full;