@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.
@@ -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-DZDhomaN.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-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-BDvasXc4.mjs
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
- /** List notifications with pagination and optional filters. */
11115
- async function listNotifications(db, orgId, query) {
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 rows = await db.select().from(notifications).where(where).orderBy(desc(notifications.createdAt)).limit(query.limit + 1);
11123
- const hasMore = rows.length > query.limit;
11124
- const items = hasMore ? rows.slice(0, query.limit) : rows;
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, organizationId) {
11137
- const [updated] = await db.update(notifications).set({ read: true }).where(and(eq(notifications.id, notificationId), eq(notifications.organizationId, organizationId))).returning();
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 as read for an organization. */
11141
- async function markAllRead(db, orgId) {
11142
- await db.update(notifications).set({ read: true }).where(and(eq(notifications.organizationId, orgId), eq(notifications.read, false)));
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 automatically.
11146
- * Fire-and-forget errors are swallowed.
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, message, chatId) {
11250
+ async function notifyAgentEvent(db, agentId, type, severity, chatId) {
11149
11251
  try {
11150
- const [agent] = await db.select({
11151
- organizationId: agents.organizationId,
11152
- name: agents.name,
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: agent.organizationId,
11256
+ organizationId: agentCtx.organizationId,
11160
11257
  type,
11161
11258
  severity,
11162
11259
  agentId,
11163
11260
  chatId: chatId ?? null,
11164
- message: resolvedMessage
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
- /** GET /admin/notifications — list notifications scoped to caller's org */
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 ws.send(sharedData);
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", `Agent ${agentId} entered error state`).catch(() => {});
13249
- else if (payload.runtimeState === "blocked" && shouldNotify(agentId, "agent_blocked")) notifyAgentEvent(app.db, agentId, "agent_blocked", "medium", `Agent ${agentId} is blocked`).catch(() => {});
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", `Agent ${agentId} completed a task`, payload.chatId).catch(() => {});
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", `Agent ${agentId} disconnected`).catch(() => {});
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", `Agent ${agentId} is unresponsive`).catch(() => {});
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
- const pkg = createRequire(import.meta.url)("../../package.json");
15615
- const COMMAND_VERSION = typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : "0.0.0";
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-DZDhomaN.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-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 };