@integrity-labs/agt-cli 0.28.324 → 0.28.326

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.
@@ -14693,6 +14693,7 @@ function createKanbanCardActiveClient(args) {
14693
14693
  const agentId = args.agentId;
14694
14694
  const apiKey = args.agtApiKey;
14695
14695
  const cache = /* @__PURE__ */ new Map();
14696
+ const threadCache = /* @__PURE__ */ new Map();
14696
14697
  let cachedToken = null;
14697
14698
  let cachedTokenExpiresAt = 0;
14698
14699
  async function getToken() {
@@ -14737,6 +14738,26 @@ function createKanbanCardActiveClient(args) {
14737
14738
  const data = await resp.json();
14738
14739
  return data.active === true;
14739
14740
  }
14741
+ async function queryThreadOnce(sourceIntegration, channel) {
14742
+ const token = await getToken();
14743
+ const qs = new URLSearchParams({ agent_id: agentId, source_integration: sourceIntegration, channel });
14744
+ return fetchImpl(`${base}/host/kanban/active-source-thread?${qs.toString()}`, {
14745
+ method: "GET",
14746
+ headers: { Authorization: `Bearer ${token}` },
14747
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS2)
14748
+ });
14749
+ }
14750
+ async function queryThread(sourceIntegration, channel) {
14751
+ let resp = await queryThreadOnce(sourceIntegration, channel);
14752
+ if (resp.status === 401) {
14753
+ cachedToken = null;
14754
+ cachedTokenExpiresAt = 0;
14755
+ resp = await queryThreadOnce(sourceIntegration, channel);
14756
+ }
14757
+ if (!resp.ok) return void 0;
14758
+ const data = await resp.json();
14759
+ return data.thread_ts ?? void 0;
14760
+ }
14740
14761
  return {
14741
14762
  async isCardActive(sourceIntegration, sourceExternalId) {
14742
14763
  const key = `${sourceIntegration}:${sourceExternalId}`;
@@ -14751,6 +14772,20 @@ function createKanbanCardActiveClient(args) {
14751
14772
  log(`kanban-card-active: query threw key=${key}: ${err.message}`);
14752
14773
  return false;
14753
14774
  }
14775
+ },
14776
+ async getActiveCardSourceThread(sourceIntegration, channel) {
14777
+ const key = `thread:${sourceIntegration}:${channel}`;
14778
+ const t = now();
14779
+ const hit = threadCache.get(key);
14780
+ if (hit && t < hit.expiresAt) return hit.threadTs;
14781
+ try {
14782
+ const threadTs = await queryThread(sourceIntegration, channel);
14783
+ threadCache.set(key, { threadTs, expiresAt: t + (threadTs ? positiveTtlMs : negativeTtlMs) });
14784
+ return threadTs;
14785
+ } catch (err) {
14786
+ log(`kanban-card-active: thread query threw key=${key}: ${err.message}`);
14787
+ return void 0;
14788
+ }
14754
14789
  }
14755
14790
  };
14756
14791
  }
@@ -14549,6 +14549,11 @@ function decideModeForward(evt, policy) {
14549
14549
  const label = extractAugmentedSlackLabel(evt);
14550
14550
  if (!label)
14551
14551
  return { forward: false, reason: "sender_policy" };
14552
+ if (policy.registeredAgentSlackUserIds !== void 0) {
14553
+ if (typeof evt.user !== "string" || !policy.registeredAgentSlackUserIds.has(evt.user)) {
14554
+ return { forward: false, reason: "sender_policy" };
14555
+ }
14556
+ }
14552
14557
  if (policy.mode === "team_agents_only" || policy.mode === "manager_only" || policy.mode === "team_only") {
14553
14558
  if (!policy.teamId || label.teamId !== policy.teamId) {
14554
14559
  return { forward: false, reason: "sender_policy" };
@@ -16279,6 +16284,7 @@ function createKanbanCardActiveClient(args) {
16279
16284
  const agentId = args.agentId;
16280
16285
  const apiKey = args.agtApiKey;
16281
16286
  const cache = /* @__PURE__ */ new Map();
16287
+ const threadCache = /* @__PURE__ */ new Map();
16282
16288
  let cachedToken = null;
16283
16289
  let cachedTokenExpiresAt = 0;
16284
16290
  async function getToken() {
@@ -16323,6 +16329,26 @@ function createKanbanCardActiveClient(args) {
16323
16329
  const data = await resp.json();
16324
16330
  return data.active === true;
16325
16331
  }
16332
+ async function queryThreadOnce(sourceIntegration, channel) {
16333
+ const token = await getToken();
16334
+ const qs = new URLSearchParams({ agent_id: agentId, source_integration: sourceIntegration, channel });
16335
+ return fetchImpl(`${base}/host/kanban/active-source-thread?${qs.toString()}`, {
16336
+ method: "GET",
16337
+ headers: { Authorization: `Bearer ${token}` },
16338
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
16339
+ });
16340
+ }
16341
+ async function queryThread(sourceIntegration, channel) {
16342
+ let resp = await queryThreadOnce(sourceIntegration, channel);
16343
+ if (resp.status === 401) {
16344
+ cachedToken = null;
16345
+ cachedTokenExpiresAt = 0;
16346
+ resp = await queryThreadOnce(sourceIntegration, channel);
16347
+ }
16348
+ if (!resp.ok) return void 0;
16349
+ const data = await resp.json();
16350
+ return data.thread_ts ?? void 0;
16351
+ }
16326
16352
  return {
16327
16353
  async isCardActive(sourceIntegration, sourceExternalId) {
16328
16354
  const key2 = `${sourceIntegration}:${sourceExternalId}`;
@@ -16337,6 +16363,20 @@ function createKanbanCardActiveClient(args) {
16337
16363
  log(`kanban-card-active: query threw key=${key2}: ${err.message}`);
16338
16364
  return false;
16339
16365
  }
16366
+ },
16367
+ async getActiveCardSourceThread(sourceIntegration, channel) {
16368
+ const key2 = `thread:${sourceIntegration}:${channel}`;
16369
+ const t = now();
16370
+ const hit = threadCache.get(key2);
16371
+ if (hit && t < hit.expiresAt) return hit.threadTs;
16372
+ try {
16373
+ const threadTs = await queryThread(sourceIntegration, channel);
16374
+ threadCache.set(key2, { threadTs, expiresAt: t + (threadTs ? positiveTtlMs : negativeTtlMs) });
16375
+ return threadTs;
16376
+ } catch (err) {
16377
+ log(`kanban-card-active: thread query threw key=${key2}: ${err.message}`);
16378
+ return void 0;
16379
+ }
16340
16380
  }
16341
16381
  };
16342
16382
  }
@@ -16344,7 +16384,9 @@ function createKanbanCardActiveClient(args) {
16344
16384
  // src/slack-reply-threading.ts
16345
16385
  function resolveReplyThreadTs(input) {
16346
16386
  if (input.threadTs) return input.threadTs;
16347
- return input.messageTs || void 0;
16387
+ if (input.messageTs) return input.messageTs;
16388
+ if (input.threadTs === null) return void 0;
16389
+ return input.activeCardThreadTs || void 0;
16348
16390
  }
16349
16391
 
16350
16392
  // src/restart-confirm.ts
@@ -17726,7 +17768,8 @@ function parsePeersEnv(raw, gateRaw) {
17726
17768
  return [];
17727
17769
  const gateMap = /* @__PURE__ */ new Map();
17728
17770
  let gateConfigInvalid = false;
17729
- if (gateRaw && gateRaw.trim().length > 0) {
17771
+ const gatePresent = !!(gateRaw && gateRaw.trim().length > 0);
17772
+ if (gatePresent) {
17730
17773
  try {
17731
17774
  const gateParsed = JSON.parse(gateRaw);
17732
17775
  if (!gateParsed || typeof gateParsed !== "object" || Array.isArray(gateParsed)) {
@@ -17769,6 +17812,8 @@ function parsePeersEnv(raw, gateRaw) {
17769
17812
  peer.gate_path = null;
17770
17813
  } else if (gateMap.has(e.bot_user_id)) {
17771
17814
  peer.gate_path = gateMap.get(e.bot_user_id) ?? null;
17815
+ } else if (gatePresent) {
17816
+ peer.gate_path = null;
17772
17817
  }
17773
17818
  out.push(peer);
17774
17819
  }
@@ -18343,13 +18388,21 @@ var SLACK_SENDER_POLICY = (() => {
18343
18388
  );
18344
18389
  }
18345
18390
  const internalOnlyFields = internalOnly ? { internalOnly: true, homeTeamId } : {};
18346
- if (raw === "all") return { mode: "all", ...internalOnlyFields };
18347
- if (raw === "agents_only") return { mode: "agents_only", ...internalOnlyFields };
18391
+ const slackPeersRaw = process.env.SLACK_PEERS;
18392
+ const registryFields = slackPeersRaw && slackPeersRaw.trim().length > 0 ? {
18393
+ registeredAgentSlackUserIds: new Set(
18394
+ parsePeersEnv(slackPeersRaw, process.env.SLACK_PEERS_GATE).map(
18395
+ (p) => p.bot_user_id
18396
+ )
18397
+ )
18398
+ } : {};
18399
+ if (raw === "all") return { mode: "all", ...internalOnlyFields, ...registryFields };
18400
+ if (raw === "agents_only") return { mode: "agents_only", ...internalOnlyFields, ...registryFields };
18348
18401
  if (raw === "team_agents_only") {
18349
18402
  if (!AGT_TEAM_ID) {
18350
18403
  throw new Error("SLACK_SENDER_POLICY=team_agents_only requires AGT_TEAM_ID");
18351
18404
  }
18352
- return { mode: "team_agents_only", teamId: AGT_TEAM_ID, ...internalOnlyFields };
18405
+ return { mode: "team_agents_only", teamId: AGT_TEAM_ID, ...internalOnlyFields, ...registryFields };
18353
18406
  }
18354
18407
  if (raw === "team_only") {
18355
18408
  if (!AGT_TEAM_ID) {
@@ -18361,7 +18414,8 @@ var SLACK_SENDER_POLICY = (() => {
18361
18414
  mode: "team_only",
18362
18415
  teamId: AGT_TEAM_ID,
18363
18416
  teamPrincipalSlackUserIds,
18364
- ...internalOnlyFields
18417
+ ...internalOnlyFields,
18418
+ ...registryFields
18365
18419
  };
18366
18420
  }
18367
18421
  if (raw === "manager_only") {
@@ -18374,7 +18428,7 @@ var SLACK_SENDER_POLICY = (() => {
18374
18428
  "SLACK_SENDER_POLICY=manager_only requires SLACK_SENDER_POLICY_PRINCIPAL_ID. Set agent.reports_to_person with a slack_user_id in contact_preferences and re-run /host/refresh."
18375
18429
  );
18376
18430
  }
18377
- return { mode: "manager_only", teamId: AGT_TEAM_ID, principalSlackUserId, ...internalOnlyFields };
18431
+ return { mode: "manager_only", teamId: AGT_TEAM_ID, principalSlackUserId, ...internalOnlyFields, ...registryFields };
18378
18432
  }
18379
18433
  throw new Error(`Invalid SLACK_SENDER_POLICY=${JSON.stringify(process.env.SLACK_SENDER_POLICY)}`);
18380
18434
  })();
@@ -21145,10 +21199,12 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
21145
21199
  };
21146
21200
  }
21147
21201
  const bindingFeedback = bindingMode === "warn" ? binding.agentFeedback : void 0;
21202
+ const activeCardThreadTs = !scheduledDest && !enforceBound && !channelGuard.corrected && !thread_ts && !message_ts && kanbanCardActiveClient && typeof channel === "string" && channel ? await kanbanCardActiveClient.getActiveCardSourceThread("slack", channel) : void 0;
21148
21203
  const effectiveThreadTs = scheduledDest ? scheduledDest.thread_ts : enforceBound ? binding.threadTs : channelGuard.corrected ? channelGuard.threadTs : resolveReplyThreadTs({
21149
21204
  channel,
21150
21205
  threadTs: thread_ts,
21151
- messageTs: message_ts
21206
+ messageTs: message_ts,
21207
+ activeCardThreadTs
21152
21208
  });
21153
21209
  if (effectiveChannel) {
21154
21210
  recordReplyTargetClassification(
@@ -21826,10 +21882,12 @@ async function handleSendStructured(args) {
21826
21882
  if (typeof text !== "string" || !text) {
21827
21883
  return errResult("text is required (used as fallback for push notifications and unfurls)");
21828
21884
  }
21885
+ const activeCardThreadTs = !thread_ts && !message_ts && kanbanCardActiveClient ? await kanbanCardActiveClient.getActiveCardSourceThread("slack", channel) : void 0;
21829
21886
  const effectiveThreadTs = resolveReplyThreadTs({
21830
21887
  channel,
21831
21888
  threadTs: thread_ts,
21832
- messageTs: message_ts
21889
+ messageTs: message_ts,
21890
+ activeCardThreadTs
21833
21891
  });
21834
21892
  recordReplyTargetClassification(
21835
21893
  SLACK_AGENT_DIR,
@@ -14796,7 +14796,13 @@ function decideTeamsActivityForward(input) {
14796
14796
  }
14797
14797
  if (allowedTeamIds.size > 0) {
14798
14798
  const teamId = activity.channelData?.team?.id;
14799
- if (teamId && !allowedTeamIds.has(teamId)) {
14799
+ const convType = activity.conversation?.conversationType;
14800
+ const isDirect = convType === "personal" || convType === "groupChat";
14801
+ if (isDirect) {
14802
+ if (teamId && !allowedTeamIds.has(teamId)) {
14803
+ return { forward: false, reason: "team_not_allowed" };
14804
+ }
14805
+ } else if (!teamId || !allowedTeamIds.has(teamId)) {
14800
14806
  return { forward: false, reason: "team_not_allowed" };
14801
14807
  }
14802
14808
  }
@@ -14922,7 +14922,8 @@ function parsePeersEnv(raw, gateRaw) {
14922
14922
  return [];
14923
14923
  const gateMap = /* @__PURE__ */ new Map();
14924
14924
  let gateConfigInvalid = false;
14925
- if (gateRaw && gateRaw.trim().length > 0) {
14925
+ const gatePresent = !!(gateRaw && gateRaw.trim().length > 0);
14926
+ if (gatePresent) {
14926
14927
  try {
14927
14928
  const gateParsed = JSON.parse(gateRaw);
14928
14929
  if (!gateParsed || typeof gateParsed !== "object" || Array.isArray(gateParsed)) {
@@ -14963,6 +14964,8 @@ function parsePeersEnv(raw, gateRaw) {
14963
14964
  const key2 = String(e.bot_id);
14964
14965
  if (gateMap.has(key2)) {
14965
14966
  peer.gate_path = gateMap.get(key2) ?? null;
14967
+ } else if (gatePresent) {
14968
+ peer.gate_path = null;
14966
14969
  }
14967
14970
  }
14968
14971
  out.push(peer);
@@ -17106,6 +17109,7 @@ function createKanbanCardActiveClient(args) {
17106
17109
  const agentId = args.agentId;
17107
17110
  const apiKey = args.agtApiKey;
17108
17111
  const cache = /* @__PURE__ */ new Map();
17112
+ const threadCache = /* @__PURE__ */ new Map();
17109
17113
  let cachedToken = null;
17110
17114
  let cachedTokenExpiresAt = 0;
17111
17115
  async function getToken() {
@@ -17150,6 +17154,26 @@ function createKanbanCardActiveClient(args) {
17150
17154
  const data = await resp.json();
17151
17155
  return data.active === true;
17152
17156
  }
17157
+ async function queryThreadOnce(sourceIntegration, channel) {
17158
+ const token = await getToken();
17159
+ const qs = new URLSearchParams({ agent_id: agentId, source_integration: sourceIntegration, channel });
17160
+ return fetchImpl(`${base}/host/kanban/active-source-thread?${qs.toString()}`, {
17161
+ method: "GET",
17162
+ headers: { Authorization: `Bearer ${token}` },
17163
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS6)
17164
+ });
17165
+ }
17166
+ async function queryThread(sourceIntegration, channel) {
17167
+ let resp = await queryThreadOnce(sourceIntegration, channel);
17168
+ if (resp.status === 401) {
17169
+ cachedToken = null;
17170
+ cachedTokenExpiresAt = 0;
17171
+ resp = await queryThreadOnce(sourceIntegration, channel);
17172
+ }
17173
+ if (!resp.ok) return void 0;
17174
+ const data = await resp.json();
17175
+ return data.thread_ts ?? void 0;
17176
+ }
17153
17177
  return {
17154
17178
  async isCardActive(sourceIntegration, sourceExternalId) {
17155
17179
  const key2 = `${sourceIntegration}:${sourceExternalId}`;
@@ -17164,6 +17188,20 @@ function createKanbanCardActiveClient(args) {
17164
17188
  log(`kanban-card-active: query threw key=${key2}: ${err.message}`);
17165
17189
  return false;
17166
17190
  }
17191
+ },
17192
+ async getActiveCardSourceThread(sourceIntegration, channel) {
17193
+ const key2 = `thread:${sourceIntegration}:${channel}`;
17194
+ const t = now();
17195
+ const hit = threadCache.get(key2);
17196
+ if (hit && t < hit.expiresAt) return hit.threadTs;
17197
+ try {
17198
+ const threadTs = await queryThread(sourceIntegration, channel);
17199
+ threadCache.set(key2, { threadTs, expiresAt: t + (threadTs ? positiveTtlMs : negativeTtlMs) });
17200
+ return threadTs;
17201
+ } catch (err) {
17202
+ log(`kanban-card-active: thread query threw key=${key2}: ${err.message}`);
17203
+ return void 0;
17204
+ }
17167
17205
  }
17168
17206
  };
17169
17207
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@integrity-labs/agt-cli",
3
- "version": "0.28.324",
3
+ "version": "0.28.326",
4
4
  "description": "Augmented Team CLI — agent provisioning and management",
5
5
  "type": "module",
6
6
  "engines": {