@agent-team-foundation/first-tree-hub 0.12.9 → 0.12.10
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-BCZC1ki6.mjs → bootstrap-CW73oEYn.mjs} +1 -1
- package/dist/cli/index.mjs +7 -7
- package/dist/{client-OMwJMCQt-R1T06ZH6.mjs → client-BViGcaUC-CZb2Svgh.mjs} +53 -5
- package/dist/{client-CjGIGddS-BrpazWa3.mjs → client-DNiLcPEq-db3YS57z.mjs} +2 -2
- package/dist/{dist-CnjqakXS.mjs → dist-B1GHzMLc.mjs} +107 -35
- package/dist/drizzle/0041_notifications_dedup_key.sql +29 -0
- package/dist/drizzle/0042_notifications_drop_legacy_types.sql +36 -0
- package/dist/drizzle/meta/_journal.json +14 -0
- package/dist/{feishu-DrnBbl8T.mjs → feishu-30vUx69l.mjs} +1 -1
- package/dist/index.mjs +5 -5
- package/dist/{invitation-C299fxkP-KKslbta2.mjs → invitation-C299fxkP-CZgGbsN_.mjs} +1 -1
- package/dist/{saas-connect-CXZhK485.mjs → saas-connect-Fgnnnola.mjs} +1285 -678
- package/dist/web/assets/{index-BPMrSv_A.js → index-B7FIVwrn.js} +1 -1
- package/dist/web/assets/index-DJbUySaH.css +1 -0
- package/dist/web/assets/index-DiDfVdIH.js +421 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-DxAYxUpz.css +0 -1
- package/dist/web/assets/index-ntmzuk5X.js +0 -421
|
@@ -598,7 +598,7 @@ const serverConfigSchema = defineConfig({
|
|
|
598
598
|
rateLimit: optional({
|
|
599
599
|
max: field(z.number().default(100), { env: "FIRST_TREE_HUB_RATE_LIMIT_MAX" }),
|
|
600
600
|
loginMax: field(z.number().default(5), { env: "FIRST_TREE_HUB_RATE_LIMIT_LOGIN_MAX" }),
|
|
601
|
-
webhookMax: field(z.number().default(
|
|
601
|
+
webhookMax: field(z.number().default(600), { env: "FIRST_TREE_HUB_RATE_LIMIT_WEBHOOK_MAX" }),
|
|
602
602
|
contextTreeSnapshotMax: field(z.number().default(6), { env: "FIRST_TREE_HUB_RATE_LIMIT_CONTEXT_TREE_SNAPSHOT_MAX" }),
|
|
603
603
|
agentMessageMax: field(z.number().default(30), { env: "FIRST_TREE_HUB_RATE_LIMIT_AGENT_MESSAGE_MAX" })
|
|
604
604
|
}),
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "../observability-BAScT_5S-BcW9HgkG.mjs";
|
|
3
|
-
import { $ as formatStaleReason, A as checkDocker, B as isServiceSupported, C as createApiNameResolver, D as checkBackgroundService, E as checkAgentConfigs, F as checkWebSocket, H as restartClientService, I as printResults, J as stopPostgres, L as reconcileAgentConfigs, M as checkServerConfig, N as checkServerHealth, O as checkClientConfig, P as checkServerReachable, Q as findStaleAliases, R as getClientServiceStatus, S as runHomeMigration, T as runMigrations, U as startClientService, W as stopClientService, X as handleClientOrgMismatch, Y as ClientRuntime, _ as formatCheckReport, a as declineUpdate, at as fail, b as onboardCreate, c as detectInstallMode, ct as ClientUserMismatchError, d as startServer, dt as SessionRegistry, et as removeLocalAgent, f as reconcileLocalRuntimeProviders, ft as cleanWorkspaces, g as promptMissingFields, h as promptAddAgent, ht as configureClientLoggerForService, i as createExecuteUpdate, it as resolveSenderName, j as checkNodeVersion, k as checkDatabase, l as fetchLatestVersion, lt as FirstTreeHubSDK, m as isInteractive, mt as applyClientLoggerConfig, o as promptUpdate, ot as success, p as uploadClientCapabilities, pt as probeCapabilities, r as registerSaaSConnectCommand, rt as resolveReplyToFromEnv, s as PACKAGE_NAME, st as ClientOrgMismatchError, tt as createOwner, u as installGlobalLatest, ut as SdkError, v as loadOnboardState, w as migrateLocalAgentDirs, x as saveOnboardState, y as onboardCheck, z as installClientService } from "../saas-connect-
|
|
3
|
+
import { $ as formatStaleReason, A as checkDocker, B as isServiceSupported, C as createApiNameResolver, D as checkBackgroundService, E as checkAgentConfigs, F as checkWebSocket, H as restartClientService, I as printResults, J as stopPostgres, L as reconcileAgentConfigs, M as checkServerConfig, N as checkServerHealth, O as checkClientConfig, P as checkServerReachable, Q as findStaleAliases, R as getClientServiceStatus, S as runHomeMigration, T as runMigrations, U as startClientService, W as stopClientService, X as handleClientOrgMismatch, Y as ClientRuntime, _ as formatCheckReport, a as declineUpdate, at as fail, b as onboardCreate, c as detectInstallMode, ct as ClientUserMismatchError, d as startServer, dt as SessionRegistry, et as removeLocalAgent, f as reconcileLocalRuntimeProviders, ft as cleanWorkspaces, g as promptMissingFields, h as promptAddAgent, ht as configureClientLoggerForService, i as createExecuteUpdate, it as resolveSenderName, j as checkNodeVersion, k as checkDatabase, l as fetchLatestVersion, lt as FirstTreeHubSDK, m as isInteractive, mt as applyClientLoggerConfig, o as promptUpdate, ot as success, p as uploadClientCapabilities, pt as probeCapabilities, r as registerSaaSConnectCommand, rt as resolveReplyToFromEnv, s as PACKAGE_NAME, st as ClientOrgMismatchError, tt as createOwner, u as installGlobalLatest, ut as SdkError, v as loadOnboardState, w as migrateLocalAgentDirs, x as saveOnboardState, y as onboardCheck, z as installClientService } from "../saas-connect-Fgnnnola.mjs";
|
|
4
4
|
import "../logger-core-BTmvdflj-DjW8FM4T.mjs";
|
|
5
|
-
import { C as resetConfigMeta, E as setConfigValue, S as resetConfig, T as serverConfigSchema, _ as getConfigValue, a as ensureFreshAdminToken, c as resolveServerUrl, d as DEFAULT_CONFIG_DIR, f as DEFAULT_DATA_DIR, h as clientConfigSchema, i as ensureFreshAccessToken, l as saveAgentConfig, m as agentConfigSchema, o as loadCredentials, p as DEFAULT_HOME_DIR, u as saveCredentials, v as initConfig, w as resolveConfigReadonly, x as readConfigFile, y as loadAgents } from "../bootstrap-
|
|
5
|
+
import { C as resetConfigMeta, E as setConfigValue, S as resetConfig, T as serverConfigSchema, _ as getConfigValue, a as ensureFreshAdminToken, c as resolveServerUrl, d as DEFAULT_CONFIG_DIR, f as DEFAULT_DATA_DIR, h as clientConfigSchema, i as ensureFreshAccessToken, l as saveAgentConfig, m as agentConfigSchema, o as loadCredentials, p as DEFAULT_HOME_DIR, u as saveCredentials, v as initConfig, w as resolveConfigReadonly, x as readConfigFile, y as loadAgents } from "../bootstrap-CW73oEYn.mjs";
|
|
6
6
|
import { a as print, n as CLI_USER_AGENT, o as setJsonMode, r as COMMAND_VERSION, t as cliFetch } from "../cli-fetch--tiwKm5S.mjs";
|
|
7
|
-
import "../dist-
|
|
8
|
-
import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-
|
|
7
|
+
import "../dist-B1GHzMLc.mjs";
|
|
8
|
+
import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-30vUx69l.mjs";
|
|
9
9
|
import "../errors-CF5evtJt-B0NTIVPt.mjs";
|
|
10
10
|
import "../src-DNBS5Yjj.mjs";
|
|
11
|
-
import "../client-
|
|
11
|
+
import "../client-BViGcaUC-CZb2Svgh.mjs";
|
|
12
12
|
import "../invitation-Bg0TRiyx-BsZH4GCS.mjs";
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
import { existsSync, mkdirSync, readFileSync, readdirSync } from "node:fs";
|
|
@@ -1707,13 +1707,13 @@ function isSecretField(schema, dotPath) {
|
|
|
1707
1707
|
//#region src/commands/onboard.ts
|
|
1708
1708
|
async function promptMissing(args) {
|
|
1709
1709
|
if (!args.server) try {
|
|
1710
|
-
const { resolveServerUrl } = await import("../bootstrap-
|
|
1710
|
+
const { resolveServerUrl } = await import("../bootstrap-CW73oEYn.mjs").then((n) => n.r);
|
|
1711
1711
|
resolveServerUrl();
|
|
1712
1712
|
} catch {
|
|
1713
1713
|
args.server = await input({ message: "Hub server URL:" });
|
|
1714
1714
|
saveOnboardState(args);
|
|
1715
1715
|
}
|
|
1716
|
-
const { loadCredentials } = await import("../bootstrap-
|
|
1716
|
+
const { loadCredentials } = await import("../bootstrap-CW73oEYn.mjs").then((n) => n.r);
|
|
1717
1717
|
if (!loadCredentials()) throw new Error("No saved credentials. Run `first-tree-hub client connect <server-url>` before onboarding.");
|
|
1718
1718
|
if (!args.id) {
|
|
1719
1719
|
args.id = await input({ message: "Agent ID:" });
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { O as withSpan, f as messageAttrs, s as createLogger } from "./observability-BAScT_5S-BcW9HgkG.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { F as extractMentions, b as clientCapabilitiesSchema, i as AGENT_STATUSES, it as questionMessageContentSchema, j as defaultParticipantMode, lt as scanMentionTokens, o as AGENT_VISIBILITY, rt as questionAnswerMessageContentSchema, s as CHAT_ENGAGEMENT_STATUSES, v as agentTypeSchema } from "./dist-B1GHzMLc.mjs";
|
|
3
3
|
import { a as ConflictError, i as ClientUserMismatchError, l as organizations, n as BadRequestError, o as ForbiddenError, s as NotFoundError, u as users } from "./errors-CF5evtJt-B0NTIVPt.mjs";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import { and, desc, eq, inArray, isNotNull, lt, ne, or, sql } from "drizzle-orm";
|
|
6
6
|
import { bigserial, boolean, index, integer, jsonb, pgTable, primaryKey, text, timestamp, unique } from "drizzle-orm/pg-core";
|
|
7
|
-
//#region ../server/dist/client-
|
|
7
|
+
//#region ../server/dist/client-BViGcaUC.mjs
|
|
8
8
|
/**
|
|
9
9
|
* Client connections. A client is a single SDK process (AgentRuntime) that may
|
|
10
10
|
* host multiple agents. From the unified-user-token milestone on, a client is
|
|
@@ -237,7 +237,7 @@ function invalidateChatAudience(chatId) {
|
|
|
237
237
|
* - for each row, `peerAgentTypes` is the type of every OTHER participant
|
|
238
238
|
* being inserted in the same call PLUS every EXISTING speaker of
|
|
239
239
|
* the chat. This matters only for `direct` chats; the helper ignores
|
|
240
|
-
* it for `group
|
|
240
|
+
* it for `group`.
|
|
241
241
|
*
|
|
242
242
|
* Writes one INSERT (multi-row) per call.
|
|
243
243
|
*
|
|
@@ -250,7 +250,7 @@ async function addChatParticipants(tx, chatId, participants, options = {}) {
|
|
|
250
250
|
if (participants.length === 0) return;
|
|
251
251
|
const [chat] = await tx.select({ type: chats.type }).from(chats).where(eq(chats.id, chatId)).limit(1);
|
|
252
252
|
if (!chat) throw new NotFoundError(`Chat "${chatId}" not found`);
|
|
253
|
-
if (chat.type !== "direct" && chat.type !== "group"
|
|
253
|
+
if (chat.type !== "direct" && chat.type !== "group") throw new Error(`Unexpected chat type "${chat.type}" for chat "${chatId}"`);
|
|
254
254
|
const chatType = chat.type;
|
|
255
255
|
const agentIds = participants.map((p) => p.agentId);
|
|
256
256
|
const agentRows = await tx.select({
|
|
@@ -1314,17 +1314,27 @@ const RUNTIME_STATE_CHANNEL = "runtime_state_changes";
|
|
|
1314
1314
|
* inbox NOTIFY path that only reaches speakers.
|
|
1315
1315
|
*/
|
|
1316
1316
|
const CHAT_MESSAGE_CHANNEL = "chat_message_events";
|
|
1317
|
+
/**
|
|
1318
|
+
* Generic admin-broadcast envelope channel. Producers (e.g. notification.ts)
|
|
1319
|
+
* emit a JSON-stringified `AdminBroadcastPayload`; every server instance
|
|
1320
|
+
* LISTENs and hands the envelope to its local admin-socket fanout. Keeps
|
|
1321
|
+
* cross-instance and single-instance code paths identical from the admin WS
|
|
1322
|
+
* route's perspective.
|
|
1323
|
+
*/
|
|
1324
|
+
const ADMIN_BROADCAST_CHANNEL = "admin_broadcast_envelopes";
|
|
1317
1325
|
function createNotifier(listenClient) {
|
|
1318
1326
|
const subscriptions = /* @__PURE__ */ new Map();
|
|
1319
1327
|
const configChangeHandlers = [];
|
|
1320
1328
|
const sessionStateChangeHandlers = [];
|
|
1321
1329
|
const runtimeStateChangeHandlers = [];
|
|
1322
1330
|
const chatMessageHandlers = [];
|
|
1331
|
+
const adminBroadcastHandlers = [];
|
|
1323
1332
|
let unlistenInboxFn = null;
|
|
1324
1333
|
let unlistenConfigFn = null;
|
|
1325
1334
|
let unlistenSessionStateFn = null;
|
|
1326
1335
|
let unlistenRuntimeStateFn = null;
|
|
1327
1336
|
let unlistenChatMessageFn = null;
|
|
1337
|
+
let unlistenAdminBroadcastFn = null;
|
|
1328
1338
|
function handleNotification(payload) {
|
|
1329
1339
|
const sepIdx = payload.indexOf(":");
|
|
1330
1340
|
if (sepIdx === -1) return;
|
|
@@ -1384,6 +1394,21 @@ function createNotifier(listenClient) {
|
|
|
1384
1394
|
await listenClient`SELECT pg_notify(${CHAT_MESSAGE_CHANNEL}, ${`${chatId}:${messageId}`})`;
|
|
1385
1395
|
} catch {}
|
|
1386
1396
|
},
|
|
1397
|
+
async notifyAdminBroadcast(payload) {
|
|
1398
|
+
let encoded;
|
|
1399
|
+
try {
|
|
1400
|
+
encoded = JSON.stringify(payload);
|
|
1401
|
+
} catch {
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1404
|
+
if (encoded.length > 7500) {
|
|
1405
|
+
console.error(`[notifier] admin broadcast payload too large (${encoded.length} bytes); refusing to NOTIFY`);
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
try {
|
|
1409
|
+
await listenClient`SELECT pg_notify(${ADMIN_BROADCAST_CHANNEL}, ${encoded})`;
|
|
1410
|
+
} catch {}
|
|
1411
|
+
},
|
|
1387
1412
|
async pushFrameToInbox(inboxId, frame) {
|
|
1388
1413
|
const map = subscriptions.get(inboxId);
|
|
1389
1414
|
if (!map) return 0;
|
|
@@ -1413,6 +1438,9 @@ function createNotifier(listenClient) {
|
|
|
1413
1438
|
onChatMessage(handler) {
|
|
1414
1439
|
chatMessageHandlers.push(handler);
|
|
1415
1440
|
},
|
|
1441
|
+
onAdminBroadcast(handler) {
|
|
1442
|
+
adminBroadcastHandlers.push(handler);
|
|
1443
|
+
},
|
|
1416
1444
|
async start() {
|
|
1417
1445
|
unlistenInboxFn = (await listenClient.listen(INBOX_CHANNEL, (payload) => {
|
|
1418
1446
|
if (payload) handleNotification(payload);
|
|
@@ -1468,6 +1496,20 @@ function createNotifier(listenClient) {
|
|
|
1468
1496
|
});
|
|
1469
1497
|
} catch {}
|
|
1470
1498
|
})).unlisten;
|
|
1499
|
+
unlistenAdminBroadcastFn = (await listenClient.listen(ADMIN_BROADCAST_CHANNEL, (payload) => {
|
|
1500
|
+
if (!payload) return;
|
|
1501
|
+
let parsed;
|
|
1502
|
+
try {
|
|
1503
|
+
parsed = JSON.parse(payload);
|
|
1504
|
+
} catch {
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
if (typeof parsed !== "object" || parsed === null) return;
|
|
1508
|
+
const envelope = parsed;
|
|
1509
|
+
for (const handler of adminBroadcastHandlers) try {
|
|
1510
|
+
handler(envelope);
|
|
1511
|
+
} catch {}
|
|
1512
|
+
})).unlisten;
|
|
1471
1513
|
},
|
|
1472
1514
|
async stop() {
|
|
1473
1515
|
if (unlistenInboxFn) {
|
|
@@ -1490,6 +1532,10 @@ function createNotifier(listenClient) {
|
|
|
1490
1532
|
await unlistenChatMessageFn();
|
|
1491
1533
|
unlistenChatMessageFn = null;
|
|
1492
1534
|
}
|
|
1535
|
+
if (unlistenAdminBroadcastFn) {
|
|
1536
|
+
await unlistenAdminBroadcastFn();
|
|
1537
|
+
unlistenAdminBroadcastFn = null;
|
|
1538
|
+
}
|
|
1493
1539
|
}
|
|
1494
1540
|
};
|
|
1495
1541
|
}
|
|
@@ -1700,6 +1746,7 @@ async function sendMessageInner(db, chatId, senderId, data, options) {
|
|
|
1700
1746
|
...incomingMeta,
|
|
1701
1747
|
mentions: mergedMentions
|
|
1702
1748
|
} : incomingMeta;
|
|
1749
|
+
const dmAutoProjection = chatType === "direct" ? [...new Set([...mergedMentions, ...participants.filter((p) => p.agentId !== senderId).map((p) => p.agentId)])] : mergedMentions;
|
|
1703
1750
|
if (options.enforceGroupMention && chatType === "group") {
|
|
1704
1751
|
if (mergedMentions.filter((id) => id !== senderId).length === 0) throw new BadRequestError("Sending to a group chat requires an explicit @mention. Use `agent send <name>` to message a single agent, or @<name> in the content to address one or more group members.");
|
|
1705
1752
|
}
|
|
@@ -1726,6 +1773,7 @@ async function sendMessageInner(db, chatId, senderId, data, options) {
|
|
|
1726
1773
|
senderId,
|
|
1727
1774
|
source: data.source ?? null
|
|
1728
1775
|
}, "silent send: empty content after mention strip — no fan-out wake-up");
|
|
1776
|
+
const projectionMentions = isSilentSend ? [] : dmAutoProjection;
|
|
1729
1777
|
const messageId = randomUUID();
|
|
1730
1778
|
const [msg] = await tx.insert(messages).values({
|
|
1731
1779
|
id: messageId,
|
|
@@ -1782,7 +1830,7 @@ async function sendMessageInner(db, chatId, senderId, data, options) {
|
|
|
1782
1830
|
chatId,
|
|
1783
1831
|
messageId: msg.id,
|
|
1784
1832
|
senderId,
|
|
1785
|
-
mentionedAgentIds:
|
|
1833
|
+
mentionedAgentIds: projectionMentions,
|
|
1786
1834
|
contentPreview: previewText,
|
|
1787
1835
|
messageCreatedAt: msg.createdAt
|
|
1788
1836
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "./observability-BAScT_5S-BcW9HgkG.mjs";
|
|
2
2
|
import "./logger-core-BTmvdflj-DjW8FM4T.mjs";
|
|
3
|
-
import "./dist-
|
|
3
|
+
import "./dist-B1GHzMLc.mjs";
|
|
4
4
|
import "./errors-CF5evtJt-B0NTIVPt.mjs";
|
|
5
5
|
import "./src-DNBS5Yjj.mjs";
|
|
6
|
-
import { J as listMyPinnedAgents } from "./client-
|
|
6
|
+
import { J as listMyPinnedAgents } from "./client-BViGcaUC-CZb2Svgh.mjs";
|
|
7
7
|
export { listMyPinnedAgents };
|
|
@@ -74,7 +74,7 @@ function scanMentionTokens(content) {
|
|
|
74
74
|
*/
|
|
75
75
|
function defaultParticipantMode(chatType, agentType, peerAgentTypes = []) {
|
|
76
76
|
if (agentType === "human") return "full";
|
|
77
|
-
if (chatType === "group"
|
|
77
|
+
if (chatType === "group") return "mention_only";
|
|
78
78
|
return peerAgentTypes.every((t) => t !== "human") ? "mention_only" : "full";
|
|
79
79
|
}
|
|
80
80
|
/**
|
|
@@ -476,6 +476,11 @@ z.object({
|
|
|
476
476
|
});
|
|
477
477
|
const runtimeProviderSchema = z.enum(["claude-code", "codex"]);
|
|
478
478
|
const DEFAULT_RUNTIME_PROVIDER = "claude-code";
|
|
479
|
+
const AGENT_TYPES = {
|
|
480
|
+
HUMAN: "human",
|
|
481
|
+
PERSONAL_ASSISTANT: "personal_assistant",
|
|
482
|
+
AUTONOMOUS_AGENT: "autonomous_agent"
|
|
483
|
+
};
|
|
479
484
|
const agentTypeSchema = z.enum([
|
|
480
485
|
"human",
|
|
481
486
|
"personal_assistant",
|
|
@@ -627,11 +632,7 @@ const chatMetadataSchema = z.discriminatedUnion("source", [githubChatMetadataSch
|
|
|
627
632
|
* sneak through `{ source: "github" }` without the required fields.
|
|
628
633
|
*/
|
|
629
634
|
const optionalChatMetadataSchema = z.union([z.object({}).strict(), chatMetadataSchema]);
|
|
630
|
-
const chatTypeSchema = z.enum([
|
|
631
|
-
"direct",
|
|
632
|
-
"group",
|
|
633
|
-
"thread"
|
|
634
|
-
]);
|
|
635
|
+
const chatTypeSchema = z.enum(["direct", "group"]);
|
|
635
636
|
/**
|
|
636
637
|
* Per-(chat, user) engagement state. Stored on `chat_user_state` so each
|
|
637
638
|
* user manages their own view independently of structural membership.
|
|
@@ -1179,7 +1180,8 @@ const meChatRowSchema = z.object({
|
|
|
1179
1180
|
lastMessagePreview: z.string().nullable(),
|
|
1180
1181
|
unreadMentionCount: z.number().int(),
|
|
1181
1182
|
canReply: z.boolean(),
|
|
1182
|
-
engagementStatus: chatEngagementStatusSchema
|
|
1183
|
+
engagementStatus: chatEngagementStatusSchema,
|
|
1184
|
+
workingAgentIds: z.array(z.string())
|
|
1183
1185
|
});
|
|
1184
1186
|
z.object({
|
|
1185
1187
|
rows: z.array(meChatRowSchema),
|
|
@@ -1287,15 +1289,106 @@ memberSchema.extend({
|
|
|
1287
1289
|
displayName: z.string(),
|
|
1288
1290
|
password: z.string()
|
|
1289
1291
|
});
|
|
1292
|
+
/**
|
|
1293
|
+
* Origin of a normalized webhook event. After the GitHub App ingestion
|
|
1294
|
+
* cutover this is single-form (App installations only); the type stays
|
|
1295
|
+
* a structured object so future non-GitHub sources can be added by
|
|
1296
|
+
* widening it into a discriminated union without churning callers.
|
|
1297
|
+
*/
|
|
1298
|
+
const webhookSourceSchema = z.object({
|
|
1299
|
+
kind: z.literal("github-app-installation"),
|
|
1300
|
+
installationId: z.number().int(),
|
|
1301
|
+
organizationId: z.string().min(1)
|
|
1302
|
+
});
|
|
1303
|
+
const involveReasonSchema = z.enum([
|
|
1304
|
+
"mentioned",
|
|
1305
|
+
"review_requested",
|
|
1306
|
+
"assigned"
|
|
1307
|
+
]);
|
|
1308
|
+
const normalizedEventKindSchema = z.enum([
|
|
1309
|
+
"opened",
|
|
1310
|
+
"edited",
|
|
1311
|
+
"closed",
|
|
1312
|
+
"merged",
|
|
1313
|
+
"reopened",
|
|
1314
|
+
"commented",
|
|
1315
|
+
"review_requested",
|
|
1316
|
+
"reviewed",
|
|
1317
|
+
"review_comment",
|
|
1318
|
+
"synchronized",
|
|
1319
|
+
"commit_commented",
|
|
1320
|
+
"assigned",
|
|
1321
|
+
"other"
|
|
1322
|
+
]);
|
|
1323
|
+
const normalizedEntitySchema = z.object({
|
|
1324
|
+
type: githubEntityTypeSchema,
|
|
1325
|
+
repo: z.string().min(1),
|
|
1326
|
+
key: z.string().min(1),
|
|
1327
|
+
title: z.string().optional(),
|
|
1328
|
+
url: z.string().optional()
|
|
1329
|
+
});
|
|
1330
|
+
const normalizedActorSchema = z.object({
|
|
1331
|
+
githubLogin: z.string().min(1),
|
|
1332
|
+
isBot: z.boolean()
|
|
1333
|
+
});
|
|
1334
|
+
const normalizedInvolveSchema = z.object({
|
|
1335
|
+
githubLogin: z.string().min(1),
|
|
1336
|
+
reason: involveReasonSchema
|
|
1337
|
+
});
|
|
1338
|
+
const normalizedSurfaceSchema = z.object({
|
|
1339
|
+
title: z.string(),
|
|
1340
|
+
body: z.string(),
|
|
1341
|
+
url: z.string()
|
|
1342
|
+
});
|
|
1343
|
+
const normalizedRelatedRefSchema = z.object({
|
|
1344
|
+
type: z.literal("issue"),
|
|
1345
|
+
key: z.string().min(1)
|
|
1346
|
+
});
|
|
1347
|
+
z.object({
|
|
1348
|
+
source: webhookSourceSchema,
|
|
1349
|
+
deliveryId: z.string().nullable(),
|
|
1350
|
+
rawEventType: z.string().min(1),
|
|
1351
|
+
rawAction: z.string().nullable(),
|
|
1352
|
+
entity: normalizedEntitySchema,
|
|
1353
|
+
actor: normalizedActorSchema,
|
|
1354
|
+
kind: normalizedEventKindSchema,
|
|
1355
|
+
involves: z.array(normalizedInvolveSchema),
|
|
1356
|
+
surface: normalizedSurfaceSchema,
|
|
1357
|
+
relatedRefs: z.array(normalizedRelatedRefSchema)
|
|
1358
|
+
});
|
|
1359
|
+
const githubEventCardReasonSchema = z.enum([
|
|
1360
|
+
"mentioned",
|
|
1361
|
+
"review_requested",
|
|
1362
|
+
"assigned",
|
|
1363
|
+
"subscribed"
|
|
1364
|
+
]);
|
|
1365
|
+
z.object({
|
|
1366
|
+
type: z.literal("github_event"),
|
|
1367
|
+
reason: githubEventCardReasonSchema,
|
|
1368
|
+
event: z.string().min(1),
|
|
1369
|
+
action: z.string().nullable(),
|
|
1370
|
+
kind: normalizedEventKindSchema,
|
|
1371
|
+
repository: z.string(),
|
|
1372
|
+
sender: z.string(),
|
|
1373
|
+
title: z.string(),
|
|
1374
|
+
body: z.string(),
|
|
1375
|
+
url: z.string(),
|
|
1376
|
+
entity: z.object({
|
|
1377
|
+
type: githubEntityTypeSchema,
|
|
1378
|
+
key: z.string().min(1),
|
|
1379
|
+
url: z.string().nullable()
|
|
1380
|
+
}),
|
|
1381
|
+
mentionedUser: z.string().optional()
|
|
1382
|
+
});
|
|
1383
|
+
const NOTIFICATION_TYPES = {
|
|
1384
|
+
AGENT_ERROR: "agent_error",
|
|
1385
|
+
AGENT_BLOCKED: "agent_blocked",
|
|
1386
|
+
AGENT_STALE: "agent_stale"
|
|
1387
|
+
};
|
|
1290
1388
|
const notificationTypeSchema = z.enum([
|
|
1291
1389
|
"agent_error",
|
|
1292
|
-
"session_error",
|
|
1293
|
-
"agent_needs_decision",
|
|
1294
1390
|
"agent_blocked",
|
|
1295
|
-
"agent_stale"
|
|
1296
|
-
"agent_disconnected",
|
|
1297
|
-
"agent_connected",
|
|
1298
|
-
"session_completed"
|
|
1391
|
+
"agent_stale"
|
|
1299
1392
|
]);
|
|
1300
1393
|
const notificationSeveritySchema = z.enum([
|
|
1301
1394
|
"high",
|
|
@@ -1430,12 +1523,6 @@ const orgContextTreeOutputSchema = z.object({
|
|
|
1430
1523
|
repo: z.string().optional(),
|
|
1431
1524
|
branch: z.string().optional()
|
|
1432
1525
|
});
|
|
1433
|
-
const orgGithubIntegrationStorageSchema = z.object({ webhookSecretCipher: z.string().optional() });
|
|
1434
|
-
const orgGithubIntegrationInputSchema = z.object({ webhookSecret: z.string().min(1).nullish() });
|
|
1435
|
-
const orgGithubIntegrationOutputSchema = z.object({
|
|
1436
|
-
webhookSecretConfigured: z.boolean(),
|
|
1437
|
-
webhookUrl: z.string()
|
|
1438
|
-
});
|
|
1439
1526
|
const orgSourceReposStorageSchema = z.object({ repos: z.array(z.object({
|
|
1440
1527
|
url: repoUrlSchema,
|
|
1441
1528
|
defaultBranch: z.string().optional()
|
|
@@ -1455,12 +1542,6 @@ const ORG_SETTINGS_NAMESPACES = {
|
|
|
1455
1542
|
output: orgContextTreeOutputSchema,
|
|
1456
1543
|
readPolicy: "member"
|
|
1457
1544
|
},
|
|
1458
|
-
github_integration: {
|
|
1459
|
-
storage: orgGithubIntegrationStorageSchema,
|
|
1460
|
-
input: orgGithubIntegrationInputSchema,
|
|
1461
|
-
output: orgGithubIntegrationOutputSchema,
|
|
1462
|
-
readPolicy: "admin"
|
|
1463
|
-
},
|
|
1464
1545
|
source_repos: {
|
|
1465
1546
|
storage: orgSourceReposStorageSchema,
|
|
1466
1547
|
input: orgSourceReposInputSchema,
|
|
@@ -1678,15 +1759,6 @@ const sessionEventMessageSchema = z.object({
|
|
|
1678
1759
|
chatId: z.string(),
|
|
1679
1760
|
event: sessionEventSchema
|
|
1680
1761
|
});
|
|
1681
|
-
/**
|
|
1682
|
-
* WS control message: client signals that a query completed end-to-end.
|
|
1683
|
-
* Decoupled from `session:event` so the `session_completed` notification
|
|
1684
|
-
* fires on actual result forwarding, not on incidental tool activity.
|
|
1685
|
-
*/
|
|
1686
|
-
const sessionCompletionMessageSchema = z.object({
|
|
1687
|
-
agentId: z.string(),
|
|
1688
|
-
chatId: z.string()
|
|
1689
|
-
});
|
|
1690
1762
|
/** Client → server: list locally-held chatIds; server replies with the subset to drop. */
|
|
1691
1763
|
const sessionReconcileRequestSchema = z.object({
|
|
1692
1764
|
type: z.literal("session:reconcile"),
|
|
@@ -1754,4 +1826,4 @@ z.object({
|
|
|
1754
1826
|
capabilities: serverCapabilitiesSchema.optional()
|
|
1755
1827
|
}).passthrough();
|
|
1756
1828
|
//#endregion
|
|
1757
|
-
export {
|
|
1829
|
+
export { onboardingEventSchema as $, createOrgFromMeSchema as A, githubStartQuerySchema as B, contextTreeSnapshotSchema as C, updateClientCapabilitiesSchema as Ct, createChatSchema as D, createAgentSchema as E, wsAuthFrameSchema as Et, extractMentions as F, isOrgSettingNamespace as G, inboxAckFrameSchema as H, githubAppInstallationClaimBodySchema as I, joinByInvitationSchema as J, isRedactedEnvValue as K, githubAppInstallationPermissionsSchema as L, defaultRuntimeConfigPayload as M, delegateFeishuUserSchema as N, createMeChatSchema as O, dryRunAgentRuntimeConfigSchema as P, notificationQuerySchema as Q, githubCallbackQuerySchema as R, connectTokenExchangeSchema as S, updateChatSchema as St, createAdapterMappingSchema as T, updateOrganizationSchema as Tt, inboxDeliverFrameSchema as U, imageInlineContentSchema as V, inboxPollQuerySchema as W, loginSchema as X, listMeChatsQuerySchema as Y, messageSourceSchema as Z, agentRuntimeConfigPayloadSchema as _, stripCode as _t, AGENT_TYPES as a, rebindAgentSchema as at, clientCapabilitiesSchema as b, updateAgentRuntimeConfigSchema as bt, DEFAULT_RUNTIME_PROVIDER as c, safeRedirectPath as ct, ORG_SETTINGS_NAMESPACES as d, sendMessageSchema as dt, paginationQuerySchema as et, WS_AUTH_FRAME_TIMEOUT_MS as f, sendToAgentSchema as ft, agentPinnedMessageSchema as g, sessionStateMessageSchema as gt, agentBindRequestSchema as h, sessionReconcileRequestSchema as ht, AGENT_STATUSES as i, questionMessageContentSchema as it, defaultParticipantMode as j, createMemberSchema as k, MENTION_REGEX as l, scanMentionTokens as lt, addParticipantSchema as m, sessionEventSchema as mt, AGENT_NAME_REGEX as n, patchOnboardingSchema as nt, AGENT_VISIBILITY as o, refreshTokenSchema as ot, addMeChatParticipantsSchema as p, sessionEventMessageSchema as pt, isReservedAgentName as q, AGENT_SELECTOR_HEADER as r, questionAnswerMessageContentSchema as rt, CHAT_ENGAGEMENT_STATUSES as s, runtimeStateMessageSchema as st, AGENT_BIND_REJECT_REASONS as t, patchChatEngagementSchema as tt, NOTIFICATION_TYPES as u, selfServiceFeishuBotSchema as ut, agentTypeSchema as v, submitQuestionAnswerSchema as vt, createAdapterConfigSchema as w, updateMemberSchema as wt, clientRegisterSchema as x, updateAgentSchema as xt, chatMetadataSchema as y, updateAdapterConfigSchema as yt, githubDevCallbackQuerySchema as z };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
-- DB-backed deduplication for the notifications table.
|
|
2
|
+
--
|
|
3
|
+
-- Before this migration, the server kept an in-memory Map keyed by
|
|
4
|
+
-- `(agentId, notificationType)` to suppress duplicate notifications within a
|
|
5
|
+
-- five-minute window. That broke in two important ways:
|
|
6
|
+
--
|
|
7
|
+
-- 1. Multi-instance deployments: each server process owned its own Map, so
|
|
8
|
+
-- a notification produced on instance A passed instance B's dedupe gate
|
|
9
|
+
-- unchanged. Operators saw the same row inserted multiple times during
|
|
10
|
+
-- load-balancer failover or rolling restart.
|
|
11
|
+
-- 2. Process restart wiped the Map, re-opening the dedupe window for
|
|
12
|
+
-- whatever events had fired just before.
|
|
13
|
+
--
|
|
14
|
+
-- The new model is purely DB-driven. A producer optionally supplies a
|
|
15
|
+
-- `dedup_key` (e.g. `agent:{uuid}:error`, `chat:{uuid}:completed`). The
|
|
16
|
+
-- partial unique index below scopes uniqueness to `(organization_id,
|
|
17
|
+
-- dedup_key)` *while the prior row is still unread*. Re-emitting the same
|
|
18
|
+
-- key while the previous notification sits unread is a no-op (the
|
|
19
|
+
-- application uses `ON CONFLICT DO NOTHING`); after the user acknowledges
|
|
20
|
+
-- the prior row, a fresh notification can land again.
|
|
21
|
+
--
|
|
22
|
+
-- Producers without a `dedup_key` keep the legacy always-insert behaviour,
|
|
23
|
+
-- so this column is purely additive — no back-fill, no NOT NULL constraint.
|
|
24
|
+
|
|
25
|
+
ALTER TABLE "notifications" ADD COLUMN "dedup_key" text;
|
|
26
|
+
|
|
27
|
+
CREATE UNIQUE INDEX "uq_notifications_org_dedup_unread"
|
|
28
|
+
ON "notifications" ("organization_id", "dedup_key")
|
|
29
|
+
WHERE read = false AND dedup_key IS NOT NULL;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
-- Drop notification rows whose `type` is no longer in the shared schema.
|
|
2
|
+
--
|
|
3
|
+
-- Three types were removed in earlier work but their rows were left behind:
|
|
4
|
+
--
|
|
5
|
+
-- * `agent_disconnected` — producer deleted by PR #348 (the
|
|
6
|
+
-- systemctl-restart spam fix), schema enum entry deleted later. 37 rows
|
|
7
|
+
-- still in dev hub at the time of writing, with no UI label and no
|
|
8
|
+
-- click target.
|
|
9
|
+
-- * `session_completed` — removed end-to-end in this PR (was 56% of recent
|
|
10
|
+
-- notifications, duplicating the conversation list's "latest message"
|
|
11
|
+
-- signal). Without the cleanup, the bell would keep surfacing them
|
|
12
|
+
-- until each org's user marks them all read manually.
|
|
13
|
+
-- * `session_error` — long-dead type: schema entry + UI label existed,
|
|
14
|
+
-- but no producer ever wrote one. Cleanup is defensive — any stray
|
|
15
|
+
-- rows from a hand-written test or prod-DB experiment go.
|
|
16
|
+
--
|
|
17
|
+
-- `notifications.type` is a `text` column (not a PG enum), so this is a
|
|
18
|
+
-- pure data-cleanup migration — no schema change, no type-cast risk.
|
|
19
|
+
--
|
|
20
|
+
-- The unread-only branch handles the transition window for rows still on
|
|
21
|
+
-- the previous per-type dedup key (`agent:{id}:agent_error|blocked|stale`).
|
|
22
|
+
-- They co-exist with new `agent:{id}:fault` rows for the same agent until
|
|
23
|
+
-- one or the other is marked read, which is exactly the redundancy this PR
|
|
24
|
+
-- is fixing — so wipe them too. Read rows on those keys stay (history).
|
|
25
|
+
|
|
26
|
+
DELETE FROM "notifications"
|
|
27
|
+
WHERE "type" IN ('agent_disconnected', 'session_completed', 'session_error');
|
|
28
|
+
|
|
29
|
+
DELETE FROM "notifications"
|
|
30
|
+
WHERE "read" = false
|
|
31
|
+
AND "dedup_key" IN (
|
|
32
|
+
SELECT "dedup_key" FROM "notifications"
|
|
33
|
+
WHERE "dedup_key" LIKE 'agent:%:agent_error'
|
|
34
|
+
OR "dedup_key" LIKE 'agent:%:agent_blocked'
|
|
35
|
+
OR "dedup_key" LIKE 'agent:%:agent_stale'
|
|
36
|
+
);
|
|
@@ -288,6 +288,20 @@
|
|
|
288
288
|
"when": 1779235200000,
|
|
289
289
|
"tag": "0040_chat_user_state_engagement",
|
|
290
290
|
"breakpoints": true
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
"idx": 41,
|
|
294
|
+
"version": "7",
|
|
295
|
+
"when": 1779321600000,
|
|
296
|
+
"tag": "0041_notifications_dedup_key",
|
|
297
|
+
"breakpoints": true
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
"idx": 42,
|
|
301
|
+
"version": "7",
|
|
302
|
+
"when": 1779408000000,
|
|
303
|
+
"tag": "0042_notifications_drop_legacy_types",
|
|
304
|
+
"breakpoints": true
|
|
291
305
|
}
|
|
292
306
|
]
|
|
293
307
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { r as __exportAll } from "./chunk-BSw8zbkd.mjs";
|
|
2
2
|
import { t as cliFetch } from "./cli-fetch--tiwKm5S.mjs";
|
|
3
|
-
import { r as AGENT_SELECTOR_HEADER } from "./dist-
|
|
3
|
+
import { r as AGENT_SELECTOR_HEADER } from "./dist-B1GHzMLc.mjs";
|
|
4
4
|
//#region src/core/feishu.ts
|
|
5
5
|
var feishu_exports = /* @__PURE__ */ __exportAll({
|
|
6
6
|
bindFeishuBot: () => bindFeishuBot,
|
package/dist/index.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import "./observability-BAScT_5S-BcW9HgkG.mjs";
|
|
2
|
-
import { A as checkDocker, B as isServiceSupported, E as checkAgentConfigs, F as checkWebSocket, G as uninstallClientService, H as restartClientService, I as printResults, J as stopPostgres, K as ensurePostgres, M as checkServerConfig, N as checkServerHealth, O as checkClientConfig, P as checkServerReachable, R as getClientServiceStatus, S as runHomeMigration, T as runMigrations, U as startClientService, V as resolveCliInvocation, W as stopClientService, X as handleClientOrgMismatch, Y as ClientRuntime, Z as rotateClientIdWithBackup, _ as formatCheckReport, b as onboardCreate, d as startServer, g as promptMissingFields, h as promptAddAgent, j as checkNodeVersion, k as checkDatabase, lt as FirstTreeHubSDK, m as isInteractive, n as deriveHubUrlFromToken, nt as hasUser, q as isDockerAvailable, t as HubUrlDerivationError, tt as createOwner, ut as SdkError, y as onboardCheck, z as installClientService } from "./saas-connect-
|
|
2
|
+
import { A as checkDocker, B as isServiceSupported, E as checkAgentConfigs, F as checkWebSocket, G as uninstallClientService, H as restartClientService, I as printResults, J as stopPostgres, K as ensurePostgres, M as checkServerConfig, N as checkServerHealth, O as checkClientConfig, P as checkServerReachable, R as getClientServiceStatus, S as runHomeMigration, T as runMigrations, U as startClientService, V as resolveCliInvocation, W as stopClientService, X as handleClientOrgMismatch, Y as ClientRuntime, Z as rotateClientIdWithBackup, _ as formatCheckReport, b as onboardCreate, d as startServer, g as promptMissingFields, h as promptAddAgent, j as checkNodeVersion, k as checkDatabase, lt as FirstTreeHubSDK, m as isInteractive, n as deriveHubUrlFromToken, nt as hasUser, q as isDockerAvailable, t as HubUrlDerivationError, tt as createOwner, ut as SdkError, y as onboardCheck, z as installClientService } from "./saas-connect-Fgnnnola.mjs";
|
|
3
3
|
import "./logger-core-BTmvdflj-DjW8FM4T.mjs";
|
|
4
|
-
import { a as ensureFreshAdminToken, c as resolveServerUrl, i as ensureFreshAccessToken, n as AuthRefreshRateLimitedError, s as resolveAccessToken, t as AuthRefreshFailedError } from "./bootstrap-
|
|
4
|
+
import { a as ensureFreshAdminToken, c as resolveServerUrl, i as ensureFreshAccessToken, n as AuthRefreshRateLimitedError, s as resolveAccessToken, t as AuthRefreshFailedError } from "./bootstrap-CW73oEYn.mjs";
|
|
5
5
|
import { i as blank, s as status } from "./cli-fetch--tiwKm5S.mjs";
|
|
6
|
-
import "./dist-
|
|
7
|
-
import { n as bindFeishuUser, t as bindFeishuBot } from "./feishu-
|
|
6
|
+
import "./dist-B1GHzMLc.mjs";
|
|
7
|
+
import { n as bindFeishuUser, t as bindFeishuBot } from "./feishu-30vUx69l.mjs";
|
|
8
8
|
import "./errors-CF5evtJt-B0NTIVPt.mjs";
|
|
9
9
|
import "./src-DNBS5Yjj.mjs";
|
|
10
|
-
import "./client-
|
|
10
|
+
import "./client-BViGcaUC-CZb2Svgh.mjs";
|
|
11
11
|
import "./invitation-Bg0TRiyx-BsZH4GCS.mjs";
|
|
12
12
|
export { AuthRefreshFailedError, AuthRefreshRateLimitedError, ClientRuntime, FirstTreeHubSDK, HubUrlDerivationError, SdkError, bindFeishuBot, bindFeishuUser, blank, checkAgentConfigs, checkClientConfig, checkDatabase, checkDocker, checkNodeVersion, checkServerConfig, checkServerHealth, checkServerReachable, checkWebSocket, createOwner, deriveHubUrlFromToken, ensureFreshAccessToken, ensureFreshAdminToken, ensurePostgres, formatCheckReport, getClientServiceStatus, handleClientOrgMismatch, hasUser, installClientService, isDockerAvailable, isInteractive, isServiceSupported, onboardCheck, onboardCreate, printResults, promptAddAgent, promptMissingFields, resolveAccessToken, resolveCliInvocation, resolveServerUrl, restartClientService, rotateClientIdWithBackup, runHomeMigration, runMigrations, startClientService, startServer, status, stopClientService, stopPostgres, uninstallClientService };
|