@agent-team-foundation/first-tree-hub 0.8.1 → 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.
- package/dist/{bootstrap-8nCntTrK.mjs → bootstrap-99vUYmLs.mjs} +26 -8
- package/dist/cli/index.mjs +8 -5
- package/dist/{core-BA5U1v9L.mjs → core-CNR-lUlr.mjs} +570 -322
- package/dist/esm-CYu4tXXn.mjs +1551 -0
- package/dist/execAsync-CP8iWV5b.mjs +10 -0
- package/dist/{feishu-D9JkMZnU.mjs → feishu-BOISS0DK.mjs} +1 -1
- package/dist/getMachineId-bsd-BB-fnFLA.mjs +26 -0
- package/dist/getMachineId-darwin-DAYWNsYK.mjs +23 -0
- package/dist/getMachineId-linux-BU7Fi6S0.mjs +19 -0
- package/dist/getMachineId-unsupported-BhWCxKBo.mjs +14 -0
- package/dist/getMachineId-win-H5RT49ov.mjs +25 -0
- package/dist/index.mjs +5 -3
- package/dist/logger-core-2yeIU1fc-B-__AsQO.mjs +111 -0
- package/dist/observability-CJzDFY_G-CmvgUuzc.mjs +33892 -0
- package/dist/observability-Xi-sEZI7.mjs +4 -0
- package/package.json +1 -1
- package/dist/rolldown-runtime-twds-ZHy.mjs +0 -14
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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",
|
|
@@ -1350,12 +1483,7 @@ defineConfig({
|
|
|
1350
1483
|
auto: "client-id",
|
|
1351
1484
|
env: "FIRST_TREE_HUB_CLIENT_ID"
|
|
1352
1485
|
}) },
|
|
1353
|
-
logLevel: field(
|
|
1354
|
-
"debug",
|
|
1355
|
-
"info",
|
|
1356
|
-
"warn",
|
|
1357
|
-
"error"
|
|
1358
|
-
]).default("info"), { env: "FIRST_TREE_HUB_LOG_LEVEL" })
|
|
1486
|
+
logLevel: field(logLevelSchema.default("info"), { env: "FIRST_TREE_HUB_LOG_LEVEL" })
|
|
1359
1487
|
});
|
|
1360
1488
|
const DEFAULT_HOME_DIR = process.env.FIRST_TREE_HUB_HOME ?? join(homedir(), ".first-tree-hub");
|
|
1361
1489
|
join(DEFAULT_HOME_DIR, "config");
|
|
@@ -1422,7 +1550,29 @@ defineConfig({
|
|
|
1422
1550
|
secret: true
|
|
1423
1551
|
}),
|
|
1424
1552
|
hubPublicUrl: field(z.string(), { env: "FIRST_TREE_HUB_PUBLIC_URL" })
|
|
1425
|
-
})
|
|
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
|
+
}
|
|
1426
1576
|
});
|
|
1427
1577
|
join(DEFAULT_DATA_DIR, "context-tree");
|
|
1428
1578
|
/**
|
|
@@ -4371,7 +4521,7 @@ async function onboardCreate(args) {
|
|
|
4371
4521
|
}
|
|
4372
4522
|
const runtimeAgent = args.type === "human" ? args.assistant : args.id;
|
|
4373
4523
|
if (args.feishuBotAppId && args.feishuBotAppSecret) {
|
|
4374
|
-
const { bindFeishuBot } = await import("./feishu-
|
|
4524
|
+
const { bindFeishuBot } = await import("./feishu-BOISS0DK.mjs").then((n) => n.r);
|
|
4375
4525
|
const targetAgentUuid = args.type === "human" ? assistantUuid : primary.uuid;
|
|
4376
4526
|
if (!targetAgentUuid) process.stderr.write(`Warning: Cannot bind Feishu bot — no runtime agent available for "${args.id}".\n`);
|
|
4377
4527
|
else {
|
|
@@ -4512,7 +4662,7 @@ function setNestedByDot(obj, dotPath, value) {
|
|
|
4512
4662
|
if (lastKey !== void 0) current[lastKey] = value;
|
|
4513
4663
|
}
|
|
4514
4664
|
//#endregion
|
|
4515
|
-
//#region ../server/dist/app-
|
|
4665
|
+
//#region ../server/dist/app-DJEePmWL.mjs
|
|
4516
4666
|
var __defProp = Object.defineProperty;
|
|
4517
4667
|
var __exportAll = (all, no_symbols) => {
|
|
4518
4668
|
let target = {};
|
|
@@ -5275,6 +5425,7 @@ async function deleteAdapterConfig(db, id) {
|
|
|
5275
5425
|
const [row] = await db.delete(adapterConfigs).where(eq(adapterConfigs.id, id)).returning();
|
|
5276
5426
|
if (!row) throw new NotFoundError(`Adapter config "${id}" not found`);
|
|
5277
5427
|
}
|
|
5428
|
+
const log$4 = createLogger("AdminAdapters");
|
|
5278
5429
|
function parseId(raw) {
|
|
5279
5430
|
const id = Number(raw);
|
|
5280
5431
|
if (!Number.isInteger(id) || id <= 0) throw new BadRequestError(`Invalid adapter ID: "${raw}"`);
|
|
@@ -5293,7 +5444,7 @@ async function adminAdapterRoutes(app) {
|
|
|
5293
5444
|
const scope = memberScope(request);
|
|
5294
5445
|
await assertCanManage(app.db, scope, body.agentId);
|
|
5295
5446
|
const config = await createAdapterConfig(app.db, body, app.config.secrets.encryptionKey);
|
|
5296
|
-
app.adapterManager.reload().catch((err) =>
|
|
5447
|
+
app.adapterManager.reload().catch((err) => log$4.error({ err }, "adapter reload failed after create"));
|
|
5297
5448
|
app.notifier.notifyConfigChange("adapter_configs").catch(() => {});
|
|
5298
5449
|
return reply.status(201).send({
|
|
5299
5450
|
...config,
|
|
@@ -5317,7 +5468,7 @@ async function adminAdapterRoutes(app) {
|
|
|
5317
5468
|
const existing = await getAdapterConfig(app.db, id);
|
|
5318
5469
|
await assertCanManage(app.db, scope, existing.agentId);
|
|
5319
5470
|
const config = await updateAdapterConfig(app.db, id, body, app.config.secrets.encryptionKey);
|
|
5320
|
-
app.adapterManager.reload().catch((err) =>
|
|
5471
|
+
app.adapterManager.reload().catch((err) => log$4.error({ err }, "adapter reload failed after update"));
|
|
5321
5472
|
app.notifier.notifyConfigChange("adapter_configs").catch(() => {});
|
|
5322
5473
|
return {
|
|
5323
5474
|
...config,
|
|
@@ -5331,7 +5482,7 @@ async function adminAdapterRoutes(app) {
|
|
|
5331
5482
|
const existing = await getAdapterConfig(app.db, id);
|
|
5332
5483
|
await assertCanManage(app.db, scope, existing.agentId);
|
|
5333
5484
|
await deleteAdapterConfig(app.db, id);
|
|
5334
|
-
app.adapterManager.reload().catch((err) =>
|
|
5485
|
+
app.adapterManager.reload().catch((err) => log$4.error({ err }, "adapter reload failed after delete"));
|
|
5335
5486
|
app.notifier.notifyConfigChange("adapter_configs").catch(() => {});
|
|
5336
5487
|
return reply.status(204).send();
|
|
5337
5488
|
});
|
|
@@ -6310,6 +6461,13 @@ const inboxEntries = pgTable("inbox_entries", {
|
|
|
6310
6461
|
ackedAt: timestamp("acked_at", { withTimezone: true })
|
|
6311
6462
|
}, (table) => [unique("uq_inbox_delivery").on(table.inboxId, table.messageId, table.chatId), index("idx_inbox_pending").on(table.inboxId, table.createdAt)]);
|
|
6312
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) {
|
|
6313
6471
|
return db.transaction(async (tx) => {
|
|
6314
6472
|
const messageId = randomUUID();
|
|
6315
6473
|
const [msg] = await tx.insert(messages).values({
|
|
@@ -8315,7 +8473,11 @@ function adminWsRoutes(notifier, jwtSecret) {
|
|
|
8315
8473
|
});
|
|
8316
8474
|
});
|
|
8317
8475
|
return async (app) => {
|
|
8318
|
-
app.get("/admin", {
|
|
8476
|
+
app.get("/admin", {
|
|
8477
|
+
websocket: true,
|
|
8478
|
+
config: { otel: false }
|
|
8479
|
+
}, async (socket, request) => {
|
|
8480
|
+
startWsConnectionSpan(socket, { remoteIp: request.ip });
|
|
8319
8481
|
const token = request.query.token;
|
|
8320
8482
|
if (!token) {
|
|
8321
8483
|
socket.send(JSON.stringify({
|
|
@@ -8323,6 +8485,7 @@ function adminWsRoutes(notifier, jwtSecret) {
|
|
|
8323
8485
|
message: "Missing token query parameter"
|
|
8324
8486
|
}));
|
|
8325
8487
|
socket.close(4001, "Missing token");
|
|
8488
|
+
endWsConnectionSpan(socket, 4001);
|
|
8326
8489
|
return;
|
|
8327
8490
|
}
|
|
8328
8491
|
let organizationId;
|
|
@@ -8335,6 +8498,7 @@ function adminWsRoutes(notifier, jwtSecret) {
|
|
|
8335
8498
|
message: "Invalid token type"
|
|
8336
8499
|
}));
|
|
8337
8500
|
socket.close(4001, "Invalid token");
|
|
8501
|
+
endWsConnectionSpan(socket, 4001);
|
|
8338
8502
|
return;
|
|
8339
8503
|
}
|
|
8340
8504
|
organizationId = payload.organizationId;
|
|
@@ -8345,8 +8509,13 @@ function adminWsRoutes(notifier, jwtSecret) {
|
|
|
8345
8509
|
message: "Invalid or expired token"
|
|
8346
8510
|
}));
|
|
8347
8511
|
socket.close(4001, "Auth failed");
|
|
8512
|
+
endWsConnectionSpan(socket, 4001);
|
|
8348
8513
|
return;
|
|
8349
8514
|
}
|
|
8515
|
+
setWsConnectionAttrs(socket, {
|
|
8516
|
+
organizationId,
|
|
8517
|
+
memberId
|
|
8518
|
+
});
|
|
8350
8519
|
const visibleAgentIds = await loadVisibleAgentIds(app.db, organizationId, memberId);
|
|
8351
8520
|
adminSockets.set(socket, {
|
|
8352
8521
|
organizationId,
|
|
@@ -8354,8 +8523,9 @@ function adminWsRoutes(notifier, jwtSecret) {
|
|
|
8354
8523
|
visibleAgentIds
|
|
8355
8524
|
});
|
|
8356
8525
|
socket.send(JSON.stringify({ type: "admin:connected" }));
|
|
8357
|
-
socket.on("close", () => {
|
|
8526
|
+
socket.on("close", (code) => {
|
|
8358
8527
|
adminSockets.delete(socket);
|
|
8528
|
+
endWsConnectionSpan(socket, code);
|
|
8359
8529
|
});
|
|
8360
8530
|
});
|
|
8361
8531
|
};
|
|
@@ -8430,6 +8600,7 @@ async function agentConfigRoutes(app) {
|
|
|
8430
8600
|
return await app.configService.getDecrypted(identity.uuid);
|
|
8431
8601
|
});
|
|
8432
8602
|
}
|
|
8603
|
+
const log$3 = createLogger("AgentFeishuBot");
|
|
8433
8604
|
async function agentFeishuBotRoutes(app) {
|
|
8434
8605
|
/**
|
|
8435
8606
|
* PUT /agent/me/feishu-bot
|
|
@@ -8457,7 +8628,7 @@ async function agentFeishuBotRoutes(app) {
|
|
|
8457
8628
|
},
|
|
8458
8629
|
status: "active"
|
|
8459
8630
|
}, app.config.secrets.encryptionKey);
|
|
8460
|
-
app.adapterManager.reload().catch((err) =>
|
|
8631
|
+
app.adapterManager.reload().catch((err) => log$3.error({ err }, "adapter reload failed after self-service bind"));
|
|
8461
8632
|
app.notifier.notifyConfigChange("adapter_configs").catch(() => {});
|
|
8462
8633
|
return reply.status(current ? 200 : 201).send({
|
|
8463
8634
|
...config,
|
|
@@ -8474,7 +8645,7 @@ async function agentFeishuBotRoutes(app) {
|
|
|
8474
8645
|
const current = (await listAdapterConfigs(app.db)).find((c) => c.agentId === identity.uuid && c.platform === "feishu");
|
|
8475
8646
|
if (!current) return reply.status(204).send();
|
|
8476
8647
|
await deleteAdapterConfig(app.db, current.id);
|
|
8477
|
-
app.adapterManager.reload().catch((err) =>
|
|
8648
|
+
app.adapterManager.reload().catch((err) => log$3.error({ err }, "adapter reload failed after self-service unbind"));
|
|
8478
8649
|
app.notifier.notifyConfigChange("adapter_configs").catch(() => {});
|
|
8479
8650
|
return reply.status(204).send();
|
|
8480
8651
|
});
|
|
@@ -8561,6 +8732,12 @@ async function resolveAgentId(db, source) {
|
|
|
8561
8732
|
const DEFAULT_INBOX_TIMEOUT_SECONDS = 300;
|
|
8562
8733
|
const DEFAULT_MAX_RETRY_COUNT = 3;
|
|
8563
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) {
|
|
8564
8741
|
return await db.transaction(async (tx) => {
|
|
8565
8742
|
const claimed = await tx.execute(sql`
|
|
8566
8743
|
UPDATE inbox_entries
|
|
@@ -8615,12 +8792,17 @@ async function pollInbox(db, inboxId, limit) {
|
|
|
8615
8792
|
});
|
|
8616
8793
|
}
|
|
8617
8794
|
async function ackEntry$2(db, entryId, inboxId) {
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
}
|
|
8622
|
-
|
|
8623
|
-
|
|
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
|
+
});
|
|
8624
8806
|
}
|
|
8625
8807
|
async function renewEntry(db, entryId, inboxId) {
|
|
8626
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();
|
|
@@ -8683,6 +8865,7 @@ async function agentMeRoutes(app) {
|
|
|
8683
8865
|
};
|
|
8684
8866
|
});
|
|
8685
8867
|
}
|
|
8868
|
+
const log$2 = createLogger("AgentMessages");
|
|
8686
8869
|
const editMessageSchema = z.object({
|
|
8687
8870
|
format: z.string().optional(),
|
|
8688
8871
|
content: z.unknown()
|
|
@@ -8704,10 +8887,10 @@ async function agentMessageRoutes(app) {
|
|
|
8704
8887
|
await assertParticipant(app.db, request.params.chatId, identity.uuid);
|
|
8705
8888
|
const body = editMessageSchema.parse(request.body);
|
|
8706
8889
|
const msg = await editMessage(app.db, request.params.chatId, request.params.messageId, identity.uuid, body);
|
|
8707
|
-
app.adapterManager.editOutboundMessage(msg.id, msg.format, msg.content).catch((err) =>
|
|
8890
|
+
app.adapterManager.editOutboundMessage(msg.id, msg.format, msg.content).catch((err) => log$2.error({
|
|
8708
8891
|
err,
|
|
8709
8892
|
messageId: msg.id
|
|
8710
|
-
}, "
|
|
8893
|
+
}, "failed to edit outbound message"));
|
|
8711
8894
|
return {
|
|
8712
8895
|
...msg,
|
|
8713
8896
|
createdAt: msg.createdAt.toISOString()
|
|
@@ -8866,7 +9049,11 @@ function sendRejected(socket, ref, reason) {
|
|
|
8866
9049
|
function clientWsRoutes(notifier, instanceId) {
|
|
8867
9050
|
return async (app) => {
|
|
8868
9051
|
const jwtSecretBytes = new TextEncoder().encode(app.config.secrets.jwtSecret);
|
|
8869
|
-
app.get("/client", {
|
|
9052
|
+
app.get("/client", {
|
|
9053
|
+
websocket: true,
|
|
9054
|
+
config: { otel: false }
|
|
9055
|
+
}, async (socket) => {
|
|
9056
|
+
startWsConnectionSpan(socket);
|
|
8870
9057
|
let session = null;
|
|
8871
9058
|
let clientId = null;
|
|
8872
9059
|
let authExpiryTimer = null;
|
|
@@ -8965,6 +9152,10 @@ function clientWsRoutes(notifier, instanceId) {
|
|
|
8965
9152
|
organizationId: member.organizationId,
|
|
8966
9153
|
role: member.role
|
|
8967
9154
|
};
|
|
9155
|
+
setWsConnectionAttrs(socket, {
|
|
9156
|
+
"organization.id": member.organizationId,
|
|
9157
|
+
"member.id": member.id
|
|
9158
|
+
});
|
|
8968
9159
|
clearTimeout(authTimeout);
|
|
8969
9160
|
scheduleAuthExpiry(claims.exp);
|
|
8970
9161
|
socket.send(JSON.stringify({ type: "auth:ok" }));
|
|
@@ -8978,223 +9169,228 @@ function clientWsRoutes(notifier, instanceId) {
|
|
|
8978
9169
|
}
|
|
8979
9170
|
return;
|
|
8980
9171
|
}
|
|
8981
|
-
|
|
8982
|
-
if (
|
|
8983
|
-
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
|
|
8991
|
-
|
|
8992
|
-
|
|
8993
|
-
|
|
8994
|
-
|
|
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);
|
|
8995
9198
|
socket.send(JSON.stringify({
|
|
8996
|
-
type: "client:
|
|
8997
|
-
|
|
9199
|
+
type: "client:registered",
|
|
9200
|
+
clientId: data.clientId
|
|
8998
9201
|
}));
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
|
|
9002
|
-
|
|
9003
|
-
|
|
9004
|
-
socket.send(JSON.stringify({
|
|
9005
|
-
type: "client:registered",
|
|
9006
|
-
clientId: data.clientId
|
|
9007
|
-
}));
|
|
9008
|
-
try {
|
|
9009
|
-
const pinned = await listActiveAgentsPinnedToClient(app.db, data.clientId);
|
|
9010
|
-
for (const agent of pinned) {
|
|
9011
|
-
const parsed = agentPinnedMessageSchema$1.safeParse({
|
|
9012
|
-
type: "agent:pinned",
|
|
9013
|
-
agentId: agent.uuid,
|
|
9014
|
-
name: agent.name,
|
|
9015
|
-
displayName: agent.displayName,
|
|
9016
|
-
agentType: agent.type
|
|
9017
|
-
});
|
|
9018
|
-
if (!parsed.success) {
|
|
9019
|
-
app.log.warn({
|
|
9020
|
-
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",
|
|
9021
9207
|
agentId: agent.uuid,
|
|
9022
|
-
|
|
9023
|
-
|
|
9024
|
-
|
|
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));
|
|
9025
9221
|
}
|
|
9026
|
-
|
|
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`");
|
|
9027
9227
|
}
|
|
9028
|
-
}
|
|
9029
|
-
|
|
9030
|
-
|
|
9031
|
-
|
|
9032
|
-
|
|
9033
|
-
|
|
9034
|
-
|
|
9035
|
-
if (!clientId) {
|
|
9036
|
-
socket.send(JSON.stringify({
|
|
9037
|
-
type: "error",
|
|
9038
|
-
ref,
|
|
9039
|
-
message: "Must register client first"
|
|
9040
|
-
}));
|
|
9041
|
-
return;
|
|
9042
|
-
}
|
|
9043
|
-
const bindRequest = agentBindRequestSchema.parse(msg);
|
|
9044
|
-
const [agent] = await app.db.select({
|
|
9045
|
-
id: agents.uuid,
|
|
9046
|
-
displayName: agents.displayName,
|
|
9047
|
-
type: agents.type,
|
|
9048
|
-
organizationId: agents.organizationId,
|
|
9049
|
-
inboxId: agents.inboxId,
|
|
9050
|
-
status: agents.status,
|
|
9051
|
-
clientId: agents.clientId,
|
|
9052
|
-
clientUserId: clients.userId,
|
|
9053
|
-
managerUserId: members.userId
|
|
9054
|
-
}).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);
|
|
9055
|
-
if (!agent) {
|
|
9056
|
-
sendRejected(socket, ref, AGENT_BIND_REJECT_REASONS.UNKNOWN_AGENT);
|
|
9057
|
-
return;
|
|
9058
|
-
}
|
|
9059
|
-
if (agent.organizationId !== session.organizationId) {
|
|
9060
|
-
sendRejected(socket, ref, AGENT_BIND_REJECT_REASONS.WRONG_ORG);
|
|
9061
|
-
return;
|
|
9062
|
-
}
|
|
9063
|
-
if (agent.status !== "active") {
|
|
9064
|
-
sendRejected(socket, ref, AGENT_BIND_REJECT_REASONS.AGENT_SUSPENDED);
|
|
9065
|
-
return;
|
|
9066
|
-
}
|
|
9067
|
-
if (agent.clientId === null) {
|
|
9068
|
-
if (!agent.managerUserId || agent.managerUserId !== session.userId) {
|
|
9069
|
-
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
|
+
}));
|
|
9070
9235
|
return;
|
|
9071
9236
|
}
|
|
9072
|
-
|
|
9073
|
-
|
|
9074
|
-
|
|
9075
|
-
|
|
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) {
|
|
9076
9274
|
sendRejected(socket, ref, AGENT_BIND_REJECT_REASONS.WRONG_CLIENT);
|
|
9077
9275
|
return;
|
|
9276
|
+
} else if (!agent.clientUserId || agent.clientUserId !== session.userId) {
|
|
9277
|
+
sendRejected(socket, ref, AGENT_BIND_REJECT_REASONS.NOT_OWNED);
|
|
9278
|
+
return;
|
|
9078
9279
|
}
|
|
9079
|
-
|
|
9080
|
-
|
|
9081
|
-
|
|
9082
|
-
|
|
9083
|
-
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
|
|
9088
|
-
|
|
9089
|
-
|
|
9090
|
-
|
|
9091
|
-
});
|
|
9092
|
-
bindAgentToClient(clientId, agent.id);
|
|
9093
|
-
boundAgents.set(agent.id, {
|
|
9094
|
-
agentId: agent.id,
|
|
9095
|
-
inboxId: agent.inboxId
|
|
9096
|
-
});
|
|
9097
|
-
notifier.subscribe(agent.inboxId, socket);
|
|
9098
|
-
socket.send(JSON.stringify({
|
|
9099
|
-
type: "agent:bound",
|
|
9100
|
-
ref,
|
|
9101
|
-
agentId: agent.id,
|
|
9102
|
-
displayName: agent.displayName,
|
|
9103
|
-
agentType: agent.type
|
|
9104
|
-
}));
|
|
9105
|
-
} else if (type === "agent:unbind") {
|
|
9106
|
-
const agentId = parsed.data.agentId;
|
|
9107
|
-
if (!agentId || !boundAgents.has(agentId)) {
|
|
9108
|
-
socket.send(JSON.stringify({
|
|
9109
|
-
type: "error",
|
|
9110
|
-
message: "Agent not bound"
|
|
9111
|
-
}));
|
|
9112
|
-
return;
|
|
9113
|
-
}
|
|
9114
|
-
const info = boundAgents.get(agentId);
|
|
9115
|
-
if (info) notifier.unsubscribe(info.inboxId, socket);
|
|
9116
|
-
await unbindAgent(app.db, agentId);
|
|
9117
|
-
unbindAgentFromClient(agentId);
|
|
9118
|
-
boundAgents.delete(agentId);
|
|
9119
|
-
socket.send(JSON.stringify({
|
|
9120
|
-
type: "agent:unbound",
|
|
9121
|
-
agentId
|
|
9122
|
-
}));
|
|
9123
|
-
} else if (type === "session:state") {
|
|
9124
|
-
const agentId = parsed.data.agentId;
|
|
9125
|
-
if (!agentId || !boundAgents.has(agentId)) {
|
|
9126
|
-
socket.send(JSON.stringify({
|
|
9127
|
-
type: "error",
|
|
9128
|
-
message: "Agent not bound"
|
|
9129
|
-
}));
|
|
9130
|
-
return;
|
|
9131
|
-
}
|
|
9132
|
-
const payload = sessionStateMessageSchema.parse(msg);
|
|
9133
|
-
if (payload.state === "evicted") chainSessionOp(agentId, payload.chatId, () => clearEvents(app.db, agentId, payload.chatId).catch(() => {}));
|
|
9134
|
-
await upsertSessionState(app.db, agentId, payload.chatId, payload.state, session.organizationId, notifier);
|
|
9135
|
-
} else if (type === "runtime:state") {
|
|
9136
|
-
const agentId = parsed.data.agentId;
|
|
9137
|
-
if (!agentId || !boundAgents.has(agentId)) {
|
|
9138
|
-
socket.send(JSON.stringify({
|
|
9139
|
-
type: "error",
|
|
9140
|
-
message: "Agent not bound"
|
|
9141
|
-
}));
|
|
9142
|
-
return;
|
|
9143
|
-
}
|
|
9144
|
-
const payload = runtimeStateMessageSchema.parse(msg);
|
|
9145
|
-
await setRuntimeState(app.db, agentId, payload.runtimeState, {
|
|
9146
|
-
organizationId: session.organizationId,
|
|
9147
|
-
notifier
|
|
9148
|
-
});
|
|
9149
|
-
if (payload.runtimeState === "error" && shouldNotify(agentId, "agent_error")) notifyAgentEvent(app.db, agentId, "agent_error", "high", `Agent ${agentId} entered error state`).catch(() => {});
|
|
9150
|
-
else if (payload.runtimeState === "blocked" && shouldNotify(agentId, "agent_blocked")) notifyAgentEvent(app.db, agentId, "agent_blocked", "medium", `Agent ${agentId} is blocked`).catch(() => {});
|
|
9151
|
-
} else if (type === "session:event") {
|
|
9152
|
-
const agentId = parsed.data.agentId;
|
|
9153
|
-
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);
|
|
9154
9292
|
socket.send(JSON.stringify({
|
|
9155
|
-
type: "
|
|
9156
|
-
|
|
9293
|
+
type: "agent:bound",
|
|
9294
|
+
ref,
|
|
9295
|
+
agentId: agent.id,
|
|
9296
|
+
displayName: agent.displayName,
|
|
9297
|
+
agentType: agent.type
|
|
9157
9298
|
}));
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
|
|
9161
|
-
chainSessionOp(agentId, payload.chatId, async () => {
|
|
9162
|
-
try {
|
|
9163
|
-
await appendEvent(app.db, agentId, payload.chatId, payload.event);
|
|
9164
|
-
} catch (err) {
|
|
9299
|
+
} else if (type === "agent:unbind") {
|
|
9300
|
+
const agentId = parsed.data.agentId;
|
|
9301
|
+
if (!agentId || !boundAgents.has(agentId)) {
|
|
9165
9302
|
socket.send(JSON.stringify({
|
|
9166
9303
|
type: "error",
|
|
9167
|
-
message:
|
|
9304
|
+
message: "Agent not bound"
|
|
9168
9305
|
}));
|
|
9306
|
+
return;
|
|
9169
9307
|
}
|
|
9170
|
-
|
|
9171
|
-
|
|
9172
|
-
|
|
9173
|
-
|
|
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);
|
|
9174
9313
|
socket.send(JSON.stringify({
|
|
9175
|
-
type: "
|
|
9176
|
-
|
|
9314
|
+
type: "agent:unbound",
|
|
9315
|
+
agentId
|
|
9177
9316
|
}));
|
|
9178
|
-
|
|
9179
|
-
|
|
9180
|
-
|
|
9181
|
-
|
|
9182
|
-
|
|
9183
|
-
|
|
9184
|
-
|
|
9185
|
-
|
|
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" }));
|
|
9186
9382
|
}
|
|
9187
|
-
|
|
9383
|
+
} catch (err) {
|
|
9384
|
+
const message = err instanceof Error ? err.message : "Internal error";
|
|
9385
|
+
socket.send(JSON.stringify({
|
|
9386
|
+
type: "error",
|
|
9387
|
+
message
|
|
9388
|
+
}));
|
|
9188
9389
|
}
|
|
9189
|
-
}
|
|
9190
|
-
const message = err instanceof Error ? err.message : "Internal error";
|
|
9191
|
-
socket.send(JSON.stringify({
|
|
9192
|
-
type: "error",
|
|
9193
|
-
message
|
|
9194
|
-
}));
|
|
9195
|
-
}
|
|
9390
|
+
});
|
|
9196
9391
|
});
|
|
9197
|
-
socket.on("close", async () => {
|
|
9392
|
+
socket.on("close", async (closeCode) => {
|
|
9393
|
+
endWsConnectionSpan(socket, closeCode);
|
|
9198
9394
|
clearTimeout(authTimeout);
|
|
9199
9395
|
if (authExpiryTimer) clearTimeout(authExpiryTimer);
|
|
9200
9396
|
for (const [agentId, info] of boundAgents) {
|
|
@@ -9601,6 +9797,7 @@ async function memberRoutes(app) {
|
|
|
9601
9797
|
return reply.status(204).send();
|
|
9602
9798
|
});
|
|
9603
9799
|
}
|
|
9800
|
+
const log$1 = createLogger("GithubWebhook");
|
|
9604
9801
|
const GITHUB_ADAPTER_ID = "github-adapter";
|
|
9605
9802
|
function verifySignature(secret, rawBody, signatureHeader) {
|
|
9606
9803
|
const expected = `sha256=${createHmac("sha256", secret).update(rawBody).digest("hex")}`;
|
|
@@ -9687,7 +9884,10 @@ async function routeMentionDelegations(app, mentionedNames, ctx) {
|
|
|
9687
9884
|
status: agents.status
|
|
9688
9885
|
}).from(agents).where(eq(agents.uuid, agent.delegateMention)).limit(1);
|
|
9689
9886
|
if (!target || target.status !== "active") {
|
|
9690
|
-
|
|
9887
|
+
log$1.warn({
|
|
9888
|
+
targetAgent: agent.delegateMention,
|
|
9889
|
+
sourceAgent: agent.name
|
|
9890
|
+
}, "delegate_mention target not active, skipping");
|
|
9691
9891
|
continue;
|
|
9692
9892
|
}
|
|
9693
9893
|
try {
|
|
@@ -9713,7 +9913,11 @@ async function routeMentionDelegations(app, mentionedNames, ctx) {
|
|
|
9713
9913
|
notifyRecipients(app.notifier, recipients, msg.id);
|
|
9714
9914
|
routed++;
|
|
9715
9915
|
} catch (err) {
|
|
9716
|
-
|
|
9916
|
+
log$1.error({
|
|
9917
|
+
err,
|
|
9918
|
+
sourceAgent: agent.name,
|
|
9919
|
+
targetAgent: agent.delegateMention
|
|
9920
|
+
}, "failed to route mention delegation");
|
|
9717
9921
|
}
|
|
9718
9922
|
}
|
|
9719
9923
|
return routed;
|
|
@@ -9974,7 +10178,10 @@ async function handleIssuesEvent(app, eventType, payload, reply) {
|
|
|
9974
10178
|
});
|
|
9975
10179
|
const [senderId, targetAgentId] = await Promise.all([ensureGitHubAdapterAgent(app.db), findTargetAgent(app.db, data.repository.full_name)]);
|
|
9976
10180
|
if (!targetAgentId) {
|
|
9977
|
-
|
|
10181
|
+
log$1.warn({
|
|
10182
|
+
repo: data.repository.full_name,
|
|
10183
|
+
event: "issue"
|
|
10184
|
+
}, "no target agent found for GitHub event");
|
|
9978
10185
|
return reply.status(200).send({
|
|
9979
10186
|
ok: true,
|
|
9980
10187
|
event: "issues",
|
|
@@ -10026,7 +10233,10 @@ async function handleIssueCommentEvent(app, eventType, payload, reply) {
|
|
|
10026
10233
|
});
|
|
10027
10234
|
const [senderId, targetAgentId] = await Promise.all([ensureGitHubAdapterAgent(app.db), findTargetAgent(app.db, data.repository.full_name)]);
|
|
10028
10235
|
if (!targetAgentId) {
|
|
10029
|
-
|
|
10236
|
+
log$1.warn({
|
|
10237
|
+
repo: data.repository.full_name,
|
|
10238
|
+
event: "issue_comment"
|
|
10239
|
+
}, "no target agent found for GitHub event");
|
|
10030
10240
|
return reply.status(200).send({
|
|
10031
10241
|
ok: true,
|
|
10032
10242
|
event: "issue_comment",
|
|
@@ -10202,7 +10412,11 @@ function createAdapterManager(db, encryptionKey, log, notifier) {
|
|
|
10202
10412
|
const bot = bots.get(appId);
|
|
10203
10413
|
if (!bot) return;
|
|
10204
10414
|
try {
|
|
10205
|
-
await
|
|
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));
|
|
10206
10420
|
bot.lastActiveAt = /* @__PURE__ */ new Date();
|
|
10207
10421
|
} catch (err) {
|
|
10208
10422
|
await unclaimEvent(db, event.eventId, "feishu");
|
|
@@ -10290,15 +10504,17 @@ function createAdapterManager(db, encryptionKey, log, notifier) {
|
|
|
10290
10504
|
sent: 0,
|
|
10291
10505
|
errors: 0
|
|
10292
10506
|
};
|
|
10293
|
-
|
|
10294
|
-
|
|
10295
|
-
|
|
10296
|
-
|
|
10297
|
-
|
|
10298
|
-
|
|
10299
|
-
|
|
10300
|
-
|
|
10301
|
-
|
|
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
|
+
});
|
|
10302
10518
|
},
|
|
10303
10519
|
async editOutboundMessage(messageId, format, content) {
|
|
10304
10520
|
const ref = await findExternalMessageByInternalId(db, "feishu", messageId);
|
|
@@ -10648,6 +10864,7 @@ function formatForFeishu(format, content) {
|
|
|
10648
10864
|
content: JSON.stringify({ text })
|
|
10649
10865
|
};
|
|
10650
10866
|
}
|
|
10867
|
+
const log = createLogger("BackgroundTasks");
|
|
10651
10868
|
function createBackgroundTasks(app, instanceId, adapterManager, kaelRuntime) {
|
|
10652
10869
|
let inboxTimer = null;
|
|
10653
10870
|
let heartbeatTimer = null;
|
|
@@ -10663,7 +10880,7 @@ function createBackgroundTasks(app, instanceId, adapterManager, kaelRuntime) {
|
|
|
10663
10880
|
const maxRetries = configs.max_retry_count ?? 3;
|
|
10664
10881
|
await resetTimedOutEntries(app.db, timeoutSeconds, maxRetries);
|
|
10665
10882
|
} catch (err) {
|
|
10666
|
-
|
|
10883
|
+
log.error({ err }, "failed to reset timed-out inbox entries");
|
|
10667
10884
|
}
|
|
10668
10885
|
}, 6e4);
|
|
10669
10886
|
heartbeatTimer = setInterval(async () => {
|
|
@@ -10674,37 +10891,40 @@ function createBackgroundTasks(app, instanceId, adapterManager, kaelRuntime) {
|
|
|
10674
10891
|
await cleanupStaleClients(app.db, staleSeconds);
|
|
10675
10892
|
const staleAgents = await markStaleAgents(app.db, staleSeconds);
|
|
10676
10893
|
if (staleAgents.length > 0) {
|
|
10677
|
-
|
|
10894
|
+
log.info({
|
|
10895
|
+
count: staleAgents.length,
|
|
10896
|
+
agentIds: staleAgents
|
|
10897
|
+
}, "marked agents as stale");
|
|
10678
10898
|
for (const agentId of staleAgents) notifyAgentEvent(app.db, agentId, "agent_stale", "medium", `Agent ${agentId} is unresponsive`).catch(() => {});
|
|
10679
10899
|
}
|
|
10680
10900
|
} catch (err) {
|
|
10681
|
-
|
|
10901
|
+
log.error({ err }, "failed to heartbeat / cleanup presence");
|
|
10682
10902
|
}
|
|
10683
10903
|
}, 3e4);
|
|
10684
10904
|
adapterOutboundTimer = setInterval(async () => {
|
|
10685
10905
|
try {
|
|
10686
10906
|
await adapterManager.processOutbound();
|
|
10687
10907
|
} catch (err) {
|
|
10688
|
-
|
|
10908
|
+
log.error({ err }, "adapter outbound processing failed");
|
|
10689
10909
|
}
|
|
10690
10910
|
}, 5e3);
|
|
10691
10911
|
if (kaelRuntime) kaelOutboundTimer = setInterval(async () => {
|
|
10692
10912
|
try {
|
|
10693
10913
|
await kaelRuntime.processOutbound();
|
|
10694
10914
|
} catch (err) {
|
|
10695
|
-
|
|
10915
|
+
log.error({ err }, "kael outbound processing failed");
|
|
10696
10916
|
}
|
|
10697
10917
|
}, 5e3);
|
|
10698
10918
|
sessionCleanupTimer = setInterval(async () => {
|
|
10699
10919
|
try {
|
|
10700
10920
|
const deleted = await cleanupStaleSessions(app.db);
|
|
10701
|
-
if (deleted > 0)
|
|
10921
|
+
if (deleted > 0) log.info({ count: deleted }, "cleaned up stale sessions");
|
|
10702
10922
|
} catch (err) {
|
|
10703
|
-
|
|
10923
|
+
log.error({ err }, "failed to clean up stale sessions");
|
|
10704
10924
|
}
|
|
10705
10925
|
}, 36e5);
|
|
10706
10926
|
heartbeatInstance(app.db, instanceId).catch((err) => {
|
|
10707
|
-
|
|
10927
|
+
log.error({ err }, "failed initial heartbeat");
|
|
10708
10928
|
});
|
|
10709
10929
|
},
|
|
10710
10930
|
stop() {
|
|
@@ -11064,11 +11284,15 @@ function createKaelRuntime(db, encryptionKey, kaelEndpoint, kaelApiKey, serverUr
|
|
|
11064
11284
|
sent: 0,
|
|
11065
11285
|
errors: 0
|
|
11066
11286
|
};
|
|
11067
|
-
|
|
11068
|
-
|
|
11069
|
-
|
|
11070
|
-
|
|
11071
|
-
|
|
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`
|
|
11072
11296
|
UPDATE inbox_entries
|
|
11073
11297
|
SET status = 'delivered', delivered_at = NOW()
|
|
11074
11298
|
WHERE id IN (
|
|
@@ -11084,75 +11308,76 @@ function createKaelRuntime(db, encryptionKey, kaelEndpoint, kaelApiKey, serverUr
|
|
|
11084
11308
|
)
|
|
11085
11309
|
RETURNING id, inbox_id, message_id, chat_id
|
|
11086
11310
|
`);
|
|
11087
|
-
|
|
11088
|
-
|
|
11089
|
-
|
|
11090
|
-
|
|
11091
|
-
|
|
11092
|
-
|
|
11093
|
-
|
|
11094
|
-
|
|
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
|
+
}
|
|
11095
11354
|
await ackEntry(db, entry.id);
|
|
11096
|
-
|
|
11097
|
-
}
|
|
11098
|
-
const messageContent = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
11099
|
-
const payload = {
|
|
11100
|
-
hub_chat_id: entry.chat_id ?? msg.chatId,
|
|
11101
|
-
hub_agent_id: config.agentId,
|
|
11102
|
-
hub_server_url: serverUrl,
|
|
11103
|
-
hub_agent_token: config.agentToken,
|
|
11104
|
-
user_id: config.kaelUserId,
|
|
11105
|
-
project_id: config.kaelProjectId,
|
|
11106
|
-
message: messageContent,
|
|
11107
|
-
sender_id: msg.senderId,
|
|
11108
|
-
format: msg.format
|
|
11109
|
-
};
|
|
11110
|
-
if (agentsMd) payload.agents_md = agentsMd;
|
|
11111
|
-
const response = await fetch(`${kaelEndpoint}/api/v1/hub/messages`, {
|
|
11112
|
-
method: "POST",
|
|
11113
|
-
headers: {
|
|
11114
|
-
"Content-Type": "application/json",
|
|
11115
|
-
...kaelApiKey ? { "X-Internal-API-Key": kaelApiKey } : {}
|
|
11116
|
-
},
|
|
11117
|
-
body: JSON.stringify(payload)
|
|
11118
|
-
});
|
|
11119
|
-
if (!response.ok) {
|
|
11120
|
-
const body = await response.text().catch(() => "");
|
|
11355
|
+
sent++;
|
|
11356
|
+
} catch (err) {
|
|
11121
11357
|
log.error({
|
|
11122
11358
|
entryId: entry.id,
|
|
11123
|
-
|
|
11124
|
-
|
|
11125
|
-
|
|
11126
|
-
|
|
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
|
+
});
|
|
11127
11367
|
errorCount++;
|
|
11128
|
-
continue;
|
|
11129
11368
|
}
|
|
11130
|
-
await ackEntry(db, entry.id);
|
|
11131
|
-
sent++;
|
|
11132
11369
|
} catch (err) {
|
|
11133
|
-
log.error({
|
|
11134
|
-
|
|
11135
|
-
|
|
11136
|
-
|
|
11137
|
-
|
|
11138
|
-
log.error({
|
|
11139
|
-
entryId: entry.id,
|
|
11140
|
-
err: nackErr
|
|
11141
|
-
}, "Failed to NACK entry");
|
|
11142
|
-
});
|
|
11143
|
-
errorCount++;
|
|
11370
|
+
log.error({ err }, "Kael outbound processing error");
|
|
11371
|
+
return {
|
|
11372
|
+
sent: 0,
|
|
11373
|
+
errors: 1
|
|
11374
|
+
};
|
|
11144
11375
|
}
|
|
11145
|
-
} catch (err) {
|
|
11146
|
-
log.error({ err }, "Kael outbound processing error");
|
|
11147
11376
|
return {
|
|
11148
|
-
sent
|
|
11149
|
-
errors:
|
|
11377
|
+
sent,
|
|
11378
|
+
errors: errorCount
|
|
11150
11379
|
};
|
|
11151
|
-
}
|
|
11152
|
-
return {
|
|
11153
|
-
sent,
|
|
11154
|
-
errors: errorCount
|
|
11155
|
-
};
|
|
11380
|
+
});
|
|
11156
11381
|
},
|
|
11157
11382
|
shutdown() {
|
|
11158
11383
|
aborted = true;
|
|
@@ -11262,7 +11487,15 @@ function createPulseAggregator(options) {
|
|
|
11262
11487
|
};
|
|
11263
11488
|
}
|
|
11264
11489
|
async function buildApp(config) {
|
|
11265
|
-
|
|
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);
|
|
11266
11499
|
const db = connectDatabase(config.database.url);
|
|
11267
11500
|
app.decorate("db", db);
|
|
11268
11501
|
app.decorate("config", config);
|
|
@@ -11282,14 +11515,23 @@ async function buildApp(config) {
|
|
|
11282
11515
|
const memberAuth = memberAuthHook(db, config.secrets.jwtSecret);
|
|
11283
11516
|
const adminOnly = requireAdminRoleHook();
|
|
11284
11517
|
const agentSelector = agentSelectorHook(db);
|
|
11285
|
-
app.setErrorHandler((error,
|
|
11286
|
-
|
|
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
|
+
});
|
|
11287
11525
|
if (error instanceof ZodError) return reply.status(400).send({
|
|
11288
11526
|
error: "Validation error",
|
|
11289
|
-
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
|
|
11290
11534
|
});
|
|
11291
|
-
app.log.error(error);
|
|
11292
|
-
return reply.status(500).send({ error: "Internal server error" });
|
|
11293
11535
|
});
|
|
11294
11536
|
await app.register(healthzRoutes);
|
|
11295
11537
|
await app.register(async (api) => {
|
|
@@ -11423,10 +11665,11 @@ async function buildApp(config) {
|
|
|
11423
11665
|
notifier,
|
|
11424
11666
|
broadcast: broadcastToAdmins
|
|
11425
11667
|
});
|
|
11668
|
+
const hotReloadLog = createLogger("HotReload");
|
|
11426
11669
|
notifier.onConfigChange((configType) => {
|
|
11427
11670
|
if (configType === "adapter_configs") {
|
|
11428
|
-
adapterManager.reload().catch((err) =>
|
|
11429
|
-
kaelRuntime?.reload().catch((err) =>
|
|
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)"));
|
|
11430
11673
|
}
|
|
11431
11674
|
});
|
|
11432
11675
|
app.addHook("onReady", async () => {
|
|
@@ -11500,13 +11743,18 @@ async function startServer(options) {
|
|
|
11500
11743
|
const config = {
|
|
11501
11744
|
...serverConfig,
|
|
11502
11745
|
webDistPath: webDistPath ?? void 0,
|
|
11503
|
-
instanceId: `srv_${randomUUID().slice(0, 8)}
|
|
11504
|
-
logger: true
|
|
11746
|
+
instanceId: `srv_${randomUUID().slice(0, 8)}`
|
|
11505
11747
|
};
|
|
11748
|
+
const { initTelemetry, shutdownTelemetry } = await import("./observability-Xi-sEZI7.mjs");
|
|
11749
|
+
await initTelemetry(serverConfig.observability.tracing, config.instanceId);
|
|
11506
11750
|
const app = await buildApp(config);
|
|
11507
11751
|
const shutdown = async () => {
|
|
11508
11752
|
process.stderr.write("\n Shutting down...\n");
|
|
11509
|
-
|
|
11753
|
+
try {
|
|
11754
|
+
await app.close();
|
|
11755
|
+
} finally {
|
|
11756
|
+
await shutdownTelemetry();
|
|
11757
|
+
}
|
|
11510
11758
|
process.exit(0);
|
|
11511
11759
|
};
|
|
11512
11760
|
process.on("SIGINT", () => void shutdown());
|
|
@@ -11896,4 +12144,4 @@ function uninstallClientService() {
|
|
|
11896
12144
|
return getClientServiceStatus();
|
|
11897
12145
|
}
|
|
11898
12146
|
//#endregion
|
|
11899
|
-
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 };
|