@integrity-labs/agt-cli 0.28.303 → 0.28.305

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.
@@ -40400,9 +40400,9 @@ var FLAG_REGISTRY = [
40400
40400
  },
40401
40401
  {
40402
40402
  key: "notify-dispatch",
40403
- description: "Membership-based channel-notify dispatch (ENG-7682 / notify Slice 1). off = today's behaviour: a non-@mention top-level channel message is dropped by the mention_only engagement gate (SLACK_CHANNEL_RESPONSE_MODE). membership = the agent's Slack MCP ADMITS non-@mention channel messages in channels its bot is a member of (the Socket Mode websocket already receives them), waking the agent in-thread via the SAME existing wake path used for @mentions/DMs. Only the mention_only engagement gate is relaxed - the echo/dedup, bot_id/self, and peer-classifier safety leaves still apply, so this never opens a bot-to-bot wake loop. The channel server can't evaluate flags, so the manager materializes the resolved value into the AGT_NOTIFY_DISPATCH spawn env (operator/canary override, ADR-0022). Enum leaves room for a later Slice 2 'filter' value (per-channel filters). Ships dark (default off).",
40403
+ description: "Membership-based channel-notify dispatch (ENG-7682 / notify Slice 1). off = today's behaviour: a non-@mention top-level channel message is dropped by the mention_only engagement gate (SLACK_CHANNEL_RESPONSE_MODE). membership = the agent's Slack MCP ADMITS non-@mention channel messages in channels its bot is a member of (the Socket Mode websocket already receives them), waking the agent in-thread via the SAME existing wake path used for @mentions/DMs. Only the mention_only engagement gate is relaxed - the echo/dedup, bot_id/self, and peer-classifier safety leaves still apply, so this never opens a bot-to-bot wake loop. The channel server can't evaluate flags, so the manager materializes the resolved value into the AGT_NOTIFY_DISPATCH spawn env (operator/canary override, ADR-0022). filter = like membership (opt-out) but honours a per-channel mute list: a member-channel message wakes the agent UNLESS the user muted that channel via the /notify slash command. The manager materializes the muted set into AGT_NOTIFY_MUTED_CHANNELS from agent_notify_channel_prefs (ENG-7682 Slice 2). Ships dark (default off).",
40404
40404
  flagType: "enum",
40405
- allowedValues: ["off", "membership"],
40405
+ allowedValues: ["off", "membership", "filter"],
40406
40406
  // Declared safe value is `off`: the mention_only gate stays in force, byte-
40407
40407
  // identical to today. membership is the deliberate, per-agent flip that
40408
40408
  // widens what wakes the agent.
@@ -6997,8 +6997,10 @@ __export(slack_block_kit_runtime_exports, {
6997
6997
  decodeActionId: () => decodeActionId,
6998
6998
  encodeActionId: () => encodeActionId,
6999
6999
  generateOptionToken: () => generateOptionToken,
7000
+ getMutedNotifyChannels: () => getMutedNotifyChannels,
7000
7001
  recordSlackDelivery: () => recordSlackDelivery,
7001
7002
  resolveInteractive: () => resolveInteractive,
7003
+ setNotifyChannelPref: () => setNotifyChannelPref,
7002
7004
  submitInteractive: () => submitInteractive,
7003
7005
  updatePendingInteractionMessageTs: () => updatePendingInteractionMessageTs,
7004
7006
  validateAskUserOptions: () => validateAskUserOptions,
@@ -7210,6 +7212,33 @@ async function recordSlackDelivery(cfg, input) {
7210
7212
  throw new Error(`recordSlackDelivery failed (${res.status}): ${body.slice(0, 200)}`);
7211
7213
  }
7212
7214
  }
7215
+ async function setNotifyChannelPref(cfg, input) {
7216
+ try {
7217
+ const res = await apiCall(cfg, "POST", "/host/notify-prefs", {
7218
+ agent_id: cfg.agentId,
7219
+ channel_id: input.channelId,
7220
+ wake: input.wake,
7221
+ updated_by: input.userId ?? null
7222
+ });
7223
+ return res.ok;
7224
+ } catch {
7225
+ return false;
7226
+ }
7227
+ }
7228
+ async function getMutedNotifyChannels(cfg) {
7229
+ try {
7230
+ const res = await apiCall(
7231
+ cfg,
7232
+ "GET",
7233
+ `/host/notify-prefs?agent_id=${encodeURIComponent(cfg.agentId)}`
7234
+ );
7235
+ if (!res.ok) return [];
7236
+ const body = await res.json().catch(() => ({}));
7237
+ return Array.isArray(body.muted_channels) ? body.muted_channels.filter((x) => typeof x === "string") : [];
7238
+ } catch {
7239
+ return [];
7240
+ }
7241
+ }
7213
7242
  var SLACK_LIMITS, SUPPORTED_BLOCK_TYPES, SUPPORTED_ACTION_ELEMENTS, ACTION_ID_RE;
7214
7243
  var init_slack_block_kit_runtime = __esm({
7215
7244
  "src/slack-block-kit-runtime.ts"() {
@@ -17430,12 +17459,32 @@ function channelMessageShouldRespond(text, mode) {
17430
17459
  }
17431
17460
  function parseNotifyDispatchMode(raw) {
17432
17461
  if (!raw) return "off";
17433
- return raw.trim().toLowerCase() === "membership" ? "membership" : "off";
17462
+ const v = raw.trim().toLowerCase();
17463
+ if (v === "membership") return "membership";
17464
+ if (v === "filter") return "filter";
17465
+ return "off";
17434
17466
  }
17435
- function channelMessageShouldWake(text, responseMode, notifyMode) {
17467
+ function parseMutedChannels(raw) {
17468
+ if (!raw) return /* @__PURE__ */ new Set();
17469
+ return new Set(
17470
+ raw.split(",").map((s) => s.trim()).filter(Boolean)
17471
+ );
17472
+ }
17473
+ function channelMessageShouldWake(text, responseMode, notifyMode, channelId, mutedChannels) {
17436
17474
  if (notifyMode === "membership") return true;
17475
+ if (notifyMode === "filter") {
17476
+ if (channelId && mutedChannels && mutedChannels.has(channelId)) return false;
17477
+ return true;
17478
+ }
17437
17479
  return channelMessageShouldRespond(text, responseMode);
17438
17480
  }
17481
+ function parseNotifyCommand(text, currentChannelId) {
17482
+ const arg = (text ?? "").trim().toLowerCase().split(/\s+/)[0] ?? "";
17483
+ if (arg === "off" || arg === "mute") return { action: "mute", channelId: currentChannelId };
17484
+ if (arg === "on" || arg === "unmute") return { action: "unmute", channelId: currentChannelId };
17485
+ if (arg === "" || arg === "status") return { action: "status", channelId: currentChannelId };
17486
+ return { action: "help", channelId: currentChannelId };
17487
+ }
17439
17488
 
17440
17489
  // src/slack-list-channels.ts
17441
17490
  function normaliseForMatch(s) {
@@ -18293,6 +18342,7 @@ var SLACK_SKIP_REACTION = (process.env.SLACK_SKIP_REACTION ?? "").trim();
18293
18342
  var SLACK_ACK_REACTION = (process.env.SLACK_ACK_REACTION ?? "").trim() || "eyes";
18294
18343
  var CHANNEL_RESPONSE_MODE = parseResponseMode(process.env.SLACK_CHANNEL_RESPONSE_MODE);
18295
18344
  var NOTIFY_DISPATCH_MODE = parseNotifyDispatchMode(process.env.AGT_NOTIFY_DISPATCH);
18345
+ var MUTED_CHANNELS = parseMutedChannels(process.env.AGT_NOTIFY_MUTED_CHANNELS);
18296
18346
  var SLACK_PEER_DISABLED_MODE = (() => {
18297
18347
  const raw = (process.env.PEER_DISABLED ?? "").trim().toLowerCase();
18298
18348
  if (raw === "" || raw === "off") return "off";
@@ -19769,6 +19819,61 @@ ${renderSlackStatusSections(merged, {
19769
19819
  })}`;
19770
19820
  await postEphemeralViaResponseUrl(responseUrl, finalReport, codeName, { replaceOriginal: true });
19771
19821
  }
19822
+ async function handleNotifyCommand(payload, codeName) {
19823
+ const responseUrl = payload.response_url;
19824
+ if (!responseUrl) return;
19825
+ const channelId = payload.channel_id ?? "";
19826
+ if (!channelId) {
19827
+ await postEphemeralViaResponseUrl(responseUrl, "Run `/notify` inside a channel.", codeName);
19828
+ return;
19829
+ }
19830
+ const notifyAllowed = getEffectivePingAllowedUsers();
19831
+ if (notifyAllowed.size === 0 || !payload.user_id || !notifyAllowed.has(payload.user_id)) {
19832
+ process.stderr.write(`slack-channel(${codeName}): /notify denied - user not in the allowlist
19833
+ `);
19834
+ await postEphemeralViaResponseUrl(
19835
+ responseUrl,
19836
+ `\u{1F6AB} \`/notify\` is limited to \`${codeName}\`'s team and the person it reports to. If that's you, link your Slack in your Augmented Team contact preferences and wait for the next refresh.`,
19837
+ codeName
19838
+ );
19839
+ return;
19840
+ }
19841
+ const inactiveNote = NOTIFY_DISPATCH_MODE === "filter" ? "" : " _(note: I\u2019m not in per-channel filter mode right now, so this is saved but won\u2019t take effect until an operator enables it.)_";
19842
+ const { action } = parseNotifyCommand(payload.text, channelId);
19843
+ if (action === "help") {
19844
+ await postEphemeralViaResponseUrl(
19845
+ responseUrl,
19846
+ "`/notify off` \u2014 stop waking me on this channel\u2019s messages. `/notify on` \u2014 resume. `/notify status` \u2014 show the current setting. (An @-mention always reaches me.)",
19847
+ codeName
19848
+ );
19849
+ return;
19850
+ }
19851
+ if (action === "status") {
19852
+ const muted = MUTED_CHANNELS.has(channelId);
19853
+ await postEphemeralViaResponseUrl(
19854
+ responseUrl,
19855
+ (muted ? ":mute: This channel is *muted* \u2014 I won\u2019t wake on its messages (an @-mention still reaches me). `/notify on` to resume." : ":bell: This channel is *active* \u2014 I wake on its messages. `/notify off` to mute.") + inactiveNote,
19856
+ codeName
19857
+ );
19858
+ return;
19859
+ }
19860
+ const mute = action === "mute";
19861
+ if (mute) MUTED_CHANNELS.add(channelId);
19862
+ else MUTED_CHANNELS.delete(channelId);
19863
+ const runtime = await Promise.resolve().then(() => (init_slack_block_kit_runtime(), slack_block_kit_runtime_exports));
19864
+ const cfg = statusRuntimeConfig();
19865
+ const persisted = cfg ? await runtime.setNotifyChannelPref(cfg, {
19866
+ channelId,
19867
+ wake: !mute,
19868
+ userId: payload.user_id ?? null
19869
+ }) : false;
19870
+ const durabilityNote = persisted ? "" : " (couldn\u2019t save it durably just now \u2014 it\u2019ll reset if I restart; re-run to retry)";
19871
+ await postEphemeralViaResponseUrl(
19872
+ responseUrl,
19873
+ (mute ? `:mute: Muted this channel \u2014 I\u2019ll stop waking on its messages (an @-mention still reaches me).${durabilityNote}` : `:bell: Un-muted this channel \u2014 I\u2019ll wake on its messages again.${durabilityNote}`) + inactiveNote,
19874
+ codeName
19875
+ );
19876
+ }
19772
19877
  async function postEphemeralViaResponseUrl(responseUrl, text, logTag, opts = {}) {
19773
19878
  try {
19774
19879
  const res = await fetch(responseUrl, {
@@ -20040,6 +20145,10 @@ async function handleSlashCommandEnvelope(payload) {
20040
20145
  await handleStatusCommand(responseUrl, codeName);
20041
20146
  return;
20042
20147
  }
20148
+ if (matchesAgentCommand(command, "/notify")) {
20149
+ await handleNotifyCommand(payload, codeName);
20150
+ return;
20151
+ }
20043
20152
  if (matchesAgentCommand(command, "/help")) {
20044
20153
  await postEphemeralViaResponseUrl(responseUrl, buildSlackHelpMessage(codeName), codeName);
20045
20154
  return;
@@ -22047,7 +22156,28 @@ async function resolveUserName(userId) {
22047
22156
  var currentWs = null;
22048
22157
  var isShuttingDown = false;
22049
22158
  var acquiredLockPath = null;
22159
+ async function seedMutedChannelsFromApi() {
22160
+ try {
22161
+ const cfg = statusRuntimeConfig();
22162
+ if (!cfg) return;
22163
+ const runtime = await Promise.resolve().then(() => (init_slack_block_kit_runtime(), slack_block_kit_runtime_exports));
22164
+ const muted = await runtime.getMutedNotifyChannels(cfg);
22165
+ for (const ch of muted) MUTED_CHANNELS.add(ch);
22166
+ if (muted.length > 0) {
22167
+ process.stderr.write(
22168
+ `slack-channel: seeded ${muted.length} muted channel(s) from notify-prefs
22169
+ `
22170
+ );
22171
+ }
22172
+ } catch (err) {
22173
+ process.stderr.write(
22174
+ `slack-channel: notify-prefs seed failed: ${err.message}
22175
+ `
22176
+ );
22177
+ }
22178
+ }
22050
22179
  function connectSocketModeSafely() {
22180
+ if (NOTIFY_DISPATCH_MODE === "filter") void seedMutedChannelsFromApi();
22051
22181
  void runOrRetry(connectSocketMode, {
22052
22182
  retryDelayMs: 1e4,
22053
22183
  onError: (err) => process.stderr.write(
@@ -22439,7 +22569,13 @@ async function connectSocketMode() {
22439
22569
  return;
22440
22570
  }
22441
22571
  } else {
22442
- if (!channelMessageShouldWake(evt.text ?? "", CHANNEL_RESPONSE_MODE, NOTIFY_DISPATCH_MODE))
22572
+ if (!channelMessageShouldWake(
22573
+ evt.text ?? "",
22574
+ CHANNEL_RESPONSE_MODE,
22575
+ NOTIFY_DISPATCH_MODE,
22576
+ evt.channel,
22577
+ MUTED_CHANNELS
22578
+ ))
22443
22579
  return;
22444
22580
  }
22445
22581
  }
@@ -6997,8 +6997,10 @@ __export(slack_block_kit_runtime_exports, {
6997
6997
  decodeActionId: () => decodeActionId,
6998
6998
  encodeActionId: () => encodeActionId,
6999
6999
  generateOptionToken: () => generateOptionToken,
7000
+ getMutedNotifyChannels: () => getMutedNotifyChannels,
7000
7001
  recordSlackDelivery: () => recordSlackDelivery,
7001
7002
  resolveInteractive: () => resolveInteractive,
7003
+ setNotifyChannelPref: () => setNotifyChannelPref,
7002
7004
  submitInteractive: () => submitInteractive,
7003
7005
  updatePendingInteractionMessageTs: () => updatePendingInteractionMessageTs,
7004
7006
  validateAskUserOptions: () => validateAskUserOptions,
@@ -7210,6 +7212,33 @@ async function recordSlackDelivery(cfg, input) {
7210
7212
  throw new Error(`recordSlackDelivery failed (${res.status}): ${body.slice(0, 200)}`);
7211
7213
  }
7212
7214
  }
7215
+ async function setNotifyChannelPref(cfg, input) {
7216
+ try {
7217
+ const res = await apiCall(cfg, "POST", "/host/notify-prefs", {
7218
+ agent_id: cfg.agentId,
7219
+ channel_id: input.channelId,
7220
+ wake: input.wake,
7221
+ updated_by: input.userId ?? null
7222
+ });
7223
+ return res.ok;
7224
+ } catch {
7225
+ return false;
7226
+ }
7227
+ }
7228
+ async function getMutedNotifyChannels(cfg) {
7229
+ try {
7230
+ const res = await apiCall(
7231
+ cfg,
7232
+ "GET",
7233
+ `/host/notify-prefs?agent_id=${encodeURIComponent(cfg.agentId)}`
7234
+ );
7235
+ if (!res.ok) return [];
7236
+ const body = await res.json().catch(() => ({}));
7237
+ return Array.isArray(body.muted_channels) ? body.muted_channels.filter((x) => typeof x === "string") : [];
7238
+ } catch {
7239
+ return [];
7240
+ }
7241
+ }
7213
7242
  var SLACK_LIMITS, SUPPORTED_BLOCK_TYPES, SUPPORTED_ACTION_ELEMENTS, ACTION_ID_RE;
7214
7243
  var init_slack_block_kit_runtime = __esm({
7215
7244
  "src/slack-block-kit-runtime.ts"() {
@@ -36,7 +36,7 @@ import {
36
36
  writeDirectChatSessionState,
37
37
  writeEgressAllowlist,
38
38
  writePersistentClaudeWrapper
39
- } from "./chunk-5WQATS6J.js";
39
+ } from "./chunk-A546IARM.js";
40
40
  import "./chunk-XWVM4KPK.js";
41
41
  export {
42
42
  EGRESS_BASELINE_DOMAINS,
@@ -77,4 +77,4 @@ export {
77
77
  writeEgressAllowlist,
78
78
  writePersistentClaudeWrapper
79
79
  };
80
- //# sourceMappingURL=persistent-session-52DPSJGK.js.map
80
+ //# sourceMappingURL=persistent-session-WYNJBCVB.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  paneLogPath
3
- } from "./chunk-5WQATS6J.js";
3
+ } from "./chunk-A546IARM.js";
4
4
  import "./chunk-XWVM4KPK.js";
5
5
 
6
6
  // src/lib/responsiveness-probe.ts
@@ -425,4 +425,4 @@ export {
425
425
  readAndResetSlackReplyBindingClassifications,
426
426
  readAndResetSlackReplyTargetClassifications
427
427
  };
428
- //# sourceMappingURL=responsiveness-probe-KXWV4FXD.js.map
428
+ //# sourceMappingURL=responsiveness-probe-2FQLPFAR.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@integrity-labs/agt-cli",
3
- "version": "0.28.303",
3
+ "version": "0.28.305",
4
4
  "description": "Augmented Team CLI — agent provisioning and management",
5
5
  "type": "module",
6
6
  "engines": {