@agent-team-foundation/first-tree-hub 0.10.0 → 0.10.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/{bootstrap-CtVqQA8a.mjs → bootstrap-Ca5Fiqz6.mjs} +10 -1
- package/dist/cli/index.mjs +9 -19
- package/dist/{feishu-DEmwoNn_.mjs → dist-CLiN7cVS.mjs} +88 -51
- package/dist/drizzle/0026_saas_onboarding.sql +153 -0
- package/dist/drizzle/meta/_journal.json +7 -0
- package/dist/feishu-FTWnoOsc.mjs +52 -0
- package/dist/{getMachineId-bsd-BB-fnFLA.mjs → getMachineId-bsd-D0w3uAZa.mjs} +1 -1
- package/dist/{getMachineId-darwin-DAYWNsYK.mjs → getMachineId-darwin-DOoYFb2_.mjs} +1 -1
- package/dist/{getMachineId-win-H5RT49ov.mjs → getMachineId-win-B6hY8edq.mjs} +1 -1
- package/dist/index.mjs +7 -5
- package/dist/invitation-BTlGMy0o-dIoR8JRj.mjs +3 -0
- package/dist/invitation-C_zAhB8x-8Khychlu.mjs +258 -0
- package/dist/{observability-DDkJwSKv.mjs → observability-C08jUFsJ.mjs} +1 -1
- package/dist/{observability-DV_fQKqV-oxfXX6Z2.mjs → observability-DPyf745N-BSc8QNcR.mjs} +6 -6
- package/dist/{core-BgiFGT7Y.mjs → saas-connect-idjpoPTk.mjs} +1111 -153
- package/dist/web/assets/index-CEAPwdg7.js +377 -0
- package/dist/web/assets/index-CzWeWItA.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-DStPeqrX.css +0 -1
- package/dist/web/assets/index-Wgxk3V_m.js +0 -371
- /package/dist/{execAsync-CP8iWV5b.mjs → execAsync-XMc-nFn-.mjs} +0 -0
- /package/dist/{getMachineId-linux-BU7Fi6S0.mjs → getMachineId-linux-MlY63Zsw.mjs} +0 -0
- /package/dist/{getMachineId-unsupported-BhWCxKBo.mjs → getMachineId-unsupported-BS652RIy.mjs} +0 -0
|
@@ -548,7 +548,8 @@ const serverConfigSchema = defineConfig({
|
|
|
548
548
|
},
|
|
549
549
|
server: {
|
|
550
550
|
port: field(z.number().default(8e3), { env: "FIRST_TREE_HUB_PORT" }),
|
|
551
|
-
host: field(z.string().default("127.0.0.1"), { env: "FIRST_TREE_HUB_HOST" })
|
|
551
|
+
host: field(z.string().default("127.0.0.1"), { env: "FIRST_TREE_HUB_HOST" }),
|
|
552
|
+
publicUrl: field(z.string().optional(), { env: "FIRST_TREE_HUB_PUBLIC_URL" })
|
|
552
553
|
},
|
|
553
554
|
secrets: {
|
|
554
555
|
jwtSecret: field(z.string(), {
|
|
@@ -576,6 +577,14 @@ const serverConfigSchema = defineConfig({
|
|
|
576
577
|
}),
|
|
577
578
|
allowedOrg: field(z.string().optional(), { env: "FIRST_TREE_HUB_GITHUB_ALLOWED_ORG" })
|
|
578
579
|
},
|
|
580
|
+
oauth: optional({ github: optional({
|
|
581
|
+
clientId: field(z.string(), { env: "FIRST_TREE_HUB_GITHUB_OAUTH_CLIENT_ID" }),
|
|
582
|
+
clientSecret: field(z.string(), {
|
|
583
|
+
env: "FIRST_TREE_HUB_GITHUB_OAUTH_CLIENT_SECRET",
|
|
584
|
+
secret: true
|
|
585
|
+
}),
|
|
586
|
+
devCallbackEnabled: field(z.boolean().default(false), { env: "FIRST_TREE_HUB_GITHUB_OAUTH_DEV_CALLBACK" })
|
|
587
|
+
}) }),
|
|
579
588
|
cors: optional({ origin: field(z.string(), { env: "FIRST_TREE_HUB_CORS_ORIGIN" }) }),
|
|
580
589
|
rateLimit: optional({
|
|
581
590
|
max: field(z.number().default(100), { env: "FIRST_TREE_HUB_RATE_LIMIT_MAX" }),
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1,26 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "../observability-
|
|
3
|
-
import { $ as
|
|
2
|
+
import "../observability-DPyf745N-BSc8QNcR.mjs";
|
|
3
|
+
import { $ as FirstTreeHubSDK, A as checkWebSocket, B as ClientRuntime, C as checkClientConfig, D as checkServerConfig, E as checkNodeVersion, G as resolveReplyToFromEnv, K as fail, M as getClientServiceStatus, N as installClientService, O as checkServerHealth, P as isServiceSupported, Q as ClientOrgMismatchError, S as checkBackgroundService, T as checkDocker, U as createOwner, V as handleClientOrgMismatch, X as setJsonMode, Y as print, _ as runHomeMigration, a as declineUpdate, b as runMigrations, c as COMMAND_VERSION, d as promptMissingFields, et as SdkError, f as formatCheckReport, g as saveOnboardState, h as onboardCreate, i as createExecuteUpdate, it as configureClientLoggerForService, j as printResults, k as checkServerReachable, l as isInteractive, m as onboardCheck, nt as cleanWorkspaces, o as promptUpdate, p as loadOnboardState, q as success, r as registerSaaSConnectCommand, rt as applyClientLoggerConfig, s as startServer, tt as SessionRegistry, u as promptAddAgent, v as createApiNameResolver, w as checkDatabase, x as checkAgentConfigs, y as migrateLocalAgentDirs, z as stopPostgres } from "../saas-connect-idjpoPTk.mjs";
|
|
4
4
|
import "../logger-core-BTmvdflj-DjW8FM4T.mjs";
|
|
5
|
-
import { C as serverConfigSchema, _ as loadAgents, b as resetConfig, c as saveCredentials, d as DEFAULT_HOME_DIR, f as agentConfigSchema, g as initConfig, h as getConfigValue, i as loadCredentials, l as DEFAULT_CONFIG_DIR, n as ensureFreshAccessToken, o as resolveServerUrl, p as clientConfigSchema, r as ensureFreshAdminToken, s as saveAgentConfig, u as DEFAULT_DATA_DIR, w as setConfigValue, x as resetConfigMeta, y as readConfigFile } from "../bootstrap-
|
|
6
|
-
import
|
|
5
|
+
import { C as serverConfigSchema, _ as loadAgents, b as resetConfig, c as saveCredentials, d as DEFAULT_HOME_DIR, f as agentConfigSchema, g as initConfig, h as getConfigValue, i as loadCredentials, l as DEFAULT_CONFIG_DIR, n as ensureFreshAccessToken, o as resolveServerUrl, p as clientConfigSchema, r as ensureFreshAdminToken, s as saveAgentConfig, u as DEFAULT_DATA_DIR, w as setConfigValue, x as resetConfigMeta, y as readConfigFile } from "../bootstrap-Ca5Fiqz6.mjs";
|
|
6
|
+
import "../dist-CLiN7cVS.mjs";
|
|
7
|
+
import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-FTWnoOsc.mjs";
|
|
8
|
+
import "../invitation-C_zAhB8x-8Khychlu.mjs";
|
|
7
9
|
import { join } from "node:path";
|
|
8
10
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
9
11
|
import { Command } from "commander";
|
|
10
12
|
import { confirm, input, password, select } from "@inquirer/prompts";
|
|
11
|
-
//#region src/cli/output.ts
|
|
12
|
-
/**
|
|
13
|
-
* CLI output re-exports. The underlying implementation lives in
|
|
14
|
-
* `core/output.ts` (the Print layer). Keep these thin wrappers so callers that
|
|
15
|
-
* only depend on `cli/output.ts` keep working during the migration.
|
|
16
|
-
*/
|
|
17
|
-
function success(data) {
|
|
18
|
-
print.result(data);
|
|
19
|
-
}
|
|
20
|
-
function fail(code, message, exitCode = 1) {
|
|
21
|
-
return print.fail(code, message, exitCode);
|
|
22
|
-
}
|
|
23
|
-
//#endregion
|
|
24
13
|
//#region src/commands/agent-config.ts
|
|
25
14
|
async function resolveAgentRecord(serverUrl, adminToken, agentName) {
|
|
26
15
|
const res = await fetch(`${serverUrl}/api/v1/admin/agents?limit=100`, {
|
|
@@ -1223,13 +1212,13 @@ function isSecretField(schema, dotPath) {
|
|
|
1223
1212
|
//#region src/commands/onboard.ts
|
|
1224
1213
|
async function promptMissing(args) {
|
|
1225
1214
|
if (!args.server) try {
|
|
1226
|
-
const { resolveServerUrl } = await import("../bootstrap-
|
|
1215
|
+
const { resolveServerUrl } = await import("../bootstrap-Ca5Fiqz6.mjs").then((n) => n.t);
|
|
1227
1216
|
resolveServerUrl();
|
|
1228
1217
|
} catch {
|
|
1229
1218
|
args.server = await input({ message: "Hub server URL:" });
|
|
1230
1219
|
saveOnboardState(args);
|
|
1231
1220
|
}
|
|
1232
|
-
const { loadCredentials } = await import("../bootstrap-
|
|
1221
|
+
const { loadCredentials } = await import("../bootstrap-Ca5Fiqz6.mjs").then((n) => n.t);
|
|
1233
1222
|
if (!loadCredentials()) throw new Error("No saved credentials. Run `first-tree-hub client connect <server-url>` before onboarding.");
|
|
1234
1223
|
if (!args.id) {
|
|
1235
1224
|
args.id = await input({ message: "Agent ID:" });
|
|
@@ -1506,6 +1495,7 @@ program.name("first-tree-hub").description("First Tree Hub — centralized colla
|
|
|
1506
1495
|
});
|
|
1507
1496
|
else applyClientLoggerConfig({ level: "warn" });
|
|
1508
1497
|
});
|
|
1498
|
+
registerSaaSConnectCommand(program);
|
|
1509
1499
|
registerServerCommands(program);
|
|
1510
1500
|
registerClientCommands(program);
|
|
1511
1501
|
registerAgentCommands(program);
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { d as __exportAll } from "./esm-CYu4tXXn.mjs";
|
|
2
1
|
import { z } from "zod";
|
|
3
2
|
//#region ../shared/dist/index.mjs
|
|
4
3
|
const MENTION_REGEX = /(?<![A-Za-z0-9_.@-])@([A-Za-z0-9][A-Za-z0-9_-]{0,63})\b/g;
|
|
@@ -38,6 +37,35 @@ function scanMentionTokens(content) {
|
|
|
38
37
|
}
|
|
39
38
|
return tokens;
|
|
40
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Single source of truth for "is this string safe to redirect to after a
|
|
42
|
+
* successful OAuth callback".
|
|
43
|
+
*
|
|
44
|
+
* Both the server (`/auth/github/start` validates `?next=` before signing
|
|
45
|
+
* the state JWT) and the web client (the fragment-consumer page validates
|
|
46
|
+
* before navigating) must agree on the regex — drift here is what enables
|
|
47
|
+
* open-redirect bugs. The server is authoritative; the client check is a
|
|
48
|
+
* defense-in-depth.
|
|
49
|
+
*
|
|
50
|
+
* Allowed: a path that begins with exactly one `/` and is not the start of
|
|
51
|
+
* an authority component (`//`, `/\`). Permits typical SPA paths with
|
|
52
|
+
* query strings and fragments. Anything else (absolute URLs, scheme-less
|
|
53
|
+
* authority components, `javascript:`) falls through to the safe default.
|
|
54
|
+
*/
|
|
55
|
+
const SAFE_NEXT_PATH = /^\/(?![/\\])[A-Za-z0-9_\-./?=&%#]*$/;
|
|
56
|
+
/**
|
|
57
|
+
* Return `next` if it is a syntactically safe relative path, otherwise the
|
|
58
|
+
* default landing path. The check is deliberately conservative — the
|
|
59
|
+
* intent is to reject anything that could be parsed as an absolute URL by
|
|
60
|
+
* a browser navigation. Length is capped at 256 chars to defang
|
|
61
|
+
* pathological inputs.
|
|
62
|
+
*/
|
|
63
|
+
function safeRedirectPath(next) {
|
|
64
|
+
if (!next || typeof next !== "string") return "/";
|
|
65
|
+
if (next.length > 256) return "/";
|
|
66
|
+
if (!SAFE_NEXT_PATH.test(next)) return "/";
|
|
67
|
+
return next;
|
|
68
|
+
}
|
|
41
69
|
const adapterPlatformSchema = z.enum([
|
|
42
70
|
"feishu",
|
|
43
71
|
"slack",
|
|
@@ -667,6 +695,42 @@ z.object({
|
|
|
667
695
|
ackedAt: z.string().nullable()
|
|
668
696
|
}).extend({ message: clientMessageSchema });
|
|
669
697
|
const inboxPollQuerySchema = z.object({ limit: z.coerce.number().int().min(1).max(50).default(10) });
|
|
698
|
+
z.object({
|
|
699
|
+
organizationId: z.string(),
|
|
700
|
+
organizationName: z.string(),
|
|
701
|
+
organizationDisplayName: z.string(),
|
|
702
|
+
role: z.string()
|
|
703
|
+
});
|
|
704
|
+
z.object({
|
|
705
|
+
id: z.string(),
|
|
706
|
+
organizationId: z.string(),
|
|
707
|
+
token: z.string(),
|
|
708
|
+
inviteUrl: z.string(),
|
|
709
|
+
role: z.string(),
|
|
710
|
+
createdAt: z.string(),
|
|
711
|
+
expiresAt: z.string().nullable()
|
|
712
|
+
});
|
|
713
|
+
/** Body for joining via invite token. */
|
|
714
|
+
const joinByInvitationSchema = z.object({ token: z.string().min(1) });
|
|
715
|
+
z.object({}).optional();
|
|
716
|
+
z.enum([
|
|
717
|
+
"connect",
|
|
718
|
+
"create_agent",
|
|
719
|
+
"completed"
|
|
720
|
+
]);
|
|
721
|
+
z.object({
|
|
722
|
+
id: z.string(),
|
|
723
|
+
name: z.string(),
|
|
724
|
+
displayName: z.string(),
|
|
725
|
+
role: z.enum(["admin", "member"])
|
|
726
|
+
});
|
|
727
|
+
/** Body for `POST /me/organizations` — operator wants to create another team. */
|
|
728
|
+
const createOrgFromMeSchema = z.object({
|
|
729
|
+
name: z.string().min(2).max(50).regex(/^[a-z0-9][a-z0-9-]*$/),
|
|
730
|
+
displayName: z.string().min(1).max(200)
|
|
731
|
+
});
|
|
732
|
+
/** Body for `POST /auth/switch-org`. */
|
|
733
|
+
const switchOrgSchema = z.object({ organizationId: z.string().min(1) });
|
|
670
734
|
const memberRoleSchema = z.enum(["admin", "member"]);
|
|
671
735
|
const memberSchema = z.object({
|
|
672
736
|
id: z.string(),
|
|
@@ -724,6 +788,28 @@ const notificationQuerySchema = z.object({
|
|
|
724
788
|
read: z.enum(["true", "false"]).transform((v) => v === "true").optional(),
|
|
725
789
|
agentId: z.string().optional()
|
|
726
790
|
});
|
|
791
|
+
/**
|
|
792
|
+
* `GET /api/v1/auth/github/start` query — `next` is the post-login landing
|
|
793
|
+
* path. It is validated again before signing the state JWT (see
|
|
794
|
+
* `safe-redirect.ts`); the schema only enforces the syntactic upper bound
|
|
795
|
+
* so over-long paths bounce with a Zod error rather than silently truncate.
|
|
796
|
+
*/
|
|
797
|
+
const githubStartQuerySchema = z.object({ next: z.string().max(256).optional() });
|
|
798
|
+
const githubCallbackQuerySchema = z.object({
|
|
799
|
+
code: z.string().min(1),
|
|
800
|
+
state: z.string().min(1)
|
|
801
|
+
});
|
|
802
|
+
/**
|
|
803
|
+
* Dev-only callback to bypass the GitHub round-trip — sign in as a stub
|
|
804
|
+
* Github user. Gated by NODE_ENV !== 'production' AND `oauth.github.devCallbackEnabled`.
|
|
805
|
+
*/
|
|
806
|
+
const githubDevCallbackQuerySchema = z.object({
|
|
807
|
+
githubId: z.string().min(1),
|
|
808
|
+
login: z.string().min(1),
|
|
809
|
+
email: z.string().email().optional(),
|
|
810
|
+
displayName: z.string().optional(),
|
|
811
|
+
next: z.string().max(256).optional()
|
|
812
|
+
});
|
|
727
813
|
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
728
814
|
const createOrganizationSchema = z.object({
|
|
729
815
|
name: z.string().min(2).max(50).regex(/^[a-z0-9][a-z0-9-]*$/, "Must start with a letter or digit and contain only lowercase alphanumeric and hyphens").refine((v) => !UUID_PATTERN.test(v), "Name must not be a UUID format"),
|
|
@@ -1061,53 +1147,4 @@ z.object({
|
|
|
1061
1147
|
serverTimeMs: z.number().int().nonnegative()
|
|
1062
1148
|
}).passthrough();
|
|
1063
1149
|
//#endregion
|
|
1064
|
-
|
|
1065
|
-
var feishu_exports = /* @__PURE__ */ __exportAll({
|
|
1066
|
-
bindFeishuBot: () => bindFeishuBot,
|
|
1067
|
-
bindFeishuUser: () => bindFeishuUser
|
|
1068
|
-
});
|
|
1069
|
-
/**
|
|
1070
|
-
* Feishu-related core operations: bind-bot, bind-user.
|
|
1071
|
-
*
|
|
1072
|
-
* All agent-scoped calls carry both the member access JWT (Authorization)
|
|
1073
|
-
* and the acting agent UUID (X-Agent-Id); the server's agent-selector
|
|
1074
|
-
* middleware enforces Rule R-RUN.
|
|
1075
|
-
*/
|
|
1076
|
-
async function bindFeishuBot(serverUrl, accessToken, agentId, appId, appSecret) {
|
|
1077
|
-
const res = await fetch(`${serverUrl}/api/v1/agent/me/feishu-bot`, {
|
|
1078
|
-
method: "PUT",
|
|
1079
|
-
headers: {
|
|
1080
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1081
|
-
[AGENT_SELECTOR_HEADER]: agentId,
|
|
1082
|
-
"Content-Type": "application/json"
|
|
1083
|
-
},
|
|
1084
|
-
body: JSON.stringify({
|
|
1085
|
-
appId,
|
|
1086
|
-
appSecret
|
|
1087
|
-
})
|
|
1088
|
-
});
|
|
1089
|
-
if (!res.ok) {
|
|
1090
|
-
const body = await res.json().catch(() => ({}));
|
|
1091
|
-
throw new Error(body.error ?? `Bind Feishu bot failed: HTTP ${res.status}`);
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
async function bindFeishuUser(serverUrl, accessToken, agentId, humanAgentId, feishuUserId, displayName) {
|
|
1095
|
-
const res = await fetch(`${serverUrl}/api/v1/agent/delegated/${encodeURIComponent(humanAgentId)}/feishu-user`, {
|
|
1096
|
-
method: "POST",
|
|
1097
|
-
headers: {
|
|
1098
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1099
|
-
[AGENT_SELECTOR_HEADER]: agentId,
|
|
1100
|
-
"Content-Type": "application/json"
|
|
1101
|
-
},
|
|
1102
|
-
body: JSON.stringify({
|
|
1103
|
-
feishuUserId,
|
|
1104
|
-
displayName
|
|
1105
|
-
})
|
|
1106
|
-
});
|
|
1107
|
-
if (!res.ok) {
|
|
1108
|
-
const body = await res.json().catch(() => ({}));
|
|
1109
|
-
throw new Error(body.error ?? `Bind Feishu user failed: HTTP ${res.status}`);
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
//#endregion
|
|
1113
|
-
export { sessionEventMessageSchema as $, createChatSchema as A, isReservedAgentName as B, agentRuntimeConfigPayloadSchema as C, createAdapterConfigSchema as D, connectTokenExchangeSchema as E, dryRunAgentRuntimeConfigSchema as F, paginationQuerySchema as G, loginSchema as H, extractMentions as I, scanMentionTokens as J, refreshTokenSchema as K, imageInlineContentSchema as L, createOrganizationSchema as M, createTaskSchema as N, createAdapterMappingSchema as O, delegateFeishuUserSchema as P, sessionCompletionMessageSchema as Q, inboxPollQuerySchema as R, agentPinnedMessageSchema as S, clientRegisterSchema as T, messageSourceSchema as U, linkTaskChatSchema as V, notificationQuerySchema as W, sendMessageSchema as X, selfServiceFeishuBotSchema as Y, sendToAgentSchema as Z, WS_AUTH_FRAME_TIMEOUT_MS as _, AGENT_NAME_REGEX as a, updateAgentRuntimeConfigSchema as at, adminUpdateTaskSchema as b, AGENT_STATUSES as c, updateMemberSchema as ct, DEFAULT_AGENT_RUNTIME_CONFIG_PAYLOAD as d, updateTaskStatusSchema as dt, sessionEventSchema as et, SYSTEM_CONFIG_DEFAULTS as f, wsAuthFrameSchema as ft, TASK_TERMINAL_STATUSES as g, TASK_STATUSES as h, AGENT_BIND_REJECT_REASONS as i, updateAdapterConfigSchema as it, createMemberSchema as j, createAgentSchema as k, AGENT_TYPES as l, updateOrganizationSchema as lt, TASK_HEALTH_SIGNALS as m, bindFeishuUser as n, sessionStateMessageSchema as nt, AGENT_SELECTOR_HEADER as o, updateAgentSchema as ot, TASK_CREATOR_TYPES as p, runtimeStateMessageSchema as q, feishu_exports as r, taskListQuerySchema as rt, AGENT_SOURCES as s, updateChatSchema as st, bindFeishuBot as t, sessionReconcileRequestSchema as tt, AGENT_VISIBILITY as u, updateSystemConfigSchema as ut, addParticipantSchema as v, agentTypeSchema as w, agentBindRequestSchema as x, adminCreateTaskSchema as y, isRedactedEnvValue as z };
|
|
1150
|
+
export { sendMessageSchema as $, createOrganizationSchema as A, isRedactedEnvValue as B, connectTokenExchangeSchema as C, createChatSchema as D, createAgentSchema as E, githubCallbackQuerySchema as F, messageSourceSchema as G, joinByInvitationSchema as H, githubDevCallbackQuerySchema as I, refreshTokenSchema as J, notificationQuerySchema as K, githubStartQuerySchema as L, delegateFeishuUserSchema as M, dryRunAgentRuntimeConfigSchema as N, createMemberSchema as O, extractMentions as P, selfServiceFeishuBotSchema as Q, imageInlineContentSchema as R, clientRegisterSchema as S, createAdapterMappingSchema as T, linkTaskChatSchema as U, isReservedAgentName as V, loginSchema as W, safeRedirectPath as X, runtimeStateMessageSchema as Y, scanMentionTokens as Z, adminUpdateTaskSchema as _, AGENT_STATUSES as a, sessionStateMessageSchema as at, agentRuntimeConfigPayloadSchema as b, DEFAULT_AGENT_RUNTIME_CONFIG_PAYLOAD as c, updateAdapterConfigSchema as ct, TASK_HEALTH_SIGNALS as d, updateChatSchema as dt, sendToAgentSchema as et, TASK_STATUSES as f, updateMemberSchema as ft, adminCreateTaskSchema as g, wsAuthFrameSchema as gt, addParticipantSchema as h, updateTaskStatusSchema as ht, AGENT_SOURCES as i, sessionReconcileRequestSchema as it, createTaskSchema as j, createOrgFromMeSchema as k, SYSTEM_CONFIG_DEFAULTS as l, updateAgentRuntimeConfigSchema as lt, WS_AUTH_FRAME_TIMEOUT_MS as m, updateSystemConfigSchema as mt, AGENT_NAME_REGEX as n, sessionEventMessageSchema as nt, AGENT_TYPES as o, switchOrgSchema as ot, TASK_TERMINAL_STATUSES as p, updateOrganizationSchema as pt, paginationQuerySchema as q, AGENT_SELECTOR_HEADER as r, sessionEventSchema as rt, AGENT_VISIBILITY as s, taskListQuerySchema as st, AGENT_BIND_REJECT_REASONS as t, sessionCompletionMessageSchema as tt, TASK_CREATOR_TYPES as u, updateAgentSchema as ut, agentBindRequestSchema as v, createAdapterConfigSchema as w, agentTypeSchema as x, agentPinnedMessageSchema as y, inboxPollQuerySchema as z };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
-- SaaS onboarding milestone — adds the data-model surface needed for
|
|
2
|
+
-- public GitHub-OAuth signup, per-org invitation links, and "leave team"
|
|
3
|
+
-- soft-delete. See proposals/hub-saas-onboarding.20260428.md for the full
|
|
4
|
+
-- design contract.
|
|
5
|
+
--
|
|
6
|
+
-- Three independent additions, no destructive changes to existing tables:
|
|
7
|
+
--
|
|
8
|
+
-- 1. `auth_identities` — third-party / local auth identities for a user.
|
|
9
|
+
-- Models the "how does this user prove who they are" boundary.
|
|
10
|
+
-- `(provider, identifier)` is globally unique. v1 stores the credential
|
|
11
|
+
-- payload (password hash, webauthn pubkey) on the same row;
|
|
12
|
+
-- v2 splits it into `auth_credentials` if multi-factor is needed
|
|
13
|
+
-- (the migration is sketched in the schema file's header comment).
|
|
14
|
+
--
|
|
15
|
+
-- 2. `invitations` + `invitation_redemptions` — org-level share links.
|
|
16
|
+
-- The "one active link per org" rule is enforced by a partial UNIQUE
|
|
17
|
+
-- index (Drizzle's TS DSL doesn't model partial uniques yet, so we
|
|
18
|
+
-- add it directly here). Rotation = revoke prior + insert new in a
|
|
19
|
+
-- single transaction. Redemptions are recorded for audit.
|
|
20
|
+
--
|
|
21
|
+
-- 3. `members.status` — "active" | "left" soft-delete marker for the
|
|
22
|
+
-- "leave team" flow. Existing rows backfill to "active" via the
|
|
23
|
+
-- column DEFAULT. The auth middleware rejects tokens that resolve to
|
|
24
|
+
-- a "left" member; join-by-invite flips a "left" row back to "active".
|
|
25
|
+
--
|
|
26
|
+
-- All three changes are append-only (new tables + new column with DEFAULT).
|
|
27
|
+
-- ALTER TABLE on `members` takes a brief ACCESS EXCLUSIVE lock, which is
|
|
28
|
+
-- safe on a v1 SaaS members table (small) but should be benchmarked on a
|
|
29
|
+
-- large multi-tenant install before rolling.
|
|
30
|
+
--
|
|
31
|
+
-- See 0020_unified_user_token.sql header for why this file does NOT wrap in
|
|
32
|
+
-- BEGIN;/COMMIT; — Drizzle migrator already runs every pending migration
|
|
33
|
+
-- inside a single outer transaction.
|
|
34
|
+
|
|
35
|
+
-- ---------------------------------------------------------------------------
|
|
36
|
+
-- 1. auth_identities
|
|
37
|
+
-- ---------------------------------------------------------------------------
|
|
38
|
+
CREATE TABLE IF NOT EXISTS "auth_identities" (
|
|
39
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
40
|
+
"user_id" text NOT NULL,
|
|
41
|
+
"provider" text NOT NULL,
|
|
42
|
+
"identifier" text NOT NULL,
|
|
43
|
+
"email" text,
|
|
44
|
+
"verified_at" timestamp with time zone,
|
|
45
|
+
"credential_type" text,
|
|
46
|
+
"credential_payload" jsonb,
|
|
47
|
+
"metadata" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
|
48
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
49
|
+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
50
|
+
CONSTRAINT "uq_auth_identities_provider_identifier" UNIQUE ("provider", "identifier")
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
--> statement-breakpoint
|
|
54
|
+
ALTER TABLE "auth_identities"
|
|
55
|
+
ADD CONSTRAINT "auth_identities_user_id_users_id_fk"
|
|
56
|
+
FOREIGN KEY ("user_id") REFERENCES "users"("id")
|
|
57
|
+
ON DELETE cascade ON UPDATE no action;
|
|
58
|
+
|
|
59
|
+
--> statement-breakpoint
|
|
60
|
+
CREATE INDEX IF NOT EXISTS "idx_auth_identities_user" ON "auth_identities" ("user_id");
|
|
61
|
+
--> statement-breakpoint
|
|
62
|
+
CREATE INDEX IF NOT EXISTS "idx_auth_identities_email" ON "auth_identities" ("email");
|
|
63
|
+
|
|
64
|
+
-- ---------------------------------------------------------------------------
|
|
65
|
+
-- 2. invitations + invitation_redemptions
|
|
66
|
+
-- ---------------------------------------------------------------------------
|
|
67
|
+
--> statement-breakpoint
|
|
68
|
+
CREATE TABLE IF NOT EXISTS "invitations" (
|
|
69
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
70
|
+
"organization_id" text NOT NULL,
|
|
71
|
+
"token" text NOT NULL UNIQUE,
|
|
72
|
+
"role" text DEFAULT 'member' NOT NULL,
|
|
73
|
+
"expires_at" timestamp with time zone,
|
|
74
|
+
"revoked_at" timestamp with time zone,
|
|
75
|
+
"created_by" text NOT NULL,
|
|
76
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
--> statement-breakpoint
|
|
80
|
+
ALTER TABLE "invitations"
|
|
81
|
+
ADD CONSTRAINT "invitations_organization_id_organizations_id_fk"
|
|
82
|
+
FOREIGN KEY ("organization_id") REFERENCES "organizations"("id")
|
|
83
|
+
ON DELETE cascade ON UPDATE no action;
|
|
84
|
+
--> statement-breakpoint
|
|
85
|
+
ALTER TABLE "invitations"
|
|
86
|
+
ADD CONSTRAINT "invitations_created_by_users_id_fk"
|
|
87
|
+
FOREIGN KEY ("created_by") REFERENCES "users"("id")
|
|
88
|
+
ON DELETE no action ON UPDATE no action;
|
|
89
|
+
|
|
90
|
+
--> statement-breakpoint
|
|
91
|
+
CREATE INDEX IF NOT EXISTS "idx_invitations_token" ON "invitations" ("token");
|
|
92
|
+
--> statement-breakpoint
|
|
93
|
+
CREATE INDEX IF NOT EXISTS "idx_invitations_org" ON "invitations" ("organization_id");
|
|
94
|
+
|
|
95
|
+
--> statement-breakpoint
|
|
96
|
+
-- v1 enforced rule: each org may have at most one non-revoked invitation.
|
|
97
|
+
-- The predicate is intentionally `revoked_at IS NULL` only — Postgres rejects
|
|
98
|
+
-- `now()` in an index predicate (must be IMMUTABLE), and conflating "expired"
|
|
99
|
+
-- with "no longer the active link" matches the v1 service contract anyway.
|
|
100
|
+
-- The runtime "is this still usable" filter (which DOES check `expires_at`)
|
|
101
|
+
-- lives in services/invitation.ts. Future "multiple links per org" relaxes by
|
|
102
|
+
-- dropping this index.
|
|
103
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "uq_invitations_active_per_org"
|
|
104
|
+
ON "invitations" ("organization_id")
|
|
105
|
+
WHERE "revoked_at" IS NULL;
|
|
106
|
+
|
|
107
|
+
--> statement-breakpoint
|
|
108
|
+
CREATE TABLE IF NOT EXISTS "invitation_redemptions" (
|
|
109
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
110
|
+
"invitation_id" text NOT NULL,
|
|
111
|
+
"user_id" text NOT NULL,
|
|
112
|
+
"redeemed_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
113
|
+
"ip" text,
|
|
114
|
+
"user_agent" text
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
--> statement-breakpoint
|
|
118
|
+
ALTER TABLE "invitation_redemptions"
|
|
119
|
+
ADD CONSTRAINT "invitation_redemptions_invitation_id_invitations_id_fk"
|
|
120
|
+
FOREIGN KEY ("invitation_id") REFERENCES "invitations"("id")
|
|
121
|
+
ON DELETE cascade ON UPDATE no action;
|
|
122
|
+
--> statement-breakpoint
|
|
123
|
+
ALTER TABLE "invitation_redemptions"
|
|
124
|
+
ADD CONSTRAINT "invitation_redemptions_user_id_users_id_fk"
|
|
125
|
+
FOREIGN KEY ("user_id") REFERENCES "users"("id")
|
|
126
|
+
ON DELETE cascade ON UPDATE no action;
|
|
127
|
+
|
|
128
|
+
--> statement-breakpoint
|
|
129
|
+
CREATE INDEX IF NOT EXISTS "idx_invitation_redemptions_invitation"
|
|
130
|
+
ON "invitation_redemptions" ("invitation_id");
|
|
131
|
+
--> statement-breakpoint
|
|
132
|
+
CREATE INDEX IF NOT EXISTS "idx_invitation_redemptions_user"
|
|
133
|
+
ON "invitation_redemptions" ("user_id");
|
|
134
|
+
|
|
135
|
+
-- ---------------------------------------------------------------------------
|
|
136
|
+
-- 3. members.status — soft-delete marker for "leave team"
|
|
137
|
+
-- ---------------------------------------------------------------------------
|
|
138
|
+
--
|
|
139
|
+
-- No partial index on `status='active'` is created in v1. Filter sites are:
|
|
140
|
+
-- - middleware/member-auth.ts: lookup by members.id (already PK-indexed)
|
|
141
|
+
-- - services/auth.ts (password login): WHERE user_id = ? AND status='active'
|
|
142
|
+
-- - services/membership.ts (listActiveMemberships): same filter
|
|
143
|
+
-- The existing `idx_members_user (user_id)` already collapses each user's
|
|
144
|
+
-- members rows to ~1-5 typical, so the in-page status check is essentially
|
|
145
|
+
-- free at the v1 SaaS scale (low six-digit users × low single-digit teams).
|
|
146
|
+
-- A partial unique-eligible index becomes worth adding when:
|
|
147
|
+
-- - members > ~100k rows AND
|
|
148
|
+
-- - average rows-per-user > ~50
|
|
149
|
+
-- whichever comes first. At that point the migration is a single
|
|
150
|
+
-- `CREATE INDEX CONCURRENTLY ... WHERE status='active'`.
|
|
151
|
+
--> statement-breakpoint
|
|
152
|
+
ALTER TABLE "members"
|
|
153
|
+
ADD COLUMN IF NOT EXISTS "status" text DEFAULT 'active' NOT NULL;
|
|
@@ -183,6 +183,13 @@
|
|
|
183
183
|
"when": 1777420800000,
|
|
184
184
|
"tag": "0025_inbox_silent_entries",
|
|
185
185
|
"breakpoints": true
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"idx": 26,
|
|
189
|
+
"version": "7",
|
|
190
|
+
"when": 1777507200000,
|
|
191
|
+
"tag": "0026_saas_onboarding",
|
|
192
|
+
"breakpoints": true
|
|
186
193
|
}
|
|
187
194
|
]
|
|
188
195
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { d as __exportAll } from "./esm-CYu4tXXn.mjs";
|
|
2
|
+
import { r as AGENT_SELECTOR_HEADER } from "./dist-CLiN7cVS.mjs";
|
|
3
|
+
//#region src/core/feishu.ts
|
|
4
|
+
var feishu_exports = /* @__PURE__ */ __exportAll({
|
|
5
|
+
bindFeishuBot: () => bindFeishuBot,
|
|
6
|
+
bindFeishuUser: () => bindFeishuUser
|
|
7
|
+
});
|
|
8
|
+
/**
|
|
9
|
+
* Feishu-related core operations: bind-bot, bind-user.
|
|
10
|
+
*
|
|
11
|
+
* All agent-scoped calls carry both the member access JWT (Authorization)
|
|
12
|
+
* and the acting agent UUID (X-Agent-Id); the server's agent-selector
|
|
13
|
+
* middleware enforces Rule R-RUN.
|
|
14
|
+
*/
|
|
15
|
+
async function bindFeishuBot(serverUrl, accessToken, agentId, appId, appSecret) {
|
|
16
|
+
const res = await fetch(`${serverUrl}/api/v1/agent/me/feishu-bot`, {
|
|
17
|
+
method: "PUT",
|
|
18
|
+
headers: {
|
|
19
|
+
Authorization: `Bearer ${accessToken}`,
|
|
20
|
+
[AGENT_SELECTOR_HEADER]: agentId,
|
|
21
|
+
"Content-Type": "application/json"
|
|
22
|
+
},
|
|
23
|
+
body: JSON.stringify({
|
|
24
|
+
appId,
|
|
25
|
+
appSecret
|
|
26
|
+
})
|
|
27
|
+
});
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
const body = await res.json().catch(() => ({}));
|
|
30
|
+
throw new Error(body.error ?? `Bind Feishu bot failed: HTTP ${res.status}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function bindFeishuUser(serverUrl, accessToken, agentId, humanAgentId, feishuUserId, displayName) {
|
|
34
|
+
const res = await fetch(`${serverUrl}/api/v1/agent/delegated/${encodeURIComponent(humanAgentId)}/feishu-user`, {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: {
|
|
37
|
+
Authorization: `Bearer ${accessToken}`,
|
|
38
|
+
[AGENT_SELECTOR_HEADER]: agentId,
|
|
39
|
+
"Content-Type": "application/json"
|
|
40
|
+
},
|
|
41
|
+
body: JSON.stringify({
|
|
42
|
+
feishuUserId,
|
|
43
|
+
displayName
|
|
44
|
+
})
|
|
45
|
+
});
|
|
46
|
+
if (!res.ok) {
|
|
47
|
+
const body = await res.json().catch(() => ({}));
|
|
48
|
+
throw new Error(body.error ?? `Bind Feishu user failed: HTTP ${res.status}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
export { bindFeishuUser as n, feishu_exports as r, bindFeishuBot as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { f as __require, l as __commonJSMin, n as init_esm, p as __toCommonJS, t as esm_exports } from "./esm-CYu4tXXn.mjs";
|
|
2
|
-
import { t as require_execAsync } from "./execAsync-
|
|
2
|
+
import { t as require_execAsync } from "./execAsync-XMc-nFn-.mjs";
|
|
3
3
|
//#region ../../node_modules/.pnpm/@opentelemetry+resources@2.7.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/resources/build/src/detectors/platform/node/machine-id/getMachineId-bsd.js
|
|
4
4
|
var require_getMachineId_bsd = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { l as __commonJSMin, n as init_esm, p as __toCommonJS, t as esm_exports } from "./esm-CYu4tXXn.mjs";
|
|
2
|
-
import { t as require_execAsync } from "./execAsync-
|
|
2
|
+
import { t as require_execAsync } from "./execAsync-XMc-nFn-.mjs";
|
|
3
3
|
//#region ../../node_modules/.pnpm/@opentelemetry+resources@2.7.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/resources/build/src/detectors/platform/node/machine-id/getMachineId-darwin.js
|
|
4
4
|
var require_getMachineId_darwin = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { f as __require, l as __commonJSMin, n as init_esm, p as __toCommonJS, t as esm_exports } from "./esm-CYu4tXXn.mjs";
|
|
2
|
-
import { t as require_execAsync } from "./execAsync-
|
|
2
|
+
import { t as require_execAsync } from "./execAsync-XMc-nFn-.mjs";
|
|
3
3
|
//#region ../../node_modules/.pnpm/@opentelemetry+resources@2.7.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/resources/build/src/detectors/platform/node/machine-id/getMachineId-win.js
|
|
4
4
|
var require_getMachineId_win = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import "./observability-
|
|
2
|
-
import { A as
|
|
1
|
+
import "./observability-DPyf745N-BSc8QNcR.mjs";
|
|
2
|
+
import { $ as FirstTreeHubSDK, A as checkWebSocket, B as ClientRuntime, C as checkClientConfig, D as checkServerConfig, E as checkNodeVersion, F as resolveCliInvocation, H as rotateClientIdWithBackup, I as uninstallClientService, J as blank, L as ensurePostgres, M as getClientServiceStatus, N as installClientService, O as checkServerHealth, P as isServiceSupported, R as isDockerAvailable, T as checkDocker, U as createOwner, V as handleClientOrgMismatch, W as hasUser, Z as status, _ as runHomeMigration, b as runMigrations, d as promptMissingFields, et as SdkError, f as formatCheckReport, h as onboardCreate, j as printResults, k as checkServerReachable, l as isInteractive, m as onboardCheck, n as deriveHubUrlFromToken, s as startServer, t as HubUrlDerivationError, u as promptAddAgent, w as checkDatabase, x as checkAgentConfigs, z as stopPostgres } from "./saas-connect-idjpoPTk.mjs";
|
|
3
3
|
import "./logger-core-BTmvdflj-DjW8FM4T.mjs";
|
|
4
|
-
import { a as resolveAccessToken, n as ensureFreshAccessToken, o as resolveServerUrl, r as ensureFreshAdminToken } from "./bootstrap-
|
|
5
|
-
import
|
|
6
|
-
|
|
4
|
+
import { a as resolveAccessToken, n as ensureFreshAccessToken, o as resolveServerUrl, r as ensureFreshAdminToken } from "./bootstrap-Ca5Fiqz6.mjs";
|
|
5
|
+
import "./dist-CLiN7cVS.mjs";
|
|
6
|
+
import { n as bindFeishuUser, t as bindFeishuBot } from "./feishu-FTWnoOsc.mjs";
|
|
7
|
+
import "./invitation-C_zAhB8x-8Khychlu.mjs";
|
|
8
|
+
export { 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, rotateClientIdWithBackup, runHomeMigration, runMigrations, startServer, status, stopPostgres, uninstallClientService };
|