@agentconnect.md/daemon 1.0.0-rc.259 → 1.0.0-rc.260

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.
@@ -3,9 +3,9 @@ import { n as require_ms, t as require_src$3 } from "./src-DDtlVutj.js";
3
3
  import { n as init_esm_shims, t as __dirname } from "./esm-shims-DxGcm4Hw.js";
4
4
  import { t as DAEMON_VERSION } from "./version-DGaRXjBK.js";
5
5
  import { C as _coercedNumber, T as NEVER, a as array, d as literal, f as looseObject, i as any, m as object, o as boolean, p as number$1, t as ZodNumber, v as string, x as url$4 } from "./schemas-CQkO9hC5.js";
6
- import { _ as decodeEnvelope, a as agentChildEnv, c as persistRelays, d as normalizeGitUrl, f as RELAY_DAEMON_SUBPROTOCOL, g as buildEnvelope, h as decodeRelayDaemonFrame, i as selectAgent, m as buildRelayDaemonFrame, n as findAgentFiles, o as loadConfig, p as RELAY_DAEMON_WS_PATH, r as loadAgents, s as persistDaemonId, v as encode, x as RESERVED_MCP_SERVER_NAME, y as MAX_FRAME_BYTES } from "./load-agents-DspcHKeP.js";
6
+ import { C as ZodIssueCode, S as RESERVED_MCP_SERVER_NAME, _ as buildEnvelope, a as agentChildEnv, b as MAX_FRAME_BYTES, c as persistRelays, d as normalizeGitUrl, f as RELAY_DAEMON_SUBPROTOCOL, g as SHARED_CONFIG_ACTION_ID, h as decodeRelayDaemonFrame, i as selectAgent, m as buildRelayDaemonFrame, n as findAgentFiles, o as loadConfig, p as RELAY_DAEMON_WS_PATH, r as loadAgents, s as persistDaemonId, v as decodeEnvelope, y as encode } from "./load-agents-CW9Q6eYo.js";
7
7
  import { l as resolveRoot, o as mcpSocketPath, u as statePath } from "./paths-DEcUO8lC.js";
8
- import { S as resolveRuntimes, _ as writeMemoryFile, a as initGitInjection, b as cachedRuntimeNames, c as simpleGit, d as memoryProviderFor, f as MemoryConflictError, g as resolveInMemoryDir, h as listMemory, i as prepareWorkspace, l as createMemoryProvider, m as MemoryTooLargeError, n as installedRuntimes, o as probeGitVersion, p as MemoryPathError, r as prefetchWorkspace, s as sessionGitEnv, t as AcpHost, u as memoryKindOf, v as MEMORY_TOOL_NAMES, x as cachedRuntimeVersions, y as toolsForIntegrations } from "./acp-host-hnOn2UR9.js";
8
+ import { S as resolveRuntimes, _ as writeMemoryFile, a as initGitInjection, b as cachedRuntimeNames, c as simpleGit, d as memoryProviderFor, f as MemoryConflictError, g as resolveInMemoryDir, h as listMemory, i as prepareWorkspace, l as createMemoryProvider, m as MemoryTooLargeError, n as installedRuntimes, o as probeGitVersion, p as MemoryPathError, r as prefetchWorkspace, s as sessionGitEnv, t as AcpHost, u as memoryKindOf, v as MEMORY_TOOL_NAMES, x as cachedRuntimeVersions, y as toolsForIntegrations } from "./acp-host-DRbb-vvg.js";
9
9
  import { i as GitCredentialCache, n as gitcredSocketPath, r as writeGitcredShim, t as GitCredServer } from "./gitcred-server-Dcsq6QJe.js";
10
10
  import { B as ToolListChangedNotificationSchema, D as ListPromptsResultSchema, E as ListChangedOptionsBaseSchema, F as PromptListChangedNotificationSchema, G as isZ4Schema, H as isJSONRPCRequest, I as ReadResourceResultSchema, J as encodeFrame, K as safeParse, L as ResourceListChangedNotificationSchema, M as ListToolsResultSchema, O as ListResourceTemplatesResultSchema, P as McpError, R as SUPPORTED_PROTOCOL_VERSIONS, S as InitializeResultSchema, T as LATEST_PROTOCOL_VERSION, U as isJSONRPCResultResponse, V as isInitializedNotification, W as getObjectShape, _ as ElicitResultSchema, a as AjvJsonSchemaValidator, b as GetPromptResultSchema, c as mergeCapabilities, d as CompleteResultSchema, f as CreateMessageRequestSchema, g as ElicitRequestSchema, h as CreateTaskResultSchema, i as assertToolsCallTaskCapability, k as ListResourcesResultSchema, m as CreateMessageResultWithToolsSchema, n as serializeMessage, o as require_fast_deep_equal, p as CreateMessageResultSchema, q as decodeFrames, r as assertClientRequestTaskCapability, s as Protocol, t as ReadBuffer, u as CallToolResultSchema, v as EmptyResultSchema, w as JSONRPCMessageSchema, y as ErrorCode } from "./stdio-DnS8WcXi.js";
11
11
  import { URL as URL$1 } from "url";
@@ -25,25 +25,6 @@ import net from "node:net";
25
25
  import { lstat, open, readdir, realpath, stat as stat$1 } from "node:fs/promises";
26
26
  import { DatabaseSync } from "node:sqlite";
27
27
  import assert from "assert";
28
- //#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/compat.js
29
- /** @deprecated Use the raw string literal codes instead, e.g. "invalid_type". */
30
- const ZodIssueCode = {
31
- invalid_type: "invalid_type",
32
- too_big: "too_big",
33
- too_small: "too_small",
34
- invalid_format: "invalid_format",
35
- not_multiple_of: "not_multiple_of",
36
- unrecognized_keys: "unrecognized_keys",
37
- invalid_union: "invalid_union",
38
- invalid_key: "invalid_key",
39
- invalid_element: "invalid_element",
40
- invalid_value: "invalid_value",
41
- custom: "custom"
42
- };
43
- /** @deprecated Do not use. Stub definition, only included for zod-to-json-schema compatibility. */
44
- var ZodFirstPartyTypeKind;
45
- ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
46
- //#endregion
47
28
  //#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/coerce.js
48
29
  function number(params) {
49
30
  return _coercedNumber(ZodNumber, params);
@@ -67116,6 +67097,7 @@ var require_dist$9 = /* @__PURE__ */ __commonJSMin(((exports) => {
67116
67097
  }));
67117
67098
  //#endregion
67118
67099
  //#region src/slack/normalize.ts
67100
+ var import_dist$1 = require_dist$13();
67119
67101
  var import_dist = /* @__PURE__ */ __toESM(require_dist$9(), 1);
67120
67102
  const MENTION_RE$1 = /<@([A-Z0-9]+)>/g;
67121
67103
  /** Map a Slack file element to a NormalizedMessage Attachment, dropping any
@@ -67311,6 +67293,32 @@ function attributionBlocks(webAppUrl) {
67311
67293
  }]
67312
67294
  };
67313
67295
  }
67296
+ /** The shared-bot footer (§10.1): "sent by <agent_name>" (linking to the agent in the
67297
+ * console) with a right-aligned ⚙ Config button that opens the per-channel
67298
+ * default-agent modal. A `section` (not `context`) because only sections take a
67299
+ * button accessory — the click lands on the RELAY (it owns Socket Mode), which reads
67300
+ * {@link SHARED_CONFIG_ACTION_ID}. */
67301
+ function sharedFooterBlocks(agentName, agentUrl) {
67302
+ return {
67303
+ text: `sent by ${agentName}`,
67304
+ blocks: [{
67305
+ type: "section",
67306
+ text: {
67307
+ type: "mrkdwn",
67308
+ text: `sent by <${agentUrl}|${agentName}>`
67309
+ },
67310
+ accessory: {
67311
+ type: "button",
67312
+ text: {
67313
+ type: "plain_text",
67314
+ text: "👤",
67315
+ emoji: true
67316
+ },
67317
+ action_id: SHARED_CONFIG_ACTION_ID
67318
+ }
67319
+ }]
67320
+ };
67321
+ }
67314
67322
  /** Pull human-readable output text out of an ACP tool_call/_update. Prefers the `content[]`
67315
67323
  * text blocks (the tool's reported output); falls back to a string `rawOutput`. diff /
67316
67324
  * terminal blocks and non-string rawOutput are skipped — they have no compact inline text. */
@@ -67779,12 +67787,15 @@ var OutputConverger = class {
67779
67787
  * — in medium/high — append a footer linking to the Web App session detail. The
67780
67788
  * link is omitted entirely when none is configured (no `local://` placeholder).
67781
67789
  */
67782
- onFinal(link, webAppUrl) {
67790
+ onFinal(link, webAppUrl, shared) {
67783
67791
  const clear = {
67784
67792
  kind: "set-status",
67785
67793
  text: ""
67786
67794
  };
67787
- const attribution = webAppUrl ? [{
67795
+ const attribution = shared ? [{
67796
+ kind: "attribution",
67797
+ ...sharedFooterBlocks(shared.agentName, shared.agentUrl)
67798
+ }] : webAppUrl ? [{
67788
67799
  kind: "attribution",
67789
67800
  ...attributionBlocks(webAppUrl)
67790
67801
  }] : [];
@@ -67793,16 +67804,10 @@ var OutputConverger = class {
67793
67804
  clear,
67794
67805
  ...attribution
67795
67806
  ];
67796
- const reasoning = this.drainReasoning();
67797
- const footer = link ? [{
67798
- kind: "notice",
67799
- text: `:white_check_mark: done — [details](${link})`
67800
- }] : [];
67801
67807
  return [
67802
- ...reasoning,
67808
+ ...this.drainReasoning(),
67803
67809
  ...this.flush(),
67804
67810
  clear,
67805
- ...footer,
67806
67811
  ...attribution
67807
67812
  ];
67808
67813
  }
@@ -67810,11 +67815,16 @@ var OutputConverger = class {
67810
67815
  //#endregion
67811
67816
  //#region src/slack/connection.ts
67812
67817
  const { App, LogLevel } = import_dist.default;
67813
- /** §6.1: one Slack Socket Mode connection per unique appToken. */
67818
+ /** §6.1: one Slack Socket Mode connection per unique appToken.
67819
+ * Shared-mode integrations are SKIPPED here — their inbound lives on a relay, so
67820
+ * the daemon opens no socket for them (it reaches them send-only via RelayClient;
67821
+ * see shared-bot-relay.md §11). They carry no appToken, so there is nothing to
67822
+ * consolidate on. */
67814
67823
  function consolidate(agents) {
67815
67824
  const groups = /* @__PURE__ */ new Map();
67816
67825
  for (const a of agents) for (const int of a.integrations) {
67817
67826
  if (int.platform !== "slack") continue;
67827
+ if (int.slack.mode === "shared" || !int.slack.appToken) continue;
67818
67828
  const k = int.slack.appToken;
67819
67829
  const g = groups.get(k) ?? {
67820
67830
  appToken: k,
@@ -67829,6 +67839,27 @@ function consolidate(agents) {
67829
67839
  }
67830
67840
  return groups;
67831
67841
  }
67842
+ /** Group SHARED-mode Slack integrations by xoxb (one send-only client per bot
67843
+ * token). These have no appToken (the relay owns inbound), so they can't be keyed
67844
+ * by appToken like {@link consolidate} — the bot token is the identity. */
67845
+ function consolidateShared(agents) {
67846
+ const groups = /* @__PURE__ */ new Map();
67847
+ for (const a of agents) for (const int of a.integrations) {
67848
+ if (int.platform !== "slack" || int.slack.mode !== "shared") continue;
67849
+ const k = int.slack.botToken;
67850
+ const g = groups.get(k) ?? {
67851
+ appToken: "",
67852
+ botToken: k,
67853
+ integrations: []
67854
+ };
67855
+ g.integrations.push({
67856
+ agentId: a.id,
67857
+ integrationId: int.id
67858
+ });
67859
+ groups.set(k, g);
67860
+ }
67861
+ return groups;
67862
+ }
67832
67863
  /** Cap on members enriched per `listChannelMembers` call (bounds users.info fan-out). */
67833
67864
  const MEMBER_ENRICH_CAP = 50;
67834
67865
  /**
@@ -67846,6 +67877,25 @@ function markdownBlock(text) {
67846
67877
  }]
67847
67878
  };
67848
67879
  }
67880
+ /** Build the send-only {@link AppLike}: a real Slack `WebClient` (xoxb) for the send
67881
+ * surface + inert event/start/stop members (shared-bot-relay.md §11). Same proxy +
67882
+ * timeout/retry tuning as the socket-mode factory. */
67883
+ function sendOnlyApp(botToken) {
67884
+ const proxyUrl = process.env.HTTPS_PROXY ?? process.env.HTTP_PROXY;
67885
+ const agent = proxyUrl ? new HttpsProxyAgent(proxyUrl) : void 0;
67886
+ return {
67887
+ message: () => {},
67888
+ event: () => {},
67889
+ action: () => {},
67890
+ client: new import_dist$1.WebClient(botToken, {
67891
+ ...agent ? { agent } : {},
67892
+ timeout: 3e4,
67893
+ retryConfig: { retries: 2 }
67894
+ }),
67895
+ start: async () => {},
67896
+ stop: async () => {}
67897
+ };
67898
+ }
67849
67899
  var SlackConnection = class {
67850
67900
  deps;
67851
67901
  app;
@@ -67876,7 +67926,7 @@ var SlackConnection = class {
67876
67926
  this.deps = deps;
67877
67927
  this.appToken = deps.group.appToken;
67878
67928
  this.botToken = deps.group.botToken;
67879
- this.app = factory({
67929
+ this.app = deps.sendOnly ? sendOnlyApp(deps.group.botToken) : factory({
67880
67930
  token: deps.group.botToken,
67881
67931
  appToken: deps.group.appToken
67882
67932
  });
@@ -67890,6 +67940,10 @@ var SlackConnection = class {
67890
67940
  this.botId = auth.bot_id ?? "";
67891
67941
  this.workspaceUrl = auth.url ?? "";
67892
67942
  log?.debug(`slack: auth.test ok → bot user ${this.botUserId} (bot_id ${this.botId || "n/a"})`);
67943
+ if (this.deps.sendOnly) {
67944
+ log?.info("slack: send-only connection ready (shared bot — inbound via relay)");
67945
+ return;
67946
+ }
67893
67947
  const deliver = (ev, kind) => {
67894
67948
  const msg = normalizeSlackEvent(this.withAssistantThread(ev), { traceId: this.deps.newTraceId() });
67895
67949
  log?.debug(`slack: inbound ${kind} ch=${msg.channel} thread=${msg.thread ?? "none"} user=${msg.sender.id} isBot=${msg.sender.isBot} isDm=${msg.isDm} mentions=[${msg.mentionedBots.join(",")}] text=${JSON.stringify(msg.text.slice(0, 80))}`);
@@ -196923,8 +196977,9 @@ var RelayClient = class {
196923
196977
  }
196924
196978
  this.deps.log.debug(`relay(${this.relayId}): ignoring ${frame.type}`);
196925
196979
  }
196926
- /** An inbound rd/msg: dispatch it, reply `rd/ack`; a webchat op additionally
196927
- * streams `rd/chat` back over this socket (hooks have no reply stream). */
196980
+ /** An inbound rd/msg: dispatch it, reply `rd/ack`; only a webchat op streams
196981
+ * `rd/chat` back over this socket. A shared-bot `im` replies out-of-band via the
196982
+ * daemon's own Slack send, and a hook fire has no reply stream — both get a noop. */
196928
196983
  handleMsg(reqId, msg) {
196929
196984
  const chat = msg.source === "webchat" ? (event) => this.sendChat(msg.chatId, event) : () => void 0;
196930
196985
  const ack = this.deps.onRelayMsg(msg, chat);
@@ -197741,8 +197796,10 @@ function toIntegration(spec) {
197741
197796
  id: spec.integrationId,
197742
197797
  platform: "slack",
197743
197798
  slack: {
197799
+ mode: spec.slack.mode,
197744
197800
  botToken: spec.slack.botToken,
197745
- appToken: spec.slack.appToken,
197801
+ ...spec.slack.appToken ? { appToken: spec.slack.appToken } : {},
197802
+ ...spec.slack.botUserId ? { botUserId: spec.slack.botUserId } : {},
197746
197803
  allowedUserIds: spec.slack.allowedUserIds,
197747
197804
  bindRules: spec.slack.bindRules
197748
197805
  }
@@ -213586,6 +213643,7 @@ var Daemon = class Daemon {
213586
213643
  agents = /* @__PURE__ */ new Map();
213587
213644
  fileAgents = /* @__PURE__ */ new Map();
213588
213645
  hosts = /* @__PURE__ */ new Map();
213646
+ hostStartedAt = /* @__PURE__ */ new Map();
213589
213647
  connections = [];
213590
213648
  telegramConns = [];
213591
213649
  discordConns = [];
@@ -213594,6 +213652,7 @@ var Daemon = class Daemon {
213594
213652
  scheduler;
213595
213653
  sessions;
213596
213654
  connByIntegration = /* @__PURE__ */ new Map();
213655
+ sharedSlackConns = /* @__PURE__ */ new Map();
213597
213656
  tgConnByIntegration = /* @__PURE__ */ new Map();
213598
213657
  dcConnByIntegration = /* @__PURE__ */ new Map();
213599
213658
  hostStarts = /* @__PURE__ */ new Map();
@@ -213819,6 +213878,7 @@ var Daemon = class Daemon {
213819
213878
  this.slackRetryTimers.set(group.appToken, timer);
213820
213879
  }
213821
213880
  }
213881
+ await this.openSharedSlackConnections(agents);
213822
213882
  await this.reconcileTelegramConnections();
213823
213883
  await this.reconcileDiscordConnections();
213824
213884
  for (const a of agents) this.scheduler.sync(a.id, a.crons);
@@ -214014,6 +214074,52 @@ var Daemon = class Daemon {
214014
214074
  this.log.error(`slack: failed to open runtime socket for appToken — leaving existing sockets intact: ${formatErr(err)}`);
214015
214075
  }
214016
214076
  }
214077
+ await this.openSharedSlackConnections([...this.agents.values()]);
214078
+ }
214079
+ /**
214080
+ * Open (or reuse) a SEND-ONLY Slack Web-API client per shared bot token and bind
214081
+ * it into `connByIntegration`, so replies / attachment fetches / MCP platform
214082
+ * tools / cron anchors resolve a connection for a `mode:'shared'` integration
214083
+ * (shared-bot-relay.md §11). No Socket Mode socket is opened — the bot's inbound
214084
+ * arrives from the relay as `rd/msg(im)`. Idempotent: an already-open client for
214085
+ * the same xoxb is reused; when a bot flips direct→shared its old direct socket
214086
+ * (same botToken) is stopped so it stops competing with the relay for the single
214087
+ * Socket Mode consumer.
214088
+ */
214089
+ async openSharedSlackConnections(agents) {
214090
+ const groups = consolidateShared(agents);
214091
+ for (const group of groups.values()) {
214092
+ const staleDirect = this.connections.find((c) => c.botToken === group.botToken && c.appToken !== "");
214093
+ if (staleDirect) {
214094
+ this.log.info(`slack: bot flipped to shared — stopping its direct socket (bot ${staleDirect.botUserId})`);
214095
+ await staleDirect.stop().catch(() => {});
214096
+ this.connections = this.connections.filter((c) => c !== staleDirect);
214097
+ }
214098
+ let conn = this.sharedSlackConns.get(group.botToken);
214099
+ if (!conn) {
214100
+ conn = new SlackConnection({
214101
+ group,
214102
+ sendOnly: true,
214103
+ newTraceId: () => randomUUID(),
214104
+ onMessage: () => {},
214105
+ onStatusAction: (a) => this.handleStatusAction(a),
214106
+ onStatusInfo: (key) => this.statusInfoForKey(key),
214107
+ log: this.log
214108
+ });
214109
+ try {
214110
+ await conn.start();
214111
+ this.sharedSlackConns.set(group.botToken, conn);
214112
+ this.log.info(`slack: send-only (shared) client ready as bot user ${conn.botUserId}`);
214113
+ } catch (err) {
214114
+ this.log.warn(`slack: shared send-only client failed — retry on next reconcile: ${formatErr(err)}`);
214115
+ continue;
214116
+ }
214117
+ }
214118
+ for (const { integrationId } of group.integrations) {
214119
+ this.botUserIds[integrationId] = conn.botUserId;
214120
+ this.connByIntegration.set(integrationId, conn);
214121
+ }
214122
+ }
214017
214123
  }
214018
214124
  /**
214019
214125
  * Reconcile the connection-derived Telegram state (`botUserIds`,
@@ -214238,6 +214344,7 @@ var Daemon = class Daemon {
214238
214344
  });
214239
214345
  }
214240
214346
  this.hosts.set(agentId, host);
214347
+ this.hostStartedAt.set(agentId, this.clock.now());
214241
214348
  return host;
214242
214349
  }
214243
214350
  /** The bare Telegram message id from a normalized `telegram:<chatId>:<messageId>`
@@ -214554,12 +214661,35 @@ var Daemon = class Daemon {
214554
214661
  this.log.debug(`relay: duplicate rd/msg ${dedupKey} — replaying ack (no re-dispatch)`);
214555
214662
  return prior;
214556
214663
  }
214557
- const ack = msg.source === "hook" ? this.dispatchRelayHook(msg) : this.dispatchRelayOp(msg, chat);
214664
+ const ack = msg.source === "webchat" ? this.dispatchRelayOp(msg, chat) : msg.source === "im" ? this.handleRelayIm(msg) : this.dispatchRelayHook(msg);
214558
214665
  if (this.relayMsgAcks.size >= 2e3) this.relayMsgAcks.clear();
214559
214666
  this.relayMsgAcks.set(dedupKey, ack);
214560
214667
  return ack;
214561
214668
  }
214562
214669
  /**
214670
+ * A shared-bot inbound (`rd/msg` `im`): the relay already arbitrated the target
214671
+ * agent + integration, so this is the explicit-agent path — no local routing. The
214672
+ * reply flows out-of-band through the agent's send-only Slack connection
214673
+ * (`replyConnFor`), NOT `rd/chat` (that is webchat-only). The `rd/ack` is a plain
214674
+ * receipt; the turn runs async.
214675
+ */
214676
+ handleRelayIm(msg) {
214677
+ if (!this.agents.get(msg.agentId)) {
214678
+ this.log.warn(`relay: rd/msg(im) for unknown agent ${msg.agentId} — dropping`);
214679
+ return {
214680
+ msgId: msg.msgId,
214681
+ accepted: false,
214682
+ reason: "no_agent"
214683
+ };
214684
+ }
214685
+ const normalized = msg.payload;
214686
+ this.dispatch(msg.agentId, normalized, msg.integrationId).catch((err) => this.log.error(`relay im dispatch failed for agent "${msg.agentId}": ${formatErr(err)}`));
214687
+ return {
214688
+ msgId: msg.msgId,
214689
+ accepted: true
214690
+ };
214691
+ }
214692
+ /**
214563
214693
  * Ack-verdict gate for one hook fire (`rd/msg` hook member) — the mirror of
214564
214694
  * {@link dispatchWebchatTurn}'s synchronous gates: the relay's rc/run-report
214565
214695
  * needs a REASONED rejection now, not a silently dropped fire-and-forget
@@ -215241,7 +215371,7 @@ var Daemon = class Daemon {
215241
215371
  });
215242
215372
  } else {
215243
215373
  const link = this.sessionLink(sessionId);
215244
- const finals = conv instanceof TelegramConverger || conv instanceof DiscordConverger ? conv.onFinal(link) : conv.onFinal(link, this.webAppBase());
215374
+ const finals = conv instanceof TelegramConverger || conv instanceof DiscordConverger ? conv.onFinal(link) : conv.onFinal(link, this.webAppBase(), this.sharedFooterFor(agentId, integrationId));
215245
215375
  for (const action of finals) this.enqueueApply(p, action);
215246
215376
  }
215247
215377
  for (const ev of rec.onFinal()) this.recordEvent(agentId, msg.channel, statusThread, ev);
@@ -215540,6 +215670,27 @@ var Daemon = class Daemon {
215540
215670
  const orgSeg = this.cpOrgSlug ? `/${encodeURIComponent(this.cpOrgSlug)}` : "";
215541
215671
  return `${this.webAppBase()}${orgSeg}/sessions/${acpSessionId}`;
215542
215672
  }
215673
+ /** The console deep link to an agent: `<base>/<orgSlug>/agents/<agentId>` — the
215674
+ * shared-bot footer's "sent by <agent_name>" link (§10.1). Same org-seg rule as
215675
+ * {@link sessionLink}. */
215676
+ agentLink(agentId) {
215677
+ const orgSeg = this.cpOrgSlug ? `/${encodeURIComponent(this.cpOrgSlug)}` : "";
215678
+ return `${this.webAppBase()}${orgSeg}/agents/${agentId}`;
215679
+ }
215680
+ /** The shared-bot footer context for a turn, or undefined when this isn't a shared
215681
+ * Slack channel (then the classic "sent by agentconnect.md" footer is used). A
215682
+ * shared integration (`slack.mode==='shared'`) means the bot's inbound rode the
215683
+ * relay, so the footer attributes the specific agent + offers the config button. */
215684
+ sharedFooterFor(agentId, integrationId) {
215685
+ if (!integrationId) return void 0;
215686
+ const agent = this.agents.get(agentId);
215687
+ const int = agent?.integrations.find((i) => i.id === integrationId);
215688
+ if (!int || int.platform !== "slack" || int.slack.mode !== "shared") return void 0;
215689
+ return {
215690
+ agentName: agent?.name ?? agentId,
215691
+ agentUrl: this.agentLink(agentId)
215692
+ };
215693
+ }
215543
215694
  /** The Web App console origin (trailing slash stripped): the explicit local `webAppUrl`,
215544
215695
  * else the CP-provided origin, else the public default. Backs both the session deep link
215545
215696
  * and the per-turn "sent by agentconnect.md" attribution footer. Always resolves. */
@@ -215901,6 +216052,7 @@ var Daemon = class Daemon {
215901
216052
  const host = this.hosts.get(agentId);
215902
216053
  this.hosts.delete(agentId);
215903
216054
  this.hostStarts.delete(agentId);
216055
+ this.hostStartedAt.delete(agentId);
215904
216056
  if (!host) return;
215905
216057
  const stop = host.stop(deadlineMs).finally(() => {
215906
216058
  if (this.hostStopping.get(agentId) === stop) this.hostStopping.delete(agentId);
@@ -215942,7 +216094,7 @@ var Daemon = class Daemon {
215942
216094
  for (const [agentId] of [...this.hosts]) {
215943
216095
  if (this.drainingAgents.has(agentId)) continue;
215944
216096
  if ([...this.pending.values()].some((p) => p.agentId === agentId)) continue;
215945
- const last = this.store.agentLastActivityTs(agentId) ?? 0;
216097
+ const last = Math.max(this.store.agentLastActivityTs(agentId) ?? 0, this.hostStartedAt.get(agentId) ?? 0);
215946
216098
  if (now - last <= ttl) continue;
215947
216099
  this.log.info(`idle: reclaiming host "${agentId}" (idle ${Math.round((now - last) / 1e3)}s) → provisioned`);
215948
216100
  this.stopHost(agentId).catch((err) => this.log.error(`idle: stop host "${agentId}" failed: ${formatErr(err)}`));
@@ -216447,4 +216599,4 @@ async function runForeground(opts, deps = {}) {
216447
216599
  //#endregion
216448
216600
  export { ClientTransport as i, CP_SUBPROTOCOL as n, CP_WS_PATH as r, runForeground as t };
216449
216601
 
216450
- //# sourceMappingURL=run-foreground-FYqgRmz9.js.map
216602
+ //# sourceMappingURL=run-foreground-BqNCGDXc.js.map