@data-club/ai-hub-server 0.0.1

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 (248) hide show
  1. package/dist/auth/argon2.d.ts +72 -0
  2. package/dist/auth/argon2.d.ts.map +1 -0
  3. package/dist/auth/argon2.js +145 -0
  4. package/dist/auth/argon2.js.map +1 -0
  5. package/dist/auth/encryption.d.ts +52 -0
  6. package/dist/auth/encryption.d.ts.map +1 -0
  7. package/dist/auth/encryption.js +98 -0
  8. package/dist/auth/encryption.js.map +1 -0
  9. package/dist/auth/jwt.d.ts +133 -0
  10. package/dist/auth/jwt.d.ts.map +1 -0
  11. package/dist/auth/jwt.js +147 -0
  12. package/dist/auth/jwt.js.map +1 -0
  13. package/dist/auth/middleware.d.ts +101 -0
  14. package/dist/auth/middleware.d.ts.map +1 -0
  15. package/dist/auth/middleware.js +161 -0
  16. package/dist/auth/middleware.js.map +1 -0
  17. package/dist/auth/passwordPolicy.d.ts +72 -0
  18. package/dist/auth/passwordPolicy.d.ts.map +1 -0
  19. package/dist/auth/passwordPolicy.js +88 -0
  20. package/dist/auth/passwordPolicy.js.map +1 -0
  21. package/dist/auth/roleRules.d.ts +200 -0
  22. package/dist/auth/roleRules.d.ts.map +1 -0
  23. package/dist/auth/roleRules.js +365 -0
  24. package/dist/auth/roleRules.js.map +1 -0
  25. package/dist/auth/userView.d.ts +55 -0
  26. package/dist/auth/userView.d.ts.map +1 -0
  27. package/dist/auth/userView.js +77 -0
  28. package/dist/auth/userView.js.map +1 -0
  29. package/dist/bootstrap.d.ts +104 -0
  30. package/dist/bootstrap.d.ts.map +1 -0
  31. package/dist/bootstrap.js +160 -0
  32. package/dist/bootstrap.js.map +1 -0
  33. package/dist/db/connection.d.ts +39 -0
  34. package/dist/db/connection.d.ts.map +1 -0
  35. package/dist/db/connection.js +62 -0
  36. package/dist/db/connection.js.map +1 -0
  37. package/dist/db/migrate.d.ts +31 -0
  38. package/dist/db/migrate.d.ts.map +1 -0
  39. package/dist/db/migrate.js +197 -0
  40. package/dist/db/migrate.js.map +1 -0
  41. package/dist/db/schema.d.ts +1902 -0
  42. package/dist/db/schema.d.ts.map +1 -0
  43. package/dist/db/schema.js +662 -0
  44. package/dist/db/schema.js.map +1 -0
  45. package/dist/db/seed.d.ts +92 -0
  46. package/dist/db/seed.d.ts.map +1 -0
  47. package/dist/db/seed.js +160 -0
  48. package/dist/db/seed.js.map +1 -0
  49. package/dist/handlers/fetch.d.ts +89 -0
  50. package/dist/handlers/fetch.d.ts.map +1 -0
  51. package/dist/handlers/fetch.js +173 -0
  52. package/dist/handlers/fetch.js.map +1 -0
  53. package/dist/handlers/nextjs.d.ts +40 -0
  54. package/dist/handlers/nextjs.d.ts.map +1 -0
  55. package/dist/handlers/nextjs.js +42 -0
  56. package/dist/handlers/nextjs.js.map +1 -0
  57. package/dist/handlers/responses.d.ts +46 -0
  58. package/dist/handlers/responses.d.ts.map +1 -0
  59. package/dist/handlers/responses.js +74 -0
  60. package/dist/handlers/responses.js.map +1 -0
  61. package/dist/handlers/routeTable.d.ts +21 -0
  62. package/dist/handlers/routeTable.d.ts.map +1 -0
  63. package/dist/handlers/routeTable.js +133 -0
  64. package/dist/handlers/routeTable.js.map +1 -0
  65. package/dist/handlers/router.d.ts +37 -0
  66. package/dist/handlers/router.d.ts.map +1 -0
  67. package/dist/handlers/router.js +82 -0
  68. package/dist/handlers/router.js.map +1 -0
  69. package/dist/handlers/sse.d.ts +38 -0
  70. package/dist/handlers/sse.d.ts.map +1 -0
  71. package/dist/handlers/sse.js +52 -0
  72. package/dist/handlers/sse.js.map +1 -0
  73. package/dist/handlers/types.d.ts +96 -0
  74. package/dist/handlers/types.d.ts.map +1 -0
  75. package/dist/handlers/types.js +14 -0
  76. package/dist/handlers/types.js.map +1 -0
  77. package/dist/index.d.ts +25 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +24 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/letta/client.d.ts +355 -0
  82. package/dist/letta/client.d.ts.map +1 -0
  83. package/dist/letta/client.js +529 -0
  84. package/dist/letta/client.js.map +1 -0
  85. package/dist/letta/eventTranslator.d.ts +166 -0
  86. package/dist/letta/eventTranslator.d.ts.map +1 -0
  87. package/dist/letta/eventTranslator.js +532 -0
  88. package/dist/letta/eventTranslator.js.map +1 -0
  89. package/dist/letta/index.d.ts +13 -0
  90. package/dist/letta/index.d.ts.map +1 -0
  91. package/dist/letta/index.js +12 -0
  92. package/dist/letta/index.js.map +1 -0
  93. package/dist/letta/modelSettings.d.ts +176 -0
  94. package/dist/letta/modelSettings.d.ts.map +1 -0
  95. package/dist/letta/modelSettings.js +314 -0
  96. package/dist/letta/modelSettings.js.map +1 -0
  97. package/dist/letta/promptWrapping.d.ts +141 -0
  98. package/dist/letta/promptWrapping.d.ts.map +1 -0
  99. package/dist/letta/promptWrapping.js +213 -0
  100. package/dist/letta/promptWrapping.js.map +1 -0
  101. package/dist/letta/sandbox.d.ts +100 -0
  102. package/dist/letta/sandbox.d.ts.map +1 -0
  103. package/dist/letta/sandbox.js +151 -0
  104. package/dist/letta/sandbox.js.map +1 -0
  105. package/dist/letta/settingsFileSeed.d.ts +146 -0
  106. package/dist/letta/settingsFileSeed.d.ts.map +1 -0
  107. package/dist/letta/settingsFileSeed.js +177 -0
  108. package/dist/letta/settingsFileSeed.js.map +1 -0
  109. package/dist/letta/subprocessThrottle.d.ts +151 -0
  110. package/dist/letta/subprocessThrottle.d.ts.map +1 -0
  111. package/dist/letta/subprocessThrottle.js +195 -0
  112. package/dist/letta/subprocessThrottle.js.map +1 -0
  113. package/dist/letta/tpmRecovery.d.ts +202 -0
  114. package/dist/letta/tpmRecovery.d.ts.map +1 -0
  115. package/dist/letta/tpmRecovery.js +314 -0
  116. package/dist/letta/tpmRecovery.js.map +1 -0
  117. package/dist/letta/types.d.ts +54 -0
  118. package/dist/letta/types.d.ts.map +1 -0
  119. package/dist/letta/types.js +16 -0
  120. package/dist/letta/types.js.map +1 -0
  121. package/dist/observability/logger.d.ts +48 -0
  122. package/dist/observability/logger.d.ts.map +1 -0
  123. package/dist/observability/logger.js +81 -0
  124. package/dist/observability/logger.js.map +1 -0
  125. package/dist/observability/metrics.d.ts +53 -0
  126. package/dist/observability/metrics.d.ts.map +1 -0
  127. package/dist/observability/metrics.js +92 -0
  128. package/dist/observability/metrics.js.map +1 -0
  129. package/dist/routes/admin/agents.d.ts +63 -0
  130. package/dist/routes/admin/agents.d.ts.map +1 -0
  131. package/dist/routes/admin/agents.js +461 -0
  132. package/dist/routes/admin/agents.js.map +1 -0
  133. package/dist/routes/admin/rotatingMessages.d.ts +51 -0
  134. package/dist/routes/admin/rotatingMessages.d.ts.map +1 -0
  135. package/dist/routes/admin/rotatingMessages.js +292 -0
  136. package/dist/routes/admin/rotatingMessages.js.map +1 -0
  137. package/dist/routes/admin/sessions.d.ts +61 -0
  138. package/dist/routes/admin/sessions.d.ts.map +1 -0
  139. package/dist/routes/admin/sessions.js +232 -0
  140. package/dist/routes/admin/sessions.js.map +1 -0
  141. package/dist/routes/admin/settings.d.ts +110 -0
  142. package/dist/routes/admin/settings.d.ts.map +1 -0
  143. package/dist/routes/admin/settings.js +401 -0
  144. package/dist/routes/admin/settings.js.map +1 -0
  145. package/dist/routes/admin/users.d.ts +115 -0
  146. package/dist/routes/admin/users.d.ts.map +1 -0
  147. package/dist/routes/admin/users.js +374 -0
  148. package/dist/routes/admin/users.js.map +1 -0
  149. package/dist/routes/agents.d.ts +30 -0
  150. package/dist/routes/agents.d.ts.map +1 -0
  151. package/dist/routes/agents.js +55 -0
  152. package/dist/routes/agents.js.map +1 -0
  153. package/dist/routes/auth.d.ts +121 -0
  154. package/dist/routes/auth.d.ts.map +1 -0
  155. package/dist/routes/auth.js +422 -0
  156. package/dist/routes/auth.js.map +1 -0
  157. package/dist/routes/chat.d.ts +97 -0
  158. package/dist/routes/chat.d.ts.map +1 -0
  159. package/dist/routes/chat.js +484 -0
  160. package/dist/routes/chat.js.map +1 -0
  161. package/dist/routes/featureToggles.d.ts +34 -0
  162. package/dist/routes/featureToggles.d.ts.map +1 -0
  163. package/dist/routes/featureToggles.js +47 -0
  164. package/dist/routes/featureToggles.js.map +1 -0
  165. package/dist/routes/health.d.ts +24 -0
  166. package/dist/routes/health.d.ts.map +1 -0
  167. package/dist/routes/health.js +37 -0
  168. package/dist/routes/health.js.map +1 -0
  169. package/dist/routes/metrics.d.ts +35 -0
  170. package/dist/routes/metrics.d.ts.map +1 -0
  171. package/dist/routes/metrics.js +106 -0
  172. package/dist/routes/metrics.js.map +1 -0
  173. package/dist/routes/sessions.d.ts +161 -0
  174. package/dist/routes/sessions.d.ts.map +1 -0
  175. package/dist/routes/sessions.js +1501 -0
  176. package/dist/routes/sessions.js.map +1 -0
  177. package/dist/routes/stubs.d.ts +25 -0
  178. package/dist/routes/stubs.d.ts.map +1 -0
  179. package/dist/routes/stubs.js +25 -0
  180. package/dist/routes/stubs.js.map +1 -0
  181. package/dist/state/cron.d.ts +58 -0
  182. package/dist/state/cron.d.ts.map +1 -0
  183. package/dist/state/cron.js +160 -0
  184. package/dist/state/cron.js.map +1 -0
  185. package/dist/state/rotationRunner.d.ts +96 -0
  186. package/dist/state/rotationRunner.d.ts.map +1 -0
  187. package/dist/state/rotationRunner.js +246 -0
  188. package/dist/state/rotationRunner.js.map +1 -0
  189. package/dist/state/rotationScheduler.d.ts +76 -0
  190. package/dist/state/rotationScheduler.d.ts.map +1 -0
  191. package/dist/state/rotationScheduler.js +124 -0
  192. package/dist/state/rotationScheduler.js.map +1 -0
  193. package/dist/state/turnRegistry.d.ts +95 -0
  194. package/dist/state/turnRegistry.d.ts.map +1 -0
  195. package/dist/state/turnRegistry.js +149 -0
  196. package/dist/state/turnRegistry.js.map +1 -0
  197. package/dist/state/turnRunner.d.ts +134 -0
  198. package/dist/state/turnRunner.d.ts.map +1 -0
  199. package/dist/state/turnRunner.js +476 -0
  200. package/dist/state/turnRunner.js.map +1 -0
  201. package/package.json +47 -0
  202. package/src/auth/argon2.ts +152 -0
  203. package/src/auth/encryption.ts +110 -0
  204. package/src/auth/jwt.ts +217 -0
  205. package/src/auth/middleware.ts +250 -0
  206. package/src/auth/passwordPolicy.ts +99 -0
  207. package/src/auth/roleRules.ts +497 -0
  208. package/src/auth/userView.ts +88 -0
  209. package/src/bootstrap.ts +254 -0
  210. package/src/db/connection.ts +75 -0
  211. package/src/db/migrate.ts +212 -0
  212. package/src/db/schema.ts +750 -0
  213. package/src/db/seed.ts +236 -0
  214. package/src/handlers/fetch.ts +251 -0
  215. package/src/handlers/nextjs.ts +49 -0
  216. package/src/handlers/responses.ts +95 -0
  217. package/src/handlers/routeTable.ts +188 -0
  218. package/src/handlers/router.ts +96 -0
  219. package/src/handlers/sse.ts +55 -0
  220. package/src/handlers/types.ts +104 -0
  221. package/src/index.ts +60 -0
  222. package/src/letta/client.ts +860 -0
  223. package/src/letta/eventTranslator.ts +667 -0
  224. package/src/letta/index.ts +46 -0
  225. package/src/letta/lettaCliShim.mjs +40 -0
  226. package/src/letta/sandbox.ts +167 -0
  227. package/src/letta/settingsFileSeed.ts +254 -0
  228. package/src/letta/subprocessThrottle.ts +251 -0
  229. package/src/letta/types.ts +65 -0
  230. package/src/observability/logger.ts +103 -0
  231. package/src/observability/metrics.ts +114 -0
  232. package/src/routes/admin/agents.ts +574 -0
  233. package/src/routes/admin/rotatingMessages.ts +351 -0
  234. package/src/routes/admin/sessions.ts +283 -0
  235. package/src/routes/admin/settings.ts +463 -0
  236. package/src/routes/admin/users.ts +455 -0
  237. package/src/routes/agents.ts +66 -0
  238. package/src/routes/auth.ts +513 -0
  239. package/src/routes/chat.ts +534 -0
  240. package/src/routes/featureToggles.ts +52 -0
  241. package/src/routes/health.ts +44 -0
  242. package/src/routes/metrics.ts +111 -0
  243. package/src/routes/sessions.ts +1878 -0
  244. package/src/state/cron.ts +179 -0
  245. package/src/state/rotationRunner.ts +326 -0
  246. package/src/state/rotationScheduler.ts +164 -0
  247. package/src/state/turnRegistry.ts +210 -0
  248. package/src/state/turnRunner.ts +588 -0
@@ -0,0 +1,461 @@
1
+ /**
2
+ * `/admin/agents/*` — power_user + admin + super_admin catalog management.
3
+ *
4
+ * Three endpoints (no DELETE by design — agents are archive-only in v1):
5
+ *
6
+ * - `GET /admin/agents` — every agent in the deployment, including archived
7
+ * ones. Lets the admin UI render the catalog with the archived rows
8
+ * filtered in/out via the `[☐ Show archived]` toggle ([06-admin-agents.md]).
9
+ * - `POST /admin/agents` — body `{ display_name, letta_agent_id, tpm_recovery_enabled?, max_recovery_attempts?, default_retry_after_seconds? }`.
10
+ * Validates `letta_agent_id` against Letta (Step 5 wires the real check;
11
+ * Step 4 stubs it through with a `TODO`).
12
+ * - `PATCH /admin/agents/:id` — body `{ display_name?, letta_agent_id?, tpm_recovery_enabled?, max_recovery_attempts?, default_retry_after_seconds?, is_archived? }`.
13
+ * Archive / unarchive is just `{ is_archived: true | false }`. All fields
14
+ * optional; empty patch is a 200 no-op.
15
+ *
16
+ * **No DELETE.** Per §6.3.4 / §11, agents cannot be hard-deleted in v1 —
17
+ * archiving keeps existing sessions readable + writable while preventing new
18
+ * sessions from starting against the agent. Step 12's UI exposes only
19
+ * archive/unarchive; this server enforces the same.
20
+ *
21
+ * @see SPECIFICATIONS.md §6.3.4 — admin agents endpoints
22
+ * @see SPECIFICATIONS.md §6.5 — TPM recovery defaults
23
+ * @see ui-sketches/06-admin-agents.md
24
+ */
25
+ import { randomUUID } from "node:crypto";
26
+ import { asc, eq } from "drizzle-orm";
27
+ import { z } from "zod";
28
+ import { protectedRoute } from "../../auth/middleware.js";
29
+ import { agent, rotatingMessage } from "../../db/schema.js";
30
+ import { errorResponse, jsonResponse, notFound, validationError, } from "../../handlers/responses.js";
31
+ import { seedDirExists, validateSeedName } from "../../letta/sandbox.js";
32
+ import { seedAgentToolsetNone } from "../../letta/settingsFileSeed.js";
33
+ /* ────────────────────────────────────────────────────────────────────────── */
34
+ /* Helpers */
35
+ /* ────────────────────────────────────────────────────────────────────────── */
36
+ /**
37
+ * Wire-format view of an `agent` row for admin consumption. Differs from the
38
+ * user-facing `/agents` response: this one includes `letta_agent_id` (admins
39
+ * need to see + edit it), the TPM-recovery fields, and the archive flag.
40
+ *
41
+ * @param row - the agent row.
42
+ * @returns the JSON-safe response shape.
43
+ */
44
+ function adminAgentView(row) {
45
+ return {
46
+ id: row.id,
47
+ display_name: row.displayName,
48
+ letta_agent_id: row.lettaAgentId,
49
+ tpm_recovery_enabled: row.tpmRecoveryEnabled,
50
+ max_recovery_attempts: row.maxRecoveryAttempts,
51
+ default_retry_after_seconds: row.defaultRetryAfterSeconds,
52
+ stream_idle_timeout_seconds: row.streamIdleTimeoutSeconds,
53
+ sandbox_seed_name: row.sandboxSeedName,
54
+ fork_source_conversation_id: row.forkSourceConversationId,
55
+ is_archived: row.isArchived,
56
+ created_at: row.createdAt,
57
+ updated_at: row.updatedAt,
58
+ };
59
+ }
60
+ /** ISO-8601 timestamp with millisecond precision. */
61
+ function nowIso() {
62
+ return new Date().toISOString();
63
+ }
64
+ /**
65
+ * Validates that `letta_agent_id` references a real Letta-side agent.
66
+ *
67
+ * Calls `ctx.lettaClient.retrieveAgent(...)` (which wraps
68
+ * `client.agents.retrieve` per §6.4 / §6.4.1) and returns `false` when the
69
+ * SDK reports the agent doesn't exist. The handler turns the `false` into
70
+ * a 400 `letta_agent_not_found` so the React modal can surface the error
71
+ * inline on the field ([06-admin-agents.md]). Any non-404 SDK error
72
+ * propagates so the route catch-all returns a 500.
73
+ *
74
+ * @param ctx - server context (carries the Letta client).
75
+ * @param lettaAgentId - candidate id.
76
+ * @returns `true` if the id resolves to a live Letta agent.
77
+ *
78
+ * @see SPECIFICATIONS.md §6.4.1 — first-class SDK surfaces
79
+ */
80
+ async function validateLettaAgentId(ctx, lettaAgentId) {
81
+ const found = await ctx.lettaClient.retrieveAgent(lettaAgentId);
82
+ return found !== null;
83
+ }
84
+ /**
85
+ * Validates the (optional) `sandbox_seed_name` and (optional)
86
+ * `fork_source_conversation_id` against the filesystem + Letta. Pure for
87
+ * the success / regex-fail cases; talks to Letta only when a fork source
88
+ * was supplied. The caller maps the outcome to one of:
89
+ * - `400 seed_name_invalid`
90
+ * - `400 seed_dir_not_found`
91
+ * - `400 fork_source_not_found`
92
+ * - `400 fork_source_agent_mismatch`
93
+ *
94
+ * `null` / `undefined` values short-circuit the corresponding check —
95
+ * those fields are explicitly opt-in.
96
+ *
97
+ * @param ctx - server context (filesDir + lettaClient).
98
+ * @param fields - candidate values.
99
+ * @returns the resolved outcome.
100
+ */
101
+ async function validateSpecializedFields(ctx, fields) {
102
+ if (fields.sandboxSeedName !== undefined &&
103
+ fields.sandboxSeedName !== null &&
104
+ fields.sandboxSeedName.length > 0) {
105
+ const nameValidation = validateSeedName(fields.sandboxSeedName);
106
+ if (nameValidation !== "ok") {
107
+ return { kind: "seed_name_invalid" };
108
+ }
109
+ if (!seedDirExists(ctx.filesDir, fields.sandboxSeedName)) {
110
+ return { kind: "seed_dir_not_found" };
111
+ }
112
+ }
113
+ if (fields.forkSourceConversationId !== undefined &&
114
+ fields.forkSourceConversationId !== null &&
115
+ fields.forkSourceConversationId.length > 0) {
116
+ const ref = fields.forkSourceConversationId;
117
+ if (ref.startsWith("rmsg-")) {
118
+ // The base is a rotating starting message. Validate against the
119
+ // `rotating_message` catalog instead of Letta — its prepared
120
+ // conversation may not exist yet (no rotation has run), which is fine
121
+ // at write time; `POST /sessions` handles the "not ready" case.
122
+ const rmsg = ctx.db
123
+ .select()
124
+ .from(rotatingMessage)
125
+ .where(eq(rotatingMessage.id, ref))
126
+ .get();
127
+ if (!rmsg || rmsg.isArchived) {
128
+ return { kind: "rotating_message_not_found" };
129
+ }
130
+ if (rmsg.lettaAgentId !== fields.expectedLettaAgentId) {
131
+ return {
132
+ kind: "rotating_message_agent_mismatch",
133
+ expectedAgentId: fields.expectedLettaAgentId,
134
+ actualAgentId: rmsg.lettaAgentId,
135
+ };
136
+ }
137
+ }
138
+ else {
139
+ const conv = await ctx.lettaClient.retrieveConversation(ref);
140
+ if (!conv) {
141
+ return { kind: "fork_source_not_found" };
142
+ }
143
+ if (conv.agentId !== fields.expectedLettaAgentId) {
144
+ return {
145
+ kind: "fork_source_agent_mismatch",
146
+ expectedAgentId: fields.expectedLettaAgentId,
147
+ actualAgentId: conv.agentId,
148
+ };
149
+ }
150
+ }
151
+ }
152
+ return { kind: "ok" };
153
+ }
154
+ /**
155
+ * Maps a `SpecializedFieldsValidation` failure to the canonical
156
+ * `errorResponse(400, code, message)`. Keeps the create + patch handlers
157
+ * tidy.
158
+ */
159
+ function respondSpecializedFailure(failure) {
160
+ switch (failure.kind) {
161
+ case "seed_name_invalid":
162
+ return errorResponse(400, "seed_name_invalid", "sandbox_seed_name must match ^[a-zA-Z0-9_-]+$ (no slashes, no dots).");
163
+ case "seed_dir_not_found":
164
+ return errorResponse(400, "seed_dir_not_found", "No seed folder with that name was found under AI_HUB_FILES_DIR/seeds/.");
165
+ case "fork_source_not_found":
166
+ return errorResponse(400, "fork_source_not_found", "No Letta conversation with that id was found.");
167
+ case "fork_source_agent_mismatch":
168
+ return errorResponse(400, "fork_source_agent_mismatch", `The fork source belongs to Letta agent ${failure.actualAgentId}, ` +
169
+ `but this ai-hub agent points at ${failure.expectedAgentId}. Letta's fork ` +
170
+ `API requires the new conversation to share the source's agent.`);
171
+ case "rotating_message_not_found":
172
+ return errorResponse(400, "rotating_message_not_found", "No active rotating starting message with that id was found.");
173
+ case "rotating_message_agent_mismatch":
174
+ return errorResponse(400, "rotating_message_agent_mismatch", `The rotating starting message runs against Letta agent ${failure.actualAgentId}, ` +
175
+ `but this ai-hub agent points at ${failure.expectedAgentId}. A rotating message ` +
176
+ `can only back agents that share its Letta agent (Letta forks must be same-agent).`);
177
+ }
178
+ }
179
+ /**
180
+ * Best-effort seed of `~/.letta/settings.json` with `{toolset: "none"}` for
181
+ * the given Letta agent id. Idempotent. Logs the outcome but **does not
182
+ * throw**: if seeding fails (read-only home dir, permission denied, …) the
183
+ * agent creation should still succeed — the toolset suppression is then
184
+ * a runtime concern the operator notices when chats start producing weird
185
+ * file-edit tool calls. The intent here is hands-off automation, not a
186
+ * required precondition.
187
+ *
188
+ * @param ctx - server context (for logging).
189
+ * @param lettaAgentId - id to seed the toolset preference for.
190
+ *
191
+ * @see ../../letta/settingsFileSeed.ts
192
+ */
193
+ function seedToolsetNoneBestEffort(ctx, lettaAgentId) {
194
+ try {
195
+ seedAgentToolsetNone({
196
+ agentId: lettaAgentId,
197
+ lettaBaseUrl: process.env.LETTA_BASE_URL,
198
+ logger: ctx.logger,
199
+ });
200
+ }
201
+ catch (err) {
202
+ ctx.logger.warn({
203
+ agent_id: lettaAgentId,
204
+ err: err instanceof Error ? err.message : String(err),
205
+ event: "letta_code_settings_seed_failed",
206
+ }, "Failed to seed Letta Code toolset preference; continuing anyway.");
207
+ }
208
+ }
209
+ /* ────────────────────────────────────────────────────────────────────────── */
210
+ /* GET /admin/agents */
211
+ /* ────────────────────────────────────────────────────────────────────────── */
212
+ /**
213
+ * `GET /admin/agents` — every agent in the deployment, archived rows
214
+ * included. The admin UI filters them client-side via the
215
+ * `[☐ Show archived]` toggle ([06-admin-agents.md]).
216
+ *
217
+ * Sorted by `display_name` for stable rendering.
218
+ *
219
+ * Available to `power_user`+ per the §8.2 matrix.
220
+ *
221
+ * @see SPECIFICATIONS.md §6.3.4
222
+ */
223
+ export const handleListAgents = protectedRoute({ requiredRole: "power_user" }, async (_req, ctx) => {
224
+ const rows = ctx.db.select().from(agent).orderBy(asc(agent.displayName)).all();
225
+ return jsonResponse(200, { agents: rows.map(adminAgentView) });
226
+ });
227
+ /* ────────────────────────────────────────────────────────────────────────── */
228
+ /* POST /admin/agents */
229
+ /* ────────────────────────────────────────────────────────────────────────── */
230
+ /** Body for `POST /admin/agents`. */
231
+ const createAgentSchema = z
232
+ .object({
233
+ display_name: z.string().min(1).max(255),
234
+ letta_agent_id: z.string().min(1).max(255),
235
+ tpm_recovery_enabled: z.boolean().optional(),
236
+ max_recovery_attempts: z.number().int().min(0).max(10).optional(),
237
+ default_retry_after_seconds: z.number().int().min(1).max(3600).optional(),
238
+ /** Per-agent inter-event idle watchdog (seconds). Default 60. */
239
+ stream_idle_timeout_seconds: z.number().int().min(5).max(3600).optional(),
240
+ /**
241
+ * Step-12.5 — name of the seed folder under `{AI_HUB_FILES_DIR}/seeds/`.
242
+ * `null` or `""` clears the field; non-null strings are validated
243
+ * against the safe-name regex + existence on disk.
244
+ */
245
+ sandbox_seed_name: z.string().max(128).nullable().optional(),
246
+ /**
247
+ * Step-12.5 — Letta conversation id (`conv-…`) to fork from at session
248
+ * create. `null` or `""` clears the field; non-null strings are
249
+ * validated via `client.conversations.retrieve` + agent-id match.
250
+ */
251
+ fork_source_conversation_id: z.string().max(255).nullable().optional(),
252
+ })
253
+ .strict();
254
+ /**
255
+ * `POST /admin/agents` — create a new ai-hub agent (catalog entry).
256
+ *
257
+ * Validates the Letta-side id via `validateLettaAgentId` (Step 4 stub —
258
+ * always passes; Step 5 wires the real check against the Letta SDK). On a
259
+ * Letta-id miss returns 400 `letta_agent_not_found` with the offending field
260
+ * named, so the React modal can surface the error inline on the field
261
+ * ([06-admin-agents.md]).
262
+ *
263
+ * @see SPECIFICATIONS.md §6.3.4
264
+ * @see ui-sketches/06-admin-agents.md
265
+ */
266
+ export const handleCreateAgent = protectedRoute({ requiredRole: "power_user" }, async (req, ctx, auth) => {
267
+ let raw;
268
+ try {
269
+ raw = await req.json();
270
+ }
271
+ catch {
272
+ return validationError("Request body must be valid JSON.");
273
+ }
274
+ const parsed = createAgentSchema.safeParse(raw);
275
+ if (!parsed.success) {
276
+ return validationError(parsed.error.issues[0]?.message ?? "Invalid request body.");
277
+ }
278
+ const body = parsed.data;
279
+ const ok = await validateLettaAgentId(ctx, body.letta_agent_id);
280
+ if (!ok) {
281
+ return errorResponse(400, "letta_agent_not_found", "No Letta agent with that id was found.");
282
+ }
283
+ // Step-12.5 — validate the optional sandbox + fork fields. Runs AFTER
284
+ // the letta_agent_id check so the agent-mismatch error has a clear
285
+ // expected value to compare against (and so an admin entering a bad
286
+ // letta_agent_id only sees that error, not the cascading 400 from
287
+ // the fork validator).
288
+ const specialized = await validateSpecializedFields(ctx, {
289
+ sandboxSeedName: body.sandbox_seed_name,
290
+ forkSourceConversationId: body.fork_source_conversation_id,
291
+ expectedLettaAgentId: body.letta_agent_id,
292
+ });
293
+ if (specialized.kind !== "ok") {
294
+ return respondSpecializedFailure(specialized);
295
+ }
296
+ const id = randomUUID();
297
+ // Empty strings normalize to null so the storage matches the
298
+ // "field cleared" semantics the React modal sends.
299
+ const normalizedSeedName = body.sandbox_seed_name && body.sandbox_seed_name.length > 0
300
+ ? body.sandbox_seed_name
301
+ : null;
302
+ const normalizedForkSrc = body.fork_source_conversation_id &&
303
+ body.fork_source_conversation_id.length > 0
304
+ ? body.fork_source_conversation_id
305
+ : null;
306
+ ctx.db
307
+ .insert(agent)
308
+ .values({
309
+ id,
310
+ displayName: body.display_name,
311
+ lettaAgentId: body.letta_agent_id,
312
+ tpmRecoveryEnabled: body.tpm_recovery_enabled ?? false,
313
+ maxRecoveryAttempts: body.max_recovery_attempts ?? 2,
314
+ defaultRetryAfterSeconds: body.default_retry_after_seconds ?? 65,
315
+ streamIdleTimeoutSeconds: body.stream_idle_timeout_seconds ?? 60,
316
+ sandboxSeedName: normalizedSeedName,
317
+ forkSourceConversationId: normalizedForkSrc,
318
+ isArchived: false,
319
+ })
320
+ .run();
321
+ const row = ctx.db.select().from(agent).where(eq(agent.id, id)).get();
322
+ if (!row)
323
+ throw new Error("Just-inserted agent vanished.");
324
+ // Seed `~/.letta/settings.json` so Letta Code's bundled CLI suppresses
325
+ // its auto-toolset (Read/Write/Bash/etc.) when chat sessions spawn
326
+ // against this agent. ai-hub wants only the tools the ADE admin
327
+ // explicitly attached. Best-effort: a write failure logs + continues.
328
+ seedToolsetNoneBestEffort(ctx, row.lettaAgentId);
329
+ ctx.logger.info({
330
+ actor_id: auth.user.id,
331
+ agent_id: row.id,
332
+ letta_agent_id: row.lettaAgentId,
333
+ event: "admin_agent_created",
334
+ }, "Admin created an ai-hub agent.");
335
+ return jsonResponse(201, adminAgentView(row));
336
+ });
337
+ /* ────────────────────────────────────────────────────────────────────────── */
338
+ /* PATCH /admin/agents/:id */
339
+ /* ────────────────────────────────────────────────────────────────────────── */
340
+ /** Body for `PATCH /admin/agents/:id`. All fields optional; empty patch is a no-op. */
341
+ const patchAgentSchema = z
342
+ .object({
343
+ display_name: z.string().min(1).max(255).optional(),
344
+ letta_agent_id: z.string().min(1).max(255).optional(),
345
+ tpm_recovery_enabled: z.boolean().optional(),
346
+ max_recovery_attempts: z.number().int().min(0).max(10).optional(),
347
+ default_retry_after_seconds: z.number().int().min(1).max(3600).optional(),
348
+ stream_idle_timeout_seconds: z.number().int().min(5).max(3600).optional(),
349
+ is_archived: z.boolean().optional(),
350
+ /** Step-12.5 — nullable to support explicit clear. */
351
+ sandbox_seed_name: z.string().max(128).nullable().optional(),
352
+ /** Step-12.5 — nullable to support explicit clear. */
353
+ fork_source_conversation_id: z.string().max(255).nullable().optional(),
354
+ })
355
+ .strict();
356
+ /**
357
+ * `PATCH /admin/agents/:id` — partial update.
358
+ *
359
+ * If `letta_agent_id` is in the patch, the same Letta-side validation runs
360
+ * as on create. `is_archived` is the archive / unarchive toggle (no separate
361
+ * endpoint). All fields optional; an empty patch (`{}`) is a 200 no-op that
362
+ * echoes the row unchanged.
363
+ *
364
+ * @see SPECIFICATIONS.md §6.3.4
365
+ * @see ui-sketches/06-admin-agents.md
366
+ */
367
+ export const handlePatchAgent = protectedRoute({ requiredRole: "power_user" }, async (req, ctx, auth, params) => {
368
+ const id = params.id;
369
+ if (!id)
370
+ return notFound();
371
+ let raw;
372
+ try {
373
+ raw = await req.json();
374
+ }
375
+ catch {
376
+ return validationError("Request body must be valid JSON.");
377
+ }
378
+ const parsed = patchAgentSchema.safeParse(raw);
379
+ if (!parsed.success) {
380
+ return validationError(parsed.error.issues[0]?.message ?? "Invalid request body.");
381
+ }
382
+ const body = parsed.data;
383
+ const existing = ctx.db.select().from(agent).where(eq(agent.id, id)).get();
384
+ if (!existing)
385
+ return notFound("Agent not found.");
386
+ if (body.letta_agent_id !== undefined && body.letta_agent_id !== existing.lettaAgentId) {
387
+ const ok = await validateLettaAgentId(ctx, body.letta_agent_id);
388
+ if (!ok) {
389
+ return errorResponse(400, "letta_agent_not_found", "No Letta agent with that id was found.");
390
+ }
391
+ }
392
+ // Step-12.5 — validate the specialized fields against the
393
+ // *effective* letta_agent_id (the patched value when supplied,
394
+ // otherwise the existing one). Only validates fields touched by
395
+ // this PATCH; unchanged fields are skipped.
396
+ const effectiveLettaAgentId = body.letta_agent_id ?? existing.lettaAgentId;
397
+ const seedTouched = body.sandbox_seed_name !== undefined;
398
+ const forkTouched = body.fork_source_conversation_id !== undefined;
399
+ if (seedTouched || forkTouched) {
400
+ const specialized = await validateSpecializedFields(ctx, {
401
+ sandboxSeedName: seedTouched ? body.sandbox_seed_name : null,
402
+ forkSourceConversationId: forkTouched
403
+ ? body.fork_source_conversation_id
404
+ : null,
405
+ expectedLettaAgentId: effectiveLettaAgentId,
406
+ });
407
+ if (specialized.kind !== "ok") {
408
+ return respondSpecializedFailure(specialized);
409
+ }
410
+ }
411
+ const update = { updatedAt: nowIso() };
412
+ if (body.display_name !== undefined)
413
+ update.displayName = body.display_name;
414
+ if (body.letta_agent_id !== undefined)
415
+ update.lettaAgentId = body.letta_agent_id;
416
+ if (body.tpm_recovery_enabled !== undefined)
417
+ update.tpmRecoveryEnabled = body.tpm_recovery_enabled;
418
+ if (body.max_recovery_attempts !== undefined)
419
+ update.maxRecoveryAttempts = body.max_recovery_attempts;
420
+ if (body.default_retry_after_seconds !== undefined)
421
+ update.defaultRetryAfterSeconds = body.default_retry_after_seconds;
422
+ if (body.stream_idle_timeout_seconds !== undefined)
423
+ update.streamIdleTimeoutSeconds = body.stream_idle_timeout_seconds;
424
+ if (body.is_archived !== undefined)
425
+ update.isArchived = body.is_archived;
426
+ // Empty strings + null both normalize to null (consistent with create).
427
+ if (body.sandbox_seed_name !== undefined) {
428
+ update.sandboxSeedName =
429
+ body.sandbox_seed_name && body.sandbox_seed_name.length > 0
430
+ ? body.sandbox_seed_name
431
+ : null;
432
+ }
433
+ if (body.fork_source_conversation_id !== undefined) {
434
+ update.forkSourceConversationId =
435
+ body.fork_source_conversation_id &&
436
+ body.fork_source_conversation_id.length > 0
437
+ ? body.fork_source_conversation_id
438
+ : null;
439
+ }
440
+ ctx.db.update(agent).set(update).where(eq(agent.id, id)).run();
441
+ const fresh = ctx.db.select().from(agent).where(eq(agent.id, id)).get();
442
+ if (!fresh)
443
+ return notFound();
444
+ // If the patch re-pointed the ai-hub agent at a different Letta agent,
445
+ // seed the new agent's toolset preference too. (We deliberately do NOT
446
+ // remove the old agent's seeded record — it may still be backing live
447
+ // sessions per the §11 schema note: existing sessions keep their
448
+ // original `letta_conversation_id` against the original Letta agent.)
449
+ if (body.letta_agent_id !== undefined &&
450
+ body.letta_agent_id !== existing.lettaAgentId) {
451
+ seedToolsetNoneBestEffort(ctx, body.letta_agent_id);
452
+ }
453
+ ctx.logger.info({
454
+ actor_id: auth.user.id,
455
+ agent_id: id,
456
+ patch_keys: Object.keys(body),
457
+ event: "admin_agent_patched",
458
+ }, "Admin updated an ai-hub agent.");
459
+ return jsonResponse(200, adminAgentView(fresh));
460
+ });
461
+ //# sourceMappingURL=agents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents.js","sourceRoot":"","sources":["../../../src/routes/admin/agents.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * `/admin/rotating-messages/*` — power_user + admin + super_admin management of
3
+ * **rotating starting messages** (auto-refreshing, forkable base conversations).
4
+ *
5
+ * Mirrors `/admin/agents` conventions (Zod `.strict()`, `power_user`+ gate,
6
+ * snake_case wire view, archive-only — no DELETE):
7
+ *
8
+ * - `GET /admin/rotating-messages` — every row, archived included.
9
+ * - `POST /admin/rotating-messages` — create; validates `letta_agent_id`
10
+ * against Letta + the schedule shape. Returns the row including its
11
+ * `rmsg-…` `id` — the identifier callers paste into an agent's Base
12
+ * conversation field. `next_rotation_at` is set to now so the first
13
+ * rotation fires on the next scheduler tick.
14
+ * - `PATCH /admin/rotating-messages/:id` — partial update; recomputes
15
+ * `next_rotation_at` when the schedule changes; re-validates the agent when
16
+ * it changes; `is_archived` toggle.
17
+ * - `POST /admin/rotating-messages/:id/rotate-now` — runs a rotation inline
18
+ * (preview / on-demand refresh) and returns the refreshed row.
19
+ *
20
+ * @see docs/server/rotating-messages.md
21
+ */
22
+ import type { RouteHandler } from "../../handlers/types.js";
23
+ /**
24
+ * `GET /admin/rotating-messages` — every rotating message, archived included.
25
+ * Sorted by `display_name`. `power_user`+ per the §8.2 matrix.
26
+ */
27
+ export declare const handleListRotatingMessages: RouteHandler;
28
+ /**
29
+ * `POST /admin/rotating-messages` — create a rotating starting message.
30
+ *
31
+ * Validation order mirrors `/admin/agents`: Letta agent first (so a bad agent
32
+ * id surfaces alone), then the schedule shape.
33
+ */
34
+ export declare const handleCreateRotatingMessage: RouteHandler;
35
+ /**
36
+ * `PATCH /admin/rotating-messages/:id` — partial update.
37
+ *
38
+ * When any schedule-related field is present, the effective schedule (existing
39
+ * values overlaid with the patch) is re-validated and `next_rotation_at` is
40
+ * recomputed from now. When `letta_agent_id` changes it's re-validated against
41
+ * Letta.
42
+ */
43
+ export declare const handlePatchRotatingMessage: RouteHandler;
44
+ /**
45
+ * `POST /admin/rotating-messages/:id/rotate-now` — runs a rotation inline so
46
+ * the operator can preview / force-refresh without waiting for the schedule.
47
+ * Returns the refreshed row plus the attempt's status. The run may take a
48
+ * while (it spawns the Letta Code subprocess like a real turn).
49
+ */
50
+ export declare const handleRotateRotatingMessageNow: RouteHandler;
51
+ //# sourceMappingURL=rotatingMessages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rotatingMessages.d.ts","sourceRoot":"","sources":["../../../src/routes/admin/rotatingMessages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAgBH,OAAO,KAAK,EAAsB,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAgEhF;;;GAGG;AACH,eAAO,MAAM,0BAA0B,EAAE,YAUxC,CAAC;AAqBF;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,EAAE,YAoEzC,CAAC;AAoBF;;;;;;;GAOG;AACH,eAAO,MAAM,0BAA0B,EAAE,YAwExC,CAAC;AAMF;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,EAAE,YA6B5C,CAAC"}