@integrity-labs/agt-cli 0.27.144 → 0.27.145

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.
@@ -14305,6 +14305,45 @@ function readDeclineCooldownMs(envVarName, defaultSeconds = 1800) {
14305
14305
  return parsed * 1e3;
14306
14306
  }
14307
14307
 
14308
+ // src/inbound-access.ts
14309
+ function decideInboundAccess(input) {
14310
+ if (!input.content.forward) {
14311
+ return { kind: "drop", reason: `content:${input.content.reason}` };
14312
+ }
14313
+ if (input.isSelf) {
14314
+ return { kind: "drop", reason: "self" };
14315
+ }
14316
+ if (input.orgBoundary && !input.orgBoundary.forward) {
14317
+ return { kind: "drop", reason: "org_boundary" };
14318
+ }
14319
+ if (input.senderPolicy && !input.senderPolicy.forward) {
14320
+ const reason = input.senderPolicy.reason;
14321
+ const declineWorthy = input.sameOrg && reason !== "internal_only";
14322
+ return {
14323
+ kind: "drop",
14324
+ reason: `sender_policy:${reason}`,
14325
+ decline: declineWorthy ? politeDeclineCopy(reason) : void 0
14326
+ };
14327
+ }
14328
+ if (input.command && !input.isBot) {
14329
+ return {
14330
+ kind: "command",
14331
+ command: input.command.command,
14332
+ authorized: input.command.authorized
14333
+ };
14334
+ }
14335
+ if (input.isBot) {
14336
+ const peer = input.peer;
14337
+ if (!peer || peer.kind === "self") {
14338
+ return { kind: "drop", reason: peer?.kind === "self" ? "self" : "peer:unclassified" };
14339
+ }
14340
+ if (peer.kind === "drop") {
14341
+ return { kind: "drop", reason: `peer:${peer.reason ?? "unspecified"}` };
14342
+ }
14343
+ }
14344
+ return { kind: "admit" };
14345
+ }
14346
+
14308
14347
  // src/ack-reaction.ts
14309
14348
  import { readdirSync, readFileSync } from "fs";
14310
14349
  import { join } from "path";
@@ -19069,132 +19108,134 @@ async function connectSocketMode() {
19069
19108
  const evt = msg.payload.event;
19070
19109
  if (evt.user === botUserId) return;
19071
19110
  if (evt.type !== "app_mention" && evt.type !== "message") return;
19111
+ const isBot = Boolean(evt.bot_id);
19072
19112
  const filterDecision = decideSlackMessageForward(evt);
19073
- if (!filterDecision.forward) {
19074
- process.stderr.write(`slack-channel: dropped message event (reason=${filterDecision.reason}, subtype=${evt.subtype ?? "none"}, ts=${evt.ts ?? "n/a"})
19075
- `);
19076
- return;
19077
- }
19078
19113
  const senderPolicyDecision = decideSenderPolicyForward(evt, SLACK_SENDER_POLICY);
19079
- if (!senderPolicyDecision.forward) {
19080
- const subReason = classifySlackPolicyBlock(evt, SLACK_SENDER_POLICY);
19081
- process.stderr.write(`slack-channel: dropped message event (reason=sender_policy, sub=${subReason}, mode=${SLACK_SENDER_POLICY.mode}, ts=${evt.ts ?? "n/a"})
19082
- `);
19083
- await maybeSendSenderPolicyDecline({
19084
- channel: evt.channel,
19085
- senderId: evt.user,
19086
- // CR on PR #1623: top-level posts (DMs, channel root messages)
19087
- // arrive with no `thread_ts` and MUST decline inline, not in a
19088
- // new thread off the inbound message. The `?? evt.ts` fallback
19089
- // forced every root message into a thread reply — exactly the
19090
- // case the helper's "omit thread_ts when undefined" branch was
19091
- // meant to handle. Pass through as-is: in-thread for thread
19092
- // replies (thread_ts present), inline for everything else.
19093
- threadTs: evt.thread_ts,
19094
- subReason
19095
- }).catch((err) => {
19096
- process.stderr.write(
19097
- `slack-channel(${AGENT_CODE_NAME}): decline reply failed: ${err.message}
19098
- `
19099
- );
19100
- });
19101
- return;
19102
- }
19103
- recordActivity("inbound");
19104
- const isDirectMessage = evt.channel?.startsWith("D");
19105
- const isThreadReply = !!evt.thread_ts && evt.thread_ts !== evt.ts;
19106
- const trackTs = evt.thread_ts ?? evt.ts ?? "";
19107
- const threadKey = buildThreadKey(evt.channel, trackTs);
19114
+ const policyBlockReason = senderPolicyDecision.forward ? void 0 : classifySlackPolicyBlock(evt, SLACK_SENDER_POLICY);
19115
+ const peerClassification = isBot ? classifyPeerMessage(
19116
+ {
19117
+ text: evt.text,
19118
+ channel: evt.channel ?? "",
19119
+ channel_type: evt.channel_type,
19120
+ user: evt.user,
19121
+ bot_id: evt.bot_id,
19122
+ thread_ts: evt.thread_ts,
19123
+ parent_user_id: evt.parent_user_id
19124
+ },
19125
+ SLACK_PEER_CLASSIFIER_CONFIG,
19126
+ { bot_user_id: botUserId }
19127
+ ) : void 0;
19108
19128
  const rawText = evt.text ?? "";
19109
19129
  const strippedText = rawText.replace(/^\s*<@[^>]+>\s*/, "").trim();
19110
19130
  const restartSuffixed = agentSlashCommand("/restart");
19131
+ const helpSuffixed = agentSlashCommand("/help");
19111
19132
  const isRestartCommand = strippedText === "/restart" || strippedText.startsWith("/restart ") || strippedText === restartSuffixed || strippedText.startsWith(`${restartSuffixed} `);
19112
- const isHelpCommand = strippedText === "/help" || strippedText.startsWith("/help ");
19113
- if (isHelpCommand) {
19114
- await handleHelpCommand({
19115
- channel: evt.channel ?? "",
19116
- threadTs: evt.thread_ts,
19117
- ts: evt.ts ?? ""
19118
- });
19133
+ const isHelpCommand = strippedText === "/help" || strippedText.startsWith("/help ") || strippedText === helpSuffixed || strippedText.startsWith(`${helpSuffixed} `);
19134
+ const command = isBot ? void 0 : isHelpCommand ? { command: "help", authorized: true } : isRestartCommand ? { command: "restart", authorized: isRestartSenderAllowed(getEffectiveAllowedUsers(), evt.user) } : void 0;
19135
+ const slackHomeTeamId = process.env.SLACK_HOME_TEAM_ID;
19136
+ const sameOrg = !!slackHomeTeamId && evt.team === slackHomeTeamId;
19137
+ const access = decideInboundAccess({
19138
+ content: filterDecision.forward ? { forward: true } : { forward: false, reason: filterDecision.reason },
19139
+ // The agent's own user-echo was already skipped above; peer 'self'
19140
+ // covers the bot-id self case inside the composer.
19141
+ isSelf: false,
19142
+ isBot,
19143
+ peer: peerClassification ? {
19144
+ kind: peerClassification.kind,
19145
+ reason: peerClassification.kind === "drop" ? peerClassification.reason : void 0
19146
+ } : void 0,
19147
+ senderPolicy: senderPolicyDecision.forward ? { forward: true } : { forward: false, reason: policyBlockReason },
19148
+ command,
19149
+ sameOrg
19150
+ });
19151
+ const passedIdentity = access.kind !== "drop" || access.reason.startsWith("peer:");
19152
+ if (passedIdentity) recordActivity("inbound");
19153
+ if (access.kind === "drop") {
19154
+ const channelHash = createHash("sha256").update(evt.channel ?? "").digest("hex").slice(0, 8);
19155
+ process.stderr.write(
19156
+ `slack-channel: inbound drop reason=${access.reason} channel=${channelHash} ts=${redactSlackId(evt.ts)}
19157
+ `
19158
+ );
19159
+ if (access.decline !== void 0 && policyBlockReason) {
19160
+ await maybeSendSenderPolicyDecline({
19161
+ channel: evt.channel,
19162
+ senderId: evt.user,
19163
+ threadTs: evt.thread_ts,
19164
+ subReason: policyBlockReason
19165
+ }).catch((err) => {
19166
+ process.stderr.write(
19167
+ `slack-channel(${AGENT_CODE_NAME}): decline reply failed: ${err.message}
19168
+ `
19169
+ );
19170
+ });
19171
+ }
19172
+ if (access.reason === "peer:cross_team_grant_missing") {
19173
+ crossTeamPeerAuditClient?.emit({
19174
+ event_type: "slack.peer.drop.cross_team_grant_missing",
19175
+ peer_agent_id: null,
19176
+ gate_path: null,
19177
+ grant_id: null,
19178
+ chat_id: evt.channel ?? null,
19179
+ message_id: evt.ts ?? null
19180
+ });
19181
+ }
19119
19182
  return;
19120
19183
  }
19121
- if (isRestartCommand) {
19122
- const senderAllowed = isRestartSenderAllowed(getEffectiveAllowedUsers(), evt.user);
19123
- if (!senderAllowed) {
19124
- await denyUnauthorizedRestart({
19184
+ if (access.kind === "command") {
19185
+ if (access.command === "help") {
19186
+ await handleHelpCommand({
19125
19187
  channel: evt.channel ?? "",
19126
- threadTs: evt.thread_ts
19188
+ threadTs: evt.thread_ts,
19189
+ ts: evt.ts ?? ""
19127
19190
  });
19128
19191
  return;
19129
19192
  }
19130
- const resolvedName = await resolveUserName(evt.user);
19131
- const requesterName = resolvedName && resolvedName !== evt.user ? resolvedName : void 0;
19132
- await handleRestartCommand({
19133
- channel: evt.channel ?? "",
19134
- // Only carry thread_ts when the originating message was already
19135
- // threaded; a top-level command acks in-channel rather than
19136
- // synthesising a brand-new thread (CodeRabbit feedback).
19137
- threadTs: evt.thread_ts,
19138
- ts: evt.ts ?? "",
19139
- requesterName
19140
- });
19141
- return;
19142
- }
19143
- if (evt.bot_id) {
19144
- const peerMsg = {
19145
- text: evt.text,
19146
- channel: evt.channel ?? "",
19147
- channel_type: evt.channel_type,
19148
- user: evt.user,
19149
- bot_id: evt.bot_id,
19150
- thread_ts: evt.thread_ts,
19151
- parent_user_id: evt.parent_user_id
19152
- };
19153
- const decision = classifyPeerMessage(
19154
- peerMsg,
19155
- SLACK_PEER_CLASSIFIER_CONFIG,
19156
- { bot_user_id: botUserId }
19157
- );
19158
- if (decision.kind === "self") return;
19159
- if (decision.kind === "drop") {
19160
- const channelHash = createHash("sha256").update(evt.channel ?? "").digest("hex").slice(0, 8);
19161
- process.stderr.write(
19162
- `slack-channel: peer drop reason=${decision.reason} channel=${channelHash}
19163
- `
19164
- );
19165
- if (decision.reason === "cross_team_grant_missing") {
19166
- crossTeamPeerAuditClient?.emit({
19167
- event_type: "slack.peer.drop.cross_team_grant_missing",
19168
- peer_agent_id: null,
19169
- gate_path: null,
19170
- grant_id: null,
19171
- chat_id: evt.channel ?? null,
19172
- message_id: evt.ts ?? null
19193
+ if (access.command === "restart") {
19194
+ if (!access.authorized) {
19195
+ await denyUnauthorizedRestart({
19196
+ channel: evt.channel ?? "",
19197
+ threadTs: evt.thread_ts
19173
19198
  });
19199
+ return;
19174
19200
  }
19201
+ const resolvedName = await resolveUserName(evt.user);
19202
+ const requesterName = resolvedName && resolvedName !== evt.user ? resolvedName : void 0;
19203
+ await handleRestartCommand({
19204
+ channel: evt.channel ?? "",
19205
+ // Only carry thread_ts when the originating message was already
19206
+ // threaded; a top-level command acks in-channel.
19207
+ threadTs: evt.thread_ts,
19208
+ ts: evt.ts ?? "",
19209
+ requesterName
19210
+ });
19175
19211
  return;
19176
19212
  }
19177
- if (decision.kind === "peer-ingress") {
19178
- const peerGate = decision.peer.gate_path;
19179
- if (peerGate === "intra_org_unrestricted") {
19180
- crossTeamPeerAuditClient?.emit({
19181
- event_type: "slack.peer.ingress.cross_team",
19182
- peer_agent_id: decision.peer.agent_id || null,
19183
- gate_path: peerGate,
19184
- grant_id: null,
19185
- chat_id: evt.channel ?? null,
19186
- message_id: evt.ts ?? null
19187
- });
19188
- } else if (typeof peerGate === "string" && peerGate.startsWith("grant:")) {
19189
- crossTeamPeerAuditClient?.emit({
19190
- event_type: "slack.peer.ingress.cross_team",
19191
- peer_agent_id: decision.peer.agent_id || null,
19192
- gate_path: peerGate,
19193
- grant_id: peerGate.slice("grant:".length),
19194
- chat_id: evt.channel ?? null,
19195
- message_id: evt.ts ?? null
19196
- });
19197
- }
19213
+ return;
19214
+ }
19215
+ const isDirectMessage = evt.channel?.startsWith("D");
19216
+ const isThreadReply = !!evt.thread_ts && evt.thread_ts !== evt.ts;
19217
+ const trackTs = evt.thread_ts ?? evt.ts ?? "";
19218
+ const threadKey = buildThreadKey(evt.channel, trackTs);
19219
+ if (isBot && peerClassification?.kind === "peer-ingress") {
19220
+ const peerGate = peerClassification.peer.gate_path;
19221
+ if (peerGate === "intra_org_unrestricted") {
19222
+ crossTeamPeerAuditClient?.emit({
19223
+ event_type: "slack.peer.ingress.cross_team",
19224
+ peer_agent_id: peerClassification.peer.agent_id || null,
19225
+ gate_path: peerGate,
19226
+ grant_id: null,
19227
+ chat_id: evt.channel ?? null,
19228
+ message_id: evt.ts ?? null
19229
+ });
19230
+ } else if (typeof peerGate === "string" && peerGate.startsWith("grant:")) {
19231
+ crossTeamPeerAuditClient?.emit({
19232
+ event_type: "slack.peer.ingress.cross_team",
19233
+ peer_agent_id: peerClassification.peer.agent_id || null,
19234
+ gate_path: peerGate,
19235
+ grant_id: peerGate.slice("grant:".length),
19236
+ chat_id: evt.channel ?? null,
19237
+ message_id: evt.ts ?? null
19238
+ });
19198
19239
  }
19199
19240
  }
19200
19241
  if (evt.type === "app_mention") {
@@ -14430,6 +14430,45 @@ function readDeclineCooldownMs(envVarName, defaultSeconds = 1800) {
14430
14430
  return parsed * 1e3;
14431
14431
  }
14432
14432
 
14433
+ // src/inbound-access.ts
14434
+ function decideInboundAccess(input) {
14435
+ if (!input.content.forward) {
14436
+ return { kind: "drop", reason: `content:${input.content.reason}` };
14437
+ }
14438
+ if (input.isSelf) {
14439
+ return { kind: "drop", reason: "self" };
14440
+ }
14441
+ if (input.orgBoundary && !input.orgBoundary.forward) {
14442
+ return { kind: "drop", reason: "org_boundary" };
14443
+ }
14444
+ if (input.senderPolicy && !input.senderPolicy.forward) {
14445
+ const reason = input.senderPolicy.reason;
14446
+ const declineWorthy = input.sameOrg && reason !== "internal_only";
14447
+ return {
14448
+ kind: "drop",
14449
+ reason: `sender_policy:${reason}`,
14450
+ decline: declineWorthy ? politeDeclineCopy(reason) : void 0
14451
+ };
14452
+ }
14453
+ if (input.command && !input.isBot) {
14454
+ return {
14455
+ kind: "command",
14456
+ command: input.command.command,
14457
+ authorized: input.command.authorized
14458
+ };
14459
+ }
14460
+ if (input.isBot) {
14461
+ const peer = input.peer;
14462
+ if (!peer || peer.kind === "self") {
14463
+ return { kind: "drop", reason: peer?.kind === "self" ? "self" : "peer:unclassified" };
14464
+ }
14465
+ if (peer.kind === "drop") {
14466
+ return { kind: "drop", reason: `peer:${peer.reason ?? "unspecified"}` };
14467
+ }
14468
+ }
14469
+ return { kind: "admit" };
14470
+ }
14471
+
14433
14472
  // src/teams-peer-classifier.ts
14434
14473
  function classifyTeamsPeerMessage(input) {
14435
14474
  const { activity, config: config2 } = input;
@@ -14901,34 +14940,8 @@ async function processPendingFile(filename) {
14901
14940
  } catch {
14902
14941
  return;
14903
14942
  }
14904
- if (!decision.forward) {
14905
- process.stderr.write(
14906
- `teams-channel: dropped activity id=${activity.id ?? "?"} reason=${decision.reason}
14907
- `
14908
- );
14909
- return;
14910
- }
14911
14943
  const senderPolicyDecision = decideSenderPolicyForwardTeams(activity, MSTEAMS_SENDER_POLICY);
14912
- if (!senderPolicyDecision.forward) {
14913
- const subReason = classifyTeamsPolicyBlock(activity, MSTEAMS_SENDER_POLICY);
14914
- process.stderr.write(
14915
- `teams-channel: dropped activity id=${activity.id ?? "?"} reason=sender_policy sub=${subReason} mode=${MSTEAMS_SENDER_POLICY.mode}
14916
- `
14917
- );
14918
- await maybeSendTeamsSenderPolicyDecline({
14919
- serviceUrl: activity.serviceUrl,
14920
- conversationId: activity.conversation?.id,
14921
- replyToId: activity.id,
14922
- senderId: activity.from?.aadObjectId ?? activity.from?.id,
14923
- subReason
14924
- }).catch((err) => {
14925
- process.stderr.write(
14926
- `teams-channel: decline reply failed: ${err.message}
14927
- `
14928
- );
14929
- });
14930
- return;
14931
- }
14944
+ const policyBlockReason = senderPolicyDecision.forward ? void 0 : classifyTeamsPolicyBlock(activity, MSTEAMS_SENDER_POLICY);
14932
14945
  const peerDecision = classifyTeamsPeerMessage({
14933
14946
  activity,
14934
14947
  config: {
@@ -14937,11 +14950,42 @@ async function processPendingFile(filename) {
14937
14950
  mode: PEER_AGENT_MODE
14938
14951
  }
14939
14952
  });
14940
- if (peerDecision.classification === "peer_agent" && peerDecision.action === "drop") {
14953
+ const peerKind = peerDecision.classification === "peer_agent" && peerDecision.action === "drop" ? "drop" : peerDecision.classification === "peer_agent" ? "peer-ingress" : "human";
14954
+ const senderTenantId = activity.channelData?.tenant?.id ?? activity.conversation?.tenantId;
14955
+ const homeTenantId = process.env.MSTEAMS_HOME_TENANT_ID;
14956
+ const sameOrg = !!homeTenantId && homeTenantId !== "common" && senderTenantId === homeTenantId;
14957
+ const access = decideInboundAccess({
14958
+ content: decision.forward ? { forward: true } : { forward: false, reason: decision.reason },
14959
+ isSelf: false,
14960
+ // self-drop handled inside decideTeamsActivityForward
14961
+ isBot: true,
14962
+ // the peer classifier ran for every activity; 'human' falls through
14963
+ peer: {
14964
+ kind: peerKind,
14965
+ reason: "reason" in peerDecision ? peerDecision.reason : void 0
14966
+ },
14967
+ senderPolicy: senderPolicyDecision.forward ? { forward: true } : { forward: false, reason: policyBlockReason },
14968
+ sameOrg
14969
+ });
14970
+ if (access.kind === "drop") {
14941
14971
  process.stderr.write(
14942
- `teams-channel: dropped peer activity id=${activity.id ?? "?"} reason=${peerDecision.reason}
14972
+ `teams-channel: inbound drop id=${activity.id ?? "?"} reason=${access.reason}
14943
14973
  `
14944
14974
  );
14975
+ if (access.decline !== void 0 && policyBlockReason) {
14976
+ await maybeSendTeamsSenderPolicyDecline({
14977
+ serviceUrl: activity.serviceUrl,
14978
+ conversationId: activity.conversation?.id,
14979
+ replyToId: activity.id,
14980
+ senderId: activity.from?.aadObjectId ?? activity.from?.id,
14981
+ subReason: policyBlockReason
14982
+ }).catch((err) => {
14983
+ process.stderr.write(
14984
+ `teams-channel: decline reply failed: ${err.message}
14985
+ `
14986
+ );
14987
+ });
14988
+ }
14945
14989
  return;
14946
14990
  }
14947
14991
  if (activity.serviceUrl && activity.conversation?.id) {