@integrity-labs/agt-cli 0.19.19 → 0.19.21

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
  }
@@ -14217,6 +14217,9 @@ function classifyPeerMessage(msg, cfg, self) {
14217
14217
  if (self.bot_id !== null && msg.from.id === self.bot_id) {
14218
14218
  return { kind: "self" };
14219
14219
  }
14220
+ if (cfg.peer_disabled_mode === "all") {
14221
+ return { kind: "drop", reason: "peer_disabled_all" };
14222
+ }
14220
14223
  if (cfg.peer_agent_mode === "off") {
14221
14224
  return { kind: "drop", reason: "mode_off" };
14222
14225
  }
@@ -14232,6 +14235,12 @@ function classifyPeerMessage(msg, cfg, self) {
14232
14235
  if (!peer) {
14233
14236
  return { kind: "drop", reason: "unknown_peer" };
14234
14237
  }
14238
+ if (peer.gate_path === null) {
14239
+ return { kind: "drop", reason: "cross_team_grant_missing" };
14240
+ }
14241
+ if (cfg.peer_disabled_mode === "cross_team_only" && peer.gate_path !== void 0 && peer.gate_path !== "same_team") {
14242
+ return { kind: "drop", reason: "peer_disabled_cross_team" };
14243
+ }
14235
14244
  if (self.bot_id === null || self.bot_username === null) {
14236
14245
  return { kind: "drop", reason: "self_resolution_pending" };
14237
14246
  }
@@ -14272,7 +14281,7 @@ function classifyAddressing(msg, ourBotId, ourBotUsername) {
14272
14281
  return { addressed: false, viaReplyOnly: false };
14273
14282
  }
14274
14283
  var CODE_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
14275
- function parsePeersEnv(raw) {
14284
+ function parsePeersEnv(raw, gateRaw) {
14276
14285
  if (!raw || raw.trim().length === 0) return [];
14277
14286
  let parsed;
14278
14287
  try {
@@ -14281,11 +14290,54 @@ function parsePeersEnv(raw) {
14281
14290
  return [];
14282
14291
  }
14283
14292
  if (!Array.isArray(parsed)) return [];
14293
+ const gateMap = /* @__PURE__ */ new Map();
14294
+ let gateConfigInvalid = false;
14295
+ if (gateRaw && gateRaw.trim().length > 0) {
14296
+ try {
14297
+ const gateParsed = JSON.parse(gateRaw);
14298
+ if (!gateParsed || typeof gateParsed !== "object" || Array.isArray(gateParsed)) {
14299
+ gateConfigInvalid = true;
14300
+ } else {
14301
+ for (const [k, v] of Object.entries(gateParsed)) {
14302
+ if (v === null) {
14303
+ gateMap.set(k, null);
14304
+ } else if (typeof v === "string") {
14305
+ const isGrantPath = v.startsWith("grant:") && v.slice("grant:".length).trim().length > 0;
14306
+ if (v === "same_team" || v === "intra_org_unrestricted" || isGrantPath) {
14307
+ gateMap.set(k, v);
14308
+ } else {
14309
+ gateConfigInvalid = true;
14310
+ break;
14311
+ }
14312
+ } else {
14313
+ gateConfigInvalid = true;
14314
+ break;
14315
+ }
14316
+ }
14317
+ }
14318
+ } catch {
14319
+ gateConfigInvalid = true;
14320
+ }
14321
+ if (gateConfigInvalid) {
14322
+ console.error(
14323
+ "[telegram-peer-classifier] TELEGRAM_PEERS_GATE is present but malformed; failing closed (every peer drops as cross_team_grant_missing)"
14324
+ );
14325
+ }
14326
+ }
14284
14327
  const out = [];
14285
14328
  for (const entry of parsed) {
14286
14329
  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_id === "number" && Number.isInteger(entry.bot_id) && entry.bot_id > 0) {
14287
14330
  const e = entry;
14288
- out.push({ code_name: e.code_name, bot_id: e.bot_id, agent_id: e.agent_id });
14331
+ const peer = { code_name: e.code_name, bot_id: e.bot_id, agent_id: e.agent_id };
14332
+ if (gateConfigInvalid) {
14333
+ peer.gate_path = null;
14334
+ } else {
14335
+ const key2 = String(e.bot_id);
14336
+ if (gateMap.has(key2)) {
14337
+ peer.gate_path = gateMap.get(key2) ?? null;
14338
+ }
14339
+ }
14340
+ out.push(peer);
14289
14341
  }
14290
14342
  }
14291
14343
  return out;
@@ -14615,6 +14667,154 @@ function createDefaultPeerRateApiClient(args) {
14615
14667
  };
14616
14668
  }
14617
14669
 
14670
+ // src/cross-team-peer-audit-client.ts
14671
+ var REQUEST_TIMEOUT_MS = 1e4;
14672
+ function createCrossTeamPeerAuditClient(args) {
14673
+ if (!args.agtHost || !args.agtApiKey || !args.agentId) return null;
14674
+ const fetchImpl = args.fetchImpl ?? fetch;
14675
+ const log = args.log ?? (() => {
14676
+ });
14677
+ const base = args.agtHost.replace(/\/+$/, "");
14678
+ const agentId = args.agentId;
14679
+ const apiKey = args.agtApiKey;
14680
+ let cachedToken = null;
14681
+ let cachedTokenExpiresAt = 0;
14682
+ async function getToken() {
14683
+ if (cachedToken && Date.now() < cachedTokenExpiresAt) return cachedToken;
14684
+ const resp = await fetchImpl(`${base}/host/exchange`, {
14685
+ method: "POST",
14686
+ headers: { "Content-Type": "application/json" },
14687
+ body: JSON.stringify({ host_key: apiKey }),
14688
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
14689
+ });
14690
+ if (!resp.ok) {
14691
+ const body = await resp.text().catch(() => "");
14692
+ throw new Error(`/host/exchange failed (${resp.status}): ${body.slice(0, 200)}`);
14693
+ }
14694
+ const data = await resp.json();
14695
+ cachedToken = data.token;
14696
+ cachedTokenExpiresAt = data.expires_at ? new Date(data.expires_at).getTime() - 12e4 : Date.now() + 55 * 6e4;
14697
+ return cachedToken;
14698
+ }
14699
+ async function postOnce(event) {
14700
+ const token = await getToken();
14701
+ return fetchImpl(`${base}/host/cross-team-peer-event`, {
14702
+ method: "POST",
14703
+ headers: {
14704
+ Authorization: `Bearer ${token}`,
14705
+ "Content-Type": "application/json"
14706
+ },
14707
+ body: JSON.stringify({ agent_id: agentId, ...event }),
14708
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
14709
+ });
14710
+ }
14711
+ async function emitAsync(event) {
14712
+ try {
14713
+ let resp = await postOnce(event);
14714
+ if (resp.status === 401) {
14715
+ cachedToken = null;
14716
+ cachedTokenExpiresAt = 0;
14717
+ resp = await postOnce(event);
14718
+ }
14719
+ if (!resp.ok) {
14720
+ const body = await resp.text().catch(() => "");
14721
+ log(
14722
+ `cross-team-peer-audit: POST failed (${resp.status}) event=${event.event_type}: ${body.slice(0, 200)}`
14723
+ );
14724
+ }
14725
+ } catch (err) {
14726
+ log(`cross-team-peer-audit: emit threw event=${event.event_type}: ${err.message}`);
14727
+ }
14728
+ }
14729
+ return {
14730
+ emit(event) {
14731
+ void emitAsync(event);
14732
+ }
14733
+ };
14734
+ }
14735
+
14736
+ // src/observed-chat-client.ts
14737
+ var REQUEST_TIMEOUT_MS2 = 1e4;
14738
+ function createObservedChatClient(args) {
14739
+ if (!args.agtHost || !args.agtApiKey || !args.agentId) return null;
14740
+ const fetchImpl = args.fetchImpl ?? fetch;
14741
+ const log = args.log ?? (() => {
14742
+ });
14743
+ const base = args.agtHost.replace(/\/+$/, "");
14744
+ const agentId = args.agentId;
14745
+ const apiKey = args.agtApiKey;
14746
+ const reported = /* @__PURE__ */ new Map();
14747
+ let cachedToken = null;
14748
+ let cachedTokenExpiresAt = 0;
14749
+ async function getToken() {
14750
+ if (cachedToken && Date.now() < cachedTokenExpiresAt) return cachedToken;
14751
+ const resp = await fetchImpl(`${base}/host/exchange`, {
14752
+ method: "POST",
14753
+ headers: { "Content-Type": "application/json" },
14754
+ body: JSON.stringify({ host_key: apiKey }),
14755
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS2)
14756
+ });
14757
+ if (!resp.ok) {
14758
+ const body = await resp.text().catch(() => "");
14759
+ throw new Error(`/host/exchange failed (${resp.status}): ${body.slice(0, 200)}`);
14760
+ }
14761
+ const data = await resp.json();
14762
+ cachedToken = data.token;
14763
+ cachedTokenExpiresAt = data.expires_at ? new Date(data.expires_at).getTime() - 12e4 : Date.now() + 55 * 6e4;
14764
+ return cachedToken;
14765
+ }
14766
+ async function postOnce(chat) {
14767
+ const token = await getToken();
14768
+ return fetchImpl(`${base}/host/observed-chat`, {
14769
+ method: "POST",
14770
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
14771
+ body: JSON.stringify({ agent_id: agentId, ...chat }),
14772
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS2)
14773
+ });
14774
+ }
14775
+ async function emitAsync(chat) {
14776
+ try {
14777
+ let resp = await postOnce(chat);
14778
+ if (resp.status === 401) {
14779
+ cachedToken = null;
14780
+ cachedTokenExpiresAt = 0;
14781
+ resp = await postOnce(chat);
14782
+ }
14783
+ if (!resp.ok) {
14784
+ const body = await resp.text().catch(() => "");
14785
+ log(
14786
+ `observed-chat: POST failed (${resp.status}) chat=${chat.chat_id}: ${body.slice(0, 200)}`
14787
+ );
14788
+ return false;
14789
+ }
14790
+ return true;
14791
+ } catch (err) {
14792
+ log(`observed-chat: emit threw chat=${chat.chat_id}: ${err.message}`);
14793
+ return false;
14794
+ }
14795
+ }
14796
+ return {
14797
+ observe(chat) {
14798
+ const prev = reported.get(chat.chat_id);
14799
+ if (prev && prev.title === (chat.chat_title ?? void 0) && prev.type === (chat.chat_type ?? void 0)) {
14800
+ return;
14801
+ }
14802
+ const snapshot = {
14803
+ title: chat.chat_title ?? void 0,
14804
+ type: chat.chat_type ?? void 0
14805
+ };
14806
+ reported.set(chat.chat_id, snapshot);
14807
+ void emitAsync(chat).then((ok) => {
14808
+ if (ok) return;
14809
+ const current = reported.get(chat.chat_id);
14810
+ if (current?.title === snapshot.title && current?.type === snapshot.type) {
14811
+ reported.delete(chat.chat_id);
14812
+ }
14813
+ });
14814
+ }
14815
+ };
14816
+ }
14817
+
14618
14818
  // src/telegram-channel.ts
14619
14819
  function redactId(id) {
14620
14820
  return createHash("sha256").update(String(id)).digest("hex").slice(0, 8);
@@ -14629,18 +14829,45 @@ var ALLOWED_CHATS = new Set(
14629
14829
  var PEER_CLASSIFIER_CONFIG = {
14630
14830
  peer_agent_mode: parsePeerAgentModeEnv(process.env.TELEGRAM_PEER_AGENT_MODE),
14631
14831
  peer_group_ids: parsePeerGroupIdsEnv(process.env.TELEGRAM_PEER_GROUP_IDS),
14632
- peers: parsePeersEnv(process.env.TELEGRAM_PEERS)
14832
+ peers: parsePeersEnv(process.env.TELEGRAM_PEERS, process.env.TELEGRAM_PEERS_GATE),
14833
+ peer_disabled_mode: "off"
14834
+ // populated below from PEER_DISABLED_MODE
14633
14835
  };
14634
- var PEER_DISABLED_GLOBAL = (() => {
14635
- const v = (process.env.TELEGRAM_PEER_DISABLED ?? "").toLowerCase();
14636
- return v === "1" || v === "true" || v === "yes";
14836
+ var PEER_DISABLED_MODE = (() => {
14837
+ const next = (process.env.PEER_DISABLED ?? "").trim().toLowerCase();
14838
+ if (next === "off" || next === "cross_team_only" || next === "all") return next;
14839
+ if (next.length > 0) {
14840
+ process.stderr.write(
14841
+ `telegram-channel(${AGENT_CODE_NAME}): invalid PEER_DISABLED=${JSON.stringify(process.env.PEER_DISABLED)} \u2014 defaulting to 'all' (fail-closed)
14842
+ `
14843
+ );
14844
+ return "all";
14845
+ }
14846
+ const legacy = (process.env.TELEGRAM_PEER_DISABLED ?? "").trim().toLowerCase();
14847
+ return legacy === "1" || legacy === "true" || legacy === "yes" ? "all" : "off";
14637
14848
  })();
14638
- if (PEER_DISABLED_GLOBAL) {
14849
+ var PEER_DISABLED_GLOBAL = PEER_DISABLED_MODE === "all";
14850
+ if (PEER_DISABLED_MODE !== "off") {
14639
14851
  process.stderr.write(
14640
- `telegram-channel(${AGENT_CODE_NAME}): TELEGRAM_PEER_DISABLED=true \u2014 peer ingress + outgress short-circuited
14852
+ `telegram-channel(${AGENT_CODE_NAME}): PEER_DISABLED=${PEER_DISABLED_MODE} \u2014 ${PEER_DISABLED_MODE === "all" ? "every peer surface short-circuited" : "cross-team peers dropped, same-team peers admitted"}
14641
14853
  `
14642
14854
  );
14643
14855
  }
14856
+ PEER_CLASSIFIER_CONFIG.peer_disabled_mode = PEER_DISABLED_MODE;
14857
+ var crossTeamPeerAuditClient = createCrossTeamPeerAuditClient({
14858
+ agtHost: AGT_HOST,
14859
+ agtApiKey: AGT_API_KEY,
14860
+ agentId: process.env.AGT_AGENT_ID ?? null,
14861
+ log: (line) => process.stderr.write(`telegram-channel(${AGENT_CODE_NAME}): ${line}
14862
+ `)
14863
+ });
14864
+ var observedChatClient = createObservedChatClient({
14865
+ agtHost: AGT_HOST,
14866
+ agtApiKey: AGT_API_KEY,
14867
+ agentId: process.env.AGT_AGENT_ID ?? null,
14868
+ log: (line) => process.stderr.write(`telegram-channel(${AGENT_CODE_NAME}): ${line}
14869
+ `)
14870
+ });
14644
14871
  var peerRateApiClient = createDefaultPeerRateApiClient({
14645
14872
  agtHost: AGT_HOST,
14646
14873
  agtApiKey: AGT_API_KEY,
@@ -15588,6 +15815,11 @@ async function pollLoop() {
15588
15815
  if (content.length === 0 && classifiedAttachments.length === 0) continue;
15589
15816
  const chatId = String(msg.chat.id);
15590
15817
  if (ALLOWED_CHATS.size > 0 && !ALLOWED_CHATS.has(chatId)) continue;
15818
+ observedChatClient?.observe({
15819
+ chat_id: chatId,
15820
+ chat_title: msg.chat.title ?? msg.chat.username ?? msg.chat.first_name ?? null,
15821
+ chat_type: msg.chat.type ?? null
15822
+ });
15591
15823
  const trimmedContent = content.trim();
15592
15824
  if (isHelpSyntax(trimmedContent)) {
15593
15825
  const disposition = await classifyRestartCommand(trimmedContent.replace(/^\/help/, "/restart"));
@@ -15669,6 +15901,16 @@ async function pollLoop() {
15669
15901
  `telegram-channel(${AGENT_CODE_NAME}): peer_drop reason=${classification.reason} chat=${redactId(chatId)} from=${redactId(String(msg.from?.id ?? "unknown"))}
15670
15902
  `
15671
15903
  );
15904
+ if (classification.reason === "cross_team_grant_missing") {
15905
+ crossTeamPeerAuditClient?.emit({
15906
+ event_type: "telegram.peer.drop.cross_team_grant_missing",
15907
+ peer_agent_id: null,
15908
+ gate_path: null,
15909
+ grant_id: null,
15910
+ chat_id: String(chatId),
15911
+ message_id: String(msg.message_id)
15912
+ });
15913
+ }
15672
15914
  continue;
15673
15915
  }
15674
15916
  if (classification.kind === "peer-ingress") {
@@ -15695,6 +15937,26 @@ async function pollLoop() {
15695
15937
  code_name: classification.peer.code_name,
15696
15938
  agent_id: classification.peer.agent_id
15697
15939
  };
15940
+ const gate = classification.peer.gate_path;
15941
+ if (gate && gate !== "same_team" && gate !== "intra_org_unrestricted" && gate.startsWith("grant:")) {
15942
+ crossTeamPeerAuditClient?.emit({
15943
+ event_type: "telegram.peer.ingress.cross_team",
15944
+ peer_agent_id: classification.peer.agent_id || null,
15945
+ gate_path: gate,
15946
+ grant_id: gate.slice("grant:".length),
15947
+ chat_id: String(chatId),
15948
+ message_id: String(msg.message_id)
15949
+ });
15950
+ } else if (gate === "intra_org_unrestricted") {
15951
+ crossTeamPeerAuditClient?.emit({
15952
+ event_type: "telegram.peer.ingress.cross_team",
15953
+ peer_agent_id: classification.peer.agent_id || null,
15954
+ gate_path: gate,
15955
+ grant_id: null,
15956
+ chat_id: String(chatId),
15957
+ message_id: String(msg.message_id)
15958
+ });
15959
+ }
15698
15960
  }
15699
15961
  }
15700
15962
  const messageId = String(msg.message_id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@integrity-labs/agt-cli",
3
- "version": "0.19.19",
3
+ "version": "0.19.21",
4
4
  "description": "Augmented Team CLI — agent provisioning and management",
5
5
  "type": "module",
6
6
  "engines": {