@agent-team-foundation/first-tree-hub 0.12.5 → 0.12.7
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-C_K2CKXC.mjs → bootstrap-BCZC1ki6.mjs} +15 -5
- package/dist/cli/index.mjs +7 -7
- package/dist/{client-DSM_opoz-BH5eegXb.mjs → client-93HZWg84-MIPzQD9A.mjs} +2 -2
- package/dist/{client-DL5vHhvQ-CnYGq2x-.mjs → client-h5l7mi0m-OEX7MOBg.mjs} +163 -85
- package/dist/{dist-BwPlBZWi.mjs → dist-CTkhS6p5.mjs} +128 -9
- package/dist/drizzle/0037_github_app_installations.sql +52 -0
- package/dist/drizzle/meta/_journal.json +7 -0
- package/dist/{feishu-CKGzIamp.mjs → feishu-DJm0EaZP.mjs} +1 -1
- package/dist/index.mjs +5 -5
- package/dist/{invitation-C299fxkP-Dts66QTU.mjs → invitation-C299fxkP-jQiGR5fl.mjs} +1 -1
- package/dist/{saas-connect-DYjvx5yr.mjs → saas-connect-CY2NxeKx.mjs} +1274 -202
- package/dist/web/assets/index-BKbK8BhK.css +1 -0
- package/dist/web/assets/index-BNM-YSSu.js +421 -0
- package/dist/web/assets/index-JGwkYWtM.js +11 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-BXDLOc-s.js +0 -406
- package/dist/web/assets/index-CbOOQaWp.css +0 -1
- package/dist/web/assets/index-Dyo6TAWC.js +0 -16
|
@@ -576,12 +576,22 @@ const serverConfigSchema = defineConfig({
|
|
|
576
576
|
}),
|
|
577
577
|
githubTokenRepos: field(z.string().optional(), { env: "FIRST_TREE_HUB_CONTEXT_TREE_GITHUB_TOKEN_REPOS" })
|
|
578
578
|
}),
|
|
579
|
-
oauth: optional({
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
579
|
+
oauth: optional({ githubApp: optional({
|
|
580
|
+
appId: field(z.string().min(1), { env: "FIRST_TREE_HUB_GITHUB_APP_ID" }),
|
|
581
|
+
clientId: field(z.string().min(1), { env: "FIRST_TREE_HUB_GITHUB_APP_CLIENT_ID" }),
|
|
582
|
+
clientSecret: field(z.string().min(1), {
|
|
583
|
+
env: "FIRST_TREE_HUB_GITHUB_APP_CLIENT_SECRET",
|
|
583
584
|
secret: true
|
|
584
|
-
})
|
|
585
|
+
}),
|
|
586
|
+
privateKeyPem: field(z.string().min(1), {
|
|
587
|
+
env: "FIRST_TREE_HUB_GITHUB_APP_PRIVATE_KEY",
|
|
588
|
+
secret: true
|
|
589
|
+
}),
|
|
590
|
+
webhookSecret: field(z.string().min(1), {
|
|
591
|
+
env: "FIRST_TREE_HUB_GITHUB_APP_WEBHOOK_SECRET",
|
|
592
|
+
secret: true
|
|
593
|
+
}),
|
|
594
|
+
slug: field(z.string().min(1).optional(), { env: "FIRST_TREE_HUB_GITHUB_APP_SLUG" })
|
|
585
595
|
}) }),
|
|
586
596
|
cors: optional({ origin: field(z.string(), { env: "FIRST_TREE_HUB_CORS_ORIGIN" }) }),
|
|
587
597
|
trustProxy: field(z.boolean().default(false), { env: "FIRST_TREE_HUB_TRUST_PROXY" }),
|
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-CY2NxeKx.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-BCZC1ki6.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-CTkhS6p5.mjs";
|
|
8
|
+
import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-DJm0EaZP.mjs";
|
|
9
9
|
import "../errors-CF5evtJt-B0NTIVPt.mjs";
|
|
10
10
|
import "../src-DNBS5Yjj.mjs";
|
|
11
|
-
import "../client-
|
|
11
|
+
import "../client-h5l7mi0m-OEX7MOBg.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";
|
|
@@ -1672,13 +1672,13 @@ function isSecretField(schema, dotPath) {
|
|
|
1672
1672
|
//#region src/commands/onboard.ts
|
|
1673
1673
|
async function promptMissing(args) {
|
|
1674
1674
|
if (!args.server) try {
|
|
1675
|
-
const { resolveServerUrl } = await import("../bootstrap-
|
|
1675
|
+
const { resolveServerUrl } = await import("../bootstrap-BCZC1ki6.mjs").then((n) => n.r);
|
|
1676
1676
|
resolveServerUrl();
|
|
1677
1677
|
} catch {
|
|
1678
1678
|
args.server = await input({ message: "Hub server URL:" });
|
|
1679
1679
|
saveOnboardState(args);
|
|
1680
1680
|
}
|
|
1681
|
-
const { loadCredentials } = await import("../bootstrap-
|
|
1681
|
+
const { loadCredentials } = await import("../bootstrap-BCZC1ki6.mjs").then((n) => n.r);
|
|
1682
1682
|
if (!loadCredentials()) throw new Error("No saved credentials. Run `first-tree-hub client connect <server-url>` before onboarding.");
|
|
1683
1683
|
if (!args.id) {
|
|
1684
1684
|
args.id = await input({ message: "Agent ID:" });
|
|
@@ -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-CTkhS6p5.mjs";
|
|
4
4
|
import "./errors-CF5evtJt-B0NTIVPt.mjs";
|
|
5
5
|
import "./src-DNBS5Yjj.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { Y as listMyPinnedAgents } from "./client-h5l7mi0m-OEX7MOBg.mjs";
|
|
7
7
|
export { listMyPinnedAgents };
|
|
@@ -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 { $ as questionMessageContentSchema, M as extractMentions, O as defaultParticipantMode, Q as questionAnswerMessageContentSchema, _ as clientCapabilitiesSchema, a as AGENT_VISIBILITY, h as agentTypeSchema, i as AGENT_STATUSES, it as scanMentionTokens } from "./dist-CTkhS6p5.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-h5l7mi0m.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
|
|
@@ -185,6 +185,140 @@ function invalidateChatAudience(chatId) {
|
|
|
185
185
|
cache.delete(chatId);
|
|
186
186
|
}
|
|
187
187
|
/**
|
|
188
|
+
* Single source of truth for writing `chat_participants`.
|
|
189
|
+
*
|
|
190
|
+
* **This is the ONLY place in the codebase that may `INSERT` into the
|
|
191
|
+
* `chat_participants` table.** Do not call `tx.insert(chatParticipants)`
|
|
192
|
+
* or `db.insert(chatParticipants)` from anywhere else. The original bug
|
|
193
|
+
* (docs/chat-participant-mode-fix-design.md §1.1) was caused by ten
|
|
194
|
+
* scattered insert sites each re-deriving the `mode` rule, several of
|
|
195
|
+
* which violated `group + non-human ⇒ mention_only`. Re-introducing a
|
|
196
|
+
* second writer reopens that hole — please don't.
|
|
197
|
+
*
|
|
198
|
+
* Test fixtures under `src/__tests__/` that deliberately seed
|
|
199
|
+
* pathological rows (e.g. cross-org pollution tests) may bypass this
|
|
200
|
+
* rule; they are setting up "what bad data looks like" rather than
|
|
201
|
+
* exercising the production write path.
|
|
202
|
+
*
|
|
203
|
+
* All callers that need to add a participant — `createChat`, `addParticipant`,
|
|
204
|
+
* `ensureParticipant`, `joinChat`, `createMeChat`, `addMeChatParticipants`,
|
|
205
|
+
* `findOrCreateDirectChat`, `findOrCreateChatForChannel`, `joinAsParticipant`,
|
|
206
|
+
* … — go through `addChatParticipants`. The function performs ONE round-trip
|
|
207
|
+
* to read `chats.type` + every involved `agents.type`, runs each row through
|
|
208
|
+
* `defaultParticipantMode`, and inserts the result. `agents.type` is parsed
|
|
209
|
+
* through the shared `agentTypeSchema` so schema drift surfaces loudly
|
|
210
|
+
* instead of silently coercing to a default.
|
|
211
|
+
*
|
|
212
|
+
* `changeChatType` complements it on the type-flip path: when a `direct`
|
|
213
|
+
* chat is being upgraded to `group` by the very next participant insert, the
|
|
214
|
+
* existing non-human rows must be re-graded to `mention_only`. Callers that
|
|
215
|
+
* trigger an upgrade are expected to invoke `changeChatType` BEFORE
|
|
216
|
+
* `addChatParticipants`, inside the same transaction, so the new row picks
|
|
217
|
+
* up the post-upgrade `chats.type` and existing rows get re-graded together.
|
|
218
|
+
*/
|
|
219
|
+
/**
|
|
220
|
+
* Insert participant rows whose `mode` is derived from `(chats.type, agents.type)`.
|
|
221
|
+
*
|
|
222
|
+
* Reads:
|
|
223
|
+
* - `chats.type` for the target chat (NotFoundError on missing)
|
|
224
|
+
* - `agents.type` for every requested participant (BadRequestError on missing)
|
|
225
|
+
*
|
|
226
|
+
* Mode derivation:
|
|
227
|
+
* - for each row, `peerAgentTypes` is the type of every OTHER participant
|
|
228
|
+
* being inserted in the same call PLUS every EXISTING participant of
|
|
229
|
+
* the chat. This matters only for `direct` chats; the helper ignores
|
|
230
|
+
* it for `group` / `thread`.
|
|
231
|
+
*
|
|
232
|
+
* Writes one INSERT (multi-row) per call.
|
|
233
|
+
*
|
|
234
|
+
* No watcher / audience-cache side effects — the caller owns those, since
|
|
235
|
+
* different entrypoints have different surrounding work (state-carry, watcher
|
|
236
|
+
* recompute, audience invalidation). Keeping this module side-effect-free
|
|
237
|
+
* makes it testable from any tx context.
|
|
238
|
+
*/
|
|
239
|
+
async function addChatParticipants(tx, chatId, participants, options = {}) {
|
|
240
|
+
if (participants.length === 0) return;
|
|
241
|
+
const [chat] = await tx.select({ type: chats.type }).from(chats).where(eq(chats.id, chatId)).limit(1);
|
|
242
|
+
if (!chat) throw new NotFoundError(`Chat "${chatId}" not found`);
|
|
243
|
+
if (chat.type !== "direct" && chat.type !== "group" && chat.type !== "thread") throw new Error(`Unexpected chat type "${chat.type}" for chat "${chatId}"`);
|
|
244
|
+
const chatType = chat.type;
|
|
245
|
+
const agentIds = participants.map((p) => p.agentId);
|
|
246
|
+
const agentRows = await tx.select({
|
|
247
|
+
uuid: agents.uuid,
|
|
248
|
+
type: agents.type
|
|
249
|
+
}).from(agents).where(inArray(agents.uuid, agentIds));
|
|
250
|
+
const agentTypeById = /* @__PURE__ */ new Map();
|
|
251
|
+
for (const row of agentRows) agentTypeById.set(row.uuid, row.type);
|
|
252
|
+
const missing = agentIds.filter((id) => !agentTypeById.has(id));
|
|
253
|
+
if (missing.length > 0) throw new BadRequestError(`Agents not found: ${missing.join(", ")}`);
|
|
254
|
+
if (options.assertHuman) {
|
|
255
|
+
const nonHuman = agentRows.filter((a) => a.type !== "human");
|
|
256
|
+
if (nonHuman.length > 0) throw new BadRequestError(`assertHuman violated: agents must be of type 'human' but got ${nonHuman.map((a) => `${a.uuid}=${a.type}`).join(", ")}`);
|
|
257
|
+
}
|
|
258
|
+
let existingAgentTypes = [];
|
|
259
|
+
if (chatType === "direct") existingAgentTypes = await loadExistingAgentTypes(tx, chatId, new Set(agentIds));
|
|
260
|
+
const rows = participants.map((spec) => {
|
|
261
|
+
const rawAgentType = agentTypeById.get(spec.agentId);
|
|
262
|
+
if (rawAgentType === void 0) throw new Error("Unexpected: agent type lookup unset after presence check");
|
|
263
|
+
const agentType = agentTypeSchema.parse(rawAgentType);
|
|
264
|
+
const peerTypesForRow = chatType === "direct" ? [...existingAgentTypes, ...participants.filter((p) => p.agentId !== spec.agentId).map((p) => agentTypeById.get(p.agentId)).filter((t) => t !== void 0)].map((t) => agentTypeSchema.parse(t)) : [];
|
|
265
|
+
return {
|
|
266
|
+
chatId,
|
|
267
|
+
agentId: spec.agentId,
|
|
268
|
+
role: spec.role ?? "member",
|
|
269
|
+
mode: defaultParticipantMode(chatType, agentType, peerTypesForRow),
|
|
270
|
+
lastReadAt: spec.carriedReadState?.lastReadAt ?? null,
|
|
271
|
+
unreadMentionCount: spec.carriedReadState?.unreadMentionCount ?? 0
|
|
272
|
+
};
|
|
273
|
+
});
|
|
274
|
+
const insert = tx.insert(chatParticipants).values(rows);
|
|
275
|
+
if (options.onConflictDoNothing) await insert.onConflictDoNothing({ target: [chatParticipants.chatId, chatParticipants.agentId] });
|
|
276
|
+
else await insert;
|
|
277
|
+
}
|
|
278
|
+
async function loadExistingAgentTypes(tx, chatId, excludeAgentIds) {
|
|
279
|
+
return (await tx.select({
|
|
280
|
+
type: agents.type,
|
|
281
|
+
agentId: chatParticipants.agentId
|
|
282
|
+
}).from(chatParticipants).innerJoin(agents, eq(chatParticipants.agentId, agents.uuid)).where(eq(chatParticipants.chatId, chatId))).filter((r) => !excludeAgentIds.has(r.agentId)).map((r) => r.type);
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Upgrade `chats.type` from `direct` → `group` AND re-grade every existing
|
|
286
|
+
* non-human participant to `mention_only`. Idempotent: if `chat.type` is
|
|
287
|
+
* already `group` (or any non-`direct` value), no-op.
|
|
288
|
+
*
|
|
289
|
+
* Callers that are about to insert a 3rd participant on a `direct` chat
|
|
290
|
+
* invoke this BEFORE `addChatParticipants` so the new row picks up the
|
|
291
|
+
* post-upgrade `chats.type` and the existing rows are re-graded in the
|
|
292
|
+
* same transaction.
|
|
293
|
+
*
|
|
294
|
+
* Note: this is the replacement for `services/chat.ts`'s
|
|
295
|
+
* `maybeUpgradeDirectToGroup` (the one in `services/watcher.ts` is
|
|
296
|
+
* removed). Keep the rename: `changeChatType` is more precise about the
|
|
297
|
+
* primary mutation; `maybe…ToGroup` overstated the conditional gate.
|
|
298
|
+
*/
|
|
299
|
+
async function changeChatType(tx, chatId, newType) {
|
|
300
|
+
const [chat] = await tx.select({ type: chats.type }).from(chats).where(eq(chats.id, chatId)).limit(1);
|
|
301
|
+
if (!chat) throw new NotFoundError(`Chat "${chatId}" not found`);
|
|
302
|
+
if (chat.type === newType) return;
|
|
303
|
+
if (newType === "group" && chat.type !== "direct") throw new BadRequestError(`Cannot change chat type from "${chat.type}" to "${newType}"`);
|
|
304
|
+
await tx.update(chats).set({
|
|
305
|
+
type: newType,
|
|
306
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
307
|
+
}).where(eq(chats.id, chatId));
|
|
308
|
+
const ids = (await tx.select({ agentId: chatParticipants.agentId }).from(chatParticipants).innerJoin(agents, eq(chatParticipants.agentId, agents.uuid)).where(and(eq(chatParticipants.chatId, chatId), ne(agents.type, "human")))).map((r) => r.agentId);
|
|
309
|
+
if (ids.length === 0) return;
|
|
310
|
+
await tx.update(chatParticipants).set({ mode: "mention_only" }).where(and(eq(chatParticipants.chatId, chatId), inArray(chatParticipants.agentId, ids)));
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Heuristic for whether an insert about to happen would push the chat past
|
|
314
|
+
* the direct → group threshold. Pure helper so callers can decide whether
|
|
315
|
+
* to call `changeChatType` before `addChatParticipants` without re-deriving
|
|
316
|
+
* the rule locally.
|
|
317
|
+
*/
|
|
318
|
+
function wouldUpgradeToGroup(currentParticipantCount, newParticipantCount) {
|
|
319
|
+
return currentParticipantCount + newParticipantCount >= 3;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
188
322
|
* Chat-first workspace — watcher subscription helpers.
|
|
189
323
|
*
|
|
190
324
|
* Watchers (rows in `chat_subscriptions`) are non-speaking observers. A
|
|
@@ -276,24 +410,6 @@ async function recomputeWatchersForMember(db, memberId) {
|
|
|
276
410
|
for (const { chatId } of rows) await recomputeChatWatchers(db, chatId);
|
|
277
411
|
}
|
|
278
412
|
/**
|
|
279
|
-
* Mirror of `services/chat.ts` `maybeUpgradeDirectToGroup`. Inlined here so
|
|
280
|
-
* `joinAsParticipant` keeps the upgrade rule + the state carry in one
|
|
281
|
-
* transaction without depending on chat.ts (avoids a circular import).
|
|
282
|
-
*/
|
|
283
|
-
async function maybeUpgradeDirectToGroup$1(tx, chatId, existingParticipantIds) {
|
|
284
|
-
if (existingParticipantIds.length + 1 < 3) return;
|
|
285
|
-
const [chat] = await tx.select({ type: chats.type }).from(chats).where(eq(chats.id, chatId)).limit(1);
|
|
286
|
-
if (!chat || chat.type !== "direct") return;
|
|
287
|
-
await tx.update(chats).set({
|
|
288
|
-
type: "group",
|
|
289
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
290
|
-
}).where(eq(chats.id, chatId));
|
|
291
|
-
if (existingParticipantIds.length === 0) return;
|
|
292
|
-
const ids = (await tx.select({ uuid: agents.uuid }).from(agents).where(and(inArray(agents.uuid, existingParticipantIds), ne(agents.type, "human")))).map((r) => r.uuid);
|
|
293
|
-
if (ids.length === 0) return;
|
|
294
|
-
await tx.update(chatParticipants).set({ mode: "mention_only" }).where(and(eq(chatParticipants.chatId, chatId), inArray(chatParticipants.agentId, ids)));
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
413
|
* Watcher → speaking participant. State-carry transaction.
|
|
298
414
|
*
|
|
299
415
|
* 1. DELETE the watcher row (returning read state).
|
|
@@ -318,15 +434,15 @@ async function joinAsParticipant(db, chatId, humanAgentId) {
|
|
|
318
434
|
inserted: false,
|
|
319
435
|
carried: carriedRow ?? null
|
|
320
436
|
};
|
|
321
|
-
|
|
322
|
-
await tx
|
|
323
|
-
chatId,
|
|
437
|
+
if (wouldUpgradeToGroup((await tx.select({ agentId: chatParticipants.agentId }).from(chatParticipants).where(eq(chatParticipants.chatId, chatId))).length, 1)) await changeChatType(tx, chatId, "group");
|
|
438
|
+
await addChatParticipants(tx, chatId, [{
|
|
324
439
|
agentId: humanAgentId,
|
|
325
440
|
role: "member",
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
441
|
+
carriedReadState: carriedRow ? {
|
|
442
|
+
lastReadAt: carriedRow.lastReadAt,
|
|
443
|
+
unreadMentionCount: carriedRow.unreadMentionCount
|
|
444
|
+
} : void 0
|
|
445
|
+
}], { assertHuman: true });
|
|
330
446
|
return {
|
|
331
447
|
chatId,
|
|
332
448
|
inserted: true,
|
|
@@ -405,28 +521,6 @@ function ensureCanJoin(membership) {
|
|
|
405
521
|
if (membership === "participant") throw new ConflictError("Already a participant in this chat");
|
|
406
522
|
if (membership === null) throw new ForbiddenError("Not a watcher of this chat — open the chat from your workspace before joining");
|
|
407
523
|
}
|
|
408
|
-
/**
|
|
409
|
-
* When a direct chat grows past 2 participants, upgrade it to `group` and
|
|
410
|
-
* flip every existing non-human agent participant to `mention_only` — see
|
|
411
|
-
* proposals/hub-agent-messaging-reply-and-mentions §3.3. The caller is
|
|
412
|
-
* expected to insert the new participant AFTER this runs, so the "existing"
|
|
413
|
-
* set excludes them.
|
|
414
|
-
*
|
|
415
|
-
* Idempotent: if the chat is already a group, no-op.
|
|
416
|
-
*/
|
|
417
|
-
async function maybeUpgradeDirectToGroup(db, chatId, existingParticipantIds, newParticipantCount) {
|
|
418
|
-
if (existingParticipantIds.length + newParticipantCount < 3) return;
|
|
419
|
-
const [chat] = await db.select({ type: chats.type }).from(chats).where(eq(chats.id, chatId)).limit(1);
|
|
420
|
-
if (!chat || chat.type !== "direct") return;
|
|
421
|
-
await db.update(chats).set({
|
|
422
|
-
type: "group",
|
|
423
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
424
|
-
}).where(eq(chats.id, chatId));
|
|
425
|
-
if (existingParticipantIds.length === 0) return;
|
|
426
|
-
const ids = (await db.select({ uuid: agents.uuid }).from(agents).where(and(inArray(agents.uuid, existingParticipantIds), ne(agents.type, "human")))).map((a) => a.uuid);
|
|
427
|
-
if (ids.length === 0) return;
|
|
428
|
-
await db.update(chatParticipants).set({ mode: "mention_only" }).where(and(eq(chatParticipants.chatId, chatId), inArray(chatParticipants.agentId, ids)));
|
|
429
|
-
}
|
|
430
524
|
async function createChat(db, creatorId, data) {
|
|
431
525
|
const chatId = randomUUID();
|
|
432
526
|
const allParticipantIds = new Set([creatorId, ...data.participantIds]);
|
|
@@ -444,7 +538,6 @@ async function createChat(db, creatorId, data) {
|
|
|
444
538
|
const orgId = creator.organizationId;
|
|
445
539
|
const crossOrg = existingAgents.filter((a) => a.organizationId !== orgId);
|
|
446
540
|
if (crossOrg.length > 0) throw new BadRequestError(`Cross-organization chat not allowed: ${crossOrg.map((a) => a.id).join(", ")}`);
|
|
447
|
-
const isDirectAgentOnly = data.type === "direct" && existingAgents.every((a) => a.type !== "human");
|
|
448
541
|
return db.transaction(async (tx) => {
|
|
449
542
|
const [chat] = await tx.insert(chats).values({
|
|
450
543
|
id: chatId,
|
|
@@ -453,13 +546,10 @@ async function createChat(db, creatorId, data) {
|
|
|
453
546
|
topic: data.topic ?? null,
|
|
454
547
|
metadata: data.metadata ?? {}
|
|
455
548
|
}).returning();
|
|
456
|
-
|
|
457
|
-
chatId,
|
|
549
|
+
await addChatParticipants(tx, chatId, [...allParticipantIds].map((agentId) => ({
|
|
458
550
|
agentId,
|
|
459
|
-
role: agentId === creatorId ? "owner" : "member"
|
|
460
|
-
|
|
461
|
-
}));
|
|
462
|
-
await tx.insert(chatParticipants).values(participantRows);
|
|
551
|
+
role: agentId === creatorId ? "owner" : "member"
|
|
552
|
+
})));
|
|
463
553
|
await recomputeChatWatchers(tx, chatId);
|
|
464
554
|
const participants = await tx.select().from(chatParticipants).where(eq(chatParticipants.chatId, chatId));
|
|
465
555
|
if (!chat) throw new Error("Unexpected: INSERT RETURNING produced no row");
|
|
@@ -532,13 +622,9 @@ async function ensureParticipant(db, chatId, agentId) {
|
|
|
532
622
|
const [existing] = await db.select({ agentId: chatParticipants.agentId }).from(chatParticipants).where(and(eq(chatParticipants.chatId, chatId), eq(chatParticipants.agentId, agentId))).limit(1);
|
|
533
623
|
if (existing) return;
|
|
534
624
|
await db.transaction(async (tx) => {
|
|
535
|
-
|
|
625
|
+
if (wouldUpgradeToGroup((await tx.select({ agentId: chatParticipants.agentId }).from(chatParticipants).where(eq(chatParticipants.chatId, chatId))).length, 1)) await changeChatType(tx, chatId, "group");
|
|
536
626
|
await tx.delete(chatSubscriptions).where(and(eq(chatSubscriptions.chatId, chatId), eq(chatSubscriptions.agentId, agentId)));
|
|
537
|
-
await tx
|
|
538
|
-
chatId,
|
|
539
|
-
agentId,
|
|
540
|
-
mode: "full"
|
|
541
|
-
}).onConflictDoNothing({ target: [chatParticipants.chatId, chatParticipants.agentId] });
|
|
627
|
+
await addChatParticipants(tx, chatId, [{ agentId }], { onConflictDoNothing: true });
|
|
542
628
|
await recomputeChatWatchers(tx, chatId);
|
|
543
629
|
});
|
|
544
630
|
invalidateChatAudience(chatId);
|
|
@@ -555,13 +641,9 @@ async function addParticipant(db, chatId, requesterId, data) {
|
|
|
555
641
|
const [existing] = await db.select({ chatId: chatParticipants.chatId }).from(chatParticipants).where(and(eq(chatParticipants.chatId, chatId), eq(chatParticipants.agentId, data.agentId))).limit(1);
|
|
556
642
|
if (existing) throw new ConflictError(`Agent "${data.agentId}" is already a participant`);
|
|
557
643
|
await db.transaction(async (tx) => {
|
|
558
|
-
|
|
644
|
+
if (wouldUpgradeToGroup((await tx.select({ agentId: chatParticipants.agentId }).from(chatParticipants).where(eq(chatParticipants.chatId, chatId))).length, 1)) await changeChatType(tx, chatId, "group");
|
|
559
645
|
await tx.delete(chatSubscriptions).where(and(eq(chatSubscriptions.chatId, chatId), eq(chatSubscriptions.agentId, data.agentId)));
|
|
560
|
-
await tx.
|
|
561
|
-
chatId,
|
|
562
|
-
agentId: data.agentId,
|
|
563
|
-
mode: data.mode ?? "full"
|
|
564
|
-
});
|
|
646
|
+
await addChatParticipants(tx, chatId, [{ agentId: data.agentId }]);
|
|
565
647
|
await recomputeChatWatchers(tx, chatId);
|
|
566
648
|
});
|
|
567
649
|
invalidateChatAudience(chatId);
|
|
@@ -669,15 +751,15 @@ async function joinChat(db, chatId, memberId, humanAgentId) {
|
|
|
669
751
|
lastReadAt: chatSubscriptions.lastReadAt,
|
|
670
752
|
unreadMentionCount: chatSubscriptions.unreadMentionCount
|
|
671
753
|
});
|
|
672
|
-
await
|
|
673
|
-
await tx
|
|
674
|
-
chatId,
|
|
754
|
+
if (wouldUpgradeToGroup(participantAgentIds.length, 1)) await changeChatType(tx, chatId, "group");
|
|
755
|
+
await addChatParticipants(tx, chatId, [{
|
|
675
756
|
agentId: humanAgentId,
|
|
676
757
|
role: "member",
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
758
|
+
carriedReadState: carriedRow ? {
|
|
759
|
+
lastReadAt: carriedRow.lastReadAt,
|
|
760
|
+
unreadMentionCount: carriedRow.unreadMentionCount
|
|
761
|
+
} : void 0
|
|
762
|
+
}], { assertHuman: true });
|
|
681
763
|
await recomputeChatWatchers(tx, chatId);
|
|
682
764
|
});
|
|
683
765
|
invalidateChatAudience(chatId);
|
|
@@ -719,7 +801,6 @@ async function findOrCreateDirectChat(db, agentAId, agentBId) {
|
|
|
719
801
|
const directChats = await db.select().from(chats).where(and(inArray(chats.id, commonChatIds), eq(chats.type, "direct"), eq(chats.organizationId, orgId))).orderBy(chats.createdAt, chats.id).limit(1);
|
|
720
802
|
if (directChats.length > 0 && directChats[0]) return directChats[0];
|
|
721
803
|
}
|
|
722
|
-
const mode = agentA.type !== "human" && agentB.type !== "human" ? "mention_only" : "full";
|
|
723
804
|
const chatId = randomUUID();
|
|
724
805
|
return db.transaction(async (tx) => {
|
|
725
806
|
const [chat] = await tx.insert(chats).values({
|
|
@@ -727,16 +808,12 @@ async function findOrCreateDirectChat(db, agentAId, agentBId) {
|
|
|
727
808
|
organizationId: orgId,
|
|
728
809
|
type: "direct"
|
|
729
810
|
}).returning();
|
|
730
|
-
await tx
|
|
731
|
-
chatId,
|
|
811
|
+
await addChatParticipants(tx, chatId, [{
|
|
732
812
|
agentId: agentAId,
|
|
733
|
-
role: "member"
|
|
734
|
-
mode
|
|
813
|
+
role: "member"
|
|
735
814
|
}, {
|
|
736
|
-
chatId,
|
|
737
815
|
agentId: agentBId,
|
|
738
|
-
role: "member"
|
|
739
|
-
mode
|
|
816
|
+
role: "member"
|
|
740
817
|
}]);
|
|
741
818
|
await recomputeChatWatchers(tx, chatId);
|
|
742
819
|
if (!chat) throw new Error("Unexpected: INSERT RETURNING produced no row");
|
|
@@ -1079,7 +1156,8 @@ async function listAgentsWithRuntime(db, scope) {
|
|
|
1079
1156
|
activeSessions: agentPresence.activeSessions,
|
|
1080
1157
|
totalSessions: agentPresence.totalSessions,
|
|
1081
1158
|
runtimeUpdatedAt: agentPresence.runtimeUpdatedAt,
|
|
1082
|
-
type: agents.type
|
|
1159
|
+
type: agents.type,
|
|
1160
|
+
managerId: agents.managerId
|
|
1083
1161
|
}).from(agentPresence).innerJoin(agents, eq(agentPresence.agentId, agents.uuid)).where(and(isNotNull(agentPresence.runtimeState), agentVisibilityCondition(scope.organizationId, scope.memberId)));
|
|
1084
1162
|
}
|
|
1085
1163
|
/**
|
|
@@ -2051,4 +2129,4 @@ async function cleanupStaleClients(db, staleSeconds = 60) {
|
|
|
2051
2129
|
return result.length;
|
|
2052
2130
|
}
|
|
2053
2131
|
//#endregion
|
|
2054
|
-
export {
|
|
2132
|
+
export { messages as $, getOnlineCount as A, listActiveAgentsPinnedToClient as B, ensureCanJoin as C, getCachedAudience as D, getActivityOverview as E, invalidateChatAudience as F, listChatsForMember as G, listAgentsWithRuntime as H, joinAsParticipant as I, listMessages as J, listClients as K, joinChat as L, heartbeatClient as M, heartbeatInstance as N, getChatDetail as O, inboxEntries as P, members as Q, leaveAsParticipant as R, editMessage as S, findOrCreateDirectChat as T, listChatParticipantsWithNames as U, listAgentsManagedByUser as V, listChats as W, markStaleAgents as X, listMyPinnedAgents as Y, markSupersededByChat as Z, clients as _, touchAgent as _t, agentVisibilityCondition as a, registerChatMessageDispatcher as at, deriveAuthState as b, upsertSessionState as bt, assertParticipant as c, resetActivity as ct, chatParticipants as d, sendMessage as dt, notifyRecipients as et, chatSubscriptions as f, sendToAgent as ft, cleanupStalePresence as g, submitAnswer as gt, cleanupStaleClients as h, setRuntimeState as ht, agentPresence as i, recomputeWatchersForMember as it, getPresence as j, getClient as k, bindAgent as l, resolveChatMembership as lt, claimClient as m, setOffline as mt, addParticipant as n, recomputeChatWatchers as nt, agents as o, registerClient as ot, chats as p, serverInstances as pt, listClientsForOrgAdmin as q, agentChatSessions as r, recomputeWatchersForAgent as rt, assertClientOwner as s, removeParticipant as st, addChatParticipants as t, pendingQuestions as tt, changeChatType as u, retireClient as ut, createChat as v, unbindAgent as vt, ensureParticipant as w, disconnectClient as x, createNotifier as y, updateClientCapabilities as yt, leaveChat as z };
|
|
@@ -45,6 +45,39 @@ function scanMentionTokens(content) {
|
|
|
45
45
|
return tokens;
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
|
+
* Derive the `chat_participants.mode` that a freshly inserted row MUST get,
|
|
49
|
+
* given the chat's `type` and the joining agent's `type`. This is the single
|
|
50
|
+
* authoritative rule for the invariant
|
|
51
|
+
*
|
|
52
|
+
* `(chat.type === 'group' && agent.type !== 'human') ⇒ mode === 'mention_only'`
|
|
53
|
+
*
|
|
54
|
+
* plus the legacy "agent-only direct chat" anti-echo rule. The helper is
|
|
55
|
+
* pure and synchronous; all DB lookups are the caller's responsibility (see
|
|
56
|
+
* `services/participant-mode.ts::addChatParticipants` for the canonical
|
|
57
|
+
* server entrypoint that wires it).
|
|
58
|
+
*
|
|
59
|
+
* Rule (encoded once, here):
|
|
60
|
+
*
|
|
61
|
+
* - `agent.type === 'human'` → 'full'
|
|
62
|
+
* - `chat.type === 'group'` (and agent is non-human) → 'mention_only'
|
|
63
|
+
* - `chat.type === 'direct'` + agent non-human:
|
|
64
|
+
* - if every other participant on this chat is also non-human →
|
|
65
|
+
* 'mention_only' (prevents the A↔B reply loop noted in migration
|
|
66
|
+
* 0029)
|
|
67
|
+
* - otherwise → 'full' (the peer is a human / external user, so the
|
|
68
|
+
* agent should listen to every message in this 1:1 line)
|
|
69
|
+
*
|
|
70
|
+
* `peerAgentTypes` is read only in the `direct` branch; callers may pass
|
|
71
|
+
* an empty array (or omit it) for `group` chats — it's ignored. Watcher /
|
|
72
|
+
* subscription-side `chat_subscriptions` rows are unaffected; the helper
|
|
73
|
+
* only governs the "speaking" mode column.
|
|
74
|
+
*/
|
|
75
|
+
function defaultParticipantMode(chatType, agentType, peerAgentTypes = []) {
|
|
76
|
+
if (agentType === "human") return "full";
|
|
77
|
+
if (chatType === "group" || chatType === "thread") return "mention_only";
|
|
78
|
+
return peerAgentTypes.every((t) => t !== "human") ? "mention_only" : "full";
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
48
81
|
* Single source of truth for "is this string safe to redirect to after a
|
|
49
82
|
* successful OAuth callback".
|
|
50
83
|
*
|
|
@@ -379,10 +412,15 @@ const runtimeStateSchema = z.enum([
|
|
|
379
412
|
z.enum([
|
|
380
413
|
"active",
|
|
381
414
|
"suspended",
|
|
382
|
-
"evicted"
|
|
415
|
+
"evicted",
|
|
416
|
+
"errored"
|
|
383
417
|
]);
|
|
384
418
|
/** Wire-level states a client may report. `evicted` from a stale client is rejected. */
|
|
385
|
-
const clientSessionStateSchema = z.enum([
|
|
419
|
+
const clientSessionStateSchema = z.enum([
|
|
420
|
+
"active",
|
|
421
|
+
"suspended",
|
|
422
|
+
"errored"
|
|
423
|
+
]);
|
|
386
424
|
const sessionStateMessageSchema = z.object({
|
|
387
425
|
chatId: z.string().min(1),
|
|
388
426
|
state: clientSessionStateSchema
|
|
@@ -626,10 +664,17 @@ z.object({
|
|
|
626
664
|
firstMessagePreview: z.string().nullable()
|
|
627
665
|
});
|
|
628
666
|
const updateChatSchema = z.object({ topic: z.string().trim().max(500).nullable() });
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
667
|
+
/**
|
|
668
|
+
* Public API body for `POST /api/v1/agent/chats/:chatId/participants`.
|
|
669
|
+
* Phase 1 removed the `mode` field: participant mode is derived server-side
|
|
670
|
+
* from `(chats.type, agents.type)` via `services/participant-mode.ts` and
|
|
671
|
+
* cannot be overridden by the caller. The handler still inspects the raw
|
|
672
|
+
* body and rejects with `400 MODE_FIELD_DEPRECATED` if `mode` is present,
|
|
673
|
+
* so an out-of-tree caller that still sends it gets a clear error and a
|
|
674
|
+
* telemetry counter increments — see `chat-participant-mode-fix-design.md`
|
|
675
|
+
* §3.2 / §6.
|
|
676
|
+
*/
|
|
677
|
+
const addParticipantSchema = z.object({ agentId: z.string().min(1) });
|
|
633
678
|
z.object({ agentId: z.string().min(1) });
|
|
634
679
|
const clientStatusSchema = z.enum(["connected", "disconnected"]);
|
|
635
680
|
/**
|
|
@@ -813,6 +858,62 @@ const contextTreeSnapshotSchema = z.object({
|
|
|
813
858
|
edges: z.array(contextTreeEdgeSchema),
|
|
814
859
|
changes: z.array(contextTreeChangeSchema)
|
|
815
860
|
});
|
|
861
|
+
const githubAccountTypeSchema = z.enum(["User", "Organization"]);
|
|
862
|
+
const githubPermissionLevelSchema = z.enum([
|
|
863
|
+
"read",
|
|
864
|
+
"write",
|
|
865
|
+
"admin"
|
|
866
|
+
]);
|
|
867
|
+
/**
|
|
868
|
+
* `installation.permissions` blob from GitHub. Key is the permission name
|
|
869
|
+
* (`contents`, `pull_requests`, `issues`, `members`, …) — we keep this as a
|
|
870
|
+
* free-form `z.record` because GitHub adds new permission keys over time
|
|
871
|
+
* and we don't want a Hub-side `app_id` upgrade just to surface a new key
|
|
872
|
+
* in the integrations panel.
|
|
873
|
+
*/
|
|
874
|
+
const githubAppInstallationPermissionsSchema = z.record(z.string(), githubPermissionLevelSchema);
|
|
875
|
+
/**
|
|
876
|
+
* Subscribed event-name list, e.g. `["issues", "pull_request", "push"]`.
|
|
877
|
+
* Free-form for the same forward-compat reason as `permissions`.
|
|
878
|
+
*/
|
|
879
|
+
const githubAppInstallationEventsSchema = z.array(z.string());
|
|
880
|
+
z.object({
|
|
881
|
+
login: z.string().optional(),
|
|
882
|
+
accessToken: z.string().optional(),
|
|
883
|
+
accessTokenExpiresAt: z.string().datetime({ offset: true }).optional(),
|
|
884
|
+
refreshToken: z.string().optional(),
|
|
885
|
+
refreshTokenExpiresAt: z.string().datetime({ offset: true }).optional()
|
|
886
|
+
});
|
|
887
|
+
/**
|
|
888
|
+
* GET-side projection returned by the Hub admin API for the Integrations
|
|
889
|
+
* panel. Secrets are never echoed — only the metadata needed to render
|
|
890
|
+
* "you're connected as @octocat (Organization), 7 repos selected".
|
|
891
|
+
*
|
|
892
|
+
* `selectedRepoCount` is derived from a separate join (App webhook
|
|
893
|
+
* `installation_repositories` events update a children table not modeled
|
|
894
|
+
* here yet); included now so the panel's API shape is stable from the
|
|
895
|
+
* first ship.
|
|
896
|
+
*/
|
|
897
|
+
/**
|
|
898
|
+
* Body for `POST /orgs/:orgId/github-app-installation/claim` — manual
|
|
899
|
+
* recovery for an installation row that ended up unbound (codex P1-5 + H1).
|
|
900
|
+
* The orphan-reclaim sweep at sign-in auto-claims the single-orphan case;
|
|
901
|
+
* this endpoint backs the Settings "Claim install" buttons when there's
|
|
902
|
+
* more than one (or the account is an org, where auto-claim is too risky).
|
|
903
|
+
*/
|
|
904
|
+
const githubAppInstallationClaimBodySchema = z.object({ installationId: z.number().int().positive() });
|
|
905
|
+
z.object({
|
|
906
|
+
installationId: z.number().int().positive(),
|
|
907
|
+
accountType: githubAccountTypeSchema,
|
|
908
|
+
accountLogin: z.string(),
|
|
909
|
+
accountGithubId: z.number().int().positive(),
|
|
910
|
+
permissions: githubAppInstallationPermissionsSchema,
|
|
911
|
+
events: githubAppInstallationEventsSchema,
|
|
912
|
+
suspended: z.boolean(),
|
|
913
|
+
manageUrl: z.string().url(),
|
|
914
|
+
createdAt: z.string().datetime({ offset: true }),
|
|
915
|
+
updatedAt: z.string().datetime({ offset: true })
|
|
916
|
+
});
|
|
816
917
|
const supportedImageMimeSchema = z.enum([
|
|
817
918
|
"image/png",
|
|
818
919
|
"image/jpeg",
|
|
@@ -1199,18 +1300,36 @@ const notificationQuerySchema = z.object({
|
|
|
1199
1300
|
const githubStartQuerySchema = z.object({ next: z.string().max(256).optional() });
|
|
1200
1301
|
const githubCallbackQuerySchema = z.object({
|
|
1201
1302
|
code: z.string().min(1),
|
|
1202
|
-
state: z.string().min(1)
|
|
1303
|
+
state: z.string().min(1),
|
|
1304
|
+
installation_id: z.string().regex(/^\d+$/).optional(),
|
|
1305
|
+
setup_action: z.enum([
|
|
1306
|
+
"install",
|
|
1307
|
+
"update",
|
|
1308
|
+
"request"
|
|
1309
|
+
]).optional()
|
|
1203
1310
|
});
|
|
1204
1311
|
/**
|
|
1205
1312
|
* Dev-only callback to bypass the GitHub round-trip — sign in as a stub
|
|
1206
1313
|
* Github user. Gated by NODE_ENV !== 'production'; production always 404s.
|
|
1314
|
+
*
|
|
1315
|
+
* The App-flow extension fields (`installationId`, `installationAccountType`,
|
|
1316
|
+
* `installationAccountLogin`, `installationAccountGithubId`) let the dev
|
|
1317
|
+
* flow simulate a GitHub App install in the same redirect — when present
|
|
1318
|
+
* they stub a `github_app_installations` row before the OAuth flow
|
|
1319
|
+
* completes, so the rest of the dev session can exercise the App-bound
|
|
1320
|
+
* code paths (Settings → Integrations panel, webhook routing) without a
|
|
1321
|
+
* real install. Missing → legacy OAuth-only dev flow.
|
|
1207
1322
|
*/
|
|
1208
1323
|
const githubDevCallbackQuerySchema = z.object({
|
|
1209
1324
|
githubId: z.string().min(1),
|
|
1210
1325
|
login: z.string().min(1),
|
|
1211
1326
|
email: z.string().email().optional(),
|
|
1212
1327
|
displayName: z.string().optional(),
|
|
1213
|
-
next: z.string().max(256).optional()
|
|
1328
|
+
next: z.string().max(256).optional(),
|
|
1329
|
+
installationId: z.string().regex(/^\d+$/).optional(),
|
|
1330
|
+
installationAccountType: z.enum(["User", "Organization"]).optional(),
|
|
1331
|
+
installationAccountLogin: z.string().min(1).optional(),
|
|
1332
|
+
installationAccountGithubId: z.string().regex(/^\d+$/).optional()
|
|
1214
1333
|
});
|
|
1215
1334
|
/**
|
|
1216
1335
|
* Per-organization settings — schemas, namespaces, and the registry that
|
|
@@ -1606,4 +1725,4 @@ z.object({
|
|
|
1606
1725
|
capabilities: serverCapabilitiesSchema.optional()
|
|
1607
1726
|
}).passthrough();
|
|
1608
1727
|
//#endregion
|
|
1609
|
-
export {
|
|
1728
|
+
export { questionMessageContentSchema as $, delegateFeishuUserSchema as A, inboxPollQuerySchema as B, createAgentSchema as C, createOrgFromMeSchema as D, createMemberSchema as E, githubDevCallbackQuerySchema as F, listMeChatsQuerySchema as G, isRedactedEnvValue as H, githubStartQuerySchema as I, notificationQuerySchema as J, loginSchema as K, imageInlineContentSchema as L, extractMentions as M, githubAppInstallationClaimBodySchema as N, defaultParticipantMode as O, githubCallbackQuerySchema as P, questionAnswerMessageContentSchema as Q, inboxAckFrameSchema as R, createAdapterMappingSchema as S, wsAuthFrameSchema as St, createMeChatSchema as T, isReservedAgentName as U, isOrgSettingNamespace as V, joinByInvitationSchema as W, paginationQuerySchema as X, onboardingEventSchema as Y, patchOnboardingSchema as Z, clientCapabilitiesSchema as _, updateAgentSchema as _t, AGENT_VISIBILITY as a, selfServiceFeishuBotSchema as at, contextTreeSnapshotSchema as b, updateMemberSchema as bt, ORG_SETTINGS_NAMESPACES as c, sessionCompletionMessageSchema as ct, addParticipantSchema as d, sessionReconcileRequestSchema as dt, rebindAgentSchema as et, agentBindRequestSchema as f, sessionStateMessageSchema as ft, chatMetadataSchema as g, updateAgentRuntimeConfigSchema as gt, agentTypeSchema as h, updateAdapterConfigSchema as ht, AGENT_STATUSES as i, scanMentionTokens as it, dryRunAgentRuntimeConfigSchema as j, defaultRuntimeConfigPayload as k, WS_AUTH_FRAME_TIMEOUT_MS as l, sessionEventMessageSchema as lt, agentRuntimeConfigPayloadSchema as m, submitQuestionAnswerSchema as mt, AGENT_NAME_REGEX as n, runtimeStateMessageSchema as nt, DEFAULT_RUNTIME_PROVIDER as o, sendMessageSchema as ot, agentPinnedMessageSchema as p, stripCode as pt, messageSourceSchema as q, AGENT_SELECTOR_HEADER as r, safeRedirectPath as rt, MENTION_REGEX as s, sendToAgentSchema as st, AGENT_BIND_REJECT_REASONS as t, refreshTokenSchema as tt, addMeChatParticipantsSchema as u, sessionEventSchema as ut, clientRegisterSchema as v, updateChatSchema as vt, createChatSchema as w, createAdapterConfigSchema as x, updateOrganizationSchema as xt, connectTokenExchangeSchema as y, updateClientCapabilitiesSchema as yt, inboxDeliverFrameSchema as z };
|