claudeye 0.2.2 → 0.3.0

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.

Potentially problematic release.


This version of claudeye might be problematic. Click here for more details.

Files changed (191) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +1 -0
  3. package/.next/standalone/.next/build-manifest.json +5 -5
  4. package/.next/standalone/.next/routes-manifest.json +9 -0
  5. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
  6. package/.next/standalone/.next/server/app/_global-error/page.js +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +2 -2
  10. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  15. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
  17. package/.next/standalone/.next/server/app/_not-found/page.js +1 -1
  18. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  20. package/.next/standalone/.next/server/app/_not-found.html +2 -2
  21. package/.next/standalone/.next/server/app/_not-found.rsc +5 -5
  22. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +5 -5
  23. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  24. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +5 -5
  25. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  26. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  27. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  28. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route/app-paths-manifest.json +3 -0
  29. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route/build-manifest.json +11 -0
  30. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route/server-reference-manifest.json +4 -0
  31. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +6 -0
  32. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.map +5 -0
  33. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -0
  34. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route_client-reference-manifest.js +2 -0
  35. package/.next/standalone/.next/server/app/icon.png/route.js +2 -1
  36. package/.next/standalone/.next/server/app/icon.png/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
  38. package/.next/standalone/.next/server/app/page.js +1 -1
  39. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  41. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
  42. package/.next/standalone/.next/server/app/project/[name]/page.js +1 -1
  43. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
  46. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +1 -1
  47. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +35 -5
  48. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +4 -3
  49. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  51. package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
  52. package/.next/standalone/.next/server/chunks/[root-of-the-server]__f408c708._.js +21 -0
  53. package/.next/standalone/.next/server/chunks/[root-of-the-server]__fde83e67._.js +3 -0
  54. package/.next/standalone/.next/server/chunks/ce889_server_app_api_download_[project]_[session]_route_actions_bbdd823f.js +3 -0
  55. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_64175717.js +3 -0
  56. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__070e2009._.js +1 -1
  57. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0a745465._.js +3 -0
  58. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__bc37261c._.js → [root-of-the-server]__14f58da3._.js} +2 -2
  59. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__164d9311._.js +3 -0
  60. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__2822fd21._.js +6 -0
  61. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__31b4c2fd._.js +3 -0
  62. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__45656df2._.js → [root-of-the-server]__4e339665._.js} +2 -2
  63. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__55018089._.js +3 -0
  64. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__6313e929._.js +3 -0
  65. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__7e21395a._.js +1 -1
  66. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__ee388ee0._.js +3 -0
  67. package/.next/standalone/.next/server/chunks/ssr/_0b4924bd._.js +3 -0
  68. package/.next/standalone/.next/server/chunks/ssr/{node_modules_9e089768._.js → _1404b353._.js} +2 -2
  69. package/.next/standalone/.next/server/chunks/ssr/_3d21dde5._.js +8 -0
  70. package/.next/standalone/.next/server/chunks/ssr/_fd9b1ff7._.js +3 -0
  71. package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_7769b563._.js → node_modules_next_dist_esm_eedfc1fd._.js} +2 -2
  72. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  73. package/.next/standalone/.next/server/pages/404.html +2 -2
  74. package/.next/standalone/.next/server/pages/500.html +2 -2
  75. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  76. package/.next/standalone/.next/server/server-reference-manifest.json +35 -5
  77. package/.next/standalone/.next/static/chunks/0e266948a26a3cdf.js +1 -0
  78. package/.next/standalone/.next/static/chunks/2774382cf796393c.js +4 -0
  79. package/.next/standalone/.next/static/chunks/6189ca16caad4352.js +3 -0
  80. package/.next/standalone/.next/static/chunks/8111dbe882e31821.js +1 -0
  81. package/.next/standalone/.next/static/chunks/{5a424275276f2bb9.js → bdeaeb8c9876394b.js} +1 -1
  82. package/.next/standalone/.next/static/chunks/cdbb6932218650fd.js +1 -0
  83. package/.next/standalone/.next/static/chunks/ea03555bb726c073.css +1 -0
  84. package/.next/standalone/.next/static/chunks/f091501564eb2ea3.js +4 -0
  85. package/.next/standalone/.next/static/chunks/{turbopack-2315171089e56fad.js → turbopack-fc1f23734a087d36.js} +1 -1
  86. package/.next/standalone/README.md +528 -41
  87. package/.next/standalone/app/actions/run-enrichments.ts +26 -5
  88. package/.next/standalone/app/actions/run-evals.ts +26 -5
  89. package/.next/standalone/app/actions/run-subagent-enrichments.ts +89 -0
  90. package/.next/standalone/app/actions/run-subagent-evals.ts +88 -0
  91. package/.next/standalone/app/api/download/[project]/[session]/route.ts +49 -0
  92. package/.next/standalone/app/components/copy-button.tsx +37 -0
  93. package/.next/standalone/app/components/enrichment-results-panel.tsx +33 -13
  94. package/.next/standalone/app/components/eval-results-panel.tsx +33 -13
  95. package/.next/standalone/app/components/log-viewer/entry-row.tsx +43 -14
  96. package/.next/standalone/app/components/log-viewer/queue-divider.tsx +50 -7
  97. package/.next/standalone/app/components/log-viewer/tool-input-output.tsx +13 -3
  98. package/.next/standalone/app/components/project-list.tsx +11 -11
  99. package/.next/standalone/app/components/raw-log-viewer.tsx +80 -11
  100. package/.next/standalone/app/components/refresh-button.tsx +79 -0
  101. package/.next/standalone/app/components/sessions-list.tsx +23 -14
  102. package/.next/standalone/app/project/[name]/session/[sessionId]/page.tsx +23 -12
  103. package/.next/standalone/bin/claudeye.mjs +112 -25
  104. package/.next/standalone/components/navbar.tsx +2 -0
  105. package/.next/standalone/dist/app.js +10 -4
  106. package/.next/standalone/dist/condition-registry.js +20 -0
  107. package/.next/standalone/dist/enrich-registry.js +26 -3
  108. package/.next/standalone/dist/enrich-runner.js +68 -13
  109. package/.next/standalone/dist/registry.js +26 -3
  110. package/.next/standalone/dist/runner.js +78 -20
  111. package/.next/standalone/dist/server-spawn.js +58 -34
  112. package/.next/standalone/lib/cache/hash.ts +67 -0
  113. package/.next/standalone/lib/cache/index.ts +9 -0
  114. package/.next/standalone/lib/cache/local-backend.ts +81 -0
  115. package/.next/standalone/lib/cache/manager.ts +127 -0
  116. package/.next/standalone/lib/cache/types.ts +19 -0
  117. package/.next/standalone/lib/evals/app.ts +30 -7
  118. package/.next/standalone/lib/evals/condition-registry.ts +26 -0
  119. package/.next/standalone/lib/evals/enrich-registry.ts +29 -3
  120. package/.next/standalone/lib/evals/enrich-runner.ts +68 -14
  121. package/.next/standalone/lib/evals/enrich-types.ts +6 -1
  122. package/.next/standalone/lib/evals/index.ts +3 -1
  123. package/.next/standalone/lib/evals/registry.ts +29 -4
  124. package/.next/standalone/lib/evals/runner.ts +77 -20
  125. package/.next/standalone/lib/evals/server-spawn.ts +67 -41
  126. package/.next/standalone/lib/evals/types.ts +16 -0
  127. package/.next/standalone/lib/log-format.ts +22 -1
  128. package/.next/standalone/package-lock.json +244 -308
  129. package/.next/standalone/package.json +1 -1
  130. package/.next/standalone/scripts/dev.ts +3 -1
  131. package/.next/standalone/scripts/parse-script-args.ts +30 -2
  132. package/.next/standalone/scripts/start.ts +3 -1
  133. package/.next/standalone/tsconfig.tsbuildinfo +1 -1
  134. package/README.md +528 -41
  135. package/bin/claudeye.mjs +112 -25
  136. package/dist/app.d.ts +17 -3
  137. package/dist/app.d.ts.map +1 -1
  138. package/dist/app.js +10 -4
  139. package/dist/app.js.map +1 -1
  140. package/dist/condition-registry.d.ts +9 -0
  141. package/dist/condition-registry.d.ts.map +1 -0
  142. package/dist/condition-registry.js +20 -0
  143. package/dist/condition-registry.js.map +1 -0
  144. package/dist/enrich-registry.d.ts +5 -1
  145. package/dist/enrich-registry.d.ts.map +1 -1
  146. package/dist/enrich-registry.js +26 -3
  147. package/dist/enrich-registry.js.map +1 -1
  148. package/dist/enrich-runner.d.ts +3 -3
  149. package/dist/enrich-runner.d.ts.map +1 -1
  150. package/dist/enrich-runner.js +68 -13
  151. package/dist/enrich-runner.js.map +1 -1
  152. package/dist/enrich-types.d.ts +6 -1
  153. package/dist/enrich-types.d.ts.map +1 -1
  154. package/dist/index.d.ts +2 -2
  155. package/dist/index.d.ts.map +1 -1
  156. package/dist/registry.d.ts +5 -2
  157. package/dist/registry.d.ts.map +1 -1
  158. package/dist/registry.js +26 -3
  159. package/dist/registry.js.map +1 -1
  160. package/dist/runner.d.ts +2 -2
  161. package/dist/runner.d.ts.map +1 -1
  162. package/dist/runner.js +78 -20
  163. package/dist/runner.js.map +1 -1
  164. package/dist/server-spawn.d.ts +2 -1
  165. package/dist/server-spawn.d.ts.map +1 -1
  166. package/dist/server-spawn.js +58 -34
  167. package/dist/server-spawn.js.map +1 -1
  168. package/dist/types.d.ts +14 -0
  169. package/dist/types.d.ts.map +1 -1
  170. package/package.json +1 -1
  171. package/.next/standalone/.next/server/chunks/[root-of-the-server]__24a1e50a._.js +0 -21
  172. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__32f115c9._.js +0 -6
  173. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__476a1712._.js +0 -3
  174. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__4ddcabf2._.js +0 -3
  175. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__ad593585._.js +0 -3
  176. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__afd8e13b._.js +0 -3
  177. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__dd7ee810._.js +0 -3
  178. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__ff3004de._.js +0 -3
  179. package/.next/standalone/.next/server/chunks/ssr/_53472598._.js +0 -3
  180. package/.next/standalone/.next/server/chunks/ssr/_863b6ca8._.js +0 -3
  181. package/.next/standalone/.next/server/chunks/ssr/_f7347c74._.js +0 -5
  182. package/.next/standalone/.next/static/chunks/2243ff2814e7a781.js +0 -3
  183. package/.next/standalone/.next/static/chunks/50531467396cea91.css +0 -1
  184. package/.next/standalone/.next/static/chunks/8f288c01f8d7ef2d.js +0 -1
  185. package/.next/standalone/.next/static/chunks/abab1b00b2788443.js +0 -4
  186. package/.next/standalone/.next/static/chunks/d250d7f6f0a8c325.js +0 -1
  187. package/.next/standalone/.next/static/chunks/d7a572a8b7eb1ec8.js +0 -1
  188. package/.next/standalone/.next/static/chunks/fb1b0b9da3f03023.js +0 -4
  189. /package/.next/standalone/.next/static/{LoGIEEP4cORCqcFv-Ywg0 → 5JsV7rfAEOIwNOQPaX3UP}/_buildManifest.js +0 -0
  190. /package/.next/standalone/.next/static/{LoGIEEP4cORCqcFv-Ywg0 → 5JsV7rfAEOIwNOQPaX3UP}/_clientMiddlewareManifest.json +0 -0
  191. /package/.next/standalone/.next/static/{LoGIEEP4cORCqcFv-Ywg0 → 5JsV7rfAEOIwNOQPaX3UP}/_ssgManifest.js +0 -0
@@ -1,15 +1,16 @@
1
1
  "use server";
2
2
 
3
3
  import { ensureEvalsLoaded } from "@/lib/evals/loader";
4
- import { hasEnrichers } from "@/lib/evals/enrich-registry";
4
+ import { getSessionScopedEnrichers } from "@/lib/evals/enrich-registry";
5
5
  import { runAllEnrichers } from "@/lib/evals/enrich-runner";
6
6
  import { getCachedSessionLog } from "@/lib/log-entries";
7
7
  import { calculateLogStats } from "@/lib/log-stats";
8
+ import { getCachedResult, setCachedResult } from "@/lib/cache";
8
9
  import type { EnrichRunSummary } from "@/lib/evals/enrich-types";
9
10
  import type { EvalLogEntry } from "@/lib/evals/types";
10
11
 
11
12
  export type EnrichActionResult =
12
- | { ok: true; summary: EnrichRunSummary; hasEnrichers: true }
13
+ | { ok: true; summary: EnrichRunSummary; hasEnrichers: true; cached: boolean }
13
14
  | { ok: true; hasEnrichers: false }
14
15
  | { ok: false; error: string };
15
16
 
@@ -20,20 +21,40 @@ export type EnrichActionResult =
20
21
  export async function runEnrichments(
21
22
  projectName: string,
22
23
  sessionId: string,
24
+ forceRefresh: boolean = false,
23
25
  ): Promise<EnrichActionResult> {
24
26
  try {
25
27
  await ensureEvalsLoaded();
26
28
 
27
- if (!hasEnrichers()) {
29
+ const sessionEnrichers = getSessionScopedEnrichers();
30
+ if (sessionEnrichers.length === 0) {
28
31
  return { ok: true, hasEnrichers: false };
29
32
  }
30
33
 
34
+ const registeredNames = sessionEnrichers.map((e) => e.name);
35
+
36
+ // Check cache unless force refresh requested
37
+ if (!forceRefresh) {
38
+ const cached = await getCachedResult<EnrichRunSummary>(
39
+ "enrichments",
40
+ projectName,
41
+ sessionId,
42
+ registeredNames,
43
+ );
44
+ if (cached) {
45
+ return { ok: true, summary: cached.value, hasEnrichers: true, cached: true };
46
+ }
47
+ }
48
+
31
49
  const entries = await getCachedSessionLog(projectName, sessionId);
32
50
  const stats = calculateLogStats(entries);
33
51
  // Cast is safe: LogEntry is structurally compatible with EvalLogEntry
34
- const summary = await runAllEnrichers(entries as unknown as EvalLogEntry[], stats, projectName, sessionId);
52
+ const summary = await runAllEnrichers(entries as unknown as EvalLogEntry[], stats, projectName, sessionId, sessionEnrichers, { scope: 'session' });
53
+
54
+ // Store in cache (fire-and-forget)
55
+ setCachedResult("enrichments", projectName, sessionId, summary, registeredNames);
35
56
 
36
- return { ok: true, summary, hasEnrichers: true };
57
+ return { ok: true, summary, hasEnrichers: true, cached: false };
37
58
  } catch (err) {
38
59
  return {
39
60
  ok: false,
@@ -1,14 +1,15 @@
1
1
  "use server";
2
2
 
3
3
  import { ensureEvalsLoaded } from "@/lib/evals/loader";
4
- import { hasEvals } from "@/lib/evals/registry";
4
+ import { getSessionScopedEvals } from "@/lib/evals/registry";
5
5
  import { runAllEvals } from "@/lib/evals/runner";
6
6
  import { getCachedSessionLog } from "@/lib/log-entries";
7
7
  import { calculateLogStats } from "@/lib/log-stats";
8
+ import { getCachedResult, setCachedResult } from "@/lib/cache";
8
9
  import type { EvalRunSummary, EvalLogEntry } from "@/lib/evals/types";
9
10
 
10
11
  export type EvalActionResult =
11
- | { ok: true; summary: EvalRunSummary; hasEvals: true }
12
+ | { ok: true; summary: EvalRunSummary; hasEvals: true; cached: boolean }
12
13
  | { ok: true; hasEvals: false }
13
14
  | { ok: false; error: string };
14
15
 
@@ -19,21 +20,41 @@ export type EvalActionResult =
19
20
  export async function runEvals(
20
21
  projectName: string,
21
22
  sessionId: string,
23
+ forceRefresh: boolean = false,
22
24
  ): Promise<EvalActionResult> {
23
25
  try {
24
26
  await ensureEvalsLoaded();
25
27
 
26
- if (!hasEvals()) {
28
+ const sessionEvals = getSessionScopedEvals();
29
+ if (sessionEvals.length === 0) {
27
30
  return { ok: true, hasEvals: false };
28
31
  }
29
32
 
33
+ const registeredNames = sessionEvals.map((e) => e.name);
34
+
35
+ // Check cache unless force refresh requested
36
+ if (!forceRefresh) {
37
+ const cached = await getCachedResult<EvalRunSummary>(
38
+ "evals",
39
+ projectName,
40
+ sessionId,
41
+ registeredNames,
42
+ );
43
+ if (cached) {
44
+ return { ok: true, summary: cached.value, hasEvals: true, cached: true };
45
+ }
46
+ }
47
+
30
48
  const entries = await getCachedSessionLog(projectName, sessionId);
31
49
  const stats = calculateLogStats(entries);
32
50
  // Cast is safe: LogEntry is structurally compatible with EvalLogEntry
33
51
  // (the structural types in types.ts are intentionally loose for the dist build)
34
- const summary = await runAllEvals(entries as unknown as EvalLogEntry[], stats, projectName, sessionId);
52
+ const summary = await runAllEvals(entries as unknown as EvalLogEntry[], stats, projectName, sessionId, sessionEvals, { scope: 'session' });
53
+
54
+ // Store in cache (fire-and-forget)
55
+ setCachedResult("evals", projectName, sessionId, summary, registeredNames);
35
56
 
36
- return { ok: true, summary, hasEvals: true };
57
+ return { ok: true, summary, hasEvals: true, cached: false };
37
58
  } catch (err) {
38
59
  return {
39
60
  ok: false,
@@ -0,0 +1,89 @@
1
+ "use server";
2
+
3
+ import { ensureEvalsLoaded } from "@/lib/evals/loader";
4
+ import { getSubagentScopedEnrichers } from "@/lib/evals/enrich-registry";
5
+ import { runAllEnrichers } from "@/lib/evals/enrich-runner";
6
+ import { loadSubagentLog } from "@/app/actions/load-subagent-log";
7
+ import { calculateLogStats } from "@/lib/log-stats";
8
+ import { getCachedResult, setCachedResult, hashSubagentFile } from "@/lib/cache";
9
+ import type { EvalLogEntry } from "@/lib/evals/types";
10
+ import type { EnrichRunSummary } from "@/lib/evals/enrich-types";
11
+
12
+ export type SubagentEnrichActionResult =
13
+ | { ok: true; summary: EnrichRunSummary; hasEnrichers: true; cached: boolean }
14
+ | { ok: true; hasEnrichers: false }
15
+ | { ok: false; error: string };
16
+
17
+ /**
18
+ * Server action that runs subagent-scoped enrichers against a subagent's log entries.
19
+ */
20
+ export async function runSubagentEnrichments(
21
+ projectName: string,
22
+ sessionId: string,
23
+ agentId: string,
24
+ subagentType?: string,
25
+ subagentDescription?: string,
26
+ forceRefresh: boolean = false,
27
+ ): Promise<SubagentEnrichActionResult> {
28
+ try {
29
+ await ensureEvalsLoaded();
30
+
31
+ const subagentEnrichers = getSubagentScopedEnrichers(subagentType);
32
+ if (subagentEnrichers.length === 0) {
33
+ return { ok: true, hasEnrichers: false };
34
+ }
35
+
36
+ const registeredNames = subagentEnrichers.map((e) => e.name);
37
+ const sessionKey = `${sessionId}/agent-${agentId}`;
38
+ const contentHash = await hashSubagentFile(projectName, sessionId, agentId);
39
+
40
+ // Check cache unless force refresh requested
41
+ if (!forceRefresh && contentHash) {
42
+ const cached = await getCachedResult<EnrichRunSummary>(
43
+ "enrichments",
44
+ projectName,
45
+ sessionKey,
46
+ registeredNames,
47
+ contentHash,
48
+ );
49
+ if (cached) {
50
+ return { ok: true, summary: cached.value, hasEnrichers: true, cached: true };
51
+ }
52
+ }
53
+
54
+ const logResult = await loadSubagentLog(projectName, sessionId, agentId);
55
+ if (!logResult.ok) {
56
+ return { ok: false, error: logResult.error };
57
+ }
58
+
59
+ const entries = logResult.entries;
60
+ const stats = calculateLogStats(entries);
61
+
62
+ const summary = await runAllEnrichers(
63
+ entries as unknown as EvalLogEntry[],
64
+ stats,
65
+ projectName,
66
+ sessionId,
67
+ subagentEnrichers,
68
+ {
69
+ scope: 'subagent',
70
+ subagentId: agentId,
71
+ subagentType,
72
+ subagentDescription,
73
+ parentSessionId: sessionId,
74
+ },
75
+ );
76
+
77
+ // Store in cache (fire-and-forget)
78
+ if (contentHash) {
79
+ setCachedResult("enrichments", projectName, sessionKey, summary, registeredNames, contentHash);
80
+ }
81
+
82
+ return { ok: true, summary, hasEnrichers: true, cached: false };
83
+ } catch (err) {
84
+ return {
85
+ ok: false,
86
+ error: err instanceof Error ? err.message : String(err),
87
+ };
88
+ }
89
+ }
@@ -0,0 +1,88 @@
1
+ "use server";
2
+
3
+ import { ensureEvalsLoaded } from "@/lib/evals/loader";
4
+ import { getSubagentScopedEvals } from "@/lib/evals/registry";
5
+ import { runAllEvals } from "@/lib/evals/runner";
6
+ import { loadSubagentLog } from "@/app/actions/load-subagent-log";
7
+ import { calculateLogStats } from "@/lib/log-stats";
8
+ import { getCachedResult, setCachedResult, hashSubagentFile } from "@/lib/cache";
9
+ import type { EvalRunSummary, EvalLogEntry } from "@/lib/evals/types";
10
+
11
+ export type SubagentEvalActionResult =
12
+ | { ok: true; summary: EvalRunSummary; hasEvals: true; cached: boolean }
13
+ | { ok: true; hasEvals: false }
14
+ | { ok: false; error: string };
15
+
16
+ /**
17
+ * Server action that runs subagent-scoped evals against a subagent's log entries.
18
+ */
19
+ export async function runSubagentEvals(
20
+ projectName: string,
21
+ sessionId: string,
22
+ agentId: string,
23
+ subagentType?: string,
24
+ subagentDescription?: string,
25
+ forceRefresh: boolean = false,
26
+ ): Promise<SubagentEvalActionResult> {
27
+ try {
28
+ await ensureEvalsLoaded();
29
+
30
+ const subagentEvals = getSubagentScopedEvals(subagentType);
31
+ if (subagentEvals.length === 0) {
32
+ return { ok: true, hasEvals: false };
33
+ }
34
+
35
+ const registeredNames = subagentEvals.map((e) => e.name);
36
+ const sessionKey = `${sessionId}/agent-${agentId}`;
37
+ const contentHash = await hashSubagentFile(projectName, sessionId, agentId);
38
+
39
+ // Check cache unless force refresh requested
40
+ if (!forceRefresh && contentHash) {
41
+ const cached = await getCachedResult<EvalRunSummary>(
42
+ "evals",
43
+ projectName,
44
+ sessionKey,
45
+ registeredNames,
46
+ contentHash,
47
+ );
48
+ if (cached) {
49
+ return { ok: true, summary: cached.value, hasEvals: true, cached: true };
50
+ }
51
+ }
52
+
53
+ const logResult = await loadSubagentLog(projectName, sessionId, agentId);
54
+ if (!logResult.ok) {
55
+ return { ok: false, error: logResult.error };
56
+ }
57
+
58
+ const entries = logResult.entries;
59
+ const stats = calculateLogStats(entries);
60
+
61
+ const summary = await runAllEvals(
62
+ entries as unknown as EvalLogEntry[],
63
+ stats,
64
+ projectName,
65
+ sessionId,
66
+ subagentEvals,
67
+ {
68
+ scope: 'subagent',
69
+ subagentId: agentId,
70
+ subagentType,
71
+ subagentDescription,
72
+ parentSessionId: sessionId,
73
+ },
74
+ );
75
+
76
+ // Store in cache (fire-and-forget)
77
+ if (contentHash) {
78
+ setCachedResult("evals", projectName, sessionKey, summary, registeredNames, contentHash);
79
+ }
80
+
81
+ return { ok: true, summary, hasEvals: true, cached: false };
82
+ } catch (err) {
83
+ return {
84
+ ok: false,
85
+ error: err instanceof Error ? err.message : String(err),
86
+ };
87
+ }
88
+ }
@@ -0,0 +1,49 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { readFile } from "fs/promises";
3
+ import { join, relative } from "path";
4
+ import { getClaudeProjectsPath } from "@/lib/paths";
5
+
6
+ const UUID_RE = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
7
+ const PATH_TRAVERSAL_RE = /(^|[\\/])\.\.($|[\\/])/;
8
+
9
+ function jsonError(message: string, status: number): NextResponse {
10
+ return NextResponse.json({ error: message }, { status });
11
+ }
12
+
13
+ export async function GET(
14
+ _request: NextRequest,
15
+ { params }: { params: Promise<{ project: string; session: string }> }
16
+ ): Promise<NextResponse> {
17
+ const { project, session } = await params;
18
+
19
+ if (!UUID_RE.test(session)) {
20
+ return jsonError("Invalid session ID", 400);
21
+ }
22
+
23
+ if (!project || PATH_TRAVERSAL_RE.test(project)) {
24
+ return jsonError("Invalid project name", 400);
25
+ }
26
+
27
+ const projectsPath = getClaudeProjectsPath();
28
+ const filePath = join(projectsPath, project, `${session}.jsonl`);
29
+
30
+ if (relative(projectsPath, filePath).startsWith("..")) {
31
+ return jsonError("Path traversal detected", 400);
32
+ }
33
+
34
+ try {
35
+ const content = await readFile(filePath, "utf-8");
36
+ return new NextResponse(content, {
37
+ headers: {
38
+ "Content-Type": "application/x-ndjson",
39
+ "Content-Disposition": `attachment; filename="${session}.jsonl"`,
40
+ },
41
+ });
42
+ } catch (e) {
43
+ const code = (e as NodeJS.ErrnoException).code;
44
+ if (code === "ENOENT") {
45
+ return jsonError("Session log not found", 404);
46
+ }
47
+ return jsonError("Failed to read session log", 500);
48
+ }
49
+ }
@@ -0,0 +1,37 @@
1
+ "use client";
2
+
3
+ import { useState, useCallback } from "react";
4
+ import { Copy, Check } from "lucide-react";
5
+ import { cn } from "@/lib/utils";
6
+
7
+ interface CopyButtonProps {
8
+ text: string;
9
+ className?: string;
10
+ }
11
+
12
+ export function CopyButton({ text, className }: CopyButtonProps) {
13
+ const [copied, setCopied] = useState(false);
14
+
15
+ const handleCopy = useCallback(async () => {
16
+ await navigator.clipboard.writeText(text);
17
+ setCopied(true);
18
+ setTimeout(() => setCopied(false), 2000);
19
+ }, [text]);
20
+
21
+ return (
22
+ <button
23
+ onClick={handleCopy}
24
+ title="Copy to clipboard"
25
+ className={cn(
26
+ "inline-flex items-center justify-center rounded p-1 text-muted-foreground hover:text-foreground hover:bg-muted/80 transition-colors",
27
+ className
28
+ )}
29
+ >
30
+ {copied ? (
31
+ <Check className="w-3.5 h-3.5 text-green-500" />
32
+ ) : (
33
+ <Copy className="w-3.5 h-3.5" />
34
+ )}
35
+ </button>
36
+ );
37
+ }
@@ -9,11 +9,16 @@ import {
9
9
  RefreshCw,
10
10
  } from "lucide-react";
11
11
  import { runEnrichments } from "@/app/actions/run-enrichments";
12
+ import { runSubagentEnrichments } from "@/app/actions/run-subagent-enrichments";
12
13
  import type { EnrichRunSummary, EnrichRunResult } from "@/lib/evals/enrich-types";
13
14
 
14
15
  interface EnrichmentResultsPanelProps {
15
16
  projectName: string;
16
17
  sessionId: string;
18
+ agentId?: string;
19
+ subagentType?: string;
20
+ subagentDescription?: string;
21
+ compact?: boolean;
17
22
  }
18
23
 
19
24
  function formatValue(value: string | number | boolean): string {
@@ -60,14 +65,15 @@ function EnricherGroup({ result }: { result: EnrichRunResult }) {
60
65
  );
61
66
  }
62
67
 
63
- export default function EnrichmentResultsPanel({ projectName, sessionId }: EnrichmentResultsPanelProps) {
68
+ export default function EnrichmentResultsPanel({ projectName, sessionId, agentId, subagentType, subagentDescription, compact }: EnrichmentResultsPanelProps) {
64
69
  const [summary, setSummary] = useState<EnrichRunSummary | null>(null);
65
70
  const [loading, setLoading] = useState(false);
66
71
  const [error, setError] = useState<string | null>(null);
67
72
  const [noEnrichers, setNoEnrichers] = useState(false);
73
+ const [cached, setCached] = useState(false);
68
74
  const abortRef = useRef<AbortController | null>(null);
69
75
 
70
- const run = useCallback(async () => {
76
+ const run = useCallback(async (forceRefresh = false) => {
71
77
  abortRef.current?.abort();
72
78
  const controller = new AbortController();
73
79
  abortRef.current = controller;
@@ -75,7 +81,9 @@ export default function EnrichmentResultsPanel({ projectName, sessionId }: Enric
75
81
  setLoading(true);
76
82
  setError(null);
77
83
  try {
78
- const result = await runEnrichments(projectName, sessionId);
84
+ const result = agentId
85
+ ? await runSubagentEnrichments(projectName, sessionId, agentId, subagentType, subagentDescription, forceRefresh)
86
+ : await runEnrichments(projectName, sessionId, forceRefresh);
79
87
  if (controller.signal.aborted) return;
80
88
  if (!result.ok) {
81
89
  setError(result.error);
@@ -83,6 +91,7 @@ export default function EnrichmentResultsPanel({ projectName, sessionId }: Enric
83
91
  setNoEnrichers(true);
84
92
  } else {
85
93
  setSummary(result.summary);
94
+ setCached(result.cached);
86
95
  }
87
96
  } catch (e) {
88
97
  if (controller.signal.aborted) return;
@@ -92,11 +101,11 @@ export default function EnrichmentResultsPanel({ projectName, sessionId }: Enric
92
101
  setLoading(false);
93
102
  }
94
103
  }
95
- }, [projectName, sessionId]);
104
+ }, [projectName, sessionId, agentId, subagentType, subagentDescription]);
96
105
 
97
- // Auto-run on mount; abort on unmount
106
+ // Auto-run on mount (uses cache); abort on unmount
98
107
  useEffect(() => {
99
- run();
108
+ run(false);
100
109
  return () => {
101
110
  abortRef.current?.abort();
102
111
  };
@@ -105,11 +114,14 @@ export default function EnrichmentResultsPanel({ projectName, sessionId }: Enric
105
114
  // No enrichers registered — render nothing
106
115
  if (noEnrichers) return null;
107
116
 
117
+ const panelPadding = compact ? "p-2.5" : "p-4";
118
+ const fontSize = compact ? "text-xs" : "text-sm";
119
+
108
120
  // Loading state before first result
109
121
  if (loading && !summary) {
110
122
  return (
111
- <div className="bg-card border border-border rounded-lg p-4">
112
- <div className="flex items-center gap-2 text-sm text-muted-foreground">
123
+ <div className={`bg-card border border-border rounded-lg ${panelPadding}`}>
124
+ <div className={`flex items-center gap-2 ${fontSize} text-muted-foreground`}>
113
125
  <RefreshCw className="w-4 h-4 animate-spin" />
114
126
  Running enrichments...
115
127
  </div>
@@ -120,8 +132,8 @@ export default function EnrichmentResultsPanel({ projectName, sessionId }: Enric
120
132
  // Error state
121
133
  if (error && !summary) {
122
134
  return (
123
- <div className="bg-card border border-border rounded-lg p-4">
124
- <div className="flex items-center gap-2 text-sm text-destructive">
135
+ <div className={`bg-card border border-border rounded-lg ${panelPadding}`}>
136
+ <div className={`flex items-center gap-2 ${fontSize} text-destructive`}>
125
137
  <AlertTriangle className="w-4 h-4" />
126
138
  {error}
127
139
  </div>
@@ -131,8 +143,11 @@ export default function EnrichmentResultsPanel({ projectName, sessionId }: Enric
131
143
 
132
144
  if (!summary) return null;
133
145
 
146
+ const visibleResults = summary.results.filter((r) => !r.skipped);
147
+ if (visibleResults.length === 0) return null;
148
+
134
149
  return (
135
- <div className="bg-card border border-border rounded-lg p-4 space-y-3">
150
+ <div className={`bg-card border border-border rounded-lg ${panelPadding} space-y-3`}>
136
151
  {/* Header */}
137
152
  <div className="flex items-center justify-between">
138
153
  <div className="flex items-center gap-2">
@@ -149,9 +164,14 @@ export default function EnrichmentResultsPanel({ projectName, sessionId }: Enric
149
164
  <div className="flex items-center gap-1 text-xs text-muted-foreground">
150
165
  <Clock className="w-3.5 h-3.5" />
151
166
  <span>{summary.totalDurationMs}ms</span>
167
+ {cached && (
168
+ <span className="text-[10px] font-medium text-muted-foreground/70 bg-muted px-1.5 py-0.5 rounded">
169
+ cached
170
+ </span>
171
+ )}
152
172
  </div>
153
173
  <button
154
- onClick={run}
174
+ onClick={() => run(true)}
155
175
  disabled={loading}
156
176
  className="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded border border-primary/30 bg-primary/10 text-primary hover:bg-primary/20 transition-colors disabled:opacity-50"
157
177
  >
@@ -167,7 +187,7 @@ export default function EnrichmentResultsPanel({ projectName, sessionId }: Enric
167
187
 
168
188
  {/* Enricher groups */}
169
189
  <div className="space-y-3 divide-y divide-border/50">
170
- {summary.results.map((result) => (
190
+ {visibleResults.map((result) => (
171
191
  <div key={result.name} className="pt-2 first:pt-0">
172
192
  <EnricherGroup result={result} />
173
193
  </div>
@@ -11,11 +11,16 @@ import {
11
11
  RefreshCw,
12
12
  } from "lucide-react";
13
13
  import { runEvals } from "@/app/actions/run-evals";
14
+ import { runSubagentEvals } from "@/app/actions/run-subagent-evals";
14
15
  import type { EvalRunSummary, EvalRunResult } from "@/lib/evals/types";
15
16
 
16
17
  interface EvalResultsPanelProps {
17
18
  projectName: string;
18
19
  sessionId: string;
20
+ agentId?: string;
21
+ subagentType?: string;
22
+ subagentDescription?: string;
23
+ compact?: boolean;
19
24
  }
20
25
 
21
26
  function ScoreBar({ score }: { score: number }) {
@@ -72,14 +77,15 @@ function EvalResultRow({ result }: { result: EvalRunResult }) {
72
77
  );
73
78
  }
74
79
 
75
- export default function EvalResultsPanel({ projectName, sessionId }: EvalResultsPanelProps) {
80
+ export default function EvalResultsPanel({ projectName, sessionId, agentId, subagentType, subagentDescription, compact }: EvalResultsPanelProps) {
76
81
  const [summary, setSummary] = useState<EvalRunSummary | null>(null);
77
82
  const [loading, setLoading] = useState(false);
78
83
  const [error, setError] = useState<string | null>(null);
79
84
  const [noEvals, setNoEvals] = useState(false);
85
+ const [cached, setCached] = useState(false);
80
86
  const abortRef = useRef<AbortController | null>(null);
81
87
 
82
- const run = useCallback(async () => {
88
+ const run = useCallback(async (forceRefresh = false) => {
83
89
  // Abort any in-flight request
84
90
  abortRef.current?.abort();
85
91
  const controller = new AbortController();
@@ -88,7 +94,9 @@ export default function EvalResultsPanel({ projectName, sessionId }: EvalResults
88
94
  setLoading(true);
89
95
  setError(null);
90
96
  try {
91
- const result = await runEvals(projectName, sessionId);
97
+ const result = agentId
98
+ ? await runSubagentEvals(projectName, sessionId, agentId, subagentType, subagentDescription, forceRefresh)
99
+ : await runEvals(projectName, sessionId, forceRefresh);
92
100
  if (controller.signal.aborted) return;
93
101
  if (!result.ok) {
94
102
  setError(result.error);
@@ -96,6 +104,7 @@ export default function EvalResultsPanel({ projectName, sessionId }: EvalResults
96
104
  setNoEvals(true);
97
105
  } else {
98
106
  setSummary(result.summary);
107
+ setCached(result.cached);
99
108
  }
100
109
  } catch (e) {
101
110
  if (controller.signal.aborted) return;
@@ -105,11 +114,11 @@ export default function EvalResultsPanel({ projectName, sessionId }: EvalResults
105
114
  setLoading(false);
106
115
  }
107
116
  }
108
- }, [projectName, sessionId]);
117
+ }, [projectName, sessionId, agentId, subagentType, subagentDescription]);
109
118
 
110
- // Auto-run on mount; abort on unmount
119
+ // Auto-run on mount (uses cache); abort on unmount
111
120
  useEffect(() => {
112
- run();
121
+ run(false);
113
122
  return () => {
114
123
  abortRef.current?.abort();
115
124
  };
@@ -118,11 +127,14 @@ export default function EvalResultsPanel({ projectName, sessionId }: EvalResults
118
127
  // No evals registered — render nothing
119
128
  if (noEvals) return null;
120
129
 
130
+ const panelPadding = compact ? "p-2.5" : "p-4";
131
+ const fontSize = compact ? "text-xs" : "text-sm";
132
+
121
133
  // Loading state before first result
122
134
  if (loading && !summary) {
123
135
  return (
124
- <div className="bg-card border border-border rounded-lg p-4">
125
- <div className="flex items-center gap-2 text-sm text-muted-foreground">
136
+ <div className={`bg-card border border-border rounded-lg ${panelPadding}`}>
137
+ <div className={`flex items-center gap-2 ${fontSize} text-muted-foreground`}>
126
138
  <RefreshCw className="w-4 h-4 animate-spin" />
127
139
  Running evals...
128
140
  </div>
@@ -133,8 +145,8 @@ export default function EvalResultsPanel({ projectName, sessionId }: EvalResults
133
145
  // Error state
134
146
  if (error && !summary) {
135
147
  return (
136
- <div className="bg-card border border-border rounded-lg p-4">
137
- <div className="flex items-center gap-2 text-sm text-destructive">
148
+ <div className={`bg-card border border-border rounded-lg ${panelPadding}`}>
149
+ <div className={`flex items-center gap-2 ${fontSize} text-destructive`}>
138
150
  <AlertTriangle className="w-4 h-4" />
139
151
  {error}
140
152
  </div>
@@ -144,8 +156,11 @@ export default function EvalResultsPanel({ projectName, sessionId }: EvalResults
144
156
 
145
157
  if (!summary) return null;
146
158
 
159
+ const visibleResults = summary.results.filter((r) => !r.skipped);
160
+ if (visibleResults.length === 0) return null;
161
+
147
162
  return (
148
- <div className="bg-card border border-border rounded-lg p-4 space-y-3">
163
+ <div className={`bg-card border border-border rounded-lg ${panelPadding} space-y-3`}>
149
164
  {/* Header */}
150
165
  <div className="flex items-center justify-between">
151
166
  <div className="flex items-center gap-2">
@@ -153,7 +168,7 @@ export default function EvalResultsPanel({ projectName, sessionId }: EvalResults
153
168
  <span className="text-sm font-medium">Eval Results</span>
154
169
  </div>
155
170
  <button
156
- onClick={run}
171
+ onClick={() => run(true)}
157
172
  disabled={loading}
158
173
  className="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded border border-primary/30 bg-primary/10 text-primary hover:bg-primary/20 transition-colors disabled:opacity-50"
159
174
  >
@@ -190,12 +205,17 @@ export default function EvalResultsPanel({ projectName, sessionId }: EvalResults
190
205
  <div className="flex items-center gap-1.5 ml-auto">
191
206
  <Clock className="w-3.5 h-3.5 text-muted-foreground" />
192
207
  <span className="text-muted-foreground">{summary.totalDurationMs}ms</span>
208
+ {cached && (
209
+ <span className="text-[10px] font-medium text-muted-foreground/70 bg-muted px-1.5 py-0.5 rounded">
210
+ cached
211
+ </span>
212
+ )}
193
213
  </div>
194
214
  </div>
195
215
 
196
216
  {/* Results list */}
197
217
  <div className="divide-y divide-border/50">
198
- {summary.results.map((result) => (
218
+ {visibleResults.map((result) => (
199
219
  <EvalResultRow key={result.name} result={result} />
200
220
  ))}
201
221
  </div>