@agentconnect.md/daemon 1.0.0-rc.318 → 1.0.0-rc.319

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.
@@ -6,7 +6,7 @@ import { C as _coercedNumber, T as NEVER, a as array, d as literal, f as looseOb
6
6
  import { a as RELAY_DAEMON_WS_PATH, c as SHARED_CONFIG_ACTION_ID, d as encode, f as MAX_FRAME_BYTES, g as ZodIssueCode, h as RESERVED_MCP_SERVER_NAME, i as RELAY_DAEMON_SUBPROTOCOL, l as buildEnvelope, n as gitRepoLabel, o as buildRelayDaemonFrame, r as normalizeGitUrl, s as decodeRelayDaemonFrame, u as decodeEnvelope } from "./dist-BG425zHm.js";
7
7
  import { a as agentChildEnv, c as persistRelays, i as selectAgent, n as findAgentFiles, o as loadConfig, r as loadAgents, s as persistDaemonId } from "./load-agents-CROyMyq9.js";
8
8
  import { l as resolveRoot, o as mcpSocketPath, u as statePath } from "./paths-DEcUO8lC.js";
9
- import { C as cachedRuntimeVersions, S as cachedRuntimeNames, _ as resolveInMemoryDir, a as prepareWorkspace, b as MEMORY_TOOL_NAMES, c as sessionGitEnv, d as memoryKindOf, f as memoryProviderFor, g as listMemory, h as MemoryTooLargeError, i as prefetchWorkspace, l as simpleGit, m as MemoryPathError, n as turnFailureReason, o as initGitInjection, p as MemoryConflictError, r as installedRuntimes, s as probeGitVersion, t as AcpHost, u as createMemoryProvider, v as writeMemoryFile, w as resolveRuntimes, x as toolsForIntegrations, y as ALL_TOOL_NAMES } from "./acp-host-4pA0zUPK.js";
9
+ import { C as cachedRuntimeVersions, S as cachedRuntimeNames, _ as resolveInMemoryDir, a as prepareWorkspace, b as MEMORY_TOOL_NAMES, c as sessionGitEnv, d as memoryKindOf, f as memoryProviderFor, g as listMemory, h as MemoryTooLargeError, i as prefetchWorkspace, l as simpleGit, m as MemoryPathError, n as turnFailureReason, o as initGitInjection, p as MemoryConflictError, r as installedRuntimes, s as probeGitVersion, t as AcpHost, u as createMemoryProvider, v as writeMemoryFile, w as resolveRuntimes, x as toolsForIntegrations, y as ALL_TOOL_NAMES } from "./acp-host-Dsm0i9DH.js";
10
10
  import { i as GitCredentialCache, n as gitcredSocketPath, r as writeGitcredShim, t as GitCredServer } from "./gitcred-server-Bu-Svl7L.js";
11
11
  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";
12
12
  import { createRequire } from "node:module";
@@ -6581,6 +6581,11 @@ var LocalStore = class {
6581
6581
  getSessionByAcpId(acpSessionId) {
6582
6582
  return this.db.prepare("SELECT * FROM sessions WHERE acpSessionId = ?").get(acpSessionId);
6583
6583
  }
6584
+ /** Agent-scoped ACP id lookup for callbacks from one runtime process. ACP ids
6585
+ * are runtime-owned and need not be globally unique across agents. */
6586
+ getSessionByAcpIdForAgent(agentId, acpSessionId) {
6587
+ return this.db.prepare("SELECT * FROM sessions WHERE agentId = ? AND acpSessionId = ?").get(agentId, acpSessionId);
6588
+ }
6584
6589
  /**
6585
6590
  * One newest-first page of a thread's full transcript (all kinds), for `session/history`.
6586
6591
  * Pages backward via `beforeSeq` (the lowest seq already seen; null ⇒ newest page).
@@ -6656,7 +6661,7 @@ var LocalStore = class {
6656
6661
  throw err;
6657
6662
  }
6658
6663
  }
6659
- /** Human-facing session title from the runtime's `session_info_update` (latest
6664
+ /** Human-facing session title from ACP or the AgentConnect title tool (latest
6660
6665
  * wins; null clears per ACP semantics). No-op on an unknown key. */
6661
6666
  setSessionTitle(key, title) {
6662
6667
  this.db.prepare("UPDATE sessions SET title = ? WHERE key = ?").run(title, key);
@@ -6841,10 +6846,10 @@ var LocalStore = class {
6841
6846
  return this.db.prepare("SELECT * FROM transcript WHERE channel = ? AND thread = ? AND kind = 'text' AND ts > ? ORDER BY ts ASC").all(channel, thread, sinceTs);
6842
6847
  }
6843
6848
  /** The earliest inbound (non-agent) `text` message in a thread — the triggering user
6844
- * message. Used as a session-title fallback when the runtime never pushed one
6845
- * (`setSessionTitle`): many runtimes don't (e.g. the webchat/playground path), so
6846
- * without this the console shows only "Session <id>". Returns undefined when the
6847
- * thread holds no non-agent text row yet. Indexed by (channel, thread, seq). */
6849
+ * message. Used as a session-title fallback when neither ACP nor the title tool
6850
+ * supplied one. Before the first meaningful request, this avoids showing only
6851
+ * "Session <id>". Returns undefined when the thread holds no non-agent text row
6852
+ * yet. Indexed by (channel, thread, seq). */
6848
6853
  firstMessageText(channel, thread, agentId) {
6849
6854
  return this.db.prepare("SELECT text FROM transcript WHERE channel = ? AND thread = ? AND kind = 'text' AND sender != ? ORDER BY seq ASC LIMIT 1").get(channel, thread, agentId)?.text;
6850
6855
  }
@@ -7275,7 +7280,7 @@ var SessionManager = class {
7275
7280
  const dormant = this.deps.store.closedSessionAgents(channel, thread);
7276
7281
  return dormant.length === 1 ? dormant[0] : null;
7277
7282
  }
7278
- async handle(agentId, msg, signal) {
7283
+ async handle(agentId, msg, signal, integrationId) {
7279
7284
  const agent = this.deps.agentById(agentId);
7280
7285
  if (!agent) throw new Error(`unknown agent ${agentId}`);
7281
7286
  this.deps.memory.ensure({ agentId }, agent.name);
@@ -7306,7 +7311,12 @@ You keep a persistent memory across sessions; below is its INDEX. Your context i
7306
7311
  `- Source: ${msg.platform}`,
7307
7312
  `- Channel: ${msg.channel}`,
7308
7313
  ...channelName ? [`- Channel name: ${channelName}`] : [],
7309
- ...agent.description ? ["", agent.description] : []
7314
+ ...agent.description ? ["", agent.description] : [],
7315
+ ...this.deps.mcpServersFor ? [
7316
+ "",
7317
+ "# Session naming",
7318
+ "Before sending your first substantive answer, after you understand the first meaningful user request (not a greeting or acknowledgement), call `setSessionTitle` with a concise, specific title. Call it again only if the task focus materially changes. Do not mention this housekeeping action."
7319
+ ] : []
7310
7320
  ].join("\n");
7311
7321
  const sessionContext = [
7312
7322
  agentMeta,
@@ -7322,7 +7332,9 @@ You keep a persistent memory across sessions; below is its INDEX. Your context i
7322
7332
  agent,
7323
7333
  platform: msg.platform,
7324
7334
  channel: msg.channel,
7325
- thread
7335
+ thread,
7336
+ ...integrationId !== void 0 ? { integrationId } : {},
7337
+ isDm: msg.isDm
7326
7338
  }) ?? [];
7327
7339
  const acpSessionId = await abortable(() => host.newSession(cwd, mcpServers, effortOverride, metaContext), signal);
7328
7340
  created = true;
@@ -7346,7 +7358,9 @@ You keep a persistent memory across sessions; below is its INDEX. Your context i
7346
7358
  agent,
7347
7359
  platform: msg.platform,
7348
7360
  channel: msg.channel,
7349
- thread
7361
+ thread,
7362
+ ...integrationId !== void 0 ? { integrationId } : {},
7363
+ isDm: msg.isDm
7350
7364
  }) ?? [];
7351
7365
  let resumed = false;
7352
7366
  if (host.loadSupported?.()) {
@@ -7565,6 +7579,21 @@ function guessMimeFromUrl(url) {
7565
7579
  */
7566
7580
  async function executeTool(ctx, name, args, deps) {
7567
7581
  if (deps.canRun && !deps.canRun(ctx)) throw new Error("this agent turn has been stopped");
7582
+ if (name === "setSessionTitle") {
7583
+ const title = requireString(args, "title").replace(/\s+/g, " ").trim();
7584
+ if (!title) throw new Error("missing required string argument: title");
7585
+ if ([...title].length > 80) throw new Error("session title must be at most 80 characters");
7586
+ await deps.setSessionTitle({
7587
+ agentId: ctx.agentId,
7588
+ platform: ctx.platform,
7589
+ ...ctx.integrationId !== void 0 ? { integrationId: ctx.integrationId } : {},
7590
+ isDm: ctx.isDm,
7591
+ channel: ctx.channel,
7592
+ thread: ctx.thread,
7593
+ title
7594
+ });
7595
+ return { mcpContent: [] };
7596
+ }
7568
7597
  if (name === "listMemory" || name === "readMemory" || name === "writeMemory") {
7569
7598
  const scope = { agentId: ctx.agentId };
7570
7599
  try {
@@ -215484,7 +215513,10 @@ const IDLE_FLUSH_MS = 2e3;
215484
215513
  /** Public Web App console origin used for session deep links when neither a local
215485
215514
  * `webAppUrl` config nor a CP-provided one is set — so "View session" always resolves. */
215486
215515
  const DEFAULT_WEB_APP_URL = "https://app.agentconnect.md";
215487
- /** Per-in-flight-turn rendering state, keyed by ACP sessionId in `this.pending`. */
215516
+ /** ACP session ids are scoped to one agent runtime, not globally unique. */
215517
+ function pendingTurnKey(agentId, acpSessionId) {
215518
+ return JSON.stringify([agentId, acpSessionId]);
215519
+ }
215488
215520
  /** Cap on agent→agent hop depth (design §2.4/§4.5) — reject a `messageAgent` that
215489
215521
  * would push the outgoing hopCount past this, so an A↔B wake loop can't run away. */
215490
215522
  const MAX_AGENT_CALL_HOPS = 8;
@@ -215616,6 +215648,9 @@ var Daemon = class Daemon {
215616
215648
  activeDispatchesByAgent = /* @__PURE__ */ new Map();
215617
215649
  activeDispatchDoneByKey = /* @__PURE__ */ new Map();
215618
215650
  activeReplyConnectionUses = /* @__PURE__ */ new Map();
215651
+ sessionDeliveryBindings = /* @__PURE__ */ new Map();
215652
+ sessionInitializationsByAgent = /* @__PURE__ */ new Map();
215653
+ earlySessionMetadata = /* @__PURE__ */ new Map();
215619
215654
  hostStopping = /* @__PURE__ */ new Map();
215620
215655
  idleSweepTimer;
215621
215656
  cancelTimers = /* @__PURE__ */ new Map();
@@ -215739,6 +215774,7 @@ var Daemon = class Daemon {
215739
215774
  const active = this.activeGateEntries.get(key);
215740
215775
  return !this.paused(ctx.agentId) && active !== void 0 && !active.cancelledReason;
215741
215776
  },
215777
+ setSessionTitle: (req) => this.setSessionTitleFromTool(req),
215742
215778
  gatewayFor: (integrationId) => this.connByIntegration.get(integrationId) ?? this.tgConnByIntegration.get(integrationId) ?? this.dcConnByIntegration.get(integrationId),
215743
215779
  channelAgents: (req) => {
215744
215780
  const client = this.cpClient;
@@ -215767,16 +215803,16 @@ var Daemon = class Daemon {
215767
215803
  hostFor: (agentId) => this.ensureHostAsync(agentId),
215768
215804
  agentById: (id) => this.agents.get(id),
215769
215805
  memory: this.memory,
215770
- mcpServersFor: ({ agent, platform, channel, thread }) => {
215806
+ mcpServersFor: ({ agent, platform, channel, thread, integrationId, isDm }) => {
215771
215807
  const servers = [];
215772
215808
  let tools = toolsForIntegrations(agent.integrations);
215773
215809
  if (memoryKindOf(agent) === "native") tools = tools.filter((t) => !MEMORY_TOOL_NAMES.has(t.name));
215774
- const integrationId = agent.integrations[0]?.id;
215775
215810
  if (tools.length > 0) {
215776
215811
  const token = this.mcp.register({
215777
215812
  agentId: agent.id,
215778
215813
  platform,
215779
215814
  ...integrationId ? { integrationId } : {},
215815
+ isDm,
215780
215816
  channel,
215781
215817
  thread,
215782
215818
  tools
@@ -216677,7 +216713,7 @@ var Daemon = class Daemon {
216677
216713
  const host = this.hosts.get(rec.agentId);
216678
216714
  if (!acpSessionId || !host?.hasSession(acpSessionId)) return;
216679
216715
  host.setSessionModel(acpSessionId, model).then((applied) => {
216680
- const p = this.pending.get(acpSessionId);
216716
+ const p = this.pending.get(pendingTurnKey(rec.agentId, acpSessionId));
216681
216717
  if (applied && p) this.emitStatusBar(p);
216682
216718
  }).catch((err) => this.log.warn(`set-model failed: ${err.message}`));
216683
216719
  }
@@ -216743,7 +216779,7 @@ var Daemon = class Daemon {
216743
216779
  * (model / effort / fast) is reflected. No-op when the session is idle. */
216744
216780
  refreshStatusBarForKey(key) {
216745
216781
  const rec = this.store.getSession(key);
216746
- const p = rec?.acpSessionId ? this.pending.get(rec.acpSessionId) : void 0;
216782
+ const p = rec?.acpSessionId ? this.pending.get(pendingTurnKey(rec.agentId, rec.acpSessionId)) : void 0;
216747
216783
  if (p) this.emitStatusBar(p);
216748
216784
  }
216749
216785
  /** Set a session's Slack output verbosity by its local key. Purely daemon-side (no ACP):
@@ -216759,8 +216795,8 @@ var Daemon = class Daemon {
216759
216795
  * the conversation's in-flight turn like `!cancel` (no mute; follow-ups still dispatch).
216760
216796
  * Resolved by conversationId (the op carries no agentId). No-op when idle. */
216761
216797
  handleWebchatCancel(conversationId) {
216762
- for (const [acpSessionId, p] of this.pending) if (p.webchat?.conversationId === conversationId) {
216763
- this.interruptTurn(p.agentId, p.sessionKey, "cancel", acpSessionId);
216798
+ for (const p of this.pending.values()) if (p.webchat?.conversationId === conversationId) {
216799
+ this.interruptTurn(p.agentId, p.sessionKey, "cancel", p.acpSessionId);
216764
216800
  return;
216765
216801
  }
216766
216802
  for (const [key, entry] of this.activeGateEntries) if (entry.webchat?.conversationId === conversationId) {
@@ -218062,9 +218098,9 @@ var Daemon = class Daemon {
218062
218098
  entry.cancelledReason = "loop protection";
218063
218099
  targets.set(key, { agentId: entry.agentId });
218064
218100
  }
218065
- for (const [acpSessionId, pending] of this.pending) if (pending.loopGuardScope === scope) targets.set(pending.sessionKey, {
218101
+ for (const pending of this.pending.values()) if (pending.loopGuardScope === scope) targets.set(pending.sessionKey, {
218066
218102
  agentId: pending.agentId,
218067
- acpSessionId
218103
+ acpSessionId: pending.acpSessionId
218068
218104
  });
218069
218105
  for (const [key, queued] of this.serialQueue) {
218070
218106
  const entry = queued.find((candidate) => loopGuardScope(candidate.msg) === scope);
@@ -218371,10 +218407,26 @@ var Daemon = class Daemon {
218371
218407
  const conv = msg.platform === "telegram" ? new TelegramConverger(mode) : msg.platform === "discord" ? new DiscordConverger(mode) : new OutputConverger(mode);
218372
218408
  this.showActivity(replyConn, msg.channel, statusThread, wasRunning ? "is thinking…" : "is starting up…");
218373
218409
  const releaseReplyConn = this.holdReplyConnection(replyConn);
218410
+ const previousDeliveryBinding = this.sessionDeliveryBindings.get(key);
218411
+ const deliveryBinding = {
218412
+ agentId,
218413
+ platform: msg.platform,
218414
+ ...integrationId !== void 0 ? { integrationId } : {},
218415
+ isDm: msg.isDm
218416
+ };
218417
+ this.sessionDeliveryBindings.set(key, deliveryBinding);
218418
+ this.sessionInitializationsByAgent.set(agentId, (this.sessionInitializationsByAgent.get(agentId) ?? 0) + 1);
218419
+ const restoreDeliveryBinding = () => {
218420
+ if (this.sessionDeliveryBindings.get(key) !== deliveryBinding) return;
218421
+ if (previousDeliveryBinding) this.sessionDeliveryBindings.set(key, previousDeliveryBinding);
218422
+ else this.sessionDeliveryBindings.delete(key);
218423
+ };
218374
218424
  let handled;
218375
218425
  try {
218376
- handled = await this.sessions.handle(agentId, msg, entry.initAbort.signal);
218426
+ handled = await this.sessions.handle(agentId, msg, entry.initAbort.signal, integrationId);
218377
218427
  } catch (err) {
218428
+ this.finishSessionInitialization(agentId);
218429
+ restoreDeliveryBinding();
218378
218430
  const interrupted = this.dispatchGateReason(entry);
218379
218431
  try {
218380
218432
  this.store.setSessionState(key, "idle", this.clock.now());
@@ -218398,6 +218450,8 @@ var Daemon = class Daemon {
218398
218450
  const { sessionId, blocks, created } = handled;
218399
218451
  const initializedGate = this.dispatchGateReason(entry);
218400
218452
  if (initializedGate) {
218453
+ this.finishSessionInitialization(agentId);
218454
+ restoreDeliveryBinding();
218401
218455
  const interruptedSession = this.store.getSession(key);
218402
218456
  if (created && interruptedSession?.acpSessionId === sessionId) this.store.upsertSession({
218403
218457
  ...interruptedSession,
@@ -218421,6 +218475,7 @@ var Daemon = class Daemon {
218421
218475
  channel: msg.channel,
218422
218476
  thread: statusThread
218423
218477
  });
218478
+ this.finishSessionInitialization(agentId, sessionId);
218424
218479
  let resolveDone;
218425
218480
  const done = new Promise((r) => resolveDone = r);
218426
218481
  const pendingWebchat = webchat ? Object.assign(webchat, {
@@ -218453,7 +218508,7 @@ var Daemon = class Daemon {
218453
218508
  deferredFinalTranscript: false
218454
218509
  } } : {}
218455
218510
  };
218456
- this.pending.set(sessionId, p);
218511
+ this.pending.set(pendingTurnKey(agentId, sessionId), p);
218457
218512
  if (callMeta) this.activeTurnCallMeta.set(key, callMeta);
218458
218513
  let finalPhase = "end";
218459
218514
  try {
@@ -218572,12 +218627,12 @@ var Daemon = class Daemon {
218572
218627
  }
218573
218628
  throw err;
218574
218629
  } finally {
218575
- this.clearCancelBackstop(sessionId);
218630
+ this.clearCancelBackstop(agentId, sessionId);
218576
218631
  if (p.outputSuppressed) await p.applyChain.catch(() => {});
218577
218632
  releaseReplyConn();
218578
218633
  this.clearIdle(p);
218579
- this.releasePermissions(sessionId);
218580
- this.releaseElicits(sessionId);
218634
+ this.releasePermissions(agentId, sessionId);
218635
+ this.releaseElicits(agentId, sessionId);
218581
218636
  if (callMeta) this.activeTurnCallMeta.delete(key);
218582
218637
  const githubFinal = p.github && !p.outputSuppressed && finalPhase === "end" ? p.github.collector.finalText(true) : void 0;
218583
218638
  if (p.github?.deferredFinalTranscript && githubFinal?.trim()) this.store.appendTranscript({
@@ -218589,7 +218644,7 @@ var Daemon = class Daemon {
218589
218644
  text: githubFinal
218590
218645
  });
218591
218646
  if (p.github && !p.outputSuppressed) await p.github.poster.publish(githubFinal).catch((err) => this.log.warn(`github poster: final publish failed (${formatErr(err)})`));
218592
- this.pending.delete(sessionId);
218647
+ this.pending.delete(pendingTurnKey(agentId, sessionId));
218593
218648
  this.store.setSessionState(key, "idle", this.clock.now());
218594
218649
  this.emitSessionMetadataSnapshot({
218595
218650
  sessionId,
@@ -218627,9 +218682,8 @@ var Daemon = class Daemon {
218627
218682
  else e.reject(new FailStopError(key));
218628
218683
  }
218629
218684
  }
218630
- const livePair = acpSessionId ? [acpSessionId, this.pending.get(acpSessionId)] : [...this.pending.entries()].find(([, pending]) => pending.sessionKey === key);
218631
- const liveSessionId = livePair?.[0];
218632
- const live = livePair?.[1];
218685
+ const live = acpSessionId ? this.pending.get(pendingTurnKey(agentId, acpSessionId)) : [...this.pending.values()].find((pending) => pending.sessionKey === key);
218686
+ const liveSessionId = acpSessionId ?? live?.acpSessionId;
218633
218687
  if (live) {
218634
218688
  live.outputSuppressed ??= reason;
218635
218689
  this.clearIdle(live);
@@ -218652,8 +218706,8 @@ var Daemon = class Daemon {
218652
218706
  if (activeEntry && active) this.armColdCancelBackstop(agentId, key, reason, active);
218653
218707
  return;
218654
218708
  }
218655
- this.releasePermissions(liveSessionId);
218656
- this.releaseElicits(liveSessionId);
218709
+ this.releasePermissions(agentId, liveSessionId);
218710
+ this.releaseElicits(agentId, liveSessionId);
218657
218711
  this.store.setSessionState(key, "cancelling", this.clock.now());
218658
218712
  this.hosts.get(agentId)?.cancel(liveSessionId).catch((err) => this.log.error(`command ${reason}: cancel failed: ${err.message}`));
218659
218713
  this.armCancelBackstop(agentId, liveSessionId, key, reason);
@@ -218670,7 +218724,7 @@ var Daemon = class Daemon {
218670
218724
  entry.cancelledReason = reason;
218671
218725
  targets.set(key, void 0);
218672
218726
  }
218673
- for (const [acpSessionId, p] of this.pending) if (p.agentId === agentId) targets.set(p.sessionKey, acpSessionId);
218727
+ for (const p of this.pending.values()) if (p.agentId === agentId) targets.set(p.sessionKey, p.acpSessionId);
218674
218728
  for (const [key, queued] of this.serialQueue) if (queued.some((entry) => entry.agentId === agentId) && !targets.has(key)) targets.set(key, this.store.getSession(key)?.acpSessionId ?? void 0);
218675
218729
  if (targets.size > 0) this.log.info(`${reason}: interrupting ${targets.size} active session(s) for agent "${agentId}"`);
218676
218730
  for (const [key, acpSessionId] of targets) this.interruptTurn(agentId, key, reason, acpSessionId, { dropQueued: true });
@@ -218681,21 +218735,23 @@ var Daemon = class Daemon {
218681
218735
  * can't be stuck in `cancelling` forever. dispatch's finally clears this timer
218682
218736
  * the moment the turn yields on its own. */
218683
218737
  armCancelBackstop(agentId, acpSessionId, key, reason) {
218684
- this.clearCancelBackstop(acpSessionId);
218738
+ this.clearCancelBackstop(agentId, acpSessionId);
218685
218739
  const ms = this.cfg.limits.cancelBackstopMs;
218686
- this.cancelTimers.set(acpSessionId, this.clock.setTimeout(() => {
218687
- this.cancelTimers.delete(acpSessionId);
218688
- if (!this.pending.has(acpSessionId)) return;
218740
+ const pendingKey = pendingTurnKey(agentId, acpSessionId);
218741
+ this.cancelTimers.set(pendingKey, this.clock.setTimeout(() => {
218742
+ this.cancelTimers.delete(pendingKey);
218743
+ if (!this.pending.has(pendingKey)) return;
218689
218744
  this.log.warn(`${reason}: agent "${agentId}" ignored session/cancel for ${ms}ms — force-stopping host (session ${acpSessionId})`);
218690
218745
  this.stopHost(agentId, 0).catch((err) => this.log.error(`${reason}: force-stop failed for agent "${agentId}": ${formatErr(err)}`));
218691
218746
  this.store.setSessionState(key, "idle", this.clock.now());
218692
218747
  }, ms));
218693
218748
  }
218694
- clearCancelBackstop(acpSessionId) {
218695
- const t = this.cancelTimers.get(acpSessionId);
218749
+ clearCancelBackstop(agentId, acpSessionId) {
218750
+ const key = pendingTurnKey(agentId, acpSessionId);
218751
+ const t = this.cancelTimers.get(key);
218696
218752
  if (t !== void 0) {
218697
218753
  this.clock.clearTimeout(t);
218698
- this.cancelTimers.delete(acpSessionId);
218754
+ this.cancelTimers.delete(key);
218699
218755
  }
218700
218756
  }
218701
218757
  armColdCancelBackstop(agentId, key, reason, active) {
@@ -219024,7 +219080,7 @@ var Daemon = class Daemon {
219024
219080
  if (row?.threadUrl !== void 0) event.threadUrl = row.threadUrl;
219025
219081
  const agent = this.agents.get(input.agentId);
219026
219082
  if (agent?.runtime) event.runtime = agent.runtime;
219027
- const storeKey = this.store.getSessionByAcpId(input.sessionId)?.key;
219083
+ const storeKey = this.store.getSessionByAcpIdForAgent(input.agentId, input.sessionId)?.key;
219028
219084
  const model = (storeKey ? this.store.getModelOverride(storeKey) : void 0) ?? agent?.runtimeOverrides?.model;
219029
219085
  if (model !== void 0) event.model = model;
219030
219086
  const effort = (storeKey ? this.store.getEffortOverride(storeKey) : void 0) ?? agent?.reasoningEffort;
@@ -219177,6 +219233,7 @@ var Daemon = class Daemon {
219177
219233
  if (p.idleTimer) clearTimeout(p.idleTimer);
219178
219234
  p.idleTimer = void 0;
219179
219235
  }
219236
+ /** In-flight rendering state keyed by (agentId, ACP sessionId). */
219180
219237
  pending = /* @__PURE__ */ new Map();
219181
219238
  /**
219182
219239
  * DAEMON-PRIVATE trusted call metadata of the CURRENTLY in-flight turn, keyed by the
@@ -219199,7 +219256,7 @@ var Daemon = class Daemon {
219199
219256
  * promise stays pending until the user taps a button (handlePermissionChoice) or the
219200
219257
  * turn is cancelled/ends (releasePermissions).
219201
219258
  */
219202
- async onAcpPermission(_agentId, sessionId, params) {
219259
+ async onAcpPermission(agentId, sessionId, params) {
219203
219260
  if (isBuiltinSystemTool(params)) {
219204
219261
  const allow = params.options.find((o) => o.kind === "allow_always" || o.kind === "allow_once");
219205
219262
  if (allow) return { outcome: {
@@ -219207,7 +219264,7 @@ var Daemon = class Daemon {
219207
219264
  optionId: allow.optionId
219208
219265
  } };
219209
219266
  }
219210
- const p = this.pending.get(sessionId);
219267
+ const p = this.pending.get(pendingTurnKey(agentId, sessionId));
219211
219268
  if (!p) return void 0;
219212
219269
  if (p.outputSuppressed) return { outcome: { outcome: "cancelled" } };
219213
219270
  const conn = p.conn;
@@ -219218,6 +219275,7 @@ var Daemon = class Daemon {
219218
219275
  let resolveResult;
219219
219276
  const result = new Promise((resolve) => resolveResult = resolve);
219220
219277
  this.pendingPerms.set(requestId, {
219278
+ agentId,
219221
219279
  sessionId,
219222
219280
  params,
219223
219281
  conn,
@@ -219256,9 +219314,9 @@ var Daemon = class Daemon {
219256
219314
  /** Resolve every outstanding permission request for a session as `cancelled` — ACP
219257
219315
  * requires this when a prompt turn is cancelled, and it also unblocks a turn whose
219258
219316
  * card the user abandoned. Edits each card to a neutral cancelled state. */
219259
- releasePermissions(sessionId) {
219317
+ releasePermissions(agentId, sessionId) {
219260
219318
  for (const [id, rec] of this.pendingPerms) {
219261
- if (rec.sessionId !== sessionId) continue;
219319
+ if (rec.agentId !== agentId || rec.sessionId !== sessionId) continue;
219262
219320
  this.pendingPerms.delete(id);
219263
219321
  if (rec.ts) rec.conn.updateBlocks(rec.channel, rec.ts, buildPermissionResolvedCard(rec.params, "Cancelled", void 0), "Permission cancelled").catch(() => {});
219264
219322
  rec.resolve({ outcome: { outcome: "cancelled" } });
@@ -219273,8 +219331,8 @@ var Daemon = class Daemon {
219273
219331
  * Slack, or the form has no field we can render inline. Stays pending until the user
219274
219332
  * taps a button (handleElicitChoice) or the turn ends/cancels (releaseElicits).
219275
219333
  */
219276
- async onAcpElicit(_agentId, sessionId, params) {
219277
- const p = this.pending.get(sessionId);
219334
+ async onAcpElicit(agentId, sessionId, params) {
219335
+ const p = this.pending.get(pendingTurnKey(agentId, sessionId));
219278
219336
  if (!p) return void 0;
219279
219337
  if (p.outputSuppressed) return { action: "cancel" };
219280
219338
  const conn = p.conn;
@@ -219288,6 +219346,7 @@ var Daemon = class Daemon {
219288
219346
  let resolveResult;
219289
219347
  const result = new Promise((resolve) => resolveResult = resolve);
219290
219348
  this.pendingElicits.set(requestId, {
219349
+ agentId,
219291
219350
  sessionId,
219292
219351
  params,
219293
219352
  propName: target.propName,
@@ -219335,9 +219394,9 @@ var Daemon = class Daemon {
219335
219394
  }
219336
219395
  /** Resolve every outstanding elicitation for a session as `cancel` — ACP's cancellation
219337
219396
  * contract, and it unblocks a turn whose card the user abandoned. */
219338
- releaseElicits(sessionId) {
219397
+ releaseElicits(agentId, sessionId) {
219339
219398
  for (const [id, rec] of this.pendingElicits) {
219340
- if (rec.sessionId !== sessionId) continue;
219399
+ if (rec.agentId !== agentId || rec.sessionId !== sessionId) continue;
219341
219400
  this.pendingElicits.delete(id);
219342
219401
  if (rec.ts) rec.conn.updateBlocks(rec.channel, rec.ts, buildElicitationResolvedCard(rec.params, ":hourglass: Cancelled"), "Cancelled").catch(() => {});
219343
219402
  rec.resolve({ action: "cancel" });
@@ -219361,11 +219420,105 @@ var Daemon = class Daemon {
219361
219420
  this.log.debug(`${late ? "late " : ""}usage report emit failed (session ${sessionId}): ${err.message}`);
219362
219421
  }
219363
219422
  }
219364
- onAcpUpdate(_agentId, sessionId, update) {
219365
- const p = this.pending.get(sessionId);
219423
+ bufferEarlySessionMetadata(agentId, sessionId, update) {
219424
+ if ((this.sessionInitializationsByAgent.get(agentId) ?? 0) === 0) return false;
219425
+ const key = pendingTurnKey(agentId, sessionId);
219426
+ const entry = this.earlySessionMetadata.get(key) ?? {
219427
+ agentId,
219428
+ sessionId,
219429
+ updates: []
219430
+ };
219431
+ const previous = entry.updates.findIndex((candidate) => candidate.sessionUpdate === update.sessionUpdate);
219432
+ if (previous >= 0) entry.updates[previous] = update;
219433
+ else entry.updates.push(update);
219434
+ this.earlySessionMetadata.set(key, entry);
219435
+ return true;
219436
+ }
219437
+ /** Complete one SessionManager initialization and replay any metadata the ACP
219438
+ * adapter emitted before its local session row existed. Leftover unknown ids
219439
+ * are discarded once the agent has no other initialization in flight. */
219440
+ finishSessionInitialization(agentId, sessionId) {
219441
+ if (sessionId) {
219442
+ const key = pendingTurnKey(agentId, sessionId);
219443
+ const entry = this.earlySessionMetadata.get(key);
219444
+ this.earlySessionMetadata.delete(key);
219445
+ if (entry) for (const update of entry.updates) this.onAcpUpdate(agentId, sessionId, update);
219446
+ }
219447
+ const remaining = Math.max(0, (this.sessionInitializationsByAgent.get(agentId) ?? 1) - 1);
219448
+ if (remaining > 0) {
219449
+ this.sessionInitializationsByAgent.set(agentId, remaining);
219450
+ return;
219451
+ }
219452
+ this.sessionInitializationsByAgent.delete(agentId);
219453
+ for (const [key, entry] of this.earlySessionMetadata) if (entry.agentId === agentId) this.earlySessionMetadata.delete(key);
219454
+ }
219455
+ /** Persist one authoritative title and push the CP metadata projection. */
219456
+ persistSessionTitle(rec, title) {
219457
+ this.store.setSessionTitle(rec.key, title);
219458
+ if (!rec.acpSessionId) return;
219459
+ this.emitSessionMetadataSnapshot({
219460
+ sessionId: rec.acpSessionId,
219461
+ agentId: rec.agentId,
219462
+ phase: "plan",
219463
+ platform: rec.platform,
219464
+ channel: rec.channel,
219465
+ thread: rec.thread
219466
+ });
219467
+ }
219468
+ /** Rename a Slack app-DM through the exact integration that delivered this
219469
+ * session. The connection lease prevents reconcile from closing it mid-call. */
219470
+ async setSlackTitleForBinding(rec, binding, title) {
219471
+ if (binding.agentId !== rec.agentId || binding.platform !== "slack" || rec.platform !== "slack" || !binding.isDm || !binding.integrationId || !title) return;
219472
+ const conn = this.connByIntegration.get(binding.integrationId);
219473
+ if (!conn) return;
219474
+ const release = this.holdReplyConnection(conn);
219475
+ try {
219476
+ await conn.setTitle(rec.channel, rec.thread, title);
219477
+ } catch (err) {
219478
+ this.log.debug(`session title: Slack update failed (${formatErr(err)})`);
219479
+ } finally {
219480
+ release();
219481
+ }
219482
+ }
219483
+ /** MCP callback for runtimes (notably Codex ACP) that do not generate semantic
219484
+ * ACP session titles themselves. The tool input contains only the title; every
219485
+ * coordinate and delivery route was captured in the trusted SessionContext. */
219486
+ async setSessionTitleFromTool(req) {
219487
+ const key = sessionKey(req.platform, req.channel, req.thread, req.agentId);
219488
+ const rec = this.store.getSession(key);
219489
+ if (!rec?.acpSessionId) throw new Error("the current session is not addressable yet");
219490
+ const binding = this.sessionDeliveryBindings.get(key) ?? {
219491
+ agentId: req.agentId,
219492
+ platform: req.platform,
219493
+ ...req.integrationId !== void 0 ? { integrationId: req.integrationId } : {},
219494
+ isDm: req.isDm
219495
+ };
219496
+ this.persistSessionTitle(rec, req.title);
219497
+ const p = this.pending.get(pendingTurnKey(rec.agentId, rec.acpSessionId));
219498
+ if (p && p.sessionKey === key && !p.outputSuppressed) {
219499
+ if (p.webchat) this.emitWebchatUpdate(p, {
219500
+ sessionUpdate: "session_info_update",
219501
+ title: req.title
219502
+ });
219503
+ if (p.platform === "slack" && p.isDm && p.conn) {
219504
+ this.enqueueApply(p, {
219505
+ kind: "set-title",
219506
+ text: req.title
219507
+ });
219508
+ await p.applyChain;
219509
+ return;
219510
+ }
219511
+ }
219512
+ await this.setSlackTitleForBinding(rec, binding, req.title);
219513
+ }
219514
+ onAcpUpdate(agentId, sessionId, update) {
219515
+ const p = this.pending.get(pendingTurnKey(agentId, sessionId));
219366
219516
  if (p?.outputSuppressed) return;
219517
+ const isEarlyMetadata = update?.sessionUpdate === "usage_update" || update?.sessionUpdate === "session_info_update" && update.title !== void 0;
219518
+ const detachedRec = !p && isEarlyMetadata ? this.store.getSessionByAcpIdForAgent(agentId, sessionId) : void 0;
219519
+ if (!p && isEarlyMetadata && !detachedRec && this.bufferEarlySessionMetadata(agentId, sessionId, update)) return;
219367
219520
  if (update?.sessionUpdate === "usage_update") {
219368
- const rec = p ? void 0 : this.store.getSessionByAcpId(sessionId);
219521
+ const rec = p ? void 0 : detachedRec;
219369
219522
  const key = p?.sessionKey ?? rec?.key;
219370
219523
  if (key) {
219371
219524
  if (p && update.cost?.amount !== void 0) p.runtimeCostReported = true;
@@ -219381,23 +219534,22 @@ var Daemon = class Daemon {
219381
219534
  } else if (rec) this.emitStoredUsageReport(sessionId, rec.agentId, rec.platform, rec.channel, key, true);
219382
219535
  }
219383
219536
  }
219384
- if (!p) return;
219385
219537
  if (update?.sessionUpdate === "session_info_update" && update.title !== void 0) {
219386
- this.store.setSessionTitle(p.sessionKey, update.title);
219387
- this.emitSessionMetadataSnapshot({
219388
- sessionId,
219389
- agentId: p.agentId,
219390
- phase: "plan",
219391
- platform: p.platform,
219392
- channel: p.channel,
219393
- thread: p.statusThread
219394
- });
219395
- const slackTitle = typeof update.title === "string" ? update.title.trim() : "";
219396
- if (p.platform === "slack" && p.isDm && slackTitle) this.enqueueApply(p, {
219397
- kind: "set-title",
219398
- text: slackTitle
219399
- });
219538
+ const rec = p ? this.store.getSession(p.sessionKey) : detachedRec;
219539
+ if (rec?.agentId === agentId && rec.acpSessionId === sessionId) {
219540
+ this.persistSessionTitle(rec, update.title);
219541
+ const slackTitle = typeof update.title === "string" ? update.title.trim() : "";
219542
+ if (p?.platform === "slack" && p.isDm && slackTitle) this.enqueueApply(p, {
219543
+ kind: "set-title",
219544
+ text: slackTitle
219545
+ });
219546
+ else if (!p && slackTitle) {
219547
+ const binding = this.sessionDeliveryBindings.get(rec.key);
219548
+ if (binding) this.setSlackTitleForBinding(rec, binding, slackTitle);
219549
+ }
219550
+ }
219400
219551
  }
219552
+ if (!p) return;
219401
219553
  const isHeadlessGithubFinal = p.github !== void 0 && p.platform === "hook" && update?.sessionUpdate === "agent_message_chunk" && update?._meta?.codex?.phase === "final_answer";
219402
219554
  if (p.github) {
219403
219555
  p.github.collector.onUpdate(update);
@@ -219617,12 +219769,22 @@ var Daemon = class Daemon {
219617
219769
  const host = this.hosts.get(agentId);
219618
219770
  this.hosts.delete(agentId);
219619
219771
  this.hostStartedAt.delete(agentId);
219620
- if (!host) return;
219772
+ const clearDeliveryBindings = () => {
219773
+ for (const [key, binding] of this.sessionDeliveryBindings) if (binding.agentId === agentId) this.sessionDeliveryBindings.delete(key);
219774
+ };
219775
+ if (!host) {
219776
+ clearDeliveryBindings();
219777
+ return;
219778
+ }
219621
219779
  const stop = Promise.resolve(host.stop(deadlineMs)).finally(() => {
219622
219780
  if (this.hostStopping.get(agentId) === stop) this.hostStopping.delete(agentId);
219623
219781
  });
219624
219782
  this.hostStopping.set(agentId, stop);
219625
- await stop;
219783
+ try {
219784
+ await stop;
219785
+ } finally {
219786
+ clearDeliveryBindings();
219787
+ }
219626
219788
  }
219627
219789
  /** Recurring idle sweep (§7.2/§7.3): reap idle adapter children and TTL-close
219628
219790
  * idle sessions. Driven by the injected Clock so a FakeClock advances it in tests. */
@@ -219715,10 +219877,10 @@ var Daemon = class Daemon {
219715
219877
  })));
219716
219878
  const res = await this.raceDeadline(work, deadlineMs);
219717
219879
  settled = true;
219718
- if (res === "timeout") for (const [sid, p] of this.pending) {
219880
+ if (res === "timeout") for (const p of this.pending.values()) {
219719
219881
  if (!match(p)) continue;
219720
- this.log.warn(`drain[${scope.kind}]: cancelling straggler turn (session ${sid})`);
219721
- await this.hosts.get(p.agentId)?.cancel(sid).catch(() => {});
219882
+ this.log.warn(`drain[${scope.kind}]: cancelling straggler turn (session ${p.acpSessionId})`);
219883
+ await this.hosts.get(p.agentId)?.cancel(p.acpSessionId).catch(() => {});
219722
219884
  }
219723
219885
  return {
219724
219886
  matched: [...matched.values()],
@@ -219827,11 +219989,11 @@ var Daemon = class Daemon {
219827
219989
  entry.initAbort.abort(/* @__PURE__ */ new Error("daemon shutdown deadline"));
219828
219990
  forceAgents.add(entry.agentId);
219829
219991
  }
219830
- for (const [sid, p] of this.pending) {
219992
+ for (const p of this.pending.values()) {
219831
219993
  p.outputSuppressed ??= "shutdown";
219832
- this.releasePermissions(sid);
219833
- this.releaseElicits(sid);
219834
- this.hosts.get(p.agentId)?.cancel(sid).catch(() => {});
219994
+ this.releasePermissions(p.agentId, p.acpSessionId);
219995
+ this.releaseElicits(p.agentId, p.acpSessionId);
219996
+ this.hosts.get(p.agentId)?.cancel(p.acpSessionId).catch(() => {});
219835
219997
  }
219836
219998
  const forceStops = [...forceAgents].filter((id) => !coldAgents.has(id)).map(stopFailClosed);
219837
219999
  await Promise.all([
@@ -220511,4 +220673,4 @@ async function runForeground(opts, deps = {}) {
220511
220673
  //#endregion
220512
220674
  export { ClientTransport as i, CP_SUBPROTOCOL as n, CP_WS_PATH as r, runForeground as t };
220513
220675
 
220514
- //# sourceMappingURL=run-foreground-Ce7nBe9z.js.map
220676
+ //# sourceMappingURL=run-foreground-BRT8VM8Q.js.map