@agent-team-foundation/first-tree-hub 0.11.2 → 0.11.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{bootstrap-B-FRMuvL.mjs → bootstrap-D-Yf8yOc.mjs} +2 -16
- package/dist/cli/index.mjs +44 -41
- package/dist/cli-fetch--tiwKm5S.mjs +167 -0
- package/dist/client-By1K4VVT-DuI6EnSh.mjs +4 -0
- package/dist/{client-CLdRbuml-B416INrm.mjs → client-CLdRbuml-svTO0Eat.mjs} +2 -2
- package/dist/{dist-BLY7Bu-l.mjs → dist-BQtAQNRD.mjs} +1 -1
- package/dist/{dist-FuUBFTEB.mjs → dist-ClFs4WMj.mjs} +55 -1
- package/dist/drizzle/0032_organization_settings.sql +36 -0
- package/dist/drizzle/meta/_journal.json +8 -1
- package/dist/{feishu-GvFABWW5.mjs → feishu-AI3pwmqN.mjs} +4 -3
- package/dist/{getMachineId-bsd-DjLgZlll.mjs → getMachineId-bsd-DyySs8xz.mjs} +2 -2
- package/dist/{getMachineId-bsd-DR4-Dysy.mjs → getMachineId-bsd-c2VImogj.mjs} +2 -2
- package/dist/{getMachineId-darwin-CaD2juTg.mjs → getMachineId-darwin-Cl7TSzgO.mjs} +2 -2
- package/dist/{getMachineId-darwin-B6WCAhc4.mjs → getMachineId-darwin-DKgI8b1d.mjs} +2 -2
- package/dist/{getMachineId-linux-Dk3gWdQK.mjs → getMachineId-linux-1OIMWfdh.mjs} +1 -1
- package/dist/{getMachineId-linux-BeWHG1gK.mjs → getMachineId-linux-cT7EbP10.mjs} +1 -1
- package/dist/{getMachineId-unsupported-BMJQItvF.mjs → getMachineId-unsupported-CkX-YOG1.mjs} +1 -1
- package/dist/{getMachineId-unsupported-Bgz_Je1J.mjs → getMachineId-unsupported-CmVlhzIo.mjs} +1 -1
- package/dist/{getMachineId-win-vJ6VfDRI.mjs → getMachineId-win-C2cM60YT.mjs} +2 -2
- package/dist/{getMachineId-win-CdgcrzCW.mjs → getMachineId-win-Chl03TYe.mjs} +2 -2
- package/dist/index.mjs +10 -9
- package/dist/invitation-DWlyNb8x-BvXubk24.mjs +4 -0
- package/dist/{invitation-Dnn5gGGX-Ce7zbZpn.mjs → invitation-Dnn5gGGX-DXryyvRG.mjs} +1 -1
- package/dist/{multipart-parser-BIksYTkk.mjs → multipart-parser-QRu3OKK4.mjs} +1 -1
- package/dist/{observability-C3nY6Jcz-Bk7FX689.mjs → observability-BAScT_5S-gw1ODB_o.mjs} +140 -17
- package/dist/observability-CYsdAcoF.mjs +5 -0
- package/dist/{saas-connect-Df2CVAGp.mjs → saas-connect-CVoRK0Ex.mjs} +462 -214
- package/dist/{src-CzQ5KF6D.mjs → src-DFlbpJfU.mjs} +2 -2
- package/dist/web/assets/{index-CD7rTdqm.js → index-Bm6hgcvt.js} +1 -1
- package/dist/web/assets/{index-43trJLR8.js → index-k2bWRKc-.js} +87 -87
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/dist/client-By1K4VVT-nVOhsXBy.mjs +0 -4
- package/dist/invitation-DWlyNb8x-BEgoZ9k1.mjs +0 -4
- package/dist/observability-DttujCqj.mjs +0 -5
- /package/dist/{errors-BmyRwN0Y-CIZZ_sDc.mjs → errors-BmyRwN0Y-Dad3eV8F.mjs} +0 -0
- /package/dist/{esm-iadMkGbV.mjs → esm-Ci8E1Gtj.mjs} +0 -0
- /package/dist/{execAsync-pImxPKN5.mjs → execAsync-DUfRkc4a.mjs} +0 -0
- /package/dist/{execAsync-CCyouKZM.mjs → execAsync-YbEZSOYd.mjs} +0 -0
- /package/dist/{from-CaD373S1.mjs → from-DQ7eNRwu.mjs} +0 -0
- /package/dist/{src-DNBS5Yjj.mjs → src-aJMV60mR.mjs} +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { r as __exportAll } from "./chunk-BSw8zbkd.mjs";
|
|
2
2
|
import { o as logFormatSchema, s as logLevelSchema } from "./logger-core-BTmvdflj-DjW8FM4T.mjs";
|
|
3
|
+
import { t as cliFetch } from "./cli-fetch--tiwKm5S.mjs";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
import { dirname, join } from "node:path";
|
|
5
6
|
import { homedir } from "node:os";
|
|
@@ -568,21 +569,6 @@ const serverConfigSchema = defineConfig({
|
|
|
568
569
|
refreshTokenExpiry: field(z.string().default("30d"), { env: "FIRST_TREE_HUB_AUTH_REFRESH_TOKEN_EXPIRY" }),
|
|
569
570
|
connectTokenExpiry: field(z.string().default("10m"), { env: "FIRST_TREE_HUB_AUTH_CONNECT_TOKEN_EXPIRY" })
|
|
570
571
|
},
|
|
571
|
-
contextTree: optional({
|
|
572
|
-
repo: field(z.string().optional(), {
|
|
573
|
-
env: "FIRST_TREE_HUB_CONTEXT_TREE_REPO",
|
|
574
|
-
prompt: { message: "Context Tree repo URL (e.g. https://github.com/org/first-tree):" }
|
|
575
|
-
}),
|
|
576
|
-
localPath: field(z.string().optional(), { env: "FIRST_TREE_HUB_CONTEXT_TREE_PATH" }),
|
|
577
|
-
branch: field(z.string().default("main"))
|
|
578
|
-
}),
|
|
579
|
-
github: {
|
|
580
|
-
webhookSecret: field(z.string().optional(), {
|
|
581
|
-
env: "FIRST_TREE_HUB_GITHUB_WEBHOOK_SECRET",
|
|
582
|
-
secret: true
|
|
583
|
-
}),
|
|
584
|
-
allowedOrg: field(z.string().optional(), { env: "FIRST_TREE_HUB_GITHUB_ALLOWED_ORG" })
|
|
585
|
-
},
|
|
586
572
|
oauth: optional({ github: optional({
|
|
587
573
|
clientId: field(z.string(), { env: "FIRST_TREE_HUB_GITHUB_OAUTH_CLIENT_ID" }),
|
|
588
574
|
clientSecret: field(z.string(), {
|
|
@@ -784,7 +770,7 @@ async function ensureFreshAccessToken(opts) {
|
|
|
784
770
|
if (!isTokenStale(creds.accessToken, minValidityMs)) return creds.accessToken;
|
|
785
771
|
if (inflightRefresh) return inflightRefresh;
|
|
786
772
|
inflightRefresh = (async () => {
|
|
787
|
-
const res = await
|
|
773
|
+
const res = await cliFetch(`${creds.serverUrl}/api/v1/auth/refresh`, {
|
|
788
774
|
method: "POST",
|
|
789
775
|
headers: { "Content-Type": "application/json" },
|
|
790
776
|
body: JSON.stringify({ refreshToken: creds.refreshToken }),
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "../observability-
|
|
3
|
-
import { $ as
|
|
2
|
+
import "../observability-BAScT_5S-gw1ODB_o.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 success, b as onboardCreate, c as detectInstallMode, ct as FirstTreeHubSDK, d as startServer, dt as cleanWorkspaces, et as removeLocalAgent, f as reconcileLocalRuntimeProviders, ft as probeCapabilities, g as promptMissingFields, h as promptAddAgent, i as createExecuteUpdate, it as fail, j as checkNodeVersion, k as checkDatabase, l as fetchLatestVersion, lt as SdkError, m as isInteractive, mt as configureClientLoggerForService, o as promptUpdate, ot as ClientOrgMismatchError, p as uploadClientCapabilities, pt as applyClientLoggerConfig, r as registerSaaSConnectCommand, rt as resolveReplyToFromEnv, s as PACKAGE_NAME, st as ClientUserMismatchError, tt as createOwner, u as installGlobalLatest, ut as SessionRegistry, v as loadOnboardState, w as migrateLocalAgentDirs, x as saveOnboardState, y as onboardCheck, z as installClientService } from "../saas-connect-CVoRK0Ex.mjs";
|
|
4
4
|
import "../logger-core-BTmvdflj-DjW8FM4T.mjs";
|
|
5
|
-
import { C as resetConfigMeta, E as setConfigValue, S as resetConfig, T as serverConfigSchema, _ as getConfigValue, a as ensureFreshAdminToken, c as resolveServerUrl, d as DEFAULT_CONFIG_DIR, f as DEFAULT_DATA_DIR, h as clientConfigSchema, i as ensureFreshAccessToken, l as saveAgentConfig, m as agentConfigSchema, o as loadCredentials, p as DEFAULT_HOME_DIR, u as saveCredentials, v as initConfig, w as resolveConfigReadonly, x as readConfigFile, y as loadAgents } from "../bootstrap-
|
|
6
|
-
import "../
|
|
7
|
-
import
|
|
8
|
-
import "../
|
|
9
|
-
import "../
|
|
10
|
-
import "../
|
|
11
|
-
import "../
|
|
5
|
+
import { C as resetConfigMeta, E as setConfigValue, S as resetConfig, T as serverConfigSchema, _ as getConfigValue, a as ensureFreshAdminToken, c as resolveServerUrl, d as DEFAULT_CONFIG_DIR, f as DEFAULT_DATA_DIR, h as clientConfigSchema, i as ensureFreshAccessToken, l as saveAgentConfig, m as agentConfigSchema, o as loadCredentials, p as DEFAULT_HOME_DIR, u as saveCredentials, v as initConfig, w as resolveConfigReadonly, x as readConfigFile, y as loadAgents } from "../bootstrap-D-Yf8yOc.mjs";
|
|
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-ClFs4WMj.mjs";
|
|
8
|
+
import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-AI3pwmqN.mjs";
|
|
9
|
+
import "../errors-BmyRwN0Y-Dad3eV8F.mjs";
|
|
10
|
+
import "../client-CLdRbuml-svTO0Eat.mjs";
|
|
11
|
+
import "../src-aJMV60mR.mjs";
|
|
12
|
+
import "../invitation-Dnn5gGGX-DXryyvRG.mjs";
|
|
12
13
|
import { join } from "node:path";
|
|
13
14
|
import { existsSync, mkdirSync, readFileSync, readdirSync } from "node:fs";
|
|
14
15
|
import * as semver from "semver";
|
|
@@ -16,7 +17,7 @@ import { Command } from "commander";
|
|
|
16
17
|
import { confirm, input, password, select } from "@inquirer/prompts";
|
|
17
18
|
//#region src/commands/agent-config.ts
|
|
18
19
|
async function resolveAgentRecord(serverUrl, adminToken, agentName) {
|
|
19
|
-
const res = await
|
|
20
|
+
const res = await cliFetch(`${serverUrl}/api/v1/me/managed-agents`, {
|
|
20
21
|
headers: { Authorization: `Bearer ${adminToken}` },
|
|
21
22
|
signal: AbortSignal.timeout(1e4)
|
|
22
23
|
});
|
|
@@ -27,7 +28,7 @@ async function resolveAgentRecord(serverUrl, adminToken, agentName) {
|
|
|
27
28
|
}
|
|
28
29
|
async function adminFetch(url, init) {
|
|
29
30
|
const { adminToken, headers, ...rest } = init;
|
|
30
|
-
const res = await
|
|
31
|
+
const res = await cliFetch(url, {
|
|
31
32
|
...rest,
|
|
32
33
|
headers: {
|
|
33
34
|
Authorization: `Bearer ${adminToken}`,
|
|
@@ -240,7 +241,8 @@ function createSdk(agentName) {
|
|
|
240
241
|
return new FirstTreeHubSDK({
|
|
241
242
|
serverUrl,
|
|
242
243
|
getAccessToken: (opts) => ensureFreshAccessToken(opts),
|
|
243
|
-
agentId
|
|
244
|
+
agentId,
|
|
245
|
+
userAgent: CLI_USER_AGENT
|
|
244
246
|
});
|
|
245
247
|
}
|
|
246
248
|
function handleSdkError(error) {
|
|
@@ -276,7 +278,7 @@ function readStdin() {
|
|
|
276
278
|
});
|
|
277
279
|
}
|
|
278
280
|
async function resolveAgent(serverUrl, adminToken, agentName) {
|
|
279
|
-
const res = await
|
|
281
|
+
const res = await cliFetch(`${serverUrl}/api/v1/me/managed-agents`, {
|
|
280
282
|
headers: { Authorization: `Bearer ${adminToken}` },
|
|
281
283
|
signal: AbortSignal.timeout(1e4)
|
|
282
284
|
});
|
|
@@ -339,7 +341,8 @@ function registerAgentCommands(program) {
|
|
|
339
341
|
const clientId = readClientId();
|
|
340
342
|
const sdk = new FirstTreeHubSDK({
|
|
341
343
|
serverUrl,
|
|
342
|
-
getAccessToken: (opts) => ensureFreshAccessToken(opts)
|
|
344
|
+
getAccessToken: (opts) => ensureFreshAccessToken(opts),
|
|
345
|
+
userAgent: CLI_USER_AGENT
|
|
343
346
|
});
|
|
344
347
|
const stale = await findStaleAliases({
|
|
345
348
|
clientId,
|
|
@@ -406,7 +409,7 @@ function registerAgentCommands(program) {
|
|
|
406
409
|
try {
|
|
407
410
|
const serverUrl = resolveServerUrl(options.server);
|
|
408
411
|
const token = await ensureFreshAccessToken();
|
|
409
|
-
const res = await
|
|
412
|
+
const res = await cliFetch(`${serverUrl}/api/v1/me/managed-agents`, {
|
|
410
413
|
headers: { Authorization: `Bearer ${token}` },
|
|
411
414
|
signal: AbortSignal.timeout(1e4)
|
|
412
415
|
});
|
|
@@ -433,7 +436,7 @@ function registerAgentCommands(program) {
|
|
|
433
436
|
Authorization: `Bearer ${adminToken}`,
|
|
434
437
|
"Content-Type": "application/json"
|
|
435
438
|
};
|
|
436
|
-
const meRes = await
|
|
439
|
+
const meRes = await cliFetch(`${serverUrl}/api/v1/me`, {
|
|
437
440
|
headers: { Authorization: `Bearer ${adminToken}` },
|
|
438
441
|
signal: AbortSignal.timeout(1e4)
|
|
439
442
|
});
|
|
@@ -456,7 +459,7 @@ function registerAgentCommands(program) {
|
|
|
456
459
|
runtimeProvider: options.runtime
|
|
457
460
|
};
|
|
458
461
|
if (options.displayName) createBody.displayName = options.displayName;
|
|
459
|
-
const createRes = await
|
|
462
|
+
const createRes = await cliFetch(`${serverUrl}/api/v1/orgs/${encodeURIComponent(orgId)}/agents`, {
|
|
460
463
|
method: "POST",
|
|
461
464
|
headers,
|
|
462
465
|
body: JSON.stringify(createBody),
|
|
@@ -476,14 +479,14 @@ function registerAgentCommands(program) {
|
|
|
476
479
|
try {
|
|
477
480
|
const serverUrl = resolveServerUrl(options.server);
|
|
478
481
|
const accessToken = await ensureFreshAccessToken();
|
|
479
|
-
const meRes = await
|
|
482
|
+
const meRes = await cliFetch(`${serverUrl}/api/v1/me`, {
|
|
480
483
|
headers: { Authorization: `Bearer ${accessToken}` },
|
|
481
484
|
signal: AbortSignal.timeout(1e4)
|
|
482
485
|
});
|
|
483
486
|
if (!meRes.ok) fail("ME_ERROR", `Failed to fetch current member (HTTP ${meRes.status})`, 1);
|
|
484
487
|
const me = await meRes.json();
|
|
485
488
|
const target = await resolveAgent(serverUrl, accessToken, agentName);
|
|
486
|
-
const patchRes = await
|
|
489
|
+
const patchRes = await cliFetch(`${serverUrl}/api/v1/agents/${target.uuid}`, {
|
|
487
490
|
method: "PATCH",
|
|
488
491
|
headers: {
|
|
489
492
|
Authorization: `Bearer ${accessToken}`,
|
|
@@ -526,7 +529,7 @@ function registerAgentCommands(program) {
|
|
|
526
529
|
const serverUrl = resolveServerUrl(options.server);
|
|
527
530
|
const accessToken = await ensureFreshAccessToken();
|
|
528
531
|
const target = await resolveAgent(serverUrl, accessToken, agentName);
|
|
529
|
-
const patchRes = await
|
|
532
|
+
const patchRes = await cliFetch(`${serverUrl}/api/v1/agents/${target.uuid}`, {
|
|
530
533
|
method: "PATCH",
|
|
531
534
|
headers: {
|
|
532
535
|
Authorization: `Bearer ${accessToken}`,
|
|
@@ -636,7 +639,7 @@ function registerAgentCommands(program) {
|
|
|
636
639
|
try {
|
|
637
640
|
const serverUrl = resolveServerUrl(options?.server);
|
|
638
641
|
const accessToken = await ensureFreshAccessToken();
|
|
639
|
-
const meRes = await
|
|
642
|
+
const meRes = await cliFetch(`${serverUrl}/api/v1/me`, {
|
|
640
643
|
headers: { Authorization: `Bearer ${accessToken}` },
|
|
641
644
|
signal: AbortSignal.timeout(1e4)
|
|
642
645
|
});
|
|
@@ -655,7 +658,7 @@ function registerAgentCommands(program) {
|
|
|
655
658
|
agents: []
|
|
656
659
|
};
|
|
657
660
|
for (const m of me.memberships) {
|
|
658
|
-
const r = await
|
|
661
|
+
const r = await cliFetch(`${serverUrl}/api/v1/orgs/${encodeURIComponent(m.organizationId)}/activity`, {
|
|
659
662
|
headers: { Authorization: `Bearer ${accessToken}` },
|
|
660
663
|
signal: AbortSignal.timeout(1e4)
|
|
661
664
|
});
|
|
@@ -704,8 +707,7 @@ function registerAgentCommands(program) {
|
|
|
704
707
|
});
|
|
705
708
|
agent.command("reset <name>").description("Reset agent error state to idle").option("--server <url>", "Hub server URL").action(async (name, options) => {
|
|
706
709
|
try {
|
|
707
|
-
const
|
|
708
|
-
const response = await fetch(`${serverUrl}/api/v1/agents/${name}/reset-activity`, {
|
|
710
|
+
const response = await cliFetch(`${resolveServerUrl(options.server)}/api/v1/agents/${name}/reset-activity`, {
|
|
709
711
|
method: "POST",
|
|
710
712
|
headers: { Authorization: `Bearer ${await ensureFreshAccessToken()}` },
|
|
711
713
|
signal: AbortSignal.timeout(1e4)
|
|
@@ -721,8 +723,7 @@ function registerAgentCommands(program) {
|
|
|
721
723
|
const serverUrl = resolveServerUrl(options.server);
|
|
722
724
|
const adminToken = await ensureFreshAccessToken();
|
|
723
725
|
const agentId = (await resolveAgent(serverUrl, adminToken, agentName)).uuid;
|
|
724
|
-
const
|
|
725
|
-
const response = await fetch(`${serverUrl}/api/v1/agents/${agentId}/sessions${qs}`, {
|
|
726
|
+
const response = await cliFetch(`${serverUrl}/api/v1/agents/${agentId}/sessions${options.state ? `?state=${options.state}` : ""}`, {
|
|
726
727
|
headers: { Authorization: `Bearer ${adminToken}` },
|
|
727
728
|
signal: AbortSignal.timeout(1e4)
|
|
728
729
|
});
|
|
@@ -751,7 +752,7 @@ function registerAgentCommands(program) {
|
|
|
751
752
|
const serverUrl = resolveServerUrl(options.server);
|
|
752
753
|
const adminToken = await ensureFreshAccessToken();
|
|
753
754
|
const agentId = (await resolveAgent(serverUrl, adminToken, agentName)).uuid;
|
|
754
|
-
const response = await
|
|
755
|
+
const response = await cliFetch(`${serverUrl}/api/v1/agents/${agentId}/sessions/${chatId}/${cmd}`, {
|
|
755
756
|
method: "POST",
|
|
756
757
|
headers: { Authorization: `Bearer ${adminToken}` },
|
|
757
758
|
signal: AbortSignal.timeout(1e4)
|
|
@@ -774,7 +775,7 @@ function registerAgentCommands(program) {
|
|
|
774
775
|
"Content-Type": "application/json"
|
|
775
776
|
};
|
|
776
777
|
const targetAgent = await resolveAgent(serverUrl, adminToken, agentName);
|
|
777
|
-
const dmRes = await
|
|
778
|
+
const dmRes = await cliFetch(`${serverUrl}/api/v1/agents/${targetAgent.uuid}/chats`, {
|
|
778
779
|
method: "POST",
|
|
779
780
|
headers,
|
|
780
781
|
signal: AbortSignal.timeout(1e4)
|
|
@@ -796,7 +797,7 @@ function registerAgentCommands(program) {
|
|
|
796
797
|
const pollMessages = async () => {
|
|
797
798
|
try {
|
|
798
799
|
const qs = lastSeenAt ? `?limit=50` : `?limit=10`;
|
|
799
|
-
const msgRes = await
|
|
800
|
+
const msgRes = await cliFetch(`${serverUrl}/api/v1/chats/${dm.id}/messages${qs}`, {
|
|
800
801
|
headers,
|
|
801
802
|
signal: AbortSignal.timeout(1e4)
|
|
802
803
|
});
|
|
@@ -827,7 +828,7 @@ function registerAgentCommands(program) {
|
|
|
827
828
|
return;
|
|
828
829
|
}
|
|
829
830
|
try {
|
|
830
|
-
const sendRes = await
|
|
831
|
+
const sendRes = await cliFetch(`${serverUrl}/api/v1/chats/${dm.id}/messages`, {
|
|
831
832
|
method: "POST",
|
|
832
833
|
headers,
|
|
833
834
|
body: JSON.stringify({
|
|
@@ -963,7 +964,7 @@ function printIsolationGuide(newServerUrl) {
|
|
|
963
964
|
* Authenticate via connect token — exchange for full JWT credentials.
|
|
964
965
|
*/
|
|
965
966
|
async function authenticateWithToken(url, token) {
|
|
966
|
-
const res = await
|
|
967
|
+
const res = await cliFetch(`${url}/api/v1/auth/connect-token`, {
|
|
967
968
|
method: "POST",
|
|
968
969
|
headers: { "Content-Type": "application/json" },
|
|
969
970
|
body: JSON.stringify({ token }),
|
|
@@ -979,7 +980,7 @@ async function authenticateInteractive(url) {
|
|
|
979
980
|
print.line("\n Log in to Hub:\n");
|
|
980
981
|
const username = await input({ message: " Username:" });
|
|
981
982
|
const pw = await password({ message: " Password:" });
|
|
982
|
-
const loginRes = await
|
|
983
|
+
const loginRes = await cliFetch(`${url}/api/v1/auth/login`, {
|
|
983
984
|
method: "POST",
|
|
984
985
|
headers: { "Content-Type": "application/json" },
|
|
985
986
|
body: JSON.stringify({
|
|
@@ -1270,7 +1271,8 @@ function registerClientCommands(program) {
|
|
|
1270
1271
|
});
|
|
1271
1272
|
const sdk = new FirstTreeHubSDK({
|
|
1272
1273
|
serverUrl,
|
|
1273
|
-
getAccessToken: (opts) => ensureFreshAccessToken(opts)
|
|
1274
|
+
getAccessToken: (opts) => ensureFreshAccessToken(opts),
|
|
1275
|
+
userAgent: CLI_USER_AGENT
|
|
1274
1276
|
});
|
|
1275
1277
|
agentCheck = await reconcileAgentConfigs({
|
|
1276
1278
|
clientId: cfg.client.id,
|
|
@@ -1396,7 +1398,7 @@ function registerClientCommands(program) {
|
|
|
1396
1398
|
try {
|
|
1397
1399
|
const serverUrl = resolveServerUrl(options.server);
|
|
1398
1400
|
const token = await ensureFreshAccessToken();
|
|
1399
|
-
const meRes = await
|
|
1401
|
+
const meRes = await cliFetch(`${serverUrl}/api/v1/me`, {
|
|
1400
1402
|
headers: { Authorization: `Bearer ${token}` },
|
|
1401
1403
|
signal: AbortSignal.timeout(1e4)
|
|
1402
1404
|
});
|
|
@@ -1404,7 +1406,7 @@ function registerClientCommands(program) {
|
|
|
1404
1406
|
const adminOrgs = (await meRes.json()).memberships.filter((m) => m.role === "admin");
|
|
1405
1407
|
const clients = [];
|
|
1406
1408
|
for (const m of adminOrgs) {
|
|
1407
|
-
const r = await
|
|
1409
|
+
const r = await cliFetch(`${serverUrl}/api/v1/orgs/${encodeURIComponent(m.organizationId)}/clients`, {
|
|
1408
1410
|
headers: { Authorization: `Bearer ${token}` },
|
|
1409
1411
|
signal: AbortSignal.timeout(1e4)
|
|
1410
1412
|
});
|
|
@@ -1453,7 +1455,7 @@ function registerClientCommands(program) {
|
|
|
1453
1455
|
}
|
|
1454
1456
|
}
|
|
1455
1457
|
const token = await ensureFreshAccessToken();
|
|
1456
|
-
const response = await
|
|
1458
|
+
const response = await cliFetch(`${serverUrl}/api/v1/clients/${encodeURIComponent(clientId)}/claim`, {
|
|
1457
1459
|
method: "POST",
|
|
1458
1460
|
headers: {
|
|
1459
1461
|
Authorization: `Bearer ${token}`,
|
|
@@ -1471,7 +1473,8 @@ function registerClientCommands(program) {
|
|
|
1471
1473
|
try {
|
|
1472
1474
|
const sdk = new FirstTreeHubSDK({
|
|
1473
1475
|
serverUrl,
|
|
1474
|
-
getAccessToken: (opts) => ensureFreshAccessToken(opts)
|
|
1476
|
+
getAccessToken: (opts) => ensureFreshAccessToken(opts),
|
|
1477
|
+
userAgent: CLI_USER_AGENT
|
|
1475
1478
|
});
|
|
1476
1479
|
const stale = await findStaleAliases({
|
|
1477
1480
|
clientId,
|
|
@@ -1520,7 +1523,7 @@ function registerClientCommands(program) {
|
|
|
1520
1523
|
try {
|
|
1521
1524
|
const serverUrl = resolveServerUrl(options.server);
|
|
1522
1525
|
const token = await ensureFreshAccessToken();
|
|
1523
|
-
const response = await
|
|
1526
|
+
const response = await cliFetch(`${serverUrl}/api/v1/clients/${encodeURIComponent(clientId)}/disconnect`, {
|
|
1524
1527
|
method: "POST",
|
|
1525
1528
|
headers: { Authorization: `Bearer ${token}` },
|
|
1526
1529
|
signal: AbortSignal.timeout(1e4)
|
|
@@ -1667,13 +1670,13 @@ function isSecretField(schema, dotPath) {
|
|
|
1667
1670
|
//#region src/commands/onboard.ts
|
|
1668
1671
|
async function promptMissing(args) {
|
|
1669
1672
|
if (!args.server) try {
|
|
1670
|
-
const { resolveServerUrl } = await import("../bootstrap-
|
|
1673
|
+
const { resolveServerUrl } = await import("../bootstrap-D-Yf8yOc.mjs").then((n) => n.r);
|
|
1671
1674
|
resolveServerUrl();
|
|
1672
1675
|
} catch {
|
|
1673
1676
|
args.server = await input({ message: "Hub server URL:" });
|
|
1674
1677
|
saveOnboardState(args);
|
|
1675
1678
|
}
|
|
1676
|
-
const { loadCredentials } = await import("../bootstrap-
|
|
1679
|
+
const { loadCredentials } = await import("../bootstrap-D-Yf8yOc.mjs").then((n) => n.r);
|
|
1677
1680
|
if (!loadCredentials()) throw new Error("No saved credentials. Run `first-tree-hub client connect <server-url>` before onboarding.");
|
|
1678
1681
|
if (!args.id) {
|
|
1679
1682
|
args.id = await input({ message: "Agent ID:" });
|
|
@@ -1833,7 +1836,7 @@ function registerServerCommands(program) {
|
|
|
1833
1836
|
server.command("status").description("Show server health and status").action(async () => {
|
|
1834
1837
|
const url = process.env.FIRST_TREE_HUB_SERVER_URL ?? "http://localhost:8000";
|
|
1835
1838
|
try {
|
|
1836
|
-
const res = await
|
|
1839
|
+
const res = await cliFetch(`${url}/api/v1/health`);
|
|
1837
1840
|
if (res.ok) {
|
|
1838
1841
|
const data = await res.json();
|
|
1839
1842
|
process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { dirname, resolve } from "node:path";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
//#region src/core/output.ts
|
|
5
|
+
/**
|
|
6
|
+
* Print layer — the only place CLI code should write to stdout/stderr.
|
|
7
|
+
*
|
|
8
|
+
* Contract:
|
|
9
|
+
* - `print.result(data)` / `print.fail(...)` emit machine-readable JSON on
|
|
10
|
+
* stdout / stderr respectively. Scripts pipe into `jq` and expect a clean
|
|
11
|
+
* envelope, so nothing else may touch stdout.
|
|
12
|
+
* - `print.status` / `print.check` / `print.blank` / `print.line` are
|
|
13
|
+
* human-friendly and go to stderr so they never pollute a redirected stdout.
|
|
14
|
+
* In `--json` mode they are silenced — scripted consumers only care about
|
|
15
|
+
* the envelope.
|
|
16
|
+
*/
|
|
17
|
+
let jsonMode = false;
|
|
18
|
+
function setJsonMode(enabled) {
|
|
19
|
+
jsonMode = enabled;
|
|
20
|
+
}
|
|
21
|
+
function result(data) {
|
|
22
|
+
process.stdout.write(`${JSON.stringify({
|
|
23
|
+
ok: true,
|
|
24
|
+
data
|
|
25
|
+
})}\n`);
|
|
26
|
+
}
|
|
27
|
+
function fail(code, message, exitCode = 1) {
|
|
28
|
+
process.stderr.write(`${JSON.stringify({
|
|
29
|
+
ok: false,
|
|
30
|
+
error: {
|
|
31
|
+
code,
|
|
32
|
+
message
|
|
33
|
+
}
|
|
34
|
+
})}\n`);
|
|
35
|
+
process.exit(exitCode);
|
|
36
|
+
}
|
|
37
|
+
function status(label, message) {
|
|
38
|
+
if (jsonMode) return;
|
|
39
|
+
process.stderr.write(` ${label.padEnd(20)} ${message}\n`);
|
|
40
|
+
}
|
|
41
|
+
function check(pass, label, detail = "") {
|
|
42
|
+
if (jsonMode) return;
|
|
43
|
+
const icon = pass ? "✓" : "✗";
|
|
44
|
+
const tail = detail ? ` ${detail}` : "";
|
|
45
|
+
process.stderr.write(` ${icon} ${label.padEnd(22)}${tail}\n`);
|
|
46
|
+
}
|
|
47
|
+
function blank() {
|
|
48
|
+
if (jsonMode) return;
|
|
49
|
+
process.stderr.write("\n");
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Generic stderr writer for pre-formatted human text (multi-line tables,
|
|
53
|
+
* interactive prompts). Prefer `status` / `check` when the text fits; this
|
|
54
|
+
* exists so the `--json` mode gate can silence arbitrary human chatter.
|
|
55
|
+
*/
|
|
56
|
+
function line(text) {
|
|
57
|
+
if (jsonMode) return;
|
|
58
|
+
process.stderr.write(text);
|
|
59
|
+
}
|
|
60
|
+
const print = {
|
|
61
|
+
result,
|
|
62
|
+
fail,
|
|
63
|
+
status,
|
|
64
|
+
check,
|
|
65
|
+
blank,
|
|
66
|
+
line
|
|
67
|
+
};
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/core/version.ts
|
|
70
|
+
/**
|
|
71
|
+
* Version of the consumer-facing `@agent-team-foundation/first-tree-hub`
|
|
72
|
+
* package. Read once at module load so the CLI, client runtime, and server
|
|
73
|
+
* bootstrap all quote the same string.
|
|
74
|
+
*
|
|
75
|
+
* Path-based lookups (`require("../../package.json")`) do not survive the
|
|
76
|
+
* tsdown bundle: the source lives at `src/core/version.ts` but every
|
|
77
|
+
* emitted chunk lands in `dist/` — shifting the relative depth by one and
|
|
78
|
+
* pointing at `packages/package.json` instead of our own manifest (the
|
|
79
|
+
* v0.9.1 "Cannot find module ../../package.json" crash). Walk up from this
|
|
80
|
+
* module's URL and accept the first `package.json` whose `name` matches, so
|
|
81
|
+
* dev runs (`tsx src/cli/index.ts`) and the published bundle
|
|
82
|
+
* (`dist/cli/index.mjs`) both resolve the same file.
|
|
83
|
+
*/
|
|
84
|
+
const PACKAGE_NAME = "@agent-team-foundation/first-tree-hub";
|
|
85
|
+
/**
|
|
86
|
+
* Sentinel returned when the walker exhausts every parent directory without
|
|
87
|
+
* finding our manifest. Deliberately NOT valid SemVer so the client-side
|
|
88
|
+
* `UpdateManager` drops into its `semver.valid(current) === false` warn-and-
|
|
89
|
+
* skip branch instead of treating it as `< target` and triggering a spurious
|
|
90
|
+
* self-update loop (the scenario where the startup crash this module fixes
|
|
91
|
+
* would otherwise quietly reincarnate as repeated `npm install -g @latest`).
|
|
92
|
+
*/
|
|
93
|
+
const UNRESOLVED_VERSION = "unknown";
|
|
94
|
+
/**
|
|
95
|
+
* Exported for tests. Walks up from `moduleUrl`'s directory looking for a
|
|
96
|
+
* `package.json` whose `name` field equals {@link PACKAGE_NAME}. Returns
|
|
97
|
+
* {@link UNRESOLVED_VERSION} as a last-resort fallback so the CLI never
|
|
98
|
+
* crashes on a missing manifest.
|
|
99
|
+
*/
|
|
100
|
+
function resolveCommandVersion(moduleUrl = import.meta.url) {
|
|
101
|
+
let dir = dirname(fileURLToPath(moduleUrl));
|
|
102
|
+
for (let i = 0; i < 10; i++) {
|
|
103
|
+
try {
|
|
104
|
+
const pkg = JSON.parse(readFileSync(resolve(dir, "package.json"), "utf8"));
|
|
105
|
+
if (pkg.name === PACKAGE_NAME && typeof pkg.version === "string" && pkg.version.length > 0) return pkg.version;
|
|
106
|
+
} catch (err) {
|
|
107
|
+
const code = err.code;
|
|
108
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") {
|
|
109
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
110
|
+
print.line(`[first-tree-hub] warning: could not read ${dir}/package.json: ${message}\n`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const parent = dirname(dir);
|
|
114
|
+
if (parent === dir) break;
|
|
115
|
+
dir = parent;
|
|
116
|
+
}
|
|
117
|
+
return UNRESOLVED_VERSION;
|
|
118
|
+
}
|
|
119
|
+
const COMMAND_VERSION = resolveCommandVersion();
|
|
120
|
+
/**
|
|
121
|
+
* `User-Agent` string sent on every CLI-originated HTTP request (SDK fetches,
|
|
122
|
+
* `/auth/refresh`, etc.). Without this Node defaults to `User-Agent: node`,
|
|
123
|
+
* which hides install / version / platform context from server-side trace
|
|
124
|
+
* backends — see issue #246. The format follows RFC 7231 §5.5.3 conventions
|
|
125
|
+
* (`product/version (comment)`).
|
|
126
|
+
*/
|
|
127
|
+
const CLI_USER_AGENT = `first-tree-hub-cli/${COMMAND_VERSION} (${process.platform} ${process.arch})`;
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/core/cli-fetch.ts
|
|
130
|
+
/**
|
|
131
|
+
* Drop-in `fetch` wrapper that stamps `User-Agent: first-tree-hub-cli/<version> (<platform> <arch>)`
|
|
132
|
+
* on every request.
|
|
133
|
+
*
|
|
134
|
+
* Issue #246: every CLI-originated HTTP request must carry a stable
|
|
135
|
+
* `User-Agent` so trace backends can group failures (401 / 429 / 5xx) by
|
|
136
|
+
* install. Plain `globalThis.fetch` defaults to `User-Agent: node`, which
|
|
137
|
+
* collapses every install into a single bucket. Centralising the header
|
|
138
|
+
* here means new direct-fetch sites pick the right UA up automatically —
|
|
139
|
+
* no need to remember at each call site.
|
|
140
|
+
*
|
|
141
|
+
* SDK-routed requests stamp UA via {@link FirstTreeHubSDK} (see
|
|
142
|
+
* `packages/client/src/sdk.ts`). This helper is for the
|
|
143
|
+
* direct-`fetch` paths the SDK doesn't cover (auth bootstrap,
|
|
144
|
+
* doctor probes, raw admin calls).
|
|
145
|
+
*
|
|
146
|
+
* Header precedence: caller-provided `User-Agent` in `init.headers` wins,
|
|
147
|
+
* so callers can override (e.g. tests injecting a deterministic UA).
|
|
148
|
+
*/
|
|
149
|
+
function cliFetch(input, init) {
|
|
150
|
+
const merged = mergeHeaders(init?.headers);
|
|
151
|
+
return fetch(input, {
|
|
152
|
+
...init,
|
|
153
|
+
headers: merged
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
function mergeHeaders(provided) {
|
|
157
|
+
const out = {};
|
|
158
|
+
if (provided) if (provided instanceof Headers) provided.forEach((v, k) => {
|
|
159
|
+
out[k] = v;
|
|
160
|
+
});
|
|
161
|
+
else if (Array.isArray(provided)) for (const [k, v] of provided) out[k] = v;
|
|
162
|
+
else Object.assign(out, provided);
|
|
163
|
+
if (!Object.keys(out).some((k) => k.toLowerCase() === "user-agent")) out["User-Agent"] = CLI_USER_AGENT;
|
|
164
|
+
return out;
|
|
165
|
+
}
|
|
166
|
+
//#endregion
|
|
167
|
+
export { print as a, blank as i, CLI_USER_AGENT as n, setJsonMode as o, COMMAND_VERSION as r, status as s, cliFetch as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as ConflictError, i as ClientUserMismatchError, l as organizations, n as BadRequestError, s as NotFoundError, u as users } from "./errors-BmyRwN0Y-
|
|
1
|
+
import { w as clientCapabilitiesSchema } from "./dist-ClFs4WMj.mjs";
|
|
2
|
+
import { a as ConflictError, i as ClientUserMismatchError, l as organizations, n as BadRequestError, s as NotFoundError, u as users } from "./errors-BmyRwN0Y-Dad3eV8F.mjs";
|
|
3
3
|
import { and, eq, inArray, ne, sql } from "drizzle-orm";
|
|
4
4
|
import { index, integer, jsonb, pgTable, text, timestamp, unique } from "drizzle-orm/pg-core";
|
|
5
5
|
//#region ../server/dist/client-CLdRbuml.mjs
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as __require, t as __commonJSMin } from "./chunk-BSw8zbkd.mjs";
|
|
2
|
-
import { t as require_src } from "./src-
|
|
2
|
+
import { t as require_src } from "./src-aJMV60mR.mjs";
|
|
3
3
|
//#region ../../node_modules/.pnpm/agent-base@7.1.4/node_modules/agent-base/dist/helpers.js
|
|
4
4
|
var require_helpers = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
5
5
|
var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
|
|
@@ -1140,6 +1140,60 @@ const githubDevCallbackQuerySchema = z.object({
|
|
|
1140
1140
|
displayName: z.string().optional(),
|
|
1141
1141
|
next: z.string().max(256).optional()
|
|
1142
1142
|
});
|
|
1143
|
+
/**
|
|
1144
|
+
* Per-organization settings — schemas, namespaces, and the registry that
|
|
1145
|
+
* dispatches `(orgId, namespace)` lookups to the right validator.
|
|
1146
|
+
*
|
|
1147
|
+
* Each namespace has three schemas:
|
|
1148
|
+
* - `storage` — what is persisted in `organization_settings.value`. For
|
|
1149
|
+
* namespaces with secrets, the storage schema names the *cipher* field
|
|
1150
|
+
* (e.g. `webhookSecretCipher`); plaintext never touches the row.
|
|
1151
|
+
* - `input` — what the admin API accepts in PUT bodies. For namespaces
|
|
1152
|
+
* with secrets, `webhookSecret` is plaintext; the service layer
|
|
1153
|
+
* encrypts it before merging into storage.
|
|
1154
|
+
* - `output` — what GET returns. Secrets are replaced by a boolean
|
|
1155
|
+
* `…Configured` flag — plaintext is never echoed.
|
|
1156
|
+
*
|
|
1157
|
+
* Adding a new per-org config group:
|
|
1158
|
+
* 1. Define three schemas (storage / input / output).
|
|
1159
|
+
* 2. Add a key to `ORG_SETTINGS_NAMESPACES`.
|
|
1160
|
+
* 3. Done. No DB migration, no new API route.
|
|
1161
|
+
*/
|
|
1162
|
+
const orgContextTreeStorageSchema = z.object({
|
|
1163
|
+
repo: z.string().url().optional(),
|
|
1164
|
+
branch: z.string().default("main")
|
|
1165
|
+
});
|
|
1166
|
+
const orgContextTreeInputSchema = z.object({
|
|
1167
|
+
repo: z.string().url().min(1).nullish(),
|
|
1168
|
+
branch: z.string().min(1).nullish()
|
|
1169
|
+
});
|
|
1170
|
+
const orgContextTreeOutputSchema = z.object({
|
|
1171
|
+
repo: z.string().optional(),
|
|
1172
|
+
branch: z.string().optional()
|
|
1173
|
+
});
|
|
1174
|
+
const orgGithubIntegrationStorageSchema = z.object({ webhookSecretCipher: z.string().optional() });
|
|
1175
|
+
const orgGithubIntegrationInputSchema = z.object({ webhookSecret: z.string().min(1).nullish() });
|
|
1176
|
+
const orgGithubIntegrationOutputSchema = z.object({
|
|
1177
|
+
webhookSecretConfigured: z.boolean(),
|
|
1178
|
+
webhookUrl: z.string()
|
|
1179
|
+
});
|
|
1180
|
+
const ORG_SETTINGS_NAMESPACES = {
|
|
1181
|
+
context_tree: {
|
|
1182
|
+
storage: orgContextTreeStorageSchema,
|
|
1183
|
+
input: orgContextTreeInputSchema,
|
|
1184
|
+
output: orgContextTreeOutputSchema
|
|
1185
|
+
},
|
|
1186
|
+
github_integration: {
|
|
1187
|
+
storage: orgGithubIntegrationStorageSchema,
|
|
1188
|
+
input: orgGithubIntegrationInputSchema,
|
|
1189
|
+
output: orgGithubIntegrationOutputSchema
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
const ORG_SETTINGS_NAMESPACE_KEYS = Object.keys(ORG_SETTINGS_NAMESPACES);
|
|
1193
|
+
z.enum(ORG_SETTINGS_NAMESPACE_KEYS);
|
|
1194
|
+
function isOrgSettingNamespace(value) {
|
|
1195
|
+
return typeof value === "string" && ORG_SETTINGS_NAMESPACE_KEYS.includes(value);
|
|
1196
|
+
}
|
|
1143
1197
|
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1144
1198
|
z.object({
|
|
1145
1199
|
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"),
|
|
@@ -1467,4 +1521,4 @@ z.object({
|
|
|
1467
1521
|
capabilities: serverCapabilitiesSchema.optional()
|
|
1468
1522
|
}).passthrough();
|
|
1469
1523
|
//#endregion
|
|
1470
|
-
export {
|
|
1524
|
+
export { loginSchema as $, createAgentSchema as A, githubCallbackQuerySchema as B, agentTypeSchema as C, updateMemberSchema as Ct, contextTreeSnapshotSchema as D, connectTokenExchangeSchema as E, wsAuthFrameSchema as Et, createTaskSchema as F, inboxDeliverFrameSchema as G, githubStartQuerySchema as H, defaultRuntimeConfigPayload as I, isRedactedEnvValue as J, inboxPollQuerySchema as K, delegateFeishuUserSchema as L, createMeChatSchema as M, createMemberSchema as N, createAdapterConfigSchema as O, createOrgFromMeSchema as P, listMeChatsQuerySchema as Q, dryRunAgentRuntimeConfigSchema as R, agentRuntimeConfigPayloadSchema as S, updateClientCapabilitiesSchema as St, clientRegisterSchema as T, updateTaskStatusSchema as Tt, imageInlineContentSchema as U, githubDevCallbackQuerySchema as V, inboxAckFrameSchema as W, joinByInvitationSchema as X, isReservedAgentName as Y, linkTaskChatSchema as Z, addParticipantSchema as _, taskListQuerySchema as _t, AGENT_STATUSES as a, runtimeStateMessageSchema as at, agentBindRequestSchema as b, updateAgentSchema as bt, DEFAULT_RUNTIME_PROVIDER as c, selfServiceFeishuBotSchema as ct, TASK_CREATOR_TYPES as d, sessionCompletionMessageSchema as dt, messageSourceSchema as et, TASK_HEALTH_SIGNALS as f, sessionEventMessageSchema as ft, addMeChatParticipantsSchema as g, stripCode as gt, WS_AUTH_FRAME_TIMEOUT_MS as h, sessionStateMessageSchema as ht, AGENT_SOURCES as i, refreshTokenSchema as it, createChatSchema as j, createAdapterMappingSchema as k, MENTION_REGEX as l, sendMessageSchema as lt, TASK_TERMINAL_STATUSES as m, sessionReconcileRequestSchema as mt, AGENT_NAME_REGEX as n, paginationQuerySchema as nt, AGENT_TYPES as o, safeRedirectPath as ot, TASK_STATUSES as p, sessionEventSchema as pt, isOrgSettingNamespace as q, AGENT_SELECTOR_HEADER as r, rebindAgentSchema as rt, AGENT_VISIBILITY as s, scanMentionTokens as st, AGENT_BIND_REJECT_REASONS as t, notificationQuerySchema as tt, ORG_SETTINGS_NAMESPACES as u, sendToAgentSchema as ut, adminCreateTaskSchema as v, updateAdapterConfigSchema as vt, clientCapabilitiesSchema as w, updateOrganizationSchema as wt, agentPinnedMessageSchema as x, updateChatSchema as xt, adminUpdateTaskSchema as y, updateAgentRuntimeConfigSchema as yt, extractMentions as z };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
-- Per-organization settings, keyed by (organization_id, namespace).
|
|
2
|
+
--
|
|
3
|
+
-- Each row holds an entire group of related config as a JSONB blob; the
|
|
4
|
+
-- schema for each namespace lives in @agent-team-foundation/first-tree-hub-shared
|
|
5
|
+
-- (ORG_SETTINGS_NAMESPACES) and is enforced by the service layer on every
|
|
6
|
+
-- read/write. Adding a new config group means registering a new namespace +
|
|
7
|
+
-- Zod schema in shared — the DB does not change.
|
|
8
|
+
--
|
|
9
|
+
-- `version` is reserved for future optimistic locking (PUT with If-Match).
|
|
10
|
+
-- We keep the column from day one so tightening to compare-and-swap later
|
|
11
|
+
-- is a code-only change with no migration.
|
|
12
|
+
--
|
|
13
|
+
-- Sensitive fields inside `value` (e.g. github_integration.webhookSecret)
|
|
14
|
+
-- are AES-256-GCM-encrypted at the service layer using crypto.ts's
|
|
15
|
+
-- encryptValue / decryptValue — same pattern as adapter_configs.
|
|
16
|
+
--
|
|
17
|
+
-- ON DELETE CASCADE on organization_id: settings have no independent
|
|
18
|
+
-- lifecycle, deleting an org must drop them. updated_by is SET NULL so a
|
|
19
|
+
-- user deletion does not cascade-clobber unrelated config rows.
|
|
20
|
+
|
|
21
|
+
CREATE TABLE IF NOT EXISTS "organization_settings" (
|
|
22
|
+
"organization_id" text NOT NULL,
|
|
23
|
+
"namespace" text NOT NULL,
|
|
24
|
+
"value" jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
25
|
+
"version" integer NOT NULL DEFAULT 0,
|
|
26
|
+
"updated_by" text,
|
|
27
|
+
"updated_at" timestamp with time zone NOT NULL DEFAULT now(),
|
|
28
|
+
CONSTRAINT "organization_settings_pkey" PRIMARY KEY ("organization_id", "namespace"),
|
|
29
|
+
CONSTRAINT "organization_settings_organization_id_organizations_id_fk"
|
|
30
|
+
FOREIGN KEY ("organization_id") REFERENCES "organizations"("id") ON DELETE CASCADE,
|
|
31
|
+
CONSTRAINT "organization_settings_updated_by_users_id_fk"
|
|
32
|
+
FOREIGN KEY ("updated_by") REFERENCES "users"("id") ON DELETE SET NULL
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
CREATE INDEX IF NOT EXISTS "idx_org_settings_namespace"
|
|
36
|
+
ON "organization_settings" ("namespace");
|
|
@@ -225,6 +225,13 @@
|
|
|
225
225
|
"when": 1777939200000,
|
|
226
226
|
"tag": "0031_drop_system_configs",
|
|
227
227
|
"breakpoints": true
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"idx": 32,
|
|
231
|
+
"version": "7",
|
|
232
|
+
"when": 1778198400000,
|
|
233
|
+
"tag": "0032_organization_settings",
|
|
234
|
+
"breakpoints": true
|
|
228
235
|
}
|
|
229
236
|
]
|
|
230
|
-
}
|
|
237
|
+
}
|