@tonyclaw/agent-inspector 2.0.33 → 2.0.34

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 (49) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-ClTJxdoU.js → CompareDrawer-s5T7W5_w.js} +1 -1
  3. package/.output/public/assets/{ProxyViewerContainer--Pes01q2.js → ProxyViewerContainer-DfGfP9VH.js} +4 -4
  4. package/.output/public/assets/{ReplayDialog-iuzfh85b.js → ReplayDialog-DIxp-ccL.js} +1 -1
  5. package/.output/public/assets/{RequestAnatomy-FZnnP9Gx.js → RequestAnatomy-tfdpiRgw.js} +1 -1
  6. package/.output/public/assets/{ResponseView-MZ6ZWvbL.js → ResponseView-DMZp3aLW.js} +1 -1
  7. package/.output/public/assets/{StreamingChunkSequence-B8mZMdzs.js → StreamingChunkSequence-xXzLVw7m.js} +1 -1
  8. package/.output/public/assets/_sessionId-Dfxcty_X.js +1 -0
  9. package/.output/public/assets/index-DztLtWHr.js +1 -0
  10. package/.output/public/assets/{main-B797Hnge.js → main-DHq8ZD95.js} +2 -2
  11. package/.output/server/_libs/ajv.mjs +827 -6191
  12. package/.output/server/_libs/cookie-es.mjs +7 -21
  13. package/.output/server/_libs/h3-v2.mjs +18 -7
  14. package/.output/server/_libs/json-schema-traverse.mjs +1 -90
  15. package/.output/server/_libs/seroval-plugins.mjs +5 -5
  16. package/.output/server/_libs/seroval.mjs +606 -596
  17. package/.output/server/_libs/srvx.mjs +10 -5
  18. package/.output/server/_libs/tanstack__history.mjs +55 -38
  19. package/.output/server/_libs/tanstack__router-core.mjs +4366 -4075
  20. package/.output/server/{_sessionId-DD05qd8d.mjs → _sessionId-Bow47H8m.mjs} +4 -4
  21. package/.output/server/_ssr/{CompareDrawer-oGOG5-kQ.mjs → CompareDrawer-Ctn56NJX.mjs} +4 -4
  22. package/.output/server/_ssr/{ProxyViewerContainer-Bbl5tqT1.mjs → ProxyViewerContainer-DgPsLI2Q.mjs} +7 -7
  23. package/.output/server/_ssr/{ReplayDialog-BegfwWIy.mjs → ReplayDialog-BD9_rksD.mjs} +5 -5
  24. package/.output/server/_ssr/{RequestAnatomy-BW_X2DyZ.mjs → RequestAnatomy-BJyzD8pO.mjs} +4 -4
  25. package/.output/server/_ssr/{ResponseView-Bx_uJ4RS.mjs → ResponseView-lebGC0QX.mjs} +4 -4
  26. package/.output/server/_ssr/{StreamingChunkSequence-CQp5QQnB.mjs → StreamingChunkSequence-BcOLywzn.mjs} +4 -4
  27. package/.output/server/_ssr/{index-ODSKeDD8.mjs → index-CgJOsItz.mjs} +4 -4
  28. package/.output/server/_ssr/index.mjs +20 -11
  29. package/.output/server/_ssr/{router-QcI4GOvo.mjs → router-BMv4lGQi.mjs} +5192 -5268
  30. package/.output/server/{_tanstack-start-manifest_v-dEujKJ3-.mjs → _tanstack-start-manifest_v-D2LyT1-l.mjs} +1 -1
  31. package/.output/server/index.mjs +65 -65
  32. package/README.md +23 -0
  33. package/package.json +6 -2
  34. package/src/lib/groupContract.ts +0 -2
  35. package/src/lib/runContract.ts +0 -3
  36. package/src/mcp/server.ts +101 -164
  37. package/src/mcp/toolHandlers.ts +80 -177
  38. package/src/routes/api/groups.$groupId.evidence.ts +14 -33
  39. package/src/routes/api/groups.$groupId.sessions.ts +9 -9
  40. package/src/routes/api/groups.$groupId.ts +19 -13
  41. package/src/routes/api/groups.ts +3 -3
  42. package/src/routes/api/runs.$runId.evidence.ts +14 -21
  43. package/src/routes/api/runs.$runId.ts +9 -9
  44. package/src/routes/api/runs.ts +9 -5
  45. package/src/services/groups.ts +110 -0
  46. package/src/services/result.ts +17 -0
  47. package/src/services/runs.ts +76 -0
  48. package/.output/public/assets/_sessionId-Bc26Rfxs.js +0 -1
  49. package/.output/public/assets/index-C6AiRPsJ.js +0 -1
@@ -1,9 +1,11 @@
1
1
  /**
2
- * Pure (dependency-injected) tool handler implementations for the MCP
3
- * server. These are split out from `server.ts` so unit tests can pass a
4
- * mock `callApi` without spinning up a real HTTP loopback. The
5
- * `registerTool` calls in `server.ts` simply wrap each impl in `safeCall`
6
- * and bind the real `callApi` from `./loopback`.
2
+ * Tool handler implementations for the MCP server. These are split out from
3
+ * `server.ts` so the wire-level MCP registration stays thin.
4
+ *
5
+ * Log/provider/knowledge handlers still use an injected `callApi` while those
6
+ * domains are being migrated. Run/group/evidence handlers call the shared
7
+ * service layer directly so MCP and REST API are peers instead of MCP looping
8
+ * back through `/api/*`.
7
9
  *
8
10
  * Each impl returns a `CallToolResult`-shaped object: either
9
11
  * `{ content: [{ type: "text", text: string }] }` on success or
@@ -15,27 +17,34 @@
15
17
 
16
18
  import { Buffer } from "node:buffer";
17
19
  import { z } from "zod";
20
+ import { getCurrentPort } from "../lib/serverPort";
18
21
  import { LoopbackTimeoutError, type CallApiOptions } from "./loopback";
19
22
  import { extractLastUserMessagePreview, extractResponsePreview } from "./previewExtractor";
20
23
  import {
21
- DeleteInspectorGroupResponseSchema,
22
- GroupEvidenceExportResultSchema,
23
- InspectorGroupSchema,
24
- InspectorGroupsListResponseSchema,
25
24
  type AddInspectorGroupSessionInput,
26
25
  type CreateInspectorGroupInput,
27
26
  type DeleteInspectorGroupOptions,
28
27
  type UpdateInspectorGroupInput,
29
28
  } from "../lib/groupContract";
30
- import {
31
- EvidenceExportResultSchema,
32
- InspectorRunSchema,
33
- RecentFailuresResponseSchema,
34
- type CreateInspectorRunInput,
35
- type UpdateInspectorRunInput,
36
- } from "../lib/runContract";
29
+ import { type CreateInspectorRunInput, type UpdateInspectorRunInput } from "../lib/runContract";
37
30
  import { SessionInfoSchema } from "../lib/sessionInfoContract";
38
31
  import { CapturedLogSchema, type CapturedLog } from "../proxy/schemas";
32
+ import {
33
+ addInspectorGroupSession,
34
+ createInspectorGroup,
35
+ deleteInspectorGroup,
36
+ exportInspectorGroupEvidence,
37
+ getInspectorGroup,
38
+ listInspectorGroups,
39
+ updateInspectorGroup,
40
+ } from "../services/groups";
41
+ import {
42
+ createInspectorRun,
43
+ exportInspectorRunEvidence,
44
+ getInspectorRun,
45
+ listInspectorRecentFailures,
46
+ updateInspectorRun,
47
+ } from "../services/runs";
39
48
 
40
49
  /** Minimal callApi contract: same shape as the real `callApi` in loopback.ts. */
41
50
  export type CallApiFn = (path: string, options?: CallApiOptions) => Promise<Response>;
@@ -81,6 +90,10 @@ function toolError(message: string): ToolResult {
81
90
  return { content: [{ type: "text", text: message }], isError: true };
82
91
  }
83
92
 
93
+ function inspectorBaseUrl(): string {
94
+ return `http://127.0.0.1:${String(getCurrentPort())}`;
95
+ }
96
+
84
97
  function textByteLength(value: string | null): number | null {
85
98
  if (value === null) return null;
86
99
  return Buffer.byteLength(value, "utf8");
@@ -288,73 +301,38 @@ export async function getSessionImpl(
288
301
  return textJson(parsed.data);
289
302
  }
290
303
 
291
- export async function createRunImpl(
292
- callApi: CallApiFn,
293
- args: CreateInspectorRunInput,
294
- ): Promise<ToolResult> {
295
- const res = await callApi("/api/runs", {
296
- method: "POST",
297
- headers: { "content-type": "application/json" },
298
- body: JSON.stringify(args ?? {}),
299
- });
300
- if (!res.ok) return toolError(`POST /api/runs returned ${res.status}`);
301
- const rawBody: unknown = await res.json();
302
- const parsed = InspectorRunSchema.safeParse(rawBody);
303
- if (!parsed.success) return toolError("POST /api/runs returned an unparseable run");
304
- return textJson(parsed.data);
304
+ export function createRunImpl(args: CreateInspectorRunInput): ToolResult {
305
+ return textJson(createInspectorRun(args));
305
306
  }
306
307
 
307
308
  export type GetRunArgs = {
308
309
  runId: string;
309
310
  };
310
311
 
311
- export async function getRunImpl(callApi: CallApiFn, args: GetRunArgs): Promise<ToolResult> {
312
- const path = `/api/runs/${encodeURIComponent(args.runId)}`;
313
- const res = await callApi(path);
314
- if (!res.ok) return toolError(`GET ${path} returned ${res.status}`);
315
- const rawBody: unknown = await res.json();
316
- const parsed = InspectorRunSchema.safeParse(rawBody);
317
- if (!parsed.success) return toolError("GET /api/runs/{runId} returned an unparseable run");
318
- return textJson(parsed.data);
312
+ export function getRunImpl(args: GetRunArgs): ToolResult {
313
+ const run = getInspectorRun(args.runId);
314
+ if (!run.ok) return toolError(run.message);
315
+ return textJson(run.value);
319
316
  }
320
317
 
321
318
  export type UpdateRunArgs = {
322
319
  runId: string;
323
320
  } & UpdateInspectorRunInput;
324
321
 
325
- export async function updateRunImpl(callApi: CallApiFn, args: UpdateRunArgs): Promise<ToolResult> {
322
+ export function updateRunImpl(args: UpdateRunArgs): ToolResult {
326
323
  const { runId, ...patch } = args;
327
- const path = `/api/runs/${encodeURIComponent(runId)}`;
328
- const res = await callApi(path, {
329
- method: "PATCH",
330
- headers: { "content-type": "application/json" },
331
- body: JSON.stringify(patch),
332
- });
333
- if (!res.ok) return toolError(`PATCH ${path} returned ${res.status}`);
334
- const rawBody: unknown = await res.json();
335
- const parsed = InspectorRunSchema.safeParse(rawBody);
336
- if (!parsed.success) return toolError("PATCH /api/runs/{runId} returned an unparseable run");
337
- return textJson(parsed.data);
324
+ const run = updateInspectorRun(runId, patch);
325
+ if (!run.ok) return toolError(run.message);
326
+ return textJson(run.value);
338
327
  }
339
328
 
340
329
  export type GetRecentFailuresArgs = {
341
330
  limit?: number;
342
331
  };
343
332
 
344
- export async function getRecentFailuresImpl(
345
- callApi: CallApiFn,
346
- args: GetRecentFailuresArgs,
347
- ): Promise<ToolResult> {
333
+ export function getRecentFailuresImpl(args: GetRecentFailuresArgs): ToolResult {
348
334
  const limit = args.limit ?? 5;
349
- const params = new URLSearchParams({ failures: "1", limit: String(limit) });
350
- const res = await callApi(`/api/runs?${params.toString()}`);
351
- if (!res.ok) return toolError(`GET /api/runs?failures=1 returned ${res.status}`);
352
- const rawBody: unknown = await res.json();
353
- const parsed = RecentFailuresResponseSchema.safeParse(rawBody);
354
- if (!parsed.success) {
355
- return toolError("GET /api/runs?failures=1 returned an unparseable failure list");
356
- }
357
- return textJson(parsed.data);
335
+ return textJson(listInspectorRecentFailures(limit));
358
336
  }
359
337
 
360
338
  export type ExportEvidenceArgs = {
@@ -363,133 +341,67 @@ export type ExportEvidenceArgs = {
363
341
  latestLogLimit?: number;
364
342
  };
365
343
 
366
- export async function exportEvidenceImpl(
367
- callApi: CallApiFn,
368
- args: ExportEvidenceArgs,
369
- ): Promise<ToolResult> {
370
- const path = `/api/runs/${encodeURIComponent(args.runId)}/evidence`;
371
- const res = await callApi(path, {
372
- method: "POST",
373
- headers: { "content-type": "application/json" },
374
- body: JSON.stringify({
344
+ export async function exportEvidenceImpl(args: ExportEvidenceArgs): Promise<ToolResult> {
345
+ const exported = await exportInspectorRunEvidence(
346
+ args.runId,
347
+ {
375
348
  includeHistory: args.includeHistory,
376
349
  latestLogLimit: args.latestLogLimit,
377
- }),
378
- });
379
- if (!res.ok) return toolError(`POST ${path} returned ${res.status}`);
380
- const rawBody: unknown = await res.json();
381
- const parsed = EvidenceExportResultSchema.safeParse(rawBody);
382
- if (!parsed.success) {
383
- return toolError("POST /api/runs/{runId}/evidence returned an unparseable evidence result");
384
- }
385
- return textJson(parsed.data);
350
+ },
351
+ inspectorBaseUrl(),
352
+ );
353
+ if (!exported.ok) return toolError(exported.message);
354
+ return textJson(exported.value);
386
355
  }
387
356
 
388
- export async function listGroupsImpl(callApi: CallApiFn): Promise<ToolResult> {
389
- const res = await callApi("/api/groups");
390
- if (!res.ok) return toolError(`GET /api/groups returned ${res.status}`);
391
- const rawBody: unknown = await res.json();
392
- const parsed = InspectorGroupsListResponseSchema.safeParse(rawBody);
393
- if (!parsed.success) return toolError("GET /api/groups returned an unparseable group list");
394
- return textJson(parsed.data);
357
+ export function listGroupsImpl(): ToolResult {
358
+ return textJson(listInspectorGroups());
395
359
  }
396
360
 
397
- export async function createGroupImpl(
398
- callApi: CallApiFn,
399
- args: CreateInspectorGroupInput,
400
- ): Promise<ToolResult> {
401
- const res = await callApi("/api/groups", {
402
- method: "POST",
403
- headers: { "content-type": "application/json" },
404
- body: JSON.stringify(args ?? {}),
405
- });
406
- if (!res.ok) return toolError(`POST /api/groups returned ${res.status}`);
407
- const rawBody: unknown = await res.json();
408
- const parsed = InspectorGroupSchema.safeParse(rawBody);
409
- if (!parsed.success) return toolError("POST /api/groups returned an unparseable group");
410
- return textJson(parsed.data);
361
+ export function createGroupImpl(args: CreateInspectorGroupInput): ToolResult {
362
+ return textJson(createInspectorGroup(args));
411
363
  }
412
364
 
413
365
  export type GetGroupArgs = {
414
366
  groupId: string;
415
367
  };
416
368
 
417
- export async function getGroupImpl(callApi: CallApiFn, args: GetGroupArgs): Promise<ToolResult> {
418
- const path = `/api/groups/${encodeURIComponent(args.groupId)}`;
419
- const res = await callApi(path);
420
- if (!res.ok) return toolError(`GET ${path} returned ${res.status}`);
421
- const rawBody: unknown = await res.json();
422
- const parsed = InspectorGroupSchema.safeParse(rawBody);
423
- if (!parsed.success) return toolError("GET /api/groups/{groupId} returned an unparseable group");
424
- return textJson(parsed.data);
369
+ export function getGroupImpl(args: GetGroupArgs): ToolResult {
370
+ const group = getInspectorGroup(args.groupId);
371
+ if (!group.ok) return toolError(group.message);
372
+ return textJson(group.value);
425
373
  }
426
374
 
427
375
  export type UpdateGroupArgs = {
428
376
  groupId: string;
429
377
  } & UpdateInspectorGroupInput;
430
378
 
431
- export async function updateGroupImpl(
432
- callApi: CallApiFn,
433
- args: UpdateGroupArgs,
434
- ): Promise<ToolResult> {
379
+ export function updateGroupImpl(args: UpdateGroupArgs): ToolResult {
435
380
  const { groupId, ...patch } = args;
436
- const path = `/api/groups/${encodeURIComponent(groupId)}`;
437
- const res = await callApi(path, {
438
- method: "PATCH",
439
- headers: { "content-type": "application/json" },
440
- body: JSON.stringify(patch),
441
- });
442
- if (!res.ok) return toolError(`PATCH ${path} returned ${res.status}`);
443
- const rawBody: unknown = await res.json();
444
- const parsed = InspectorGroupSchema.safeParse(rawBody);
445
- if (!parsed.success) {
446
- return toolError("PATCH /api/groups/{groupId} returned an unparseable group");
447
- }
448
- return textJson(parsed.data);
381
+ const group = updateInspectorGroup(groupId, patch);
382
+ if (!group.ok) return toolError(group.message);
383
+ return textJson(group.value);
449
384
  }
450
385
 
451
386
  export type DeleteGroupArgs = {
452
387
  groupId: string;
453
388
  } & DeleteInspectorGroupOptions;
454
389
 
455
- export async function deleteGroupImpl(
456
- callApi: CallApiFn,
457
- args: DeleteGroupArgs,
458
- ): Promise<ToolResult> {
459
- const query = args.deleteEvidence === true ? "?deleteEvidence=1" : "";
460
- const path = `/api/groups/${encodeURIComponent(args.groupId)}${query}`;
461
- const res = await callApi(path, { method: "DELETE" });
462
- if (!res.ok) return toolError(`DELETE ${path} returned ${res.status}`);
463
- const rawBody: unknown = await res.json();
464
- const parsed = DeleteInspectorGroupResponseSchema.safeParse(rawBody);
465
- if (!parsed.success) {
466
- return toolError("DELETE /api/groups/{groupId} returned an unparseable delete result");
467
- }
468
- return textJson(parsed.data);
390
+ export function deleteGroupImpl(args: DeleteGroupArgs): ToolResult {
391
+ const deleted = deleteInspectorGroup(args.groupId, { deleteEvidence: args.deleteEvidence });
392
+ if (!deleted.ok) return toolError(deleted.message);
393
+ return textJson(deleted.value);
469
394
  }
470
395
 
471
396
  export type AddGroupSessionArgs = {
472
397
  groupId: string;
473
398
  } & AddInspectorGroupSessionInput;
474
399
 
475
- export async function addGroupSessionImpl(
476
- callApi: CallApiFn,
477
- args: AddGroupSessionArgs,
478
- ): Promise<ToolResult> {
400
+ export function addGroupSessionImpl(args: AddGroupSessionArgs): ToolResult {
479
401
  const { groupId, ...session } = args;
480
- const path = `/api/groups/${encodeURIComponent(groupId)}/sessions`;
481
- const res = await callApi(path, {
482
- method: "POST",
483
- headers: { "content-type": "application/json" },
484
- body: JSON.stringify(session),
485
- });
486
- if (!res.ok) return toolError(`POST ${path} returned ${res.status}`);
487
- const rawBody: unknown = await res.json();
488
- const parsed = InspectorGroupSchema.safeParse(rawBody);
489
- if (!parsed.success) {
490
- return toolError("POST /api/groups/{groupId}/sessions returned an unparseable group");
491
- }
492
- return textJson(parsed.data);
402
+ const group = addInspectorGroupSession(groupId, session);
403
+ if (!group.ok) return toolError(group.message);
404
+ return textJson(group.value);
493
405
  }
494
406
 
495
407
  export type ExportGroupEvidenceArgs = {
@@ -498,26 +410,17 @@ export type ExportGroupEvidenceArgs = {
498
410
  latestLogLimit?: number;
499
411
  };
500
412
 
501
- export async function exportGroupEvidenceImpl(
502
- callApi: CallApiFn,
503
- args: ExportGroupEvidenceArgs,
504
- ): Promise<ToolResult> {
505
- const path = `/api/groups/${encodeURIComponent(args.groupId)}/evidence`;
506
- const res = await callApi(path, {
507
- method: "POST",
508
- headers: { "content-type": "application/json" },
509
- body: JSON.stringify({
413
+ export async function exportGroupEvidenceImpl(args: ExportGroupEvidenceArgs): Promise<ToolResult> {
414
+ const exported = await exportInspectorGroupEvidence(
415
+ args.groupId,
416
+ {
510
417
  includeHistory: args.includeHistory,
511
418
  latestLogLimit: args.latestLogLimit,
512
- }),
513
- });
514
- if (!res.ok) return toolError(`POST ${path} returned ${res.status}`);
515
- const rawBody: unknown = await res.json();
516
- const parsed = GroupEvidenceExportResultSchema.safeParse(rawBody);
517
- if (!parsed.success) {
518
- return toolError("POST /api/groups/{groupId}/evidence returned an unparseable evidence result");
519
- }
520
- return textJson(parsed.data);
419
+ },
420
+ inspectorBaseUrl(),
421
+ );
422
+ if (!exported.ok) return toolError(exported.message);
423
+ return textJson(exported.value);
521
424
  }
522
425
 
523
426
  export async function listModelsImpl(callApi: CallApiFn): Promise<ToolResult> {
@@ -724,7 +627,7 @@ export async function getProjectContextImpl(
724
627
  * CallToolResult (instead of a JSON-RPC protocol error, which would abort
725
628
  * the whole session). Loopback timeouts get a clear message.
726
629
  */
727
- export async function safeCall(fn: () => Promise<ToolResult>): Promise<ToolResult> {
630
+ export async function safeCall(fn: () => ToolResult | Promise<ToolResult>): Promise<ToolResult> {
728
631
  try {
729
632
  return await fn();
730
633
  } catch (err) {
@@ -1,11 +1,6 @@
1
1
  import { createFileRoute } from "@tanstack/react-router";
2
2
  import { GroupEvidenceExportOptionsSchema } from "../../lib/groupContract";
3
- import {
4
- exportGroupEvidence,
5
- readGroupEvidenceDocument,
6
- readGroupEvidenceMarkdown,
7
- } from "../../proxy/groupEvidenceExporter";
8
- import { getGroup } from "../../proxy/groupStore";
3
+ import { exportInspectorGroupEvidence, readInspectorGroupEvidence } from "../../services/groups";
9
4
 
10
5
  type JsonBodyResult = { ok: true; value: unknown } | { ok: false };
11
6
 
@@ -23,35 +18,14 @@ export const Route = createFileRoute("/api/groups/$groupId/evidence")({
23
18
  server: {
24
19
  handlers: {
25
20
  GET: ({ params }: { params: { groupId: string } }) => {
26
- const group = getGroup(params.groupId);
27
- if (group === null) {
28
- return Response.json({ error: "Group not found" }, { status: 404 });
21
+ const evidence = readInspectorGroupEvidence(params.groupId);
22
+ if (!evidence.ok) {
23
+ return Response.json({ error: evidence.message }, { status: evidence.status });
29
24
  }
30
25
 
31
- const markdown = readGroupEvidenceMarkdown(group);
32
- if (group.evidence === null || markdown === null) {
33
- return Response.json(
34
- { error: "Evidence has not been exported for this group" },
35
- { status: 404 },
36
- );
37
- }
38
-
39
- const document = readGroupEvidenceDocument(group);
40
-
41
- return Response.json({
42
- group,
43
- evidence: group.evidence,
44
- markdown,
45
- summary: document?.summary ?? null,
46
- report: document?.jenkinsReport ?? null,
47
- });
26
+ return Response.json(evidence.value);
48
27
  },
49
28
  POST: async ({ params, request }: { params: { groupId: string }; request: Request }) => {
50
- const group = getGroup(params.groupId);
51
- if (group === null) {
52
- return Response.json({ error: "Group not found" }, { status: 404 });
53
- }
54
-
55
29
  const body = await readJsonBody(request);
56
30
  if (!body.ok) {
57
31
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
@@ -65,9 +39,16 @@ export const Route = createFileRoute("/api/groups/$groupId/evidence")({
65
39
  );
66
40
  }
67
41
 
68
- return Response.json(
69
- await exportGroupEvidence(group, parsed.data, new URL(request.url).origin),
42
+ const exported = await exportInspectorGroupEvidence(
43
+ params.groupId,
44
+ parsed.data,
45
+ new URL(request.url).origin,
70
46
  );
47
+ if (!exported.ok) {
48
+ return Response.json({ error: exported.message }, { status: exported.status });
49
+ }
50
+
51
+ return Response.json(exported.value);
71
52
  },
72
53
  },
73
54
  },
@@ -1,6 +1,6 @@
1
1
  import { createFileRoute } from "@tanstack/react-router";
2
2
  import { AddInspectorGroupSessionInputSchema } from "../../lib/groupContract";
3
- import { addGroupSession, getGroup } from "../../proxy/groupStore";
3
+ import { addInspectorGroupSession, readInspectorGroupSessions } from "../../services/groups";
4
4
 
5
5
  type JsonBodyResult = { ok: true; value: unknown } | { ok: false };
6
6
 
@@ -18,11 +18,11 @@ export const Route = createFileRoute("/api/groups/$groupId/sessions")({
18
18
  server: {
19
19
  handlers: {
20
20
  GET: ({ params }: { params: { groupId: string } }) => {
21
- const group = getGroup(params.groupId);
22
- if (group === null) {
23
- return Response.json({ error: "Group not found" }, { status: 404 });
21
+ const sessions = readInspectorGroupSessions(params.groupId);
22
+ if (!sessions.ok) {
23
+ return Response.json({ error: sessions.message }, { status: sessions.status });
24
24
  }
25
- return Response.json({ groupId: group.id, members: group.members });
25
+ return Response.json(sessions.value);
26
26
  },
27
27
  POST: async ({ params, request }: { params: { groupId: string }; request: Request }) => {
28
28
  const body = await readJsonBody(request);
@@ -38,12 +38,12 @@ export const Route = createFileRoute("/api/groups/$groupId/sessions")({
38
38
  );
39
39
  }
40
40
 
41
- const updated = addGroupSession(params.groupId, parsed.data);
42
- if (updated === null) {
43
- return Response.json({ error: "Group not found" }, { status: 404 });
41
+ const updated = addInspectorGroupSession(params.groupId, parsed.data);
42
+ if (!updated.ok) {
43
+ return Response.json({ error: updated.message }, { status: updated.status });
44
44
  }
45
45
 
46
- return Response.json(updated);
46
+ return Response.json(updated.value);
47
47
  },
48
48
  },
49
49
  },
@@ -1,6 +1,10 @@
1
1
  import { createFileRoute } from "@tanstack/react-router";
2
2
  import { UpdateInspectorGroupInputSchema } from "../../lib/groupContract";
3
- import { deleteGroup, getGroup, updateGroup } from "../../proxy/groupStore";
3
+ import {
4
+ deleteInspectorGroup,
5
+ getInspectorGroup,
6
+ updateInspectorGroup,
7
+ } from "../../services/groups";
4
8
 
5
9
  type JsonBodyResult = { ok: true; value: unknown } | { ok: false };
6
10
  type DeleteEvidenceFlagResult = { ok: true; value: boolean | undefined } | { ok: false };
@@ -39,11 +43,11 @@ export const Route = createFileRoute("/api/groups/$groupId")({
39
43
  server: {
40
44
  handlers: {
41
45
  GET: ({ params }: { params: { groupId: string } }) => {
42
- const group = getGroup(params.groupId);
43
- if (group === null) {
44
- return Response.json({ error: "Group not found" }, { status: 404 });
46
+ const group = getInspectorGroup(params.groupId);
47
+ if (!group.ok) {
48
+ return Response.json({ error: group.message }, { status: group.status });
45
49
  }
46
- return Response.json(group);
50
+ return Response.json(group.value);
47
51
  },
48
52
  PATCH: async ({ params, request }: { params: { groupId: string }; request: Request }) => {
49
53
  const body = await readJsonBody(request);
@@ -59,12 +63,12 @@ export const Route = createFileRoute("/api/groups/$groupId")({
59
63
  );
60
64
  }
61
65
 
62
- const updated = updateGroup(params.groupId, parsed.data);
63
- if (updated === null) {
64
- return Response.json({ error: "Group not found" }, { status: 404 });
66
+ const updated = updateInspectorGroup(params.groupId, parsed.data);
67
+ if (!updated.ok) {
68
+ return Response.json({ error: updated.message }, { status: updated.status });
65
69
  }
66
70
 
67
- return Response.json(updated);
71
+ return Response.json(updated.value);
68
72
  },
69
73
  DELETE: ({ params, request }: { params: { groupId: string }; request: Request }) => {
70
74
  const deleteEvidence = readDeleteEvidenceFlag(request);
@@ -75,12 +79,14 @@ export const Route = createFileRoute("/api/groups/$groupId")({
75
79
  );
76
80
  }
77
81
 
78
- const deleted = deleteGroup(params.groupId, { deleteEvidence: deleteEvidence.value });
79
- if (deleted === null) {
80
- return Response.json({ error: "Group not found" }, { status: 404 });
82
+ const deleted = deleteInspectorGroup(params.groupId, {
83
+ deleteEvidence: deleteEvidence.value,
84
+ });
85
+ if (!deleted.ok) {
86
+ return Response.json({ error: deleted.message }, { status: deleted.status });
81
87
  }
82
88
 
83
- return Response.json(deleted);
89
+ return Response.json(deleted.value);
84
90
  },
85
91
  },
86
92
  },
@@ -1,6 +1,6 @@
1
1
  import { createFileRoute } from "@tanstack/react-router";
2
2
  import { CreateInspectorGroupInputSchema } from "../../lib/groupContract";
3
- import { createGroup, listGroups } from "../../proxy/groupStore";
3
+ import { createInspectorGroup, listInspectorGroups } from "../../services/groups";
4
4
 
5
5
  type JsonBodyResult = { ok: true; value: unknown } | { ok: false };
6
6
 
@@ -17,7 +17,7 @@ async function readJsonBody(request: Request): Promise<JsonBodyResult> {
17
17
  export const Route = createFileRoute("/api/groups")({
18
18
  server: {
19
19
  handlers: {
20
- GET: () => Response.json({ groups: listGroups() }),
20
+ GET: () => Response.json(listInspectorGroups()),
21
21
  POST: async ({ request }: { request: Request }) => {
22
22
  const body = await readJsonBody(request);
23
23
  if (!body.ok) {
@@ -32,7 +32,7 @@ export const Route = createFileRoute("/api/groups")({
32
32
  );
33
33
  }
34
34
 
35
- return Response.json(createGroup(parsed.data), { status: 201 });
35
+ return Response.json(createInspectorGroup(parsed.data), { status: 201 });
36
36
  },
37
37
  },
38
38
  },
@@ -1,7 +1,6 @@
1
1
  import { createFileRoute } from "@tanstack/react-router";
2
2
  import { EvidenceExportOptionsSchema } from "../../lib/runContract";
3
- import { exportRunEvidence, readEvidenceMarkdown } from "../../proxy/evidenceExporter";
4
- import { getRun } from "../../proxy/runStore";
3
+ import { exportInspectorRunEvidence, readInspectorRunEvidence } from "../../services/runs";
5
4
 
6
5
  type JsonBodyResult = { ok: true; value: unknown } | { ok: false };
7
6
 
@@ -19,27 +18,14 @@ export const Route = createFileRoute("/api/runs/$runId/evidence")({
19
18
  server: {
20
19
  handlers: {
21
20
  GET: ({ params }: { params: { runId: string } }) => {
22
- const run = getRun(params.runId);
23
- if (run === null) {
24
- return Response.json({ error: "Run not found" }, { status: 404 });
21
+ const evidence = readInspectorRunEvidence(params.runId);
22
+ if (!evidence.ok) {
23
+ return Response.json({ error: evidence.message }, { status: evidence.status });
25
24
  }
26
25
 
27
- const markdown = readEvidenceMarkdown(run);
28
- if (run.evidence === null || markdown === null) {
29
- return Response.json(
30
- { error: "Evidence has not been exported for this run" },
31
- { status: 404 },
32
- );
33
- }
34
-
35
- return Response.json({ run, evidence: run.evidence, markdown });
26
+ return Response.json(evidence.value);
36
27
  },
37
28
  POST: async ({ params, request }: { params: { runId: string }; request: Request }) => {
38
- const run = getRun(params.runId);
39
- if (run === null) {
40
- return Response.json({ error: "Run not found" }, { status: 404 });
41
- }
42
-
43
29
  const body = await readJsonBody(request);
44
30
  if (!body.ok) {
45
31
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
@@ -53,9 +39,16 @@ export const Route = createFileRoute("/api/runs/$runId/evidence")({
53
39
  );
54
40
  }
55
41
 
56
- return Response.json(
57
- await exportRunEvidence(run, parsed.data, new URL(request.url).origin),
42
+ const exported = await exportInspectorRunEvidence(
43
+ params.runId,
44
+ parsed.data,
45
+ new URL(request.url).origin,
58
46
  );
47
+ if (!exported.ok) {
48
+ return Response.json({ error: exported.message }, { status: exported.status });
49
+ }
50
+
51
+ return Response.json(exported.value);
59
52
  },
60
53
  },
61
54
  },
@@ -1,6 +1,6 @@
1
1
  import { createFileRoute } from "@tanstack/react-router";
2
2
  import { UpdateInspectorRunInputSchema } from "../../lib/runContract";
3
- import { getRun, updateRun } from "../../proxy/runStore";
3
+ import { getInspectorRun, updateInspectorRun } from "../../services/runs";
4
4
 
5
5
  type JsonBodyResult = { ok: true; value: unknown } | { ok: false };
6
6
 
@@ -18,11 +18,11 @@ export const Route = createFileRoute("/api/runs/$runId")({
18
18
  server: {
19
19
  handlers: {
20
20
  GET: ({ params }: { params: { runId: string } }) => {
21
- const run = getRun(params.runId);
22
- if (run === null) {
23
- return Response.json({ error: "Run not found" }, { status: 404 });
21
+ const run = getInspectorRun(params.runId);
22
+ if (!run.ok) {
23
+ return Response.json({ error: run.message }, { status: run.status });
24
24
  }
25
- return Response.json(run);
25
+ return Response.json(run.value);
26
26
  },
27
27
  PATCH: async ({ params, request }: { params: { runId: string }; request: Request }) => {
28
28
  const body = await readJsonBody(request);
@@ -38,12 +38,12 @@ export const Route = createFileRoute("/api/runs/$runId")({
38
38
  );
39
39
  }
40
40
 
41
- const updated = updateRun(params.runId, parsed.data);
42
- if (updated === null) {
43
- return Response.json({ error: "Run not found" }, { status: 404 });
41
+ const updated = updateInspectorRun(params.runId, parsed.data);
42
+ if (!updated.ok) {
43
+ return Response.json({ error: updated.message }, { status: updated.status });
44
44
  }
45
45
 
46
- return Response.json(updated);
46
+ return Response.json(updated.value);
47
47
  },
48
48
  },
49
49
  },