@agent-team-foundation/first-tree-hub 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { S as setConfigValue, a as getGitHubUsername, b as resolveConfigReadonly, c as DEFAULT_CONFIG_DIR, d as agentConfigSchema, f as clientConfigSchema, g as loadAgents, h as initConfig, p as collectMissingPrompts, s as resolveServerUrl, t as bootstrapToken$1, u as DEFAULT_HOME_DIR$1, x as serverConfigSchema } from "./bootstrap-uyPaaI05.mjs";
1
+ import { S as setConfigValue, a as getGitHubUsername, b as resolveConfigReadonly, c as DEFAULT_CONFIG_DIR, d as agentConfigSchema, f as clientConfigSchema, g as loadAgents, h as initConfig, p as collectMissingPrompts, s as resolveServerUrl, t as bootstrapToken$1, u as DEFAULT_HOME_DIR$1, x as serverConfigSchema } from "./bootstrap-BU_7B03u.mjs";
2
2
  import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
3
3
  import { dirname, join, resolve } from "node:path";
4
4
  import { EventEmitter } from "node:events";
@@ -45,7 +45,7 @@ var FirstTreeHubSDK = class {
45
45
  async register() {
46
46
  const agent = await this.requestJson("/api/v1/agent/me");
47
47
  return {
48
- agentId: agent.id,
48
+ agentId: agent.uuid,
49
49
  inboxId: agent.inboxId,
50
50
  status: agent.status,
51
51
  displayName: agent.displayName,
@@ -79,8 +79,8 @@ var FirstTreeHubSDK = class {
79
79
  });
80
80
  }
81
81
  /** Send a direct message to another agent. */
82
- async sendToAgent(agentId, data) {
83
- return this.requestJson(`/api/v1/agent/agents/${agentId}/messages`, {
82
+ async sendToAgent(agentName, data) {
83
+ return this.requestJson(`/api/v1/agent/agents/${agentName}/messages`, {
84
84
  method: "POST",
85
85
  body: JSON.stringify(data)
86
86
  });
@@ -2408,7 +2408,7 @@ const AGENT_STATUSES = {
2408
2408
  };
2409
2409
  z.enum(["active", "suspended"]);
2410
2410
  const createAgentSchema = z.object({
2411
- id: z.string().min(1).max(100).regex(/^[a-z0-9_-]+$/, "Only lowercase alphanumeric, hyphens, and underscores").optional(),
2411
+ name: z.string().min(1).max(100).regex(/^[a-z0-9_-]+$/, "Only lowercase alphanumeric, hyphens, and underscores").optional(),
2412
2412
  type: agentTypeSchema,
2413
2413
  displayName: z.string().max(200).optional(),
2414
2414
  delegateMention: z.string().max(100).optional(),
@@ -2424,7 +2424,8 @@ const updateAgentSchema = z.object({
2424
2424
  metadata: z.record(z.string(), z.unknown()).optional()
2425
2425
  });
2426
2426
  z.object({
2427
- id: z.string(),
2427
+ uuid: z.string(),
2428
+ name: z.string().nullable(),
2428
2429
  organizationId: z.string(),
2429
2430
  type: agentTypeSchema,
2430
2431
  displayName: z.string().nullable(),
@@ -2573,7 +2574,7 @@ const SYSTEM_CONFIG_DEFAULTS = {
2573
2574
  [SYSTEM_CONFIG_KEYS.PRESENCE_CLEANUP_SECONDS]: 60
2574
2575
  };
2575
2576
  //#endregion
2576
- //#region ../server/dist/app-41WnR_ri.mjs
2577
+ //#region ../server/dist/app-dUnTcJpC.mjs
2577
2578
  var __defProp = Object.defineProperty;
2578
2579
  var __exportAll = (all, no_symbols) => {
2579
2580
  let target = {};
@@ -2586,7 +2587,8 @@ var __exportAll = (all, no_symbols) => {
2586
2587
  };
2587
2588
  /** Agent registration. Each agent owns a unique inboxId for message delivery. */
2588
2589
  const agents = pgTable("agents", {
2589
- id: text("id").primaryKey(),
2590
+ uuid: text("uuid").primaryKey(),
2591
+ name: text("name"),
2590
2592
  organizationId: text("organization_id").notNull().default("default"),
2591
2593
  type: text("type").notNull(),
2592
2594
  displayName: text("display_name"),
@@ -2597,13 +2599,13 @@ const agents = pgTable("agents", {
2597
2599
  metadata: jsonb("metadata").$type().notNull().default({}),
2598
2600
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
2599
2601
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
2600
- }, (table) => [index("idx_agents_org").on(table.organizationId)]);
2602
+ }, (table) => [index("idx_agents_org").on(table.organizationId), unique("uq_agents_org_name").on(table.organizationId, table.name)]);
2601
2603
  /** Maps external user identities to internal Agents. */
2602
2604
  const adapterAgentMappings = pgTable("adapter_agent_mappings", {
2603
2605
  id: serial("id").primaryKey(),
2604
2606
  platform: text("platform").notNull(),
2605
2607
  externalUserId: text("external_user_id").notNull(),
2606
- agentId: text("agent_id").notNull().references(() => agents.id),
2608
+ agentId: text("agent_id").notNull().references(() => agents.uuid),
2607
2609
  boundVia: text("bound_via"),
2608
2610
  displayName: text("display_name"),
2609
2611
  metadata: jsonb("metadata").$type().notNull().default({}),
@@ -2661,7 +2663,7 @@ const chats = pgTable("chats", {
2661
2663
  /** Chat participants (M:N). */
2662
2664
  const chatParticipants = pgTable("chat_participants", {
2663
2665
  chatId: text("chat_id").notNull().references(() => chats.id, { onDelete: "cascade" }),
2664
- agentId: text("agent_id").notNull().references(() => agents.id),
2666
+ agentId: text("agent_id").notNull().references(() => agents.uuid),
2665
2667
  role: text("role").notNull().default("member"),
2666
2668
  mode: text("mode").notNull().default("full"),
2667
2669
  joinedAt: timestamp("joined_at", { withTimezone: true }).notNull().defaultNow()
@@ -2778,7 +2780,7 @@ async function findOrCreateChatForChannel(db, data) {
2778
2780
  const chatId = randomUUID();
2779
2781
  const internalType = data.chatType === "p2p" ? "direct" : "group";
2780
2782
  return db.transaction(async (tx) => {
2781
- const [botAgent] = await tx.select({ organizationId: agents.organizationId }).from(agents).where(eq(agents.id, data.botAgentId)).limit(1);
2783
+ const [botAgent] = await tx.select({ organizationId: agents.organizationId }).from(agents).where(eq(agents.uuid, data.botAgentId)).limit(1);
2782
2784
  const orgId = botAgent?.organizationId ?? "default";
2783
2785
  await tx.insert(chats).values({
2784
2786
  id: chatId,
@@ -2861,10 +2863,10 @@ async function adminAdapterMappingRoutes(app) {
2861
2863
  app.post("/", async (request, reply) => {
2862
2864
  const body = createAdapterMappingSchema.parse(request.body);
2863
2865
  const [agent] = await app.db.select({
2864
- id: agents.id,
2866
+ id: agents.uuid,
2865
2867
  type: agents.type,
2866
2868
  status: agents.status
2867
- }).from(agents).where(eq(agents.id, body.agentId)).limit(1);
2869
+ }).from(agents).where(eq(agents.uuid, body.agentId)).limit(1);
2868
2870
  if (!agent || agent.status === "deleted") throw new NotFoundError(`Agent "${body.agentId}" not found`);
2869
2871
  if (agent.type !== "human") throw new BadRequestError("User bindings can only be created for human agents");
2870
2872
  const row = await createAgentMapping(app.db, {
@@ -2900,7 +2902,7 @@ async function adminAdapterStatusRoutes(app) {
2900
2902
  const adapterConfigs = pgTable("adapter_configs", {
2901
2903
  id: serial("id").primaryKey(),
2902
2904
  platform: text("platform").notNull(),
2903
- agentId: text("agent_id").notNull().references(() => agents.id),
2905
+ agentId: text("agent_id").notNull().references(() => agents.uuid),
2904
2906
  credentials: jsonb("credentials").$type().notNull(),
2905
2907
  status: text("status").notNull().default("active"),
2906
2908
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
@@ -2962,9 +2964,9 @@ function requireEncryptionKey(key) {
2962
2964
  }
2963
2965
  async function validateAgentId(db, agentId) {
2964
2966
  const [agent] = await db.select({
2965
- id: agents.id,
2967
+ id: agents.uuid,
2966
2968
  type: agents.type
2967
- }).from(agents).where(and(eq(agents.id, agentId), ne(agents.status, "deleted"))).limit(1);
2969
+ }).from(agents).where(and(eq(agents.uuid, agentId), ne(agents.status, "deleted"))).limit(1);
2968
2970
  if (!agent) throw new NotFoundError(`Agent "${agentId}" not found`);
2969
2971
  if (agent.type === "human") throw new BadRequestError("Adapter configs can only be bound to non-human agents");
2970
2972
  }
@@ -3079,7 +3081,7 @@ async function adminAdapterRoutes(app) {
3079
3081
  }
3080
3082
  /** Agent online status. Tracked via WebSocket connections; stale entries are cleaned up using server_instances heartbeat. */
3081
3083
  const agentPresence = pgTable("agent_presence", {
3082
- agentId: text("agent_id").primaryKey().references(() => agents.id, { onDelete: "cascade" }),
3084
+ agentId: text("agent_id").primaryKey().references(() => agents.uuid, { onDelete: "cascade" }),
3083
3085
  status: text("status").notNull().default("offline"),
3084
3086
  instanceId: text("instance_id"),
3085
3087
  connectedAt: timestamp("connected_at", { withTimezone: true }),
@@ -3088,7 +3090,7 @@ const agentPresence = pgTable("agent_presence", {
3088
3090
  /** Agent bearer tokens. Multiple tokens can coexist for zero-downtime rotation. */
3089
3091
  const agentTokens = pgTable("agent_tokens", {
3090
3092
  id: text("id").primaryKey(),
3091
- agentId: text("agent_id").notNull().references(() => agents.id, { onDelete: "cascade" }),
3093
+ agentId: text("agent_id").notNull().references(() => agents.uuid, { onDelete: "cascade" }),
3092
3094
  tokenHash: text("token_hash").notNull(),
3093
3095
  name: text("name"),
3094
3096
  expiresAt: timestamp("expires_at", { withTimezone: true }),
@@ -3096,56 +3098,71 @@ const agentTokens = pgTable("agent_tokens", {
3096
3098
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
3097
3099
  lastUsedAt: timestamp("last_used_at", { withTimezone: true })
3098
3100
  }, (table) => [index("idx_agent_tokens_agent").on(table.agentId), index("idx_agent_tokens_hash").on(table.tokenHash)]);
3101
+ /** Generate a UUID v7 (time-ordered). No external dependency. */
3102
+ function uuidv7() {
3103
+ const now = BigInt(Date.now());
3104
+ const bytes = new Uint8Array(16);
3105
+ bytes[0] = Number(now >> 40n & 255n);
3106
+ bytes[1] = Number(now >> 32n & 255n);
3107
+ bytes[2] = Number(now >> 24n & 255n);
3108
+ bytes[3] = Number(now >> 16n & 255n);
3109
+ bytes[4] = Number(now >> 8n & 255n);
3110
+ bytes[5] = Number(now & 255n);
3111
+ const rand = randomBytes(10);
3112
+ for (let i = 0; i < 10; i++) {
3113
+ const b = rand[i];
3114
+ if (b !== void 0) bytes[6 + i] = b;
3115
+ }
3116
+ bytes[6] = (bytes[6] ?? 0) & 15 | 112;
3117
+ bytes[8] = (bytes[8] ?? 0) & 63 | 128;
3118
+ const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
3119
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
3120
+ }
3099
3121
  function hashToken$1(raw) {
3100
3122
  return createHash("sha256").update(raw).digest("hex");
3101
3123
  }
3102
3124
  async function createAgent(db, data) {
3103
- const id = data.id ?? randomUUID();
3104
- const inboxId = `inbox_${id}`;
3105
- const [existing] = await db.select({
3106
- id: agents.id,
3107
- status: agents.status
3108
- }).from(agents).where(eq(agents.id, id)).limit(1);
3109
- if (existing) {
3110
- if (existing.status !== AGENT_STATUSES.DELETED) throw new ConflictError(`Agent "${id}" already exists`);
3111
- const [agent] = await db.update(agents).set({
3112
- organizationId: data.organizationId ?? "default",
3125
+ const uuid = uuidv7();
3126
+ const name = data.name ?? null;
3127
+ const inboxId = `inbox_${uuid}`;
3128
+ const orgId = data.organizationId ?? "default";
3129
+ try {
3130
+ const [agent] = await db.insert(agents).values({
3131
+ uuid,
3132
+ name,
3133
+ organizationId: orgId,
3113
3134
  type: data.type,
3114
3135
  displayName: data.displayName ?? null,
3115
3136
  delegateMention: data.delegateMention ?? null,
3116
3137
  profile: data.profile ?? null,
3117
- status: "active",
3118
- metadata: data.metadata ?? {},
3119
- updatedAt: /* @__PURE__ */ new Date()
3120
- }).where(eq(agents.id, id)).returning();
3121
- if (!agent) throw new Error("Unexpected: UPDATE RETURNING produced no row");
3138
+ inboxId,
3139
+ metadata: data.metadata ?? {}
3140
+ }).returning();
3141
+ if (!agent) throw new Error("Unexpected: INSERT RETURNING produced no row");
3122
3142
  return agent;
3143
+ } catch (err) {
3144
+ if ((err?.code ?? err?.cause?.code ?? "") === "23505" && name) throw new ConflictError(`Agent name "${name}" already exists in organization "${orgId}"`);
3145
+ throw err;
3123
3146
  }
3124
- const [agent] = await db.insert(agents).values({
3125
- id,
3126
- organizationId: data.organizationId ?? "default",
3127
- type: data.type,
3128
- displayName: data.displayName ?? null,
3129
- delegateMention: data.delegateMention ?? null,
3130
- profile: data.profile ?? null,
3131
- inboxId,
3132
- metadata: data.metadata ?? {}
3133
- }).returning();
3134
- if (!agent) throw new Error("Unexpected: INSERT RETURNING produced no row");
3147
+ }
3148
+ async function getAgent(db, uuid) {
3149
+ const [agent] = await db.select().from(agents).where(and(eq(agents.uuid, uuid), ne(agents.status, AGENT_STATUSES.DELETED))).limit(1);
3150
+ if (!agent) throw new NotFoundError(`Agent "${uuid}" not found`);
3135
3151
  return agent;
3136
3152
  }
3137
- async function getAgent(db, id) {
3138
- const [agent] = await db.select().from(agents).where(and(eq(agents.id, id), ne(agents.status, AGENT_STATUSES.DELETED))).limit(1);
3139
- if (!agent) throw new NotFoundError(`Agent "${id}" not found`);
3153
+ async function getAgentByName(db, orgId, name) {
3154
+ const [agent] = await db.select().from(agents).where(and(eq(agents.organizationId, orgId), eq(agents.name, name), ne(agents.status, AGENT_STATUSES.DELETED))).limit(1);
3155
+ if (!agent) throw new NotFoundError(`Agent "${name}" not found in organization "${orgId}"`);
3140
3156
  return agent;
3141
3157
  }
3142
- async function listAgents(db, limit, cursor, type) {
3143
- const conditions = [ne(agents.status, AGENT_STATUSES.DELETED)];
3158
+ async function listAgents(db, orgId, limit, cursor, type) {
3159
+ const conditions = [ne(agents.status, AGENT_STATUSES.DELETED), eq(agents.organizationId, orgId)];
3144
3160
  if (cursor) conditions.push(lt(agents.createdAt, new Date(cursor)));
3145
3161
  if (type) conditions.push(eq(agents.type, type));
3146
3162
  const where = and(...conditions);
3147
3163
  const rows = await db.select({
3148
- id: agents.id,
3164
+ uuid: agents.uuid,
3165
+ name: agents.name,
3149
3166
  organizationId: agents.organizationId,
3150
3167
  type: agents.type,
3151
3168
  displayName: agents.displayName,
@@ -3157,7 +3174,7 @@ async function listAgents(db, limit, cursor, type) {
3157
3174
  createdAt: agents.createdAt,
3158
3175
  updatedAt: agents.updatedAt,
3159
3176
  presenceStatus: agentPresence.status
3160
- }).from(agents).leftJoin(agentPresence, eq(agents.id, agentPresence.agentId)).where(where).orderBy(desc(agents.createdAt)).limit(limit + 1);
3177
+ }).from(agents).leftJoin(agentPresence, eq(agents.uuid, agentPresence.agentId)).where(where).orderBy(desc(agents.createdAt)).limit(limit + 1);
3161
3178
  const hasMore = rows.length > limit;
3162
3179
  const items = hasMore ? rows.slice(0, limit) : rows;
3163
3180
  const last = items[items.length - 1];
@@ -3166,72 +3183,74 @@ async function listAgents(db, limit, cursor, type) {
3166
3183
  nextCursor: hasMore && last ? last.createdAt.toISOString() : null
3167
3184
  };
3168
3185
  }
3169
- async function updateAgent(db, id, data) {
3170
- const agent = await getAgent(db, id);
3186
+ async function updateAgent(db, uuid, data) {
3187
+ const agent = await getAgent(db, uuid);
3171
3188
  const updates = { updatedAt: /* @__PURE__ */ new Date() };
3172
3189
  if (data.type !== void 0) updates.type = data.type;
3173
3190
  if (data.displayName !== void 0) updates.displayName = data.displayName;
3174
3191
  if (data.delegateMention !== void 0) updates.delegateMention = data.delegateMention;
3175
3192
  if (data.profile !== void 0) updates.profile = data.profile;
3176
3193
  if (data.metadata !== void 0) updates.metadata = data.metadata;
3177
- const [updated] = await db.update(agents).set(updates).where(eq(agents.id, agent.id)).returning();
3194
+ const [updated] = await db.update(agents).set(updates).where(eq(agents.uuid, agent.uuid)).returning();
3178
3195
  if (!updated) throw new Error("Unexpected: UPDATE RETURNING produced no row");
3179
3196
  return updated;
3180
3197
  }
3181
3198
  /**
3182
3199
  * Reactivate a suspended agent.
3183
3200
  */
3184
- async function reactivateAgent(db, id) {
3201
+ async function reactivateAgent(db, uuid) {
3185
3202
  const [existing] = await db.select({
3186
- id: agents.id,
3203
+ uuid: agents.uuid,
3187
3204
  status: agents.status
3188
- }).from(agents).where(eq(agents.id, id)).limit(1);
3189
- if (!existing || existing.status === AGENT_STATUSES.DELETED) throw new NotFoundError(`Agent "${id}" not found`);
3205
+ }).from(agents).where(eq(agents.uuid, uuid)).limit(1);
3206
+ if (!existing || existing.status === AGENT_STATUSES.DELETED) throw new NotFoundError(`Agent "${uuid}" not found`);
3190
3207
  if (existing.status !== AGENT_STATUSES.SUSPENDED) throw new BadRequestError("Only suspended agents can be reactivated.");
3191
3208
  const [agent] = await db.update(agents).set({
3192
3209
  status: AGENT_STATUSES.ACTIVE,
3193
3210
  updatedAt: /* @__PURE__ */ new Date()
3194
- }).where(eq(agents.id, id)).returning();
3211
+ }).where(eq(agents.uuid, uuid)).returning();
3195
3212
  if (!agent) throw new Error("Unexpected: UPDATE RETURNING produced no row");
3196
3213
  return agent;
3197
3214
  }
3198
3215
  /**
3199
3216
  * Suspend an agent. Revokes all active tokens so the agent can no longer authenticate.
3200
3217
  */
3201
- async function suspendAgent(db, id) {
3218
+ async function suspendAgent(db, uuid) {
3202
3219
  const [agent] = await db.update(agents).set({
3203
3220
  status: AGENT_STATUSES.SUSPENDED,
3204
3221
  updatedAt: /* @__PURE__ */ new Date()
3205
- }).where(and(eq(agents.id, id), ne(agents.status, AGENT_STATUSES.DELETED))).returning();
3206
- if (!agent) throw new NotFoundError(`Agent "${id}" not found`);
3207
- await db.update(agentTokens).set({ revokedAt: /* @__PURE__ */ new Date() }).where(and(eq(agentTokens.agentId, id), isNull(agentTokens.revokedAt)));
3222
+ }).where(and(eq(agents.uuid, uuid), ne(agents.status, AGENT_STATUSES.DELETED))).returning();
3223
+ if (!agent) throw new NotFoundError(`Agent "${uuid}" not found`);
3224
+ await db.update(agentTokens).set({ revokedAt: /* @__PURE__ */ new Date() }).where(and(eq(agentTokens.agentId, uuid), isNull(agentTokens.revokedAt)));
3208
3225
  return agent;
3209
3226
  }
3210
3227
  /**
3211
3228
  * Delete an agent. Only allowed when status is "suspended".
3212
3229
  * Suspend the agent first to revoke tokens, then delete.
3230
+ * Sets name to NULL to release the name for reuse.
3213
3231
  */
3214
- async function deleteAgent(db, id) {
3232
+ async function deleteAgent(db, uuid) {
3215
3233
  const [existing] = await db.select({
3216
- id: agents.id,
3234
+ uuid: agents.uuid,
3217
3235
  status: agents.status
3218
- }).from(agents).where(eq(agents.id, id)).limit(1);
3219
- if (!existing || existing.status === AGENT_STATUSES.DELETED) throw new NotFoundError(`Agent "${id}" not found`);
3236
+ }).from(agents).where(eq(agents.uuid, uuid)).limit(1);
3237
+ if (!existing || existing.status === AGENT_STATUSES.DELETED) throw new NotFoundError(`Agent "${uuid}" not found`);
3220
3238
  if (existing.status !== AGENT_STATUSES.SUSPENDED) throw new BadRequestError("Only suspended agents can be deleted. Suspend the agent first.");
3221
3239
  const [agent] = await db.update(agents).set({
3222
3240
  status: AGENT_STATUSES.DELETED,
3241
+ name: null,
3223
3242
  updatedAt: /* @__PURE__ */ new Date()
3224
- }).where(eq(agents.id, id)).returning();
3225
- await db.update(agentTokens).set({ revokedAt: /* @__PURE__ */ new Date() }).where(and(eq(agentTokens.agentId, id), isNull(agentTokens.revokedAt)));
3226
- await db.delete(adapterConfigs).where(eq(adapterConfigs.agentId, id));
3227
- await db.delete(adapterAgentMappings).where(eq(adapterAgentMappings.agentId, id));
3243
+ }).where(eq(agents.uuid, uuid)).returning();
3244
+ await db.update(agentTokens).set({ revokedAt: /* @__PURE__ */ new Date() }).where(and(eq(agentTokens.agentId, uuid), isNull(agentTokens.revokedAt)));
3245
+ await db.delete(adapterConfigs).where(eq(adapterConfigs.agentId, uuid));
3246
+ await db.delete(adapterAgentMappings).where(eq(adapterAgentMappings.agentId, uuid));
3228
3247
  if (!agent) throw new Error("Unexpected: UPDATE RETURNING produced no row");
3229
3248
  return agent;
3230
3249
  }
3231
- async function bootstrapToken(db, agentId, githubUsername, options) {
3250
+ async function bootstrapToken(db, agentName, orgId, githubUsername, options) {
3232
3251
  let agent;
3233
3252
  try {
3234
- agent = await getAgent(db, agentId);
3253
+ agent = await getAgentByName(db, orgId, agentName);
3235
3254
  } catch (err) {
3236
3255
  if (err instanceof NotFoundError) {
3237
3256
  const metadata = {
@@ -3239,19 +3258,20 @@ async function bootstrapToken(db, agentId, githubUsername, options) {
3239
3258
  owners: [githubUsername]
3240
3259
  };
3241
3260
  agent = await createAgent(db, {
3242
- id: agentId,
3261
+ name: agentName,
3243
3262
  type: options?.type ?? "autonomous_agent",
3244
- displayName: options?.displayName ?? agentId,
3263
+ displayName: options?.displayName ?? agentName,
3245
3264
  delegateMention: options?.delegateMention,
3246
3265
  profile: options?.profile,
3266
+ organizationId: orgId,
3247
3267
  metadata
3248
3268
  });
3249
3269
  } else throw err;
3250
3270
  }
3251
- if (!(Array.isArray(agent.metadata?.owners) ? agent.metadata.owners : []).includes(githubUsername)) throw new ForbiddenError(`GitHub user "${githubUsername}" is not in the owners list for agent "${agentId}"`);
3252
- const activeTokens = await db.select({ id: agentTokens.id }).from(agentTokens).where(and(eq(agentTokens.agentId, agentId), isNull(agentTokens.revokedAt)));
3253
- if (activeTokens.length > 0) throw new ConflictError(`Agent "${agentId}" already has ${activeTokens.length} active token(s). Revoke all tokens first to re-bootstrap.`);
3254
- return createToken(db, agentId, { name: options?.tokenName ?? "bootstrap" });
3271
+ if (!(Array.isArray(agent.metadata?.owners) ? agent.metadata.owners : []).includes(githubUsername)) throw new ForbiddenError(`GitHub user "${githubUsername}" is not in the owners list for agent "${agentName}"`);
3272
+ const activeTokens = await db.select({ id: agentTokens.id }).from(agentTokens).where(and(eq(agentTokens.agentId, agent.uuid), isNull(agentTokens.revokedAt)));
3273
+ if (activeTokens.length > 0) throw new ConflictError(`Agent "${agentName}" already has ${activeTokens.length} active token(s). Revoke all tokens first to re-bootstrap.`);
3274
+ return createToken(db, agent.uuid, { name: options?.tokenName ?? "bootstrap" });
3255
3275
  }
3256
3276
  /**
3257
3277
  * Check if a GitHub user belongs to a specific organization.
@@ -3264,14 +3284,14 @@ async function checkGitHubOrgMembership(githubToken, org) {
3264
3284
  if (!res.ok) return false;
3265
3285
  return (await res.json()).some((o) => o.login.toLowerCase() === org.toLowerCase());
3266
3286
  }
3267
- async function createToken(db, agentId, data) {
3268
- await getAgent(db, agentId);
3287
+ async function createToken(db, agentUuid, data) {
3288
+ await getAgent(db, agentUuid);
3269
3289
  const raw = `aghub_${randomBytes(32).toString("hex")}`;
3270
3290
  const tokenHash = hashToken$1(raw);
3271
- const tokenId = randomUUID();
3291
+ const tokenId = uuidv7();
3272
3292
  const [token] = await db.insert(agentTokens).values({
3273
3293
  id: tokenId,
3274
- agentId,
3294
+ agentId: agentUuid,
3275
3295
  tokenHash,
3276
3296
  name: data.name ?? null,
3277
3297
  expiresAt: data.expiresAt ? new Date(data.expiresAt) : null
@@ -3288,7 +3308,7 @@ async function createToken(db, agentId, data) {
3288
3308
  token: raw
3289
3309
  };
3290
3310
  }
3291
- async function listTokens(db, agentId) {
3311
+ async function listTokens(db, agentUuid) {
3292
3312
  return db.select({
3293
3313
  id: agentTokens.id,
3294
3314
  agentId: agentTokens.agentId,
@@ -3297,10 +3317,10 @@ async function listTokens(db, agentId) {
3297
3317
  revokedAt: agentTokens.revokedAt,
3298
3318
  createdAt: agentTokens.createdAt,
3299
3319
  lastUsedAt: agentTokens.lastUsedAt
3300
- }).from(agentTokens).where(eq(agentTokens.agentId, agentId)).orderBy(desc(agentTokens.createdAt));
3320
+ }).from(agentTokens).where(eq(agentTokens.agentId, agentUuid)).orderBy(desc(agentTokens.createdAt));
3301
3321
  }
3302
- async function revokeToken(db, agentId, tokenId) {
3303
- const [token] = await db.update(agentTokens).set({ revokedAt: /* @__PURE__ */ new Date() }).where(and(eq(agentTokens.id, tokenId), eq(agentTokens.agentId, agentId), isNull(agentTokens.revokedAt))).returning();
3322
+ async function revokeToken(db, agentUuid, tokenId) {
3323
+ const [token] = await db.update(agentTokens).set({ revokedAt: /* @__PURE__ */ new Date() }).where(and(eq(agentTokens.id, tokenId), eq(agentTokens.agentId, agentUuid), isNull(agentTokens.revokedAt))).returning();
3304
3324
  if (!token) throw new NotFoundError("Token not found or already revoked");
3305
3325
  return token;
3306
3326
  }
@@ -3308,9 +3328,9 @@ async function createChat(db, creatorId, data) {
3308
3328
  const chatId = randomUUID();
3309
3329
  const allParticipantIds = new Set([creatorId, ...data.participantIds]);
3310
3330
  const existingAgents = await db.select({
3311
- id: agents.id,
3331
+ id: agents.uuid,
3312
3332
  organizationId: agents.organizationId
3313
- }).from(agents).where(inArray(agents.id, [...allParticipantIds]));
3333
+ }).from(agents).where(inArray(agents.uuid, [...allParticipantIds]));
3314
3334
  if (existingAgents.length !== allParticipantIds.size) {
3315
3335
  const found = new Set(existingAgents.map((a) => a.id));
3316
3336
  throw new BadRequestError(`Agents not found: ${[...allParticipantIds].filter((id) => !found.has(id)).join(", ")}`);
@@ -3379,9 +3399,9 @@ async function addParticipant(db, chatId, requesterId, data) {
3379
3399
  const chat = await getChat(db, chatId);
3380
3400
  await assertParticipant(db, chatId, requesterId);
3381
3401
  const [targetAgent] = await db.select({
3382
- id: agents.id,
3402
+ id: agents.uuid,
3383
3403
  organizationId: agents.organizationId
3384
- }).from(agents).where(eq(agents.id, data.agentId)).limit(1);
3404
+ }).from(agents).where(eq(agents.uuid, data.agentId)).limit(1);
3385
3405
  if (!targetAgent) throw new NotFoundError(`Agent "${data.agentId}" not found`);
3386
3406
  if (targetAgent.organizationId !== chat.organizationId) throw new BadRequestError("Cannot add agent from different organization");
3387
3407
  const [existing] = await db.select({ chatId: chatParticipants.chatId }).from(chatParticipants).where(and(eq(chatParticipants.chatId, chatId), eq(chatParticipants.agentId, data.agentId))).limit(1);
@@ -3409,7 +3429,7 @@ async function findOrCreateDirectChat(db, agentAId, agentBId) {
3409
3429
  const directChats = await db.select().from(chats).where(and(inArray(chats.id, commonChatIds), eq(chats.type, "direct")));
3410
3430
  if (directChats.length > 0 && directChats[0]) return directChats[0];
3411
3431
  }
3412
- const [agentA] = await db.select({ organizationId: agents.organizationId }).from(agents).where(eq(agents.id, agentAId)).limit(1);
3432
+ const [agentA] = await db.select({ organizationId: agents.organizationId }).from(agents).where(eq(agents.uuid, agentAId)).limit(1);
3413
3433
  if (!agentA) throw new NotFoundError(`Agent "${agentAId}" not found`);
3414
3434
  const chatId = randomUUID();
3415
3435
  return db.transaction(async (tx) => {
@@ -3489,7 +3509,7 @@ async function sendMessage(db, chatId, senderId, data) {
3489
3509
  const entries = (await tx.select({
3490
3510
  agentId: chatParticipants.agentId,
3491
3511
  inboxId: agents.inboxId
3492
- }).from(chatParticipants).innerJoin(agents, eq(chatParticipants.agentId, agents.id)).where(eq(chatParticipants.chatId, chatId))).filter((p) => p.agentId !== senderId).map((p) => ({
3512
+ }).from(chatParticipants).innerJoin(agents, eq(chatParticipants.agentId, agents.uuid)).where(eq(chatParticipants.chatId, chatId))).filter((p) => p.agentId !== senderId).map((p) => ({
3493
3513
  inboxId: p.inboxId,
3494
3514
  messageId,
3495
3515
  chatId
@@ -3518,18 +3538,15 @@ async function sendMessage(db, chatId, senderId, data) {
3518
3538
  };
3519
3539
  });
3520
3540
  }
3521
- async function sendToAgent(db, senderId, targetAgentId, data) {
3541
+ async function sendToAgent(db, senderUuid, targetName, data) {
3522
3542
  const [sender] = await db.select({
3523
- id: agents.id,
3543
+ uuid: agents.uuid,
3524
3544
  organizationId: agents.organizationId
3525
- }).from(agents).where(eq(agents.id, senderId)).limit(1);
3526
- if (!sender) throw new NotFoundError(`Agent "${senderId}" not found`);
3527
- const [target] = await db.select({
3528
- id: agents.id,
3529
- organizationId: agents.organizationId
3530
- }).from(agents).where(eq(agents.id, targetAgentId)).limit(1);
3531
- if (!target) throw new NotFoundError(`Agent "${targetAgentId}" not found`);
3532
- return sendMessage(db, (await findOrCreateDirectChat(db, senderId, targetAgentId)).id, senderId, {
3545
+ }).from(agents).where(eq(agents.uuid, senderUuid)).limit(1);
3546
+ if (!sender) throw new NotFoundError(`Agent "${senderUuid}" not found`);
3547
+ const [target] = await db.select({ uuid: agents.uuid }).from(agents).where(and(eq(agents.organizationId, sender.organizationId), eq(agents.name, targetName), ne(agents.status, "deleted"))).limit(1);
3548
+ if (!target) throw new NotFoundError(`Agent "${targetName}" not found`);
3549
+ return sendMessage(db, (await findOrCreateDirectChat(db, senderUuid, target.uuid)).id, senderUuid, {
3533
3550
  format: data.format,
3534
3551
  content: data.content,
3535
3552
  metadata: data.metadata,
@@ -3703,7 +3720,8 @@ async function adminAgentRoutes(app) {
3703
3720
  app.get("/", async (request) => {
3704
3721
  const query = paginationQuerySchema.parse(request.query);
3705
3722
  const { type } = listAgentsFilterSchema.parse(request.query);
3706
- const result = await listAgents(app.db, query.limit, query.cursor, type);
3723
+ const org = request.query.org ?? "default";
3724
+ const result = await listAgents(app.db, org, query.limit, query.cursor, type);
3707
3725
  return {
3708
3726
  items: result.items.map((a) => ({
3709
3727
  ...a,
@@ -3723,26 +3741,26 @@ async function adminAgentRoutes(app) {
3723
3741
  updatedAt: agent.updatedAt.toISOString()
3724
3742
  });
3725
3743
  });
3726
- app.patch("/:agentId", async (request) => {
3744
+ app.patch("/:uuid", async (request) => {
3727
3745
  const body = updateAgentSchema.parse(request.body);
3728
- const agent = await updateAgent(app.db, request.params.agentId, body);
3746
+ const agent = await updateAgent(app.db, request.params.uuid, body);
3729
3747
  return {
3730
3748
  ...agent,
3731
3749
  createdAt: agent.createdAt.toISOString(),
3732
3750
  updatedAt: agent.updatedAt.toISOString()
3733
3751
  };
3734
3752
  });
3735
- app.get("/:agentId", async (request) => {
3736
- const agent = await getAgent(app.db, request.params.agentId);
3753
+ app.get("/:uuid", async (request) => {
3754
+ const agent = await getAgent(app.db, request.params.uuid);
3737
3755
  return {
3738
3756
  ...agent,
3739
3757
  createdAt: agent.createdAt.toISOString(),
3740
3758
  updatedAt: agent.updatedAt.toISOString()
3741
3759
  };
3742
3760
  });
3743
- app.post("/:agentId/tokens", async (request, reply) => {
3761
+ app.post("/:uuid/tokens", async (request, reply) => {
3744
3762
  const body = createAgentTokenSchema.parse(request.body);
3745
- const result = await createToken(app.db, request.params.agentId, body);
3763
+ const result = await createToken(app.db, request.params.uuid, body);
3746
3764
  return reply.status(201).send({
3747
3765
  ...result,
3748
3766
  expiresAt: serializeDate$1(result.expiresAt),
@@ -3751,8 +3769,8 @@ async function adminAgentRoutes(app) {
3751
3769
  lastUsedAt: serializeDate$1(result.lastUsedAt)
3752
3770
  });
3753
3771
  });
3754
- app.get("/:agentId/tokens", async (request) => {
3755
- return (await listTokens(app.db, request.params.agentId)).map((t) => ({
3772
+ app.get("/:uuid/tokens", async (request) => {
3773
+ return (await listTokens(app.db, request.params.uuid)).map((t) => ({
3756
3774
  ...t,
3757
3775
  expiresAt: serializeDate$1(t.expiresAt),
3758
3776
  revokedAt: serializeDate$1(t.revokedAt),
@@ -3760,55 +3778,55 @@ async function adminAgentRoutes(app) {
3760
3778
  lastUsedAt: serializeDate$1(t.lastUsedAt)
3761
3779
  }));
3762
3780
  });
3763
- app.delete("/:agentId/tokens/:tokenId", async (request, reply) => {
3764
- await revokeToken(app.db, request.params.agentId, request.params.tokenId);
3781
+ app.delete("/:uuid/tokens/:tokenId", async (request, reply) => {
3782
+ await revokeToken(app.db, request.params.uuid, request.params.tokenId);
3765
3783
  return reply.status(204).send();
3766
3784
  });
3767
- app.post("/:agentId/disconnect", async (request, reply) => {
3768
- const { agentId } = request.params;
3769
- await getAgent(app.db, agentId);
3770
- const wasConnected = forceDisconnect(agentId);
3771
- await setOffline(app.db, agentId);
3785
+ app.post("/:uuid/disconnect", async (request, reply) => {
3786
+ const { uuid } = request.params;
3787
+ await getAgent(app.db, uuid);
3788
+ const wasConnected = forceDisconnect(uuid);
3789
+ await setOffline(app.db, uuid);
3772
3790
  return reply.status(200).send({ disconnected: wasConnected });
3773
3791
  });
3774
- app.post("/:agentId/suspend", async (request) => {
3775
- const agent = await suspendAgent(app.db, request.params.agentId);
3792
+ app.post("/:uuid/suspend", async (request) => {
3793
+ const agent = await suspendAgent(app.db, request.params.uuid);
3776
3794
  return {
3777
3795
  ...agent,
3778
3796
  createdAt: agent.createdAt.toISOString(),
3779
3797
  updatedAt: agent.updatedAt.toISOString()
3780
3798
  };
3781
3799
  });
3782
- app.post("/:agentId/reactivate", async (request) => {
3783
- const agent = await reactivateAgent(app.db, request.params.agentId);
3800
+ app.post("/:uuid/reactivate", async (request) => {
3801
+ const agent = await reactivateAgent(app.db, request.params.uuid);
3784
3802
  return {
3785
3803
  ...agent,
3786
3804
  createdAt: agent.createdAt.toISOString(),
3787
3805
  updatedAt: agent.updatedAt.toISOString()
3788
3806
  };
3789
3807
  });
3790
- app.delete("/:agentId", async (request, reply) => {
3791
- await deleteAgent(app.db, request.params.agentId);
3808
+ app.delete("/:uuid", async (request, reply) => {
3809
+ await deleteAgent(app.db, request.params.uuid);
3792
3810
  return reply.status(204).send();
3793
3811
  });
3794
- app.post("/:agentId/test", async (request, reply) => {
3795
- const { agentId } = request.params;
3796
- const [, presence] = await Promise.all([getAgent(app.db, agentId), getPresence(app.db, agentId)]);
3812
+ app.post("/:uuid/test", async (request, reply) => {
3813
+ const { uuid } = request.params;
3814
+ const [, presence] = await Promise.all([getAgent(app.db, uuid), getPresence(app.db, uuid)]);
3797
3815
  if (!presence || presence.status !== "online") return reply.status(200).send({
3798
3816
  status: "offline",
3799
3817
  message: "Agent is not connected. Start the client first."
3800
3818
  });
3801
- const [owner] = await app.db.select({ id: agents.id }).from(agents).where(and(eq(agents.delegateMention, agentId), eq(agents.status, "active"))).limit(1);
3802
- let senderId = owner?.id ?? null;
3819
+ const [owner] = await app.db.select({ uuid: agents.uuid }).from(agents).where(and(eq(agents.delegateMention, uuid), eq(agents.status, "active"))).limit(1);
3820
+ let senderId = owner?.uuid ?? null;
3803
3821
  if (!senderId) {
3804
- const [other] = await app.db.select({ id: agents.id }).from(agents).where(and(ne(agents.id, agentId), eq(agents.status, "active"))).limit(1);
3805
- senderId = other?.id ?? null;
3822
+ const [other] = await app.db.select({ uuid: agents.uuid }).from(agents).where(and(ne(agents.uuid, uuid), eq(agents.status, "active"))).limit(1);
3823
+ senderId = other?.uuid ?? null;
3806
3824
  }
3807
3825
  if (!senderId) return reply.status(200).send({
3808
3826
  status: "error",
3809
3827
  message: "No suitable sender found. Need at least one other active agent."
3810
3828
  });
3811
- const chat = await findOrCreateDirectChat(app.db, senderId, agentId);
3829
+ const chat = await findOrCreateDirectChat(app.db, senderId, uuid);
3812
3830
  const testContent = `[System Test] Verify your connection. Respond with your identity and role. Time: ${(/* @__PURE__ */ new Date()).toISOString()}`;
3813
3831
  const result = await sendMessage(app.db, chat.id, senderId, {
3814
3832
  format: "text",
@@ -3821,7 +3839,7 @@ async function adminAgentRoutes(app) {
3821
3839
  const pollStart = Date.now();
3822
3840
  while (Date.now() - pollStart < POLL_TIMEOUT) {
3823
3841
  await new Promise((r) => setTimeout(r, POLL_INTERVAL));
3824
- const [response] = await app.db.select().from(messages).where(and(eq(messages.chatId, chat.id), eq(messages.senderId, agentId), gt(messages.createdAt, threshold))).limit(1);
3842
+ const [response] = await app.db.select().from(messages).where(and(eq(messages.chatId, chat.id), eq(messages.senderId, uuid), gt(messages.createdAt, threshold))).limit(1);
3825
3843
  if (response) {
3826
3844
  const content = typeof response.content === "string" ? response.content.slice(0, 500) : JSON.stringify(response.content).slice(0, 500);
3827
3845
  return reply.status(200).send({
@@ -3903,7 +3921,10 @@ async function adminChatRoutes(app) {
3903
3921
  /** List chats with participant count */
3904
3922
  app.get("/", async (request) => {
3905
3923
  const query = paginationQuerySchema.parse(request.query);
3906
- const where = query.cursor ? lt(chats.createdAt, new Date(query.cursor)) : void 0;
3924
+ const org = request.query.org ?? "default";
3925
+ const conditions = [eq(chats.organizationId, org)];
3926
+ if (query.cursor) conditions.push(lt(chats.createdAt, new Date(query.cursor)));
3927
+ const where = and(...conditions);
3907
3928
  const rows = await app.db.select({
3908
3929
  id: chats.id,
3909
3930
  organizationId: chats.organizationId,
@@ -3982,9 +4003,10 @@ async function adminChatRoutes(app) {
3982
4003
  });
3983
4004
  }
3984
4005
  async function adminOverviewRoutes(app) {
3985
- app.get("/", async () => {
3986
- const [agentCount] = await app.db.select({ count: sql`count(*)::int` }).from(agents).where(ne(agents.status, "deleted"));
3987
- const [chatCount] = await app.db.select({ count: sql`count(*)::int` }).from(chats);
4006
+ app.get("/", async (request) => {
4007
+ const org = (request.query ?? {}).org ?? "default";
4008
+ const [agentCount] = await app.db.select({ count: sql`count(*)::int` }).from(agents).where(and(ne(agents.status, "deleted"), eq(agents.organizationId, org)));
4009
+ const [chatCount] = await app.db.select({ count: sql`count(*)::int` }).from(chats).where(eq(chats.organizationId, org));
3988
4010
  const onlineCount = await getOnlineCount(app.db);
3989
4011
  return {
3990
4012
  agents: agentCount?.count ?? 0,
@@ -4137,7 +4159,7 @@ async function agentChatRoutes(app) {
4137
4159
  app.post("/", async (request, reply) => {
4138
4160
  const identity = requireAgent(request);
4139
4161
  const body = createChatSchema.parse(request.body);
4140
- const result = await createChat(app.db, identity.id, body);
4162
+ const result = await createChat(app.db, identity.uuid, body);
4141
4163
  return reply.status(201).send({
4142
4164
  ...serializeChat(result),
4143
4165
  participants: result.participants.map((p) => ({
@@ -4149,7 +4171,7 @@ async function agentChatRoutes(app) {
4149
4171
  app.get("/", async (request) => {
4150
4172
  const identity = requireAgent(request);
4151
4173
  const query = paginationQuerySchema.parse(request.query);
4152
- const result = await listChats(app.db, identity.id, query.limit, query.cursor);
4174
+ const result = await listChats(app.db, identity.uuid, query.limit, query.cursor);
4153
4175
  return {
4154
4176
  items: result.items.map(serializeChat),
4155
4177
  nextCursor: result.nextCursor
@@ -4157,7 +4179,7 @@ async function agentChatRoutes(app) {
4157
4179
  });
4158
4180
  app.get("/:chatId", async (request) => {
4159
4181
  const identity = requireAgent(request);
4160
- await assertParticipant(app.db, request.params.chatId, identity.id);
4182
+ await assertParticipant(app.db, request.params.chatId, identity.uuid);
4161
4183
  const detail = await getChatDetail(app.db, request.params.chatId);
4162
4184
  return {
4163
4185
  ...serializeChat(detail),
@@ -4170,7 +4192,7 @@ async function agentChatRoutes(app) {
4170
4192
  app.post("/:chatId/participants", async (request, reply) => {
4171
4193
  const identity = requireAgent(request);
4172
4194
  const body = addParticipantSchema.parse(request.body);
4173
- const participants = await addParticipant(app.db, request.params.chatId, identity.id, body);
4195
+ const participants = await addParticipant(app.db, request.params.chatId, identity.uuid, body);
4174
4196
  return reply.status(201).send(participants.map((p) => ({
4175
4197
  ...p,
4176
4198
  joinedAt: p.joinedAt.toISOString()
@@ -4178,7 +4200,7 @@ async function agentChatRoutes(app) {
4178
4200
  });
4179
4201
  app.delete("/:chatId/participants/:agentId", async (request, reply) => {
4180
4202
  const identity = requireAgent(request);
4181
- await removeParticipant(app.db, request.params.chatId, identity.id, request.params.agentId);
4203
+ await removeParticipant(app.db, request.params.chatId, identity.uuid, request.params.agentId);
4182
4204
  return reply.status(204).send();
4183
4205
  });
4184
4206
  }
@@ -4190,8 +4212,8 @@ async function agentFeishuBotRoutes(app) {
4190
4212
  app.put("/me/feishu-bot", async (request, reply) => {
4191
4213
  const identity = requireAgent(request);
4192
4214
  const body = selfServiceFeishuBotSchema.parse(request.body);
4193
- if ((await getAgent(app.db, identity.id)).type === "human") throw new BadRequestError("Human agents cannot bind Feishu bots. Use bind-user instead.");
4194
- const current = (await listAdapterConfigs(app.db)).find((c) => c.agentId === identity.id && c.platform === "feishu");
4215
+ if ((await getAgent(app.db, identity.uuid)).type === "human") throw new BadRequestError("Human agents cannot bind Feishu bots. Use bind-user instead.");
4216
+ const current = (await listAdapterConfigs(app.db)).find((c) => c.agentId === identity.uuid && c.platform === "feishu");
4195
4217
  let config;
4196
4218
  if (current) config = await updateAdapterConfig(app.db, current.id, {
4197
4219
  credentials: {
@@ -4202,7 +4224,7 @@ async function agentFeishuBotRoutes(app) {
4202
4224
  }, app.config.secrets.encryptionKey);
4203
4225
  else config = await createAdapterConfig(app.db, {
4204
4226
  platform: "feishu",
4205
- agentId: identity.id,
4227
+ agentId: identity.uuid,
4206
4228
  credentials: {
4207
4229
  app_id: body.appId,
4208
4230
  app_secret: body.appSecret
@@ -4223,7 +4245,7 @@ async function agentFeishuBotRoutes(app) {
4223
4245
  */
4224
4246
  app.delete("/me/feishu-bot", async (request, reply) => {
4225
4247
  const identity = requireAgent(request);
4226
- const current = (await listAdapterConfigs(app.db)).find((c) => c.agentId === identity.id && c.platform === "feishu");
4248
+ const current = (await listAdapterConfigs(app.db)).find((c) => c.agentId === identity.uuid && c.platform === "feishu");
4227
4249
  if (!current) return reply.status(204).send();
4228
4250
  await deleteAdapterConfig(app.db, current.id);
4229
4251
  app.adapterManager.reload().catch((err) => app.log.error(err, "Adapter reload failed after self-service unbind"));
@@ -4242,7 +4264,7 @@ async function agentFeishuUserRoutes(app) {
4242
4264
  const body = delegateFeishuUserSchema.parse(request.body);
4243
4265
  const humanAgent = await getAgent(app.db, humanAgentId);
4244
4266
  if (humanAgent.type !== "human") throw new BadRequestError(`Agent "${humanAgentId}" is not a human agent`);
4245
- if (humanAgent.delegateMention !== identity.id) throw new ForbiddenError(`Agent "${identity.id}" is not the delegate of "${humanAgentId}". Expected delegate_mention="${identity.id}" but found "${humanAgent.delegateMention ?? "(none)"}".`);
4267
+ if (humanAgent.delegateMention !== identity.uuid) throw new ForbiddenError(`Agent "${identity.uuid}" is not the delegate of "${humanAgentId}". Expected delegate_mention="${identity.uuid}" but found "${humanAgent.delegateMention ?? "(none)"}".`);
4246
4268
  const mapping = await createAgentMapping(app.db, {
4247
4269
  platform: "feishu",
4248
4270
  externalUserId: body.feishuUserId,
@@ -4267,7 +4289,7 @@ async function agentFeishuUserRoutes(app) {
4267
4289
  app.delete("/:humanAgentId/feishu-user", async (request, reply) => {
4268
4290
  const identity = requireAgent(request);
4269
4291
  const { humanAgentId } = request.params;
4270
- if ((await getAgent(app.db, humanAgentId)).delegateMention !== identity.id) throw new ForbiddenError(`Agent "${identity.id}" is not the delegate of "${humanAgentId}"`);
4292
+ if ((await getAgent(app.db, humanAgentId)).delegateMention !== identity.uuid) throw new ForbiddenError(`Agent "${identity.uuid}" is not the delegate of "${humanAgentId}"`);
4271
4293
  await app.db.delete(adapterAgentMappings).where(and(eq(adapterAgentMappings.platform, "feishu"), eq(adapterAgentMappings.agentId, humanAgentId)));
4272
4294
  return reply.status(204).send();
4273
4295
  });
@@ -4376,7 +4398,7 @@ async function agentInboxRoutes(app) {
4376
4398
  async function agentMeRoutes(app) {
4377
4399
  app.get("/me", async (request) => {
4378
4400
  const identity = requireAgent(request);
4379
- const agent = await getAgent(app.db, identity.id);
4401
+ const agent = await getAgent(app.db, identity.uuid);
4380
4402
  return {
4381
4403
  ...agent,
4382
4404
  createdAt: agent.createdAt.toISOString(),
@@ -4391,9 +4413,9 @@ const editMessageSchema = z.object({
4391
4413
  async function agentMessageRoutes(app) {
4392
4414
  app.post("/:chatId/messages", async (request, reply) => {
4393
4415
  const identity = requireAgent(request);
4394
- await assertParticipant(app.db, request.params.chatId, identity.id);
4416
+ await assertParticipant(app.db, request.params.chatId, identity.uuid);
4395
4417
  const body = sendMessageSchema.parse(request.body);
4396
- const { message: msg, recipients } = await sendMessage(app.db, request.params.chatId, identity.id, body);
4418
+ const { message: msg, recipients } = await sendMessage(app.db, request.params.chatId, identity.uuid, body);
4397
4419
  notifyRecipients(app.notifier, recipients, msg.id);
4398
4420
  return reply.status(201).send({
4399
4421
  ...msg,
@@ -4402,9 +4424,9 @@ async function agentMessageRoutes(app) {
4402
4424
  });
4403
4425
  app.patch("/:chatId/messages/:messageId", async (request) => {
4404
4426
  const identity = requireAgent(request);
4405
- await assertParticipant(app.db, request.params.chatId, identity.id);
4427
+ await assertParticipant(app.db, request.params.chatId, identity.uuid);
4406
4428
  const body = editMessageSchema.parse(request.body);
4407
- const msg = await editMessage(app.db, request.params.chatId, request.params.messageId, identity.id, body);
4429
+ const msg = await editMessage(app.db, request.params.chatId, request.params.messageId, identity.uuid, body);
4408
4430
  app.adapterManager.editOutboundMessage(msg.id, msg.format, msg.content).catch((err) => app.log.error({
4409
4431
  err,
4410
4432
  messageId: msg.id
@@ -4416,7 +4438,7 @@ async function agentMessageRoutes(app) {
4416
4438
  });
4417
4439
  app.get("/:chatId/messages", async (request) => {
4418
4440
  const identity = requireAgent(request);
4419
- await assertParticipant(app.db, request.params.chatId, identity.id);
4441
+ await assertParticipant(app.db, request.params.chatId, identity.uuid);
4420
4442
  const query = paginationQuerySchema.parse(request.query);
4421
4443
  const result = await listMessages(app.db, request.params.chatId, query.limit, query.cursor);
4422
4444
  return {
@@ -4429,10 +4451,10 @@ async function agentMessageRoutes(app) {
4429
4451
  });
4430
4452
  }
4431
4453
  async function agentSendToAgentRoutes(app) {
4432
- app.post("/:agentId/messages", async (request, reply) => {
4454
+ app.post("/:name/messages", async (request, reply) => {
4433
4455
  const identity = requireAgent(request);
4434
4456
  const body = sendToAgentSchema.parse(request.body);
4435
- const { message: msg, recipients } = await sendToAgent(app.db, identity.id, request.params.agentId, body);
4457
+ const { message: msg, recipients } = await sendToAgent(app.db, identity.uuid, request.params.name, body);
4436
4458
  notifyRecipients(app.notifier, recipients, msg.id);
4437
4459
  return reply.status(201).send({
4438
4460
  ...msg,
@@ -4448,18 +4470,18 @@ function agentWsRoutes(notifier, instanceId) {
4448
4470
  socket.close(4001, "Unauthorized");
4449
4471
  return;
4450
4472
  }
4451
- if (hasActiveConnection(agent.id)) {
4473
+ if (hasActiveConnection(agent.uuid)) {
4452
4474
  socket.close(WS_CLOSE_ALREADY_CONNECTED, "Agent already connected");
4453
4475
  return;
4454
4476
  }
4455
4477
  const inboxId = agent.inboxId;
4456
- await setOnline(app.db, agent.id, instanceId);
4457
- setConnection(agent.id, socket);
4478
+ await setOnline(app.db, agent.uuid, instanceId);
4479
+ setConnection(agent.uuid, socket);
4458
4480
  notifier.subscribe(inboxId, socket);
4459
4481
  socket.on("close", async () => {
4460
4482
  notifier.unsubscribe(inboxId, socket);
4461
- if (removeConnection(agent.id, socket)) try {
4462
- await setOffline(app.db, agent.id);
4483
+ if (removeConnection(agent.uuid, socket)) try {
4484
+ await setOffline(app.db, agent.uuid);
4463
4485
  } catch {}
4464
4486
  });
4465
4487
  });
@@ -4473,13 +4495,13 @@ async function bootstrapConfigRoutes(app) {
4473
4495
  }
4474
4496
  async function bootstrapRoutes(app) {
4475
4497
  /**
4476
- * POST /bootstrap/:agentId/token
4498
+ * POST /bootstrap/:name/token
4477
4499
  * GitHub identity → Agent token.
4478
4500
  * Auto-creates the agent if it does not exist.
4479
4501
  * Only works when the agent has no active tokens.
4480
4502
  */
4481
- app.post("/:agentId/token", async (request, reply) => {
4482
- const { agentId } = request.params;
4503
+ app.post("/:name/token", async (request, reply) => {
4504
+ const { name } = request.params;
4483
4505
  const githubUser = request.githubUser;
4484
4506
  if (!githubUser) throw new ForbiddenError("GitHub authentication required");
4485
4507
  const allowedOrg = app.config.github.allowedOrg;
@@ -4488,7 +4510,7 @@ async function bootstrapRoutes(app) {
4488
4510
  if (!githubToken || typeof githubToken !== "string") throw new ForbiddenError("Missing GitHub token for org membership check");
4489
4511
  if (!await checkGitHubOrgMembership(githubToken, allowedOrg)) throw new ForbiddenError(`GitHub user "${githubUser.username}" is not a member of organization "${allowedOrg}"`);
4490
4512
  const body = bootstrapTokenRequestSchema.parse(request.body ?? {});
4491
- const result = await bootstrapToken(app.db, agentId, githubUser.username, {
4513
+ const result = await bootstrapToken(app.db, name, "default", githubUser.username, {
4492
4514
  tokenName: body.name,
4493
4515
  type: body.type,
4494
4516
  displayName: body.displayName,
@@ -4506,16 +4528,16 @@ async function bootstrapRoutes(app) {
4506
4528
  });
4507
4529
  });
4508
4530
  /**
4509
- * GET /bootstrap/:agentId/status
4531
+ * GET /bootstrap/:name/status
4510
4532
  * Check if an agent exists and its status (for polling).
4511
4533
  */
4512
- app.get("/:agentId/status", async (request) => {
4513
- const { agentId } = request.params;
4534
+ app.get("/:name/status", async (request) => {
4535
+ const { name } = request.params;
4514
4536
  const githubUser = request.githubUser;
4515
4537
  if (!githubUser) throw new ForbiddenError("GitHub authentication required");
4516
4538
  try {
4517
- const agent = await getAgent(app.db, agentId);
4518
- if (!(Array.isArray(agent.metadata?.owners) ? agent.metadata.owners : []).includes(githubUser.username)) throw new ForbiddenError(`GitHub user "${githubUser.username}" is not in the owners list for agent "${agentId}"`);
4539
+ const agent = await getAgentByName(app.db, "default", name);
4540
+ if (!(Array.isArray(agent.metadata?.owners) ? agent.metadata.owners : []).includes(githubUser.username)) throw new ForbiddenError(`GitHub user "${githubUser.username}" is not in the owners list for agent "${name}"`);
4519
4541
  return {
4520
4542
  exists: true,
4521
4543
  status: agent.status
@@ -4580,34 +4602,35 @@ function verifySignature(secret, rawBody, signatureHeader) {
4580
4602
  if (expectedBuf.length !== receivedBuf.length || !timingSafeEqual(expectedBuf, receivedBuf)) throw new UnauthorizedError("Invalid webhook signature");
4581
4603
  }
4582
4604
  async function ensureGitHubAdapterAgent(db) {
4583
- const [existing] = await db.select({ id: agents.id }).from(agents).where(eq(agents.id, GITHUB_ADAPTER_ID)).limit(1);
4584
- if (existing) return existing.id;
4605
+ const [existing] = await db.select({ uuid: agents.uuid }).from(agents).where(and(eq(agents.organizationId, "default"), eq(agents.name, GITHUB_ADAPTER_ID))).limit(1);
4606
+ if (existing) return existing.uuid;
4585
4607
  try {
4586
4608
  return (await createAgent(db, {
4587
- id: GITHUB_ADAPTER_ID,
4609
+ name: GITHUB_ADAPTER_ID,
4588
4610
  type: "autonomous_agent",
4589
4611
  displayName: "GitHub Adapter",
4590
4612
  metadata: {
4591
4613
  source: "github",
4592
4614
  managed: true
4593
4615
  }
4594
- })).id;
4616
+ })).uuid;
4595
4617
  } catch (err) {
4596
4618
  if (err instanceof ConflictError) {
4597
- const [created] = await db.select({ id: agents.id }).from(agents).where(eq(agents.id, GITHUB_ADAPTER_ID)).limit(1);
4598
- if (created) return created.id;
4619
+ const [created] = await db.select({ uuid: agents.uuid }).from(agents).where(and(eq(agents.organizationId, "default"), eq(agents.name, GITHUB_ADAPTER_ID))).limit(1);
4620
+ if (created) return created.uuid;
4599
4621
  }
4600
4622
  throw err;
4601
4623
  }
4602
4624
  }
4603
4625
  async function findTargetAgent(db, repoFullName) {
4604
4626
  const allAgents = await db.select({
4605
- id: agents.id,
4627
+ id: agents.uuid,
4628
+ name: agents.name,
4606
4629
  metadata: agents.metadata,
4607
4630
  type: agents.type
4608
4631
  }).from(agents).where(eq(agents.status, "active"));
4609
4632
  for (const agent of allAgents) {
4610
- if (agent.id === GITHUB_ADAPTER_ID) continue;
4633
+ if (agent.name === GITHUB_ADAPTER_ID) continue;
4611
4634
  const meta = agent.metadata;
4612
4635
  if (meta && typeof meta === "object" && "github" in meta) {
4613
4636
  const github = meta.github;
@@ -4642,27 +4665,29 @@ function extractMentions(text) {
4642
4665
  async function routeMentionDelegations(app, mentionedNames, ctx) {
4643
4666
  if (mentionedNames.length === 0) return 0;
4644
4667
  const delegates = await app.db.select({
4645
- id: agents.id,
4668
+ id: agents.uuid,
4669
+ name: agents.name,
4646
4670
  delegateMention: agents.delegateMention,
4647
4671
  status: agents.status
4648
- }).from(agents).where(and(inArray(agents.id, mentionedNames), isNotNull(agents.delegateMention)));
4672
+ }).from(agents).where(and(inArray(agents.name, mentionedNames), isNotNull(agents.delegateMention)));
4649
4673
  let routed = 0;
4650
4674
  for (const agent of delegates) {
4651
4675
  if (agent.status !== "active" || !agent.delegateMention) continue;
4652
4676
  const [target] = await app.db.select({
4653
- id: agents.id,
4677
+ id: agents.uuid,
4654
4678
  status: agents.status
4655
- }).from(agents).where(eq(agents.id, agent.delegateMention)).limit(1);
4679
+ }).from(agents).where(eq(agents.uuid, agent.delegateMention)).limit(1);
4656
4680
  if (!target || target.status !== "active") {
4657
- app.log.warn(`delegate_mention target "${agent.delegateMention}" for "${agent.id}" is not active, skipping`);
4681
+ app.log.warn(`delegate_mention target "${agent.delegateMention}" for "${agent.name}" is not active, skipping`);
4658
4682
  continue;
4659
4683
  }
4660
4684
  try {
4661
- const { message: msg, recipients } = await sendToAgent(app.db, agent.id, agent.delegateMention, {
4685
+ const chat = await findOrCreateDirectChat(app.db, agent.id, agent.delegateMention);
4686
+ const { message: msg, recipients } = await sendMessage(app.db, chat.id, agent.id, {
4662
4687
  format: "card",
4663
4688
  content: {
4664
4689
  type: "github_mention",
4665
- mentionedUser: agent.id,
4690
+ mentionedUser: agent.name,
4666
4691
  event: ctx.event,
4667
4692
  repository: ctx.repository,
4668
4693
  sender: ctx.sender,
@@ -4673,13 +4698,13 @@ async function routeMentionDelegations(app, mentionedNames, ctx) {
4673
4698
  metadata: {
4674
4699
  source: "github",
4675
4700
  event: "mention_delegation",
4676
- mentionedUser: agent.id
4701
+ mentionedUser: agent.name
4677
4702
  }
4678
4703
  });
4679
4704
  notifyRecipients(app.notifier, recipients, msg.id);
4680
4705
  routed++;
4681
4706
  } catch (err) {
4682
- app.log.error(err, `Failed to route mention delegation from "${agent.id}" to "${agent.delegateMention}"`);
4707
+ app.log.error(err, `Failed to route mention delegation from "${agent.name}" to "${agent.delegateMention}"`);
4683
4708
  }
4684
4709
  }
4685
4710
  return routed;
@@ -4967,7 +4992,8 @@ async function handleIssuesEvent(app, eventType, payload, reply) {
4967
4992
  event: "issues",
4968
4993
  action: data.action
4969
4994
  };
4970
- const { message: msg, recipients } = await sendToAgent(app.db, senderId, targetAgentId, {
4995
+ const chat = await findOrCreateDirectChat(app.db, senderId, targetAgentId);
4996
+ const { message: msg, recipients } = await sendMessage(app.db, chat.id, senderId, {
4971
4997
  format: "card",
4972
4998
  content,
4973
4999
  metadata
@@ -5022,7 +5048,8 @@ async function handleIssueCommentEvent(app, eventType, payload, reply) {
5022
5048
  event: "issue_comment",
5023
5049
  action: data.action
5024
5050
  };
5025
- const { message: msg, recipients } = await sendToAgent(app.db, senderId, targetAgentId, {
5051
+ const chat = await findOrCreateDirectChat(app.db, senderId, targetAgentId);
5052
+ const { message: msg, recipients } = await sendMessage(app.db, chat.id, senderId, {
5026
5053
  format: "card",
5027
5054
  content,
5028
5055
  metadata
@@ -5096,10 +5123,11 @@ function agentAuthHook(db) {
5096
5123
  const [tokenDetail] = await db.select({ expiresAt: agentTokens.expiresAt }).from(agentTokens).where(eq(agentTokens.id, tokenRow.tokenId)).limit(1);
5097
5124
  if (tokenDetail?.expiresAt && tokenDetail.expiresAt < now) throw new UnauthorizedError("Token has expired");
5098
5125
  const [agent] = await db.select({
5099
- id: agents.id,
5126
+ uuid: agents.uuid,
5127
+ name: agents.name,
5100
5128
  organizationId: agents.organizationId,
5101
5129
  inboxId: agents.inboxId
5102
- }).from(agents).where(and(eq(agents.id, tokenRow.agentId), eq(agents.status, "active"))).limit(1);
5130
+ }).from(agents).where(and(eq(agents.uuid, tokenRow.agentId), eq(agents.status, "active"))).limit(1);
5103
5131
  if (!agent) throw new UnauthorizedError("Agent is suspended or not found");
5104
5132
  db.update(agentTokens).set({ lastUsedAt: now }).where(eq(agentTokens.id, tokenRow.tokenId)).then(() => {}, () => {});
5105
5133
  request.agent = agent;
@@ -5458,10 +5486,10 @@ async function handleBindCommand(db, bot, event, agentId, log) {
5458
5486
  return;
5459
5487
  }
5460
5488
  const [agent] = await db.select({
5461
- id: agents.id,
5489
+ id: agents.uuid,
5462
5490
  type: agents.type,
5463
5491
  status: agents.status
5464
- }).from(agents).where(eq(agents.id, agentId)).limit(1);
5492
+ }).from(agents).where(eq(agents.uuid, agentId)).limit(1);
5465
5493
  if (!agent) {
5466
5494
  await reply(`Agent "${agentId}" not found. Check the ID and try again.`);
5467
5495
  return;
@@ -5517,7 +5545,7 @@ async function processFeishuOutbound(db, findBotByAgentId, log) {
5517
5545
  WHERE id IN (
5518
5546
  SELECT ie.id FROM inbox_entries ie
5519
5547
  JOIN agents a ON ie.inbox_id = a.inbox_id
5520
- JOIN adapter_agent_mappings aam ON a.id = aam.agent_id
5548
+ JOIN adapter_agent_mappings aam ON a.uuid = aam.agent_id
5521
5549
  WHERE aam.platform = 'feishu' AND ie.status = 'pending'
5522
5550
  ORDER BY ie.created_at
5523
5551
  LIMIT ${OUTBOUND_BATCH_SIZE$1}
@@ -5708,8 +5736,8 @@ function createKaelRuntime(db, encryptionKey, kaelEndpoint, kaelApiKey, serverUr
5708
5736
  }
5709
5737
  const configs = await db.select().from(adapterConfigs).where(and(eq(adapterConfigs.platform, "kael"), eq(adapterConfigs.status, "active")));
5710
5738
  const configAgentIds = configs.filter((c) => c.credentials).map((c) => c.agentId);
5711
- const agentRows = configAgentIds.length > 0 ? await db.execute(sql`SELECT id, inbox_id FROM agents WHERE id IN (${sql.join(configAgentIds.map((id) => sql`${id}`), sql`, `)}) AND status = 'active'`) : [];
5712
- const agentInboxMap = new Map(agentRows.map((a) => [a.id, a.inbox_id]));
5739
+ const agentRows = configAgentIds.length > 0 ? await db.execute(sql`SELECT uuid, inbox_id FROM agents WHERE uuid IN (${sql.join(configAgentIds.map((id) => sql`${id}`), sql`, `)}) AND status = 'active'`) : [];
5740
+ const agentInboxMap = new Map(agentRows.map((a) => [a.uuid, a.inbox_id]));
5713
5741
  const seen = /* @__PURE__ */ new Set();
5714
5742
  for (const config of configs) {
5715
5743
  if (!config.credentials) continue;
@@ -5769,10 +5797,10 @@ function createKaelRuntime(db, encryptionKey, kaelEndpoint, kaelApiKey, serverUr
5769
5797
  WHERE id IN (
5770
5798
  SELECT ie.id FROM inbox_entries ie
5771
5799
  JOIN agents a ON ie.inbox_id = a.inbox_id
5772
- JOIN adapter_configs ac ON a.id = ac.agent_id
5800
+ JOIN adapter_configs ac ON a.uuid = ac.agent_id
5773
5801
  WHERE ac.platform = 'kael' AND ac.status = 'active'
5774
5802
  AND ie.status = 'pending'
5775
- AND a.id IN (${sql.join(agentIds.map((id) => sql`${id}`), sql`, `)})
5803
+ AND a.uuid IN (${sql.join(agentIds.map((id) => sql`${id}`), sql`, `)})
5776
5804
  ORDER BY ie.created_at
5777
5805
  LIMIT ${OUTBOUND_BATCH_SIZE}
5778
5806
  FOR UPDATE OF ie SKIP LOCKED