@naisys/supervisor 3.0.0-beta.10

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 (57) hide show
  1. package/bin/naisys-supervisor +2 -0
  2. package/client-dist/android-chrome-192x192.png +0 -0
  3. package/client-dist/android-chrome-512x512.png +0 -0
  4. package/client-dist/apple-touch-icon.png +0 -0
  5. package/client-dist/assets/index-CKg0vgt5.css +1 -0
  6. package/client-dist/assets/index-WzoDF0aQ.js +177 -0
  7. package/client-dist/assets/naisys-logo-CzoPnn5I.webp +0 -0
  8. package/client-dist/favicon-16x16.png +0 -0
  9. package/client-dist/favicon-32x32.png +0 -0
  10. package/client-dist/favicon.ico +0 -0
  11. package/client-dist/index.html +49 -0
  12. package/client-dist/site.webmanifest +22 -0
  13. package/dist/api-reference.js +54 -0
  14. package/dist/auth-middleware.js +116 -0
  15. package/dist/database/hubDb.js +26 -0
  16. package/dist/database/supervisorDb.js +18 -0
  17. package/dist/error-helpers.js +13 -0
  18. package/dist/hateoas.js +61 -0
  19. package/dist/logger.js +11 -0
  20. package/dist/route-helpers.js +7 -0
  21. package/dist/routes/admin.js +209 -0
  22. package/dist/routes/agentChat.js +194 -0
  23. package/dist/routes/agentConfig.js +265 -0
  24. package/dist/routes/agentLifecycle.js +350 -0
  25. package/dist/routes/agentMail.js +171 -0
  26. package/dist/routes/agentRuns.js +90 -0
  27. package/dist/routes/agents.js +236 -0
  28. package/dist/routes/api.js +52 -0
  29. package/dist/routes/attachments.js +18 -0
  30. package/dist/routes/auth.js +103 -0
  31. package/dist/routes/costs.js +51 -0
  32. package/dist/routes/hosts.js +296 -0
  33. package/dist/routes/models.js +152 -0
  34. package/dist/routes/root.js +56 -0
  35. package/dist/routes/schemas.js +31 -0
  36. package/dist/routes/status.js +20 -0
  37. package/dist/routes/users.js +420 -0
  38. package/dist/routes/variables.js +103 -0
  39. package/dist/schema-registry.js +23 -0
  40. package/dist/services/agentConfigService.js +182 -0
  41. package/dist/services/agentHostStatusService.js +178 -0
  42. package/dist/services/agentService.js +291 -0
  43. package/dist/services/attachmentProxyService.js +131 -0
  44. package/dist/services/browserSocketService.js +78 -0
  45. package/dist/services/chatService.js +201 -0
  46. package/dist/services/configExportService.js +61 -0
  47. package/dist/services/costsService.js +127 -0
  48. package/dist/services/hostService.js +156 -0
  49. package/dist/services/hubConnectionService.js +320 -0
  50. package/dist/services/logFileService.js +11 -0
  51. package/dist/services/mailService.js +154 -0
  52. package/dist/services/modelService.js +92 -0
  53. package/dist/services/runsService.js +168 -0
  54. package/dist/services/userService.js +147 -0
  55. package/dist/services/variableService.js +23 -0
  56. package/dist/supervisorServer.js +221 -0
  57. package/package.json +79 -0
@@ -0,0 +1,350 @@
1
+ import { AgentActionResultSchema, AgentStartRequestSchema, AgentStartResultSchema, AgentStopRequestSchema, AgentStopResultSchema, AgentToggleRequestSchema, AgentUsernameParamsSchema, ErrorResponseSchema, SetLeadAgentRequestSchema, } from "@naisys/supervisor-shared";
2
+ import { requirePermission } from "../auth-middleware.js";
3
+ import { hubDb } from "../database/hubDb.js";
4
+ import { badRequest, notFound } from "../error-helpers.js";
5
+ import { isAgentActive } from "../services/agentHostStatusService.js";
6
+ import { archiveAgent, deleteAgent, disableAgent, enableAgent, getAgent, resetAgentSpend, resolveAgentId, unarchiveAgent, updateLeadAgent, } from "../services/agentService.js";
7
+ import { isHubConnected, sendAgentStart, sendAgentStop, sendUserListChanged, } from "../services/hubConnectionService.js";
8
+ async function findSubordinates(parentUserId, filter) {
9
+ const allUsers = await hubDb.users.findMany({
10
+ select: { id: true, lead_user_id: true },
11
+ });
12
+ const result = [];
13
+ function collect(parentId) {
14
+ for (const user of allUsers) {
15
+ if (user.lead_user_id === parentId) {
16
+ if (!filter || filter(user.id)) {
17
+ result.push(user.id);
18
+ }
19
+ collect(user.id);
20
+ }
21
+ }
22
+ }
23
+ collect(parentUserId);
24
+ return result;
25
+ }
26
+ export default function agentLifecycleRoutes(fastify, _options) {
27
+ // POST /:username/start — Start agent via hub
28
+ fastify.post("/:username/start", {
29
+ preHandler: [requirePermission("manage_agents")],
30
+ schema: {
31
+ description: "Start an agent via the hub",
32
+ tags: ["Agents"],
33
+ params: AgentUsernameParamsSchema,
34
+ body: AgentStartRequestSchema,
35
+ response: {
36
+ 200: AgentStartResultSchema,
37
+ 503: ErrorResponseSchema,
38
+ 500: ErrorResponseSchema,
39
+ },
40
+ security: [{ cookieAuth: [] }],
41
+ },
42
+ }, async (request, reply) => {
43
+ const { username } = request.params;
44
+ const { task } = request.body;
45
+ const id = resolveAgentId(username);
46
+ if (!id) {
47
+ return notFound(reply, "Agent not found");
48
+ }
49
+ if (!isHubConnected()) {
50
+ return reply.status(503).send({
51
+ success: false,
52
+ message: "Hub is not connected",
53
+ });
54
+ }
55
+ const naisysUser = (await hubDb.users.findFirst({
56
+ where: { uuid: request.supervisorUser.uuid },
57
+ select: { id: true },
58
+ })) ??
59
+ (await hubDb.users.findFirst({
60
+ where: { username: "admin" },
61
+ select: { id: true },
62
+ }));
63
+ if (!naisysUser) {
64
+ return reply.status(500).send({
65
+ success: false,
66
+ message: "No matching user found in NAISYS database",
67
+ });
68
+ }
69
+ const response = await sendAgentStart(id, task, naisysUser.id);
70
+ if (response.success) {
71
+ return {
72
+ success: true,
73
+ message: "Agent started",
74
+ hostname: response.hostname,
75
+ };
76
+ }
77
+ else {
78
+ return reply.status(500).send({
79
+ success: false,
80
+ message: response.error || "Failed to start agent",
81
+ });
82
+ }
83
+ });
84
+ // POST /:username/stop — Stop agent via hub
85
+ fastify.post("/:username/stop", {
86
+ preHandler: [requirePermission("manage_agents")],
87
+ schema: {
88
+ description: "Stop an agent via the hub",
89
+ tags: ["Agents"],
90
+ params: AgentUsernameParamsSchema,
91
+ body: AgentStopRequestSchema,
92
+ response: {
93
+ 200: AgentStopResultSchema,
94
+ 503: ErrorResponseSchema,
95
+ 500: ErrorResponseSchema,
96
+ },
97
+ security: [{ cookieAuth: [] }],
98
+ },
99
+ }, async (request, reply) => {
100
+ const { username } = request.params;
101
+ const { recursive } = request.body;
102
+ const id = resolveAgentId(username);
103
+ if (!id) {
104
+ return notFound(reply, "Agent not found");
105
+ }
106
+ if (!isHubConnected()) {
107
+ return reply.status(503).send({
108
+ success: false,
109
+ message: "Hub is not connected",
110
+ });
111
+ }
112
+ // Fire-and-forget stops for subordinates when recursive
113
+ if (recursive) {
114
+ const subordinates = await findSubordinates(id, isAgentActive);
115
+ void Promise.all(subordinates.map((subId) => sendAgentStop(subId, "Stopped from supervisor (recursive)").catch((err) => request.log.error(err, `Failed to stop subordinate agent ${subId}`))));
116
+ }
117
+ const response = await sendAgentStop(id, "Stopped from supervisor");
118
+ if (response.success) {
119
+ return {
120
+ success: true,
121
+ message: recursive
122
+ ? "Agent and subordinates stopped"
123
+ : "Agent stopped",
124
+ };
125
+ }
126
+ else {
127
+ return reply.status(500).send({
128
+ success: false,
129
+ message: response.error || "Failed to stop agent",
130
+ });
131
+ }
132
+ });
133
+ // POST /:username/enable — Enable agent
134
+ fastify.post("/:username/enable", {
135
+ preHandler: [requirePermission("manage_agents")],
136
+ schema: {
137
+ description: "Enable an agent",
138
+ tags: ["Agents"],
139
+ params: AgentUsernameParamsSchema,
140
+ body: AgentToggleRequestSchema,
141
+ response: {
142
+ 200: AgentActionResultSchema,
143
+ 400: ErrorResponseSchema,
144
+ 500: ErrorResponseSchema,
145
+ },
146
+ security: [{ cookieAuth: [] }],
147
+ },
148
+ }, async (request, reply) => {
149
+ const { username } = request.params;
150
+ const { recursive } = request.body;
151
+ const id = resolveAgentId(username);
152
+ if (!id) {
153
+ return notFound(reply, "Agent not found");
154
+ }
155
+ const subordinateIds = recursive ? await findSubordinates(id) : [];
156
+ await enableAgent(id);
157
+ await Promise.all(subordinateIds.map((subId) => enableAgent(subId)));
158
+ sendUserListChanged();
159
+ const count = subordinateIds.length + 1;
160
+ return {
161
+ success: true,
162
+ message: recursive && count > 1
163
+ ? `Enabled ${count} agent(s)`
164
+ : "Agent enabled",
165
+ };
166
+ });
167
+ // POST /:username/disable — Disable agent
168
+ fastify.post("/:username/disable", {
169
+ preHandler: [requirePermission("manage_agents")],
170
+ schema: {
171
+ description: "Disable an agent",
172
+ tags: ["Agents"],
173
+ params: AgentUsernameParamsSchema,
174
+ body: AgentToggleRequestSchema,
175
+ response: {
176
+ 200: AgentActionResultSchema,
177
+ 400: ErrorResponseSchema,
178
+ 500: ErrorResponseSchema,
179
+ },
180
+ security: [{ cookieAuth: [] }],
181
+ },
182
+ }, async (request, reply) => {
183
+ const { username } = request.params;
184
+ const { recursive } = request.body;
185
+ const id = resolveAgentId(username);
186
+ if (!id) {
187
+ return notFound(reply, "Agent not found");
188
+ }
189
+ const subordinateIds = recursive ? await findSubordinates(id) : [];
190
+ const allIds = [id, ...subordinateIds];
191
+ // Disable all in DB first, then try to stop any active ones
192
+ await Promise.all(allIds.map((agentId) => disableAgent(agentId)));
193
+ sendUserListChanged();
194
+ if (isHubConnected()) {
195
+ const activeIds = allIds.filter((agentId) => isAgentActive(agentId));
196
+ if (activeIds.length > 0) {
197
+ void Promise.all(activeIds.map((agentId) => sendAgentStop(agentId, "Agent disabled").catch((err) => request.log.error(err, `Failed to stop disabled agent ${agentId}`))));
198
+ }
199
+ }
200
+ const count = allIds.length;
201
+ return {
202
+ success: true,
203
+ message: recursive && count > 1
204
+ ? `Disabled ${count} agent(s); stop requested for active ones`
205
+ : isAgentActive(id)
206
+ ? "Agent disabled; stop requested"
207
+ : "Agent disabled",
208
+ };
209
+ });
210
+ // POST /:username/archive — Archive agent
211
+ fastify.post("/:username/archive", {
212
+ preHandler: [requirePermission("manage_agents")],
213
+ schema: {
214
+ description: "Archive an agent",
215
+ tags: ["Agents"],
216
+ params: AgentUsernameParamsSchema,
217
+ response: {
218
+ 200: AgentActionResultSchema,
219
+ 400: ErrorResponseSchema,
220
+ 500: ErrorResponseSchema,
221
+ },
222
+ security: [{ cookieAuth: [] }],
223
+ },
224
+ }, async (request, reply) => {
225
+ const { username } = request.params;
226
+ const id = resolveAgentId(username);
227
+ if (!id) {
228
+ return notFound(reply, "Agent not found");
229
+ }
230
+ if (isAgentActive(id)) {
231
+ return badRequest(reply, "Cannot archive an active agent. Stop it first.");
232
+ }
233
+ await archiveAgent(id);
234
+ sendUserListChanged();
235
+ return { success: true, message: "Agent archived" };
236
+ });
237
+ // POST /:username/unarchive — Unarchive agent
238
+ fastify.post("/:username/unarchive", {
239
+ preHandler: [requirePermission("manage_agents")],
240
+ schema: {
241
+ description: "Unarchive an agent",
242
+ tags: ["Agents"],
243
+ params: AgentUsernameParamsSchema,
244
+ response: {
245
+ 200: AgentActionResultSchema,
246
+ 400: ErrorResponseSchema,
247
+ 500: ErrorResponseSchema,
248
+ },
249
+ security: [{ cookieAuth: [] }],
250
+ },
251
+ }, async (request, reply) => {
252
+ const { username } = request.params;
253
+ const id = resolveAgentId(username);
254
+ if (!id) {
255
+ return notFound(reply, "Agent not found");
256
+ }
257
+ await unarchiveAgent(id);
258
+ sendUserListChanged();
259
+ return { success: true, message: "Agent unarchived" };
260
+ });
261
+ // PUT /:username/lead — Set or clear lead agent
262
+ fastify.put("/:username/lead", {
263
+ preHandler: [requirePermission("manage_agents")],
264
+ schema: {
265
+ description: "Set or clear the lead agent",
266
+ tags: ["Agents"],
267
+ params: AgentUsernameParamsSchema,
268
+ body: SetLeadAgentRequestSchema,
269
+ response: {
270
+ 200: AgentActionResultSchema,
271
+ 400: ErrorResponseSchema,
272
+ 500: ErrorResponseSchema,
273
+ },
274
+ security: [{ cookieAuth: [] }],
275
+ },
276
+ }, async (request, reply) => {
277
+ const { username } = request.params;
278
+ const { leadAgentUsername } = request.body;
279
+ const id = resolveAgentId(username);
280
+ if (!id) {
281
+ return notFound(reply, "Agent not found");
282
+ }
283
+ await updateLeadAgent(id, leadAgentUsername);
284
+ sendUserListChanged();
285
+ return {
286
+ success: true,
287
+ message: leadAgentUsername
288
+ ? "Lead agent updated"
289
+ : "Lead agent cleared",
290
+ };
291
+ });
292
+ // POST /:username/reset-spend — Reset agent spend counter
293
+ fastify.post("/:username/reset-spend", {
294
+ preHandler: [requirePermission("manage_agents")],
295
+ schema: {
296
+ description: "Reset an agent's spend counter",
297
+ tags: ["Agents"],
298
+ params: AgentUsernameParamsSchema,
299
+ response: {
300
+ 200: AgentActionResultSchema,
301
+ 400: ErrorResponseSchema,
302
+ 500: ErrorResponseSchema,
303
+ },
304
+ security: [{ cookieAuth: [] }],
305
+ },
306
+ }, async (request, reply) => {
307
+ const { username } = request.params;
308
+ const id = resolveAgentId(username);
309
+ if (!id) {
310
+ return notFound(reply, "Agent not found");
311
+ }
312
+ await resetAgentSpend(id);
313
+ return { success: true, message: "Spend counter reset" };
314
+ });
315
+ // DELETE /:username — Permanently delete agent
316
+ fastify.delete("/:username", {
317
+ preHandler: [requirePermission("manage_agents")],
318
+ schema: {
319
+ description: "Permanently delete an archived agent",
320
+ tags: ["Agents"],
321
+ params: AgentUsernameParamsSchema,
322
+ response: {
323
+ 200: AgentActionResultSchema,
324
+ 400: ErrorResponseSchema,
325
+ 500: ErrorResponseSchema,
326
+ },
327
+ security: [{ cookieAuth: [] }],
328
+ },
329
+ }, async (request, reply) => {
330
+ const { username } = request.params;
331
+ const id = resolveAgentId(username);
332
+ if (!id) {
333
+ return notFound(reply, "Agent not found");
334
+ }
335
+ if (isAgentActive(id)) {
336
+ return badRequest(reply, "Cannot delete an active agent. Stop it first.");
337
+ }
338
+ const agent = await getAgent(id);
339
+ if (!agent) {
340
+ return notFound(reply, "Agent not found");
341
+ }
342
+ if (!agent.archived) {
343
+ return badRequest(reply, "Agent must be archived before it can be deleted.");
344
+ }
345
+ await deleteAgent(id);
346
+ sendUserListChanged();
347
+ return { success: true, message: "Agent permanently deleted" };
348
+ });
349
+ }
350
+ //# sourceMappingURL=agentLifecycle.js.map
@@ -0,0 +1,171 @@
1
+ import { AgentUsernameParamsSchema, ArchiveMailResponseSchema, ErrorResponseSchema, MailDataRequestSchema, MailDataResponseSchema, SendMailRequestSchema, SendMailResponseSchema, } from "@naisys/supervisor-shared";
2
+ import { hasPermission, requirePermission } from "../auth-middleware.js";
3
+ import { badRequest, notFound } from "../error-helpers.js";
4
+ import { API_PREFIX } from "../hateoas.js";
5
+ import { resolveAgentId } from "../services/agentService.js";
6
+ import { archiveAllMailMessages, getMailDataByUserId, sendMessage, } from "../services/mailService.js";
7
+ export default function agentMailRoutes(fastify, _options) {
8
+ // GET /:username/mail — Mail for agent
9
+ fastify.get("/:username/mail", {
10
+ schema: {
11
+ description: "Get mail data for a specific agent",
12
+ tags: ["Mail"],
13
+ params: AgentUsernameParamsSchema,
14
+ querystring: MailDataRequestSchema,
15
+ response: {
16
+ 200: MailDataResponseSchema,
17
+ 500: ErrorResponseSchema,
18
+ },
19
+ },
20
+ }, async (request, reply) => {
21
+ const { username } = request.params;
22
+ const { updatedSince, page, count } = request.query;
23
+ const id = resolveAgentId(username);
24
+ if (!id) {
25
+ return notFound(reply, `Agent '${username}' not found`);
26
+ }
27
+ const data = await getMailDataByUserId(id, updatedSince, page, count, "mail");
28
+ const canSend = hasPermission(request.supervisorUser, "agent_communication");
29
+ return {
30
+ success: true,
31
+ message: "Mail data retrieved successfully",
32
+ data,
33
+ _links: data
34
+ ? [
35
+ {
36
+ rel: "next",
37
+ href: `${API_PREFIX}/agents/${username}/mail?updatedSince=${encodeURIComponent(data.timestamp)}`,
38
+ title: "Poll for newer mail",
39
+ },
40
+ ]
41
+ : undefined,
42
+ _actions: canSend
43
+ ? [
44
+ {
45
+ rel: "send",
46
+ href: `${API_PREFIX}/agents/${username}/mail`,
47
+ method: "POST",
48
+ title: "Send Mail",
49
+ schema: `${API_PREFIX}/schemas/SendMail`,
50
+ body: { fromId: 0, toIds: [0], subject: "", message: "" },
51
+ alternateEncoding: {
52
+ contentType: "multipart/form-data",
53
+ description: "Send as multipart to include file attachments",
54
+ fileFields: ["attachments"],
55
+ },
56
+ },
57
+ {
58
+ rel: "archive",
59
+ href: `${API_PREFIX}/agents/${username}/mail/archive`,
60
+ method: "POST",
61
+ title: "Archive All Mail Messages",
62
+ },
63
+ ]
64
+ : undefined,
65
+ };
66
+ });
67
+ // POST /:username/mail/archive — Archive all mail messages
68
+ fastify.post("/:username/mail/archive", {
69
+ preHandler: [requirePermission("agent_communication")],
70
+ schema: {
71
+ description: "Archive all mail messages for an agent",
72
+ tags: ["Mail"],
73
+ params: AgentUsernameParamsSchema,
74
+ response: {
75
+ 200: ArchiveMailResponseSchema,
76
+ 500: ErrorResponseSchema,
77
+ },
78
+ security: [{ cookieAuth: [] }],
79
+ },
80
+ }, async (request, reply) => {
81
+ const { username } = request.params;
82
+ const id = resolveAgentId(username);
83
+ if (!id) {
84
+ return notFound(reply, `Agent '${username}' not found`);
85
+ }
86
+ const archivedCount = await archiveAllMailMessages(id);
87
+ return { success: true, archivedCount };
88
+ });
89
+ // POST /:username/mail — Send mail as agent
90
+ fastify.post("/:username/mail", {
91
+ preHandler: [requirePermission("agent_communication")],
92
+ schema: {
93
+ description: "Send email as agent with optional attachments. Supports JSON and multipart/form-data",
94
+ tags: ["Mail"],
95
+ params: AgentUsernameParamsSchema,
96
+ // No body schema — multipart requests are parsed manually via request.parts()
97
+ response: {
98
+ 200: SendMailResponseSchema,
99
+ 400: ErrorResponseSchema,
100
+ 500: ErrorResponseSchema,
101
+ },
102
+ security: [{ cookieAuth: [] }],
103
+ },
104
+ }, async (request, reply) => {
105
+ const contentType = request.headers["content-type"];
106
+ let fromId = 0, toIds = [], subject = "", message = "";
107
+ let attachments = [];
108
+ if (contentType?.includes("multipart/form-data")) {
109
+ const parts = request.parts();
110
+ for await (const part of parts) {
111
+ if (part.type === "field") {
112
+ const field = part;
113
+ switch (field.fieldname) {
114
+ case "fromId":
115
+ fromId = Number(field.value);
116
+ break;
117
+ case "toIds":
118
+ try {
119
+ toIds = JSON.parse(field.value);
120
+ }
121
+ catch {
122
+ return badRequest(reply, "toIds must be valid JSON array");
123
+ }
124
+ break;
125
+ case "subject":
126
+ subject = field.value;
127
+ break;
128
+ case "message":
129
+ message = field.value;
130
+ break;
131
+ }
132
+ }
133
+ else if (part.type === "file") {
134
+ const file = part;
135
+ if (file.fieldname === "attachments") {
136
+ const buffer = await file.toBuffer();
137
+ attachments.push({
138
+ filename: file.filename || "unnamed_file",
139
+ data: buffer,
140
+ });
141
+ }
142
+ }
143
+ }
144
+ }
145
+ else {
146
+ const body = request.body;
147
+ fromId = body.fromId;
148
+ toIds = body.toIds;
149
+ subject = body.subject;
150
+ message = body.message;
151
+ }
152
+ const parsed = SendMailRequestSchema.safeParse({
153
+ fromId,
154
+ toIds,
155
+ subject,
156
+ message,
157
+ });
158
+ if (!parsed.success) {
159
+ return badRequest(reply, parsed.error.message);
160
+ }
161
+ ({ fromId, toIds, subject, message } = parsed.data);
162
+ const result = await sendMessage({ fromId, toIds, subject, message }, attachments.length > 0 ? attachments : undefined);
163
+ if (result.success) {
164
+ return reply.code(200).send(result);
165
+ }
166
+ else {
167
+ return reply.code(500).send(result);
168
+ }
169
+ });
170
+ }
171
+ //# sourceMappingURL=agentMail.js.map
@@ -0,0 +1,90 @@
1
+ import { AgentUsernameParamsSchema, ContextLogParamsSchema, ContextLogRequestSchema, ContextLogResponseSchema, RunsDataRequestSchema, RunsDataResponseSchema, } from "@naisys/supervisor-shared";
2
+ import { hasPermission } from "../auth-middleware.js";
3
+ import { notFound } from "../error-helpers.js";
4
+ import { API_PREFIX } from "../hateoas.js";
5
+ import { resolveAgentId } from "../services/agentService.js";
6
+ import { getContextLog, getRunsData, obfuscateLogs, } from "../services/runsService.js";
7
+ export default function agentRunsRoutes(fastify, _options) {
8
+ // GET /:username/runs — Runs for agent
9
+ fastify.get("/:username/runs", {
10
+ schema: {
11
+ description: "Get run sessions for a specific agent",
12
+ tags: ["Runs"],
13
+ params: AgentUsernameParamsSchema,
14
+ querystring: RunsDataRequestSchema,
15
+ response: {
16
+ 200: RunsDataResponseSchema,
17
+ 500: RunsDataResponseSchema,
18
+ },
19
+ },
20
+ }, async (request, reply) => {
21
+ const { username } = request.params;
22
+ const { updatedSince, page, count } = request.query;
23
+ const id = resolveAgentId(username);
24
+ if (!id) {
25
+ return notFound(reply, `Agent '${username}' not found`);
26
+ }
27
+ const data = await getRunsData(id, updatedSince, page, count);
28
+ return {
29
+ success: true,
30
+ message: "Runs data retrieved successfully",
31
+ data: data ?? undefined,
32
+ _linkTemplates: [
33
+ {
34
+ rel: "logs",
35
+ hrefTemplate: `${API_PREFIX}/agents/${username}/runs/{runId}/sessions/{sessionId}/logs`,
36
+ },
37
+ ],
38
+ _links: data
39
+ ? [
40
+ {
41
+ rel: "next",
42
+ href: `${API_PREFIX}/agents/${username}/runs?updatedSince=${encodeURIComponent(data.timestamp)}`,
43
+ title: "Poll for updated runs",
44
+ },
45
+ ]
46
+ : undefined,
47
+ };
48
+ });
49
+ // GET /:username/runs/:runId/sessions/:sessionId/logs — Context log
50
+ fastify.get("/:username/runs/:runId/sessions/:sessionId/logs", {
51
+ schema: {
52
+ description: "Get context log for a specific run session",
53
+ tags: ["Runs"],
54
+ params: ContextLogParamsSchema,
55
+ querystring: ContextLogRequestSchema,
56
+ response: {
57
+ 200: ContextLogResponseSchema,
58
+ 500: ContextLogResponseSchema,
59
+ },
60
+ },
61
+ }, async (request, reply) => {
62
+ const { username, runId, sessionId } = request.params;
63
+ const { logsAfter, logsBefore } = request.query;
64
+ const id = resolveAgentId(username);
65
+ if (!id) {
66
+ return notFound(reply, `Agent '${username}' not found`);
67
+ }
68
+ let data = await getContextLog(id, runId, sessionId, logsAfter, logsBefore);
69
+ // Obfuscate log text for users without view_run_logs permission
70
+ if (!hasPermission(request.supervisorUser, "view_run_logs")) {
71
+ data = obfuscateLogs(data);
72
+ }
73
+ const maxLogId = data?.logs.length
74
+ ? Math.max(...data.logs.map((l) => l.id))
75
+ : (logsAfter ?? 0);
76
+ return {
77
+ success: true,
78
+ message: "Context log retrieved successfully",
79
+ data,
80
+ _links: [
81
+ {
82
+ rel: "next",
83
+ href: `${API_PREFIX}/agents/${username}/runs/${runId}/sessions/${sessionId}/logs?logsAfter=${maxLogId}`,
84
+ title: "Poll for newer logs",
85
+ },
86
+ ],
87
+ };
88
+ });
89
+ }
90
+ //# sourceMappingURL=agentRuns.js.map