@agentconnect.md/daemon 1.0.0-rc.248 → 1.0.0-rc.249

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-NvLpcg5H.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";
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-CUct-2CR.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";
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";
@@ -4385,6 +4385,17 @@ var CpClient = class {
4385
4385
  if (this.state !== "READY" && this.state !== "DRAINING") return;
4386
4386
  this.transport?.send(encode(buildEnvelope("cron/report", report)));
4387
4387
  }
4388
+ /**
4389
+ * Report a hook fire's turn outcome (D→C `hook/report` EVT, fire-and-forget) —
4390
+ * the completion half of the HookRun two-stage lifecycle (the relay's
4391
+ * `rc/run-report` opened the row). A drop is absorbed by the CP's
4392
+ * HookRunReaper (`failed(orphaned)`, still overwritable by a late report).
4393
+ * No-op unless READY/DRAINING.
4394
+ */
4395
+ emitHookReport(report) {
4396
+ if (this.state !== "READY" && this.state !== "DRAINING") return;
4397
+ this.transport?.send(encode(buildEnvelope("hook/report", report)));
4398
+ }
4388
4399
  request(type, payload) {
4389
4400
  const frame = buildEnvelope(type, payload);
4390
4401
  return this.correlator.request(frame, (e) => this.transport.send(e));
@@ -196518,7 +196529,7 @@ function buildSyntheticMessage(agentId, cron, traceId) {
196518
196529
  msgId: `cron:${cron.id}:${traceId}`,
196519
196530
  traceId,
196520
196531
  source: "cron",
196521
- platform: "slack",
196532
+ platform: cron.target?.platform ?? "slack",
196522
196533
  channel: cron.target?.channel ?? `cron:${cron.id}`,
196523
196534
  thread: `cron:${cron.id}:${traceId}`,
196524
196535
  sender: {
@@ -196577,6 +196588,121 @@ var Scheduler = class {
196577
196588
  }
196578
196589
  };
196579
196590
  //#endregion
196591
+ //#region src/messages/hook-message.ts
196592
+ /** channel/thread from the affinity key — see the sessionKey grammar on {@link RdMsgHook}. */
196593
+ function splitSessionKey(msg) {
196594
+ if (msg.sessionKey === msg.hookId) return { channel: msg.hookId };
196595
+ if (msg.sessionKey === `${msg.hookId}:${msg.deliveryKey}`) return {
196596
+ channel: msg.hookId,
196597
+ thread: msg.deliveryKey
196598
+ };
196599
+ const hash = msg.sessionKey.lastIndexOf("#");
196600
+ if (hash > 0) return {
196601
+ channel: msg.sessionKey.slice(0, hash),
196602
+ thread: msg.sessionKey.slice(hash + 1)
196603
+ };
196604
+ return { channel: msg.sessionKey };
196605
+ }
196606
+ /** The well-known payload fields a caller can speak through, in priority order. */
196607
+ const MESSAGE_FIELDS = [
196608
+ "prompt",
196609
+ "text",
196610
+ "message"
196611
+ ];
196612
+ /**
196613
+ * Pull the caller's message out of the delivery body: a bare JSON string is
196614
+ * the message itself; a JSON object speaks through its first `prompt`/`text`/
196615
+ * `message` string field (remaining fields become a context appendix); any
196616
+ * other shape (or non-JSON — can't happen via the relay's json-only ingress,
196617
+ * but stay tolerant) is presented whole as the payload.
196618
+ */
196619
+ function deliveryMessage(body) {
196620
+ try {
196621
+ const parsed = JSON.parse(body);
196622
+ if (typeof parsed === "string" && parsed.trim()) return { message: parsed };
196623
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
196624
+ const obj = parsed;
196625
+ for (const field of MESSAGE_FIELDS) {
196626
+ const v = obj[field];
196627
+ if (typeof v === "string" && v.trim()) {
196628
+ const rest = Object.fromEntries(Object.entries(obj).filter(([k]) => k !== field));
196629
+ return Object.keys(rest).length ? {
196630
+ message: v,
196631
+ rest: JSON.stringify(rest, null, 2)
196632
+ } : { message: v };
196633
+ }
196634
+ }
196635
+ }
196636
+ } catch {}
196637
+ return {
196638
+ message: "",
196639
+ rest: body
196640
+ };
196641
+ }
196642
+ /** The turn text: the caller's payload-borne message (+ leftover fields as context). */
196643
+ function buildHookText(msg) {
196644
+ const parts = [];
196645
+ const body = msg.context?.body;
196646
+ if (body) {
196647
+ const { message, rest } = deliveryMessage(body);
196648
+ if (message) parts.push(message);
196649
+ if (rest) {
196650
+ const label = msg.context?.truncated ? "Delivery payload (truncated):" : message ? "Rest of the delivery payload:" : "Delivery payload:";
196651
+ parts.push([
196652
+ label,
196653
+ "```json",
196654
+ rest,
196655
+ "```"
196656
+ ].join("\n"));
196657
+ }
196658
+ }
196659
+ return parts.length ? parts.join("\n\n") : `Webhook delivery ${msg.deliveryKey} arrived with an empty body.`;
196660
+ }
196661
+ /** A short anchor line for target-channel fires: the caller's message when one
196662
+ * is extractable (first line, capped), else the delivery key. */
196663
+ function hookAnchorText(msg) {
196664
+ const body = msg.context?.body;
196665
+ const line = (body ? deliveryMessage(body).message : "").split("\n", 1)[0].trim();
196666
+ return `🪝 ${(line.length > 140 ? `${line.slice(0, 139)}…` : line) || `Webhook delivery ${msg.deliveryKey}`}`;
196667
+ }
196668
+ function buildHookMessage(msg, traceId) {
196669
+ const { channel, thread } = splitSessionKey(msg);
196670
+ const target = msg.target;
196671
+ if (target) return {
196672
+ msgId: msg.msgId,
196673
+ traceId,
196674
+ source: "hook",
196675
+ platform: target.platform,
196676
+ channel: target.channel,
196677
+ thread: msg.msgId,
196678
+ sender: {
196679
+ id: `hook:${msg.hookId}`,
196680
+ isBot: false
196681
+ },
196682
+ text: buildHookText(msg),
196683
+ mentionedBots: [],
196684
+ isDm: false,
196685
+ trigger: "hook"
196686
+ };
196687
+ return {
196688
+ msgId: msg.msgId,
196689
+ traceId,
196690
+ source: "hook",
196691
+ platform: "hook",
196692
+ channel,
196693
+ ...thread ? { thread } : {},
196694
+ sender: {
196695
+ id: `hook:${msg.hookId}`,
196696
+ isBot: false
196697
+ },
196698
+ text: buildHookText(msg),
196699
+ mentionedBots: [],
196700
+ isDm: false,
196701
+ trigger: "hook",
196702
+ headless: true
196703
+ };
196704
+ }
196705
+ //#endregion
196580
196706
  //#region src/runtimes/runtime-prober.ts
196581
196707
  const DEFAULT_TIMEOUT_MS = 3e4;
196582
196708
  const DEFAULT_CONCURRENCY = 3;
@@ -196797,9 +196923,11 @@ var RelayClient = class {
196797
196923
  }
196798
196924
  this.deps.log.debug(`relay(${this.relayId}): ignoring ${frame.type}`);
196799
196925
  }
196800
- /** An inbound webchat op: dispatch it, reply `rd/ack`, and let the daemon stream `rd/chat`. */
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). */
196801
196928
  handleMsg(reqId, msg) {
196802
- const ack = this.deps.onRelayMsg(msg, (event) => this.sendChat(msg.chatId, event));
196929
+ const chat = msg.source === "webchat" ? (event) => this.sendChat(msg.chatId, event) : () => void 0;
196930
+ const ack = this.deps.onRelayMsg(msg, chat);
196803
196931
  this.transport?.send(JSON.stringify(buildRelayDaemonFrame("rd/ack", ack, { corr: reqId })));
196804
196932
  }
196805
196933
  /** Send one reply chunk back over THIS relay's socket, stamping the per-chat `seq`. */
@@ -214408,14 +214536,16 @@ var Daemon = class Daemon {
214408
214536
  }
214409
214537
  relayMsgAcks = /* @__PURE__ */ new Map();
214410
214538
  /**
214411
- * Dispatch one inbound relay webchat op (`rd/msg`) and return the `rd/ack` verdict —
214412
- * the relay-path entry, reusing the SAME turn engine + by-key cores as the (retiring)
214413
- * CP path. The `chat` callback streams the reply back as `rd/chat` over the relay
214414
- * socket the op arrived on (wrapped into the sink).
214539
+ * Dispatch one inbound relay item (`rd/msg` — a webchat op or a hook fire) and
214540
+ * return the `rd/ack` verdict — the relay-path entry, reusing the SAME turn
214541
+ * engine + by-key cores as the (retiring) CP path. The `chat` callback streams
214542
+ * a webchat reply back as `rd/chat` over the relay socket the op arrived on
214543
+ * (no-op for hooks — their outcome goes to the CP as `hook/report`).
214415
214544
  *
214416
214545
  * Deduped by the relay-minted (sessionKey, msgId): the wire is at-least-once, so a
214417
214546
  * retransmitted rd/msg (identical msgId) must NOT run the user's turn a second time —
214418
- * replay the original ack (so the relay settles) without re-dispatching.
214547
+ * replay the original ack (so the relay settles) without re-dispatching. For hooks
214548
+ * the same replay absorbs a GitHub/manual REDELIVERY of the same deliveryKey.
214419
214549
  */
214420
214550
  handleRelayMsg(msg, chat) {
214421
214551
  const dedupKey = `${msg.sessionKey}:${msg.msgId}`;
@@ -214424,11 +214554,79 @@ var Daemon = class Daemon {
214424
214554
  this.log.debug(`relay: duplicate rd/msg ${dedupKey} — replaying ack (no re-dispatch)`);
214425
214555
  return prior;
214426
214556
  }
214427
- const ack = this.dispatchRelayOp(msg, chat);
214557
+ const ack = msg.source === "hook" ? this.dispatchRelayHook(msg) : this.dispatchRelayOp(msg, chat);
214428
214558
  if (this.relayMsgAcks.size >= 2e3) this.relayMsgAcks.clear();
214429
214559
  this.relayMsgAcks.set(dedupKey, ack);
214430
214560
  return ack;
214431
214561
  }
214562
+ /**
214563
+ * Ack-verdict gate for one hook fire (`rd/msg` hook member) — the mirror of
214564
+ * {@link dispatchWebchatTurn}'s synchronous gates: the relay's rc/run-report
214565
+ * needs a REASONED rejection now, not a silently dropped fire-and-forget
214566
+ * dispatch. Accepted fires run async via {@link onHookFire}.
214567
+ */
214568
+ dispatchRelayHook(msg) {
214569
+ if (!this.agents.has(msg.agentId)) {
214570
+ this.log.warn(`hook: no agent "${msg.agentId}" on this daemon — rejecting fire ${msg.msgId}`);
214571
+ return {
214572
+ msgId: msg.msgId,
214573
+ accepted: false,
214574
+ reason: "no_agent"
214575
+ };
214576
+ }
214577
+ if (this.agents.get(msg.agentId)?.pause) {
214578
+ this.log.info(`hook: agent "${msg.agentId}" is paused — rejecting fire ${msg.msgId}`);
214579
+ return {
214580
+ msgId: msg.msgId,
214581
+ accepted: false,
214582
+ reason: "paused"
214583
+ };
214584
+ }
214585
+ if (this.draining || this.drainingAgents.has(msg.agentId)) {
214586
+ this.log.info(`hook: agent "${msg.agentId}" is draining — rejecting fire ${msg.msgId}`);
214587
+ return {
214588
+ msgId: msg.msgId,
214589
+ accepted: false,
214590
+ reason: "draining"
214591
+ };
214592
+ }
214593
+ this.onHookFire(msg).catch((err) => this.log.error(`hook fire dispatch failed for agent "${msg.agentId}": ${formatErr(err)}`));
214594
+ return {
214595
+ msgId: msg.msgId,
214596
+ accepted: true
214597
+ };
214598
+ }
214599
+ /**
214600
+ * A hook fired for `agentId` (explicit target — no routing;
214601
+ * webhook-triggers-and-github-events.md). The relay already opened the HookRun
214602
+ * row (`rc/run-report accepted`); when the turn ends this closes it with a
214603
+ * completion `hook/report` EVT on the control WS (the cron/report pattern).
214604
+ * Anchoring rides the shared {@link fireTrigger} path: with a target the
214605
+ * trigger text lands in the channel and the session threads under it,
214606
+ * without one the fire runs headless.
214607
+ */
214608
+ async onHookFire(msg) {
214609
+ const firedAt = this.clock.now();
214610
+ const report = (status, extra = {}) => {
214611
+ this.cpClient?.emitHookReport({
214612
+ hookId: msg.hookId,
214613
+ agentId: msg.agentId,
214614
+ deliveryKey: msg.deliveryKey,
214615
+ status,
214616
+ durationMs: Math.max(0, this.clock.now() - firedAt),
214617
+ ...extra
214618
+ });
214619
+ };
214620
+ const nmsg = buildHookMessage(msg, randomUUID());
214621
+ try {
214622
+ const sessionId = await this.fireTrigger(msg.agentId, nmsg, msg.target, hookAnchorText(msg), `hook "${msg.hookId}"`);
214623
+ if (sessionId !== null) report("success", { sessionId });
214624
+ else report("failed", { reason: "dropped" });
214625
+ } catch (err) {
214626
+ report("failed", { reason: err.message?.slice(0, 300) || "dispatch failed" });
214627
+ throw err;
214628
+ }
214629
+ }
214432
214630
  /** The op-switch behind {@link handleRelayMsg} (dedup handled by the caller). */
214433
214631
  dispatchRelayOp(msg, chat) {
214434
214632
  const sink = {
@@ -215938,37 +216136,46 @@ var Daemon = class Daemon {
215938
216136
  }
215939
216137
  }
215940
216138
  /**
215941
- * A cron fired for `agentId` (§8.6 bypass — the cron names its agent; no
215942
- * routing). The fire is stamped into D11 first (the daemon owns last-run,
215943
- * protocol §5.4). With a target channel, the trigger text is posted there as a
215944
- * real ANCHOR message and the session threads its replies under it —
215945
- * equivalent to a user posting the trigger in-channel. Without one (or when
215946
- * the anchor can't be posted) the turn still runs; a `headless` fire
215947
- * additionally suppresses all platform output in dispatch.
216139
+ * Shared fire path for the non-message trigger sources (cron, hook — both
216140
+ * name their agent; no routing). With a target channel, `anchorText` is
216141
+ * posted there as a real ANCHOR message (through the target integration's
216142
+ * platform connection — Slack/Telegram/Discord alike) and the session
216143
+ * threads its replies under it — equivalent to a user posting the trigger
216144
+ * in-channel. Without one (or when the anchor can't be posted) the turn
216145
+ * still runs; a `headless` msg additionally suppresses all platform output
216146
+ * in dispatch. Resolves with dispatch's sessionId (null = gate-dropped).
215948
216147
  */
215949
- async onCronFire(agentId, msg, cron) {
215950
- const firedAt = this.clock.now();
215951
- this.store.setCronLastRun(`${agentId}:${cron.id}`, firedAt);
215952
- if (cron.origin === "cp") this.cpClient?.emitCronReport({
215953
- cronId: cron.id,
215954
- agentId,
215955
- firedAt: new Date(firedAt).toISOString()
215956
- });
215957
- if (cron.target?.channel) {
215958
- const conn = this.replyConnFor(agentId, cron.target.integrationId);
215959
- if (!conn) this.log.warn(`cron "${cron.id}": agent "${agentId}" has no live slack connection — running without output`);
216148
+ async fireTrigger(agentId, msg, target, anchorText, label) {
216149
+ if (target?.channel) {
216150
+ const conn = this.replyConnFor(agentId, target.integrationId);
216151
+ if (!conn) this.log.warn(`${label}: agent "${agentId}" has no live platform connection — running without output`);
215960
216152
  else try {
215961
- const ts = await conn.postMessage(cron.target.channel, `⏰ ${cron.trigger}`);
216153
+ const ts = await conn.postMessage(target.channel, anchorText);
215962
216154
  if (ts) msg = {
215963
216155
  ...msg,
215964
216156
  thread: ts
215965
216157
  };
215966
216158
  } catch (err) {
215967
- this.log.warn(`cron "${cron.id}": failed to post trigger to ${cron.target.channel} (${formatErr(err)}) — running without anchor`);
216159
+ this.log.warn(`${label}: failed to post trigger to ${target.channel} (${formatErr(err)}) — running without anchor`);
215968
216160
  }
215969
216161
  }
216162
+ return this.dispatch(agentId, msg, target?.integrationId);
216163
+ }
216164
+ /**
216165
+ * A cron fired for `agentId` (§8.6 bypass). The fire is stamped into D11
216166
+ * first (the daemon owns last-run, protocol §5.4); the anchor+dispatch ride
216167
+ * the shared {@link fireTrigger} path.
216168
+ */
216169
+ async onCronFire(agentId, msg, cron) {
216170
+ const firedAt = this.clock.now();
216171
+ this.store.setCronLastRun(`${agentId}:${cron.id}`, firedAt);
216172
+ if (cron.origin === "cp") this.cpClient?.emitCronReport({
216173
+ cronId: cron.id,
216174
+ agentId,
216175
+ firedAt: new Date(firedAt).toISOString()
216176
+ });
215970
216177
  try {
215971
- const sessionId = await this.dispatch(agentId, msg, cron.target?.integrationId);
216178
+ const sessionId = await this.fireTrigger(agentId, msg, cron.target, `⏰ ${cron.trigger}`, `cron "${cron.id}"`);
215972
216179
  if (cron.origin === "cp" && sessionId !== null) this.cpClient?.emitCronReport({
215973
216180
  cronId: cron.id,
215974
216181
  agentId,
@@ -216230,4 +216437,4 @@ async function runForeground(opts, deps = {}) {
216230
216437
  //#endregion
216231
216438
  export { ClientTransport as i, CP_SUBPROTOCOL as n, CP_WS_PATH as r, runForeground as t };
216232
216439
 
216233
- //# sourceMappingURL=run-foreground-K9FNooBp.js.map
216440
+ //# sourceMappingURL=run-foreground-CN7_7yMq.js.map