@agent-team-foundation/first-tree-hub 0.10.1 → 0.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/{bootstrap-CtVqQA8a.mjs → bootstrap-CBAVWQUT.mjs} +9 -1
  2. package/dist/cli/index.mjs +63 -20
  3. package/dist/{feishu-DEmwoNn_.mjs → dist-DUCelK3Z.mjs} +202 -62
  4. package/dist/drizzle/0026_saas_onboarding.sql +153 -0
  5. package/dist/drizzle/0027_runtime_provider.sql +10 -0
  6. package/dist/drizzle/0028_auth_identity_user_github_unique.sql +12 -0
  7. package/dist/drizzle/meta/_journal.json +21 -0
  8. package/dist/feishu-Boy3n8CT.mjs +52 -0
  9. package/dist/{getMachineId-bsd-BB-fnFLA.mjs → getMachineId-bsd-D0w3uAZa.mjs} +1 -1
  10. package/dist/{getMachineId-darwin-DAYWNsYK.mjs → getMachineId-darwin-DOoYFb2_.mjs} +1 -1
  11. package/dist/{getMachineId-win-H5RT49ov.mjs → getMachineId-win-B6hY8edq.mjs} +1 -1
  12. package/dist/index.mjs +7 -5
  13. package/dist/invitation-BTlGMy0o-Coj07kYi.mjs +3 -0
  14. package/dist/invitation-C_zAhB8x-8Khychlu.mjs +258 -0
  15. package/dist/{observability-DDkJwSKv.mjs → observability-C08jUFsJ.mjs} +1 -1
  16. package/dist/{observability-DV_fQKqV-oxfXX6Z2.mjs → observability-DPyf745N-BSc8QNcR.mjs} +6 -6
  17. package/dist/{core-BgiFGT7Y.mjs → saas-connect-3p-vBkuY.mjs} +2459 -430
  18. package/dist/web/assets/index-CHoaSIzI.js +21 -0
  19. package/dist/web/assets/index-CP8uLPyO.css +1 -0
  20. package/dist/web/assets/index-D7OzKrI2.js +387 -0
  21. package/dist/web/index.html +2 -2
  22. package/package.json +3 -2
  23. package/dist/web/assets/index-Cd290Lq6.css +0 -1
  24. package/dist/web/assets/index-xi7JmCtW.js +0 -361
  25. /package/dist/{execAsync-CP8iWV5b.mjs → execAsync-XMc-nFn-.mjs} +0 -0
  26. /package/dist/{getMachineId-linux-BU7Fi6S0.mjs → getMachineId-linux-MlY63Zsw.mjs} +0 -0
  27. /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,13 @@ 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
+ }) }),
579
587
  cors: optional({ origin: field(z.string(), { env: "FIRST_TREE_HUB_CORS_ORIGIN" }) }),
580
588
  rateLimit: optional({
581
589
  max: field(z.number().default(100), { env: "FIRST_TREE_HUB_RATE_LIMIT_MAX" }),
@@ -1,26 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import "../observability-DV_fQKqV-oxfXX6Z2.mjs";
3
- import { $ as configureClientLoggerForService, A as installClientService, B as createOwner, C as checkNodeVersion, D as checkWebSocket, E as checkServerReachable, G as setJsonMode, H as resolveReplyToFromEnv, I as stopPostgres, J as FirstTreeHubSDK, L as ClientRuntime, O as printResults, Q as applyClientLoggerConfig, R as handleClientOrgMismatch, S as checkDocker, T as checkServerHealth, W as print, X as SessionRegistry, Y as SdkError, Z as cleanWorkspaces, _ as runMigrations, a as COMMAND_VERSION, b as checkClientConfig, c as promptMissingFields, d as onboardCheck, f as onboardCreate, g as migrateLocalAgentDirs, h as createApiNameResolver, i as startServer, j as isServiceSupported, k as getClientServiceStatus, l as formatCheckReport, m as runHomeMigration, n as declineUpdate, o as isInteractive, p as saveOnboardState, q as ClientOrgMismatchError, r as promptUpdate, s as promptAddAgent, t as createExecuteUpdate, u as loadOnboardState, v as checkAgentConfigs, w as checkServerConfig, x as checkDatabase, y as checkBackgroundService } from "../core-BgiFGT7Y.mjs";
2
+ import "../observability-DPyf745N-BSc8QNcR.mjs";
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-3p-vBkuY.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-CtVqQA8a.mjs";
6
- import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-DEmwoNn_.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-CBAVWQUT.mjs";
6
+ import "../dist-DUCelK3Z.mjs";
7
+ import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-Boy3n8CT.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`, {
@@ -360,7 +349,8 @@ function registerAgentCommands(program) {
360
349
  const createBody = {
361
350
  name,
362
351
  type: options.type,
363
- clientId: options.clientId
352
+ clientId: options.clientId,
353
+ runtimeProvider: options.runtime
364
354
  };
365
355
  if (options.displayName) createBody.displayName = options.displayName;
366
356
  const createRes = await fetch(`${serverUrl}/api/v1/admin/agents`, {
@@ -927,6 +917,20 @@ function registerConnectCommand(parent) {
927
917
  const msg = err instanceof Error ? err.message : String(err);
928
918
  print.status("⚠️", `agent-dir migration skipped: ${msg}`);
929
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
+ }
930
934
  const agents = loadAgents({
931
935
  schema: agentConfigSchema,
932
936
  agentsDir
@@ -941,6 +945,18 @@ function registerConnectCommand(parent) {
941
945
  });
942
946
  for (const [name, agentConfig] of agents) runtime.addAgent(name, agentConfig);
943
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
+ }
944
960
  runtime.watchAgentsDir(agentsDir);
945
961
  const shutdown = async () => {
946
962
  print.line("\n Shutting down...\n");
@@ -1000,6 +1016,20 @@ function registerClientCommands(program) {
1000
1016
  const msg = err instanceof Error ? err.message : String(err);
1001
1017
  print.status("⚠️", `agent-dir migration skipped: ${msg}`);
1002
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
+ }
1003
1033
  const agents = loadAgents({
1004
1034
  schema: agentConfigSchema,
1005
1035
  agentsDir
@@ -1016,6 +1046,18 @@ function registerClientCommands(program) {
1016
1046
  });
1017
1047
  for (const [name, agentConfig] of agents) runtime.addAgent(name, agentConfig);
1018
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
+ }
1019
1061
  runtime.watchAgentsDir(agentsDir);
1020
1062
  const shutdown = async () => {
1021
1063
  print.line("\n Shutting down...\n");
@@ -1223,13 +1265,13 @@ function isSecretField(schema, dotPath) {
1223
1265
  //#region src/commands/onboard.ts
1224
1266
  async function promptMissing(args) {
1225
1267
  if (!args.server) try {
1226
- const { resolveServerUrl } = await import("../bootstrap-CtVqQA8a.mjs").then((n) => n.t);
1268
+ const { resolveServerUrl } = await import("../bootstrap-CBAVWQUT.mjs").then((n) => n.t);
1227
1269
  resolveServerUrl();
1228
1270
  } catch {
1229
1271
  args.server = await input({ message: "Hub server URL:" });
1230
1272
  saveOnboardState(args);
1231
1273
  }
1232
- const { loadCredentials } = await import("../bootstrap-CtVqQA8a.mjs").then((n) => n.t);
1274
+ const { loadCredentials } = await import("../bootstrap-CBAVWQUT.mjs").then((n) => n.t);
1233
1275
  if (!loadCredentials()) throw new Error("No saved credentials. Run `first-tree-hub client connect <server-url>` before onboarding.");
1234
1276
  if (!args.id) {
1235
1277
  args.id = await input({ message: "Agent ID:" });
@@ -1506,6 +1548,7 @@ program.name("first-tree-hub").description("First Tree Hub — centralized colla
1506
1548
  });
1507
1549
  else applyClientLoggerConfig({ level: "warn" });
1508
1550
  });
1551
+ registerSaaSConnectCommand(program);
1509
1552
  registerServerCommands(program);
1510
1553
  registerClientCommands(program);
1511
1554
  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",
@@ -132,14 +160,16 @@ const AGENT_BIND_REJECT_REASONS = {
132
160
  NOT_OWNED: "not_owned",
133
161
  AGENT_SUSPENDED: "agent_suspended",
134
162
  WRONG_ORG: "wrong_org",
135
- UNKNOWN_AGENT: "unknown_agent"
163
+ UNKNOWN_AGENT: "unknown_agent",
164
+ RUNTIME_PROVIDER_MISMATCH: "runtime_provider_mismatch"
136
165
  };
137
166
  z.enum([
138
167
  "wrong_client",
139
168
  "not_owned",
140
169
  "agent_suspended",
141
170
  "wrong_org",
142
- "unknown_agent"
171
+ "unknown_agent",
172
+ "runtime_provider_mismatch"
143
173
  ]);
144
174
  /** Header used on agent-scoped HTTP calls to select which managed agent the JWT acts as. */
145
175
  const AGENT_SELECTOR_HEADER = "x-agent-id";
@@ -167,6 +197,8 @@ z.object({
167
197
  }),
168
198
  clients: z.number().int()
169
199
  });
200
+ const runtimeProviderSchema = z.enum(["claude-code", "codex"]);
201
+ const DEFAULT_RUNTIME_PROVIDER = "claude-code";
170
202
  const AGENT_TYPES = {
171
203
  HUMAN: "human",
172
204
  PERSONAL_ASSISTANT: "personal_assistant",
@@ -226,7 +258,8 @@ const createAgentSchema = z.object({
226
258
  visibility: agentVisibilitySchema.optional(),
227
259
  metadata: z.record(z.string(), z.unknown()).optional(),
228
260
  managerId: z.string().optional(),
229
- clientId: z.string().min(1).max(100).optional()
261
+ clientId: z.string().min(1).max(100).optional(),
262
+ runtimeProvider: runtimeProviderSchema.optional()
230
263
  });
231
264
  const updateAgentSchema = z.object({
232
265
  type: agentTypeSchema.optional(),
@@ -237,6 +270,18 @@ const updateAgentSchema = z.object({
237
270
  managerId: z.string().nullable().optional(),
238
271
  clientId: z.string().min(1).max(100).nullable().optional()
239
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
+ });
240
285
  z.object({
241
286
  uuid: z.string(),
242
287
  name: z.string().nullable(),
@@ -251,6 +296,7 @@ z.object({
251
296
  metadata: z.record(z.string(), z.unknown()),
252
297
  managerId: z.string().nullable(),
253
298
  clientId: z.string().nullable(),
299
+ runtimeProvider: runtimeProviderSchema,
254
300
  presenceStatus: presenceStatusSchema.optional(),
255
301
  createdAt: z.string(),
256
302
  updatedAt: z.string()
@@ -270,14 +316,16 @@ const agentPinnedMessageSchema = z.object({
270
316
  agentId: z.string(),
271
317
  name: z.string().nullable(),
272
318
  displayName: z.string(),
273
- agentType: agentTypeSchema
319
+ agentType: agentTypeSchema,
320
+ runtimeProvider: runtimeProviderSchema
274
321
  });
275
322
  /**
276
- * Agent runtime configuration — M1 (Claude Code only).
323
+ * Agent runtime configuration.
277
324
  *
278
325
  * Defines the 5 user-tunable field groups that the Hub centrally manages
279
326
  * and pushes down to the client runtime: prompt append, model, MCP servers,
280
- * 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.
281
329
  *
282
330
  * NOTE: do not co-locate with `packages/shared/src/config/` — that namespace
283
331
  * is reserved for the local YAML config (`agent.yaml` / server / client) and
@@ -321,9 +369,11 @@ const gitRepoSchema = z.object({
321
369
  localPath: z.string().min(1).optional()
322
370
  });
323
371
  /**
324
- * Base shape (no refinements) used for `.partial()` derivations such as the
325
- * PATCH payload schema. Zod 4 forbids `.partial()` on a refined object, so we
326
- * keep refinements on a separate full schema below.
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.
327
377
  */
328
378
  const agentRuntimeConfigPayloadShape = z.object({
329
379
  prompt: promptConfigSchema.default({ append: "" }),
@@ -332,6 +382,17 @@ const agentRuntimeConfigPayloadShape = z.object({
332
382
  env: z.array(envEntrySchema).default([]),
333
383
  gitRepos: z.array(gitRepoSchema).default([])
334
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]);
335
396
  const payloadDuplicatesRefinement = (payload, ctx) => {
336
397
  const seenMcp = /* @__PURE__ */ new Set();
337
398
  payload.mcpServers.forEach((server, idx) => {
@@ -376,15 +437,54 @@ const payloadDuplicatesRefinement = (payload, ctx) => {
376
437
  seenPaths.add(path);
377
438
  });
378
439
  };
379
- const agentRuntimeConfigPayloadSchema = agentRuntimeConfigPayloadShape.superRefine(payloadDuplicatesRefinement);
380
- /** Default payload used when creating a fresh agent. */
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. */
381
456
  const DEFAULT_AGENT_RUNTIME_CONFIG_PAYLOAD = {
457
+ kind: "claude-code",
382
458
  prompt: { append: "" },
383
459
  model: "opus",
384
460
  mcpServers: [],
385
461
  env: [],
386
462
  gitRepos: []
387
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
+ }
388
488
  const agentRuntimeConfigSchema = z.object({
389
489
  agentId: z.string(),
390
490
  version: z.number().int().positive(),
@@ -503,6 +603,37 @@ const clientRegisterSchema = z.object({
503
603
  os: z.string().max(50).optional(),
504
604
  sdkVersion: z.string().max(50).optional()
505
605
  });
606
+ const capabilityStateSchema = z.enum([
607
+ "ok",
608
+ "missing",
609
+ "unauthenticated",
610
+ "error"
611
+ ]);
612
+ const capabilityAuthMethodSchema = z.enum([
613
+ "api_key",
614
+ "oauth",
615
+ "auth_json",
616
+ "none"
617
+ ]);
618
+ const capabilityEntrySchema = z.object({
619
+ state: capabilityStateSchema,
620
+ available: z.boolean(),
621
+ authenticated: z.boolean(),
622
+ sdkVersion: z.string().nullable().optional(),
623
+ authMethod: capabilityAuthMethodSchema,
624
+ error: z.string().nullable().optional(),
625
+ detectedAt: z.string()
626
+ });
627
+ /**
628
+ * Capabilities snapshot keyed by runtime provider name. Recorded as a plain
629
+ * `Record<string, CapabilityEntry>` — every entry is optional (a client may
630
+ * report only the runtimes it actually probed) and the key set evolves
631
+ * naturally as new providers ship without a schema migration. Service-layer
632
+ * lookups (`agents.runtime_provider ∈ keys(capabilities)`) treat the keys
633
+ * as `RuntimeProvider` strings.
634
+ */
635
+ const clientCapabilitiesSchema = z.record(z.string(), capabilityEntrySchema);
636
+ const updateClientCapabilitiesSchema = z.object({ capabilities: clientCapabilitiesSchema });
506
637
  const paginationQuerySchema = z.object({
507
638
  limit: z.coerce.number().int().min(1).max(100).default(20),
508
639
  cursor: z.string().optional()
@@ -667,6 +798,42 @@ z.object({
667
798
  ackedAt: z.string().nullable()
668
799
  }).extend({ message: clientMessageSchema });
669
800
  const inboxPollQuerySchema = z.object({ limit: z.coerce.number().int().min(1).max(50).default(10) });
801
+ z.object({
802
+ organizationId: z.string(),
803
+ organizationName: z.string(),
804
+ organizationDisplayName: z.string(),
805
+ role: z.string()
806
+ });
807
+ z.object({
808
+ id: z.string(),
809
+ organizationId: z.string(),
810
+ token: z.string(),
811
+ inviteUrl: z.string(),
812
+ role: z.string(),
813
+ createdAt: z.string(),
814
+ expiresAt: z.string().nullable()
815
+ });
816
+ /** Body for joining via invite token. */
817
+ const joinByInvitationSchema = z.object({ token: z.string().min(1) });
818
+ z.object({}).optional();
819
+ z.enum([
820
+ "connect",
821
+ "create_agent",
822
+ "completed"
823
+ ]);
824
+ z.object({
825
+ id: z.string(),
826
+ name: z.string(),
827
+ displayName: z.string(),
828
+ role: z.enum(["admin", "member"])
829
+ });
830
+ /** Body for `POST /me/organizations` — operator wants to create another team. */
831
+ const createOrgFromMeSchema = z.object({
832
+ name: z.string().min(2).max(50).regex(/^[a-z0-9][a-z0-9-]*$/),
833
+ displayName: z.string().min(1).max(200)
834
+ });
835
+ /** Body for `POST /auth/switch-org`. */
836
+ const switchOrgSchema = z.object({ organizationId: z.string().min(1) });
670
837
  const memberRoleSchema = z.enum(["admin", "member"]);
671
838
  const memberSchema = z.object({
672
839
  id: z.string(),
@@ -724,6 +891,28 @@ const notificationQuerySchema = z.object({
724
891
  read: z.enum(["true", "false"]).transform((v) => v === "true").optional(),
725
892
  agentId: z.string().optional()
726
893
  });
894
+ /**
895
+ * `GET /api/v1/auth/github/start` query — `next` is the post-login landing
896
+ * path. It is validated again before signing the state JWT (see
897
+ * `safe-redirect.ts`); the schema only enforces the syntactic upper bound
898
+ * so over-long paths bounce with a Zod error rather than silently truncate.
899
+ */
900
+ const githubStartQuerySchema = z.object({ next: z.string().max(256).optional() });
901
+ const githubCallbackQuerySchema = z.object({
902
+ code: z.string().min(1),
903
+ state: z.string().min(1)
904
+ });
905
+ /**
906
+ * Dev-only callback to bypass the GitHub round-trip — sign in as a stub
907
+ * Github user. Gated by NODE_ENV !== 'production'; production always 404s.
908
+ */
909
+ const githubDevCallbackQuerySchema = z.object({
910
+ githubId: z.string().min(1),
911
+ login: z.string().min(1),
912
+ email: z.string().email().optional(),
913
+ displayName: z.string().optional(),
914
+ next: z.string().max(256).optional()
915
+ });
727
916
  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
917
  const createOrganizationSchema = z.object({
729
918
  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 +1250,4 @@ z.object({
1061
1250
  serverTimeMs: z.number().int().nonnegative()
1062
1251
  }).passthrough();
1063
1252
  //#endregion
1064
- //#region src/core/feishu.ts
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 };
1253
+ export { safeRedirectPath as $, createOrgFromMeSchema as A, imageInlineContentSchema as B, clientRegisterSchema as C, createAgentSchema as D, createAdapterMappingSchema as E, dryRunAgentRuntimeConfigSchema as F, linkTaskChatSchema as G, isRedactedEnvValue as H, extractMentions as I, notificationQuerySchema as J, loginSchema as K, githubCallbackQuerySchema as L, createTaskSchema as M, defaultRuntimeConfigPayload as N, createChatSchema as O, delegateFeishuUserSchema as P, runtimeStateMessageSchema as Q, githubDevCallbackQuerySchema as R, clientCapabilitiesSchema as S, createAdapterConfigSchema as T, isReservedAgentName as U, inboxPollQuerySchema as V, joinByInvitationSchema as W, rebindAgentSchema as X, paginationQuerySchema as Y, refreshTokenSchema as Z, adminUpdateTaskSchema as _, updateOrganizationSchema as _t, AGENT_STATUSES as a, sessionEventMessageSchema as at, agentRuntimeConfigPayloadSchema as b, wsAuthFrameSchema as bt, DEFAULT_RUNTIME_PROVIDER as c, sessionStateMessageSchema as ct, TASK_HEALTH_SIGNALS as d, updateAdapterConfigSchema as dt, scanMentionTokens as et, TASK_STATUSES as f, updateAgentRuntimeConfigSchema as ft, adminCreateTaskSchema as g, updateMemberSchema as gt, addParticipantSchema as h, updateClientCapabilitiesSchema as ht, AGENT_SOURCES as i, sessionCompletionMessageSchema as it, createOrganizationSchema as j, createMemberSchema as k, SYSTEM_CONFIG_DEFAULTS as l, switchOrgSchema as lt, WS_AUTH_FRAME_TIMEOUT_MS as m, updateChatSchema as mt, AGENT_NAME_REGEX as n, sendMessageSchema as nt, AGENT_TYPES as o, sessionEventSchema as ot, TASK_TERMINAL_STATUSES as p, updateAgentSchema as pt, messageSourceSchema as q, AGENT_SELECTOR_HEADER as r, sendToAgentSchema as rt, AGENT_VISIBILITY as s, sessionReconcileRequestSchema as st, AGENT_BIND_REJECT_REASONS as t, selfServiceFeishuBotSchema as tt, TASK_CREATOR_TYPES as u, taskListQuerySchema as ut, agentBindRequestSchema as v, updateSystemConfigSchema as vt, connectTokenExchangeSchema as w, agentTypeSchema as x, agentPinnedMessageSchema as y, updateTaskStatusSchema as yt, githubStartQuerySchema as z };