@agent-team-foundation/first-tree-hub 0.12.0 → 0.12.2
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/cli/index.mjs +4 -4
- package/dist/{client-BPUdUaZT-CyCrpCTP.mjs → client-DHCSQ8kg-DjlSmE9q.mjs} +3 -3
- package/dist/{client-BhCtO2df-BGOu-rRN.mjs → client-WubcgX-W-B2bOvgJ1.mjs} +2 -2
- package/dist/{dist-UOZ6vMUW.mjs → dist-DHHd2dar.mjs} +39 -14
- package/dist/{feishu-C6qlhju2.mjs → feishu-fLnwqCOs.mjs} +1 -1
- package/dist/index.mjs +4 -4
- package/dist/{invitation-C299fxkP-KyCNax4T.mjs → invitation-C299fxkP-B89eqDos.mjs} +1 -1
- package/dist/{saas-connect-Drn9g6cR.mjs → saas-connect-_2M4kfPR.mjs} +484 -74
- package/dist/web/assets/{index-Dy3jIUX5.js → index-C1DBMrHD.js} +86 -86
- package/dist/web/assets/{index-B_Tf2I6v.css → index-CwC0zzF5.css} +1 -1
- package/dist/web/assets/{index-Bnyz7inW.js → index-D5RJDuFw.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
|
@@ -2,10 +2,10 @@ import { a as __toCommonJS, o as __toESM, t as __commonJSMin } from "./chunk-BSw
|
|
|
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
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-C_K2CKXC.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 loginSchema, A as createAgentSchema, At as updateTaskStatusSchema, B as githubCallbackQuerySchema, C as agentTypeSchema$1, Ct as updateAdapterConfigSchema, D as contextTreeSnapshotSchema, Dt as updateClientCapabilitiesSchema, E as connectTokenExchangeSchema, Et as updateChatSchema, F as createTaskSchema, G as inboxDeliverFrameSchema$1, H as githubStartQuerySchema, I as defaultRuntimeConfigPayload, J as isRedactedEnvValue, K as inboxPollQuerySchema, L as delegateFeishuUserSchema, M as createMeChatSchema, N as createMemberSchema, O as createAdapterConfigSchema, Ot as updateMemberSchema, P as createOrgFromMeSchema, Q as listMeChatsQuerySchema, R as dryRunAgentRuntimeConfigSchema, S as agentRuntimeConfigPayloadSchema$1, St as taskListQuerySchema, T as clientRegisterSchema, Tt as updateAgentSchema, U as imageInlineContentSchema, V as githubDevCallbackQuerySchema, W as inboxAckFrameSchema, X as joinByInvitationSchema, Y as isReservedAgentName$1, Z as linkTaskChatSchema, _ as addParticipantSchema, _t as sessionEventSchema$1, a as AGENT_STATUSES, b as agentBindRequestSchema, bt as stripCode, ct as refreshTokenSchema, d as TASK_CREATOR_TYPES, et as messageSourceSchema$1, f as TASK_HEALTH_SIGNALS, ft as selfServiceFeishuBotSchema, g as addMeChatParticipantsSchema, gt as sessionEventMessageSchema, h as WS_AUTH_FRAME_TIMEOUT_MS, ht as sessionCompletionMessageSchema, i as AGENT_SOURCES, it as patchOnboardingSchema, j as createChatSchema, jt as wsAuthFrameSchema, k as createAdapterMappingSchema, kt as updateOrganizationSchema, l as MENTION_REGEX, lt as runtimeStateMessageSchema, m as TASK_TERMINAL_STATUSES, mt as sendToAgentSchema, n as AGENT_NAME_REGEX$1, nt as onboardingEventSchema, o as AGENT_TYPES, p as TASK_STATUSES, pt as sendMessageSchema, q as isOrgSettingNamespace, r as AGENT_SELECTOR_HEADER$1, rt as paginationQuerySchema, s as AGENT_VISIBILITY, st as rebindAgentSchema, t as AGENT_BIND_REJECT_REASONS, tt as notificationQuerySchema, u as ORG_SETTINGS_NAMESPACES$1, ut as safeRedirectPath, v as adminCreateTaskSchema, vt as sessionReconcileRequestSchema, wt as updateAgentRuntimeConfigSchema, x as agentPinnedMessageSchema$1, xt as submitQuestionAnswerSchema, y as adminUpdateTaskSchema, yt as sessionStateMessageSchema } from "./dist-
|
|
5
|
+
import { $ as loginSchema, A as createAgentSchema, At as updateTaskStatusSchema, B as githubCallbackQuerySchema, C as agentTypeSchema$1, Ct as updateAdapterConfigSchema, D as contextTreeSnapshotSchema, Dt as updateClientCapabilitiesSchema, E as connectTokenExchangeSchema, Et as updateChatSchema, F as createTaskSchema, G as inboxDeliverFrameSchema$1, H as githubStartQuerySchema, I as defaultRuntimeConfigPayload, J as isRedactedEnvValue, K as inboxPollQuerySchema, L as delegateFeishuUserSchema, M as createMeChatSchema, N as createMemberSchema, O as createAdapterConfigSchema, Ot as updateMemberSchema, P as createOrgFromMeSchema, Q as listMeChatsQuerySchema, R as dryRunAgentRuntimeConfigSchema, S as agentRuntimeConfigPayloadSchema$1, St as taskListQuerySchema, T as clientRegisterSchema, Tt as updateAgentSchema, U as imageInlineContentSchema, V as githubDevCallbackQuerySchema, W as inboxAckFrameSchema, X as joinByInvitationSchema, Y as isReservedAgentName$1, Z as linkTaskChatSchema, _ as addParticipantSchema, _t as sessionEventSchema$1, a as AGENT_STATUSES, b as agentBindRequestSchema, bt as stripCode, ct as refreshTokenSchema, d as TASK_CREATOR_TYPES, et as messageSourceSchema$1, f as TASK_HEALTH_SIGNALS, ft as selfServiceFeishuBotSchema, g as addMeChatParticipantsSchema, gt as sessionEventMessageSchema, h as WS_AUTH_FRAME_TIMEOUT_MS, ht as sessionCompletionMessageSchema, i as AGENT_SOURCES, it as patchOnboardingSchema, j as createChatSchema, jt as wsAuthFrameSchema, k as createAdapterMappingSchema, kt as updateOrganizationSchema, l as MENTION_REGEX, lt as runtimeStateMessageSchema, m as TASK_TERMINAL_STATUSES, mt as sendToAgentSchema, n as AGENT_NAME_REGEX$1, nt as onboardingEventSchema, o as AGENT_TYPES, p as TASK_STATUSES, pt as sendMessageSchema, q as isOrgSettingNamespace, r as AGENT_SELECTOR_HEADER$1, rt as paginationQuerySchema, s as AGENT_VISIBILITY, st as rebindAgentSchema, t as AGENT_BIND_REJECT_REASONS, tt as notificationQuerySchema, u as ORG_SETTINGS_NAMESPACES$1, ut as safeRedirectPath, v as adminCreateTaskSchema, vt as sessionReconcileRequestSchema, wt as updateAgentRuntimeConfigSchema, x as agentPinnedMessageSchema$1, xt as submitQuestionAnswerSchema, y as adminUpdateTaskSchema, yt as sessionStateMessageSchema } from "./dist-DHHd2dar.mjs";
|
|
6
6
|
import { a as ConflictError, c as UnauthorizedError, i as ClientUserMismatchError$1, l as organizations, n as BadRequestError, o as ForbiddenError, r as ClientOrgMismatchError$1, s as NotFoundError, t as AppError, u as users } from "./errors-CF5evtJt-B0NTIVPt.mjs";
|
|
7
7
|
import { n as init_esm, r as trace, t as esm_exports } from "./esm-iadMkGbV.mjs";
|
|
8
|
-
import { $ as pendingQuestions, A as heartbeatClient, B as listAgentsWithRuntime, C as findOrCreateDirectChat, D as getClient, E as getChatDetail, F as joinChat, G as listClientsForOrgAdmin, H as listChats, I as leaveAsParticipant, J as markStaleAgents, K as listMessages, L as leaveChat, M as inboxEntries, N as invalidateChatAudience, O as getOnlineCount, P as joinAsParticipant, Q as notifyRecipients, R as listActiveAgentsPinnedToClient, S as ensureParticipant$1, T as getCachedAudience, U as listChatsForMember, V as listChatParticipantsWithNames, W as listClients, X as members, Y as markSupersededByChat, Z as messages, _ as createNotifier, _t as updateClientCapabilities, a as agents, at as removeParticipant, b as editMessage, c as bindAgent, ct as retireClient, d as chats, dt as serverInstances, et as recomputeChatWatchers, f as claimClient, ft as setOffline, g as createChat, gt as unbindAgent, h as clients, ht as touchAgent, i as agentVisibilityCondition, it as registerClient, j as heartbeatInstance, k as getPresence, l as chatParticipants, lt as sendMessage, m as cleanupStalePresence, mt as submitAnswer, n as agentChatSessions, nt as recomputeWatchersForMember, o as assertClientOwner, ot as resetActivity, p as cleanupStaleClients, pt as setRuntimeState, r as agentPresence, rt as registerChatMessageDispatcher, s as assertParticipant, st as resolveChatMembership, t as addParticipant, tt as recomputeWatchersForAgent, u as chatSubscriptions, ut as sendToAgent$1, v as deriveAuthState, vt as upsertSessionState, w as getActivityOverview, x as ensureCanJoin, y as disconnectClient, z as listAgentsManagedByUser } from "./client-
|
|
8
|
+
import { $ as pendingQuestions, A as heartbeatClient, B as listAgentsWithRuntime, C as findOrCreateDirectChat, D as getClient, E as getChatDetail, F as joinChat, G as listClientsForOrgAdmin, H as listChats, I as leaveAsParticipant, J as markStaleAgents, K as listMessages, L as leaveChat, M as inboxEntries, N as invalidateChatAudience, O as getOnlineCount, P as joinAsParticipant, Q as notifyRecipients, R as listActiveAgentsPinnedToClient, S as ensureParticipant$1, T as getCachedAudience, U as listChatsForMember, V as listChatParticipantsWithNames, W as listClients, X as members, Y as markSupersededByChat, Z as messages, _ as createNotifier, _t as updateClientCapabilities, a as agents, at as removeParticipant, b as editMessage, c as bindAgent, ct as retireClient, d as chats, dt as serverInstances, et as recomputeChatWatchers, f as claimClient, ft as setOffline, g as createChat, gt as unbindAgent, h as clients, ht as touchAgent, i as agentVisibilityCondition, it as registerClient, j as heartbeatInstance, k as getPresence, l as chatParticipants, lt as sendMessage, m as cleanupStalePresence, mt as submitAnswer, n as agentChatSessions, nt as recomputeWatchersForMember, o as assertClientOwner, ot as resetActivity, p as cleanupStaleClients, pt as setRuntimeState, r as agentPresence, rt as registerChatMessageDispatcher, s as assertParticipant, st as resolveChatMembership, t as addParticipant, tt as recomputeWatchersForAgent, u as chatSubscriptions, ut as sendToAgent$1, v as deriveAuthState, vt as upsertSessionState, w as getActivityOverview, x as ensureCanJoin, y as disconnectClient, z as listAgentsManagedByUser } from "./client-DHCSQ8kg-DjlSmE9q.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, u as uuidv7 } from "./invitation-Bg0TRiyx-BsZH4GCS.mjs";
|
|
10
10
|
import { createRequire } from "node:module";
|
|
11
11
|
import { ZodError, z } from "zod";
|
|
@@ -1365,26 +1365,51 @@ z.object({
|
|
|
1365
1365
|
* 2. Add a key to `ORG_SETTINGS_NAMESPACES`.
|
|
1366
1366
|
* 3. Done. No DB migration, no new API route.
|
|
1367
1367
|
*/
|
|
1368
|
-
const
|
|
1368
|
+
const SCP_LIKE_SSH_RE = /^(?:[A-Za-z0-9_.-]+@)?[A-Za-z0-9.-]+:(?!\d+(?:\/|$))[^/:@\s][^:@\s]*$/;
|
|
1369
|
+
function isScpLikeSshUrl(value) {
|
|
1370
|
+
if (value.includes("://")) return false;
|
|
1371
|
+
return SCP_LIKE_SSH_RE.test(value);
|
|
1372
|
+
}
|
|
1373
|
+
const repoUrlSchema = z.string().min(1).superRefine((value, ctx) => {
|
|
1374
|
+
if (isScpLikeSshUrl(value)) return;
|
|
1375
|
+
let url;
|
|
1369
1376
|
try {
|
|
1370
|
-
|
|
1377
|
+
url = new URL(value);
|
|
1371
1378
|
} catch {
|
|
1372
|
-
|
|
1379
|
+
ctx.addIssue({
|
|
1380
|
+
code: z.ZodIssueCode.custom,
|
|
1381
|
+
message: "Repo URL must be HTTPS, SSH (ssh://...), or scp-like (git@host:path)."
|
|
1382
|
+
});
|
|
1383
|
+
return;
|
|
1373
1384
|
}
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
return
|
|
1385
|
+
if (url.protocol !== "https:" && url.protocol !== "ssh:") {
|
|
1386
|
+
ctx.addIssue({
|
|
1387
|
+
code: z.ZodIssueCode.custom,
|
|
1388
|
+
message: "Repo URL must use HTTPS or SSH."
|
|
1389
|
+
});
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
if (url.password.length > 0) {
|
|
1393
|
+
ctx.addIssue({
|
|
1394
|
+
code: z.ZodIssueCode.custom,
|
|
1395
|
+
message: "Repo URL must not include credentials."
|
|
1396
|
+
});
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
if (url.protocol === "https:" && url.username.length > 0) {
|
|
1400
|
+
ctx.addIssue({
|
|
1401
|
+
code: z.ZodIssueCode.custom,
|
|
1402
|
+
message: "Repo URL must not include credentials."
|
|
1403
|
+
});
|
|
1404
|
+
return;
|
|
1380
1405
|
}
|
|
1381
|
-
}
|
|
1406
|
+
});
|
|
1382
1407
|
const orgContextTreeStorageSchema = z.object({
|
|
1383
|
-
repo:
|
|
1408
|
+
repo: repoUrlSchema.optional(),
|
|
1384
1409
|
branch: z.string().default("main")
|
|
1385
1410
|
});
|
|
1386
1411
|
const orgContextTreeInputSchema = z.object({
|
|
1387
|
-
repo:
|
|
1412
|
+
repo: repoUrlSchema.nullish(),
|
|
1388
1413
|
branch: z.string().min(1).nullish()
|
|
1389
1414
|
});
|
|
1390
1415
|
const orgContextTreeOutputSchema = z.object({
|
|
@@ -1398,11 +1423,11 @@ const orgGithubIntegrationOutputSchema = z.object({
|
|
|
1398
1423
|
webhookUrl: z.string()
|
|
1399
1424
|
});
|
|
1400
1425
|
const orgSourceReposStorageSchema = z.object({ repos: z.array(z.object({
|
|
1401
|
-
url:
|
|
1426
|
+
url: repoUrlSchema,
|
|
1402
1427
|
defaultBranch: z.string().optional()
|
|
1403
1428
|
})).default([]) });
|
|
1404
1429
|
const orgSourceReposInputSchema = z.object({ repos: z.array(z.object({
|
|
1405
|
-
url:
|
|
1430
|
+
url: repoUrlSchema,
|
|
1406
1431
|
defaultBranch: z.string().min(1).optional()
|
|
1407
1432
|
})).optional() });
|
|
1408
1433
|
const orgSourceReposOutputSchema = z.object({ repos: z.array(z.object({
|
|
@@ -3029,8 +3054,65 @@ For content with quotes, \`$\`, backticks, or newlines, prefer stdin to avoid sh
|
|
|
3029
3054
|
const DEFAULT_CLONE_TIMEOUT_MS = 300 * 1e3;
|
|
3030
3055
|
const FETCH_REFSPEC = "+refs/heads/*:refs/remotes/origin/*";
|
|
3031
3056
|
const SESSION_BRANCH_PREFIX = "hub-session";
|
|
3057
|
+
/**
|
|
3058
|
+
* Hash a repo URL into the mirror directory name.
|
|
3059
|
+
*
|
|
3060
|
+
* The hash is computed over a *canonical* form (host + path, lowercased
|
|
3061
|
+
* host, no `.git` suffix, no leading/trailing slash, no protocol, no
|
|
3062
|
+
* `user@` prefix), so the same upstream repo addressed via any of the
|
|
3063
|
+
* accepted forms (HTTPS, `ssh://`, or scp-like) all land in the same
|
|
3064
|
+
* mirror dir. That matters because:
|
|
3065
|
+
* - admins may write any of those three forms into `source_repos[].url`
|
|
3066
|
+
* (the schema accepts all of them)
|
|
3067
|
+
* - the `fetchOrigin` fallback below transparently swaps protocols when
|
|
3068
|
+
* credentials are missing — without canonical hashing, the fallback
|
|
3069
|
+
* would silently maintain a second mirror dir
|
|
3070
|
+
*
|
|
3071
|
+
* Migration cost: pre-existing mirrors created before this change use the
|
|
3072
|
+
* raw-URL hash and will be orphaned after the upgrade. `gcMirrors` removes
|
|
3073
|
+
* them on the next run; the next fetch repopulates the canonical-keyed
|
|
3074
|
+
* mirror. No data loss — mirror is a cache, not state.
|
|
3075
|
+
*/
|
|
3032
3076
|
function hashUrl(url) {
|
|
3033
|
-
return createHash("sha256").update(url).digest("hex").slice(0, 32);
|
|
3077
|
+
return createHash("sha256").update(canonicalizeRepoUrl(url)).digest("hex").slice(0, 32);
|
|
3078
|
+
}
|
|
3079
|
+
/**
|
|
3080
|
+
* Reduce a repo URL to `<host[:port]>/<path-without-.git>`. Used as the
|
|
3081
|
+
* input to the mirror dir hash and exposed for unit testing.
|
|
3082
|
+
*
|
|
3083
|
+
* `https://github.com/foo/bar.git` → `github.com/foo/bar`
|
|
3084
|
+
* `git@github.com:foo/bar.git` → `github.com/foo/bar`
|
|
3085
|
+
* `ssh://git@github.com/foo/bar.git` → `github.com/foo/bar`
|
|
3086
|
+
* `ssh://git@gitlab.example.com:2222/x/y` → `gitlab.example.com:2222/x/y`
|
|
3087
|
+
*
|
|
3088
|
+
* Falls back to the raw input for un-parseable strings — better to keep
|
|
3089
|
+
* a stable mirror than to throw mid-bootstrap.
|
|
3090
|
+
*/
|
|
3091
|
+
function canonicalizeRepoUrl(url) {
|
|
3092
|
+
if (!url.includes("://")) {
|
|
3093
|
+
const m = url.match(/^(?:[A-Za-z0-9_.-]+@)?([A-Za-z0-9.-]+):([^/@:\s][^@:\s]*)$/);
|
|
3094
|
+
const host = m?.[1];
|
|
3095
|
+
const rawPath = m?.[2];
|
|
3096
|
+
if (host && rawPath && !/^\d+(?:\/|$)/.test(rawPath)) {
|
|
3097
|
+
const path = normalizePath(rawPath);
|
|
3098
|
+
return `${host.toLowerCase()}/${path}`;
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
try {
|
|
3102
|
+
const parsed = new URL(url);
|
|
3103
|
+
const host = parsed.hostname.toLowerCase();
|
|
3104
|
+
return `${parsed.port === "" || parsed.protocol === "https:" && parsed.port === "443" || parsed.protocol === "ssh:" && parsed.port === "22" ? host : `${host}:${parsed.port}`}/${normalizePath(parsed.pathname)}`;
|
|
3105
|
+
} catch {
|
|
3106
|
+
return url;
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
/**
|
|
3110
|
+
* Strip leading slashes, trailing slashes, and a trailing `.git`. Used by
|
|
3111
|
+
* `canonicalizeRepoUrl` so that `…/foo/bar`, `…/foo/bar/`, and `…/foo/bar.git`
|
|
3112
|
+
* all collapse to the same canonical path (and therefore the same mirror dir).
|
|
3113
|
+
*/
|
|
3114
|
+
function normalizePath(rawPath) {
|
|
3115
|
+
return rawPath.replace(/^\/+/, "").replace(/\/+$/, "").replace(/\.git$/i, "");
|
|
3034
3116
|
}
|
|
3035
3117
|
function shortHash(input) {
|
|
3036
3118
|
return createHash("sha256").update(input).digest("hex").slice(0, 8);
|
|
@@ -3067,10 +3149,14 @@ function createGitMirrorManager(opts) {
|
|
|
3067
3149
|
}
|
|
3068
3150
|
async function git(args, cwd, timeoutMs, env) {
|
|
3069
3151
|
const start = Date.now();
|
|
3152
|
+
const finalEnv = {
|
|
3153
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
3154
|
+
...env ?? process.env
|
|
3155
|
+
};
|
|
3070
3156
|
return await new Promise((resolveExec, rejectExec) => {
|
|
3071
3157
|
const proc = spawn("git", args, {
|
|
3072
3158
|
cwd: cwd ?? void 0,
|
|
3073
|
-
env:
|
|
3159
|
+
env: finalEnv,
|
|
3074
3160
|
stdio: [
|
|
3075
3161
|
"ignore",
|
|
3076
3162
|
"pipe",
|
|
@@ -3114,6 +3200,116 @@ function createGitMirrorManager(opts) {
|
|
|
3114
3200
|
}
|
|
3115
3201
|
}
|
|
3116
3202
|
/**
|
|
3203
|
+
* `git fetch --prune origin` with one-shot bidirectional protocol fallback.
|
|
3204
|
+
*
|
|
3205
|
+
* Decides direction from `originUrl`'s protocol:
|
|
3206
|
+
* - HTTPS origin → on credential-shaped failure, retry as SSH
|
|
3207
|
+
* - SSH origin → on credential-shaped failure, retry as HTTPS
|
|
3208
|
+
* The fallback fires only when **all** hold:
|
|
3209
|
+
* 1. The first attempt failed with a credential-shaped error in the
|
|
3210
|
+
* direction-appropriate sense (`isLikelyHttpsAuthFailure` /
|
|
3211
|
+
* `isLikelySshAuthFailure`). Network failures, missing-ref errors,
|
|
3212
|
+
* and TLS/host-key surprises propagate as-is — those won't be cured
|
|
3213
|
+
* by switching transports and silently retrying would mask real bugs.
|
|
3214
|
+
* 2. The origin URL maps to a usable peer-protocol form
|
|
3215
|
+
* (see `httpsToSshBaseRewrite` / `sshToHttpsBaseRewrite`). Custom
|
|
3216
|
+
* ports beyond the protocol default disable the rewrite — there's no
|
|
3217
|
+
* universal mapping between `https://host:8443` and an SSH peer.
|
|
3218
|
+
*
|
|
3219
|
+
* Implementation: the retry uses `git -c url.<peer-base>.insteadOf=<origin-base>`
|
|
3220
|
+
* — git resolves origin's URL through that rewrite for the duration of
|
|
3221
|
+
* one subprocess and never persists anything to disk. The mirror's
|
|
3222
|
+
* `remote.origin.url` stays as configured, so the next fetch starts back
|
|
3223
|
+
* at the original protocol unless this fallback fires again.
|
|
3224
|
+
*/
|
|
3225
|
+
/**
|
|
3226
|
+
* `remote set-head --auto` is a remote-talking op too — under the same
|
|
3227
|
+
* credential rules as `fetch`. If we don't apply the same fallback,
|
|
3228
|
+
* mirrors whose HTTPS creds are missing end up with `refs/remotes/origin/HEAD`
|
|
3229
|
+
* unset, which then breaks `resolveBase()` for callers without an explicit
|
|
3230
|
+
* `ref` (the default-branch lookup throws).
|
|
3231
|
+
*
|
|
3232
|
+
* Strategy mirrors `fetchOrigin`: try the configured protocol first, fall
|
|
3233
|
+
* back to the peer protocol once via `-c url.<peer>.insteadOf=<origin>`.
|
|
3234
|
+
* Returns true on either success, false if both attempts failed (which
|
|
3235
|
+
* mirrors the historical `gitOk` semantics — non-fatal). No `GitMirrorAuthError`
|
|
3236
|
+
* here: callers that need origin/HEAD already get a clear
|
|
3237
|
+
* `GitMirrorError("Cannot resolve default branch …")` if both attempts fail.
|
|
3238
|
+
*/
|
|
3239
|
+
async function setHeadAuto(mirrorPath, originUrl) {
|
|
3240
|
+
if (await gitOk([
|
|
3241
|
+
"remote",
|
|
3242
|
+
"set-head",
|
|
3243
|
+
"origin",
|
|
3244
|
+
"--auto"
|
|
3245
|
+
], mirrorPath, 3e4)) return true;
|
|
3246
|
+
const direction = pickFallbackDirection(originUrl);
|
|
3247
|
+
if (!direction) return false;
|
|
3248
|
+
return await gitOk([
|
|
3249
|
+
"-c",
|
|
3250
|
+
`url.${direction.peerBase}.insteadOf=${direction.originBase}`,
|
|
3251
|
+
"remote",
|
|
3252
|
+
"set-head",
|
|
3253
|
+
"origin",
|
|
3254
|
+
"--auto"
|
|
3255
|
+
], mirrorPath, 3e4);
|
|
3256
|
+
}
|
|
3257
|
+
async function readOriginUrl(mirrorPath) {
|
|
3258
|
+
try {
|
|
3259
|
+
const { stdout } = await git([
|
|
3260
|
+
"config",
|
|
3261
|
+
"--get",
|
|
3262
|
+
"remote.origin.url"
|
|
3263
|
+
], mirrorPath, 1e4);
|
|
3264
|
+
return stdout.trim() || null;
|
|
3265
|
+
} catch {
|
|
3266
|
+
return null;
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
async function fetchOrigin(mirrorPath, originUrl) {
|
|
3270
|
+
const direction = pickFallbackDirection(originUrl);
|
|
3271
|
+
try {
|
|
3272
|
+
const { elapsedMs } = await git([
|
|
3273
|
+
"fetch",
|
|
3274
|
+
"--prune",
|
|
3275
|
+
"origin"
|
|
3276
|
+
], mirrorPath, cloneTimeoutMs);
|
|
3277
|
+
return {
|
|
3278
|
+
elapsedMs,
|
|
3279
|
+
usedFallback: false
|
|
3280
|
+
};
|
|
3281
|
+
} catch (primaryErr) {
|
|
3282
|
+
const primaryMessage = primaryErr instanceof Error ? primaryErr.message : String(primaryErr);
|
|
3283
|
+
if (!direction || !direction.shouldRetry(primaryMessage)) throw primaryErr;
|
|
3284
|
+
log?.info({
|
|
3285
|
+
gitUrl: originUrl,
|
|
3286
|
+
fromProtocol: direction.fromProtocol,
|
|
3287
|
+
toProtocol: direction.toProtocol,
|
|
3288
|
+
peerBase: direction.peerBase
|
|
3289
|
+
}, "fetch failed with credential-shaped error; retrying with peer-protocol insteadOf rewrite");
|
|
3290
|
+
try {
|
|
3291
|
+
const { elapsedMs } = await git([
|
|
3292
|
+
"-c",
|
|
3293
|
+
`url.${direction.peerBase}.insteadOf=${direction.originBase}`,
|
|
3294
|
+
"fetch",
|
|
3295
|
+
"--prune",
|
|
3296
|
+
"origin"
|
|
3297
|
+
], mirrorPath, cloneTimeoutMs);
|
|
3298
|
+
log?.info({
|
|
3299
|
+
gitUrl: originUrl,
|
|
3300
|
+
toProtocol: direction.toProtocol
|
|
3301
|
+
}, "protocol-fallback fetch succeeded");
|
|
3302
|
+
return {
|
|
3303
|
+
elapsedMs,
|
|
3304
|
+
usedFallback: true
|
|
3305
|
+
};
|
|
3306
|
+
} catch (peerErr) {
|
|
3307
|
+
const peerMessage = peerErr instanceof Error ? peerErr.message : String(peerErr);
|
|
3308
|
+
throw new GitMirrorAuthError(`Could not fetch ${originUrl} over ${direction.fromProtocol.toUpperCase()} or ${direction.toProtocol.toUpperCase()}. ${direction.fromProtocol.toUpperCase()} attempt failed: ${truncate(primaryMessage)} ${direction.toProtocol.toUpperCase()} retry (${direction.peerBase}) failed: ${truncate(peerMessage)}`);
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
/**
|
|
3117
3313
|
* Bring the mirror's config to the invariant expected by this module:
|
|
3118
3314
|
* fetch refspec = `+refs/heads/*:refs/remotes/origin/*`, `remote.origin.mirror`
|
|
3119
3315
|
* absent, `refs/remotes/origin/HEAD` resolvable.
|
|
@@ -3181,17 +3377,8 @@ function createGitMirrorManager(opts) {
|
|
|
3181
3377
|
migrated = true;
|
|
3182
3378
|
}
|
|
3183
3379
|
if (migrated) {
|
|
3184
|
-
await
|
|
3185
|
-
|
|
3186
|
-
"--prune",
|
|
3187
|
-
"origin"
|
|
3188
|
-
], mirrorPath, cloneTimeoutMs);
|
|
3189
|
-
await gitOk([
|
|
3190
|
-
"remote",
|
|
3191
|
-
"set-head",
|
|
3192
|
-
"origin",
|
|
3193
|
-
"--auto"
|
|
3194
|
-
], mirrorPath, 3e4);
|
|
3380
|
+
await fetchOrigin(mirrorPath, url);
|
|
3381
|
+
await setHeadAuto(mirrorPath, url);
|
|
3195
3382
|
log?.info({ gitUrl: url }, "mirror config migrated");
|
|
3196
3383
|
}
|
|
3197
3384
|
return { migrated };
|
|
@@ -3221,17 +3408,8 @@ function createGitMirrorManager(opts) {
|
|
|
3221
3408
|
"remote.origin.fetch",
|
|
3222
3409
|
FETCH_REFSPEC
|
|
3223
3410
|
], mirrorPath, 1e4);
|
|
3224
|
-
await
|
|
3225
|
-
|
|
3226
|
-
"--prune",
|
|
3227
|
-
"origin"
|
|
3228
|
-
], mirrorPath, cloneTimeoutMs);
|
|
3229
|
-
await gitOk([
|
|
3230
|
-
"remote",
|
|
3231
|
-
"set-head",
|
|
3232
|
-
"origin",
|
|
3233
|
-
"--auto"
|
|
3234
|
-
], mirrorPath, 3e4);
|
|
3411
|
+
await fetchOrigin(mirrorPath, url);
|
|
3412
|
+
await setHeadAuto(mirrorPath, url);
|
|
3235
3413
|
}
|
|
3236
3414
|
async function branchExists(mirrorPath, branchName) {
|
|
3237
3415
|
return await gitOk([
|
|
@@ -3257,6 +3435,21 @@ function createGitMirrorManager(opts) {
|
|
|
3257
3435
|
"--quiet",
|
|
3258
3436
|
"refs/remotes/origin/HEAD"
|
|
3259
3437
|
], mirrorPath, 1e4)) return "refs/remotes/origin/HEAD";
|
|
3438
|
+
const url = await readOriginUrl(mirrorPath);
|
|
3439
|
+
if (url && await setHeadAuto(mirrorPath, url)) {
|
|
3440
|
+
if (await gitOk([
|
|
3441
|
+
"rev-parse",
|
|
3442
|
+
"--verify",
|
|
3443
|
+
"--quiet",
|
|
3444
|
+
"refs/remotes/origin/HEAD"
|
|
3445
|
+
], mirrorPath, 1e4)) {
|
|
3446
|
+
log?.info({
|
|
3447
|
+
mirrorPath,
|
|
3448
|
+
gitUrl: url
|
|
3449
|
+
}, "origin/HEAD self-healed via setHeadAuto");
|
|
3450
|
+
return "refs/remotes/origin/HEAD";
|
|
3451
|
+
}
|
|
3452
|
+
}
|
|
3260
3453
|
throw new GitMirrorError("Cannot resolve default branch: refs/remotes/origin/HEAD is missing. Re-run with an explicit `ref`.");
|
|
3261
3454
|
}
|
|
3262
3455
|
if (looksLikeCommitSha(ref)) {
|
|
@@ -3325,16 +3518,12 @@ function createGitMirrorManager(opts) {
|
|
|
3325
3518
|
const path = mirrorDir(url);
|
|
3326
3519
|
if (!existsSync(join(path, "HEAD"))) throw new GitMirrorError(`Cannot fetch — no mirror exists for "${url}"`);
|
|
3327
3520
|
try {
|
|
3328
|
-
const { elapsedMs } = await
|
|
3329
|
-
"fetch",
|
|
3330
|
-
"--prune",
|
|
3331
|
-
"origin"
|
|
3332
|
-
], path, cloneTimeoutMs);
|
|
3521
|
+
const { elapsedMs } = await fetchOrigin(path, url);
|
|
3333
3522
|
return { elapsedMs };
|
|
3334
3523
|
} catch (err) {
|
|
3335
3524
|
log?.warn({
|
|
3336
3525
|
gitUrl: url,
|
|
3337
|
-
errorCode: err instanceof GitMirrorError ? "git-failed" : "unknown",
|
|
3526
|
+
errorCode: err instanceof GitMirrorAuthError ? "auth-failed" : err instanceof GitMirrorTimeoutError ? "timeout" : err instanceof GitMirrorError ? "git-failed" : "unknown",
|
|
3338
3527
|
stderr: err instanceof Error ? err.message.slice(0, 1024) : String(err).slice(0, 1024)
|
|
3339
3528
|
}, "mirror fetch failed");
|
|
3340
3529
|
throw err;
|
|
@@ -3472,6 +3661,166 @@ var GitMirrorWorktreeConflictError = class extends GitMirrorError {
|
|
|
3472
3661
|
}
|
|
3473
3662
|
};
|
|
3474
3663
|
/**
|
|
3664
|
+
* Thrown when both the HTTPS fetch and the SSH fallback fail. The message
|
|
3665
|
+
* carries trimmed stderr from both attempts so the operator can see whether
|
|
3666
|
+
* the host's HTTPS credentials are missing, the SSH key is missing, or both.
|
|
3667
|
+
*/
|
|
3668
|
+
var GitMirrorAuthError = class extends GitMirrorError {
|
|
3669
|
+
constructor(message) {
|
|
3670
|
+
super(message);
|
|
3671
|
+
this.name = "GitMirrorAuthError";
|
|
3672
|
+
}
|
|
3673
|
+
};
|
|
3674
|
+
/**
|
|
3675
|
+
* Heuristic for HTTPS-side credential failures. Matches the indicators git
|
|
3676
|
+
* itself emits over libcurl / git-credential helpers, regardless of platform.
|
|
3677
|
+
*
|
|
3678
|
+
* Negative space (intentionally NOT matched): network errors
|
|
3679
|
+
* (`Could not resolve host`, `connection refused`), repo errors
|
|
3680
|
+
* (`Repository not found`, `couldn't find remote ref`), TLS errors
|
|
3681
|
+
* (`SSL certificate problem`). Those won't be cured by switching transports
|
|
3682
|
+
* and silently retrying would mask the real bug.
|
|
3683
|
+
*
|
|
3684
|
+
* Exported for unit testing.
|
|
3685
|
+
*/
|
|
3686
|
+
function isLikelyHttpsAuthFailure(message) {
|
|
3687
|
+
if (!message) return false;
|
|
3688
|
+
return /could not read Username/i.test(message) || /could not read Password/i.test(message) || /Authentication failed/i.test(message) || /terminal prompts disabled/i.test(message) || /HTTP\s*(?:Basic:\s*)?Access denied/i.test(message) || /\bHTTP[/ ]?(1\.[01]|2|2\.0)?\s*40[13]\b/i.test(message) || /\bfatal:\s*unable to access\b.*\b(401|403)\b/i.test(message) || /\bremote: Invalid username or password\b/i.test(message);
|
|
3689
|
+
}
|
|
3690
|
+
/**
|
|
3691
|
+
* Heuristic for SSH-side credential failures (no key on disk, key not
|
|
3692
|
+
* accepted by remote, agent has nothing usable, host key mismatch).
|
|
3693
|
+
*
|
|
3694
|
+
* Negative space (intentionally NOT matched): SSH-level network errors
|
|
3695
|
+
* (`Could not resolve hostname`, `Connection refused`, `Connection timed out`).
|
|
3696
|
+
* Those are network reachability issues — switching to HTTPS won't help
|
|
3697
|
+
* unless the network policy specifically blocks port 22, which is rare
|
|
3698
|
+
* enough that we'd rather surface the original error than guess.
|
|
3699
|
+
*
|
|
3700
|
+
* Exported for unit testing.
|
|
3701
|
+
*/
|
|
3702
|
+
function isLikelySshAuthFailure(message) {
|
|
3703
|
+
if (!message) return false;
|
|
3704
|
+
return /Permission denied\s*(?:\(|,)/i.test(message) || /Could not read from remote repository/i.test(message) || /Host key verification failed/i.test(message) || /no matching host key type/i.test(message) || /no mutual signature algorithm/i.test(message);
|
|
3705
|
+
}
|
|
3706
|
+
/**
|
|
3707
|
+
* Map an HTTPS git URL to the `insteadOf` rewrite needed to make git resolve
|
|
3708
|
+
* it through SSH. Returns *base* strings (suitable for
|
|
3709
|
+
* `git -c url.<sshBase>.insteadOf=<httpsBase>`) — git's `insteadOf` is a
|
|
3710
|
+
* prefix match, so we only need the host segment.
|
|
3711
|
+
*
|
|
3712
|
+
* `https://github.com/owner/repo.git` → `git@github.com:` / `https://github.com/`
|
|
3713
|
+
*
|
|
3714
|
+
* Returns `null` for inputs that should NOT trigger fallback:
|
|
3715
|
+
* - non-HTTPS URLs (already SSH, `git://`, `file://`, etc.)
|
|
3716
|
+
* - URLs with embedded credentials (schema rejects these on input;
|
|
3717
|
+
* belt-and-braces — never silently downgrade auth strength)
|
|
3718
|
+
* - HTTPS URLs with a non-default port — there is no portable mapping to
|
|
3719
|
+
* an SSH port (HTTPS:8443 ↔ SSH:???), so we refuse to guess
|
|
3720
|
+
* - URLs that fail to parse
|
|
3721
|
+
*
|
|
3722
|
+
* Exported for unit testing.
|
|
3723
|
+
*/
|
|
3724
|
+
function httpsToSshBaseRewrite(url) {
|
|
3725
|
+
if (!url || !/^https:\/\//i.test(url)) return null;
|
|
3726
|
+
let parsed;
|
|
3727
|
+
try {
|
|
3728
|
+
parsed = new URL(url);
|
|
3729
|
+
} catch {
|
|
3730
|
+
return null;
|
|
3731
|
+
}
|
|
3732
|
+
if (parsed.protocol !== "https:") return null;
|
|
3733
|
+
if (parsed.username.length > 0 || parsed.password.length > 0) return null;
|
|
3734
|
+
if (!parsed.hostname) return null;
|
|
3735
|
+
if (parsed.port && parsed.port !== "443") return null;
|
|
3736
|
+
return {
|
|
3737
|
+
httpsBase: `https://${parsed.hostname}/`,
|
|
3738
|
+
sshBase: `git@${parsed.hostname}:`
|
|
3739
|
+
};
|
|
3740
|
+
}
|
|
3741
|
+
/**
|
|
3742
|
+
* Map an SSH git URL (either `ssh://` or scp-like `[user@]host:path`) to the
|
|
3743
|
+
* `insteadOf` rewrite for resolving via HTTPS. Mirror of
|
|
3744
|
+
* `httpsToSshBaseRewrite`.
|
|
3745
|
+
*
|
|
3746
|
+
* `git@github.com:owner/repo.git` → `git@github.com:` / `https://github.com/`
|
|
3747
|
+
* `ssh://git@github.com/owner/repo.git` → `ssh://git@github.com/` / `https://github.com/`
|
|
3748
|
+
* `ssh://git@gitlab.example.com:22/x/y.git` → `ssh://git@gitlab.example.com:22/` / `https://gitlab.example.com/`
|
|
3749
|
+
*
|
|
3750
|
+
* Returns `null` when:
|
|
3751
|
+
* - URL is not SSH-shaped
|
|
3752
|
+
* - URL has an embedded password (`user:pass@`)
|
|
3753
|
+
* - SSH URL has a non-default port (≠ 22) — no portable mapping to HTTPS
|
|
3754
|
+
*
|
|
3755
|
+
* Exported for unit testing.
|
|
3756
|
+
*/
|
|
3757
|
+
function sshToHttpsBaseRewrite(url) {
|
|
3758
|
+
if (!url) return null;
|
|
3759
|
+
if (!url.includes("://")) {
|
|
3760
|
+
const m = url.match(/^((?:[A-Za-z0-9_.-]+@)?)([A-Za-z0-9.-]+):([^/@:\s][^@:\s]*)$/);
|
|
3761
|
+
const userAt = m?.[1];
|
|
3762
|
+
const host = m?.[2];
|
|
3763
|
+
const path = m?.[3];
|
|
3764
|
+
if (userAt === void 0 || !host || !path) return null;
|
|
3765
|
+
if (/^\d+(?:\/|$)/.test(path)) return null;
|
|
3766
|
+
return {
|
|
3767
|
+
sshBase: `${userAt}${host}:`,
|
|
3768
|
+
httpsBase: `https://${host}/`
|
|
3769
|
+
};
|
|
3770
|
+
}
|
|
3771
|
+
let parsed;
|
|
3772
|
+
try {
|
|
3773
|
+
parsed = new URL(url);
|
|
3774
|
+
} catch {
|
|
3775
|
+
return null;
|
|
3776
|
+
}
|
|
3777
|
+
if (parsed.protocol !== "ssh:") return null;
|
|
3778
|
+
if (parsed.password.length > 0) return null;
|
|
3779
|
+
if (!parsed.hostname) return null;
|
|
3780
|
+
if (parsed.port && parsed.port !== "22") return null;
|
|
3781
|
+
const userAt = parsed.username.length > 0 ? `${parsed.username}@` : "";
|
|
3782
|
+
return {
|
|
3783
|
+
sshBase: parsed.port ? `ssh://${userAt}${parsed.hostname}:${parsed.port}/` : `ssh://${userAt}${parsed.hostname}/`,
|
|
3784
|
+
httpsBase: `https://${parsed.hostname}/`
|
|
3785
|
+
};
|
|
3786
|
+
}
|
|
3787
|
+
/**
|
|
3788
|
+
* Same shape as `SCP_LIKE_SSH_RE` in the shared schema — kept in sync so
|
|
3789
|
+
* what the schema accepts is exactly what we route through the SSH-side
|
|
3790
|
+
* fallback. Single source of truth would be ideal, but cross-package import
|
|
3791
|
+
* for a regex isn't worth the build coupling.
|
|
3792
|
+
*/
|
|
3793
|
+
const SCP_LIKE_RE = /^(?:[A-Za-z0-9_.-]+@)?[A-Za-z0-9.-]+:(?!\d+(?:\/|$))[^/:@\s][^:@\s]*$/;
|
|
3794
|
+
function pickFallbackDirection(originUrl) {
|
|
3795
|
+
if (/^https:\/\//i.test(originUrl)) {
|
|
3796
|
+
const r = httpsToSshBaseRewrite(originUrl);
|
|
3797
|
+
if (!r) return null;
|
|
3798
|
+
return {
|
|
3799
|
+
fromProtocol: "https",
|
|
3800
|
+
toProtocol: "ssh",
|
|
3801
|
+
originBase: r.httpsBase,
|
|
3802
|
+
peerBase: r.sshBase,
|
|
3803
|
+
shouldRetry: isLikelyHttpsAuthFailure
|
|
3804
|
+
};
|
|
3805
|
+
}
|
|
3806
|
+
if (/^ssh:\/\//i.test(originUrl) || SCP_LIKE_RE.test(originUrl)) {
|
|
3807
|
+
const r = sshToHttpsBaseRewrite(originUrl);
|
|
3808
|
+
if (!r) return null;
|
|
3809
|
+
return {
|
|
3810
|
+
fromProtocol: "ssh",
|
|
3811
|
+
toProtocol: "https",
|
|
3812
|
+
originBase: r.sshBase,
|
|
3813
|
+
peerBase: r.httpsBase,
|
|
3814
|
+
shouldRetry: isLikelySshAuthFailure
|
|
3815
|
+
};
|
|
3816
|
+
}
|
|
3817
|
+
return null;
|
|
3818
|
+
}
|
|
3819
|
+
function truncate(text, max = 512) {
|
|
3820
|
+
if (text.length <= max) return text;
|
|
3821
|
+
return `${text.slice(0, max)}…[truncated]`;
|
|
3822
|
+
}
|
|
3823
|
+
/**
|
|
3475
3824
|
* InputController — push-based async iterable bridge.
|
|
3476
3825
|
*
|
|
3477
3826
|
* Bridges imperative `push()` calls to the `AsyncIterable` that
|
|
@@ -8680,7 +9029,7 @@ async function onboardCreate(args) {
|
|
|
8680
9029
|
}
|
|
8681
9030
|
const runtimeAgent = args.type === "human" ? args.assistant : args.id;
|
|
8682
9031
|
if (args.feishuBotAppId && args.feishuBotAppSecret) {
|
|
8683
|
-
const { bindFeishuBot } = await import("./feishu-
|
|
9032
|
+
const { bindFeishuBot } = await import("./feishu-fLnwqCOs.mjs").then((n) => n.r);
|
|
8684
9033
|
const targetAgentUuid = args.type === "human" ? assistantUuid : primary.uuid;
|
|
8685
9034
|
if (!targetAgentUuid) print.line(`Warning: Cannot bind Feishu bot — no runtime agent available for "${args.id}".\n`);
|
|
8686
9035
|
else {
|
|
@@ -9893,7 +10242,7 @@ function createFeedbackHandler(config) {
|
|
|
9893
10242
|
return { handle };
|
|
9894
10243
|
}
|
|
9895
10244
|
//#endregion
|
|
9896
|
-
//#region ../server/dist/app-
|
|
10245
|
+
//#region ../server/dist/app-CkYiQS_D.mjs
|
|
9897
10246
|
var import_fastify_opentelemetry = /* @__PURE__ */ __toESM(require_fastify_opentelemetry(), 1);
|
|
9898
10247
|
init_esm();
|
|
9899
10248
|
var __defProp = Object.defineProperty;
|
|
@@ -15388,19 +15737,26 @@ async function deleteOrgSetting(db, orgId, namespace) {
|
|
|
15388
15737
|
await db.delete(organizationSettings).where(and(eq(organizationSettings.organizationId, orgId), eq(organizationSettings.namespace, namespace)));
|
|
15389
15738
|
}
|
|
15390
15739
|
/**
|
|
15391
|
-
* Resolve the caller's "primary org"
|
|
15392
|
-
*
|
|
15393
|
-
*
|
|
15394
|
-
*
|
|
15395
|
-
*
|
|
15740
|
+
* Resolve the caller's "primary org" for user-scoped routes that
|
|
15741
|
+
* historically didn't take an `:orgId` (e.g. `/context-tree/info`,
|
|
15742
|
+
* `/context-tree/snapshot`).
|
|
15743
|
+
*
|
|
15744
|
+
* Uses the same `pickDefaultMembership` helper that `/me` uses to compute
|
|
15745
|
+
* `defaultOrganizationId` (most-recently-active membership, id desc tie-break).
|
|
15746
|
+
* That guarantees the org `/me` reports as the default is the same org these
|
|
15747
|
+
* server-internal lookups read from — earlier the two sides used opposite
|
|
15748
|
+
* orderings (`/me` desc, this fn asc), so multi-org users saw `/info`
|
|
15749
|
+
* resolve to a different (often unconfigured) org than the one Team Settings
|
|
15750
|
+
* was edited for.
|
|
15396
15751
|
*
|
|
15397
|
-
* Returns `null` for users with no active membership.
|
|
15398
|
-
* "explicit org selector" is a future change-once-multi-org-clients-arrive
|
|
15399
|
-
* concern. (#7)
|
|
15752
|
+
* Returns `null` for users with no active membership.
|
|
15400
15753
|
*/
|
|
15401
15754
|
async function resolveUserPrimaryOrgId(db, userId) {
|
|
15402
|
-
|
|
15403
|
-
|
|
15755
|
+
return pickDefaultMembership(await db.select({
|
|
15756
|
+
id: members.id,
|
|
15757
|
+
organizationId: members.organizationId,
|
|
15758
|
+
createdAt: members.createdAt
|
|
15759
|
+
}).from(members).where(and(eq(members.userId, userId), eq(members.status, "active"))))?.organizationId ?? null;
|
|
15404
15760
|
}
|
|
15405
15761
|
async function contextTreeInfoRoutes(app) {
|
|
15406
15762
|
/**
|
|
@@ -16333,7 +16689,7 @@ function ghostNodeId(path) {
|
|
|
16333
16689
|
function toPosix(path) {
|
|
16334
16690
|
return sep === "/" ? path : path.split(sep).join("/");
|
|
16335
16691
|
}
|
|
16336
|
-
const querySchema = z.object({ window: z.enum([
|
|
16692
|
+
const querySchema$1 = z.object({ window: z.enum([
|
|
16337
16693
|
"1d",
|
|
16338
16694
|
"7d",
|
|
16339
16695
|
"30d"
|
|
@@ -16344,7 +16700,7 @@ async function contextTreeSnapshotRoutes(app) {
|
|
|
16344
16700
|
timeWindow: "1 minute",
|
|
16345
16701
|
keyGenerator: (request) => request.user?.userId ?? request.ip
|
|
16346
16702
|
} } }, async (request) => {
|
|
16347
|
-
const query = querySchema.parse(request.query);
|
|
16703
|
+
const query = querySchema$1.parse(request.query);
|
|
16348
16704
|
const { userId } = requireUser(request);
|
|
16349
16705
|
const orgId = await resolveUserPrimaryOrgId(app.db, userId);
|
|
16350
16706
|
const binding = orgId ? await getOrgContextTree(app.db, orgId) : {};
|
|
@@ -16482,7 +16838,7 @@ async function healthzRoutes(app) {
|
|
|
16482
16838
|
* `api/orgs/invitations.ts` (Class B, admin-gated).
|
|
16483
16839
|
*/
|
|
16484
16840
|
async function publicInvitationRoutes(app) {
|
|
16485
|
-
const { previewInvitation } = await import("./invitation-C299fxkP-
|
|
16841
|
+
const { previewInvitation } = await import("./invitation-C299fxkP-B89eqDos.mjs");
|
|
16486
16842
|
app.get("/:token/preview", async (request, reply) => {
|
|
16487
16843
|
if (!request.params.token) throw new UnauthorizedError("Token required");
|
|
16488
16844
|
const preview = await previewInvitation(app.db, request.params.token);
|
|
@@ -16662,7 +17018,7 @@ async function meRoutes(app) {
|
|
|
16662
17018
|
*/
|
|
16663
17019
|
app.get("/me/pinned-agents", async (request) => {
|
|
16664
17020
|
const { userId } = requireUser(request);
|
|
16665
|
-
const { listMyPinnedAgents } = await import("./client-
|
|
17021
|
+
const { listMyPinnedAgents } = await import("./client-WubcgX-W-B2bOvgJ1.mjs");
|
|
16666
17022
|
return listMyPinnedAgents(app.db, { userId });
|
|
16667
17023
|
});
|
|
16668
17024
|
/**
|
|
@@ -17136,6 +17492,34 @@ async function orgClientRoutes(app) {
|
|
|
17136
17492
|
}));
|
|
17137
17493
|
});
|
|
17138
17494
|
}
|
|
17495
|
+
const querySchema = z.object({ window: z.enum([
|
|
17496
|
+
"1d",
|
|
17497
|
+
"7d",
|
|
17498
|
+
"30d"
|
|
17499
|
+
]).optional() }).strict();
|
|
17500
|
+
async function orgContextTreeSnapshotRoutes(app) {
|
|
17501
|
+
app.get("/snapshot", { config: { rateLimit: {
|
|
17502
|
+
max: app.config.rateLimit?.contextTreeSnapshotMax ?? 6,
|
|
17503
|
+
timeWindow: "1 minute",
|
|
17504
|
+
keyGenerator: (request) => `${request.user?.userId ?? request.ip}:${orgIdParam(request.params) ?? "unknown-org"}`
|
|
17505
|
+
} } }, async (request) => {
|
|
17506
|
+
const query = querySchema.parse(request.query);
|
|
17507
|
+
const scope = await requireOrgMembership(request, app.db);
|
|
17508
|
+
const binding = await getOrgContextTree(app.db, scope.organizationId);
|
|
17509
|
+
const githubToken = contextTreeGithubTokenForRepo(binding.repo, app.config.contextTreeSync);
|
|
17510
|
+
const snapshot = await getContextTreeSnapshot({
|
|
17511
|
+
...binding,
|
|
17512
|
+
githubToken
|
|
17513
|
+
}, query.window ?? "7d");
|
|
17514
|
+
return contextTreeSnapshotSchema.parse(snapshot);
|
|
17515
|
+
});
|
|
17516
|
+
}
|
|
17517
|
+
function orgIdParam(params) {
|
|
17518
|
+
if (!params || typeof params !== "object") return null;
|
|
17519
|
+
if (!("orgId" in params)) return null;
|
|
17520
|
+
const orgId = params.orgId;
|
|
17521
|
+
return typeof orgId === "string" ? orgId : null;
|
|
17522
|
+
}
|
|
17139
17523
|
/**
|
|
17140
17524
|
* Class B — `/api/v1/orgs/:orgId` itself: read & rename the org row.
|
|
17141
17525
|
* Replaces the deleted `/admin/organizations/:id` GET/PATCH pair.
|
|
@@ -17928,18 +18312,43 @@ async function githubWebhookRoutes(app) {
|
|
|
17928
18312
|
ok: true,
|
|
17929
18313
|
event: "ping"
|
|
17930
18314
|
});
|
|
17931
|
-
|
|
17932
|
-
|
|
17933
|
-
|
|
17934
|
-
|
|
17935
|
-
|
|
17936
|
-
|
|
17937
|
-
|
|
17938
|
-
|
|
17939
|
-
|
|
17940
|
-
|
|
17941
|
-
|
|
17942
|
-
|
|
18315
|
+
const deliveryHeader = request.headers["x-github-delivery"];
|
|
18316
|
+
const deliveryId = typeof deliveryHeader === "string" && deliveryHeader.length > 0 ? deliveryHeader : null;
|
|
18317
|
+
if (deliveryId) {
|
|
18318
|
+
if (!await claimEvent(app.db, deliveryId, "github")) {
|
|
18319
|
+
log$1.info({
|
|
18320
|
+
deliveryId,
|
|
18321
|
+
eventType
|
|
18322
|
+
}, "duplicate GitHub delivery, skipping");
|
|
18323
|
+
return reply.status(200).send({
|
|
18324
|
+
ok: true,
|
|
18325
|
+
event: eventType,
|
|
18326
|
+
deduped: true
|
|
18327
|
+
});
|
|
18328
|
+
}
|
|
18329
|
+
}
|
|
18330
|
+
try {
|
|
18331
|
+
if (eventType === "issues") return await handleIssuesEvent(app, orgId, eventType, payload, reply);
|
|
18332
|
+
if (eventType === "issue_comment") return await handleIssueCommentEvent(app, orgId, eventType, payload, reply);
|
|
18333
|
+
let mentionsRouted = 0;
|
|
18334
|
+
const allowedActions = MENTION_ACTIONS[eventType];
|
|
18335
|
+
const action = isRecord(payload) && typeof payload.action === "string" ? payload.action : void 0;
|
|
18336
|
+
if (allowedActions && action && allowedActions.includes(action)) mentionsRouted = await handleMentionDelegation(app, orgId, eventType, payload);
|
|
18337
|
+
return reply.status(200).send({
|
|
18338
|
+
ok: true,
|
|
18339
|
+
event: eventType,
|
|
18340
|
+
handled: mentionsRouted > 0,
|
|
18341
|
+
mentionsRouted
|
|
18342
|
+
});
|
|
18343
|
+
} catch (err) {
|
|
18344
|
+
if (deliveryId) await unclaimEvent(app.db, deliveryId, "github").catch((unclaimErr) => {
|
|
18345
|
+
log$1.error({
|
|
18346
|
+
err: unclaimErr,
|
|
18347
|
+
deliveryId
|
|
18348
|
+
}, "failed to unclaim GitHub delivery after handler error");
|
|
18349
|
+
});
|
|
18350
|
+
throw err;
|
|
18351
|
+
}
|
|
17943
18352
|
});
|
|
17944
18353
|
}
|
|
17945
18354
|
/** Extract text body from any GitHub webhook event for @mention scanning. */
|
|
@@ -19748,6 +20157,7 @@ async function buildApp(config) {
|
|
|
19748
20157
|
await scope.register(orgInvitationRoutes, { prefix: "/invitations" });
|
|
19749
20158
|
await scope.register(orgMemberRoutes, { prefix: "/members" });
|
|
19750
20159
|
await scope.register(orgSettingsRoutes, { prefix: "/settings" });
|
|
20160
|
+
await scope.register(orgContextTreeSnapshotRoutes, { prefix: "/context-tree" });
|
|
19751
20161
|
}), { prefix: "/orgs/:orgId" });
|
|
19752
20162
|
await api.register(orgWsRoutes(notifier, config.secrets.jwtSecret), { prefix: "/orgs/:orgId/ws" });
|
|
19753
20163
|
await api.register(userScope("resourcesScope", async (scope) => {
|