@agent-team-foundation/first-tree-hub 0.9.0 → 0.9.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/cli/index.mjs +1 -1
- package/dist/{core-DZDhomaN.mjs → core-Y7M3d2aZ.mjs} +200 -40
- package/dist/index.mjs +1 -1
- package/dist/web/assets/{index-CKtxY9Dp.js → index-CDv9Rfc_.js} +73 -73
- package/dist/web/assets/index-DlK6gHQF.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-D9iKLIsB.css +0 -1
package/dist/cli/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import "../logger-core-2yeIU1fc-B-__AsQO.mjs";
|
|
3
3
|
import { C as serverConfigSchema, _ as loadAgents, b as resetConfig, c as saveCredentials, f as agentConfigSchema, g as initConfig, h as getConfigValue, i as loadCredentials, l as DEFAULT_CONFIG_DIR, n as ensureFreshAccessToken, o as resolveServerUrl, p as clientConfigSchema, r as ensureFreshAdminToken, s as saveAgentConfig, u as DEFAULT_DATA_DIR, w as setConfigValue, x as resetConfigMeta, y as readConfigFile } from "../bootstrap-CWcBzk6C.mjs";
|
|
4
4
|
import "../observability-CJzDFY_G-CmvgUuzc.mjs";
|
|
5
|
-
import { A as printResults, B as SdkError, C as checkDatabase, D as checkServerHealth, E as checkServerConfig, F as stopPostgres, H as cleanWorkspaces, I as ClientRuntime, L as createOwner, O as checkServerReachable, S as checkClientConfig, T as checkNodeVersion, U as applyClientLoggerConfig, V as SessionRegistry, _ as isServiceSupported, a as COMMAND_VERSION, b as runMigrations, c as promptMissingFields, d as onboardCheck, f as onboardCreate, g as installClientService, h as getClientServiceStatus, i as startServer, k as checkWebSocket, l as formatCheckReport, m as runHomeMigration, n as declineUpdate, o as isInteractive, p as saveOnboardState, r as promptUpdate, s as promptAddAgent, t as createExecuteUpdate, u as loadOnboardState, w as checkDocker, x as checkAgentConfigs, y as uninstallClientService, z as FirstTreeHubSDK } from "../core-
|
|
5
|
+
import { A as printResults, B as SdkError, C as checkDatabase, D as checkServerHealth, E as checkServerConfig, F as stopPostgres, H as cleanWorkspaces, I as ClientRuntime, L as createOwner, O as checkServerReachable, S as checkClientConfig, T as checkNodeVersion, U as applyClientLoggerConfig, V as SessionRegistry, _ as isServiceSupported, a as COMMAND_VERSION, b as runMigrations, c as promptMissingFields, d as onboardCheck, f as onboardCreate, g as installClientService, h as getClientServiceStatus, i as startServer, k as checkWebSocket, l as formatCheckReport, m as runHomeMigration, n as declineUpdate, o as isInteractive, p as saveOnboardState, r as promptUpdate, s as promptAddAgent, t as createExecuteUpdate, u as loadOnboardState, w as checkDocker, x as checkAgentConfigs, y as uninstallClientService, z as FirstTreeHubSDK } from "../core-Y7M3d2aZ.mjs";
|
|
6
6
|
import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-GlaczcVf.mjs";
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
@@ -7807,7 +7807,7 @@ var require_secure_json_parse = /* @__PURE__ */ __commonJSMin(((exports, module)
|
|
|
7807
7807
|
module.exports.scan = filter;
|
|
7808
7808
|
}));
|
|
7809
7809
|
//#endregion
|
|
7810
|
-
//#region ../server/dist/app-
|
|
7810
|
+
//#region ../server/dist/app-BcKNAbK-.mjs
|
|
7811
7811
|
var import_multipart = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7812
7812
|
const Busboy = require_main();
|
|
7813
7813
|
const os = __require("node:os");
|
|
@@ -11111,17 +11111,35 @@ async function createNotification(db, data) {
|
|
|
11111
11111
|
pushToWebhook(db, notification).catch(() => {});
|
|
11112
11112
|
return notification;
|
|
11113
11113
|
}
|
|
11114
|
-
/**
|
|
11115
|
-
|
|
11114
|
+
/**
|
|
11115
|
+
* List notifications with pagination and optional filters, scoped to the
|
|
11116
|
+
* caller's visible agents.
|
|
11117
|
+
*
|
|
11118
|
+
* Rule: a member sees a notification iff
|
|
11119
|
+
* - it carries an `agentId` the member can see
|
|
11120
|
+
* (`agents.visibility = organization` OR `agents.managerId = self`), OR
|
|
11121
|
+
* - it has no `agentId` (org-wide system notification)
|
|
11122
|
+
*
|
|
11123
|
+
* Private agents owned by other members never surface.
|
|
11124
|
+
*/
|
|
11125
|
+
async function listNotifications(db, orgId, memberId, query) {
|
|
11126
|
+
const visibleAgents = await loadVisibleAgentIds$1(db, orgId, memberId);
|
|
11127
|
+
if (query.agentId && !visibleAgents.has(query.agentId)) return {
|
|
11128
|
+
items: [],
|
|
11129
|
+
nextCursor: null
|
|
11130
|
+
};
|
|
11116
11131
|
const conditions = [eq(notifications.organizationId, orgId)];
|
|
11117
11132
|
if (query.cursor) conditions.push(lt(notifications.createdAt, new Date(query.cursor)));
|
|
11118
11133
|
if (query.severity) conditions.push(eq(notifications.severity, query.severity));
|
|
11119
11134
|
if (query.read !== void 0) conditions.push(eq(notifications.read, query.read));
|
|
11120
11135
|
if (query.agentId) conditions.push(eq(notifications.agentId, query.agentId));
|
|
11121
11136
|
const where = and(...conditions);
|
|
11122
|
-
const
|
|
11123
|
-
const
|
|
11124
|
-
const
|
|
11137
|
+
const overscanFactor = 4;
|
|
11138
|
+
const targetLimit = query.limit;
|
|
11139
|
+
const rawLimit = Math.min(targetLimit * overscanFactor + 1, 400);
|
|
11140
|
+
const visible = (await db.select().from(notifications).where(where).orderBy(desc(notifications.createdAt)).limit(rawLimit)).filter((n) => n.agentId === null || visibleAgents.has(n.agentId));
|
|
11141
|
+
const hasMore = visible.length > targetLimit;
|
|
11142
|
+
const items = hasMore ? visible.slice(0, targetLimit) : visible;
|
|
11125
11143
|
const last = items[items.length - 1];
|
|
11126
11144
|
const nextCursor = hasMore && last ? last.createdAt.toISOString() : null;
|
|
11127
11145
|
return {
|
|
@@ -11132,36 +11150,116 @@ async function listNotifications(db, orgId, query) {
|
|
|
11132
11150
|
nextCursor
|
|
11133
11151
|
};
|
|
11134
11152
|
}
|
|
11135
|
-
/** Mark a single notification as read, scoped to organization. */
|
|
11136
|
-
async function markRead(db, notificationId,
|
|
11137
|
-
const [
|
|
11153
|
+
/** Mark a single notification as read, scoped to organization + visible agents. */
|
|
11154
|
+
async function markRead(db, notificationId, orgId, memberId) {
|
|
11155
|
+
const [existing] = await db.select({
|
|
11156
|
+
id: notifications.id,
|
|
11157
|
+
agentId: notifications.agentId
|
|
11158
|
+
}).from(notifications).where(and(eq(notifications.id, notificationId), eq(notifications.organizationId, orgId))).limit(1);
|
|
11159
|
+
if (!existing) return null;
|
|
11160
|
+
if (existing.agentId) {
|
|
11161
|
+
if (!(await loadVisibleAgentIds$1(db, orgId, memberId)).has(existing.agentId)) return null;
|
|
11162
|
+
}
|
|
11163
|
+
const [updated] = await db.update(notifications).set({ read: true }).where(and(eq(notifications.id, notificationId), eq(notifications.organizationId, orgId))).returning();
|
|
11138
11164
|
return updated ?? null;
|
|
11139
11165
|
}
|
|
11140
|
-
/** Mark all notifications
|
|
11141
|
-
async function markAllRead(db, orgId) {
|
|
11142
|
-
|
|
11166
|
+
/** Mark all notifications visible to this member as read. */
|
|
11167
|
+
async function markAllRead(db, orgId, memberId) {
|
|
11168
|
+
const visible = await loadVisibleAgentIds$1(db, orgId, memberId);
|
|
11169
|
+
const idsToMark = (await db.select({
|
|
11170
|
+
id: notifications.id,
|
|
11171
|
+
agentId: notifications.agentId
|
|
11172
|
+
}).from(notifications).where(and(eq(notifications.organizationId, orgId), eq(notifications.read, false))).limit(1e3)).filter((n) => n.agentId === null || visible.has(n.agentId)).map((n) => n.id);
|
|
11173
|
+
if (idsToMark.length === 0) return;
|
|
11174
|
+
const batchSize = 200;
|
|
11175
|
+
for (let i = 0; i < idsToMark.length; i += batchSize) {
|
|
11176
|
+
const batch = idsToMark.slice(i, i + batchSize);
|
|
11177
|
+
await db.update(notifications).set({ read: true }).where(and(eq(notifications.organizationId, orgId), eq(notifications.read, false), inArray(notifications.id, batch)));
|
|
11178
|
+
}
|
|
11179
|
+
}
|
|
11180
|
+
/**
|
|
11181
|
+
* Shared visibility predicate. Mirrors
|
|
11182
|
+
* {@link packages/server/src/services/access-control.ts#agentVisibilityCondition}
|
|
11183
|
+
* but returns a Set because the notification query joins are mostly in Node.
|
|
11184
|
+
*/
|
|
11185
|
+
async function loadVisibleAgentIds$1(db, orgId, memberId) {
|
|
11186
|
+
const rows = await db.select({ id: agents.uuid }).from(agents).where(and(eq(agents.organizationId, orgId), ne(agents.status, AGENT_STATUSES.DELETED), or(eq(agents.visibility, AGENT_VISIBILITY.ORGANIZATION), eq(agents.managerId, memberId))));
|
|
11187
|
+
return new Set(rows.map((r) => r.id));
|
|
11188
|
+
}
|
|
11189
|
+
async function resolveAgentContext(db, agentId) {
|
|
11190
|
+
const [agent] = await db.select({
|
|
11191
|
+
organizationId: agents.organizationId,
|
|
11192
|
+
name: agents.name,
|
|
11193
|
+
displayName: agents.displayName,
|
|
11194
|
+
clientId: agents.clientId
|
|
11195
|
+
}).from(agents).where(eq(agents.uuid, agentId)).limit(1);
|
|
11196
|
+
if (!agent) return null;
|
|
11197
|
+
let clientLabel = null;
|
|
11198
|
+
if (agent.clientId) {
|
|
11199
|
+
const [client] = await db.select({
|
|
11200
|
+
hostname: clients.hostname,
|
|
11201
|
+
id: clients.id
|
|
11202
|
+
}).from(clients).where(eq(clients.id, agent.clientId)).limit(1);
|
|
11203
|
+
clientLabel = client?.hostname ?? agent.clientId;
|
|
11204
|
+
}
|
|
11205
|
+
return {
|
|
11206
|
+
organizationId: agent.organizationId,
|
|
11207
|
+
agentName: agent.displayName ?? agent.name ?? agentId,
|
|
11208
|
+
clientId: agent.clientId,
|
|
11209
|
+
clientLabel
|
|
11210
|
+
};
|
|
11211
|
+
}
|
|
11212
|
+
async function resolveChatContext(db, chatId) {
|
|
11213
|
+
const [chat] = await db.select({ topic: chats.topic }).from(chats).where(eq(chats.id, chatId)).limit(1);
|
|
11214
|
+
const shortId = chatId.slice(0, 8);
|
|
11215
|
+
return { chatLabel: chat?.topic && chat.topic.trim().length > 0 ? chat.topic.trim() : `Chat ${shortId}` };
|
|
11216
|
+
}
|
|
11217
|
+
/**
|
|
11218
|
+
* Compose a human-readable message for each notification type.
|
|
11219
|
+
*
|
|
11220
|
+
* Keep subjects consistent with what the dashboard shows the member:
|
|
11221
|
+
* - Session-scoped events → subject is the chat (topic / "Chat xxxxxxxx")
|
|
11222
|
+
* - Client-scoped events → subject is the computer (hostname / clientId)
|
|
11223
|
+
* - Agent-scoped events → subject is the agent display name
|
|
11224
|
+
*/
|
|
11225
|
+
function composeMessage(type, agentCtx, chatCtx) {
|
|
11226
|
+
const agent = agentCtx.agentName;
|
|
11227
|
+
const computer = agentCtx.clientLabel ?? "Unknown computer";
|
|
11228
|
+
const chat = chatCtx?.chatLabel ?? null;
|
|
11229
|
+
switch (type) {
|
|
11230
|
+
case "session_completed": return chat ? `${chat} completed` : `${agent} completed a task`;
|
|
11231
|
+
case "session_error": return chat ? `${chat} hit an error` : `${agent} hit a session error`;
|
|
11232
|
+
case "agent_disconnected": return `Computer ${computer} disconnected`;
|
|
11233
|
+
case "agent_connected": return `Computer ${computer} reconnected`;
|
|
11234
|
+
case "agent_stale": return `Computer ${computer} is unresponsive`;
|
|
11235
|
+
case "agent_error": return `${agent} entered error state`;
|
|
11236
|
+
case "agent_blocked": return `${agent} is blocked`;
|
|
11237
|
+
case "agent_needs_decision": return chat ? `${agent} needs a decision in ${chat}` : `${agent} needs a decision`;
|
|
11238
|
+
default: return `${agent} event`;
|
|
11239
|
+
}
|
|
11143
11240
|
}
|
|
11144
11241
|
/**
|
|
11145
|
-
* Convenience: create a notification for an agent event, resolving org
|
|
11146
|
-
*
|
|
11242
|
+
* Convenience: create a notification for an agent event, resolving org,
|
|
11243
|
+
* agent display name, computer hostname, and chat topic automatically.
|
|
11244
|
+
* Callers supply the event type and severity; the message text is generated
|
|
11245
|
+
* here so language/phrasing is centralized (see {@link composeMessage}).
|
|
11246
|
+
*
|
|
11247
|
+
* Fire-and-forget — errors are swallowed so event producers never fail just
|
|
11248
|
+
* because the notification pipeline is unhealthy.
|
|
11147
11249
|
*/
|
|
11148
|
-
async function notifyAgentEvent(db, agentId, type, severity,
|
|
11250
|
+
async function notifyAgentEvent(db, agentId, type, severity, chatId) {
|
|
11149
11251
|
try {
|
|
11150
|
-
const
|
|
11151
|
-
|
|
11152
|
-
|
|
11153
|
-
displayName: agents.displayName
|
|
11154
|
-
}).from(agents).where(eq(agents.uuid, agentId)).limit(1);
|
|
11155
|
-
if (!agent) return;
|
|
11156
|
-
const name = agent.displayName ?? agent.name ?? agentId;
|
|
11157
|
-
const resolvedMessage = message.replace(agentId, name);
|
|
11252
|
+
const agentCtx = await resolveAgentContext(db, agentId);
|
|
11253
|
+
if (!agentCtx) return;
|
|
11254
|
+
const message = composeMessage(type, agentCtx, chatId ? await resolveChatContext(db, chatId) : null);
|
|
11158
11255
|
await createNotification(db, {
|
|
11159
|
-
organizationId:
|
|
11256
|
+
organizationId: agentCtx.organizationId,
|
|
11160
11257
|
type,
|
|
11161
11258
|
severity,
|
|
11162
11259
|
agentId,
|
|
11163
11260
|
chatId: chatId ?? null,
|
|
11164
|
-
|
|
11261
|
+
clientId: agentCtx.clientId,
|
|
11262
|
+
message
|
|
11165
11263
|
});
|
|
11166
11264
|
} catch {}
|
|
11167
11265
|
}
|
|
@@ -11169,6 +11267,7 @@ function pushToAdminWs(notification) {
|
|
|
11169
11267
|
broadcastToAdmins({
|
|
11170
11268
|
type: "notification",
|
|
11171
11269
|
organizationId: notification.organizationId,
|
|
11270
|
+
agentId: notification.agentId ?? null,
|
|
11172
11271
|
data: notification
|
|
11173
11272
|
});
|
|
11174
11273
|
}
|
|
@@ -11185,26 +11284,34 @@ async function pushToWebhook(db, notification) {
|
|
|
11185
11284
|
} catch {}
|
|
11186
11285
|
}
|
|
11187
11286
|
async function adminNotificationRoutes(app) {
|
|
11188
|
-
/**
|
|
11287
|
+
/**
|
|
11288
|
+
* GET /admin/notifications — list notifications visible to the caller.
|
|
11289
|
+
*
|
|
11290
|
+
* Scoped by (a) organization (via JWT) and (b) per-agent visibility: the
|
|
11291
|
+
* member only sees notifications whose agentId is visible to them
|
|
11292
|
+
* (organization-visible agents or agents they manage), plus org-wide
|
|
11293
|
+
* system notifications with no agentId. This mirrors the rule the admin
|
|
11294
|
+
* WebSocket route enforces on live pushes — REST and WS stay in sync.
|
|
11295
|
+
*/
|
|
11189
11296
|
app.get("/", async (request) => {
|
|
11190
11297
|
const member = requireMember(request);
|
|
11191
11298
|
const query = notificationQuerySchema.parse(request.query);
|
|
11192
|
-
return listNotifications(app.db, member.organizationId, query);
|
|
11299
|
+
return listNotifications(app.db, member.organizationId, member.memberId, query);
|
|
11193
11300
|
});
|
|
11194
11301
|
/** POST /admin/notifications/:id/read — mark a single notification as read */
|
|
11195
11302
|
app.post("/:id/read", async (request) => {
|
|
11196
11303
|
const member = requireMember(request);
|
|
11197
|
-
const result = await markRead(app.db, request.params.id, member.organizationId);
|
|
11304
|
+
const result = await markRead(app.db, request.params.id, member.organizationId, member.memberId);
|
|
11198
11305
|
if (!result) throw new NotFoundError(`Notification "${request.params.id}" not found`);
|
|
11199
11306
|
return {
|
|
11200
11307
|
...result,
|
|
11201
11308
|
createdAt: result.createdAt.toISOString()
|
|
11202
11309
|
};
|
|
11203
11310
|
});
|
|
11204
|
-
/** POST /admin/notifications/read-all — mark all notifications as read */
|
|
11311
|
+
/** POST /admin/notifications/read-all — mark all visible notifications as read */
|
|
11205
11312
|
app.post("/read-all", async (request) => {
|
|
11206
11313
|
const member = requireMember(request);
|
|
11207
|
-
await markAllRead(app.db, member.organizationId);
|
|
11314
|
+
await markAllRead(app.db, member.organizationId, member.memberId);
|
|
11208
11315
|
return { status: "ok" };
|
|
11209
11316
|
});
|
|
11210
11317
|
}
|
|
@@ -12315,6 +12422,8 @@ function adminWsRoutes(notifier, jwtSecret) {
|
|
|
12315
12422
|
const orgId = payload.organizationId;
|
|
12316
12423
|
if (typeof orgId !== "string" || orgId.length === 0) return;
|
|
12317
12424
|
const isPulseTick = payload.type === "pulse:tick" && typeof payload.agents === "object" && payload.agents !== null;
|
|
12425
|
+
const isNotification = payload.type === "notification";
|
|
12426
|
+
const notificationAgentId = isNotification && typeof payload.agentId === "string" && payload.agentId.length > 0 ? payload.agentId : null;
|
|
12318
12427
|
const sharedData = isPulseTick ? null : JSON.stringify(payload);
|
|
12319
12428
|
for (const [ws, meta] of adminSockets) {
|
|
12320
12429
|
if (ws.readyState !== 1 || meta.organizationId !== orgId) continue;
|
|
@@ -12324,7 +12433,10 @@ function adminWsRoutes(notifier, jwtSecret) {
|
|
|
12324
12433
|
...payload,
|
|
12325
12434
|
agents: filtered
|
|
12326
12435
|
}));
|
|
12327
|
-
} else
|
|
12436
|
+
} else {
|
|
12437
|
+
if (isNotification && notificationAgentId && !meta.visibleAgentIds.has(notificationAgentId)) continue;
|
|
12438
|
+
ws.send(sharedData);
|
|
12439
|
+
}
|
|
12328
12440
|
}
|
|
12329
12441
|
}
|
|
12330
12442
|
registerAdminBroadcaster(broadcastOrgScoped);
|
|
@@ -13245,8 +13357,8 @@ function clientWsRoutes(notifier, instanceId) {
|
|
|
13245
13357
|
organizationId: session.organizationId,
|
|
13246
13358
|
notifier
|
|
13247
13359
|
});
|
|
13248
|
-
if (payload.runtimeState === "error" && shouldNotify(agentId, "agent_error")) notifyAgentEvent(app.db, agentId, "agent_error", "high"
|
|
13249
|
-
else if (payload.runtimeState === "blocked" && shouldNotify(agentId, "agent_blocked")) notifyAgentEvent(app.db, agentId, "agent_blocked", "medium"
|
|
13360
|
+
if (payload.runtimeState === "error" && shouldNotify(agentId, "agent_error")) notifyAgentEvent(app.db, agentId, "agent_error", "high").catch(() => {});
|
|
13361
|
+
else if (payload.runtimeState === "blocked" && shouldNotify(agentId, "agent_blocked")) notifyAgentEvent(app.db, agentId, "agent_blocked", "medium").catch(() => {});
|
|
13250
13362
|
} else if (type === "session:event") {
|
|
13251
13363
|
const agentId = parsed.data.agentId;
|
|
13252
13364
|
if (!agentId || !boundAgents.has(agentId)) {
|
|
@@ -13277,7 +13389,7 @@ function clientWsRoutes(notifier, instanceId) {
|
|
|
13277
13389
|
return;
|
|
13278
13390
|
}
|
|
13279
13391
|
const payload = sessionCompletionMessageSchema.parse(msg);
|
|
13280
|
-
if (shouldNotify(agentId, `session_completed:${payload.chatId}`)) notifyAgentEvent(app.db, agentId, "session_completed", "low",
|
|
13392
|
+
if (shouldNotify(agentId, `session_completed:${payload.chatId}`)) notifyAgentEvent(app.db, agentId, "session_completed", "low", payload.chatId).catch(() => {});
|
|
13281
13393
|
} else if (type === "heartbeat") {
|
|
13282
13394
|
if (clientId) {
|
|
13283
13395
|
await heartbeatClient(app.db, clientId);
|
|
@@ -13302,7 +13414,7 @@ function clientWsRoutes(notifier, instanceId) {
|
|
|
13302
13414
|
notifier.unsubscribe(info.inboxId, socket);
|
|
13303
13415
|
if (getAgentClientId(agentId) === clientId) try {
|
|
13304
13416
|
await unbindAgent(app.db, agentId);
|
|
13305
|
-
if (shouldNotify(agentId, "agent_disconnected")) notifyAgentEvent(app.db, agentId, "agent_disconnected", "medium"
|
|
13417
|
+
if (shouldNotify(agentId, "agent_disconnected")) notifyAgentEvent(app.db, agentId, "agent_disconnected", "medium").catch(() => {});
|
|
13306
13418
|
} catch {}
|
|
13307
13419
|
}
|
|
13308
13420
|
boundAgents.clear();
|
|
@@ -14804,7 +14916,7 @@ function createBackgroundTasks(app, instanceId, adapterManager, kaelRuntime) {
|
|
|
14804
14916
|
count: staleAgents.length,
|
|
14805
14917
|
agentIds: staleAgents
|
|
14806
14918
|
}, "marked agents as stale");
|
|
14807
|
-
for (const agentId of staleAgents) notifyAgentEvent(app.db, agentId, "agent_stale", "medium"
|
|
14919
|
+
for (const agentId of staleAgents) notifyAgentEvent(app.db, agentId, "agent_stale", "medium").catch(() => {});
|
|
14808
14920
|
}
|
|
14809
14921
|
} catch (err) {
|
|
14810
14922
|
log.error({ err }, "failed to heartbeat / cleanup presence");
|
|
@@ -15390,7 +15502,7 @@ function createPulseAggregator(options) {
|
|
|
15390
15502
|
* Returning a string (rather than undefined) keeps the welcome frame well-
|
|
15391
15503
|
* formed — the client treats the value advisorily.
|
|
15392
15504
|
*/
|
|
15393
|
-
function resolveCommandVersion(injected) {
|
|
15505
|
+
function resolveCommandVersion$1(injected) {
|
|
15394
15506
|
if (injected && injected.trim().length > 0) return injected;
|
|
15395
15507
|
try {
|
|
15396
15508
|
const pkg = createRequire(import.meta.url)("../package.json");
|
|
@@ -15411,7 +15523,7 @@ async function buildApp(config) {
|
|
|
15411
15523
|
const db = connectDatabase(config.database.url);
|
|
15412
15524
|
app.decorate("db", db);
|
|
15413
15525
|
app.decorate("config", config);
|
|
15414
|
-
const commandVersion = resolveCommandVersion(config.commandVersion);
|
|
15526
|
+
const commandVersion = resolveCommandVersion$1(config.commandVersion);
|
|
15415
15527
|
app.decorate("commandVersion", commandVersion);
|
|
15416
15528
|
app.log.info({ commandVersion }, "Hub server advertising command version");
|
|
15417
15529
|
const listenClient = postgres(config.database.url, { max: 1 });
|
|
@@ -15611,8 +15723,56 @@ async function buildApp(config) {
|
|
|
15611
15723
|
}
|
|
15612
15724
|
//#endregion
|
|
15613
15725
|
//#region src/core/version.ts
|
|
15614
|
-
|
|
15615
|
-
|
|
15726
|
+
/**
|
|
15727
|
+
* Version of the consumer-facing `@agent-team-foundation/first-tree-hub`
|
|
15728
|
+
* package. Read once at module load so the CLI, client runtime, and server
|
|
15729
|
+
* bootstrap all quote the same string.
|
|
15730
|
+
*
|
|
15731
|
+
* Path-based lookups (`require("../../package.json")`) do not survive the
|
|
15732
|
+
* tsdown bundle: the source lives at `src/core/version.ts` but every
|
|
15733
|
+
* emitted chunk lands in `dist/` — shifting the relative depth by one and
|
|
15734
|
+
* pointing at `packages/package.json` instead of our own manifest (the
|
|
15735
|
+
* v0.9.1 "Cannot find module ../../package.json" crash). Walk up from this
|
|
15736
|
+
* module's URL and accept the first `package.json` whose `name` matches, so
|
|
15737
|
+
* dev runs (`tsx src/cli/index.ts`) and the published bundle
|
|
15738
|
+
* (`dist/cli/index.mjs`) both resolve the same file.
|
|
15739
|
+
*/
|
|
15740
|
+
const PACKAGE_NAME$1 = "@agent-team-foundation/first-tree-hub";
|
|
15741
|
+
/**
|
|
15742
|
+
* Sentinel returned when the walker exhausts every parent directory without
|
|
15743
|
+
* finding our manifest. Deliberately NOT valid SemVer so the client-side
|
|
15744
|
+
* `UpdateManager` drops into its `semver.valid(current) === false` warn-and-
|
|
15745
|
+
* skip branch instead of treating it as `< target` and triggering a spurious
|
|
15746
|
+
* self-update loop (the scenario where the startup crash this module fixes
|
|
15747
|
+
* would otherwise quietly reincarnate as repeated `npm install -g @latest`).
|
|
15748
|
+
*/
|
|
15749
|
+
const UNRESOLVED_VERSION = "unknown";
|
|
15750
|
+
/**
|
|
15751
|
+
* Exported for tests. Walks up from `moduleUrl`'s directory looking for a
|
|
15752
|
+
* `package.json` whose `name` field equals {@link PACKAGE_NAME}. Returns
|
|
15753
|
+
* {@link UNRESOLVED_VERSION} as a last-resort fallback so the CLI never
|
|
15754
|
+
* crashes on a missing manifest.
|
|
15755
|
+
*/
|
|
15756
|
+
function resolveCommandVersion(moduleUrl = import.meta.url) {
|
|
15757
|
+
let dir = dirname(fileURLToPath(moduleUrl));
|
|
15758
|
+
for (let i = 0; i < 10; i++) {
|
|
15759
|
+
try {
|
|
15760
|
+
const pkg = JSON.parse(readFileSync(resolve(dir, "package.json"), "utf8"));
|
|
15761
|
+
if (pkg.name === PACKAGE_NAME$1 && typeof pkg.version === "string" && pkg.version.length > 0) return pkg.version;
|
|
15762
|
+
} catch (err) {
|
|
15763
|
+
const code = err.code;
|
|
15764
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") {
|
|
15765
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
15766
|
+
process.stderr.write(`[first-tree-hub] warning: could not read ${dir}/package.json: ${message}\n`);
|
|
15767
|
+
}
|
|
15768
|
+
}
|
|
15769
|
+
const parent = dirname(dir);
|
|
15770
|
+
if (parent === dir) break;
|
|
15771
|
+
dir = parent;
|
|
15772
|
+
}
|
|
15773
|
+
return UNRESOLVED_VERSION;
|
|
15774
|
+
}
|
|
15775
|
+
const COMMAND_VERSION = resolveCommandVersion();
|
|
15616
15776
|
//#endregion
|
|
15617
15777
|
//#region src/core/server.ts
|
|
15618
15778
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "./logger-core-2yeIU1fc-B-__AsQO.mjs";
|
|
2
2
|
import { a as resolveAccessToken, n as ensureFreshAccessToken, o as resolveServerUrl, r as ensureFreshAdminToken } from "./bootstrap-CWcBzk6C.mjs";
|
|
3
3
|
import "./observability-CJzDFY_G-CmvgUuzc.mjs";
|
|
4
|
-
import { A as printResults, B as SdkError, C as checkDatabase, D as checkServerHealth, E as checkServerConfig, F as stopPostgres, I as ClientRuntime, L as createOwner, M as status, N as ensurePostgres, O as checkServerReachable, P as isDockerAvailable, R as hasUser, S as checkClientConfig, T as checkNodeVersion, _ as isServiceSupported, b as runMigrations, c as promptMissingFields, d as onboardCheck, f as onboardCreate, g as installClientService, h as getClientServiceStatus, i as startServer, j as blank, k as checkWebSocket, l as formatCheckReport, m as runHomeMigration, o as isInteractive, s as promptAddAgent, v as resolveCliInvocation, w as checkDocker, x as checkAgentConfigs, y as uninstallClientService, z as FirstTreeHubSDK } from "./core-
|
|
4
|
+
import { A as printResults, B as SdkError, C as checkDatabase, D as checkServerHealth, E as checkServerConfig, F as stopPostgres, I as ClientRuntime, L as createOwner, M as status, N as ensurePostgres, O as checkServerReachable, P as isDockerAvailable, R as hasUser, S as checkClientConfig, T as checkNodeVersion, _ as isServiceSupported, b as runMigrations, c as promptMissingFields, d as onboardCheck, f as onboardCreate, g as installClientService, h as getClientServiceStatus, i as startServer, j as blank, k as checkWebSocket, l as formatCheckReport, m as runHomeMigration, o as isInteractive, s as promptAddAgent, v as resolveCliInvocation, w as checkDocker, x as checkAgentConfigs, y as uninstallClientService, z as FirstTreeHubSDK } from "./core-Y7M3d2aZ.mjs";
|
|
5
5
|
import { n as bindFeishuUser, t as bindFeishuBot } from "./feishu-GlaczcVf.mjs";
|
|
6
6
|
export { ClientRuntime, FirstTreeHubSDK, SdkError, bindFeishuBot, bindFeishuUser, blank, checkAgentConfigs, checkClientConfig, checkDatabase, checkDocker, checkNodeVersion, checkServerConfig, checkServerHealth, checkServerReachable, checkWebSocket, createOwner, ensureFreshAccessToken, ensureFreshAdminToken, ensurePostgres, formatCheckReport, getClientServiceStatus, hasUser, installClientService, isDockerAvailable, isInteractive, isServiceSupported, onboardCheck, onboardCreate, printResults, promptAddAgent, promptMissingFields, resolveAccessToken, resolveCliInvocation, resolveServerUrl, runHomeMigration, runMigrations, startServer, status, stopPostgres, uninstallClientService };
|