- commit
- ebbf8ac
- parent
- e8fba02
- author
- im_wower
- date
- 2026-04-02 00:16:50 +0800 CST
feat: add conductor ui shell
19 files changed,
+1735,
-12
Raw patch view.
1diff --git a/apps/conductor-daemon/package.json b/apps/conductor-daemon/package.json
2index fecbbf39ec5c681de004178c12a4c5398c6fad9f..8bd7d3c69f8d32d056bdd76e5490cb7f59495c48 100644
3--- a/apps/conductor-daemon/package.json
4+++ b/apps/conductor-daemon/package.json
5@@ -10,10 +10,10 @@
6 "@baa-conductor/host-ops": "workspace:*"
7 },
8 "scripts": {
9- "build": "pnpm -C ../.. -F @baa-conductor/db build && pnpm -C ../.. -F @baa-conductor/artifact-db build && pnpm -C ../.. -F @baa-conductor/d1-client build && pnpm -C ../.. -F @baa-conductor/host-ops build && pnpm -C ../.. -F @baa-conductor/status-api build && pnpm exec tsc -p tsconfig.json",
10+ "build": "pnpm -C ../.. -F @baa-conductor/db build && pnpm -C ../.. -F @baa-conductor/artifact-db build && pnpm -C ../.. -F @baa-conductor/d1-client build && pnpm -C ../.. -F @baa-conductor/host-ops build && pnpm -C ../.. -F @baa-conductor/status-api build && pnpm -C ../.. -F @baa-conductor/conductor-ui build && pnpm exec tsc -p tsconfig.json",
11 "dev": "pnpm run build && node dist/index.js",
12 "start": "node dist/index.js",
13 "test": "pnpm run build && node --test src/index.test.js",
14- "typecheck": "pnpm -C ../.. -F @baa-conductor/db build && pnpm -C ../.. -F @baa-conductor/artifact-db build && pnpm -C ../.. -F @baa-conductor/d1-client build && pnpm -C ../.. -F @baa-conductor/host-ops build && pnpm -C ../.. -F @baa-conductor/status-api build && pnpm exec tsc --noEmit -p tsconfig.json"
15+ "typecheck": "pnpm -C ../.. -F @baa-conductor/db build && pnpm -C ../.. -F @baa-conductor/artifact-db build && pnpm -C ../.. -F @baa-conductor/d1-client build && pnpm -C ../.. -F @baa-conductor/host-ops build && pnpm -C ../.. -F @baa-conductor/status-api build && pnpm -C ../.. -F @baa-conductor/conductor-ui typecheck && pnpm exec tsc --noEmit -p tsconfig.json"
16 }
17 }
18diff --git a/apps/conductor-daemon/src/index.test.js b/apps/conductor-daemon/src/index.test.js
19index eef834882b46e5d18c389835464ce25e7b45e5b0..287b190aa40c2fac71f6436f2aa7cf8c03433ee8 100644
20--- a/apps/conductor-daemon/src/index.test.js
21+++ b/apps/conductor-daemon/src/index.test.js
22@@ -7675,6 +7675,27 @@ test("ConductorRuntime serves health and migrated local API endpoints over HTTP"
23 assert.match(statusViewUiHtml, /Readable automation state for people and browser controls\./u);
24 assert.match(statusViewUiHtml, /HTML endpoint: <strong>\/v1\/status\/ui<\/strong>/u);
25
26+ const conductorUiResponse = await fetch(`${baseUrl}/app`);
27+ assert.equal(conductorUiResponse.status, 200);
28+ assert.equal(conductorUiResponse.headers.get("content-type"), "text/html; charset=utf-8");
29+ assert.equal(conductorUiResponse.headers.get("cache-control"), "no-store");
30+ const conductorUiHtml = await conductorUiResponse.text();
31+ assert.match(conductorUiHtml, /<title>BAA Conductor UI<\/title>/u);
32+ assert.match(conductorUiHtml, /\/app\/assets\/[^"' ]+\.(?:css|js)/u);
33+
34+ const conductorUiControlResponse = await fetch(`${baseUrl}/app/control`);
35+ assert.equal(conductorUiControlResponse.status, 200);
36+ assert.equal(conductorUiControlResponse.headers.get("content-type"), "text/html; charset=utf-8");
37+ const conductorUiControlHtml = await conductorUiControlResponse.text();
38+ assert.match(conductorUiControlHtml, /<title>BAA Conductor UI<\/title>/u);
39+
40+ const conductorUiAssetMatch = conductorUiHtml.match(/\/app\/assets\/[^"' )]+\.(?:css|js)/u);
41+ assert.ok(conductorUiAssetMatch);
42+
43+ const conductorUiAssetResponse = await fetch(`${baseUrl}${conductorUiAssetMatch[0]}`);
44+ assert.equal(conductorUiAssetResponse.status, 200);
45+ assert.equal(conductorUiAssetResponse.headers.get("cache-control"), "public, max-age=31536000, immutable");
46+
47 const codexStatusResponse = await fetch(`${baseUrl}/v1/codex`);
48 assert.equal(codexStatusResponse.status, 200);
49 const codexStatusPayload = await codexStatusResponse.json();
50diff --git a/apps/conductor-daemon/src/local-api.ts b/apps/conductor-daemon/src/local-api.ts
51index fb8af94b1e6d72cc91dfb4b9ea6e3b35593da64e..3702ce105a840b5a925da4715508864af1edd709 100644
52--- a/apps/conductor-daemon/src/local-api.ts
53+++ b/apps/conductor-daemon/src/local-api.ts
54@@ -1,6 +1,7 @@
55 import * as nodeFs from "node:fs";
56 import { randomUUID } from "node:crypto";
57 import * as nodePath from "node:path";
58+import { fileURLToPath } from "node:url";
59 import {
60 buildArtifactRelativePath,
61 buildArtifactPublicUrl,
62@@ -120,11 +121,32 @@ const HOST_OPERATIONS_ROUTE_IDS = new Set(["host.exec", "host.files.read", "host
63 const HOST_OPERATIONS_AUTH_HEADER = "Authorization: Bearer <BAA_SHARED_TOKEN>";
64 const HOST_OPERATIONS_WWW_AUTHENTICATE = 'Bearer realm="baa-conductor-host-ops"';
65 const DEFAULT_CODE_ROOT_DIR = "/Users/george/code/";
66+const DEFAULT_CONDUCTOR_UI_DIST_DIR = fileURLToPath(new URL("../../conductor-ui/dist/", import.meta.url));
67 const CODE_ROUTE_CONTENT_TYPE = "text/plain; charset=utf-8";
68 const STATUS_VIEW_HTML_HEADERS = {
69 "cache-control": "no-store",
70 "content-type": "text/html; charset=utf-8"
71 } as const;
72+const CONDUCTOR_UI_HTML_HEADERS = {
73+ "cache-control": "no-store",
74+ "content-type": "text/html; charset=utf-8"
75+} as const;
76+const CONDUCTOR_UI_ASSET_CACHE_CONTROL = "public, max-age=31536000, immutable";
77+const CONDUCTOR_UI_CONTENT_TYPES: Record<string, string> = {
78+ ".css": "text/css; charset=utf-8",
79+ ".html": "text/html; charset=utf-8",
80+ ".ico": "image/x-icon",
81+ ".js": "application/javascript; charset=utf-8",
82+ ".json": "application/json; charset=utf-8",
83+ ".map": "application/json; charset=utf-8",
84+ ".mjs": "application/javascript; charset=utf-8",
85+ ".png": "image/png",
86+ ".svg": "image/svg+xml",
87+ ".txt": "text/plain; charset=utf-8",
88+ ".webp": "image/webp",
89+ ".woff": "font/woff",
90+ ".woff2": "font/woff2"
91+};
92 const ROBOTS_TXT_BODY = "User-agent: *\nAllow: /artifact/";
93 const SSE_RESPONSE_HEADERS = {
94 "cache-control": "no-store",
95@@ -437,6 +459,30 @@ const LOCAL_API_ROUTES: LocalApiRouteDefinition[] = [
96 pathPattern: "/robots.txt",
97 summary: "返回允许 AI 访问 /artifact/ 的 robots.txt"
98 },
99+ {
100+ id: "service.ui.index",
101+ exposeInDescribe: false,
102+ kind: "read",
103+ method: "GET",
104+ pathPattern: "/app",
105+ summary: "返回 conductor 正式 Web 工作台 HTML 壳"
106+ },
107+ {
108+ id: "service.ui.assets",
109+ exposeInDescribe: false,
110+ kind: "read",
111+ method: "GET",
112+ pathPattern: "/app/assets/:asset_path*",
113+ summary: "返回 conductor Web 工作台静态资源"
114+ },
115+ {
116+ id: "service.ui.spa",
117+ exposeInDescribe: false,
118+ kind: "read",
119+ method: "GET",
120+ pathPattern: "/app/:app_path*",
121+ summary: "返回 conductor Web 工作台 SPA history fallback"
122+ },
123 // Keep the repo route ahead of the generic artifact route so repo root URLs
124 // can fall back to log.html instead of being claimed by the generic matcher.
125 {
126@@ -6678,6 +6724,131 @@ function isPathOutsideRoot(rootPath: string, targetPath: string): boolean {
127 return firstSegment === "..";
128 }
129
130+function buildConductorUiNotBuiltError(): LocalApiHttpError {
131+ return new LocalApiHttpError(
132+ 503,
133+ "conductor_ui_not_built",
134+ `Conductor UI assets were not found at "${DEFAULT_CONDUCTOR_UI_DIST_DIR}". Run "pnpm -C apps/conductor-ui build".`
135+ );
136+}
137+
138+function buildConductorUiNotFoundError(pathname: string): LocalApiHttpError {
139+ return new LocalApiHttpError(404, "not_found", `Conductor UI asset "${normalizePathname(pathname)}" was not found.`);
140+}
141+
142+function getConductorUiContentType(filePath: string): string {
143+ return CONDUCTOR_UI_CONTENT_TYPES[extname(filePath).toLowerCase()] ?? "application/octet-stream";
144+}
145+
146+function getConductorUiDistDir(): string {
147+ try {
148+ const distDir = realpathSync(DEFAULT_CONDUCTOR_UI_DIST_DIR);
149+ const stats = statSync(distDir);
150+
151+ if (!stats.isDirectory()) {
152+ throw buildConductorUiNotBuiltError();
153+ }
154+
155+ return distDir;
156+ } catch (error) {
157+ if (error instanceof LocalApiHttpError) {
158+ throw error;
159+ }
160+
161+ if (isMissingFileError(error)) {
162+ throw buildConductorUiNotBuiltError();
163+ }
164+
165+ throw error;
166+ }
167+}
168+
169+function assertAllowedConductorUiAssetPath(assetPath: string, pathname: string): void {
170+ if (assetPath === "" || assetPath.includes("\u0000") || assetPath.includes("\\") || assetPath.startsWith("/")) {
171+ throw buildConductorUiNotFoundError(pathname);
172+ }
173+
174+ const segments = assetPath.split("/");
175+
176+ if (segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
177+ throw buildConductorUiNotFoundError(pathname);
178+ }
179+}
180+
181+function readConductorUiIndexFile(distDir: string): ConductorHttpResponse {
182+ const indexPath = resolve(distDir, "index.html");
183+
184+ try {
185+ const stats = statSync(indexPath);
186+
187+ if (stats.isDirectory()) {
188+ throw buildConductorUiNotBuiltError();
189+ }
190+
191+ return binaryResponse(200, readFileSync(indexPath), CONDUCTOR_UI_HTML_HEADERS);
192+ } catch (error) {
193+ if (error instanceof LocalApiHttpError) {
194+ throw error;
195+ }
196+
197+ if (isMissingFileError(error)) {
198+ throw buildConductorUiNotBuiltError();
199+ }
200+
201+ throw error;
202+ }
203+}
204+
205+async function handleConductorUiIndexRead(): Promise<ConductorHttpResponse> {
206+ return readConductorUiIndexFile(getConductorUiDistDir());
207+}
208+
209+async function handleConductorUiAssetRead(context: LocalApiRequestContext): Promise<ConductorHttpResponse> {
210+ const assetPath = context.params.asset_path ?? "";
211+ assertAllowedConductorUiAssetPath(assetPath, context.url.pathname);
212+
213+ const distDir = getConductorUiDistDir();
214+ const assetsRoot = resolve(distDir, "assets");
215+ const requestedPath = resolve(assetsRoot, assetPath);
216+
217+ if (isPathOutsideRoot(assetsRoot, requestedPath)) {
218+ throw buildConductorUiNotFoundError(context.url.pathname);
219+ }
220+
221+ try {
222+ const filePath = realpathSync(requestedPath);
223+
224+ if (isPathOutsideRoot(assetsRoot, filePath)) {
225+ throw buildConductorUiNotFoundError(context.url.pathname);
226+ }
227+
228+ const stats = statSync(filePath);
229+
230+ if (stats.isDirectory()) {
231+ throw buildConductorUiNotFoundError(context.url.pathname);
232+ }
233+
234+ return binaryResponse(200, readFileSync(filePath), {
235+ "cache-control": CONDUCTOR_UI_ASSET_CACHE_CONTROL,
236+ "content-type": getConductorUiContentType(filePath)
237+ });
238+ } catch (error) {
239+ if (error instanceof LocalApiHttpError) {
240+ throw error;
241+ }
242+
243+ if (isMissingFileError(error)) {
244+ throw buildConductorUiNotFoundError(context.url.pathname);
245+ }
246+
247+ throw error;
248+ }
249+}
250+
251+async function handleConductorUiSpaRead(): Promise<ConductorHttpResponse> {
252+ return readConductorUiIndexFile(getConductorUiDistDir());
253+}
254+
255 function buildCodeAccessForbiddenError(): LocalApiHttpError {
256 return new LocalApiHttpError(403, "forbidden", "Requested code path is not allowed.");
257 }
258@@ -7752,6 +7923,12 @@ async function dispatchBusinessRoute(
259 return handleScopedDescribeRead(context, version, "control");
260 case "service.robots":
261 return handleRobotsRead();
262+ case "service.ui.index":
263+ return handleConductorUiIndexRead();
264+ case "service.ui.assets":
265+ return handleConductorUiAssetRead(context);
266+ case "service.ui.spa":
267+ return handleConductorUiSpaRead();
268 case "service.artifact.read":
269 return handleArtifactRead(context);
270 case "service.artifact.repo":
271diff --git a/apps/conductor-ui/index.html b/apps/conductor-ui/index.html
272new file mode 100644
273index 0000000000000000000000000000000000000000..6870e509fa2d32c14440f9c79c6eefe0e4620e71
274--- /dev/null
275+++ b/apps/conductor-ui/index.html
276@@ -0,0 +1,16 @@
277+<!doctype html>
278+<html lang="zh-CN">
279+ <head>
280+ <meta charset="UTF-8" />
281+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
282+ <title>BAA Conductor UI</title>
283+ <meta
284+ name="description"
285+ content="BAA Conductor 正式 Web 工作台。"
286+ />
287+ </head>
288+ <body>
289+ <div id="app"></div>
290+ <script type="module" src="/src/main.ts"></script>
291+ </body>
292+ </html>
293diff --git a/apps/conductor-ui/package.json b/apps/conductor-ui/package.json
294new file mode 100644
295index 0000000000000000000000000000000000000000..6e031cac03a22c6861af8ff017b54fd754eb5fa1
296--- /dev/null
297+++ b/apps/conductor-ui/package.json
298@@ -0,0 +1,21 @@
299+{
300+ "name": "@baa-conductor/conductor-ui",
301+ "private": true,
302+ "type": "module",
303+ "dependencies": {
304+ "vue": "^3.5.13",
305+ "vue-router": "^4.5.1"
306+ },
307+ "devDependencies": {
308+ "@types/node": "^22.13.13",
309+ "@vitejs/plugin-vue": "^5.2.3",
310+ "vite": "^6.2.2",
311+ "vue-tsc": "^2.2.8"
312+ },
313+ "scripts": {
314+ "build": "pnpm exec vite build",
315+ "dev": "pnpm exec vite",
316+ "preview": "pnpm exec vite preview",
317+ "typecheck": "pnpm exec vue-tsc --noEmit -p tsconfig.json"
318+ }
319+}
320diff --git a/apps/conductor-ui/src/App.vue b/apps/conductor-ui/src/App.vue
321new file mode 100644
322index 0000000000000000000000000000000000000000..543cfdbab78c08f07ccec245e955b5d84ad0f7ab
323--- /dev/null
324+++ b/apps/conductor-ui/src/App.vue
325@@ -0,0 +1,7 @@
326+<template>
327+ <RouterView />
328+</template>
329+
330+<script setup lang="ts">
331+import { RouterView } from "vue-router";
332+</script>
333diff --git a/apps/conductor-ui/src/env.d.ts b/apps/conductor-ui/src/env.d.ts
334new file mode 100644
335index 0000000000000000000000000000000000000000..158d2b152e84c89862547e85a0a3de20724e046f
336--- /dev/null
337+++ b/apps/conductor-ui/src/env.d.ts
338@@ -0,0 +1,8 @@
339+/// <reference types="vite/client" />
340+
341+declare module "*.vue" {
342+ import type { DefineComponent } from "vue";
343+
344+ const component: DefineComponent<Record<string, never>, Record<string, never>, unknown>;
345+ export default component;
346+}
347diff --git a/apps/conductor-ui/src/features/control/views/ControlWorkspaceView.vue b/apps/conductor-ui/src/features/control/views/ControlWorkspaceView.vue
348new file mode 100644
349index 0000000000000000000000000000000000000000..9a9b6fab3082946f85dd8657187daa7a5c6d68f9
350--- /dev/null
351+++ b/apps/conductor-ui/src/features/control/views/ControlWorkspaceView.vue
352@@ -0,0 +1,98 @@
353+<template>
354+ <main class="shell">
355+ <section class="hero panel">
356+ <div>
357+ <p class="eyebrow">BAA Conductor / Web Workspace</p>
358+ <h1>Control Workspace Shell</h1>
359+ <p class="lede">
360+ `/app` 已经作为正式工作台入口接入 `conductor-daemon`。本页当前只提供布局壳、路由壳和
361+ T-S071/T-S072 的明确占位,不把插件调试页误当成正式入口。
362+ </p>
363+ </div>
364+ <div class="hero-meta">
365+ <div class="pill pill-live">Vue 3</div>
366+ <div class="pill">/app</div>
367+ <div class="pill pill-muted">Session Pending</div>
368+ </div>
369+ </section>
370+
371+ <section class="workspace-grid">
372+ <aside class="panel rail">
373+ <div class="rail-head">
374+ <p class="section-label">Workspace</p>
375+ <p class="rail-title">Control</p>
376+ </div>
377+
378+ <nav class="nav-list" aria-label="workspace sections">
379+ <a class="nav-item nav-item-active" href="/app/control" @click.prevent>Overview</a>
380+ <span class="nav-item nav-item-disabled">Browser</span>
381+ <span class="nav-item nav-item-disabled">Renewal</span>
382+ <span class="nav-item nav-item-disabled">Tasks & Runs</span>
383+ <span class="nav-item nav-item-disabled">Codex</span>
384+ <span class="nav-item nav-item-disabled">Artifacts</span>
385+ </nav>
386+
387+ <div class="status-box">
388+ <p class="section-label">Runtime</p>
389+ <p class="status-title">Session 未接入</p>
390+ <p class="status-copy">
391+ T-S071 将在这里接入浏览器会话、登录页和角色守卫。当前壳页面明确展示为未认证状态。
392+ </p>
393+ </div>
394+ </aside>
395+
396+ <section class="content">
397+ <div class="cards">
398+ <article class="panel card">
399+ <p class="section-label">T-S070</p>
400+ <h2>Shell + Hosting</h2>
401+ <p>
402+ Vue 3 应用壳、`/app` history fallback 和前端构建产物托管已经作为本任务交付目标。
403+ </p>
404+ </article>
405+
406+ <article class="panel card">
407+ <p class="section-label">T-S071</p>
408+ <h2>Session Auth</h2>
409+ <p>
410+ 登录页、`browser_session`、`readonly` 角色和 `/v1/ui/session/*` 接口将在下一任务接入。
411+ </p>
412+ </article>
413+
414+ <article class="panel card">
415+ <p class="section-label">T-S072</p>
416+ <h2>Control Workspace</h2>
417+ <p>
418+ 现有 `/v1/system/state`、`/v1/browser`、`/v1/renewal/*`、`/v1/tasks*` 等正式控制面会在下一阶段连进来。
419+ </p>
420+ </article>
421+ </div>
422+
423+ <section class="panel roadmap">
424+ <div class="roadmap-head">
425+ <div>
426+ <p class="section-label">Phase Map</p>
427+ <h2>当前交付边界</h2>
428+ </div>
429+ <span class="roadmap-tag">Task 70</span>
430+ </div>
431+
432+ <div class="roadmap-list">
433+ <div class="roadmap-row">
434+ <strong>已完成本页目标</strong>
435+ <span>Vue 3 脚手架、`/app` 路由壳、同源静态托管</span>
436+ </div>
437+ <div class="roadmap-row">
438+ <strong>下一步</strong>
439+ <span>UI session、登录页、受保护的 `/app/control`</span>
440+ </div>
441+ <div class="roadmap-row">
442+ <strong>再下一步</strong>
443+ <span>正式 Control 面板、控制动作、结构化详情抽屉</span>
444+ </div>
445+ </div>
446+ </section>
447+ </section>
448+ </section>
449+ </main>
450+</template>
451diff --git a/apps/conductor-ui/src/features/control/views/index.ts b/apps/conductor-ui/src/features/control/views/index.ts
452new file mode 100644
453index 0000000000000000000000000000000000000000..4ecb99fe82782af27a4a3d86d280a21e24284a27
454--- /dev/null
455+++ b/apps/conductor-ui/src/features/control/views/index.ts
456@@ -0,0 +1 @@
457+export { default as ControlWorkspaceView } from "./ControlWorkspaceView.vue";
458diff --git a/apps/conductor-ui/src/features/system/views/NotFoundView.vue b/apps/conductor-ui/src/features/system/views/NotFoundView.vue
459new file mode 100644
460index 0000000000000000000000000000000000000000..c2a267dc9f08f7baf81178e3bedecca3388b7d32
461--- /dev/null
462+++ b/apps/conductor-ui/src/features/system/views/NotFoundView.vue
463@@ -0,0 +1,12 @@
464+<template>
465+ <main class="shell not-found-shell">
466+ <section class="panel not-found-panel">
467+ <p class="eyebrow">BAA Conductor / App Route</p>
468+ <h1>Unknown Workspace Route</h1>
469+ <p class="lede">
470+ 当前 `/app` 仅接入了最小工作台壳。未知路径会保留在同一个前端 shell 内,避免落回 API 404。
471+ </p>
472+ <a class="back-link" href="/app/control">返回 Control</a>
473+ </section>
474+ </main>
475+</template>
476diff --git a/apps/conductor-ui/src/features/system/views/index.ts b/apps/conductor-ui/src/features/system/views/index.ts
477new file mode 100644
478index 0000000000000000000000000000000000000000..2655ecbe0e58233afa1d872fc67094c62a1fd79a
479--- /dev/null
480+++ b/apps/conductor-ui/src/features/system/views/index.ts
481@@ -0,0 +1 @@
482+export { default as NotFoundView } from "./NotFoundView.vue";
483diff --git a/apps/conductor-ui/src/main.ts b/apps/conductor-ui/src/main.ts
484new file mode 100644
485index 0000000000000000000000000000000000000000..56bb71b83faa00bf3516a8e44cd0d9c422b4b421
486--- /dev/null
487+++ b/apps/conductor-ui/src/main.ts
488@@ -0,0 +1,7 @@
489+import { createApp } from "vue";
490+
491+import App from "./App.vue";
492+import { router } from "./routes";
493+import "./styles/base.css";
494+
495+createApp(App).use(router).mount("#app");
496diff --git a/apps/conductor-ui/src/routes/index.ts b/apps/conductor-ui/src/routes/index.ts
497new file mode 100644
498index 0000000000000000000000000000000000000000..adf5337245c56805cc501d895d4a9bb73d6f0864
499--- /dev/null
500+++ b/apps/conductor-ui/src/routes/index.ts
501@@ -0,0 +1,25 @@
502+import { createRouter, createWebHistory } from "vue-router";
503+
504+import { ControlWorkspaceView } from "@/features/control/views";
505+import { NotFoundView } from "@/features/system/views";
506+
507+export const router = createRouter({
508+ history: createWebHistory("/app/"),
509+ routes: [
510+ {
511+ path: "/",
512+ redirect: "/control"
513+ },
514+ {
515+ path: "/control",
516+ component: ControlWorkspaceView
517+ },
518+ {
519+ path: "/:pathMatch(.*)*",
520+ component: NotFoundView
521+ }
522+ ],
523+ scrollBehavior() {
524+ return { left: 0, top: 0 };
525+ }
526+});
527diff --git a/apps/conductor-ui/src/styles/base.css b/apps/conductor-ui/src/styles/base.css
528new file mode 100644
529index 0000000000000000000000000000000000000000..ddc6d43bb42dda22730d569de47127f088e03862
530--- /dev/null
531+++ b/apps/conductor-ui/src/styles/base.css
532@@ -0,0 +1,310 @@
533+:root {
534+ color-scheme: light;
535+ --bg: #efe5d1;
536+ --bg-deep: #dcc7a2;
537+ --panel: rgba(255, 250, 241, 0.84);
538+ --panel-strong: rgba(255, 247, 233, 0.95);
539+ --ink: #1d1811;
540+ --muted: #645847;
541+ --line: rgba(29, 24, 17, 0.12);
542+ --accent: #0a6c74;
543+ --accent-soft: rgba(10, 108, 116, 0.12);
544+ --warn: #a0551b;
545+ --shadow: 0 24px 60px rgba(78, 54, 28, 0.14);
546+}
547+
548+* {
549+ box-sizing: border-box;
550+}
551+
552+html,
553+body,
554+#app {
555+ min-height: 100%;
556+}
557+
558+body {
559+ margin: 0;
560+ color: var(--ink);
561+ background:
562+ radial-gradient(circle at top left, rgba(255, 255, 255, 0.55), transparent 34%),
563+ radial-gradient(circle at bottom right, rgba(10, 108, 116, 0.12), transparent 26%),
564+ linear-gradient(135deg, var(--bg), var(--bg-deep));
565+ font-family: "Iowan Old Style", "Palatino Linotype", "Book Antiqua", Georgia, serif;
566+}
567+
568+body::before {
569+ content: "";
570+ position: fixed;
571+ inset: 0;
572+ pointer-events: none;
573+ background-image:
574+ linear-gradient(rgba(29, 24, 17, 0.03) 1px, transparent 1px),
575+ linear-gradient(90deg, rgba(29, 24, 17, 0.03) 1px, transparent 1px);
576+ background-size: 28px 28px;
577+ mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.78), transparent 88%);
578+}
579+
580+a {
581+ color: inherit;
582+}
583+
584+.shell {
585+ position: relative;
586+ width: min(1220px, calc(100% - 32px));
587+ margin: 0 auto;
588+ padding: 32px 0 48px;
589+}
590+
591+.panel {
592+ border: 1px solid var(--line);
593+ border-radius: 24px;
594+ background: var(--panel);
595+ box-shadow: var(--shadow);
596+ backdrop-filter: blur(14px);
597+}
598+
599+.hero {
600+ display: flex;
601+ justify-content: space-between;
602+ gap: 20px;
603+ padding: 28px;
604+}
605+
606+.eyebrow,
607+.section-label,
608+.pill,
609+.status-copy,
610+.nav-item,
611+.roadmap-tag {
612+ font-family: "IBM Plex Mono", "SFMono-Regular", Menlo, monospace;
613+}
614+
615+.eyebrow,
616+.section-label {
617+ margin: 0 0 10px;
618+ font-size: 12px;
619+ letter-spacing: 0.16em;
620+ text-transform: uppercase;
621+ color: var(--muted);
622+}
623+
624+h1,
625+h2,
626+p {
627+ margin: 0;
628+}
629+
630+h1 {
631+ max-width: 11ch;
632+ font-size: clamp(34px, 5vw, 60px);
633+ line-height: 0.96;
634+}
635+
636+h2 {
637+ font-size: 24px;
638+ line-height: 1.1;
639+}
640+
641+.lede {
642+ margin-top: 16px;
643+ max-width: 58ch;
644+ color: var(--muted);
645+ font-size: 18px;
646+ line-height: 1.6;
647+}
648+
649+.hero-meta {
650+ display: flex;
651+ flex-wrap: wrap;
652+ gap: 10px;
653+ align-content: flex-start;
654+ justify-content: flex-end;
655+}
656+
657+.pill {
658+ display: inline-flex;
659+ align-items: center;
660+ min-height: 40px;
661+ padding: 0 14px;
662+ border-radius: 999px;
663+ border: 1px solid var(--line);
664+ background: var(--panel-strong);
665+ font-size: 13px;
666+}
667+
668+.pill-live {
669+ color: var(--accent);
670+}
671+
672+.pill-muted {
673+ color: var(--warn);
674+}
675+
676+.workspace-grid {
677+ display: grid;
678+ grid-template-columns: 280px minmax(0, 1fr);
679+ gap: 18px;
680+ margin-top: 18px;
681+}
682+
683+.rail {
684+ display: flex;
685+ flex-direction: column;
686+ gap: 18px;
687+ padding: 22px 18px;
688+}
689+
690+.rail-head {
691+ padding-bottom: 4px;
692+ border-bottom: 1px solid var(--line);
693+}
694+
695+.rail-title,
696+.status-title {
697+ font-size: 28px;
698+ line-height: 1;
699+}
700+
701+.nav-list {
702+ display: flex;
703+ flex-direction: column;
704+ gap: 8px;
705+}
706+
707+.nav-item {
708+ display: block;
709+ padding: 12px 14px;
710+ border-radius: 16px;
711+ border: 1px solid var(--line);
712+ background: rgba(255, 255, 255, 0.36);
713+ text-decoration: none;
714+ font-size: 13px;
715+}
716+
717+.nav-item-active {
718+ background: linear-gradient(160deg, var(--accent-soft), rgba(255, 255, 255, 0.72));
719+ color: var(--accent);
720+}
721+
722+.nav-item-disabled {
723+ opacity: 0.55;
724+}
725+
726+.status-box {
727+ display: grid;
728+ gap: 10px;
729+ padding: 18px;
730+ border-radius: 20px;
731+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.52), rgba(255, 247, 233, 0.92));
732+ border: 1px solid var(--line);
733+}
734+
735+.status-copy {
736+ color: var(--muted);
737+ font-size: 13px;
738+ line-height: 1.7;
739+}
740+
741+.content {
742+ display: grid;
743+ gap: 18px;
744+}
745+
746+.cards {
747+ display: grid;
748+ grid-template-columns: repeat(3, minmax(0, 1fr));
749+ gap: 18px;
750+}
751+
752+.card {
753+ display: grid;
754+ gap: 14px;
755+ padding: 22px;
756+}
757+
758+.card p:last-child,
759+.roadmap-row span {
760+ color: var(--muted);
761+ line-height: 1.65;
762+}
763+
764+.roadmap {
765+ padding: 24px;
766+}
767+
768+.roadmap-head {
769+ display: flex;
770+ justify-content: space-between;
771+ gap: 16px;
772+ align-items: flex-start;
773+ padding-bottom: 18px;
774+ border-bottom: 1px solid var(--line);
775+}
776+
777+.roadmap-tag,
778+.back-link {
779+ display: inline-flex;
780+ align-items: center;
781+ justify-content: center;
782+ min-height: 38px;
783+ padding: 0 14px;
784+ border: 1px solid var(--line);
785+ border-radius: 999px;
786+ background: var(--panel-strong);
787+ text-decoration: none;
788+}
789+
790+.roadmap-list {
791+ display: grid;
792+ gap: 14px;
793+ margin-top: 18px;
794+}
795+
796+.roadmap-row {
797+ display: grid;
798+ grid-template-columns: minmax(0, 220px) minmax(0, 1fr);
799+ gap: 16px;
800+ padding: 14px 0;
801+ border-bottom: 1px solid rgba(29, 24, 17, 0.08);
802+}
803+
804+.not-found-shell {
805+ display: grid;
806+ place-items: center;
807+ min-height: 100vh;
808+}
809+
810+.not-found-panel {
811+ width: min(720px, calc(100% - 32px));
812+ padding: 28px;
813+}
814+
815+.back-link {
816+ margin-top: 20px;
817+ width: fit-content;
818+}
819+
820+@media (max-width: 960px) {
821+ .workspace-grid,
822+ .cards,
823+ .roadmap-row {
824+ grid-template-columns: 1fr;
825+ }
826+}
827+
828+@media (max-width: 720px) {
829+ .shell {
830+ width: min(100% - 20px, 1220px);
831+ padding-top: 20px;
832+ padding-bottom: 28px;
833+ }
834+
835+ .hero {
836+ flex-direction: column;
837+ }
838+
839+ .hero-meta {
840+ justify-content: flex-start;
841+ }
842+}
843diff --git a/apps/conductor-ui/tsconfig.json b/apps/conductor-ui/tsconfig.json
844new file mode 100644
845index 0000000000000000000000000000000000000000..2c79f1ec147b91d380d62e51ac5c7d1551ca2540
846--- /dev/null
847+++ b/apps/conductor-ui/tsconfig.json
848@@ -0,0 +1,20 @@
849+{
850+ "extends": "../../tsconfig.base.json",
851+ "compilerOptions": {
852+ "baseUrl": ".",
853+ "target": "ES2022",
854+ "module": "ESNext",
855+ "moduleResolution": "Bundler",
856+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
857+ "paths": {
858+ "@/*": ["src/*"]
859+ },
860+ "types": ["vite/client"],
861+ "jsx": "preserve",
862+ "isolatedModules": true,
863+ "useDefineForClassFields": true,
864+ "allowImportingTsExtensions": true,
865+ "noEmit": true
866+ },
867+ "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue", "vite.config.ts"]
868+}
869diff --git a/apps/conductor-ui/vite.config.ts b/apps/conductor-ui/vite.config.ts
870new file mode 100644
871index 0000000000000000000000000000000000000000..dddbebbebe46a78c5740a0702dc324d015fa9815
872--- /dev/null
873+++ b/apps/conductor-ui/vite.config.ts
874@@ -0,0 +1,28 @@
875+import { resolve } from "node:path";
876+import { defineConfig, loadEnv } from "vite";
877+import vue from "@vitejs/plugin-vue";
878+
879+export default defineConfig(({ mode }) => {
880+ const env = loadEnv(mode, process.cwd(), "");
881+ const apiTarget = env.BAA_UI_API_PROXY_TARGET || "http://100.71.210.78:4317";
882+
883+ return {
884+ base: "/app/",
885+ plugins: [vue()],
886+ resolve: {
887+ alias: {
888+ "@": resolve(__dirname, "src")
889+ }
890+ },
891+ server: {
892+ proxy: {
893+ "/describe": apiTarget,
894+ "/health": apiTarget,
895+ "/healthz": apiTarget,
896+ "/readyz": apiTarget,
897+ "/rolez": apiTarget,
898+ "/v1": apiTarget
899+ }
900+ }
901+ };
902+});
903diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
904index c536902b68d285e0c875ffb9cf4c1f3b1adefc86..e325f2aff09cfe466b7b2c54ef9f6fcc3c2f7081 100644
905--- a/pnpm-lock.yaml
906+++ b/pnpm-lock.yaml
907@@ -31,6 +31,28 @@ importers:
908 specifier: workspace:*
909 version: link:../../packages/host-ops
910
911+ apps/conductor-ui:
912+ dependencies:
913+ vue:
914+ specifier: ^3.5.13
915+ version: 3.5.31(typescript@5.9.3)
916+ vue-router:
917+ specifier: ^4.5.1
918+ version: 4.6.4(vue@3.5.31(typescript@5.9.3))
919+ devDependencies:
920+ '@types/node':
921+ specifier: ^22.13.13
922+ version: 22.19.15
923+ '@vitejs/plugin-vue':
924+ specifier: ^5.2.3
925+ version: 5.2.4(vite@6.4.1(@types/node@22.19.15))(vue@3.5.31(typescript@5.9.3))
926+ vite:
927+ specifier: ^6.2.2
928+ version: 6.4.1(@types/node@22.19.15)
929+ vue-tsc:
930+ specifier: ^2.2.8
931+ version: 2.2.12(typescript@5.9.3)
932+
933 apps/status-api: {}
934
935 apps/worker-runner: {}
936@@ -63,11 +85,947 @@ importers:
937
938 packages:
939
940+ '@babel/helper-string-parser@7.27.1':
941+ resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
942+ engines: {node: '>=6.9.0'}
943+
944+ '@babel/helper-validator-identifier@7.28.5':
945+ resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
946+ engines: {node: '>=6.9.0'}
947+
948+ '@babel/parser@7.29.2':
949+ resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==}
950+ engines: {node: '>=6.0.0'}
951+ hasBin: true
952+
953+ '@babel/types@7.29.0':
954+ resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
955+ engines: {node: '>=6.9.0'}
956+
957+ '@esbuild/aix-ppc64@0.25.12':
958+ resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
959+ engines: {node: '>=18'}
960+ cpu: [ppc64]
961+ os: [aix]
962+
963+ '@esbuild/android-arm64@0.25.12':
964+ resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
965+ engines: {node: '>=18'}
966+ cpu: [arm64]
967+ os: [android]
968+
969+ '@esbuild/android-arm@0.25.12':
970+ resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
971+ engines: {node: '>=18'}
972+ cpu: [arm]
973+ os: [android]
974+
975+ '@esbuild/android-x64@0.25.12':
976+ resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
977+ engines: {node: '>=18'}
978+ cpu: [x64]
979+ os: [android]
980+
981+ '@esbuild/darwin-arm64@0.25.12':
982+ resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
983+ engines: {node: '>=18'}
984+ cpu: [arm64]
985+ os: [darwin]
986+
987+ '@esbuild/darwin-x64@0.25.12':
988+ resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
989+ engines: {node: '>=18'}
990+ cpu: [x64]
991+ os: [darwin]
992+
993+ '@esbuild/freebsd-arm64@0.25.12':
994+ resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
995+ engines: {node: '>=18'}
996+ cpu: [arm64]
997+ os: [freebsd]
998+
999+ '@esbuild/freebsd-x64@0.25.12':
1000+ resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
1001+ engines: {node: '>=18'}
1002+ cpu: [x64]
1003+ os: [freebsd]
1004+
1005+ '@esbuild/linux-arm64@0.25.12':
1006+ resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
1007+ engines: {node: '>=18'}
1008+ cpu: [arm64]
1009+ os: [linux]
1010+
1011+ '@esbuild/linux-arm@0.25.12':
1012+ resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
1013+ engines: {node: '>=18'}
1014+ cpu: [arm]
1015+ os: [linux]
1016+
1017+ '@esbuild/linux-ia32@0.25.12':
1018+ resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
1019+ engines: {node: '>=18'}
1020+ cpu: [ia32]
1021+ os: [linux]
1022+
1023+ '@esbuild/linux-loong64@0.25.12':
1024+ resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
1025+ engines: {node: '>=18'}
1026+ cpu: [loong64]
1027+ os: [linux]
1028+
1029+ '@esbuild/linux-mips64el@0.25.12':
1030+ resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
1031+ engines: {node: '>=18'}
1032+ cpu: [mips64el]
1033+ os: [linux]
1034+
1035+ '@esbuild/linux-ppc64@0.25.12':
1036+ resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
1037+ engines: {node: '>=18'}
1038+ cpu: [ppc64]
1039+ os: [linux]
1040+
1041+ '@esbuild/linux-riscv64@0.25.12':
1042+ resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
1043+ engines: {node: '>=18'}
1044+ cpu: [riscv64]
1045+ os: [linux]
1046+
1047+ '@esbuild/linux-s390x@0.25.12':
1048+ resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
1049+ engines: {node: '>=18'}
1050+ cpu: [s390x]
1051+ os: [linux]
1052+
1053+ '@esbuild/linux-x64@0.25.12':
1054+ resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
1055+ engines: {node: '>=18'}
1056+ cpu: [x64]
1057+ os: [linux]
1058+
1059+ '@esbuild/netbsd-arm64@0.25.12':
1060+ resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
1061+ engines: {node: '>=18'}
1062+ cpu: [arm64]
1063+ os: [netbsd]
1064+
1065+ '@esbuild/netbsd-x64@0.25.12':
1066+ resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
1067+ engines: {node: '>=18'}
1068+ cpu: [x64]
1069+ os: [netbsd]
1070+
1071+ '@esbuild/openbsd-arm64@0.25.12':
1072+ resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
1073+ engines: {node: '>=18'}
1074+ cpu: [arm64]
1075+ os: [openbsd]
1076+
1077+ '@esbuild/openbsd-x64@0.25.12':
1078+ resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
1079+ engines: {node: '>=18'}
1080+ cpu: [x64]
1081+ os: [openbsd]
1082+
1083+ '@esbuild/openharmony-arm64@0.25.12':
1084+ resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
1085+ engines: {node: '>=18'}
1086+ cpu: [arm64]
1087+ os: [openharmony]
1088+
1089+ '@esbuild/sunos-x64@0.25.12':
1090+ resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
1091+ engines: {node: '>=18'}
1092+ cpu: [x64]
1093+ os: [sunos]
1094+
1095+ '@esbuild/win32-arm64@0.25.12':
1096+ resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
1097+ engines: {node: '>=18'}
1098+ cpu: [arm64]
1099+ os: [win32]
1100+
1101+ '@esbuild/win32-ia32@0.25.12':
1102+ resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
1103+ engines: {node: '>=18'}
1104+ cpu: [ia32]
1105+ os: [win32]
1106+
1107+ '@esbuild/win32-x64@0.25.12':
1108+ resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
1109+ engines: {node: '>=18'}
1110+ cpu: [x64]
1111+ os: [win32]
1112+
1113+ '@jridgewell/sourcemap-codec@1.5.5':
1114+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
1115+
1116+ '@rollup/rollup-android-arm-eabi@4.60.1':
1117+ resolution: {integrity: sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==}
1118+ cpu: [arm]
1119+ os: [android]
1120+
1121+ '@rollup/rollup-android-arm64@4.60.1':
1122+ resolution: {integrity: sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==}
1123+ cpu: [arm64]
1124+ os: [android]
1125+
1126+ '@rollup/rollup-darwin-arm64@4.60.1':
1127+ resolution: {integrity: sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==}
1128+ cpu: [arm64]
1129+ os: [darwin]
1130+
1131+ '@rollup/rollup-darwin-x64@4.60.1':
1132+ resolution: {integrity: sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==}
1133+ cpu: [x64]
1134+ os: [darwin]
1135+
1136+ '@rollup/rollup-freebsd-arm64@4.60.1':
1137+ resolution: {integrity: sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==}
1138+ cpu: [arm64]
1139+ os: [freebsd]
1140+
1141+ '@rollup/rollup-freebsd-x64@4.60.1':
1142+ resolution: {integrity: sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==}
1143+ cpu: [x64]
1144+ os: [freebsd]
1145+
1146+ '@rollup/rollup-linux-arm-gnueabihf@4.60.1':
1147+ resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==}
1148+ cpu: [arm]
1149+ os: [linux]
1150+
1151+ '@rollup/rollup-linux-arm-musleabihf@4.60.1':
1152+ resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==}
1153+ cpu: [arm]
1154+ os: [linux]
1155+
1156+ '@rollup/rollup-linux-arm64-gnu@4.60.1':
1157+ resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==}
1158+ cpu: [arm64]
1159+ os: [linux]
1160+
1161+ '@rollup/rollup-linux-arm64-musl@4.60.1':
1162+ resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==}
1163+ cpu: [arm64]
1164+ os: [linux]
1165+
1166+ '@rollup/rollup-linux-loong64-gnu@4.60.1':
1167+ resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==}
1168+ cpu: [loong64]
1169+ os: [linux]
1170+
1171+ '@rollup/rollup-linux-loong64-musl@4.60.1':
1172+ resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==}
1173+ cpu: [loong64]
1174+ os: [linux]
1175+
1176+ '@rollup/rollup-linux-ppc64-gnu@4.60.1':
1177+ resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==}
1178+ cpu: [ppc64]
1179+ os: [linux]
1180+
1181+ '@rollup/rollup-linux-ppc64-musl@4.60.1':
1182+ resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==}
1183+ cpu: [ppc64]
1184+ os: [linux]
1185+
1186+ '@rollup/rollup-linux-riscv64-gnu@4.60.1':
1187+ resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==}
1188+ cpu: [riscv64]
1189+ os: [linux]
1190+
1191+ '@rollup/rollup-linux-riscv64-musl@4.60.1':
1192+ resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==}
1193+ cpu: [riscv64]
1194+ os: [linux]
1195+
1196+ '@rollup/rollup-linux-s390x-gnu@4.60.1':
1197+ resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==}
1198+ cpu: [s390x]
1199+ os: [linux]
1200+
1201+ '@rollup/rollup-linux-x64-gnu@4.60.1':
1202+ resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==}
1203+ cpu: [x64]
1204+ os: [linux]
1205+
1206+ '@rollup/rollup-linux-x64-musl@4.60.1':
1207+ resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==}
1208+ cpu: [x64]
1209+ os: [linux]
1210+
1211+ '@rollup/rollup-openbsd-x64@4.60.1':
1212+ resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==}
1213+ cpu: [x64]
1214+ os: [openbsd]
1215+
1216+ '@rollup/rollup-openharmony-arm64@4.60.1':
1217+ resolution: {integrity: sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==}
1218+ cpu: [arm64]
1219+ os: [openharmony]
1220+
1221+ '@rollup/rollup-win32-arm64-msvc@4.60.1':
1222+ resolution: {integrity: sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==}
1223+ cpu: [arm64]
1224+ os: [win32]
1225+
1226+ '@rollup/rollup-win32-ia32-msvc@4.60.1':
1227+ resolution: {integrity: sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==}
1228+ cpu: [ia32]
1229+ os: [win32]
1230+
1231+ '@rollup/rollup-win32-x64-gnu@4.60.1':
1232+ resolution: {integrity: sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==}
1233+ cpu: [x64]
1234+ os: [win32]
1235+
1236+ '@rollup/rollup-win32-x64-msvc@4.60.1':
1237+ resolution: {integrity: sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==}
1238+ cpu: [x64]
1239+ os: [win32]
1240+
1241+ '@types/estree@1.0.8':
1242+ resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
1243+
1244+ '@types/node@22.19.15':
1245+ resolution: {integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==}
1246+
1247+ '@vitejs/plugin-vue@5.2.4':
1248+ resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==}
1249+ engines: {node: ^18.0.0 || >=20.0.0}
1250+ peerDependencies:
1251+ vite: ^5.0.0 || ^6.0.0
1252+ vue: ^3.2.25
1253+
1254+ '@volar/language-core@2.4.15':
1255+ resolution: {integrity: sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==}
1256+
1257+ '@volar/source-map@2.4.15':
1258+ resolution: {integrity: sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==}
1259+
1260+ '@volar/typescript@2.4.15':
1261+ resolution: {integrity: sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==}
1262+
1263+ '@vue/compiler-core@3.5.31':
1264+ resolution: {integrity: sha512-k/ueL14aNIEy5Onf0OVzR8kiqF/WThgLdFhxwa4e/KF/0qe38IwIdofoSWBTvvxQOesaz6riAFAUaYjoF9fLLQ==}
1265+
1266+ '@vue/compiler-dom@3.5.31':
1267+ resolution: {integrity: sha512-BMY/ozS/xxjYqRFL+tKdRpATJYDTTgWSo0+AJvJNg4ig+Hgb0dOsHPXvloHQ5hmlivUqw1Yt2pPIqp4e0v1GUw==}
1268+
1269+ '@vue/compiler-sfc@3.5.31':
1270+ resolution: {integrity: sha512-M8wpPgR9UJ8MiRGjppvx9uWJfLV7A/T+/rL8s/y3QG3u0c2/YZgff3d6SuimKRIhcYnWg5fTfDMlz2E6seUW8Q==}
1271+
1272+ '@vue/compiler-ssr@3.5.31':
1273+ resolution: {integrity: sha512-h0xIMxrt/LHOvJKMri+vdYT92BrK3HFLtDqq9Pr/lVVfE4IyKZKvWf0vJFW10Yr6nX02OR4MkJwI0c1HDa1hog==}
1274+
1275+ '@vue/compiler-vue2@2.7.16':
1276+ resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==}
1277+
1278+ '@vue/devtools-api@6.6.4':
1279+ resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
1280+
1281+ '@vue/language-core@2.2.12':
1282+ resolution: {integrity: sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==}
1283+ peerDependencies:
1284+ typescript: '*'
1285+ peerDependenciesMeta:
1286+ typescript:
1287+ optional: true
1288+
1289+ '@vue/reactivity@3.5.31':
1290+ resolution: {integrity: sha512-DtKXxk9E/KuVvt8VxWu+6Luc9I9ETNcqR1T1oW1gf02nXaZ1kuAx58oVu7uX9XxJR0iJCro6fqBLw9oSBELo5g==}
1291+
1292+ '@vue/runtime-core@3.5.31':
1293+ resolution: {integrity: sha512-AZPmIHXEAyhpkmN7aWlqjSfYynmkWlluDNPHMCZKFHH+lLtxP/30UJmoVhXmbDoP1Ng0jG0fyY2zCj1PnSSA6Q==}
1294+
1295+ '@vue/runtime-dom@3.5.31':
1296+ resolution: {integrity: sha512-xQJsNRmGPeDCJq/u813tyonNgWBFjzfVkBwDREdEWndBnGdHLHgkwNBQxLtg4zDrzKTEcnikUy1UUNecb3lJ6g==}
1297+
1298+ '@vue/server-renderer@3.5.31':
1299+ resolution: {integrity: sha512-GJuwRvMcdZX/CriUnyIIOGkx3rMV3H6sOu0JhdKbduaeCji6zb60iOGMY7tFoN24NfsUYoFBhshZtGxGpxO4iA==}
1300+ peerDependencies:
1301+ vue: 3.5.31
1302+
1303+ '@vue/shared@3.5.31':
1304+ resolution: {integrity: sha512-nBxuiuS9Lj5bPkPbWogPUnjxxWpkRniX7e5UBQDWl6Fsf4roq9wwV+cR7ezQ4zXswNvPIlsdj1slcLB7XCsRAw==}
1305+
1306+ alien-signals@1.0.13:
1307+ resolution: {integrity: sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==}
1308+
1309+ balanced-match@1.0.2:
1310+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
1311+
1312+ brace-expansion@2.0.3:
1313+ resolution: {integrity: sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==}
1314+
1315+ csstype@3.2.3:
1316+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
1317+
1318+ de-indent@1.0.2:
1319+ resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
1320+
1321+ entities@7.0.1:
1322+ resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
1323+ engines: {node: '>=0.12'}
1324+
1325+ esbuild@0.25.12:
1326+ resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
1327+ engines: {node: '>=18'}
1328+ hasBin: true
1329+
1330+ estree-walker@2.0.2:
1331+ resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
1332+
1333+ fdir@6.5.0:
1334+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
1335+ engines: {node: '>=12.0.0'}
1336+ peerDependencies:
1337+ picomatch: ^3 || ^4
1338+ peerDependenciesMeta:
1339+ picomatch:
1340+ optional: true
1341+
1342+ fsevents@2.3.3:
1343+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
1344+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
1345+ os: [darwin]
1346+
1347+ he@1.2.0:
1348+ resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
1349+ hasBin: true
1350+
1351+ magic-string@0.30.21:
1352+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
1353+
1354+ minimatch@9.0.9:
1355+ resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==}
1356+ engines: {node: '>=16 || 14 >=14.17'}
1357+
1358+ muggle-string@0.4.1:
1359+ resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
1360+
1361+ nanoid@3.3.11:
1362+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
1363+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
1364+ hasBin: true
1365+
1366+ path-browserify@1.0.1:
1367+ resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
1368+
1369+ picocolors@1.1.1:
1370+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
1371+
1372+ picomatch@4.0.4:
1373+ resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
1374+ engines: {node: '>=12'}
1375+
1376+ postcss@8.5.8:
1377+ resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
1378+ engines: {node: ^10 || ^12 || >=14}
1379+
1380+ rollup@4.60.1:
1381+ resolution: {integrity: sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==}
1382+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
1383+ hasBin: true
1384+
1385+ source-map-js@1.2.1:
1386+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
1387+ engines: {node: '>=0.10.0'}
1388+
1389+ tinyglobby@0.2.15:
1390+ resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
1391+ engines: {node: '>=12.0.0'}
1392+
1393 typescript@5.9.3:
1394 resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
1395 engines: {node: '>=14.17'}
1396 hasBin: true
1397
1398+ undici-types@6.21.0:
1399+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
1400+
1401+ vite@6.4.1:
1402+ resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==}
1403+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
1404+ hasBin: true
1405+ peerDependencies:
1406+ '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
1407+ jiti: '>=1.21.0'
1408+ less: '*'
1409+ lightningcss: ^1.21.0
1410+ sass: '*'
1411+ sass-embedded: '*'
1412+ stylus: '*'
1413+ sugarss: '*'
1414+ terser: ^5.16.0
1415+ tsx: ^4.8.1
1416+ yaml: ^2.4.2
1417+ peerDependenciesMeta:
1418+ '@types/node':
1419+ optional: true
1420+ jiti:
1421+ optional: true
1422+ less:
1423+ optional: true
1424+ lightningcss:
1425+ optional: true
1426+ sass:
1427+ optional: true
1428+ sass-embedded:
1429+ optional: true
1430+ stylus:
1431+ optional: true
1432+ sugarss:
1433+ optional: true
1434+ terser:
1435+ optional: true
1436+ tsx:
1437+ optional: true
1438+ yaml:
1439+ optional: true
1440+
1441+ vscode-uri@3.1.0:
1442+ resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
1443+
1444+ vue-router@4.6.4:
1445+ resolution: {integrity: sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==}
1446+ peerDependencies:
1447+ vue: ^3.5.0
1448+
1449+ vue-tsc@2.2.12:
1450+ resolution: {integrity: sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==}
1451+ hasBin: true
1452+ peerDependencies:
1453+ typescript: '>=5.0.0'
1454+
1455+ vue@3.5.31:
1456+ resolution: {integrity: sha512-iV/sU9SzOlmA/0tygSmjkEN6Jbs3nPoIPFhCMLD2STrjgOU8DX7ZtzMhg4ahVwf5Rp9KoFzcXeB1ZrVbLBp5/Q==}
1457+ peerDependencies:
1458+ typescript: '*'
1459+ peerDependenciesMeta:
1460+ typescript:
1461+ optional: true
1462+
1463 snapshots:
1464
1465+ '@babel/helper-string-parser@7.27.1': {}
1466+
1467+ '@babel/helper-validator-identifier@7.28.5': {}
1468+
1469+ '@babel/parser@7.29.2':
1470+ dependencies:
1471+ '@babel/types': 7.29.0
1472+
1473+ '@babel/types@7.29.0':
1474+ dependencies:
1475+ '@babel/helper-string-parser': 7.27.1
1476+ '@babel/helper-validator-identifier': 7.28.5
1477+
1478+ '@esbuild/aix-ppc64@0.25.12':
1479+ optional: true
1480+
1481+ '@esbuild/android-arm64@0.25.12':
1482+ optional: true
1483+
1484+ '@esbuild/android-arm@0.25.12':
1485+ optional: true
1486+
1487+ '@esbuild/android-x64@0.25.12':
1488+ optional: true
1489+
1490+ '@esbuild/darwin-arm64@0.25.12':
1491+ optional: true
1492+
1493+ '@esbuild/darwin-x64@0.25.12':
1494+ optional: true
1495+
1496+ '@esbuild/freebsd-arm64@0.25.12':
1497+ optional: true
1498+
1499+ '@esbuild/freebsd-x64@0.25.12':
1500+ optional: true
1501+
1502+ '@esbuild/linux-arm64@0.25.12':
1503+ optional: true
1504+
1505+ '@esbuild/linux-arm@0.25.12':
1506+ optional: true
1507+
1508+ '@esbuild/linux-ia32@0.25.12':
1509+ optional: true
1510+
1511+ '@esbuild/linux-loong64@0.25.12':
1512+ optional: true
1513+
1514+ '@esbuild/linux-mips64el@0.25.12':
1515+ optional: true
1516+
1517+ '@esbuild/linux-ppc64@0.25.12':
1518+ optional: true
1519+
1520+ '@esbuild/linux-riscv64@0.25.12':
1521+ optional: true
1522+
1523+ '@esbuild/linux-s390x@0.25.12':
1524+ optional: true
1525+
1526+ '@esbuild/linux-x64@0.25.12':
1527+ optional: true
1528+
1529+ '@esbuild/netbsd-arm64@0.25.12':
1530+ optional: true
1531+
1532+ '@esbuild/netbsd-x64@0.25.12':
1533+ optional: true
1534+
1535+ '@esbuild/openbsd-arm64@0.25.12':
1536+ optional: true
1537+
1538+ '@esbuild/openbsd-x64@0.25.12':
1539+ optional: true
1540+
1541+ '@esbuild/openharmony-arm64@0.25.12':
1542+ optional: true
1543+
1544+ '@esbuild/sunos-x64@0.25.12':
1545+ optional: true
1546+
1547+ '@esbuild/win32-arm64@0.25.12':
1548+ optional: true
1549+
1550+ '@esbuild/win32-ia32@0.25.12':
1551+ optional: true
1552+
1553+ '@esbuild/win32-x64@0.25.12':
1554+ optional: true
1555+
1556+ '@jridgewell/sourcemap-codec@1.5.5': {}
1557+
1558+ '@rollup/rollup-android-arm-eabi@4.60.1':
1559+ optional: true
1560+
1561+ '@rollup/rollup-android-arm64@4.60.1':
1562+ optional: true
1563+
1564+ '@rollup/rollup-darwin-arm64@4.60.1':
1565+ optional: true
1566+
1567+ '@rollup/rollup-darwin-x64@4.60.1':
1568+ optional: true
1569+
1570+ '@rollup/rollup-freebsd-arm64@4.60.1':
1571+ optional: true
1572+
1573+ '@rollup/rollup-freebsd-x64@4.60.1':
1574+ optional: true
1575+
1576+ '@rollup/rollup-linux-arm-gnueabihf@4.60.1':
1577+ optional: true
1578+
1579+ '@rollup/rollup-linux-arm-musleabihf@4.60.1':
1580+ optional: true
1581+
1582+ '@rollup/rollup-linux-arm64-gnu@4.60.1':
1583+ optional: true
1584+
1585+ '@rollup/rollup-linux-arm64-musl@4.60.1':
1586+ optional: true
1587+
1588+ '@rollup/rollup-linux-loong64-gnu@4.60.1':
1589+ optional: true
1590+
1591+ '@rollup/rollup-linux-loong64-musl@4.60.1':
1592+ optional: true
1593+
1594+ '@rollup/rollup-linux-ppc64-gnu@4.60.1':
1595+ optional: true
1596+
1597+ '@rollup/rollup-linux-ppc64-musl@4.60.1':
1598+ optional: true
1599+
1600+ '@rollup/rollup-linux-riscv64-gnu@4.60.1':
1601+ optional: true
1602+
1603+ '@rollup/rollup-linux-riscv64-musl@4.60.1':
1604+ optional: true
1605+
1606+ '@rollup/rollup-linux-s390x-gnu@4.60.1':
1607+ optional: true
1608+
1609+ '@rollup/rollup-linux-x64-gnu@4.60.1':
1610+ optional: true
1611+
1612+ '@rollup/rollup-linux-x64-musl@4.60.1':
1613+ optional: true
1614+
1615+ '@rollup/rollup-openbsd-x64@4.60.1':
1616+ optional: true
1617+
1618+ '@rollup/rollup-openharmony-arm64@4.60.1':
1619+ optional: true
1620+
1621+ '@rollup/rollup-win32-arm64-msvc@4.60.1':
1622+ optional: true
1623+
1624+ '@rollup/rollup-win32-ia32-msvc@4.60.1':
1625+ optional: true
1626+
1627+ '@rollup/rollup-win32-x64-gnu@4.60.1':
1628+ optional: true
1629+
1630+ '@rollup/rollup-win32-x64-msvc@4.60.1':
1631+ optional: true
1632+
1633+ '@types/estree@1.0.8': {}
1634+
1635+ '@types/node@22.19.15':
1636+ dependencies:
1637+ undici-types: 6.21.0
1638+
1639+ '@vitejs/plugin-vue@5.2.4(vite@6.4.1(@types/node@22.19.15))(vue@3.5.31(typescript@5.9.3))':
1640+ dependencies:
1641+ vite: 6.4.1(@types/node@22.19.15)
1642+ vue: 3.5.31(typescript@5.9.3)
1643+
1644+ '@volar/language-core@2.4.15':
1645+ dependencies:
1646+ '@volar/source-map': 2.4.15
1647+
1648+ '@volar/source-map@2.4.15': {}
1649+
1650+ '@volar/typescript@2.4.15':
1651+ dependencies:
1652+ '@volar/language-core': 2.4.15
1653+ path-browserify: 1.0.1
1654+ vscode-uri: 3.1.0
1655+
1656+ '@vue/compiler-core@3.5.31':
1657+ dependencies:
1658+ '@babel/parser': 7.29.2
1659+ '@vue/shared': 3.5.31
1660+ entities: 7.0.1
1661+ estree-walker: 2.0.2
1662+ source-map-js: 1.2.1
1663+
1664+ '@vue/compiler-dom@3.5.31':
1665+ dependencies:
1666+ '@vue/compiler-core': 3.5.31
1667+ '@vue/shared': 3.5.31
1668+
1669+ '@vue/compiler-sfc@3.5.31':
1670+ dependencies:
1671+ '@babel/parser': 7.29.2
1672+ '@vue/compiler-core': 3.5.31
1673+ '@vue/compiler-dom': 3.5.31
1674+ '@vue/compiler-ssr': 3.5.31
1675+ '@vue/shared': 3.5.31
1676+ estree-walker: 2.0.2
1677+ magic-string: 0.30.21
1678+ postcss: 8.5.8
1679+ source-map-js: 1.2.1
1680+
1681+ '@vue/compiler-ssr@3.5.31':
1682+ dependencies:
1683+ '@vue/compiler-dom': 3.5.31
1684+ '@vue/shared': 3.5.31
1685+
1686+ '@vue/compiler-vue2@2.7.16':
1687+ dependencies:
1688+ de-indent: 1.0.2
1689+ he: 1.2.0
1690+
1691+ '@vue/devtools-api@6.6.4': {}
1692+
1693+ '@vue/language-core@2.2.12(typescript@5.9.3)':
1694+ dependencies:
1695+ '@volar/language-core': 2.4.15
1696+ '@vue/compiler-dom': 3.5.31
1697+ '@vue/compiler-vue2': 2.7.16
1698+ '@vue/shared': 3.5.31
1699+ alien-signals: 1.0.13
1700+ minimatch: 9.0.9
1701+ muggle-string: 0.4.1
1702+ path-browserify: 1.0.1
1703+ optionalDependencies:
1704+ typescript: 5.9.3
1705+
1706+ '@vue/reactivity@3.5.31':
1707+ dependencies:
1708+ '@vue/shared': 3.5.31
1709+
1710+ '@vue/runtime-core@3.5.31':
1711+ dependencies:
1712+ '@vue/reactivity': 3.5.31
1713+ '@vue/shared': 3.5.31
1714+
1715+ '@vue/runtime-dom@3.5.31':
1716+ dependencies:
1717+ '@vue/reactivity': 3.5.31
1718+ '@vue/runtime-core': 3.5.31
1719+ '@vue/shared': 3.5.31
1720+ csstype: 3.2.3
1721+
1722+ '@vue/server-renderer@3.5.31(vue@3.5.31(typescript@5.9.3))':
1723+ dependencies:
1724+ '@vue/compiler-ssr': 3.5.31
1725+ '@vue/shared': 3.5.31
1726+ vue: 3.5.31(typescript@5.9.3)
1727+
1728+ '@vue/shared@3.5.31': {}
1729+
1730+ alien-signals@1.0.13: {}
1731+
1732+ balanced-match@1.0.2: {}
1733+
1734+ brace-expansion@2.0.3:
1735+ dependencies:
1736+ balanced-match: 1.0.2
1737+
1738+ csstype@3.2.3: {}
1739+
1740+ de-indent@1.0.2: {}
1741+
1742+ entities@7.0.1: {}
1743+
1744+ esbuild@0.25.12:
1745+ optionalDependencies:
1746+ '@esbuild/aix-ppc64': 0.25.12
1747+ '@esbuild/android-arm': 0.25.12
1748+ '@esbuild/android-arm64': 0.25.12
1749+ '@esbuild/android-x64': 0.25.12
1750+ '@esbuild/darwin-arm64': 0.25.12
1751+ '@esbuild/darwin-x64': 0.25.12
1752+ '@esbuild/freebsd-arm64': 0.25.12
1753+ '@esbuild/freebsd-x64': 0.25.12
1754+ '@esbuild/linux-arm': 0.25.12
1755+ '@esbuild/linux-arm64': 0.25.12
1756+ '@esbuild/linux-ia32': 0.25.12
1757+ '@esbuild/linux-loong64': 0.25.12
1758+ '@esbuild/linux-mips64el': 0.25.12
1759+ '@esbuild/linux-ppc64': 0.25.12
1760+ '@esbuild/linux-riscv64': 0.25.12
1761+ '@esbuild/linux-s390x': 0.25.12
1762+ '@esbuild/linux-x64': 0.25.12
1763+ '@esbuild/netbsd-arm64': 0.25.12
1764+ '@esbuild/netbsd-x64': 0.25.12
1765+ '@esbuild/openbsd-arm64': 0.25.12
1766+ '@esbuild/openbsd-x64': 0.25.12
1767+ '@esbuild/openharmony-arm64': 0.25.12
1768+ '@esbuild/sunos-x64': 0.25.12
1769+ '@esbuild/win32-arm64': 0.25.12
1770+ '@esbuild/win32-ia32': 0.25.12
1771+ '@esbuild/win32-x64': 0.25.12
1772+
1773+ estree-walker@2.0.2: {}
1774+
1775+ fdir@6.5.0(picomatch@4.0.4):
1776+ optionalDependencies:
1777+ picomatch: 4.0.4
1778+
1779+ fsevents@2.3.3:
1780+ optional: true
1781+
1782+ he@1.2.0: {}
1783+
1784+ magic-string@0.30.21:
1785+ dependencies:
1786+ '@jridgewell/sourcemap-codec': 1.5.5
1787+
1788+ minimatch@9.0.9:
1789+ dependencies:
1790+ brace-expansion: 2.0.3
1791+
1792+ muggle-string@0.4.1: {}
1793+
1794+ nanoid@3.3.11: {}
1795+
1796+ path-browserify@1.0.1: {}
1797+
1798+ picocolors@1.1.1: {}
1799+
1800+ picomatch@4.0.4: {}
1801+
1802+ postcss@8.5.8:
1803+ dependencies:
1804+ nanoid: 3.3.11
1805+ picocolors: 1.1.1
1806+ source-map-js: 1.2.1
1807+
1808+ rollup@4.60.1:
1809+ dependencies:
1810+ '@types/estree': 1.0.8
1811+ optionalDependencies:
1812+ '@rollup/rollup-android-arm-eabi': 4.60.1
1813+ '@rollup/rollup-android-arm64': 4.60.1
1814+ '@rollup/rollup-darwin-arm64': 4.60.1
1815+ '@rollup/rollup-darwin-x64': 4.60.1
1816+ '@rollup/rollup-freebsd-arm64': 4.60.1
1817+ '@rollup/rollup-freebsd-x64': 4.60.1
1818+ '@rollup/rollup-linux-arm-gnueabihf': 4.60.1
1819+ '@rollup/rollup-linux-arm-musleabihf': 4.60.1
1820+ '@rollup/rollup-linux-arm64-gnu': 4.60.1
1821+ '@rollup/rollup-linux-arm64-musl': 4.60.1
1822+ '@rollup/rollup-linux-loong64-gnu': 4.60.1
1823+ '@rollup/rollup-linux-loong64-musl': 4.60.1
1824+ '@rollup/rollup-linux-ppc64-gnu': 4.60.1
1825+ '@rollup/rollup-linux-ppc64-musl': 4.60.1
1826+ '@rollup/rollup-linux-riscv64-gnu': 4.60.1
1827+ '@rollup/rollup-linux-riscv64-musl': 4.60.1
1828+ '@rollup/rollup-linux-s390x-gnu': 4.60.1
1829+ '@rollup/rollup-linux-x64-gnu': 4.60.1
1830+ '@rollup/rollup-linux-x64-musl': 4.60.1
1831+ '@rollup/rollup-openbsd-x64': 4.60.1
1832+ '@rollup/rollup-openharmony-arm64': 4.60.1
1833+ '@rollup/rollup-win32-arm64-msvc': 4.60.1
1834+ '@rollup/rollup-win32-ia32-msvc': 4.60.1
1835+ '@rollup/rollup-win32-x64-gnu': 4.60.1
1836+ '@rollup/rollup-win32-x64-msvc': 4.60.1
1837+ fsevents: 2.3.3
1838+
1839+ source-map-js@1.2.1: {}
1840+
1841+ tinyglobby@0.2.15:
1842+ dependencies:
1843+ fdir: 6.5.0(picomatch@4.0.4)
1844+ picomatch: 4.0.4
1845+
1846 typescript@5.9.3: {}
1847+
1848+ undici-types@6.21.0: {}
1849+
1850+ vite@6.4.1(@types/node@22.19.15):
1851+ dependencies:
1852+ esbuild: 0.25.12
1853+ fdir: 6.5.0(picomatch@4.0.4)
1854+ picomatch: 4.0.4
1855+ postcss: 8.5.8
1856+ rollup: 4.60.1
1857+ tinyglobby: 0.2.15
1858+ optionalDependencies:
1859+ '@types/node': 22.19.15
1860+ fsevents: 2.3.3
1861+
1862+ vscode-uri@3.1.0: {}
1863+
1864+ vue-router@4.6.4(vue@3.5.31(typescript@5.9.3)):
1865+ dependencies:
1866+ '@vue/devtools-api': 6.6.4
1867+ vue: 3.5.31(typescript@5.9.3)
1868+
1869+ vue-tsc@2.2.12(typescript@5.9.3):
1870+ dependencies:
1871+ '@volar/typescript': 2.4.15
1872+ '@vue/language-core': 2.2.12(typescript@5.9.3)
1873+ typescript: 5.9.3
1874+
1875+ vue@3.5.31(typescript@5.9.3):
1876+ dependencies:
1877+ '@vue/compiler-dom': 3.5.31
1878+ '@vue/compiler-sfc': 3.5.31
1879+ '@vue/runtime-dom': 3.5.31
1880+ '@vue/server-renderer': 3.5.31(vue@3.5.31(typescript@5.9.3))
1881+ '@vue/shared': 3.5.31
1882+ optionalDependencies:
1883+ typescript: 5.9.3
1884diff --git a/tasks/T-S070.md b/tasks/T-S070.md
1885index 520a6e9b389f2bb262f8a64c0ed5d6b596a2be0f..5b0a121f7461bc2fd7b1bbeb6459ee7cf88bee8e 100644
1886--- a/tasks/T-S070.md
1887+++ b/tasks/T-S070.md
1888@@ -2,7 +2,7 @@
1889
1890 ## 状态
1891
1892-- 当前状态:`待开始`
1893+- 当前状态:`已完成`
1894 - 规模预估:`M`
1895 - 依赖任务:无
1896 - 建议执行者:`Codex`(涉及 monorepo 构建、静态资源托管和本地 API 路由接入)
1897@@ -21,7 +21,7 @@
1898
1899 - 仓库:`/Users/george/code/baa-conductor`
1900 - 分支基线:`main`
1901-- 提交:`b063524`
1902+- 提交:`e8fba02`
1903
1904 ## 分支与 worktree(强制)
1905
1906@@ -143,21 +143,35 @@
1907
1908 ### 开始执行
1909
1910-- 执行者:
1911-- 开始时间:
1912+- 执行者:`Codex`
1913+- 开始时间:`2026-04-01 23:55:23 CST`
1914 - 状态变更:`待开始` → `进行中`
1915
1916 ### 完成摘要
1917
1918-- 完成时间:
1919+- 完成时间:`2026-04-02 00:07:49 CST`
1920 - 状态变更:`进行中` → `已完成`
1921 - 修改了哪些文件:
1922+ - `apps/conductor-ui/`:新建 Vue 3 + Vite + TypeScript + Vue Router 应用壳
1923+ - `apps/conductor-daemon/src/local-api.ts`:新增 `/app`、`/app/assets/*`、`/app/*` SPA fallback 静态托管
1924+ - `apps/conductor-daemon/package.json`:把 `conductor-ui` build/typecheck 接入 daemon 构建链
1925+ - `apps/conductor-daemon/src/index.test.js`:补 `/app` 与静态资源缓存策略集成测试
1926+ - `pnpm-lock.yaml`:新增前端依赖锁定
1927 - 核心实现思路:
1928+ - 用 `apps/conductor-ui/dist` 作为稳定前端产物目录,`base` 固定为 `/app/`
1929+ - `conductor-daemon` 直接同源托管 UI 壳,不复用 `/artifact/*`
1930+ - `GET /app` 和 `GET /app/*` 返回 `index.html`,`GET /app/assets/*` 返回 hashed assets,并区分 HTML `no-store` 与 asset 长缓存
1931+ - 壳页面明确展示 `Session Pending` 与后续 `T-S071`/`T-S072` 占位,避免把当前版本误判成正式可运营工作台
1932 - 跑了哪些测试:
1933+ - `pnpm -C apps/conductor-ui typecheck`
1934+ - `pnpm -C apps/conductor-ui build`
1935+ - `pnpm -C apps/conductor-daemon build`
1936+ - `pnpm -C apps/conductor-daemon typecheck`
1937+ - `pnpm -C apps/conductor-daemon test`
1938
1939 ### 执行过程中遇到的问题
1940
1941--
1942+- 无阻塞问题;主要是把 `conductor-ui` 构建产物接入 daemon 现有 build/test 链,避免 `/app` 测试依赖手工预构建
1943
1944 ### 剩余风险
1945
1946diff --git a/tasks/TASK_OVERVIEW.md b/tasks/TASK_OVERVIEW.md
1947index e8963f64dafbd294f5b96f748e0390c354f9fbd5..17c75da6e21538b91ac2aac1c8d58948b7491721 100644
1948--- a/tasks/TASK_OVERVIEW.md
1949+++ b/tasks/TASK_OVERVIEW.md
1950@@ -94,20 +94,19 @@
1951 | [`T-S068`](./T-S068.md) | ChatGPT proxy send 冷启动降级保护 | S | 无 | Codex | 已完成 |
1952 | [`T-S069`](./T-S069.md) | proxy_delivery 成功语义增强 | L | T-S060 | Codex | 已完成 |
1953 | [`T-S065`](./T-S065.md) | policy 配置化 | M | 无 | Codex | 已完成 |
1954+| [`T-S070`](./T-S070.md) | Conductor UI 基础设施:Vue 3 脚手架与 `/app` 静态托管 | M | 无 | Codex | 已完成 |
1955
1956 ### 当前下一波任务
1957
1958-建议下一波按 Web UI 工作台拆成 3 张任务卡推进:
1959+建议下一波继续按 Web UI 工作台剩余 2 张任务卡推进:
1960
1961 | 任务 | 标题 | 规模 | 依赖 | 建议 AI | 状态 |
1962 |---|---|---|---|---|---|
1963-| [`T-S070`](./T-S070.md) | Conductor UI 基础设施:Vue 3 脚手架与 `/app` 静态托管 | M | 无 | Codex | 待开始 |
1964 | [`T-S071`](./T-S071.md) | Conductor UI 会话鉴权:登录页与浏览器 session | M | T-S070 | Codex | 待开始 |
1965 | [`T-S072`](./T-S072.md) | Conductor UI `Control` 工作区首版 | L | T-S070, T-S071 | Codex | 待开始 |
1966
1967-这三张任务卡对应:
1968+这两张任务卡对应:
1969
1970-- Phase 0:前端壳与 `/app`
1971 - Phase 0.5:浏览器工作台 session 鉴权
1972 - Phase 1:正式 `Control` 工作区
1973