@integrity-labs/agt-cli 0.27.24 → 0.27.26

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.
@@ -14261,6 +14261,56 @@ function decideSenderPolicyForward(evt, policy) {
14261
14261
  return decideModeForward(evt, policy);
14262
14262
  }
14263
14263
 
14264
+ // src/sender-policy-decline.ts
14265
+ function classifySlackPolicyBlock(evt, policy) {
14266
+ if (policy.internalOnly && (!policy.homeTeamId || evt.team !== policy.homeTeamId)) {
14267
+ return "internal_only";
14268
+ }
14269
+ switch (policy.mode) {
14270
+ case "manager_only":
14271
+ return "mode_manager_only";
14272
+ case "team_agents_only":
14273
+ return "mode_team_agents_only";
14274
+ case "agents_only":
14275
+ return "mode_agents_only";
14276
+ case "all":
14277
+ return "internal_only";
14278
+ }
14279
+ }
14280
+ function politeDeclineCopy(reason) {
14281
+ switch (reason) {
14282
+ case "internal_only":
14283
+ return "Sorry, I can only respond to people inside our organisation. This conversation appears to be from outside.";
14284
+ case "mode_manager_only":
14285
+ return "Sorry, I only respond to my manager in this channel.";
14286
+ case "mode_team_agents_only":
14287
+ return "Sorry, I only respond to agents on my team in this channel.";
14288
+ case "mode_agents_only":
14289
+ return "Sorry, I only respond to other agents in this channel.";
14290
+ }
14291
+ }
14292
+ function decideDeclineReply(input) {
14293
+ const last = input.cache.get(input.key);
14294
+ if (last !== void 0) {
14295
+ const elapsed = input.now - last;
14296
+ if (elapsed < input.cooldownMs) {
14297
+ return { reply: false, remainingMs: input.cooldownMs - elapsed };
14298
+ }
14299
+ }
14300
+ input.cache.set(input.key, input.now);
14301
+ return { reply: true };
14302
+ }
14303
+ function declineCacheKey(channelId, senderId, reason) {
14304
+ return `${channelId}|${senderId}|${reason}`;
14305
+ }
14306
+ function readDeclineCooldownMs(envVarName, defaultSeconds = 1800) {
14307
+ const raw = process.env[envVarName];
14308
+ if (!raw) return defaultSeconds * 1e3;
14309
+ const parsed = Number(raw);
14310
+ if (!Number.isFinite(parsed) || parsed < 0) return defaultSeconds * 1e3;
14311
+ return parsed * 1e3;
14312
+ }
14313
+
14264
14314
  // src/ack-reaction.ts
14265
14315
  import { readdirSync, readFileSync } from "fs";
14266
14316
  import { join } from "path";
@@ -15557,6 +15607,58 @@ var SLACK_SENDER_POLICY = (() => {
15557
15607
  }
15558
15608
  throw new Error(`Invalid SLACK_SENDER_POLICY=${JSON.stringify(process.env.SLACK_SENDER_POLICY)}`);
15559
15609
  })();
15610
+ var SLACK_POLICY_DECLINE_CACHE = /* @__PURE__ */ new Map();
15611
+ var SLACK_POLICY_DECLINE_COOLDOWN_MS = readDeclineCooldownMs(
15612
+ "SLACK_SENDER_POLICY_REPLY_COOLDOWN_SEC"
15613
+ );
15614
+ async function maybeSendSenderPolicyDecline(args) {
15615
+ if (!BOT_TOKEN) return;
15616
+ if (!args.channel || !args.senderId) return;
15617
+ const key2 = declineCacheKey(args.channel, args.senderId, args.subReason);
15618
+ const decision = decideDeclineReply({
15619
+ cache: SLACK_POLICY_DECLINE_CACHE,
15620
+ key: key2,
15621
+ cooldownMs: SLACK_POLICY_DECLINE_COOLDOWN_MS,
15622
+ now: Date.now()
15623
+ });
15624
+ if (!decision.reply) {
15625
+ process.stderr.write(
15626
+ `slack-channel(${AGENT_CODE_NAME}): decline suppressed by cooldown (channel=${redactSlackId(args.channel)}, sender=${redactSlackId(args.senderId)}, reason=${args.subReason}, remaining=${decision.remainingMs}ms)
15627
+ `
15628
+ );
15629
+ return;
15630
+ }
15631
+ const text = politeDeclineCopy(args.subReason);
15632
+ const controller = new AbortController();
15633
+ const timeoutId = setTimeout(() => controller.abort(), 1e4);
15634
+ try {
15635
+ const res = await fetch("https://slack.com/api/chat.postMessage", {
15636
+ method: "POST",
15637
+ signal: controller.signal,
15638
+ headers: {
15639
+ "Content-Type": "application/json",
15640
+ Authorization: `Bearer ${BOT_TOKEN}`
15641
+ },
15642
+ body: JSON.stringify({
15643
+ channel: args.channel,
15644
+ text,
15645
+ // Reply in-thread if the inbound was a thread message — keeps
15646
+ // the decline next to the message that caused it. For top-level
15647
+ // posts (DMs / channel root) omit thread_ts and post inline.
15648
+ ...args.threadTs ? { thread_ts: args.threadTs } : {}
15649
+ })
15650
+ });
15651
+ const data = await res.json();
15652
+ if (!data.ok) {
15653
+ process.stderr.write(
15654
+ `slack-channel(${AGENT_CODE_NAME}): decline post failed: ${data.error ?? "unknown"}
15655
+ `
15656
+ );
15657
+ }
15658
+ } finally {
15659
+ clearTimeout(timeoutId);
15660
+ }
15661
+ }
15560
15662
  var BLOCK_KIT_ENABLED = process.env.SLACK_BLOCK_KIT_ENABLED === "true";
15561
15663
  var BLOCK_KIT_ASK_USER_ENABLED = process.env.SLACK_BLOCK_KIT_ASK_USER_ENABLED === "true";
15562
15664
  var BLOCK_KIT_DISABLED = process.env.SLACK_BLOCK_KIT_DISABLED === "true";
@@ -17807,8 +17909,27 @@ async function connectSocketMode() {
17807
17909
  }
17808
17910
  const senderPolicyDecision = decideSenderPolicyForward(evt, SLACK_SENDER_POLICY);
17809
17911
  if (!senderPolicyDecision.forward) {
17810
- process.stderr.write(`slack-channel: dropped message event (reason=sender_policy, mode=${SLACK_SENDER_POLICY.mode}, ts=${evt.ts ?? "n/a"})
17912
+ const subReason = classifySlackPolicyBlock(evt, SLACK_SENDER_POLICY);
17913
+ process.stderr.write(`slack-channel: dropped message event (reason=sender_policy, sub=${subReason}, mode=${SLACK_SENDER_POLICY.mode}, ts=${evt.ts ?? "n/a"})
17811
17914
  `);
17915
+ await maybeSendSenderPolicyDecline({
17916
+ channel: evt.channel,
17917
+ senderId: evt.user,
17918
+ // CR on PR #1623: top-level posts (DMs, channel root messages)
17919
+ // arrive with no `thread_ts` and MUST decline inline, not in a
17920
+ // new thread off the inbound message. The `?? evt.ts` fallback
17921
+ // forced every root message into a thread reply — exactly the
17922
+ // case the helper's "omit thread_ts when undefined" branch was
17923
+ // meant to handle. Pass through as-is: in-thread for thread
17924
+ // replies (thread_ts present), inline for everything else.
17925
+ threadTs: evt.thread_ts,
17926
+ subReason
17927
+ }).catch((err) => {
17928
+ process.stderr.write(
17929
+ `slack-channel(${AGENT_CODE_NAME}): decline reply failed: ${err.message}
17930
+ `
17931
+ );
17932
+ });
17812
17933
  return;
17813
17934
  }
17814
17935
  recordActivity("inbound");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@integrity-labs/agt-cli",
3
- "version": "0.27.24",
3
+ "version": "0.27.26",
4
4
  "description": "Augmented Team CLI — agent provisioning and management",
5
5
  "type": "module",
6
6
  "engines": {