@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.
- package/dist/{bootstrap-CRDR6NwE.mjs → bootstrap-99vUYmLs.mjs} +55 -21
- package/dist/cli/index.mjs +8 -5
- package/dist/{core-BXS5ppsG.mjs → core-CNR-lUlr.mjs} +649 -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/dist/web/assets/index-7iSpxOWW.js +333 -0
- package/dist/web/assets/index-Cze8BC63.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/rolldown-runtime-twds-ZHy.mjs +0 -14
- package/dist/web/assets/index-COlVuDVR.css +0 -1
- package/dist/web/assets/index-KhUJU9Uf.js +0 -333
|
@@ -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",
|
|
@@ -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(
|
|
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-
|
|
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-
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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", {
|
|
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) =>
|
|
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) =>
|
|
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
|
-
|
|
8540
|
-
|
|
8541
|
-
|
|
8542
|
-
}
|
|
8543
|
-
|
|
8544
|
-
|
|
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) =>
|
|
8890
|
+
app.adapterManager.editOutboundMessage(msg.id, msg.format, msg.content).catch((err) => log$2.error({
|
|
8629
8891
|
err,
|
|
8630
8892
|
messageId: msg.id
|
|
8631
|
-
}, "
|
|
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", {
|
|
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
|
-
|
|
8903
|
-
if (
|
|
8904
|
-
|
|
8905
|
-
|
|
8906
|
-
|
|
8907
|
-
|
|
8908
|
-
|
|
8909
|
-
|
|
8910
|
-
|
|
8911
|
-
|
|
8912
|
-
|
|
8913
|
-
|
|
8914
|
-
|
|
8915
|
-
|
|
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:
|
|
8918
|
-
|
|
9199
|
+
type: "client:registered",
|
|
9200
|
+
clientId: data.clientId
|
|
8919
9201
|
}));
|
|
8920
|
-
|
|
8921
|
-
|
|
8922
|
-
|
|
8923
|
-
|
|
8924
|
-
|
|
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
|
-
|
|
8944
|
-
|
|
8945
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
8953
|
-
|
|
8954
|
-
|
|
8955
|
-
|
|
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
|
-
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
|
|
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
|
-
|
|
9001
|
-
|
|
9002
|
-
|
|
9003
|
-
|
|
9004
|
-
|
|
9005
|
-
|
|
9006
|
-
|
|
9007
|
-
|
|
9008
|
-
|
|
9009
|
-
|
|
9010
|
-
|
|
9011
|
-
|
|
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: "
|
|
9077
|
-
|
|
9293
|
+
type: "agent:bound",
|
|
9294
|
+
ref,
|
|
9295
|
+
agentId: agent.id,
|
|
9296
|
+
displayName: agent.displayName,
|
|
9297
|
+
agentType: agent.type
|
|
9078
9298
|
}));
|
|
9079
|
-
|
|
9080
|
-
|
|
9081
|
-
|
|
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:
|
|
9304
|
+
message: "Agent not bound"
|
|
9089
9305
|
}));
|
|
9306
|
+
return;
|
|
9090
9307
|
}
|
|
9091
|
-
|
|
9092
|
-
|
|
9093
|
-
|
|
9094
|
-
|
|
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: "
|
|
9097
|
-
|
|
9314
|
+
type: "agent:unbound",
|
|
9315
|
+
agentId
|
|
9098
9316
|
}));
|
|
9099
|
-
|
|
9100
|
-
|
|
9101
|
-
|
|
9102
|
-
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
|
|
9106
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
10215
|
-
|
|
10216
|
-
|
|
10217
|
-
|
|
10218
|
-
|
|
10219
|
-
|
|
10220
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
10921
|
+
if (deleted > 0) log.info({ count: deleted }, "cleaned up stale sessions");
|
|
10623
10922
|
} catch (err) {
|
|
10624
|
-
|
|
10923
|
+
log.error({ err }, "failed to clean up stale sessions");
|
|
10625
10924
|
}
|
|
10626
10925
|
}, 36e5);
|
|
10627
10926
|
heartbeatInstance(app.db, instanceId).catch((err) => {
|
|
10628
|
-
|
|
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
|
-
|
|
10989
|
-
|
|
10990
|
-
|
|
10991
|
-
|
|
10992
|
-
|
|
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
|
-
|
|
11009
|
-
|
|
11010
|
-
|
|
11011
|
-
|
|
11012
|
-
|
|
11013
|
-
|
|
11014
|
-
|
|
11015
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11045
|
-
|
|
11046
|
-
|
|
11047
|
-
|
|
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
|
-
|
|
11056
|
-
|
|
11057
|
-
|
|
11058
|
-
|
|
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
|
|
11070
|
-
errors:
|
|
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
|
-
|
|
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,
|
|
11207
|
-
|
|
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) =>
|
|
11350
|
-
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)"));
|
|
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
|
-
|
|
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 };
|