@agent-team-foundation/first-tree-hub 0.10.2 → 0.10.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-Ca5Fiqz6.mjs → bootstrap-jx5nN1qZ.mjs} +2 -2
- package/dist/cli/index.mjs +61 -8
- package/dist/{dist-CLiN7cVS.mjs → dist-CbX9mUVH.mjs} +167 -16
- package/dist/drizzle/0027_runtime_provider.sql +10 -0
- package/dist/drizzle/0028_auth_identity_user_github_unique.sql +12 -0
- package/dist/drizzle/meta/_journal.json +14 -0
- package/dist/{feishu-FTWnoOsc.mjs → feishu-DvjRZMdZ.mjs} +1 -1
- package/dist/index.mjs +5 -5
- package/dist/{invitation-C_zAhB8x-8Khychlu.mjs → invitation-BljIolbO-DLeHfURd.mjs} +3 -2
- package/dist/{invitation-BTlGMy0o-dIoR8JRj.mjs → invitation-D3feYxet-366MNOor.mjs} +2 -2
- package/dist/{saas-connect-idjpoPTk.mjs → saas-connect-2puW1r3r.mjs} +1827 -258
- package/dist/web/assets/index-5SNLeFZA.js +392 -0
- package/dist/web/assets/index-BxQQDavm.js +21 -0
- package/dist/web/assets/index-DKZFiOjh.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +3 -2
- package/dist/web/assets/index-CEAPwdg7.js +0 -377
- package/dist/web/assets/index-CzWeWItA.css +0 -1
|
@@ -582,8 +582,7 @@ const serverConfigSchema = defineConfig({
|
|
|
582
582
|
clientSecret: field(z.string(), {
|
|
583
583
|
env: "FIRST_TREE_HUB_GITHUB_OAUTH_CLIENT_SECRET",
|
|
584
584
|
secret: true
|
|
585
|
-
})
|
|
586
|
-
devCallbackEnabled: field(z.boolean().default(false), { env: "FIRST_TREE_HUB_GITHUB_OAUTH_DEV_CALLBACK" })
|
|
585
|
+
})
|
|
587
586
|
}) }),
|
|
588
587
|
cors: optional({ origin: field(z.string(), { env: "FIRST_TREE_HUB_CORS_ORIGIN" }) }),
|
|
589
588
|
rateLimit: optional({
|
|
@@ -591,6 +590,7 @@ const serverConfigSchema = defineConfig({
|
|
|
591
590
|
loginMax: field(z.number().default(5), { env: "FIRST_TREE_HUB_RATE_LIMIT_LOGIN_MAX" }),
|
|
592
591
|
webhookMax: field(z.number().default(60), { env: "FIRST_TREE_HUB_RATE_LIMIT_WEBHOOK_MAX" })
|
|
593
592
|
}),
|
|
593
|
+
inbox: optional({ maxInFlightPerAgent: field(z.number().int().min(1).max(1024).default(32), { env: "FIRST_TREE_HUB_INBOX_MAX_IN_FLIGHT_PER_AGENT" }) }),
|
|
594
594
|
kael: optional({
|
|
595
595
|
endpoint: field(z.string(), { env: "KAEL_ENDPOINT" }),
|
|
596
596
|
apiKey: field(z.string(), {
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "../observability-DPyf745N-BSc8QNcR.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { A as checkServerHealth, C as checkAgentConfigs, D as checkDocker, E as checkDatabase, F as installClientService, G as createOwner, H as ClientRuntime, I as isServiceSupported, J as fail, M as checkWebSocket, N as printResults, O as checkNodeVersion, P as getClientServiceStatus, Q as setJsonMode, S as runMigrations, T as checkClientConfig, U as handleClientOrgMismatch, V as stopPostgres, Y as success, Z as print, _ as onboardCreate, a as declineUpdate, at as probeCapabilities, b as createApiNameResolver, c as COMMAND_VERSION, d as isInteractive, et as ClientOrgMismatchError, f as promptAddAgent, g as onboardCheck, h as loadOnboardState, i as createExecuteUpdate, it as cleanWorkspaces, j as checkServerReachable, k as checkServerConfig, l as reconcileLocalRuntimeProviders, m as formatCheckReport, nt as SdkError, o as promptUpdate, ot as applyClientLoggerConfig, p as promptMissingFields, q as resolveReplyToFromEnv, r as registerSaaSConnectCommand, rt as SessionRegistry, s as startServer, st as configureClientLoggerForService, tt as FirstTreeHubSDK, u as uploadClientCapabilities, v as saveOnboardState, w as checkBackgroundService, x as migrateLocalAgentDirs, y as runHomeMigration } from "../saas-connect-2puW1r3r.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 "../dist-
|
|
7
|
-
import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-
|
|
8
|
-
import "../invitation-
|
|
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-jx5nN1qZ.mjs";
|
|
6
|
+
import "../dist-CbX9mUVH.mjs";
|
|
7
|
+
import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-DvjRZMdZ.mjs";
|
|
8
|
+
import "../invitation-BljIolbO-DLeHfURd.mjs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
11
11
|
import { Command } from "commander";
|
|
@@ -349,7 +349,8 @@ function registerAgentCommands(program) {
|
|
|
349
349
|
const createBody = {
|
|
350
350
|
name,
|
|
351
351
|
type: options.type,
|
|
352
|
-
clientId: options.clientId
|
|
352
|
+
clientId: options.clientId,
|
|
353
|
+
runtimeProvider: options.runtime
|
|
353
354
|
};
|
|
354
355
|
if (options.displayName) createBody.displayName = options.displayName;
|
|
355
356
|
const createRes = await fetch(`${serverUrl}/api/v1/admin/agents`, {
|
|
@@ -916,6 +917,20 @@ function registerConnectCommand(parent) {
|
|
|
916
917
|
const msg = err instanceof Error ? err.message : String(err);
|
|
917
918
|
print.status("⚠️", `agent-dir migration skipped: ${msg}`);
|
|
918
919
|
}
|
|
920
|
+
let probedCapabilities = null;
|
|
921
|
+
try {
|
|
922
|
+
const accessToken = await ensureFreshAccessToken();
|
|
923
|
+
probedCapabilities = await probeCapabilities();
|
|
924
|
+
await reconcileLocalRuntimeProviders({
|
|
925
|
+
serverUrl: config.server.url,
|
|
926
|
+
accessToken,
|
|
927
|
+
agentsDir,
|
|
928
|
+
log: (level, msg) => print.status(level === "warn" ? "⚠️" : "•", msg)
|
|
929
|
+
});
|
|
930
|
+
} catch (err) {
|
|
931
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
932
|
+
print.status("⚠️", `runtime-provider reconcile skipped: ${msg}`);
|
|
933
|
+
}
|
|
919
934
|
const agents = loadAgents({
|
|
920
935
|
schema: agentConfigSchema,
|
|
921
936
|
agentsDir
|
|
@@ -930,6 +945,18 @@ function registerConnectCommand(parent) {
|
|
|
930
945
|
});
|
|
931
946
|
for (const [name, agentConfig] of agents) runtime.addAgent(name, agentConfig);
|
|
932
947
|
await runtime.start();
|
|
948
|
+
if (probedCapabilities) try {
|
|
949
|
+
const accessToken = await ensureFreshAccessToken();
|
|
950
|
+
await uploadClientCapabilities({
|
|
951
|
+
serverUrl: config.server.url,
|
|
952
|
+
accessToken,
|
|
953
|
+
clientId: config.client.id,
|
|
954
|
+
capabilities: probedCapabilities
|
|
955
|
+
});
|
|
956
|
+
} catch (err) {
|
|
957
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
958
|
+
print.status("⚠️", `capabilities upload skipped: ${msg}`);
|
|
959
|
+
}
|
|
933
960
|
runtime.watchAgentsDir(agentsDir);
|
|
934
961
|
const shutdown = async () => {
|
|
935
962
|
print.line("\n Shutting down...\n");
|
|
@@ -989,6 +1016,20 @@ function registerClientCommands(program) {
|
|
|
989
1016
|
const msg = err instanceof Error ? err.message : String(err);
|
|
990
1017
|
print.status("⚠️", `agent-dir migration skipped: ${msg}`);
|
|
991
1018
|
}
|
|
1019
|
+
let probedCapabilities = null;
|
|
1020
|
+
try {
|
|
1021
|
+
const accessToken = await ensureFreshAccessToken();
|
|
1022
|
+
probedCapabilities = await probeCapabilities();
|
|
1023
|
+
await reconcileLocalRuntimeProviders({
|
|
1024
|
+
serverUrl: config.server.url,
|
|
1025
|
+
accessToken,
|
|
1026
|
+
agentsDir,
|
|
1027
|
+
log: (level, msg) => print.status(level === "warn" ? "⚠️" : "•", msg)
|
|
1028
|
+
});
|
|
1029
|
+
} catch (err) {
|
|
1030
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1031
|
+
print.status("⚠️", `runtime-provider reconcile skipped: ${msg}`);
|
|
1032
|
+
}
|
|
992
1033
|
const agents = loadAgents({
|
|
993
1034
|
schema: agentConfigSchema,
|
|
994
1035
|
agentsDir
|
|
@@ -1005,6 +1046,18 @@ function registerClientCommands(program) {
|
|
|
1005
1046
|
});
|
|
1006
1047
|
for (const [name, agentConfig] of agents) runtime.addAgent(name, agentConfig);
|
|
1007
1048
|
await runtime.start();
|
|
1049
|
+
if (probedCapabilities) try {
|
|
1050
|
+
const accessToken = await ensureFreshAccessToken();
|
|
1051
|
+
await uploadClientCapabilities({
|
|
1052
|
+
serverUrl: config.server.url,
|
|
1053
|
+
accessToken,
|
|
1054
|
+
clientId: config.client.id,
|
|
1055
|
+
capabilities: probedCapabilities
|
|
1056
|
+
});
|
|
1057
|
+
} catch (err) {
|
|
1058
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1059
|
+
print.status("⚠️", `capabilities upload skipped: ${msg}`);
|
|
1060
|
+
}
|
|
1008
1061
|
runtime.watchAgentsDir(agentsDir);
|
|
1009
1062
|
const shutdown = async () => {
|
|
1010
1063
|
print.line("\n Shutting down...\n");
|
|
@@ -1212,13 +1265,13 @@ function isSecretField(schema, dotPath) {
|
|
|
1212
1265
|
//#region src/commands/onboard.ts
|
|
1213
1266
|
async function promptMissing(args) {
|
|
1214
1267
|
if (!args.server) try {
|
|
1215
|
-
const { resolveServerUrl } = await import("../bootstrap-
|
|
1268
|
+
const { resolveServerUrl } = await import("../bootstrap-jx5nN1qZ.mjs").then((n) => n.t);
|
|
1216
1269
|
resolveServerUrl();
|
|
1217
1270
|
} catch {
|
|
1218
1271
|
args.server = await input({ message: "Hub server URL:" });
|
|
1219
1272
|
saveOnboardState(args);
|
|
1220
1273
|
}
|
|
1221
|
-
const { loadCredentials } = await import("../bootstrap-
|
|
1274
|
+
const { loadCredentials } = await import("../bootstrap-jx5nN1qZ.mjs").then((n) => n.t);
|
|
1222
1275
|
if (!loadCredentials()) throw new Error("No saved credentials. Run `first-tree-hub client connect <server-url>` before onboarding.");
|
|
1223
1276
|
if (!args.id) {
|
|
1224
1277
|
args.id = await input({ message: "Agent ID:" });
|
|
@@ -160,14 +160,16 @@ const AGENT_BIND_REJECT_REASONS = {
|
|
|
160
160
|
NOT_OWNED: "not_owned",
|
|
161
161
|
AGENT_SUSPENDED: "agent_suspended",
|
|
162
162
|
WRONG_ORG: "wrong_org",
|
|
163
|
-
UNKNOWN_AGENT: "unknown_agent"
|
|
163
|
+
UNKNOWN_AGENT: "unknown_agent",
|
|
164
|
+
RUNTIME_PROVIDER_MISMATCH: "runtime_provider_mismatch"
|
|
164
165
|
};
|
|
165
166
|
z.enum([
|
|
166
167
|
"wrong_client",
|
|
167
168
|
"not_owned",
|
|
168
169
|
"agent_suspended",
|
|
169
170
|
"wrong_org",
|
|
170
|
-
"unknown_agent"
|
|
171
|
+
"unknown_agent",
|
|
172
|
+
"runtime_provider_mismatch"
|
|
171
173
|
]);
|
|
172
174
|
/** Header used on agent-scoped HTTP calls to select which managed agent the JWT acts as. */
|
|
173
175
|
const AGENT_SELECTOR_HEADER = "x-agent-id";
|
|
@@ -195,6 +197,8 @@ z.object({
|
|
|
195
197
|
}),
|
|
196
198
|
clients: z.number().int()
|
|
197
199
|
});
|
|
200
|
+
const runtimeProviderSchema = z.enum(["claude-code", "codex"]);
|
|
201
|
+
const DEFAULT_RUNTIME_PROVIDER = "claude-code";
|
|
198
202
|
const AGENT_TYPES = {
|
|
199
203
|
HUMAN: "human",
|
|
200
204
|
PERSONAL_ASSISTANT: "personal_assistant",
|
|
@@ -254,7 +258,8 @@ const createAgentSchema = z.object({
|
|
|
254
258
|
visibility: agentVisibilitySchema.optional(),
|
|
255
259
|
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
256
260
|
managerId: z.string().optional(),
|
|
257
|
-
clientId: z.string().min(1).max(100).optional()
|
|
261
|
+
clientId: z.string().min(1).max(100).optional(),
|
|
262
|
+
runtimeProvider: runtimeProviderSchema.optional()
|
|
258
263
|
});
|
|
259
264
|
const updateAgentSchema = z.object({
|
|
260
265
|
type: agentTypeSchema.optional(),
|
|
@@ -265,6 +270,18 @@ const updateAgentSchema = z.object({
|
|
|
265
270
|
managerId: z.string().nullable().optional(),
|
|
266
271
|
clientId: z.string().min(1).max(100).nullable().optional()
|
|
267
272
|
});
|
|
273
|
+
/**
|
|
274
|
+
* Service-level rebind input. Admin / owner re-binds an agent to a new
|
|
275
|
+
* client and/or a new runtime provider in one atomic operation.
|
|
276
|
+
*
|
|
277
|
+
* `force` bypasses the capability-match check (e.g. when the client is
|
|
278
|
+
* offline and capabilities are stale).
|
|
279
|
+
*/
|
|
280
|
+
const rebindAgentSchema = z.object({
|
|
281
|
+
clientId: z.string().min(1).max(100),
|
|
282
|
+
runtimeProvider: runtimeProviderSchema,
|
|
283
|
+
force: z.boolean().optional()
|
|
284
|
+
});
|
|
268
285
|
z.object({
|
|
269
286
|
uuid: z.string(),
|
|
270
287
|
name: z.string().nullable(),
|
|
@@ -279,6 +296,7 @@ z.object({
|
|
|
279
296
|
metadata: z.record(z.string(), z.unknown()),
|
|
280
297
|
managerId: z.string().nullable(),
|
|
281
298
|
clientId: z.string().nullable(),
|
|
299
|
+
runtimeProvider: runtimeProviderSchema,
|
|
282
300
|
presenceStatus: presenceStatusSchema.optional(),
|
|
283
301
|
createdAt: z.string(),
|
|
284
302
|
updatedAt: z.string()
|
|
@@ -298,14 +316,16 @@ const agentPinnedMessageSchema = z.object({
|
|
|
298
316
|
agentId: z.string(),
|
|
299
317
|
name: z.string().nullable(),
|
|
300
318
|
displayName: z.string(),
|
|
301
|
-
agentType: agentTypeSchema
|
|
319
|
+
agentType: agentTypeSchema,
|
|
320
|
+
runtimeProvider: runtimeProviderSchema
|
|
302
321
|
});
|
|
303
322
|
/**
|
|
304
|
-
* Agent runtime configuration
|
|
323
|
+
* Agent runtime configuration.
|
|
305
324
|
*
|
|
306
325
|
* Defines the 5 user-tunable field groups that the Hub centrally manages
|
|
307
326
|
* and pushes down to the client runtime: prompt append, model, MCP servers,
|
|
308
|
-
* env vars, and Git repos.
|
|
327
|
+
* env vars, and Git repos. Tagged by `kind` (a runtime provider) so future
|
|
328
|
+
* provider-specific fields can land on a dedicated variant.
|
|
309
329
|
*
|
|
310
330
|
* NOTE: do not co-locate with `packages/shared/src/config/` — that namespace
|
|
311
331
|
* is reserved for the local YAML config (`agent.yaml` / server / client) and
|
|
@@ -349,9 +369,11 @@ const gitRepoSchema = z.object({
|
|
|
349
369
|
localPath: z.string().min(1).optional()
|
|
350
370
|
});
|
|
351
371
|
/**
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
372
|
+
* Untagged base shape — 5 user-tunable fields, no `kind` discriminator.
|
|
373
|
+
* Used for `.partial()` derivations on the PATCH side, where `kind` is
|
|
374
|
+
* pinned to `agents.runtime_provider` and never changes via config PATCH.
|
|
375
|
+
* Zod 4 forbids `.partial()` on a refined object, so we keep refinements
|
|
376
|
+
* on the tagged schema below.
|
|
355
377
|
*/
|
|
356
378
|
const agentRuntimeConfigPayloadShape = z.object({
|
|
357
379
|
prompt: promptConfigSchema.default({ append: "" }),
|
|
@@ -360,6 +382,17 @@ const agentRuntimeConfigPayloadShape = z.object({
|
|
|
360
382
|
env: z.array(envEntrySchema).default([]),
|
|
361
383
|
gitRepos: z.array(gitRepoSchema).default([])
|
|
362
384
|
});
|
|
385
|
+
/**
|
|
386
|
+
* Tagged variants — read-side, full payload including `kind`. Adding a new
|
|
387
|
+
* provider means adding a variant here, plus a handler factory and a
|
|
388
|
+
* capability probe module on the client side.
|
|
389
|
+
*
|
|
390
|
+
* Provider-specific fields (e.g. codex `sandboxMode`) belong on the
|
|
391
|
+
* matching variant, not on the base shape.
|
|
392
|
+
*/
|
|
393
|
+
const claudeRuntimeConfigPayloadShape = agentRuntimeConfigPayloadShape.extend({ kind: z.literal("claude-code") });
|
|
394
|
+
const codexRuntimeConfigPayloadShape = agentRuntimeConfigPayloadShape.extend({ kind: z.literal("codex") });
|
|
395
|
+
const taggedPayloadUnion = z.discriminatedUnion("kind", [claudeRuntimeConfigPayloadShape, codexRuntimeConfigPayloadShape]);
|
|
363
396
|
const payloadDuplicatesRefinement = (payload, ctx) => {
|
|
364
397
|
const seenMcp = /* @__PURE__ */ new Set();
|
|
365
398
|
payload.mcpServers.forEach((server, idx) => {
|
|
@@ -404,15 +437,54 @@ const payloadDuplicatesRefinement = (payload, ctx) => {
|
|
|
404
437
|
seenPaths.add(path);
|
|
405
438
|
});
|
|
406
439
|
};
|
|
407
|
-
|
|
408
|
-
|
|
440
|
+
/**
|
|
441
|
+
* Read-side full payload schema. Rows persisted before 0026 do not carry
|
|
442
|
+
* `kind`; `z.preprocess` injects `"claude-code"` so they parse cleanly into
|
|
443
|
+
* the claude variant. The service layer separately enforces
|
|
444
|
+
* `payload.kind === agents.runtime_provider` on writes.
|
|
445
|
+
*/
|
|
446
|
+
const agentRuntimeConfigPayloadSchema = z.preprocess((input) => {
|
|
447
|
+
if (input && typeof input === "object" && !Array.isArray(input) && !("kind" in input)) return {
|
|
448
|
+
...input,
|
|
449
|
+
kind: "claude-code"
|
|
450
|
+
};
|
|
451
|
+
return input;
|
|
452
|
+
}, taggedPayloadUnion).superRefine((payload, ctx) => {
|
|
453
|
+
payloadDuplicatesRefinement(payload, ctx);
|
|
454
|
+
});
|
|
455
|
+
/** Default payload used when creating a fresh claude-code agent. */
|
|
409
456
|
const DEFAULT_AGENT_RUNTIME_CONFIG_PAYLOAD = {
|
|
457
|
+
kind: "claude-code",
|
|
410
458
|
prompt: { append: "" },
|
|
411
459
|
model: "opus",
|
|
412
460
|
mcpServers: [],
|
|
413
461
|
env: [],
|
|
414
462
|
gitRepos: []
|
|
415
463
|
};
|
|
464
|
+
/**
|
|
465
|
+
* Default payload for a fresh codex agent. Same 5 fields as claude-code.
|
|
466
|
+
* `model` is left empty by default so the Codex CLI picks one matching the
|
|
467
|
+
* user's auth mode — `gpt-5-codex` is rejected by ChatGPT-account auth, while
|
|
468
|
+
* an empty string lets the SDK fall through to its built-in default.
|
|
469
|
+
*/
|
|
470
|
+
const DEFAULT_CODEX_RUNTIME_CONFIG_PAYLOAD = {
|
|
471
|
+
kind: "codex",
|
|
472
|
+
prompt: { append: "" },
|
|
473
|
+
model: "",
|
|
474
|
+
mcpServers: [],
|
|
475
|
+
env: [],
|
|
476
|
+
gitRepos: []
|
|
477
|
+
};
|
|
478
|
+
/**
|
|
479
|
+
* Default payload selector by runtime provider.
|
|
480
|
+
*/
|
|
481
|
+
function defaultRuntimeConfigPayload(provider) {
|
|
482
|
+
switch (provider) {
|
|
483
|
+
case "codex": return { ...DEFAULT_CODEX_RUNTIME_CONFIG_PAYLOAD };
|
|
484
|
+
case "claude-code": return { ...DEFAULT_AGENT_RUNTIME_CONFIG_PAYLOAD };
|
|
485
|
+
default: return { ...DEFAULT_AGENT_RUNTIME_CONFIG_PAYLOAD };
|
|
486
|
+
}
|
|
487
|
+
}
|
|
416
488
|
const agentRuntimeConfigSchema = z.object({
|
|
417
489
|
agentId: z.string(),
|
|
418
490
|
version: z.number().int().positive(),
|
|
@@ -525,12 +597,53 @@ z.object({
|
|
|
525
597
|
lastSeenAt: z.string(),
|
|
526
598
|
metadata: z.record(z.string(), z.unknown()).nullable()
|
|
527
599
|
});
|
|
600
|
+
/**
|
|
601
|
+
* Optional opt-in flags the client carries on `client:register` to advertise
|
|
602
|
+
* which negotiable wire-protocol features it implements. Distinct from
|
|
603
|
+
* `clientCapabilitiesSchema` (per-runtime-provider availability — different
|
|
604
|
+
* concept). Older clients omit the field; the server treats every unset flag
|
|
605
|
+
* as `false` and falls back to the legacy path. See proposal
|
|
606
|
+
* hub-inbox-ws-data-plane §3.6.
|
|
607
|
+
*/
|
|
608
|
+
const clientWireCapabilitiesSchema = z.object({ wsInboxDeliver: z.boolean().default(false) }).partial();
|
|
528
609
|
const clientRegisterSchema = z.object({
|
|
529
610
|
clientId: z.string().min(1).max(100),
|
|
530
611
|
hostname: z.string().max(100).optional(),
|
|
531
612
|
os: z.string().max(50).optional(),
|
|
532
|
-
sdkVersion: z.string().max(50).optional()
|
|
613
|
+
sdkVersion: z.string().max(50).optional(),
|
|
614
|
+
wireCapabilities: clientWireCapabilitiesSchema.optional()
|
|
615
|
+
});
|
|
616
|
+
const capabilityStateSchema = z.enum([
|
|
617
|
+
"ok",
|
|
618
|
+
"missing",
|
|
619
|
+
"unauthenticated",
|
|
620
|
+
"error"
|
|
621
|
+
]);
|
|
622
|
+
const capabilityAuthMethodSchema = z.enum([
|
|
623
|
+
"api_key",
|
|
624
|
+
"oauth",
|
|
625
|
+
"auth_json",
|
|
626
|
+
"none"
|
|
627
|
+
]);
|
|
628
|
+
const capabilityEntrySchema = z.object({
|
|
629
|
+
state: capabilityStateSchema,
|
|
630
|
+
available: z.boolean(),
|
|
631
|
+
authenticated: z.boolean(),
|
|
632
|
+
sdkVersion: z.string().nullable().optional(),
|
|
633
|
+
authMethod: capabilityAuthMethodSchema,
|
|
634
|
+
error: z.string().nullable().optional(),
|
|
635
|
+
detectedAt: z.string()
|
|
533
636
|
});
|
|
637
|
+
/**
|
|
638
|
+
* Capabilities snapshot keyed by runtime provider name. Recorded as a plain
|
|
639
|
+
* `Record<string, CapabilityEntry>` — every entry is optional (a client may
|
|
640
|
+
* report only the runtimes it actually probed) and the key set evolves
|
|
641
|
+
* naturally as new providers ship without a schema migration. Service-layer
|
|
642
|
+
* lookups (`agents.runtime_provider ∈ keys(capabilities)`) treat the keys
|
|
643
|
+
* as `RuntimeProvider` strings.
|
|
644
|
+
*/
|
|
645
|
+
const clientCapabilitiesSchema = z.record(z.string(), capabilityEntrySchema);
|
|
646
|
+
const updateClientCapabilitiesSchema = z.object({ capabilities: clientCapabilitiesSchema });
|
|
534
647
|
const paginationQuerySchema = z.object({
|
|
535
648
|
limit: z.coerce.number().int().min(1).max(100).default(20),
|
|
536
649
|
cursor: z.string().optional()
|
|
@@ -695,11 +808,41 @@ z.object({
|
|
|
695
808
|
ackedAt: z.string().nullable()
|
|
696
809
|
}).extend({ message: clientMessageSchema });
|
|
697
810
|
const inboxPollQuerySchema = z.object({ limit: z.coerce.number().int().min(1).max(50).default(10) });
|
|
811
|
+
/**
|
|
812
|
+
* server → client: a single inbox entry pushed over the active WS connection,
|
|
813
|
+
* replacing the legacy `new_message` doorbell + HTTP `/inbox` poll round-trip.
|
|
814
|
+
*
|
|
815
|
+
* `entryId` is the server-side `inbox_entries.id` the client must echo back
|
|
816
|
+
* in `inbox:ack`. `message` is exactly what the legacy poll path returned —
|
|
817
|
+
* `clientMessageSchema` already carries `precedingMessages`, so the client-
|
|
818
|
+
* side dispatch logic is reused verbatim (see proposal
|
|
819
|
+
* hub-inbox-ws-data-plane §3.1).
|
|
820
|
+
*
|
|
821
|
+
* `.passthrough()` so a forward-rolling server may extend the frame without
|
|
822
|
+
* breaking older clients that validate strictly. Older clients drop unknown
|
|
823
|
+
* fields silently.
|
|
824
|
+
*/
|
|
825
|
+
const inboxDeliverFrameSchema = z.object({
|
|
826
|
+
type: z.literal("inbox:deliver"),
|
|
827
|
+
entryId: z.number().int().nonnegative(),
|
|
828
|
+
inboxId: z.string().min(1),
|
|
829
|
+
chatId: z.string().nullable(),
|
|
830
|
+
message: clientMessageSchema
|
|
831
|
+
}).passthrough();
|
|
832
|
+
/**
|
|
833
|
+
* client → server: ack for an `inbox:deliver` frame. Replaces the legacy
|
|
834
|
+
* `POST /inbox/:id/ack` HTTP endpoint when the WS data plane is active.
|
|
835
|
+
*/
|
|
836
|
+
const inboxAckFrameSchema = z.object({
|
|
837
|
+
type: z.literal("inbox:ack"),
|
|
838
|
+
entryId: z.number().int().nonnegative()
|
|
839
|
+
});
|
|
698
840
|
z.object({
|
|
699
841
|
organizationId: z.string(),
|
|
700
842
|
organizationName: z.string(),
|
|
701
843
|
organizationDisplayName: z.string(),
|
|
702
|
-
role: z.string()
|
|
844
|
+
role: z.string(),
|
|
845
|
+
expiresAt: z.string().nullable()
|
|
703
846
|
});
|
|
704
847
|
z.object({
|
|
705
848
|
id: z.string(),
|
|
@@ -801,7 +944,7 @@ const githubCallbackQuerySchema = z.object({
|
|
|
801
944
|
});
|
|
802
945
|
/**
|
|
803
946
|
* Dev-only callback to bypass the GitHub round-trip — sign in as a stub
|
|
804
|
-
* Github user. Gated by NODE_ENV !== 'production'
|
|
947
|
+
* Github user. Gated by NODE_ENV !== 'production'; production always 404s.
|
|
805
948
|
*/
|
|
806
949
|
const githubDevCallbackQuerySchema = z.object({
|
|
807
950
|
githubId: z.string().min(1),
|
|
@@ -1141,10 +1284,18 @@ const wsAuthFrameSchema = z.object({
|
|
|
1141
1284
|
});
|
|
1142
1285
|
/** How long the server waits for the first `auth` frame before closing the WS. */
|
|
1143
1286
|
const WS_AUTH_FRAME_TIMEOUT_MS = 5e3;
|
|
1287
|
+
/**
|
|
1288
|
+
* Negotiable wire-protocol features the server advertises in its `welcome`
|
|
1289
|
+
* frame. Older clients drop the `capabilities` field silently because the
|
|
1290
|
+
* frame is `.passthrough()`. New clients gate optional code paths on it —
|
|
1291
|
+
* absent ⇒ feature off, never assumed.
|
|
1292
|
+
*/
|
|
1293
|
+
const serverCapabilitiesSchema = z.object({ wsInboxDeliver: z.boolean().default(false) }).partial();
|
|
1144
1294
|
z.object({
|
|
1145
1295
|
type: z.literal("server:welcome"),
|
|
1146
1296
|
serverCommandVersion: z.string().min(1),
|
|
1147
|
-
serverTimeMs: z.number().int().nonnegative()
|
|
1297
|
+
serverTimeMs: z.number().int().nonnegative(),
|
|
1298
|
+
capabilities: serverCapabilitiesSchema.optional()
|
|
1148
1299
|
}).passthrough();
|
|
1149
1300
|
//#endregion
|
|
1150
|
-
export {
|
|
1301
|
+
export { refreshTokenSchema as $, createOrgFromMeSchema as A, imageInlineContentSchema as B, clientRegisterSchema as C, createAgentSchema as D, createAdapterMappingSchema as E, dryRunAgentRuntimeConfigSchema as F, isReservedAgentName as G, inboxDeliverFrameSchema as H, extractMentions as I, loginSchema as J, joinByInvitationSchema as K, githubCallbackQuerySchema as L, createTaskSchema as M, defaultRuntimeConfigPayload as N, createChatSchema as O, delegateFeishuUserSchema as P, rebindAgentSchema as Q, githubDevCallbackQuerySchema as R, clientCapabilitiesSchema as S, wsAuthFrameSchema as St, createAdapterConfigSchema as T, inboxPollQuerySchema as U, inboxAckFrameSchema as V, isRedactedEnvValue as W, notificationQuerySchema as X, messageSourceSchema as Y, paginationQuerySchema as Z, adminUpdateTaskSchema as _, updateClientCapabilitiesSchema as _t, AGENT_STATUSES as a, sendToAgentSchema as at, agentRuntimeConfigPayloadSchema as b, updateSystemConfigSchema as bt, DEFAULT_RUNTIME_PROVIDER as c, sessionEventSchema as ct, TASK_HEALTH_SIGNALS as d, switchOrgSchema as dt, runtimeStateMessageSchema as et, TASK_STATUSES as f, taskListQuerySchema as ft, adminCreateTaskSchema as g, updateChatSchema as gt, addParticipantSchema as h, updateAgentSchema as ht, AGENT_SOURCES as i, sendMessageSchema as it, createOrganizationSchema as j, createMemberSchema as k, SYSTEM_CONFIG_DEFAULTS as l, sessionReconcileRequestSchema as lt, WS_AUTH_FRAME_TIMEOUT_MS as m, updateAgentRuntimeConfigSchema as mt, AGENT_NAME_REGEX as n, scanMentionTokens as nt, AGENT_TYPES as o, sessionCompletionMessageSchema as ot, TASK_TERMINAL_STATUSES as p, updateAdapterConfigSchema as pt, linkTaskChatSchema as q, AGENT_SELECTOR_HEADER as r, selfServiceFeishuBotSchema as rt, AGENT_VISIBILITY as s, sessionEventMessageSchema as st, AGENT_BIND_REJECT_REASONS as t, safeRedirectPath as tt, TASK_CREATOR_TYPES as u, sessionStateMessageSchema as ut, agentBindRequestSchema as v, updateMemberSchema as vt, connectTokenExchangeSchema as w, agentTypeSchema as x, updateTaskStatusSchema as xt, agentPinnedMessageSchema as y, updateOrganizationSchema as yt, githubStartQuerySchema as z };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- Add runtime_provider to agents.
|
|
2
|
+
--
|
|
3
|
+
-- Tags each agent with the runtime that drives it (e.g. "claude-code", "codex").
|
|
4
|
+
-- DEFAULT 'claude-code' backfills every existing row so the NOT NULL constraint
|
|
5
|
+
-- is safe to land in a single step. Hub deploys are stop-migrate-restart, not
|
|
6
|
+
-- rolling, so we don't need a two-phase add (nullable → backfill → not null).
|
|
7
|
+
--
|
|
8
|
+
-- Capabilities reporting reuses the existing `clients.metadata` jsonb column
|
|
9
|
+
-- under the `capabilities` subkey (Option C); no SQL change for clients.
|
|
10
|
+
ALTER TABLE "agents" ADD COLUMN "runtime_provider" text DEFAULT 'claude-code' NOT NULL;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
-- Partial unique index: each user can hold at most one github identity.
|
|
2
|
+
--
|
|
3
|
+
-- Defense-in-depth. The (provider, identifier) UNIQUE catches duplicates
|
|
4
|
+
-- of the SAME githubId, but does not stop a single user from collecting
|
|
5
|
+
-- multiple DIFFERENT githubIds (e.g. a future "merge accounts" or
|
|
6
|
+
-- "rebind" flow that misfires, or a one-off SQL migration that errs).
|
|
7
|
+
-- This index makes any such double-bind fail atomically with a
|
|
8
|
+
-- unique-violation at the storage layer.
|
|
9
|
+
|
|
10
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "uq_auth_identities_user_github"
|
|
11
|
+
ON "auth_identities" ("user_id")
|
|
12
|
+
WHERE "provider" = 'github';
|
|
@@ -190,6 +190,20 @@
|
|
|
190
190
|
"when": 1777507200000,
|
|
191
191
|
"tag": "0026_saas_onboarding",
|
|
192
192
|
"breakpoints": true
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"idx": 27,
|
|
196
|
+
"version": "7",
|
|
197
|
+
"when": 1777593600000,
|
|
198
|
+
"tag": "0027_runtime_provider",
|
|
199
|
+
"breakpoints": true
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"idx": 28,
|
|
203
|
+
"version": "7",
|
|
204
|
+
"when": 1777680000000,
|
|
205
|
+
"tag": "0028_auth_identity_user_github_unique",
|
|
206
|
+
"breakpoints": true
|
|
193
207
|
}
|
|
194
208
|
]
|
|
195
209
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { d as __exportAll } from "./esm-CYu4tXXn.mjs";
|
|
2
|
-
import { r as AGENT_SELECTOR_HEADER } from "./dist-
|
|
2
|
+
import { r as AGENT_SELECTOR_HEADER } from "./dist-CbX9mUVH.mjs";
|
|
3
3
|
//#region src/core/feishu.ts
|
|
4
4
|
var feishu_exports = /* @__PURE__ */ __exportAll({
|
|
5
5
|
bindFeishuBot: () => bindFeishuBot,
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "./observability-DPyf745N-BSc8QNcR.mjs";
|
|
2
|
-
import { $ as
|
|
2
|
+
import { $ as status, A as checkServerHealth, B as isDockerAvailable, C as checkAgentConfigs, D as checkDocker, E as checkDatabase, F as installClientService, G as createOwner, H as ClientRuntime, I as isServiceSupported, K as hasUser, L as resolveCliInvocation, M as checkWebSocket, N as printResults, O as checkNodeVersion, P as getClientServiceStatus, R as uninstallClientService, S as runMigrations, T as checkClientConfig, U as handleClientOrgMismatch, V as stopPostgres, W as rotateClientIdWithBackup, X as blank, _ as onboardCreate, d as isInteractive, f as promptAddAgent, g as onboardCheck, j as checkServerReachable, k as checkServerConfig, m as formatCheckReport, n as deriveHubUrlFromToken, nt as SdkError, p as promptMissingFields, s as startServer, t as HubUrlDerivationError, tt as FirstTreeHubSDK, y as runHomeMigration, z as ensurePostgres } from "./saas-connect-2puW1r3r.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 "./dist-
|
|
6
|
-
import { n as bindFeishuUser, t as bindFeishuBot } from "./feishu-
|
|
7
|
-
import "./invitation-
|
|
4
|
+
import { a as resolveAccessToken, n as ensureFreshAccessToken, o as resolveServerUrl, r as ensureFreshAdminToken } from "./bootstrap-jx5nN1qZ.mjs";
|
|
5
|
+
import "./dist-CbX9mUVH.mjs";
|
|
6
|
+
import { n as bindFeishuUser, t as bindFeishuBot } from "./feishu-DvjRZMdZ.mjs";
|
|
7
|
+
import "./invitation-BljIolbO-DLeHfURd.mjs";
|
|
8
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 };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
2
|
import { and, desc, eq, gt, isNull, or } from "drizzle-orm";
|
|
3
3
|
import { index, integer, jsonb, pgTable, text, timestamp } from "drizzle-orm/pg-core";
|
|
4
|
-
//#region ../server/dist/invitation-
|
|
4
|
+
//#region ../server/dist/invitation-BljIolbO.mjs
|
|
5
5
|
/** Organization entity. Agents and chats belong to exactly one organization. */
|
|
6
6
|
const organizations = pgTable("organizations", {
|
|
7
7
|
id: text("id").primaryKey(),
|
|
@@ -230,7 +230,8 @@ async function previewInvitation(db, token) {
|
|
|
230
230
|
organizationId: org.id,
|
|
231
231
|
organizationName: org.name,
|
|
232
232
|
organizationDisplayName: org.displayName,
|
|
233
|
-
role: inv.role
|
|
233
|
+
role: inv.role,
|
|
234
|
+
expiresAt: inv.expiresAt ? inv.expiresAt.toISOString() : null
|
|
234
235
|
};
|
|
235
236
|
}
|
|
236
237
|
/**
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "./dist-
|
|
2
|
-
import { _ as rotateInvitation, h as previewInvitation } from "./invitation-
|
|
1
|
+
import "./dist-CbX9mUVH.mjs";
|
|
2
|
+
import { _ as rotateInvitation, h as previewInvitation } from "./invitation-BljIolbO-DLeHfURd.mjs";
|
|
3
3
|
export { previewInvitation, rotateInvitation };
|