@integrity-labs/agt-cli 0.19.18 → 0.19.20

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.
@@ -14734,6 +14734,202 @@ function buildChannelInfoResult(args) {
14734
14734
  return { ok: true, channel, bot_user_handle: botUserHandle };
14735
14735
  }
14736
14736
 
14737
+ // src/slack-peer-classifier.ts
14738
+ var CODE_NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
14739
+ function classifyPeerMessage(msg, cfg, self) {
14740
+ if (!msg.bot_id) return { kind: "human" };
14741
+ if (self.bot_user_id !== null && msg.user === self.bot_user_id) {
14742
+ return { kind: "self" };
14743
+ }
14744
+ if (cfg.peer_disabled_mode === "all") {
14745
+ return { kind: "drop", reason: "peer_disabled_all" };
14746
+ }
14747
+ if (cfg.peer_agent_mode === "off") {
14748
+ return { kind: "drop", reason: "mode_off" };
14749
+ }
14750
+ if (cfg.peer_group_ids.length === 0) {
14751
+ if (msg.channel_type !== "im") {
14752
+ return { kind: "drop", reason: "chat_not_allowed" };
14753
+ }
14754
+ } else if (!cfg.peer_group_ids.includes(msg.channel)) {
14755
+ return { kind: "drop", reason: "chat_not_allowed" };
14756
+ }
14757
+ const peer = cfg.peers.find((p) => p.bot_user_id === msg.user);
14758
+ if (!peer) {
14759
+ return { kind: "drop", reason: "unknown_peer" };
14760
+ }
14761
+ if (peer.gate_path === null) {
14762
+ return { kind: "drop", reason: "cross_team_grant_missing" };
14763
+ }
14764
+ if (cfg.peer_disabled_mode === "cross_team_only" && peer.gate_path !== void 0 && peer.gate_path !== "same_team") {
14765
+ return { kind: "drop", reason: "peer_disabled_cross_team" };
14766
+ }
14767
+ if (self.bot_user_id === null) {
14768
+ return { kind: "drop", reason: "self_resolution_pending" };
14769
+ }
14770
+ const addressing = classifyAddressing(msg, self.bot_user_id);
14771
+ if (!addressing.addressed) {
14772
+ return { kind: "drop", reason: "not_addressed" };
14773
+ }
14774
+ if (addressing.viaReplyOnly) {
14775
+ const inAllowedGroup = cfg.peer_group_ids.length > 0 && cfg.peer_group_ids.includes(msg.channel);
14776
+ const respondMode = cfg.peer_agent_mode === "respond";
14777
+ if (!(inAllowedGroup && respondMode)) {
14778
+ return { kind: "drop", reason: "not_addressed" };
14779
+ }
14780
+ }
14781
+ return { kind: "peer-ingress", peer };
14782
+ }
14783
+ function classifyAddressing(msg, ourBotUserId) {
14784
+ const text = msg.text ?? "";
14785
+ const mentionToken = `<@${ourBotUserId}>`;
14786
+ const viaMention = text.includes(mentionToken);
14787
+ const viaReply = !!msg.thread_ts && msg.thread_ts !== void 0 && msg.parent_user_id === ourBotUserId;
14788
+ if (viaMention) {
14789
+ return { addressed: true, viaReplyOnly: false };
14790
+ }
14791
+ if (viaReply) {
14792
+ return { addressed: true, viaReplyOnly: true };
14793
+ }
14794
+ return { addressed: false, viaReplyOnly: false };
14795
+ }
14796
+ function parsePeersEnv(raw, gateRaw) {
14797
+ if (!raw || raw.trim().length === 0) return [];
14798
+ let parsed;
14799
+ try {
14800
+ parsed = JSON.parse(raw);
14801
+ } catch {
14802
+ return [];
14803
+ }
14804
+ if (!Array.isArray(parsed)) return [];
14805
+ const gateMap = /* @__PURE__ */ new Map();
14806
+ let gateConfigInvalid = false;
14807
+ if (gateRaw && gateRaw.trim().length > 0) {
14808
+ try {
14809
+ const gateParsed = JSON.parse(gateRaw);
14810
+ if (!gateParsed || typeof gateParsed !== "object" || Array.isArray(gateParsed)) {
14811
+ gateConfigInvalid = true;
14812
+ } else {
14813
+ for (const [k, v] of Object.entries(gateParsed)) {
14814
+ if (v === null) {
14815
+ gateMap.set(k, null);
14816
+ } else if (typeof v === "string") {
14817
+ const isGrantPath = v.startsWith("grant:") && v.slice("grant:".length).trim().length > 0;
14818
+ if (v === "same_team" || v === "intra_org_unrestricted" || isGrantPath) {
14819
+ gateMap.set(k, v);
14820
+ } else {
14821
+ gateConfigInvalid = true;
14822
+ break;
14823
+ }
14824
+ } else {
14825
+ gateConfigInvalid = true;
14826
+ break;
14827
+ }
14828
+ }
14829
+ }
14830
+ } catch {
14831
+ gateConfigInvalid = true;
14832
+ }
14833
+ if (gateConfigInvalid) {
14834
+ console.error(
14835
+ "[slack-peer-classifier] SLACK_PEERS_GATE is present but malformed; failing closed (every peer drops as cross_team_grant_missing)"
14836
+ );
14837
+ }
14838
+ }
14839
+ const out = [];
14840
+ for (const entry of parsed) {
14841
+ if (entry && typeof entry === "object" && typeof entry.code_name === "string" && CODE_NAME_RE.test(entry.code_name) && typeof entry.agent_id === "string" && typeof entry.bot_user_id === "string" && entry.bot_user_id.length > 0) {
14842
+ const e = entry;
14843
+ const peer = {
14844
+ code_name: e.code_name,
14845
+ bot_user_id: e.bot_user_id,
14846
+ agent_id: e.agent_id
14847
+ };
14848
+ if (gateConfigInvalid) {
14849
+ peer.gate_path = null;
14850
+ } else if (gateMap.has(e.bot_user_id)) {
14851
+ peer.gate_path = gateMap.get(e.bot_user_id) ?? null;
14852
+ }
14853
+ out.push(peer);
14854
+ }
14855
+ }
14856
+ return out;
14857
+ }
14858
+ function parsePeerGroupIdsEnv(raw) {
14859
+ if (!raw) return [];
14860
+ return raw.split(",").map((s) => s.trim()).filter(Boolean);
14861
+ }
14862
+ function parsePeerAgentModeEnv(raw) {
14863
+ if (raw === "listen" || raw === "respond") return raw;
14864
+ return "off";
14865
+ }
14866
+
14867
+ // src/cross-team-peer-audit-client.ts
14868
+ var REQUEST_TIMEOUT_MS = 1e4;
14869
+ function createCrossTeamPeerAuditClient(args) {
14870
+ if (!args.agtHost || !args.agtApiKey || !args.agentId) return null;
14871
+ const fetchImpl = args.fetchImpl ?? fetch;
14872
+ const log = args.log ?? (() => {
14873
+ });
14874
+ const base = args.agtHost.replace(/\/+$/, "");
14875
+ const agentId = args.agentId;
14876
+ const apiKey = args.agtApiKey;
14877
+ let cachedToken = null;
14878
+ let cachedTokenExpiresAt = 0;
14879
+ async function getToken() {
14880
+ if (cachedToken && Date.now() < cachedTokenExpiresAt) return cachedToken;
14881
+ const resp = await fetchImpl(`${base}/host/exchange`, {
14882
+ method: "POST",
14883
+ headers: { "Content-Type": "application/json" },
14884
+ body: JSON.stringify({ host_key: apiKey }),
14885
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
14886
+ });
14887
+ if (!resp.ok) {
14888
+ const body = await resp.text().catch(() => "");
14889
+ throw new Error(`/host/exchange failed (${resp.status}): ${body.slice(0, 200)}`);
14890
+ }
14891
+ const data = await resp.json();
14892
+ cachedToken = data.token;
14893
+ cachedTokenExpiresAt = data.expires_at ? new Date(data.expires_at).getTime() - 12e4 : Date.now() + 55 * 6e4;
14894
+ return cachedToken;
14895
+ }
14896
+ async function postOnce(event) {
14897
+ const token = await getToken();
14898
+ return fetchImpl(`${base}/host/cross-team-peer-event`, {
14899
+ method: "POST",
14900
+ headers: {
14901
+ Authorization: `Bearer ${token}`,
14902
+ "Content-Type": "application/json"
14903
+ },
14904
+ body: JSON.stringify({ agent_id: agentId, ...event }),
14905
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
14906
+ });
14907
+ }
14908
+ async function emitAsync(event) {
14909
+ try {
14910
+ let resp = await postOnce(event);
14911
+ if (resp.status === 401) {
14912
+ cachedToken = null;
14913
+ cachedTokenExpiresAt = 0;
14914
+ resp = await postOnce(event);
14915
+ }
14916
+ if (!resp.ok) {
14917
+ const body = await resp.text().catch(() => "");
14918
+ log(
14919
+ `cross-team-peer-audit: POST failed (${resp.status}) event=${event.event_type}: ${body.slice(0, 200)}`
14920
+ );
14921
+ }
14922
+ } catch (err) {
14923
+ log(`cross-team-peer-audit: emit threw event=${event.event_type}: ${err.message}`);
14924
+ }
14925
+ }
14926
+ return {
14927
+ emit(event) {
14928
+ void emitAsync(event);
14929
+ }
14930
+ };
14931
+ }
14932
+
14737
14933
  // src/slack-channel.ts
14738
14934
  var BOT_TOKEN = process.env.SLACK_BOT_TOKEN;
14739
14935
  var APP_TOKEN = process.env.SLACK_APP_TOKEN;
@@ -14749,6 +14945,35 @@ var ALLOWED_USERS = new Set(
14749
14945
  );
14750
14946
  var THREAD_AUTO_FOLLOW = process.env.SLACK_THREAD_AUTO_FOLLOW ?? "off";
14751
14947
  var CHANNEL_RESPONSE_MODE = parseResponseMode(process.env.SLACK_CHANNEL_RESPONSE_MODE);
14948
+ var SLACK_PEER_DISABLED_MODE = (() => {
14949
+ const raw = (process.env.PEER_DISABLED ?? "").trim().toLowerCase();
14950
+ if (raw === "" || raw === "off") return "off";
14951
+ if (raw === "cross_team_only" || raw === "all") return raw;
14952
+ process.stderr.write(
14953
+ `slack-channel: invalid PEER_DISABLED=${JSON.stringify(process.env.PEER_DISABLED)} \u2014 defaulting to 'all' (fail-closed)
14954
+ `
14955
+ );
14956
+ return "all";
14957
+ })();
14958
+ if (SLACK_PEER_DISABLED_MODE !== "off") {
14959
+ process.stderr.write(
14960
+ `slack-channel: PEER_DISABLED=${SLACK_PEER_DISABLED_MODE} \u2014 ${SLACK_PEER_DISABLED_MODE === "all" ? "every peer surface short-circuited" : "cross-team peers dropped, same-team peers admitted"}
14961
+ `
14962
+ );
14963
+ }
14964
+ var crossTeamPeerAuditClient = createCrossTeamPeerAuditClient({
14965
+ agtHost: AGT_HOST,
14966
+ agtApiKey: AGT_API_KEY,
14967
+ agentId: AGT_AGENT_ID,
14968
+ log: (line) => process.stderr.write(`slack-channel: ${line}
14969
+ `)
14970
+ });
14971
+ var SLACK_PEER_CLASSIFIER_CONFIG = {
14972
+ peer_agent_mode: parsePeerAgentModeEnv(process.env.SLACK_PEER_AGENT_MODE),
14973
+ peer_group_ids: parsePeerGroupIdsEnv(process.env.SLACK_PEER_GROUP_IDS),
14974
+ peers: parsePeersEnv(process.env.SLACK_PEERS, process.env.SLACK_PEERS_GATE),
14975
+ peer_disabled_mode: SLACK_PEER_DISABLED_MODE
14976
+ };
14752
14977
  var RESPONSE_TIMEOUT_MS = 3e5;
14753
14978
  var pendingMessages = /* @__PURE__ */ new Map();
14754
14979
  var SLACK_AGENT_DIR = AGENT_CODE_NAME ? join2(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
@@ -16715,6 +16940,63 @@ async function connectSocketMode() {
16715
16940
  return;
16716
16941
  }
16717
16942
  if (ALLOWED_USERS.size > 0 && evt.user && !ALLOWED_USERS.has(evt.user)) return;
16943
+ if (evt.bot_id) {
16944
+ const peerMsg = {
16945
+ text: evt.text,
16946
+ channel: evt.channel ?? "",
16947
+ channel_type: evt.channel_type,
16948
+ user: evt.user,
16949
+ bot_id: evt.bot_id,
16950
+ thread_ts: evt.thread_ts,
16951
+ parent_user_id: evt.parent_user_id
16952
+ };
16953
+ const decision = classifyPeerMessage(
16954
+ peerMsg,
16955
+ SLACK_PEER_CLASSIFIER_CONFIG,
16956
+ { bot_user_id: botUserId }
16957
+ );
16958
+ if (decision.kind === "self") return;
16959
+ if (decision.kind === "drop") {
16960
+ const channelHash = createHash("sha256").update(evt.channel ?? "").digest("hex").slice(0, 8);
16961
+ process.stderr.write(
16962
+ `slack-channel: peer drop reason=${decision.reason} channel=${channelHash}
16963
+ `
16964
+ );
16965
+ if (decision.reason === "cross_team_grant_missing") {
16966
+ crossTeamPeerAuditClient?.emit({
16967
+ event_type: "slack.peer.drop.cross_team_grant_missing",
16968
+ peer_agent_id: null,
16969
+ gate_path: null,
16970
+ grant_id: null,
16971
+ chat_id: evt.channel ?? null,
16972
+ message_id: evt.ts ?? null
16973
+ });
16974
+ }
16975
+ return;
16976
+ }
16977
+ if (decision.kind === "peer-ingress") {
16978
+ const peerGate = decision.peer.gate_path;
16979
+ if (peerGate === "intra_org_unrestricted") {
16980
+ crossTeamPeerAuditClient?.emit({
16981
+ event_type: "slack.peer.ingress.cross_team",
16982
+ peer_agent_id: decision.peer.agent_id || null,
16983
+ gate_path: peerGate,
16984
+ grant_id: null,
16985
+ chat_id: evt.channel ?? null,
16986
+ message_id: evt.ts ?? null
16987
+ });
16988
+ } else if (typeof peerGate === "string" && peerGate.startsWith("grant:")) {
16989
+ crossTeamPeerAuditClient?.emit({
16990
+ event_type: "slack.peer.ingress.cross_team",
16991
+ peer_agent_id: decision.peer.agent_id || null,
16992
+ gate_path: peerGate,
16993
+ grant_id: peerGate.slice("grant:".length),
16994
+ chat_id: evt.channel ?? null,
16995
+ message_id: evt.ts ?? null
16996
+ });
16997
+ }
16998
+ }
16999
+ }
16718
17000
  if (evt.type === "app_mention") {
16719
17001
  rememberThread(evt.channel, trackTs, "mentioned");
16720
17002
  }