@agent-team-foundation/first-tree-hub 0.8.0 → 0.8.2

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,8 +1,11 @@
1
- import { C as setConfigValue, S as serverConfigSchema, _ as loadAgents, d as DEFAULT_HOME_DIR$1, f as agentConfigSchema, g as initConfig, i as loadCredentials, l as DEFAULT_CONFIG_DIR, m as collectMissingPrompts, n as ensureFreshAccessToken, o as resolveServerUrl, p as clientConfigSchema, s as saveAgentConfig, u as DEFAULT_DATA_DIR$1, x as resolveConfigReadonly } from "./bootstrap-CRDR6NwE.mjs";
2
- import { $ as updateAgentRuntimeConfigSchema, A as createMemberSchema, B as notificationQuerySchema, C as agentTypeSchema$1, D as createAdapterMappingSchema, E as createAdapterConfigSchema, F as inboxPollQuerySchema, G as sendMessageSchema, H as refreshTokenSchema, I as isRedactedEnvValue, J as sessionEventMessageSchema, K as sendToAgentSchema, L as linkTaskChatSchema, M as createTaskSchema, N as delegateFeishuUserSchema, O as createAgentSchema, P as dryRunAgentRuntimeConfigSchema, Q as updateAdapterConfigSchema, R as loginSchema, S as agentRuntimeConfigPayloadSchema$1, T as connectTokenExchangeSchema, U as runtimeStateMessageSchema, V as paginationQuerySchema, W as selfServiceFeishuBotSchema, X as sessionStateMessageSchema, Y as sessionEventSchema$1, Z as taskListQuerySchema, _ as addParticipantSchema, a as AGENT_SELECTOR_HEADER$1, at as wsAuthFrameSchema, b as agentBindRequestSchema, c as AGENT_TYPES, d as SYSTEM_CONFIG_DEFAULTS, et as updateAgentSchema, f as TASK_CREATOR_TYPES, g as WS_AUTH_FRAME_TIMEOUT_MS, h as TASK_TERMINAL_STATUSES, i as AGENT_BIND_REJECT_REASONS, it as updateTaskStatusSchema, j as createOrganizationSchema, k as createChatSchema, l as AGENT_VISIBILITY, m as TASK_STATUSES, nt as updateOrganizationSchema, o as AGENT_SOURCES, p as TASK_HEALTH_SIGNALS, q as sessionCompletionMessageSchema, rt as updateSystemConfigSchema, s as AGENT_STATUSES, tt as updateMemberSchema, u as DEFAULT_AGENT_RUNTIME_CONFIG_PAYLOAD, v as adminCreateTaskSchema, w as clientRegisterSchema, x as agentPinnedMessageSchema$1, y as adminUpdateTaskSchema, z as messageSourceSchema$1 } from "./feishu-D9JkMZnU.mjs";
1
+ import { m as __toESM } from "./esm-CYu4tXXn.mjs";
2
+ import { C as setConfigValue, S as serverConfigSchema, _ as loadAgents, d as DEFAULT_HOME_DIR$1, f as agentConfigSchema, g as initConfig, i as loadCredentials, l as DEFAULT_CONFIG_DIR, m as collectMissingPrompts, n as ensureFreshAccessToken, o as resolveServerUrl, p as clientConfigSchema, s as saveAgentConfig, u as DEFAULT_DATA_DIR$1, x as resolveConfigReadonly } from "./bootstrap-99vUYmLs.mjs";
3
+ import { _ as withSpan, a as endWsConnectionSpan, b as require_pino, c as messageAttrs, d as rootLogger$1, g as startWsConnectionSpan, i as currentTraceId, n as applyLoggerConfig, o as getFastifyOtelPlugin, p as setWsConnectionAttrs, r as createLogger, t as adapterAttrs, u as observabilityPlugin, v as withWsMessageSpan, y as FIRST_TREE_HUB_ATTR } from "./observability-CJzDFY_G-CmvgUuzc.mjs";
4
+ import { $ as updateAgentRuntimeConfigSchema, A as createMemberSchema, B as notificationQuerySchema, C as agentTypeSchema$1, D as createAdapterMappingSchema, E as createAdapterConfigSchema, F as inboxPollQuerySchema, G as sendMessageSchema, H as refreshTokenSchema, I as isRedactedEnvValue, J as sessionEventMessageSchema, K as sendToAgentSchema, L as linkTaskChatSchema, M as createTaskSchema, N as delegateFeishuUserSchema, O as createAgentSchema, P as dryRunAgentRuntimeConfigSchema, Q as updateAdapterConfigSchema, R as loginSchema, S as agentRuntimeConfigPayloadSchema$1, T as connectTokenExchangeSchema, U as runtimeStateMessageSchema, V as paginationQuerySchema, W as selfServiceFeishuBotSchema, X as sessionStateMessageSchema, Y as sessionEventSchema$1, Z as taskListQuerySchema, _ as addParticipantSchema, a as AGENT_SELECTOR_HEADER$1, at as wsAuthFrameSchema, b as agentBindRequestSchema, c as AGENT_TYPES, d as SYSTEM_CONFIG_DEFAULTS, et as updateAgentSchema, f as TASK_CREATOR_TYPES, g as WS_AUTH_FRAME_TIMEOUT_MS, h as TASK_TERMINAL_STATUSES, i as AGENT_BIND_REJECT_REASONS, it as updateTaskStatusSchema, j as createOrganizationSchema, k as createChatSchema, l as AGENT_VISIBILITY, m as TASK_STATUSES, nt as updateOrganizationSchema, o as AGENT_SOURCES, p as TASK_HEALTH_SIGNALS, q as sessionCompletionMessageSchema, rt as updateSystemConfigSchema, s as AGENT_STATUSES, tt as updateMemberSchema, u as DEFAULT_AGENT_RUNTIME_CONFIG_PAYLOAD, v as adminCreateTaskSchema, w as clientRegisterSchema, x as agentPinnedMessageSchema$1, y as adminUpdateTaskSchema, z as messageSourceSchema$1 } from "./feishu-BOISS0DK.mjs";
3
5
  import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
4
6
  import { dirname, isAbsolute, join, resolve } from "node:path";
5
7
  import { ZodError, z } from "zod";
8
+ import { Writable } from "node:stream";
6
9
  import { stringify } from "yaml";
7
10
  import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
8
11
  import { homedir, hostname, platform, userInfo } from "node:os";
@@ -25,6 +28,136 @@ import Fastify from "fastify";
25
28
  import { bigserial, boolean, index, integer, jsonb, pgTable, primaryKey, serial, text, timestamp, unique, uniqueIndex } from "drizzle-orm/pg-core";
26
29
  import { SignJWT, jwtVerify } from "jose";
27
30
  import { Client, EventDispatcher, LoggerLevel, WSClient } from "@larksuiteoapi/node-sdk";
31
+ //#region ../client/dist/observability-BUvHY6T-.mjs
32
+ var import_pino = /* @__PURE__ */ __toESM(require_pino(), 1);
33
+ /**
34
+ * Logger core — format / level primitives shared between server and client.
35
+ *
36
+ * This module intentionally has no dependency on `pino` so it can live in
37
+ * `@agent-team-foundation/first-tree-hub-shared`. Consumers construct their
38
+ * own pino instance and pass the output stream built here.
39
+ */
40
+ const LOG_LEVELS = [
41
+ "trace",
42
+ "debug",
43
+ "info",
44
+ "warn",
45
+ "error",
46
+ "fatal"
47
+ ];
48
+ const LOG_FORMATS = ["pretty", "json"];
49
+ const logLevelSchema = z.enum(LOG_LEVELS);
50
+ const logFormatSchema = z.enum(LOG_FORMATS);
51
+ /**
52
+ * Parse an env-var / config string into a LogLevel. Unknown values fall back
53
+ * to `info` so the process never fails to boot on a typo — the caller is
54
+ * responsible for emitting a warning when `fellBack` is true.
55
+ */
56
+ function parseLogLevel(raw) {
57
+ if (!raw) return {
58
+ level: "info",
59
+ fellBack: false
60
+ };
61
+ const parsed = logLevelSchema.safeParse(raw);
62
+ if (parsed.success) return {
63
+ level: parsed.data,
64
+ fellBack: false
65
+ };
66
+ return {
67
+ level: "info",
68
+ fellBack: true
69
+ };
70
+ }
71
+ const LEVEL_LABELS = {
72
+ 10: "TRACE",
73
+ 20: "DEBUG",
74
+ 30: "INFO",
75
+ 40: "WARN",
76
+ 50: "ERROR",
77
+ 60: "FATAL"
78
+ };
79
+ const LEVEL_COLORS = {
80
+ 10: "\x1B[90m",
81
+ 20: "\x1B[36m",
82
+ 30: "\x1B[32m",
83
+ 40: "\x1B[33m",
84
+ 50: "\x1B[31m",
85
+ 60: "\x1B[35m"
86
+ };
87
+ const RESET = "\x1B[0m";
88
+ const DIM = "\x1B[2m";
89
+ const SKIP_KEYS = new Set([
90
+ "level",
91
+ "time",
92
+ "msg",
93
+ "module",
94
+ "pid",
95
+ "hostname",
96
+ "v"
97
+ ]);
98
+ function formatPrettyEntry(json) {
99
+ const obj = JSON.parse(json);
100
+ const level = obj.level;
101
+ const label = LEVEL_LABELS[level] ?? "???";
102
+ const color = LEVEL_COLORS[level] ?? "";
103
+ const time = obj.time ?? (/* @__PURE__ */ new Date()).toISOString();
104
+ const module = obj.module ? `[${String(obj.module)}] ` : "";
105
+ const msg = obj.msg ?? "";
106
+ const extras = [];
107
+ let errStack = "";
108
+ for (const [k, v] of Object.entries(obj)) {
109
+ if (SKIP_KEYS.has(k)) continue;
110
+ if (k === "err" && v && typeof v === "object") {
111
+ const e = v;
112
+ if (e.message) extras.push(`err.message=${String(e.message)}`);
113
+ if (typeof e.stack === "string") errStack = `\n${DIM}${e.stack}${RESET}`;
114
+ } else extras.push(`${k}=${typeof v === "string" ? v : JSON.stringify(v)}`);
115
+ }
116
+ const extraStr = extras.length > 0 ? ` ${DIM}${extras.join(" ")}${RESET}` : "";
117
+ return `${DIM}${time}${RESET} ${color}${label.padEnd(5)}${RESET} ${module}${msg}${extraStr}${errStack}\n`;
118
+ }
119
+ function formatLocalTime() {
120
+ const d = /* @__PURE__ */ new Date();
121
+ return `${d.toLocaleDateString("sv-SE")} ${d.toLocaleTimeString("en-GB", { hour12: false })}`;
122
+ }
123
+ function createLoggerOutputStream(options) {
124
+ return new Writable({ write(chunk, _, callback) {
125
+ const text = chunk.toString();
126
+ try {
127
+ if (options.getFormat() === "pretty") process.stdout.write(formatPrettyEntry(text));
128
+ else process.stdout.write(text);
129
+ if (options.onJsonEntry) try {
130
+ const obj = JSON.parse(text);
131
+ options.onJsonEntry(obj);
132
+ } catch {}
133
+ } catch {
134
+ process.stdout.write(text);
135
+ }
136
+ callback();
137
+ } });
138
+ }
139
+ /**
140
+ * Client-side logger. Same pretty / NDJSON formats as the server logger, but
141
+ * intentionally lightweight — the client is deployed to agent user machines,
142
+ * so we skip tracing, context propagation, and error sinks.
143
+ */
144
+ const initialLevel = parseLogLevel(process.env.FIRST_TREE_HUB_LOG_LEVEL);
145
+ let _format = process.env.NODE_ENV === "production" ? "json" : "pretty";
146
+ let _level = initialLevel.level;
147
+ function applyClientLoggerConfig(options = {}) {
148
+ if (options.level) {
149
+ _level = options.level;
150
+ rootLogger.level = options.level;
151
+ }
152
+ if (options.format) _format = options.format;
153
+ }
154
+ const outputStream = createLoggerOutputStream({ getFormat: () => _format });
155
+ const rootLogger = (0, import_pino.default)({
156
+ level: _level,
157
+ timestamp: () => `,"time":"${formatLocalTime()}"`
158
+ }, outputStream);
159
+ if (initialLevel.fellBack) rootLogger.warn({ envValue: process.env.FIRST_TREE_HUB_LOG_LEVEL }, "invalid FIRST_TREE_HUB_LOG_LEVEL; falling back to info");
160
+ //#endregion
28
161
  //#region ../client/dist/index.mjs
29
162
  const adapterPlatformSchema = z.enum([
30
163
  "feishu",
@@ -946,6 +1079,7 @@ var ClientConnection = class extends EventEmitter {
946
1079
  this.serverUrl = config.serverUrl.replace(/\/+$/, "");
947
1080
  this.sdkVersion = config.sdkVersion;
948
1081
  this.getAccessToken = config.getAccessToken;
1082
+ this.on("error", () => {});
949
1083
  }
950
1084
  get isConnected() {
951
1085
  return this.ws !== null && this.ws.readyState === WebSocket.OPEN && this.registered;
@@ -1349,12 +1483,7 @@ defineConfig({
1349
1483
  auto: "client-id",
1350
1484
  env: "FIRST_TREE_HUB_CLIENT_ID"
1351
1485
  }) },
1352
- logLevel: field(z.enum([
1353
- "debug",
1354
- "info",
1355
- "warn",
1356
- "error"
1357
- ]).default("info"), { env: "FIRST_TREE_HUB_LOG_LEVEL" })
1486
+ logLevel: field(logLevelSchema.default("info"), { env: "FIRST_TREE_HUB_LOG_LEVEL" })
1358
1487
  });
1359
1488
  const DEFAULT_HOME_DIR = process.env.FIRST_TREE_HUB_HOME ?? join(homedir(), ".first-tree-hub");
1360
1489
  join(DEFAULT_HOME_DIR, "config");
@@ -1421,7 +1550,29 @@ defineConfig({
1421
1550
  secret: true
1422
1551
  }),
1423
1552
  hubPublicUrl: field(z.string(), { env: "FIRST_TREE_HUB_PUBLIC_URL" })
1424
- })
1553
+ }),
1554
+ observability: {
1555
+ logging: {
1556
+ level: field(logLevelSchema.default("info"), { env: "FIRST_TREE_HUB_LOG_LEVEL" }),
1557
+ format: field(logFormatSchema.default(process.env.NODE_ENV === "production" ? "json" : "pretty")),
1558
+ bridgeToSpanLevel: field(z.enum([
1559
+ "error",
1560
+ "warn",
1561
+ "off"
1562
+ ]).default("error"))
1563
+ },
1564
+ tracing: optional({
1565
+ endpoint: field(z.string(), { env: "FIRST_TREE_HUB_OTEL_ENDPOINT" }),
1566
+ headers: field(z.string().default(""), {
1567
+ env: "FIRST_TREE_HUB_OTEL_HEADERS",
1568
+ secret: true
1569
+ }),
1570
+ exporter: field(z.enum(["otlp-http", "otlp-grpc"]).default("otlp-http")),
1571
+ serviceName: field(z.string().default("first-tree-hub")),
1572
+ environment: field(z.string().default("development"), { env: "FIRST_TREE_HUB_OTEL_ENVIRONMENT" }),
1573
+ sampleRate: field(z.number().min(0).max(1).default(1))
1574
+ })
1575
+ }
1425
1576
  });
1426
1577
  join(DEFAULT_DATA_DIR, "context-tree");
1427
1578
  /**
@@ -1458,6 +1609,75 @@ function bootstrapWorkspace(options) {
1458
1609
  }
1459
1610
  writeFileSync(join(agentDir, "tools.md"), generateToolsDoc(), "utf-8");
1460
1611
  }
1612
+ function defaultInstallExec(command, args, options) {
1613
+ execFileSync(command, args, {
1614
+ cwd: options.cwd,
1615
+ stdio: "pipe",
1616
+ timeout: options.timeout,
1617
+ encoding: "utf-8"
1618
+ });
1619
+ }
1620
+ /**
1621
+ * Install the first-tree skill and FIRST-TREE-SOURCE-INTEGRATION block into
1622
+ * the workspace by shelling out to `first-tree tree integrate`.
1623
+ *
1624
+ * Resolution order for the CLI binary:
1625
+ * 1. `first-tree` on PATH — preferred for runtime images that pre-install it.
1626
+ * 2. `npx -y first-tree@latest` — fallback that downloads on first run.
1627
+ *
1628
+ * Graceful degradation: returns false on failure and logs. The session still
1629
+ * starts; the agent just doesn't have the first-tree skill wired up.
1630
+ */
1631
+ function installFirstTreeIntegration(options) {
1632
+ const { workspacePath, contextTreePath, workspaceId, treeRepoUrl, log } = options;
1633
+ const exec = options.exec ?? defaultInstallExec;
1634
+ const integrateArgs = [
1635
+ "tree",
1636
+ "integrate",
1637
+ "--source-path",
1638
+ workspacePath,
1639
+ "--tree-path",
1640
+ contextTreePath,
1641
+ "--mode",
1642
+ "workspace-root",
1643
+ "--workspace-id",
1644
+ workspaceId,
1645
+ ...treeRepoUrl ? ["--tree-url", treeRepoUrl] : []
1646
+ ];
1647
+ const attempts = [{
1648
+ command: "first-tree",
1649
+ args: integrateArgs,
1650
+ label: "first-tree (PATH)"
1651
+ }, {
1652
+ command: "npx",
1653
+ args: [
1654
+ "-y",
1655
+ "first-tree@latest",
1656
+ ...integrateArgs
1657
+ ],
1658
+ label: "npx first-tree@latest"
1659
+ }];
1660
+ for (let index = 0; index < attempts.length; index += 1) {
1661
+ const attempt = attempts[index];
1662
+ if (!attempt) continue;
1663
+ try {
1664
+ exec(attempt.command, attempt.args, {
1665
+ cwd: workspacePath,
1666
+ timeout: 12e4
1667
+ });
1668
+ log(`First-tree integration installed via ${attempt.label}`);
1669
+ return true;
1670
+ } catch (err) {
1671
+ const msg = err instanceof Error ? err.message : String(err);
1672
+ const binaryMissing = /ENOENT|not found|command not found/i.test(msg);
1673
+ const isLastAttempt = index === attempts.length - 1;
1674
+ if (binaryMissing && !isLastAttempt) continue;
1675
+ log(`First-tree integration skipped (${attempt.label}): ${msg.slice(0, 200)}`);
1676
+ return false;
1677
+ }
1678
+ }
1679
+ return false;
1680
+ }
1461
1681
  function generateToolsDoc() {
1462
1682
  return `# Agent Hub SDK
1463
1683
 
@@ -2472,6 +2692,12 @@ const createClaudeCodeHandler = (config) => {
2472
2692
  chatId: sessionCtx.chatId
2473
2693
  });
2474
2694
  generateClaudeMd(workspace, sessionCtx.agent, contextTreePath);
2695
+ if (contextTreePath) installFirstTreeIntegration({
2696
+ workspacePath: workspace,
2697
+ contextTreePath,
2698
+ workspaceId: sessionCtx.chatId,
2699
+ log: (msg) => sessionCtx.log(msg)
2700
+ });
2475
2701
  }
2476
2702
  const handler = {
2477
2703
  async start(message, sessionCtx) {
@@ -3483,6 +3709,9 @@ var ClientRuntime = class {
3483
3709
  this.connection.on("auth:expired", () => {
3484
3710
  process.stderr.write(" ⚠️ Access token expired — reconnecting after refresh...\n");
3485
3711
  });
3712
+ this.connection.on("error", (err) => {
3713
+ process.stderr.write(` \u26A0\uFE0F Client connection error: ${err.message}\n`);
3714
+ });
3486
3715
  this.connection.on("agent:pinned", (message) => {
3487
3716
  this.handleAgentPinned(message);
3488
3717
  });
@@ -4292,7 +4521,7 @@ async function onboardCreate(args) {
4292
4521
  }
4293
4522
  const runtimeAgent = args.type === "human" ? args.assistant : args.id;
4294
4523
  if (args.feishuBotAppId && args.feishuBotAppSecret) {
4295
- const { bindFeishuBot } = await import("./feishu-D9JkMZnU.mjs").then((n) => n.r);
4524
+ const { bindFeishuBot } = await import("./feishu-BOISS0DK.mjs").then((n) => n.r);
4296
4525
  const targetAgentUuid = args.type === "human" ? assistantUuid : primary.uuid;
4297
4526
  if (!targetAgentUuid) process.stderr.write(`Warning: Cannot bind Feishu bot — no runtime agent available for "${args.id}".\n`);
4298
4527
  else {
@@ -4433,7 +4662,7 @@ function setNestedByDot(obj, dotPath, value) {
4433
4662
  if (lastKey !== void 0) current[lastKey] = value;
4434
4663
  }
4435
4664
  //#endregion
4436
- //#region ../server/dist/app-TMhTLXuz.mjs
4665
+ //#region ../server/dist/app-DJEePmWL.mjs
4437
4666
  var __defProp = Object.defineProperty;
4438
4667
  var __exportAll = (all, no_symbols) => {
4439
4668
  let target = {};
@@ -5196,6 +5425,7 @@ async function deleteAdapterConfig(db, id) {
5196
5425
  const [row] = await db.delete(adapterConfigs).where(eq(adapterConfigs.id, id)).returning();
5197
5426
  if (!row) throw new NotFoundError(`Adapter config "${id}" not found`);
5198
5427
  }
5428
+ const log$4 = createLogger("AdminAdapters");
5199
5429
  function parseId(raw) {
5200
5430
  const id = Number(raw);
5201
5431
  if (!Number.isInteger(id) || id <= 0) throw new BadRequestError(`Invalid adapter ID: "${raw}"`);
@@ -5214,7 +5444,7 @@ async function adminAdapterRoutes(app) {
5214
5444
  const scope = memberScope(request);
5215
5445
  await assertCanManage(app.db, scope, body.agentId);
5216
5446
  const config = await createAdapterConfig(app.db, body, app.config.secrets.encryptionKey);
5217
- app.adapterManager.reload().catch((err) => app.log.error(err, "Adapter reload failed after create"));
5447
+ app.adapterManager.reload().catch((err) => log$4.error({ err }, "adapter reload failed after create"));
5218
5448
  app.notifier.notifyConfigChange("adapter_configs").catch(() => {});
5219
5449
  return reply.status(201).send({
5220
5450
  ...config,
@@ -5238,7 +5468,7 @@ async function adminAdapterRoutes(app) {
5238
5468
  const existing = await getAdapterConfig(app.db, id);
5239
5469
  await assertCanManage(app.db, scope, existing.agentId);
5240
5470
  const config = await updateAdapterConfig(app.db, id, body, app.config.secrets.encryptionKey);
5241
- app.adapterManager.reload().catch((err) => app.log.error(err, "Adapter reload failed after update"));
5471
+ app.adapterManager.reload().catch((err) => log$4.error({ err }, "adapter reload failed after update"));
5242
5472
  app.notifier.notifyConfigChange("adapter_configs").catch(() => {});
5243
5473
  return {
5244
5474
  ...config,
@@ -5252,7 +5482,7 @@ async function adminAdapterRoutes(app) {
5252
5482
  const existing = await getAdapterConfig(app.db, id);
5253
5483
  await assertCanManage(app.db, scope, existing.agentId);
5254
5484
  await deleteAdapterConfig(app.db, id);
5255
- app.adapterManager.reload().catch((err) => app.log.error(err, "Adapter reload failed after delete"));
5485
+ app.adapterManager.reload().catch((err) => log$4.error({ err }, "adapter reload failed after delete"));
5256
5486
  app.notifier.notifyConfigChange("adapter_configs").catch(() => {});
5257
5487
  return reply.status(204).send();
5258
5488
  });
@@ -6231,6 +6461,13 @@ const inboxEntries = pgTable("inbox_entries", {
6231
6461
  ackedAt: timestamp("acked_at", { withTimezone: true })
6232
6462
  }, (table) => [unique("uq_inbox_delivery").on(table.inboxId, table.messageId, table.chatId), index("idx_inbox_pending").on(table.inboxId, table.createdAt)]);
6233
6463
  async function sendMessage(db, chatId, senderId, data) {
6464
+ return withSpan("inbox.enqueue", messageAttrs({
6465
+ chatId,
6466
+ senderAgentId: senderId,
6467
+ source: data.source ?? void 0
6468
+ }), () => sendMessageInner(db, chatId, senderId, data));
6469
+ }
6470
+ async function sendMessageInner(db, chatId, senderId, data) {
6234
6471
  return db.transaction(async (tx) => {
6235
6472
  const messageId = randomUUID();
6236
6473
  const [msg] = await tx.insert(messages).values({
@@ -8236,7 +8473,11 @@ function adminWsRoutes(notifier, jwtSecret) {
8236
8473
  });
8237
8474
  });
8238
8475
  return async (app) => {
8239
- app.get("/admin", { websocket: true }, async (socket, request) => {
8476
+ app.get("/admin", {
8477
+ websocket: true,
8478
+ config: { otel: false }
8479
+ }, async (socket, request) => {
8480
+ startWsConnectionSpan(socket, { remoteIp: request.ip });
8240
8481
  const token = request.query.token;
8241
8482
  if (!token) {
8242
8483
  socket.send(JSON.stringify({
@@ -8244,6 +8485,7 @@ function adminWsRoutes(notifier, jwtSecret) {
8244
8485
  message: "Missing token query parameter"
8245
8486
  }));
8246
8487
  socket.close(4001, "Missing token");
8488
+ endWsConnectionSpan(socket, 4001);
8247
8489
  return;
8248
8490
  }
8249
8491
  let organizationId;
@@ -8256,6 +8498,7 @@ function adminWsRoutes(notifier, jwtSecret) {
8256
8498
  message: "Invalid token type"
8257
8499
  }));
8258
8500
  socket.close(4001, "Invalid token");
8501
+ endWsConnectionSpan(socket, 4001);
8259
8502
  return;
8260
8503
  }
8261
8504
  organizationId = payload.organizationId;
@@ -8266,8 +8509,13 @@ function adminWsRoutes(notifier, jwtSecret) {
8266
8509
  message: "Invalid or expired token"
8267
8510
  }));
8268
8511
  socket.close(4001, "Auth failed");
8512
+ endWsConnectionSpan(socket, 4001);
8269
8513
  return;
8270
8514
  }
8515
+ setWsConnectionAttrs(socket, {
8516
+ organizationId,
8517
+ memberId
8518
+ });
8271
8519
  const visibleAgentIds = await loadVisibleAgentIds(app.db, organizationId, memberId);
8272
8520
  adminSockets.set(socket, {
8273
8521
  organizationId,
@@ -8275,8 +8523,9 @@ function adminWsRoutes(notifier, jwtSecret) {
8275
8523
  visibleAgentIds
8276
8524
  });
8277
8525
  socket.send(JSON.stringify({ type: "admin:connected" }));
8278
- socket.on("close", () => {
8526
+ socket.on("close", (code) => {
8279
8527
  adminSockets.delete(socket);
8528
+ endWsConnectionSpan(socket, code);
8280
8529
  });
8281
8530
  });
8282
8531
  };
@@ -8351,6 +8600,7 @@ async function agentConfigRoutes(app) {
8351
8600
  return await app.configService.getDecrypted(identity.uuid);
8352
8601
  });
8353
8602
  }
8603
+ const log$3 = createLogger("AgentFeishuBot");
8354
8604
  async function agentFeishuBotRoutes(app) {
8355
8605
  /**
8356
8606
  * PUT /agent/me/feishu-bot
@@ -8378,7 +8628,7 @@ async function agentFeishuBotRoutes(app) {
8378
8628
  },
8379
8629
  status: "active"
8380
8630
  }, app.config.secrets.encryptionKey);
8381
- app.adapterManager.reload().catch((err) => app.log.error(err, "Adapter reload failed after self-service bind"));
8631
+ app.adapterManager.reload().catch((err) => log$3.error({ err }, "adapter reload failed after self-service bind"));
8382
8632
  app.notifier.notifyConfigChange("adapter_configs").catch(() => {});
8383
8633
  return reply.status(current ? 200 : 201).send({
8384
8634
  ...config,
@@ -8395,7 +8645,7 @@ async function agentFeishuBotRoutes(app) {
8395
8645
  const current = (await listAdapterConfigs(app.db)).find((c) => c.agentId === identity.uuid && c.platform === "feishu");
8396
8646
  if (!current) return reply.status(204).send();
8397
8647
  await deleteAdapterConfig(app.db, current.id);
8398
- app.adapterManager.reload().catch((err) => app.log.error(err, "Adapter reload failed after self-service unbind"));
8648
+ app.adapterManager.reload().catch((err) => log$3.error({ err }, "adapter reload failed after self-service unbind"));
8399
8649
  app.notifier.notifyConfigChange("adapter_configs").catch(() => {});
8400
8650
  return reply.status(204).send();
8401
8651
  });
@@ -8482,6 +8732,12 @@ async function resolveAgentId(db, source) {
8482
8732
  const DEFAULT_INBOX_TIMEOUT_SECONDS = 300;
8483
8733
  const DEFAULT_MAX_RETRY_COUNT = 3;
8484
8734
  async function pollInbox(db, inboxId, limit) {
8735
+ return withSpan("inbox.deliver", {
8736
+ "inbox.id": inboxId,
8737
+ "inbox.poll.limit": limit
8738
+ }, () => pollInboxInner(db, inboxId, limit));
8739
+ }
8740
+ async function pollInboxInner(db, inboxId, limit) {
8485
8741
  return await db.transaction(async (tx) => {
8486
8742
  const claimed = await tx.execute(sql`
8487
8743
  UPDATE inbox_entries
@@ -8536,12 +8792,17 @@ async function pollInbox(db, inboxId, limit) {
8536
8792
  });
8537
8793
  }
8538
8794
  async function ackEntry$2(db, entryId, inboxId) {
8539
- const [entry] = await db.update(inboxEntries).set({
8540
- status: "acked",
8541
- ackedAt: /* @__PURE__ */ new Date()
8542
- }).where(and(eq(inboxEntries.id, entryId), eq(inboxEntries.inboxId, inboxId), eq(inboxEntries.status, "delivered"))).returning();
8543
- if (!entry) throw new NotFoundError("Inbox entry not found or not in delivered status");
8544
- return entry;
8795
+ return withSpan("inbox.ack", {
8796
+ [FIRST_TREE_HUB_ATTR.INBOX_ENTRY_ID]: String(entryId),
8797
+ "inbox.id": inboxId
8798
+ }, async () => {
8799
+ const [entry] = await db.update(inboxEntries).set({
8800
+ status: "acked",
8801
+ ackedAt: /* @__PURE__ */ new Date()
8802
+ }).where(and(eq(inboxEntries.id, entryId), eq(inboxEntries.inboxId, inboxId), eq(inboxEntries.status, "delivered"))).returning();
8803
+ if (!entry) throw new NotFoundError("Inbox entry not found or not in delivered status");
8804
+ return entry;
8805
+ });
8545
8806
  }
8546
8807
  async function renewEntry(db, entryId, inboxId) {
8547
8808
  const [entry] = await db.update(inboxEntries).set({ deliveredAt: /* @__PURE__ */ new Date() }).where(and(eq(inboxEntries.id, entryId), eq(inboxEntries.inboxId, inboxId), eq(inboxEntries.status, "delivered"))).returning();
@@ -8604,6 +8865,7 @@ async function agentMeRoutes(app) {
8604
8865
  };
8605
8866
  });
8606
8867
  }
8868
+ const log$2 = createLogger("AgentMessages");
8607
8869
  const editMessageSchema = z.object({
8608
8870
  format: z.string().optional(),
8609
8871
  content: z.unknown()
@@ -8625,10 +8887,10 @@ async function agentMessageRoutes(app) {
8625
8887
  await assertParticipant(app.db, request.params.chatId, identity.uuid);
8626
8888
  const body = editMessageSchema.parse(request.body);
8627
8889
  const msg = await editMessage(app.db, request.params.chatId, request.params.messageId, identity.uuid, body);
8628
- app.adapterManager.editOutboundMessage(msg.id, msg.format, msg.content).catch((err) => app.log.error({
8890
+ app.adapterManager.editOutboundMessage(msg.id, msg.format, msg.content).catch((err) => log$2.error({
8629
8891
  err,
8630
8892
  messageId: msg.id
8631
- }, "Failed to edit outbound message"));
8893
+ }, "failed to edit outbound message"));
8632
8894
  return {
8633
8895
  ...msg,
8634
8896
  createdAt: msg.createdAt.toISOString()
@@ -8787,7 +9049,11 @@ function sendRejected(socket, ref, reason) {
8787
9049
  function clientWsRoutes(notifier, instanceId) {
8788
9050
  return async (app) => {
8789
9051
  const jwtSecretBytes = new TextEncoder().encode(app.config.secrets.jwtSecret);
8790
- app.get("/client", { websocket: true }, async (socket) => {
9052
+ app.get("/client", {
9053
+ websocket: true,
9054
+ config: { otel: false }
9055
+ }, async (socket) => {
9056
+ startWsConnectionSpan(socket);
8791
9057
  let session = null;
8792
9058
  let clientId = null;
8793
9059
  let authExpiryTimer = null;
@@ -8886,6 +9152,10 @@ function clientWsRoutes(notifier, instanceId) {
8886
9152
  organizationId: member.organizationId,
8887
9153
  role: member.role
8888
9154
  };
9155
+ setWsConnectionAttrs(socket, {
9156
+ "organization.id": member.organizationId,
9157
+ "member.id": member.id
9158
+ });
8889
9159
  clearTimeout(authTimeout);
8890
9160
  scheduleAuthExpiry(claims.exp);
8891
9161
  socket.send(JSON.stringify({ type: "auth:ok" }));
@@ -8899,223 +9169,228 @@ function clientWsRoutes(notifier, instanceId) {
8899
9169
  }
8900
9170
  return;
8901
9171
  }
8902
- try {
8903
- if (type === "client:register") {
8904
- const data = clientRegisterSchema.parse(msg);
8905
- try {
8906
- await registerClient(app.db, {
8907
- clientId: data.clientId,
8908
- userId: session.userId,
8909
- instanceId,
8910
- hostname: data.hostname,
8911
- os: data.os,
8912
- sdkVersion: data.sdkVersion
8913
- });
8914
- } catch (err) {
8915
- const message = err instanceof Error ? err.message : "client register failed";
9172
+ await withWsMessageSpan(socket, type, ref !== void 0 ? { "ws.message.ref": String(ref) } : {}, async () => {
9173
+ if (!session) return;
9174
+ try {
9175
+ if (type === "client:register") {
9176
+ const data = clientRegisterSchema.parse(msg);
9177
+ try {
9178
+ await registerClient(app.db, {
9179
+ clientId: data.clientId,
9180
+ userId: session.userId,
9181
+ instanceId,
9182
+ hostname: data.hostname,
9183
+ os: data.os,
9184
+ sdkVersion: data.sdkVersion
9185
+ });
9186
+ } catch (err) {
9187
+ const message = err instanceof Error ? err.message : "client register failed";
9188
+ socket.send(JSON.stringify({
9189
+ type: "client:register:rejected",
9190
+ message
9191
+ }));
9192
+ socket.close(4403, "client register rejected");
9193
+ return;
9194
+ }
9195
+ clientId = data.clientId;
9196
+ setWsConnectionAttrs(socket, { "client.id": data.clientId });
9197
+ setClientConnection(data.clientId, socket);
8916
9198
  socket.send(JSON.stringify({
8917
- type: "client:register:rejected",
8918
- message
9199
+ type: "client:registered",
9200
+ clientId: data.clientId
8919
9201
  }));
8920
- socket.close(4403, "client register rejected");
8921
- return;
8922
- }
8923
- clientId = data.clientId;
8924
- setClientConnection(data.clientId, socket);
8925
- socket.send(JSON.stringify({
8926
- type: "client:registered",
8927
- clientId: data.clientId
8928
- }));
8929
- try {
8930
- const pinned = await listActiveAgentsPinnedToClient(app.db, data.clientId);
8931
- for (const agent of pinned) {
8932
- const parsed = agentPinnedMessageSchema$1.safeParse({
8933
- type: "agent:pinned",
8934
- agentId: agent.uuid,
8935
- name: agent.name,
8936
- displayName: agent.displayName,
8937
- agentType: agent.type
8938
- });
8939
- if (!parsed.success) {
8940
- app.log.warn({
8941
- err: parsed.error.flatten(),
9202
+ try {
9203
+ const pinned = await listActiveAgentsPinnedToClient(app.db, data.clientId);
9204
+ for (const agent of pinned) {
9205
+ const parsed = agentPinnedMessageSchema$1.safeParse({
9206
+ type: "agent:pinned",
8942
9207
  agentId: agent.uuid,
8943
- clientId: data.clientId
8944
- }, "agent:pinned backfill frame failed schema validation — skipping");
8945
- continue;
9208
+ name: agent.name,
9209
+ displayName: agent.displayName,
9210
+ agentType: agent.type
9211
+ });
9212
+ if (!parsed.success) {
9213
+ app.log.warn({
9214
+ err: parsed.error.flatten(),
9215
+ agentId: agent.uuid,
9216
+ clientId: data.clientId
9217
+ }, "agent:pinned backfill frame failed schema validation — skipping");
9218
+ continue;
9219
+ }
9220
+ socket.send(JSON.stringify(parsed.data));
8946
9221
  }
8947
- socket.send(JSON.stringify(parsed.data));
9222
+ } catch (err) {
9223
+ app.log.error({
9224
+ err,
9225
+ clientId: data.clientId
9226
+ }, "agent:pinned backfill on client:register failed — client may need manual `agent add`");
8948
9227
  }
8949
- } catch (err) {
8950
- app.log.error({
8951
- err,
8952
- clientId: data.clientId
8953
- }, "agent:pinned backfill on client:register failed — client may need manual `agent add`");
8954
- }
8955
- } else if (type === "agent:bind") {
8956
- if (!clientId) {
8957
- socket.send(JSON.stringify({
8958
- type: "error",
8959
- ref,
8960
- message: "Must register client first"
8961
- }));
8962
- return;
8963
- }
8964
- const bindRequest = agentBindRequestSchema.parse(msg);
8965
- const [agent] = await app.db.select({
8966
- id: agents.uuid,
8967
- displayName: agents.displayName,
8968
- type: agents.type,
8969
- organizationId: agents.organizationId,
8970
- inboxId: agents.inboxId,
8971
- status: agents.status,
8972
- clientId: agents.clientId,
8973
- clientUserId: clients.userId,
8974
- managerUserId: members.userId
8975
- }).from(agents).leftJoin(clients, eq(agents.clientId, clients.id)).leftJoin(members, eq(agents.managerId, members.id)).where(and(eq(agents.uuid, bindRequest.agentId))).limit(1);
8976
- if (!agent) {
8977
- sendRejected(socket, ref, AGENT_BIND_REJECT_REASONS.UNKNOWN_AGENT);
8978
- return;
8979
- }
8980
- if (agent.organizationId !== session.organizationId) {
8981
- sendRejected(socket, ref, AGENT_BIND_REJECT_REASONS.WRONG_ORG);
8982
- return;
8983
- }
8984
- if (agent.status !== "active") {
8985
- sendRejected(socket, ref, AGENT_BIND_REJECT_REASONS.AGENT_SUSPENDED);
8986
- return;
8987
- }
8988
- if (agent.clientId === null) {
8989
- if (!agent.managerUserId || agent.managerUserId !== session.userId) {
8990
- sendRejected(socket, ref, AGENT_BIND_REJECT_REASONS.NOT_OWNED);
9228
+ } else if (type === "agent:bind") {
9229
+ if (!clientId) {
9230
+ socket.send(JSON.stringify({
9231
+ type: "error",
9232
+ ref,
9233
+ message: "Must register client first"
9234
+ }));
8991
9235
  return;
8992
9236
  }
8993
- if ((await app.db.update(agents).set({
8994
- clientId,
8995
- updatedAt: /* @__PURE__ */ new Date()
8996
- }).where(and(eq(agents.uuid, agent.id), isNull(agents.clientId))).returning({ uuid: agents.uuid })).length === 0) {
9237
+ const bindRequest = agentBindRequestSchema.parse(msg);
9238
+ const [agent] = await app.db.select({
9239
+ id: agents.uuid,
9240
+ displayName: agents.displayName,
9241
+ type: agents.type,
9242
+ organizationId: agents.organizationId,
9243
+ inboxId: agents.inboxId,
9244
+ status: agents.status,
9245
+ clientId: agents.clientId,
9246
+ clientUserId: clients.userId,
9247
+ managerUserId: members.userId
9248
+ }).from(agents).leftJoin(clients, eq(agents.clientId, clients.id)).leftJoin(members, eq(agents.managerId, members.id)).where(and(eq(agents.uuid, bindRequest.agentId))).limit(1);
9249
+ if (!agent) {
9250
+ sendRejected(socket, ref, AGENT_BIND_REJECT_REASONS.UNKNOWN_AGENT);
9251
+ return;
9252
+ }
9253
+ if (agent.organizationId !== session.organizationId) {
9254
+ sendRejected(socket, ref, AGENT_BIND_REJECT_REASONS.WRONG_ORG);
9255
+ return;
9256
+ }
9257
+ if (agent.status !== "active") {
9258
+ sendRejected(socket, ref, AGENT_BIND_REJECT_REASONS.AGENT_SUSPENDED);
9259
+ return;
9260
+ }
9261
+ if (agent.clientId === null) {
9262
+ if (!agent.managerUserId || agent.managerUserId !== session.userId) {
9263
+ sendRejected(socket, ref, AGENT_BIND_REJECT_REASONS.NOT_OWNED);
9264
+ return;
9265
+ }
9266
+ if ((await app.db.update(agents).set({
9267
+ clientId,
9268
+ updatedAt: /* @__PURE__ */ new Date()
9269
+ }).where(and(eq(agents.uuid, agent.id), isNull(agents.clientId))).returning({ uuid: agents.uuid })).length === 0) {
9270
+ sendRejected(socket, ref, AGENT_BIND_REJECT_REASONS.WRONG_CLIENT);
9271
+ return;
9272
+ }
9273
+ } else if (agent.clientId !== clientId) {
8997
9274
  sendRejected(socket, ref, AGENT_BIND_REJECT_REASONS.WRONG_CLIENT);
8998
9275
  return;
9276
+ } else if (!agent.clientUserId || agent.clientUserId !== session.userId) {
9277
+ sendRejected(socket, ref, AGENT_BIND_REJECT_REASONS.NOT_OWNED);
9278
+ return;
8999
9279
  }
9000
- } else if (agent.clientId !== clientId) {
9001
- sendRejected(socket, ref, AGENT_BIND_REJECT_REASONS.WRONG_CLIENT);
9002
- return;
9003
- } else if (!agent.clientUserId || agent.clientUserId !== session.userId) {
9004
- sendRejected(socket, ref, AGENT_BIND_REJECT_REASONS.NOT_OWNED);
9005
- return;
9006
- }
9007
- await bindAgent(app.db, agent.id, {
9008
- clientId,
9009
- instanceId,
9010
- runtimeType: bindRequest.runtimeType,
9011
- runtimeVersion: bindRequest.runtimeVersion
9012
- });
9013
- bindAgentToClient(clientId, agent.id);
9014
- boundAgents.set(agent.id, {
9015
- agentId: agent.id,
9016
- inboxId: agent.inboxId
9017
- });
9018
- notifier.subscribe(agent.inboxId, socket);
9019
- socket.send(JSON.stringify({
9020
- type: "agent:bound",
9021
- ref,
9022
- agentId: agent.id,
9023
- displayName: agent.displayName,
9024
- agentType: agent.type
9025
- }));
9026
- } else if (type === "agent:unbind") {
9027
- const agentId = parsed.data.agentId;
9028
- if (!agentId || !boundAgents.has(agentId)) {
9029
- socket.send(JSON.stringify({
9030
- type: "error",
9031
- message: "Agent not bound"
9032
- }));
9033
- return;
9034
- }
9035
- const info = boundAgents.get(agentId);
9036
- if (info) notifier.unsubscribe(info.inboxId, socket);
9037
- await unbindAgent(app.db, agentId);
9038
- unbindAgentFromClient(agentId);
9039
- boundAgents.delete(agentId);
9040
- socket.send(JSON.stringify({
9041
- type: "agent:unbound",
9042
- agentId
9043
- }));
9044
- } else if (type === "session:state") {
9045
- const agentId = parsed.data.agentId;
9046
- if (!agentId || !boundAgents.has(agentId)) {
9047
- socket.send(JSON.stringify({
9048
- type: "error",
9049
- message: "Agent not bound"
9050
- }));
9051
- return;
9052
- }
9053
- const payload = sessionStateMessageSchema.parse(msg);
9054
- if (payload.state === "evicted") chainSessionOp(agentId, payload.chatId, () => clearEvents(app.db, agentId, payload.chatId).catch(() => {}));
9055
- await upsertSessionState(app.db, agentId, payload.chatId, payload.state, session.organizationId, notifier);
9056
- } else if (type === "runtime:state") {
9057
- const agentId = parsed.data.agentId;
9058
- if (!agentId || !boundAgents.has(agentId)) {
9059
- socket.send(JSON.stringify({
9060
- type: "error",
9061
- message: "Agent not bound"
9062
- }));
9063
- return;
9064
- }
9065
- const payload = runtimeStateMessageSchema.parse(msg);
9066
- await setRuntimeState(app.db, agentId, payload.runtimeState, {
9067
- organizationId: session.organizationId,
9068
- notifier
9069
- });
9070
- if (payload.runtimeState === "error" && shouldNotify(agentId, "agent_error")) notifyAgentEvent(app.db, agentId, "agent_error", "high", `Agent ${agentId} entered error state`).catch(() => {});
9071
- else if (payload.runtimeState === "blocked" && shouldNotify(agentId, "agent_blocked")) notifyAgentEvent(app.db, agentId, "agent_blocked", "medium", `Agent ${agentId} is blocked`).catch(() => {});
9072
- } else if (type === "session:event") {
9073
- const agentId = parsed.data.agentId;
9074
- if (!agentId || !boundAgents.has(agentId)) {
9280
+ await bindAgent(app.db, agent.id, {
9281
+ clientId,
9282
+ instanceId,
9283
+ runtimeType: bindRequest.runtimeType,
9284
+ runtimeVersion: bindRequest.runtimeVersion
9285
+ });
9286
+ bindAgentToClient(clientId, agent.id);
9287
+ boundAgents.set(agent.id, {
9288
+ agentId: agent.id,
9289
+ inboxId: agent.inboxId
9290
+ });
9291
+ notifier.subscribe(agent.inboxId, socket);
9075
9292
  socket.send(JSON.stringify({
9076
- type: "error",
9077
- message: "Agent not bound"
9293
+ type: "agent:bound",
9294
+ ref,
9295
+ agentId: agent.id,
9296
+ displayName: agent.displayName,
9297
+ agentType: agent.type
9078
9298
  }));
9079
- return;
9080
- }
9081
- const payload = sessionEventMessageSchema.parse(msg);
9082
- chainSessionOp(agentId, payload.chatId, async () => {
9083
- try {
9084
- await appendEvent(app.db, agentId, payload.chatId, payload.event);
9085
- } catch (err) {
9299
+ } else if (type === "agent:unbind") {
9300
+ const agentId = parsed.data.agentId;
9301
+ if (!agentId || !boundAgents.has(agentId)) {
9086
9302
  socket.send(JSON.stringify({
9087
9303
  type: "error",
9088
- message: `Failed to persist session event: ${err instanceof Error ? err.message : String(err)}`
9304
+ message: "Agent not bound"
9089
9305
  }));
9306
+ return;
9090
9307
  }
9091
- });
9092
- } else if (type === "session:completion") {
9093
- const agentId = parsed.data.agentId;
9094
- if (!agentId || !boundAgents.has(agentId)) {
9308
+ const info = boundAgents.get(agentId);
9309
+ if (info) notifier.unsubscribe(info.inboxId, socket);
9310
+ await unbindAgent(app.db, agentId);
9311
+ unbindAgentFromClient(agentId);
9312
+ boundAgents.delete(agentId);
9095
9313
  socket.send(JSON.stringify({
9096
- type: "error",
9097
- message: "Agent not bound"
9314
+ type: "agent:unbound",
9315
+ agentId
9098
9316
  }));
9099
- return;
9100
- }
9101
- const payload = sessionCompletionMessageSchema.parse(msg);
9102
- if (shouldNotify(agentId, `session_completed:${payload.chatId}`)) notifyAgentEvent(app.db, agentId, "session_completed", "low", `Agent ${agentId} completed a task`, payload.chatId).catch(() => {});
9103
- } else if (type === "heartbeat") {
9104
- if (clientId) {
9105
- await heartbeatClient(app.db, clientId);
9106
- await Promise.all([...boundAgents.keys()].map((id) => touchAgent(app.db, id)));
9317
+ } else if (type === "session:state") {
9318
+ const agentId = parsed.data.agentId;
9319
+ if (!agentId || !boundAgents.has(agentId)) {
9320
+ socket.send(JSON.stringify({
9321
+ type: "error",
9322
+ message: "Agent not bound"
9323
+ }));
9324
+ return;
9325
+ }
9326
+ const payload = sessionStateMessageSchema.parse(msg);
9327
+ if (payload.state === "evicted") chainSessionOp(agentId, payload.chatId, () => clearEvents(app.db, agentId, payload.chatId).catch(() => {}));
9328
+ await upsertSessionState(app.db, agentId, payload.chatId, payload.state, session.organizationId, notifier);
9329
+ } else if (type === "runtime:state") {
9330
+ const agentId = parsed.data.agentId;
9331
+ if (!agentId || !boundAgents.has(agentId)) {
9332
+ socket.send(JSON.stringify({
9333
+ type: "error",
9334
+ message: "Agent not bound"
9335
+ }));
9336
+ return;
9337
+ }
9338
+ const payload = runtimeStateMessageSchema.parse(msg);
9339
+ await setRuntimeState(app.db, agentId, payload.runtimeState, {
9340
+ organizationId: session.organizationId,
9341
+ notifier
9342
+ });
9343
+ if (payload.runtimeState === "error" && shouldNotify(agentId, "agent_error")) notifyAgentEvent(app.db, agentId, "agent_error", "high", `Agent ${agentId} entered error state`).catch(() => {});
9344
+ else if (payload.runtimeState === "blocked" && shouldNotify(agentId, "agent_blocked")) notifyAgentEvent(app.db, agentId, "agent_blocked", "medium", `Agent ${agentId} is blocked`).catch(() => {});
9345
+ } else if (type === "session:event") {
9346
+ const agentId = parsed.data.agentId;
9347
+ if (!agentId || !boundAgents.has(agentId)) {
9348
+ socket.send(JSON.stringify({
9349
+ type: "error",
9350
+ message: "Agent not bound"
9351
+ }));
9352
+ return;
9353
+ }
9354
+ const payload = sessionEventMessageSchema.parse(msg);
9355
+ chainSessionOp(agentId, payload.chatId, async () => {
9356
+ try {
9357
+ await appendEvent(app.db, agentId, payload.chatId, payload.event);
9358
+ } catch (err) {
9359
+ socket.send(JSON.stringify({
9360
+ type: "error",
9361
+ message: `Failed to persist session event: ${err instanceof Error ? err.message : String(err)}`
9362
+ }));
9363
+ }
9364
+ });
9365
+ } else if (type === "session:completion") {
9366
+ const agentId = parsed.data.agentId;
9367
+ if (!agentId || !boundAgents.has(agentId)) {
9368
+ socket.send(JSON.stringify({
9369
+ type: "error",
9370
+ message: "Agent not bound"
9371
+ }));
9372
+ return;
9373
+ }
9374
+ const payload = sessionCompletionMessageSchema.parse(msg);
9375
+ if (shouldNotify(agentId, `session_completed:${payload.chatId}`)) notifyAgentEvent(app.db, agentId, "session_completed", "low", `Agent ${agentId} completed a task`, payload.chatId).catch(() => {});
9376
+ } else if (type === "heartbeat") {
9377
+ if (clientId) {
9378
+ await heartbeatClient(app.db, clientId);
9379
+ await Promise.all([...boundAgents.keys()].map((id) => touchAgent(app.db, id)));
9380
+ }
9381
+ socket.send(JSON.stringify({ type: "heartbeat:ack" }));
9107
9382
  }
9108
- socket.send(JSON.stringify({ type: "heartbeat:ack" }));
9383
+ } catch (err) {
9384
+ const message = err instanceof Error ? err.message : "Internal error";
9385
+ socket.send(JSON.stringify({
9386
+ type: "error",
9387
+ message
9388
+ }));
9109
9389
  }
9110
- } catch (err) {
9111
- const message = err instanceof Error ? err.message : "Internal error";
9112
- socket.send(JSON.stringify({
9113
- type: "error",
9114
- message
9115
- }));
9116
- }
9390
+ });
9117
9391
  });
9118
- socket.on("close", async () => {
9392
+ socket.on("close", async (closeCode) => {
9393
+ endWsConnectionSpan(socket, closeCode);
9119
9394
  clearTimeout(authTimeout);
9120
9395
  if (authExpiryTimer) clearTimeout(authExpiryTimer);
9121
9396
  for (const [agentId, info] of boundAgents) {
@@ -9522,6 +9797,7 @@ async function memberRoutes(app) {
9522
9797
  return reply.status(204).send();
9523
9798
  });
9524
9799
  }
9800
+ const log$1 = createLogger("GithubWebhook");
9525
9801
  const GITHUB_ADAPTER_ID = "github-adapter";
9526
9802
  function verifySignature(secret, rawBody, signatureHeader) {
9527
9803
  const expected = `sha256=${createHmac("sha256", secret).update(rawBody).digest("hex")}`;
@@ -9608,7 +9884,10 @@ async function routeMentionDelegations(app, mentionedNames, ctx) {
9608
9884
  status: agents.status
9609
9885
  }).from(agents).where(eq(agents.uuid, agent.delegateMention)).limit(1);
9610
9886
  if (!target || target.status !== "active") {
9611
- app.log.warn(`delegate_mention target "${agent.delegateMention}" for "${agent.name}" is not active, skipping`);
9887
+ log$1.warn({
9888
+ targetAgent: agent.delegateMention,
9889
+ sourceAgent: agent.name
9890
+ }, "delegate_mention target not active, skipping");
9612
9891
  continue;
9613
9892
  }
9614
9893
  try {
@@ -9634,7 +9913,11 @@ async function routeMentionDelegations(app, mentionedNames, ctx) {
9634
9913
  notifyRecipients(app.notifier, recipients, msg.id);
9635
9914
  routed++;
9636
9915
  } catch (err) {
9637
- app.log.error(err, `Failed to route mention delegation from "${agent.name}" to "${agent.delegateMention}"`);
9916
+ log$1.error({
9917
+ err,
9918
+ sourceAgent: agent.name,
9919
+ targetAgent: agent.delegateMention
9920
+ }, "failed to route mention delegation");
9638
9921
  }
9639
9922
  }
9640
9923
  return routed;
@@ -9895,7 +10178,10 @@ async function handleIssuesEvent(app, eventType, payload, reply) {
9895
10178
  });
9896
10179
  const [senderId, targetAgentId] = await Promise.all([ensureGitHubAdapterAgent(app.db), findTargetAgent(app.db, data.repository.full_name)]);
9897
10180
  if (!targetAgentId) {
9898
- app.log.warn(`No target agent found for GitHub issue event on ${data.repository.full_name}`);
10181
+ log$1.warn({
10182
+ repo: data.repository.full_name,
10183
+ event: "issue"
10184
+ }, "no target agent found for GitHub event");
9899
10185
  return reply.status(200).send({
9900
10186
  ok: true,
9901
10187
  event: "issues",
@@ -9947,7 +10233,10 @@ async function handleIssueCommentEvent(app, eventType, payload, reply) {
9947
10233
  });
9948
10234
  const [senderId, targetAgentId] = await Promise.all([ensureGitHubAdapterAgent(app.db), findTargetAgent(app.db, data.repository.full_name)]);
9949
10235
  if (!targetAgentId) {
9950
- app.log.warn(`No target agent found for GitHub issue_comment event on ${data.repository.full_name}`);
10236
+ log$1.warn({
10237
+ repo: data.repository.full_name,
10238
+ event: "issue_comment"
10239
+ }, "no target agent found for GitHub event");
9951
10240
  return reply.status(200).send({
9952
10241
  ok: true,
9953
10242
  event: "issue_comment",
@@ -10123,7 +10412,11 @@ function createAdapterManager(db, encryptionKey, log, notifier) {
10123
10412
  const bot = bots.get(appId);
10124
10413
  if (!bot) return;
10125
10414
  try {
10126
- await processInboundMessage(db, event, bot, log, notifier);
10415
+ await withSpan("adapter.inbound feishu", adapterAttrs({
10416
+ platform: "feishu",
10417
+ externalChatId: event.externalChannelId,
10418
+ agentId: bot.agentId
10419
+ }), () => processInboundMessage(db, event, bot, log, notifier));
10127
10420
  bot.lastActiveAt = /* @__PURE__ */ new Date();
10128
10421
  } catch (err) {
10129
10422
  await unclaimEvent(db, event.eventId, "feishu");
@@ -10211,15 +10504,17 @@ function createAdapterManager(db, encryptionKey, log, notifier) {
10211
10504
  sent: 0,
10212
10505
  errors: 0
10213
10506
  };
10214
- try {
10215
- return await processFeishuOutbound(db, findBotByAgentId, log);
10216
- } catch (err) {
10217
- log.error({ err }, "Feishu outbound processing error");
10218
- return {
10219
- sent: 0,
10220
- errors: 1
10221
- };
10222
- }
10507
+ return withSpan("adapter.outbound feishu", adapterAttrs({ platform: "feishu" }), async () => {
10508
+ try {
10509
+ return await processFeishuOutbound(db, findBotByAgentId, log);
10510
+ } catch (err) {
10511
+ log.error({ err }, "Feishu outbound processing error");
10512
+ return {
10513
+ sent: 0,
10514
+ errors: 1
10515
+ };
10516
+ }
10517
+ });
10223
10518
  },
10224
10519
  async editOutboundMessage(messageId, format, content) {
10225
10520
  const ref = await findExternalMessageByInternalId(db, "feishu", messageId);
@@ -10569,6 +10864,7 @@ function formatForFeishu(format, content) {
10569
10864
  content: JSON.stringify({ text })
10570
10865
  };
10571
10866
  }
10867
+ const log = createLogger("BackgroundTasks");
10572
10868
  function createBackgroundTasks(app, instanceId, adapterManager, kaelRuntime) {
10573
10869
  let inboxTimer = null;
10574
10870
  let heartbeatTimer = null;
@@ -10584,7 +10880,7 @@ function createBackgroundTasks(app, instanceId, adapterManager, kaelRuntime) {
10584
10880
  const maxRetries = configs.max_retry_count ?? 3;
10585
10881
  await resetTimedOutEntries(app.db, timeoutSeconds, maxRetries);
10586
10882
  } catch (err) {
10587
- app.log.error(err, "Failed to reset timed-out inbox entries");
10883
+ log.error({ err }, "failed to reset timed-out inbox entries");
10588
10884
  }
10589
10885
  }, 6e4);
10590
10886
  heartbeatTimer = setInterval(async () => {
@@ -10595,37 +10891,40 @@ function createBackgroundTasks(app, instanceId, adapterManager, kaelRuntime) {
10595
10891
  await cleanupStaleClients(app.db, staleSeconds);
10596
10892
  const staleAgents = await markStaleAgents(app.db, staleSeconds);
10597
10893
  if (staleAgents.length > 0) {
10598
- app.log.info(`Marked ${staleAgents.length} agent(s) as stale: ${staleAgents.join(", ")}`);
10894
+ log.info({
10895
+ count: staleAgents.length,
10896
+ agentIds: staleAgents
10897
+ }, "marked agents as stale");
10599
10898
  for (const agentId of staleAgents) notifyAgentEvent(app.db, agentId, "agent_stale", "medium", `Agent ${agentId} is unresponsive`).catch(() => {});
10600
10899
  }
10601
10900
  } catch (err) {
10602
- app.log.error(err, "Failed to heartbeat / cleanup presence");
10901
+ log.error({ err }, "failed to heartbeat / cleanup presence");
10603
10902
  }
10604
10903
  }, 3e4);
10605
10904
  adapterOutboundTimer = setInterval(async () => {
10606
10905
  try {
10607
10906
  await adapterManager.processOutbound();
10608
10907
  } catch (err) {
10609
- app.log.error(err, "Adapter outbound processing failed");
10908
+ log.error({ err }, "adapter outbound processing failed");
10610
10909
  }
10611
10910
  }, 5e3);
10612
10911
  if (kaelRuntime) kaelOutboundTimer = setInterval(async () => {
10613
10912
  try {
10614
10913
  await kaelRuntime.processOutbound();
10615
10914
  } catch (err) {
10616
- app.log.error(err, "Kael outbound processing failed");
10915
+ log.error({ err }, "kael outbound processing failed");
10617
10916
  }
10618
10917
  }, 5e3);
10619
10918
  sessionCleanupTimer = setInterval(async () => {
10620
10919
  try {
10621
10920
  const deleted = await cleanupStaleSessions(app.db);
10622
- if (deleted > 0) app.log.info(`Cleaned up ${deleted} stale session(s)`);
10921
+ if (deleted > 0) log.info({ count: deleted }, "cleaned up stale sessions");
10623
10922
  } catch (err) {
10624
- app.log.error(err, "Failed to clean up stale sessions");
10923
+ log.error({ err }, "failed to clean up stale sessions");
10625
10924
  }
10626
10925
  }, 36e5);
10627
10926
  heartbeatInstance(app.db, instanceId).catch((err) => {
10628
- app.log.error(err, "Failed initial heartbeat");
10927
+ log.error({ err }, "failed initial heartbeat");
10629
10928
  });
10630
10929
  },
10631
10930
  stop() {
@@ -10985,11 +11284,15 @@ function createKaelRuntime(db, encryptionKey, kaelEndpoint, kaelApiKey, serverUr
10985
11284
  sent: 0,
10986
11285
  errors: 0
10987
11286
  };
10988
- let sent = 0;
10989
- let errorCount = 0;
10990
- try {
10991
- const agentIds = [...agentConfigs.keys()];
10992
- const claimed = await db.execute(sql`
11287
+ return withSpan("kael.forward", {
11288
+ "kael.endpoint": kaelEndpoint,
11289
+ "kael.agent_count": agentConfigs.size
11290
+ }, async () => {
11291
+ let sent = 0;
11292
+ let errorCount = 0;
11293
+ try {
11294
+ const agentIds = [...agentConfigs.keys()];
11295
+ const claimed = await db.execute(sql`
10993
11296
  UPDATE inbox_entries
10994
11297
  SET status = 'delivered', delivered_at = NOW()
10995
11298
  WHERE id IN (
@@ -11005,75 +11308,76 @@ function createKaelRuntime(db, encryptionKey, kaelEndpoint, kaelApiKey, serverUr
11005
11308
  )
11006
11309
  RETURNING id, inbox_id, message_id, chat_id
11007
11310
  `);
11008
- for (const entry of claimed) try {
11009
- const [msg] = await db.select().from(messages).where(eq(messages.id, entry.message_id)).limit(1);
11010
- if (!msg) {
11011
- await ackEntry(db, entry.id);
11012
- continue;
11013
- }
11014
- const config = inboxToConfig.get(entry.inbox_id);
11015
- if (!config) {
11311
+ for (const entry of claimed) try {
11312
+ const [msg] = await db.select().from(messages).where(eq(messages.id, entry.message_id)).limit(1);
11313
+ if (!msg) {
11314
+ await ackEntry(db, entry.id);
11315
+ continue;
11316
+ }
11317
+ const config = inboxToConfig.get(entry.inbox_id);
11318
+ if (!config) {
11319
+ await ackEntry(db, entry.id);
11320
+ continue;
11321
+ }
11322
+ const messageContent = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
11323
+ const payload = {
11324
+ hub_chat_id: entry.chat_id ?? msg.chatId,
11325
+ hub_agent_id: config.agentId,
11326
+ hub_server_url: serverUrl,
11327
+ hub_agent_token: config.agentToken,
11328
+ user_id: config.kaelUserId,
11329
+ project_id: config.kaelProjectId,
11330
+ message: messageContent,
11331
+ sender_id: msg.senderId,
11332
+ format: msg.format
11333
+ };
11334
+ if (agentsMd) payload.agents_md = agentsMd;
11335
+ const response = await fetch(`${kaelEndpoint}/api/v1/hub/messages`, {
11336
+ method: "POST",
11337
+ headers: {
11338
+ "Content-Type": "application/json",
11339
+ ...kaelApiKey ? { "X-Internal-API-Key": kaelApiKey } : {}
11340
+ },
11341
+ body: JSON.stringify(payload)
11342
+ });
11343
+ if (!response.ok) {
11344
+ const body = await response.text().catch(() => "");
11345
+ log.error({
11346
+ entryId: entry.id,
11347
+ status: response.status,
11348
+ body
11349
+ }, "Kael API rejected outbound message");
11350
+ await nackEntry(db, entry.id);
11351
+ errorCount++;
11352
+ continue;
11353
+ }
11016
11354
  await ackEntry(db, entry.id);
11017
- continue;
11018
- }
11019
- const messageContent = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
11020
- const payload = {
11021
- hub_chat_id: entry.chat_id ?? msg.chatId,
11022
- hub_agent_id: config.agentId,
11023
- hub_server_url: serverUrl,
11024
- hub_agent_token: config.agentToken,
11025
- user_id: config.kaelUserId,
11026
- project_id: config.kaelProjectId,
11027
- message: messageContent,
11028
- sender_id: msg.senderId,
11029
- format: msg.format
11030
- };
11031
- if (agentsMd) payload.agents_md = agentsMd;
11032
- const response = await fetch(`${kaelEndpoint}/api/v1/hub/messages`, {
11033
- method: "POST",
11034
- headers: {
11035
- "Content-Type": "application/json",
11036
- ...kaelApiKey ? { "X-Internal-API-Key": kaelApiKey } : {}
11037
- },
11038
- body: JSON.stringify(payload)
11039
- });
11040
- if (!response.ok) {
11041
- const body = await response.text().catch(() => "");
11355
+ sent++;
11356
+ } catch (err) {
11042
11357
  log.error({
11043
11358
  entryId: entry.id,
11044
- status: response.status,
11045
- body
11046
- }, "Kael API rejected outbound message");
11047
- await nackEntry(db, entry.id);
11359
+ err
11360
+ }, "Failed to send outbound Kael message");
11361
+ await nackEntry(db, entry.id).catch((nackErr) => {
11362
+ log.error({
11363
+ entryId: entry.id,
11364
+ err: nackErr
11365
+ }, "Failed to NACK entry");
11366
+ });
11048
11367
  errorCount++;
11049
- continue;
11050
11368
  }
11051
- await ackEntry(db, entry.id);
11052
- sent++;
11053
11369
  } catch (err) {
11054
- log.error({
11055
- entryId: entry.id,
11056
- err
11057
- }, "Failed to send outbound Kael message");
11058
- await nackEntry(db, entry.id).catch((nackErr) => {
11059
- log.error({
11060
- entryId: entry.id,
11061
- err: nackErr
11062
- }, "Failed to NACK entry");
11063
- });
11064
- errorCount++;
11370
+ log.error({ err }, "Kael outbound processing error");
11371
+ return {
11372
+ sent: 0,
11373
+ errors: 1
11374
+ };
11065
11375
  }
11066
- } catch (err) {
11067
- log.error({ err }, "Kael outbound processing error");
11068
11376
  return {
11069
- sent: 0,
11070
- errors: 1
11377
+ sent,
11378
+ errors: errorCount
11071
11379
  };
11072
- }
11073
- return {
11074
- sent,
11075
- errors: errorCount
11076
- };
11380
+ });
11077
11381
  },
11078
11382
  shutdown() {
11079
11383
  aborted = true;
@@ -11183,7 +11487,15 @@ function createPulseAggregator(options) {
11183
11487
  };
11184
11488
  }
11185
11489
  async function buildApp(config) {
11186
- const app = Fastify({ logger: config.logger ?? true });
11490
+ applyLoggerConfig({
11491
+ level: config.observability.logging.level,
11492
+ format: config.observability.logging.format,
11493
+ bridgeToSpanLevel: config.observability.logging.bridgeToSpanLevel
11494
+ });
11495
+ const app = Fastify({ loggerInstance: rootLogger$1 });
11496
+ const otelPlugin = getFastifyOtelPlugin();
11497
+ if (otelPlugin) await app.register(otelPlugin);
11498
+ await app.register(observabilityPlugin);
11187
11499
  const db = connectDatabase(config.database.url);
11188
11500
  app.decorate("db", db);
11189
11501
  app.decorate("config", config);
@@ -11203,14 +11515,23 @@ async function buildApp(config) {
11203
11515
  const memberAuth = memberAuthHook(db, config.secrets.jwtSecret);
11204
11516
  const adminOnly = requireAdminRoleHook();
11205
11517
  const agentSelector = agentSelectorHook(db);
11206
- app.setErrorHandler((error, _request, reply) => {
11207
- if (error instanceof AppError) return reply.status(error.statusCode).send({ error: error.message });
11518
+ app.setErrorHandler((error, request, reply) => {
11519
+ const traceId = currentTraceId();
11520
+ const traceField = traceId ? { traceId } : {};
11521
+ if (error instanceof AppError) return reply.status(error.statusCode).send({
11522
+ error: error.message,
11523
+ ...traceField
11524
+ });
11208
11525
  if (error instanceof ZodError) return reply.status(400).send({
11209
11526
  error: "Validation error",
11210
- details: error.issues
11527
+ details: error.issues,
11528
+ ...traceField
11529
+ });
11530
+ request.log.error({ err: error }, "unhandled request error");
11531
+ return reply.status(500).send({
11532
+ error: "Internal server error",
11533
+ ...traceField
11211
11534
  });
11212
- app.log.error(error);
11213
- return reply.status(500).send({ error: "Internal server error" });
11214
11535
  });
11215
11536
  await app.register(healthzRoutes);
11216
11537
  await app.register(async (api) => {
@@ -11344,10 +11665,11 @@ async function buildApp(config) {
11344
11665
  notifier,
11345
11666
  broadcast: broadcastToAdmins
11346
11667
  });
11668
+ const hotReloadLog = createLogger("HotReload");
11347
11669
  notifier.onConfigChange((configType) => {
11348
11670
  if (configType === "adapter_configs") {
11349
- adapterManager.reload().catch((err) => app.log.error(err, "Adapter hot-reload failed (PG NOTIFY)"));
11350
- kaelRuntime?.reload().catch((err) => app.log.error(err, "Kael hot-reload failed (PG NOTIFY)"));
11671
+ adapterManager.reload().catch((err) => hotReloadLog.error({ err }, "adapter hot-reload failed (PG NOTIFY)"));
11672
+ kaelRuntime?.reload().catch((err) => hotReloadLog.error({ err }, "kael hot-reload failed (PG NOTIFY)"));
11351
11673
  }
11352
11674
  });
11353
11675
  app.addHook("onReady", async () => {
@@ -11421,13 +11743,18 @@ async function startServer(options) {
11421
11743
  const config = {
11422
11744
  ...serverConfig,
11423
11745
  webDistPath: webDistPath ?? void 0,
11424
- instanceId: `srv_${randomUUID().slice(0, 8)}`,
11425
- logger: true
11746
+ instanceId: `srv_${randomUUID().slice(0, 8)}`
11426
11747
  };
11748
+ const { initTelemetry, shutdownTelemetry } = await import("./observability-Xi-sEZI7.mjs");
11749
+ await initTelemetry(serverConfig.observability.tracing, config.instanceId);
11427
11750
  const app = await buildApp(config);
11428
11751
  const shutdown = async () => {
11429
11752
  process.stderr.write("\n Shutting down...\n");
11430
- await app.close();
11753
+ try {
11754
+ await app.close();
11755
+ } finally {
11756
+ await shutdownTelemetry();
11757
+ }
11431
11758
  process.exit(0);
11432
11759
  };
11433
11760
  process.on("SIGINT", () => void shutdown());
@@ -11817,4 +12144,4 @@ function uninstallClientService() {
11817
12144
  return getClientServiceStatus();
11818
12145
  }
11819
12146
  //#endregion
11820
- export { stopPostgres as A, checkServerReachable as C, status as D, blank as E, SdkError as F, SessionRegistry as I, cleanWorkspaces as L, createOwner as M, hasUser as N, ensurePostgres as O, FirstTreeHubSDK as P, checkServerHealth as S, printResults as T, checkClientConfig as _, uninstallClientService as a, checkNodeVersion as b, promptAddAgent as c, loadOnboardState as d, onboardCheck as f, checkAgentConfigs as g, runMigrations as h, resolveCliInvocation as i, ClientRuntime as j, isDockerAvailable as k, promptMissingFields as l, saveOnboardState as m, installClientService as n, startServer as o, onboardCreate as p, isServiceSupported as r, isInteractive as s, getClientServiceStatus as t, formatCheckReport as u, checkDatabase as v, checkWebSocket as w, checkServerConfig as x, checkDocker as y };
12147
+ export { stopPostgres as A, checkServerReachable as C, status as D, blank as E, SdkError as F, SessionRegistry as I, cleanWorkspaces as L, createOwner as M, hasUser as N, ensurePostgres as O, FirstTreeHubSDK as P, applyClientLoggerConfig as R, checkServerHealth as S, printResults as T, checkClientConfig as _, uninstallClientService as a, checkNodeVersion as b, promptAddAgent as c, loadOnboardState as d, onboardCheck as f, checkAgentConfigs as g, runMigrations as h, resolveCliInvocation as i, ClientRuntime as j, isDockerAvailable as k, promptMissingFields as l, saveOnboardState as m, installClientService as n, startServer as o, onboardCreate as p, isServiceSupported as r, isInteractive as s, getClientServiceStatus as t, formatCheckReport as u, checkDatabase as v, checkWebSocket as w, checkServerConfig as x, checkDocker as y };