dorkos 0.13.0 → 0.14.0

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.
@@ -1,4 +1,4 @@
1
- import { createRequire } from 'module'; import { fileURLToPath as __fup } from 'url'; const require = createRequire(import.meta.url); const __filename = __fup(import.meta.url);
1
+ import { createRequire as __cjsRequire } from 'module'; import { fileURLToPath as __fup } from 'url'; const require = __cjsRequire(import.meta.url); const __filename = __fup(import.meta.url);
2
2
  var __create = Object.create;
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -69022,13 +69022,13 @@ var HistoryMessageSchema = z.object({
69022
69022
  commandArgs: z.string().optional()
69023
69023
  }).openapi("HistoryMessage");
69024
69024
  var CommandEntrySchema = z.object({
69025
- namespace: z.string(),
69026
- command: z.string(),
69025
+ namespace: z.string().optional(),
69026
+ command: z.string().optional(),
69027
69027
  fullCommand: z.string(),
69028
69028
  description: z.string(),
69029
69029
  argumentHint: z.string().optional(),
69030
69030
  allowedTools: z.array(z.string()).optional(),
69031
- filePath: z.string()
69031
+ filePath: z.string().optional()
69032
69032
  }).openapi("CommandEntry");
69033
69033
  var CommandRegistrySchema = z.object({
69034
69034
  commands: z.array(CommandEntrySchema),
@@ -70918,7 +70918,7 @@ var SERVER_VERSION = resolveVersion();
70918
70918
  var IS_DEV_BUILD = checkDevBuild(SERVER_VERSION);
70919
70919
  function resolveVersion() {
70920
70920
  if (env.DORKOS_VERSION_OVERRIDE) return env.DORKOS_VERSION_OVERRIDE;
70921
- if (true) return "0.13.0";
70921
+ if (true) return "0.14.0";
70922
70922
  const pkgPath = path4.join(path4.dirname(fileURLToPath2(import.meta.url)), "../../package.json");
70923
70923
  return JSON.parse(readFileSync(pkgPath, "utf-8")).version;
70924
70924
  }
@@ -72261,11 +72261,14 @@ var AdapterBindingSchema = z11.object({
72261
72261
  id: z11.string().uuid(),
72262
72262
  adapterId: z11.string(),
72263
72263
  agentId: z11.string(),
72264
- projectPath: z11.string(),
72265
72264
  chatId: z11.string().optional(),
72266
72265
  channelType: ChannelTypeSchema.optional(),
72267
72266
  sessionStrategy: SessionStrategySchema.default("per-chat"),
72268
72267
  label: z11.string().default(""),
72268
+ permissionMode: PermissionModeSchema.optional().default("acceptEdits"),
72269
+ canInitiate: z11.boolean().default(false),
72270
+ canReply: z11.boolean().default(true),
72271
+ canReceive: z11.boolean().default(true),
72269
72272
  createdAt: z11.string().datetime(),
72270
72273
  updatedAt: z11.string().datetime()
72271
72274
  }).openapi("AdapterBinding");
@@ -73863,7 +73866,7 @@ function buildCorsOrigin() {
73863
73866
  if (envOrigin) {
73864
73867
  return envOrigin.split(",").map((o3) => o3.trim());
73865
73868
  }
73866
- const port = process.env.DORKOS_PORT || "4242";
73869
+ const port = String(env.DORKOS_PORT);
73867
73870
  const vitePort = process.env.VITE_PORT || "4241";
73868
73871
  const staticOrigins = [
73869
73872
  `http://localhost:${port}`,
@@ -76325,7 +76328,7 @@ var SessionBroadcaster = class {
76325
76328
  * @param res - Express Response object configured for SSE
76326
76329
  * @param clientId - Optional client identifier
76327
76330
  */
76328
- registerClient(sessionId, vaultRoot2, res, clientId) {
76331
+ registerClient(sessionId, vaultRoot2, res, _clientId) {
76329
76332
  if (this.totalClientCount >= SSE.MAX_TOTAL_CLIENTS) {
76330
76333
  res.status(503).json({ error: "SSE connection limit reached", code: "SSE_LIMIT" });
76331
76334
  return;
@@ -76609,7 +76612,11 @@ var CommandRegistryService = class _CommandRegistryService {
76609
76612
  static CACHE_TTL_MS = 5 * 60 * 1e3;
76610
76613
  // 5 minutes
76611
76614
  commandsDir;
76612
- /** @param vaultRoot - Must be pre-validated against directory boundary by caller */
76615
+ /**
76616
+ * Create a command registry scoped to a vault root.
76617
+ *
76618
+ * @param vaultRoot - Must be pre-validated against directory boundary by caller
76619
+ */
76613
76620
  constructor(vaultRoot2) {
76614
76621
  this.commandsDir = path15.join(vaultRoot2, ".claude", "commands");
76615
76622
  }
@@ -76622,36 +76629,32 @@ var CommandRegistryService = class _CommandRegistryService {
76622
76629
  withFileTypes: true
76623
76630
  });
76624
76631
  for (const entry of entries) {
76625
- if (!entry.isDirectory()) continue;
76626
- const nsPath = path15.join(this.commandsDir, entry.name);
76627
- const files = await fs9.readdir(nsPath);
76628
- for (const file of files) {
76629
- if (!file.endsWith(".md")) continue;
76630
- const filePath = path15.join(nsPath, file);
76631
- try {
76632
- const content3 = await fs9.readFile(filePath, "utf-8");
76633
- let frontmatter;
76634
- try {
76635
- frontmatter = matter(content3).data;
76636
- } catch {
76637
- frontmatter = parseFrontmatterFallback(content3);
76638
- }
76632
+ if (entry.isDirectory()) {
76633
+ const nsPath = path15.join(this.commandsDir, entry.name);
76634
+ const files = await fs9.readdir(nsPath);
76635
+ for (const file of files) {
76636
+ if (!file.endsWith(".md")) continue;
76637
+ const filePath = path15.join(nsPath, file);
76638
+ const parsed = await this.parseCommandFile(filePath);
76639
+ if (!parsed) continue;
76639
76640
  const commandName = file.replace(".md", "");
76640
- const allowedToolsRaw = frontmatter["allowed-tools"];
76641
76641
  commands.push({
76642
76642
  namespace: entry.name,
76643
76643
  command: commandName,
76644
76644
  fullCommand: `/${entry.name}:${commandName}`,
76645
- description: frontmatter.description || "",
76646
- argumentHint: frontmatter["argument-hint"],
76647
- allowedTools: typeof allowedToolsRaw === "string" ? allowedToolsRaw.split(",").map((t2) => t2.trim()) : allowedToolsRaw,
76648
- filePath: path15.relative(process.cwd(), filePath)
76645
+ ...parsed
76649
76646
  });
76650
- } catch (fileErr) {
76651
- logger.warn(
76652
- `[CommandRegistry] Skipping ${entry.name}/${file}: ${fileErr.message}`
76653
- );
76654
76647
  }
76648
+ } else if (entry.name.endsWith(".md")) {
76649
+ const filePath = path15.join(this.commandsDir, entry.name);
76650
+ const parsed = await this.parseCommandFile(filePath);
76651
+ if (!parsed) continue;
76652
+ const commandName = entry.name.replace(".md", "");
76653
+ commands.push({
76654
+ command: commandName,
76655
+ fullCommand: `/${commandName}`,
76656
+ ...parsed
76657
+ });
76655
76658
  }
76656
76659
  }
76657
76660
  } catch (err) {
@@ -76662,6 +76665,32 @@ var CommandRegistryService = class _CommandRegistryService {
76662
76665
  this.cacheTime = Date.now();
76663
76666
  return this.cache;
76664
76667
  }
76668
+ /**
76669
+ * Parse a command `.md` file, extracting frontmatter metadata.
76670
+ *
76671
+ * @returns Partial command fields, or `null` if the file could not be read.
76672
+ */
76673
+ async parseCommandFile(filePath) {
76674
+ try {
76675
+ const content3 = await fs9.readFile(filePath, "utf-8");
76676
+ let frontmatter;
76677
+ try {
76678
+ frontmatter = matter(content3).data;
76679
+ } catch {
76680
+ frontmatter = parseFrontmatterFallback(content3);
76681
+ }
76682
+ const allowedToolsRaw = frontmatter["allowed-tools"];
76683
+ return {
76684
+ description: frontmatter.description || "",
76685
+ argumentHint: frontmatter["argument-hint"],
76686
+ allowedTools: typeof allowedToolsRaw === "string" ? allowedToolsRaw.split(",").map((t2) => t2.trim()) : allowedToolsRaw,
76687
+ filePath: path15.relative(process.cwd(), filePath)
76688
+ };
76689
+ } catch (err) {
76690
+ logger.warn(`[CommandRegistry] Skipping ${filePath}: ${err.message}`);
76691
+ return null;
76692
+ }
76693
+ }
76665
76694
  invalidateCache() {
76666
76695
  this.cache = null;
76667
76696
  this.cacheTime = 0;
@@ -77421,6 +77450,19 @@ ${messageOpts.systemPromptAppend}` : baseAppend;
77421
77450
  logger.debug("[sendMessage] failed to fetch MCP server status", { err });
77422
77451
  });
77423
77452
  }
77453
+ if (opts.onCommandsReceived) {
77454
+ agentQuery.supportedCommands().then((commands) => {
77455
+ opts.onCommandsReceived(
77456
+ commands.map((c3) => ({
77457
+ name: c3.name,
77458
+ description: c3.description,
77459
+ argumentHint: c3.argumentHint
77460
+ }))
77461
+ );
77462
+ }).catch((err) => {
77463
+ logger.debug("[sendMessage] failed to fetch supported commands", { err });
77464
+ });
77465
+ }
77424
77466
  logger.info("[sendMessage] stream start", {
77425
77467
  session: sessionId,
77426
77468
  hasStarted: session.hasStarted,
@@ -77553,6 +77595,7 @@ var ClaudeCodeRuntime = class _ClaudeCodeRuntime {
77553
77595
  mcpServerFactory = null;
77554
77596
  cachedModels = null;
77555
77597
  cachedMcpStatus = /* @__PURE__ */ new Map();
77598
+ cachedSdkCommands = null;
77556
77599
  meshCore = null;
77557
77600
  constructor(cwd) {
77558
77601
  this.cwd = cwd ?? DEFAULT_CWD;
@@ -77699,6 +77742,12 @@ var ClaudeCodeRuntime = class _ClaudeCodeRuntime {
77699
77742
  this.cachedMcpStatus.set(key, servers);
77700
77743
  logger.debug("[sendMessage] cached MCP server status", { cwd: key, count: servers.length });
77701
77744
  },
77745
+ onCommandsReceived: !this.cachedSdkCommands ? (commands) => {
77746
+ this.cachedSdkCommands = commands;
77747
+ logger.debug("[sendMessage] cached supported commands", {
77748
+ count: commands.length
77749
+ });
77750
+ } : void 0,
77702
77751
  sdkSessionIndex: this.sdkSessionIndex,
77703
77752
  sessionMapKey: sessionId
77704
77753
  }, opts);
@@ -77794,11 +77843,48 @@ var ClaudeCodeRuntime = class _ClaudeCodeRuntime {
77794
77843
  return this.cachedModels ?? DEFAULT_MODELS;
77795
77844
  }
77796
77845
  // ---------------------------------------------------------------------------
77797
- // Commands — delegated to CommandRegistryService
77846
+ // Commands — SDK primary, filesystem enrichment
77798
77847
  // ---------------------------------------------------------------------------
77799
- /** Return the command registry for the given CWD (defaults to server CWD). */
77848
+ /**
77849
+ * Return commands for the given CWD (defaults to server CWD).
77850
+ *
77851
+ * When SDK commands are cached (populated after first SDK query), they are
77852
+ * the authoritative source and get enriched with filesystem metadata
77853
+ * (allowedTools, filePath, namespace, command). Before any SDK session,
77854
+ * falls back to the filesystem scanner for immediate availability.
77855
+ */
77800
77856
  async getCommands(forceRefresh, cwd) {
77801
77857
  const root2 = cwd || this.cwd;
77858
+ if (this.cachedSdkCommands) {
77859
+ const sdkEntries = this.cachedSdkCommands.map((c3) => ({
77860
+ fullCommand: c3.name.startsWith("/") ? c3.name : `/${c3.name}`,
77861
+ description: c3.description,
77862
+ argumentHint: c3.argumentHint || void 0
77863
+ }));
77864
+ const registry3 = this.getOrCreateRegistry(root2);
77865
+ const fsCommands = await registry3.getCommands(forceRefresh);
77866
+ const fsLookup = new Map(fsCommands.commands.map((c3) => [c3.fullCommand, c3]));
77867
+ const merged = sdkEntries.map((entry) => {
77868
+ const fsMatch = fsLookup.get(entry.fullCommand);
77869
+ if (fsMatch) {
77870
+ return {
77871
+ ...entry,
77872
+ namespace: fsMatch.namespace,
77873
+ command: fsMatch.command,
77874
+ allowedTools: fsMatch.allowedTools,
77875
+ filePath: fsMatch.filePath
77876
+ };
77877
+ }
77878
+ return entry;
77879
+ });
77880
+ merged.sort((a2, b3) => a2.fullCommand.localeCompare(b3.fullCommand));
77881
+ return { commands: merged, lastScanned: (/* @__PURE__ */ new Date()).toISOString() };
77882
+ }
77883
+ const registry2 = this.getOrCreateRegistry(root2);
77884
+ return registry2.getCommands(forceRefresh);
77885
+ }
77886
+ /** Get or create a CommandRegistryService for the given root, with LRU eviction. */
77887
+ getOrCreateRegistry(root2) {
77802
77888
  let registry2 = this.commandRegistries.get(root2);
77803
77889
  if (!registry2) {
77804
77890
  if (this.commandRegistries.size >= _ClaudeCodeRuntime.MAX_COMMAND_REGISTRIES) {
@@ -77808,7 +77894,7 @@ var ClaudeCodeRuntime = class _ClaudeCodeRuntime {
77808
77894
  registry2 = new CommandRegistryService(root2);
77809
77895
  this.commandRegistries.set(root2, registry2);
77810
77896
  }
77811
- return registry2.getCommands(forceRefresh);
77897
+ return registry2;
77812
77898
  }
77813
77899
  // ---------------------------------------------------------------------------
77814
77900
  // Lifecycle
@@ -78538,7 +78624,6 @@ function createBindingCreateHandler(deps) {
78538
78624
  const binding = await deps.bindingStore.create({
78539
78625
  adapterId: args.adapterId,
78540
78626
  agentId: args.agentId,
78541
- projectPath: args.projectPath,
78542
78627
  sessionStrategy: args.sessionStrategy ?? "per-chat",
78543
78628
  label: args.label ?? "",
78544
78629
  ...args.chatId && { chatId: args.chatId },
@@ -78576,7 +78661,6 @@ function getBindingTools(deps) {
78576
78661
  {
78577
78662
  adapterId: z20.string().describe("ID of the adapter to bind"),
78578
78663
  agentId: z20.string().describe("Agent ID to route messages to"),
78579
- projectPath: z20.string().describe("Filesystem path to the agent working directory"),
78580
78664
  sessionStrategy: z20.string().optional().describe("Session strategy: per-chat, per-user, or stateless (default per-chat)"),
78581
78665
  chatId: z20.string().optional().describe("Optional chat ID for targeted routing"),
78582
78666
  channelType: z20.string().optional().describe("Optional channel type filter: dm, group, channel, or thread"),
@@ -85388,7 +85472,7 @@ async function loadPresets(dorkHome) {
85388
85472
  function createPulseRouter(store, scheduler, meshCore2) {
85389
85473
  const router13 = Router14();
85390
85474
  router13.get("/presets", async (_req, res) => {
85391
- const dorkHome = process.env.DORK_HOME;
85475
+ const dorkHome = env.DORK_HOME;
85392
85476
  if (!dorkHome) {
85393
85477
  return res.status(500).json({ error: "DORK_HOME not configured" });
85394
85478
  }
@@ -87355,9 +87439,9 @@ var DeliveryPipeline = class {
87355
87439
  *
87356
87440
  * @param endpoint - The endpoint that received the message
87357
87441
  * @param messageId - The Maildir-assigned message ID (ULID filename)
87358
- * @param envelope - The delivered envelope
87442
+ * @param _envelope - The delivered envelope (unused; handlers receive claimed copy)
87359
87443
  */
87360
- async dispatchToSubscribers(endpoint, messageId, envelope) {
87444
+ async dispatchToSubscribers(endpoint, messageId, _envelope) {
87361
87445
  const handlers = this.deps.subscriptionRegistry.getSubscribers(endpoint.subject);
87362
87446
  if (handlers.length === 0)
87363
87447
  return;
@@ -87991,6 +88075,16 @@ var RelayCore = class {
87991
88075
  this.assertOpen();
87992
88076
  return executeGetDeadLetters(options, this.endpointDeps);
87993
88077
  }
88078
+ /**
88079
+ * Remove a single dead letter by endpoint hash and message ID.
88080
+ *
88081
+ * @param endpointHash - The endpoint hash where the dead letter resides
88082
+ * @param messageId - The ULID message ID to remove
88083
+ */
88084
+ async removeDeadLetter(endpointHash, messageId) {
88085
+ this.assertOpen();
88086
+ await this.endpointDeps.deadLetterQueue.removeDeadLetter(endpointHash, messageId);
88087
+ }
87994
88088
  /** Add an access control rule. */
87995
88089
  addAccessRule(rule) {
87996
88090
  this.assertOpen();
@@ -101976,7 +102070,7 @@ var SLACK_MANIFEST = {
101976
102070
  stepId: "create-app",
101977
102071
  title: "Create & Configure a Slack App",
101978
102072
  description: 'Go to api.slack.com/apps \u2192 Create New App \u2192 From Scratch.\n\n1. **Socket Mode** \u2014 Enable it (Settings \u2192 Socket Mode).\n2. **Event Subscriptions** \u2014 Turn on Enable Events, then subscribe to bot events: message.channels, message.groups, message.im, app_mention.\n3. **OAuth & Permissions** \u2014 Add bot token scopes: channels:history, channels:read, chat:write, groups:history, groups:read, im:history, im:read, im:write, mpim:history, app_mentions:read, users:read, reactions:write. Then install the app to your workspace.\n4. **App-Level Token** \u2014 In Basic Information \u2192 App-Level Tokens, generate a token with the connections:write scope.\n\n\u26A0\uFE0F Do NOT enable "Agents & AI Apps" \u2014 it adds user scopes that cause install failures on most workspaces.',
101979
- fields: ["botToken", "appToken", "signingSecret"]
102073
+ fields: ["botToken", "appToken", "signingSecret", "streaming", "typingIndicator"]
101980
102074
  }
101981
102075
  ],
101982
102076
  configFields: [
@@ -102600,6 +102694,8 @@ var ClaudeCodeAdapter = class {
102600
102694
  errorCount: 0
102601
102695
  };
102602
102696
  /**
102697
+ * Create a Claude Code relay adapter.
102698
+ *
102603
102699
  * @param id - Unique adapter identifier (e.g., 'claude-code')
102604
102700
  * @param config - Adapter configuration (concurrency, timeout, default cwd)
102605
102701
  * @param deps - Injected dependencies (agentManager, traceStore, pulseStore)
@@ -102804,6 +102900,48 @@ function validateAdapterShape(obj, id) {
102804
102900
  }
102805
102901
 
102806
102902
  // ../../apps/server/src/routes/relay.ts
102903
+ import { Router as Router16 } from "express";
102904
+
102905
+ // ../../apps/server/src/services/relay/subject-resolver.ts
102906
+ var SESSION_ID_PREVIEW_LENGTH = 7;
102907
+ async function resolveSubjectLabel(subject, deps) {
102908
+ const raw = subject;
102909
+ if (subject === "relay.system.console") {
102910
+ return { label: "System Console", raw };
102911
+ }
102912
+ if (subject.startsWith("relay.system.pulse.")) {
102913
+ return { label: "Pulse Scheduler", raw };
102914
+ }
102915
+ if (subject.startsWith("relay.human.console.")) {
102916
+ return { label: "You", raw };
102917
+ }
102918
+ if (subject.startsWith("relay.agent.") || subject.startsWith("relay.inbox.")) {
102919
+ const prefix = subject.startsWith("relay.agent.") ? "relay.agent." : "relay.inbox.";
102920
+ const sessionId = subject.slice(prefix.length);
102921
+ const shortId = sessionId.slice(0, SESSION_ID_PREVIEW_LENGTH);
102922
+ const fallback = { label: `Agent (${shortId})`, raw };
102923
+ if (!deps.getSession) return fallback;
102924
+ try {
102925
+ const session = await deps.getSession(sessionId);
102926
+ if (!session?.cwd || !deps.readManifest) return fallback;
102927
+ const manifest = await deps.readManifest(session.cwd);
102928
+ if (!manifest?.name) return fallback;
102929
+ return { label: manifest.name, raw };
102930
+ } catch {
102931
+ return fallback;
102932
+ }
102933
+ }
102934
+ return { label: subject, raw };
102935
+ }
102936
+ async function resolveSubjectLabels(subjects, deps) {
102937
+ const unique = [...new Set(subjects)];
102938
+ const results = await Promise.all(
102939
+ unique.map(async (s3) => [s3, await resolveSubjectLabel(s3, deps)])
102940
+ );
102941
+ return new Map(results);
102942
+ }
102943
+
102944
+ // ../../apps/server/src/routes/relay-adapters.ts
102807
102945
  import { Router as Router15 } from "express";
102808
102946
  import express2 from "express";
102809
102947
  import { z as z24 } from "zod";
@@ -102901,7 +103039,7 @@ var BindingStore = class {
102901
103039
  * Update an existing binding's mutable fields.
102902
103040
  *
102903
103041
  * @param id - The binding UUID to update
102904
- * @param updates - Fields to update (sessionStrategy, label, chatId, channelType)
103042
+ * @param updates - Fields to update (sessionStrategy, label, chatId, channelType, permissions)
102905
103043
  * @returns The updated binding, or undefined if not found
102906
103044
  */
102907
103045
  async update(id, updates) {
@@ -102951,14 +103089,6 @@ var BindingStore = class {
102951
103089
  try {
102952
103090
  const raw = await readFile4(this.filePath, "utf-8");
102953
103091
  const json = JSON.parse(raw);
102954
- if (json.bindings) {
102955
- for (const b3 of json.bindings) {
102956
- if ("agentDir" in b3 && !("projectPath" in b3)) {
102957
- b3.projectPath = b3.agentDir;
102958
- delete b3.agentDir;
102959
- }
102960
- }
102961
- }
102962
103092
  const parsed = BindingsFileSchema.parse(json);
102963
103093
  this.bindings.clear();
102964
103094
  for (const b3 of parsed.bindings) {
@@ -103163,15 +103293,37 @@ var BindingRouter = class _BindingRouter {
103163
103293
  );
103164
103294
  return;
103165
103295
  }
103296
+ if (binding.canReceive === false) {
103297
+ logger.debug(
103298
+ "[BindingRouter] Dropping inbound \u2014 canReceive=false for binding %s",
103299
+ binding.id
103300
+ );
103301
+ return;
103302
+ }
103303
+ const projectPath = this.deps.meshCore.getProjectPath(binding.agentId);
103304
+ if (!projectPath) {
103305
+ logger.warn(
103306
+ `BindingRouter: agent '${binding.agentId}' not found in mesh registry, skipping`
103307
+ );
103308
+ return;
103309
+ }
103166
103310
  const sessionId = await this.resolveSession(binding, chatId, envelope);
103167
- const enrichedPayload = binding.projectPath && envelope.payload && typeof envelope.payload === "object" ? { ...envelope.payload, cwd: binding.projectPath } : envelope.payload;
103311
+ const enrichedPayload = envelope.payload && typeof envelope.payload === "object" ? {
103312
+ ...envelope.payload,
103313
+ cwd: projectPath,
103314
+ __bindingPermissions: {
103315
+ canReply: binding.canReply,
103316
+ canInitiate: binding.canInitiate,
103317
+ permissionMode: binding.permissionMode ?? "acceptEdits"
103318
+ }
103319
+ } : envelope.payload;
103168
103320
  await this.deps.relayCore.publish(`relay.agent.${sessionId}`, enrichedPayload, {
103169
103321
  from: envelope.from,
103170
103322
  replyTo: envelope.replyTo,
103171
103323
  budget: envelope.budget
103172
103324
  });
103173
103325
  logger.info(
103174
- `BindingRouter: routed ${envelope.subject} \u2192 relay.agent.${sessionId} (binding=${binding.id}, projectPath=${binding.projectPath || "(empty)"})`
103326
+ `BindingRouter: routed ${envelope.subject} \u2192 relay.agent.${sessionId} (binding=${binding.id}, projectPath=${projectPath})`
103175
103327
  );
103176
103328
  } catch (err) {
103177
103329
  logger.error(
@@ -103240,13 +103392,20 @@ var BindingRouter = class _BindingRouter {
103240
103392
  logger.info(`BindingRouter: evicted ${excess} oldest session mapping(s)`);
103241
103393
  }
103242
103394
  async createNewSession(binding) {
103395
+ const projectPath = this.deps.meshCore.getProjectPath(binding.agentId);
103396
+ if (!projectPath) {
103397
+ throw new Error(`Agent '${binding.agentId}' not found in mesh registry`);
103398
+ }
103243
103399
  logger.debug("[BindingRouter] createNewSession", {
103244
103400
  bindingId: binding.id,
103245
103401
  adapterId: binding.adapterId,
103246
103402
  agentId: binding.agentId,
103247
- projectPath: binding.projectPath || "(empty)"
103403
+ projectPath
103248
103404
  });
103249
- const session = await this.deps.agentManager.createSession(binding.projectPath);
103405
+ const session = await this.deps.agentManager.createSession(
103406
+ projectPath,
103407
+ binding.permissionMode
103408
+ );
103250
103409
  return session.id;
103251
103410
  }
103252
103411
  /**
@@ -103635,16 +103794,21 @@ var AdapterManager = class {
103635
103794
  logger.info("[AdapterManager] AgentSessionStore initialized");
103636
103795
  const agentManager = this.deps.agentManager;
103637
103796
  const sessionCreator = {
103638
- async createSession(cwd) {
103797
+ async createSession(cwd, permissionMode) {
103639
103798
  const id = crypto.randomUUID();
103640
- agentManager.ensureSession(id, { permissionMode: "auto", cwd });
103799
+ agentManager.ensureSession(id, { permissionMode: permissionMode ?? "acceptEdits", cwd });
103641
103800
  return { id };
103642
103801
  }
103643
103802
  };
103803
+ if (!this.deps.meshCore) {
103804
+ logger.info("[AdapterManager] meshCore not provided, skipping binding subsystem");
103805
+ return;
103806
+ }
103644
103807
  this.bindingRouter = new BindingRouter({
103645
103808
  bindingStore: this.bindingStore,
103646
103809
  relayCore: this.deps.relayCore,
103647
103810
  agentManager: sessionCreator,
103811
+ meshCore: this.deps.meshCore,
103648
103812
  relayDir,
103649
103813
  resolveAdapterInstanceId: (platformType) => {
103650
103814
  const match = this.configs.find((c3) => c3.type === platformType && c3.enabled);
@@ -103859,11 +104023,15 @@ var AdapterManager = class {
103859
104023
  this.configs.splice(index2, 1);
103860
104024
  await saveAdapterConfig(this.configPath, this.configs);
103861
104025
  if (this.bindingStore) {
103862
- const knownAdapterIds = this.configs.map((c3) => c3.id);
103863
- const orphaned = this.bindingStore.getOrphaned(knownAdapterIds);
103864
- if (orphaned.length > 0) {
103865
- logger.warn(
103866
- `[AdapterManager] ${orphaned.length} orphaned binding(s) detected after removing adapter '${id}'. Binding IDs: ${orphaned.map((b3) => b3.id).join(", ")}. Delete them via DELETE /api/relay/bindings/:id to clean up.`
104026
+ const orphanBindings = this.bindingStore.getAll().filter((b3) => b3.adapterId === id);
104027
+ for (const binding of orphanBindings) {
104028
+ await this.bindingStore.delete(binding.id);
104029
+ }
104030
+ if (orphanBindings.length > 0) {
104031
+ logger.info(
104032
+ "[AdapterManager] Cleaned %d orphan binding(s) for removed adapter %s",
104033
+ orphanBindings.length,
104034
+ id
103867
104035
  );
103868
104036
  }
103869
104037
  }
@@ -104013,47 +104181,7 @@ var AdapterManager = class {
104013
104181
  }
104014
104182
  };
104015
104183
 
104016
- // ../../apps/server/src/services/relay/subject-resolver.ts
104017
- var SESSION_ID_PREVIEW_LENGTH = 7;
104018
- async function resolveSubjectLabel(subject, deps) {
104019
- const raw = subject;
104020
- if (subject === "relay.system.console") {
104021
- return { label: "System Console", raw };
104022
- }
104023
- if (subject.startsWith("relay.system.pulse.")) {
104024
- return { label: "Pulse Scheduler", raw };
104025
- }
104026
- if (subject.startsWith("relay.human.console.")) {
104027
- return { label: "You", raw };
104028
- }
104029
- if (subject.startsWith("relay.agent.") || subject.startsWith("relay.inbox.")) {
104030
- const prefix = subject.startsWith("relay.agent.") ? "relay.agent." : "relay.inbox.";
104031
- const sessionId = subject.slice(prefix.length);
104032
- const shortId = sessionId.slice(0, SESSION_ID_PREVIEW_LENGTH);
104033
- const fallback = { label: `Agent (${shortId})`, raw };
104034
- if (!deps.getSession) return fallback;
104035
- try {
104036
- const session = await deps.getSession(sessionId);
104037
- if (!session?.cwd || !deps.readManifest) return fallback;
104038
- const manifest = await deps.readManifest(session.cwd);
104039
- if (!manifest?.name) return fallback;
104040
- return { label: manifest.name, raw };
104041
- } catch {
104042
- return fallback;
104043
- }
104044
- }
104045
- return { label: subject, raw };
104046
- }
104047
- async function resolveSubjectLabels(subjects, deps) {
104048
- const unique = [...new Set(subjects)];
104049
- const results = await Promise.all(
104050
- unique.map(async (s3) => [s3, await resolveSubjectLabel(s3, deps)])
104051
- );
104052
- return new Map(results);
104053
- }
104054
-
104055
- // ../../apps/server/src/routes/relay.ts
104056
- var ALLOWED_PREFIXES = ["relay.human.console.", "relay.system.", "relay.signal."];
104184
+ // ../../apps/server/src/routes/relay-adapters.ts
104057
104185
  var ADAPTER_ERROR_STATUS = {
104058
104186
  DUPLICATE_ID: 409,
104059
104187
  UNKNOWN_TYPE: 400,
@@ -104061,15 +104189,219 @@ var ADAPTER_ERROR_STATUS = {
104061
104189
  NOT_FOUND: 404,
104062
104190
  REMOVE_BUILTIN_DENIED: 400
104063
104191
  };
104192
+ function sendAdapterError(res, err) {
104193
+ const status = ADAPTER_ERROR_STATUS[err.code] ?? 500;
104194
+ res.status(status).json({ error: err.message, code: err.code });
104195
+ }
104196
+ function createAdapterRouter(adapterManager2, traceStore2) {
104197
+ const router13 = Router15();
104198
+ router13.get("/adapters/catalog", (_req, res) => {
104199
+ try {
104200
+ res.json(adapterManager2.getCatalog());
104201
+ } catch (err) {
104202
+ const message = err instanceof Error ? err.message : "Failed to retrieve adapter catalog";
104203
+ res.status(500).json({ error: message });
104204
+ }
104205
+ });
104206
+ router13.post("/adapters/reload", async (_req, res) => {
104207
+ try {
104208
+ await adapterManager2.reload();
104209
+ return res.json({ ok: true });
104210
+ } catch (err) {
104211
+ const message = err instanceof Error ? err.message : "Reload failed";
104212
+ return res.status(500).json({ error: message });
104213
+ }
104214
+ });
104215
+ router13.get("/adapters", (_req, res) => res.json(adapterManager2.listAdapters()));
104216
+ router13.get("/adapters/:id", (req, res) => {
104217
+ const adapter = adapterManager2.getAdapter(req.params.id);
104218
+ if (!adapter) return res.status(404).json({ error: "Adapter not found" });
104219
+ return res.json(adapter);
104220
+ });
104221
+ router13.post("/adapters/test", async (req, res) => {
104222
+ const result2 = AdapterTestRequestSchema.safeParse(req.body);
104223
+ if (!result2.success) {
104224
+ return res.status(400).json({ error: "Validation failed", details: result2.error.flatten() });
104225
+ }
104226
+ try {
104227
+ const testResult = await adapterManager2.testConnection(result2.data.type, result2.data.config);
104228
+ return res.json(testResult);
104229
+ } catch (err) {
104230
+ const message = err instanceof Error ? err.message : "Test failed";
104231
+ return res.status(500).json({ error: message });
104232
+ }
104233
+ });
104234
+ router13.post("/adapters", async (req, res) => {
104235
+ const result2 = AdapterCreateRequestSchema.safeParse(req.body);
104236
+ if (!result2.success) {
104237
+ return res.status(400).json({ error: "Validation failed", details: result2.error.flatten() });
104238
+ }
104239
+ const { type, id, config, enabled, label: topLabel } = result2.data;
104240
+ const label = topLabel ?? (typeof config.label === "string" ? config.label : void 0);
104241
+ try {
104242
+ await adapterManager2.addAdapter(type, id, config, enabled, label);
104243
+ return res.status(201).json({ ok: true, id });
104244
+ } catch (err) {
104245
+ if (err instanceof AdapterError) return sendAdapterError(res, err);
104246
+ const message = err instanceof Error ? err.message : "Create failed";
104247
+ return res.status(500).json({ error: message });
104248
+ }
104249
+ });
104250
+ router13.delete("/adapters/:id", async (req, res) => {
104251
+ try {
104252
+ await adapterManager2.removeAdapter(req.params.id);
104253
+ return res.json({ ok: true });
104254
+ } catch (err) {
104255
+ if (err instanceof AdapterError) return sendAdapterError(res, err);
104256
+ const message = err instanceof Error ? err.message : "Remove failed";
104257
+ return res.status(500).json({ error: message });
104258
+ }
104259
+ });
104260
+ router13.patch("/adapters/:id/config", async (req, res) => {
104261
+ const result2 = AdapterConfigUpdateSchema.safeParse(req.body);
104262
+ if (!result2.success) {
104263
+ return res.status(400).json({ error: "Validation failed", details: result2.error.flatten() });
104264
+ }
104265
+ try {
104266
+ await adapterManager2.updateConfig(req.params.id, result2.data.config);
104267
+ return res.json({ ok: true });
104268
+ } catch (err) {
104269
+ if (err instanceof AdapterError) return sendAdapterError(res, err);
104270
+ const message = err instanceof Error ? err.message : "Update failed";
104271
+ return res.status(500).json({ error: message });
104272
+ }
104273
+ });
104274
+ router13.post("/adapters/:id/enable", async (req, res) => {
104275
+ try {
104276
+ await adapterManager2.enable(req.params.id);
104277
+ return res.json({ ok: true });
104278
+ } catch (err) {
104279
+ return res.status(400).json({ error: err instanceof Error ? err.message : "Enable failed" });
104280
+ }
104281
+ });
104282
+ router13.post("/adapters/:id/disable", async (req, res) => {
104283
+ try {
104284
+ await adapterManager2.disable(req.params.id);
104285
+ return res.json({ ok: true });
104286
+ } catch (err) {
104287
+ return res.status(400).json({ error: err instanceof Error ? err.message : "Disable failed" });
104288
+ }
104289
+ });
104290
+ router13.get("/adapters/:id/events", (_req, res) => {
104291
+ if (!traceStore2) return res.status(404).json({ error: "Tracing not available" });
104292
+ const { id } = _req.params;
104293
+ const limitParam = parseInt(_req.query.limit);
104294
+ const limit = Number.isNaN(limitParam) ? 100 : Math.min(Math.max(limitParam, 1), 500);
104295
+ const events = traceStore2.getAdapterEvents(id, limit);
104296
+ return res.json({ events });
104297
+ });
104298
+ router13.get("/adapters/:id/chats", (_req, res) => {
104299
+ if (!traceStore2) return res.status(404).json({ error: "Tracing not available" });
104300
+ const { id } = _req.params;
104301
+ const limitParam = parseInt(_req.query.limit);
104302
+ const limit = Number.isNaN(limitParam) ? 100 : Math.min(Math.max(limitParam, 1), 500);
104303
+ const chats = traceStore2.getObservedChats(id, limit);
104304
+ return res.json({ chats });
104305
+ });
104306
+ router13.get("/bindings", (_req, res) => {
104307
+ const bindingStore = adapterManager2.getBindingStore();
104308
+ if (!bindingStore) return res.status(503).json({ error: "Binding subsystem not available" });
104309
+ return res.json({ bindings: bindingStore.getAll() });
104310
+ });
104311
+ router13.get("/bindings/:id", (req, res) => {
104312
+ const bindingStore = adapterManager2.getBindingStore();
104313
+ if (!bindingStore) return res.status(503).json({ error: "Binding subsystem not available" });
104314
+ const binding = bindingStore.getById(req.params.id);
104315
+ if (!binding) return res.status(404).json({ error: "Binding not found" });
104316
+ return res.json({ binding });
104317
+ });
104318
+ router13.post("/bindings", async (req, res) => {
104319
+ const bindingStore = adapterManager2.getBindingStore();
104320
+ if (!bindingStore) return res.status(503).json({ error: "Binding subsystem not available" });
104321
+ const result2 = CreateBindingRequestSchema.safeParse(req.body);
104322
+ if (!result2.success) {
104323
+ return res.status(400).json({ error: "Validation failed", details: result2.error.flatten() });
104324
+ }
104325
+ try {
104326
+ const binding = await bindingStore.create(result2.data);
104327
+ return res.status(201).json({ binding });
104328
+ } catch (err) {
104329
+ const message = err instanceof Error ? err.message : "Create failed";
104330
+ return res.status(500).json({ error: message });
104331
+ }
104332
+ });
104333
+ router13.patch("/bindings/:id", async (req, res) => {
104334
+ const bindingStore = adapterManager2.getBindingStore();
104335
+ if (!bindingStore) {
104336
+ return res.status(503).json({ error: "Binding subsystem not available" });
104337
+ }
104338
+ const UpdateBindingSchema = z24.object({
104339
+ sessionStrategy: SessionStrategySchema.optional(),
104340
+ label: z24.string().optional(),
104341
+ chatId: z24.string().optional().nullable(),
104342
+ channelType: ChannelTypeSchema.optional().nullable(),
104343
+ canInitiate: z24.boolean().optional(),
104344
+ canReply: z24.boolean().optional(),
104345
+ canReceive: z24.boolean().optional(),
104346
+ permissionMode: PermissionModeSchema.optional()
104347
+ });
104348
+ const result2 = UpdateBindingSchema.safeParse(req.body);
104349
+ if (!result2.success) {
104350
+ return res.status(400).json({ error: "Validation failed", details: result2.error.flatten() });
104351
+ }
104352
+ const updates = {};
104353
+ for (const [key, value] of Object.entries(result2.data)) {
104354
+ if (value !== void 0) {
104355
+ updates[key] = value === null ? void 0 : value;
104356
+ }
104357
+ }
104358
+ const updated = await bindingStore.update(req.params.id, updates);
104359
+ if (!updated) {
104360
+ return res.status(404).json({ error: "Binding not found" });
104361
+ }
104362
+ return res.json({ binding: updated });
104363
+ });
104364
+ router13.delete("/bindings/:id", async (req, res) => {
104365
+ const bindingStore = adapterManager2.getBindingStore();
104366
+ if (!bindingStore) return res.status(503).json({ error: "Binding subsystem not available" });
104367
+ const deleted = await bindingStore.delete(req.params.id);
104368
+ if (!deleted) return res.status(404).json({ error: "Binding not found" });
104369
+ const bindingRouter = adapterManager2.getBindingRouter();
104370
+ if (bindingRouter) {
104371
+ const activeBindingIds = new Set(bindingStore.getAll().map((b3) => b3.id));
104372
+ await bindingRouter.cleanupOrphanedSessions(activeBindingIds);
104373
+ }
104374
+ return res.json({ ok: true });
104375
+ });
104376
+ router13.post("/webhooks/:adapterId", express2.raw({ type: "*/*" }), async (req, res) => {
104377
+ const adapterInfo = adapterManager2.getAdapter(req.params.adapterId);
104378
+ if (!adapterInfo || adapterInfo.config.type !== "webhook") {
104379
+ return res.status(404).json({ error: "Webhook adapter not found" });
104380
+ }
104381
+ const registry2 = adapterManager2.getRegistry();
104382
+ const adapter = registry2.get(req.params.adapterId);
104383
+ if (!adapter) return res.status(404).json({ error: "Adapter not running" });
104384
+ if (!("handleInbound" in adapter) || typeof adapter.handleInbound !== "function") {
104385
+ return res.status(500).json({ error: "Adapter does not support webhook ingestion" });
104386
+ }
104387
+ const webhookAdapter = adapter;
104388
+ const result2 = await webhookAdapter.handleInbound(
104389
+ req.body,
104390
+ req.headers
104391
+ );
104392
+ if (result2.ok) return res.status(200).json({ ok: true });
104393
+ return res.status(401).json({ error: result2.error });
104394
+ });
104395
+ return router13;
104396
+ }
104397
+
104398
+ // ../../apps/server/src/routes/relay.ts
104399
+ var ALLOWED_PREFIXES = ["relay.human.console.", "relay.system.", "relay.signal."];
104064
104400
  var PREVIEW_MAX_CHARS = 120;
104065
104401
  function validateSubscriptionPattern(pattern) {
104066
104402
  if (pattern === ">") return false;
104067
104403
  return ALLOWED_PREFIXES.some((prefix) => pattern.startsWith(prefix));
104068
104404
  }
104069
- function sendAdapterError(res, err) {
104070
- const status = ADAPTER_ERROR_STATUS[err.code] ?? 500;
104071
- res.status(status).json({ error: err.message, code: err.code });
104072
- }
104073
104405
  function buildConversations(messages, deadLetters, labelMap) {
104074
104406
  const deadLetterMap = new Map(deadLetters.map((dl) => [dl.messageId, dl]));
104075
104407
  const requests = [];
@@ -104130,7 +104462,7 @@ function buildConversations(messages, deadLetters, labelMap) {
104130
104462
  });
104131
104463
  }
104132
104464
  function createRelayRouter(relayCore2, adapterManager2, traceStore2) {
104133
- const router13 = Router15();
104465
+ const router13 = Router16();
104134
104466
  router13.post("/messages", async (req, res) => {
104135
104467
  const result2 = SendMessageRequestSchema2.safeParse(req.body);
104136
104468
  if (!result2.success) {
@@ -104225,6 +104557,44 @@ function createRelayRouter(relayCore2, adapterManager2, traceStore2) {
104225
104557
  );
104226
104558
  return res.json(deadLetters);
104227
104559
  });
104560
+ router13.get("/dead-letters/aggregated", async (_req, res) => {
104561
+ const deadLetters = await relayCore2.getDeadLetters();
104562
+ const groups = /* @__PURE__ */ new Map();
104563
+ for (const dl of deadLetters) {
104564
+ const source = dl.envelope?.from ?? "unknown";
104565
+ const key = `${source}::${dl.reason}`;
104566
+ const existing = groups.get(key);
104567
+ if (existing) {
104568
+ existing.count++;
104569
+ if (dl.failedAt < existing.firstSeen) existing.firstSeen = dl.failedAt;
104570
+ if (dl.failedAt > existing.lastSeen) existing.lastSeen = dl.failedAt;
104571
+ } else {
104572
+ groups.set(key, {
104573
+ source,
104574
+ reason: dl.reason,
104575
+ count: 1,
104576
+ firstSeen: dl.failedAt,
104577
+ lastSeen: dl.failedAt,
104578
+ sample: dl.envelope
104579
+ });
104580
+ }
104581
+ }
104582
+ return res.json({ groups: [...groups.values()] });
104583
+ });
104584
+ router13.delete("/dead-letters", async (req, res) => {
104585
+ const { source, reason } = req.body;
104586
+ if (!source || !reason) {
104587
+ return res.status(400).json({ error: "source and reason are required" });
104588
+ }
104589
+ const deadLetters = await relayCore2.getDeadLetters();
104590
+ const toRemove = deadLetters.filter(
104591
+ (dl) => (dl.envelope?.from ?? "unknown") === source && dl.reason === reason
104592
+ );
104593
+ for (const dl of toRemove) {
104594
+ await relayCore2.removeDeadLetter(dl.endpointHash, dl.messageId);
104595
+ }
104596
+ return res.json({ removed: toRemove.length });
104597
+ });
104228
104598
  router13.get("/metrics", (_req, res) => res.json(relayCore2.getMetrics()));
104229
104599
  router13.get("/messages/:id/trace", (_req, res) => {
104230
104600
  if (!traceStore2) return res.status(404).json({ error: "Tracing not available" });
@@ -104306,199 +104676,7 @@ function createRelayRouter(relayCore2, adapterManager2, traceStore2) {
104306
104676
  });
104307
104677
  });
104308
104678
  if (adapterManager2) {
104309
- router13.get("/adapters/catalog", (_req, res) => {
104310
- try {
104311
- res.json(adapterManager2.getCatalog());
104312
- } catch (err) {
104313
- const message = err instanceof Error ? err.message : "Failed to retrieve adapter catalog";
104314
- res.status(500).json({ error: message });
104315
- }
104316
- });
104317
- router13.post("/adapters/reload", async (_req, res) => {
104318
- try {
104319
- await adapterManager2.reload();
104320
- return res.json({ ok: true });
104321
- } catch (err) {
104322
- const message = err instanceof Error ? err.message : "Reload failed";
104323
- return res.status(500).json({ error: message });
104324
- }
104325
- });
104326
- router13.get("/adapters", (_req, res) => res.json(adapterManager2.listAdapters()));
104327
- router13.get("/adapters/:id", (req, res) => {
104328
- const adapter = adapterManager2.getAdapter(req.params.id);
104329
- if (!adapter) return res.status(404).json({ error: "Adapter not found" });
104330
- return res.json(adapter);
104331
- });
104332
- router13.post("/adapters/test", async (req, res) => {
104333
- const result2 = AdapterTestRequestSchema.safeParse(req.body);
104334
- if (!result2.success) {
104335
- return res.status(400).json({ error: "Validation failed", details: result2.error.flatten() });
104336
- }
104337
- try {
104338
- const testResult = await adapterManager2.testConnection(result2.data.type, result2.data.config);
104339
- return res.json(testResult);
104340
- } catch (err) {
104341
- const message = err instanceof Error ? err.message : "Test failed";
104342
- return res.status(500).json({ error: message });
104343
- }
104344
- });
104345
- router13.post("/adapters", async (req, res) => {
104346
- const result2 = AdapterCreateRequestSchema.safeParse(req.body);
104347
- if (!result2.success) {
104348
- return res.status(400).json({ error: "Validation failed", details: result2.error.flatten() });
104349
- }
104350
- const { type, id, config, enabled, label: topLabel } = result2.data;
104351
- const label = topLabel ?? (typeof config.label === "string" ? config.label : void 0);
104352
- try {
104353
- await adapterManager2.addAdapter(type, id, config, enabled, label);
104354
- return res.status(201).json({ ok: true, id });
104355
- } catch (err) {
104356
- if (err instanceof AdapterError) return sendAdapterError(res, err);
104357
- const message = err instanceof Error ? err.message : "Create failed";
104358
- return res.status(500).json({ error: message });
104359
- }
104360
- });
104361
- router13.delete("/adapters/:id", async (req, res) => {
104362
- try {
104363
- await adapterManager2.removeAdapter(req.params.id);
104364
- return res.json({ ok: true });
104365
- } catch (err) {
104366
- if (err instanceof AdapterError) return sendAdapterError(res, err);
104367
- const message = err instanceof Error ? err.message : "Remove failed";
104368
- return res.status(500).json({ error: message });
104369
- }
104370
- });
104371
- router13.patch("/adapters/:id/config", async (req, res) => {
104372
- const result2 = AdapterConfigUpdateSchema.safeParse(req.body);
104373
- if (!result2.success) {
104374
- return res.status(400).json({ error: "Validation failed", details: result2.error.flatten() });
104375
- }
104376
- try {
104377
- await adapterManager2.updateConfig(req.params.id, result2.data.config);
104378
- return res.json({ ok: true });
104379
- } catch (err) {
104380
- if (err instanceof AdapterError) return sendAdapterError(res, err);
104381
- const message = err instanceof Error ? err.message : "Update failed";
104382
- return res.status(500).json({ error: message });
104383
- }
104384
- });
104385
- router13.post("/adapters/:id/enable", async (req, res) => {
104386
- try {
104387
- await adapterManager2.enable(req.params.id);
104388
- return res.json({ ok: true });
104389
- } catch (err) {
104390
- return res.status(400).json({ error: err instanceof Error ? err.message : "Enable failed" });
104391
- }
104392
- });
104393
- router13.post("/adapters/:id/disable", async (req, res) => {
104394
- try {
104395
- await adapterManager2.disable(req.params.id);
104396
- return res.json({ ok: true });
104397
- } catch (err) {
104398
- return res.status(400).json({ error: err instanceof Error ? err.message : "Disable failed" });
104399
- }
104400
- });
104401
- router13.get("/adapters/:id/events", (_req, res) => {
104402
- if (!traceStore2) return res.status(404).json({ error: "Tracing not available" });
104403
- const { id } = _req.params;
104404
- const limitParam = parseInt(_req.query.limit);
104405
- const limit = Number.isNaN(limitParam) ? 100 : Math.min(Math.max(limitParam, 1), 500);
104406
- const events = traceStore2.getAdapterEvents(id, limit);
104407
- return res.json({ events });
104408
- });
104409
- router13.get("/adapters/:id/chats", (_req, res) => {
104410
- if (!traceStore2) return res.status(404).json({ error: "Tracing not available" });
104411
- const { id } = _req.params;
104412
- const limitParam = parseInt(_req.query.limit);
104413
- const limit = Number.isNaN(limitParam) ? 100 : Math.min(Math.max(limitParam, 1), 500);
104414
- const chats = traceStore2.getObservedChats(id, limit);
104415
- return res.json({ chats });
104416
- });
104417
- router13.get("/bindings", (_req, res) => {
104418
- const bindingStore = adapterManager2.getBindingStore();
104419
- if (!bindingStore) return res.status(503).json({ error: "Binding subsystem not available" });
104420
- return res.json({ bindings: bindingStore.getAll() });
104421
- });
104422
- router13.get("/bindings/:id", (req, res) => {
104423
- const bindingStore = adapterManager2.getBindingStore();
104424
- if (!bindingStore) return res.status(503).json({ error: "Binding subsystem not available" });
104425
- const binding = bindingStore.getById(req.params.id);
104426
- if (!binding) return res.status(404).json({ error: "Binding not found" });
104427
- return res.json({ binding });
104428
- });
104429
- router13.post("/bindings", async (req, res) => {
104430
- const bindingStore = adapterManager2.getBindingStore();
104431
- if (!bindingStore) return res.status(503).json({ error: "Binding subsystem not available" });
104432
- const result2 = CreateBindingRequestSchema.safeParse(req.body);
104433
- if (!result2.success) {
104434
- return res.status(400).json({ error: "Validation failed", details: result2.error.flatten() });
104435
- }
104436
- try {
104437
- const binding = await bindingStore.create(result2.data);
104438
- return res.status(201).json({ binding });
104439
- } catch (err) {
104440
- const message = err instanceof Error ? err.message : "Create failed";
104441
- return res.status(500).json({ error: message });
104442
- }
104443
- });
104444
- router13.patch("/bindings/:id", async (req, res) => {
104445
- const bindingStore = adapterManager2.getBindingStore();
104446
- if (!bindingStore) {
104447
- return res.status(503).json({ error: "Binding subsystem not available" });
104448
- }
104449
- const UpdateBindingSchema = z24.object({
104450
- sessionStrategy: SessionStrategySchema.optional(),
104451
- label: z24.string().optional(),
104452
- chatId: z24.string().optional().nullable(),
104453
- channelType: ChannelTypeSchema.optional().nullable()
104454
- });
104455
- const result2 = UpdateBindingSchema.safeParse(req.body);
104456
- if (!result2.success) {
104457
- return res.status(400).json({ error: "Validation failed", details: result2.error.flatten() });
104458
- }
104459
- const updates = {};
104460
- for (const [key, value] of Object.entries(result2.data)) {
104461
- if (value !== void 0) {
104462
- updates[key] = value === null ? void 0 : value;
104463
- }
104464
- }
104465
- const updated = await bindingStore.update(req.params.id, updates);
104466
- if (!updated) {
104467
- return res.status(404).json({ error: "Binding not found" });
104468
- }
104469
- return res.json({ binding: updated });
104470
- });
104471
- router13.delete("/bindings/:id", async (req, res) => {
104472
- const bindingStore = adapterManager2.getBindingStore();
104473
- if (!bindingStore) return res.status(503).json({ error: "Binding subsystem not available" });
104474
- const deleted = await bindingStore.delete(req.params.id);
104475
- if (!deleted) return res.status(404).json({ error: "Binding not found" });
104476
- const bindingRouter = adapterManager2.getBindingRouter();
104477
- if (bindingRouter) {
104478
- const activeBindingIds = new Set(bindingStore.getAll().map((b3) => b3.id));
104479
- await bindingRouter.cleanupOrphanedSessions(activeBindingIds);
104480
- }
104481
- return res.json({ ok: true });
104482
- });
104483
- router13.post("/webhooks/:adapterId", express2.raw({ type: "*/*" }), async (req, res) => {
104484
- const adapterInfo = adapterManager2.getAdapter(req.params.adapterId);
104485
- if (!adapterInfo || adapterInfo.config.type !== "webhook") {
104486
- return res.status(404).json({ error: "Webhook adapter not found" });
104487
- }
104488
- const registry2 = adapterManager2.getRegistry();
104489
- const adapter = registry2.get(req.params.adapterId);
104490
- if (!adapter) return res.status(404).json({ error: "Adapter not running" });
104491
- if (!("handleInbound" in adapter) || typeof adapter.handleInbound !== "function") {
104492
- return res.status(500).json({ error: "Adapter does not support webhook ingestion" });
104493
- }
104494
- const webhookAdapter = adapter;
104495
- const result2 = await webhookAdapter.handleInbound(
104496
- req.body,
104497
- req.headers
104498
- );
104499
- if (result2.ok) return res.status(200).json({ ok: true });
104500
- return res.status(401).json({ error: result2.error });
104501
- });
104679
+ router13.use("/", createAdapterRouter(adapterManager2, traceStore2));
104502
104680
  }
104503
104681
  return router13;
104504
104682
  }
@@ -104583,24 +104761,31 @@ var TraceStore = class {
104583
104761
  getTrace(traceId) {
104584
104762
  return this.db.select().from(relayTraces).where(eq(relayTraces.traceId, traceId)).all();
104585
104763
  }
104586
- /** Compute live delivery metrics from Drizzle aggregate queries. */
104587
- getMetrics() {
104764
+ /**
104765
+ * Compute live delivery metrics from Drizzle aggregate queries.
104766
+ *
104767
+ * @param options - Optional filter parameters
104768
+ * @param options.since - ISO 8601 timestamp; only spans with sentAt >= since are counted.
104769
+ * Defaults to 24 hours ago.
104770
+ */
104771
+ getMetrics(options) {
104772
+ const sinceIso = options?.since ?? new Date(Date.now() - 864e5).toISOString();
104588
104773
  const [counts] = this.db.select({
104589
104774
  total: count(),
104590
104775
  delivered: count(sql`CASE WHEN ${relayTraces.status} = 'delivered' THEN 1 END`),
104591
104776
  failed: count(sql`CASE WHEN ${relayTraces.status} = 'failed' THEN 1 END`),
104592
104777
  deadLettered: count(sql`CASE WHEN ${relayTraces.status} = 'timeout' THEN 1 END`)
104593
- }).from(relayTraces).all();
104778
+ }).from(relayTraces).where(sql`${relayTraces.sentAt} >= ${sinceIso}`).all();
104594
104779
  const [latency] = this.db.select({
104595
104780
  avgMs: sql`AVG(
104596
104781
  CASE WHEN ${relayTraces.deliveredAt} IS NOT NULL AND ${relayTraces.sentAt} IS NOT NULL
104597
104782
  THEN (strftime('%s', ${relayTraces.deliveredAt}) - strftime('%s', ${relayTraces.sentAt})) * 1000
104598
104783
  END
104599
104784
  )`
104600
- }).from(relayTraces).all();
104785
+ }).from(relayTraces).where(sql`${relayTraces.sentAt} >= ${sinceIso}`).all();
104601
104786
  const [endpointCount] = this.db.select({
104602
104787
  cnt: sql`COUNT(DISTINCT ${relayTraces.subject})`
104603
- }).from(relayTraces).all();
104788
+ }).from(relayTraces).where(sql`${relayTraces.sentAt} >= ${sinceIso}`).all();
104604
104789
  return {
104605
104790
  totalMessages: counts.total,
104606
104791
  deliveredCount: counts.delivered,
@@ -106043,7 +106228,11 @@ var MeshCore = class {
106043
106228
  logger;
106044
106229
  reconcileTimer = null;
106045
106230
  onUnregisterCallbacks = [];
106046
- /** @param options - Configuration options */
106231
+ /**
106232
+ * Create the Mesh coordination core.
106233
+ *
106234
+ * @param options - Configuration options
106235
+ */
106047
106236
  constructor(options) {
106048
106237
  const registry2 = new AgentRegistry(options.db);
106049
106238
  const denialList = new DenialList(options.db);
@@ -106206,7 +106395,7 @@ var MeshCore = class {
106206
106395
  };
106207
106396
 
106208
106397
  // ../../apps/server/src/routes/mesh.ts
106209
- import { Router as Router16 } from "express";
106398
+ import { Router as Router17 } from "express";
106210
106399
  function enrichTopology(topology, deps) {
106211
106400
  const scheduleCounts = /* @__PURE__ */ new Map();
106212
106401
  if (deps.pulseStore) {
@@ -106291,7 +106480,7 @@ function enrichAgent(agent, namespace, deps, scheduleCounts, relayEndpoints) {
106291
106480
  function createMeshRouter(deps) {
106292
106481
  const resolvedDeps = "meshCore" in deps ? deps : { meshCore: deps };
106293
106482
  const meshCore2 = resolvedDeps.meshCore;
106294
- const router13 = Router16();
106483
+ const router13 = Router17();
106295
106484
  router13.post("/discover", async (req, res) => {
106296
106485
  const result2 = DiscoverRequestSchema.safeParse(req.body);
106297
106486
  if (!result2.success) {
@@ -106474,10 +106663,10 @@ function createMeshRouter(deps) {
106474
106663
  }
106475
106664
 
106476
106665
  // ../../apps/server/src/routes/agents.ts
106477
- import { Router as Router17 } from "express";
106666
+ import { Router as Router18 } from "express";
106478
106667
  import path31 from "path";
106479
106668
  function createAgentsRouter(meshCore2) {
106480
- const router13 = Router17();
106669
+ const router13 = Router18();
106481
106670
  router13.get("/current", async (req, res) => {
106482
106671
  try {
106483
106672
  const agentPath = req.query.path;
@@ -106594,7 +106783,7 @@ function createAgentsRouter(meshCore2) {
106594
106783
  }
106595
106784
 
106596
106785
  // ../../apps/server/src/routes/discovery.ts
106597
- import { Router as Router18 } from "express";
106786
+ import { Router as Router19 } from "express";
106598
106787
  import { z as z25 } from "zod";
106599
106788
  var ScanRequestSchema = z25.object({
106600
106789
  root: z25.string().optional(),
@@ -106603,7 +106792,7 @@ var ScanRequestSchema = z25.object({
106603
106792
  timeout: z25.number().int().min(1e3).max(12e4).optional()
106604
106793
  });
106605
106794
  function createDiscoveryRouter(meshCore2) {
106606
- const router13 = Router18();
106795
+ const router13 = Router19();
106607
106796
  router13.post("/scan", async (req, res) => {
106608
106797
  const data = parseBody(ScanRequestSchema, req.body, res);
106609
106798
  if (!data) return;
@@ -106652,7 +106841,7 @@ function createDiscoveryRouter(meshCore2) {
106652
106841
  }
106653
106842
 
106654
106843
  // ../../apps/server/src/routes/admin.ts
106655
- import { Router as Router19 } from "express";
106844
+ import { Router as Router20 } from "express";
106656
106845
  import { spawn } from "child_process";
106657
106846
  import fs19 from "fs/promises";
106658
106847
 
@@ -107556,12 +107745,13 @@ var rate_limit_default = rateLimit;
107556
107745
 
107557
107746
  // ../../apps/server/src/routes/admin.ts
107558
107747
  function triggerRestart() {
107559
- if (process.env.NODE_ENV === "development") {
107748
+ if (env.NODE_ENV === "development") {
107560
107749
  process.exit(0);
107561
107750
  } else {
107562
107751
  const child = spawn(process.argv[0], process.argv.slice(1), {
107563
107752
  detached: true,
107564
107753
  stdio: "inherit",
107754
+ // eslint-disable-next-line no-restricted-syntax -- passing full env to spawned child process
107565
107755
  env: process.env
107566
107756
  });
107567
107757
  child.unref();
@@ -107569,7 +107759,7 @@ function triggerRestart() {
107569
107759
  }
107570
107760
  }
107571
107761
  function createAdminRouter(deps) {
107572
- const router13 = Router19();
107762
+ const router13 = Router20();
107573
107763
  const adminLimiter = rate_limit_default({
107574
107764
  windowMs: 5 * 60 * 1e3,
107575
107765
  // 5 minutes
@@ -113344,7 +113534,6 @@ function createExternalMcpServer(deps) {
113344
113534
  {
113345
113535
  adapterId: z27.string().describe("ID of the adapter to bind"),
113346
113536
  agentId: z27.string().describe("Agent ID to route messages to"),
113347
- projectPath: z27.string().describe("Filesystem path to the agent working directory"),
113348
113537
  sessionStrategy: z27.string().optional().describe("Session strategy: per-chat, per-user, or stateless (default per-chat)"),
113349
113538
  chatId: z27.string().optional().describe("Optional chat ID for targeted routing"),
113350
113539
  channelType: z27.string().optional().describe("Optional channel type filter: dm, group, channel, or thread"),
@@ -113448,7 +113637,7 @@ function createExternalMcpServer(deps) {
113448
113637
  }
113449
113638
 
113450
113639
  // ../../apps/server/src/routes/mcp.ts
113451
- import { Router as Router20 } from "express";
113640
+ import { Router as Router21 } from "express";
113452
113641
 
113453
113642
  // ../../node_modules/.pnpm/@hono+node-server@1.19.11_hono@4.12.5/node_modules/@hono/node-server/dist/index.mjs
113454
113643
  import { Http2ServerRequest as Http2ServerRequest2 } from "http2";
@@ -114707,7 +114896,7 @@ var StreamableHTTPServerTransport = class {
114707
114896
 
114708
114897
  // ../../apps/server/src/routes/mcp.ts
114709
114898
  function createMcpRouter(serverFactory) {
114710
- const router13 = Router20();
114899
+ const router13 = Router21();
114711
114900
  router13.post("/", async (req, res) => {
114712
114901
  try {
114713
114902
  const server = serverFactory();