@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.
- package/dist/bootstrap-BmeaRhRp.mjs +3 -0
- package/dist/{bootstrap-CQQGgIx1.mjs → bootstrap-CmkHQsnS.mjs} +24 -16
- package/dist/cli/index.mjs +6 -94
- package/dist/{dist-CrdnqZjv.mjs → feishu-BE7QRxnE.mjs} +170 -379
- package/dist/feishu-De9_bA91.mjs +3 -0
- package/dist/index.mjs +5 -12
- package/dist/saas-connect-CNY9Ve5V.mjs +13748 -0
- package/package.json +4 -12
- package/dist/chunk-BSw8zbkd.mjs +0 -37
- package/dist/client-BPRIfrOT-CoV_2o7e.mjs +0 -4230
- package/dist/client-CEdYVnoj-BGiGcJbH.mjs +0 -7
- package/dist/dist-LgF7LHpE.mjs +0 -430
- package/dist/drizzle/0000_shocking_darkhawk.sql +0 -92
- package/dist/drizzle/0001_v2_schema_updates.sql +0 -26
- package/dist/drizzle/0002_adapter_tables.sql +0 -64
- package/dist/drizzle/0003_feishu_adapter.sql +0 -21
- package/dist/drizzle/0004_adapter_refactor.sql +0 -13
- package/dist/drizzle/0005_delegate_mention.sql +0 -1
- package/dist/drizzle/0006_agent_tree_path.sql +0 -1
- package/dist/drizzle/0007_decouple_context_tree.sql +0 -2
- package/dist/drizzle/0008_uuid_identity.sql +0 -12
- package/dist/drizzle/0009_agent_runtime_m1.sql +0 -31
- package/dist/drizzle/0010_cloud_multi_tenancy.sql +0 -34
- package/dist/drizzle/0011_org_uuid_pk.sql +0 -22
- package/dist/drizzle/0012_session_level_state.sql +0 -19
- package/dist/drizzle/0013_hub_tasks.sql +0 -38
- package/dist/drizzle/0014_drop_task_fks.sql +0 -9
- package/dist/drizzle/0015_member_system.sql +0 -34
- package/dist/drizzle/0016_strange_havok.sql +0 -25
- package/dist/drizzle/0017_session_outputs_unique.sql +0 -1
- package/dist/drizzle/0018_agent_visibility.sql +0 -13
- package/dist/drizzle/0019_agent_configs.sql +0 -30
- package/dist/drizzle/0020_unified_user_token.sql +0 -154
- package/dist/drizzle/0021_drop_agents_profile.sql +0 -10
- package/dist/drizzle/0022_session_events.sql +0 -32
- package/dist/drizzle/0023_clients_org_scoping.sql +0 -40
- package/dist/drizzle/0024_display_name_not_null.sql +0 -31
- package/dist/drizzle/0025_inbox_silent_entries.sql +0 -53
- package/dist/drizzle/0026_saas_onboarding.sql +0 -153
- package/dist/drizzle/0027_runtime_provider.sql +0 -10
- package/dist/drizzle/0028_auth_identity_user_github_unique.sql +0 -12
- package/dist/drizzle/0029_direct_agent_only_mention_only.sql +0 -28
- package/dist/drizzle/0030_chat_first_workspace.sql +0 -129
- package/dist/drizzle/0031_drop_system_configs.sql +0 -11
- package/dist/drizzle/0032_organization_settings.sql +0 -36
- package/dist/drizzle/0033_onboarding_dismissed_at.sql +0 -13
- package/dist/drizzle/0034_pending_questions.sql +0 -34
- package/dist/drizzle/0035_drop_hub_tasks.sql +0 -7
- package/dist/drizzle/0036_github_entity_chat_mappings.sql +0 -47
- package/dist/drizzle/0037_github_app_installations.sql +0 -52
- package/dist/drizzle/0038_chat_membership_user_state.sql +0 -223
- package/dist/drizzle/0039_drop_chat_participants_subscriptions.sql +0 -26
- package/dist/drizzle/0040_chat_user_state_engagement.sql +0 -24
- package/dist/drizzle/0041_notifications_dedup_key.sql +0 -29
- package/dist/drizzle/0042_notifications_drop_legacy_types.sql +0 -36
- package/dist/drizzle/0043_onboarding_completed_at.sql +0 -32
- package/dist/drizzle/0044_agent_avatar_color.sql +0 -11
- package/dist/drizzle/0045_agent_avatar_image.sql +0 -17
- package/dist/drizzle/meta/0000_snapshot.json +0 -687
- package/dist/drizzle/meta/0001_snapshot.json +0 -687
- package/dist/drizzle/meta/0012_snapshot.json +0 -1451
- package/dist/drizzle/meta/0013_snapshot.json +0 -1771
- package/dist/drizzle/meta/0014_snapshot.json +0 -1717
- package/dist/drizzle/meta/0016_snapshot.json +0 -1917
- package/dist/drizzle/meta/0018_snapshot.json +0 -1938
- package/dist/drizzle/meta/_journal.json +0 -328
- package/dist/esm-iadMkGbV.mjs +0 -1516
- package/dist/execAsync-DUfRkc4a.mjs +0 -10
- package/dist/execAsync-YbEZSOYd.mjs +0 -10
- package/dist/feishu-DNoBroKK.mjs +0 -53
- package/dist/from-DQ7eNRwu.mjs +0 -3840
- package/dist/getMachineId-bsd-BmasEOJr.mjs +0 -27
- package/dist/getMachineId-bsd-Dh3h0DDE.mjs +0 -27
- package/dist/getMachineId-darwin-CuhM3hfZ.mjs +0 -24
- package/dist/getMachineId-darwin-D9wR0SLj.mjs +0 -24
- package/dist/getMachineId-linux-CYfb0oxZ.mjs +0 -20
- package/dist/getMachineId-linux-D8ZaSjAC.mjs +0 -20
- package/dist/getMachineId-unsupported-Cu3iisaD.mjs +0 -15
- package/dist/getMachineId-unsupported-DZqI4ZT5.mjs +0 -15
- package/dist/getMachineId-win-8ZJbtrdf.mjs +0 -26
- package/dist/getMachineId-win-DT-hqwVp.mjs +0 -26
- package/dist/invitation-C9m2gQx4-C_4f5VTs.mjs +0 -4
- package/dist/invitation-D_ENPHyj-5ETiae5r.mjs +0 -167
- package/dist/logger-core-BTmvdflj-DjW8FM4T.mjs +0 -146
- package/dist/multipart-parser-QRu3OKK4.mjs +0 -294
- package/dist/observability-BAScT_5S-BcW9HgkG.mjs +0 -96129
- package/dist/observability-eLA9iNK_.mjs +0 -5
- package/dist/saas-connect-Da55XxRX.mjs +0 -21635
- package/dist/src-DFlbpJfU.mjs +0 -1176
- package/dist/src-DNBS5Yjj.mjs +0 -735
- package/dist/uuid-DbS_4vFh-iFghv4zA.mjs +0 -129
- package/dist/web/assets/index-9wK0udbH.js +0 -416
- package/dist/web/assets/index-C7x7O7dG.js +0 -11
- package/dist/web/assets/index-DE7Q3QWE.css +0 -1
- package/dist/web/favicon.svg +0 -9
- package/dist/web/fonts/inter-latin-ext.woff2 +0 -0
- package/dist/web/fonts/inter-latin.woff2 +0 -0
- package/dist/web/fonts/jetbrains-mono-latin-ext.woff2 +0 -0
- package/dist/web/fonts/jetbrains-mono-latin.woff2 +0 -0
- package/dist/web/index.html +0 -39
- /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
|
-
*
|
|
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
|
-
*
|
|
7
|
+
* Shared by:
|
|
8
|
+
* - web link click handlers (`docPreviewPathFromHref`)
|
|
9
|
+
* - client runtime snapshot scan (`buildMessageDocumentSnapshots`)
|
|
10
|
+
* - server snapshot schema refinement (`snapshotDocSchema`)
|
|
60
11
|
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
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
|
-
*
|
|
71
|
-
* an
|
|
72
|
-
*
|
|
73
|
-
*
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
if (
|
|
78
|
-
|
|
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
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
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
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
+
z.object({
|
|
142
91
|
appId: z.string().min(1),
|
|
143
92
|
appSecret: z.string().min(1)
|
|
144
93
|
});
|
|
145
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
359
|
+
z.object({
|
|
454
360
|
chatId: z.string().min(1),
|
|
455
361
|
state: clientSessionStateSchema
|
|
456
362
|
});
|
|
457
|
-
|
|
458
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
647
|
-
|
|
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
|
-
|
|
725
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
859
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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({
|
|
1367
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 };
|