@agentconnect.md/daemon 1.0.0-rc.59 → 1.0.0-rc.60

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.
package/dist/index.js CHANGED
@@ -13132,6 +13132,22 @@ const IntegrationSpec = object({
13132
13132
  const IntegrationUpsert = IntegrationSpec;
13133
13133
  /** C→D EVT — remove an integration from the daemon. */
13134
13134
  const IntegrationRemove = object({ integrationId: string().uuid() });
13135
+ /** One channel the bot is currently a member of (metadata only — no messages). */
13136
+ const IntegrationChannel = object({
13137
+ id: string(),
13138
+ name: string().optional(),
13139
+ isPrivate: boolean().optional()
13140
+ });
13141
+ /**
13142
+ * D→C EVT — the FULL set of channels the integration's bot is a member of
13143
+ * (fire-and-forget, latest-wins). Emitted on socket start and whenever the bot
13144
+ * is invited to / removed from a channel, so the console can offer per-channel
13145
+ * trigger config. Channel names are control metadata, never message content.
13146
+ */
13147
+ const IntegrationChannels = object({
13148
+ integrationId: string().uuid(),
13149
+ channels: array(IntegrationChannel)
13150
+ });
13135
13151
  //#endregion
13136
13152
  //#region ../protocol/dist/frames/register.js
13137
13153
  /**
@@ -13284,6 +13300,7 @@ const FRAME_SCHEMAS = {
13284
13300
  "cron/remove": CronRemove,
13285
13301
  "integration/upsert": IntegrationUpsert,
13286
13302
  "integration/remove": IntegrationRemove,
13303
+ "integration/channels": IntegrationChannels,
13287
13304
  "secrets/request": SecretsRequest,
13288
13305
  "secrets/grant": SecretsGrant,
13289
13306
  "secrets/renew": SecretsRenew,
@@ -13408,6 +13425,7 @@ discriminatedUnion("type", [
13408
13425
  frame("cron/remove", FRAME_SCHEMAS["cron/remove"]),
13409
13426
  frame("integration/upsert", FRAME_SCHEMAS["integration/upsert"]),
13410
13427
  frame("integration/remove", FRAME_SCHEMAS["integration/remove"]),
13428
+ frame("integration/channels", FRAME_SCHEMAS["integration/channels"]),
13411
13429
  frame("secrets/request", FRAME_SCHEMAS["secrets/request"]),
13412
13430
  frame("secrets/grant", FRAME_SCHEMAS["secrets/grant"]),
13413
13431
  frame("secrets/renew", FRAME_SCHEMAS["secrets/renew"]),
@@ -80633,6 +80651,16 @@ var SlackConnection = class {
80633
80651
  if (!ev.channel) return;
80634
80652
  deliver(ev, "app_mention");
80635
80653
  });
80654
+ this.app.event("member_joined_channel", async ({ event }) => {
80655
+ const ev = event;
80656
+ if (ev.user !== this.botUserId) return;
80657
+ log?.debug(`slack: bot joined channel ${ev.channel ?? "?"}`);
80658
+ this.deps.onChannelsChanged?.();
80659
+ });
80660
+ for (const type of ["channel_left", "group_left"]) this.app.event(type, async ({ event }) => {
80661
+ log?.debug(`slack: bot left channel ${event.channel ?? "?"} (${type})`);
80662
+ this.deps.onChannelsChanged?.();
80663
+ });
80636
80664
  log?.debug("slack: app.start → opening Socket Mode WebSocket (wss://…slack.com)…");
80637
80665
  await this.app.start();
80638
80666
  log?.debug("slack: app.start resolved → socket established");
@@ -80750,6 +80778,39 @@ var SlackConnection = class {
80750
80778
  isBot: p.isBot
80751
80779
  })).catch(() => ({ id }))));
80752
80780
  }
80781
+ /**
80782
+ * The channels this bot is a MEMBER of (users.conversations, cursor-paginated) —
80783
+ * the membership snapshot behind the console's per-channel trigger config. DMs /
80784
+ * group DMs are excluded (they are not configurable channels). Returns null on
80785
+ * any API failure so the caller never mistakes an error for "left all channels".
80786
+ */
80787
+ async listBotChannels() {
80788
+ const out = [];
80789
+ let cursor;
80790
+ try {
80791
+ do {
80792
+ const res = await this.app.client.users.conversations({
80793
+ types: "public_channel,private_channel",
80794
+ exclude_archived: true,
80795
+ limit: 200,
80796
+ ...cursor ? { cursor } : {}
80797
+ });
80798
+ for (const c of res.channels ?? []) {
80799
+ if (!c.id || c.is_im || c.is_mpim) continue;
80800
+ out.push({
80801
+ id: c.id,
80802
+ ...c.name ? { name: c.name } : {},
80803
+ ...c.is_private ? { isPrivate: true } : {}
80804
+ });
80805
+ }
80806
+ cursor = res.response_metadata?.next_cursor || void 0;
80807
+ } while (cursor);
80808
+ } catch (err) {
80809
+ this.deps.log?.debug(`slack: users.conversations failed: ${err.message}`);
80810
+ return null;
80811
+ }
80812
+ return out;
80813
+ }
80753
80814
  async listChannels() {
80754
80815
  return ((await this.app.client.conversations.list({
80755
80816
  types: "public_channel,private_channel",
@@ -82098,6 +82159,16 @@ var CpClient = class {
82098
82159
  if (this.state !== "READY" && this.state !== "DRAINING") return;
82099
82160
  this.transport?.send(encode(buildEnvelope("usage/report", report)));
82100
82161
  }
82162
+ /**
82163
+ * Report an integration's channel-membership snapshot (D→C `integration/channels`
82164
+ * EVT, fire-and-forget, latest-wins). No-op unless READY/DRAINING — the daemon
82165
+ * re-emits its cached snapshots on each (re)connect (see onReady in daemon.ts),
82166
+ * so a dropped report only delays the console's channel list, never loses it.
82167
+ */
82168
+ emitIntegrationChannels(snapshot) {
82169
+ if (this.state !== "READY" && this.state !== "DRAINING") return;
82170
+ this.transport?.send(encode(buildEnvelope("integration/channels", snapshot)));
82171
+ }
82101
82172
  request(type, payload) {
82102
82173
  const frame = buildEnvelope(type, payload);
82103
82174
  return this.correlator.request(frame, (e) => this.transport.send(e));
@@ -82770,6 +82841,7 @@ var Daemon = class Daemon {
82770
82841
  lastProbeAtMs = 0;
82771
82842
  cpClient;
82772
82843
  cpCrons;
82844
+ channelSnapshots = /* @__PURE__ */ new Map();
82773
82845
  cpAgents;
82774
82846
  cpIntegrations;
82775
82847
  botUserIds = {};
@@ -82894,6 +82966,7 @@ var Daemon = class Daemon {
82894
82966
  group,
82895
82967
  newTraceId: () => randomUUID(),
82896
82968
  onMessage: (msg) => this.onInbound(msg),
82969
+ onChannelsChanged: () => void this.refreshChannels(conn),
82897
82970
  log: this.log,
82898
82971
  boltDebug: cfg.logging.level === "debug" || cfg.logging.level === "trace"
82899
82972
  });
@@ -82905,6 +82978,7 @@ var Daemon = class Daemon {
82905
82978
  this.connByIntegration.set(integrationId, conn);
82906
82979
  }
82907
82980
  this.connections.push(conn);
82981
+ this.refreshChannels(conn);
82908
82982
  }
82909
82983
  let cronCount = 0;
82910
82984
  for (const a of agents) for (const c of a.crons) {
@@ -83051,11 +83125,14 @@ var Daemon = class Daemon {
83051
83125
  const existing = [...this.connByIntegration.values()].find((c) => c.appToken === group.appToken);
83052
83126
  if (existing) {
83053
83127
  if (existing.botToken !== group.botToken) this.log.warn(`slack: botToken changed for appToken (socket bot user ${existing.botUserId}) — deferred: the live socket keeps the old botToken until it is closed+reopened`);
83128
+ let bound = false;
83054
83129
  for (const { integrationId } of group.integrations) if (this.connByIntegration.get(integrationId) !== existing) {
83055
83130
  this.botUserIds[integrationId] = existing.botUserId;
83056
83131
  this.connByIntegration.set(integrationId, existing);
83057
83132
  this.log.info(`slack: bound integration ${integrationId} onto existing socket (appToken reuse)`);
83133
+ bound = true;
83058
83134
  }
83135
+ if (bound) this.refreshChannels(existing);
83059
83136
  continue;
83060
83137
  }
83061
83138
  try {
@@ -83063,6 +83140,7 @@ var Daemon = class Daemon {
83063
83140
  group,
83064
83141
  newTraceId: () => randomUUID(),
83065
83142
  onMessage: (msg) => this.onInbound(msg),
83143
+ onChannelsChanged: () => void this.refreshChannels(conn),
83066
83144
  log: this.log,
83067
83145
  boltDebug: this.cfg.logging.level === "debug" || this.cfg.logging.level === "trace"
83068
83146
  });
@@ -83074,11 +83152,37 @@ var Daemon = class Daemon {
83074
83152
  this.connByIntegration.set(integrationId, conn);
83075
83153
  }
83076
83154
  this.connections.push(conn);
83155
+ this.refreshChannels(conn);
83077
83156
  } catch (err) {
83078
83157
  this.log.error(`slack: failed to open runtime socket for appToken — leaving existing sockets intact: ${formatErr(err)}`);
83079
83158
  }
83080
83159
  }
83081
83160
  }
83161
+ /**
83162
+ * Re-list the channels this connection's bot is a member of and report the
83163
+ * snapshot to the CP for every integration bound to the connection (one bot ⇒
83164
+ * one membership set, fanned out per integrationId). Best-effort + never throws:
83165
+ * a Slack API failure keeps the previous snapshot (listBotChannels returns null),
83166
+ * and the emit is a no-op while the CP is down — the cached snapshot is re-emitted
83167
+ * on the next CP (re)connect (see startCpClient's onReady).
83168
+ */
83169
+ async refreshChannels(conn) {
83170
+ try {
83171
+ const channels = await conn.listBotChannels();
83172
+ if (!channels) return;
83173
+ for (const [integrationId, c] of this.connByIntegration) {
83174
+ if (c !== conn) continue;
83175
+ this.channelSnapshots.set(integrationId, channels);
83176
+ this.cpClient?.emitIntegrationChannels({
83177
+ integrationId,
83178
+ channels
83179
+ });
83180
+ }
83181
+ this.log.debug(`slack: channel snapshot for bot ${conn.botUserId}: ${channels.length} channel(s)`);
83182
+ } catch (err) {
83183
+ this.log.debug(`slack: channel snapshot refresh failed: ${formatErr(err)}`);
83184
+ }
83185
+ }
83082
83186
  ensureHost(agentId, cfg) {
83083
83187
  let host = this.hosts.get(agentId);
83084
83188
  if (host) return host;
@@ -83768,7 +83872,13 @@ var Daemon = class Daemon {
83768
83872
  features: []
83769
83873
  }),
83770
83874
  runtimeProfiles: () => Object.keys(this.runtimes).map((id) => this.runtimeProfileFor(id)),
83771
- onReady: () => void this.probeRuntimesAndEmit(),
83875
+ onReady: () => {
83876
+ this.probeRuntimesAndEmit();
83877
+ for (const [integrationId, channels] of this.channelSnapshots) this.cpClient?.emitIntegrationChannels({
83878
+ integrationId,
83879
+ channels
83880
+ });
83881
+ },
83772
83882
  localState: () => ({
83773
83883
  assignments: [],
83774
83884
  crons: this.cpCrons?.ids() ?? [],