@agent-team-foundation/first-tree-hub 0.14.4 → 0.14.6

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 (101) hide show
  1. package/dist/bootstrap-BmeaRhRp.mjs +3 -0
  2. package/dist/{bootstrap-CQQGgIx1.mjs → bootstrap-CmkHQsnS.mjs} +24 -16
  3. package/dist/cli/index.mjs +6 -94
  4. package/dist/{dist-CrdnqZjv.mjs → feishu-BE7QRxnE.mjs} +170 -379
  5. package/dist/feishu-De9_bA91.mjs +3 -0
  6. package/dist/index.mjs +5 -12
  7. package/dist/saas-connect-CNY9Ve5V.mjs +13748 -0
  8. package/package.json +4 -12
  9. package/dist/chunk-BSw8zbkd.mjs +0 -37
  10. package/dist/client-BPRIfrOT-CoV_2o7e.mjs +0 -4230
  11. package/dist/client-CEdYVnoj-BGiGcJbH.mjs +0 -7
  12. package/dist/dist-LgF7LHpE.mjs +0 -430
  13. package/dist/drizzle/0000_shocking_darkhawk.sql +0 -92
  14. package/dist/drizzle/0001_v2_schema_updates.sql +0 -26
  15. package/dist/drizzle/0002_adapter_tables.sql +0 -64
  16. package/dist/drizzle/0003_feishu_adapter.sql +0 -21
  17. package/dist/drizzle/0004_adapter_refactor.sql +0 -13
  18. package/dist/drizzle/0005_delegate_mention.sql +0 -1
  19. package/dist/drizzle/0006_agent_tree_path.sql +0 -1
  20. package/dist/drizzle/0007_decouple_context_tree.sql +0 -2
  21. package/dist/drizzle/0008_uuid_identity.sql +0 -12
  22. package/dist/drizzle/0009_agent_runtime_m1.sql +0 -31
  23. package/dist/drizzle/0010_cloud_multi_tenancy.sql +0 -34
  24. package/dist/drizzle/0011_org_uuid_pk.sql +0 -22
  25. package/dist/drizzle/0012_session_level_state.sql +0 -19
  26. package/dist/drizzle/0013_hub_tasks.sql +0 -38
  27. package/dist/drizzle/0014_drop_task_fks.sql +0 -9
  28. package/dist/drizzle/0015_member_system.sql +0 -34
  29. package/dist/drizzle/0016_strange_havok.sql +0 -25
  30. package/dist/drizzle/0017_session_outputs_unique.sql +0 -1
  31. package/dist/drizzle/0018_agent_visibility.sql +0 -13
  32. package/dist/drizzle/0019_agent_configs.sql +0 -30
  33. package/dist/drizzle/0020_unified_user_token.sql +0 -154
  34. package/dist/drizzle/0021_drop_agents_profile.sql +0 -10
  35. package/dist/drizzle/0022_session_events.sql +0 -32
  36. package/dist/drizzle/0023_clients_org_scoping.sql +0 -40
  37. package/dist/drizzle/0024_display_name_not_null.sql +0 -31
  38. package/dist/drizzle/0025_inbox_silent_entries.sql +0 -53
  39. package/dist/drizzle/0026_saas_onboarding.sql +0 -153
  40. package/dist/drizzle/0027_runtime_provider.sql +0 -10
  41. package/dist/drizzle/0028_auth_identity_user_github_unique.sql +0 -12
  42. package/dist/drizzle/0029_direct_agent_only_mention_only.sql +0 -28
  43. package/dist/drizzle/0030_chat_first_workspace.sql +0 -129
  44. package/dist/drizzle/0031_drop_system_configs.sql +0 -11
  45. package/dist/drizzle/0032_organization_settings.sql +0 -36
  46. package/dist/drizzle/0033_onboarding_dismissed_at.sql +0 -13
  47. package/dist/drizzle/0034_pending_questions.sql +0 -34
  48. package/dist/drizzle/0035_drop_hub_tasks.sql +0 -7
  49. package/dist/drizzle/0036_github_entity_chat_mappings.sql +0 -47
  50. package/dist/drizzle/0037_github_app_installations.sql +0 -52
  51. package/dist/drizzle/0038_chat_membership_user_state.sql +0 -223
  52. package/dist/drizzle/0039_drop_chat_participants_subscriptions.sql +0 -26
  53. package/dist/drizzle/0040_chat_user_state_engagement.sql +0 -24
  54. package/dist/drizzle/0041_notifications_dedup_key.sql +0 -29
  55. package/dist/drizzle/0042_notifications_drop_legacy_types.sql +0 -36
  56. package/dist/drizzle/0043_onboarding_completed_at.sql +0 -32
  57. package/dist/drizzle/0044_agent_avatar_color.sql +0 -11
  58. package/dist/drizzle/0045_agent_avatar_image.sql +0 -17
  59. package/dist/drizzle/meta/0000_snapshot.json +0 -687
  60. package/dist/drizzle/meta/0001_snapshot.json +0 -687
  61. package/dist/drizzle/meta/0012_snapshot.json +0 -1451
  62. package/dist/drizzle/meta/0013_snapshot.json +0 -1771
  63. package/dist/drizzle/meta/0014_snapshot.json +0 -1717
  64. package/dist/drizzle/meta/0016_snapshot.json +0 -1917
  65. package/dist/drizzle/meta/0018_snapshot.json +0 -1938
  66. package/dist/drizzle/meta/_journal.json +0 -328
  67. package/dist/esm-iadMkGbV.mjs +0 -1516
  68. package/dist/execAsync-DUfRkc4a.mjs +0 -10
  69. package/dist/execAsync-YbEZSOYd.mjs +0 -10
  70. package/dist/feishu-DNoBroKK.mjs +0 -53
  71. package/dist/from-DQ7eNRwu.mjs +0 -3840
  72. package/dist/getMachineId-bsd-BmasEOJr.mjs +0 -27
  73. package/dist/getMachineId-bsd-Dh3h0DDE.mjs +0 -27
  74. package/dist/getMachineId-darwin-CuhM3hfZ.mjs +0 -24
  75. package/dist/getMachineId-darwin-D9wR0SLj.mjs +0 -24
  76. package/dist/getMachineId-linux-CYfb0oxZ.mjs +0 -20
  77. package/dist/getMachineId-linux-D8ZaSjAC.mjs +0 -20
  78. package/dist/getMachineId-unsupported-Cu3iisaD.mjs +0 -15
  79. package/dist/getMachineId-unsupported-DZqI4ZT5.mjs +0 -15
  80. package/dist/getMachineId-win-8ZJbtrdf.mjs +0 -26
  81. package/dist/getMachineId-win-DT-hqwVp.mjs +0 -26
  82. package/dist/invitation-C9m2gQx4-C_4f5VTs.mjs +0 -4
  83. package/dist/invitation-D_ENPHyj-5ETiae5r.mjs +0 -167
  84. package/dist/logger-core-BTmvdflj-DjW8FM4T.mjs +0 -146
  85. package/dist/multipart-parser-QRu3OKK4.mjs +0 -294
  86. package/dist/observability-BAScT_5S-BcW9HgkG.mjs +0 -96129
  87. package/dist/observability-eLA9iNK_.mjs +0 -5
  88. package/dist/saas-connect-Da55XxRX.mjs +0 -21635
  89. package/dist/src-DFlbpJfU.mjs +0 -1176
  90. package/dist/src-DNBS5Yjj.mjs +0 -735
  91. package/dist/uuid-DbS_4vFh-iFghv4zA.mjs +0 -129
  92. package/dist/web/assets/index-9wK0udbH.js +0 -416
  93. package/dist/web/assets/index-C7x7O7dG.js +0 -11
  94. package/dist/web/assets/index-DE7Q3QWE.css +0 -1
  95. package/dist/web/favicon.svg +0 -9
  96. package/dist/web/fonts/inter-latin-ext.woff2 +0 -0
  97. package/dist/web/fonts/inter-latin.woff2 +0 -0
  98. package/dist/web/fonts/jetbrains-mono-latin-ext.woff2 +0 -0
  99. package/dist/web/fonts/jetbrains-mono-latin.woff2 +0 -0
  100. package/dist/web/index.html +0 -39
  101. /package/dist/{cli-fetch--tiwKm5S.mjs → cli-fetch-BGVItZxo.mjs} +0 -0
@@ -1,110 +1,59 @@
1
+ import { t as cliFetch } from "./cli-fetch-BGVItZxo.mjs";
1
2
  import { z } from "zod";
2
3
  //#region ../shared/dist/index.mjs
3
- const MENTION_REGEX = /(?<![A-Za-z0-9_.@-])@([A-Za-z0-9][A-Za-z0-9_-]{0,63})\b/g;
4
4
  /**
5
- * Strip Markdown code regions (fenced + inline) so identifier-shaped
6
- * tokens inside code (`@param`, `@staticmethod`, etc.) don't get
7
- * misclassified as mentions. Shared between `extractMentions` (routing)
8
- * and `extractSummary` (auto-title) so they agree on what counts as a
9
- * "real" mention vs a code reference.
10
- */
11
- function stripCode(content) {
12
- return content.replace(/```[\s\S]*?```/g, "").replace(/~~~[\s\S]*?~~~/g, "").replace(/`[^`\n]+`/g, "");
13
- }
14
- /**
15
- * Resolve `@<name>` mentions in `content` to a list of participant agentIds.
16
- * Names match case-insensitively; unknown `@tokens` are dropped.
17
- */
18
- function extractMentions(content, participants) {
19
- const stripped = stripCode(content);
20
- const nameMap = /* @__PURE__ */ new Map();
21
- for (const p of participants) if (p.name) nameMap.set(p.name.toLowerCase(), p.agentId);
22
- if (nameMap.size === 0) return [];
23
- const hits = /* @__PURE__ */ new Set();
24
- for (const m of stripped.matchAll(MENTION_REGEX)) {
25
- const token = m[1];
26
- if (!token) continue;
27
- const id = nameMap.get(token.toLowerCase());
28
- if (id) hits.add(id);
29
- }
30
- return [...hits];
31
- }
32
- /**
33
- * Return every `@<name>` token that survives the code-stripping / word-
34
- * boundary gates, regardless of whether it matches a participant. The
35
- * caller uses this to log unmatched tokens (typos, renamed agents) without
36
- * polluting the authoritative mention list.
37
- */
38
- function scanMentionTokens(content) {
39
- const stripped = stripCode(content);
40
- const tokens = [];
41
- for (const m of stripped.matchAll(MENTION_REGEX)) {
42
- const token = m[1];
43
- if (token) tokens.push(token.toLowerCase());
44
- }
45
- return tokens;
46
- }
47
- /**
48
- * Derive the `chat_membership.mode` that a freshly inserted speaker row MUST
49
- * get, given the chat's `type` and the joining agent's `type`. This is the
50
- * single authoritative rule for the invariant
51
- *
52
- * `(chat.type === 'group' && agent.type !== 'human') ⇒ mode === 'mention_only'`
53
- *
54
- * plus the legacy "agent-only direct chat" anti-echo rule. The helper is
55
- * pure and synchronous; all DB lookups are the caller's responsibility (see
56
- * `services/participant-mode.ts::addChatParticipants` for the canonical
57
- * server entrypoint that wires it).
5
+ * Workspace-relative markdown doc path normalisation.
58
6
  *
59
- * Rule (encoded once, here):
7
+ * Shared by:
8
+ * - web link click handlers (`docPreviewPathFromHref`)
9
+ * - client runtime snapshot scan (`buildMessageDocumentSnapshots`)
10
+ * - server snapshot schema refinement (`snapshotDocSchema`)
60
11
  *
61
- * - `agent.type === 'human'` → 'full'
62
- * - `chat.type === 'group'` (and agent is non-human) 'mention_only'
63
- * - `chat.type === 'direct'` + agent non-human:
64
- * - if every other participant on this chat is also non-human →
65
- * 'mention_only' (prevents the A↔B reply loop noted in migration
66
- * 0029)
67
- * - otherwise → 'full' (the peer is a human / external user, so the
68
- * agent should listen to every message in this 1:1 line)
12
+ * All three must agree on canonical form or runtime stores one shape and
13
+ * web looks up another, producing silent cache misses. The canonical form
14
+ * is POSIX-style ("/"-separated), no leading "/", no "./" / ".." segments,
15
+ * and no empty segments.
69
16
  *
70
- * `peerAgentTypes` is read only in the `direct` branch; callers may pass
71
- * an empty array (or omit it) for `group` chats — it's ignored. Watcher
72
- * rows (`chat_membership.access_mode = 'watcher'`) are unaffected; the
73
- * helper only governs the "speaking" mode column.
17
+ * Returns `null` when:
18
+ * - the input is an external link (has a scheme like `https:`, `mailto:`,
19
+ * or is `//`-scheme-relative, or starts with `#` as a pure fragment) —
20
+ * these must NEVER be interpreted as workspace paths. Runtime would
21
+ * otherwise try to open `<workspace>/https:/foo/bar.md` on disk;
22
+ * - the path escapes the workspace (resolves above root);
23
+ * - the path is empty after normalisation;
24
+ * - any segment is hidden (".dotfile" / ".agent/" / ".git/") — defence in
25
+ * depth against link paths that try to walk into hidden dirs.
74
26
  */
75
- function defaultParticipantMode(chatType, agentType, peerAgentTypes = []) {
76
- if (agentType === "human") return "full";
77
- if (chatType === "group") return "mention_only";
78
- return peerAgentTypes.every((t) => t !== "human") ? "mention_only" : "full";
27
+ const SCHEME_RE = /^[a-z][a-z0-9+.-]*:/i;
28
+ function normalizeDocLinkPath(raw) {
29
+ if (typeof raw !== "string") return null;
30
+ const trimmed = raw.trim();
31
+ if (!trimmed) return null;
32
+ if (trimmed.startsWith("#") || trimmed.startsWith("//") || SCHEME_RE.test(trimmed)) return null;
33
+ if (trimmed.includes("?") || trimmed.includes("#")) return null;
34
+ const stripped = trimmed.startsWith("/") ? trimmed.slice(1) : trimmed;
35
+ const parts = [];
36
+ for (const part of stripped.split("/")) {
37
+ if (!part || part === ".") continue;
38
+ if (part === "..") {
39
+ if (parts.length === 0) return null;
40
+ parts.pop();
41
+ continue;
42
+ }
43
+ if (part.startsWith(".")) return null;
44
+ parts.push(part);
45
+ }
46
+ return parts.length > 0 ? parts.join("/") : null;
79
47
  }
80
48
  /**
81
- * Single source of truth for "is this string safe to redirect to after a
82
- * successful OAuth callback".
83
- *
84
- * Both the server (`/auth/github/start` validates `?next=` before signing
85
- * the state JWT) and the web client (the fragment-consumer page validates
86
- * before navigating) must agree on the regex — drift here is what enables
87
- * open-redirect bugs. The server is authoritative; the client check is a
88
- * defense-in-depth.
89
- *
90
- * Allowed: a path that begins with exactly one `/` and is not the start of
91
- * an authority component (`//`, `/\`). Permits typical SPA paths with
92
- * query strings and fragments. Anything else (absolute URLs, scheme-less
93
- * authority components, `javascript:`) falls through to the safe default.
94
- */
95
- const SAFE_NEXT_PATH = /^\/(?![/\\])[A-Za-z0-9_\-./?=&%#]*$/;
96
- /**
97
- * Return `next` if it is a syntactically safe relative path, otherwise the
98
- * default landing path. The check is deliberately conservative — the
99
- * intent is to reject anything that could be parsed as an absolute URL by
100
- * a browser navigation. Length is capped at 256 chars to defang
101
- * pathological inputs.
49
+ * `true` when `path` is already in canonical form per `normalizeDocLinkPath`.
50
+ * Used by the server snapshot schema to reject anything the runtime didn't
51
+ * normalise correctly — the wire format must carry canonical paths so the
52
+ * web cache lookup is deterministic.
102
53
  */
103
- function safeRedirectPath(next) {
104
- if (!next || typeof next !== "string") return "/";
105
- if (next.length > 256) return "/";
106
- if (!SAFE_NEXT_PATH.test(next)) return "/";
107
- return next;
54
+ function isCanonicalDocLinkPath(path) {
55
+ const normalized = normalizeDocLinkPath(path);
56
+ return normalized !== null && normalized === path;
108
57
  }
109
58
  const adapterPlatformSchema = z.enum([
110
59
  "feishu",
@@ -112,13 +61,13 @@ const adapterPlatformSchema = z.enum([
112
61
  "kael"
113
62
  ]);
114
63
  const adapterStatusSchema = z.enum(["active", "inactive"]);
115
- const createAdapterConfigSchema = z.object({
64
+ z.object({
116
65
  platform: adapterPlatformSchema,
117
66
  agentId: z.string().min(1),
118
67
  credentials: z.record(z.string(), z.unknown()),
119
68
  status: adapterStatusSchema.default("active")
120
69
  });
121
- const updateAdapterConfigSchema = z.object({
70
+ z.object({
122
71
  agentId: z.string().min(1).optional(),
123
72
  credentials: z.record(z.string(), z.unknown()).optional(),
124
73
  status: adapterStatusSchema.optional()
@@ -138,11 +87,11 @@ const adapterBindMethodSchema = z.enum([
138
87
  "oauth",
139
88
  "manual"
140
89
  ]);
141
- const selfServiceFeishuBotSchema = z.object({
90
+ z.object({
142
91
  appId: z.string().min(1),
143
92
  appSecret: z.string().min(1)
144
93
  });
145
- const createAdapterMappingSchema = z.object({
94
+ z.object({
146
95
  platform: adapterPlatformSchema,
147
96
  externalUserId: z.string().min(1),
148
97
  agentId: z.string().min(1),
@@ -158,7 +107,7 @@ z.object({
158
107
  displayName: z.string().nullable(),
159
108
  createdAt: z.string()
160
109
  });
161
- const delegateFeishuUserSchema = z.object({
110
+ z.object({
162
111
  feishuUserId: z.string().min(1),
163
112
  displayName: z.string().max(200).optional()
164
113
  });
@@ -332,39 +281,6 @@ const agentRuntimeConfigPayloadSchema = z.preprocess((input) => {
332
281
  }, taggedPayloadUnion).superRefine((payload, ctx) => {
333
282
  payloadDuplicatesRefinement(payload, ctx);
334
283
  });
335
- /** Default payload used when creating a fresh claude-code agent. */
336
- const DEFAULT_AGENT_RUNTIME_CONFIG_PAYLOAD = {
337
- kind: "claude-code",
338
- prompt: { append: "" },
339
- model: "opus",
340
- mcpServers: [],
341
- env: [],
342
- gitRepos: []
343
- };
344
- /**
345
- * Default payload for a fresh codex agent. Same 5 fields as claude-code.
346
- * `model` is left empty by default so the Codex CLI picks one matching the
347
- * user's auth mode — `gpt-5-codex` is rejected by ChatGPT-account auth, while
348
- * an empty string lets the SDK fall through to its built-in default.
349
- */
350
- const DEFAULT_CODEX_RUNTIME_CONFIG_PAYLOAD = {
351
- kind: "codex",
352
- prompt: { append: "" },
353
- model: "",
354
- mcpServers: [],
355
- env: [],
356
- gitRepos: []
357
- };
358
- /**
359
- * Default payload selector by runtime provider.
360
- */
361
- function defaultRuntimeConfigPayload(provider) {
362
- switch (provider) {
363
- case "codex": return { ...DEFAULT_CODEX_RUNTIME_CONFIG_PAYLOAD };
364
- case "claude-code": return { ...DEFAULT_AGENT_RUNTIME_CONFIG_PAYLOAD };
365
- default: return { ...DEFAULT_AGENT_RUNTIME_CONFIG_PAYLOAD };
366
- }
367
- }
368
284
  const agentRuntimeConfigSchema = z.object({
369
285
  agentId: z.string(),
370
286
  version: z.number().int().positive(),
@@ -392,17 +308,11 @@ const agentRuntimeConfigPatchShape = z.object({
392
308
  env: z.array(envEntrySchema),
393
309
  gitRepos: z.array(gitRepoSchema)
394
310
  }).partial();
395
- /**
396
- * Patch payload for PATCH /api/v1/admin/agents/:uuid/config.
397
- *
398
- * - `expectedVersion` enforces optimistic locking; mismatch → 409.
399
- * - All payload fields are optional; omitted fields are left untouched.
400
- */
401
- const updateAgentRuntimeConfigSchema = z.object({
311
+ z.object({
402
312
  expectedVersion: z.number().int().positive(),
403
313
  payload: agentRuntimeConfigPatchShape
404
314
  });
405
- const dryRunAgentRuntimeConfigSchema = z.object({ payload: agentRuntimeConfigPatchShape });
315
+ z.object({ payload: agentRuntimeConfigPatchShape });
406
316
  z.object({
407
317
  current: agentRuntimeConfigSchema,
408
318
  next: agentRuntimeConfigPayloadSchema,
@@ -417,10 +327,6 @@ z.object({
417
327
  after: z.unknown().optional()
418
328
  }))
419
329
  });
420
- /** Mask of a previously-stored sensitive value when echoing back to admin. */
421
- function isRedactedEnvValue(value) {
422
- return value === "***";
423
- }
424
330
  /**
425
331
  * Derive a default local path from a repo URL.
426
332
  * Used both for validation (duplicate detection) and at runtime when the user
@@ -450,25 +356,16 @@ const clientSessionStateSchema = z.enum([
450
356
  "suspended",
451
357
  "errored"
452
358
  ]);
453
- const sessionStateMessageSchema = z.object({
359
+ z.object({
454
360
  chatId: z.string().min(1),
455
361
  state: clientSessionStateSchema
456
362
  });
457
- /** Client-reported runtime state override (client server, per-agent). */
458
- const runtimeStateMessageSchema = z.object({ runtimeState: runtimeStateSchema });
459
- const agentBindRequestSchema = z.object({
363
+ z.object({ runtimeState: runtimeStateSchema });
364
+ z.object({
460
365
  agentId: z.string().min(1),
461
366
  runtimeType: z.string().max(50),
462
367
  runtimeVersion: z.string().max(50).optional()
463
368
  });
464
- const AGENT_BIND_REJECT_REASONS = {
465
- WRONG_CLIENT: "wrong_client",
466
- NOT_OWNED: "not_owned",
467
- AGENT_SUSPENDED: "agent_suspended",
468
- WRONG_ORG: "wrong_org",
469
- UNKNOWN_AGENT: "unknown_agent",
470
- RUNTIME_PROVIDER_MISMATCH: "runtime_provider_mismatch"
471
- };
472
369
  z.enum([
473
370
  "wrong_client",
474
371
  "not_owned",
@@ -504,21 +401,11 @@ z.object({
504
401
  clients: z.number().int()
505
402
  });
506
403
  const runtimeProviderSchema = z.enum(["claude-code", "codex"]);
507
- const DEFAULT_RUNTIME_PROVIDER = "claude-code";
508
- const AGENT_TYPES = {
509
- HUMAN: "human",
510
- PERSONAL_ASSISTANT: "personal_assistant",
511
- AUTONOMOUS_AGENT: "autonomous_agent"
512
- };
513
404
  const agentTypeSchema = z.enum([
514
405
  "human",
515
406
  "personal_assistant",
516
407
  "autonomous_agent"
517
408
  ]);
518
- const AGENT_VISIBILITY = {
519
- PRIVATE: "private",
520
- ORGANIZATION: "organization"
521
- };
522
409
  const agentVisibilitySchema = z.enum(["private", "organization"]);
523
410
  const avatarColorTokenSchema = z.enum([
524
411
  "hue-0",
@@ -530,11 +417,6 @@ const avatarColorTokenSchema = z.enum([
530
417
  "hue-6",
531
418
  "hue-7"
532
419
  ]);
533
- const AGENT_STATUSES = {
534
- ACTIVE: "active",
535
- SUSPENDED: "suspended",
536
- DELETED: "deleted"
537
- };
538
420
  const agentSourceSchema = z.enum(["admin-api", "portal"]);
539
421
  z.enum(["active", "suspended"]);
540
422
  /**
@@ -560,7 +442,7 @@ const RESERVED_AGENT_NAMES_SET = new Set([
560
442
  function isReservedAgentName(name) {
561
443
  return RESERVED_AGENT_NAMES_SET.has(name);
562
444
  }
563
- const createAgentSchema = z.object({
445
+ z.object({
564
446
  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(),
565
447
  type: agentTypeSchema,
566
448
  displayName: z.string().min(1).max(200).optional(),
@@ -574,7 +456,7 @@ const createAgentSchema = z.object({
574
456
  runtimeProvider: runtimeProviderSchema.optional(),
575
457
  gitRepos: z.array(gitRepoSchema).optional()
576
458
  });
577
- const updateAgentSchema = z.object({
459
+ z.object({
578
460
  type: agentTypeSchema.optional(),
579
461
  displayName: z.string().min(1).max(200).optional(),
580
462
  delegateMention: z.string().max(100).nullable().optional(),
@@ -584,14 +466,7 @@ const updateAgentSchema = z.object({
584
466
  clientId: z.string().min(1).max(100).nullable().optional(),
585
467
  avatarColorToken: avatarColorTokenSchema.nullable().optional()
586
468
  });
587
- /**
588
- * Service-level rebind input. Admin / owner re-binds an agent to a new
589
- * client and/or a new runtime provider in one atomic operation.
590
- *
591
- * `force` bypasses the capability-match check (e.g. when the client is
592
- * offline and capabilities are stale).
593
- */
594
- const rebindAgentSchema = z.object({
469
+ z.object({
595
470
  clientId: z.string().min(1).max(100),
596
471
  runtimeProvider: runtimeProviderSchema,
597
472
  force: z.boolean().optional()
@@ -621,13 +496,7 @@ z.object({
621
496
  repo: z.string().nullable(),
622
497
  branch: z.string().nullable()
623
498
  });
624
- /**
625
- * Server → client WebSocket frame announcing that an agent has just been
626
- * pinned to the connected client (either created with `clientId` or bound via
627
- * PATCH NULL → ID). The client can auto-register a local config from this so
628
- * the operator doesn't have to run `first-tree-hub agent add` manually.
629
- */
630
- const agentPinnedMessageSchema = z.object({
499
+ z.object({
631
500
  type: z.literal("agent:pinned"),
632
501
  agentId: z.string(),
633
502
  name: z.string().nullable(),
@@ -635,7 +504,7 @@ const agentPinnedMessageSchema = z.object({
635
504
  agentType: agentTypeSchema,
636
505
  runtimeProvider: runtimeProviderSchema
637
506
  });
638
- const loginSchema = z.object({
507
+ z.object({
639
508
  username: z.string().min(1),
640
509
  password: z.string().min(1)
641
510
  });
@@ -643,37 +512,19 @@ z.object({
643
512
  accessToken: z.string(),
644
513
  refreshToken: z.string()
645
514
  });
646
- const refreshTokenSchema = z.object({ refreshToken: z.string().min(1) });
647
- const connectTokenExchangeSchema = z.object({ token: z.string().min(1) });
515
+ z.object({ refreshToken: z.string().min(1) });
516
+ z.object({ token: z.string().min(1) });
648
517
  z.object({
649
518
  token: z.string(),
650
519
  expiresIn: z.number(),
651
520
  command: z.string()
652
521
  });
653
- /**
654
- * `chats.metadata` is `jsonb` at the DB layer. Without a typed contract every
655
- * writer was free to invent their own keys, so a GitHub webhook writing
656
- * `{ source: "github", entityKey: "..." }` and a Feishu adapter writing
657
- * `{ source: "feishu", externalChannelId: "..." }` could collide on shared
658
- * keys (`source`, `title`). The discriminated union below pins both writers
659
- * to a single shape and lets TypeScript narrow on `metadata.source`.
660
- *
661
- * Add new variants here; do not extend with free-form `Record<string, unknown>`.
662
- */
663
- /**
664
- * Source of truth for which github entities get their own tag in the
665
- * conversation list. Adding a new entry here extends `ChatSource`
666
- * (`github_${entityType}`) and the SQL CASE/IN-list in
667
- * `server/services/me-chat.ts` without touching them — both derive from
668
- * this constant.
669
- */
670
- const GITHUB_ENTITY_TYPES = [
522
+ const githubEntityTypeSchema = z.enum([
671
523
  "issue",
672
524
  "pull_request",
673
525
  "discussion",
674
526
  "commit"
675
- ];
676
- const githubEntityTypeSchema = z.enum(GITHUB_ENTITY_TYPES);
527
+ ]);
677
528
  const githubChatMetadataSchema = z.object({
678
529
  source: z.literal("github"),
679
530
  entityType: githubEntityTypeSchema,
@@ -701,28 +552,13 @@ const chatSourceSchema = z.enum([
701
552
  "feishu"
702
553
  ]);
703
554
  const chatTypeSchema = z.enum(["direct", "group"]);
704
- /**
705
- * Per-(chat, user) engagement state. Stored on `chat_user_state` so each
706
- * user manages their own view independently of structural membership.
707
- *
708
- * active — default; chat is in the user's active conversation list.
709
- * archived — user-snoozed; auto-revives to `active` when a new message
710
- * lands in the chat (see `services/chat-projection.ts`).
711
- * deleted — user-removed; never auto-revives. Restorable only by the
712
- * user from the chat detail page.
713
- */
714
- const CHAT_ENGAGEMENT_STATUSES = {
715
- ACTIVE: "active",
716
- ARCHIVED: "archived",
717
- DELETED: "deleted"
718
- };
719
555
  const chatEngagementStatusSchema = z.enum([
720
556
  "active",
721
557
  "archived",
722
558
  "deleted"
723
559
  ]);
724
- const patchChatEngagementSchema = z.object({ status: chatEngagementStatusSchema });
725
- const createChatSchema = z.object({
560
+ z.object({ status: chatEngagementStatusSchema });
561
+ z.object({
726
562
  type: chatTypeSchema,
727
563
  topic: z.string().max(500).optional(),
728
564
  participantIds: z.array(z.string()).min(1),
@@ -759,18 +595,8 @@ z.object({
759
595
  engagementStatus: chatEngagementStatusSchema,
760
596
  viewerMembershipKind: z.enum(["participant", "watching"]).nullable()
761
597
  });
762
- const updateChatSchema = z.object({ topic: z.string().trim().max(500).nullable() });
763
- /**
764
- * Public API body for `POST /api/v1/agent/chats/:chatId/participants`.
765
- * Phase 1 removed the `mode` field: participant mode is derived server-side
766
- * from `(chats.type, agents.type)` via `services/participant-mode.ts` and
767
- * cannot be overridden by the caller. The handler still inspects the raw
768
- * body and rejects with `400 MODE_FIELD_DEPRECATED` if `mode` is present,
769
- * so an out-of-tree caller that still sends it gets a clear error and a
770
- * telemetry counter increments — see `chat-participant-mode-fix-design.md`
771
- * §3.2 / §6.
772
- */
773
- const addParticipantSchema = z.object({ agentId: z.string().min(1) });
598
+ z.object({ topic: z.string().trim().max(500).nullable() });
599
+ z.object({ agentId: z.string().min(1) });
774
600
  z.object({ agentId: z.string().min(1) });
775
601
  const clientStatusSchema = z.enum(["connected", "disconnected"]);
776
602
  /**
@@ -813,7 +639,7 @@ z.object({
813
639
  * mandatory on this server build.
814
640
  */
815
641
  const clientWireCapabilitiesSchema = z.object({ wsInboxDeliver: z.boolean().default(false) }).partial();
816
- const clientRegisterSchema = z.object({
642
+ z.object({
817
643
  clientId: z.string().min(1).max(100),
818
644
  hostname: z.string().max(100).optional(),
819
645
  os: z.string().max(50).optional(),
@@ -855,8 +681,8 @@ const capabilityEntrySchema = z.object({
855
681
  * as `RuntimeProvider` strings.
856
682
  */
857
683
  const clientCapabilitiesSchema = z.record(z.string(), capabilityEntrySchema);
858
- const updateClientCapabilitiesSchema = z.object({ capabilities: clientCapabilitiesSchema });
859
- const paginationQuerySchema = z.object({
684
+ z.object({ capabilities: clientCapabilitiesSchema });
685
+ z.object({
860
686
  limit: z.coerce.number().int().min(1).max(100).default(20),
861
687
  cursor: z.string().optional()
862
688
  });
@@ -950,7 +776,7 @@ const contextTreeUsageSummarySchema = z.object({
950
776
  agentCount: z.number().int().nonnegative(),
951
777
  usageCount: z.number().int().nonnegative()
952
778
  });
953
- const contextTreeSnapshotSchema = z.object({
779
+ z.object({
954
780
  repo: z.string().nullable(),
955
781
  branch: z.string().nullable(),
956
782
  headCommit: z.string().nullable(),
@@ -990,24 +816,7 @@ z.object({
990
816
  refreshToken: z.string().optional(),
991
817
  refreshTokenExpiresAt: z.string().datetime({ offset: true }).optional()
992
818
  });
993
- /**
994
- * GET-side projection returned by the Hub admin API for the Integrations
995
- * panel. Secrets are never echoed — only the metadata needed to render
996
- * "you're connected as @octocat (Organization), 7 repos selected".
997
- *
998
- * `selectedRepoCount` is derived from a separate join (App webhook
999
- * `installation_repositories` events update a children table not modeled
1000
- * here yet); included now so the panel's API shape is stable from the
1001
- * first ship.
1002
- */
1003
- /**
1004
- * Body for `POST /orgs/:orgId/github-app-installation/claim` — manual
1005
- * recovery for an installation row that ended up unbound (codex P1-5 + H1).
1006
- * The orphan-reclaim sweep at sign-in auto-claims the single-orphan case;
1007
- * this endpoint backs the Settings "Claim install" buttons when there's
1008
- * more than one (or the account is an org, where auto-claim is too risky).
1009
- */
1010
- const githubAppInstallationClaimBodySchema = z.object({ installationId: z.number().int().positive() });
819
+ z.object({ installationId: z.number().int().positive() });
1011
820
  z.object({
1012
821
  installationId: z.number().int().positive(),
1013
822
  accountType: githubAccountTypeSchema,
@@ -1026,14 +835,7 @@ const supportedImageMimeSchema = z.enum([
1026
835
  "image/gif",
1027
836
  "image/webp"
1028
837
  ]);
1029
- /**
1030
- * Legacy inbound shape: an image message with base64 bytes inlined into
1031
- * `messages.content`. Web still uploads in this shape so no new endpoint is
1032
- * needed; the server extracts the bytes, broadcasts them as an `image_payload`
1033
- * WS frame, then rewrites `content` to {@link imageRefContentSchema} before
1034
- * the DB insert.
1035
- */
1036
- const imageInlineContentSchema = z.object({
838
+ z.object({
1037
839
  data: z.string().min(1),
1038
840
  mimeType: supportedImageMimeSchema,
1039
841
  filename: z.string().min(1),
@@ -1088,7 +890,7 @@ const messageFormatSchema = z.enum([
1088
890
  * adapter, etc.) and goes through the normal enforcement profile.
1089
891
  */
1090
892
  const messagePurposeSchema = z.enum(["agent-final-text"]);
1091
- const sendMessageSchema = z.object({
893
+ z.object({
1092
894
  format: messageFormatSchema.default("text"),
1093
895
  content: z.unknown(),
1094
896
  metadata: z.record(z.string(), z.unknown()).optional(),
@@ -1098,7 +900,7 @@ const sendMessageSchema = z.object({
1098
900
  source: messageSourceSchema.optional(),
1099
901
  purpose: messagePurposeSchema.optional()
1100
902
  });
1101
- const sendToAgentSchema = z.object({
903
+ z.object({
1102
904
  format: messageFormatSchema.default("text"),
1103
905
  content: z.unknown(),
1104
906
  metadata: z.record(z.string(), z.unknown()).optional(),
@@ -1199,29 +1001,15 @@ z.object({
1199
1001
  deliveredAt: z.string().nullable(),
1200
1002
  ackedAt: z.string().nullable()
1201
1003
  }).extend({ message: clientMessageSchema });
1202
- const inboxPollQuerySchema = z.object({ limit: z.coerce.number().int().min(1).max(50).default(10) });
1203
- /**
1204
- * server → client: a single inbox entry pushed over the active WS connection.
1205
- *
1206
- * `entryId` is the server-side `inbox_entries.id` the client must echo back
1207
- * in `inbox:ack`. `clientMessageSchema` carries `precedingMessages`, so the
1208
- * client-side dispatch logic handles the silent-context bundle uniformly.
1209
- *
1210
- * `.passthrough()` so a forward-rolling server may extend the frame without
1211
- * breaking older clients that validate strictly. Older clients drop unknown
1212
- * fields silently.
1213
- */
1214
- const inboxDeliverFrameSchema = z.object({
1004
+ z.object({ limit: z.coerce.number().int().min(1).max(50).default(10) });
1005
+ z.object({
1215
1006
  type: z.literal("inbox:deliver"),
1216
1007
  entryId: z.number().int().nonnegative(),
1217
1008
  inboxId: z.string().min(1),
1218
1009
  chatId: z.string().nullable(),
1219
1010
  message: clientMessageSchema
1220
1011
  }).passthrough();
1221
- /**
1222
- * client → server: ack for an `inbox:deliver` frame.
1223
- */
1224
- const inboxAckFrameSchema = z.object({
1012
+ z.object({
1225
1013
  type: z.literal("inbox:ack"),
1226
1014
  entryId: z.number().int().nonnegative()
1227
1015
  });
@@ -1241,8 +1029,7 @@ z.object({
1241
1029
  createdAt: z.string(),
1242
1030
  expiresAt: z.string().nullable()
1243
1031
  });
1244
- /** Body for joining via invite token. */
1245
- const joinByInvitationSchema = z.object({ token: z.string().min(1) });
1032
+ z.object({ token: z.string().min(1) });
1246
1033
  z.object({}).optional();
1247
1034
  const meChatFilterSchema = z.enum([
1248
1035
  "all",
@@ -1255,7 +1042,7 @@ const chatEngagementViewSchema = z.enum([
1255
1042
  "archived",
1256
1043
  "all"
1257
1044
  ]);
1258
- const listMeChatsQuerySchema = z.object({
1045
+ z.object({
1259
1046
  cursor: z.string().optional(),
1260
1047
  limit: z.coerce.number().int().min(1).max(200).default(50),
1261
1048
  filter: meChatFilterSchema.default("all"),
@@ -1289,8 +1076,6 @@ const liveActivitySchema = z.object({
1289
1076
  label: z.string(),
1290
1077
  startedAt: z.string()
1291
1078
  });
1292
- /** Stale threshold (ms) past which a `session_events` row stops driving liveActivity. */
1293
- const LIVE_ACTIVITY_STALE_MS = 6e4;
1294
1079
  const meChatRowSchema = z.object({
1295
1080
  chatId: z.string(),
1296
1081
  type: z.string(),
@@ -1311,11 +1096,11 @@ z.object({
1311
1096
  rows: z.array(meChatRowSchema),
1312
1097
  nextCursor: z.string().nullable()
1313
1098
  });
1314
- const createMeChatSchema = z.object({
1099
+ z.object({
1315
1100
  participantIds: z.array(z.string().min(1)).min(1),
1316
1101
  topic: z.string().trim().max(500).optional().nullable()
1317
1102
  });
1318
- const addMeChatParticipantsSchema = z.object({ participantIds: z.array(z.string().min(1)).min(1) });
1103
+ z.object({ participantIds: z.array(z.string().min(1)).min(1) });
1319
1104
  z.object({
1320
1105
  chatId: z.string(),
1321
1106
  lastReadAt: z.string(),
@@ -1354,7 +1139,7 @@ const chatSourceCountSchema = z.object({
1354
1139
  chatCount: z.number().int().nonnegative(),
1355
1140
  unreadChatCount: z.number().int().nonnegative()
1356
1141
  });
1357
- const listMeChatSourceCountsQuerySchema = z.object({ engagement: chatEngagementViewSchema.default("active") });
1142
+ z.object({ engagement: chatEngagementViewSchema.default("active") });
1358
1143
  z.object({ counts: z.partialRecord(chatSourceSchema, chatSourceCountSchema) });
1359
1144
  const workspaceDocRefSchema = z.object({
1360
1145
  type: z.literal("workspace"),
@@ -1363,13 +1148,33 @@ const workspaceDocRefSchema = z.object({
1363
1148
  basePath: z.string().trim().optional(),
1364
1149
  path: z.string().trim().min(1)
1365
1150
  });
1366
- z.object({ basePath: z.string().trim().min(1) });
1367
- const getMeDocSchema = z.object({
1151
+ const snapshotDocSchema = z.object({
1152
+ path: z.string().trim().min(1).refine(isCanonicalDocLinkPath, { message: "path must be a canonical workspace-relative doc path (no leading /, no ./, no ..)" }).refine((p) => p.toLowerCase().endsWith(".md"), { message: "path must have a .md extension" }),
1153
+ sha256: z.string().regex(/^[0-9a-f]{64}$/, "sha256 must be 64 lowercase hex chars"),
1154
+ size: z.number().int().nonnegative(),
1155
+ content: z.string()
1156
+ });
1157
+ const snapshotDocumentContextSchema = z.object({
1158
+ kind: z.literal("snapshot"),
1159
+ docs: z.array(snapshotDocSchema).min(1).max(5)
1160
+ });
1161
+ const pathDocumentContextSchema = z.object({
1162
+ kind: z.literal("path"),
1163
+ basePath: z.string().trim().min(1)
1164
+ });
1165
+ z.preprocess((val) => {
1166
+ if (val && typeof val === "object" && !Array.isArray(val) && !("kind" in val) && "basePath" in val) return {
1167
+ kind: "path",
1168
+ ...val
1169
+ };
1170
+ return val;
1171
+ }, z.discriminatedUnion("kind", [snapshotDocumentContextSchema, pathDocumentContextSchema]));
1172
+ z.object({
1368
1173
  agentId: z.string().trim().min(1),
1369
1174
  basePath: z.string().trim().optional(),
1370
1175
  path: z.string().trim().min(1)
1371
1176
  });
1372
- const getMeDocResponseSchema = z.object({
1177
+ z.object({
1373
1178
  ref: workspaceDocRefSchema,
1374
1179
  path: z.string(),
1375
1180
  content: z.string()
@@ -1385,18 +1190,11 @@ z.object({
1385
1190
  displayName: z.string(),
1386
1191
  role: z.enum(["admin", "member"])
1387
1192
  });
1388
- /** Body for `POST /me/organizations` — operator wants to create another team. */
1389
- const createOrgFromMeSchema = z.object({
1193
+ z.object({
1390
1194
  name: z.string().min(2).max(50).regex(/^[a-z0-9][a-z0-9-]*$/),
1391
1195
  displayName: z.string().min(1).max(200)
1392
1196
  });
1393
- /**
1394
- * Body for `PATCH /me/onboarding`. v1 only mutates `dismissed` — true to
1395
- * hide the onboarding stepper (server stamps `users.onboarding_dismissed_at
1396
- * = NOW()`), false to restore it. See docs/new-user-onboarding-design.md
1397
- * §8.4.
1398
- */
1399
- const patchOnboardingSchema = z.object({ dismissed: z.boolean().optional() });
1197
+ z.object({ dismissed: z.boolean().optional() });
1400
1198
  /**
1401
1199
  * Body for `POST /me/onboarding/events`. The web SPA reports key
1402
1200
  * milestones so the server can log them as a single funnel-trackable
@@ -1418,7 +1216,7 @@ const onboardingEventNameSchema = z.enum([
1418
1216
  "tree_chat_started",
1419
1217
  "tree_intro_dismissed"
1420
1218
  ]);
1421
- const onboardingEventSchema = z.object({
1219
+ z.object({
1422
1220
  event: onboardingEventNameSchema,
1423
1221
  attrs: z.record(z.string(), z.union([
1424
1222
  z.string(),
@@ -1444,13 +1242,12 @@ const memberSchema = z.object({
1444
1242
  role: memberRoleSchema,
1445
1243
  createdAt: z.string()
1446
1244
  });
1447
- /** Admin creates a member — password is auto-generated, returned once. */
1448
- const createMemberSchema = z.object({
1245
+ z.object({
1449
1246
  username: z.string().min(1).max(100),
1450
1247
  displayName: z.string().min(1).max(200),
1451
1248
  role: memberRoleSchema.default("member")
1452
1249
  });
1453
- const updateMemberSchema = z.object({
1250
+ z.object({
1454
1251
  role: memberRoleSchema.optional(),
1455
1252
  displayName: z.string().min(1).max(200).optional()
1456
1253
  });
@@ -1550,11 +1347,6 @@ z.object({
1550
1347
  }),
1551
1348
  mentionedUser: z.string().optional()
1552
1349
  });
1553
- const NOTIFICATION_TYPES = {
1554
- AGENT_ERROR: "agent_error",
1555
- AGENT_BLOCKED: "agent_blocked",
1556
- AGENT_STALE: "agent_stale"
1557
- };
1558
1350
  const notificationTypeSchema = z.enum([
1559
1351
  "agent_error",
1560
1352
  "agent_blocked",
@@ -1576,21 +1368,15 @@ z.object({
1576
1368
  read: z.boolean(),
1577
1369
  createdAt: z.string()
1578
1370
  });
1579
- const notificationQuerySchema = z.object({
1371
+ z.object({
1580
1372
  limit: z.coerce.number().int().min(1).max(100).default(20),
1581
1373
  cursor: z.string().optional(),
1582
1374
  severity: notificationSeveritySchema.optional(),
1583
1375
  read: z.enum(["true", "false"]).transform((v) => v === "true").optional(),
1584
1376
  agentId: z.string().optional()
1585
1377
  });
1586
- /**
1587
- * `GET /api/v1/auth/github/start` query — `next` is the post-login landing
1588
- * path. It is validated again before signing the state JWT (see
1589
- * `safe-redirect.ts`); the schema only enforces the syntactic upper bound
1590
- * so over-long paths bounce with a Zod error rather than silently truncate.
1591
- */
1592
- const githubStartQuerySchema = z.object({ next: z.string().max(256).optional() });
1593
- const githubCallbackQuerySchema = z.object({
1378
+ z.object({ next: z.string().max(256).optional() });
1379
+ z.object({
1594
1380
  code: z.string().min(1),
1595
1381
  state: z.string().min(1),
1596
1382
  installation_id: z.string().regex(/^\d+$/).optional(),
@@ -1600,19 +1386,7 @@ const githubCallbackQuerySchema = z.object({
1600
1386
  "request"
1601
1387
  ]).optional()
1602
1388
  });
1603
- /**
1604
- * Dev-only callback to bypass the GitHub round-trip — sign in as a stub
1605
- * Github user. Gated by NODE_ENV !== 'production'; production always 404s.
1606
- *
1607
- * The App-flow extension fields (`installationId`, `installationAccountType`,
1608
- * `installationAccountLogin`, `installationAccountGithubId`) let the dev
1609
- * flow simulate a GitHub App install in the same redirect — when present
1610
- * they stub a `github_app_installations` row before the OAuth flow
1611
- * completes, so the rest of the dev session can exercise the App-bound
1612
- * code paths (Settings → Integrations panel, webhook routing) without a
1613
- * real install. Missing → legacy OAuth-only dev flow.
1614
- */
1615
- const githubDevCallbackQuerySchema = z.object({
1389
+ z.object({
1616
1390
  githubId: z.string().min(1),
1617
1391
  login: z.string().min(1),
1618
1392
  email: z.string().email().optional(),
@@ -1721,9 +1495,6 @@ const ORG_SETTINGS_NAMESPACES = {
1721
1495
  };
1722
1496
  const ORG_SETTINGS_NAMESPACE_KEYS = Object.keys(ORG_SETTINGS_NAMESPACES);
1723
1497
  z.enum(ORG_SETTINGS_NAMESPACE_KEYS);
1724
- function isOrgSettingNamespace(value) {
1725
- return typeof value === "string" && ORG_SETTINGS_NAMESPACE_KEYS.includes(value);
1726
- }
1727
1498
  const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1728
1499
  z.object({
1729
1500
  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"),
@@ -1732,7 +1503,7 @@ z.object({
1732
1503
  maxMessagesPerMinute: z.number().int().min(0).default(0),
1733
1504
  features: z.record(z.string(), z.unknown()).default({})
1734
1505
  });
1735
- const updateOrganizationSchema = z.object({
1506
+ z.object({
1736
1507
  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").optional(),
1737
1508
  displayName: z.string().min(1).max(200).optional(),
1738
1509
  maxAgents: z.number().int().min(0).optional(),
@@ -1807,33 +1578,17 @@ const questionItemSchema = z.object({
1807
1578
  });
1808
1579
  /** Session-level preview format hint. Mirrors `toolConfig.askUserQuestion.previewFormat`. */
1809
1580
  const questionPreviewFormatSchema = z.enum(["html", "markdown"]).nullable();
1810
- /**
1811
- * Content payload for a message whose `format === "question"`.
1812
- *
1813
- * `correlationId` ties the question to its eventual answer message AND to the
1814
- * server-side `pending_questions` row; client runtime uses it to resolve the
1815
- * waiting `canUseTool` promise. Reuse the SDK `tool_use_id` when available so
1816
- * a single id flows end-to-end.
1817
- */
1818
- const questionMessageContentSchema = z.object({
1581
+ z.object({
1819
1582
  correlationId: z.string().min(1),
1820
1583
  questions: z.array(questionItemSchema).min(1).max(4),
1821
1584
  previewFormat: questionPreviewFormatSchema,
1822
1585
  allowFreeText: z.boolean()
1823
1586
  });
1824
- /**
1825
- * Content payload for a message whose `format === "question_answer"`.
1826
- *
1827
- * `answers` is keyed by `QuestionItem.question` text. For `multiSelect` questions
1828
- * the value is a `, `-joined string of selected labels (matches SDK convention).
1829
- * For free-text answers the value is the user's raw input.
1830
- */
1831
- const questionAnswerMessageContentSchema = z.object({
1587
+ z.object({
1832
1588
  correlationId: z.string().min(1),
1833
1589
  answers: z.record(z.string().min(1), z.string())
1834
1590
  });
1835
- /** Submit-answer request body for `POST /api/admin/questions/:correlationId/answer`. */
1836
- const submitQuestionAnswerSchema = z.object({ answers: z.record(z.string().min(1), z.string()) });
1591
+ z.object({ answers: z.record(z.string().min(1), z.string()) });
1837
1592
  z.enum([
1838
1593
  "pending",
1839
1594
  "answered",
@@ -1933,14 +1688,12 @@ z.object({
1933
1688
  ]),
1934
1689
  createdAt: z.string()
1935
1690
  });
1936
- /** WS message: client reports a session event (tool_call / error) to the server. */
1937
- const sessionEventMessageSchema = z.object({
1691
+ z.object({
1938
1692
  agentId: z.string(),
1939
1693
  chatId: z.string(),
1940
1694
  event: sessionEventSchema
1941
1695
  });
1942
- /** Client → server: list locally-held chatIds; server replies with the subset to drop. */
1943
- const sessionReconcileRequestSchema = z.object({
1696
+ z.object({
1944
1697
  type: z.literal("session:reconcile"),
1945
1698
  agentId: z.string().min(1),
1946
1699
  chatIds: z.array(z.string().min(1)).max(500)
@@ -1981,17 +1734,10 @@ z.object({
1981
1734
  displayName: z.string().min(1).max(200).optional(),
1982
1735
  password: z.string().min(8).max(200).optional()
1983
1736
  });
1984
- /**
1985
- * First-frame auth envelope sent by the SDK on the client WS connection.
1986
- * The server rejects the connection with close code 4401 if this frame does
1987
- * not arrive within {@link WS_AUTH_FRAME_TIMEOUT_MS} or fails verification.
1988
- */
1989
- const wsAuthFrameSchema = z.object({
1737
+ z.object({
1990
1738
  type: z.literal("auth"),
1991
1739
  token: z.string().min(1)
1992
1740
  });
1993
- /** How long the server waits for the first `auth` frame before closing the WS. */
1994
- const WS_AUTH_FRAME_TIMEOUT_MS = 5e3;
1995
1741
  /**
1996
1742
  * Negotiable wire-protocol features the server advertises in its `welcome`
1997
1743
  * frame. Older clients drop the `capabilities` field silently because the
@@ -2011,4 +1757,49 @@ z.object({
2011
1757
  capabilities: serverCapabilitiesSchema.optional()
2012
1758
  }).passthrough();
2013
1759
  //#endregion
2014
- export { listMeChatSourceCountsQuerySchema as $, createMeChatSchema as A, updateOrganizationSchema as At, githubAppInstallationClaimBodySchema as B, clientRegisterSchema as C, submitQuestionAnswerSchema as Ct, createAdapterMappingSchema as D, updateChatSchema as Dt, createAdapterConfigSchema as E, updateAgentSchema as Et, delegateFeishuUserSchema as F, imageInlineContentSchema as G, githubCallbackQuerySchema as H, dryRunAgentRuntimeConfigSchema as I, inboxPollQuerySchema as J, inboxAckFrameSchema as K, extractMentions as L, createOrgFromMeSchema as M, defaultParticipantMode as N, createAgentSchema as O, updateClientCapabilitiesSchema as Ot, defaultRuntimeConfigPayload as P, joinByInvitationSchema as Q, getMeDocResponseSchema as R, clientCapabilitiesSchema as S, stripCode as St, contextTreeSnapshotSchema as T, updateAgentRuntimeConfigSchema as Tt, githubDevCallbackQuerySchema as U, githubAppInstallationPermissionsSchema as V, githubStartQuerySchema as W, isRedactedEnvValue as X, isOrgSettingNamespace as Y, isReservedAgentName as Z, agentBindRequestSchema as _, sendToAgentSchema as _t, AGENT_TYPES as a, paginationQuerySchema as at, agentTypeSchema as b, sessionReconcileRequestSchema as bt, DEFAULT_RUNTIME_PROVIDER as c, questionAnswerMessageContentSchema as ct, MENTION_REGEX as d, refreshTokenSchema as dt, listMeChatsQuerySchema as et, NOTIFICATION_TYPES as f, runtimeStateMessageSchema as ft, addParticipantSchema as g, sendMessageSchema as gt, addMeChatParticipantsSchema as h, selfServiceFeishuBotSchema as ht, AGENT_STATUSES as i, onboardingEventSchema as it, createMemberSchema as j, wsAuthFrameSchema as jt, createChatSchema as k, updateMemberSchema as kt, GITHUB_ENTITY_TYPES as l, questionMessageContentSchema as lt, WS_AUTH_FRAME_TIMEOUT_MS as m, scanMentionTokens as mt, AGENT_NAME_REGEX as n, messageSourceSchema as nt, AGENT_VISIBILITY as o, patchChatEngagementSchema as ot, ORG_SETTINGS_NAMESPACES as p, safeRedirectPath as pt, inboxDeliverFrameSchema as q, AGENT_SELECTOR_HEADER as r, notificationQuerySchema as rt, CHAT_ENGAGEMENT_STATUSES as s, patchOnboardingSchema as st, AGENT_BIND_REJECT_REASONS as t, loginSchema as tt, LIVE_ACTIVITY_STALE_MS as u, rebindAgentSchema as ut, agentPinnedMessageSchema as v, sessionEventMessageSchema as vt, connectTokenExchangeSchema as w, updateAdapterConfigSchema as wt, chatMetadataSchema as x, sessionStateMessageSchema as xt, agentRuntimeConfigPayloadSchema as y, sessionEventSchema as yt, getMeDocSchema as z };
1760
+ //#region src/core/feishu.ts
1761
+ /**
1762
+ * Feishu-related core operations: bind-bot, bind-user.
1763
+ *
1764
+ * All agent-scoped calls carry both the member access JWT (Authorization)
1765
+ * and the acting agent UUID (X-Agent-Id); the server's agent-selector
1766
+ * middleware enforces Rule R-RUN.
1767
+ */
1768
+ async function bindFeishuBot(serverUrl, accessToken, agentId, appId, appSecret) {
1769
+ const res = await cliFetch(`${serverUrl}/api/v1/agent/me/feishu-bot`, {
1770
+ method: "PUT",
1771
+ headers: {
1772
+ Authorization: `Bearer ${accessToken}`,
1773
+ [AGENT_SELECTOR_HEADER]: agentId,
1774
+ "Content-Type": "application/json"
1775
+ },
1776
+ body: JSON.stringify({
1777
+ appId,
1778
+ appSecret
1779
+ })
1780
+ });
1781
+ if (!res.ok) {
1782
+ const body = await res.json().catch(() => ({}));
1783
+ throw new Error(body.error ?? `Bind Feishu bot failed: HTTP ${res.status}`);
1784
+ }
1785
+ }
1786
+ async function bindFeishuUser(serverUrl, accessToken, agentId, humanAgentId, feishuUserId, displayName) {
1787
+ const res = await cliFetch(`${serverUrl}/api/v1/agent/delegated/${encodeURIComponent(humanAgentId)}/feishu-user`, {
1788
+ method: "POST",
1789
+ headers: {
1790
+ Authorization: `Bearer ${accessToken}`,
1791
+ [AGENT_SELECTOR_HEADER]: agentId,
1792
+ "Content-Type": "application/json"
1793
+ },
1794
+ body: JSON.stringify({
1795
+ feishuUserId,
1796
+ displayName
1797
+ })
1798
+ });
1799
+ if (!res.ok) {
1800
+ const body = await res.json().catch(() => ({}));
1801
+ throw new Error(body.error ?? `Bind Feishu user failed: HTTP ${res.status}`);
1802
+ }
1803
+ }
1804
+ //#endregion
1805
+ export { bindFeishuUser as n, bindFeishuBot as t };