@agent-team-foundation/first-tree-hub 0.14.2 → 0.14.4
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-C15ZBOCC.mjs → bootstrap-CQQGgIx1.mjs} +0 -7
- package/dist/cli/index.mjs +8 -25
- package/dist/{client-CZ_VnbEc-CBF46cJd.mjs → client-BPRIfrOT-CoV_2o7e.mjs} +61 -44
- package/dist/{client-CDw0f-kN-BPzOVd8L.mjs → client-CEdYVnoj-BGiGcJbH.mjs} +2 -2
- package/dist/{dist-DmYxT5Kb.mjs → dist-CrdnqZjv.mjs} +20 -14
- package/dist/{feishu-CCWd-JE4.mjs → feishu-DNoBroKK.mjs} +1 -1
- package/dist/index.mjs +5 -5
- package/dist/{invitation-C9m2gQx4-CkwWteA3.mjs → invitation-C9m2gQx4-C_4f5VTs.mjs} +1 -1
- package/dist/{saas-connect-DgCSZ8Yk.mjs → saas-connect-Da55XxRX.mjs} +265 -231
- package/dist/web/assets/index-9wK0udbH.js +416 -0
- package/dist/web/assets/{index-B76SVAz8.js → index-C7x7O7dG.js} +1 -1
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/dist/web/assets/index-DAAemCLz.js +0 -416
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { a as __toCommonJS, o as __toESM, t as __commonJSMin } from "./chunk-BSw8zbkd.mjs";
|
|
2
2
|
import { A as FIRST_TREE_HUB_ATTR, C as stampOrgScope, D as untrustedAttrs, E as startWsConnectionSpan, M as require_pino, O as withSpan, S as stampChatResource, _ as rootLogger$1, a as buildRateLimitError, c as currentTraceId, g as reportErrorToRoot, i as bodyCaptureOnSendHook, j as redactUrl, k as withWsMessageSpan, l as decodeJwtForTrace, m as observabilityPlugin, n as applyLoggerConfig, o as classifyJoseError, r as attachRequestContext, s as createLogger$1, t as adapterAttrs, u as endWsConnectionSpan, x as stampAgentResource, y as setWsConnectionAttrs } from "./observability-BAScT_5S-BcW9HgkG.mjs";
|
|
3
|
-
import { C as resetConfigMeta, E as setConfigValue, S as resetConfig, T as serverConfigSchema, b as migrateLegacyHome, c as resolveServerUrl, d as DEFAULT_CONFIG_DIR, f as DEFAULT_DATA_DIR$1, g as collectMissingPrompts, h as clientConfigSchema, i as ensureFreshAccessToken, l as saveAgentConfig, m as agentConfigSchema, o as loadCredentials, p as DEFAULT_HOME_DIR$1, u as saveCredentials, v as initConfig, w as resolveConfigReadonly, y as loadAgents } from "./bootstrap-
|
|
3
|
+
import { C as resetConfigMeta, E as setConfigValue, S as resetConfig, T as serverConfigSchema, b as migrateLegacyHome, c as resolveServerUrl, d as DEFAULT_CONFIG_DIR, f as DEFAULT_DATA_DIR$1, g as collectMissingPrompts, h as clientConfigSchema, i as ensureFreshAccessToken, l as saveAgentConfig, m as agentConfigSchema, o as loadCredentials, p as DEFAULT_HOME_DIR$1, u as saveCredentials, v as initConfig, w as resolveConfigReadonly, y as loadAgents } from "./bootstrap-CQQGgIx1.mjs";
|
|
4
4
|
import { a as print, i as blank, n as CLI_USER_AGENT, r as COMMAND_VERSION, s as status, t as cliFetch } from "./cli-fetch--tiwKm5S.mjs";
|
|
5
|
-
import { $ as listMeChatSourceCountsQuerySchema, A as createMeChatSchema, At as updateOrganizationSchema, B as githubAppInstallationClaimBodySchema, C as clientRegisterSchema, Ct as submitQuestionAnswerSchema, D as createAdapterMappingSchema, Dt as updateChatSchema, E as createAdapterConfigSchema, Et as updateAgentSchema, F as delegateFeishuUserSchema, G as imageInlineContentSchema, H as githubCallbackQuerySchema, I as dryRunAgentRuntimeConfigSchema, J as inboxPollQuerySchema, K as inboxAckFrameSchema, M as createOrgFromMeSchema, O as createAgentSchema, Ot as updateClientCapabilitiesSchema, Q as joinByInvitationSchema, R as getMeDocResponseSchema, T as contextTreeSnapshotSchema, Tt as updateAgentRuntimeConfigSchema, U as githubDevCallbackQuerySchema, V as githubAppInstallationPermissionsSchema$1, W as githubStartQuerySchema, X as isRedactedEnvValue, Y as isOrgSettingNamespace, _ as agentBindRequestSchema, _t as sendToAgentSchema, at as paginationQuerySchema, b as agentTypeSchema$1, bt as sessionReconcileRequestSchema, dt as refreshTokenSchema, et as listMeChatsQuerySchema, f as NOTIFICATION_TYPES, ft as runtimeStateMessageSchema, g as addParticipantSchema, gt as sendMessageSchema, h as addMeChatParticipantsSchema, ht as selfServiceFeishuBotSchema, i as AGENT_STATUSES, it as onboardingEventSchema, j as createMemberSchema, jt as wsAuthFrameSchema, k as createChatSchema, kt as updateMemberSchema, m as WS_AUTH_FRAME_TIMEOUT_MS, o as AGENT_VISIBILITY, ot as patchChatEngagementSchema, p as ORG_SETTINGS_NAMESPACES$1, pt as safeRedirectPath, q as inboxDeliverFrameSchema$1, r as AGENT_SELECTOR_HEADER$1, rt as notificationQuerySchema, s as CHAT_ENGAGEMENT_STATUSES, st as patchOnboardingSchema, t as AGENT_BIND_REJECT_REASONS, tt as loginSchema, ut as rebindAgentSchema, v as agentPinnedMessageSchema$1, vt as sessionEventMessageSchema, w as connectTokenExchangeSchema, wt as updateAdapterConfigSchema, x as chatMetadataSchema$1, xt as sessionStateMessageSchema, y as agentRuntimeConfigPayloadSchema$1, yt as sessionEventSchema$1, z as getMeDocSchema } from "./dist-
|
|
5
|
+
import { $ as listMeChatSourceCountsQuerySchema, A as createMeChatSchema, At as updateOrganizationSchema, B as githubAppInstallationClaimBodySchema, C as clientRegisterSchema, Ct as submitQuestionAnswerSchema, D as createAdapterMappingSchema, Dt as updateChatSchema, E as createAdapterConfigSchema, Et as updateAgentSchema, F as delegateFeishuUserSchema, G as imageInlineContentSchema, H as githubCallbackQuerySchema, I as dryRunAgentRuntimeConfigSchema, J as inboxPollQuerySchema, K as inboxAckFrameSchema, M as createOrgFromMeSchema, O as createAgentSchema, Ot as updateClientCapabilitiesSchema, Q as joinByInvitationSchema, R as getMeDocResponseSchema, T as contextTreeSnapshotSchema, Tt as updateAgentRuntimeConfigSchema, U as githubDevCallbackQuerySchema, V as githubAppInstallationPermissionsSchema$1, W as githubStartQuerySchema, X as isRedactedEnvValue, Y as isOrgSettingNamespace, _ as agentBindRequestSchema, _t as sendToAgentSchema, at as paginationQuerySchema, b as agentTypeSchema$1, bt as sessionReconcileRequestSchema, dt as refreshTokenSchema, et as listMeChatsQuerySchema, f as NOTIFICATION_TYPES, ft as runtimeStateMessageSchema, g as addParticipantSchema, gt as sendMessageSchema, h as addMeChatParticipantsSchema, ht as selfServiceFeishuBotSchema, i as AGENT_STATUSES, it as onboardingEventSchema, j as createMemberSchema, jt as wsAuthFrameSchema, k as createChatSchema, kt as updateMemberSchema, m as WS_AUTH_FRAME_TIMEOUT_MS, o as AGENT_VISIBILITY, ot as patchChatEngagementSchema, p as ORG_SETTINGS_NAMESPACES$1, pt as safeRedirectPath, q as inboxDeliverFrameSchema$1, r as AGENT_SELECTOR_HEADER$1, rt as notificationQuerySchema, s as CHAT_ENGAGEMENT_STATUSES, st as patchOnboardingSchema, t as AGENT_BIND_REJECT_REASONS, tt as loginSchema, ut as rebindAgentSchema, v as agentPinnedMessageSchema$1, vt as sessionEventMessageSchema, w as connectTokenExchangeSchema, wt as updateAdapterConfigSchema, x as chatMetadataSchema$1, xt as sessionStateMessageSchema, y as agentRuntimeConfigPayloadSchema$1, yt as sessionEventSchema$1, z as getMeDocSchema } from "./dist-CrdnqZjv.mjs";
|
|
6
6
|
import { a as ClientUserMismatchError$1, c as NotFoundError, d as users, f as uuidv7, i as ClientOrgMismatchError$1, l as UnauthorizedError, n as AppError, o as ConflictError, r as BadRequestError, s as ForbiddenError, u as organizations } from "./uuid-DbS_4vFh-iFghv4zA.mjs";
|
|
7
7
|
import { n as init_esm, r as trace, t as esm_exports } from "./esm-iadMkGbV.mjs";
|
|
8
|
-
import { $ as getSession, $t as suspendSession, A as
|
|
8
|
+
import { $ as getSession, $t as suspendSession, A as createChat, At as pollInbox, B as fetchUserAvatarForHumanAgent, Bt as resolveAvatarImageUrl, C as claimBacklogForPush, Ct as markMeChatRead, D as clearAgentAvatarImage, Dt as messages, E as cleanupStalePresence, Et as members, F as disconnectClient, Ft as registerChatMessageDispatcher, G as getAgentAvatarImage, Gt as sendToAgent$1, H as findOrCreateDirectChat, Ht as resolveDefaultOrgId$1, I as editMessage, It as registerClient, J as getChatDetail, Jt as setChatEngagement, K as getCachedAudience, Kt as serverInstances, L as ensureDefaultOrganization, Lt as removeParticipant, M as createNotifier, Mt as reactivateAgent, N as deleteAgent, Nt as rebindAgent, O as clients, Ot as notifyRecipients, P as deriveAuthState, Pt as recomputeWatchersForMember, Q as getPresence, Qt as suspendAgent, R as ensureParticipant, Rt as resetActivity, S as claimAndBuildForPush, T as cleanupStaleClients, Tt as markStaleAgents, U as getActivityOverview, Ut as retireClient, V as filterSessionsByParticipant, Vt as resolveChatTitle, W as getAgent, Wt as sendMessage, X as getOnlineCount, Xt as setRuntimeState, Y as getClient, Yt as setOffline, Z as getOrganization, Zt as submitAnswer, _ as bindAgent, _t as listClients, a as adapterConfigs, an as upsertSessionState, at as leaveChat, b as chats, bt as listMeChats, c as addParticipant, ct as listAgentSessions, d as agentConfigs, dt as listAgentsManagedByUser, en as touchAgent, et as heartbeatClient, f as agentPresence, ft as listAgentsWithRuntime, g as assertParticipant, gt as listChatsForMember, h as assertClientOwner, ht as listChats, i as adapterAgentMappings, in as updateOrganization, it as joinMeChat, j as createMeChat, jt as pruneStaleSilentEntries, k as createAgent, kt as pendingQuestions, l as agentAvatarImageUrl, lt as listAgentsForAdmin, m as archiveSession, mt as listChatParticipantsWithNames, n as SUPPORTED_AVATAR_IMAGE_MIMES, nn as updateAgent, nt as inboxEntries, o as addChatParticipants, ot as leaveMeChat, p as agents, pt as listAllSessions, q as getCallerEngagement, qt as setAgentAvatarImage, r as ackEntryByIdForBoundAgents, rn as updateClientCapabilities, rt as joinChat, s as addMeChatParticipants, st as listActiveAgentsPinnedToClient, t as MAX_AVATAR_IMAGE_BYTES, tn as unbindAgent, tt as heartbeatInstance, u as agentChatSessions, ut as listAgentsForMember, v as chatMembership, vt as listClientsForOrgAdmin, w as claimClient, wt as markMeChatUnread, x as checkAgentNameAvailability, xt as listMessages, y as chatUserState, yt as listMeChatSourceCounts, z as extractSummary, zt as resetTimedOutEntries } from "./client-BPRIfrOT-CoV_2o7e.mjs";
|
|
9
9
|
import { a as invitationRedemptions, c as recordRedemption, i as getActiveInvitation, l as rotateInvitation, n as ensureActiveInvitation, o as invitations, r as findActiveByToken, t as buildInviteUrl } from "./invitation-D_ENPHyj-5ETiae5r.mjs";
|
|
10
10
|
import { createRequire } from "node:module";
|
|
11
11
|
import { ZodError, z } from "zod";
|
|
@@ -850,7 +850,8 @@ z.object({
|
|
|
850
850
|
participants: z.array(chatParticipantDetailSchema),
|
|
851
851
|
title: z.string(),
|
|
852
852
|
firstMessagePreview: z.string().nullable(),
|
|
853
|
-
engagementStatus: chatEngagementStatusSchema
|
|
853
|
+
engagementStatus: chatEngagementStatusSchema,
|
|
854
|
+
viewerMembershipKind: z.enum(["participant", "watching"]).nullable()
|
|
854
855
|
});
|
|
855
856
|
z.object({ topic: z.string().trim().max(500).nullable() });
|
|
856
857
|
z.object({ agentId: z.string().min(1) });
|
|
@@ -887,9 +888,13 @@ z.object({
|
|
|
887
888
|
* Optional opt-in flags the client carries on `client:register` to advertise
|
|
888
889
|
* which negotiable wire-protocol features it implements. Distinct from
|
|
889
890
|
* `clientCapabilitiesSchema` (per-runtime-provider availability — different
|
|
890
|
-
* concept).
|
|
891
|
-
*
|
|
892
|
-
*
|
|
891
|
+
* concept).
|
|
892
|
+
*
|
|
893
|
+
* 0.10.4 ~ 0.14.2 clients still send this block (with `wsInboxDeliver: true`
|
|
894
|
+
* hard-coded). The 0.14.3+ runtime omits it. The schema is retained so that
|
|
895
|
+
* middle-version `client:register` frames still parse, even though the
|
|
896
|
+
* server no longer reads any of these fields — the WS inbox data plane is
|
|
897
|
+
* mandatory on this server build.
|
|
893
898
|
*/
|
|
894
899
|
const clientWireCapabilitiesSchema = z.object({ wsInboxDeliver: z.boolean().default(false) }).partial();
|
|
895
900
|
z.object({
|
|
@@ -1114,8 +1119,8 @@ z.object({
|
|
|
1114
1119
|
});
|
|
1115
1120
|
/**
|
|
1116
1121
|
* Server → client WS frame carrying the full image bytes for an image
|
|
1117
|
-
* message. Pushed before the corresponding `
|
|
1118
|
-
*
|
|
1122
|
+
* message. Pushed before the corresponding `inbox:deliver` frame so the
|
|
1123
|
+
* client has the file on disk by the time it renders the message.
|
|
1119
1124
|
*
|
|
1120
1125
|
* Best-effort: if the target client WS lives on a different server
|
|
1121
1126
|
* instance (or is offline), the frame is lost and the reference message
|
|
@@ -1276,14 +1281,11 @@ z.object({
|
|
|
1276
1281
|
}).extend({ message: clientMessageSchema });
|
|
1277
1282
|
z.object({ limit: z.coerce.number().int().min(1).max(50).default(10) });
|
|
1278
1283
|
/**
|
|
1279
|
-
* server → client: a single inbox entry pushed over the active WS connection
|
|
1280
|
-
* replacing the legacy `new_message` doorbell + HTTP `/inbox` poll round-trip.
|
|
1284
|
+
* server → client: a single inbox entry pushed over the active WS connection.
|
|
1281
1285
|
*
|
|
1282
1286
|
* `entryId` is the server-side `inbox_entries.id` the client must echo back
|
|
1283
|
-
* in `inbox:ack`. `
|
|
1284
|
-
*
|
|
1285
|
-
* side dispatch logic is reused verbatim (see proposal
|
|
1286
|
-
* hub-inbox-ws-data-plane §3.1).
|
|
1287
|
+
* in `inbox:ack`. `clientMessageSchema` carries `precedingMessages`, so the
|
|
1288
|
+
* client-side dispatch logic handles the silent-context bundle uniformly.
|
|
1287
1289
|
*
|
|
1288
1290
|
* `.passthrough()` so a forward-rolling server may extend the frame without
|
|
1289
1291
|
* breaking older clients that validate strictly. Older clients drop unknown
|
|
@@ -2015,8 +2017,13 @@ z.object({
|
|
|
2015
2017
|
/**
|
|
2016
2018
|
* Negotiable wire-protocol features the server advertises in its `welcome`
|
|
2017
2019
|
* frame. Older clients drop the `capabilities` field silently because the
|
|
2018
|
-
* frame is `.passthrough()`.
|
|
2019
|
-
*
|
|
2020
|
+
* frame is `.passthrough()`.
|
|
2021
|
+
*
|
|
2022
|
+
* Required by clients in the 0.10.4 ~ 0.14.2 range: those builds read
|
|
2023
|
+
* `wsInboxDeliver` here to decide whether to skip the local HTTP poll loop
|
|
2024
|
+
* and rely on `inbox:deliver` push frames. The 0.14.3+ runtime ignores the
|
|
2025
|
+
* field (push is the only path) but the server still emits it so middle-
|
|
2026
|
+
* version clients keep working.
|
|
2020
2027
|
*/
|
|
2021
2028
|
const serverCapabilitiesSchema = z.object({ wsInboxDeliver: z.boolean().default(false) }).partial();
|
|
2022
2029
|
/**
|
|
@@ -2145,13 +2152,6 @@ defineConfig({
|
|
|
2145
2152
|
refreshTokenExpiry: field(z.string().default("30d"), { env: "FIRST_TREE_HUB_AUTH_REFRESH_TOKEN_EXPIRY" }),
|
|
2146
2153
|
connectTokenExpiry: field(z.string().default("10m"), { env: "FIRST_TREE_HUB_AUTH_CONNECT_TOKEN_EXPIRY" })
|
|
2147
2154
|
},
|
|
2148
|
-
contextTreeSync: optional({
|
|
2149
|
-
githubToken: field(z.string(), {
|
|
2150
|
-
env: "FIRST_TREE_HUB_CONTEXT_TREE_GITHUB_TOKEN",
|
|
2151
|
-
secret: true
|
|
2152
|
-
}),
|
|
2153
|
-
githubTokenRepos: field(z.string().optional(), { env: "FIRST_TREE_HUB_CONTEXT_TREE_GITHUB_TOKEN_REPOS" })
|
|
2154
|
-
}),
|
|
2155
2155
|
oauth: optional({ githubApp: optional({
|
|
2156
2156
|
appId: field(z.string().min(1), { env: "FIRST_TREE_HUB_GITHUB_APP_ID" }),
|
|
2157
2157
|
clientId: field(z.string().min(1), { env: "FIRST_TREE_HUB_GITHUB_APP_CLIENT_ID" }),
|
|
@@ -2407,15 +2407,6 @@ var FirstTreeHubSDK = class {
|
|
|
2407
2407
|
return false;
|
|
2408
2408
|
}
|
|
2409
2409
|
}
|
|
2410
|
-
async pull(limit = 10) {
|
|
2411
|
-
return { entries: await this.requestJson(`/api/v1/agent/inbox?limit=${limit}`) };
|
|
2412
|
-
}
|
|
2413
|
-
async ack(entryId) {
|
|
2414
|
-
await this.requestVoid(`/api/v1/agent/inbox/${entryId}/ack`, { method: "POST" });
|
|
2415
|
-
}
|
|
2416
|
-
async renew(entryId) {
|
|
2417
|
-
await this.requestVoid(`/api/v1/agent/inbox/${entryId}/renew`, { method: "POST" });
|
|
2418
|
-
}
|
|
2419
2410
|
async sendMessage(chatId, data) {
|
|
2420
2411
|
return this.requestJson(`/api/v1/agent/chats/${chatId}/messages`, {
|
|
2421
2412
|
method: "POST",
|
|
@@ -2583,17 +2574,6 @@ const RECONNECT_MAX_MS = 3e4;
|
|
|
2583
2574
|
const WS_CONNECT_TIMEOUT_MS = 1e4;
|
|
2584
2575
|
const HEARTBEAT_INTERVAL_MS = 3e4;
|
|
2585
2576
|
/**
|
|
2586
|
-
* Client-side opt-in for the WS inbox data plane. Gates BOTH the
|
|
2587
|
-
* `wireCapabilities.wsInboxDeliver` flag we declare on `client:register`
|
|
2588
|
-
* AND how we interpret the server's welcome capability — without this AND,
|
|
2589
|
-
* a future client kill-switch could land in a half-state where we tell the
|
|
2590
|
-
* server "no thanks" but still treat welcome's `wsInboxDeliver:true` as
|
|
2591
|
-
* authoritative and stop the 5s HTTP poll, leaving messages stuck if a
|
|
2592
|
-
* NOTIFY ever drops. Hard-coded `true` for now; flip to a config knob if
|
|
2593
|
-
* you need a runtime kill-switch.
|
|
2594
|
-
*/
|
|
2595
|
-
const WS_INBOX_DELIVER_OPT_IN = true;
|
|
2596
|
-
/**
|
|
2597
2577
|
* Unified-user-token C5: reconnect PROACTIVELY this many ms before the JWT's
|
|
2598
2578
|
* `exp` claim so the client rotates to a fresh JWT without ever hitting the
|
|
2599
2579
|
* server-side `auth:expired` push. The provider's next `getAccessToken()` call
|
|
@@ -2653,15 +2633,6 @@ var ClientConnection = class extends EventEmitter {
|
|
|
2653
2633
|
/** Count of `server:welcome` frames received; drives `isReconnect` flag. */
|
|
2654
2634
|
welcomeFramesReceived = 0;
|
|
2655
2635
|
/**
|
|
2656
|
-
* Whether the most recent `server:welcome` frame advertised
|
|
2657
|
-
* `capabilities.wsInboxDeliver`. The runtime (AgentSlot) reads this
|
|
2658
|
-
* (via {@link supportsWsInboxDeliver}) to decide whether to keep the
|
|
2659
|
-
* legacy 5s HTTP poll or rely entirely on `inbox:deliver` push frames.
|
|
2660
|
-
* Re-evaluated on every reconnect — the welcome frame is the source of
|
|
2661
|
-
* truth, never assumed sticky across connections.
|
|
2662
|
-
*/
|
|
2663
|
-
wsInboxDeliverActive = false;
|
|
2664
|
-
/**
|
|
2665
2636
|
* Last handshake error, stashed for the `close` handler to surface a typed
|
|
2666
2637
|
* reason (e.g. {@link ClientOrgMismatchError}) instead of a generic
|
|
2667
2638
|
* "closed before ready" when `connect()` is pending.
|
|
@@ -2674,11 +2645,11 @@ var ClientConnection = class extends EventEmitter {
|
|
|
2674
2645
|
desiredBindings = /* @__PURE__ */ new Map();
|
|
2675
2646
|
pendingBinds = /* @__PURE__ */ new Map();
|
|
2676
2647
|
/**
|
|
2677
|
-
* In-flight image writes from recent `image_payload` frames.
|
|
2678
|
-
*
|
|
2679
|
-
*
|
|
2680
|
-
*
|
|
2681
|
-
*
|
|
2648
|
+
* In-flight image writes from recent `image_payload` frames. `image_payload`
|
|
2649
|
+
* arrives on the WS just before `inbox:deliver` for the same message, but
|
|
2650
|
+
* the EventEmitter dispatch is sync — so without gating, the deliver
|
|
2651
|
+
* handler can fire before the image bytes hit disk. Block `inbox:deliver`
|
|
2652
|
+
* emission until these settle.
|
|
2682
2653
|
*/
|
|
2683
2654
|
pendingImageWrites = /* @__PURE__ */ new Set();
|
|
2684
2655
|
constructor(config) {
|
|
@@ -2699,24 +2670,21 @@ var ClientConnection = class extends EventEmitter {
|
|
|
2699
2670
|
return this.boundAgents;
|
|
2700
2671
|
}
|
|
2701
2672
|
/**
|
|
2702
|
-
*
|
|
2703
|
-
*
|
|
2704
|
-
*
|
|
2705
|
-
*
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
return this.wsInboxDeliverActive;
|
|
2709
|
-
}
|
|
2710
|
-
/**
|
|
2711
|
-
* Ack a delivered inbox entry over the WS data plane. Replaces the legacy
|
|
2712
|
-
* `sdk.ack()` HTTP call when the connection has negotiated
|
|
2713
|
-
* `wsInboxDeliver`. Safe to call when the WS is closed — the frame is
|
|
2714
|
-
* dropped silently and the entry will time out and re-deliver on
|
|
2715
|
-
* reconnect, mirroring how the legacy timeout reaper handles HTTP
|
|
2716
|
-
* ack-loss.
|
|
2673
|
+
* Ack a delivered inbox entry over the WS data plane. Safe to call when the
|
|
2674
|
+
* WS is closed — the frame is dropped (logged) and the entry will time out
|
|
2675
|
+
* server-side and re-deliver on reconnect. The handler has by then already
|
|
2676
|
+
* started processing, so reaper-driven redelivery surfaces as a duplicate
|
|
2677
|
+
* dispatch on the next connect; SessionManager's dedupe key
|
|
2678
|
+
* `(chatId, messageId)` collapses it.
|
|
2717
2679
|
*/
|
|
2718
2680
|
sendInboxAck(entryId) {
|
|
2719
|
-
if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
|
|
2681
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
2682
|
+
this.wsLogger.warn({
|
|
2683
|
+
entryId,
|
|
2684
|
+
readyState: this.ws?.readyState
|
|
2685
|
+
}, "inbox:ack dropped — socket not OPEN");
|
|
2686
|
+
return;
|
|
2687
|
+
}
|
|
2720
2688
|
this.ws.send(JSON.stringify({
|
|
2721
2689
|
type: "inbox:ack",
|
|
2722
2690
|
entryId
|
|
@@ -2873,7 +2841,6 @@ var ClientConnection = class extends EventEmitter {
|
|
|
2873
2841
|
this.clearAuthRefreshTimer();
|
|
2874
2842
|
const wasRegistered = this.registered;
|
|
2875
2843
|
this.registered = false;
|
|
2876
|
-
this.wsInboxDeliverActive = false;
|
|
2877
2844
|
this.rejectAllPendingBinds("WebSocket closed");
|
|
2878
2845
|
if (!settled) {
|
|
2879
2846
|
this.wsLogger.warn({ code }, "closed before ready");
|
|
@@ -2909,8 +2876,7 @@ var ClientConnection = class extends EventEmitter {
|
|
|
2909
2876
|
clientId: this.clientId,
|
|
2910
2877
|
hostname: hostname(),
|
|
2911
2878
|
os: platform(),
|
|
2912
|
-
sdkVersion: this.sdkVersion
|
|
2913
|
-
wireCapabilities: { wsInboxDeliver: WS_INBOX_DELIVER_OPT_IN }
|
|
2879
|
+
sdkVersion: this.sdkVersion
|
|
2914
2880
|
}));
|
|
2915
2881
|
return;
|
|
2916
2882
|
}
|
|
@@ -2920,7 +2886,6 @@ var ClientConnection = class extends EventEmitter {
|
|
|
2920
2886
|
this.wsLogger.warn({ issues: parsed.error.issues.map((i) => i.message) }, "ignoring malformed server:welcome frame");
|
|
2921
2887
|
return;
|
|
2922
2888
|
}
|
|
2923
|
-
this.wsInboxDeliverActive = parsed.data.capabilities?.wsInboxDeliver === true && WS_INBOX_DELIVER_OPT_IN;
|
|
2924
2889
|
const isReconnect = this.welcomeFramesReceived > 0;
|
|
2925
2890
|
this.welcomeFramesReceived++;
|
|
2926
2891
|
this.emit("server:welcome", {
|
|
@@ -3058,15 +3023,6 @@ var ClientConnection = class extends EventEmitter {
|
|
|
3058
3023
|
write.finally(() => this.pendingImageWrites.delete(write));
|
|
3059
3024
|
return;
|
|
3060
3025
|
}
|
|
3061
|
-
if (type === "new_message") {
|
|
3062
|
-
const inboxId = msg.inboxId;
|
|
3063
|
-
if (!inboxId) return;
|
|
3064
|
-
if (this.pendingImageWrites.size > 0) Promise.all([...this.pendingImageWrites]).finally(() => {
|
|
3065
|
-
this.emit("agent:message", inboxId, msg);
|
|
3066
|
-
});
|
|
3067
|
-
else this.emit("agent:message", inboxId, msg);
|
|
3068
|
-
return;
|
|
3069
|
-
}
|
|
3070
3026
|
if (type === "inbox:deliver") {
|
|
3071
3027
|
const parsed = inboxDeliverFrameSchema.safeParse(msg);
|
|
3072
3028
|
if (!parsed.success) {
|
|
@@ -6812,18 +6768,13 @@ var SessionManager = class {
|
|
|
6812
6768
|
this.config.onStateChange(chatId, state);
|
|
6813
6769
|
}
|
|
6814
6770
|
/**
|
|
6815
|
-
* ACK an inbox entry — delayed until handler starts processing.
|
|
6816
|
-
*
|
|
6817
|
-
* Routes through `config.ackEntry` when set (WS push path) or falls back to
|
|
6818
|
-
* `sdk.ack` (HTTP poll path). One ack per entry, one channel per slot —
|
|
6819
|
-
* mixing channels in one slot would leak the server's per-agent in-flight
|
|
6820
|
-
* counter (proposal hub-inbox-ws-data-plane §3.5).
|
|
6771
|
+
* ACK an inbox entry — delayed until handler starts processing. Routes
|
|
6772
|
+
* through `config.ackEntry`, which is wired to the WS data plane.
|
|
6821
6773
|
*/
|
|
6822
6774
|
async ackEntry(entryId, chatId) {
|
|
6823
6775
|
if (entryId === void 0) return;
|
|
6824
6776
|
try {
|
|
6825
|
-
|
|
6826
|
-
else await this.config.sdk.ack(entryId);
|
|
6777
|
+
await this.config.ackEntry(entryId);
|
|
6827
6778
|
} catch {
|
|
6828
6779
|
this.config.log.warn({
|
|
6829
6780
|
chatId,
|
|
@@ -6976,9 +6927,7 @@ var AgentSlot = class {
|
|
|
6976
6927
|
sessionManager = null;
|
|
6977
6928
|
config;
|
|
6978
6929
|
logger;
|
|
6979
|
-
sdk = null;
|
|
6980
6930
|
agentConfigCache = null;
|
|
6981
|
-
pollingTimer = null;
|
|
6982
6931
|
reconcileTimer = null;
|
|
6983
6932
|
listeners = [];
|
|
6984
6933
|
/**
|
|
@@ -7016,7 +6965,6 @@ var AgentSlot = class {
|
|
|
7016
6965
|
}
|
|
7017
6966
|
async start(contextTreeBinding) {
|
|
7018
6967
|
const sdk = (await this.clientConnection.bindAgent(this.config.agentId, this.config.runtimeType ?? this.config.type, this.config.runtimeVersion)).sdk;
|
|
7019
|
-
this.sdk = sdk;
|
|
7020
6968
|
const agent = await sdk.register();
|
|
7021
6969
|
this.logger.info({ displayName: agent.displayName }, "agent bound");
|
|
7022
6970
|
if (agent.type === "human") {
|
|
@@ -7036,9 +6984,6 @@ var AgentSlot = class {
|
|
|
7036
6984
|
throw new Error(`Hub unreachable while loading agent config: ${msg}`);
|
|
7037
6985
|
}
|
|
7038
6986
|
this.inboxId = agent.inboxId;
|
|
7039
|
-
const onMessage = (agentId) => {
|
|
7040
|
-
if (agentId === this.config.agentId) this.pullAndDispatch();
|
|
7041
|
-
};
|
|
7042
6987
|
const onInboxDeliver = (inboxId, frame) => {
|
|
7043
6988
|
if (inboxId !== this.inboxId) return;
|
|
7044
6989
|
this.dispatchPushedFrame(frame).catch((err) => {
|
|
@@ -7057,14 +7002,10 @@ var AgentSlot = class {
|
|
|
7057
7002
|
const onReconcileResult = (result) => {
|
|
7058
7003
|
if (result.agentId === this.config.agentId && this.sessionManager) this.sessionManager.applyStaleChatIds(result.staleChatIds);
|
|
7059
7004
|
};
|
|
7060
|
-
this.clientConnection.on("agent:message", onMessage);
|
|
7061
7005
|
this.clientConnection.on("inbox:deliver", onInboxDeliver);
|
|
7062
7006
|
this.clientConnection.on("agent:bound", onBound);
|
|
7063
7007
|
this.clientConnection.on("session:reconcile:result", onReconcileResult);
|
|
7064
7008
|
this.listeners.push({
|
|
7065
|
-
event: "agent:message",
|
|
7066
|
-
fn: onMessage
|
|
7067
|
-
}, {
|
|
7068
7009
|
event: "inbox:deliver",
|
|
7069
7010
|
fn: onInboxDeliver
|
|
7070
7011
|
}, {
|
|
@@ -7082,10 +7023,10 @@ var AgentSlot = class {
|
|
|
7082
7023
|
agentId: this.config.agentId
|
|
7083
7024
|
})
|
|
7084
7025
|
});
|
|
7085
|
-
const ackEntry =
|
|
7026
|
+
const ackEntry = (entryId) => {
|
|
7086
7027
|
this.clientConnection.sendInboxAck(entryId);
|
|
7087
7028
|
return Promise.resolve();
|
|
7088
|
-
}
|
|
7029
|
+
};
|
|
7089
7030
|
this.sessionManager = new SessionManager({
|
|
7090
7031
|
session: this.config.session,
|
|
7091
7032
|
concurrency: this.config.concurrency,
|
|
@@ -7128,24 +7069,15 @@ var AgentSlot = class {
|
|
|
7128
7069
|
event: "session:command",
|
|
7129
7070
|
fn: onCommand
|
|
7130
7071
|
});
|
|
7131
|
-
this.startPolling();
|
|
7132
7072
|
this.startReconcileLoop();
|
|
7133
7073
|
return agent;
|
|
7134
7074
|
}
|
|
7135
7075
|
async stop() {
|
|
7136
|
-
if (this.pollingTimer) {
|
|
7137
|
-
clearInterval(this.pollingTimer);
|
|
7138
|
-
this.pollingTimer = null;
|
|
7139
|
-
}
|
|
7140
7076
|
if (this.reconcileTimer) {
|
|
7141
7077
|
clearInterval(this.reconcileTimer);
|
|
7142
7078
|
this.reconcileTimer = null;
|
|
7143
7079
|
}
|
|
7144
|
-
for (const entry of this.listeners)
|
|
7145
|
-
else if (entry.event === "inbox:deliver") this.clientConnection.off(entry.event, entry.fn);
|
|
7146
|
-
else if (entry.event === "agent:bound") this.clientConnection.off(entry.event, entry.fn);
|
|
7147
|
-
else if (entry.event === "session:reconcile:result") this.clientConnection.off(entry.event, entry.fn);
|
|
7148
|
-
else this.clientConnection.off(entry.event, entry.fn);
|
|
7080
|
+
for (const entry of this.listeners) this.clientConnection.off(entry.event, entry.fn);
|
|
7149
7081
|
this.listeners = [];
|
|
7150
7082
|
await this.clientConnection.unbindAgent(this.config.agentId);
|
|
7151
7083
|
await this.sessionManager?.shutdown();
|
|
@@ -7166,31 +7098,18 @@ var AgentSlot = class {
|
|
|
7166
7098
|
const runtimeState = this.sessionManager.getAggregateRuntimeState();
|
|
7167
7099
|
if (runtimeState) this.clientConnection.reportRuntimeState(this.config.agentId, runtimeState);
|
|
7168
7100
|
}
|
|
7169
|
-
startPolling() {
|
|
7170
|
-
if (this.clientConnection.supportsWsInboxDeliver) {
|
|
7171
|
-
this.logger.info("WS inbox data plane active — skipping 5s HTTP poll");
|
|
7172
|
-
return;
|
|
7173
|
-
}
|
|
7174
|
-
this.pollingTimer = setInterval(() => {
|
|
7175
|
-
this.pullAndDispatch();
|
|
7176
|
-
}, 5e3);
|
|
7177
|
-
this.pullAndDispatch();
|
|
7178
|
-
}
|
|
7179
7101
|
/**
|
|
7180
7102
|
* Translate an `inbox:deliver` push frame into the {@link InboxEntryWithMessage}
|
|
7181
7103
|
* shape `SessionManager.dispatch` expects, then dispatch.
|
|
7182
7104
|
*
|
|
7183
7105
|
* Ack happens INSIDE `dispatch` via the `ackEntry` callback we pinned at
|
|
7184
|
-
* construction time —
|
|
7185
|
-
*
|
|
7186
|
-
*
|
|
7187
|
-
*
|
|
7188
|
-
* which leaks the server's per-agent in-flight counter and stalls push
|
|
7189
|
-
* after `inboxMaxInFlightPerAgent` messages.
|
|
7106
|
+
* construction time — `clientConnection.sendInboxAck`. Sending an additional
|
|
7107
|
+
* ack here would double-ack: a WS frame the server cannot match against any
|
|
7108
|
+
* `delivered` row, which leaks the server's per-agent in-flight counter and
|
|
7109
|
+
* stalls push after `inboxMaxInFlightPerAgent` messages.
|
|
7190
7110
|
*
|
|
7191
7111
|
* Dispatch errors propagate up; the entry stays `delivered` server-side
|
|
7192
|
-
* and the 300s timeout reaper rolls it back to `pending` for replay
|
|
7193
|
-
* (proposal §3.7).
|
|
7112
|
+
* and the 300s timeout reaper rolls it back to `pending` for replay.
|
|
7194
7113
|
*/
|
|
7195
7114
|
async dispatchPushedFrame(frame) {
|
|
7196
7115
|
if (!this.sessionManager) return;
|
|
@@ -7218,15 +7137,6 @@ var AgentSlot = class {
|
|
|
7218
7137
|
if (chatIds.length === 0) return;
|
|
7219
7138
|
this.clientConnection.sendSessionReconcile(this.config.agentId, chatIds);
|
|
7220
7139
|
}
|
|
7221
|
-
async pullAndDispatch() {
|
|
7222
|
-
if (!this.sdk || !this.sessionManager) return;
|
|
7223
|
-
try {
|
|
7224
|
-
const { entries } = await this.sdk.pull(10);
|
|
7225
|
-
for (const entry of entries) await this.sessionManager.dispatch(entry);
|
|
7226
|
-
} catch (err) {
|
|
7227
|
-
this.logger.warn({ err }, "poll error");
|
|
7228
|
-
}
|
|
7229
|
-
}
|
|
7230
7140
|
};
|
|
7231
7141
|
/**
|
|
7232
7142
|
* Top-level marker file Claude Code writes after a successful OAuth login.
|
|
@@ -9853,7 +9763,7 @@ async function onboardCreate(args) {
|
|
|
9853
9763
|
}
|
|
9854
9764
|
const runtimeAgent = args.type === "human" ? args.assistant : args.id;
|
|
9855
9765
|
if (args.feishuBotAppId && args.feishuBotAppSecret) {
|
|
9856
|
-
const { bindFeishuBot } = await import("./feishu-
|
|
9766
|
+
const { bindFeishuBot } = await import("./feishu-DNoBroKK.mjs").then((n) => n.r);
|
|
9857
9767
|
const targetAgentUuid = args.type === "human" ? assistantUuid : primary.uuid;
|
|
9858
9768
|
if (!targetAgentUuid) print.line(`Warning: Cannot bind Feishu bot — no runtime agent available for "${args.id}".\n`);
|
|
9859
9769
|
else {
|
|
@@ -11066,7 +10976,7 @@ function createFeedbackHandler(config) {
|
|
|
11066
10976
|
return { handle };
|
|
11067
10977
|
}
|
|
11068
10978
|
//#endregion
|
|
11069
|
-
//#region ../server/dist/app-
|
|
10979
|
+
//#region ../server/dist/app-D4mz6WSP.mjs
|
|
11070
10980
|
var import_fastify_opentelemetry = /* @__PURE__ */ __toESM(require_fastify_opentelemetry(), 1);
|
|
11071
10981
|
init_esm();
|
|
11072
10982
|
var __defProp = Object.defineProperty;
|
|
@@ -11844,18 +11754,6 @@ async function agentInboxRoutes(app) {
|
|
|
11844
11754
|
const query = inboxPollQuerySchema.parse(request.query);
|
|
11845
11755
|
return await pollInbox(app.db, identity.inboxId, query.limit);
|
|
11846
11756
|
});
|
|
11847
|
-
app.post("/:entryId/ack", async (request, reply) => {
|
|
11848
|
-
const identity = requireAgent(request);
|
|
11849
|
-
const entryId = Number(request.params.entryId);
|
|
11850
|
-
await ackEntry$2(app.db, entryId, identity.inboxId);
|
|
11851
|
-
return reply.status(204).send();
|
|
11852
|
-
});
|
|
11853
|
-
app.post("/:entryId/renew", async (request, reply) => {
|
|
11854
|
-
const identity = requireAgent(request);
|
|
11855
|
-
const entryId = Number(request.params.entryId);
|
|
11856
|
-
await renewEntry(app.db, entryId, identity.inboxId);
|
|
11857
|
-
return reply.status(204).send();
|
|
11858
|
-
});
|
|
11859
11757
|
}
|
|
11860
11758
|
async function agentMeRoutes(app) {
|
|
11861
11759
|
app.get("/me", async (request) => {
|
|
@@ -11887,11 +11785,11 @@ async function agentMeRoutes(app) {
|
|
|
11887
11785
|
* {imageId, mimeType, filename, size}
|
|
11888
11786
|
*
|
|
11889
11787
|
* The push is fire-and-forget: `ws.send()` queues the frame into the socket's
|
|
11890
|
-
* send buffer synchronously, which is the only ordering guarantee we need
|
|
11891
|
-
*
|
|
11892
|
-
*
|
|
11893
|
-
*
|
|
11894
|
-
*
|
|
11788
|
+
* send buffer synchronously, which is the only ordering guarantee we need —
|
|
11789
|
+
* the subsequent `inbox:deliver` frame is driven by a PG NOTIFY round trip,
|
|
11790
|
+
* so the image lands first on the wire. Awaiting the TCP flush here would
|
|
11791
|
+
* put a slow subscriber's backpressure on the sender's HTTP response for a
|
|
11792
|
+
* feature that is already best-effort.
|
|
11895
11793
|
*
|
|
11896
11794
|
* Non-image messages are returned unchanged. Missing-subscriber / wrong-
|
|
11897
11795
|
* instance cases are acceptable loss per the image-out-of-messages design
|
|
@@ -12588,8 +12486,7 @@ async function summarizeContextTreeUsage(db, organizationId, windowDays) {
|
|
|
12588
12486
|
/**
|
|
12589
12487
|
* Default per-agent in-flight cap when `server.inbox.maxInFlightPerAgent` is
|
|
12590
12488
|
* unset. Mirrors the schema default so a hub running without an explicit
|
|
12591
|
-
* `inbox` block still gets reasonable backpressure
|
|
12592
|
-
* flipped on. See proposal hub-inbox-ws-data-plane §3.5.
|
|
12489
|
+
* `inbox` block still gets reasonable backpressure.
|
|
12593
12490
|
*/
|
|
12594
12491
|
const DEFAULT_INBOX_MAX_IN_FLIGHT_PER_AGENT = 32;
|
|
12595
12492
|
/**
|
|
@@ -12634,23 +12531,12 @@ function clientWsRoutes(notifier, instanceId) {
|
|
|
12634
12531
|
let authExpiryTimer = null;
|
|
12635
12532
|
const boundAgents = /* @__PURE__ */ new Map();
|
|
12636
12533
|
/**
|
|
12637
|
-
* Whether the connected client opted into the WS inbox data plane via
|
|
12638
|
-
* `client:register.wireCapabilities.wsInboxDeliver`. Set per-socket
|
|
12639
|
-
* because client SDKs are upgraded independently — an old client
|
|
12640
|
-
* connecting to a new server must keep receiving the legacy
|
|
12641
|
-
* `new_message` doorbell + HTTP poll path (proposal §3.6).
|
|
12642
|
-
*/
|
|
12643
|
-
let clientWantsWsInboxDeliver = false;
|
|
12644
|
-
/**
|
|
12645
12534
|
* Per-agent in-flight `inbox:deliver` counter for backpressure. Lives on
|
|
12646
12535
|
* the socket — when the WS closes it goes with it; that's intentional,
|
|
12647
12536
|
* because re-counting on a fresh connection would bias the cap against
|
|
12648
|
-
* a healthy reconnect
|
|
12537
|
+
* a healthy reconnect.
|
|
12649
12538
|
*/
|
|
12650
12539
|
const inboxInFlight = /* @__PURE__ */ new Map();
|
|
12651
|
-
function pushUseWsDataPlane() {
|
|
12652
|
-
return clientWantsWsInboxDeliver;
|
|
12653
|
-
}
|
|
12654
12540
|
/**
|
|
12655
12541
|
* Returns `false` when the socket has already moved out of `OPEN` —
|
|
12656
12542
|
* the only failure mode the caller can observe synchronously.
|
|
@@ -12893,7 +12779,6 @@ function clientWsRoutes(notifier, instanceId) {
|
|
|
12893
12779
|
try {
|
|
12894
12780
|
if (type === "client:register") {
|
|
12895
12781
|
const data = clientRegisterSchema.parse(msg);
|
|
12896
|
-
clientWantsWsInboxDeliver = data.wireCapabilities?.wsInboxDeliver === true;
|
|
12897
12782
|
let placeholderOrgId = jwtDefaultOrgId;
|
|
12898
12783
|
if (!placeholderOrgId) {
|
|
12899
12784
|
const [m] = await app.db.select({ organizationId: members.organizationId }).from(members).where(and(eq(members.userId, session.userId), eq(members.status, "active"))).orderBy(desc(members.createdAt), desc(members.id)).limit(1);
|
|
@@ -13031,9 +12916,7 @@ function clientWsRoutes(notifier, instanceId) {
|
|
|
13031
12916
|
inboxId: agent.inboxId,
|
|
13032
12917
|
organizationId: agent.organizationId
|
|
13033
12918
|
});
|
|
13034
|
-
|
|
13035
|
-
if (wsPushActive) notifier.subscribe(agent.inboxId, socket, makeInboxPushHandler(agent.id, agent.inboxId));
|
|
13036
|
-
else notifier.subscribe(agent.inboxId, socket);
|
|
12919
|
+
notifier.subscribe(agent.inboxId, socket, makeInboxPushHandler(agent.id, agent.inboxId));
|
|
13037
12920
|
socket.send(JSON.stringify({
|
|
13038
12921
|
type: "agent:bound",
|
|
13039
12922
|
ref,
|
|
@@ -13041,7 +12924,7 @@ function clientWsRoutes(notifier, instanceId) {
|
|
|
13041
12924
|
displayName: agent.displayName,
|
|
13042
12925
|
agentType: agent.type
|
|
13043
12926
|
}));
|
|
13044
|
-
|
|
12927
|
+
drainBacklogForAgent(agent.id, agent.inboxId).catch((err) => {
|
|
13045
12928
|
app.log.error({
|
|
13046
12929
|
err,
|
|
13047
12930
|
agentId: agent.id
|
|
@@ -13162,6 +13045,14 @@ function clientWsRoutes(notifier, instanceId) {
|
|
|
13162
13045
|
} else if (type === "inbox:ack") {
|
|
13163
13046
|
const payloadResult = inboxAckFrameSchema.safeParse(msg);
|
|
13164
13047
|
if (!payloadResult.success) {
|
|
13048
|
+
app.log.warn({
|
|
13049
|
+
clientId,
|
|
13050
|
+
issues: payloadResult.error.issues.map((i) => ({
|
|
13051
|
+
path: i.path.join("."),
|
|
13052
|
+
code: i.code,
|
|
13053
|
+
message: i.message
|
|
13054
|
+
}))
|
|
13055
|
+
}, "malformed inbox:ack frame — replying error");
|
|
13165
13056
|
socket.send(JSON.stringify({
|
|
13166
13057
|
type: "error",
|
|
13167
13058
|
message: "Malformed inbox:ack frame"
|
|
@@ -13171,7 +13062,14 @@ function clientWsRoutes(notifier, instanceId) {
|
|
|
13171
13062
|
const { entryId } = payloadResult.data;
|
|
13172
13063
|
try {
|
|
13173
13064
|
const ackedEntry = await ackEntryByIdForBoundAgents(app.db, entryId, [...boundAgents.values()].map((a) => a.inboxId));
|
|
13174
|
-
if (!ackedEntry)
|
|
13065
|
+
if (!ackedEntry) {
|
|
13066
|
+
app.log.debug({
|
|
13067
|
+
clientId,
|
|
13068
|
+
entryId,
|
|
13069
|
+
boundInboxes: boundAgents.size
|
|
13070
|
+
}, "inbox:ack matched no row — stale ack or reaper race");
|
|
13071
|
+
return;
|
|
13072
|
+
}
|
|
13175
13073
|
const owner = [...boundAgents.values()].find((a) => a.inboxId === ackedEntry.inboxId);
|
|
13176
13074
|
if (owner) {
|
|
13177
13075
|
inboxInFlight.set(owner.agentId, Math.max(0, (inboxInFlight.get(owner.agentId) ?? 1) - 1));
|
|
@@ -13237,17 +13135,23 @@ async function agentActivityRoutes(app) {
|
|
|
13237
13135
|
/**
|
|
13238
13136
|
* Project a DB agent row into its wire shape. Strips the inline image
|
|
13239
13137
|
* `avatarImageData` (large bytea, only meant for the image-serve route)
|
|
13240
|
-
* and synthesises the public `avatarImageUrl`
|
|
13241
|
-
*
|
|
13242
|
-
*
|
|
13138
|
+
* and synthesises the public `avatarImageUrl` via {@link resolveAvatarImageUrl}
|
|
13139
|
+
* so human agents fall back to the backing user's external avatar URL
|
|
13140
|
+
* (e.g. GitHub) when no upload exists. `createdAt`/`updatedAt` are
|
|
13141
|
+
* coerced to ISO strings so the response is pure JSON.
|
|
13243
13142
|
*/
|
|
13244
|
-
function serializeAgent(agent) {
|
|
13143
|
+
function serializeAgent(agent, userAvatarUrl) {
|
|
13245
13144
|
const { avatarImageData: _data, avatarImageMime: _mime, avatarImageUpdatedAt, createdAt, updatedAt, ...rest } = agent;
|
|
13246
13145
|
return {
|
|
13247
13146
|
...rest,
|
|
13248
13147
|
createdAt: createdAt.toISOString(),
|
|
13249
13148
|
updatedAt: updatedAt.toISOString(),
|
|
13250
|
-
avatarImageUrl:
|
|
13149
|
+
avatarImageUrl: resolveAvatarImageUrl({
|
|
13150
|
+
uuid: agent.uuid,
|
|
13151
|
+
type: agent.type,
|
|
13152
|
+
avatarImageUpdatedAt,
|
|
13153
|
+
userAvatarUrl
|
|
13154
|
+
})
|
|
13251
13155
|
};
|
|
13252
13156
|
}
|
|
13253
13157
|
/**
|
|
@@ -13279,7 +13183,7 @@ async function agentRoutes(app) {
|
|
|
13279
13183
|
}
|
|
13280
13184
|
app.get("/:uuid", async (request) => {
|
|
13281
13185
|
const { agent } = await requireAgentAccess(request, app.db, "visible");
|
|
13282
|
-
return serializeAgent(agent);
|
|
13186
|
+
return serializeAgent(agent, await fetchUserAvatarForHumanAgent(app.db, agent));
|
|
13283
13187
|
});
|
|
13284
13188
|
app.patch("/:uuid", { config: { otelRecordBody: true } }, async (request) => {
|
|
13285
13189
|
const { scope } = await requireAgentAccess(request, app.db, "manage");
|
|
@@ -13288,14 +13192,14 @@ async function agentRoutes(app) {
|
|
|
13288
13192
|
const before = body.clientId !== void 0 ? await getAgent(app.db, request.params.uuid) : null;
|
|
13289
13193
|
const agent = await updateAgent(app.db, request.params.uuid, body);
|
|
13290
13194
|
if (before && before.clientId === null && agent.clientId !== null) notifyClientAgentPinned(agent);
|
|
13291
|
-
return serializeAgent(agent);
|
|
13195
|
+
return serializeAgent(agent, await fetchUserAvatarForHumanAgent(app.db, agent));
|
|
13292
13196
|
});
|
|
13293
13197
|
app.patch("/:uuid/rebind", { config: { otelRecordBody: true } }, async (request) => {
|
|
13294
13198
|
await requireAgentAccess(request, app.db, "manage");
|
|
13295
13199
|
const body = rebindAgentSchema.parse(request.body);
|
|
13296
13200
|
const agent = await rebindAgent(app.db, request.params.uuid, body);
|
|
13297
13201
|
notifyClientAgentPinned(agent);
|
|
13298
|
-
return serializeAgent(agent);
|
|
13202
|
+
return serializeAgent(agent, await fetchUserAvatarForHumanAgent(app.db, agent));
|
|
13299
13203
|
});
|
|
13300
13204
|
app.post("/:uuid/disconnect", async (request, reply) => {
|
|
13301
13205
|
await requireAgentAccess(request, app.db, "manage");
|
|
@@ -13305,11 +13209,13 @@ async function agentRoutes(app) {
|
|
|
13305
13209
|
});
|
|
13306
13210
|
app.post("/:uuid/suspend", async (request) => {
|
|
13307
13211
|
await requireAgentAccess(request, app.db, "manage");
|
|
13308
|
-
|
|
13212
|
+
const agent = await suspendAgent(app.db, request.params.uuid);
|
|
13213
|
+
return serializeAgent(agent, await fetchUserAvatarForHumanAgent(app.db, agent));
|
|
13309
13214
|
});
|
|
13310
13215
|
app.post("/:uuid/reactivate", async (request) => {
|
|
13311
13216
|
await requireAgentAccess(request, app.db, "manage");
|
|
13312
|
-
|
|
13217
|
+
const agent = await reactivateAgent(app.db, request.params.uuid);
|
|
13218
|
+
return serializeAgent(agent, await fetchUserAvatarForHumanAgent(app.db, agent));
|
|
13313
13219
|
});
|
|
13314
13220
|
app.delete("/:uuid", async (request, reply) => {
|
|
13315
13221
|
await requireAgentAccess(request, app.db, "manage");
|
|
@@ -13859,6 +13765,7 @@ const APP_JWT_EXPIRY = "9m";
|
|
|
13859
13765
|
* caller's side; the docs recommend 60 seconds. We mirror that.
|
|
13860
13766
|
*/
|
|
13861
13767
|
const APP_JWT_IAT_SKEW_SECONDS = 60;
|
|
13768
|
+
const APP_INSTALLATION_TOKEN_URL = (id) => `https://api.github.com/app/installations/${id}/access_tokens`;
|
|
13862
13769
|
const APP_INSTALLATION_URL = (id) => `https://api.github.com/app/installations/${id}`;
|
|
13863
13770
|
const OAUTH_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
13864
13771
|
const OAUTH_AUTHORIZE_URL = "https://github.com/login/oauth/authorize";
|
|
@@ -13963,6 +13870,39 @@ async function listUserAccessibleInstallationIds(userAccessToken, opts = {}) {
|
|
|
13963
13870
|
return out;
|
|
13964
13871
|
}
|
|
13965
13872
|
/**
|
|
13873
|
+
* Mint a per-installation token (server-to-server). The token is cheap
|
|
13874
|
+
* (one signature + one HTTP round-trip) and the upstream TTL is ~1h, so
|
|
13875
|
+
* the recommended caller pattern is "mint per request" rather than caching
|
|
13876
|
+
* — caching forces the caller to also track expiry, suspended state, and
|
|
13877
|
+
* GitHub-side permission churn, which the design explicitly punts to Phase
|
|
13878
|
+
* 4. We give callers a typed result and let them cache if profiling shows
|
|
13879
|
+
* the round-trip is hot.
|
|
13880
|
+
*
|
|
13881
|
+
* Throws `GithubAppApiError` on non-2xx. 401 means the App JWT is bad or
|
|
13882
|
+
* the App's key has been rotated upstream; 404 means the installation
|
|
13883
|
+
* was uninstalled. Callers SHOULD persist the suspension state when 403
|
|
13884
|
+
* comes back with `suspended` (the design tracks this as `suspended_at`
|
|
13885
|
+
* on `github_app_installations`).
|
|
13886
|
+
*/
|
|
13887
|
+
async function mintInstallationToken(appJwt, installationId, opts = {}) {
|
|
13888
|
+
const res = await (opts.fetcher ?? fetch)(APP_INSTALLATION_TOKEN_URL(installationId), {
|
|
13889
|
+
method: "POST",
|
|
13890
|
+
headers: {
|
|
13891
|
+
Authorization: `Bearer ${appJwt}`,
|
|
13892
|
+
Accept: "application/vnd.github+json",
|
|
13893
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
13894
|
+
}
|
|
13895
|
+
});
|
|
13896
|
+
if (!res.ok) throw new GithubAppApiError(res.status, `GitHub App installation-token request failed (${res.status})`);
|
|
13897
|
+
const body = await res.json();
|
|
13898
|
+
return {
|
|
13899
|
+
token: body.token,
|
|
13900
|
+
expiresAt: body.expires_at,
|
|
13901
|
+
permissions: body.permissions ?? {},
|
|
13902
|
+
repositorySelection: body.repository_selection ?? "all"
|
|
13903
|
+
};
|
|
13904
|
+
}
|
|
13905
|
+
/**
|
|
13966
13906
|
* Trade an expiring user-to-server access token for a fresh pair using
|
|
13967
13907
|
* its refresh token. Thrown on:
|
|
13968
13908
|
* - Network / 5xx — `GithubAppApiError(status, …)`
|
|
@@ -15068,11 +15008,14 @@ async function chatRoutes(app) {
|
|
|
15068
15008
|
}));
|
|
15069
15009
|
const title = resolveChatTitle(chat.topic, firstMessagePreview, participantsForTitle, scope.humanAgentId);
|
|
15070
15010
|
const engagementStatus = await getCallerEngagement(app.db, chat.id, scope.humanAgentId);
|
|
15011
|
+
const [callerMembership] = await app.db.select({ accessMode: chatMembership.accessMode }).from(chatMembership).where(and(eq(chatMembership.chatId, chat.id), eq(chatMembership.agentId, scope.humanAgentId))).limit(1);
|
|
15012
|
+
const viewerMembershipKind = callerMembership ? callerMembership.accessMode === "speaker" ? "participant" : "watching" : null;
|
|
15071
15013
|
return {
|
|
15072
15014
|
...chat,
|
|
15073
15015
|
title,
|
|
15074
15016
|
firstMessagePreview,
|
|
15075
15017
|
engagementStatus,
|
|
15018
|
+
viewerMembershipKind,
|
|
15076
15019
|
createdAt: chat.createdAt.toISOString(),
|
|
15077
15020
|
updatedAt: chat.updatedAt.toISOString(),
|
|
15078
15021
|
participants: participants.map((p) => ({
|
|
@@ -15695,6 +15638,26 @@ function normalizeRemoteRepoUrl(value) {
|
|
|
15695
15638
|
if (/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+(?:\.git)?$/.test(value)) return `https://github.com/${value}`;
|
|
15696
15639
|
return value;
|
|
15697
15640
|
}
|
|
15641
|
+
/**
|
|
15642
|
+
* Whether this binding actually drives a GitHub-hosted remote fetch — the
|
|
15643
|
+
* only case where minting a GitHub App installation token is meaningful.
|
|
15644
|
+
*
|
|
15645
|
+
* Returns false when:
|
|
15646
|
+
* - `localPath` is set (sync code short-circuits to the local checkout
|
|
15647
|
+
* before ever looking at `repo`)
|
|
15648
|
+
* - `repo` is missing
|
|
15649
|
+
* - `repo` is a file:// URL, a non-GitHub HTTPS URL, or otherwise
|
|
15650
|
+
* unparseable
|
|
15651
|
+
*
|
|
15652
|
+
* Used by the snapshot routes to gate the "install the GitHub App"
|
|
15653
|
+
* guidance — without this gate, every unavailable snapshot (missing repo,
|
|
15654
|
+
* bad branch, …) gets a misleading App-install hint appended.
|
|
15655
|
+
*/
|
|
15656
|
+
function isGithubRemoteBinding(binding) {
|
|
15657
|
+
if (binding.localPath && binding.localPath.trim().length > 0) return false;
|
|
15658
|
+
if (!binding.repo) return false;
|
|
15659
|
+
return isGithubHttpsRepo(normalizeRemoteRepoUrl(binding.repo));
|
|
15660
|
+
}
|
|
15698
15661
|
function managedContextTreeCacheRoot() {
|
|
15699
15662
|
return join(DEFAULT_DATA_DIR$1, "context-tree-repos");
|
|
15700
15663
|
}
|
|
@@ -15868,7 +15831,7 @@ function errorMessage(error) {
|
|
|
15868
15831
|
return redactSecret(error.message.trim().split("\n")[0] ?? "");
|
|
15869
15832
|
}
|
|
15870
15833
|
function redactSecret(message) {
|
|
15871
|
-
return message.replace(/(https?:\/\/)[^/@\s]+@/g, "$1[redacted]@").replace(/\
|
|
15834
|
+
return message.replace(/(https?:\/\/)[^/@\s]+@/g, "$1[redacted]@").replace(/\b(?:ghp|ghs|ghu|gho|ghr)_[A-Za-z0-9_]+/g, "[redacted]").replace(/\bgithub_pat_[A-Za-z0-9_]+/g, "[redacted]");
|
|
15872
15835
|
}
|
|
15873
15836
|
function unavailableSnapshot(repo, branch, detail) {
|
|
15874
15837
|
return {
|
|
@@ -16448,6 +16411,80 @@ function ghostNodeId(path) {
|
|
|
16448
16411
|
function toPosix(path) {
|
|
16449
16412
|
return sep === "/" ? path : path.split(sep).join("/");
|
|
16450
16413
|
}
|
|
16414
|
+
/**
|
|
16415
|
+
* Mint a short-lived GitHub App installation token for the given installation.
|
|
16416
|
+
* Returns `ok: false` (with a precise reason) when the org has no App
|
|
16417
|
+
* configured, no installation row, the installation is suspended, or GitHub
|
|
16418
|
+
* rejects the mint — callers fall back to unauthenticated git fetch (public
|
|
16419
|
+
* repos still resolve; private repos surface as an unavailable snapshot
|
|
16420
|
+
* with a remediation message).
|
|
16421
|
+
*
|
|
16422
|
+
* Takes the `installation` row directly so the helper has no DB dependency
|
|
16423
|
+
* — route handlers do the `findInstallationByOrg` lookup themselves. Keeps
|
|
16424
|
+
* this module a pure transform that's trivial to unit-test.
|
|
16425
|
+
*
|
|
16426
|
+
* Credentials use the narrow `GithubAppCredentials` shape so the helper
|
|
16427
|
+
* isn't coupled to the broader OAuth config surface; callers pass
|
|
16428
|
+
* `config.oauth?.githubApp`, which structurally satisfies it.
|
|
16429
|
+
*/
|
|
16430
|
+
async function mintContextTreeInstallationToken(installation, appCredentials, options = {}) {
|
|
16431
|
+
if (!appCredentials) return {
|
|
16432
|
+
ok: false,
|
|
16433
|
+
reason: "no-app-config"
|
|
16434
|
+
};
|
|
16435
|
+
if (!installation) return {
|
|
16436
|
+
ok: false,
|
|
16437
|
+
reason: "no-installation"
|
|
16438
|
+
};
|
|
16439
|
+
if (installation.suspendedAt) return {
|
|
16440
|
+
ok: false,
|
|
16441
|
+
reason: "suspended"
|
|
16442
|
+
};
|
|
16443
|
+
try {
|
|
16444
|
+
return {
|
|
16445
|
+
ok: true,
|
|
16446
|
+
token: (await mintInstallationToken(await createAppJwt({
|
|
16447
|
+
appId: appCredentials.appId,
|
|
16448
|
+
privateKeyPem: appCredentials.privateKeyPem
|
|
16449
|
+
}), installation.installationId, { fetcher: options.fetcher })).token
|
|
16450
|
+
};
|
|
16451
|
+
} catch (error) {
|
|
16452
|
+
return {
|
|
16453
|
+
ok: false,
|
|
16454
|
+
reason: "mint-failed",
|
|
16455
|
+
detail: error instanceof GithubAppApiError ? `GitHub returned ${error.status} when minting an installation token.` : "Hub could not mint a GitHub App installation token."
|
|
16456
|
+
};
|
|
16457
|
+
}
|
|
16458
|
+
}
|
|
16459
|
+
/**
|
|
16460
|
+
* Append a remediation hint to an unavailable snapshot's `contextStatus.detail`
|
|
16461
|
+
* when the underlying cause is a missing / suspended / failed GitHub App token
|
|
16462
|
+
* mint. Public-repo snapshots (mint reason `no-app-config`) are left untouched
|
|
16463
|
+
* — the deployment may legitimately have no App configured.
|
|
16464
|
+
*
|
|
16465
|
+
* Gated on `isGithubRemoteBinding(binding)` so unrelated unavailable
|
|
16466
|
+
* reasons (no repo configured, localPath missing, illegal branch name,
|
|
16467
|
+
* public-repo fetch error) don't get a misleading "install the GitHub
|
|
16468
|
+
* App" hint appended.
|
|
16469
|
+
*
|
|
16470
|
+
* Lives next to `mintContextTreeInstallationToken` so the two routes that
|
|
16471
|
+
* call mint share one shaping function; the snapshot service itself stays
|
|
16472
|
+
* token-agnostic.
|
|
16473
|
+
*/
|
|
16474
|
+
function decorateSnapshotWithMintGuidance(snapshot, binding, mintResult) {
|
|
16475
|
+
if (mintResult.ok) return snapshot;
|
|
16476
|
+
if (snapshot.snapshotStatus !== "unavailable") return snapshot;
|
|
16477
|
+
if (mintResult.reason === "no-app-config") return snapshot;
|
|
16478
|
+
if (!isGithubRemoteBinding(binding)) return snapshot;
|
|
16479
|
+
const guidance = mintResult.reason === "no-installation" ? "Install the First Tree GitHub App from Team Settings and grant it access to this repo." : mintResult.reason === "suspended" ? "The GitHub App installation is suspended — unsuspend it from your GitHub account settings." : `Hub could not mint a GitHub App installation token.${mintResult.detail ? ` ${mintResult.detail}` : ""}`;
|
|
16480
|
+
return {
|
|
16481
|
+
...snapshot,
|
|
16482
|
+
contextStatus: {
|
|
16483
|
+
...snapshot.contextStatus,
|
|
16484
|
+
detail: `${snapshot.contextStatus.detail} ${guidance}`
|
|
16485
|
+
}
|
|
16486
|
+
};
|
|
16487
|
+
}
|
|
16451
16488
|
const querySchema$1 = z.object({ window: z.enum([
|
|
16452
16489
|
"1d",
|
|
16453
16490
|
"7d",
|
|
@@ -16463,12 +16500,15 @@ async function contextTreeSnapshotRoutes(app) {
|
|
|
16463
16500
|
const { userId } = requireUser(request);
|
|
16464
16501
|
const orgId = await resolveUserPrimaryOrgId(app.db, userId);
|
|
16465
16502
|
const binding = orgId ? await getOrgContextTree(app.db, orgId) : {};
|
|
16466
|
-
|
|
16503
|
+
let mintResult = null;
|
|
16504
|
+
if (orgId && isGithubRemoteBinding(binding)) mintResult = await mintContextTreeInstallationToken(await findInstallationByOrg(app.db, orgId), app.config.oauth?.githubApp);
|
|
16505
|
+
const githubToken = mintResult?.ok ? mintResult.token : void 0;
|
|
16467
16506
|
const window = query.window ?? "7d";
|
|
16468
|
-
const
|
|
16507
|
+
const rawSnapshot = await getContextTreeSnapshot({
|
|
16469
16508
|
...binding,
|
|
16470
16509
|
githubToken
|
|
16471
16510
|
}, window);
|
|
16511
|
+
const snapshot = mintResult ? decorateSnapshotWithMintGuidance(rawSnapshot, binding, mintResult) : rawSnapshot;
|
|
16472
16512
|
const usage = orgId ? await summarizeContextTreeUsage(app.db, orgId, contextTreeSnapshotWindowDays(window)) : snapshot.usage;
|
|
16473
16513
|
return contextTreeSnapshotSchema.parse({
|
|
16474
16514
|
...snapshot,
|
|
@@ -16476,31 +16516,6 @@ async function contextTreeSnapshotRoutes(app) {
|
|
|
16476
16516
|
});
|
|
16477
16517
|
});
|
|
16478
16518
|
}
|
|
16479
|
-
function contextTreeGithubTokenForRepo(repo, syncConfig) {
|
|
16480
|
-
if (!repo || !syncConfig?.githubToken) return void 0;
|
|
16481
|
-
const repoKey = githubRepoKey(repo);
|
|
16482
|
-
if (!repoKey) return void 0;
|
|
16483
|
-
return new Set((syncConfig.githubTokenRepos ?? "").split(",").map((entry) => normalizeGithubRepoKey(entry)).filter((entry) => entry !== null)).has(repoKey) ? syncConfig.githubToken : void 0;
|
|
16484
|
-
}
|
|
16485
|
-
function githubRepoKey(value) {
|
|
16486
|
-
const shorthand = normalizeGithubRepoKey(value);
|
|
16487
|
-
if (shorthand) return shorthand;
|
|
16488
|
-
let url;
|
|
16489
|
-
try {
|
|
16490
|
-
url = new URL(value);
|
|
16491
|
-
} catch {
|
|
16492
|
-
return null;
|
|
16493
|
-
}
|
|
16494
|
-
if (url.protocol !== "https:" || url.hostname.toLowerCase() !== "github.com") return null;
|
|
16495
|
-
if (url.username || url.password) return null;
|
|
16496
|
-
return normalizeGithubRepoKey(url.pathname.replace(/^\/+/, ""));
|
|
16497
|
-
}
|
|
16498
|
-
function normalizeGithubRepoKey(value) {
|
|
16499
|
-
const trimmed = value.trim().replace(/^\/+/, "").replace(/\.git$/i, "");
|
|
16500
|
-
const match = /^([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/.exec(trimmed);
|
|
16501
|
-
if (!match) return null;
|
|
16502
|
-
return `${match[1]?.toLowerCase()}/${match[2]?.toLowerCase()}`;
|
|
16503
|
-
}
|
|
16504
16519
|
/**
|
|
16505
16520
|
* Resolve the client IP for rate-limit attribution.
|
|
16506
16521
|
*
|
|
@@ -16602,7 +16617,7 @@ async function healthzRoutes(app) {
|
|
|
16602
16617
|
* `api/orgs/invitations.ts` (Class B, admin-gated).
|
|
16603
16618
|
*/
|
|
16604
16619
|
async function publicInvitationRoutes(app) {
|
|
16605
|
-
const { previewInvitation } = await import("./invitation-C9m2gQx4-
|
|
16620
|
+
const { previewInvitation } = await import("./invitation-C9m2gQx4-C_4f5VTs.mjs");
|
|
16606
16621
|
app.get("/:token/preview", async (request, reply) => {
|
|
16607
16622
|
if (!request.params.token) throw new UnauthorizedError("Token required");
|
|
16608
16623
|
const preview = await previewInvitation(app.db, request.params.token);
|
|
@@ -16881,7 +16896,13 @@ async function meRoutes(app) {
|
|
|
16881
16896
|
inboxId: r.inboxId,
|
|
16882
16897
|
visibility: r.visibility,
|
|
16883
16898
|
runtimeProvider: r.runtimeProvider,
|
|
16884
|
-
clientId: r.clientId
|
|
16899
|
+
clientId: r.clientId,
|
|
16900
|
+
avatarImageUrl: resolveAvatarImageUrl({
|
|
16901
|
+
uuid: r.uuid,
|
|
16902
|
+
type: r.type,
|
|
16903
|
+
avatarImageUpdatedAt: r.avatarImageUpdatedAt,
|
|
16904
|
+
userAvatarUrl: r.userAvatarUrl
|
|
16905
|
+
})
|
|
16885
16906
|
}));
|
|
16886
16907
|
});
|
|
16887
16908
|
/**
|
|
@@ -16891,7 +16912,7 @@ async function meRoutes(app) {
|
|
|
16891
16912
|
*/
|
|
16892
16913
|
app.get("/me/pinned-agents", async (request) => {
|
|
16893
16914
|
const { userId } = requireUser(request);
|
|
16894
|
-
const { listMyPinnedAgents } = await import("./client-
|
|
16915
|
+
const { listMyPinnedAgents } = await import("./client-CEdYVnoj-BGiGcJbH.mjs");
|
|
16895
16916
|
return listMyPinnedAgents(app.db, { userId });
|
|
16896
16917
|
});
|
|
16897
16918
|
/**
|
|
@@ -17275,7 +17296,7 @@ async function orgAgentRoutes(app) {
|
|
|
17275
17296
|
const { type } = listAgentsFilterSchema.parse(request.query);
|
|
17276
17297
|
const result = await listAgentsForMember(app.db, scope, query.limit, query.cursor, type);
|
|
17277
17298
|
return {
|
|
17278
|
-
items: result.items.map(({ avatarImageUpdatedAt, ...a }) => ({
|
|
17299
|
+
items: result.items.map(({ avatarImageUpdatedAt, userAvatarUrl, ...a }) => ({
|
|
17279
17300
|
...a,
|
|
17280
17301
|
managerId: a.managerId ?? null,
|
|
17281
17302
|
presenceStatus: a.presenceStatus ?? "offline",
|
|
@@ -17285,7 +17306,12 @@ async function orgAgentRoutes(app) {
|
|
|
17285
17306
|
runtimeType: a.runtimeType ?? null,
|
|
17286
17307
|
runtimeState: a.runtimeState ?? null,
|
|
17287
17308
|
activeSessions: a.activeSessions ?? null,
|
|
17288
|
-
avatarImageUrl:
|
|
17309
|
+
avatarImageUrl: resolveAvatarImageUrl({
|
|
17310
|
+
uuid: a.uuid,
|
|
17311
|
+
type: a.type,
|
|
17312
|
+
avatarImageUpdatedAt,
|
|
17313
|
+
userAvatarUrl
|
|
17314
|
+
})
|
|
17289
17315
|
})),
|
|
17290
17316
|
nextCursor: result.nextCursor
|
|
17291
17317
|
};
|
|
@@ -17301,7 +17327,7 @@ async function orgAgentRoutes(app) {
|
|
|
17301
17327
|
const query = paginationQuerySchema.parse(request.query);
|
|
17302
17328
|
const result = await listAgentsForAdmin(app.db, scope, query.limit, query.cursor);
|
|
17303
17329
|
return {
|
|
17304
|
-
items: result.items.map(({ avatarImageUpdatedAt, ...a }) => ({
|
|
17330
|
+
items: result.items.map(({ avatarImageUpdatedAt, userAvatarUrl, ...a }) => ({
|
|
17305
17331
|
...a,
|
|
17306
17332
|
managerId: a.managerId ?? null,
|
|
17307
17333
|
presenceStatus: a.presenceStatus ?? "offline",
|
|
@@ -17311,7 +17337,12 @@ async function orgAgentRoutes(app) {
|
|
|
17311
17337
|
runtimeType: a.runtimeType ?? null,
|
|
17312
17338
|
runtimeState: a.runtimeState ?? null,
|
|
17313
17339
|
activeSessions: a.activeSessions ?? null,
|
|
17314
|
-
avatarImageUrl:
|
|
17340
|
+
avatarImageUrl: resolveAvatarImageUrl({
|
|
17341
|
+
uuid: a.uuid,
|
|
17342
|
+
type: a.type,
|
|
17343
|
+
avatarImageUpdatedAt,
|
|
17344
|
+
userAvatarUrl
|
|
17345
|
+
})
|
|
17315
17346
|
})),
|
|
17316
17347
|
nextCursor: result.nextCursor
|
|
17317
17348
|
};
|
|
@@ -17467,12 +17498,15 @@ async function orgContextTreeSnapshotRoutes(app) {
|
|
|
17467
17498
|
const query = querySchema.parse(request.query);
|
|
17468
17499
|
const scope = await requireOrgMembership(request, app.db);
|
|
17469
17500
|
const binding = await getOrgContextTree(app.db, scope.organizationId);
|
|
17470
|
-
|
|
17501
|
+
let mintResult = null;
|
|
17502
|
+
if (isGithubRemoteBinding(binding)) mintResult = await mintContextTreeInstallationToken(await findInstallationByOrg(app.db, scope.organizationId), app.config.oauth?.githubApp);
|
|
17503
|
+
const githubToken = mintResult?.ok ? mintResult.token : void 0;
|
|
17471
17504
|
const window = query.window ?? "7d";
|
|
17472
|
-
const
|
|
17505
|
+
const rawSnapshot = await getContextTreeSnapshot({
|
|
17473
17506
|
...binding,
|
|
17474
17507
|
githubToken
|
|
17475
17508
|
}, window);
|
|
17509
|
+
const snapshot = mintResult ? decorateSnapshotWithMintGuidance(rawSnapshot, binding, mintResult) : rawSnapshot;
|
|
17476
17510
|
const usage = await summarizeContextTreeUsage(app.db, scope.organizationId, contextTreeSnapshotWindowDays(window));
|
|
17477
17511
|
return contextTreeSnapshotSchema.parse({
|
|
17478
17512
|
...snapshot,
|
|
@@ -21226,7 +21260,7 @@ function detectInstallMode(argv1 = process.argv[1] ?? "") {
|
|
|
21226
21260
|
resolvedArgv1 = argv1;
|
|
21227
21261
|
}
|
|
21228
21262
|
const start = dirname(resolve(resolvedArgv1));
|
|
21229
|
-
{
|
|
21263
|
+
if (!/(?:^|[\\/])node_modules[\\/]/.test(resolvedArgv1)) {
|
|
21230
21264
|
let dir = start;
|
|
21231
21265
|
for (let i = 0; i < 10; i++) {
|
|
21232
21266
|
if (existsSync(resolve(dir, ".git"))) return "source";
|