@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.
- package/dist/{bootstrap-uyPaaI05.mjs → bootstrap-BU_7B03u.mjs} +6 -6
- package/dist/cli/index.mjs +8 -8
- package/dist/{core-CMeOAZmx.mjs → core-jjk1xFW_.mjs} +228 -200
- package/dist/drizzle/0008_uuid_identity.sql +12 -0
- package/dist/drizzle/meta/_journal.json +7 -0
- package/dist/index.mjs +2 -2
- package/dist/web/assets/index-LFh6j4ki.js +280 -0
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/dist/web/assets/index-C4J9gHaF.js +0 -280
|
@@ -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-
|
|
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.
|
|
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(
|
|
83
|
-
return this.requestJson(`/api/v1/agent/agents/${
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
2866
|
+
id: agents.uuid,
|
|
2865
2867
|
type: agents.type,
|
|
2866
2868
|
status: agents.status
|
|
2867
|
-
}).from(agents).where(eq(agents.
|
|
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.
|
|
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.
|
|
2967
|
+
id: agents.uuid,
|
|
2966
2968
|
type: agents.type
|
|
2967
|
-
}).from(agents).where(and(eq(agents.
|
|
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.
|
|
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.
|
|
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
|
|
3104
|
-
const
|
|
3105
|
-
const
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
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
|
-
|
|
3118
|
-
metadata: data.metadata ?? {}
|
|
3119
|
-
|
|
3120
|
-
|
|
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
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
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
|
|
3138
|
-
const [agent] = await db.select().from(agents).where(and(eq(agents.
|
|
3139
|
-
if (!agent) throw new NotFoundError(`Agent "${
|
|
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
|
-
|
|
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.
|
|
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,
|
|
3170
|
-
const agent = await getAgent(db,
|
|
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.
|
|
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,
|
|
3201
|
+
async function reactivateAgent(db, uuid) {
|
|
3185
3202
|
const [existing] = await db.select({
|
|
3186
|
-
|
|
3203
|
+
uuid: agents.uuid,
|
|
3187
3204
|
status: agents.status
|
|
3188
|
-
}).from(agents).where(eq(agents.
|
|
3189
|
-
if (!existing || existing.status === AGENT_STATUSES.DELETED) throw new NotFoundError(`Agent "${
|
|
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.
|
|
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,
|
|
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.
|
|
3206
|
-
if (!agent) throw new NotFoundError(`Agent "${
|
|
3207
|
-
await db.update(agentTokens).set({ revokedAt: /* @__PURE__ */ new Date() }).where(and(eq(agentTokens.agentId,
|
|
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,
|
|
3232
|
+
async function deleteAgent(db, uuid) {
|
|
3215
3233
|
const [existing] = await db.select({
|
|
3216
|
-
|
|
3234
|
+
uuid: agents.uuid,
|
|
3217
3235
|
status: agents.status
|
|
3218
|
-
}).from(agents).where(eq(agents.
|
|
3219
|
-
if (!existing || existing.status === AGENT_STATUSES.DELETED) throw new NotFoundError(`Agent "${
|
|
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.
|
|
3225
|
-
await db.update(agentTokens).set({ revokedAt: /* @__PURE__ */ new Date() }).where(and(eq(agentTokens.agentId,
|
|
3226
|
-
await db.delete(adapterConfigs).where(eq(adapterConfigs.agentId,
|
|
3227
|
-
await db.delete(adapterAgentMappings).where(eq(adapterAgentMappings.agentId,
|
|
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,
|
|
3250
|
+
async function bootstrapToken(db, agentName, orgId, githubUsername, options) {
|
|
3232
3251
|
let agent;
|
|
3233
3252
|
try {
|
|
3234
|
-
agent = await
|
|
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
|
-
|
|
3261
|
+
name: agentName,
|
|
3243
3262
|
type: options?.type ?? "autonomous_agent",
|
|
3244
|
-
displayName: options?.displayName ??
|
|
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 "${
|
|
3252
|
-
const activeTokens = await db.select({ id: agentTokens.id }).from(agentTokens).where(and(eq(agentTokens.agentId,
|
|
3253
|
-
if (activeTokens.length > 0) throw new ConflictError(`Agent "${
|
|
3254
|
-
return createToken(db,
|
|
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,
|
|
3268
|
-
await getAgent(db,
|
|
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 =
|
|
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,
|
|
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,
|
|
3320
|
+
}).from(agentTokens).where(eq(agentTokens.agentId, agentUuid)).orderBy(desc(agentTokens.createdAt));
|
|
3301
3321
|
}
|
|
3302
|
-
async function revokeToken(db,
|
|
3303
|
-
const [token] = await db.update(agentTokens).set({ revokedAt: /* @__PURE__ */ new Date() }).where(and(eq(agentTokens.id, tokenId), eq(agentTokens.agentId,
|
|
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.
|
|
3331
|
+
id: agents.uuid,
|
|
3312
3332
|
organizationId: agents.organizationId
|
|
3313
|
-
}).from(agents).where(inArray(agents.
|
|
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.
|
|
3402
|
+
id: agents.uuid,
|
|
3383
3403
|
organizationId: agents.organizationId
|
|
3384
|
-
}).from(agents).where(eq(agents.
|
|
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.
|
|
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.
|
|
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,
|
|
3541
|
+
async function sendToAgent(db, senderUuid, targetName, data) {
|
|
3522
3542
|
const [sender] = await db.select({
|
|
3523
|
-
|
|
3543
|
+
uuid: agents.uuid,
|
|
3524
3544
|
organizationId: agents.organizationId
|
|
3525
|
-
}).from(agents).where(eq(agents.
|
|
3526
|
-
if (!sender) throw new NotFoundError(`Agent "${
|
|
3527
|
-
const [target] = await db.select({
|
|
3528
|
-
|
|
3529
|
-
|
|
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
|
|
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("/:
|
|
3744
|
+
app.patch("/:uuid", async (request) => {
|
|
3727
3745
|
const body = updateAgentSchema.parse(request.body);
|
|
3728
|
-
const agent = await updateAgent(app.db, request.params.
|
|
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("/:
|
|
3736
|
-
const agent = await getAgent(app.db, request.params.
|
|
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("/:
|
|
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.
|
|
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("/:
|
|
3755
|
-
return (await listTokens(app.db, request.params.
|
|
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("/:
|
|
3764
|
-
await revokeToken(app.db, request.params.
|
|
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("/:
|
|
3768
|
-
const {
|
|
3769
|
-
await getAgent(app.db,
|
|
3770
|
-
const wasConnected = forceDisconnect(
|
|
3771
|
-
await setOffline(app.db,
|
|
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("/:
|
|
3775
|
-
const agent = await suspendAgent(app.db, request.params.
|
|
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("/:
|
|
3783
|
-
const agent = await reactivateAgent(app.db, request.params.
|
|
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("/:
|
|
3791
|
-
await deleteAgent(app.db, request.params.
|
|
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("/:
|
|
3795
|
-
const {
|
|
3796
|
-
const [, presence] = await Promise.all([getAgent(app.db,
|
|
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({
|
|
3802
|
-
let senderId = owner?.
|
|
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({
|
|
3805
|
-
senderId = other?.
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
3987
|
-
const [
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
4194
|
-
const current = (await listAdapterConfigs(app.db)).find((c) => c.agentId === identity.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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("/:
|
|
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.
|
|
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.
|
|
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.
|
|
4457
|
-
setConnection(agent.
|
|
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.
|
|
4462
|
-
await setOffline(app.db, agent.
|
|
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/:
|
|
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("/:
|
|
4482
|
-
const {
|
|
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,
|
|
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/:
|
|
4531
|
+
* GET /bootstrap/:name/status
|
|
4510
4532
|
* Check if an agent exists and its status (for polling).
|
|
4511
4533
|
*/
|
|
4512
|
-
app.get("/:
|
|
4513
|
-
const {
|
|
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
|
|
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 "${
|
|
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({
|
|
4584
|
-
if (existing) return existing.
|
|
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
|
-
|
|
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
|
-
})).
|
|
4616
|
+
})).uuid;
|
|
4595
4617
|
} catch (err) {
|
|
4596
4618
|
if (err instanceof ConflictError) {
|
|
4597
|
-
const [created] = await db.select({
|
|
4598
|
-
if (created) return created.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
4677
|
+
id: agents.uuid,
|
|
4654
4678
|
status: agents.status
|
|
4655
|
-
}).from(agents).where(eq(agents.
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
5489
|
+
id: agents.uuid,
|
|
5462
5490
|
type: agents.type,
|
|
5463
5491
|
status: agents.status
|
|
5464
|
-
}).from(agents).where(eq(agents.
|
|
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.
|
|
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
|
|
5712
|
-
const agentInboxMap = new Map(agentRows.map((a) => [a.
|
|
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.
|
|
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.
|
|
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
|