@agent-team-foundation/first-tree-hub 0.11.3 → 0.11.5

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.
@@ -137,195 +137,6 @@ z.object({
137
137
  connected: z.boolean(),
138
138
  lastActiveAt: z.string().nullable()
139
139
  });
140
- const presenceStatusSchema = z.enum(["online", "offline"]);
141
- const runtimeStateSchema = z.enum([
142
- "idle",
143
- "working",
144
- "blocked",
145
- "error"
146
- ]);
147
- z.enum([
148
- "active",
149
- "suspended",
150
- "evicted"
151
- ]);
152
- /** Wire-level states a client may report. `evicted` from a stale client is rejected. */
153
- const clientSessionStateSchema = z.enum(["active", "suspended"]);
154
- const sessionStateMessageSchema = z.object({
155
- chatId: z.string().min(1),
156
- state: clientSessionStateSchema
157
- });
158
- /** Client-reported runtime state override (client → server, per-agent). */
159
- const runtimeStateMessageSchema = z.object({ runtimeState: runtimeStateSchema });
160
- const agentBindRequestSchema = z.object({
161
- agentId: z.string().min(1),
162
- runtimeType: z.string().max(50),
163
- runtimeVersion: z.string().max(50).optional()
164
- });
165
- const AGENT_BIND_REJECT_REASONS = {
166
- WRONG_CLIENT: "wrong_client",
167
- NOT_OWNED: "not_owned",
168
- AGENT_SUSPENDED: "agent_suspended",
169
- WRONG_ORG: "wrong_org",
170
- UNKNOWN_AGENT: "unknown_agent",
171
- RUNTIME_PROVIDER_MISMATCH: "runtime_provider_mismatch"
172
- };
173
- z.enum([
174
- "wrong_client",
175
- "not_owned",
176
- "agent_suspended",
177
- "wrong_org",
178
- "unknown_agent",
179
- "runtime_provider_mismatch"
180
- ]);
181
- /** Header used on agent-scoped HTTP calls to select which managed agent the JWT acts as. */
182
- const AGENT_SELECTOR_HEADER = "x-agent-id";
183
- z.object({
184
- agentId: z.string(),
185
- status: presenceStatusSchema,
186
- connectedAt: z.string().nullable(),
187
- lastSeenAt: z.string(),
188
- clientId: z.string().nullable().optional(),
189
- runtimeType: z.string().nullable().optional(),
190
- runtimeVersion: z.string().nullable().optional(),
191
- runtimeState: runtimeStateSchema.nullable().optional(),
192
- activeSessions: z.number().int().nullable().optional(),
193
- totalSessions: z.number().int().nullable().optional(),
194
- runtimeUpdatedAt: z.string().nullable().optional()
195
- });
196
- z.object({
197
- total: z.number().int(),
198
- running: z.number().int(),
199
- byState: z.object({
200
- idle: z.number().int(),
201
- working: z.number().int(),
202
- blocked: z.number().int(),
203
- error: z.number().int()
204
- }),
205
- clients: z.number().int()
206
- });
207
- const runtimeProviderSchema = z.enum(["claude-code", "codex"]);
208
- const DEFAULT_RUNTIME_PROVIDER = "claude-code";
209
- const AGENT_TYPES = {
210
- HUMAN: "human",
211
- PERSONAL_ASSISTANT: "personal_assistant",
212
- AUTONOMOUS_AGENT: "autonomous_agent"
213
- };
214
- const agentTypeSchema = z.enum([
215
- "human",
216
- "personal_assistant",
217
- "autonomous_agent"
218
- ]);
219
- const AGENT_VISIBILITY = {
220
- PRIVATE: "private",
221
- ORGANIZATION: "organization"
222
- };
223
- const agentVisibilitySchema = z.enum(["private", "organization"]);
224
- const AGENT_STATUSES = {
225
- ACTIVE: "active",
226
- SUSPENDED: "suspended",
227
- DELETED: "deleted"
228
- };
229
- const AGENT_SOURCES = {
230
- ADMIN_API: "admin-api",
231
- PORTAL: "portal"
232
- };
233
- const agentSourceSchema = z.enum(["admin-api", "portal"]);
234
- z.enum(["active", "suspended"]);
235
- /**
236
- * Agent-name rules (see docs/agent-naming-design.md §3.1):
237
- * - Lowercase ASCII slug, hyphens + underscores allowed.
238
- * - Must start with alphanumeric: `-` / `_` as first char collide with
239
- * CLI flag parsing and markdown list syntax.
240
- * - 1–64 chars — aligned with `MENTION_REGEX` so any valid name can be
241
- * @-mentioned in chat. Older rows created under the previous 1–100
242
- * regex are grandfathered; the tight rule only gates new creates.
243
- */
244
- const AGENT_NAME_REGEX = /^[a-z0-9][a-z0-9_-]{0,63}$/;
245
- const RESERVED_AGENT_NAMES_SET = new Set([
246
- "admin",
247
- "agent",
248
- "first-tree",
249
- "hub",
250
- "me",
251
- "null",
252
- "system",
253
- "undefined"
254
- ]);
255
- function isReservedAgentName(name) {
256
- return RESERVED_AGENT_NAMES_SET.has(name);
257
- }
258
- const createAgentSchema = z.object({
259
- name: z.string().min(1).max(64).regex(AGENT_NAME_REGEX, "Must start with a letter or digit and contain only lowercase letters, digits, hyphens (-), and underscores (_). Max 64 chars.").refine((n) => !isReservedAgentName(n), { message: "That agent name is reserved — pick a different one." }).optional(),
260
- type: agentTypeSchema,
261
- displayName: z.string().min(1).max(200).optional(),
262
- delegateMention: z.string().max(100).optional(),
263
- organizationId: z.string().min(1).max(100).optional(),
264
- source: agentSourceSchema.optional(),
265
- visibility: agentVisibilitySchema.optional(),
266
- metadata: z.record(z.string(), z.unknown()).optional(),
267
- managerId: z.string().optional(),
268
- clientId: z.string().min(1).max(100).optional(),
269
- runtimeProvider: runtimeProviderSchema.optional()
270
- });
271
- const updateAgentSchema = z.object({
272
- type: agentTypeSchema.optional(),
273
- displayName: z.string().min(1).max(200).optional(),
274
- delegateMention: z.string().max(100).nullable().optional(),
275
- visibility: agentVisibilitySchema.optional(),
276
- metadata: z.record(z.string(), z.unknown()).optional(),
277
- managerId: z.string().nullable().optional(),
278
- clientId: z.string().min(1).max(100).nullable().optional()
279
- });
280
- /**
281
- * Service-level rebind input. Admin / owner re-binds an agent to a new
282
- * client and/or a new runtime provider in one atomic operation.
283
- *
284
- * `force` bypasses the capability-match check (e.g. when the client is
285
- * offline and capabilities are stale).
286
- */
287
- const rebindAgentSchema = z.object({
288
- clientId: z.string().min(1).max(100),
289
- runtimeProvider: runtimeProviderSchema,
290
- force: z.boolean().optional()
291
- });
292
- z.object({
293
- uuid: z.string(),
294
- name: z.string().nullable(),
295
- organizationId: z.string(),
296
- type: agentTypeSchema,
297
- displayName: z.string(),
298
- delegateMention: z.string().nullable(),
299
- inboxId: z.string(),
300
- status: z.string(),
301
- source: z.string().nullable().optional(),
302
- visibility: agentVisibilitySchema,
303
- metadata: z.record(z.string(), z.unknown()),
304
- managerId: z.string().nullable(),
305
- clientId: z.string().nullable(),
306
- runtimeProvider: runtimeProviderSchema,
307
- presenceStatus: presenceStatusSchema.optional(),
308
- createdAt: z.string(),
309
- updatedAt: z.string()
310
- });
311
- z.object({
312
- repo: z.string().nullable(),
313
- branch: z.string().nullable()
314
- });
315
- /**
316
- * Server → client WebSocket frame announcing that an agent has just been
317
- * pinned to the connected client (either created with `clientId` or bound via
318
- * PATCH NULL → ID). The client can auto-register a local config from this so
319
- * the operator doesn't have to run `first-tree-hub agent add` manually.
320
- */
321
- const agentPinnedMessageSchema = z.object({
322
- type: z.literal("agent:pinned"),
323
- agentId: z.string(),
324
- name: z.string().nullable(),
325
- displayName: z.string(),
326
- agentType: agentTypeSchema,
327
- runtimeProvider: runtimeProviderSchema
328
- });
329
140
  /**
330
141
  * Agent runtime configuration.
331
142
  *
@@ -500,6 +311,26 @@ const agentRuntimeConfigSchema = z.object({
500
311
  updatedBy: z.string()
501
312
  });
502
313
  /**
314
+ * Write-side shape with no `.default()` per field.
315
+ *
316
+ * `agentRuntimeConfigPayloadShape` carries `.default()` on every field for the
317
+ * read path (so legacy DB rows parse cleanly). On the PATCH side those defaults
318
+ * are actively harmful: Zod 4's `.partial()` makes a field optional but keeps
319
+ * the inner `ZodDefault`, so a body like `{ mcpServers: [...] }` parses to a
320
+ * fully-populated patch where the omitted fields are filled with their
321
+ * defaults — the service layer's `patch.x ?? current.x` then sees a truthy
322
+ * default and *replaces* the user's saved value with empty. Mirroring the 5
323
+ * fields here without defaults keeps "field absent" → `undefined` in the
324
+ * parsed patch, which is what the merge logic expects.
325
+ */
326
+ const agentRuntimeConfigPatchShape = z.object({
327
+ prompt: promptConfigSchema,
328
+ model: z.string(),
329
+ mcpServers: z.array(mcpServerSchema),
330
+ env: z.array(envEntrySchema),
331
+ gitRepos: z.array(gitRepoSchema)
332
+ }).partial();
333
+ /**
503
334
  * Patch payload for PATCH /api/v1/admin/agents/:uuid/config.
504
335
  *
505
336
  * - `expectedVersion` enforces optimistic locking; mismatch → 409.
@@ -507,9 +338,9 @@ const agentRuntimeConfigSchema = z.object({
507
338
  */
508
339
  const updateAgentRuntimeConfigSchema = z.object({
509
340
  expectedVersion: z.number().int().positive(),
510
- payload: agentRuntimeConfigPayloadShape.partial()
341
+ payload: agentRuntimeConfigPatchShape
511
342
  });
512
- const dryRunAgentRuntimeConfigSchema = z.object({ payload: agentRuntimeConfigPayloadShape.partial() });
343
+ const dryRunAgentRuntimeConfigSchema = z.object({ payload: agentRuntimeConfigPatchShape });
513
344
  z.object({
514
345
  current: agentRuntimeConfigSchema,
515
346
  next: agentRuntimeConfigPayloadSchema,
@@ -538,6 +369,196 @@ function deriveRepoLocalPath(url) {
538
369
  if (!trimmed) return "";
539
370
  return ((trimmed.split(/[?#]/)[0] ?? "").split(/[/:]/).filter(Boolean).pop() ?? "").replace(/\.git$/i, "");
540
371
  }
372
+ const presenceStatusSchema = z.enum(["online", "offline"]);
373
+ const runtimeStateSchema = z.enum([
374
+ "idle",
375
+ "working",
376
+ "blocked",
377
+ "error"
378
+ ]);
379
+ z.enum([
380
+ "active",
381
+ "suspended",
382
+ "evicted"
383
+ ]);
384
+ /** Wire-level states a client may report. `evicted` from a stale client is rejected. */
385
+ const clientSessionStateSchema = z.enum(["active", "suspended"]);
386
+ const sessionStateMessageSchema = z.object({
387
+ chatId: z.string().min(1),
388
+ state: clientSessionStateSchema
389
+ });
390
+ /** Client-reported runtime state override (client → server, per-agent). */
391
+ const runtimeStateMessageSchema = z.object({ runtimeState: runtimeStateSchema });
392
+ const agentBindRequestSchema = z.object({
393
+ agentId: z.string().min(1),
394
+ runtimeType: z.string().max(50),
395
+ runtimeVersion: z.string().max(50).optional()
396
+ });
397
+ const AGENT_BIND_REJECT_REASONS = {
398
+ WRONG_CLIENT: "wrong_client",
399
+ NOT_OWNED: "not_owned",
400
+ AGENT_SUSPENDED: "agent_suspended",
401
+ WRONG_ORG: "wrong_org",
402
+ UNKNOWN_AGENT: "unknown_agent",
403
+ RUNTIME_PROVIDER_MISMATCH: "runtime_provider_mismatch"
404
+ };
405
+ z.enum([
406
+ "wrong_client",
407
+ "not_owned",
408
+ "agent_suspended",
409
+ "wrong_org",
410
+ "unknown_agent",
411
+ "runtime_provider_mismatch"
412
+ ]);
413
+ /** Header used on agent-scoped HTTP calls to select which managed agent the JWT acts as. */
414
+ const AGENT_SELECTOR_HEADER = "x-agent-id";
415
+ z.object({
416
+ agentId: z.string(),
417
+ status: presenceStatusSchema,
418
+ connectedAt: z.string().nullable(),
419
+ lastSeenAt: z.string(),
420
+ clientId: z.string().nullable().optional(),
421
+ runtimeType: z.string().nullable().optional(),
422
+ runtimeVersion: z.string().nullable().optional(),
423
+ runtimeState: runtimeStateSchema.nullable().optional(),
424
+ activeSessions: z.number().int().nullable().optional(),
425
+ totalSessions: z.number().int().nullable().optional(),
426
+ runtimeUpdatedAt: z.string().nullable().optional()
427
+ });
428
+ z.object({
429
+ total: z.number().int(),
430
+ running: z.number().int(),
431
+ byState: z.object({
432
+ idle: z.number().int(),
433
+ working: z.number().int(),
434
+ blocked: z.number().int(),
435
+ error: z.number().int()
436
+ }),
437
+ clients: z.number().int()
438
+ });
439
+ const runtimeProviderSchema = z.enum(["claude-code", "codex"]);
440
+ const DEFAULT_RUNTIME_PROVIDER = "claude-code";
441
+ const AGENT_TYPES = {
442
+ HUMAN: "human",
443
+ PERSONAL_ASSISTANT: "personal_assistant",
444
+ AUTONOMOUS_AGENT: "autonomous_agent"
445
+ };
446
+ const agentTypeSchema = z.enum([
447
+ "human",
448
+ "personal_assistant",
449
+ "autonomous_agent"
450
+ ]);
451
+ const AGENT_VISIBILITY = {
452
+ PRIVATE: "private",
453
+ ORGANIZATION: "organization"
454
+ };
455
+ const agentVisibilitySchema = z.enum(["private", "organization"]);
456
+ const AGENT_STATUSES = {
457
+ ACTIVE: "active",
458
+ SUSPENDED: "suspended",
459
+ DELETED: "deleted"
460
+ };
461
+ const AGENT_SOURCES = {
462
+ ADMIN_API: "admin-api",
463
+ PORTAL: "portal"
464
+ };
465
+ const agentSourceSchema = z.enum(["admin-api", "portal"]);
466
+ z.enum(["active", "suspended"]);
467
+ /**
468
+ * Agent-name rules (see docs/agent-naming-design.md §3.1):
469
+ * - Lowercase ASCII slug, hyphens + underscores allowed.
470
+ * - Must start with alphanumeric: `-` / `_` as first char collide with
471
+ * CLI flag parsing and markdown list syntax.
472
+ * - 1–64 chars — aligned with `MENTION_REGEX` so any valid name can be
473
+ * @-mentioned in chat. Older rows created under the previous 1–100
474
+ * regex are grandfathered; the tight rule only gates new creates.
475
+ */
476
+ const AGENT_NAME_REGEX = /^[a-z0-9][a-z0-9_-]{0,63}$/;
477
+ const RESERVED_AGENT_NAMES_SET = new Set([
478
+ "admin",
479
+ "agent",
480
+ "first-tree",
481
+ "hub",
482
+ "me",
483
+ "null",
484
+ "system",
485
+ "undefined"
486
+ ]);
487
+ function isReservedAgentName(name) {
488
+ return RESERVED_AGENT_NAMES_SET.has(name);
489
+ }
490
+ const createAgentSchema = z.object({
491
+ name: z.string().min(1).max(64).regex(AGENT_NAME_REGEX, "Must start with a letter or digit and contain only lowercase letters, digits, hyphens (-), and underscores (_). Max 64 chars.").refine((n) => !isReservedAgentName(n), { message: "That agent name is reserved — pick a different one." }).optional(),
492
+ type: agentTypeSchema,
493
+ displayName: z.string().min(1).max(200).optional(),
494
+ delegateMention: z.string().max(100).optional(),
495
+ organizationId: z.string().min(1).max(100).optional(),
496
+ source: agentSourceSchema.optional(),
497
+ visibility: agentVisibilitySchema.optional(),
498
+ metadata: z.record(z.string(), z.unknown()).optional(),
499
+ managerId: z.string().optional(),
500
+ clientId: z.string().min(1).max(100).optional(),
501
+ runtimeProvider: runtimeProviderSchema.optional(),
502
+ gitRepos: z.array(gitRepoSchema).optional()
503
+ });
504
+ const updateAgentSchema = z.object({
505
+ type: agentTypeSchema.optional(),
506
+ displayName: z.string().min(1).max(200).optional(),
507
+ delegateMention: z.string().max(100).nullable().optional(),
508
+ visibility: agentVisibilitySchema.optional(),
509
+ metadata: z.record(z.string(), z.unknown()).optional(),
510
+ managerId: z.string().nullable().optional(),
511
+ clientId: z.string().min(1).max(100).nullable().optional()
512
+ });
513
+ /**
514
+ * Service-level rebind input. Admin / owner re-binds an agent to a new
515
+ * client and/or a new runtime provider in one atomic operation.
516
+ *
517
+ * `force` bypasses the capability-match check (e.g. when the client is
518
+ * offline and capabilities are stale).
519
+ */
520
+ const rebindAgentSchema = z.object({
521
+ clientId: z.string().min(1).max(100),
522
+ runtimeProvider: runtimeProviderSchema,
523
+ force: z.boolean().optional()
524
+ });
525
+ z.object({
526
+ uuid: z.string(),
527
+ name: z.string().nullable(),
528
+ organizationId: z.string(),
529
+ type: agentTypeSchema,
530
+ displayName: z.string(),
531
+ delegateMention: z.string().nullable(),
532
+ inboxId: z.string(),
533
+ status: z.string(),
534
+ source: z.string().nullable().optional(),
535
+ visibility: agentVisibilitySchema,
536
+ metadata: z.record(z.string(), z.unknown()),
537
+ managerId: z.string().nullable(),
538
+ clientId: z.string().nullable(),
539
+ runtimeProvider: runtimeProviderSchema,
540
+ presenceStatus: presenceStatusSchema.optional(),
541
+ createdAt: z.string(),
542
+ updatedAt: z.string()
543
+ });
544
+ z.object({
545
+ repo: z.string().nullable(),
546
+ branch: z.string().nullable()
547
+ });
548
+ /**
549
+ * Server → client WebSocket frame announcing that an agent has just been
550
+ * pinned to the connected client (either created with `clientId` or bound via
551
+ * PATCH NULL → ID). The client can auto-register a local config from this so
552
+ * the operator doesn't have to run `first-tree-hub agent add` manually.
553
+ */
554
+ const agentPinnedMessageSchema = z.object({
555
+ type: z.literal("agent:pinned"),
556
+ agentId: z.string(),
557
+ name: z.string().nullable(),
558
+ displayName: z.string(),
559
+ agentType: agentTypeSchema,
560
+ runtimeProvider: runtimeProviderSchema
561
+ });
541
562
  const loginSchema = z.object({
542
563
  username: z.string().min(1),
543
564
  password: z.string().min(1)
@@ -1054,6 +1075,43 @@ const createOrgFromMeSchema = z.object({
1054
1075
  name: z.string().min(2).max(50).regex(/^[a-z0-9][a-z0-9-]*$/),
1055
1076
  displayName: z.string().min(1).max(200)
1056
1077
  });
1078
+ /**
1079
+ * Body for `PATCH /me/onboarding`. v1 only mutates `dismissed` — true to
1080
+ * hide the onboarding stepper (server stamps `users.onboarding_dismissed_at
1081
+ * = NOW()`), false to restore it. See docs/new-user-onboarding-design.md
1082
+ * §8.4.
1083
+ */
1084
+ const patchOnboardingSchema = z.object({ dismissed: z.boolean().optional() });
1085
+ /**
1086
+ * Body for `POST /me/onboarding/events`. The web SPA reports key
1087
+ * milestones so the server can log them as a single funnel-trackable
1088
+ * stream alongside server-emitted events (`team_created`, `dismissed`).
1089
+ *
1090
+ * Server emits:
1091
+ * - `team_created` — at OAuth callback when joinPath === "solo"
1092
+ * - `dismissed` — when PATCH /me/onboarding flips dismissed
1093
+ *
1094
+ * Web reports:
1095
+ * - `team_renamed` — Step 1 user changed the auto-named team
1096
+ * - `agent_created` — Step 2 successfully created the agent
1097
+ * - `tree_chat_started` — Step 3 [Yes, set it up] succeeded
1098
+ * - `tree_intro_dismissed` — Step 3 [I'll do it later] clicked
1099
+ */
1100
+ const onboardingEventNameSchema = z.enum([
1101
+ "team_renamed",
1102
+ "agent_created",
1103
+ "tree_chat_started",
1104
+ "tree_intro_dismissed"
1105
+ ]);
1106
+ const onboardingEventSchema = z.object({
1107
+ event: onboardingEventNameSchema,
1108
+ attrs: z.record(z.string(), z.union([
1109
+ z.string(),
1110
+ z.number(),
1111
+ z.boolean(),
1112
+ z.null()
1113
+ ])).optional()
1114
+ });
1057
1115
  z.object({
1058
1116
  id: z.string(),
1059
1117
  organizationId: z.string(),
@@ -1140,6 +1198,74 @@ const githubDevCallbackQuerySchema = z.object({
1140
1198
  displayName: z.string().optional(),
1141
1199
  next: z.string().max(256).optional()
1142
1200
  });
1201
+ /**
1202
+ * Per-organization settings — schemas, namespaces, and the registry that
1203
+ * dispatches `(orgId, namespace)` lookups to the right validator.
1204
+ *
1205
+ * Each namespace has three schemas:
1206
+ * - `storage` — what is persisted in `organization_settings.value`. For
1207
+ * namespaces with secrets, the storage schema names the *cipher* field
1208
+ * (e.g. `webhookSecretCipher`); plaintext never touches the row.
1209
+ * - `input` — what the admin API accepts in PUT bodies. For namespaces
1210
+ * with secrets, `webhookSecret` is plaintext; the service layer
1211
+ * encrypts it before merging into storage.
1212
+ * - `output` — what GET returns. Secrets are replaced by a boolean
1213
+ * `…Configured` flag — plaintext is never echoed.
1214
+ *
1215
+ * Adding a new per-org config group:
1216
+ * 1. Define three schemas (storage / input / output).
1217
+ * 2. Add a key to `ORG_SETTINGS_NAMESPACES`.
1218
+ * 3. Done. No DB migration, no new API route.
1219
+ */
1220
+ const orgContextTreeRepoUrlSchema = z.string().url().refine((value) => {
1221
+ try {
1222
+ return new URL(value).protocol === "https:";
1223
+ } catch {
1224
+ return false;
1225
+ }
1226
+ }, { message: "Context Tree repo URL must use HTTPS." }).refine((value) => {
1227
+ try {
1228
+ const url = new URL(value);
1229
+ return url.username.length === 0 && url.password.length === 0;
1230
+ } catch {
1231
+ return false;
1232
+ }
1233
+ }, { message: "Context Tree repo URL must not include credentials." });
1234
+ const orgContextTreeStorageSchema = z.object({
1235
+ repo: orgContextTreeRepoUrlSchema.optional(),
1236
+ branch: z.string().default("main")
1237
+ });
1238
+ const orgContextTreeInputSchema = z.object({
1239
+ repo: orgContextTreeRepoUrlSchema.nullish(),
1240
+ branch: z.string().min(1).nullish()
1241
+ });
1242
+ const orgContextTreeOutputSchema = z.object({
1243
+ repo: z.string().optional(),
1244
+ branch: z.string().optional()
1245
+ });
1246
+ const orgGithubIntegrationStorageSchema = z.object({ webhookSecretCipher: z.string().optional() });
1247
+ const orgGithubIntegrationInputSchema = z.object({ webhookSecret: z.string().min(1).nullish() });
1248
+ const orgGithubIntegrationOutputSchema = z.object({
1249
+ webhookSecretConfigured: z.boolean(),
1250
+ webhookUrl: z.string()
1251
+ });
1252
+ const ORG_SETTINGS_NAMESPACES = {
1253
+ context_tree: {
1254
+ storage: orgContextTreeStorageSchema,
1255
+ input: orgContextTreeInputSchema,
1256
+ output: orgContextTreeOutputSchema
1257
+ },
1258
+ github_integration: {
1259
+ storage: orgGithubIntegrationStorageSchema,
1260
+ input: orgGithubIntegrationInputSchema,
1261
+ output: orgGithubIntegrationOutputSchema
1262
+ }
1263
+ };
1264
+ const ORG_SETTINGS_NAMESPACE_KEYS = Object.keys(ORG_SETTINGS_NAMESPACES);
1265
+ z.enum(ORG_SETTINGS_NAMESPACE_KEYS);
1266
+ function isOrgSettingNamespace(value) {
1267
+ return typeof value === "string" && ORG_SETTINGS_NAMESPACE_KEYS.includes(value);
1268
+ }
1143
1269
  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
1270
  z.object({
1145
1271
  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 +1593,4 @@ z.object({
1467
1593
  capabilities: serverCapabilitiesSchema.optional()
1468
1594
  }).passthrough();
1469
1595
  //#endregion
1470
- export { notificationQuerySchema as $, createChatSchema as A, githubDevCallbackQuerySchema as B, clientCapabilitiesSchema as C, updateTaskStatusSchema as Ct, createAdapterConfigSchema as D, contextTreeSnapshotSchema as E, defaultRuntimeConfigPayload as F, inboxPollQuerySchema as G, imageInlineContentSchema as H, delegateFeishuUserSchema as I, joinByInvitationSchema as J, isRedactedEnvValue as K, dryRunAgentRuntimeConfigSchema as L, createMemberSchema as M, createOrgFromMeSchema as N, createAdapterMappingSchema as O, createTaskSchema as P, messageSourceSchema as Q, extractMentions as R, agentTypeSchema as S, updateOrganizationSchema as St, connectTokenExchangeSchema as T, inboxAckFrameSchema as U, githubStartQuerySchema as V, inboxDeliverFrameSchema as W, listMeChatsQuerySchema as X, linkTaskChatSchema as Y, loginSchema as Z, adminCreateTaskSchema as _, updateAgentRuntimeConfigSchema as _t, AGENT_STATUSES as a, scanMentionTokens as at, agentPinnedMessageSchema as b, updateClientCapabilitiesSchema as bt, DEFAULT_RUNTIME_PROVIDER as c, sendToAgentSchema as ct, TASK_HEALTH_SIGNALS as d, sessionEventSchema as dt, paginationQuerySchema as et, TASK_STATUSES as f, sessionReconcileRequestSchema as ft, addParticipantSchema as g, updateAdapterConfigSchema as gt, addMeChatParticipantsSchema as h, taskListQuerySchema as ht, AGENT_SOURCES as i, safeRedirectPath as it, createMeChatSchema as j, createAgentSchema as k, MENTION_REGEX as l, sessionCompletionMessageSchema as lt, WS_AUTH_FRAME_TIMEOUT_MS as m, stripCode as mt, AGENT_NAME_REGEX as n, refreshTokenSchema as nt, AGENT_TYPES as o, selfServiceFeishuBotSchema as ot, TASK_TERMINAL_STATUSES as p, sessionStateMessageSchema as pt, isReservedAgentName as q, AGENT_SELECTOR_HEADER as r, runtimeStateMessageSchema as rt, AGENT_VISIBILITY as s, sendMessageSchema as st, AGENT_BIND_REJECT_REASONS as t, rebindAgentSchema as tt, TASK_CREATOR_TYPES as u, sessionEventMessageSchema as ut, adminUpdateTaskSchema as v, updateAgentSchema as vt, clientRegisterSchema as w, wsAuthFrameSchema as wt, agentRuntimeConfigPayloadSchema as x, updateMemberSchema as xt, agentBindRequestSchema as y, updateChatSchema as yt, githubCallbackQuerySchema as z };
1596
+ export { loginSchema as $, createAgentSchema as A, githubCallbackQuerySchema as B, agentTypeSchema as C, updateChatSchema as Ct, contextTreeSnapshotSchema as D, updateTaskStatusSchema as Dt, connectTokenExchangeSchema as E, updateOrganizationSchema 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, wsAuthFrameSchema as Ot, createOrgFromMeSchema as P, listMeChatsQuerySchema as Q, dryRunAgentRuntimeConfigSchema as R, agentRuntimeConfigPayloadSchema as S, updateAgentSchema as St, clientRegisterSchema as T, updateMemberSchema as Tt, imageInlineContentSchema as U, githubDevCallbackQuerySchema as V, inboxAckFrameSchema as W, joinByInvitationSchema as X, isReservedAgentName as Y, linkTaskChatSchema as Z, addParticipantSchema as _, sessionStateMessageSchema as _t, AGENT_STATUSES as a, rebindAgentSchema as at, agentBindRequestSchema as b, updateAdapterConfigSchema as bt, DEFAULT_RUNTIME_PROVIDER as c, safeRedirectPath as ct, TASK_CREATOR_TYPES as d, sendMessageSchema as dt, messageSourceSchema as et, TASK_HEALTH_SIGNALS as f, sendToAgentSchema as ft, addMeChatParticipantsSchema as g, sessionReconcileRequestSchema as gt, WS_AUTH_FRAME_TIMEOUT_MS as h, sessionEventSchema as ht, AGENT_SOURCES as i, patchOnboardingSchema as it, createChatSchema as j, createAdapterMappingSchema as k, MENTION_REGEX as l, scanMentionTokens as lt, TASK_TERMINAL_STATUSES as m, sessionEventMessageSchema as mt, AGENT_NAME_REGEX as n, onboardingEventSchema as nt, AGENT_TYPES as o, refreshTokenSchema as ot, TASK_STATUSES as p, sessionCompletionMessageSchema as pt, isOrgSettingNamespace as q, AGENT_SELECTOR_HEADER as r, paginationQuerySchema as rt, AGENT_VISIBILITY as s, runtimeStateMessageSchema as st, AGENT_BIND_REJECT_REASONS as t, notificationQuerySchema as tt, ORG_SETTINGS_NAMESPACES as u, selfServiceFeishuBotSchema as ut, adminCreateTaskSchema as v, stripCode as vt, clientCapabilitiesSchema as w, updateClientCapabilitiesSchema as wt, agentPinnedMessageSchema as x, updateAgentRuntimeConfigSchema as xt, adminUpdateTaskSchema as y, taskListQuerySchema 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");
@@ -0,0 +1,13 @@
1
+ -- Onboarding stepper dismissal flag. Decoupled from the server-side
2
+ -- `onboardingStep` enum so the stepper keeps rendering across all three
3
+ -- UI steps (server-side onboardingStep flips to `completed` at the end of
4
+ -- Step 2; Step 3 is purely client-driven and the stepper must keep
5
+ -- showing during the tree-init chat).
6
+ --
7
+ -- See docs/new-user-onboarding-design.md §8.
8
+ --
9
+ -- NULL → stepper renders
10
+ -- value → user clicked the `✕`; stepper unmounts. Irreversible from UI v1.
11
+
12
+ ALTER TABLE "users"
13
+ ADD COLUMN IF NOT EXISTS "onboarding_dismissed_at" timestamp with time zone;
@@ -225,6 +225,20 @@
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
235
+ },
236
+ {
237
+ "idx": 33,
238
+ "version": "7",
239
+ "when": 1778284800000,
240
+ "tag": "0033_onboarding_dismissed_at",
241
+ "breakpoints": true
228
242
  }
229
243
  ]
230
- }
244
+ }
@@ -1,5 +1,5 @@
1
1
  import { integer, jsonb, pgTable, text, timestamp } from "drizzle-orm/pg-core";
2
- //#region ../server/dist/errors-BmyRwN0Y.mjs
2
+ //#region ../server/dist/errors-CF5evtJt.mjs
3
3
  /** Organization entity. Agents and chats belong to exactly one organization. */
4
4
  const organizations = pgTable("organizations", {
5
5
  id: text("id").primaryKey(),
@@ -19,6 +19,7 @@ const users = pgTable("users", {
19
19
  displayName: text("display_name").notNull(),
20
20
  avatarUrl: text("avatar_url"),
21
21
  status: text("status").notNull().default("active"),
22
+ onboardingDismissedAt: timestamp("onboarding_dismissed_at", { withTimezone: true }),
22
23
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
23
24
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
24
25
  });
@@ -1,6 +1,6 @@
1
1
  import { r as __exportAll } from "./chunk-BSw8zbkd.mjs";
2
2
  import { t as cliFetch } from "./cli-fetch--tiwKm5S.mjs";
3
- import { r as AGENT_SELECTOR_HEADER } from "./dist-BAqGZkco.mjs";
3
+ import { r as AGENT_SELECTOR_HEADER } from "./dist-CfvCT4E0.mjs";
4
4
  //#region src/core/feishu.ts
5
5
  var feishu_exports = /* @__PURE__ */ __exportAll({
6
6
  bindFeishuBot: () => bindFeishuBot,