@integrity-labs/agt-cli 0.28.551 → 0.28.553

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/mcp/index.js CHANGED
@@ -22456,7 +22456,7 @@ server.tool(
22456
22456
  );
22457
22457
  server.tool(
22458
22458
  "kanban_assign",
22459
- "Delegate a task to ANOTHER agent by placing a new card on THEIR board. Use this to hand off or route work to a teammate agent (e.g. you triage a request and route the fix to a specialist agent). This is different from kanban_add, which adds a card to YOUR OWN board. By default the target must be an agent on your OWN team. To assign across teams within your organization, also pass `target_team` (the other team's slug) \u2014 this only works if your org has enabled cross-team assignment and a peer grant links the two teams; otherwise it reads as not-found. When the target finishes, you get a completion notice back on your board. Don't know the agent's code_name or team slug? Call directory_lookup first to find them.",
22459
+ 'Delegate a task to ANOTHER agent by placing a new card on THEIR board. Use this to hand off or route work to a teammate agent (e.g. you triage a request and route the fix to a specialist agent). This is different from kanban_add, which adds a card to YOUR OWN board. By default the target must be an agent on your OWN team. To assign across teams within your organization, also pass `target_team` (the other team\'s slug) \u2014 this only works if your org has enabled cross-team assignment and a peer grant links the two teams; otherwise it reads as not-found. When the target finishes, you get a completion notice back on your board. Delegating a ticket? Pass `source_integration` + `source_external_id` (e.g. "composio/linear" / "ENG-123") so the card is linked to it and completing the card pushes status back to the issue \u2014 putting the URL in the description only leaves the link in prose. Don\'t know the agent\'s code_name or team slug? Call directory_lookup first to find them.',
22460
22460
  {
22461
22461
  target_agent: external_exports.string().describe(
22462
22462
  "The teammate to assign to: their agent code_name (kebab-case) or agent UUID. On your own team unless target_team is also set."
@@ -22473,7 +22473,16 @@ server.tool(
22473
22473
  deliverable: external_exports.string().optional().describe("Expected output/deliverable from the target"),
22474
22474
  deliver_to: external_exports.string().trim().regex(/^[UW][A-Z0-9]{6,}$/, "deliver_to must be a Slack user id (e.g. U01234567)").optional().describe(
22475
22475
  "Optional: a Slack user id (e.g. U01234567) to DM the completion notice to directly when the target finishes, in ADDITION to the notice landing on your board. Use this to deliver the result straight to the person who wanted it (e.g. the requester's Slack id from the originating message) instead of hand-forwarding it yourself. Must be a Slack user id, not a @handle or display name."
22476
- )
22476
+ ),
22477
+ // ENG-8649: the source linkage kanban_add has always had. Pass BOTH or
22478
+ // neither — one alone is rejected.
22479
+ source_integration: external_exports.string().optional().describe(
22480
+ 'Integration the work came from \u2014 the toolkit slug, e.g. "composio/linear" or "composio/jira". A bare name ("linear") is accepted and normalised. Pass together with source_external_id when you are delegating a ticket, so completing the card pushes the status back to the issue instead of the link living only in prose. Status write-back only happens for integrations with an import adapter (linear, jira, asana, trello, notion); for anything else the linkage is recorded but nothing is pushed upstream.'
22481
+ ),
22482
+ source_external_id: external_exports.string().optional().describe(
22483
+ 'ID in the external system (e.g. "ENG-123"). Required alongside source_integration.'
22484
+ ),
22485
+ source_url: external_exports.string().optional().describe("Deep link to the external source (e.g. the Linear issue URL).")
22477
22486
  },
22478
22487
  async (params) => {
22479
22488
  try {
@@ -22493,7 +22502,12 @@ server.tool(
22493
22502
  ...params.status !== void 0 ? { status: params.status } : {},
22494
22503
  deliverable: params.deliverable,
22495
22504
  // CS-1438: only sent when set; the route treats absence as board-ack-only.
22496
- ...params.deliver_to !== void 0 ? { deliver_to: params.deliver_to } : {}
22505
+ ...params.deliver_to !== void 0 ? { deliver_to: params.deliver_to } : {},
22506
+ // ENG-8649: only sent when set, so an existing caller's request body is
22507
+ // byte-for-byte unchanged and the route keeps its channel-origin path.
22508
+ ...params.source_integration !== void 0 ? { source_integration: params.source_integration } : {},
22509
+ ...params.source_external_id !== void 0 ? { source_external_id: params.source_external_id } : {},
22510
+ ...params.source_url !== void 0 ? { source_url: params.source_url } : {}
22497
22511
  });
22498
22512
  const where = data.cross_team ? ` on team ${data.target_team ?? params.target_team}` : "";
22499
22513
  const inOrg = data.organization_name ? ` in ${data.organization_name}` : "";
@@ -37442,6 +37442,47 @@ var SLACK_EGRESS_TOOLS = /* @__PURE__ */ new Set([
37442
37442
  "slack.update"
37443
37443
  ]);
37444
37444
 
37445
+ // ../core/dist/channels/governance/slack-ingress-only-threads.js
37446
+ function buildIngressThreadKey(channel, threadTs) {
37447
+ if (!channel || !threadTs)
37448
+ return null;
37449
+ return `${channel}:${threadTs}`;
37450
+ }
37451
+ function createIngressOnlyThreadRegistry(maxEntries = 500) {
37452
+ const marked = /* @__PURE__ */ new Set();
37453
+ return {
37454
+ mark(channel, threadTs) {
37455
+ const key2 = buildIngressThreadKey(channel, threadTs);
37456
+ if (!key2)
37457
+ return;
37458
+ marked.delete(key2);
37459
+ marked.add(key2);
37460
+ while (marked.size > maxEntries) {
37461
+ const oldest = marked.values().next();
37462
+ if (oldest.done)
37463
+ break;
37464
+ marked.delete(oldest.value);
37465
+ }
37466
+ },
37467
+ clear(channel, threadTs) {
37468
+ const key2 = buildIngressThreadKey(channel, threadTs);
37469
+ if (!key2)
37470
+ return;
37471
+ marked.delete(key2);
37472
+ },
37473
+ isIngressOnly(channel, threadTs) {
37474
+ const key2 = buildIngressThreadKey(channel, threadTs);
37475
+ if (!key2)
37476
+ return false;
37477
+ return marked.has(key2);
37478
+ },
37479
+ size() {
37480
+ return marked.size;
37481
+ }
37482
+ };
37483
+ }
37484
+ var INGRESS_ONLY_REFUSAL = "This thread was opened by a third-party bot (an alert or issue card), which can wake you but is not someone you can talk to - replying would start a bot-to-bot exchange in a shared channel. Act on the content instead: work the issue, or raise it with a person in a thread they are in. If a human posts here, this thread becomes repliable.";
37485
+
37445
37486
  // src/strip-repost-preamble.ts
37446
37487
  var PRIOR_ATTEMPT_CUE = /\b(a moment ago|just now|a second ago|earlier|moments ago|previously|last (?:time|attempt)|drafted|dialled|dialed|typed|composed|wrote|sent) (?:this|that|it)?\b|\b(?:drafted|dialled|dialed|typed|composed)\b/i;
37447
37488
  var FAILURE_CUE = /\b(?:did(?:n['’]t| not)|could(?:n['’]t| not)|would(?:n['’]t| not)|wasn['’]t|fail(?:ed)?|never)\b[^.!?\n]*?\b(?:parse[d]?|send|sent|go through|went through|land|deliver(?:ed)?|post(?:ed)?|come through|came through)\b/i;
@@ -39956,6 +39997,16 @@ function classifyPeerMessage(msg, cfg, self) {
39956
39997
  }
39957
39998
  const peer = cfg.peers.find((p2) => p2.bot_user_id === msg.user);
39958
39999
  if (!peer) {
40000
+ const thirdParty = cfg.third_party_bots?.find((b) => b.bot_user_id === msg.user);
40001
+ if (thirdParty) {
40002
+ if (cfg.peer_disabled_mode === "cross_team_only") {
40003
+ return { kind: "drop", reason: "peer_disabled_cross_team" };
40004
+ }
40005
+ if (thirdParty.channel_ids.includes(msg.channel)) {
40006
+ return { kind: "bot-ingress", bot: thirdParty };
40007
+ }
40008
+ return { kind: "drop", reason: "third_party_channel_not_allowlisted" };
40009
+ }
39959
40010
  return { kind: "drop", reason: "unknown_peer" };
39960
40011
  }
39961
40012
  if (peer.gate_path === null) {
@@ -40063,6 +40114,33 @@ function parsePeerGroupIdsEnv(raw) {
40063
40114
  return [];
40064
40115
  return raw.split(",").map((s) => s.trim()).filter(Boolean);
40065
40116
  }
40117
+ function parseThirdPartyBotsEnv(raw) {
40118
+ if (!raw || !raw.trim())
40119
+ return [];
40120
+ let parsed;
40121
+ try {
40122
+ parsed = JSON.parse(raw);
40123
+ } catch {
40124
+ return [];
40125
+ }
40126
+ if (!Array.isArray(parsed))
40127
+ return [];
40128
+ const out = [];
40129
+ for (const item of parsed) {
40130
+ if (!item || typeof item !== "object")
40131
+ continue;
40132
+ const rec = item;
40133
+ const botUserId2 = typeof rec["bot_user_id"] === "string" ? rec["bot_user_id"].trim() : "";
40134
+ if (!botUserId2)
40135
+ continue;
40136
+ if (!Array.isArray(rec["channel_ids"]))
40137
+ continue;
40138
+ const channels = rec["channel_ids"].filter((c) => typeof c === "string" && c.trim() !== "").map((c) => c.trim());
40139
+ const label = typeof rec["label"] === "string" && rec["label"].trim() ? rec["label"].trim() : void 0;
40140
+ out.push({ bot_user_id: botUserId2, channel_ids: channels, ...label ? { label } : {} });
40141
+ }
40142
+ return out;
40143
+ }
40066
40144
  function parsePeerAgentModeEnv(raw) {
40067
40145
  if (raw === "listen" || raw === "respond")
40068
40146
  return raw;
@@ -41074,8 +41152,14 @@ var SLACK_PEER_CLASSIFIER_CONFIG = {
41074
41152
  peer_agent_mode: parsePeerAgentModeEnv(process.env.SLACK_PEER_AGENT_MODE),
41075
41153
  peer_group_ids: parsePeerGroupIdsEnv(process.env.SLACK_PEER_GROUP_IDS),
41076
41154
  peers: parsePeersEnv(process.env.SLACK_PEERS, process.env.SLACK_PEERS_GATE),
41077
- peer_disabled_mode: SLACK_PEER_DISABLED_MODE
41155
+ peer_disabled_mode: SLACK_PEER_DISABLED_MODE,
41156
+ // ENG-8669: third-party bots (Linear, Amazon Q, CloudWatch, PagerDuty) that
41157
+ // may WAKE this agent, per channel. Absent env = empty = the pre-ENG-8669
41158
+ // behaviour exactly, so this ships inert for every agent that has not opted
41159
+ // in. Ingress-only by construction - see isReplyEligible in the classifier.
41160
+ third_party_bots: parseThirdPartyBotsEnv(process.env.SLACK_THIRD_PARTY_BOTS)
41078
41161
  };
41162
+ var ingressOnlyThreads = createIngressOnlyThreadRegistry();
41079
41163
  var SLACK_TEAM_PEER_USER_IDS = parseTeamPeerUserIdsEnv(
41080
41164
  process.env.SLACK_TEAM_PEER_USER_IDS
41081
41165
  );
@@ -43815,6 +43899,16 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
43815
43899
  if (isImpersonating() && !channelsEnabledOverride() && SLACK_EGRESS_TOOLS.has(name)) {
43816
43900
  return buildImpersonationRefusal(name);
43817
43901
  }
43902
+ if (SLACK_EGRESS_TOOLS.has(name)) {
43903
+ const egressArgs = args ?? {};
43904
+ const targetThread = egressArgs.thread_ts ?? egressArgs.message_ts;
43905
+ if (ingressOnlyThreads.isIngressOnly(egressArgs.channel, targetThread)) {
43906
+ return {
43907
+ content: [{ type: "text", text: INGRESS_ONLY_REFUSAL }],
43908
+ isError: true
43909
+ };
43910
+ }
43911
+ }
43818
43912
  if (name === "slack.reply") {
43819
43913
  const { channel, text, thread_ts, message_ts, interim, inbound_id, proactive, to_channel_root } = args;
43820
43914
  const bindingMode = slackReplyBindingMode();
@@ -45491,6 +45585,12 @@ async function connectSocketMode() {
45491
45585
  });
45492
45586
  const passedIdentity = access.kind !== "drop" || access.reason.startsWith("peer:");
45493
45587
  if (passedIdentity) recordActivity("inbound");
45588
+ const ingressThreadTs = evt.thread_ts ?? evt.ts;
45589
+ if (peerClassification?.kind === "bot-ingress" && access.kind !== "drop") {
45590
+ ingressOnlyThreads.mark(evt.channel, ingressThreadTs);
45591
+ } else if (!isBot) {
45592
+ ingressOnlyThreads.clear(evt.channel, ingressThreadTs);
45593
+ }
45494
45594
  if (access.kind === "drop") {
45495
45595
  const channelHash = createHash3("sha256").update(evt.channel ?? "").digest("hex").slice(0, 8);
45496
45596
  const subtypeSuffix = access.reason === "content:subtype" ? ` subtype=${evt.subtype ?? "none"}` : "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@integrity-labs/agt-cli",
3
- "version": "0.28.551",
3
+ "version": "0.28.553",
4
4
  "description": "Augmented Team CLI — agent provisioning and management",
5
5
  "type": "module",
6
6
  "engines": {