@percepta/kaizen 0.9.0 → 0.10.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.
Files changed (81) hide show
  1. package/dist/dashboard/pages/api/langfuse-action.js +9 -2
  2. package/dist/dashboard/pages/api/langfuse-action.js.map +1 -1
  3. package/dist/dashboard/pages/api/langfuse-dataset-item.js +5 -2
  4. package/dist/dashboard/pages/api/langfuse-dataset-item.js.map +1 -1
  5. package/dist/dashboard/pages/api/langfuse-dataset-mutation.js +1 -1
  6. package/dist/dashboard/pages/api/langfuse-dataset-mutation.js.map +1 -1
  7. package/dist/dashboard/pages/api/langfuse-dataset.js.map +1 -1
  8. package/dist/dashboard/pages/api/langfuse-datasets.js.map +1 -1
  9. package/dist/dashboard/pages/api/langfuse-trace-memberships.js +1 -1
  10. package/dist/dashboard/pages/api/langfuse-trace-memberships.js.map +1 -1
  11. package/dist/dashboard/pages/api/langfuse-trace.js.map +1 -1
  12. package/dist/dashboard/pages/api/langfuse-traces.js.map +1 -1
  13. package/dist/dashboard/pages/api/linear-ideas.js.map +1 -1
  14. package/dist/dashboard/pages/api/run-events.js.map +1 -1
  15. package/dist/dashboard/pages/api/run-failures.js.map +1 -1
  16. package/dist/dashboard/pages/api/run-traces.js.map +1 -1
  17. package/dist/dashboard/pages/api/runs.js.map +1 -1
  18. package/dist/dashboard/pages/api/systems.js +1 -1
  19. package/dist/dashboard/pages/api/systems.js.map +1 -1
  20. package/dist/dashboard/pages/api/trace-renderer-version.js +10 -3
  21. package/dist/dashboard/pages/api/trace-renderer-version.js.map +1 -1
  22. package/dist/dashboard/pages/api/trace-renderer.js +12 -26
  23. package/dist/dashboard/pages/api/trace-renderer.js.map +1 -1
  24. package/dist/dashboard/src/lib/bundle-custom-renderer.js +167 -0
  25. package/dist/dashboard/src/lib/bundle-custom-renderer.js.map +1 -0
  26. package/dist/dashboard/src/lib/custom-renderer-files.js.map +1 -1
  27. package/dist/dashboard/src/lib/custom-renderer-metadata.js.map +1 -1
  28. package/dist/dashboard/src/lib/custom-view-paths.js.map +1 -1
  29. package/dist/dashboard/src/lib/dataset-item-labeling.js +20 -0
  30. package/dist/dashboard/src/lib/dataset-item-labeling.js.map +1 -0
  31. package/dist/dashboard/src/lib/env.js.map +1 -1
  32. package/dist/dashboard/src/lib/langfuse-cache.js.map +1 -1
  33. package/dist/dashboard/src/lib/langfuse-creds.js.map +1 -1
  34. package/dist/dashboard/src/lib/langfuse-demo.js.map +1 -1
  35. package/dist/dashboard/src/lib/langfuse-errors.js.map +1 -1
  36. package/dist/dashboard/src/lib/langfuse-helpers.js.map +1 -1
  37. package/dist/dashboard/src/lib/run-api.js.map +1 -1
  38. package/dist/dashboard/src/lib/run-store.js.map +1 -1
  39. package/dist/dashboard/src/lib/types.js.map +1 -1
  40. package/dist/dashboard/src/lib/workspace-env.js.map +1 -1
  41. package/dist/dashboard/src/lib/workspace.js.map +1 -1
  42. package/dist/index.js.map +1 -1
  43. package/dist/langfuse.d.ts.map +1 -1
  44. package/dist/langfuse.js.map +1 -1
  45. package/dist/package.js +2 -2
  46. package/dist/shared/env-file.js.map +1 -1
  47. package/dist/shared/linear-ideas.js.map +1 -1
  48. package/dist/shared/linear-issue.js.map +1 -1
  49. package/dist/shared/view-types.d.ts.map +1 -1
  50. package/dist/shared/workspace-paths.js.map +1 -1
  51. package/dist/src/commands/create-view.js.map +1 -1
  52. package/dist/src/commands/guide.js.map +1 -1
  53. package/dist/src/commands/ideas.js.map +1 -1
  54. package/dist/src/commands/init-system.js.map +1 -1
  55. package/dist/src/commands/init.js.map +1 -1
  56. package/dist/src/commands/log.js.map +1 -1
  57. package/dist/src/commands/rebuild.js.map +1 -1
  58. package/dist/src/commands/run.js.map +1 -1
  59. package/dist/src/commands/studio.js.map +1 -1
  60. package/dist/src/lib/bootstrap.js.map +1 -1
  61. package/dist/src/lib/cli.js.map +1 -1
  62. package/dist/src/lib/events.js +2 -0
  63. package/dist/src/lib/events.js.map +1 -1
  64. package/dist/src/lib/fs-utils.js.map +1 -1
  65. package/dist/src/lib/leaderboard.js.map +1 -1
  66. package/dist/src/lib/parse-args.js.map +1 -1
  67. package/dist/src/lib/paths.js.map +1 -1
  68. package/dist/src/lib/promotion.js.map +1 -1
  69. package/dist/src/lib/prompt.js.map +1 -1
  70. package/dist/src/lib/run-dir.js.map +1 -1
  71. package/dist/src/lib/runner.js.map +1 -1
  72. package/dist/src/lib/system.js.map +1 -1
  73. package/dist/studio/client/assets/index-Dc4zGLjQ.css +1 -0
  74. package/dist/studio/client/assets/index-ElL5OoiH.js +9 -0
  75. package/dist/studio/client/index.html +2 -2
  76. package/dist/studio/server.d.ts.map +1 -1
  77. package/dist/studio/server.js.map +1 -1
  78. package/dist/types.d.ts.map +1 -1
  79. package/package.json +3 -3
  80. package/dist/studio/client/assets/index-Bwj0gucs.css +0 -1
  81. package/dist/studio/client/assets/index-DKAiSaYs.js +0 -9
@@ -1 +1 @@
1
- {"version":3,"file":"langfuse-traces.js","names":[],"sources":["../../../../dashboard/pages/api/langfuse-traces.ts"],"sourcesContent":["import { createTtlCache } from \"../../src/lib/langfuse-cache\";\nimport {\n langfuseStatus,\n resolveLangfuseCreds,\n} from \"../../src/lib/langfuse-creds\";\nimport {\n DEMO_CONNECTION,\n getDemoTraces,\n isDemoMode,\n} from \"../../src/lib/langfuse-demo\";\nimport {\n formatLangfuseHttpError,\n formatLangfuseRequestError,\n} from \"../../src/lib/langfuse-errors\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\nconst cache = createTtlCache({ ttlMs: 15_000 });\n\nexport default async function handler(req: StudioRequest, res: StudioResponse) {\n const page =\n typeof req.query.page === \"string\"\n ? Number.parseInt(req.query.page, 10)\n : 1;\n const limit =\n typeof req.query.limit === \"string\"\n ? Number.parseInt(req.query.limit, 10)\n : 50;\n if (\n !Number.isInteger(page) ||\n page < 1 ||\n !Number.isInteger(limit) ||\n limit < 1\n ) {\n res.status(400).json({ error: \"page and limit must be positive integers\" });\n return;\n }\n const tags =\n typeof req.query.tags === \"string\" && req.query.tags.trim()\n ? req.query.tags.split(\",\").map((t) => t.trim())\n : Array.isArray(req.query.tags)\n ? req.query.tags.filter((t): t is string => typeof t === \"string\")\n : undefined;\n const name =\n typeof req.query.name === \"string\" && req.query.name.trim()\n ? req.query.name.trim()\n : undefined;\n\n // Default to last 7 days when no fromTimestamp is supplied.\n const fromTimestamp = parseIsoTimestamp(req.query.fromTimestamp);\n const toTimestamp = parseIsoTimestamp(req.query.toTimestamp);\n const resolvedFrom =\n fromTimestamp ??\n (() => {\n // Round to the nearest minute so the cache key is stable across requests.\n const ms = Date.now() - 7 * 24 * 60 * 60 * 1000;\n return new Date(ms - (ms % 60_000)).toISOString();\n })();\n\n const creds = resolveLangfuseCreds();\n if (!creds.host || !creds.auth) {\n if (isDemoMode()) {\n const result = getDemoTraces(\n page,\n limit,\n tags,\n resolvedFrom,\n toTimestamp,\n );\n res.status(200).json({ ...result, connection: DEMO_CONNECTION });\n return;\n }\n res.status(200).json({ data: [], meta: {}, connection: creds.connection });\n return;\n }\n\n const params = new URLSearchParams();\n params.set(\"page\", String(page));\n params.set(\"limit\", String(limit));\n params.set(\"orderBy\", \"timestamp.desc\");\n if (tags) {\n for (const tag of tags) {\n params.append(\"tags\", tag);\n }\n }\n if (name) {\n params.set(\"name\", name);\n }\n params.set(\"fromTimestamp\", resolvedFrom);\n if (toTimestamp) {\n params.set(\"toTimestamp\", toTimestamp);\n }\n\n const noCache = req.query.noCache === \"1\";\n const cacheKey = `traces:${params.toString()}`;\n if (!noCache) {\n const cached = cache.get(cacheKey);\n if (cached !== undefined) {\n res.status(200).json(cached);\n return;\n }\n }\n\n const base = creds.host.replace(/\\/$/, \"\");\n const url = `${base}/api/public/traces?${params.toString()}`;\n\n // Retry once on 5xx with 500ms backoff.\n const maxAttempts = 2;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 10_000);\n try {\n const lfRes = await fetch(url, {\n headers: { Authorization: `Basic ${creds.auth}` },\n signal: controller.signal,\n });\n if (!lfRes.ok) {\n // Retry on 5xx\n if (lfRes.status >= 500 && attempt < maxAttempts) {\n clearTimeout(timeout);\n await new Promise((r) => setTimeout(r, 500));\n continue;\n }\n const detail = await formatLangfuseHttpError(lfRes, \"traces\");\n const state =\n lfRes.status === 401 || lfRes.status === 403\n ? \"auth_failed\"\n : \"query_failed\";\n res.status(lfRes.status).json({\n error: detail,\n connection: langfuseStatus(state as \"auth_failed\" | \"query_failed\", {\n detail,\n }),\n });\n return;\n }\n const body = await lfRes.json();\n const result = {\n data: body.data ?? [],\n meta: body.meta ?? {},\n connection: creds.connection,\n };\n cache.set(cacheKey, result);\n res.status(200).json(result);\n return;\n } catch (err) {\n if (attempt < maxAttempts) {\n clearTimeout(timeout);\n await new Promise((r) => setTimeout(r, 500));\n continue;\n }\n const detail = formatLangfuseRequestError(err);\n res.status(502).json({\n error: detail,\n connection: langfuseStatus(\"network_error\", { detail }),\n });\n return;\n } finally {\n clearTimeout(timeout);\n }\n }\n}\n\nfunction parseIsoTimestamp(value: unknown): string | undefined {\n if (typeof value !== \"string\" || !value.trim()) return undefined;\n const parsed = new Date(value);\n return Number.isNaN(parsed.getTime()) ? undefined : parsed.toISOString();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"langfuse-traces.js","names":[],"sources":["../../../../dashboard/pages/api/langfuse-traces.ts"],"sourcesContent":["import { createTtlCache } from \"../../src/lib/langfuse-cache\";\nimport {\n langfuseStatus,\n resolveLangfuseCreds,\n} from \"../../src/lib/langfuse-creds\";\nimport {\n DEMO_CONNECTION,\n getDemoTraces,\n isDemoMode,\n} from \"../../src/lib/langfuse-demo\";\nimport {\n formatLangfuseHttpError,\n formatLangfuseRequestError,\n} from \"../../src/lib/langfuse-errors\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\nconst cache = createTtlCache({ ttlMs: 15_000 });\n\nexport default async function handler(req: StudioRequest, res: StudioResponse) {\n const page =\n typeof req.query.page === \"string\"\n ? Number.parseInt(req.query.page, 10)\n : 1;\n const limit =\n typeof req.query.limit === \"string\"\n ? Number.parseInt(req.query.limit, 10)\n : 50;\n if (\n !Number.isInteger(page) ||\n page < 1 ||\n !Number.isInteger(limit) ||\n limit < 1\n ) {\n res.status(400).json({ error: \"page and limit must be positive integers\" });\n return;\n }\n const tags =\n typeof req.query.tags === \"string\" && req.query.tags.trim()\n ? req.query.tags.split(\",\").map((t) => t.trim())\n : Array.isArray(req.query.tags)\n ? req.query.tags.filter((t): t is string => typeof t === \"string\")\n : undefined;\n const name =\n typeof req.query.name === \"string\" && req.query.name.trim()\n ? req.query.name.trim()\n : undefined;\n\n // Default to last 7 days when no fromTimestamp is supplied.\n const fromTimestamp = parseIsoTimestamp(req.query.fromTimestamp);\n const toTimestamp = parseIsoTimestamp(req.query.toTimestamp);\n const resolvedFrom =\n fromTimestamp ??\n (() => {\n // Round to the nearest minute so the cache key is stable across requests.\n const ms = Date.now() - 7 * 24 * 60 * 60 * 1000;\n return new Date(ms - (ms % 60_000)).toISOString();\n })();\n\n const creds = resolveLangfuseCreds();\n if (!creds.host || !creds.auth) {\n if (isDemoMode()) {\n const result = getDemoTraces(\n page,\n limit,\n tags,\n resolvedFrom,\n toTimestamp,\n );\n res.status(200).json({ ...result, connection: DEMO_CONNECTION });\n return;\n }\n res.status(200).json({ data: [], meta: {}, connection: creds.connection });\n return;\n }\n\n const params = new URLSearchParams();\n params.set(\"page\", String(page));\n params.set(\"limit\", String(limit));\n params.set(\"orderBy\", \"timestamp.desc\");\n if (tags) {\n for (const tag of tags) {\n params.append(\"tags\", tag);\n }\n }\n if (name) {\n params.set(\"name\", name);\n }\n params.set(\"fromTimestamp\", resolvedFrom);\n if (toTimestamp) {\n params.set(\"toTimestamp\", toTimestamp);\n }\n\n const noCache = req.query.noCache === \"1\";\n const cacheKey = `traces:${params.toString()}`;\n if (!noCache) {\n const cached = cache.get(cacheKey);\n if (cached !== undefined) {\n res.status(200).json(cached);\n return;\n }\n }\n\n const base = creds.host.replace(/\\/$/, \"\");\n const url = `${base}/api/public/traces?${params.toString()}`;\n\n // Retry once on 5xx with 500ms backoff.\n const maxAttempts = 2;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 10_000);\n try {\n const lfRes = await fetch(url, {\n headers: { Authorization: `Basic ${creds.auth}` },\n signal: controller.signal,\n });\n if (!lfRes.ok) {\n // Retry on 5xx\n if (lfRes.status >= 500 && attempt < maxAttempts) {\n clearTimeout(timeout);\n await new Promise((r) => setTimeout(r, 500));\n continue;\n }\n const detail = await formatLangfuseHttpError(lfRes, \"traces\");\n const state =\n lfRes.status === 401 || lfRes.status === 403\n ? \"auth_failed\"\n : \"query_failed\";\n res.status(lfRes.status).json({\n error: detail,\n connection: langfuseStatus(state as \"auth_failed\" | \"query_failed\", {\n detail,\n }),\n });\n return;\n }\n const body = await lfRes.json();\n const result = {\n data: body.data ?? [],\n meta: body.meta ?? {},\n connection: creds.connection,\n };\n cache.set(cacheKey, result);\n res.status(200).json(result);\n return;\n } catch (err) {\n if (attempt < maxAttempts) {\n clearTimeout(timeout);\n await new Promise((r) => setTimeout(r, 500));\n continue;\n }\n const detail = formatLangfuseRequestError(err);\n res.status(502).json({\n error: detail,\n connection: langfuseStatus(\"network_error\", { detail }),\n });\n return;\n } finally {\n clearTimeout(timeout);\n }\n }\n}\n\nfunction parseIsoTimestamp(value: unknown): string | undefined {\n if (typeof value !== \"string\" || !value.trim()) return undefined;\n const parsed = new Date(value);\n return Number.isNaN(parsed.getTime()) ? undefined : parsed.toISOString();\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"linear-ideas.js","names":[],"sources":["../../../../dashboard/pages/api/linear-ideas.ts"],"sourcesContent":["import {\n buildIssueFilter,\n ISSUE_QUERY,\n KAIZEN_LINEAR_LABEL,\n linearProjectRefFromFields,\n linearHttpMessage,\n parseLinearBody,\n resolveLinearEnv,\n resolveLinearProject,\n type LinearEnv,\n type LinearConnectionStatus,\n type LinearProjectRef,\n} from \"../../../shared/linear-ideas.js\";\nimport { readSystemMeta } from \"../../src/lib/env\";\nimport {\n missingEnvVars,\n readWorkspaceEnvLocal,\n WORKSPACE_ENV_FILE_NAME,\n} from \"../../src/lib/workspace-env\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\ninterface LinearIdeasConfig {\n systemId: string;\n teamKey: string | null;\n projectName: string | null;\n projectId: string | null;\n projectUrl: string | null;\n projectRef: string | null;\n projectRefKind: \"id\" | \"url\" | null;\n label: string;\n}\n\nexport default async function handler(req: StudioRequest, res: StudioResponse) {\n const systemId =\n typeof req.query.systemId === \"string\" && req.query.systemId !== \"\"\n ? req.query.systemId\n : null;\n if (!systemId) {\n res.status(400).json({ error: \"systemId query param required\" });\n return;\n }\n\n const meta = readSystemMeta(systemId);\n const rootEnv = readWorkspaceEnvLocal();\n const missingRootVars = missingEnvVars(rootEnv, [\"LINEAR_API_KEY\"]);\n const linearEnv = resolveLinearEnv(rootEnv, {});\n const token = linearEnv.apiKey;\n const config = linearConfig(meta, linearEnv, systemId);\n const projectRef = config.projectRef\n ? projectRefFromConfig(config.projectRefKind, config.projectRef)\n : null;\n\n if (!projectRef) {\n res.status(200).json({\n ideas: [],\n config,\n connection: linearStatus(\"missing_project\"),\n });\n return;\n }\n\n if (!token || missingRootVars.length > 0) {\n res.status(200).json({\n ideas: [],\n config,\n connection: linearStatus(\"missing_api_key\", {\n missingEnvVars: missingRootVars,\n }),\n });\n return;\n }\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 30_000);\n let responseConfig = config;\n try {\n const project = await resolveLinearProject(\n token,\n projectRef,\n controller.signal,\n );\n const resolvedConfig = {\n ...config,\n projectId: project?.id ?? null,\n projectName: project?.name ?? null,\n projectUrl: project?.url ?? null,\n };\n responseConfig = resolvedConfig;\n if (!project) {\n res.status(200).json({\n ideas: [],\n config: resolvedConfig,\n connection: linearStatus(\"project_not_found\", {\n detail: `Configured project ${projectRef.value} was not found.`,\n }),\n });\n return;\n }\n\n const linearRes = await fetch(\"https://api.linear.app/graphql\", {\n method: \"POST\",\n headers: {\n Authorization: token,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n query: ISSUE_QUERY,\n variables: {\n first: 50,\n filter: buildIssueFilter({\n projectId: project.id,\n teamKey: config.teamKey,\n label: config.label,\n }),\n },\n }),\n signal: controller.signal,\n });\n\n const text = await linearRes.text();\n const body = parseLinearBody(text);\n if (!linearRes.ok || body.errors?.length) {\n res.status(200).json({\n ideas: [],\n config: resolvedConfig,\n connection: linearStatus(\n linearRes.status === 401 || linearRes.status === 403\n ? \"auth_failed\"\n : \"query_failed\",\n {\n detail: linearHttpMessage(linearRes, body.errors),\n },\n ),\n });\n return;\n }\n\n res.status(200).json({\n ideas: body.data?.issues?.nodes ?? [],\n config: resolvedConfig,\n connection: linearStatus(\"connected\"),\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n res.status(200).json({\n ideas: [],\n config: responseConfig,\n connection: linearStatus(\"network_error\", { detail: message }),\n });\n } finally {\n clearTimeout(timeout);\n }\n}\n\nfunction linearConfig(\n meta: Record<string, unknown>,\n env: LinearEnv,\n systemId: string,\n): LinearIdeasConfig {\n const projectRef = linearProjectRefFromFields({\n project: stringMeta(meta, \"linear_project\"),\n projectId: stringMeta(meta, \"linear_project_id\"),\n projectUrl: stringMeta(meta, \"linear_project_url\"),\n });\n return {\n systemId,\n teamKey: stringMeta(meta, \"linear_team\") ?? env.teamKey,\n projectName: null,\n projectId: null,\n projectUrl: null,\n projectRef: projectRef?.value ?? null,\n projectRefKind: projectRef?.kind ?? null,\n label: KAIZEN_LINEAR_LABEL,\n };\n}\n\nfunction stringMeta(meta: Record<string, unknown>, key: string): string | null {\n const value = meta[key];\n return typeof value === \"string\" && value.trim() ? value.trim() : null;\n}\n\nfunction projectRefFromConfig(\n kind: \"id\" | \"url\" | null,\n value: string,\n): LinearProjectRef | null {\n if (kind === \"id\" || kind === \"url\") return { kind, value };\n return null;\n}\n\nfunction linearStatus(\n state: LinearConnectionStatus[\"state\"],\n options: { detail?: string; missingEnvVars?: string[] } = {},\n): LinearConnectionStatus {\n const common = {\n requiredEnvVars: [\"LINEAR_API_KEY\"],\n optionalEnvVars: [\"LINEAR_TEAM_KEY\"],\n expectedEnvFile: WORKSPACE_ENV_FILE_NAME,\n missingEnvVars: options.missingEnvVars ?? [],\n lookup: [\n \"repo root .env.local for LINEAR_API_KEY\",\n \"system frontmatter linear_project for the Linear project URL or ID\",\n ],\n detail: options.detail,\n };\n\n if (state === \"connected\") {\n return {\n ...common,\n state,\n message: \"Linear is connected.\",\n remediation:\n \"Kaizen is reading LINEAR_API_KEY from the workspace root .env.local file and resolving this system's Linear project by URL or ID.\",\n };\n }\n if (state === \"missing_project\") {\n return {\n ...common,\n state,\n message: \"This system does not have a Linear project configured.\",\n remediation:\n \"Add linear_project to the system frontmatter with the Linear project URL or project ID. Use a stable URL or ID, not the project name.\",\n };\n }\n if (state === \"missing_api_key\") {\n return {\n ...common,\n state,\n message: \"Linear is not connected because LINEAR_API_KEY is missing.\",\n remediation:\n \"Create a Linear personal API key, add LINEAR_API_KEY to the repo root .env.local file, then refresh this view.\",\n };\n }\n if (state === \"project_not_found\") {\n return {\n ...common,\n state,\n message: \"Kaizen could not find the configured Linear project.\",\n remediation:\n \"Check the linear_project URL or ID in this system's frontmatter and confirm LINEAR_API_KEY can access that project.\",\n };\n }\n if (state === \"auth_failed\") {\n return {\n ...common,\n state,\n message: \"Linear rejected the configured API key.\",\n remediation:\n \"Replace LINEAR_API_KEY in the repo root .env.local file with a valid Linear personal API key, then refresh this view.\",\n };\n }\n if (state === \"query_failed\") {\n return {\n ...common,\n state,\n message: \"Kaizen reached Linear, but the ideas query failed.\",\n remediation:\n \"Check the Linear team and project URL or ID configured for this system, then refresh.\",\n };\n }\n return {\n ...common,\n state,\n message: \"Kaizen could not reach Linear.\",\n remediation:\n \"Check network access to Linear and confirm LINEAR_API_KEY is present in the repo root .env.local file, then refresh this view.\",\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"linear-ideas.js","names":[],"sources":["../../../../dashboard/pages/api/linear-ideas.ts"],"sourcesContent":["import {\n buildIssueFilter,\n ISSUE_QUERY,\n KAIZEN_LINEAR_LABEL,\n linearProjectRefFromFields,\n linearHttpMessage,\n parseLinearBody,\n resolveLinearEnv,\n resolveLinearProject,\n type LinearEnv,\n type LinearConnectionStatus,\n type LinearProjectRef,\n} from \"../../../shared/linear-ideas.js\";\nimport { readSystemMeta } from \"../../src/lib/env\";\nimport {\n missingEnvVars,\n readWorkspaceEnvLocal,\n WORKSPACE_ENV_FILE_NAME,\n} from \"../../src/lib/workspace-env\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\ninterface LinearIdeasConfig {\n systemId: string;\n teamKey: string | null;\n projectName: string | null;\n projectId: string | null;\n projectUrl: string | null;\n projectRef: string | null;\n projectRefKind: \"id\" | \"url\" | null;\n label: string;\n}\n\nexport default async function handler(req: StudioRequest, res: StudioResponse) {\n const systemId =\n typeof req.query.systemId === \"string\" && req.query.systemId !== \"\"\n ? req.query.systemId\n : null;\n if (!systemId) {\n res.status(400).json({ error: \"systemId query param required\" });\n return;\n }\n\n const meta = readSystemMeta(systemId);\n const rootEnv = readWorkspaceEnvLocal();\n const missingRootVars = missingEnvVars(rootEnv, [\"LINEAR_API_KEY\"]);\n const linearEnv = resolveLinearEnv(rootEnv, {});\n const token = linearEnv.apiKey;\n const config = linearConfig(meta, linearEnv, systemId);\n const projectRef = config.projectRef\n ? projectRefFromConfig(config.projectRefKind, config.projectRef)\n : null;\n\n if (!projectRef) {\n res.status(200).json({\n ideas: [],\n config,\n connection: linearStatus(\"missing_project\"),\n });\n return;\n }\n\n if (!token || missingRootVars.length > 0) {\n res.status(200).json({\n ideas: [],\n config,\n connection: linearStatus(\"missing_api_key\", {\n missingEnvVars: missingRootVars,\n }),\n });\n return;\n }\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 30_000);\n let responseConfig = config;\n try {\n const project = await resolveLinearProject(\n token,\n projectRef,\n controller.signal,\n );\n const resolvedConfig = {\n ...config,\n projectId: project?.id ?? null,\n projectName: project?.name ?? null,\n projectUrl: project?.url ?? null,\n };\n responseConfig = resolvedConfig;\n if (!project) {\n res.status(200).json({\n ideas: [],\n config: resolvedConfig,\n connection: linearStatus(\"project_not_found\", {\n detail: `Configured project ${projectRef.value} was not found.`,\n }),\n });\n return;\n }\n\n const linearRes = await fetch(\"https://api.linear.app/graphql\", {\n method: \"POST\",\n headers: {\n Authorization: token,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n query: ISSUE_QUERY,\n variables: {\n first: 50,\n filter: buildIssueFilter({\n projectId: project.id,\n teamKey: config.teamKey,\n label: config.label,\n }),\n },\n }),\n signal: controller.signal,\n });\n\n const text = await linearRes.text();\n const body = parseLinearBody(text);\n if (!linearRes.ok || body.errors?.length) {\n res.status(200).json({\n ideas: [],\n config: resolvedConfig,\n connection: linearStatus(\n linearRes.status === 401 || linearRes.status === 403\n ? \"auth_failed\"\n : \"query_failed\",\n {\n detail: linearHttpMessage(linearRes, body.errors),\n },\n ),\n });\n return;\n }\n\n res.status(200).json({\n ideas: body.data?.issues?.nodes ?? [],\n config: resolvedConfig,\n connection: linearStatus(\"connected\"),\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n res.status(200).json({\n ideas: [],\n config: responseConfig,\n connection: linearStatus(\"network_error\", { detail: message }),\n });\n } finally {\n clearTimeout(timeout);\n }\n}\n\nfunction linearConfig(\n meta: Record<string, unknown>,\n env: LinearEnv,\n systemId: string,\n): LinearIdeasConfig {\n const projectRef = linearProjectRefFromFields({\n project: stringMeta(meta, \"linear_project\"),\n projectId: stringMeta(meta, \"linear_project_id\"),\n projectUrl: stringMeta(meta, \"linear_project_url\"),\n });\n return {\n systemId,\n teamKey: stringMeta(meta, \"linear_team\") ?? env.teamKey,\n projectName: null,\n projectId: null,\n projectUrl: null,\n projectRef: projectRef?.value ?? null,\n projectRefKind: projectRef?.kind ?? null,\n label: KAIZEN_LINEAR_LABEL,\n };\n}\n\nfunction stringMeta(meta: Record<string, unknown>, key: string): string | null {\n const value = meta[key];\n return typeof value === \"string\" && value.trim() ? value.trim() : null;\n}\n\nfunction projectRefFromConfig(\n kind: \"id\" | \"url\" | null,\n value: string,\n): LinearProjectRef | null {\n if (kind === \"id\" || kind === \"url\") return { kind, value };\n return null;\n}\n\nfunction linearStatus(\n state: LinearConnectionStatus[\"state\"],\n options: { detail?: string; missingEnvVars?: string[] } = {},\n): LinearConnectionStatus {\n const common = {\n requiredEnvVars: [\"LINEAR_API_KEY\"],\n optionalEnvVars: [\"LINEAR_TEAM_KEY\"],\n expectedEnvFile: WORKSPACE_ENV_FILE_NAME,\n missingEnvVars: options.missingEnvVars ?? [],\n lookup: [\n \"repo root .env.local for LINEAR_API_KEY\",\n \"system frontmatter linear_project for the Linear project URL or ID\",\n ],\n detail: options.detail,\n };\n\n if (state === \"connected\") {\n return {\n ...common,\n state,\n message: \"Linear is connected.\",\n remediation:\n \"Kaizen is reading LINEAR_API_KEY from the workspace root .env.local file and resolving this system's Linear project by URL or ID.\",\n };\n }\n if (state === \"missing_project\") {\n return {\n ...common,\n state,\n message: \"This system does not have a Linear project configured.\",\n remediation:\n \"Add linear_project to the system frontmatter with the Linear project URL or project ID. Use a stable URL or ID, not the project name.\",\n };\n }\n if (state === \"missing_api_key\") {\n return {\n ...common,\n state,\n message: \"Linear is not connected because LINEAR_API_KEY is missing.\",\n remediation:\n \"Create a Linear personal API key, add LINEAR_API_KEY to the repo root .env.local file, then refresh this view.\",\n };\n }\n if (state === \"project_not_found\") {\n return {\n ...common,\n state,\n message: \"Kaizen could not find the configured Linear project.\",\n remediation:\n \"Check the linear_project URL or ID in this system's frontmatter and confirm LINEAR_API_KEY can access that project.\",\n };\n }\n if (state === \"auth_failed\") {\n return {\n ...common,\n state,\n message: \"Linear rejected the configured API key.\",\n remediation:\n \"Replace LINEAR_API_KEY in the repo root .env.local file with a valid Linear personal API key, then refresh this view.\",\n };\n }\n if (state === \"query_failed\") {\n return {\n ...common,\n state,\n message: \"Kaizen reached Linear, but the ideas query failed.\",\n remediation:\n \"Check the Linear team and project URL or ID configured for this system, then refresh.\",\n };\n }\n return {\n ...common,\n state,\n message: \"Kaizen could not reach Linear.\",\n remediation:\n \"Check network access to Linear and confirm LINEAR_API_KEY is present in the repo root .env.local file, then refresh this view.\",\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"run-events.js","names":[],"sources":["../../../../dashboard/pages/api/run-events.ts"],"sourcesContent":["import { isSafeRunPathSegment } from \"../../src/lib/run-api\";\nimport { readEvents } from \"../../src/lib/run-store\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\nexport default function handler(req: StudioRequest, res: StudioResponse) {\n const system =\n typeof req.query.system === \"string\" ? req.query.system : undefined;\n const { runId } = req.query;\n if (typeof system !== \"string\" || typeof runId !== \"string\") {\n res.status(400).json({ error: \"system and runId query params required\" });\n return;\n }\n if (!isSafeRunPathSegment(system) || !isSafeRunPathSegment(runId)) {\n res.status(400).json({ error: \"invalid system or runId\" });\n return;\n }\n res.status(200).json({ events: readEvents(system, runId) });\n}\n"],"mappings":";;;AAOA,SAAwB,QAAQ,KAAoB,KAAqB;CACvE,MAAM,SACJ,OAAO,IAAI,MAAM,WAAW,WAAW,IAAI,MAAM,SAAS,KAAA;CAC5D,MAAM,EAAE,UAAU,IAAI;AACtB,KAAI,OAAO,WAAW,YAAY,OAAO,UAAU,UAAU;AAC3D,MAAI,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,0CAA0C,CAAC;AACzE;;AAEF,KAAI,CAAC,qBAAqB,OAAO,IAAI,CAAC,qBAAqB,MAAM,EAAE;AACjE,MAAI,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,2BAA2B,CAAC;AAC1D;;AAEF,KAAI,OAAO,IAAI,CAAC,KAAK,EAAE,QAAQ,WAAW,QAAQ,MAAM,EAAE,CAAC"}
1
+ {"version":3,"file":"run-events.js","names":[],"sources":["../../../../dashboard/pages/api/run-events.ts"],"sourcesContent":["import { isSafeRunPathSegment } from \"../../src/lib/run-api\";\nimport { readEvents } from \"../../src/lib/run-store\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\nexport default function handler(req: StudioRequest, res: StudioResponse) {\n const system =\n typeof req.query.system === \"string\" ? req.query.system : undefined;\n const { runId } = req.query;\n if (typeof system !== \"string\" || typeof runId !== \"string\") {\n res.status(400).json({ error: \"system and runId query params required\" });\n return;\n }\n if (!isSafeRunPathSegment(system) || !isSafeRunPathSegment(runId)) {\n res.status(400).json({ error: \"invalid system or runId\" });\n return;\n }\n res.status(200).json({ events: readEvents(system, runId) });\n}\n"],"mappings":";;;AAOA,SAAwB,QAAQ,KAAoB,KAAqB;CACvE,MAAM,SACJ,OAAO,IAAI,MAAM,WAAW,WAAW,IAAI,MAAM,SAAS,KAAA;CAC5D,MAAM,EAAE,UAAU,IAAI;CACtB,IAAI,OAAO,WAAW,YAAY,OAAO,UAAU,UAAU;EAC3D,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,yCAAyC,CAAC;EACxE;CACF;CACA,IAAI,CAAC,qBAAqB,MAAM,KAAK,CAAC,qBAAqB,KAAK,GAAG;EACjE,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,0BAA0B,CAAC;EACzD;CACF;CACA,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,QAAQ,WAAW,QAAQ,KAAK,EAAE,CAAC;AAC5D"}
@@ -1 +1 @@
1
- {"version":3,"file":"run-failures.js","names":[],"sources":["../../../../dashboard/pages/api/run-failures.ts"],"sourcesContent":["import { isSafeRunPathSegment } from \"../../src/lib/run-api\";\nimport { readFailures } from \"../../src/lib/run-store\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\nexport default function handler(req: StudioRequest, res: StudioResponse) {\n const system =\n typeof req.query.system === \"string\" ? req.query.system : undefined;\n const { runId } = req.query;\n if (typeof system !== \"string\" || typeof runId !== \"string\") {\n res.status(400).json({ error: \"system and runId query params required\" });\n return;\n }\n if (!isSafeRunPathSegment(system) || !isSafeRunPathSegment(runId)) {\n res.status(400).json({ error: \"invalid system or runId\" });\n return;\n }\n res.status(200).json({ failures: readFailures(system, runId) });\n}\n"],"mappings":";;;AAOA,SAAwB,QAAQ,KAAoB,KAAqB;CACvE,MAAM,SACJ,OAAO,IAAI,MAAM,WAAW,WAAW,IAAI,MAAM,SAAS,KAAA;CAC5D,MAAM,EAAE,UAAU,IAAI;AACtB,KAAI,OAAO,WAAW,YAAY,OAAO,UAAU,UAAU;AAC3D,MAAI,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,0CAA0C,CAAC;AACzE;;AAEF,KAAI,CAAC,qBAAqB,OAAO,IAAI,CAAC,qBAAqB,MAAM,EAAE;AACjE,MAAI,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,2BAA2B,CAAC;AAC1D;;AAEF,KAAI,OAAO,IAAI,CAAC,KAAK,EAAE,UAAU,aAAa,QAAQ,MAAM,EAAE,CAAC"}
1
+ {"version":3,"file":"run-failures.js","names":[],"sources":["../../../../dashboard/pages/api/run-failures.ts"],"sourcesContent":["import { isSafeRunPathSegment } from \"../../src/lib/run-api\";\nimport { readFailures } from \"../../src/lib/run-store\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\nexport default function handler(req: StudioRequest, res: StudioResponse) {\n const system =\n typeof req.query.system === \"string\" ? req.query.system : undefined;\n const { runId } = req.query;\n if (typeof system !== \"string\" || typeof runId !== \"string\") {\n res.status(400).json({ error: \"system and runId query params required\" });\n return;\n }\n if (!isSafeRunPathSegment(system) || !isSafeRunPathSegment(runId)) {\n res.status(400).json({ error: \"invalid system or runId\" });\n return;\n }\n res.status(200).json({ failures: readFailures(system, runId) });\n}\n"],"mappings":";;;AAOA,SAAwB,QAAQ,KAAoB,KAAqB;CACvE,MAAM,SACJ,OAAO,IAAI,MAAM,WAAW,WAAW,IAAI,MAAM,SAAS,KAAA;CAC5D,MAAM,EAAE,UAAU,IAAI;CACtB,IAAI,OAAO,WAAW,YAAY,OAAO,UAAU,UAAU;EAC3D,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,yCAAyC,CAAC;EACxE;CACF;CACA,IAAI,CAAC,qBAAqB,MAAM,KAAK,CAAC,qBAAqB,KAAK,GAAG;EACjE,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,0BAA0B,CAAC;EACzD;CACF;CACA,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,UAAU,aAAa,QAAQ,KAAK,EAAE,CAAC;AAChE"}
@@ -1 +1 @@
1
- {"version":3,"file":"run-traces.js","names":[],"sources":["../../../../dashboard/pages/api/run-traces.ts"],"sourcesContent":["import { getDemoRunTraceItems, isDemoMode } from \"../../src/lib/langfuse-demo\";\nimport { isSafeRunPathSegment } from \"../../src/lib/run-api\";\nimport { readEvents } from \"../../src/lib/run-store\";\nimport { DEMO_SYSTEM_ID } from \"../../src/lib/systems\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\ninterface RunTraceItem {\n id: string;\n score: number;\n traceId: string | null;\n breakdown?: Record<string, number>;\n}\n\nexport default function handler(req: StudioRequest, res: StudioResponse) {\n const system =\n typeof req.query.system === \"string\" ? req.query.system : undefined;\n const runId =\n typeof req.query.runId === \"string\" ? req.query.runId : undefined;\n\n if (!system || !runId) {\n res.status(400).json({ error: \"system and runId query params required\" });\n return;\n }\n if (!isSafeRunPathSegment(system) || !isSafeRunPathSegment(runId)) {\n res.status(400).json({ error: \"invalid system or runId\" });\n return;\n }\n\n const events = readEvents(system, runId);\n const traces: RunTraceItem[] = [];\n\n for (const event of events) {\n if (!isRecord(event) || event.type !== \"item\") continue;\n traces.push({\n id: typeof event.id === \"string\" ? event.id : \"\",\n score: typeof event.score === \"number\" ? event.score : 0,\n traceId: typeof event.trace_id === \"string\" ? event.trace_id : null,\n breakdown: isBreakdown(event.breakdown) ? event.breakdown : undefined,\n });\n }\n\n if (traces.length === 0 && isDemoMode() && system === DEMO_SYSTEM_ID) {\n const demoTraces = getDemoRunTraceItems(runId);\n if (demoTraces) {\n res.status(200).json({ traces: demoTraces });\n return;\n }\n }\n\n res.status(200).json({ traces });\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isBreakdown(value: unknown): value is Record<string, number> {\n if (!isRecord(value)) return false;\n return Object.values(value).every((v) => typeof v === \"number\");\n}\n"],"mappings":";;;;;AAgBA,SAAwB,QAAQ,KAAoB,KAAqB;CACvE,MAAM,SACJ,OAAO,IAAI,MAAM,WAAW,WAAW,IAAI,MAAM,SAAS,KAAA;CAC5D,MAAM,QACJ,OAAO,IAAI,MAAM,UAAU,WAAW,IAAI,MAAM,QAAQ,KAAA;AAE1D,KAAI,CAAC,UAAU,CAAC,OAAO;AACrB,MAAI,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,0CAA0C,CAAC;AACzE;;AAEF,KAAI,CAAC,qBAAqB,OAAO,IAAI,CAAC,qBAAqB,MAAM,EAAE;AACjE,MAAI,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,2BAA2B,CAAC;AAC1D;;CAGF,MAAM,SAAS,WAAW,QAAQ,MAAM;CACxC,MAAM,SAAyB,EAAE;AAEjC,MAAK,MAAM,SAAS,QAAQ;AAC1B,MAAI,CAAC,SAAS,MAAM,IAAI,MAAM,SAAS,OAAQ;AAC/C,SAAO,KAAK;GACV,IAAI,OAAO,MAAM,OAAO,WAAW,MAAM,KAAK;GAC9C,OAAO,OAAO,MAAM,UAAU,WAAW,MAAM,QAAQ;GACvD,SAAS,OAAO,MAAM,aAAa,WAAW,MAAM,WAAW;GAC/D,WAAW,YAAY,MAAM,UAAU,GAAG,MAAM,YAAY,KAAA;GAC7D,CAAC;;AAGJ,KAAI,OAAO,WAAW,KAAK,YAAY,IAAI,WAAA,eAA2B;EACpE,MAAM,aAAa,qBAAqB,MAAM;AAC9C,MAAI,YAAY;AACd,OAAI,OAAO,IAAI,CAAC,KAAK,EAAE,QAAQ,YAAY,CAAC;AAC5C;;;AAIJ,KAAI,OAAO,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC;;AAGlC,SAAS,SAAS,OAAkD;AAClE,QAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM;;AAG7E,SAAS,YAAY,OAAiD;AACpE,KAAI,CAAC,SAAS,MAAM,CAAE,QAAO;AAC7B,QAAO,OAAO,OAAO,MAAM,CAAC,OAAO,MAAM,OAAO,MAAM,SAAS"}
1
+ {"version":3,"file":"run-traces.js","names":[],"sources":["../../../../dashboard/pages/api/run-traces.ts"],"sourcesContent":["import { getDemoRunTraceItems, isDemoMode } from \"../../src/lib/langfuse-demo\";\nimport { isSafeRunPathSegment } from \"../../src/lib/run-api\";\nimport { readEvents } from \"../../src/lib/run-store\";\nimport { DEMO_SYSTEM_ID } from \"../../src/lib/systems\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\ninterface RunTraceItem {\n id: string;\n score: number;\n traceId: string | null;\n breakdown?: Record<string, number>;\n}\n\nexport default function handler(req: StudioRequest, res: StudioResponse) {\n const system =\n typeof req.query.system === \"string\" ? req.query.system : undefined;\n const runId =\n typeof req.query.runId === \"string\" ? req.query.runId : undefined;\n\n if (!system || !runId) {\n res.status(400).json({ error: \"system and runId query params required\" });\n return;\n }\n if (!isSafeRunPathSegment(system) || !isSafeRunPathSegment(runId)) {\n res.status(400).json({ error: \"invalid system or runId\" });\n return;\n }\n\n const events = readEvents(system, runId);\n const traces: RunTraceItem[] = [];\n\n for (const event of events) {\n if (!isRecord(event) || event.type !== \"item\") continue;\n traces.push({\n id: typeof event.id === \"string\" ? event.id : \"\",\n score: typeof event.score === \"number\" ? event.score : 0,\n traceId: typeof event.trace_id === \"string\" ? event.trace_id : null,\n breakdown: isBreakdown(event.breakdown) ? event.breakdown : undefined,\n });\n }\n\n if (traces.length === 0 && isDemoMode() && system === DEMO_SYSTEM_ID) {\n const demoTraces = getDemoRunTraceItems(runId);\n if (demoTraces) {\n res.status(200).json({ traces: demoTraces });\n return;\n }\n }\n\n res.status(200).json({ traces });\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isBreakdown(value: unknown): value is Record<string, number> {\n if (!isRecord(value)) return false;\n return Object.values(value).every((v) => typeof v === \"number\");\n}\n"],"mappings":";;;;;AAgBA,SAAwB,QAAQ,KAAoB,KAAqB;CACvE,MAAM,SACJ,OAAO,IAAI,MAAM,WAAW,WAAW,IAAI,MAAM,SAAS,KAAA;CAC5D,MAAM,QACJ,OAAO,IAAI,MAAM,UAAU,WAAW,IAAI,MAAM,QAAQ,KAAA;CAE1D,IAAI,CAAC,UAAU,CAAC,OAAO;EACrB,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,yCAAyC,CAAC;EACxE;CACF;CACA,IAAI,CAAC,qBAAqB,MAAM,KAAK,CAAC,qBAAqB,KAAK,GAAG;EACjE,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,0BAA0B,CAAC;EACzD;CACF;CAEA,MAAM,SAAS,WAAW,QAAQ,KAAK;CACvC,MAAM,SAAyB,CAAC;CAEhC,KAAK,MAAM,SAAS,QAAQ;EAC1B,IAAI,CAAC,SAAS,KAAK,KAAK,MAAM,SAAS,QAAQ;EAC/C,OAAO,KAAK;GACV,IAAI,OAAO,MAAM,OAAO,WAAW,MAAM,KAAK;GAC9C,OAAO,OAAO,MAAM,UAAU,WAAW,MAAM,QAAQ;GACvD,SAAS,OAAO,MAAM,aAAa,WAAW,MAAM,WAAW;GAC/D,WAAW,YAAY,MAAM,SAAS,IAAI,MAAM,YAAY,KAAA;EAC9D,CAAC;CACH;CAEA,IAAI,OAAO,WAAW,KAAK,WAAW,KAAK,WAAA,eAA2B;EACpE,MAAM,aAAa,qBAAqB,KAAK;EAC7C,IAAI,YAAY;GACd,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,QAAQ,WAAW,CAAC;GAC3C;EACF;CACF;CAEA,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC;AACjC;AAEA,SAAS,SAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,YAAY,OAAiD;CACpE,IAAI,CAAC,SAAS,KAAK,GAAG,OAAO;CAC7B,OAAO,OAAO,OAAO,KAAK,EAAE,OAAO,MAAM,OAAO,MAAM,QAAQ;AAChE"}
@@ -1 +1 @@
1
- {"version":3,"file":"runs.js","names":[],"sources":["../../../../dashboard/pages/api/runs.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { getDemoRunStatuses, isDemoMode } from \"../../src/lib/langfuse-demo\";\nimport { listRunRecords, toRunStatus } from \"../../src/lib/run-store\";\nimport { DEMO_SYSTEM_ID } from \"../../src/lib/systems\";\nimport { getKaizenStateDir } from \"../../src/lib/workspace\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\nexport default function handler(req: StudioRequest, res: StudioResponse) {\n const accept = req.headers.accept ?? \"\";\n\n // SSE mode\n if (accept.includes(\"text/event-stream\")) {\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache, no-transform\",\n Connection: \"keep-alive\",\n });\n\n const send = () => {\n const runs = listVisibleRuns();\n res.write(`data: ${JSON.stringify({ runs })}\\n\\n`);\n };\n\n send();\n\n const statusDir = path.join(getKaizenStateDir(), \"runs\");\n fs.mkdirSync(statusDir, { recursive: true });\n\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n const watcher = fs.watch(statusDir, { recursive: true }, () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(send, 200);\n });\n\n const heartbeat = setInterval(() => {\n res.write(\": heartbeat\\n\\n\");\n }, 15000);\n\n req.on(\"close\", () => {\n watcher.close();\n clearInterval(heartbeat);\n if (debounceTimer) clearTimeout(debounceTimer);\n });\n\n return;\n }\n\n // Regular JSON mode (fallback)\n const runs = listVisibleRuns();\n res.status(200).json({ runs });\n}\n\nfunction listVisibleRuns() {\n if (!isDemoMode()) return listRunRecords().map(toRunStatus);\n const runs = listRunRecords(DEMO_SYSTEM_ID).map(toRunStatus);\n return runs.length > 0 ? runs : getDemoRunStatuses();\n}\n"],"mappings":";;;;;;;AAWA,SAAwB,QAAQ,KAAoB,KAAqB;AAIvE,MAHe,IAAI,QAAQ,UAAU,IAG1B,SAAS,oBAAoB,EAAE;AACxC,MAAI,UAAU,KAAK;GACjB,gBAAgB;GAChB,iBAAiB;GACjB,YAAY;GACb,CAAC;EAEF,MAAM,aAAa;GACjB,MAAM,OAAO,iBAAiB;AAC9B,OAAI,MAAM,SAAS,KAAK,UAAU,EAAE,MAAM,CAAC,CAAC,MAAM;;AAGpD,QAAM;EAEN,MAAM,YAAY,KAAK,KAAK,mBAAmB,EAAE,OAAO;AACxD,KAAG,UAAU,WAAW,EAAE,WAAW,MAAM,CAAC;EAE5C,IAAI,gBAAsD;EAC1D,MAAM,UAAU,GAAG,MAAM,WAAW,EAAE,WAAW,MAAM,QAAQ;AAC7D,OAAI,cAAe,cAAa,cAAc;AAC9C,mBAAgB,WAAW,MAAM,IAAI;IACrC;EAEF,MAAM,YAAY,kBAAkB;AAClC,OAAI,MAAM,kBAAkB;KAC3B,KAAM;AAET,MAAI,GAAG,eAAe;AACpB,WAAQ,OAAO;AACf,iBAAc,UAAU;AACxB,OAAI,cAAe,cAAa,cAAc;IAC9C;AAEF;;CAIF,MAAM,OAAO,iBAAiB;AAC9B,KAAI,OAAO,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC;;AAGhC,SAAS,kBAAkB;AACzB,KAAI,CAAC,YAAY,CAAE,QAAO,gBAAgB,CAAC,IAAI,YAAY;CAC3D,MAAM,OAAO,eAAe,eAAe,CAAC,IAAI,YAAY;AAC5D,QAAO,KAAK,SAAS,IAAI,OAAO,oBAAoB"}
1
+ {"version":3,"file":"runs.js","names":[],"sources":["../../../../dashboard/pages/api/runs.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { getDemoRunStatuses, isDemoMode } from \"../../src/lib/langfuse-demo\";\nimport { listRunRecords, toRunStatus } from \"../../src/lib/run-store\";\nimport { DEMO_SYSTEM_ID } from \"../../src/lib/systems\";\nimport { getKaizenStateDir } from \"../../src/lib/workspace\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\nexport default function handler(req: StudioRequest, res: StudioResponse) {\n const accept = req.headers.accept ?? \"\";\n\n // SSE mode\n if (accept.includes(\"text/event-stream\")) {\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache, no-transform\",\n Connection: \"keep-alive\",\n });\n\n const send = () => {\n const runs = listVisibleRuns();\n res.write(`data: ${JSON.stringify({ runs })}\\n\\n`);\n };\n\n send();\n\n const statusDir = path.join(getKaizenStateDir(), \"runs\");\n fs.mkdirSync(statusDir, { recursive: true });\n\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n const watcher = fs.watch(statusDir, { recursive: true }, () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(send, 200);\n });\n\n const heartbeat = setInterval(() => {\n res.write(\": heartbeat\\n\\n\");\n }, 15000);\n\n req.on(\"close\", () => {\n watcher.close();\n clearInterval(heartbeat);\n if (debounceTimer) clearTimeout(debounceTimer);\n });\n\n return;\n }\n\n // Regular JSON mode (fallback)\n const runs = listVisibleRuns();\n res.status(200).json({ runs });\n}\n\nfunction listVisibleRuns() {\n if (!isDemoMode()) return listRunRecords().map(toRunStatus);\n const runs = listRunRecords(DEMO_SYSTEM_ID).map(toRunStatus);\n return runs.length > 0 ? runs : getDemoRunStatuses();\n}\n"],"mappings":";;;;;;;AAWA,SAAwB,QAAQ,KAAoB,KAAqB;CAIvE,KAHe,IAAI,QAAQ,UAAU,IAG1B,SAAS,mBAAmB,GAAG;EACxC,IAAI,UAAU,KAAK;GACjB,gBAAgB;GAChB,iBAAiB;GACjB,YAAY;EACd,CAAC;EAED,MAAM,aAAa;GACjB,MAAM,OAAO,gBAAgB;GAC7B,IAAI,MAAM,SAAS,KAAK,UAAU,EAAE,KAAK,CAAC,EAAE,KAAK;EACnD;EAEA,KAAK;EAEL,MAAM,YAAY,KAAK,KAAK,kBAAkB,GAAG,MAAM;EACvD,GAAG,UAAU,WAAW,EAAE,WAAW,KAAK,CAAC;EAE3C,IAAI,gBAAsD;EAC1D,MAAM,UAAU,GAAG,MAAM,WAAW,EAAE,WAAW,KAAK,SAAS;GAC7D,IAAI,eAAe,aAAa,aAAa;GAC7C,gBAAgB,WAAW,MAAM,GAAG;EACtC,CAAC;EAED,MAAM,YAAY,kBAAkB;GAClC,IAAI,MAAM,iBAAiB;EAC7B,GAAG,IAAK;EAER,IAAI,GAAG,eAAe;GACpB,QAAQ,MAAM;GACd,cAAc,SAAS;GACvB,IAAI,eAAe,aAAa,aAAa;EAC/C,CAAC;EAED;CACF;CAGA,MAAM,OAAO,gBAAgB;CAC7B,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC;AAC/B;AAEA,SAAS,kBAAkB;CACzB,IAAI,CAAC,WAAW,GAAG,OAAO,eAAe,EAAE,IAAI,WAAW;CAC1D,MAAM,OAAO,eAAe,cAAc,EAAE,IAAI,WAAW;CAC3D,OAAO,KAAK,SAAS,IAAI,OAAO,mBAAmB;AACrD"}
@@ -112,7 +112,7 @@ function buildDemoSystem() {
112
112
  customerLogo: null,
113
113
  repo: "",
114
114
  status: runs.length > 0 ? "in_progress" : "not_started",
115
- description: "Demo Kaizen system for exploring traces, datasets, benchmarked experiments, and custom views.",
115
+ description: "Demo Kaizen system for exploring traces, datasets, experiments, and custom views.",
116
116
  evalDataset: "golden-eval-set",
117
117
  evalVersion: 1,
118
118
  evalType: "llm_judge",
@@ -1 +1 @@
1
- {"version":3,"file":"systems.js","names":[],"sources":["../../../../dashboard/pages/api/systems.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport YAML from \"yaml\";\nimport {\n kaizenSystemPath,\n kaizenSystemsDir,\n} from \"../../../shared/workspace-paths.js\";\nimport {\n findCustomTraceRendererPath,\n findCustomViewPath,\n} from \"../../src/lib/custom-view-paths\";\nimport { getDemoRunStatuses, isDemoMode } from \"../../src/lib/langfuse-demo\";\nimport { listRunRecords, toRunStatus } from \"../../src/lib/run-store\";\nimport {\n DEMO_SYSTEM_ID,\n type EvalType,\n type SystemDefinition,\n} from \"../../src/lib/systems\";\nimport { getPrimaryMetricValue } from \"../../src/lib/types\";\nimport { getWorkspaceRoot } from \"../../src/lib/workspace\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\n/** Parse an optional frontmatter string field, treating YAML \"null\" and empty/missing as null. */\nfunction optionalMeta(val: string | undefined): string | null {\n return !val || val === \"null\" ? null : val;\n}\n\nfunction optionalNumberMeta(val: string | undefined): number | null {\n if (!val || val === \"null\") return null;\n const parsed = Number(val);\n return Number.isFinite(parsed) ? parsed : null;\n}\n\nfunction parseFrontmatter(raw: string): {\n meta: Record<string, string>;\n content: string;\n} {\n const match = raw.match(/^---\\n([\\s\\S]*?)\\n---\\n([\\s\\S]*)$/);\n if (!match) return { meta: {}, content: raw };\n const parsed = YAML.parse(match[1]);\n const meta: Record<string, string> = {};\n if (parsed && typeof parsed === \"object\") {\n for (const [k, v] of Object.entries(parsed)) {\n meta[k] =\n v == null\n ? \"null\"\n : typeof v === \"string\"\n ? v\n : typeof v === \"number\" || typeof v === \"boolean\"\n ? String(v)\n : JSON.stringify(v);\n }\n }\n return { meta, content: match[2] };\n}\n\nexport default function handler(_req: StudioRequest, res: StudioResponse) {\n const workspace = getWorkspaceRoot();\n if (isDemoMode()) {\n res.status(200).json([buildDemoSystem()]);\n return;\n }\n\n const systemsDir = kaizenSystemsDir(workspace);\n if (!fs.existsSync(systemsDir)) {\n res.status(200).json([]);\n return;\n }\n\n const allRuns = listRunRecords().map(toRunStatus);\n\n const entries = fs.readdirSync(systemsDir).filter((name) => {\n const systemPath = kaizenSystemPath(workspace, name);\n try {\n return (\n fs.statSync(path.join(systemsDir, name)).isDirectory() &&\n fs.existsSync(systemPath)\n );\n } catch {\n return false;\n }\n });\n\n const systems: SystemDefinition[] = entries.map((id) => {\n const raw = fs.readFileSync(kaizenSystemPath(workspace, id), \"utf-8\");\n const { meta, content } = parseFrontmatter(raw);\n const target =\n meta.target && meta.target !== \"null\" ? parseFloat(meta.target) : null;\n const primaryMetric = optionalMeta(meta.primary_metric);\n\n // Find runs belonging to this system from the deterministic kaizen/.kaizen store.\n const systemRuns = allRuns.filter((run) => run.system === id);\n const effectiveRuns = systemRuns;\n\n const completedRuns = effectiveRuns.filter(\n (run) => run.status === \"complete\",\n );\n\n // Compute best score based on primary metric\n let bestScore: number | null = null;\n for (const run of completedRuns) {\n const score = getPrimaryMetricValue(run, primaryMetric);\n if (score != null && (bestScore === null || score > bestScore)) {\n bestScore = score;\n }\n }\n\n // Derive status from search state\n let status: \"not_started\" | \"in_progress\" | \"completed\";\n if (effectiveRuns.length === 0) {\n status = \"not_started\";\n } else if (target !== null && bestScore !== null && bestScore >= target) {\n status = \"completed\";\n } else {\n status = \"in_progress\";\n }\n\n const customerId = optionalMeta(meta.customer) ?? \"\";\n const rubric =\n optionalMeta(meta.rubric) ??\n (fs.existsSync(path.join(systemsDir, id, \"rubric.md\"))\n ? `kaizen/systems/${id}/rubric.md`\n : null);\n\n return {\n id,\n name: meta.name || id,\n customerId,\n customerName: customerId,\n customerLogo: null,\n repo: meta.repo || \"\",\n status,\n description: meta.description || \"\",\n evalDataset: optionalMeta(meta.dataset_version),\n evalVersion: optionalNumberMeta(meta.eval_version),\n evalType: evalTypeValue(\n optionalMeta(meta.eval_type) ??\n normalizeEvalStyle(optionalMeta(meta.eval_style)),\n ),\n primaryMetric,\n rubric,\n target,\n content,\n bestScore,\n totalRuns: effectiveRuns.length,\n traceRenderer: findCustomTraceRendererPath(workspace, id),\n datasetItemRenderer: findCustomViewPath(workspace, id, \"dataset-item\"),\n };\n });\n\n res.status(200).json(systems);\n}\n\nfunction buildDemoSystem(): SystemDefinition {\n const workspace = getWorkspaceRoot();\n const storedRuns = listRunRecords(DEMO_SYSTEM_ID).map(toRunStatus);\n const runs = storedRuns.length > 0 ? storedRuns : getDemoRunStatuses();\n const primaryMetric = \"judge_quality\";\n const completedRuns = runs.filter((run) => run.status === \"complete\");\n let bestScore: number | null = null;\n for (const run of completedRuns) {\n const score = getPrimaryMetricValue(run, primaryMetric);\n if (score != null && (bestScore === null || score > bestScore)) {\n bestScore = score;\n }\n }\n\n const traceRenderer = findCustomTraceRendererPath(workspace, DEMO_SYSTEM_ID);\n return {\n id: DEMO_SYSTEM_ID,\n name: \"Demo System\",\n customerId: \"demo\",\n customerName: \"Demo Workspace\",\n customerLogo: null,\n repo: \"\",\n status: runs.length > 0 ? \"in_progress\" : \"not_started\",\n description:\n \"Demo Kaizen system for exploring traces, datasets, benchmarked experiments, and custom views.\",\n evalDataset: \"golden-eval-set\",\n evalVersion: 1,\n evalType: \"llm_judge\",\n primaryMetric,\n rubric: \"kaizen/systems/demo-system/rubric.md\",\n target: null,\n content:\n \"This synthetic system is shown only when KAIZEN_DEMO_MODE=1. It keeps demo traces, datasets, and experiment runs under one system so local Studio development matches the installed customer-repo model.\",\n bestScore,\n totalRuns: runs.length,\n traceRenderer,\n datasetItemRenderer: findCustomViewPath(\n workspace,\n DEMO_SYSTEM_ID,\n \"dataset-item\",\n ),\n };\n}\n\nfunction normalizeEvalStyle(style: string | null): string | null {\n if (style === \"ground-truth\") return \"ground_truth\";\n if (style === \"llm-as-judge\") return \"llm_judge\";\n return style;\n}\n\nfunction evalTypeValue(value: string | null): EvalType | null {\n if (\n value === \"ground_truth\" ||\n value === \"llm_judge\" ||\n value === \"human\" ||\n value === \"policy\"\n ) {\n return value;\n }\n return null;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"systems.js","names":[],"sources":["../../../../dashboard/pages/api/systems.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport YAML from \"yaml\";\nimport {\n kaizenSystemPath,\n kaizenSystemsDir,\n} from \"../../../shared/workspace-paths.js\";\nimport {\n findCustomTraceRendererPath,\n findCustomViewPath,\n} from \"../../src/lib/custom-view-paths\";\nimport { getDemoRunStatuses, isDemoMode } from \"../../src/lib/langfuse-demo\";\nimport { listRunRecords, toRunStatus } from \"../../src/lib/run-store\";\nimport {\n DEMO_SYSTEM_ID,\n type EvalType,\n type SystemDefinition,\n} from \"../../src/lib/systems\";\nimport { getPrimaryMetricValue } from \"../../src/lib/types\";\nimport { getWorkspaceRoot } from \"../../src/lib/workspace\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\n/** Parse an optional frontmatter string field, treating YAML \"null\" and empty/missing as null. */\nfunction optionalMeta(val: string | undefined): string | null {\n return !val || val === \"null\" ? null : val;\n}\n\nfunction optionalNumberMeta(val: string | undefined): number | null {\n if (!val || val === \"null\") return null;\n const parsed = Number(val);\n return Number.isFinite(parsed) ? parsed : null;\n}\n\nfunction parseFrontmatter(raw: string): {\n meta: Record<string, string>;\n content: string;\n} {\n const match = raw.match(/^---\\n([\\s\\S]*?)\\n---\\n([\\s\\S]*)$/);\n if (!match) return { meta: {}, content: raw };\n const parsed = YAML.parse(match[1]);\n const meta: Record<string, string> = {};\n if (parsed && typeof parsed === \"object\") {\n for (const [k, v] of Object.entries(parsed)) {\n meta[k] =\n v == null\n ? \"null\"\n : typeof v === \"string\"\n ? v\n : typeof v === \"number\" || typeof v === \"boolean\"\n ? String(v)\n : JSON.stringify(v);\n }\n }\n return { meta, content: match[2] };\n}\n\nexport default function handler(_req: StudioRequest, res: StudioResponse) {\n const workspace = getWorkspaceRoot();\n if (isDemoMode()) {\n res.status(200).json([buildDemoSystem()]);\n return;\n }\n\n const systemsDir = kaizenSystemsDir(workspace);\n if (!fs.existsSync(systemsDir)) {\n res.status(200).json([]);\n return;\n }\n\n const allRuns = listRunRecords().map(toRunStatus);\n\n const entries = fs.readdirSync(systemsDir).filter((name) => {\n const systemPath = kaizenSystemPath(workspace, name);\n try {\n return (\n fs.statSync(path.join(systemsDir, name)).isDirectory() &&\n fs.existsSync(systemPath)\n );\n } catch {\n return false;\n }\n });\n\n const systems: SystemDefinition[] = entries.map((id) => {\n const raw = fs.readFileSync(kaizenSystemPath(workspace, id), \"utf-8\");\n const { meta, content } = parseFrontmatter(raw);\n const target =\n meta.target && meta.target !== \"null\" ? parseFloat(meta.target) : null;\n const primaryMetric = optionalMeta(meta.primary_metric);\n\n // Find runs belonging to this system from the deterministic kaizen/.kaizen store.\n const systemRuns = allRuns.filter((run) => run.system === id);\n const effectiveRuns = systemRuns;\n\n const completedRuns = effectiveRuns.filter(\n (run) => run.status === \"complete\",\n );\n\n // Compute best score based on primary metric\n let bestScore: number | null = null;\n for (const run of completedRuns) {\n const score = getPrimaryMetricValue(run, primaryMetric);\n if (score != null && (bestScore === null || score > bestScore)) {\n bestScore = score;\n }\n }\n\n // Derive status from search state\n let status: \"not_started\" | \"in_progress\" | \"completed\";\n if (effectiveRuns.length === 0) {\n status = \"not_started\";\n } else if (target !== null && bestScore !== null && bestScore >= target) {\n status = \"completed\";\n } else {\n status = \"in_progress\";\n }\n\n const customerId = optionalMeta(meta.customer) ?? \"\";\n const rubric =\n optionalMeta(meta.rubric) ??\n (fs.existsSync(path.join(systemsDir, id, \"rubric.md\"))\n ? `kaizen/systems/${id}/rubric.md`\n : null);\n\n return {\n id,\n name: meta.name || id,\n customerId,\n customerName: customerId,\n customerLogo: null,\n repo: meta.repo || \"\",\n status,\n description: meta.description || \"\",\n evalDataset: optionalMeta(meta.dataset_version),\n evalVersion: optionalNumberMeta(meta.eval_version),\n evalType: evalTypeValue(\n optionalMeta(meta.eval_type) ??\n normalizeEvalStyle(optionalMeta(meta.eval_style)),\n ),\n primaryMetric,\n rubric,\n target,\n content,\n bestScore,\n totalRuns: effectiveRuns.length,\n traceRenderer: findCustomTraceRendererPath(workspace, id),\n datasetItemRenderer: findCustomViewPath(workspace, id, \"dataset-item\"),\n };\n });\n\n res.status(200).json(systems);\n}\n\nfunction buildDemoSystem(): SystemDefinition {\n const workspace = getWorkspaceRoot();\n const storedRuns = listRunRecords(DEMO_SYSTEM_ID).map(toRunStatus);\n const runs = storedRuns.length > 0 ? storedRuns : getDemoRunStatuses();\n const primaryMetric = \"judge_quality\";\n const completedRuns = runs.filter((run) => run.status === \"complete\");\n let bestScore: number | null = null;\n for (const run of completedRuns) {\n const score = getPrimaryMetricValue(run, primaryMetric);\n if (score != null && (bestScore === null || score > bestScore)) {\n bestScore = score;\n }\n }\n\n const traceRenderer = findCustomTraceRendererPath(workspace, DEMO_SYSTEM_ID);\n return {\n id: DEMO_SYSTEM_ID,\n name: \"Demo System\",\n customerId: \"demo\",\n customerName: \"Demo Workspace\",\n customerLogo: null,\n repo: \"\",\n status: runs.length > 0 ? \"in_progress\" : \"not_started\",\n description:\n \"Demo Kaizen system for exploring traces, datasets, experiments, and custom views.\",\n evalDataset: \"golden-eval-set\",\n evalVersion: 1,\n evalType: \"llm_judge\",\n primaryMetric,\n rubric: \"kaizen/systems/demo-system/rubric.md\",\n target: null,\n content:\n \"This synthetic system is shown only when KAIZEN_DEMO_MODE=1. It keeps demo traces, datasets, and experiment runs under one system so local Studio development matches the installed customer-repo model.\",\n bestScore,\n totalRuns: runs.length,\n traceRenderer,\n datasetItemRenderer: findCustomViewPath(\n workspace,\n DEMO_SYSTEM_ID,\n \"dataset-item\",\n ),\n };\n}\n\nfunction normalizeEvalStyle(style: string | null): string | null {\n if (style === \"ground-truth\") return \"ground_truth\";\n if (style === \"llm-as-judge\") return \"llm_judge\";\n return style;\n}\n\nfunction evalTypeValue(value: string | null): EvalType | null {\n if (\n value === \"ground_truth\" ||\n value === \"llm_judge\" ||\n value === \"human\" ||\n value === \"policy\"\n ) {\n return value;\n }\n return null;\n}\n"],"mappings":"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"}
@@ -1,9 +1,10 @@
1
1
  import { getWorkspaceRoot } from "../../src/lib/workspace.js";
2
2
  import { isSafeRunPathSegment } from "../../src/lib/run-api.js";
3
+ import { bundleCustomRenderer } from "../../src/lib/bundle-custom-renderer.js";
3
4
  import { readCustomRendererFile } from "../../src/lib/custom-renderer-files.js";
4
5
  import { parseCustomViewSurface } from "../../src/lib/custom-renderer-metadata.js";
5
6
  //#region dashboard/pages/api/trace-renderer-version.ts
6
- function handler(req, res) {
7
+ async function handler(req, res) {
7
8
  const systemId = typeof req.query.systemId === "string" ? req.query.systemId : void 0;
8
9
  if (!systemId) {
9
10
  res.status(400).json({ error: "systemId query param required" });
@@ -18,13 +19,19 @@ function handler(req, res) {
18
19
  res.status(400).json({ error: "surface must be trace or dataset-item" });
19
20
  return;
20
21
  }
21
- const rendererFile = readCustomRendererFile(getWorkspaceRoot(), systemId, surface);
22
+ const workspace = getWorkspaceRoot();
23
+ const rendererFile = readCustomRendererFile(workspace, systemId, surface);
22
24
  if (!rendererFile.ok) {
23
25
  res.status(rendererFile.status).json({ error: rendererFile.error });
24
26
  return;
25
27
  }
28
+ const bundled = await bundleCustomRenderer(rendererFile.file.fullPath, workspace);
29
+ if (!bundled.ok) {
30
+ res.status(500).json({ error: `bundle failed: ${bundled.error}` });
31
+ return;
32
+ }
26
33
  res.setHeader("Cache-Control", "no-store");
27
- res.status(200).json({ version: rendererFile.file.version });
34
+ res.status(200).json({ version: bundled.version });
28
35
  }
29
36
  //#endregion
30
37
  export { handler as default };
@@ -1 +1 @@
1
- {"version":3,"file":"trace-renderer-version.js","names":[],"sources":["../../../../dashboard/pages/api/trace-renderer-version.ts"],"sourcesContent":["import { readCustomRendererFile } from \"../../src/lib/custom-renderer-files\";\nimport { parseCustomViewSurface } from \"../../src/lib/custom-renderer-metadata\";\nimport { isSafeRunPathSegment } from \"../../src/lib/run-api\";\nimport { getWorkspaceRoot } from \"../../src/lib/workspace\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\nexport default function handler(req: StudioRequest, res: StudioResponse) {\n const systemId =\n typeof req.query.systemId === \"string\" ? req.query.systemId : undefined;\n if (!systemId) {\n res.status(400).json({ error: \"systemId query param required\" });\n return;\n }\n if (!isSafeRunPathSegment(systemId)) {\n res.status(400).json({ error: \"invalid systemId\" });\n return;\n }\n\n const surface = parseCustomViewSurface(req.query.surface);\n if (!surface) {\n res.status(400).json({ error: \"surface must be trace or dataset-item\" });\n return;\n }\n\n const rendererFile = readCustomRendererFile(\n getWorkspaceRoot(),\n systemId,\n surface,\n );\n if (!rendererFile.ok) {\n res.status(rendererFile.status).json({ error: rendererFile.error });\n return;\n }\n\n res.setHeader(\"Cache-Control\", \"no-store\");\n res.status(200).json({ version: rendererFile.file.version });\n}\n"],"mappings":";;;;;AASA,SAAwB,QAAQ,KAAoB,KAAqB;CACvE,MAAM,WACJ,OAAO,IAAI,MAAM,aAAa,WAAW,IAAI,MAAM,WAAW,KAAA;AAChE,KAAI,CAAC,UAAU;AACb,MAAI,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,iCAAiC,CAAC;AAChE;;AAEF,KAAI,CAAC,qBAAqB,SAAS,EAAE;AACnC,MAAI,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,oBAAoB,CAAC;AACnD;;CAGF,MAAM,UAAU,uBAAuB,IAAI,MAAM,QAAQ;AACzD,KAAI,CAAC,SAAS;AACZ,MAAI,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,yCAAyC,CAAC;AACxE;;CAGF,MAAM,eAAe,uBACnB,kBAAkB,EAClB,UACA,QACD;AACD,KAAI,CAAC,aAAa,IAAI;AACpB,MAAI,OAAO,aAAa,OAAO,CAAC,KAAK,EAAE,OAAO,aAAa,OAAO,CAAC;AACnE;;AAGF,KAAI,UAAU,iBAAiB,WAAW;AAC1C,KAAI,OAAO,IAAI,CAAC,KAAK,EAAE,SAAS,aAAa,KAAK,SAAS,CAAC"}
1
+ {"version":3,"file":"trace-renderer-version.js","names":[],"sources":["../../../../dashboard/pages/api/trace-renderer-version.ts"],"sourcesContent":["import { bundleCustomRenderer } from \"../../src/lib/bundle-custom-renderer\";\nimport { readCustomRendererFile } from \"../../src/lib/custom-renderer-files\";\nimport { parseCustomViewSurface } from \"../../src/lib/custom-renderer-metadata\";\nimport { isSafeRunPathSegment } from \"../../src/lib/run-api\";\nimport { getWorkspaceRoot } from \"../../src/lib/workspace\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\nexport default async function handler(req: StudioRequest, res: StudioResponse) {\n const systemId =\n typeof req.query.systemId === \"string\" ? req.query.systemId : undefined;\n if (!systemId) {\n res.status(400).json({ error: \"systemId query param required\" });\n return;\n }\n if (!isSafeRunPathSegment(systemId)) {\n res.status(400).json({ error: \"invalid systemId\" });\n return;\n }\n\n const surface = parseCustomViewSurface(req.query.surface);\n if (!surface) {\n res.status(400).json({ error: \"surface must be trace or dataset-item\" });\n return;\n }\n\n const workspace = getWorkspaceRoot();\n const rendererFile = readCustomRendererFile(workspace, systemId, surface);\n if (!rendererFile.ok) {\n res.status(rendererFile.status).json({ error: rendererFile.error });\n return;\n }\n\n const bundled = await bundleCustomRenderer(\n rendererFile.file.fullPath,\n workspace,\n );\n if (!bundled.ok) {\n res.status(500).json({ error: `bundle failed: ${bundled.error}` });\n return;\n }\n\n res.setHeader(\"Cache-Control\", \"no-store\");\n res.status(200).json({ version: bundled.version });\n}\n"],"mappings":";;;;;;AAUA,eAA8B,QAAQ,KAAoB,KAAqB;CAC7E,MAAM,WACJ,OAAO,IAAI,MAAM,aAAa,WAAW,IAAI,MAAM,WAAW,KAAA;CAChE,IAAI,CAAC,UAAU;EACb,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,gCAAgC,CAAC;EAC/D;CACF;CACA,IAAI,CAAC,qBAAqB,QAAQ,GAAG;EACnC,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,mBAAmB,CAAC;EAClD;CACF;CAEA,MAAM,UAAU,uBAAuB,IAAI,MAAM,OAAO;CACxD,IAAI,CAAC,SAAS;EACZ,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,wCAAwC,CAAC;EACvE;CACF;CAEA,MAAM,YAAY,iBAAiB;CACnC,MAAM,eAAe,uBAAuB,WAAW,UAAU,OAAO;CACxE,IAAI,CAAC,aAAa,IAAI;EACpB,IAAI,OAAO,aAAa,MAAM,EAAE,KAAK,EAAE,OAAO,aAAa,MAAM,CAAC;EAClE;CACF;CAEA,MAAM,UAAU,MAAM,qBACpB,aAAa,KAAK,UAClB,SACF;CACA,IAAI,CAAC,QAAQ,IAAI;EACf,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,kBAAkB,QAAQ,QAAQ,CAAC;EACjE;CACF;CAEA,IAAI,UAAU,iBAAiB,UAAU;CACzC,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,SAAS,QAAQ,QAAQ,CAAC;AACnD"}
@@ -1,8 +1,8 @@
1
1
  import { getWorkspaceRoot } from "../../src/lib/workspace.js";
2
2
  import { isSafeRunPathSegment } from "../../src/lib/run-api.js";
3
+ import { bundleCustomRenderer } from "../../src/lib/bundle-custom-renderer.js";
3
4
  import { readCustomRendererFile } from "../../src/lib/custom-renderer-files.js";
4
5
  import { CUSTOM_RENDERER_VERSION_HEADER, parseCustomViewSurface } from "../../src/lib/custom-renderer-metadata.js";
5
- import { transform } from "esbuild";
6
6
  //#region dashboard/pages/api/trace-renderer.ts
7
7
  async function handler(req, res) {
8
8
  const systemId = typeof req.query.systemId === "string" ? req.query.systemId : void 0;
@@ -19,36 +19,22 @@ async function handler(req, res) {
19
19
  res.status(400).json({ error: "surface must be trace or dataset-item" });
20
20
  return;
21
21
  }
22
- const rendererFile = readCustomRendererFile(getWorkspaceRoot(), systemId, surface);
22
+ const workspace = getWorkspaceRoot();
23
+ const rendererFile = readCustomRendererFile(workspace, systemId, surface);
23
24
  if (!rendererFile.ok) {
24
25
  res.status(rendererFile.status).json({ error: rendererFile.error });
25
26
  return;
26
27
  }
27
- const { fullPath, rendererPath, source, version } = rendererFile.file;
28
- try {
29
- const result = await transform(source, {
30
- loader: extToLoader(fullPath),
31
- jsx: "automatic",
32
- format: "esm",
33
- target: "es2020",
34
- sourcemap: "inline",
35
- sourcefile: rendererPath
36
- });
37
- res.setHeader("Content-Type", "application/javascript; charset=utf-8");
38
- res.setHeader("Cache-Control", "no-store");
39
- res.setHeader(CUSTOM_RENDERER_VERSION_HEADER, version);
40
- res.status(200).send(result.code);
41
- } catch (err) {
42
- const message = err instanceof Error ? err.message : String(err);
43
- res.status(500).json({ error: `transpile failed: ${message}` });
28
+ const { fullPath } = rendererFile.file;
29
+ const bundled = await bundleCustomRenderer(fullPath, workspace);
30
+ if (!bundled.ok) {
31
+ res.status(500).json({ error: `bundle failed: ${bundled.error}` });
32
+ return;
44
33
  }
45
- }
46
- function extToLoader(filePath) {
47
- const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
48
- if (ext === ".tsx") return "tsx";
49
- if (ext === ".ts") return "ts";
50
- if (ext === ".jsx") return "jsx";
51
- return "js";
34
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
35
+ res.setHeader("Cache-Control", "no-store");
36
+ res.setHeader(CUSTOM_RENDERER_VERSION_HEADER, bundled.version);
37
+ res.status(200).send(bundled.code);
52
38
  }
53
39
  //#endregion
54
40
  export { handler as default };
@@ -1 +1 @@
1
- {"version":3,"file":"trace-renderer.js","names":[],"sources":["../../../../dashboard/pages/api/trace-renderer.ts"],"sourcesContent":["import { transform } from \"esbuild\";\nimport { readCustomRendererFile } from \"../../src/lib/custom-renderer-files\";\nimport {\n CUSTOM_RENDERER_VERSION_HEADER,\n parseCustomViewSurface,\n} from \"../../src/lib/custom-renderer-metadata\";\nimport { isSafeRunPathSegment } from \"../../src/lib/run-api\";\nimport { getWorkspaceRoot } from \"../../src/lib/workspace\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\nexport default async function handler(req: StudioRequest, res: StudioResponse) {\n const systemId =\n typeof req.query.systemId === \"string\" ? req.query.systemId : undefined;\n if (!systemId) {\n res.status(400).json({ error: \"systemId query param required\" });\n return;\n }\n if (!isSafeRunPathSegment(systemId)) {\n res.status(400).json({ error: \"invalid systemId\" });\n return;\n }\n\n const surface = parseCustomViewSurface(req.query.surface);\n if (!surface) {\n res.status(400).json({ error: \"surface must be trace or dataset-item\" });\n return;\n }\n\n const workspace = getWorkspaceRoot();\n const rendererFile = readCustomRendererFile(workspace, systemId, surface);\n if (!rendererFile.ok) {\n res.status(rendererFile.status).json({ error: rendererFile.error });\n return;\n }\n\n const { fullPath, rendererPath, source, version } = rendererFile.file;\n\n try {\n const result = await transform(source, {\n loader: extToLoader(fullPath),\n jsx: \"automatic\",\n format: \"esm\",\n target: \"es2020\",\n sourcemap: \"inline\",\n sourcefile: rendererPath,\n });\n\n res.setHeader(\"Content-Type\", \"application/javascript; charset=utf-8\");\n res.setHeader(\"Cache-Control\", \"no-store\");\n res.setHeader(CUSTOM_RENDERER_VERSION_HEADER, version);\n res.status(200).send(result.code);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n res.status(500).json({ error: `transpile failed: ${message}` });\n }\n}\n\nfunction extToLoader(filePath: string): \"tsx\" | \"ts\" | \"jsx\" | \"js\" {\n const ext = filePath.slice(filePath.lastIndexOf(\".\")).toLowerCase();\n if (ext === \".tsx\") return \"tsx\";\n if (ext === \".ts\") return \"ts\";\n if (ext === \".jsx\") return \"jsx\";\n return \"js\";\n}\n"],"mappings":";;;;;;AAaA,eAA8B,QAAQ,KAAoB,KAAqB;CAC7E,MAAM,WACJ,OAAO,IAAI,MAAM,aAAa,WAAW,IAAI,MAAM,WAAW,KAAA;AAChE,KAAI,CAAC,UAAU;AACb,MAAI,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,iCAAiC,CAAC;AAChE;;AAEF,KAAI,CAAC,qBAAqB,SAAS,EAAE;AACnC,MAAI,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,oBAAoB,CAAC;AACnD;;CAGF,MAAM,UAAU,uBAAuB,IAAI,MAAM,QAAQ;AACzD,KAAI,CAAC,SAAS;AACZ,MAAI,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,yCAAyC,CAAC;AACxE;;CAIF,MAAM,eAAe,uBADH,kBACmC,EAAE,UAAU,QAAQ;AACzE,KAAI,CAAC,aAAa,IAAI;AACpB,MAAI,OAAO,aAAa,OAAO,CAAC,KAAK,EAAE,OAAO,aAAa,OAAO,CAAC;AACnE;;CAGF,MAAM,EAAE,UAAU,cAAc,QAAQ,YAAY,aAAa;AAEjE,KAAI;EACF,MAAM,SAAS,MAAM,UAAU,QAAQ;GACrC,QAAQ,YAAY,SAAS;GAC7B,KAAK;GACL,QAAQ;GACR,QAAQ;GACR,WAAW;GACX,YAAY;GACb,CAAC;AAEF,MAAI,UAAU,gBAAgB,wCAAwC;AACtE,MAAI,UAAU,iBAAiB,WAAW;AAC1C,MAAI,UAAU,gCAAgC,QAAQ;AACtD,MAAI,OAAO,IAAI,CAAC,KAAK,OAAO,KAAK;UAC1B,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,MAAI,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,qBAAqB,WAAW,CAAC;;;AAInE,SAAS,YAAY,UAA+C;CAClE,MAAM,MAAM,SAAS,MAAM,SAAS,YAAY,IAAI,CAAC,CAAC,aAAa;AACnE,KAAI,QAAQ,OAAQ,QAAO;AAC3B,KAAI,QAAQ,MAAO,QAAO;AAC1B,KAAI,QAAQ,OAAQ,QAAO;AAC3B,QAAO"}
1
+ {"version":3,"file":"trace-renderer.js","names":[],"sources":["../../../../dashboard/pages/api/trace-renderer.ts"],"sourcesContent":["import { bundleCustomRenderer } from \"../../src/lib/bundle-custom-renderer\";\nimport { readCustomRendererFile } from \"../../src/lib/custom-renderer-files\";\nimport {\n CUSTOM_RENDERER_VERSION_HEADER,\n parseCustomViewSurface,\n} from \"../../src/lib/custom-renderer-metadata\";\nimport { isSafeRunPathSegment } from \"../../src/lib/run-api\";\nimport { getWorkspaceRoot } from \"../../src/lib/workspace\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\nexport default async function handler(req: StudioRequest, res: StudioResponse) {\n const systemId =\n typeof req.query.systemId === \"string\" ? req.query.systemId : undefined;\n if (!systemId) {\n res.status(400).json({ error: \"systemId query param required\" });\n return;\n }\n if (!isSafeRunPathSegment(systemId)) {\n res.status(400).json({ error: \"invalid systemId\" });\n return;\n }\n\n const surface = parseCustomViewSurface(req.query.surface);\n if (!surface) {\n res.status(400).json({ error: \"surface must be trace or dataset-item\" });\n return;\n }\n\n const workspace = getWorkspaceRoot();\n const rendererFile = readCustomRendererFile(workspace, systemId, surface);\n if (!rendererFile.ok) {\n res.status(rendererFile.status).json({ error: rendererFile.error });\n return;\n }\n\n const { fullPath } = rendererFile.file;\n\n // Bundle (not just transpile) so a renderer can import shared sibling modules\n // within the workspace — e.g. a timeline component shared by the trace and\n // dataset-item views. React stays external for the client's host-React\n // rewrite; the dependency graph is bounded to the workspace.\n const bundled = await bundleCustomRenderer(fullPath, workspace);\n if (!bundled.ok) {\n res.status(500).json({ error: `bundle failed: ${bundled.error}` });\n return;\n }\n\n res.setHeader(\"Content-Type\", \"application/javascript; charset=utf-8\");\n res.setHeader(\"Cache-Control\", \"no-store\");\n res.setHeader(CUSTOM_RENDERER_VERSION_HEADER, bundled.version);\n res.status(200).send(bundled.code);\n}\n"],"mappings":";;;;;;AAaA,eAA8B,QAAQ,KAAoB,KAAqB;CAC7E,MAAM,WACJ,OAAO,IAAI,MAAM,aAAa,WAAW,IAAI,MAAM,WAAW,KAAA;CAChE,IAAI,CAAC,UAAU;EACb,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,gCAAgC,CAAC;EAC/D;CACF;CACA,IAAI,CAAC,qBAAqB,QAAQ,GAAG;EACnC,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,mBAAmB,CAAC;EAClD;CACF;CAEA,MAAM,UAAU,uBAAuB,IAAI,MAAM,OAAO;CACxD,IAAI,CAAC,SAAS;EACZ,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,wCAAwC,CAAC;EACvE;CACF;CAEA,MAAM,YAAY,iBAAiB;CACnC,MAAM,eAAe,uBAAuB,WAAW,UAAU,OAAO;CACxE,IAAI,CAAC,aAAa,IAAI;EACpB,IAAI,OAAO,aAAa,MAAM,EAAE,KAAK,EAAE,OAAO,aAAa,MAAM,CAAC;EAClE;CACF;CAEA,MAAM,EAAE,aAAa,aAAa;CAMlC,MAAM,UAAU,MAAM,qBAAqB,UAAU,SAAS;CAC9D,IAAI,CAAC,QAAQ,IAAI;EACf,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,kBAAkB,QAAQ,QAAQ,CAAC;EACjE;CACF;CAEA,IAAI,UAAU,gBAAgB,uCAAuC;CACrE,IAAI,UAAU,iBAAiB,UAAU;CACzC,IAAI,UAAU,gCAAgC,QAAQ,OAAO;CAC7D,IAAI,OAAO,GAAG,EAAE,KAAK,QAAQ,IAAI;AACnC"}
@@ -0,0 +1,167 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ import { build } from "esbuild";
5
+ //#region dashboard/src/lib/bundle-custom-renderer.ts
6
+ const REACT_EXTERNALS = ["react", "react/jsx-runtime"];
7
+ const RESOLVE_EXTENSIONS = [
8
+ ".tsx",
9
+ ".ts",
10
+ ".jsx",
11
+ ".js"
12
+ ];
13
+ /**
14
+ * Bundle a custom renderer entry file into a single ESM module.
15
+ *
16
+ * Unlike a bare transpile, bundling lets a renderer `import` shared sibling
17
+ * modules within the workspace (e.g. a timeline component shared by the trace
18
+ * and dataset-item views). esbuild resolves relative imports against each
19
+ * file's directory and inlines them; React stays external so the client's
20
+ * existing react/export rewrite evaluates the result unchanged. Single-file
21
+ * renderers bundle to themselves, so this is backward compatible.
22
+ *
23
+ * `workspaceRoot` bounds the dependency graph: every resolved on-disk import
24
+ * must stay inside it, mirroring `resolveWorkspaceViewPath`'s protection for
25
+ * the entry file so a renderer can't reach outside the workspace via `../`.
26
+ */
27
+ async function bundleCustomRenderer(entryFullPath, workspaceRoot) {
28
+ const root = normalizePathForContainment(path.resolve(workspaceRoot));
29
+ const entry = normalizePathForContainment(path.resolve(root, entryFullPath));
30
+ if (!isInsideWorkspace(root, entry)) return {
31
+ ok: false,
32
+ error: "renderer entry resolves outside the workspace"
33
+ };
34
+ try {
35
+ const code = (await build({
36
+ absWorkingDir: root,
37
+ entryPoints: [entry],
38
+ bundle: true,
39
+ write: false,
40
+ format: "esm",
41
+ target: "es2020",
42
+ jsx: "automatic",
43
+ sourcemap: "inline",
44
+ logLevel: "silent",
45
+ external: [...REACT_EXTERNALS],
46
+ plugins: [workspaceImportPolicyPlugin(root)]
47
+ })).outputFiles?.[0]?.text;
48
+ if (code == null) return {
49
+ ok: false,
50
+ error: "bundle produced no output"
51
+ };
52
+ return {
53
+ ok: true,
54
+ code,
55
+ version: createBundledRendererVersion(code)
56
+ };
57
+ } catch (err) {
58
+ return {
59
+ ok: false,
60
+ error: err instanceof Error ? err.message : String(err)
61
+ };
62
+ }
63
+ }
64
+ /**
65
+ * esbuild plugin that keeps renderer dependencies workspace-local. React-family
66
+ * specifiers are external (left to the host), local filesystem imports must stay
67
+ * under the workspace, and other bare package imports are rejected instead of
68
+ * being bundled from node_modules.
69
+ */
70
+ function workspaceImportPolicyPlugin(workspaceRoot) {
71
+ return {
72
+ name: "kaizen-workspace-import-policy",
73
+ setup(pluginBuild) {
74
+ pluginBuild.onResolve({ filter: /.*/ }, (args) => {
75
+ if (isReactExternal(args.path)) return { external: true };
76
+ if (args.kind === "entry-point") return;
77
+ if (isBareImport(args.path)) return { errors: [{ text: `bare import "${args.path}" is not supported in custom renderers; use a relative workspace module or React` }] };
78
+ const resolved = resolveWorkspaceImport(workspaceRoot, args.resolveDir || (args.importer ? path.dirname(args.importer) : workspaceRoot), args.path);
79
+ if (!resolved.ok) return { errors: [{ text: resolved.error }] };
80
+ return { path: resolved.path };
81
+ });
82
+ }
83
+ };
84
+ }
85
+ function resolveWorkspaceImport(workspaceRoot, baseDir, importPath) {
86
+ if (path.isAbsolute(importPath)) return {
87
+ ok: false,
88
+ error: `import "${importPath}" resolves outside the workspace`
89
+ };
90
+ const root = normalizePathForContainment(workspaceRoot);
91
+ const resolvedBaseDir = normalizePathForContainment(baseDir);
92
+ if (!isInsideWorkspace(root, resolvedBaseDir)) return {
93
+ ok: false,
94
+ error: `import "${importPath}" resolves outside the workspace`
95
+ };
96
+ const baseDirRelativeToRoot = path.relative(root, resolvedBaseDir);
97
+ const resolvedTarget = path.resolve(root, baseDirRelativeToRoot, importPath);
98
+ const relativeToRoot = path.relative(root, resolvedTarget);
99
+ if (relativeToRoot.startsWith("..") || path.isAbsolute(relativeToRoot)) return {
100
+ ok: false,
101
+ error: `import "${importPath}" resolves outside the workspace`
102
+ };
103
+ const target = normalizePathForContainment(resolvedTarget);
104
+ if (!isInsideWorkspace(root, target)) return {
105
+ ok: false,
106
+ error: `import "${importPath}" resolves outside the workspace`
107
+ };
108
+ for (const candidate of getLocalImportCandidates(target)) {
109
+ const resolved = resolveExistingFileInsideWorkspace(workspaceRoot, candidate);
110
+ if (resolved.status === "outside") return {
111
+ ok: false,
112
+ error: `import "${importPath}" resolves outside the workspace`
113
+ };
114
+ if (resolved.status === "file") return {
115
+ ok: true,
116
+ path: resolved.path
117
+ };
118
+ }
119
+ return {
120
+ ok: false,
121
+ error: `could not resolve "${importPath}" inside the workspace`
122
+ };
123
+ }
124
+ function getLocalImportCandidates(target) {
125
+ const candidates = [target];
126
+ for (const ext of RESOLVE_EXTENSIONS) candidates.push(`${target}${ext}`);
127
+ return candidates;
128
+ }
129
+ function resolveExistingFileInsideWorkspace(workspaceRoot, candidate) {
130
+ if (!isInsideWorkspace(workspaceRoot, candidate)) return { status: "outside" };
131
+ try {
132
+ fs.lstatSync(candidate);
133
+ } catch {
134
+ return { status: "missing" };
135
+ }
136
+ const realPath = normalizePathForContainment(fs.realpathSync.native(candidate));
137
+ if (!isInsideWorkspace(workspaceRoot, realPath)) return { status: "outside" };
138
+ if (!fs.statSync(candidate).isFile()) return { status: "missing" };
139
+ return {
140
+ status: "file",
141
+ path: realPath
142
+ };
143
+ }
144
+ function createBundledRendererVersion(code) {
145
+ return createHash("sha256").update(code).digest("hex");
146
+ }
147
+ function isReactExternal(specifier) {
148
+ return REACT_EXTERNALS.some((external) => external === specifier);
149
+ }
150
+ function isBareImport(specifier) {
151
+ return !specifier.startsWith(".") && !path.isAbsolute(specifier);
152
+ }
153
+ function isInsideWorkspace(root, fullPath) {
154
+ const resolvedRoot = normalizePathForContainment(root);
155
+ const resolved = normalizePathForContainment(fullPath);
156
+ const relative = path.relative(resolvedRoot, resolved);
157
+ return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
158
+ }
159
+ function normalizePathForContainment(value) {
160
+ const normalized = path.normalize(value).replace(/^(\.\.(\/|\\|$))+/, "");
161
+ if (process.platform === "darwin" && normalized.startsWith("/var/")) return `/private${normalized}`;
162
+ return normalized;
163
+ }
164
+ //#endregion
165
+ export { bundleCustomRenderer };
166
+
167
+ //# sourceMappingURL=bundle-custom-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundle-custom-renderer.js","names":[],"sources":["../../../../dashboard/src/lib/bundle-custom-renderer.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { build, type Plugin } from \"esbuild\";\n\n// React (and its jsx-runtime) must stay a single host instance: the client's\n// `evaluateModule` rewrites these imports to the host globals. Mark them\n// external so the bundle keeps the bare `import ... from \"react\"` statements\n// instead of inlining a second copy. Anything else (the renderer's own relative\n// imports of shared modules) IS bundled inline.\nexport const REACT_EXTERNALS = [\"react\", \"react/jsx-runtime\"] as const;\n\nexport type BundleCustomRendererResult =\n | { ok: true; code: string; version: string }\n | { ok: false; error: string };\n\nconst RESOLVE_EXTENSIONS = [\".tsx\", \".ts\", \".jsx\", \".js\"] as const;\n\n/**\n * Bundle a custom renderer entry file into a single ESM module.\n *\n * Unlike a bare transpile, bundling lets a renderer `import` shared sibling\n * modules within the workspace (e.g. a timeline component shared by the trace\n * and dataset-item views). esbuild resolves relative imports against each\n * file's directory and inlines them; React stays external so the client's\n * existing react/export rewrite evaluates the result unchanged. Single-file\n * renderers bundle to themselves, so this is backward compatible.\n *\n * `workspaceRoot` bounds the dependency graph: every resolved on-disk import\n * must stay inside it, mirroring `resolveWorkspaceViewPath`'s protection for\n * the entry file so a renderer can't reach outside the workspace via `../`.\n */\nexport async function bundleCustomRenderer(\n entryFullPath: string,\n workspaceRoot: string,\n): Promise<BundleCustomRendererResult> {\n const root = normalizePathForContainment(path.resolve(workspaceRoot));\n const entry = normalizePathForContainment(path.resolve(root, entryFullPath));\n if (!isInsideWorkspace(root, entry)) {\n return {\n ok: false,\n error: \"renderer entry resolves outside the workspace\",\n };\n }\n\n try {\n const result = await build({\n absWorkingDir: root,\n entryPoints: [entry],\n bundle: true,\n write: false,\n format: \"esm\",\n target: \"es2020\",\n jsx: \"automatic\",\n sourcemap: \"inline\",\n logLevel: \"silent\",\n external: [...REACT_EXTERNALS],\n plugins: [workspaceImportPolicyPlugin(root)],\n });\n const code = result.outputFiles?.[0]?.text;\n if (code == null) {\n return { ok: false, error: \"bundle produced no output\" };\n }\n\n return { ok: true, code, version: createBundledRendererVersion(code) };\n } catch (err) {\n return {\n ok: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\n/**\n * esbuild plugin that keeps renderer dependencies workspace-local. React-family\n * specifiers are external (left to the host), local filesystem imports must stay\n * under the workspace, and other bare package imports are rejected instead of\n * being bundled from node_modules.\n */\nfunction workspaceImportPolicyPlugin(workspaceRoot: string): Plugin {\n return {\n name: \"kaizen-workspace-import-policy\",\n setup(pluginBuild) {\n pluginBuild.onResolve({ filter: /.*/ }, (args) => {\n if (isReactExternal(args.path)) {\n return { external: true };\n }\n\n if (args.kind === \"entry-point\") {\n return undefined;\n }\n\n if (isBareImport(args.path)) {\n return {\n errors: [\n {\n text:\n `bare import \"${args.path}\" is not supported in custom renderers; ` +\n \"use a relative workspace module or React\",\n },\n ],\n };\n }\n\n const resolved = resolveWorkspaceImport(\n workspaceRoot,\n args.resolveDir ||\n (args.importer ? path.dirname(args.importer) : workspaceRoot),\n args.path,\n );\n if (!resolved.ok) {\n return {\n errors: [{ text: resolved.error }],\n };\n }\n\n return { path: resolved.path };\n });\n },\n };\n}\n\nfunction resolveWorkspaceImport(\n workspaceRoot: string,\n baseDir: string,\n importPath: string,\n): { ok: true; path: string } | { ok: false; error: string } {\n if (path.isAbsolute(importPath)) {\n return {\n ok: false,\n error: `import \"${importPath}\" resolves outside the workspace`,\n };\n }\n\n const root = normalizePathForContainment(workspaceRoot);\n const resolvedBaseDir = normalizePathForContainment(baseDir);\n if (!isInsideWorkspace(root, resolvedBaseDir)) {\n return {\n ok: false,\n error: `import \"${importPath}\" resolves outside the workspace`,\n };\n }\n\n const baseDirRelativeToRoot = path.relative(root, resolvedBaseDir);\n const resolvedTarget = path.resolve(root, baseDirRelativeToRoot, importPath);\n const relativeToRoot = path.relative(root, resolvedTarget);\n if (relativeToRoot.startsWith(\"..\") || path.isAbsolute(relativeToRoot)) {\n return {\n ok: false,\n error: `import \"${importPath}\" resolves outside the workspace`,\n };\n }\n const target = normalizePathForContainment(resolvedTarget);\n if (!isInsideWorkspace(root, target)) {\n return {\n ok: false,\n error: `import \"${importPath}\" resolves outside the workspace`,\n };\n }\n\n for (const candidate of getLocalImportCandidates(target)) {\n const resolved = resolveExistingFileInsideWorkspace(\n workspaceRoot,\n candidate,\n );\n if (resolved.status === \"outside\") {\n return {\n ok: false,\n error: `import \"${importPath}\" resolves outside the workspace`,\n };\n }\n if (resolved.status === \"file\") {\n return { ok: true, path: resolved.path };\n }\n }\n\n return {\n ok: false,\n error: `could not resolve \"${importPath}\" inside the workspace`,\n };\n}\n\nfunction getLocalImportCandidates(target: string): string[] {\n const candidates = [target];\n for (const ext of RESOLVE_EXTENSIONS) {\n candidates.push(`${target}${ext}`);\n }\n return candidates;\n}\n\nfunction resolveExistingFileInsideWorkspace(\n workspaceRoot: string,\n candidate: string,\n): { status: \"file\"; path: string } | { status: \"missing\" | \"outside\" } {\n if (!isInsideWorkspace(workspaceRoot, candidate)) {\n return { status: \"outside\" };\n }\n\n try {\n fs.lstatSync(candidate);\n } catch {\n return { status: \"missing\" };\n }\n\n const realPath = normalizePathForContainment(\n fs.realpathSync.native(candidate),\n );\n if (!isInsideWorkspace(workspaceRoot, realPath)) {\n return { status: \"outside\" };\n }\n\n const stat = fs.statSync(candidate);\n if (!stat.isFile()) {\n return { status: \"missing\" };\n }\n\n return { status: \"file\", path: realPath };\n}\n\nfunction createBundledRendererVersion(code: string): string {\n return createHash(\"sha256\").update(code).digest(\"hex\");\n}\n\nfunction isReactExternal(specifier: string): boolean {\n return REACT_EXTERNALS.some((external) => external === specifier);\n}\n\nfunction isBareImport(specifier: string): boolean {\n return !specifier.startsWith(\".\") && !path.isAbsolute(specifier);\n}\n\nfunction isInsideWorkspace(root: string, fullPath: string): boolean {\n const resolvedRoot = normalizePathForContainment(root);\n const resolved = normalizePathForContainment(fullPath);\n const relative = path.relative(resolvedRoot, resolved);\n return (\n relative === \"\" ||\n (!relative.startsWith(\"..\") && !path.isAbsolute(relative))\n );\n}\n\nfunction normalizePathForContainment(value: string): string {\n const normalized = path.normalize(value).replace(/^(\\.\\.(\\/|\\\\|$))+/, \"\");\n if (process.platform === \"darwin\" && normalized.startsWith(\"/var/\")) {\n return `/private${normalized}`;\n }\n return normalized;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"custom-renderer-files.js","names":[],"sources":["../../../../dashboard/src/lib/custom-renderer-files.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport fs from \"node:fs\";\nimport type { CustomViewSurface } from \"./custom-renderer-metadata\";\nimport {\n findCustomTraceRendererPath,\n findCustomViewPath,\n resolveWorkspaceViewPath,\n} from \"./custom-view-paths\";\nimport { isSafeRunPathSegment } from \"./run-api\";\n\ninterface CustomRendererFile {\n fullPath: string;\n rendererPath: string;\n source: string;\n version: string;\n}\n\ntype CustomRendererFileResult =\n | { ok: true; file: CustomRendererFile }\n | { ok: false; status: 400 | 403 | 404; error: string };\n\nexport function readCustomRendererFile(\n workspace: string,\n systemId: string,\n surface: CustomViewSurface,\n): CustomRendererFileResult {\n if (!isSafeRunPathSegment(systemId)) {\n return { ok: false, status: 400, error: \"invalid systemId\" };\n }\n\n const rendererPath =\n surface === \"trace\"\n ? findCustomTraceRendererPath(workspace, systemId)\n : findCustomViewPath(workspace, systemId, surface);\n if (!rendererPath) {\n return {\n ok: false,\n status: 404,\n error: `no custom ${surface} view found at kaizen/systems/${systemId}/${surface}.tsx`,\n };\n }\n\n const fullPath = resolveWorkspaceViewPath(workspace, rendererPath);\n if (!fullPath) {\n return {\n ok: false,\n status: 403,\n error: \"custom view path escapes workspace\",\n };\n }\n\n if (!fs.existsSync(fullPath)) {\n return {\n ok: false,\n status: 404,\n error: `trace renderer not found: ${rendererPath}`,\n };\n }\n\n const source = fs.readFileSync(fullPath, \"utf-8\");\n return {\n ok: true,\n file: {\n fullPath,\n rendererPath,\n source,\n version: createCustomRendererVersion(rendererPath, source),\n },\n };\n}\n\nfunction createCustomRendererVersion(\n rendererPath: string,\n source: string,\n): string {\n return createHash(\"sha256\")\n .update(rendererPath)\n .update(\"\\0\")\n .update(source)\n .digest(\"hex\");\n}\n"],"mappings":";;;;;AAqBA,SAAgB,uBACd,WACA,UACA,SAC0B;AAC1B,KAAI,CAAC,qBAAqB,SAAS,CACjC,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAK,OAAO;EAAoB;CAG9D,MAAM,eACJ,YAAY,UACR,4BAA4B,WAAW,SAAS,GAChD,mBAAmB,WAAW,UAAU,QAAQ;AACtD,KAAI,CAAC,aACH,QAAO;EACL,IAAI;EACJ,QAAQ;EACR,OAAO,aAAa,QAAQ,gCAAgC,SAAS,GAAG,QAAQ;EACjF;CAGH,MAAM,WAAW,yBAAyB,WAAW,aAAa;AAClE,KAAI,CAAC,SACH,QAAO;EACL,IAAI;EACJ,QAAQ;EACR,OAAO;EACR;AAGH,KAAI,CAAC,GAAG,WAAW,SAAS,CAC1B,QAAO;EACL,IAAI;EACJ,QAAQ;EACR,OAAO,6BAA6B;EACrC;CAGH,MAAM,SAAS,GAAG,aAAa,UAAU,QAAQ;AACjD,QAAO;EACL,IAAI;EACJ,MAAM;GACJ;GACA;GACA;GACA,SAAS,4BAA4B,cAAc,OAAO;GAC3D;EACF;;AAGH,SAAS,4BACP,cACA,QACQ;AACR,QAAO,WAAW,SAAS,CACxB,OAAO,aAAa,CACpB,OAAO,KAAK,CACZ,OAAO,OAAO,CACd,OAAO,MAAM"}
1
+ {"version":3,"file":"custom-renderer-files.js","names":[],"sources":["../../../../dashboard/src/lib/custom-renderer-files.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport fs from \"node:fs\";\nimport type { CustomViewSurface } from \"./custom-renderer-metadata\";\nimport {\n findCustomTraceRendererPath,\n findCustomViewPath,\n resolveWorkspaceViewPath,\n} from \"./custom-view-paths\";\nimport { isSafeRunPathSegment } from \"./run-api\";\n\ninterface CustomRendererFile {\n fullPath: string;\n rendererPath: string;\n source: string;\n version: string;\n}\n\ntype CustomRendererFileResult =\n | { ok: true; file: CustomRendererFile }\n | { ok: false; status: 400 | 403 | 404; error: string };\n\nexport function readCustomRendererFile(\n workspace: string,\n systemId: string,\n surface: CustomViewSurface,\n): CustomRendererFileResult {\n if (!isSafeRunPathSegment(systemId)) {\n return { ok: false, status: 400, error: \"invalid systemId\" };\n }\n\n const rendererPath =\n surface === \"trace\"\n ? findCustomTraceRendererPath(workspace, systemId)\n : findCustomViewPath(workspace, systemId, surface);\n if (!rendererPath) {\n return {\n ok: false,\n status: 404,\n error: `no custom ${surface} view found at kaizen/systems/${systemId}/${surface}.tsx`,\n };\n }\n\n const fullPath = resolveWorkspaceViewPath(workspace, rendererPath);\n if (!fullPath) {\n return {\n ok: false,\n status: 403,\n error: \"custom view path escapes workspace\",\n };\n }\n\n if (!fs.existsSync(fullPath)) {\n return {\n ok: false,\n status: 404,\n error: `trace renderer not found: ${rendererPath}`,\n };\n }\n\n const source = fs.readFileSync(fullPath, \"utf-8\");\n return {\n ok: true,\n file: {\n fullPath,\n rendererPath,\n source,\n version: createCustomRendererVersion(rendererPath, source),\n },\n };\n}\n\nfunction createCustomRendererVersion(\n rendererPath: string,\n source: string,\n): string {\n return createHash(\"sha256\")\n .update(rendererPath)\n .update(\"\\0\")\n .update(source)\n .digest(\"hex\");\n}\n"],"mappings":";;;;;AAqBA,SAAgB,uBACd,WACA,UACA,SAC0B;CAC1B,IAAI,CAAC,qBAAqB,QAAQ,GAChC,OAAO;EAAE,IAAI;EAAO,QAAQ;EAAK,OAAO;CAAmB;CAG7D,MAAM,eACJ,YAAY,UACR,4BAA4B,WAAW,QAAQ,IAC/C,mBAAmB,WAAW,UAAU,OAAO;CACrD,IAAI,CAAC,cACH,OAAO;EACL,IAAI;EACJ,QAAQ;EACR,OAAO,aAAa,QAAQ,gCAAgC,SAAS,GAAG,QAAQ;CAClF;CAGF,MAAM,WAAW,yBAAyB,WAAW,YAAY;CACjE,IAAI,CAAC,UACH,OAAO;EACL,IAAI;EACJ,QAAQ;EACR,OAAO;CACT;CAGF,IAAI,CAAC,GAAG,WAAW,QAAQ,GACzB,OAAO;EACL,IAAI;EACJ,QAAQ;EACR,OAAO,6BAA6B;CACtC;CAGF,MAAM,SAAS,GAAG,aAAa,UAAU,OAAO;CAChD,OAAO;EACL,IAAI;EACJ,MAAM;GACJ;GACA;GACA;GACA,SAAS,4BAA4B,cAAc,MAAM;EAC3D;CACF;AACF;AAEA,SAAS,4BACP,cACA,QACQ;CACR,OAAO,WAAW,QAAQ,EACvB,OAAO,YAAY,EACnB,OAAO,IAAI,EACX,OAAO,MAAM,EACb,OAAO,KAAK;AACjB"}
@@ -1 +1 @@
1
- {"version":3,"file":"custom-renderer-metadata.js","names":[],"sources":["../../../../dashboard/src/lib/custom-renderer-metadata.ts"],"sourcesContent":["export type CustomViewSurface = \"trace\" | \"dataset-item\";\n\nexport const CUSTOM_RENDERER_VERSION_HEADER = \"X-Kaizen-Renderer-Version\";\n\nexport function parseCustomViewSurface(\n value: string | string[] | undefined,\n): CustomViewSurface | null {\n const raw = Array.isArray(value) ? value[0] : value;\n if (raw === undefined || raw === \"trace\") return \"trace\";\n if (raw === \"dataset-item\") return \"dataset-item\";\n return null;\n}\n"],"mappings":";AAEA,MAAa,iCAAiC;AAE9C,SAAgB,uBACd,OAC0B;CAC1B,MAAM,MAAM,MAAM,QAAQ,MAAM,GAAG,MAAM,KAAK;AAC9C,KAAI,QAAQ,KAAA,KAAa,QAAQ,QAAS,QAAO;AACjD,KAAI,QAAQ,eAAgB,QAAO;AACnC,QAAO"}
1
+ {"version":3,"file":"custom-renderer-metadata.js","names":[],"sources":["../../../../dashboard/src/lib/custom-renderer-metadata.ts"],"sourcesContent":["export type CustomViewSurface = \"trace\" | \"dataset-item\";\n\nexport const CUSTOM_RENDERER_VERSION_HEADER = \"X-Kaizen-Renderer-Version\";\n\nexport function parseCustomViewSurface(\n value: string | string[] | undefined,\n): CustomViewSurface | null {\n const raw = Array.isArray(value) ? value[0] : value;\n if (raw === undefined || raw === \"trace\") return \"trace\";\n if (raw === \"dataset-item\") return \"dataset-item\";\n return null;\n}\n"],"mappings":";AAEA,MAAa,iCAAiC;AAE9C,SAAgB,uBACd,OAC0B;CAC1B,MAAM,MAAM,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK;CAC9C,IAAI,QAAQ,KAAA,KAAa,QAAQ,SAAS,OAAO;CACjD,IAAI,QAAQ,gBAAgB,OAAO;CACnC,OAAO;AACT"}