@agent-team-foundation/first-tree-hub 0.14.2 → 0.14.3
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-BSfCc0pJ-BP_1f21y.mjs} +14 -39
- package/dist/{client-CDw0f-kN-BPzOVd8L.mjs → client-q1EYQD1n-ypjoumIO.mjs} +2 -2
- package/dist/{dist-DmYxT5Kb.mjs → dist-CwsiHGX7.mjs} +18 -13
- package/dist/{feishu-CCWd-JE4.mjs → feishu-DHSy6WDD.mjs} +1 -1
- package/dist/index.mjs +5 -5
- package/dist/{invitation-C9m2gQx4-CkwWteA3.mjs → invitation-C9m2gQx4-BSErdb8x.mjs} +1 -1
- package/dist/{saas-connect-DgCSZ8Yk.mjs → saas-connect-ChxZv2YQ.mjs} +220 -214
- package/package.json +1 -1
|
@@ -570,13 +570,6 @@ const serverConfigSchema = defineConfig({
|
|
|
570
570
|
refreshTokenExpiry: field(z.string().default("30d"), { env: "FIRST_TREE_HUB_AUTH_REFRESH_TOKEN_EXPIRY" }),
|
|
571
571
|
connectTokenExpiry: field(z.string().default("10m"), { env: "FIRST_TREE_HUB_AUTH_CONNECT_TOKEN_EXPIRY" })
|
|
572
572
|
},
|
|
573
|
-
contextTreeSync: optional({
|
|
574
|
-
githubToken: field(z.string(), {
|
|
575
|
-
env: "FIRST_TREE_HUB_CONTEXT_TREE_GITHUB_TOKEN",
|
|
576
|
-
secret: true
|
|
577
|
-
}),
|
|
578
|
-
githubTokenRepos: field(z.string().optional(), { env: "FIRST_TREE_HUB_CONTEXT_TREE_GITHUB_TOKEN_REPOS" })
|
|
579
|
-
}),
|
|
580
573
|
oauth: optional({ githubApp: optional({
|
|
581
574
|
appId: field(z.string().min(1), { env: "FIRST_TREE_HUB_GITHUB_APP_ID" }),
|
|
582
575
|
clientId: field(z.string().min(1), { env: "FIRST_TREE_HUB_GITHUB_APP_CLIENT_ID" }),
|
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-ChxZv2YQ.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, 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, v as initConfig, w as resolveConfigReadonly, x as readConfigFile, y as loadAgents } from "../bootstrap-CQQGgIx1.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-CwsiHGX7.mjs";
|
|
8
|
+
import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-DHSy6WDD.mjs";
|
|
9
9
|
import "../uuid-DbS_4vFh-iFghv4zA.mjs";
|
|
10
10
|
import "../src-DNBS5Yjj.mjs";
|
|
11
|
-
import "../client-
|
|
11
|
+
import "../client-BSfCc0pJ-BP_1f21y.mjs";
|
|
12
12
|
import "../invitation-D_ENPHyj-5ETiae5r.mjs";
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
import { existsSync, mkdirSync, readFileSync, readdirSync } from "node:fs";
|
|
@@ -255,11 +255,6 @@ function handleSdkError$1(error) {
|
|
|
255
255
|
if (error instanceof TypeError && "cause" in error) fail("CONNECTION_ERROR", `Cannot connect to server: ${error.message}`, 6);
|
|
256
256
|
fail("UNKNOWN_ERROR", error instanceof Error ? error.message : String(error), 1);
|
|
257
257
|
}
|
|
258
|
-
function parseLimit$1(value, max) {
|
|
259
|
-
const limit = Number.parseInt(value, 10);
|
|
260
|
-
if (Number.isNaN(limit) || limit < 1 || limit > max) fail("INVALID_LIMIT", `Limit must be between 1 and ${max}.`, 2);
|
|
261
|
-
return limit;
|
|
262
|
-
}
|
|
263
258
|
async function resolveAgent$1(serverUrl, adminToken, agentName) {
|
|
264
259
|
const res = await cliFetch(`${serverUrl}/api/v1/me/managed-agents`, {
|
|
265
260
|
headers: { Authorization: `Bearer ${adminToken}` },
|
|
@@ -693,25 +688,13 @@ function registerAgentCommands(program) {
|
|
|
693
688
|
fail("SESSION_CMD_ERROR", error instanceof Error ? error.message : String(error));
|
|
694
689
|
}
|
|
695
690
|
});
|
|
696
|
-
|
|
697
|
-
debugCmd.command("register").description("Register this agent and return identity info").option("--agent <name>", "Agent name on the Hub (default: first configured on this client)").action(async (options) => {
|
|
691
|
+
agent.command("debug", { hidden: true }).description("Low-level SDK debug commands").command("register").description("Register this agent and return identity info").option("--agent <name>", "Agent name on the Hub (default: first configured on this client)").action(async (options) => {
|
|
698
692
|
try {
|
|
699
693
|
success(await createSdk$1(options.agent).register());
|
|
700
694
|
} catch (error) {
|
|
701
695
|
handleSdkError$1(error);
|
|
702
696
|
}
|
|
703
697
|
});
|
|
704
|
-
debugCmd.command("pull").description("Pull pending messages from inbox").option("-l, --limit <number>", "Maximum entries to return", "10").option("-a, --ack", "Automatically ACK entries after pulling").option("--agent <name>", "Agent name on the Hub (default: first configured on this client)").action(async (options) => {
|
|
705
|
-
try {
|
|
706
|
-
const sdk = createSdk$1(options.agent);
|
|
707
|
-
const limit = parseLimit$1(options.limit, 50);
|
|
708
|
-
const result = await sdk.pull(limit);
|
|
709
|
-
if (options.ack && result.entries.length > 0) await Promise.all(result.entries.map((entry) => sdk.ack(entry.id)));
|
|
710
|
-
success(result);
|
|
711
|
-
} catch (error) {
|
|
712
|
-
handleSdkError$1(error);
|
|
713
|
-
}
|
|
714
|
-
});
|
|
715
698
|
}
|
|
716
699
|
//#endregion
|
|
717
700
|
//#region src/commands/chat.ts
|
|
@@ -1487,13 +1470,13 @@ function decodeJwtExpSeconds(token) {
|
|
|
1487
1470
|
//#region src/commands/onboard.ts
|
|
1488
1471
|
async function promptMissing(args) {
|
|
1489
1472
|
if (!args.server) try {
|
|
1490
|
-
const { resolveServerUrl } = await import("../bootstrap-
|
|
1473
|
+
const { resolveServerUrl } = await import("../bootstrap-CQQGgIx1.mjs").then((n) => n.r);
|
|
1491
1474
|
resolveServerUrl();
|
|
1492
1475
|
} catch {
|
|
1493
1476
|
args.server = await input({ message: "Hub server URL:" });
|
|
1494
1477
|
saveOnboardState(args);
|
|
1495
1478
|
}
|
|
1496
|
-
const { loadCredentials } = await import("../bootstrap-
|
|
1479
|
+
const { loadCredentials } = await import("../bootstrap-CQQGgIx1.mjs").then((n) => n.r);
|
|
1497
1480
|
if (!loadCredentials()) throw new Error("No saved credentials. Run `first-tree-hub connect <token>` before onboarding.");
|
|
1498
1481
|
if (!args.id) {
|
|
1499
1482
|
args.id = await input({ message: "Agent ID:" });
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { A as FIRST_TREE_HUB_ATTR, O as withSpan, f as messageAttrs, s as createLogger } from "./observability-BAScT_5S-BcW9HgkG.mjs";
|
|
2
|
-
import { L as extractMentions, N as defaultParticipantMode, P as defaultRuntimeConfigPayload, S as clientCapabilitiesSchema, St as stripCode, Z as isReservedAgentName, a as AGENT_TYPES, b as agentTypeSchema, ct as questionAnswerMessageContentSchema, d as MENTION_REGEX, i as AGENT_STATUSES, l as GITHUB_ENTITY_TYPES, lt as questionMessageContentSchema, mt as scanMentionTokens, n as AGENT_NAME_REGEX, nt as messageSourceSchema, o as AGENT_VISIBILITY, s as CHAT_ENGAGEMENT_STATUSES } from "./dist-
|
|
2
|
+
import { L as extractMentions, N as defaultParticipantMode, P as defaultRuntimeConfigPayload, S as clientCapabilitiesSchema, St as stripCode, Z as isReservedAgentName, a as AGENT_TYPES, b as agentTypeSchema, ct as questionAnswerMessageContentSchema, d as MENTION_REGEX, i as AGENT_STATUSES, l as GITHUB_ENTITY_TYPES, lt as questionMessageContentSchema, mt as scanMentionTokens, n as AGENT_NAME_REGEX, nt as messageSourceSchema, o as AGENT_VISIBILITY, s as CHAT_ENGAGEMENT_STATUSES } from "./dist-CwsiHGX7.mjs";
|
|
3
3
|
import { a as ClientUserMismatchError, c as NotFoundError, d as users, f as uuidv7, o as ConflictError, r as BadRequestError, s as ForbiddenError, t as AgentSendNonMemberError, u as organizations } from "./uuid-DbS_4vFh-iFghv4zA.mjs";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import { and, asc, count, desc, eq, gt, gte, inArray, isNotNull, lt, ne, or, sql } from "drizzle-orm";
|
|
6
6
|
import { bigserial, boolean, customType, index, integer, jsonb, pgTable, primaryKey, serial, text, timestamp, unique } from "drizzle-orm/pg-core";
|
|
7
|
-
//#region ../server/dist/client-
|
|
7
|
+
//#region ../server/dist/client-BSfCc0pJ.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
|
|
@@ -401,7 +401,7 @@ async function pollInboxInner(db, inboxId, limit) {
|
|
|
401
401
|
/**
|
|
402
402
|
* Shared payload assembler for already-claimed `inbox_entries` rows.
|
|
403
403
|
*
|
|
404
|
-
* Both the
|
|
404
|
+
* Both the debug `GET /inbox` path (`pollInbox`) and the WS push path
|
|
405
405
|
* (`claimAndBuildForPush`) call this with rows they have just `UPDATE`d to
|
|
406
406
|
* `status='delivered'`. Keeping the silent-context bundling in one place is
|
|
407
407
|
* the only way to keep the two paths from drifting (proposal
|
|
@@ -473,9 +473,9 @@ const PUSH_CLAIM_BATCH_LIMIT = 8;
|
|
|
473
473
|
* WS-push path: atomically claim every pending entry the just-fired
|
|
474
474
|
* `NOTIFY (inboxId:messageId)` references and assemble their wire payloads.
|
|
475
475
|
*
|
|
476
|
-
* Returns `[]` if no row matches — benign race with
|
|
477
|
-
*
|
|
478
|
-
* (proposal §3.2).
|
|
476
|
+
* Returns `[]` if no row matches — benign race with another server instance
|
|
477
|
+
* (or the debug `GET /inbox` endpoint) that already claimed the entry.
|
|
478
|
+
* NOTIFY is fire-and-forget (proposal §3.2).
|
|
479
479
|
*
|
|
480
480
|
* Why an array, not a single row: `sendMessage` can write **two** rows for
|
|
481
481
|
* the same `(inbox, messageId)` pair when the recipient is both a chat
|
|
@@ -500,9 +500,9 @@ async function claimAndBuildForPush(db, inboxId, messageId) {
|
|
|
500
500
|
/**
|
|
501
501
|
* WS-push backlog path: on agent rebind (or once an in-flight slot frees up
|
|
502
502
|
* after an ack), drain up to `limit` pending `notify=true` entries oldest-
|
|
503
|
-
* first and assemble wire payloads. Identical claim shape to
|
|
504
|
-
*
|
|
505
|
-
*
|
|
503
|
+
* first and assemble wire payloads. Identical claim shape to `pollInbox` —
|
|
504
|
+
* they are intentionally interchangeable so a hot-path bug fixed in one
|
|
505
|
+
* shows up in the other (proposal §3.3 / §3.5).
|
|
506
506
|
*/
|
|
507
507
|
async function claimBacklogForPush(db, inboxId, limit) {
|
|
508
508
|
return withSpan("inbox.deliver.backlog", {
|
|
@@ -563,28 +563,14 @@ async function collectPrecedingContext(tx, inboxId, triggers) {
|
|
|
563
563
|
}
|
|
564
564
|
return result;
|
|
565
565
|
}
|
|
566
|
-
async function ackEntry(db, entryId, inboxId) {
|
|
567
|
-
return withSpan("inbox.ack", {
|
|
568
|
-
[FIRST_TREE_HUB_ATTR.INBOX_ENTRY_ID]: String(entryId),
|
|
569
|
-
"inbox.id": inboxId
|
|
570
|
-
}, async () => {
|
|
571
|
-
const [entry] = await db.update(inboxEntries).set({
|
|
572
|
-
status: "acked",
|
|
573
|
-
ackedAt: /* @__PURE__ */ new Date()
|
|
574
|
-
}).where(and(eq(inboxEntries.id, entryId), eq(inboxEntries.inboxId, inboxId), eq(inboxEntries.status, "delivered"))).returning();
|
|
575
|
-
if (!entry) throw new NotFoundError("Inbox entry not found or not in delivered status");
|
|
576
|
-
return entry;
|
|
577
|
-
});
|
|
578
|
-
}
|
|
579
566
|
/**
|
|
580
567
|
* Ack a delivered entry from the WS data plane, scoped to the inboxes the
|
|
581
568
|
* connected socket has bound. Returns the acked row on success, `null` if no
|
|
582
569
|
* row matches — a benign outcome the caller should ignore (the entry may
|
|
583
570
|
* have already been acked, timed out, or never belonged to this socket).
|
|
584
571
|
*
|
|
585
|
-
*
|
|
586
|
-
*
|
|
587
|
-
* are eligible. Empty `inboxIds` short-circuits to `null`.
|
|
572
|
+
* Trusts only the `inboxId` set the connected socket has bound (no `inboxId`
|
|
573
|
+
* on the wire), and short-circuits on an empty `inboxIds`.
|
|
588
574
|
*/
|
|
589
575
|
async function ackEntryByIdForBoundAgents(db, entryId, inboxIds) {
|
|
590
576
|
if (inboxIds.length === 0) return null;
|
|
@@ -596,11 +582,6 @@ async function ackEntryByIdForBoundAgents(db, entryId, inboxIds) {
|
|
|
596
582
|
return entry ?? null;
|
|
597
583
|
});
|
|
598
584
|
}
|
|
599
|
-
async function renewEntry(db, entryId, inboxId) {
|
|
600
|
-
const [entry] = await db.update(inboxEntries).set({ deliveredAt: /* @__PURE__ */ new Date() }).where(and(eq(inboxEntries.id, entryId), eq(inboxEntries.inboxId, inboxId), eq(inboxEntries.status, "delivered"))).returning();
|
|
601
|
-
if (!entry) throw new NotFoundError("Inbox entry not found or not in delivered status");
|
|
602
|
-
return entry;
|
|
603
|
-
}
|
|
604
585
|
async function resetTimedOutEntries(db, timeoutSeconds = DEFAULT_INBOX_TIMEOUT_SECONDS, maxRetries = DEFAULT_MAX_RETRY_COUNT) {
|
|
605
586
|
const reset = await db.update(inboxEntries).set({
|
|
606
587
|
status: "pending",
|
|
@@ -2298,15 +2279,9 @@ function createNotifier(listenClient) {
|
|
|
2298
2279
|
const messageId = payload.slice(sepIdx + 1);
|
|
2299
2280
|
const sockets = subscriptions.get(inboxId);
|
|
2300
2281
|
if (!sockets) return;
|
|
2301
|
-
const doorbellFrame = JSON.stringify({
|
|
2302
|
-
type: "new_message",
|
|
2303
|
-
inboxId,
|
|
2304
|
-
messageId
|
|
2305
|
-
});
|
|
2306
2282
|
for (const [ws, pushHandler] of sockets) {
|
|
2307
2283
|
if (ws.readyState !== ws.OPEN) continue;
|
|
2308
|
-
|
|
2309
|
-
else ws.send(doorbellFrame);
|
|
2284
|
+
Promise.resolve(pushHandler(messageId)).catch(() => {});
|
|
2310
2285
|
}
|
|
2311
2286
|
}
|
|
2312
2287
|
return {
|
|
@@ -2316,7 +2291,7 @@ function createNotifier(listenClient) {
|
|
|
2316
2291
|
map = /* @__PURE__ */ new Map();
|
|
2317
2292
|
subscriptions.set(inboxId, map);
|
|
2318
2293
|
}
|
|
2319
|
-
map.set(ws, pushHandler
|
|
2294
|
+
map.set(ws, pushHandler);
|
|
2320
2295
|
},
|
|
2321
2296
|
unsubscribe(inboxId, ws) {
|
|
2322
2297
|
const map = subscriptions.get(inboxId);
|
|
@@ -4210,4 +4185,4 @@ async function cleanupStaleClients(db, staleSeconds = 60) {
|
|
|
4210
4185
|
return result.length;
|
|
4211
4186
|
}
|
|
4212
4187
|
//#endregion
|
|
4213
|
-
export {
|
|
4188
|
+
export { heartbeatClient as $, unbindAgent as $t, createChat as A, pruneStaleSilentEntries as At, filterSessionsByParticipant as B, resolveDefaultOrgId as Bt, claimBacklogForPush as C, markMeChatUnread as Ct, clearAgentAvatarImage as D, notifyRecipients as Dt, cleanupStalePresence as E, messages as Et, disconnectClient as F, registerClient as Ft, getCachedAudience as G, setAgentAvatarImage as Gt, getActivityOverview as H, sendMessage as Ht, editMessage as I, removeParticipant as It, getClient as J, setRuntimeState as Jt, getCallerEngagement as K, setChatEngagement as Kt, ensureDefaultOrganization as L, resetActivity as Lt, createNotifier as M, rebindAgent as Mt, deleteAgent as N, recomputeWatchersForMember as Nt, clients as O, pendingQuestions as Ot, deriveAuthState as P, registerChatMessageDispatcher as Pt, getSession as Q, touchAgent as Qt, ensureParticipant as R, resetTimedOutEntries as Rt, claimAndBuildForPush as S, markMeChatRead as St, cleanupStaleClients as T, members as Tt, getAgent as U, sendToAgent as Ut, findOrCreateDirectChat as V, retireClient as Vt, getAgentAvatarImage as W, serverInstances as Wt, getOrganization as X, suspendAgent as Xt, getOnlineCount as Y, submitAnswer as Yt, getPresence as Z, suspendSession as Zt, bindAgent as _, listClientsForOrgAdmin as _t, adapterConfigs as a, leaveMeChat as at, chats as b, listMessages as bt, addParticipant as c, listAgentsForAdmin as ct, agentConfigs as d, listAgentsWithRuntime as dt, updateAgent as en, heartbeatInstance as et, agentPresence as f, listAllSessions as ft, assertParticipant as g, listClients as gt, assertClientOwner as h, listChatsForMember as ht, adapterAgentMappings as i, leaveChat as it, createMeChat as j, reactivateAgent as jt, createAgent as k, pollInbox as kt, agentAvatarImageUrl as l, listAgentsForMember as lt, archiveSession as m, listChats as mt, SUPPORTED_AVATAR_IMAGE_MIMES as n, updateOrganization as nn, joinChat as nt, addChatParticipants as o, listActiveAgentsPinnedToClient as ot, agents as p, listChatParticipantsWithNames as pt, getChatDetail as q, setOffline as qt, ackEntryByIdForBoundAgents as r, upsertSessionState as rn, joinMeChat as rt, addMeChatParticipants as s, listAgentSessions as st, MAX_AVATAR_IMAGE_BYTES as t, updateClientCapabilities as tn, inboxEntries as tt, agentChatSessions as u, listAgentsManagedByUser as ut, chatMembership as v, listMeChatSourceCounts as vt, claimClient as w, markStaleAgents as wt, checkAgentNameAvailability as x, listMyPinnedAgents as xt, chatUserState as y, listMeChats as yt, extractSummary as z, resolveChatTitle as zt };
|
|
@@ -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-CwsiHGX7.mjs";
|
|
4
4
|
import "./uuid-DbS_4vFh-iFghv4zA.mjs";
|
|
5
5
|
import "./src-DNBS5Yjj.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { xt as listMyPinnedAgents } from "./client-BSfCc0pJ-BP_1f21y.mjs";
|
|
7
7
|
export { listMyPinnedAgents };
|
|
@@ -803,9 +803,13 @@ z.object({
|
|
|
803
803
|
* Optional opt-in flags the client carries on `client:register` to advertise
|
|
804
804
|
* which negotiable wire-protocol features it implements. Distinct from
|
|
805
805
|
* `clientCapabilitiesSchema` (per-runtime-provider availability — different
|
|
806
|
-
* concept).
|
|
807
|
-
*
|
|
808
|
-
*
|
|
806
|
+
* concept).
|
|
807
|
+
*
|
|
808
|
+
* 0.10.4 ~ 0.14.2 clients still send this block (with `wsInboxDeliver: true`
|
|
809
|
+
* hard-coded). The 0.14.3+ runtime omits it. The schema is retained so that
|
|
810
|
+
* middle-version `client:register` frames still parse, even though the
|
|
811
|
+
* server no longer reads any of these fields — the WS inbox data plane is
|
|
812
|
+
* mandatory on this server build.
|
|
809
813
|
*/
|
|
810
814
|
const clientWireCapabilitiesSchema = z.object({ wsInboxDeliver: z.boolean().default(false) }).partial();
|
|
811
815
|
const clientRegisterSchema = z.object({
|
|
@@ -1196,14 +1200,11 @@ z.object({
|
|
|
1196
1200
|
}).extend({ message: clientMessageSchema });
|
|
1197
1201
|
const inboxPollQuerySchema = z.object({ limit: z.coerce.number().int().min(1).max(50).default(10) });
|
|
1198
1202
|
/**
|
|
1199
|
-
* server → client: a single inbox entry pushed over the active WS connection
|
|
1200
|
-
* replacing the legacy `new_message` doorbell + HTTP `/inbox` poll round-trip.
|
|
1203
|
+
* server → client: a single inbox entry pushed over the active WS connection.
|
|
1201
1204
|
*
|
|
1202
1205
|
* `entryId` is the server-side `inbox_entries.id` the client must echo back
|
|
1203
|
-
* in `inbox:ack`. `
|
|
1204
|
-
*
|
|
1205
|
-
* side dispatch logic is reused verbatim (see proposal
|
|
1206
|
-
* hub-inbox-ws-data-plane §3.1).
|
|
1206
|
+
* in `inbox:ack`. `clientMessageSchema` carries `precedingMessages`, so the
|
|
1207
|
+
* client-side dispatch logic handles the silent-context bundle uniformly.
|
|
1207
1208
|
*
|
|
1208
1209
|
* `.passthrough()` so a forward-rolling server may extend the frame without
|
|
1209
1210
|
* breaking older clients that validate strictly. Older clients drop unknown
|
|
@@ -1217,8 +1218,7 @@ const inboxDeliverFrameSchema = z.object({
|
|
|
1217
1218
|
message: clientMessageSchema
|
|
1218
1219
|
}).passthrough();
|
|
1219
1220
|
/**
|
|
1220
|
-
* client → server: ack for an `inbox:deliver` frame.
|
|
1221
|
-
* `POST /inbox/:id/ack` HTTP endpoint when the WS data plane is active.
|
|
1221
|
+
* client → server: ack for an `inbox:deliver` frame.
|
|
1222
1222
|
*/
|
|
1223
1223
|
const inboxAckFrameSchema = z.object({
|
|
1224
1224
|
type: z.literal("inbox:ack"),
|
|
@@ -1994,8 +1994,13 @@ const WS_AUTH_FRAME_TIMEOUT_MS = 5e3;
|
|
|
1994
1994
|
/**
|
|
1995
1995
|
* Negotiable wire-protocol features the server advertises in its `welcome`
|
|
1996
1996
|
* frame. Older clients drop the `capabilities` field silently because the
|
|
1997
|
-
* frame is `.passthrough()`.
|
|
1998
|
-
*
|
|
1997
|
+
* frame is `.passthrough()`.
|
|
1998
|
+
*
|
|
1999
|
+
* Required by clients in the 0.10.4 ~ 0.14.2 range: those builds read
|
|
2000
|
+
* `wsInboxDeliver` here to decide whether to skip the local HTTP poll loop
|
|
2001
|
+
* and rely on `inbox:deliver` push frames. The 0.14.3+ runtime ignores the
|
|
2002
|
+
* field (push is the only path) but the server still emits it so middle-
|
|
2003
|
+
* version clients keep working.
|
|
1999
2004
|
*/
|
|
2000
2005
|
const serverCapabilitiesSchema = z.object({ wsInboxDeliver: z.boolean().default(false) }).partial();
|
|
2001
2006
|
z.object({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { r as __exportAll } from "./chunk-BSw8zbkd.mjs";
|
|
2
2
|
import { t as cliFetch } from "./cli-fetch--tiwKm5S.mjs";
|
|
3
|
-
import { r as AGENT_SELECTOR_HEADER } from "./dist-
|
|
3
|
+
import { r as AGENT_SELECTOR_HEADER } from "./dist-CwsiHGX7.mjs";
|
|
4
4
|
//#region src/core/feishu.ts
|
|
5
5
|
var feishu_exports = /* @__PURE__ */ __exportAll({
|
|
6
6
|
bindFeishuBot: () => bindFeishuBot,
|
package/dist/index.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import "./observability-BAScT_5S-BcW9HgkG.mjs";
|
|
2
|
-
import { A as checkDocker, B as isServiceSupported, E as checkAgentConfigs, F as checkWebSocket, G as uninstallClientService, H as restartClientService, I as printResults, J as stopPostgres, K as ensurePostgres, M as checkServerConfig, N as checkServerHealth, O as checkClientConfig, P as checkServerReachable, R as getClientServiceStatus, S as runHomeMigration, T as runMigrations, U as startClientService, V as resolveCliInvocation, W as stopClientService, X as handleClientOrgMismatch, Y as ClientRuntime, Z as rotateClientIdWithBackup, _ as formatCheckReport, b as onboardCreate, d as startServer, g as promptMissingFields, h as promptAddAgent, j as checkNodeVersion, k as checkDatabase, lt as FirstTreeHubSDK, m as isInteractive, n as deriveHubUrlFromToken, nt as hasUser, q as isDockerAvailable, t as HubUrlDerivationError, tt as createOwner, ut as SdkError, y as onboardCheck, z as installClientService } from "./saas-connect-
|
|
2
|
+
import { A as checkDocker, B as isServiceSupported, E as checkAgentConfigs, F as checkWebSocket, G as uninstallClientService, H as restartClientService, I as printResults, J as stopPostgres, K as ensurePostgres, M as checkServerConfig, N as checkServerHealth, O as checkClientConfig, P as checkServerReachable, R as getClientServiceStatus, S as runHomeMigration, T as runMigrations, U as startClientService, V as resolveCliInvocation, W as stopClientService, X as handleClientOrgMismatch, Y as ClientRuntime, Z as rotateClientIdWithBackup, _ as formatCheckReport, b as onboardCreate, d as startServer, g as promptMissingFields, h as promptAddAgent, j as checkNodeVersion, k as checkDatabase, lt as FirstTreeHubSDK, m as isInteractive, n as deriveHubUrlFromToken, nt as hasUser, q as isDockerAvailable, t as HubUrlDerivationError, tt as createOwner, ut as SdkError, y as onboardCheck, z as installClientService } from "./saas-connect-ChxZv2YQ.mjs";
|
|
3
3
|
import "./logger-core-BTmvdflj-DjW8FM4T.mjs";
|
|
4
|
-
import { a as ensureFreshAdminToken, c as resolveServerUrl, i as ensureFreshAccessToken, n as AuthRefreshRateLimitedError, s as resolveAccessToken, t as AuthRefreshFailedError } from "./bootstrap-
|
|
4
|
+
import { a as ensureFreshAdminToken, c as resolveServerUrl, i as ensureFreshAccessToken, n as AuthRefreshRateLimitedError, s as resolveAccessToken, t as AuthRefreshFailedError } from "./bootstrap-CQQGgIx1.mjs";
|
|
5
5
|
import { i as blank, s as status } from "./cli-fetch--tiwKm5S.mjs";
|
|
6
|
-
import "./dist-
|
|
7
|
-
import { n as bindFeishuUser, t as bindFeishuBot } from "./feishu-
|
|
6
|
+
import "./dist-CwsiHGX7.mjs";
|
|
7
|
+
import { n as bindFeishuUser, t as bindFeishuBot } from "./feishu-DHSy6WDD.mjs";
|
|
8
8
|
import "./uuid-DbS_4vFh-iFghv4zA.mjs";
|
|
9
9
|
import "./src-DNBS5Yjj.mjs";
|
|
10
|
-
import "./client-
|
|
10
|
+
import "./client-BSfCc0pJ-BP_1f21y.mjs";
|
|
11
11
|
import "./invitation-D_ENPHyj-5ETiae5r.mjs";
|
|
12
12
|
export { AuthRefreshFailedError, AuthRefreshRateLimitedError, ClientRuntime, FirstTreeHubSDK, HubUrlDerivationError, SdkError, bindFeishuBot, bindFeishuUser, blank, checkAgentConfigs, checkClientConfig, checkDatabase, checkDocker, checkNodeVersion, checkServerConfig, checkServerHealth, checkServerReachable, checkWebSocket, createOwner, deriveHubUrlFromToken, ensureFreshAccessToken, ensureFreshAdminToken, ensurePostgres, formatCheckReport, getClientServiceStatus, handleClientOrgMismatch, hasUser, installClientService, isDockerAvailable, isInteractive, isServiceSupported, onboardCheck, onboardCreate, printResults, promptAddAgent, promptMissingFields, resolveAccessToken, resolveCliInvocation, resolveServerUrl, restartClientService, rotateClientIdWithBackup, runHomeMigration, runMigrations, startClientService, startServer, status, stopClientService, stopPostgres, uninstallClientService };
|
|
@@ -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-CwsiHGX7.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
|
|
8
|
+
import { $ as heartbeatClient, $t as unbindAgent, A as createChat, At as pruneStaleSilentEntries, B as filterSessionsByParticipant, Bt as resolveDefaultOrgId$1, C as claimBacklogForPush, Ct as markMeChatUnread, D as clearAgentAvatarImage, Dt as notifyRecipients, E as cleanupStalePresence, Et as messages, F as disconnectClient, Ft as registerClient, G as getCachedAudience, Gt as setAgentAvatarImage, H as getActivityOverview, Ht as sendMessage, I as editMessage, It as removeParticipant, J as getClient, Jt as setRuntimeState, K as getCallerEngagement, Kt as setChatEngagement, L as ensureDefaultOrganization, Lt as resetActivity, M as createNotifier, Mt as rebindAgent, N as deleteAgent, Nt as recomputeWatchersForMember, O as clients, Ot as pendingQuestions, P as deriveAuthState, Pt as registerChatMessageDispatcher, Q as getSession, Qt as touchAgent, R as ensureParticipant, Rt as resetTimedOutEntries, S as claimAndBuildForPush, St as markMeChatRead, T as cleanupStaleClients, Tt as members, U as getAgent, Ut as sendToAgent$1, V as findOrCreateDirectChat, Vt as retireClient, W as getAgentAvatarImage, Wt as serverInstances, X as getOrganization, Xt as suspendAgent, Y as getOnlineCount, Yt as submitAnswer, Z as getPresence, Zt as suspendSession, _ as bindAgent, _t as listClientsForOrgAdmin, a as adapterConfigs, at as leaveMeChat, b as chats, bt as listMessages, c as addParticipant, ct as listAgentsForAdmin, d as agentConfigs, dt as listAgentsWithRuntime, en as updateAgent, et as heartbeatInstance, f as agentPresence, ft as listAllSessions, g as assertParticipant, gt as listClients, h as assertClientOwner, ht as listChatsForMember, i as adapterAgentMappings, it as leaveChat, j as createMeChat, jt as reactivateAgent, k as createAgent, kt as pollInbox, l as agentAvatarImageUrl, lt as listAgentsForMember, m as archiveSession, mt as listChats, n as SUPPORTED_AVATAR_IMAGE_MIMES, nn as updateOrganization, nt as joinChat, o as addChatParticipants, ot as listActiveAgentsPinnedToClient, p as agents, pt as listChatParticipantsWithNames, q as getChatDetail, qt as setOffline, r as ackEntryByIdForBoundAgents, rn as upsertSessionState, rt as joinMeChat, s as addMeChatParticipants, st as listAgentSessions, t as MAX_AVATAR_IMAGE_BYTES, tn as updateClientCapabilities, tt as inboxEntries, u as agentChatSessions, ut as listAgentsManagedByUser, v as chatMembership, vt as listMeChatSourceCounts, w as claimClient, wt as markStaleAgents, x as checkAgentNameAvailability, y as chatUserState, yt as listMeChats, z as extractSummary, zt as resolveChatTitle } from "./client-BSfCc0pJ-BP_1f21y.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";
|
|
@@ -887,9 +887,13 @@ z.object({
|
|
|
887
887
|
* Optional opt-in flags the client carries on `client:register` to advertise
|
|
888
888
|
* which negotiable wire-protocol features it implements. Distinct from
|
|
889
889
|
* `clientCapabilitiesSchema` (per-runtime-provider availability — different
|
|
890
|
-
* concept).
|
|
891
|
-
*
|
|
892
|
-
*
|
|
890
|
+
* concept).
|
|
891
|
+
*
|
|
892
|
+
* 0.10.4 ~ 0.14.2 clients still send this block (with `wsInboxDeliver: true`
|
|
893
|
+
* hard-coded). The 0.14.3+ runtime omits it. The schema is retained so that
|
|
894
|
+
* middle-version `client:register` frames still parse, even though the
|
|
895
|
+
* server no longer reads any of these fields — the WS inbox data plane is
|
|
896
|
+
* mandatory on this server build.
|
|
893
897
|
*/
|
|
894
898
|
const clientWireCapabilitiesSchema = z.object({ wsInboxDeliver: z.boolean().default(false) }).partial();
|
|
895
899
|
z.object({
|
|
@@ -1114,8 +1118,8 @@ z.object({
|
|
|
1114
1118
|
});
|
|
1115
1119
|
/**
|
|
1116
1120
|
* Server → client WS frame carrying the full image bytes for an image
|
|
1117
|
-
* message. Pushed before the corresponding `
|
|
1118
|
-
*
|
|
1121
|
+
* message. Pushed before the corresponding `inbox:deliver` frame so the
|
|
1122
|
+
* client has the file on disk by the time it renders the message.
|
|
1119
1123
|
*
|
|
1120
1124
|
* Best-effort: if the target client WS lives on a different server
|
|
1121
1125
|
* instance (or is offline), the frame is lost and the reference message
|
|
@@ -1276,14 +1280,11 @@ z.object({
|
|
|
1276
1280
|
}).extend({ message: clientMessageSchema });
|
|
1277
1281
|
z.object({ limit: z.coerce.number().int().min(1).max(50).default(10) });
|
|
1278
1282
|
/**
|
|
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.
|
|
1283
|
+
* server → client: a single inbox entry pushed over the active WS connection.
|
|
1281
1284
|
*
|
|
1282
1285
|
* `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).
|
|
1286
|
+
* in `inbox:ack`. `clientMessageSchema` carries `precedingMessages`, so the
|
|
1287
|
+
* client-side dispatch logic handles the silent-context bundle uniformly.
|
|
1287
1288
|
*
|
|
1288
1289
|
* `.passthrough()` so a forward-rolling server may extend the frame without
|
|
1289
1290
|
* breaking older clients that validate strictly. Older clients drop unknown
|
|
@@ -2015,8 +2016,13 @@ z.object({
|
|
|
2015
2016
|
/**
|
|
2016
2017
|
* Negotiable wire-protocol features the server advertises in its `welcome`
|
|
2017
2018
|
* frame. Older clients drop the `capabilities` field silently because the
|
|
2018
|
-
* frame is `.passthrough()`.
|
|
2019
|
-
*
|
|
2019
|
+
* frame is `.passthrough()`.
|
|
2020
|
+
*
|
|
2021
|
+
* Required by clients in the 0.10.4 ~ 0.14.2 range: those builds read
|
|
2022
|
+
* `wsInboxDeliver` here to decide whether to skip the local HTTP poll loop
|
|
2023
|
+
* and rely on `inbox:deliver` push frames. The 0.14.3+ runtime ignores the
|
|
2024
|
+
* field (push is the only path) but the server still emits it so middle-
|
|
2025
|
+
* version clients keep working.
|
|
2020
2026
|
*/
|
|
2021
2027
|
const serverCapabilitiesSchema = z.object({ wsInboxDeliver: z.boolean().default(false) }).partial();
|
|
2022
2028
|
/**
|
|
@@ -2145,13 +2151,6 @@ defineConfig({
|
|
|
2145
2151
|
refreshTokenExpiry: field(z.string().default("30d"), { env: "FIRST_TREE_HUB_AUTH_REFRESH_TOKEN_EXPIRY" }),
|
|
2146
2152
|
connectTokenExpiry: field(z.string().default("10m"), { env: "FIRST_TREE_HUB_AUTH_CONNECT_TOKEN_EXPIRY" })
|
|
2147
2153
|
},
|
|
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
2154
|
oauth: optional({ githubApp: optional({
|
|
2156
2155
|
appId: field(z.string().min(1), { env: "FIRST_TREE_HUB_GITHUB_APP_ID" }),
|
|
2157
2156
|
clientId: field(z.string().min(1), { env: "FIRST_TREE_HUB_GITHUB_APP_CLIENT_ID" }),
|
|
@@ -2407,15 +2406,6 @@ var FirstTreeHubSDK = class {
|
|
|
2407
2406
|
return false;
|
|
2408
2407
|
}
|
|
2409
2408
|
}
|
|
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
2409
|
async sendMessage(chatId, data) {
|
|
2420
2410
|
return this.requestJson(`/api/v1/agent/chats/${chatId}/messages`, {
|
|
2421
2411
|
method: "POST",
|
|
@@ -2583,17 +2573,6 @@ const RECONNECT_MAX_MS = 3e4;
|
|
|
2583
2573
|
const WS_CONNECT_TIMEOUT_MS = 1e4;
|
|
2584
2574
|
const HEARTBEAT_INTERVAL_MS = 3e4;
|
|
2585
2575
|
/**
|
|
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
2576
|
* Unified-user-token C5: reconnect PROACTIVELY this many ms before the JWT's
|
|
2598
2577
|
* `exp` claim so the client rotates to a fresh JWT without ever hitting the
|
|
2599
2578
|
* server-side `auth:expired` push. The provider's next `getAccessToken()` call
|
|
@@ -2653,15 +2632,6 @@ var ClientConnection = class extends EventEmitter {
|
|
|
2653
2632
|
/** Count of `server:welcome` frames received; drives `isReconnect` flag. */
|
|
2654
2633
|
welcomeFramesReceived = 0;
|
|
2655
2634
|
/**
|
|
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
2635
|
* Last handshake error, stashed for the `close` handler to surface a typed
|
|
2666
2636
|
* reason (e.g. {@link ClientOrgMismatchError}) instead of a generic
|
|
2667
2637
|
* "closed before ready" when `connect()` is pending.
|
|
@@ -2674,11 +2644,11 @@ var ClientConnection = class extends EventEmitter {
|
|
|
2674
2644
|
desiredBindings = /* @__PURE__ */ new Map();
|
|
2675
2645
|
pendingBinds = /* @__PURE__ */ new Map();
|
|
2676
2646
|
/**
|
|
2677
|
-
* In-flight image writes from recent `image_payload` frames.
|
|
2678
|
-
*
|
|
2679
|
-
*
|
|
2680
|
-
*
|
|
2681
|
-
*
|
|
2647
|
+
* In-flight image writes from recent `image_payload` frames. `image_payload`
|
|
2648
|
+
* arrives on the WS just before `inbox:deliver` for the same message, but
|
|
2649
|
+
* the EventEmitter dispatch is sync — so without gating, the deliver
|
|
2650
|
+
* handler can fire before the image bytes hit disk. Block `inbox:deliver`
|
|
2651
|
+
* emission until these settle.
|
|
2682
2652
|
*/
|
|
2683
2653
|
pendingImageWrites = /* @__PURE__ */ new Set();
|
|
2684
2654
|
constructor(config) {
|
|
@@ -2699,24 +2669,21 @@ var ClientConnection = class extends EventEmitter {
|
|
|
2699
2669
|
return this.boundAgents;
|
|
2700
2670
|
}
|
|
2701
2671
|
/**
|
|
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.
|
|
2672
|
+
* Ack a delivered inbox entry over the WS data plane. Safe to call when the
|
|
2673
|
+
* WS is closed — the frame is dropped (logged) and the entry will time out
|
|
2674
|
+
* server-side and re-deliver on reconnect. The handler has by then already
|
|
2675
|
+
* started processing, so reaper-driven redelivery surfaces as a duplicate
|
|
2676
|
+
* dispatch on the next connect; SessionManager's dedupe key
|
|
2677
|
+
* `(chatId, messageId)` collapses it.
|
|
2717
2678
|
*/
|
|
2718
2679
|
sendInboxAck(entryId) {
|
|
2719
|
-
if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
|
|
2680
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
2681
|
+
this.wsLogger.warn({
|
|
2682
|
+
entryId,
|
|
2683
|
+
readyState: this.ws?.readyState
|
|
2684
|
+
}, "inbox:ack dropped — socket not OPEN");
|
|
2685
|
+
return;
|
|
2686
|
+
}
|
|
2720
2687
|
this.ws.send(JSON.stringify({
|
|
2721
2688
|
type: "inbox:ack",
|
|
2722
2689
|
entryId
|
|
@@ -2873,7 +2840,6 @@ var ClientConnection = class extends EventEmitter {
|
|
|
2873
2840
|
this.clearAuthRefreshTimer();
|
|
2874
2841
|
const wasRegistered = this.registered;
|
|
2875
2842
|
this.registered = false;
|
|
2876
|
-
this.wsInboxDeliverActive = false;
|
|
2877
2843
|
this.rejectAllPendingBinds("WebSocket closed");
|
|
2878
2844
|
if (!settled) {
|
|
2879
2845
|
this.wsLogger.warn({ code }, "closed before ready");
|
|
@@ -2909,8 +2875,7 @@ var ClientConnection = class extends EventEmitter {
|
|
|
2909
2875
|
clientId: this.clientId,
|
|
2910
2876
|
hostname: hostname(),
|
|
2911
2877
|
os: platform(),
|
|
2912
|
-
sdkVersion: this.sdkVersion
|
|
2913
|
-
wireCapabilities: { wsInboxDeliver: WS_INBOX_DELIVER_OPT_IN }
|
|
2878
|
+
sdkVersion: this.sdkVersion
|
|
2914
2879
|
}));
|
|
2915
2880
|
return;
|
|
2916
2881
|
}
|
|
@@ -2920,7 +2885,6 @@ var ClientConnection = class extends EventEmitter {
|
|
|
2920
2885
|
this.wsLogger.warn({ issues: parsed.error.issues.map((i) => i.message) }, "ignoring malformed server:welcome frame");
|
|
2921
2886
|
return;
|
|
2922
2887
|
}
|
|
2923
|
-
this.wsInboxDeliverActive = parsed.data.capabilities?.wsInboxDeliver === true && WS_INBOX_DELIVER_OPT_IN;
|
|
2924
2888
|
const isReconnect = this.welcomeFramesReceived > 0;
|
|
2925
2889
|
this.welcomeFramesReceived++;
|
|
2926
2890
|
this.emit("server:welcome", {
|
|
@@ -3058,15 +3022,6 @@ var ClientConnection = class extends EventEmitter {
|
|
|
3058
3022
|
write.finally(() => this.pendingImageWrites.delete(write));
|
|
3059
3023
|
return;
|
|
3060
3024
|
}
|
|
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
3025
|
if (type === "inbox:deliver") {
|
|
3071
3026
|
const parsed = inboxDeliverFrameSchema.safeParse(msg);
|
|
3072
3027
|
if (!parsed.success) {
|
|
@@ -6812,18 +6767,13 @@ var SessionManager = class {
|
|
|
6812
6767
|
this.config.onStateChange(chatId, state);
|
|
6813
6768
|
}
|
|
6814
6769
|
/**
|
|
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).
|
|
6770
|
+
* ACK an inbox entry — delayed until handler starts processing. Routes
|
|
6771
|
+
* through `config.ackEntry`, which is wired to the WS data plane.
|
|
6821
6772
|
*/
|
|
6822
6773
|
async ackEntry(entryId, chatId) {
|
|
6823
6774
|
if (entryId === void 0) return;
|
|
6824
6775
|
try {
|
|
6825
|
-
|
|
6826
|
-
else await this.config.sdk.ack(entryId);
|
|
6776
|
+
await this.config.ackEntry(entryId);
|
|
6827
6777
|
} catch {
|
|
6828
6778
|
this.config.log.warn({
|
|
6829
6779
|
chatId,
|
|
@@ -6976,9 +6926,7 @@ var AgentSlot = class {
|
|
|
6976
6926
|
sessionManager = null;
|
|
6977
6927
|
config;
|
|
6978
6928
|
logger;
|
|
6979
|
-
sdk = null;
|
|
6980
6929
|
agentConfigCache = null;
|
|
6981
|
-
pollingTimer = null;
|
|
6982
6930
|
reconcileTimer = null;
|
|
6983
6931
|
listeners = [];
|
|
6984
6932
|
/**
|
|
@@ -7016,7 +6964,6 @@ var AgentSlot = class {
|
|
|
7016
6964
|
}
|
|
7017
6965
|
async start(contextTreeBinding) {
|
|
7018
6966
|
const sdk = (await this.clientConnection.bindAgent(this.config.agentId, this.config.runtimeType ?? this.config.type, this.config.runtimeVersion)).sdk;
|
|
7019
|
-
this.sdk = sdk;
|
|
7020
6967
|
const agent = await sdk.register();
|
|
7021
6968
|
this.logger.info({ displayName: agent.displayName }, "agent bound");
|
|
7022
6969
|
if (agent.type === "human") {
|
|
@@ -7036,9 +6983,6 @@ var AgentSlot = class {
|
|
|
7036
6983
|
throw new Error(`Hub unreachable while loading agent config: ${msg}`);
|
|
7037
6984
|
}
|
|
7038
6985
|
this.inboxId = agent.inboxId;
|
|
7039
|
-
const onMessage = (agentId) => {
|
|
7040
|
-
if (agentId === this.config.agentId) this.pullAndDispatch();
|
|
7041
|
-
};
|
|
7042
6986
|
const onInboxDeliver = (inboxId, frame) => {
|
|
7043
6987
|
if (inboxId !== this.inboxId) return;
|
|
7044
6988
|
this.dispatchPushedFrame(frame).catch((err) => {
|
|
@@ -7057,14 +7001,10 @@ var AgentSlot = class {
|
|
|
7057
7001
|
const onReconcileResult = (result) => {
|
|
7058
7002
|
if (result.agentId === this.config.agentId && this.sessionManager) this.sessionManager.applyStaleChatIds(result.staleChatIds);
|
|
7059
7003
|
};
|
|
7060
|
-
this.clientConnection.on("agent:message", onMessage);
|
|
7061
7004
|
this.clientConnection.on("inbox:deliver", onInboxDeliver);
|
|
7062
7005
|
this.clientConnection.on("agent:bound", onBound);
|
|
7063
7006
|
this.clientConnection.on("session:reconcile:result", onReconcileResult);
|
|
7064
7007
|
this.listeners.push({
|
|
7065
|
-
event: "agent:message",
|
|
7066
|
-
fn: onMessage
|
|
7067
|
-
}, {
|
|
7068
7008
|
event: "inbox:deliver",
|
|
7069
7009
|
fn: onInboxDeliver
|
|
7070
7010
|
}, {
|
|
@@ -7082,10 +7022,10 @@ var AgentSlot = class {
|
|
|
7082
7022
|
agentId: this.config.agentId
|
|
7083
7023
|
})
|
|
7084
7024
|
});
|
|
7085
|
-
const ackEntry =
|
|
7025
|
+
const ackEntry = (entryId) => {
|
|
7086
7026
|
this.clientConnection.sendInboxAck(entryId);
|
|
7087
7027
|
return Promise.resolve();
|
|
7088
|
-
}
|
|
7028
|
+
};
|
|
7089
7029
|
this.sessionManager = new SessionManager({
|
|
7090
7030
|
session: this.config.session,
|
|
7091
7031
|
concurrency: this.config.concurrency,
|
|
@@ -7128,24 +7068,15 @@ var AgentSlot = class {
|
|
|
7128
7068
|
event: "session:command",
|
|
7129
7069
|
fn: onCommand
|
|
7130
7070
|
});
|
|
7131
|
-
this.startPolling();
|
|
7132
7071
|
this.startReconcileLoop();
|
|
7133
7072
|
return agent;
|
|
7134
7073
|
}
|
|
7135
7074
|
async stop() {
|
|
7136
|
-
if (this.pollingTimer) {
|
|
7137
|
-
clearInterval(this.pollingTimer);
|
|
7138
|
-
this.pollingTimer = null;
|
|
7139
|
-
}
|
|
7140
7075
|
if (this.reconcileTimer) {
|
|
7141
7076
|
clearInterval(this.reconcileTimer);
|
|
7142
7077
|
this.reconcileTimer = null;
|
|
7143
7078
|
}
|
|
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);
|
|
7079
|
+
for (const entry of this.listeners) this.clientConnection.off(entry.event, entry.fn);
|
|
7149
7080
|
this.listeners = [];
|
|
7150
7081
|
await this.clientConnection.unbindAgent(this.config.agentId);
|
|
7151
7082
|
await this.sessionManager?.shutdown();
|
|
@@ -7166,31 +7097,18 @@ var AgentSlot = class {
|
|
|
7166
7097
|
const runtimeState = this.sessionManager.getAggregateRuntimeState();
|
|
7167
7098
|
if (runtimeState) this.clientConnection.reportRuntimeState(this.config.agentId, runtimeState);
|
|
7168
7099
|
}
|
|
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
7100
|
/**
|
|
7180
7101
|
* Translate an `inbox:deliver` push frame into the {@link InboxEntryWithMessage}
|
|
7181
7102
|
* shape `SessionManager.dispatch` expects, then dispatch.
|
|
7182
7103
|
*
|
|
7183
7104
|
* 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.
|
|
7105
|
+
* construction time — `clientConnection.sendInboxAck`. Sending an additional
|
|
7106
|
+
* ack here would double-ack: a WS frame the server cannot match against any
|
|
7107
|
+
* `delivered` row, which leaks the server's per-agent in-flight counter and
|
|
7108
|
+
* stalls push after `inboxMaxInFlightPerAgent` messages.
|
|
7190
7109
|
*
|
|
7191
7110
|
* 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).
|
|
7111
|
+
* and the 300s timeout reaper rolls it back to `pending` for replay.
|
|
7194
7112
|
*/
|
|
7195
7113
|
async dispatchPushedFrame(frame) {
|
|
7196
7114
|
if (!this.sessionManager) return;
|
|
@@ -7218,15 +7136,6 @@ var AgentSlot = class {
|
|
|
7218
7136
|
if (chatIds.length === 0) return;
|
|
7219
7137
|
this.clientConnection.sendSessionReconcile(this.config.agentId, chatIds);
|
|
7220
7138
|
}
|
|
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
7139
|
};
|
|
7231
7140
|
/**
|
|
7232
7141
|
* Top-level marker file Claude Code writes after a successful OAuth login.
|
|
@@ -9853,7 +9762,7 @@ async function onboardCreate(args) {
|
|
|
9853
9762
|
}
|
|
9854
9763
|
const runtimeAgent = args.type === "human" ? args.assistant : args.id;
|
|
9855
9764
|
if (args.feishuBotAppId && args.feishuBotAppSecret) {
|
|
9856
|
-
const { bindFeishuBot } = await import("./feishu-
|
|
9765
|
+
const { bindFeishuBot } = await import("./feishu-DHSy6WDD.mjs").then((n) => n.r);
|
|
9857
9766
|
const targetAgentUuid = args.type === "human" ? assistantUuid : primary.uuid;
|
|
9858
9767
|
if (!targetAgentUuid) print.line(`Warning: Cannot bind Feishu bot — no runtime agent available for "${args.id}".\n`);
|
|
9859
9768
|
else {
|
|
@@ -11066,7 +10975,7 @@ function createFeedbackHandler(config) {
|
|
|
11066
10975
|
return { handle };
|
|
11067
10976
|
}
|
|
11068
10977
|
//#endregion
|
|
11069
|
-
//#region ../server/dist/app-
|
|
10978
|
+
//#region ../server/dist/app-z2-jRXbZ.mjs
|
|
11070
10979
|
var import_fastify_opentelemetry = /* @__PURE__ */ __toESM(require_fastify_opentelemetry(), 1);
|
|
11071
10980
|
init_esm();
|
|
11072
10981
|
var __defProp = Object.defineProperty;
|
|
@@ -11844,18 +11753,6 @@ async function agentInboxRoutes(app) {
|
|
|
11844
11753
|
const query = inboxPollQuerySchema.parse(request.query);
|
|
11845
11754
|
return await pollInbox(app.db, identity.inboxId, query.limit);
|
|
11846
11755
|
});
|
|
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
11756
|
}
|
|
11860
11757
|
async function agentMeRoutes(app) {
|
|
11861
11758
|
app.get("/me", async (request) => {
|
|
@@ -11887,11 +11784,11 @@ async function agentMeRoutes(app) {
|
|
|
11887
11784
|
* {imageId, mimeType, filename, size}
|
|
11888
11785
|
*
|
|
11889
11786
|
* 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
|
-
*
|
|
11787
|
+
* send buffer synchronously, which is the only ordering guarantee we need —
|
|
11788
|
+
* the subsequent `inbox:deliver` frame is driven by a PG NOTIFY round trip,
|
|
11789
|
+
* so the image lands first on the wire. Awaiting the TCP flush here would
|
|
11790
|
+
* put a slow subscriber's backpressure on the sender's HTTP response for a
|
|
11791
|
+
* feature that is already best-effort.
|
|
11895
11792
|
*
|
|
11896
11793
|
* Non-image messages are returned unchanged. Missing-subscriber / wrong-
|
|
11897
11794
|
* instance cases are acceptable loss per the image-out-of-messages design
|
|
@@ -12588,8 +12485,7 @@ async function summarizeContextTreeUsage(db, organizationId, windowDays) {
|
|
|
12588
12485
|
/**
|
|
12589
12486
|
* Default per-agent in-flight cap when `server.inbox.maxInFlightPerAgent` is
|
|
12590
12487
|
* 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.
|
|
12488
|
+
* `inbox` block still gets reasonable backpressure.
|
|
12593
12489
|
*/
|
|
12594
12490
|
const DEFAULT_INBOX_MAX_IN_FLIGHT_PER_AGENT = 32;
|
|
12595
12491
|
/**
|
|
@@ -12634,23 +12530,12 @@ function clientWsRoutes(notifier, instanceId) {
|
|
|
12634
12530
|
let authExpiryTimer = null;
|
|
12635
12531
|
const boundAgents = /* @__PURE__ */ new Map();
|
|
12636
12532
|
/**
|
|
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
12533
|
* Per-agent in-flight `inbox:deliver` counter for backpressure. Lives on
|
|
12646
12534
|
* the socket — when the WS closes it goes with it; that's intentional,
|
|
12647
12535
|
* because re-counting on a fresh connection would bias the cap against
|
|
12648
|
-
* a healthy reconnect
|
|
12536
|
+
* a healthy reconnect.
|
|
12649
12537
|
*/
|
|
12650
12538
|
const inboxInFlight = /* @__PURE__ */ new Map();
|
|
12651
|
-
function pushUseWsDataPlane() {
|
|
12652
|
-
return clientWantsWsInboxDeliver;
|
|
12653
|
-
}
|
|
12654
12539
|
/**
|
|
12655
12540
|
* Returns `false` when the socket has already moved out of `OPEN` —
|
|
12656
12541
|
* the only failure mode the caller can observe synchronously.
|
|
@@ -12893,7 +12778,6 @@ function clientWsRoutes(notifier, instanceId) {
|
|
|
12893
12778
|
try {
|
|
12894
12779
|
if (type === "client:register") {
|
|
12895
12780
|
const data = clientRegisterSchema.parse(msg);
|
|
12896
|
-
clientWantsWsInboxDeliver = data.wireCapabilities?.wsInboxDeliver === true;
|
|
12897
12781
|
let placeholderOrgId = jwtDefaultOrgId;
|
|
12898
12782
|
if (!placeholderOrgId) {
|
|
12899
12783
|
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 +12915,7 @@ function clientWsRoutes(notifier, instanceId) {
|
|
|
13031
12915
|
inboxId: agent.inboxId,
|
|
13032
12916
|
organizationId: agent.organizationId
|
|
13033
12917
|
});
|
|
13034
|
-
|
|
13035
|
-
if (wsPushActive) notifier.subscribe(agent.inboxId, socket, makeInboxPushHandler(agent.id, agent.inboxId));
|
|
13036
|
-
else notifier.subscribe(agent.inboxId, socket);
|
|
12918
|
+
notifier.subscribe(agent.inboxId, socket, makeInboxPushHandler(agent.id, agent.inboxId));
|
|
13037
12919
|
socket.send(JSON.stringify({
|
|
13038
12920
|
type: "agent:bound",
|
|
13039
12921
|
ref,
|
|
@@ -13041,7 +12923,7 @@ function clientWsRoutes(notifier, instanceId) {
|
|
|
13041
12923
|
displayName: agent.displayName,
|
|
13042
12924
|
agentType: agent.type
|
|
13043
12925
|
}));
|
|
13044
|
-
|
|
12926
|
+
drainBacklogForAgent(agent.id, agent.inboxId).catch((err) => {
|
|
13045
12927
|
app.log.error({
|
|
13046
12928
|
err,
|
|
13047
12929
|
agentId: agent.id
|
|
@@ -13162,6 +13044,14 @@ function clientWsRoutes(notifier, instanceId) {
|
|
|
13162
13044
|
} else if (type === "inbox:ack") {
|
|
13163
13045
|
const payloadResult = inboxAckFrameSchema.safeParse(msg);
|
|
13164
13046
|
if (!payloadResult.success) {
|
|
13047
|
+
app.log.warn({
|
|
13048
|
+
clientId,
|
|
13049
|
+
issues: payloadResult.error.issues.map((i) => ({
|
|
13050
|
+
path: i.path.join("."),
|
|
13051
|
+
code: i.code,
|
|
13052
|
+
message: i.message
|
|
13053
|
+
}))
|
|
13054
|
+
}, "malformed inbox:ack frame — replying error");
|
|
13165
13055
|
socket.send(JSON.stringify({
|
|
13166
13056
|
type: "error",
|
|
13167
13057
|
message: "Malformed inbox:ack frame"
|
|
@@ -13171,7 +13061,14 @@ function clientWsRoutes(notifier, instanceId) {
|
|
|
13171
13061
|
const { entryId } = payloadResult.data;
|
|
13172
13062
|
try {
|
|
13173
13063
|
const ackedEntry = await ackEntryByIdForBoundAgents(app.db, entryId, [...boundAgents.values()].map((a) => a.inboxId));
|
|
13174
|
-
if (!ackedEntry)
|
|
13064
|
+
if (!ackedEntry) {
|
|
13065
|
+
app.log.debug({
|
|
13066
|
+
clientId,
|
|
13067
|
+
entryId,
|
|
13068
|
+
boundInboxes: boundAgents.size
|
|
13069
|
+
}, "inbox:ack matched no row — stale ack or reaper race");
|
|
13070
|
+
return;
|
|
13071
|
+
}
|
|
13175
13072
|
const owner = [...boundAgents.values()].find((a) => a.inboxId === ackedEntry.inboxId);
|
|
13176
13073
|
if (owner) {
|
|
13177
13074
|
inboxInFlight.set(owner.agentId, Math.max(0, (inboxInFlight.get(owner.agentId) ?? 1) - 1));
|
|
@@ -13859,6 +13756,7 @@ const APP_JWT_EXPIRY = "9m";
|
|
|
13859
13756
|
* caller's side; the docs recommend 60 seconds. We mirror that.
|
|
13860
13757
|
*/
|
|
13861
13758
|
const APP_JWT_IAT_SKEW_SECONDS = 60;
|
|
13759
|
+
const APP_INSTALLATION_TOKEN_URL = (id) => `https://api.github.com/app/installations/${id}/access_tokens`;
|
|
13862
13760
|
const APP_INSTALLATION_URL = (id) => `https://api.github.com/app/installations/${id}`;
|
|
13863
13761
|
const OAUTH_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
13864
13762
|
const OAUTH_AUTHORIZE_URL = "https://github.com/login/oauth/authorize";
|
|
@@ -13963,6 +13861,39 @@ async function listUserAccessibleInstallationIds(userAccessToken, opts = {}) {
|
|
|
13963
13861
|
return out;
|
|
13964
13862
|
}
|
|
13965
13863
|
/**
|
|
13864
|
+
* Mint a per-installation token (server-to-server). The token is cheap
|
|
13865
|
+
* (one signature + one HTTP round-trip) and the upstream TTL is ~1h, so
|
|
13866
|
+
* the recommended caller pattern is "mint per request" rather than caching
|
|
13867
|
+
* — caching forces the caller to also track expiry, suspended state, and
|
|
13868
|
+
* GitHub-side permission churn, which the design explicitly punts to Phase
|
|
13869
|
+
* 4. We give callers a typed result and let them cache if profiling shows
|
|
13870
|
+
* the round-trip is hot.
|
|
13871
|
+
*
|
|
13872
|
+
* Throws `GithubAppApiError` on non-2xx. 401 means the App JWT is bad or
|
|
13873
|
+
* the App's key has been rotated upstream; 404 means the installation
|
|
13874
|
+
* was uninstalled. Callers SHOULD persist the suspension state when 403
|
|
13875
|
+
* comes back with `suspended` (the design tracks this as `suspended_at`
|
|
13876
|
+
* on `github_app_installations`).
|
|
13877
|
+
*/
|
|
13878
|
+
async function mintInstallationToken(appJwt, installationId, opts = {}) {
|
|
13879
|
+
const res = await (opts.fetcher ?? fetch)(APP_INSTALLATION_TOKEN_URL(installationId), {
|
|
13880
|
+
method: "POST",
|
|
13881
|
+
headers: {
|
|
13882
|
+
Authorization: `Bearer ${appJwt}`,
|
|
13883
|
+
Accept: "application/vnd.github+json",
|
|
13884
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
13885
|
+
}
|
|
13886
|
+
});
|
|
13887
|
+
if (!res.ok) throw new GithubAppApiError(res.status, `GitHub App installation-token request failed (${res.status})`);
|
|
13888
|
+
const body = await res.json();
|
|
13889
|
+
return {
|
|
13890
|
+
token: body.token,
|
|
13891
|
+
expiresAt: body.expires_at,
|
|
13892
|
+
permissions: body.permissions ?? {},
|
|
13893
|
+
repositorySelection: body.repository_selection ?? "all"
|
|
13894
|
+
};
|
|
13895
|
+
}
|
|
13896
|
+
/**
|
|
13966
13897
|
* Trade an expiring user-to-server access token for a fresh pair using
|
|
13967
13898
|
* its refresh token. Thrown on:
|
|
13968
13899
|
* - Network / 5xx — `GithubAppApiError(status, …)`
|
|
@@ -15695,6 +15626,26 @@ function normalizeRemoteRepoUrl(value) {
|
|
|
15695
15626
|
if (/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+(?:\.git)?$/.test(value)) return `https://github.com/${value}`;
|
|
15696
15627
|
return value;
|
|
15697
15628
|
}
|
|
15629
|
+
/**
|
|
15630
|
+
* Whether this binding actually drives a GitHub-hosted remote fetch — the
|
|
15631
|
+
* only case where minting a GitHub App installation token is meaningful.
|
|
15632
|
+
*
|
|
15633
|
+
* Returns false when:
|
|
15634
|
+
* - `localPath` is set (sync code short-circuits to the local checkout
|
|
15635
|
+
* before ever looking at `repo`)
|
|
15636
|
+
* - `repo` is missing
|
|
15637
|
+
* - `repo` is a file:// URL, a non-GitHub HTTPS URL, or otherwise
|
|
15638
|
+
* unparseable
|
|
15639
|
+
*
|
|
15640
|
+
* Used by the snapshot routes to gate the "install the GitHub App"
|
|
15641
|
+
* guidance — without this gate, every unavailable snapshot (missing repo,
|
|
15642
|
+
* bad branch, …) gets a misleading App-install hint appended.
|
|
15643
|
+
*/
|
|
15644
|
+
function isGithubRemoteBinding(binding) {
|
|
15645
|
+
if (binding.localPath && binding.localPath.trim().length > 0) return false;
|
|
15646
|
+
if (!binding.repo) return false;
|
|
15647
|
+
return isGithubHttpsRepo(normalizeRemoteRepoUrl(binding.repo));
|
|
15648
|
+
}
|
|
15698
15649
|
function managedContextTreeCacheRoot() {
|
|
15699
15650
|
return join(DEFAULT_DATA_DIR$1, "context-tree-repos");
|
|
15700
15651
|
}
|
|
@@ -15868,7 +15819,7 @@ function errorMessage(error) {
|
|
|
15868
15819
|
return redactSecret(error.message.trim().split("\n")[0] ?? "");
|
|
15869
15820
|
}
|
|
15870
15821
|
function redactSecret(message) {
|
|
15871
|
-
return message.replace(/(https?:\/\/)[^/@\s]+@/g, "$1[redacted]@").replace(/\
|
|
15822
|
+
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
15823
|
}
|
|
15873
15824
|
function unavailableSnapshot(repo, branch, detail) {
|
|
15874
15825
|
return {
|
|
@@ -16448,6 +16399,80 @@ function ghostNodeId(path) {
|
|
|
16448
16399
|
function toPosix(path) {
|
|
16449
16400
|
return sep === "/" ? path : path.split(sep).join("/");
|
|
16450
16401
|
}
|
|
16402
|
+
/**
|
|
16403
|
+
* Mint a short-lived GitHub App installation token for the given installation.
|
|
16404
|
+
* Returns `ok: false` (with a precise reason) when the org has no App
|
|
16405
|
+
* configured, no installation row, the installation is suspended, or GitHub
|
|
16406
|
+
* rejects the mint — callers fall back to unauthenticated git fetch (public
|
|
16407
|
+
* repos still resolve; private repos surface as an unavailable snapshot
|
|
16408
|
+
* with a remediation message).
|
|
16409
|
+
*
|
|
16410
|
+
* Takes the `installation` row directly so the helper has no DB dependency
|
|
16411
|
+
* — route handlers do the `findInstallationByOrg` lookup themselves. Keeps
|
|
16412
|
+
* this module a pure transform that's trivial to unit-test.
|
|
16413
|
+
*
|
|
16414
|
+
* Credentials use the narrow `GithubAppCredentials` shape so the helper
|
|
16415
|
+
* isn't coupled to the broader OAuth config surface; callers pass
|
|
16416
|
+
* `config.oauth?.githubApp`, which structurally satisfies it.
|
|
16417
|
+
*/
|
|
16418
|
+
async function mintContextTreeInstallationToken(installation, appCredentials, options = {}) {
|
|
16419
|
+
if (!appCredentials) return {
|
|
16420
|
+
ok: false,
|
|
16421
|
+
reason: "no-app-config"
|
|
16422
|
+
};
|
|
16423
|
+
if (!installation) return {
|
|
16424
|
+
ok: false,
|
|
16425
|
+
reason: "no-installation"
|
|
16426
|
+
};
|
|
16427
|
+
if (installation.suspendedAt) return {
|
|
16428
|
+
ok: false,
|
|
16429
|
+
reason: "suspended"
|
|
16430
|
+
};
|
|
16431
|
+
try {
|
|
16432
|
+
return {
|
|
16433
|
+
ok: true,
|
|
16434
|
+
token: (await mintInstallationToken(await createAppJwt({
|
|
16435
|
+
appId: appCredentials.appId,
|
|
16436
|
+
privateKeyPem: appCredentials.privateKeyPem
|
|
16437
|
+
}), installation.installationId, { fetcher: options.fetcher })).token
|
|
16438
|
+
};
|
|
16439
|
+
} catch (error) {
|
|
16440
|
+
return {
|
|
16441
|
+
ok: false,
|
|
16442
|
+
reason: "mint-failed",
|
|
16443
|
+
detail: error instanceof GithubAppApiError ? `GitHub returned ${error.status} when minting an installation token.` : "Hub could not mint a GitHub App installation token."
|
|
16444
|
+
};
|
|
16445
|
+
}
|
|
16446
|
+
}
|
|
16447
|
+
/**
|
|
16448
|
+
* Append a remediation hint to an unavailable snapshot's `contextStatus.detail`
|
|
16449
|
+
* when the underlying cause is a missing / suspended / failed GitHub App token
|
|
16450
|
+
* mint. Public-repo snapshots (mint reason `no-app-config`) are left untouched
|
|
16451
|
+
* — the deployment may legitimately have no App configured.
|
|
16452
|
+
*
|
|
16453
|
+
* Gated on `isGithubRemoteBinding(binding)` so unrelated unavailable
|
|
16454
|
+
* reasons (no repo configured, localPath missing, illegal branch name,
|
|
16455
|
+
* public-repo fetch error) don't get a misleading "install the GitHub
|
|
16456
|
+
* App" hint appended.
|
|
16457
|
+
*
|
|
16458
|
+
* Lives next to `mintContextTreeInstallationToken` so the two routes that
|
|
16459
|
+
* call mint share one shaping function; the snapshot service itself stays
|
|
16460
|
+
* token-agnostic.
|
|
16461
|
+
*/
|
|
16462
|
+
function decorateSnapshotWithMintGuidance(snapshot, binding, mintResult) {
|
|
16463
|
+
if (mintResult.ok) return snapshot;
|
|
16464
|
+
if (snapshot.snapshotStatus !== "unavailable") return snapshot;
|
|
16465
|
+
if (mintResult.reason === "no-app-config") return snapshot;
|
|
16466
|
+
if (!isGithubRemoteBinding(binding)) return snapshot;
|
|
16467
|
+
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}` : ""}`;
|
|
16468
|
+
return {
|
|
16469
|
+
...snapshot,
|
|
16470
|
+
contextStatus: {
|
|
16471
|
+
...snapshot.contextStatus,
|
|
16472
|
+
detail: `${snapshot.contextStatus.detail} ${guidance}`
|
|
16473
|
+
}
|
|
16474
|
+
};
|
|
16475
|
+
}
|
|
16451
16476
|
const querySchema$1 = z.object({ window: z.enum([
|
|
16452
16477
|
"1d",
|
|
16453
16478
|
"7d",
|
|
@@ -16463,12 +16488,15 @@ async function contextTreeSnapshotRoutes(app) {
|
|
|
16463
16488
|
const { userId } = requireUser(request);
|
|
16464
16489
|
const orgId = await resolveUserPrimaryOrgId(app.db, userId);
|
|
16465
16490
|
const binding = orgId ? await getOrgContextTree(app.db, orgId) : {};
|
|
16466
|
-
|
|
16491
|
+
let mintResult = null;
|
|
16492
|
+
if (orgId && isGithubRemoteBinding(binding)) mintResult = await mintContextTreeInstallationToken(await findInstallationByOrg(app.db, orgId), app.config.oauth?.githubApp);
|
|
16493
|
+
const githubToken = mintResult?.ok ? mintResult.token : void 0;
|
|
16467
16494
|
const window = query.window ?? "7d";
|
|
16468
|
-
const
|
|
16495
|
+
const rawSnapshot = await getContextTreeSnapshot({
|
|
16469
16496
|
...binding,
|
|
16470
16497
|
githubToken
|
|
16471
16498
|
}, window);
|
|
16499
|
+
const snapshot = mintResult ? decorateSnapshotWithMintGuidance(rawSnapshot, binding, mintResult) : rawSnapshot;
|
|
16472
16500
|
const usage = orgId ? await summarizeContextTreeUsage(app.db, orgId, contextTreeSnapshotWindowDays(window)) : snapshot.usage;
|
|
16473
16501
|
return contextTreeSnapshotSchema.parse({
|
|
16474
16502
|
...snapshot,
|
|
@@ -16476,31 +16504,6 @@ async function contextTreeSnapshotRoutes(app) {
|
|
|
16476
16504
|
});
|
|
16477
16505
|
});
|
|
16478
16506
|
}
|
|
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
16507
|
/**
|
|
16505
16508
|
* Resolve the client IP for rate-limit attribution.
|
|
16506
16509
|
*
|
|
@@ -16602,7 +16605,7 @@ async function healthzRoutes(app) {
|
|
|
16602
16605
|
* `api/orgs/invitations.ts` (Class B, admin-gated).
|
|
16603
16606
|
*/
|
|
16604
16607
|
async function publicInvitationRoutes(app) {
|
|
16605
|
-
const { previewInvitation } = await import("./invitation-C9m2gQx4-
|
|
16608
|
+
const { previewInvitation } = await import("./invitation-C9m2gQx4-BSErdb8x.mjs");
|
|
16606
16609
|
app.get("/:token/preview", async (request, reply) => {
|
|
16607
16610
|
if (!request.params.token) throw new UnauthorizedError("Token required");
|
|
16608
16611
|
const preview = await previewInvitation(app.db, request.params.token);
|
|
@@ -16891,7 +16894,7 @@ async function meRoutes(app) {
|
|
|
16891
16894
|
*/
|
|
16892
16895
|
app.get("/me/pinned-agents", async (request) => {
|
|
16893
16896
|
const { userId } = requireUser(request);
|
|
16894
|
-
const { listMyPinnedAgents } = await import("./client-
|
|
16897
|
+
const { listMyPinnedAgents } = await import("./client-q1EYQD1n-ypjoumIO.mjs");
|
|
16895
16898
|
return listMyPinnedAgents(app.db, { userId });
|
|
16896
16899
|
});
|
|
16897
16900
|
/**
|
|
@@ -17467,12 +17470,15 @@ async function orgContextTreeSnapshotRoutes(app) {
|
|
|
17467
17470
|
const query = querySchema.parse(request.query);
|
|
17468
17471
|
const scope = await requireOrgMembership(request, app.db);
|
|
17469
17472
|
const binding = await getOrgContextTree(app.db, scope.organizationId);
|
|
17470
|
-
|
|
17473
|
+
let mintResult = null;
|
|
17474
|
+
if (isGithubRemoteBinding(binding)) mintResult = await mintContextTreeInstallationToken(await findInstallationByOrg(app.db, scope.organizationId), app.config.oauth?.githubApp);
|
|
17475
|
+
const githubToken = mintResult?.ok ? mintResult.token : void 0;
|
|
17471
17476
|
const window = query.window ?? "7d";
|
|
17472
|
-
const
|
|
17477
|
+
const rawSnapshot = await getContextTreeSnapshot({
|
|
17473
17478
|
...binding,
|
|
17474
17479
|
githubToken
|
|
17475
17480
|
}, window);
|
|
17481
|
+
const snapshot = mintResult ? decorateSnapshotWithMintGuidance(rawSnapshot, binding, mintResult) : rawSnapshot;
|
|
17476
17482
|
const usage = await summarizeContextTreeUsage(app.db, scope.organizationId, contextTreeSnapshotWindowDays(window));
|
|
17477
17483
|
return contextTreeSnapshotSchema.parse({
|
|
17478
17484
|
...snapshot,
|