@researai/deepscientist 1.5.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/AGENTS.md +26 -0
  2. package/README.md +19 -179
  3. package/assets/connectors/lingzhu/openclaw-bridge/README.md +124 -0
  4. package/assets/connectors/lingzhu/openclaw-bridge/index.ts +162 -0
  5. package/assets/connectors/lingzhu/openclaw-bridge/openclaw.plugin.json +145 -0
  6. package/assets/connectors/lingzhu/openclaw-bridge/package.json +35 -0
  7. package/assets/connectors/lingzhu/openclaw-bridge/src/cli.ts +180 -0
  8. package/assets/connectors/lingzhu/openclaw-bridge/src/config.ts +196 -0
  9. package/assets/connectors/lingzhu/openclaw-bridge/src/debug-log.ts +111 -0
  10. package/assets/connectors/lingzhu/openclaw-bridge/src/events.ts +4 -0
  11. package/assets/connectors/lingzhu/openclaw-bridge/src/http-handler.ts +1133 -0
  12. package/assets/connectors/lingzhu/openclaw-bridge/src/image-cache.ts +75 -0
  13. package/assets/connectors/lingzhu/openclaw-bridge/src/lingzhu-tools.ts +246 -0
  14. package/assets/connectors/lingzhu/openclaw-bridge/src/transform.ts +541 -0
  15. package/assets/connectors/lingzhu/openclaw-bridge/src/types.ts +131 -0
  16. package/assets/connectors/lingzhu/openclaw-bridge/tsconfig.json +14 -0
  17. package/assets/connectors/lingzhu/openclaw.lingzhu.config.template.json +39 -0
  18. package/bin/ds.js +233 -53
  19. package/docs/en/00_QUICK_START.md +134 -0
  20. package/docs/en/01_SETTINGS_REFERENCE.md +1104 -0
  21. package/docs/en/02_START_RESEARCH_GUIDE.md +404 -0
  22. package/docs/en/03_QQ_CONNECTOR_GUIDE.md +325 -0
  23. package/docs/en/04_LINGZHU_CONNECTOR_GUIDE.md +216 -0
  24. package/docs/en/05_TUI_GUIDE.md +141 -0
  25. package/docs/en/06_RUNTIME_AND_CANVAS.md +679 -0
  26. package/docs/en/07_MEMORY_AND_MCP.md +253 -0
  27. package/docs/en/08_FIGURE_STYLE_GUIDE.md +97 -0
  28. package/docs/en/09_DOCTOR.md +108 -0
  29. package/docs/en/90_ARCHITECTURE.md +245 -0
  30. package/docs/en/91_DEVELOPMENT.md +195 -0
  31. package/docs/en/99_ACKNOWLEDGEMENTS.md +29 -0
  32. package/docs/zh/00_QUICK_START.md +134 -0
  33. package/docs/zh/01_SETTINGS_REFERENCE.md +1137 -0
  34. package/docs/zh/02_START_RESEARCH_GUIDE.md +414 -0
  35. package/docs/zh/03_QQ_CONNECTOR_GUIDE.md +324 -0
  36. package/docs/zh/04_LINGZHU_CONNECTOR_GUIDE.md +230 -0
  37. package/docs/zh/05_TUI_GUIDE.md +128 -0
  38. package/docs/zh/06_RUNTIME_AND_CANVAS.md +271 -0
  39. package/docs/zh/07_MEMORY_AND_MCP.md +235 -0
  40. package/docs/zh/08_FIGURE_STYLE_GUIDE.md +97 -0
  41. package/docs/zh/09_DOCTOR.md +112 -0
  42. package/docs/zh/99_ACKNOWLEDGEMENTS.md +29 -0
  43. package/install.sh +32 -8
  44. package/package.json +4 -2
  45. package/pyproject.toml +1 -1
  46. package/src/deepscientist/artifact/guidance.py +9 -2
  47. package/src/deepscientist/artifact/service.py +482 -22
  48. package/src/deepscientist/bash_exec/monitor.py +27 -5
  49. package/src/deepscientist/bash_exec/runtime.py +639 -0
  50. package/src/deepscientist/bash_exec/service.py +99 -16
  51. package/src/deepscientist/bridges/base.py +3 -0
  52. package/src/deepscientist/bridges/connectors.py +292 -13
  53. package/src/deepscientist/channels/qq.py +19 -2
  54. package/src/deepscientist/channels/relay.py +1 -0
  55. package/src/deepscientist/cli.py +32 -25
  56. package/src/deepscientist/config/models.py +28 -2
  57. package/src/deepscientist/config/service.py +201 -6
  58. package/src/deepscientist/connector_runtime.py +2 -0
  59. package/src/deepscientist/daemon/api/handlers.py +50 -5
  60. package/src/deepscientist/daemon/api/router.py +1 -0
  61. package/src/deepscientist/daemon/app.py +442 -15
  62. package/src/deepscientist/doctor.py +444 -0
  63. package/src/deepscientist/home.py +1 -0
  64. package/src/deepscientist/latex_runtime.py +17 -4
  65. package/src/deepscientist/lingzhu_support.py +182 -0
  66. package/src/deepscientist/mcp/server.py +49 -2
  67. package/src/deepscientist/prompts/builder.py +181 -58
  68. package/src/deepscientist/quest/layout.py +1 -0
  69. package/src/deepscientist/quest/service.py +63 -2
  70. package/src/deepscientist/quest/stage_views.py +19 -1
  71. package/src/deepscientist/runtime_tools/__init__.py +16 -0
  72. package/src/deepscientist/runtime_tools/builtins.py +19 -0
  73. package/src/deepscientist/runtime_tools/models.py +29 -0
  74. package/src/deepscientist/runtime_tools/registry.py +40 -0
  75. package/src/deepscientist/runtime_tools/service.py +59 -0
  76. package/src/deepscientist/runtime_tools/tinytex.py +25 -0
  77. package/src/deepscientist/tinytex.py +276 -0
  78. package/src/prompts/connectors/lingzhu.md +12 -0
  79. package/src/prompts/connectors/qq.md +121 -0
  80. package/src/prompts/system.md +177 -33
  81. package/src/skills/analysis-campaign/SKILL.md +22 -6
  82. package/src/skills/baseline/SKILL.md +5 -4
  83. package/src/skills/decision/SKILL.md +4 -3
  84. package/src/skills/experiment/SKILL.md +5 -4
  85. package/src/skills/finalize/SKILL.md +5 -4
  86. package/src/skills/idea/SKILL.md +5 -4
  87. package/src/skills/intake-audit/SKILL.md +277 -0
  88. package/src/skills/intake-audit/references/state-audit-template.md +41 -0
  89. package/src/skills/rebuttal/SKILL.md +407 -0
  90. package/src/skills/rebuttal/references/action-plan-template.md +63 -0
  91. package/src/skills/rebuttal/references/evidence-update-template.md +30 -0
  92. package/src/skills/rebuttal/references/response-letter-template.md +113 -0
  93. package/src/skills/rebuttal/references/review-matrix-template.md +55 -0
  94. package/src/skills/review/SKILL.md +293 -0
  95. package/src/skills/review/references/experiment-todo-template.md +29 -0
  96. package/src/skills/review/references/review-report-template.md +83 -0
  97. package/src/skills/review/references/revision-log-template.md +40 -0
  98. package/src/skills/scout/SKILL.md +5 -4
  99. package/src/skills/write/SKILL.md +7 -3
  100. package/src/tui/dist/components/WelcomePanel.js +17 -43
  101. package/src/tui/dist/components/messages/BashExecOperationMessage.js +3 -2
  102. package/src/tui/package.json +1 -1
  103. package/src/ui/dist/assets/{AiManusChatView-7v-dHngU.js → AiManusChatView-w5lF2Ttt.js} +109 -575
  104. package/src/ui/dist/assets/{AnalysisPlugin-B_Xmz-KE.js → AnalysisPlugin-DJOED79I.js} +1 -1
  105. package/src/ui/dist/assets/{AutoFigurePlugin-Cko-0tm1.js → AutoFigurePlugin-DaG61Y0M.js} +63 -8
  106. package/src/ui/dist/assets/{CliPlugin-BsU0ht7q.js → CliPlugin-CV4LqUB_.js} +43 -609
  107. package/src/ui/dist/assets/{CodeEditorPlugin-DcMMP0Rt.js → CodeEditorPlugin-DylfAea4.js} +8 -8
  108. package/src/ui/dist/assets/{CodeViewerPlugin-BqoQ5QyY.js → CodeViewerPlugin-F7saY0LM.js} +5 -5
  109. package/src/ui/dist/assets/{DocViewerPlugin-D7eHNhU6.js → DocViewerPlugin-COP0c7jf.js} +3 -3
  110. package/src/ui/dist/assets/{GitDiffViewerPlugin-DLJN42T5.js → GitDiffViewerPlugin-CAS05pT9.js} +1 -1
  111. package/src/ui/dist/assets/{ImageViewerPlugin-gJMV7MOu.js → ImageViewerPlugin-Bco1CN_w.js} +5 -6
  112. package/src/ui/dist/assets/{LabCopilotPanel-B857sfxP.js → LabCopilotPanel-CvMlCD99.js} +12 -15
  113. package/src/ui/dist/assets/LabPlugin-BYankkE4.js +2676 -0
  114. package/src/ui/dist/assets/LabPlugin-D9jVIo0A.css +2698 -0
  115. package/src/ui/dist/assets/{LatexPlugin-DWKEo-Wj.js → LatexPlugin-LDSMR-t-.js} +16 -16
  116. package/src/ui/dist/assets/{MarkdownViewerPlugin-DBzoEmhv.js → MarkdownViewerPlugin-B7o80jgm.js} +4 -4
  117. package/src/ui/dist/assets/{MarketplacePlugin-DoHc-8vo.js → MarketplacePlugin-CM6ZOcpC.js} +3 -3
  118. package/src/ui/dist/assets/{NotebookEditor-CKjKH-yS.js → NotebookEditor-Dc61cXmK.js} +3 -3
  119. package/src/ui/dist/assets/{PdfLoader-zFoL0VPo.js → PdfLoader-DWowuQwx.js} +1 -1
  120. package/src/ui/dist/assets/{PdfMarkdownPlugin-DXPaL9Nt.js → PdfMarkdownPlugin-BsJM1q_a.js} +3 -3
  121. package/src/ui/dist/assets/{PdfViewerPlugin-DhK8qCFp.js → PdfViewerPlugin-DB2eEEFQ.js} +10 -10
  122. package/src/ui/dist/assets/{SearchPlugin-CdSi6krf.js → SearchPlugin-CraThSvt.js} +1 -1
  123. package/src/ui/dist/assets/{Stepper-V-WiDQJl.js → Stepper-CgocRTPq.js} +1 -1
  124. package/src/ui/dist/assets/{TextViewerPlugin-hIs1Efiu.js → TextViewerPlugin-B1JGhKtd.js} +4 -4
  125. package/src/ui/dist/assets/{VNCViewer-DG8b0q2X.js → VNCViewer-CclFC7FM.js} +9 -10
  126. package/src/ui/dist/assets/{bibtex-HDac6fVW.js → bibtex-D3IKsMl7.js} +1 -1
  127. package/src/ui/dist/assets/{code-BnBeNxBc.js → code-BP37Xx0p.js} +1 -1
  128. package/src/ui/dist/assets/{file-content-IRQ3jHb8.js → file-content-BAJSu-9r.js} +1 -1
  129. package/src/ui/dist/assets/{file-diff-panel-DZoQ9I6r.js → file-diff-panel-DUGeCTuy.js} +1 -1
  130. package/src/ui/dist/assets/{file-socket-BMCdLc-P.js → file-socket-CXc1Ojf7.js} +1 -1
  131. package/src/ui/dist/assets/{file-utils-CltILB3w.js → file-utils-2J21jt7M.js} +1 -1
  132. package/src/ui/dist/assets/{image-Boe6ffhu.js → image-CMMmgvcn.js} +1 -1
  133. package/src/ui/dist/assets/{index-BlplpvE1.js → index-BaVumsQT.js} +2 -2
  134. package/src/ui/dist/assets/{index-DZqJ-qAM.js → index-CWgMgpow.js} +60 -2154
  135. package/src/ui/dist/assets/{index-DO43pFZP.js → index-DmwmJmbW.js} +6372 -8434
  136. package/src/ui/dist/assets/{index-Bq2bvfkl.css → index-KGt-z-dD.css} +225 -2920
  137. package/src/ui/dist/assets/{index-2Zf65FZt.js → index-s7aHnNQ4.js} +1 -1
  138. package/src/ui/dist/assets/{message-square-mUHn_Ssb.js → message-square-CQRfX0Am.js} +1 -1
  139. package/src/ui/dist/assets/{monaco-fe0arNEU.js → monaco-B4TbdsrF.js} +1 -1
  140. package/src/ui/dist/assets/{popover-D_7i19qU.js → popover-B8Rokodk.js} +1 -1
  141. package/src/ui/dist/assets/{project-sync-DyVGrU7H.js → project-sync-D_i96KH4.js} +2 -8
  142. package/src/ui/dist/assets/{sigma-BzazRyxQ.js → sigma-D12PnzCN.js} +1 -1
  143. package/src/ui/dist/assets/{tooltip-DN_yjHFH.js → tooltip-B6YrI4aJ.js} +1 -1
  144. package/src/ui/dist/assets/trash-Bc8jGp0V.js +32 -0
  145. package/src/ui/dist/assets/{useCliAccess-DV2L2Qxy.js → useCliAccess-mXVCYSZ-.js} +12 -42
  146. package/src/ui/dist/assets/{useFileDiffOverlay-DyTj-p_V.js → useFileDiffOverlay-Bg6b9H9K.js} +1 -1
  147. package/src/ui/dist/assets/{wrap-text-ozYHtUwq.js → wrap-text-Drh5GEnL.js} +1 -1
  148. package/src/ui/dist/assets/{zoom-out-BN9MUyCQ.js → zoom-out-CJj9DZLn.js} +1 -1
  149. package/src/ui/dist/index.html +2 -2
  150. package/assets/fonts/Inter-Variable.ttf +0 -0
  151. package/assets/fonts/NotoSerifSC-Regular-C94HN_ZN.ttf +0 -0
  152. package/assets/fonts/NunitoSans-Variable.ttf +0 -0
  153. package/assets/fonts/Satoshi-Medium-ByP-Zb-9.woff2 +0 -0
  154. package/assets/fonts/SourceSans3-Variable.ttf +0 -0
  155. package/assets/fonts/ds-fonts.css +0 -83
  156. package/src/ui/dist/assets/Inter-Variable-VF2RPR_K.ttf +0 -0
  157. package/src/ui/dist/assets/LabPlugin-bL7rpic8.js +0 -43
  158. package/src/ui/dist/assets/NotoSerifSC-Regular-C94HN_ZN-C94HN_ZN.ttf +0 -0
  159. package/src/ui/dist/assets/NunitoSans-Variable-B_ZymHAd.ttf +0 -0
  160. package/src/ui/dist/assets/Satoshi-Medium-ByP-Zb-9-GkA34YXu.woff2 +0 -0
  161. package/src/ui/dist/assets/SourceSans3-Variable-CD-WOsSK.ttf +0 -0
  162. package/src/ui/dist/assets/info-CcsK_htA.js +0 -18
  163. package/src/ui/dist/assets/user-plus-BusDx-hF.js +0 -79
@@ -0,0 +1,2676 @@
1
+ import { z as createLucideIcon, af as useQuery, r as reactExports, j as jsxRuntimeExports, bf as Skeleton, bg as Badge, ag as client, bh as ReactFlowProvider, u as useI18n, aF as resolveAgentDisplayName, aT as resolveAgentLogo, aW as pickAvatarFrameColor, aQ as isLabWorkingStatus, bi as normalizeLabStatus, bj as useNodesState, bk as useReactFlow, bl as index, bm as Background, b as cn, bn as Handle, bo as Position, bp as CircleHelp, bq as OrbitLogoStatus, br as Clock, bs as Moon, aI as formatRelativeTime, n as ChevronDown, aM as useQueryClient, aA as useLabCopilotStore, aN as useLabGraphSelectionStore, bt as resolveLabCanvasSelectionSemantic, a3 as Button, g as RefreshCw, bu as LabQuestGraphCanvas, bv as ArrowLeft, aZ as ScrollArea, D as Sparkles, bw as GitBranch, bx as Bot, by as LAB_CANVAS_SEMANTIC_TONE_META, bz as getLabQuest, bA as getLabQuestSummary, bB as getLabQuestEventPayload, bC as getLabQuestNodeTrace, bD as isQuestRuntimeSurface, bE as getApiBaseUrl, bF as redirectToLanding, bG as useCliStore, bH as useActiveTab, y as BUILTIN_PLUGINS, bI as listLabTemplates, aB as listLabAgents, aC as listLabQuests, bJ as getLabOverview, bK as getProject, bL as useMaxEntitlement } from './index-DmwmJmbW.js';
2
+
3
+ /**
4
+ * @license lucide-react v0.511.0 - ISC
5
+ *
6
+ * This source code is licensed under the ISC license.
7
+ * See the LICENSE file in the root directory of this source tree.
8
+ */
9
+
10
+
11
+ const __iconNode$1 = [
12
+ ["polyline", { points: "22 12 16 12 14 15 10 15 8 12 2 12", key: "o97t9d" }],
13
+ [
14
+ "path",
15
+ {
16
+ d: "M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z",
17
+ key: "oot6mr"
18
+ }
19
+ ]
20
+ ];
21
+ const Inbox = createLucideIcon("inbox", __iconNode$1);
22
+
23
+ /**
24
+ * @license lucide-react v0.511.0 - ISC
25
+ *
26
+ * This source code is licensed under the ISC license.
27
+ * See the LICENSE file in the root directory of this source tree.
28
+ */
29
+
30
+
31
+ const __iconNode = [
32
+ ["rect", { width: "7", height: "9", x: "3", y: "3", rx: "1", key: "10lvy0" }],
33
+ ["rect", { width: "7", height: "5", x: "14", y: "3", rx: "1", key: "16une8" }],
34
+ ["rect", { width: "7", height: "9", x: "14", y: "12", rx: "1", key: "1hutg5" }],
35
+ ["rect", { width: "7", height: "5", x: "3", y: "16", rx: "1", key: "ldoo1y" }]
36
+ ];
37
+ const LayoutDashboard = createLucideIcon("layout-dashboard", __iconNode);
38
+
39
+ const formatStateLabel$1 = (value) => {
40
+ const normalized = String(value || "").trim().replace(/[_-]+/g, " ");
41
+ if (!normalized) return "N/A";
42
+ return normalized.replace(/\b\w/g, (char) => char.toUpperCase());
43
+ };
44
+ const prettyJson = (value) => {
45
+ try {
46
+ return JSON.stringify(value, null, 2);
47
+ } catch {
48
+ return String(value);
49
+ }
50
+ };
51
+ const asRecord$1 = (value) => {
52
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
53
+ return value;
54
+ };
55
+ const asString = (value) => {
56
+ if (typeof value === "string") return value.trim() || null;
57
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
58
+ return null;
59
+ };
60
+ function MetaCard({
61
+ label,
62
+ value
63
+ }) {
64
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-[14px] border border-[var(--lab-border)] bg-[var(--lab-background)] px-3 py-2.5", children: [
65
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] font-semibold uppercase tracking-[0.12em] text-[var(--lab-text-secondary)]", children: label }),
66
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 text-sm text-[var(--lab-text-primary)]", children: value })
67
+ ] });
68
+ }
69
+ function LabNodeTraceDetail({
70
+ projectId,
71
+ questId,
72
+ trace,
73
+ isLoading,
74
+ payloadJson,
75
+ payloadTruncated
76
+ }) {
77
+ const primaryPayload = trace?.payload_json || asRecord$1(payloadJson)?.payload || payloadJson || null;
78
+ const primaryAction = [...trace?.actions || []].reverse().find((action) => action.artifact_id || action.head_commit) || null;
79
+ const headCommit = trace?.head_commit || primaryAction?.head_commit || null;
80
+ const artifactKind = trace?.artifact_kind || primaryAction?.artifact_kind || null;
81
+ const artifactId = trace?.artifact_id || primaryAction?.artifact_id || null;
82
+ const commitQuery = useQuery({
83
+ queryKey: ["lab-trace-commit", questId, headCommit],
84
+ queryFn: () => client.gitCommit(questId, headCommit),
85
+ enabled: Boolean(questId && headCommit),
86
+ staleTime: 15e3
87
+ });
88
+ const fileCandidates = reactExports.useMemo(() => {
89
+ const explicit = [...trace?.changed_files || []].filter(Boolean);
90
+ const commitFiles = commitQuery.data?.files?.map((file) => file.path).filter(Boolean) || [];
91
+ const payloadPaths = Object.values(trace?.paths_map || {}).map((entry) => asString(entry)).filter((entry) => Boolean(entry && !entry.startsWith("/")));
92
+ return [.../* @__PURE__ */ new Set([...explicit, ...commitFiles, ...payloadPaths])];
93
+ }, [commitQuery.data?.files, trace?.changed_files, trace?.paths_map]);
94
+ const [selectedPath, setSelectedPath] = reactExports.useState(null);
95
+ reactExports.useEffect(() => {
96
+ if (!fileCandidates.length) {
97
+ setSelectedPath(null);
98
+ return;
99
+ }
100
+ if (!selectedPath || !fileCandidates.includes(selectedPath)) {
101
+ setSelectedPath(fileCandidates[0] || null);
102
+ }
103
+ }, [fileCandidates, selectedPath]);
104
+ const commitFileQuery = useQuery({
105
+ queryKey: ["lab-trace-commit-file", questId, headCommit, selectedPath],
106
+ queryFn: () => client.gitCommitFile(questId, headCommit, selectedPath),
107
+ enabled: Boolean(questId && headCommit && selectedPath),
108
+ staleTime: 15e3
109
+ });
110
+ if (isLoading && !trace) {
111
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [
112
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-24 w-full rounded-[18px]" }),
113
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-20 w-full rounded-[18px]" }),
114
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-32 w-full rounded-[18px]" })
115
+ ] });
116
+ }
117
+ if (!trace) {
118
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-[18px] border border-[var(--lab-border)] bg-[var(--lab-surface)] px-4 py-4 text-sm text-[var(--lab-text-secondary)]", children: "No trace is attached to this node yet." });
119
+ }
120
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [
121
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-[18px] border border-[var(--lab-border)] bg-[var(--lab-surface)] px-4 py-4", children: [
122
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
123
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: formatStateLabel$1(trace.selection_type) }),
124
+ trace.stage_title ? /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: trace.stage_title }) : null,
125
+ trace.status ? /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: formatStateLabel$1(trace.status) }) : null,
126
+ artifactKind ? /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: formatStateLabel$1(artifactKind) }) : null
127
+ ] }),
128
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-3 text-sm font-semibold text-[var(--lab-text-primary)]", children: trace.title }),
129
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 text-xs leading-5 text-[var(--lab-text-secondary)]", children: trace.summary || "No normalized summary is available for this node." })
130
+ ] }),
131
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
132
+ /* @__PURE__ */ jsxRuntimeExports.jsx(MetaCard, { label: "Branch", value: trace.branch_name || "N/A" }),
133
+ /* @__PURE__ */ jsxRuntimeExports.jsx(MetaCard, { label: "Stage", value: trace.stage_title || "N/A" }),
134
+ /* @__PURE__ */ jsxRuntimeExports.jsx(MetaCard, { label: "Artifact", value: artifactId || "N/A" }),
135
+ /* @__PURE__ */ jsxRuntimeExports.jsx(MetaCard, { label: "Commit", value: headCommit || "N/A" }),
136
+ /* @__PURE__ */ jsxRuntimeExports.jsx(MetaCard, { label: "Actions", value: trace.counts?.actions ?? trace.actions.length }),
137
+ /* @__PURE__ */ jsxRuntimeExports.jsx(MetaCard, { label: "Updated", value: trace.updated_at || "N/A" })
138
+ ] }),
139
+ primaryPayload ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-[18px] border border-[var(--lab-border)] bg-[var(--lab-surface)] px-4 py-4", children: [
140
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
141
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs font-semibold uppercase tracking-[0.12em] text-[var(--lab-text-secondary)]", children: "Artifact Payload" }),
142
+ payloadTruncated ? /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: "Truncated" }) : null
143
+ ] }),
144
+ /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "mt-3 max-h-[280px] overflow-auto whitespace-pre-wrap rounded-[14px] bg-[var(--lab-background)] p-3 text-[11px] leading-5 text-[var(--lab-text-primary)]", children: prettyJson(primaryPayload) })
145
+ ] }) : null,
146
+ headCommit ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-[18px] border border-[var(--lab-border)] bg-[var(--lab-surface)] px-4 py-4", children: [
147
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs font-semibold uppercase tracking-[0.12em] text-[var(--lab-text-secondary)]", children: "Commit" }),
148
+ commitQuery.isLoading ? /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "mt-3 h-20 w-full rounded-[14px]" }) : commitQuery.data ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3 space-y-2 text-xs text-[var(--lab-text-secondary)]", children: [
149
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-semibold text-[var(--lab-text-primary)]", children: commitQuery.data.subject }),
150
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
151
+ commitQuery.data.short_sha,
152
+ " · ",
153
+ commitQuery.data.author_name || "Unknown author"
154
+ ] }),
155
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: commitQuery.data.authored_at || "Unknown time" }),
156
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
157
+ commitQuery.data.file_count || 0,
158
+ " files · +",
159
+ commitQuery.data.stats?.added || 0,
160
+ " / -",
161
+ commitQuery.data.stats?.removed || 0
162
+ ] })
163
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-3 text-sm text-[var(--lab-text-secondary)]", children: "Commit metadata is unavailable." })
164
+ ] }) : null,
165
+ fileCandidates.length ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-[18px] border border-[var(--lab-border)] bg-[var(--lab-surface)] px-4 py-4", children: [
166
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs font-semibold uppercase tracking-[0.12em] text-[var(--lab-text-secondary)]", children: "Changed Files" }),
167
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-3 flex flex-wrap gap-2", children: fileCandidates.slice(0, 12).map((path) => /* @__PURE__ */ jsxRuntimeExports.jsx(
168
+ "button",
169
+ {
170
+ type: "button",
171
+ className: `rounded-full border px-2.5 py-1 text-[11px] ${selectedPath === path ? "border-[var(--lab-accent-strong)] bg-[rgba(64,113,175,0.1)] text-[var(--lab-text-primary)]" : "border-[var(--lab-border)] bg-[var(--lab-background)] text-[var(--lab-text-secondary)]"}`,
172
+ onClick: () => setSelectedPath(path),
173
+ children: path
174
+ },
175
+ path
176
+ )) }),
177
+ selectedPath ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3", children: [
178
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs font-semibold text-[var(--lab-text-primary)]", children: selectedPath }),
179
+ commitFileQuery.isLoading ? /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "mt-2 h-36 w-full rounded-[14px]" }) : commitFileQuery.data?.lines?.length ? /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "mt-2 max-h-[320px] overflow-auto whitespace-pre-wrap rounded-[14px] bg-[var(--lab-background)] p-3 text-[11px] leading-5 text-[var(--lab-text-primary)]", children: commitFileQuery.data.lines.join("\n") }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 text-sm text-[var(--lab-text-secondary)]", children: projectId && questId ? "Preview unavailable for this file." : "Preview unavailable." })
180
+ ] }) : null
181
+ ] }) : null,
182
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-[18px] border border-[var(--lab-border)] bg-[var(--lab-surface)] px-4 py-4", children: [
183
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs font-semibold uppercase tracking-[0.12em] text-[var(--lab-text-secondary)]", children: "Trace Actions" }),
184
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-3 space-y-3", children: trace.actions.length ? trace.actions.map((action) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
185
+ "div",
186
+ {
187
+ className: "rounded-[14px] border border-[var(--lab-border)] bg-[var(--lab-background)] px-3 py-3",
188
+ children: [
189
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
190
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-semibold text-[var(--lab-text-primary)]", children: action.title || action.tool_name || action.raw_event_type || action.kind || action.action_id }),
191
+ action.kind ? /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: formatStateLabel$1(action.kind) }) : null,
192
+ action.status ? /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: formatStateLabel$1(action.status) }) : null,
193
+ action.artifact_kind ? /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: formatStateLabel$1(action.artifact_kind) }) : null
194
+ ] }),
195
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 text-[11px] text-[var(--lab-text-secondary)]", children: action.created_at || "N/A" }),
196
+ action.summary ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 text-xs leading-5 text-[var(--lab-text-secondary)]", children: action.summary }) : null,
197
+ action.head_commit ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 text-xs text-[var(--lab-text-secondary)]", children: [
198
+ "Commit: ",
199
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-semibold text-[var(--lab-text-primary)]", children: action.head_commit })
200
+ ] }) : null,
201
+ action.tool_name ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 text-xs text-[var(--lab-text-secondary)]", children: [
202
+ "Tool: ",
203
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-semibold text-[var(--lab-text-primary)]", children: action.tool_name })
204
+ ] }) : null,
205
+ action.mcp_server || action.mcp_tool ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 text-xs text-[var(--lab-text-secondary)]", children: [
206
+ "MCP:",
207
+ " ",
208
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-semibold text-[var(--lab-text-primary)]", children: [
209
+ action.mcp_server || "unknown",
210
+ action.mcp_tool ? `.${action.mcp_tool}` : ""
211
+ ] })
212
+ ] }) : null,
213
+ action.args ? /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "mt-2 max-h-36 overflow-auto whitespace-pre-wrap rounded-[12px] bg-[rgba(15,23,42,0.04)] p-2 text-[11px] leading-5 text-[var(--lab-text-primary)]", children: action.args }) : null,
214
+ action.output ? /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "mt-2 max-h-40 overflow-auto whitespace-pre-wrap rounded-[12px] bg-[rgba(15,23,42,0.04)] p-2 text-[11px] leading-5 text-[var(--lab-text-primary)]", children: action.output }) : null
215
+ ]
216
+ },
217
+ action.action_id
218
+ )) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm text-[var(--lab-text-secondary)]", children: "No action records yet." }) })
219
+ ] })
220
+ ] });
221
+ }
222
+
223
+ const LAB_FOCUS_EVENT$1 = "ds:lab:focus";
224
+ const dispatchLabFocus = (payload) => {
225
+ if (typeof window === "undefined") return;
226
+ window.dispatchEvent(new CustomEvent(LAB_FOCUS_EVENT$1, { detail: payload }));
227
+ };
228
+
229
+ const getOverviewNodeKind = (data) => {
230
+ if ("kind" in data) return data.kind;
231
+ return "quest";
232
+ };
233
+ const isOverviewNodeDataEqual = (left, right) => {
234
+ const leftKind = getOverviewNodeKind(left);
235
+ const rightKind = getOverviewNodeKind(right);
236
+ if (leftKind !== rightKind) return false;
237
+ if (leftKind === "quest" && rightKind === "quest") {
238
+ const a = left;
239
+ const b = right;
240
+ return a.questId === b.questId && a.title === b.title && a.status === b.status && a.piState === b.piState && a.baselineBound === b.baselineBound && a.hasCliBinding === b.hasCliBinding && a.headBranch === b.headBranch && a.lastEventAt === b.lastEventAt && a.pendingQuestionCount === b.pendingQuestionCount && a.piAgentName === b.piAgentName && a.piAgentLogo === b.piAgentLogo && a.piAgentFrameColor === b.piAgentFrameColor && a.createdAt === b.createdAt && a.isActive === b.isActive;
241
+ }
242
+ if (leftKind === "agent" && rightKind === "agent") {
243
+ const a = left;
244
+ const b = right;
245
+ return a.questId === b.questId && a.agentId === b.agentId && a.name === b.name && a.avatar === b.avatar && a.avatarColor === b.avatarColor && a.statusTone === b.statusTone && a.statusState === b.statusState && a.statusLabel === b.statusLabel;
246
+ }
247
+ if (leftKind === "pi" && rightKind === "pi") {
248
+ const a = left;
249
+ const b = right;
250
+ return a.agentId === b.agentId && a.name === b.name && a.avatar === b.avatar && a.avatarColor === b.avatarColor && a.statusTone === b.statusTone && a.statusState === b.statusState && a.statusLabel === b.statusLabel;
251
+ }
252
+ if (leftKind === "pending" && rightKind === "pending") {
253
+ const a = left;
254
+ const b = right;
255
+ return a.count === b.count;
256
+ }
257
+ return false;
258
+ };
259
+ const STORAGE_VERSION = 2;
260
+ const FLOATING_PANEL_MARGIN = 14;
261
+ const ORB_SIZE = 44;
262
+ const ORB_STEP = ORB_SIZE + 12;
263
+ const OVERVIEW_PANEL_WIDTH = 420;
264
+ const OVERVIEW_PANEL_HEIGHT = 520;
265
+ const ACTION_PANEL_WIDTH = 420;
266
+ const ACTION_PANEL_HEIGHT = 600;
267
+ const readStoredLayout = (projectId) => {
268
+ if (typeof window === "undefined") return null;
269
+ try {
270
+ const raw = window.localStorage.getItem(`ds:lab:overview_layout:v${STORAGE_VERSION}:${projectId}`);
271
+ if (!raw) return null;
272
+ const parsed = JSON.parse(raw);
273
+ if (parsed?.version !== STORAGE_VERSION) return null;
274
+ return parsed;
275
+ } catch {
276
+ return null;
277
+ }
278
+ };
279
+ const writeStoredLayout = (projectId, layout) => {
280
+ if (typeof window === "undefined") return;
281
+ try {
282
+ window.localStorage.setItem(
283
+ `ds:lab:overview_layout:v${STORAGE_VERSION}:${projectId}`,
284
+ JSON.stringify(layout)
285
+ );
286
+ } catch {
287
+ }
288
+ };
289
+ const computeDefaultQuestPosition = (index, origin) => {
290
+ const cols = 3;
291
+ const gapX = 260;
292
+ const gapY = 170;
293
+ const col = index % cols;
294
+ const row = Math.floor(index / cols);
295
+ return { x: origin.x + col * gapX, y: origin.y + row * gapY };
296
+ };
297
+ const GUIDE_COLUMNS = 2;
298
+ const GUIDE_GAP_X = 494;
299
+ const GUIDE_GAP_Y = 221;
300
+ const GUIDE_NODE_WIDTH = 340;
301
+ const GUIDE_NODE_HEIGHT = 150;
302
+ const GUIDE_GROUP_PADDING = 28;
303
+ const GUIDE_GROUP_HEADER_HEIGHT = 28;
304
+ const GUIDE_CENTER_X = 0;
305
+ const GUIDE_START_Y = 40;
306
+ const PI_NODE_WIDTH = 220;
307
+ const computeGuideGroupRect = (stepsCount, cardHeight) => {
308
+ const rows = Math.max(1, Math.ceil(stepsCount / GUIDE_COLUMNS));
309
+ const totalWidth = (GUIDE_COLUMNS - 1) * GUIDE_GAP_X + GUIDE_NODE_WIDTH;
310
+ const totalHeight = (rows - 1) * GUIDE_GAP_Y + cardHeight;
311
+ const width = totalWidth + GUIDE_GROUP_PADDING * 2;
312
+ const height = totalHeight + GUIDE_GROUP_PADDING * 2 + GUIDE_GROUP_HEADER_HEIGHT;
313
+ const x = GUIDE_CENTER_X - totalWidth / 2 - GUIDE_GROUP_PADDING;
314
+ const y = GUIDE_START_Y - GUIDE_GROUP_PADDING - GUIDE_GROUP_HEADER_HEIGHT;
315
+ return { x, y, width, height };
316
+ };
317
+ const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
318
+ const rectsOverlap = (a, b) => {
319
+ return !(a.x + a.width <= b.x || b.x + b.width <= a.x || a.y + a.height <= b.y || b.y + b.height <= a.y);
320
+ };
321
+ const POSITION_EPS = 0.5;
322
+ const sanitizePosition = (pos, fallback) => {
323
+ const x = Number.isFinite(pos.x) ? pos.x : fallback.x;
324
+ const y = Number.isFinite(pos.y) ? pos.y : fallback.y;
325
+ return { x, y };
326
+ };
327
+ const isPositionDifferent = (a, b) => {
328
+ return Math.abs(a.x - b.x) > POSITION_EPS || Math.abs(a.y - b.y) > POSITION_EPS;
329
+ };
330
+ const resolveOrbPosition = (current, bounds, blockers) => {
331
+ const leftX = FLOATING_PANEL_MARGIN;
332
+ const rightX = Math.max(FLOATING_PANEL_MARGIN, bounds.width - ORB_SIZE - FLOATING_PANEL_MARGIN);
333
+ const topY = FLOATING_PANEL_MARGIN;
334
+ const bottomY = Math.max(FLOATING_PANEL_MARGIN, bounds.height - ORB_SIZE - FLOATING_PANEL_MARGIN);
335
+ const clampedX = clamp(current.x, leftX, rightX);
336
+ const clampedY = clamp(current.y, topY, bottomY);
337
+ const distances = [
338
+ { edge: "left", dist: Math.abs(clampedX - leftX) },
339
+ { edge: "right", dist: Math.abs(clampedX - rightX) },
340
+ { edge: "top", dist: Math.abs(clampedY - topY) },
341
+ { edge: "bottom", dist: Math.abs(clampedY - bottomY) }
342
+ ].sort((a, b) => a.dist - b.dist);
343
+ const primary = distances[0]?.edge ?? "left";
344
+ const candidates = [];
345
+ const pushCandidate = (x, y) => {
346
+ candidates.push({
347
+ x: clamp(x, leftX, rightX),
348
+ y: clamp(y, topY, bottomY)
349
+ });
350
+ };
351
+ if (primary === "left" || primary === "right") {
352
+ const edgeX = primary === "left" ? leftX : rightX;
353
+ pushCandidate(edgeX, clampedY);
354
+ pushCandidate(edgeX, clampedY + ORB_STEP);
355
+ pushCandidate(edgeX, clampedY - ORB_STEP);
356
+ pushCandidate(primary === "left" ? rightX : leftX, clampedY);
357
+ } else {
358
+ const edgeY = primary === "top" ? topY : bottomY;
359
+ pushCandidate(clampedX, edgeY);
360
+ pushCandidate(clampedX + ORB_STEP, edgeY);
361
+ pushCandidate(clampedX - ORB_STEP, edgeY);
362
+ pushCandidate(clampedX, primary === "top" ? bottomY : topY);
363
+ }
364
+ pushCandidate(leftX, topY);
365
+ pushCandidate(rightX, topY);
366
+ pushCandidate(leftX, bottomY);
367
+ pushCandidate(rightX, bottomY);
368
+ const seen = /* @__PURE__ */ new Set();
369
+ const uniqueCandidates = candidates.filter((candidate) => {
370
+ const key = `${candidate.x}:${candidate.y}`;
371
+ if (seen.has(key)) return false;
372
+ seen.add(key);
373
+ return true;
374
+ });
375
+ for (const candidate of uniqueCandidates) {
376
+ const orbRect = { x: candidate.x, y: candidate.y, width: ORB_SIZE, height: ORB_SIZE };
377
+ if (!blockers.some((block) => rectsOverlap(orbRect, block))) {
378
+ return candidate;
379
+ }
380
+ }
381
+ return uniqueCandidates[0] ?? { x: leftX, y: topY };
382
+ };
383
+ const resolvePanelSize = (kind, collapsed, bounds) => {
384
+ if (collapsed) {
385
+ return { width: ORB_SIZE, height: ORB_SIZE };
386
+ }
387
+ const baseWidth = kind === "action" ? ACTION_PANEL_WIDTH : OVERVIEW_PANEL_WIDTH;
388
+ const baseHeight = kind === "action" ? ACTION_PANEL_HEIGHT : OVERVIEW_PANEL_HEIGHT;
389
+ return {
390
+ width: Math.min(baseWidth, Math.max(ORB_SIZE, bounds.width - 24)),
391
+ height: Math.min(baseHeight, Math.max(ORB_SIZE, bounds.height - 24))
392
+ };
393
+ };
394
+ const resolvePanelState = (stored, fallback) => {
395
+ return {
396
+ x: Number.isFinite(stored?.x) ? Number(stored?.x) : fallback.x,
397
+ y: Number.isFinite(stored?.y) ? Number(stored?.y) : fallback.y,
398
+ collapsed: typeof stored?.collapsed === "boolean" ? stored.collapsed : fallback.collapsed
399
+ };
400
+ };
401
+ const OverviewPendingActionPanelContext = reactExports.createContext(null);
402
+ const OverviewQuestNode = ({ data }) => {
403
+ const { t } = useI18n("lab");
404
+ const nodeData = data;
405
+ const status = nodeData.status ? String(nodeData.status).toLowerCase() : null;
406
+ const pi = nodeData.piState ? String(nodeData.piState).toLowerCase() : null;
407
+ const baselineBound = Boolean(nodeData.baselineBound);
408
+ const headBranch = nodeData.headBranch?.trim() || "main";
409
+ const hasCliBinding = Boolean(nodeData.hasCliBinding);
410
+ const pendingQuestionCount = Math.max(0, Number(nodeData.pendingQuestionCount ?? 0));
411
+ const hasPendingQuestions = pendingQuestionCount > 0;
412
+ const lastEventLabel = nodeData.lastEventAt ? formatRelativeTime(nodeData.lastEventAt) : t("overview_last_event_none", void 0, "No event yet");
413
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
414
+ "div",
415
+ {
416
+ className: cn(
417
+ "lab-overview-quest-node",
418
+ nodeData.isActive && "is-active",
419
+ status ? `is-${status}` : null
420
+ ),
421
+ children: [
422
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Handle, { type: "target", position: Position.Left, className: "lab-flow-handle" }),
423
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Handle, { type: "source", position: Position.Right, className: "lab-flow-handle" }),
424
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-overview-quest-node__title-row", children: [
425
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-overview-quest-node__title", children: nodeData.title || "Quest" }),
426
+ nodeData.piAgentName ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
427
+ "button",
428
+ {
429
+ type: "button",
430
+ className: "lab-overview-pi-chip nodrag nopan nowheel",
431
+ onClick: (event) => {
432
+ event.stopPropagation();
433
+ nodeData.onPiClick?.();
434
+ },
435
+ "aria-label": t("overview_open_pi_chat", void 0, "Open PI chat"),
436
+ title: t("overview_open_pi_chat", void 0, "Open PI chat"),
437
+ children: [
438
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
439
+ "span",
440
+ {
441
+ className: "lab-overview-pi-avatar",
442
+ style: nodeData.piAgentFrameColor ? { borderColor: nodeData.piAgentFrameColor } : void 0,
443
+ children: nodeData.piAgentLogo ? (
444
+ // eslint-disable-next-line @next/next/no-img-element
445
+ /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: nodeData.piAgentLogo, alt: "" })
446
+ ) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-overview-pi-avatar__fallback", children: t("overview_pi_label") })
447
+ }
448
+ ),
449
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-overview-pi-label", children: nodeData.piAgentName })
450
+ ]
451
+ }
452
+ ) : null
453
+ ] }),
454
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-overview-quest-node__meta", children: [
455
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: status || t("overview_unknown", void 0, "unknown") }),
456
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
457
+ t("overview_pi_label", void 0, "PI"),
458
+ " ",
459
+ pi || t("overview_unknown", void 0, "unknown")
460
+ ] })
461
+ ] }),
462
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-overview-quest-node__chips", children: [
463
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-overview-quest-chip", children: baselineBound ? t("overview_baseline_bound", void 0, "Baseline bound") : t("overview_baseline_pending", void 0, "Baseline pending") }),
464
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-overview-quest-chip", children: t("overview_head_branch", { branch: headBranch }, "Head {branch}") }),
465
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-overview-quest-chip", children: hasCliBinding ? t("overview_cli_bound", void 0, "CLI bound") : t("overview_cli_pending", void 0, "CLI pending") }),
466
+ hasPendingQuestions ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-overview-quest-chip lab-overview-quest-chip--attention", children: t("overview_decision_count", { count: pendingQuestionCount }, "{count} decisions") }) : null,
467
+ typeof nodeData.progressingCount === "number" ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-overview-quest-chip", children: t("overview_progressing_count", { count: nodeData.progressingCount }, "{count} progressing") }) : null,
468
+ typeof nodeData.experimentCount === "number" ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-overview-quest-chip", children: t("overview_experiment_count", { count: nodeData.experimentCount }, "{count} experiments") }) : null,
469
+ typeof nodeData.pushFailedCount === "number" && nodeData.pushFailedCount > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-overview-quest-chip lab-overview-quest-chip--attention", children: t("overview_push_failed_count", { count: nodeData.pushFailedCount }, "{count} push failed") }) : null,
470
+ typeof nodeData.writerConflictCount === "number" && nodeData.writerConflictCount > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-overview-quest-chip lab-overview-quest-chip--attention", children: t(
471
+ "overview_writer_conflict_count",
472
+ { count: nodeData.writerConflictCount },
473
+ "{count} writer conflicts"
474
+ ) }) : null,
475
+ nodeData.runtimeLabel ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-overview-quest-chip", children: nodeData.runtimeLabel }) : null,
476
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-overview-quest-chip", title: nodeData.lastEventAt ?? void 0, children: t("overview_last_event", { value: lastEventLabel }, "Last event {value}") })
477
+ ] })
478
+ ]
479
+ }
480
+ );
481
+ };
482
+ const OverviewAgentNode = ({ data }) => {
483
+ const nodeData = data;
484
+ const statusClass = `lab-status-${nodeData.statusTone}`;
485
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-overview-agent-node", children: [
486
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Handle, { type: "target", position: Position.Left, className: "lab-flow-handle" }),
487
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cn("lab-avatar lab-avatar-sm", statusClass), children: [
488
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-avatar-ring", style: { borderColor: nodeData.avatarColor } }),
489
+ /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: nodeData.avatar, alt: nodeData.name })
490
+ ] }),
491
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-overview-agent-node__meta", children: [
492
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-overview-agent-node__name", children: nodeData.name }),
493
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-overview-agent-node__status", children: [
494
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
495
+ "span",
496
+ {
497
+ className: cn(
498
+ "lab-status-indicator",
499
+ nodeData.statusState === "working" && "lab-status-indicator-working",
500
+ nodeData.statusState === "waiting" && "lab-status-indicator-waiting"
501
+ ),
502
+ "aria-label": nodeData.statusLabel,
503
+ title: nodeData.statusLabel,
504
+ children: nodeData.statusState === "waiting" ? /* @__PURE__ */ jsxRuntimeExports.jsx(CircleHelp, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) : nodeData.statusState === "working" ? /* @__PURE__ */ jsxRuntimeExports.jsx(OrbitLogoStatus, { compact: true, sizePx: 16, className: "lab-status-orbit" }) : nodeData.statusState === "paused" ? /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Moon, { className: "h-3.5 w-3.5", "aria-hidden": "true" })
505
+ }
506
+ ),
507
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-overview-agent-node__status-label", children: nodeData.statusLabel })
508
+ ] })
509
+ ] })
510
+ ] });
511
+ };
512
+ const OverviewPiNode = ({ data }) => {
513
+ const nodeData = data;
514
+ const statusClass = `lab-status-${nodeData.statusTone}`;
515
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-overview-agent-node", children: [
516
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Handle, { type: "source", position: Position.Right, className: "lab-flow-handle" }),
517
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cn("lab-avatar lab-avatar-sm", statusClass), children: [
518
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-avatar-ring", style: { borderColor: nodeData.avatarColor } }),
519
+ /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: nodeData.avatar, alt: nodeData.name })
520
+ ] }),
521
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-overview-agent-node__meta", children: [
522
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-overview-agent-node__name", children: nodeData.name }),
523
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-overview-agent-node__status", children: [
524
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
525
+ "span",
526
+ {
527
+ className: cn(
528
+ "lab-status-indicator",
529
+ nodeData.statusState === "working" && "lab-status-indicator-working",
530
+ nodeData.statusState === "waiting" && "lab-status-indicator-waiting"
531
+ ),
532
+ "aria-label": nodeData.statusLabel,
533
+ title: nodeData.statusLabel,
534
+ children: nodeData.statusState === "waiting" ? /* @__PURE__ */ jsxRuntimeExports.jsx(CircleHelp, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) : nodeData.statusState === "working" ? /* @__PURE__ */ jsxRuntimeExports.jsx(OrbitLogoStatus, { compact: true, sizePx: 16, className: "lab-status-orbit" }) : nodeData.statusState === "paused" ? /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Moon, { className: "h-3.5 w-3.5", "aria-hidden": "true" })
535
+ }
536
+ ),
537
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-overview-agent-node__status-label", children: nodeData.statusLabel })
538
+ ] })
539
+ ] })
540
+ ] });
541
+ };
542
+ const OverviewPendingNode = ({ data }) => {
543
+ const { t } = useI18n("lab");
544
+ const nodeData = data;
545
+ const actionPanel = reactExports.useContext(OverviewPendingActionPanelContext);
546
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-overview-pending-node", "aria-label": t("overview_pending_decisions"), children: [
547
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-overview-pending-node__header", children: [
548
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
549
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-overview-pending-node__title", children: t("overview_pending_decisions") }),
550
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-overview-pending-node__subtitle", children: nodeData.count === 1 ? t("overview_pending_single") : t("overview_pending_plural", { count: nodeData.count }) })
551
+ ] }),
552
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-overview-pending-node__count", "aria-hidden": "true", children: nodeData.count })
553
+ ] }),
554
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-overview-pending-node__content nodrag nowheel", children: actionPanel })
555
+ ] });
556
+ };
557
+ const OVERVIEW_NODE_TYPES = {
558
+ overviewQuest: OverviewQuestNode,
559
+ overviewAgent: OverviewAgentNode,
560
+ overviewPi: OverviewPiNode,
561
+ overviewPending: OverviewPendingNode
562
+ };
563
+ function FloatingPanel({
564
+ boundsRef,
565
+ title,
566
+ icon,
567
+ state,
568
+ zIndex,
569
+ onChange,
570
+ onActivate,
571
+ className,
572
+ children
573
+ }) {
574
+ const dragRef = reactExports.useRef(null);
575
+ const rootRef = reactExports.useRef(null);
576
+ const cleanupRef = reactExports.useRef(null);
577
+ const lastDragMovedRef = reactExports.useRef(false);
578
+ reactExports.useEffect(() => {
579
+ return () => {
580
+ cleanupRef.current?.();
581
+ cleanupRef.current = null;
582
+ };
583
+ }, []);
584
+ const startDrag = reactExports.useCallback(
585
+ (event) => {
586
+ event.stopPropagation();
587
+ if (typeof event.button === "number" && event.button !== 0) return;
588
+ onActivate?.();
589
+ if (!rootRef.current) return;
590
+ const bounds = boundsRef.current;
591
+ if (!bounds) return;
592
+ cleanupRef.current?.();
593
+ lastDragMovedRef.current = false;
594
+ const boundsRect = bounds.getBoundingClientRect();
595
+ const rootRect = rootRef.current.getBoundingClientRect();
596
+ const originX = rootRect.left - boundsRect.left;
597
+ const originY = rootRect.top - boundsRect.top;
598
+ dragRef.current = {
599
+ originX,
600
+ originY,
601
+ pointerId: event.pointerId,
602
+ startX: event.clientX,
603
+ startY: event.clientY,
604
+ moved: false
605
+ };
606
+ rootRef.current.setPointerCapture(event.pointerId);
607
+ const handleMove = (nextEvent) => {
608
+ if (!dragRef.current) return;
609
+ if (nextEvent.pointerId !== dragRef.current.pointerId) return;
610
+ const dx = nextEvent.clientX - dragRef.current.startX;
611
+ const dy = nextEvent.clientY - dragRef.current.startY;
612
+ if (Math.abs(dx) + Math.abs(dy) > 2) {
613
+ dragRef.current.moved = true;
614
+ }
615
+ const maxX = Math.max(
616
+ FLOATING_PANEL_MARGIN,
617
+ boundsRect.width - rootRect.width - FLOATING_PANEL_MARGIN
618
+ );
619
+ const maxY = Math.max(
620
+ FLOATING_PANEL_MARGIN,
621
+ boundsRect.height - rootRect.height - FLOATING_PANEL_MARGIN
622
+ );
623
+ const nextX = clamp(
624
+ dragRef.current.originX + dx,
625
+ FLOATING_PANEL_MARGIN,
626
+ maxX
627
+ );
628
+ const nextY = clamp(
629
+ dragRef.current.originY + dy,
630
+ FLOATING_PANEL_MARGIN,
631
+ maxY
632
+ );
633
+ onChange({ ...state, x: nextX, y: nextY });
634
+ };
635
+ const handleUp = (nextEvent) => {
636
+ if (!dragRef.current) return;
637
+ if (nextEvent.pointerId !== dragRef.current.pointerId) return;
638
+ lastDragMovedRef.current = dragRef.current.moved;
639
+ dragRef.current = null;
640
+ cleanupRef.current?.();
641
+ cleanupRef.current = null;
642
+ };
643
+ window.addEventListener("pointermove", handleMove);
644
+ window.addEventListener("pointerup", handleUp);
645
+ window.addEventListener("pointercancel", handleUp);
646
+ cleanupRef.current = () => {
647
+ window.removeEventListener("pointermove", handleMove);
648
+ window.removeEventListener("pointerup", handleUp);
649
+ window.removeEventListener("pointercancel", handleUp);
650
+ };
651
+ },
652
+ [boundsRef, onActivate, onChange, state]
653
+ );
654
+ const collapse = reactExports.useCallback(
655
+ (event) => {
656
+ event.stopPropagation();
657
+ onChange({ ...state, collapsed: true });
658
+ },
659
+ [onChange, state]
660
+ );
661
+ const expand = reactExports.useCallback(() => {
662
+ if (lastDragMovedRef.current) {
663
+ lastDragMovedRef.current = false;
664
+ return;
665
+ }
666
+ onActivate?.();
667
+ onChange({ ...state, collapsed: false });
668
+ }, [onActivate, onChange, state]);
669
+ if (state.collapsed) {
670
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
671
+ "button",
672
+ {
673
+ ref: rootRef,
674
+ type: "button",
675
+ className: cn("lab-floating-orb nowheel nopan", className),
676
+ style: { transform: `translate3d(${state.x}px, ${state.y}px, 0)`, zIndex },
677
+ onPointerDown: startDrag,
678
+ onClick: (event) => {
679
+ event.stopPropagation();
680
+ expand();
681
+ },
682
+ "aria-label": `Show ${title}`,
683
+ children: icon
684
+ }
685
+ );
686
+ }
687
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
688
+ "div",
689
+ {
690
+ ref: rootRef,
691
+ className: cn("lab-floating-panel nowheel nopan", className),
692
+ style: { transform: `translate3d(${state.x}px, ${state.y}px, 0)`, zIndex },
693
+ onPointerDown: (event) => {
694
+ event.stopPropagation();
695
+ onActivate?.();
696
+ },
697
+ children: [
698
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-floating-panel__header", onPointerDown: startDrag, children: [
699
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "lab-floating-panel__title", children: [
700
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "lab-floating-panel__icon", children: icon }),
701
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: title })
702
+ ] }),
703
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
704
+ "button",
705
+ {
706
+ type: "button",
707
+ className: "lab-floating-panel__collapse",
708
+ onPointerDown: (event) => event.stopPropagation(),
709
+ onClick: collapse,
710
+ "aria-label": `Collapse ${title}`,
711
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 16 })
712
+ }
713
+ )
714
+ ] }),
715
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-floating-panel__content", children })
716
+ ]
717
+ }
718
+ );
719
+ }
720
+ function LabOverviewCanvasInner({
721
+ projectId,
722
+ quests,
723
+ agents = [],
724
+ templates = [],
725
+ graphVm,
726
+ waitingAnswerByAgentId = /* @__PURE__ */ new Map(),
727
+ pendingDecisionCount,
728
+ activeQuestId,
729
+ hasPiAgent,
730
+ piAgent,
731
+ readOnly,
732
+ canvasLevel = "map",
733
+ actionPanel,
734
+ overviewPanel,
735
+ showFloatingPanels = true,
736
+ onOpenCanvas,
737
+ onOpenQuestPiChat,
738
+ onOpenPiChat,
739
+ onSelectAgent
740
+ }) {
741
+ const { t } = useI18n("lab");
742
+ const boundsRef = reactExports.useRef(null);
743
+ const [canvasBoundsReady, setCanvasBoundsReady] = reactExports.useState(false);
744
+ const storedLayout = reactExports.useMemo(() => readStoredLayout(projectId), [projectId]);
745
+ const storedViewport = storedLayout?.viewport ?? null;
746
+ const initialViewport = storedViewport && Number.isFinite(storedViewport.x) && Number.isFinite(storedViewport.y) && Number.isFinite(storedViewport.zoom) && storedViewport.zoom >= 0.15 && storedViewport.zoom <= 3 ? storedViewport : void 0;
747
+ const fittedViewportRef = reactExports.useRef(false);
748
+ const viewportCheckedRef = reactExports.useRef(false);
749
+ const nodesRef = reactExports.useRef([]);
750
+ const viewportRef = reactExports.useRef(storedLayout?.viewport ?? null);
751
+ const saveTimeoutRef = reactExports.useRef(null);
752
+ const zCounterRef = reactExports.useRef(2);
753
+ const [panelZ, setPanelZ] = reactExports.useState({
754
+ overview: 1,
755
+ action: 2
756
+ });
757
+ const [overviewPanelState, setOverviewPanelState] = reactExports.useState(
758
+ () => resolvePanelState(storedLayout?.panels?.overview, { x: 16, y: 16, collapsed: false })
759
+ );
760
+ const [actionPanelState, setActionPanelState] = reactExports.useState(
761
+ () => resolvePanelState(storedLayout?.panels?.action, { x: 16, y: 88, collapsed: false })
762
+ );
763
+ const governanceByQuestId = reactExports.useMemo(() => {
764
+ const map = /* @__PURE__ */ new Map();
765
+ graphVm?.quests?.forEach((questVm) => {
766
+ map.set(questVm.questId, questVm);
767
+ });
768
+ return map;
769
+ }, [graphVm]);
770
+ const bringToFront = reactExports.useCallback((key) => {
771
+ zCounterRef.current += 1;
772
+ const nextZ = zCounterRef.current;
773
+ setPanelZ((prev) => ({ ...prev, [key]: nextZ }));
774
+ }, []);
775
+ const scheduleSave = reactExports.useCallback(() => {
776
+ if (typeof window === "undefined") return;
777
+ if (saveTimeoutRef.current) {
778
+ window.clearTimeout(saveTimeoutRef.current);
779
+ }
780
+ saveTimeoutRef.current = window.setTimeout(() => {
781
+ const nodePositions = {};
782
+ nodesRef.current.forEach((node) => {
783
+ nodePositions[node.id] = { x: node.position.x, y: node.position.y };
784
+ });
785
+ writeStoredLayout(projectId, {
786
+ version: STORAGE_VERSION,
787
+ viewport: viewportRef.current,
788
+ nodes: nodePositions,
789
+ panels: {
790
+ overview: {
791
+ x: overviewPanelState.x,
792
+ y: overviewPanelState.y,
793
+ collapsed: overviewPanelState.collapsed
794
+ },
795
+ action: {
796
+ x: actionPanelState.x,
797
+ y: actionPanelState.y,
798
+ collapsed: actionPanelState.collapsed
799
+ }
800
+ }
801
+ });
802
+ }, 450);
803
+ }, [actionPanelState, overviewPanelState, projectId]);
804
+ const guideGroupRect = reactExports.useMemo(() => {
805
+ return computeGuideGroupRect(4, GUIDE_NODE_HEIGHT);
806
+ }, []);
807
+ const questOrigin = reactExports.useMemo(() => {
808
+ const x = Math.round(guideGroupRect.x + guideGroupRect.width + 140);
809
+ const y = Math.round(guideGroupRect.y);
810
+ return { x, y };
811
+ }, [guideGroupRect]);
812
+ const resolvedPendingCount = reactExports.useMemo(() => {
813
+ if (typeof pendingDecisionCount === "number") {
814
+ return Math.max(0, pendingDecisionCount);
815
+ }
816
+ let count = 0;
817
+ for (const value of waitingAnswerByAgentId.values()) {
818
+ if (value) count += 1;
819
+ }
820
+ return count;
821
+ }, [pendingDecisionCount, waitingAnswerByAgentId]);
822
+ const isOpsMode = canvasLevel === "ops";
823
+ const hasPendingDecisions = resolvedPendingCount > 0;
824
+ const hasPendingNode = hasPendingDecisions && !isOpsMode;
825
+ const showActionPanelFloating = isOpsMode || !hasPendingNode;
826
+ reactExports.useLayoutEffect(() => {
827
+ const bounds = boundsRef.current;
828
+ if (!bounds) return;
829
+ const rect = bounds.getBoundingClientRect();
830
+ setActionPanelState((prev) => {
831
+ const hasStoredX = Number.isFinite(storedLayout?.panels?.action?.x);
832
+ const hasStoredY = Number.isFinite(storedLayout?.panels?.action?.y);
833
+ if (hasStoredX || hasStoredY) return prev;
834
+ if (prev.x !== 16 || prev.y !== 88) return prev;
835
+ const nextX = Math.max(FLOATING_PANEL_MARGIN, rect.width - ACTION_PANEL_WIDTH - FLOATING_PANEL_MARGIN);
836
+ return { ...prev, x: nextX };
837
+ });
838
+ }, [storedLayout?.panels?.action?.x, storedLayout?.panels?.action?.y]);
839
+ reactExports.useLayoutEffect(() => {
840
+ const bounds = boundsRef.current;
841
+ if (!bounds) return;
842
+ const rect = bounds.getBoundingClientRect();
843
+ const clampPanel = (panel, size) => {
844
+ const maxX = Math.max(FLOATING_PANEL_MARGIN, rect.width - size.width - FLOATING_PANEL_MARGIN);
845
+ const maxY = Math.max(FLOATING_PANEL_MARGIN, rect.height - size.height - FLOATING_PANEL_MARGIN);
846
+ return {
847
+ x: clamp(panel.x, FLOATING_PANEL_MARGIN, maxX),
848
+ y: clamp(panel.y, FLOATING_PANEL_MARGIN, maxY)
849
+ };
850
+ };
851
+ const overviewSize = resolvePanelSize("overview", overviewPanelState.collapsed, rect);
852
+ const actionSize = resolvePanelSize("action", actionPanelState.collapsed, rect);
853
+ let nextOverview = overviewPanelState;
854
+ if (!overviewPanelState.collapsed) {
855
+ const clamped = clampPanel(overviewPanelState, overviewSize);
856
+ if (clamped.x !== overviewPanelState.x || clamped.y !== overviewPanelState.y) {
857
+ nextOverview = { ...overviewPanelState, ...clamped };
858
+ }
859
+ }
860
+ let nextAction = actionPanelState;
861
+ if (!actionPanelState.collapsed) {
862
+ const clamped = clampPanel(actionPanelState, actionSize);
863
+ if (clamped.x !== actionPanelState.x || clamped.y !== actionPanelState.y) {
864
+ nextAction = { ...actionPanelState, ...clamped };
865
+ }
866
+ }
867
+ const blockers = [];
868
+ if (!nextOverview.collapsed) {
869
+ blockers.push({ x: nextOverview.x, y: nextOverview.y, ...overviewSize });
870
+ }
871
+ if (!nextAction.collapsed) {
872
+ blockers.push({ x: nextAction.x, y: nextAction.y, ...actionSize });
873
+ }
874
+ if (nextOverview.collapsed) {
875
+ const pos = sanitizePosition(resolveOrbPosition(nextOverview, rect, blockers), nextOverview);
876
+ if (isPositionDifferent(pos, nextOverview)) {
877
+ nextOverview = { ...nextOverview, ...pos };
878
+ }
879
+ blockers.push({ x: nextOverview.x, y: nextOverview.y, width: ORB_SIZE, height: ORB_SIZE });
880
+ }
881
+ if (nextAction.collapsed) {
882
+ const pos = sanitizePosition(resolveOrbPosition(nextAction, rect, blockers), nextAction);
883
+ if (isPositionDifferent(pos, nextAction)) {
884
+ nextAction = { ...nextAction, ...pos };
885
+ }
886
+ }
887
+ if (isPositionDifferent(nextOverview, overviewPanelState) || nextOverview.collapsed !== overviewPanelState.collapsed) {
888
+ setOverviewPanelState(nextOverview);
889
+ }
890
+ if (isPositionDifferent(nextAction, actionPanelState) || nextAction.collapsed !== actionPanelState.collapsed) {
891
+ setActionPanelState(nextAction);
892
+ }
893
+ }, [actionPanelState, overviewPanelState, storedLayout?.panels?.action?.x, storedLayout?.panels?.action?.y]);
894
+ reactExports.useEffect(() => {
895
+ if (!isOpsMode) return;
896
+ bringToFront("action");
897
+ setActionPanelState((prev) => prev.collapsed ? { ...prev, collapsed: false } : prev);
898
+ }, [bringToFront, isOpsMode]);
899
+ const pendingNode = reactExports.useMemo(() => {
900
+ if (!hasPendingNode) return null;
901
+ const width = ACTION_PANEL_WIDTH;
902
+ const height = ACTION_PANEL_HEIGHT;
903
+ const stored = storedLayout?.nodes?.["overview:pending"] ?? null;
904
+ const fallbackX = Math.round(guideGroupRect.x + guideGroupRect.width / 2 - width / 2);
905
+ const fallbackY = Math.round(guideGroupRect.y + guideGroupRect.height + 96);
906
+ const position = stored ? stored : { x: fallbackX, y: fallbackY };
907
+ return {
908
+ id: "overview:pending",
909
+ type: "overviewPending",
910
+ position,
911
+ draggable: !readOnly,
912
+ selectable: !readOnly,
913
+ data: {
914
+ kind: "pending",
915
+ count: resolvedPendingCount
916
+ },
917
+ style: {
918
+ width,
919
+ height,
920
+ zIndex: 2
921
+ }
922
+ };
923
+ }, [guideGroupRect, hasPendingNode, readOnly, resolvedPendingCount, storedLayout?.nodes]);
924
+ const questNodes = reactExports.useMemo(() => {
925
+ const storedNodes = storedLayout?.nodes ?? {};
926
+ return quests.map((quest, index) => {
927
+ const governanceQuest = governanceByQuestId.get(quest.quest_id);
928
+ const id = `quest:${quest.quest_id}`;
929
+ const stored = storedNodes?.[id] ?? null;
930
+ const position = stored ? stored : computeDefaultQuestPosition(index, questOrigin);
931
+ const summary = governanceQuest?.summary;
932
+ const runtime = governanceQuest?.runtime;
933
+ const runtimeLabel = runtime && runtime.runningAgents > 0 ? t("overview_runtime_running", { count: runtime.runningAgents }, "{count} running") : null;
934
+ return {
935
+ id,
936
+ type: "overviewQuest",
937
+ position,
938
+ draggable: !readOnly,
939
+ data: {
940
+ questId: quest.quest_id,
941
+ title: quest.title || "Quest",
942
+ status: quest.status ?? null,
943
+ piState: quest.pi_state ?? runtime?.piState ?? null,
944
+ baselineBound: governanceQuest?.governance.formalBaselineState === "confirmed" || quest.governance?.formalBaselineState === "confirmed",
945
+ hasCliBinding: Boolean(quest.cli_server_id),
946
+ headBranch: governanceQuest?.topology.headBranch ?? quest.git_head_branch ?? null,
947
+ lastEventAt: quest.last_event_at ?? null,
948
+ pendingQuestionCount: Math.max(0, Number(quest.pending_question_count ?? 0)),
949
+ piAgentName: piAgent?.name ?? null,
950
+ piAgentLogo: piAgent?.logo ?? null,
951
+ piAgentFrameColor: piAgent?.frameColor ?? null,
952
+ onPiClick: piAgent?.name ? () => {
953
+ if (onOpenQuestPiChat) {
954
+ onOpenQuestPiChat(quest.quest_id);
955
+ return;
956
+ }
957
+ onOpenPiChat?.();
958
+ } : null,
959
+ createdAt: quest.created_at ?? null,
960
+ isActive: quest.quest_id === activeQuestId,
961
+ progressingCount: summary?.progressingCount ?? void 0,
962
+ experimentCount: summary?.experimentCount ?? void 0,
963
+ runtimeLabel,
964
+ pushFailedCount: summary?.pushFailedCount ?? void 0,
965
+ writerConflictCount: summary?.writerConflictCount ?? void 0
966
+ }
967
+ };
968
+ });
969
+ }, [
970
+ activeQuestId,
971
+ governanceByQuestId,
972
+ onOpenPiChat,
973
+ onOpenQuestPiChat,
974
+ piAgent,
975
+ t,
976
+ quests,
977
+ readOnly,
978
+ storedLayout?.nodes,
979
+ questOrigin
980
+ ]);
981
+ const templatesById = reactExports.useMemo(() => {
982
+ return new Map(templates.map((template) => [template.template_id, template]));
983
+ }, [templates]);
984
+ const piNode = reactExports.useMemo(() => {
985
+ if (!hasPiAgent) return null;
986
+ if (!agents.length) return null;
987
+ const piTemplate = templates.find((template2) => template2.template_key === "pi") ?? null;
988
+ const piAgentInstance = (piTemplate ? agents.find((agent) => agent.template_id === piTemplate.template_id) : agents.find((agent) => agent.agent_id === "pi")) ?? null;
989
+ if (!piAgentInstance) return null;
990
+ const storedNodes = storedLayout?.nodes ?? {};
991
+ const defaultPosition = {
992
+ x: GUIDE_CENTER_X - PI_NODE_WIDTH / 2,
993
+ y: guideGroupRect.y + guideGroupRect.height + 48
994
+ };
995
+ const nodeId = `pi:${piAgentInstance.instance_id}`;
996
+ const position = storedNodes?.[nodeId] ?? defaultPosition;
997
+ const template = piAgentInstance.template_id ? templatesById.get(piAgentInstance.template_id) ?? null : null;
998
+ const displayName = resolveAgentDisplayName(piAgentInstance) || "PI";
999
+ const avatar = resolveAgentLogo(piAgentInstance, template);
1000
+ const avatarColor = piAgentInstance.avatar_frame_color?.trim() || pickAvatarFrameColor(piAgentInstance.instance_id, 0);
1001
+ const statusLabelRaw = typeof piAgentInstance.status === "string" ? piAgentInstance.status.toLowerCase() : "idle";
1002
+ const waitingForAnswer = waitingAnswerByAgentId.get(piAgentInstance.instance_id) ?? false;
1003
+ const isPaused = statusLabelRaw === "waiting" && !waitingForAnswer;
1004
+ const effectiveStatus = statusLabelRaw === "waiting" && !waitingForAnswer ? "idle" : statusLabelRaw;
1005
+ const isWaiting = waitingForAnswer;
1006
+ const isWorking = !isWaiting && isLabWorkingStatus(effectiveStatus);
1007
+ const statusTone = isWaiting ? "waiting" : isWorking ? "working" : normalizeLabStatus(effectiveStatus);
1008
+ const statusState = isWaiting ? "waiting" : isWorking ? "working" : isPaused ? "paused" : "resting";
1009
+ const statusLabel = isWaiting ? "Waiting for answer" : isWorking ? "Working" : isPaused ? "Paused" : "Resting";
1010
+ return {
1011
+ id: nodeId,
1012
+ type: "overviewPi",
1013
+ position,
1014
+ draggable: !readOnly,
1015
+ data: {
1016
+ kind: "pi",
1017
+ agentId: piAgentInstance.instance_id,
1018
+ name: displayName,
1019
+ avatar,
1020
+ avatarColor,
1021
+ statusTone,
1022
+ statusState,
1023
+ statusLabel
1024
+ }
1025
+ };
1026
+ }, [
1027
+ agents,
1028
+ guideGroupRect,
1029
+ hasPiAgent,
1030
+ readOnly,
1031
+ storedLayout?.nodes,
1032
+ templates,
1033
+ templatesById,
1034
+ waitingAnswerByAgentId
1035
+ ]);
1036
+ const agentNodes = reactExports.useMemo(() => {
1037
+ if (!agents.length || !questNodes.length) return [];
1038
+ const storedNodes = storedLayout?.nodes ?? {};
1039
+ const questNodeById = new Map(questNodes.map((node) => [node.id, node]));
1040
+ const questIds = new Set(quests.map((quest) => quest.quest_id));
1041
+ const agentsByQuest = /* @__PURE__ */ new Map();
1042
+ agents.forEach((agent) => {
1043
+ const questId = agent.active_quest_id;
1044
+ if (!questId || !questIds.has(questId)) return;
1045
+ const list = agentsByQuest.get(questId) ?? [];
1046
+ list.push(agent);
1047
+ agentsByQuest.set(questId, list);
1048
+ });
1049
+ const nodes2 = [];
1050
+ agentsByQuest.forEach((questAgents, questId) => {
1051
+ const questNode = questNodeById.get(`quest:${questId}`);
1052
+ if (!questNode) return;
1053
+ const columns = questAgents.length > 3 ? 2 : 1;
1054
+ const rows = Math.ceil(questAgents.length / columns);
1055
+ questAgents.forEach((agent, index) => {
1056
+ const col = index % columns;
1057
+ const row = Math.floor(index / columns);
1058
+ const offsetX = 240 + col * 170;
1059
+ const offsetY = (row - (rows - 1) / 2) * 90;
1060
+ const nodeId = `agent:${questId}:${agent.instance_id}`;
1061
+ const stored = storedNodes?.[nodeId] ?? null;
1062
+ const position = stored ?? { x: questNode.position.x + offsetX, y: questNode.position.y + offsetY };
1063
+ const template = agent.template_id ? templatesById.get(agent.template_id) ?? null : null;
1064
+ const displayName = resolveAgentDisplayName(agent);
1065
+ const avatar = resolveAgentLogo(agent, template);
1066
+ const avatarColor = agent.avatar_frame_color?.trim() || pickAvatarFrameColor(agent.instance_id, index);
1067
+ const statusLabelRaw = typeof agent.status === "string" ? agent.status.toLowerCase() : "idle";
1068
+ const waitingForAnswer = waitingAnswerByAgentId.get(agent.instance_id) ?? false;
1069
+ const isPaused = statusLabelRaw === "waiting" && !waitingForAnswer;
1070
+ const effectiveStatus = statusLabelRaw === "waiting" && !waitingForAnswer ? "idle" : statusLabelRaw;
1071
+ const isWaiting = waitingForAnswer;
1072
+ const isWorking = !isWaiting && isLabWorkingStatus(effectiveStatus);
1073
+ const statusTone = isWaiting ? "waiting" : isWorking ? "working" : normalizeLabStatus(effectiveStatus);
1074
+ const statusState = isWaiting ? "waiting" : isWorking ? "working" : isPaused ? "paused" : "resting";
1075
+ const statusLabel = isWaiting ? "Waiting for answer" : isWorking ? "Working" : isPaused ? "Paused" : "Resting";
1076
+ nodes2.push({
1077
+ id: nodeId,
1078
+ type: "overviewAgent",
1079
+ position,
1080
+ draggable: !readOnly,
1081
+ data: {
1082
+ kind: "agent",
1083
+ questId,
1084
+ agentId: agent.instance_id,
1085
+ name: displayName,
1086
+ avatar,
1087
+ avatarColor,
1088
+ statusTone,
1089
+ statusState,
1090
+ statusLabel
1091
+ }
1092
+ });
1093
+ });
1094
+ });
1095
+ return nodes2;
1096
+ }, [
1097
+ agents,
1098
+ questNodes,
1099
+ quests,
1100
+ readOnly,
1101
+ storedLayout?.nodes,
1102
+ templatesById,
1103
+ waitingAnswerByAgentId
1104
+ ]);
1105
+ const initialNodes = reactExports.useMemo(() => {
1106
+ const piNodes = piNode ? [piNode] : [];
1107
+ const pendingNodes = pendingNode ? [pendingNode] : [];
1108
+ return [...pendingNodes, ...piNodes, ...questNodes, ...agentNodes];
1109
+ }, [agentNodes, pendingNode, piNode, questNodes]);
1110
+ const [nodes, setNodes, onNodesChange] = useNodesState(
1111
+ initialNodes
1112
+ );
1113
+ const agentEdges = reactExports.useMemo(() => {
1114
+ const baseStyle = { stroke: "var(--lab-border-strong)", strokeWidth: 1.2, strokeDasharray: "4 6" };
1115
+ return agentNodes.map((node) => ({
1116
+ id: `agent-edge:${node.data.questId}:${node.data.agentId}`,
1117
+ source: `quest:${node.data.questId}`,
1118
+ target: node.id,
1119
+ type: "smoothstep",
1120
+ style: baseStyle,
1121
+ animated: false
1122
+ }));
1123
+ }, [agentNodes]);
1124
+ const piQuestEdges = reactExports.useMemo(() => {
1125
+ if (!piNode) return [];
1126
+ return questNodes.map((questNode) => ({
1127
+ id: `pi-edge:${piNode.id}:${questNode.id}`,
1128
+ source: piNode.id,
1129
+ target: questNode.id,
1130
+ type: "smoothstep",
1131
+ style: { stroke: "var(--lab-accent-strong)", strokeWidth: 1.4, strokeDasharray: "2 6" },
1132
+ animated: false
1133
+ }));
1134
+ }, [piNode, questNodes]);
1135
+ const edges = reactExports.useMemo(() => {
1136
+ return [...piQuestEdges, ...agentEdges];
1137
+ }, [agentEdges, piQuestEdges]);
1138
+ const flow = useReactFlow();
1139
+ reactExports.useEffect(() => {
1140
+ nodesRef.current = nodes;
1141
+ }, [nodes]);
1142
+ reactExports.useEffect(() => {
1143
+ setNodes((prev) => {
1144
+ const prevById = new Map(prev.map((node) => [node.id, node]));
1145
+ const piNodes = piNode ? [piNode] : [];
1146
+ const pendingNodes = pendingNode ? [pendingNode] : [];
1147
+ const next = [...pendingNodes, ...piNodes, ...questNodes, ...agentNodes].map((node) => {
1148
+ const existing = prevById.get(node.id);
1149
+ if (!existing) return node;
1150
+ const preservePosition = node.type === "overviewQuest" || node.type === "overviewAgent" || node.type === "overviewPi" || node.type === "overviewPending";
1151
+ return preservePosition ? { ...node, position: existing.position } : node;
1152
+ });
1153
+ if (prev.length === next.length) {
1154
+ let unchanged = true;
1155
+ for (let i = 0; i < next.length; i += 1) {
1156
+ const a = prev[i];
1157
+ const b = next[i];
1158
+ if (!a || !b || a.id !== b.id) {
1159
+ unchanged = false;
1160
+ break;
1161
+ }
1162
+ if (a.position.x !== b.position.x || a.position.y !== b.position.y) {
1163
+ unchanged = false;
1164
+ break;
1165
+ }
1166
+ if (!isOverviewNodeDataEqual(a.data, b.data)) {
1167
+ unchanged = false;
1168
+ break;
1169
+ }
1170
+ }
1171
+ if (unchanged) return prev;
1172
+ }
1173
+ return next;
1174
+ });
1175
+ }, [activeQuestId, agentNodes, pendingNode, piNode, questNodes, setNodes]);
1176
+ reactExports.useEffect(() => {
1177
+ scheduleSave();
1178
+ }, [actionPanelState, overviewPanelState, scheduleSave]);
1179
+ reactExports.useEffect(() => {
1180
+ if (!nodes.length) return;
1181
+ if (initialViewport) return;
1182
+ if (fittedViewportRef.current) return;
1183
+ let raf = 0;
1184
+ let attempts = 0;
1185
+ const tryFit = () => {
1186
+ attempts += 1;
1187
+ try {
1188
+ flow.fitView({ padding: 0.2, duration: 450 });
1189
+ fittedViewportRef.current = true;
1190
+ } catch {
1191
+ if (attempts < 8 && typeof window !== "undefined") {
1192
+ raf = window.requestAnimationFrame(tryFit);
1193
+ }
1194
+ }
1195
+ };
1196
+ tryFit();
1197
+ return () => {
1198
+ if (raf && typeof window !== "undefined") {
1199
+ window.cancelAnimationFrame(raf);
1200
+ }
1201
+ };
1202
+ }, [flow, initialViewport, nodes.length]);
1203
+ reactExports.useEffect(() => {
1204
+ if (!nodes.length) return;
1205
+ if (!initialViewport) return;
1206
+ if (fittedViewportRef.current) return;
1207
+ if (viewportCheckedRef.current) return;
1208
+ const bounds = boundsRef.current;
1209
+ if (!bounds) return;
1210
+ const rect = bounds.getBoundingClientRect();
1211
+ if (!rect.width || !rect.height) return;
1212
+ const tx = initialViewport.x;
1213
+ const ty = initialViewport.y;
1214
+ const zoom = initialViewport.zoom;
1215
+ const padding = 160;
1216
+ const anyVisible = nodes.some((node) => {
1217
+ const sx = node.position.x * zoom + tx;
1218
+ const sy = node.position.y * zoom + ty;
1219
+ return sx > -padding && sx < rect.width + padding && sy > -padding && sy < rect.height + padding;
1220
+ });
1221
+ if (!anyVisible) {
1222
+ let raf = 0;
1223
+ let attempts = 0;
1224
+ const tryFit = () => {
1225
+ attempts += 1;
1226
+ try {
1227
+ flow.fitView({ padding: 0.2, duration: 450 });
1228
+ fittedViewportRef.current = true;
1229
+ } catch {
1230
+ if (attempts < 8 && typeof window !== "undefined") {
1231
+ raf = window.requestAnimationFrame(tryFit);
1232
+ }
1233
+ }
1234
+ };
1235
+ tryFit();
1236
+ return () => {
1237
+ if (raf && typeof window !== "undefined") {
1238
+ window.cancelAnimationFrame(raf);
1239
+ }
1240
+ };
1241
+ }
1242
+ viewportCheckedRef.current = true;
1243
+ }, [flow, initialViewport, nodes]);
1244
+ reactExports.useEffect(() => {
1245
+ return () => {
1246
+ if (saveTimeoutRef.current) {
1247
+ window.clearTimeout(saveTimeoutRef.current);
1248
+ }
1249
+ };
1250
+ }, []);
1251
+ reactExports.useLayoutEffect(() => {
1252
+ setCanvasBoundsReady(false);
1253
+ const bounds = boundsRef.current;
1254
+ if (!bounds || typeof window === "undefined") return;
1255
+ let raf = 0;
1256
+ let attempts = 0;
1257
+ const check = () => {
1258
+ attempts += 1;
1259
+ const rect = bounds.getBoundingClientRect();
1260
+ if (rect.width > 0 && rect.height > 0) {
1261
+ setCanvasBoundsReady(true);
1262
+ return;
1263
+ }
1264
+ if (attempts < 6) {
1265
+ raf = window.requestAnimationFrame(check);
1266
+ }
1267
+ };
1268
+ check();
1269
+ return () => {
1270
+ if (raf) {
1271
+ window.cancelAnimationFrame(raf);
1272
+ }
1273
+ };
1274
+ }, [projectId]);
1275
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(OverviewPendingActionPanelContext.Provider, { value: hasPendingNode ? actionPanel : null, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref: boundsRef, className: cn("lab-overview-canvas", isOpsMode && "lab-overview-canvas--ops"), children: [
1276
+ !canvasBoundsReady ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-3 p-4", children: showFloatingPanels ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1277
+ !isOpsMode ? overviewPanel : null,
1278
+ actionPanel
1279
+ ] }) : null }) : null,
1280
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "lab-quest-graph-shell lab-quest-graph-shell--full", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1281
+ index,
1282
+ {
1283
+ nodes,
1284
+ edges,
1285
+ nodeTypes: OVERVIEW_NODE_TYPES,
1286
+ onNodesChange,
1287
+ nodesDraggable: !readOnly,
1288
+ panOnDrag: true,
1289
+ zoomOnScroll: true,
1290
+ zoomOnPinch: true,
1291
+ minZoom: 0.15,
1292
+ maxZoom: 3,
1293
+ defaultViewport: initialViewport,
1294
+ onNodeDragStop: () => scheduleSave(),
1295
+ onMoveEnd: (_, viewport) => {
1296
+ viewportRef.current = viewport;
1297
+ scheduleSave();
1298
+ },
1299
+ onNodeClick: (_, node) => {
1300
+ const data = node.data;
1301
+ if (data && typeof data === "object" && data.kind === "pending") {
1302
+ dispatchLabFocus({
1303
+ projectId,
1304
+ focusType: "overview",
1305
+ focusId: projectId
1306
+ });
1307
+ if (showFloatingPanels && showActionPanelFloating) {
1308
+ bringToFront("action");
1309
+ setActionPanelState((prev) => ({ ...prev, collapsed: false }));
1310
+ }
1311
+ return;
1312
+ }
1313
+ if (data && typeof data === "object" && data.kind === "agent") {
1314
+ const payload = data;
1315
+ onSelectAgent?.(payload.agentId, payload.questId);
1316
+ return;
1317
+ }
1318
+ if (data && typeof data === "object" && data.kind === "pi") {
1319
+ const payload = data;
1320
+ onSelectAgent?.(payload.agentId, null);
1321
+ onOpenPiChat?.();
1322
+ return;
1323
+ }
1324
+ const questId = node.data?.questId;
1325
+ if (!questId) return;
1326
+ dispatchLabFocus({
1327
+ projectId,
1328
+ focusType: "quest",
1329
+ focusId: questId
1330
+ });
1331
+ onOpenCanvas?.(questId);
1332
+ },
1333
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(Background, { color: "var(--lab-border)", gap: 28, size: 1, variant: "dots" })
1334
+ }
1335
+ ) }),
1336
+ canvasBoundsReady ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1337
+ showFloatingPanels && !isOpsMode ? /* @__PURE__ */ jsxRuntimeExports.jsx(
1338
+ FloatingPanel,
1339
+ {
1340
+ boundsRef,
1341
+ title: t("overview_overall"),
1342
+ icon: /* @__PURE__ */ jsxRuntimeExports.jsx(LayoutDashboard, { size: 16 }),
1343
+ state: overviewPanelState,
1344
+ zIndex: panelZ.overview,
1345
+ onChange: setOverviewPanelState,
1346
+ onActivate: () => bringToFront("overview"),
1347
+ className: "lab-overview-panel--overview",
1348
+ children: overviewPanel
1349
+ }
1350
+ ) : null,
1351
+ showFloatingPanels && showActionPanelFloating ? /* @__PURE__ */ jsxRuntimeExports.jsx(
1352
+ FloatingPanel,
1353
+ {
1354
+ boundsRef,
1355
+ title: t("overview_action_center"),
1356
+ icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Inbox, { size: 16 }),
1357
+ state: actionPanelState,
1358
+ zIndex: panelZ.action,
1359
+ onChange: setActionPanelState,
1360
+ onActivate: () => bringToFront("action"),
1361
+ className: "lab-overview-panel--action",
1362
+ children: actionPanel
1363
+ }
1364
+ ) : null
1365
+ ] }) : null
1366
+ ] }) });
1367
+ }
1368
+ function LabOverviewCanvas(props) {
1369
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(ReactFlowProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(LabOverviewCanvasInner, { ...props }) });
1370
+ }
1371
+
1372
+ const formatStateLabel = (value) => {
1373
+ const normalized = String(value || "").trim().replace(/[_-]+/g, " ");
1374
+ if (!normalized) return "N/A";
1375
+ return normalized.replace(/\b\w/g, (char) => char.toUpperCase());
1376
+ };
1377
+ function LabStatusPill({
1378
+ children,
1379
+ mono = false
1380
+ }) {
1381
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
1382
+ "span",
1383
+ {
1384
+ className: cn(
1385
+ "inline-flex max-w-full items-center rounded-full border border-[var(--lab-border)] px-2.5 py-1 text-[10px] font-medium uppercase tracking-[0.14em] text-[var(--lab-text-secondary)]",
1386
+ mono && "font-mono normal-case tracking-[0.02em]"
1387
+ ),
1388
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children })
1389
+ }
1390
+ );
1391
+ }
1392
+ function LabDetailSection({
1393
+ title,
1394
+ hint,
1395
+ actions,
1396
+ children,
1397
+ first = false
1398
+ }) {
1399
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
1400
+ "section",
1401
+ {
1402
+ className: cn(
1403
+ "py-5",
1404
+ first ? "pt-0" : "border-t border-dashed border-[var(--lab-border)]"
1405
+ ),
1406
+ children: [
1407
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-5 flex flex-wrap items-start justify-between gap-3", children: [
1408
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
1409
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-[var(--lab-text-secondary)]", children: title }),
1410
+ hint ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 text-sm leading-7 text-[var(--lab-text-secondary)]", children: hint }) : null
1411
+ ] }),
1412
+ actions ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "shrink-0", children: actions }) : null
1413
+ ] }),
1414
+ children
1415
+ ]
1416
+ }
1417
+ );
1418
+ }
1419
+ function LabOverviewMetric({
1420
+ icon,
1421
+ label,
1422
+ value,
1423
+ hint
1424
+ }) {
1425
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
1426
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.16em] text-[var(--lab-text-secondary)]", children: [
1427
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0", children: icon }),
1428
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: label })
1429
+ ] }),
1430
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 break-words text-[15px] font-semibold leading-6 text-[var(--lab-text-primary)]", children: value }),
1431
+ hint ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 break-words text-sm leading-6 text-[var(--lab-text-secondary)]", children: hint }) : null
1432
+ ] });
1433
+ }
1434
+ function LabFactRows({
1435
+ items
1436
+ }) {
1437
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "divide-y divide-dashed divide-[var(--lab-border)]", children: items.map((item) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid gap-2 py-3 sm:grid-cols-[130px_minmax(0,1fr)]", children: [
1438
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[11px] uppercase tracking-[0.16em] text-[var(--lab-text-secondary)]", children: item.label }),
1439
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "break-words text-sm leading-7 text-[var(--lab-text-primary)]", children: item.value })
1440
+ ] }, item.label)) });
1441
+ }
1442
+ function semanticToneBadgeClass(tone) {
1443
+ if (tone === "truth") {
1444
+ return "border-[rgba(64,113,175,0.24)] bg-[rgba(64,113,175,0.1)] text-[#315c97] dark:text-[#9ec5ff]";
1445
+ }
1446
+ if (tone === "runtime") {
1447
+ return "border-[rgba(99,102,241,0.24)] bg-[rgba(99,102,241,0.1)] text-[#4f46e5] dark:text-[#c7d2fe]";
1448
+ }
1449
+ if (tone === "overlay") {
1450
+ return "border-[rgba(83,176,174,0.26)] bg-[rgba(83,176,174,0.12)] text-[#0f766e] dark:text-[#8be4db]";
1451
+ }
1452
+ return "border-[rgba(148,163,184,0.26)] bg-[rgba(148,163,184,0.12)] text-[var(--lab-text-secondary)]";
1453
+ }
1454
+ function LabCanvasStudio({
1455
+ projectId,
1456
+ readOnly,
1457
+ onRefresh,
1458
+ onOpenStageSelection,
1459
+ cliStatus,
1460
+ labStream,
1461
+ projectName,
1462
+ templates,
1463
+ agents,
1464
+ quests,
1465
+ overview,
1466
+ isLoading,
1467
+ lockedQuestId = null,
1468
+ immersiveLockedQuest = false
1469
+ }) {
1470
+ const { t } = useI18n("lab");
1471
+ const queryClient = useQueryClient();
1472
+ const setActiveQuest = useLabCopilotStore((state) => state.setActiveQuest);
1473
+ const selection = useLabGraphSelectionStore((state) => state.selection);
1474
+ const clearGraphSelection = useLabGraphSelectionStore((state) => state.clear);
1475
+ const [selectedQuestIdState, setSelectedQuestIdState] = reactExports.useState(null);
1476
+ const [selectedQuestBranch, setSelectedQuestBranch] = reactExports.useState(null);
1477
+ const [selectedQuestEventId, setSelectedQuestEventId] = reactExports.useState(null);
1478
+ const [selectedAgentId, setSelectedAgentId] = reactExports.useState(null);
1479
+ const selectedQuestId = lockedQuestId ?? selectedQuestIdState;
1480
+ const templatesById = reactExports.useMemo(
1481
+ () => new Map(templates.map((template) => [template.template_id, template])),
1482
+ [templates]
1483
+ );
1484
+ const selectedQuest = reactExports.useMemo(
1485
+ () => quests.find((quest) => quest.quest_id === selectedQuestId) ?? null,
1486
+ [quests, selectedQuestId]
1487
+ );
1488
+ const selectedAgent = reactExports.useMemo(
1489
+ () => agents.find((agent) => agent.instance_id === selectedAgentId) ?? null,
1490
+ [agents, selectedAgentId]
1491
+ );
1492
+ const piAgentInstance = reactExports.useMemo(() => {
1493
+ const piTemplates = templates.filter(
1494
+ (template) => ["pi", "principal-investigator"].includes(String(template.template_key || "").trim().toLowerCase())
1495
+ );
1496
+ if (piTemplates.length) {
1497
+ const fromTemplate = agents.find((agent) => piTemplates.some((template) => agent.template_id === template.template_id)) ?? null;
1498
+ if (fromTemplate) return fromTemplate;
1499
+ }
1500
+ return agents.find((agent) => {
1501
+ const agentId = String(agent.agent_id || "").trim().toLowerCase();
1502
+ const mention = String(agent.mention_label || "").trim().replace(/^@/, "").toLowerCase();
1503
+ return agentId === "pi" || mention === "pi" || agentId.endsWith(":pi") || String(agent.template_id || "").trim().toLowerCase().includes("principal");
1504
+ }) ?? null;
1505
+ }, [agents, templates]);
1506
+ const piTemplate = piAgentInstance?.template_id ? templatesById.get(piAgentInstance.template_id) ?? null : null;
1507
+ const piAgent = piAgentInstance ? {
1508
+ name: resolveAgentDisplayName(piAgentInstance),
1509
+ logo: resolveAgentLogo(piAgentInstance, piTemplate),
1510
+ frameColor: piAgentInstance.avatar_frame_color || pickAvatarFrameColor(piAgentInstance.instance_id, 0)
1511
+ } : null;
1512
+ const pendingDecisionCount = reactExports.useMemo(() => {
1513
+ const fromGraph = overview.graph_vm?.project?.pendingDecisionCount;
1514
+ if (typeof fromGraph === "number") return Math.max(0, fromGraph);
1515
+ return quests.reduce((sum, quest) => sum + Math.max(0, Number(quest.pending_question_count ?? 0)), 0);
1516
+ }, [overview.graph_vm?.project?.pendingDecisionCount, quests]);
1517
+ reactExports.useEffect(() => {
1518
+ if (lockedQuestId) {
1519
+ return;
1520
+ }
1521
+ if (selectedQuestId && !selectedQuest && !isLoading.quests) {
1522
+ setSelectedQuestIdState(null);
1523
+ setSelectedQuestBranch(null);
1524
+ setSelectedQuestEventId(null);
1525
+ clearGraphSelection();
1526
+ }
1527
+ }, [clearGraphSelection, isLoading.quests, lockedQuestId, selectedQuest, selectedQuestId]);
1528
+ reactExports.useEffect(() => {
1529
+ if (!lockedQuestId) return;
1530
+ setSelectedQuestBranch((current) => current ?? null);
1531
+ setSelectedQuestEventId((current) => current ?? null);
1532
+ setSelectedAgentId(null);
1533
+ }, [lockedQuestId]);
1534
+ reactExports.useEffect(() => {
1535
+ setActiveQuest(selectedQuestId);
1536
+ }, [selectedQuestId, setActiveQuest]);
1537
+ reactExports.useEffect(() => {
1538
+ const handler = (event) => {
1539
+ const detail = event.detail;
1540
+ if (!detail) return;
1541
+ if (detail.projectId && detail.projectId !== projectId) return;
1542
+ if (detail.focusType === "overview" || detail.focusType === "canvas") {
1543
+ if (!lockedQuestId) {
1544
+ setSelectedQuestIdState(null);
1545
+ }
1546
+ setSelectedQuestBranch(null);
1547
+ setSelectedQuestEventId(null);
1548
+ setSelectedAgentId(null);
1549
+ clearGraphSelection();
1550
+ return;
1551
+ }
1552
+ if (detail.focusType === "agent") {
1553
+ setSelectedAgentId(detail.focusId ?? null);
1554
+ return;
1555
+ }
1556
+ if (detail.focusType === "quest" || detail.focusType === "quest-branch" || detail.focusType === "quest-event") {
1557
+ if (lockedQuestId && detail.focusId && detail.focusId !== lockedQuestId) {
1558
+ return;
1559
+ }
1560
+ if (!lockedQuestId) {
1561
+ setSelectedQuestIdState(detail.focusId ?? null);
1562
+ }
1563
+ setSelectedQuestBranch(detail.branch ?? null);
1564
+ setSelectedQuestEventId(detail.eventId ?? null);
1565
+ setSelectedAgentId(null);
1566
+ clearGraphSelection();
1567
+ }
1568
+ };
1569
+ window.addEventListener(LAB_FOCUS_EVENT$1, handler);
1570
+ return () => window.removeEventListener(LAB_FOCUS_EVENT$1, handler);
1571
+ }, [clearGraphSelection, lockedQuestId, projectId]);
1572
+ const selectedQuestDetailQuery = useQuery({
1573
+ queryKey: ["lab-quest-detail", projectId, selectedQuestId, "canvas-studio"],
1574
+ queryFn: () => getLabQuest(projectId, selectedQuestId),
1575
+ enabled: Boolean(projectId && selectedQuestId),
1576
+ staleTime: 1e4
1577
+ });
1578
+ const selectedQuestSummaryQuery = useQuery({
1579
+ queryKey: ["lab-quest-summary", projectId, selectedQuestId, selectedQuestEventId ?? null, "canvas-studio"],
1580
+ queryFn: () => getLabQuestSummary(projectId, selectedQuestId, {
1581
+ atEventId: selectedQuestEventId
1582
+ }),
1583
+ enabled: Boolean(projectId && selectedQuestId),
1584
+ staleTime: 1e4
1585
+ });
1586
+ const selectedEventPayloadQuery = useQuery({
1587
+ queryKey: [
1588
+ "lab-quest-event-payload",
1589
+ projectId,
1590
+ selectedQuestId,
1591
+ selection?.selection_type ?? null,
1592
+ selection?.selection_ref ?? null,
1593
+ "canvas-studio"
1594
+ ],
1595
+ queryFn: () => getLabQuestEventPayload(projectId, selectedQuestId, selection?.selection_ref, {
1596
+ maxBytes: 1e5
1597
+ }),
1598
+ enabled: Boolean(
1599
+ projectId && selectedQuestId && selection?.selection_type === "event_node" && selection?.selection_ref
1600
+ ),
1601
+ staleTime: 1e4
1602
+ });
1603
+ const selectedNodeTraceQuery = useQuery({
1604
+ queryKey: [
1605
+ "lab-quest-node-trace",
1606
+ projectId,
1607
+ selectedQuestId,
1608
+ selection?.selection_type ?? null,
1609
+ selection?.selection_ref ?? null,
1610
+ "canvas-studio"
1611
+ ],
1612
+ queryFn: () => getLabQuestNodeTrace(projectId, selectedQuestId, selection?.selection_ref, {
1613
+ selectionType: selection?.selection_type ?? null
1614
+ }),
1615
+ enabled: Boolean(
1616
+ projectId && selectedQuestId && selection?.selection_ref && ["branch_node", "event_node", "stage_node"].includes(String(selection?.selection_type || ""))
1617
+ ),
1618
+ staleTime: 1e4
1619
+ });
1620
+ const selectionSemantic = reactExports.useMemo(
1621
+ () => resolveLabCanvasSelectionSemantic({
1622
+ selectionType: selection?.selection_type,
1623
+ edgeType: selection?.summary ?? selection?.selection_ref ?? null,
1624
+ hasActiveProposal: false
1625
+ }),
1626
+ [selection?.selection_ref, selection?.selection_type, selection?.summary]
1627
+ );
1628
+ const handleRefresh = reactExports.useCallback(() => {
1629
+ void Promise.resolve(onRefresh?.());
1630
+ queryClient.invalidateQueries({ queryKey: ["lab-agents", projectId] });
1631
+ queryClient.invalidateQueries({ queryKey: ["lab-quests", projectId] });
1632
+ queryClient.invalidateQueries({ queryKey: ["lab-overview", projectId] });
1633
+ if (selectedQuestId) {
1634
+ queryClient.invalidateQueries({ queryKey: ["lab-quest-detail", projectId, selectedQuestId] });
1635
+ queryClient.invalidateQueries({ queryKey: ["lab-quest-summary", projectId, selectedQuestId] });
1636
+ queryClient.invalidateQueries({ queryKey: ["lab-quest-graph", projectId, selectedQuestId] });
1637
+ queryClient.invalidateQueries({ queryKey: ["lab-quest-node-trace", projectId, selectedQuestId] });
1638
+ }
1639
+ }, [onRefresh, projectId, queryClient, selectedQuestId]);
1640
+ const openQuestCanvas = reactExports.useCallback(
1641
+ (questId, branch, eventId) => {
1642
+ if (lockedQuestId && questId !== lockedQuestId) {
1643
+ return;
1644
+ }
1645
+ if (!lockedQuestId) {
1646
+ setSelectedQuestIdState(questId);
1647
+ }
1648
+ setSelectedQuestBranch(branch ?? null);
1649
+ setSelectedQuestEventId(eventId ?? null);
1650
+ setSelectedAgentId(null);
1651
+ clearGraphSelection();
1652
+ },
1653
+ [clearGraphSelection, lockedQuestId]
1654
+ );
1655
+ const closeQuestCanvas = reactExports.useCallback(() => {
1656
+ if (!lockedQuestId) {
1657
+ setSelectedQuestIdState(null);
1658
+ }
1659
+ setSelectedQuestBranch(null);
1660
+ setSelectedQuestEventId(null);
1661
+ clearGraphSelection();
1662
+ }, [clearGraphSelection, lockedQuestId]);
1663
+ const renderOverviewDetail = () => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-0", children: [
1664
+ isLoading.quests || isLoading.agents || isLoading.overview ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [
1665
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-24 w-full rounded-[18px]" }),
1666
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-20 w-full rounded-[18px]" })
1667
+ ] }) : null,
1668
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
1669
+ LabDetailSection,
1670
+ {
1671
+ first: true,
1672
+ title: "Overall",
1673
+ hint: "Select a quest node to enter its canvas. After that, click a branch, event, or stage node to inspect its durable detail state here.",
1674
+ children: [
1675
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
1676
+ /* @__PURE__ */ jsxRuntimeExports.jsx(LabStatusPill, { children: "Home" }),
1677
+ /* @__PURE__ */ jsxRuntimeExports.jsx(LabStatusPill, { children: formatStateLabel(cliStatus) }),
1678
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(LabStatusPill, { children: [
1679
+ quests.length,
1680
+ " quests"
1681
+ ] }),
1682
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(LabStatusPill, { children: [
1683
+ agents.length,
1684
+ " agents"
1685
+ ] })
1686
+ ] }),
1687
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-4 flex items-center gap-2", children: [
1688
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { className: "h-4 w-4 text-[#4071af]" }),
1689
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[22px] font-semibold tracking-[-0.03em] text-[var(--lab-text-primary)]", children: projectName || t("plugin_home_title", void 0, "Home") })
1690
+ ] })
1691
+ ]
1692
+ }
1693
+ ),
1694
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
1695
+ LabDetailSection,
1696
+ {
1697
+ title: "Operational Status",
1698
+ hint: "Keep this panel focused on high-signal canvas state rather than dashboard clutter.",
1699
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid gap-x-8 gap-y-5 sm:grid-cols-2", children: [
1700
+ /* @__PURE__ */ jsxRuntimeExports.jsx(LabOverviewMetric, { icon: /* @__PURE__ */ jsxRuntimeExports.jsx(GitBranch, { className: "h-4 w-4" }), label: "Quests", value: quests.length, hint: "Durable research repositories tracked in this project." }),
1701
+ /* @__PURE__ */ jsxRuntimeExports.jsx(LabOverviewMetric, { icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Bot, { className: "h-4 w-4" }), label: "Agents", value: agents.length, hint: "Live agent instances visible to the Lab surface." }),
1702
+ /* @__PURE__ */ jsxRuntimeExports.jsx(LabOverviewMetric, { icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { className: "h-4 w-4" }), label: "Pending", value: pendingDecisionCount, hint: "User decisions or unresolved blocking items." }),
1703
+ /* @__PURE__ */ jsxRuntimeExports.jsx(LabOverviewMetric, { icon: /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { className: "h-4 w-4" }), label: "CLI", value: formatStateLabel(cliStatus), hint: "Shared runtime binding for this project workspace." })
1704
+ ] })
1705
+ }
1706
+ ),
1707
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
1708
+ LabDetailSection,
1709
+ {
1710
+ title: "Next Step",
1711
+ hint: "The Lab plugin is now canvas-first. Explorer handles navigation, center tabs handle content, and this rail only summarizes context.",
1712
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1713
+ LabFactRows,
1714
+ {
1715
+ items: [
1716
+ { label: "Canvas", value: "Open a quest node to switch from map view into its research graph." },
1717
+ { label: "Selection", value: "Branch, event, and stage nodes expose stable detail summaries here." },
1718
+ { label: "Explorer", value: "Changed files now open in center tabs instead of rendering inline in the navigator." }
1719
+ ]
1720
+ }
1721
+ )
1722
+ }
1723
+ ),
1724
+ selectedAgent ? renderAgentDetail(selectedAgent) : null
1725
+ ] });
1726
+ const renderAgentDetail = (agent) => {
1727
+ const template = agent.template_id ? templatesById.get(agent.template_id) ?? null : null;
1728
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
1729
+ LabDetailSection,
1730
+ {
1731
+ title: "Agent",
1732
+ hint: "Current agent focus selected from the overview canvas.",
1733
+ children: [
1734
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [
1735
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
1736
+ "div",
1737
+ {
1738
+ className: "flex h-10 w-10 items-center justify-center rounded-full border border-[var(--lab-border)] bg-[var(--lab-background)]",
1739
+ style: {
1740
+ boxShadow: `0 0 0 2px ${agent.avatar_frame_color || pickAvatarFrameColor(agent.instance_id)}`
1741
+ },
1742
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(Bot, { className: "h-4 w-4 text-[var(--lab-text-primary)]" })
1743
+ }
1744
+ ),
1745
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
1746
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "truncate text-sm font-semibold text-[var(--lab-text-primary)]", children: resolveAgentDisplayName(agent) }),
1747
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "truncate text-xs text-[var(--lab-text-secondary)]", children: template?.template_key || agent.agent_id })
1748
+ ] })
1749
+ ] }),
1750
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-5", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1751
+ LabFactRows,
1752
+ {
1753
+ items: [
1754
+ { label: "Status", value: formatStateLabel(agent.status) },
1755
+ { label: "Quest", value: agent.active_quest_id || "Unassigned" },
1756
+ { label: "Branch", value: agent.active_quest_branch || "N/A" },
1757
+ { label: "Stage", value: agent.active_quest_stage_key || "N/A" }
1758
+ ]
1759
+ }
1760
+ ) })
1761
+ ]
1762
+ }
1763
+ );
1764
+ };
1765
+ const renderQuestDetail = () => {
1766
+ if (!selectedQuestId) return null;
1767
+ const questDetail = selectedQuestDetailQuery.data?.quest ?? selectedQuest;
1768
+ const questSummary = selectedQuestSummaryQuery.data?.quest ?? null;
1769
+ const questRuntime = questSummary?.runtime ?? null;
1770
+ const questGovernance = questSummary?.governance ?? null;
1771
+ const pushStatus = questGovernance?.lastPushStatus || (typeof questDetail?.github_push?.status === "string" ? questDetail.github_push.status : null) || "N/A";
1772
+ if (selectedQuestDetailQuery.isLoading && !questDetail) {
1773
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [
1774
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-28 w-full rounded-[18px]" }),
1775
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-20 w-full rounded-[18px]" }),
1776
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Skeleton, { className: "h-20 w-full rounded-[18px]" })
1777
+ ] });
1778
+ }
1779
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-0", children: [
1780
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
1781
+ LabDetailSection,
1782
+ {
1783
+ first: true,
1784
+ title: "Overall",
1785
+ hint: questDetail?.description?.trim() || questDetail?.summary?.trim() || "No quest description yet.",
1786
+ children: [
1787
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
1788
+ /* @__PURE__ */ jsxRuntimeExports.jsx(LabStatusPill, { children: formatStateLabel(questDetail?.status) }),
1789
+ /* @__PURE__ */ jsxRuntimeExports.jsx(LabStatusPill, { children: questSummary?.topology?.headBranch || questDetail?.git_head_branch || "main" }),
1790
+ /* @__PURE__ */ jsxRuntimeExports.jsx(LabStatusPill, { mono: true, children: selectedQuestId }),
1791
+ selectedQuestBranch ? /* @__PURE__ */ jsxRuntimeExports.jsx(LabStatusPill, { children: selectedQuestBranch }) : null
1792
+ ] }),
1793
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-4 text-[22px] font-semibold tracking-[-0.03em] text-[var(--lab-text-primary)]", children: questDetail?.title || selectedQuestId }),
1794
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-5 grid gap-x-8 gap-y-5 sm:grid-cols-2", children: [
1795
+ /* @__PURE__ */ jsxRuntimeExports.jsx(LabOverviewMetric, { icon: /* @__PURE__ */ jsxRuntimeExports.jsx(GitBranch, { className: "h-4 w-4" }), label: "Branches", value: questSummary?.topology?.branchCount ?? 0, hint: `Head ${questSummary?.topology?.headBranch || questDetail?.git_head_branch || "main"}` }),
1796
+ /* @__PURE__ */ jsxRuntimeExports.jsx(LabOverviewMetric, { icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Bot, { className: "h-4 w-4" }), label: "Running Agents", value: questRuntime?.runningAgents ?? 0, hint: "Live agents attached to this quest." }),
1797
+ /* @__PURE__ */ jsxRuntimeExports.jsx(LabOverviewMetric, { icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { className: "h-4 w-4" }), label: "Pending Questions", value: Math.max(0, Number(questDetail?.pending_question_count ?? 0)), hint: "Open decisions or direct user replies still waiting." }),
1798
+ /* @__PURE__ */ jsxRuntimeExports.jsx(LabOverviewMetric, { icon: /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { className: "h-4 w-4" }), label: "Push Status", value: pushStatus, hint: questDetail?.last_event_at ? `Last event ${formatRelativeTime(questDetail.last_event_at)}` : "No event timestamp yet." })
1799
+ ] })
1800
+ ]
1801
+ }
1802
+ ),
1803
+ selection ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1804
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
1805
+ LabDetailSection,
1806
+ {
1807
+ title: "Selection",
1808
+ hint: selection.summary || "No structured summary is attached to this node yet.",
1809
+ children: [
1810
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
1811
+ selectionSemantic ? /* @__PURE__ */ jsxRuntimeExports.jsx(
1812
+ "span",
1813
+ {
1814
+ className: cn(
1815
+ "inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] font-semibold",
1816
+ semanticToneBadgeClass(selectionSemantic.tone)
1817
+ ),
1818
+ children: t(
1819
+ LAB_CANVAS_SEMANTIC_TONE_META[selectionSemantic.tone].labelKey,
1820
+ void 0,
1821
+ LAB_CANVAS_SEMANTIC_TONE_META[selectionSemantic.tone].labelDefault
1822
+ )
1823
+ }
1824
+ ) : null,
1825
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: formatStateLabel(selection.selection_type) })
1826
+ ] }),
1827
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-4 text-base font-semibold text-[var(--lab-text-primary)]", children: selection.label || selection.selection_ref }),
1828
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-5", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1829
+ LabFactRows,
1830
+ {
1831
+ items: [
1832
+ { label: "Ref", value: selection.selection_ref },
1833
+ { label: "Branch No", value: selection.branch_no || "N/A" },
1834
+ { label: "Branch", value: selection.branch_name || "N/A" },
1835
+ { label: "Parent", value: selection.parent_branch || "N/A" },
1836
+ { label: "Foundation", value: selection.foundation_label || "N/A" },
1837
+ { label: "Stage", value: selection.stage_key || "N/A" },
1838
+ { label: "Worktree", value: selection.worktree_rel_path || "N/A" },
1839
+ { label: "Agent", value: selection.agent_instance_id || "N/A" }
1840
+ ]
1841
+ }
1842
+ ) })
1843
+ ]
1844
+ }
1845
+ ),
1846
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
1847
+ LabDetailSection,
1848
+ {
1849
+ title: "Trace",
1850
+ hint: "Durable actions, artifact payloads, and commit evidence attached to the selected node.",
1851
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1852
+ LabNodeTraceDetail,
1853
+ {
1854
+ projectId,
1855
+ questId: selectedQuestId,
1856
+ trace: selectedNodeTraceQuery.data?.trace ?? null,
1857
+ isLoading: selectedNodeTraceQuery.isLoading,
1858
+ payloadJson: selectedEventPayloadQuery.data?.payload_json ?? null,
1859
+ payloadTruncated: selectedEventPayloadQuery.data?.truncated ?? null
1860
+ }
1861
+ )
1862
+ }
1863
+ )
1864
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
1865
+ LabDetailSection,
1866
+ {
1867
+ title: "Operational Status",
1868
+ hint: "Quest-level runtime, topology, and governance signals.",
1869
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1870
+ LabFactRows,
1871
+ {
1872
+ items: [
1873
+ { label: "Head Branch", value: questSummary?.topology?.headBranch || questDetail?.git_head_branch || "main" },
1874
+ { label: "Pending Questions", value: Math.max(0, Number(questDetail?.pending_question_count ?? 0)) },
1875
+ { label: "Branch Count", value: questSummary?.topology?.branchCount ?? 0 },
1876
+ { label: "Running Agents", value: questRuntime?.runningAgents ?? 0 },
1877
+ { label: "Push Status", value: pushStatus },
1878
+ { label: "Last Event", value: questDetail?.last_event_at ? formatRelativeTime(questDetail.last_event_at) : "N/A" },
1879
+ { label: "Created", value: questDetail?.created_at ? formatRelativeTime(questDetail.created_at) : "N/A" }
1880
+ ]
1881
+ }
1882
+ )
1883
+ }
1884
+ )
1885
+ ] });
1886
+ };
1887
+ const canvasTitle = selectedQuest ? selectedQuest.title || selectedQuest.quest_id : projectName || t("plugin_home_title", void 0, "Home");
1888
+ const canvasSubtitle = selectedQuest ? lockedQuestId ? "Research canvas" : "Quest canvas" : "Quest map";
1889
+ if (lockedQuestId && immersiveLockedQuest && selectedQuestId) {
1890
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative flex h-full min-h-0 w-full flex-1 overflow-hidden", children: [
1891
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute right-4 top-4 z-20", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
1892
+ Button,
1893
+ {
1894
+ type: "button",
1895
+ size: "sm",
1896
+ variant: "outline",
1897
+ className: "h-9 rounded-full border-[var(--lab-border)] bg-[rgba(255,255,255,0.94)] px-3 text-[11px] text-[var(--lab-text-primary)] shadow-[0_10px_28px_rgba(15,23,42,0.08)] backdrop-blur dark:bg-[rgba(28,29,34,0.94)]",
1898
+ onClick: handleRefresh,
1899
+ children: [
1900
+ /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { className: "mr-1.5 h-3.5 w-3.5" }),
1901
+ "Refresh"
1902
+ ]
1903
+ }
1904
+ ) }),
1905
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-full min-h-0 w-full overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1906
+ LabQuestGraphCanvas,
1907
+ {
1908
+ projectId,
1909
+ questId: selectedQuestId,
1910
+ readOnly,
1911
+ highlightBranch: selectedQuestBranch,
1912
+ atEventId: selectedQuestEventId,
1913
+ preferredViewMode: selectedQuestEventId ? "event" : "branch",
1914
+ activeBranch: selectedQuestBranch,
1915
+ onBranchSelect: (branch) => {
1916
+ setSelectedQuestBranch(branch);
1917
+ setSelectedQuestEventId(null);
1918
+ },
1919
+ onEventSelect: (eventId, branchName) => {
1920
+ setSelectedQuestEventId(eventId);
1921
+ if (branchName) {
1922
+ setSelectedQuestBranch(branchName);
1923
+ }
1924
+ },
1925
+ showFloatingPanels: false,
1926
+ minimalChrome: true,
1927
+ onStageOpen: onOpenStageSelection
1928
+ }
1929
+ ) })
1930
+ ] });
1931
+ }
1932
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex h-full min-h-0 flex-col gap-4", children: [
1933
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 rounded-[20px] border border-[var(--lab-border)] bg-[var(--lab-surface)] px-4 py-3", children: [
1934
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
1935
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
1936
+ selectedQuestId && !lockedQuestId ? /* @__PURE__ */ jsxRuntimeExports.jsx(
1937
+ Button,
1938
+ {
1939
+ type: "button",
1940
+ size: "sm",
1941
+ variant: "ghost",
1942
+ className: "h-8 px-2",
1943
+ onClick: closeQuestCanvas,
1944
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowLeft, { className: "h-4 w-4" })
1945
+ }
1946
+ ) : null,
1947
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "truncate text-sm font-semibold text-[var(--lab-text-primary)]", children: canvasTitle })
1948
+ ] }),
1949
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-1 flex flex-wrap items-center gap-2 text-xs text-[var(--lab-text-secondary)]", children: [
1950
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: canvasSubtitle }),
1951
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "·" }),
1952
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
1953
+ quests.length,
1954
+ " quests"
1955
+ ] }),
1956
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "·" }),
1957
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
1958
+ agents.length,
1959
+ " agents"
1960
+ ] }),
1961
+ labStream?.status && labStream.status !== "idle" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1962
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "·" }),
1963
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
1964
+ "stream ",
1965
+ labStream.status
1966
+ ] })
1967
+ ] }) : null
1968
+ ] })
1969
+ ] }),
1970
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
1971
+ selectedQuestBranch ? /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: selectedQuestBranch }) : null,
1972
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: formatStateLabel(cliStatus) }),
1973
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
1974
+ Button,
1975
+ {
1976
+ type: "button",
1977
+ size: "sm",
1978
+ variant: "outline",
1979
+ className: "h-8 px-3",
1980
+ onClick: handleRefresh,
1981
+ children: [
1982
+ /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { className: "mr-1.5 h-3.5 w-3.5" }),
1983
+ "Refresh"
1984
+ ]
1985
+ }
1986
+ )
1987
+ ] })
1988
+ ] }),
1989
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid min-h-0 flex-1 gap-4 xl:grid-cols-[minmax(0,1fr)_360px]", children: [
1990
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "min-h-0 overflow-hidden rounded-[24px] border border-[var(--lab-border)] bg-[var(--lab-surface)]", children: selectedQuestId ? /* @__PURE__ */ jsxRuntimeExports.jsx(
1991
+ LabQuestGraphCanvas,
1992
+ {
1993
+ projectId,
1994
+ questId: selectedQuestId,
1995
+ readOnly,
1996
+ highlightBranch: selectedQuestBranch,
1997
+ atEventId: selectedQuestEventId,
1998
+ preferredViewMode: selectedQuestEventId ? "event" : "branch",
1999
+ activeBranch: selectedQuestBranch,
2000
+ onBranchSelect: (branch) => {
2001
+ setSelectedQuestBranch(branch);
2002
+ setSelectedQuestEventId(null);
2003
+ },
2004
+ onEventSelect: (eventId, branchName) => {
2005
+ setSelectedQuestEventId(eventId);
2006
+ if (branchName) {
2007
+ setSelectedQuestBranch(branchName);
2008
+ }
2009
+ },
2010
+ showFloatingPanels: false,
2011
+ onStageOpen: onOpenStageSelection
2012
+ }
2013
+ ) : /* @__PURE__ */ jsxRuntimeExports.jsx(
2014
+ LabOverviewCanvas,
2015
+ {
2016
+ projectId,
2017
+ quests,
2018
+ agents,
2019
+ templates,
2020
+ graphVm: overview.graph_vm ?? null,
2021
+ pendingDecisionCount,
2022
+ activeQuestId: selectedQuestId,
2023
+ hasPiAgent: Boolean(piAgent),
2024
+ piAgent,
2025
+ readOnly,
2026
+ actionPanel: null,
2027
+ overviewPanel: null,
2028
+ showFloatingPanels: false,
2029
+ onOpenCanvas: openQuestCanvas,
2030
+ onSelectAgent: (agentId) => {
2031
+ setSelectedAgentId(agentId);
2032
+ }
2033
+ }
2034
+ ) }),
2035
+ /* @__PURE__ */ jsxRuntimeExports.jsx("aside", { className: "min-h-0 overflow-hidden rounded-[24px] border border-[var(--lab-border)] bg-[var(--lab-surface)]", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollArea, { className: "h-full", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3 p-4", children: [
2036
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between gap-3", children: [
2037
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
2038
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-semibold text-[var(--lab-text-primary)]", children: "Details" }),
2039
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 text-xs text-[var(--lab-text-secondary)]", children: selectedQuestId ? "Selected node and quest context." : "Overview context and node guidance." })
2040
+ ] }),
2041
+ selectedQuestId ? /* @__PURE__ */ jsxRuntimeExports.jsx(
2042
+ Button,
2043
+ {
2044
+ type: "button",
2045
+ size: "sm",
2046
+ variant: "ghost",
2047
+ className: "h-8 px-3",
2048
+ onClick: () => {
2049
+ clearGraphSelection();
2050
+ setSelectedQuestEventId(null);
2051
+ },
2052
+ children: "Clear node"
2053
+ }
2054
+ ) : null
2055
+ ] }),
2056
+ selectedQuestId ? renderQuestDetail() : renderOverviewDetail()
2057
+ ] }) }) })
2058
+ ] })
2059
+ ] });
2060
+ }
2061
+
2062
+ const INVALIDATION_BATCH_MS = 80;
2063
+ const AGENT_EVENT_TYPES = /* @__PURE__ */ new Set(["lab.agent.changed", "lab.agent.status"]);
2064
+ const ASSIGNMENT_EVENT_TYPES = /* @__PURE__ */ new Set(["lab.assignment.changed"]);
2065
+ const QUEST_EVENT_TYPES = /* @__PURE__ */ new Set([
2066
+ "lab.quest.changed",
2067
+ "lab.quest.event",
2068
+ "lab.quest.sync"
2069
+ ]);
2070
+ const RUNTIME_EVENT_TYPES = /* @__PURE__ */ new Set(["lab.quest.runtime"]);
2071
+ const MEMORY_EVENT_TYPES = /* @__PURE__ */ new Set(["lab.memory.changed"]);
2072
+ const PAPER_EVENT_TYPES = /* @__PURE__ */ new Set(["lab.paper.changed"]);
2073
+ const BASELINE_EVENT_TYPES = /* @__PURE__ */ new Set(["lab.baseline.archive", "lab.baseline.restore"]);
2074
+ const asRecord = (value) => {
2075
+ if (!value || typeof value !== "object") return null;
2076
+ return value;
2077
+ };
2078
+ const toNonEmptyString = (value) => {
2079
+ if (typeof value !== "string") return null;
2080
+ const trimmed = value.trim();
2081
+ return trimmed ? trimmed : null;
2082
+ };
2083
+ const toBoolean = (value) => {
2084
+ if (typeof value === "boolean") return value;
2085
+ if (typeof value === "number") return value !== 0;
2086
+ if (typeof value === "string") {
2087
+ const normalized = value.trim().toLowerCase();
2088
+ return normalized === "1" || normalized === "true" || normalized === "yes";
2089
+ }
2090
+ return false;
2091
+ };
2092
+ const toStringArray = (value) => {
2093
+ if (!Array.isArray(value)) return [];
2094
+ return value.map((item) => toNonEmptyString(item)).filter((item) => Boolean(item));
2095
+ };
2096
+ const resolveQuestId = (envelope) => {
2097
+ if (!envelope) return null;
2098
+ const payloadData = asRecord(envelope.data);
2099
+ const directQuest = toNonEmptyString(payloadData?.quest_id) ?? toNonEmptyString(payloadData?.questId) ?? toNonEmptyString(payloadData?.active_quest_id) ?? toNonEmptyString(envelope.quest_id) ?? toNonEmptyString(envelope.questId) ?? toNonEmptyString(envelope.active_quest_id);
2100
+ if (directQuest) return directQuest;
2101
+ const nestedQuest = asRecord(payloadData?.quest);
2102
+ return toNonEmptyString(nestedQuest?.quest_id) ?? toNonEmptyString(nestedQuest?.id);
2103
+ };
2104
+ const baseProjectTargets = (projectId) => {
2105
+ return [
2106
+ { queryKey: ["lab-agents", projectId] },
2107
+ { queryKey: ["lab-quests", projectId] },
2108
+ { queryKey: ["lab-overview", projectId] }
2109
+ ];
2110
+ };
2111
+ const questTargets = (projectId, questId) => {
2112
+ if (!questId) {
2113
+ return [
2114
+ { queryKey: ["lab-quest-detail", projectId] },
2115
+ { queryKey: ["lab-quest", projectId] },
2116
+ { queryKey: ["lab-quest-summary", projectId] },
2117
+ { queryKey: ["lab-quest-graph", projectId] },
2118
+ { queryKey: ["lab-quest-events", projectId] },
2119
+ { queryKey: ["lab-quest-event-payload", projectId] },
2120
+ { queryKey: ["lab-quest-node-trace", projectId] },
2121
+ { queryKey: ["lab-quest-decision-events", projectId] },
2122
+ { queryKey: ["lab-quest-pi-qa-events", projectId] },
2123
+ { queryKey: ["lab-quest-branch-insights", projectId] },
2124
+ { queryKey: ["lab-quest-runtime", projectId] },
2125
+ { queryKey: ["lab-papers", projectId] },
2126
+ { queryKey: ["lab-memory", projectId] }
2127
+ ];
2128
+ }
2129
+ return [
2130
+ { queryKey: ["lab-quest-detail", projectId, questId] },
2131
+ { queryKey: ["lab-quest", projectId, questId] },
2132
+ { queryKey: ["lab-quest-summary", projectId, questId] },
2133
+ { queryKey: ["lab-quest-graph", projectId, questId] },
2134
+ { queryKey: ["lab-quest-events", projectId, questId] },
2135
+ { queryKey: ["lab-quest-event-payload", projectId, questId] },
2136
+ { queryKey: ["lab-quest-node-trace", projectId, questId] },
2137
+ { queryKey: ["lab-quest-decision-events", projectId, questId] },
2138
+ { queryKey: ["lab-quest-pi-qa-events", projectId, questId] },
2139
+ { queryKey: ["lab-quest-branch-insights", projectId, questId] },
2140
+ { queryKey: ["lab-quest-runtime", projectId, questId] },
2141
+ { queryKey: ["lab-papers", projectId, questId] },
2142
+ { queryKey: ["lab-memory", projectId, questId] }
2143
+ ];
2144
+ };
2145
+ const memoryTargets = (projectId) => {
2146
+ return [
2147
+ { queryKey: ["lab-memory", projectId] }
2148
+ ];
2149
+ };
2150
+ const buildTargetsForEvent = (eventType, projectId, questId) => {
2151
+ if (AGENT_EVENT_TYPES.has(eventType)) {
2152
+ return [
2153
+ { queryKey: ["lab-agents", projectId] },
2154
+ { queryKey: ["lab-overview", projectId] }
2155
+ ];
2156
+ }
2157
+ if (ASSIGNMENT_EVENT_TYPES.has(eventType)) {
2158
+ return [
2159
+ { queryKey: ["lab-agents", projectId] },
2160
+ { queryKey: ["lab-quests", projectId] },
2161
+ { queryKey: ["lab-overview", projectId] },
2162
+ ...questTargets(projectId, questId)
2163
+ ];
2164
+ }
2165
+ if (QUEST_EVENT_TYPES.has(eventType)) {
2166
+ return [
2167
+ { queryKey: ["lab-agents", projectId] },
2168
+ { queryKey: ["lab-quests", projectId] },
2169
+ { queryKey: ["lab-overview", projectId] },
2170
+ ...questTargets(projectId, questId)
2171
+ ];
2172
+ }
2173
+ if (RUNTIME_EVENT_TYPES.has(eventType)) {
2174
+ return [
2175
+ { queryKey: ["lab-quests", projectId] },
2176
+ { queryKey: ["lab-overview", projectId] },
2177
+ ...questTargets(projectId, questId)
2178
+ ];
2179
+ }
2180
+ if (MEMORY_EVENT_TYPES.has(eventType)) {
2181
+ return [...memoryTargets(projectId), ...questTargets(projectId, questId)];
2182
+ }
2183
+ if (PAPER_EVENT_TYPES.has(eventType)) {
2184
+ return [
2185
+ { queryKey: ["lab-papers", projectId] },
2186
+ { queryKey: ["lab-overview", projectId] },
2187
+ ...questTargets(projectId, questId)
2188
+ ];
2189
+ }
2190
+ if (BASELINE_EVENT_TYPES.has(eventType)) {
2191
+ return [
2192
+ { queryKey: ["lab-baselines", projectId] },
2193
+ { queryKey: ["lab-quests", projectId] },
2194
+ { queryKey: ["lab-overview", projectId] },
2195
+ ...questTargets(projectId, questId)
2196
+ ];
2197
+ }
2198
+ if (eventType.startsWith("lab.")) {
2199
+ return [
2200
+ { queryKey: ["lab-quests", projectId] },
2201
+ { queryKey: ["lab-overview", projectId] },
2202
+ ...questId ? questTargets(projectId, questId) : []
2203
+ ];
2204
+ }
2205
+ return baseProjectTargets(projectId);
2206
+ };
2207
+ const buildFocusPayloadForEvent = (eventType, projectId, envelope) => {
2208
+ if (eventType !== "lab.agent.changed") return null;
2209
+ const payload = asRecord(envelope?.data);
2210
+ if (!payload) return null;
2211
+ const action = toNonEmptyString(payload.action)?.toLowerCase();
2212
+ if (action !== "created" && action !== "promoted") return null;
2213
+ if (!toBoolean(payload.auto_focus)) return null;
2214
+ const agentId = toNonEmptyString(payload.agent_instance_id);
2215
+ if (!agentId) return null;
2216
+ return {
2217
+ projectId,
2218
+ focusType: "agent",
2219
+ focusId: agentId
2220
+ };
2221
+ };
2222
+ function useLabProjectStream({ projectId, enabled }) {
2223
+ const queryClient = useQueryClient();
2224
+ const [state, setState] = reactExports.useState({ status: "idle", lastEventAt: null });
2225
+ const localQuestSurface = isQuestRuntimeSurface();
2226
+ reactExports.useEffect(() => {
2227
+ if (!enabled || !projectId || localQuestSurface) {
2228
+ setState((current) => current.status === "idle" ? current : { ...current, status: "idle" });
2229
+ return;
2230
+ }
2231
+ let closed = false;
2232
+ let reconnectTimer = null;
2233
+ let invalidateTimer = null;
2234
+ let attempts = 0;
2235
+ let lastEventId = null;
2236
+ let abortController = null;
2237
+ const seenEventIds = /* @__PURE__ */ new Set();
2238
+ const seenEventOrder = [];
2239
+ const maxSeenEventIds = 512;
2240
+ const pendingTargets = /* @__PURE__ */ new Map();
2241
+ const streamUrl = `${getApiBaseUrl()}/api/v1/projects/${projectId}/lab/stream`;
2242
+ const parseEventBlock = (block) => {
2243
+ const lines = block.split(/\n/);
2244
+ let eventType = "";
2245
+ let eventId = "";
2246
+ const dataLines = [];
2247
+ for (const line of lines) {
2248
+ if (!line) continue;
2249
+ if (line.startsWith("event:")) {
2250
+ eventType = line.slice(6).trim();
2251
+ continue;
2252
+ }
2253
+ if (line.startsWith("data:")) {
2254
+ dataLines.push(line.slice(5).trimStart());
2255
+ continue;
2256
+ }
2257
+ if (line.startsWith("id:")) {
2258
+ eventId = line.slice(3).trim();
2259
+ }
2260
+ }
2261
+ if (dataLines.length === 0) return null;
2262
+ return {
2263
+ event: eventType || "message",
2264
+ data: dataLines.join("\n"),
2265
+ id: eventId || void 0
2266
+ };
2267
+ };
2268
+ const isDuplicateEventId = (eventType, eventId) => {
2269
+ const normalizedId = (eventId || "").trim();
2270
+ if (!normalizedId) return false;
2271
+ const normalizedType = (eventType || "").trim().toLowerCase();
2272
+ const dedupeKey = normalizedType ? `${normalizedType}|${normalizedId}` : normalizedId;
2273
+ if (seenEventIds.has(dedupeKey)) return true;
2274
+ seenEventIds.add(dedupeKey);
2275
+ seenEventOrder.push(dedupeKey);
2276
+ if (seenEventOrder.length > maxSeenEventIds) {
2277
+ const evicted = seenEventOrder.shift();
2278
+ if (evicted) seenEventIds.delete(evicted);
2279
+ }
2280
+ return false;
2281
+ };
2282
+ const flushInvalidations = () => {
2283
+ if (pendingTargets.size === 0) return;
2284
+ const entries = Array.from(pendingTargets.values());
2285
+ pendingTargets.clear();
2286
+ entries.forEach((target) => {
2287
+ queryClient.invalidateQueries({
2288
+ queryKey: target.queryKey,
2289
+ exact: target.exact
2290
+ });
2291
+ });
2292
+ };
2293
+ const enqueueInvalidations = (targets) => {
2294
+ targets.forEach((target) => {
2295
+ const key = `${target.exact ? 1 : 0}:${JSON.stringify(target.queryKey)}`;
2296
+ pendingTargets.set(key, target);
2297
+ });
2298
+ if (invalidateTimer) return;
2299
+ invalidateTimer = window.setTimeout(() => {
2300
+ invalidateTimer = null;
2301
+ flushInvalidations();
2302
+ }, INVALIDATION_BATCH_MS);
2303
+ };
2304
+ const handleStreamEvent = (eventType, payloadText) => {
2305
+ if (eventType === "lab.ping") return;
2306
+ setState((current) => ({ ...current, lastEventAt: Date.now() }));
2307
+ let envelope = null;
2308
+ try {
2309
+ const parsed = JSON.parse(payloadText || "{}");
2310
+ envelope = asRecord(parsed);
2311
+ } catch {
2312
+ envelope = null;
2313
+ }
2314
+ const questId = resolveQuestId(envelope);
2315
+ const payload = asRecord(envelope?.data);
2316
+ const questIds = toStringArray(payload?.quest_ids);
2317
+ const targets = buildTargetsForEvent(eventType, projectId, questId);
2318
+ enqueueInvalidations(targets);
2319
+ questIds.forEach((id) => {
2320
+ enqueueInvalidations(questTargets(projectId, id));
2321
+ });
2322
+ const focusPayload = buildFocusPayloadForEvent(eventType, projectId, envelope);
2323
+ if (focusPayload) {
2324
+ dispatchLabFocus(focusPayload);
2325
+ }
2326
+ };
2327
+ const scheduleReconnect = () => {
2328
+ if (closed) return;
2329
+ if (reconnectTimer) {
2330
+ window.clearTimeout(reconnectTimer);
2331
+ }
2332
+ const capped = Math.min(attempts, 4);
2333
+ const baseDelay = Math.min(1e4, 1e3 * 2 ** capped);
2334
+ const jitter = Math.min(250, attempts * 50);
2335
+ setState(
2336
+ (current) => current.status === "connecting" || current.status === "open" ? { ...current, status: "reconnecting" } : current
2337
+ );
2338
+ reconnectTimer = window.setTimeout(connect, baseDelay + jitter);
2339
+ };
2340
+ const connect = () => {
2341
+ if (closed) return;
2342
+ if (abortController) {
2343
+ abortController.abort();
2344
+ }
2345
+ abortController = new AbortController();
2346
+ const controller = abortController;
2347
+ setState((current) => ({
2348
+ ...current,
2349
+ status: attempts > 0 ? "reconnecting" : "connecting"
2350
+ }));
2351
+ void (async () => {
2352
+ try {
2353
+ const token = typeof window !== "undefined" ? window.localStorage.getItem("ds_access_token") : null;
2354
+ if (!token) return;
2355
+ const headers = {
2356
+ Accept: "text/event-stream",
2357
+ Authorization: `Bearer ${token}`
2358
+ };
2359
+ if (lastEventId) {
2360
+ headers["Last-Event-ID"] = lastEventId;
2361
+ }
2362
+ const response = await fetch(streamUrl, {
2363
+ method: "GET",
2364
+ headers,
2365
+ signal: controller.signal
2366
+ });
2367
+ if (closed || controller.signal.aborted) return;
2368
+ if (response.status === 401) {
2369
+ if (typeof window !== "undefined") {
2370
+ window.localStorage.removeItem("ds_access_token");
2371
+ redirectToLanding("session_expired");
2372
+ }
2373
+ return;
2374
+ }
2375
+ if (!response.ok) {
2376
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
2377
+ }
2378
+ attempts = 0;
2379
+ setState((current) => ({ ...current, status: "open" }));
2380
+ enqueueInvalidations(baseProjectTargets(projectId));
2381
+ const reader = response.body?.getReader();
2382
+ if (!reader) {
2383
+ throw new Error("No response body");
2384
+ }
2385
+ const decoder = new TextDecoder();
2386
+ let buffer = "";
2387
+ while (true) {
2388
+ if (closed || controller.signal.aborted) break;
2389
+ const { done, value } = await reader.read();
2390
+ if (done) break;
2391
+ if (closed || controller.signal.aborted) break;
2392
+ buffer += decoder.decode(value, { stream: true });
2393
+ if (buffer.includes("\r")) {
2394
+ buffer = buffer.replace(/\r\n/g, "\n");
2395
+ }
2396
+ let boundaryIndex = buffer.indexOf("\n\n");
2397
+ while (boundaryIndex !== -1) {
2398
+ const raw = buffer.slice(0, boundaryIndex);
2399
+ buffer = buffer.slice(boundaryIndex + 2);
2400
+ const normalized = raw.replace(/\r\n/g, "\n").trim();
2401
+ const parsed = parseEventBlock(normalized);
2402
+ if (parsed?.id) {
2403
+ lastEventId = parsed.id;
2404
+ if (isDuplicateEventId(parsed.event, parsed.id)) {
2405
+ boundaryIndex = buffer.indexOf("\n\n");
2406
+ continue;
2407
+ }
2408
+ }
2409
+ if (parsed && !closed && !controller.signal.aborted) {
2410
+ handleStreamEvent(parsed.event, parsed.data);
2411
+ }
2412
+ boundaryIndex = buffer.indexOf("\n\n");
2413
+ }
2414
+ }
2415
+ const trailing = buffer.replace(/\r\n/g, "\n").trim();
2416
+ if (trailing && !closed && !controller.signal.aborted) {
2417
+ const parsed = parseEventBlock(trailing);
2418
+ if (parsed?.id) {
2419
+ lastEventId = parsed.id;
2420
+ if (isDuplicateEventId(parsed.event, parsed.id)) {
2421
+ return;
2422
+ }
2423
+ }
2424
+ if (parsed) {
2425
+ handleStreamEvent(parsed.event, parsed.data);
2426
+ }
2427
+ }
2428
+ if (!closed && !controller.signal.aborted) {
2429
+ attempts += 1;
2430
+ scheduleReconnect();
2431
+ }
2432
+ } catch {
2433
+ if (closed || controller.signal.aborted) return;
2434
+ setState((current) => ({ ...current, status: "error" }));
2435
+ attempts += 1;
2436
+ scheduleReconnect();
2437
+ }
2438
+ })();
2439
+ };
2440
+ connect();
2441
+ return () => {
2442
+ closed = true;
2443
+ flushInvalidations();
2444
+ if (reconnectTimer) {
2445
+ window.clearTimeout(reconnectTimer);
2446
+ }
2447
+ if (invalidateTimer) {
2448
+ window.clearTimeout(invalidateTimer);
2449
+ }
2450
+ if (abortController) {
2451
+ abortController.abort();
2452
+ }
2453
+ };
2454
+ }, [enabled, localQuestSurface, projectId, queryClient]);
2455
+ return state;
2456
+ }
2457
+
2458
+ function resolveLabListPollingInterval({
2459
+ liveEnabled,
2460
+ streamStatus,
2461
+ fastMs,
2462
+ slowMs
2463
+ }) {
2464
+ if (!liveEnabled) return false;
2465
+ return streamStatus === "open" ? slowMs : fastMs;
2466
+ }
2467
+
2468
+ function LabSurface({
2469
+ projectId,
2470
+ readOnly,
2471
+ lockedQuestId = null,
2472
+ immersiveLockedQuest = false,
2473
+ onRefresh,
2474
+ onOpenStageSelection
2475
+ }) {
2476
+ const queryClient = useQueryClient();
2477
+ const setActiveQuest = useLabCopilotStore((state) => state.setActiveQuest);
2478
+ const activeQuestId = useLabCopilotStore((state) => state.activeQuestId);
2479
+ const clearSelections = useLabCopilotStore((state) => state.clearSelections);
2480
+ const cliServers = useCliStore((state) => state.servers);
2481
+ const loadCliServers = useCliStore((state) => state.loadServers);
2482
+ const cliProjectId = useCliStore((state) => state.projectId);
2483
+ const activeTab = useActiveTab();
2484
+ const isLabTabActive = reactExports.useMemo(() => {
2485
+ const customData = activeTab?.context?.customData;
2486
+ const tabProjectId = typeof customData?.projectId === "string" ? customData.projectId : null;
2487
+ const matchesProject = !tabProjectId || tabProjectId === projectId;
2488
+ return activeTab?.pluginId === BUILTIN_PLUGINS.LAB && matchesProject;
2489
+ }, [activeTab, projectId]);
2490
+ reactExports.useEffect(() => {
2491
+ if (!projectId) return;
2492
+ if (cliProjectId === projectId) return;
2493
+ void loadCliServers(projectId);
2494
+ }, [cliProjectId, loadCliServers, projectId]);
2495
+ const onlineCliServers = reactExports.useMemo(
2496
+ () => cliServers.filter((server) => server.status !== "offline" && server.status !== "error"),
2497
+ [cliServers]
2498
+ );
2499
+ const cliStatus = cliServers.length === 0 ? "unbound" : onlineCliServers.length > 0 ? "online" : "offline";
2500
+ const labReadOnly = true;
2501
+ const [isPageActive, setIsPageActive] = reactExports.useState(true);
2502
+ reactExports.useEffect(() => {
2503
+ const handleVisibility = () => {
2504
+ setIsPageActive(typeof document !== "undefined" && document.visibilityState === "visible");
2505
+ };
2506
+ handleVisibility();
2507
+ document.addEventListener("visibilitychange", handleVisibility);
2508
+ return () => document.removeEventListener("visibilitychange", handleVisibility);
2509
+ }, []);
2510
+ const labStaleTime = 3e4;
2511
+ const liveRefetchEnabled = Boolean(projectId && isLabTabActive && isPageActive);
2512
+ const labStreamEnabled = Boolean(projectId && isPageActive);
2513
+ const LIVE_AGENTS_INTERVAL_MS = 5e3;
2514
+ const LIVE_QUESTS_INTERVAL_MS = 1e4;
2515
+ const LIVE_OVERVIEW_INTERVAL_MS = 1e4;
2516
+ const labStream = useLabProjectStream({ projectId, enabled: labStreamEnabled });
2517
+ const labStreamStatus = labStream?.status ?? "idle";
2518
+ const agentsRefetchInterval = resolveLabListPollingInterval({
2519
+ liveEnabled: liveRefetchEnabled,
2520
+ streamStatus: labStreamStatus,
2521
+ fastMs: LIVE_AGENTS_INTERVAL_MS,
2522
+ // Agent statuses are derived from chat session state; until we publish per-agent status events,
2523
+ // keep polling relatively frequent even when the project stream is healthy.
2524
+ slowMs: LIVE_AGENTS_INTERVAL_MS
2525
+ });
2526
+ const questsRefetchInterval = resolveLabListPollingInterval({
2527
+ liveEnabled: liveRefetchEnabled,
2528
+ streamStatus: labStreamStatus,
2529
+ fastMs: LIVE_QUESTS_INTERVAL_MS,
2530
+ slowMs: 3e4
2531
+ });
2532
+ const overviewRefetchInterval = resolveLabListPollingInterval({
2533
+ liveEnabled: liveRefetchEnabled,
2534
+ streamStatus: labStreamStatus,
2535
+ fastMs: LIVE_OVERVIEW_INTERVAL_MS,
2536
+ slowMs: 3e4
2537
+ });
2538
+ const wasLiveRefetchEnabledRef = reactExports.useRef(liveRefetchEnabled);
2539
+ reactExports.useEffect(() => {
2540
+ const wasEnabled = wasLiveRefetchEnabledRef.current;
2541
+ wasLiveRefetchEnabledRef.current = liveRefetchEnabled;
2542
+ if (!projectId || !liveRefetchEnabled || wasEnabled) return;
2543
+ queryClient.invalidateQueries({ queryKey: ["lab-agents", projectId] });
2544
+ queryClient.invalidateQueries({ queryKey: ["lab-quests", projectId] });
2545
+ queryClient.invalidateQueries({ queryKey: ["lab-overview", projectId] });
2546
+ }, [liveRefetchEnabled, projectId, queryClient]);
2547
+ const templatesQuery = useQuery({
2548
+ queryKey: ["lab-templates", projectId],
2549
+ queryFn: () => listLabTemplates(projectId),
2550
+ enabled: Boolean(projectId),
2551
+ staleTime: labStaleTime
2552
+ });
2553
+ const agentsQuery = useQuery({
2554
+ queryKey: ["lab-agents", projectId],
2555
+ queryFn: () => listLabAgents(projectId, { silent: true }),
2556
+ enabled: Boolean(projectId),
2557
+ staleTime: labStaleTime,
2558
+ refetchInterval: agentsRefetchInterval
2559
+ });
2560
+ const questsQuery = useQuery({
2561
+ queryKey: ["lab-quests", projectId],
2562
+ queryFn: () => listLabQuests(projectId, { silent: true }),
2563
+ enabled: Boolean(projectId),
2564
+ staleTime: labStaleTime,
2565
+ refetchInterval: questsRefetchInterval
2566
+ });
2567
+ const overviewQuery = useQuery({
2568
+ queryKey: ["lab-overview", projectId],
2569
+ queryFn: () => getLabOverview(projectId, { silent: true }),
2570
+ staleTime: labStaleTime,
2571
+ refetchInterval: overviewRefetchInterval
2572
+ });
2573
+ const projectQuery = useQuery({
2574
+ queryKey: ["project", projectId],
2575
+ queryFn: () => getProject(projectId),
2576
+ enabled: Boolean(projectId),
2577
+ staleTime: labStaleTime
2578
+ });
2579
+ const templates = templatesQuery.data?.items ?? [];
2580
+ const agents = agentsQuery.data?.items ?? [];
2581
+ const quests = questsQuery.data?.items ?? [];
2582
+ const overview = overviewQuery.data ?? {};
2583
+ const latestQuestId = reactExports.useMemo(() => {
2584
+ if (quests.length === 0) return null;
2585
+ const latest = quests.reduce((current, candidate) => {
2586
+ const currentTime = current?.created_at ? new Date(current.created_at).getTime() : 0;
2587
+ const candidateTime = candidate?.created_at ? new Date(candidate.created_at).getTime() : 0;
2588
+ return candidateTime > currentTime ? candidate : current;
2589
+ }, quests[0]);
2590
+ return latest?.quest_id ?? null;
2591
+ }, [quests]);
2592
+ const projectName = projectQuery.data?.name ?? null;
2593
+ reactExports.useEffect(() => {
2594
+ if (lockedQuestId) {
2595
+ setActiveQuest(lockedQuestId);
2596
+ return;
2597
+ }
2598
+ if (activeQuestId || !latestQuestId) return;
2599
+ setActiveQuest(latestQuestId);
2600
+ }, [activeQuestId, latestQuestId, lockedQuestId, setActiveQuest]);
2601
+ reactExports.useEffect(() => {
2602
+ clearSelections();
2603
+ }, [clearSelections, projectId]);
2604
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
2605
+ "div",
2606
+ {
2607
+ className: "lab-root flex h-full min-h-0 flex-col",
2608
+ "data-activity": isPageActive ? "active" : "inactive",
2609
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: cn("lab-panel h-full min-h-0 flex-1 overflow-hidden"), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2610
+ LabCanvasStudio,
2611
+ {
2612
+ projectId,
2613
+ readOnly: labReadOnly,
2614
+ onRefresh,
2615
+ onOpenStageSelection,
2616
+ cliStatus,
2617
+ labStream,
2618
+ projectName,
2619
+ templates,
2620
+ agents,
2621
+ quests,
2622
+ overview,
2623
+ isLoading: {
2624
+ agents: agentsQuery.isLoading,
2625
+ quests: questsQuery.isLoading,
2626
+ overview: overviewQuery.isLoading
2627
+ },
2628
+ lockedQuestId,
2629
+ immersiveLockedQuest
2630
+ }
2631
+ ) })
2632
+ }
2633
+ );
2634
+ }
2635
+
2636
+ const LAB_FOCUS_EVENT = "ds:lab:focus";
2637
+ function LabPlugin({ context, setTitle }) {
2638
+ const { t } = useI18n("lab");
2639
+ const maxEntitlement = useMaxEntitlement("lab.use");
2640
+ const projectId = typeof context.customData?.projectId === "string" ? context.customData.projectId : null;
2641
+ const readOnly = Boolean(context.customData?.readOnly);
2642
+ const focusType = context.customData?.focusType === "agent" || context.customData?.focusType === "quest" || context.customData?.focusType === "quest-branch" || context.customData?.focusType === "quest-event" || context.customData?.focusType === "overview" ? context.customData.focusType : null;
2643
+ const focusId = typeof context.customData?.focusId === "string" ? context.customData.focusId : null;
2644
+ const focusBranch = typeof context.customData?.branch === "string" ? context.customData.branch : null;
2645
+ const focusEventId = typeof context.customData?.eventId === "string" ? context.customData.eventId : null;
2646
+ reactExports.useEffect(() => {
2647
+ setTitle(t("plugin_home_title", void 0, "Home"));
2648
+ }, [setTitle, t]);
2649
+ reactExports.useEffect(() => {
2650
+ if (!projectId || !focusType) return;
2651
+ if (!focusId && focusType !== "overview") return;
2652
+ window.dispatchEvent(
2653
+ new CustomEvent(LAB_FOCUS_EVENT, {
2654
+ detail: {
2655
+ projectId,
2656
+ focusType,
2657
+ focusId,
2658
+ branch: focusBranch,
2659
+ eventId: focusEventId
2660
+ }
2661
+ })
2662
+ );
2663
+ }, [focusBranch, focusEventId, focusId, focusType, projectId]);
2664
+ if (!projectId) {
2665
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex h-full items-center justify-center rounded-2xl border border-[var(--soft-border)] bg-[var(--soft-bg-surface)]", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm text-[var(--soft-text-secondary)]", children: t("plugin_project_not_found", void 0, "Project not found.") }) });
2666
+ }
2667
+ if (!maxEntitlement.isEntitlementLoading && !maxEntitlement.isMaxEntitled) {
2668
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex h-full items-center justify-center rounded-2xl border border-[var(--soft-border)] bg-[var(--soft-bg-surface)]", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-6 text-center", children: [
2669
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-medium text-[var(--soft-text-primary)]", children: t("plugin_plan_access_required", void 0, "Plan access required") }),
2670
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 text-xs text-[var(--soft-text-secondary)]", children: t("plugin_max_only_desc", void 0, "Lab is currently available for Max users only.") })
2671
+ ] }) });
2672
+ }
2673
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(LabSurface, { projectId, readOnly });
2674
+ }
2675
+
2676
+ export { LabPlugin as default };