- commit
- 2dd3664
- parent
- 68a85cf
- author
- codex@macbookpro
- date
- 2026-04-03 14:46:47 +0800 CST
feat: add conductor ui shell and app hosting
19 files changed,
+1839,
-13
Raw patch view.
1diff --git a/apps/conductor-daemon/package.json b/apps/conductor-daemon/package.json
2index fecbbf39ec5c681de004178c12a4c5398c6fad9f..8110b642a05e925b3fd0ec6456ec00f71a30ab95 100644
3--- a/apps/conductor-daemon/package.json
4+++ b/apps/conductor-daemon/package.json
5@@ -10,7 +10,7 @@
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",
14diff --git a/apps/conductor-daemon/src/index.test.js b/apps/conductor-daemon/src/index.test.js
15index a8427b699caeadba57c7b0c83c6e47d9c1ace747..c03bfaf5c037ba0216687d5610f20f08194ad293 100644
16--- a/apps/conductor-daemon/src/index.test.js
17+++ b/apps/conductor-daemon/src/index.test.js
18@@ -600,10 +600,50 @@ async function withArtifactStoreFixture(callback) {
19 }
20 }
21
22+async function withConductorUiDistFixture(callback) {
23+ const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-ui-dist-"));
24+ const uiDistDir = join(stateDir, "dist");
25+ const assetFile = "app-shell.js";
26+
27+ mkdirSync(join(uiDistDir, "assets"), {
28+ recursive: true
29+ });
30+ writeFileSync(
31+ join(uiDistDir, "index.html"),
32+ [
33+ "<!doctype html>",
34+ "<html lang=\"zh-CN\">",
35+ "<head><meta charset=\"UTF-8\" /><title>Conductor UI Shell Fixture</title></head>",
36+ "<body>",
37+ "<div id=\"app\">fixture</div>",
38+ `<script type="module" src="/app/assets/${assetFile}"></script>`,
39+ "</body>",
40+ "</html>"
41+ ].join("")
42+ );
43+ writeFileSync(join(uiDistDir, "assets", assetFile), "console.log('fixture ui shell');\n");
44+
45+ try {
46+ return await callback({
47+ assetFile,
48+ uiDistDir
49+ });
50+ } finally {
51+ rmSync(stateDir, {
52+ force: true,
53+ recursive: true
54+ });
55+ }
56+}
57+
58 function parseJsonBody(response) {
59 return JSON.parse(response.body);
60 }
61
62+function readBinaryBodyText(response) {
63+ return typeof response.body === "string" ? response.body : Buffer.from(response.body).toString("utf8");
64+}
65+
66 test(
67 "BAA instruction extraction and parsing supports the four parameter forms and ignores ordinary code fences",
68 () => {
69@@ -7675,6 +7715,24 @@ test("ConductorRuntime serves health and migrated local API endpoints over HTTP"
70 assert.match(statusViewUiHtml, /Readable automation state for people and browser controls\./u);
71 assert.match(statusViewUiHtml, /HTML endpoint: <strong>\/v1\/status\/ui<\/strong>/u);
72
73+ const appResponse = await fetch(`${baseUrl}/app`);
74+ assert.equal(appResponse.status, 200);
75+ assert.equal(appResponse.headers.get("cache-control"), "no-store");
76+ assert.equal(appResponse.headers.get("content-type"), "text/html; charset=utf-8");
77+ const appHtml = await appResponse.text();
78+ assert.match(appHtml, /<div id="app"><\/div>/u);
79+ const appAssetMatch = appHtml.match(/\/app\/assets\/[^"' )]+/u);
80+ assert.ok(appAssetMatch);
81+
82+ const appControlResponse = await fetch(`${baseUrl}/app/control`);
83+ assert.equal(appControlResponse.status, 200);
84+ assert.equal(await appControlResponse.text(), appHtml);
85+
86+ const appAssetResponse = await fetch(`${baseUrl}${appAssetMatch[0]}`);
87+ assert.equal(appAssetResponse.status, 200);
88+ assert.equal(appAssetResponse.headers.get("cache-control"), "public, max-age=31536000, immutable");
89+ assert.ok(appAssetResponse.headers.get("content-type"));
90+
91 const codexStatusResponse = await fetch(`${baseUrl}/v1/codex`);
92 assert.equal(codexStatusResponse.status, 200);
93 const codexStatusPayload = await codexStatusResponse.json();
94@@ -7963,6 +8021,82 @@ test("handleConductorHttpRequest serves artifact files and robots.txt", async ()
95 });
96 });
97
98+test("handleConductorHttpRequest serves conductor-ui shell assets and history fallback", async () => {
99+ const { repository, snapshot } = await createLocalApiFixture();
100+
101+ await withConductorUiDistFixture(async ({ assetFile, uiDistDir }) => {
102+ const context = {
103+ repository,
104+ snapshotLoader: () => snapshot,
105+ uiDistDir
106+ };
107+
108+ const appResponse = await handleConductorHttpRequest(
109+ {
110+ method: "GET",
111+ path: "/app"
112+ },
113+ context
114+ );
115+ assert.equal(appResponse.status, 200);
116+ assert.equal(appResponse.headers["cache-control"], "no-store");
117+ assert.equal(appResponse.headers["content-type"], "text/html; charset=utf-8");
118+ const appHtml = readBinaryBodyText(appResponse);
119+ assert.match(appHtml, /Conductor UI Shell Fixture/u);
120+
121+ const appSlashResponse = await handleConductorHttpRequest(
122+ {
123+ method: "GET",
124+ path: "/app/"
125+ },
126+ context
127+ );
128+ assert.equal(readBinaryBodyText(appSlashResponse), appHtml);
129+
130+ const historyResponse = await handleConductorHttpRequest(
131+ {
132+ method: "GET",
133+ path: "/app/control"
134+ },
135+ context
136+ );
137+ assert.equal(historyResponse.status, 200);
138+ assert.equal(readBinaryBodyText(historyResponse), appHtml);
139+
140+ const assetResponse = await handleConductorHttpRequest(
141+ {
142+ method: "GET",
143+ path: `/app/assets/${assetFile}`
144+ },
145+ context
146+ );
147+ assert.equal(assetResponse.status, 200);
148+ assert.equal(assetResponse.headers["cache-control"], "public, max-age=31536000, immutable");
149+ assert.equal(assetResponse.headers["content-type"], "text/javascript; charset=utf-8");
150+ assert.equal(readBinaryBodyText(assetResponse), "console.log('fixture ui shell');\n");
151+
152+ const missingAssetResponse = await handleConductorHttpRequest(
153+ {
154+ method: "GET",
155+ path: "/app/assets/missing.js"
156+ },
157+ context
158+ );
159+ assert.equal(missingAssetResponse.status, 404);
160+ assert.equal(parseJsonBody(missingAssetResponse).error, "not_found");
161+
162+ const assetRootResponse = await handleConductorHttpRequest(
163+ {
164+ method: "GET",
165+ path: "/app/assets"
166+ },
167+ context
168+ );
169+ assert.equal(assetRootResponse.status, 404);
170+ assert.equal(parseJsonBody(assetRootResponse).error, "not_found");
171+ });
172+});
173+
174 test("handleConductorHttpRequest serves code files and blocks unsafe paths", async () => {
175 const { repository, snapshot } = await createLocalApiFixture();
176 const codeRootDir = mkdtempSync(join(tmpdir(), "baa-conductor-code-route-"));
177diff --git a/apps/conductor-daemon/src/local-api.ts b/apps/conductor-daemon/src/local-api.ts
178index 58aa47a437480f6c8fcfce89c1d832f279c41a6e..1a2370d743d09a829aa0da6ab91f4122d0378c64 100644
179--- a/apps/conductor-daemon/src/local-api.ts
180+++ b/apps/conductor-daemon/src/local-api.ts
181@@ -1,6 +1,7 @@
182 import * as nodeFs from "node:fs";
183 import { randomUUID } from "node:crypto";
184 import * as nodePath from "node:path";
185+import { fileURLToPath } from "node:url";
186 import {
187 buildArtifactRelativePath,
188 buildArtifactPublicUrl,
189@@ -120,11 +121,34 @@ const HOST_OPERATIONS_ROUTE_IDS = new Set(["host.exec", "host.files.read", "host
190 const HOST_OPERATIONS_AUTH_HEADER = "Authorization: Bearer <BAA_SHARED_TOKEN>";
191 const HOST_OPERATIONS_WWW_AUTHENTICATE = 'Bearer realm="baa-conductor-host-ops"';
192 const DEFAULT_CODE_ROOT_DIR = "/Users/george/code/";
193+const DEFAULT_CONDUCTOR_UI_DIST_DIR = resolve(fileURLToPath(new URL("../../conductor-ui/dist/", import.meta.url)));
194 const CODE_ROUTE_CONTENT_TYPE = "text/plain; charset=utf-8";
195 const STATUS_VIEW_HTML_HEADERS = {
196 "cache-control": "no-store",
197 "content-type": "text/html; charset=utf-8"
198 } as const;
199+const CONDUCTOR_UI_ENTRY_FILE = "index.html";
200+const CONDUCTOR_UI_HTML_HEADERS = {
201+ "cache-control": "no-store",
202+ "content-type": "text/html; charset=utf-8"
203+} as const;
204+const CONDUCTOR_UI_ASSET_CACHE_CONTROL = "public, max-age=31536000, immutable";
205+const CONDUCTOR_UI_ASSET_CONTENT_TYPES: Record<string, string> = {
206+ ".css": "text/css; charset=utf-8",
207+ ".ico": "image/x-icon",
208+ ".jpeg": "image/jpeg",
209+ ".jpg": "image/jpeg",
210+ ".js": "text/javascript; charset=utf-8",
211+ ".json": "application/json; charset=utf-8",
212+ ".map": "application/json; charset=utf-8",
213+ ".png": "image/png",
214+ ".svg": "image/svg+xml",
215+ ".ttf": "font/ttf",
216+ ".txt": "text/plain; charset=utf-8",
217+ ".webp": "image/webp",
218+ ".woff": "font/woff",
219+ ".woff2": "font/woff2"
220+};
221 const ROBOTS_TXT_BODY = "User-agent: *\nAllow: /artifact/";
222 const SSE_RESPONSE_HEADERS = {
223 "cache-control": "no-store",
224@@ -308,6 +332,7 @@ interface LocalApiRequestContext {
225 requestId: string;
226 sharedToken: string | null;
227 snapshotLoader: () => ConductorRuntimeApiSnapshot;
228+ uiDistDir: string;
229 url: URL;
230 }
231
232@@ -360,6 +385,7 @@ export interface ConductorLocalApiContext {
233 repository: ControlPlaneRepository | null;
234 sharedToken?: string | null;
235 snapshotLoader: () => ConductorRuntimeApiSnapshot;
236+ uiDistDir?: string | null;
237 version?: string | null;
238 }
239
240@@ -445,6 +471,30 @@ const LOCAL_API_ROUTES: LocalApiRouteDefinition[] = [
241 pathPattern: "/artifact/:artifact_scope/:artifact_file",
242 summary: "读取 artifact 静态文件"
243 },
244+ {
245+ id: "service.app.shell",
246+ exposeInDescribe: false,
247+ kind: "read",
248+ method: "GET",
249+ pathPattern: "/app",
250+ summary: "读取 conductor-ui SPA 入口"
251+ },
252+ {
253+ id: "service.app.assets",
254+ exposeInDescribe: false,
255+ kind: "read",
256+ method: "GET",
257+ pathPattern: "/app/assets/:app_asset_path*",
258+ summary: "读取 conductor-ui 构建后的静态资源"
259+ },
260+ {
261+ id: "service.app.history",
262+ exposeInDescribe: false,
263+ kind: "read",
264+ method: "GET",
265+ pathPattern: "/app/:app_path*",
266+ summary: "为 conductor-ui 提供 SPA history fallback"
267+ },
268 {
269 id: "service.code.read",
270 kind: "read",
271@@ -6572,6 +6622,114 @@ function buildPlainTextBinaryResponse(status: number, body: string): ConductorHt
272 });
273 }
274
275+function resolveUiDistDir(value: string | null | undefined): string {
276+ return resolve(normalizeOptionalString(value) ?? DEFAULT_CONDUCTOR_UI_DIST_DIR);
277+}
278+
279+function buildUiUnavailableError(uiDistDir: string): LocalApiHttpError {
280+ return new LocalApiHttpError(
281+ 503,
282+ "ui_not_available",
283+ `Conductor UI build output "${uiDistDir}" is not available. Run "pnpm -C apps/conductor-ui build" first.`
284+ );
285+}
286+
287+function getUiDistRealPath(uiDistDir: string): string {
288+ try {
289+ return realpathSync(uiDistDir);
290+ } catch (error) {
291+ if (isMissingFileError(error)) {
292+ throw buildUiUnavailableError(uiDistDir);
293+ }
294+
295+ throw error;
296+ }
297+}
298+
299+function readUiEntryFile(uiDistDir: string): Uint8Array {
300+ const uiDistRealPath = getUiDistRealPath(uiDistDir);
301+
302+ try {
303+ return readFileSync(join(uiDistRealPath, CONDUCTOR_UI_ENTRY_FILE));
304+ } catch (error) {
305+ if (isMissingFileError(error)) {
306+ throw buildUiUnavailableError(uiDistDir);
307+ }
308+
309+ throw error;
310+ }
311+}
312+
313+function buildUiAssetNotFoundError(pathname: string): LocalApiHttpError {
314+ return new LocalApiHttpError(404, "not_found", `UI asset "${normalizePathname(pathname)}" was not found.`);
315+}
316+
317+function assertAllowedUiAssetPath(assetPath: string, pathname: string): void {
318+ if (assetPath.includes("\u0000") || assetPath.includes("\\") || assetPath.startsWith("/")) {
319+ throw buildUiAssetNotFoundError(pathname);
320+ }
321+
322+ const segments = assetPath.split("/");
323+
324+ if (segments.some((segment) => segment === "" || segment === "..")) {
325+ throw buildUiAssetNotFoundError(pathname);
326+ }
327+}
328+
329+function resolveUiAssetFilePath(uiDistDir: string, assetPath: string, pathname: string): string {
330+ if (assetPath.trim() === "") {
331+ throw buildUiAssetNotFoundError(pathname);
332+ }
333+
334+ assertAllowedUiAssetPath(assetPath, pathname);
335+ const uiDistRealPath = getUiDistRealPath(uiDistDir);
336+ const assetsRoot = resolve(uiDistRealPath, "assets");
337+
338+ try {
339+ const resolvedPath = realpathSync(resolve(assetsRoot, assetPath));
340+
341+ if (isPathOutsideRoot(uiDistRealPath, resolvedPath)) {
342+ throw buildUiAssetNotFoundError(pathname);
343+ }
344+
345+ return resolvedPath;
346+ } catch (error) {
347+ if (error instanceof LocalApiHttpError) {
348+ throw error;
349+ }
350+
351+ if (isMissingFileError(error)) {
352+ throw buildUiAssetNotFoundError(pathname);
353+ }
354+
355+ throw error;
356+ }
357+}
358+
359+function getUiAssetContentType(filePath: string): string {
360+ return CONDUCTOR_UI_ASSET_CONTENT_TYPES[extname(filePath).toLowerCase()] ?? "application/octet-stream";
361+}
362+
363+async function handleConductorUiEntryRead(context: LocalApiRequestContext): Promise<ConductorHttpResponse> {
364+ return binaryResponse(200, readUiEntryFile(context.uiDistDir), {
365+ ...CONDUCTOR_UI_HTML_HEADERS
366+ });
367+}
368+
369+async function handleConductorUiAssetRead(context: LocalApiRequestContext): Promise<ConductorHttpResponse> {
370+ const assetPath = context.params.app_asset_path ?? "";
371+ const resolvedPath = resolveUiAssetFilePath(context.uiDistDir, assetPath, context.url.pathname);
372+
373+ return binaryResponse(200, readFileSync(resolvedPath), {
374+ "cache-control": CONDUCTOR_UI_ASSET_CACHE_CONTROL,
375+ "content-type": getUiAssetContentType(resolvedPath)
376+ });
377+}
378+
379+async function handleConductorUiHistoryRead(context: LocalApiRequestContext): Promise<ConductorHttpResponse> {
380+ return handleConductorUiEntryRead(context);
381+}
382+
383 function resolveCodeRootDir(value: string | null | undefined): string {
384 return resolve(normalizeOptionalString(value) ?? DEFAULT_CODE_ROOT_DIR);
385 }
386@@ -7683,6 +7841,12 @@ async function dispatchBusinessRoute(
387 return handleRobotsRead();
388 case "service.artifact.read":
389 return handleArtifactRead(context);
390+ case "service.app.shell":
391+ return handleConductorUiEntryRead(context);
392+ case "service.app.assets":
393+ return handleConductorUiAssetRead(context);
394+ case "service.app.history":
395+ return handleConductorUiHistoryRead(context);
396 case "service.code.read":
397 return handleCodeRead(context);
398 case "service.health":
399@@ -7982,6 +8146,7 @@ export async function handleConductorHttpRequest(
400 requestId,
401 sharedToken: normalizeOptionalString(context.sharedToken),
402 snapshotLoader: context.snapshotLoader,
403+ uiDistDir: resolveUiDistDir(context.uiDistDir),
404 url
405 },
406 version
407diff --git a/apps/conductor-ui/index.html b/apps/conductor-ui/index.html
408new file mode 100644
409index 0000000000000000000000000000000000000000..509a7f85dc3fb1f4e15de3b9d25a41e72fc6be41
410--- /dev/null
411+++ b/apps/conductor-ui/index.html
412@@ -0,0 +1,18 @@
413+<!doctype html>
414+<html lang="zh-CN">
415+ <head>
416+ <meta charset="UTF-8" />
417+ <meta
418+ name="viewport"
419+ content="width=device-width, initial-scale=1.0"
420+ />
421+ <title>Conductor UI</title>
422+ </head>
423+ <body>
424+ <div id="app"></div>
425+ <script
426+ type="module"
427+ src="/src/main.ts"
428+ ></script>
429+ </body>
430+</html>
431diff --git a/apps/conductor-ui/package.json b/apps/conductor-ui/package.json
432new file mode 100644
433index 0000000000000000000000000000000000000000..e30d5abb0c19168a46f30444deddf7ef6cfef334
434--- /dev/null
435+++ b/apps/conductor-ui/package.json
436@@ -0,0 +1,23 @@
437+{
438+ "name": "@baa-conductor/conductor-ui",
439+ "private": true,
440+ "type": "module",
441+ "scripts": {
442+ "build": "vite build",
443+ "dev": "vite",
444+ "preview": "vite preview",
445+ "typecheck": "vue-tsc --noEmit",
446+ "test": "pnpm run typecheck",
447+ "smoke": "pnpm run build"
448+ },
449+ "dependencies": {
450+ "vue": "^3.5.18",
451+ "vue-router": "^4.5.1"
452+ },
453+ "devDependencies": {
454+ "@vitejs/plugin-vue": "^6.0.1",
455+ "typescript": "^5.8.2",
456+ "vite": "^7.1.1",
457+ "vue-tsc": "^3.0.3"
458+ }
459+}
460diff --git a/apps/conductor-ui/src/App.vue b/apps/conductor-ui/src/App.vue
461new file mode 100644
462index 0000000000000000000000000000000000000000..543cfdbab78c08f07ccec245e955b5d84ad0f7ab
463--- /dev/null
464+++ b/apps/conductor-ui/src/App.vue
465@@ -0,0 +1,7 @@
466+<template>
467+ <RouterView />
468+</template>
469+
470+<script setup lang="ts">
471+import { RouterView } from "vue-router";
472+</script>
473diff --git a/apps/conductor-ui/src/env.d.ts b/apps/conductor-ui/src/env.d.ts
474new file mode 100644
475index 0000000000000000000000000000000000000000..158d2b152e84c89862547e85a0a3de20724e046f
476--- /dev/null
477+++ b/apps/conductor-ui/src/env.d.ts
478@@ -0,0 +1,8 @@
479+/// <reference types="vite/client" />
480+
481+declare module "*.vue" {
482+ import type { DefineComponent } from "vue";
483+
484+ const component: DefineComponent<Record<string, never>, Record<string, never>, unknown>;
485+ export default component;
486+}
487diff --git a/apps/conductor-ui/src/layouts/WorkbenchLayout.vue b/apps/conductor-ui/src/layouts/WorkbenchLayout.vue
488new file mode 100644
489index 0000000000000000000000000000000000000000..47b4c56213b333ea899ca9eb94ccb44854879cd7
490--- /dev/null
491+++ b/apps/conductor-ui/src/layouts/WorkbenchLayout.vue
492@@ -0,0 +1,90 @@
493+<template>
494+ <div class="shell-page">
495+ <div class="shell-backdrop shell-backdrop-left"></div>
496+ <div class="shell-backdrop shell-backdrop-right"></div>
497+ <main class="shell-frame">
498+ <aside class="shell-sidebar">
499+ <div class="shell-brand">
500+ <p class="eyebrow">Conductor UI Shell</p>
501+ <h1>正式工作台入口</h1>
502+ <p class="lede">
503+ `/app` 已经作为同源入口挂到 conductor。当前版本只提供可持续演进的壳和导航骨架。
504+ </p>
505+ </div>
506+
507+ <div class="session-card">
508+ <span class="status-badge status-badge-warn">未接登录态</span>
509+ <p class="session-copy">
510+ 首版不做浏览器 session 鉴权,后续会在 `/app/login` 和受保护写接口中补齐。
511+ </p>
512+ </div>
513+
514+ <nav class="nav-list">
515+ <RouterLink
516+ v-for="item in navItems"
517+ :key="item.to"
518+ :to="item.to"
519+ class="nav-item"
520+ :class="{ 'nav-item-active': route.path === item.to }"
521+ >
522+ <span class="nav-label">{{ item.label }}</span>
523+ <span class="nav-note">{{ item.note }}</span>
524+ </RouterLink>
525+ </nav>
526+
527+ <section class="sidebar-panel">
528+ <p class="sidebar-title">当前范围</p>
529+ <ul class="sidebar-list">
530+ <li>Vue 3 + Vite + TypeScript + Vue Router</li>
531+ <li>`/app` 同源静态托管</li>
532+ <li>Control / Channels / Login 占位路由</li>
533+ </ul>
534+ </section>
535+ </aside>
536+
537+ <section class="shell-main">
538+ <header class="hero-card">
539+ <div>
540+ <p class="eyebrow">Workspace</p>
541+ <h2>{{ currentItem.label }}</h2>
542+ </div>
543+ <div class="hero-meta">
544+ <span class="status-badge status-badge-info">Phase 1</span>
545+ <span class="status-badge status-badge-muted">Shell Ready</span>
546+ </div>
547+ </header>
548+
549+ <RouterView />
550+ </section>
551+ </main>
552+ </div>
553+</template>
554+
555+<script setup lang="ts">
556+import { computed } from "vue";
557+import { RouterLink, RouterView, useRoute } from "vue-router";
558+
559+const route = useRoute();
560+
561+const navItems = [
562+ {
563+ label: "Control",
564+ note: "系统态与 browser 运维入口",
565+ to: "/control"
566+ },
567+ {
568+ label: "Channels",
569+ note: "未来正式业务线程入口",
570+ to: "/channels"
571+ },
572+ {
573+ label: "Login",
574+ note: "预留浏览器 session 登录页",
575+ to: "/login"
576+ }
577+] as const;
578+
579+const currentItem = computed(
580+ () => navItems.find((item) => route.path === item.to) ?? navItems[0]
581+);
582+</script>
583diff --git a/apps/conductor-ui/src/main.ts b/apps/conductor-ui/src/main.ts
584new file mode 100644
585index 0000000000000000000000000000000000000000..de370f30f625d260c8d695de6ae00b8089a21b27
586--- /dev/null
587+++ b/apps/conductor-ui/src/main.ts
588@@ -0,0 +1,7 @@
589+import { createApp } from "vue";
590+
591+import App from "./App.vue";
592+import router from "./routes";
593+import "./style.css";
594+
595+createApp(App).use(router).mount("#app");
596diff --git a/apps/conductor-ui/src/routes/index.ts b/apps/conductor-ui/src/routes/index.ts
597new file mode 100644
598index 0000000000000000000000000000000000000000..ab6707085940fcf02581c96d965f53dfe3cc29bb
599--- /dev/null
600+++ b/apps/conductor-ui/src/routes/index.ts
601@@ -0,0 +1,41 @@
602+import { createRouter, createWebHistory } from "vue-router";
603+
604+import WorkbenchLayout from "../layouts/WorkbenchLayout.vue";
605+import ChannelsPlaceholderView from "../views/ChannelsPlaceholderView.vue";
606+import ControlOverviewView from "../views/ControlOverviewView.vue";
607+import LoginPlaceholderView from "../views/LoginPlaceholderView.vue";
608+
609+const router = createRouter({
610+ history: createWebHistory("/app/"),
611+ routes: [
612+ {
613+ path: "/",
614+ component: WorkbenchLayout,
615+ children: [
616+ {
617+ path: "",
618+ redirect: {
619+ name: "control"
620+ }
621+ },
622+ {
623+ path: "control",
624+ name: "control",
625+ component: ControlOverviewView
626+ },
627+ {
628+ path: "channels",
629+ name: "channels",
630+ component: ChannelsPlaceholderView
631+ },
632+ {
633+ path: "login",
634+ name: "login",
635+ component: LoginPlaceholderView
636+ }
637+ ]
638+ }
639+ ]
640+});
641+
642+export default router;
643diff --git a/apps/conductor-ui/src/style.css b/apps/conductor-ui/src/style.css
644new file mode 100644
645index 0000000000000000000000000000000000000000..7e1aa0f64a1d7ab97e20d827b5ed274265305908
646--- /dev/null
647+++ b/apps/conductor-ui/src/style.css
648@@ -0,0 +1,289 @@
649+:root {
650+ color: #142231;
651+ background:
652+ radial-gradient(circle at top left, rgba(255, 184, 92, 0.28), transparent 28%),
653+ radial-gradient(circle at top right, rgba(22, 119, 122, 0.2), transparent 32%),
654+ linear-gradient(180deg, #fffaf2 0%, #f3f8f7 100%);
655+ font-family: "Avenir Next", "Segoe UI", sans-serif;
656+ font-synthesis: none;
657+ line-height: 1.5;
658+ text-rendering: optimizeLegibility;
659+ -webkit-font-smoothing: antialiased;
660+ -moz-osx-font-smoothing: grayscale;
661+}
662+
663+* {
664+ box-sizing: border-box;
665+}
666+
667+html,
668+body,
669+#app {
670+ margin: 0;
671+ min-height: 100%;
672+}
673+
674+body {
675+ min-width: 320px;
676+}
677+
678+a {
679+ color: inherit;
680+ text-decoration: none;
681+}
682+
683+.shell-page {
684+ min-height: 100vh;
685+ overflow: hidden;
686+ position: relative;
687+}
688+
689+.shell-backdrop {
690+ filter: blur(18px);
691+ position: absolute;
692+ transform: rotate(-6deg);
693+}
694+
695+.shell-backdrop-left {
696+ background: rgba(244, 179, 84, 0.16);
697+ border-radius: 48px;
698+ height: 14rem;
699+ left: -4rem;
700+ top: 5rem;
701+ width: 14rem;
702+}
703+
704+.shell-backdrop-right {
705+ background: rgba(20, 107, 110, 0.12);
706+ border-radius: 48px;
707+ height: 20rem;
708+ right: -4rem;
709+ top: 12rem;
710+ width: 20rem;
711+}
712+
713+.shell-frame {
714+ display: grid;
715+ gap: 1.5rem;
716+ grid-template-columns: minmax(16rem, 22rem) minmax(0, 1fr);
717+ margin: 0 auto;
718+ max-width: 86rem;
719+ padding: 2rem;
720+ position: relative;
721+}
722+
723+.shell-sidebar,
724+.shell-main {
725+ backdrop-filter: blur(14px);
726+ background: rgba(255, 255, 255, 0.72);
727+ border: 1px solid rgba(20, 34, 49, 0.08);
728+ border-radius: 1.75rem;
729+ box-shadow: 0 24px 60px rgba(34, 56, 72, 0.08);
730+}
731+
732+.shell-sidebar {
733+ display: flex;
734+ flex-direction: column;
735+ gap: 1.25rem;
736+ padding: 1.5rem;
737+}
738+
739+.shell-main {
740+ display: flex;
741+ flex-direction: column;
742+ gap: 1.25rem;
743+ padding: 1.5rem;
744+}
745+
746+.shell-brand h1,
747+.hero-card h2,
748+.content-card h3 {
749+ font-family: "Iowan Old Style", "Palatino Linotype", "Book Antiqua", serif;
750+ letter-spacing: -0.03em;
751+ margin: 0;
752+}
753+
754+.shell-brand h1 {
755+ font-size: clamp(2rem, 3vw, 2.8rem);
756+ line-height: 1.05;
757+}
758+
759+.hero-card h2 {
760+ font-size: clamp(1.8rem, 2.6vw, 2.4rem);
761+}
762+
763+.eyebrow {
764+ color: #0f696d;
765+ font-size: 0.78rem;
766+ font-weight: 700;
767+ letter-spacing: 0.14em;
768+ margin: 0 0 0.5rem;
769+ text-transform: uppercase;
770+}
771+
772+.lede,
773+.content-copy,
774+.session-copy {
775+ color: #425566;
776+ margin: 0;
777+}
778+
779+.session-card,
780+.sidebar-panel,
781+.content-card,
782+.hero-card {
783+ border: 1px solid rgba(20, 34, 49, 0.08);
784+ border-radius: 1.25rem;
785+ padding: 1.1rem 1.2rem;
786+}
787+
788+.session-card {
789+ background: linear-gradient(135deg, rgba(255, 236, 208, 0.8), rgba(255, 255, 255, 0.7));
790+}
791+
792+.sidebar-panel,
793+.content-card,
794+.hero-card {
795+ background: rgba(255, 255, 255, 0.7);
796+}
797+
798+.sidebar-title,
799+.card-title {
800+ font-size: 0.95rem;
801+ font-weight: 700;
802+ margin: 0 0 0.8rem;
803+}
804+
805+.sidebar-list,
806+.detail-list {
807+ color: #425566;
808+ display: grid;
809+ gap: 0.55rem;
810+ margin: 0;
811+ padding-left: 1.2rem;
812+}
813+
814+.nav-list {
815+ display: grid;
816+ gap: 0.7rem;
817+}
818+
819+.nav-item {
820+ background: rgba(245, 249, 248, 0.72);
821+ border: 1px solid rgba(20, 34, 49, 0.06);
822+ border-radius: 1rem;
823+ display: block;
824+ padding: 0.95rem 1rem;
825+ transition:
826+ transform 140ms ease,
827+ border-color 140ms ease,
828+ background 140ms ease;
829+}
830+
831+.nav-item:hover {
832+ border-color: rgba(15, 105, 109, 0.28);
833+ transform: translateY(-1px);
834+}
835+
836+.nav-item-active {
837+ background: linear-gradient(135deg, rgba(15, 105, 109, 0.12), rgba(244, 179, 84, 0.08));
838+ border-color: rgba(15, 105, 109, 0.26);
839+}
840+
841+.nav-label {
842+ display: block;
843+ font-size: 1rem;
844+ font-weight: 700;
845+}
846+
847+.nav-note {
848+ color: #587083;
849+ display: block;
850+ font-size: 0.88rem;
851+ margin-top: 0.2rem;
852+}
853+
854+.hero-card {
855+ align-items: center;
856+ display: flex;
857+ justify-content: space-between;
858+ gap: 1rem;
859+}
860+
861+.hero-meta {
862+ display: flex;
863+ flex-wrap: wrap;
864+ gap: 0.55rem;
865+}
866+
867+.status-badge {
868+ border-radius: 999px;
869+ display: inline-flex;
870+ font-size: 0.78rem;
871+ font-weight: 700;
872+ padding: 0.34rem 0.7rem;
873+}
874+
875+.status-badge-info {
876+ background: rgba(15, 105, 109, 0.12);
877+ color: #0f696d;
878+}
879+
880+.status-badge-muted {
881+ background: rgba(66, 85, 102, 0.12);
882+ color: #425566;
883+}
884+
885+.status-badge-warn {
886+ background: rgba(244, 179, 84, 0.22);
887+ color: #8b5c12;
888+}
889+
890+.content-grid {
891+ display: grid;
892+ gap: 1rem;
893+ grid-template-columns: repeat(2, minmax(0, 1fr));
894+}
895+
896+.content-card-primary {
897+ background: linear-gradient(135deg, rgba(255, 246, 230, 0.92), rgba(237, 247, 245, 0.92));
898+ grid-column: 1 / -1;
899+}
900+
901+.metric-list {
902+ display: grid;
903+ gap: 0.85rem;
904+}
905+
906+.metric-item {
907+ align-items: baseline;
908+ display: flex;
909+ gap: 0.8rem;
910+ justify-content: space-between;
911+}
912+
913+.metric-item strong {
914+ font-size: 1.02rem;
915+}
916+
917+.metric-label {
918+ color: #587083;
919+ font-size: 0.9rem;
920+}
921+
922+@media (max-width: 860px) {
923+ .shell-frame {
924+ grid-template-columns: minmax(0, 1fr);
925+ padding: 1rem;
926+ }
927+
928+ .hero-card,
929+ .metric-item {
930+ align-items: flex-start;
931+ flex-direction: column;
932+ }
933+
934+ .content-grid {
935+ grid-template-columns: minmax(0, 1fr);
936+ }
937+}
938diff --git a/apps/conductor-ui/src/views/ChannelsPlaceholderView.vue b/apps/conductor-ui/src/views/ChannelsPlaceholderView.vue
939new file mode 100644
940index 0000000000000000000000000000000000000000..b8a51466069576abbc71057d8d412c962733f31c
941--- /dev/null
942+++ b/apps/conductor-ui/src/views/ChannelsPlaceholderView.vue
943@@ -0,0 +1,20 @@
944+<template>
945+ <section class="content-grid">
946+ <article class="content-card content-card-primary">
947+ <p class="eyebrow">Channels</p>
948+ <h3>频道会成为人类和多平台成员协作的正式线程主体。</h3>
949+ <p class="content-copy">
950+ 这个阶段先占住 `/app/channels` 路由,不把插件调试页误导成正式入口。
951+ </p>
952+ </article>
953+
954+ <article class="content-card">
955+ <p class="card-title">后续任务</p>
956+ <ul class="detail-list">
957+ <li>只读频道列表与详情页</li>
958+ <li>消息、artifact、execution 线程聚合</li>
959+ <li>成员管理和正式写路径</li>
960+ </ul>
961+ </article>
962+ </section>
963+</template>
964diff --git a/apps/conductor-ui/src/views/ControlOverviewView.vue b/apps/conductor-ui/src/views/ControlOverviewView.vue
965new file mode 100644
966index 0000000000000000000000000000000000000000..ef4ebdff02a0c079818a2b22c0860cc5d3390482
967--- /dev/null
968+++ b/apps/conductor-ui/src/views/ControlOverviewView.vue
969@@ -0,0 +1,40 @@
970+<template>
971+ <section class="content-grid">
972+ <article class="content-card content-card-primary">
973+ <p class="eyebrow">Control</p>
974+ <h3>首版工作区会优先承载系统总览和浏览器运维。</h3>
975+ <p class="content-copy">
976+ 当前壳先固定信息架构和视觉层级,后续直接接入 `/v1/system/state`、`/v1/browser`、
977+ `/v1/controllers`、`/v1/tasks`、`/v1/runs`、`/v1/codex`。
978+ </p>
979+ </article>
980+
981+ <article class="content-card">
982+ <p class="card-title">计划接入</p>
983+ <ul class="detail-list">
984+ <li>系统模式、leader 与 queue depth</li>
985+ <li>browser bridge、shell runtime、登录态记录</li>
986+ <li>renewal conversations、links、jobs</li>
987+ <li>tasks / runs / codex sessions 概览</li>
988+ </ul>
989+ </article>
990+
991+ <article class="content-card">
992+ <p class="card-title">当前状态</p>
993+ <div class="metric-list">
994+ <div class="metric-item">
995+ <span class="metric-label">入口</span>
996+ <strong>/app/control</strong>
997+ </div>
998+ <div class="metric-item">
999+ <span class="metric-label">读接口</span>
1000+ <strong>待接入</strong>
1001+ </div>
1002+ <div class="metric-item">
1003+ <span class="metric-label">写接口</span>
1004+ <strong>鉴权后开放</strong>
1005+ </div>
1006+ </div>
1007+ </article>
1008+ </section>
1009+</template>
1010diff --git a/apps/conductor-ui/src/views/LoginPlaceholderView.vue b/apps/conductor-ui/src/views/LoginPlaceholderView.vue
1011new file mode 100644
1012index 0000000000000000000000000000000000000000..f401f74fa1347ce23598e44408227a51c76fbc24
1013--- /dev/null
1014+++ b/apps/conductor-ui/src/views/LoginPlaceholderView.vue
1015@@ -0,0 +1,20 @@
1016+<template>
1017+ <section class="content-grid">
1018+ <article class="content-card content-card-primary">
1019+ <p class="eyebrow">Login</p>
1020+ <h3>浏览器 session 登录页将在下一阶段落地。</h3>
1021+ <p class="content-copy">
1022+ 当前页面只明确声明一件事:这个工作台还没有接入正式登录态,不应被误判成可直接执行写操作的管理面。
1023+ </p>
1024+ </article>
1025+
1026+ <article class="content-card">
1027+ <p class="card-title">设计约束</p>
1028+ <ul class="detail-list">
1029+ <li>不在浏览器端保存长期共享 token</li>
1030+ <li>优先使用短期 session cookie</li>
1031+ <li>区分 `browser_admin` 与 `readonly`</li>
1032+ </ul>
1033+ </article>
1034+ </section>
1035+</template>
1036diff --git a/apps/conductor-ui/tsconfig.json b/apps/conductor-ui/tsconfig.json
1037new file mode 100644
1038index 0000000000000000000000000000000000000000..c24b466cf7a7b7f2137a772e61f8f215ed1bcc1c
1039--- /dev/null
1040+++ b/apps/conductor-ui/tsconfig.json
1041@@ -0,0 +1,11 @@
1042+{
1043+ "extends": "../../tsconfig.base.json",
1044+ "compilerOptions": {
1045+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
1046+ "module": "ESNext",
1047+ "moduleResolution": "Bundler",
1048+ "jsx": "preserve",
1049+ "types": ["vite/client"]
1050+ },
1051+ "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"]
1052+}
1053diff --git a/apps/conductor-ui/vite.config.ts b/apps/conductor-ui/vite.config.ts
1054new file mode 100644
1055index 0000000000000000000000000000000000000000..ca238eab7a41f2a53f18727635ff4b44fc628327
1056--- /dev/null
1057+++ b/apps/conductor-ui/vite.config.ts
1058@@ -0,0 +1,24 @@
1059+import { defineConfig } from "vite";
1060+import vue from "@vitejs/plugin-vue";
1061+
1062+const CONDUCTOR_API_TARGET = "http://100.71.210.78:4317";
1063+
1064+export default defineConfig({
1065+ base: "/app/",
1066+ plugins: [vue()],
1067+ server: {
1068+ host: "127.0.0.1",
1069+ port: 4318,
1070+ proxy: {
1071+ "/artifact": CONDUCTOR_API_TARGET,
1072+ "/describe": CONDUCTOR_API_TARGET,
1073+ "/health": CONDUCTOR_API_TARGET,
1074+ "/robots.txt": CONDUCTOR_API_TARGET,
1075+ "/v1": CONDUCTOR_API_TARGET,
1076+ "/version": CONDUCTOR_API_TARGET
1077+ }
1078+ },
1079+ build: {
1080+ outDir: "dist"
1081+ }
1082+});
1083diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
1084index c536902b68d285e0c875ffb9cf4c1f3b1adefc86..19be29bc356734af753e96691031962ad5a2c22c 100644
1085--- a/pnpm-lock.yaml
1086+++ b/pnpm-lock.yaml
1087@@ -31,6 +31,28 @@ importers:
1088 specifier: workspace:*
1089 version: link:../../packages/host-ops
1090
1091+ apps/conductor-ui:
1092+ dependencies:
1093+ vue:
1094+ specifier: ^3.5.18
1095+ version: 3.5.32(typescript@5.9.3)
1096+ vue-router:
1097+ specifier: ^4.5.1
1098+ version: 4.6.4(vue@3.5.32(typescript@5.9.3))
1099+ devDependencies:
1100+ '@vitejs/plugin-vue':
1101+ specifier: ^6.0.1
1102+ version: 6.0.5(vite@7.3.1)(vue@3.5.32(typescript@5.9.3))
1103+ typescript:
1104+ specifier: ^5.8.2
1105+ version: 5.9.3
1106+ vite:
1107+ specifier: ^7.1.1
1108+ version: 7.3.1
1109+ vue-tsc:
1110+ specifier: ^3.0.3
1111+ version: 3.2.6(typescript@5.9.3)
1112+
1113 apps/status-api: {}
1114
1115 apps/worker-runner: {}
1116@@ -63,11 +85,893 @@ importers:
1117
1118 packages:
1119
1120+ '@babel/helper-string-parser@7.27.1':
1121+ resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
1122+ engines: {node: '>=6.9.0'}
1123+
1124+ '@babel/helper-validator-identifier@7.28.5':
1125+ resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
1126+ engines: {node: '>=6.9.0'}
1127+
1128+ '@babel/parser@7.29.2':
1129+ resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==}
1130+ engines: {node: '>=6.0.0'}
1131+ hasBin: true
1132+
1133+ '@babel/types@7.29.0':
1134+ resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
1135+ engines: {node: '>=6.9.0'}
1136+
1137+ '@esbuild/aix-ppc64@0.27.7':
1138+ resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==}
1139+ engines: {node: '>=18'}
1140+ cpu: [ppc64]
1141+ os: [aix]
1142+
1143+ '@esbuild/android-arm64@0.27.7':
1144+ resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==}
1145+ engines: {node: '>=18'}
1146+ cpu: [arm64]
1147+ os: [android]
1148+
1149+ '@esbuild/android-arm@0.27.7':
1150+ resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==}
1151+ engines: {node: '>=18'}
1152+ cpu: [arm]
1153+ os: [android]
1154+
1155+ '@esbuild/android-x64@0.27.7':
1156+ resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==}
1157+ engines: {node: '>=18'}
1158+ cpu: [x64]
1159+ os: [android]
1160+
1161+ '@esbuild/darwin-arm64@0.27.7':
1162+ resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==}
1163+ engines: {node: '>=18'}
1164+ cpu: [arm64]
1165+ os: [darwin]
1166+
1167+ '@esbuild/darwin-x64@0.27.7':
1168+ resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==}
1169+ engines: {node: '>=18'}
1170+ cpu: [x64]
1171+ os: [darwin]
1172+
1173+ '@esbuild/freebsd-arm64@0.27.7':
1174+ resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==}
1175+ engines: {node: '>=18'}
1176+ cpu: [arm64]
1177+ os: [freebsd]
1178+
1179+ '@esbuild/freebsd-x64@0.27.7':
1180+ resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==}
1181+ engines: {node: '>=18'}
1182+ cpu: [x64]
1183+ os: [freebsd]
1184+
1185+ '@esbuild/linux-arm64@0.27.7':
1186+ resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==}
1187+ engines: {node: '>=18'}
1188+ cpu: [arm64]
1189+ os: [linux]
1190+
1191+ '@esbuild/linux-arm@0.27.7':
1192+ resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==}
1193+ engines: {node: '>=18'}
1194+ cpu: [arm]
1195+ os: [linux]
1196+
1197+ '@esbuild/linux-ia32@0.27.7':
1198+ resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==}
1199+ engines: {node: '>=18'}
1200+ cpu: [ia32]
1201+ os: [linux]
1202+
1203+ '@esbuild/linux-loong64@0.27.7':
1204+ resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==}
1205+ engines: {node: '>=18'}
1206+ cpu: [loong64]
1207+ os: [linux]
1208+
1209+ '@esbuild/linux-mips64el@0.27.7':
1210+ resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==}
1211+ engines: {node: '>=18'}
1212+ cpu: [mips64el]
1213+ os: [linux]
1214+
1215+ '@esbuild/linux-ppc64@0.27.7':
1216+ resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==}
1217+ engines: {node: '>=18'}
1218+ cpu: [ppc64]
1219+ os: [linux]
1220+
1221+ '@esbuild/linux-riscv64@0.27.7':
1222+ resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==}
1223+ engines: {node: '>=18'}
1224+ cpu: [riscv64]
1225+ os: [linux]
1226+
1227+ '@esbuild/linux-s390x@0.27.7':
1228+ resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==}
1229+ engines: {node: '>=18'}
1230+ cpu: [s390x]
1231+ os: [linux]
1232+
1233+ '@esbuild/linux-x64@0.27.7':
1234+ resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==}
1235+ engines: {node: '>=18'}
1236+ cpu: [x64]
1237+ os: [linux]
1238+
1239+ '@esbuild/netbsd-arm64@0.27.7':
1240+ resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==}
1241+ engines: {node: '>=18'}
1242+ cpu: [arm64]
1243+ os: [netbsd]
1244+
1245+ '@esbuild/netbsd-x64@0.27.7':
1246+ resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==}
1247+ engines: {node: '>=18'}
1248+ cpu: [x64]
1249+ os: [netbsd]
1250+
1251+ '@esbuild/openbsd-arm64@0.27.7':
1252+ resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==}
1253+ engines: {node: '>=18'}
1254+ cpu: [arm64]
1255+ os: [openbsd]
1256+
1257+ '@esbuild/openbsd-x64@0.27.7':
1258+ resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==}
1259+ engines: {node: '>=18'}
1260+ cpu: [x64]
1261+ os: [openbsd]
1262+
1263+ '@esbuild/openharmony-arm64@0.27.7':
1264+ resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==}
1265+ engines: {node: '>=18'}
1266+ cpu: [arm64]
1267+ os: [openharmony]
1268+
1269+ '@esbuild/sunos-x64@0.27.7':
1270+ resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==}
1271+ engines: {node: '>=18'}
1272+ cpu: [x64]
1273+ os: [sunos]
1274+
1275+ '@esbuild/win32-arm64@0.27.7':
1276+ resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==}
1277+ engines: {node: '>=18'}
1278+ cpu: [arm64]
1279+ os: [win32]
1280+
1281+ '@esbuild/win32-ia32@0.27.7':
1282+ resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==}
1283+ engines: {node: '>=18'}
1284+ cpu: [ia32]
1285+ os: [win32]
1286+
1287+ '@esbuild/win32-x64@0.27.7':
1288+ resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==}
1289+ engines: {node: '>=18'}
1290+ cpu: [x64]
1291+ os: [win32]
1292+
1293+ '@jridgewell/sourcemap-codec@1.5.5':
1294+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
1295+
1296+ '@rolldown/pluginutils@1.0.0-rc.2':
1297+ resolution: {integrity: sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==}
1298+
1299+ '@rollup/rollup-android-arm-eabi@4.60.1':
1300+ resolution: {integrity: sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==}
1301+ cpu: [arm]
1302+ os: [android]
1303+
1304+ '@rollup/rollup-android-arm64@4.60.1':
1305+ resolution: {integrity: sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==}
1306+ cpu: [arm64]
1307+ os: [android]
1308+
1309+ '@rollup/rollup-darwin-arm64@4.60.1':
1310+ resolution: {integrity: sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==}
1311+ cpu: [arm64]
1312+ os: [darwin]
1313+
1314+ '@rollup/rollup-darwin-x64@4.60.1':
1315+ resolution: {integrity: sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==}
1316+ cpu: [x64]
1317+ os: [darwin]
1318+
1319+ '@rollup/rollup-freebsd-arm64@4.60.1':
1320+ resolution: {integrity: sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==}
1321+ cpu: [arm64]
1322+ os: [freebsd]
1323+
1324+ '@rollup/rollup-freebsd-x64@4.60.1':
1325+ resolution: {integrity: sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==}
1326+ cpu: [x64]
1327+ os: [freebsd]
1328+
1329+ '@rollup/rollup-linux-arm-gnueabihf@4.60.1':
1330+ resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==}
1331+ cpu: [arm]
1332+ os: [linux]
1333+
1334+ '@rollup/rollup-linux-arm-musleabihf@4.60.1':
1335+ resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==}
1336+ cpu: [arm]
1337+ os: [linux]
1338+
1339+ '@rollup/rollup-linux-arm64-gnu@4.60.1':
1340+ resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==}
1341+ cpu: [arm64]
1342+ os: [linux]
1343+
1344+ '@rollup/rollup-linux-arm64-musl@4.60.1':
1345+ resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==}
1346+ cpu: [arm64]
1347+ os: [linux]
1348+
1349+ '@rollup/rollup-linux-loong64-gnu@4.60.1':
1350+ resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==}
1351+ cpu: [loong64]
1352+ os: [linux]
1353+
1354+ '@rollup/rollup-linux-loong64-musl@4.60.1':
1355+ resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==}
1356+ cpu: [loong64]
1357+ os: [linux]
1358+
1359+ '@rollup/rollup-linux-ppc64-gnu@4.60.1':
1360+ resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==}
1361+ cpu: [ppc64]
1362+ os: [linux]
1363+
1364+ '@rollup/rollup-linux-ppc64-musl@4.60.1':
1365+ resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==}
1366+ cpu: [ppc64]
1367+ os: [linux]
1368+
1369+ '@rollup/rollup-linux-riscv64-gnu@4.60.1':
1370+ resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==}
1371+ cpu: [riscv64]
1372+ os: [linux]
1373+
1374+ '@rollup/rollup-linux-riscv64-musl@4.60.1':
1375+ resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==}
1376+ cpu: [riscv64]
1377+ os: [linux]
1378+
1379+ '@rollup/rollup-linux-s390x-gnu@4.60.1':
1380+ resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==}
1381+ cpu: [s390x]
1382+ os: [linux]
1383+
1384+ '@rollup/rollup-linux-x64-gnu@4.60.1':
1385+ resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==}
1386+ cpu: [x64]
1387+ os: [linux]
1388+
1389+ '@rollup/rollup-linux-x64-musl@4.60.1':
1390+ resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==}
1391+ cpu: [x64]
1392+ os: [linux]
1393+
1394+ '@rollup/rollup-openbsd-x64@4.60.1':
1395+ resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==}
1396+ cpu: [x64]
1397+ os: [openbsd]
1398+
1399+ '@rollup/rollup-openharmony-arm64@4.60.1':
1400+ resolution: {integrity: sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==}
1401+ cpu: [arm64]
1402+ os: [openharmony]
1403+
1404+ '@rollup/rollup-win32-arm64-msvc@4.60.1':
1405+ resolution: {integrity: sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==}
1406+ cpu: [arm64]
1407+ os: [win32]
1408+
1409+ '@rollup/rollup-win32-ia32-msvc@4.60.1':
1410+ resolution: {integrity: sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==}
1411+ cpu: [ia32]
1412+ os: [win32]
1413+
1414+ '@rollup/rollup-win32-x64-gnu@4.60.1':
1415+ resolution: {integrity: sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==}
1416+ cpu: [x64]
1417+ os: [win32]
1418+
1419+ '@rollup/rollup-win32-x64-msvc@4.60.1':
1420+ resolution: {integrity: sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==}
1421+ cpu: [x64]
1422+ os: [win32]
1423+
1424+ '@types/estree@1.0.8':
1425+ resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
1426+
1427+ '@vitejs/plugin-vue@6.0.5':
1428+ resolution: {integrity: sha512-bL3AxKuQySfk1iGcBsQnoRVexTPJq0Z/ixFVM8OhVJAP6ZXXXLtM7NFKWhLl30Kg7uTBqIaPXbh+nuQCuBDedg==}
1429+ engines: {node: ^20.19.0 || >=22.12.0}
1430+ peerDependencies:
1431+ vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
1432+ vue: ^3.2.25
1433+
1434+ '@volar/language-core@2.4.28':
1435+ resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==}
1436+
1437+ '@volar/source-map@2.4.28':
1438+ resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==}
1439+
1440+ '@volar/typescript@2.4.28':
1441+ resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==}
1442+
1443+ '@vue/compiler-core@3.5.32':
1444+ resolution: {integrity: sha512-4x74Tbtqnda8s/NSD6e1Dr5p1c8HdMU5RWSjMSUzb8RTcUQqevDCxVAitcLBKT+ie3o0Dl9crc/S/opJM7qBGQ==}
1445+
1446+ '@vue/compiler-dom@3.5.32':
1447+ resolution: {integrity: sha512-ybHAu70NtiEI1fvAUz3oXZqkUYEe5J98GjMDpTGl5iHb0T15wQYLR4wE3h9xfuTNA+Cm2f4czfe8B4s+CCH57Q==}
1448+
1449+ '@vue/compiler-sfc@3.5.32':
1450+ resolution: {integrity: sha512-8UYUYo71cP/0YHMO814TRZlPuUUw3oifHuMR7Wp9SNoRSrxRQnhMLNlCeaODNn6kNTJsjFoQ/kqIj4qGvya4Xg==}
1451+
1452+ '@vue/compiler-ssr@3.5.32':
1453+ resolution: {integrity: sha512-Gp4gTs22T3DgRotZ8aA/6m2jMR+GMztvBXUBEUOYOcST+giyGWJ4WvFd7QLHBkzTxkfOt8IELKNdpzITLbA2rw==}
1454+
1455+ '@vue/devtools-api@6.6.4':
1456+ resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
1457+
1458+ '@vue/language-core@3.2.6':
1459+ resolution: {integrity: sha512-xYYYX3/aVup576tP/23sEUpgiEnujrENaoNRbaozC1/MA9I6EGFQRJb4xrt/MmUCAGlxTKL2RmT8JLTPqagCkg==}
1460+
1461+ '@vue/reactivity@3.5.32':
1462+ resolution: {integrity: sha512-/ORasxSGvZ6MN5gc+uE364SxFdJ0+WqVG0CENXaGW58TOCdrAW76WWaplDtECeS1qphvtBZtR+3/o1g1zL4xPQ==}
1463+
1464+ '@vue/runtime-core@3.5.32':
1465+ resolution: {integrity: sha512-pDrXCejn4UpFDFmMd27AcJEbHaLemaE5o4pbb7sLk79SRIhc6/t34BQA7SGNgYtbMnvbF/HHOftYBgFJtUoJUQ==}
1466+
1467+ '@vue/runtime-dom@3.5.32':
1468+ resolution: {integrity: sha512-1CDVv7tv/IV13V8Nip1k/aaObVbWqRlVCVezTwx3K07p7Vxossp5JU1dcPNhJk3w347gonIUT9jQOGutyJrSVQ==}
1469+
1470+ '@vue/server-renderer@3.5.32':
1471+ resolution: {integrity: sha512-IOjm2+JQwRFS7W28HNuJeXQle9KdZbODFY7hFGVtnnghF51ta20EWAZJHX+zLGtsHhaU6uC9BGPV52KVpYryMQ==}
1472+ peerDependencies:
1473+ vue: 3.5.32
1474+
1475+ '@vue/shared@3.5.32':
1476+ resolution: {integrity: sha512-ksNyrmRQzWJJ8n3cRDuSF7zNNontuJg1YHnmWRJd2AMu8Ij2bqwiiri2lH5rHtYPZjj4STkNcgcmiQqlOjiYGg==}
1477+
1478+ alien-signals@3.1.2:
1479+ resolution: {integrity: sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==}
1480+
1481+ csstype@3.2.3:
1482+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
1483+
1484+ entities@7.0.1:
1485+ resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
1486+ engines: {node: '>=0.12'}
1487+
1488+ esbuild@0.27.7:
1489+ resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==}
1490+ engines: {node: '>=18'}
1491+ hasBin: true
1492+
1493+ estree-walker@2.0.2:
1494+ resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
1495+
1496+ fdir@6.5.0:
1497+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
1498+ engines: {node: '>=12.0.0'}
1499+ peerDependencies:
1500+ picomatch: ^3 || ^4
1501+ peerDependenciesMeta:
1502+ picomatch:
1503+ optional: true
1504+
1505+ fsevents@2.3.3:
1506+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
1507+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
1508+ os: [darwin]
1509+
1510+ magic-string@0.30.21:
1511+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
1512+
1513+ muggle-string@0.4.1:
1514+ resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
1515+
1516+ nanoid@3.3.11:
1517+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
1518+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
1519+ hasBin: true
1520+
1521+ path-browserify@1.0.1:
1522+ resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
1523+
1524+ picocolors@1.1.1:
1525+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
1526+
1527+ picomatch@4.0.4:
1528+ resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
1529+ engines: {node: '>=12'}
1530+
1531+ postcss@8.5.8:
1532+ resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
1533+ engines: {node: ^10 || ^12 || >=14}
1534+
1535+ rollup@4.60.1:
1536+ resolution: {integrity: sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==}
1537+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
1538+ hasBin: true
1539+
1540+ source-map-js@1.2.1:
1541+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
1542+ engines: {node: '>=0.10.0'}
1543+
1544+ tinyglobby@0.2.15:
1545+ resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
1546+ engines: {node: '>=12.0.0'}
1547+
1548 typescript@5.9.3:
1549 resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
1550 engines: {node: '>=14.17'}
1551 hasBin: true
1552
1553+ vite@7.3.1:
1554+ resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==}
1555+ engines: {node: ^20.19.0 || >=22.12.0}
1556+ hasBin: true
1557+ peerDependencies:
1558+ '@types/node': ^20.19.0 || >=22.12.0
1559+ jiti: '>=1.21.0'
1560+ less: ^4.0.0
1561+ lightningcss: ^1.21.0
1562+ sass: ^1.70.0
1563+ sass-embedded: ^1.70.0
1564+ stylus: '>=0.54.8'
1565+ sugarss: ^5.0.0
1566+ terser: ^5.16.0
1567+ tsx: ^4.8.1
1568+ yaml: ^2.4.2
1569+ peerDependenciesMeta:
1570+ '@types/node':
1571+ optional: true
1572+ jiti:
1573+ optional: true
1574+ less:
1575+ optional: true
1576+ lightningcss:
1577+ optional: true
1578+ sass:
1579+ optional: true
1580+ sass-embedded:
1581+ optional: true
1582+ stylus:
1583+ optional: true
1584+ sugarss:
1585+ optional: true
1586+ terser:
1587+ optional: true
1588+ tsx:
1589+ optional: true
1590+ yaml:
1591+ optional: true
1592+
1593+ vscode-uri@3.1.0:
1594+ resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
1595+
1596+ vue-router@4.6.4:
1597+ resolution: {integrity: sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==}
1598+ peerDependencies:
1599+ vue: ^3.5.0
1600+
1601+ vue-tsc@3.2.6:
1602+ resolution: {integrity: sha512-gYW/kWI0XrwGzd0PKc7tVB/qpdeAkIZLNZb10/InizkQjHjnT8weZ/vBarZoj4kHKbUTZT/bAVgoOr8x4NsQ/Q==}
1603+ hasBin: true
1604+ peerDependencies:
1605+ typescript: '>=5.0.0'
1606+
1607+ vue@3.5.32:
1608+ resolution: {integrity: sha512-vM4z4Q9tTafVfMAK7IVzmxg34rSzTFMyIe0UUEijUCkn9+23lj0WRfA83dg7eQZIUlgOSGrkViIaCfqSAUXsMw==}
1609+ peerDependencies:
1610+ typescript: '*'
1611+ peerDependenciesMeta:
1612+ typescript:
1613+ optional: true
1614+
1615 snapshots:
1616
1617+ '@babel/helper-string-parser@7.27.1': {}
1618+
1619+ '@babel/helper-validator-identifier@7.28.5': {}
1620+
1621+ '@babel/parser@7.29.2':
1622+ dependencies:
1623+ '@babel/types': 7.29.0
1624+
1625+ '@babel/types@7.29.0':
1626+ dependencies:
1627+ '@babel/helper-string-parser': 7.27.1
1628+ '@babel/helper-validator-identifier': 7.28.5
1629+
1630+ '@esbuild/aix-ppc64@0.27.7':
1631+ optional: true
1632+
1633+ '@esbuild/android-arm64@0.27.7':
1634+ optional: true
1635+
1636+ '@esbuild/android-arm@0.27.7':
1637+ optional: true
1638+
1639+ '@esbuild/android-x64@0.27.7':
1640+ optional: true
1641+
1642+ '@esbuild/darwin-arm64@0.27.7':
1643+ optional: true
1644+
1645+ '@esbuild/darwin-x64@0.27.7':
1646+ optional: true
1647+
1648+ '@esbuild/freebsd-arm64@0.27.7':
1649+ optional: true
1650+
1651+ '@esbuild/freebsd-x64@0.27.7':
1652+ optional: true
1653+
1654+ '@esbuild/linux-arm64@0.27.7':
1655+ optional: true
1656+
1657+ '@esbuild/linux-arm@0.27.7':
1658+ optional: true
1659+
1660+ '@esbuild/linux-ia32@0.27.7':
1661+ optional: true
1662+
1663+ '@esbuild/linux-loong64@0.27.7':
1664+ optional: true
1665+
1666+ '@esbuild/linux-mips64el@0.27.7':
1667+ optional: true
1668+
1669+ '@esbuild/linux-ppc64@0.27.7':
1670+ optional: true
1671+
1672+ '@esbuild/linux-riscv64@0.27.7':
1673+ optional: true
1674+
1675+ '@esbuild/linux-s390x@0.27.7':
1676+ optional: true
1677+
1678+ '@esbuild/linux-x64@0.27.7':
1679+ optional: true
1680+
1681+ '@esbuild/netbsd-arm64@0.27.7':
1682+ optional: true
1683+
1684+ '@esbuild/netbsd-x64@0.27.7':
1685+ optional: true
1686+
1687+ '@esbuild/openbsd-arm64@0.27.7':
1688+ optional: true
1689+
1690+ '@esbuild/openbsd-x64@0.27.7':
1691+ optional: true
1692+
1693+ '@esbuild/openharmony-arm64@0.27.7':
1694+ optional: true
1695+
1696+ '@esbuild/sunos-x64@0.27.7':
1697+ optional: true
1698+
1699+ '@esbuild/win32-arm64@0.27.7':
1700+ optional: true
1701+
1702+ '@esbuild/win32-ia32@0.27.7':
1703+ optional: true
1704+
1705+ '@esbuild/win32-x64@0.27.7':
1706+ optional: true
1707+
1708+ '@jridgewell/sourcemap-codec@1.5.5': {}
1709+
1710+ '@rolldown/pluginutils@1.0.0-rc.2': {}
1711+
1712+ '@rollup/rollup-android-arm-eabi@4.60.1':
1713+ optional: true
1714+
1715+ '@rollup/rollup-android-arm64@4.60.1':
1716+ optional: true
1717+
1718+ '@rollup/rollup-darwin-arm64@4.60.1':
1719+ optional: true
1720+
1721+ '@rollup/rollup-darwin-x64@4.60.1':
1722+ optional: true
1723+
1724+ '@rollup/rollup-freebsd-arm64@4.60.1':
1725+ optional: true
1726+
1727+ '@rollup/rollup-freebsd-x64@4.60.1':
1728+ optional: true
1729+
1730+ '@rollup/rollup-linux-arm-gnueabihf@4.60.1':
1731+ optional: true
1732+
1733+ '@rollup/rollup-linux-arm-musleabihf@4.60.1':
1734+ optional: true
1735+
1736+ '@rollup/rollup-linux-arm64-gnu@4.60.1':
1737+ optional: true
1738+
1739+ '@rollup/rollup-linux-arm64-musl@4.60.1':
1740+ optional: true
1741+
1742+ '@rollup/rollup-linux-loong64-gnu@4.60.1':
1743+ optional: true
1744+
1745+ '@rollup/rollup-linux-loong64-musl@4.60.1':
1746+ optional: true
1747+
1748+ '@rollup/rollup-linux-ppc64-gnu@4.60.1':
1749+ optional: true
1750+
1751+ '@rollup/rollup-linux-ppc64-musl@4.60.1':
1752+ optional: true
1753+
1754+ '@rollup/rollup-linux-riscv64-gnu@4.60.1':
1755+ optional: true
1756+
1757+ '@rollup/rollup-linux-riscv64-musl@4.60.1':
1758+ optional: true
1759+
1760+ '@rollup/rollup-linux-s390x-gnu@4.60.1':
1761+ optional: true
1762+
1763+ '@rollup/rollup-linux-x64-gnu@4.60.1':
1764+ optional: true
1765+
1766+ '@rollup/rollup-linux-x64-musl@4.60.1':
1767+ optional: true
1768+
1769+ '@rollup/rollup-openbsd-x64@4.60.1':
1770+ optional: true
1771+
1772+ '@rollup/rollup-openharmony-arm64@4.60.1':
1773+ optional: true
1774+
1775+ '@rollup/rollup-win32-arm64-msvc@4.60.1':
1776+ optional: true
1777+
1778+ '@rollup/rollup-win32-ia32-msvc@4.60.1':
1779+ optional: true
1780+
1781+ '@rollup/rollup-win32-x64-gnu@4.60.1':
1782+ optional: true
1783+
1784+ '@rollup/rollup-win32-x64-msvc@4.60.1':
1785+ optional: true
1786+
1787+ '@types/estree@1.0.8': {}
1788+
1789+ '@vitejs/plugin-vue@6.0.5(vite@7.3.1)(vue@3.5.32(typescript@5.9.3))':
1790+ dependencies:
1791+ '@rolldown/pluginutils': 1.0.0-rc.2
1792+ vite: 7.3.1
1793+ vue: 3.5.32(typescript@5.9.3)
1794+
1795+ '@volar/language-core@2.4.28':
1796+ dependencies:
1797+ '@volar/source-map': 2.4.28
1798+
1799+ '@volar/source-map@2.4.28': {}
1800+
1801+ '@volar/typescript@2.4.28':
1802+ dependencies:
1803+ '@volar/language-core': 2.4.28
1804+ path-browserify: 1.0.1
1805+ vscode-uri: 3.1.0
1806+
1807+ '@vue/compiler-core@3.5.32':
1808+ dependencies:
1809+ '@babel/parser': 7.29.2
1810+ '@vue/shared': 3.5.32
1811+ entities: 7.0.1
1812+ estree-walker: 2.0.2
1813+ source-map-js: 1.2.1
1814+
1815+ '@vue/compiler-dom@3.5.32':
1816+ dependencies:
1817+ '@vue/compiler-core': 3.5.32
1818+ '@vue/shared': 3.5.32
1819+
1820+ '@vue/compiler-sfc@3.5.32':
1821+ dependencies:
1822+ '@babel/parser': 7.29.2
1823+ '@vue/compiler-core': 3.5.32
1824+ '@vue/compiler-dom': 3.5.32
1825+ '@vue/compiler-ssr': 3.5.32
1826+ '@vue/shared': 3.5.32
1827+ estree-walker: 2.0.2
1828+ magic-string: 0.30.21
1829+ postcss: 8.5.8
1830+ source-map-js: 1.2.1
1831+
1832+ '@vue/compiler-ssr@3.5.32':
1833+ dependencies:
1834+ '@vue/compiler-dom': 3.5.32
1835+ '@vue/shared': 3.5.32
1836+
1837+ '@vue/devtools-api@6.6.4': {}
1838+
1839+ '@vue/language-core@3.2.6':
1840+ dependencies:
1841+ '@volar/language-core': 2.4.28
1842+ '@vue/compiler-dom': 3.5.32
1843+ '@vue/shared': 3.5.32
1844+ alien-signals: 3.1.2
1845+ muggle-string: 0.4.1
1846+ path-browserify: 1.0.1
1847+ picomatch: 4.0.4
1848+
1849+ '@vue/reactivity@3.5.32':
1850+ dependencies:
1851+ '@vue/shared': 3.5.32
1852+
1853+ '@vue/runtime-core@3.5.32':
1854+ dependencies:
1855+ '@vue/reactivity': 3.5.32
1856+ '@vue/shared': 3.5.32
1857+
1858+ '@vue/runtime-dom@3.5.32':
1859+ dependencies:
1860+ '@vue/reactivity': 3.5.32
1861+ '@vue/runtime-core': 3.5.32
1862+ '@vue/shared': 3.5.32
1863+ csstype: 3.2.3
1864+
1865+ '@vue/server-renderer@3.5.32(vue@3.5.32(typescript@5.9.3))':
1866+ dependencies:
1867+ '@vue/compiler-ssr': 3.5.32
1868+ '@vue/shared': 3.5.32
1869+ vue: 3.5.32(typescript@5.9.3)
1870+
1871+ '@vue/shared@3.5.32': {}
1872+
1873+ alien-signals@3.1.2: {}
1874+
1875+ csstype@3.2.3: {}
1876+
1877+ entities@7.0.1: {}
1878+
1879+ esbuild@0.27.7:
1880+ optionalDependencies:
1881+ '@esbuild/aix-ppc64': 0.27.7
1882+ '@esbuild/android-arm': 0.27.7
1883+ '@esbuild/android-arm64': 0.27.7
1884+ '@esbuild/android-x64': 0.27.7
1885+ '@esbuild/darwin-arm64': 0.27.7
1886+ '@esbuild/darwin-x64': 0.27.7
1887+ '@esbuild/freebsd-arm64': 0.27.7
1888+ '@esbuild/freebsd-x64': 0.27.7
1889+ '@esbuild/linux-arm': 0.27.7
1890+ '@esbuild/linux-arm64': 0.27.7
1891+ '@esbuild/linux-ia32': 0.27.7
1892+ '@esbuild/linux-loong64': 0.27.7
1893+ '@esbuild/linux-mips64el': 0.27.7
1894+ '@esbuild/linux-ppc64': 0.27.7
1895+ '@esbuild/linux-riscv64': 0.27.7
1896+ '@esbuild/linux-s390x': 0.27.7
1897+ '@esbuild/linux-x64': 0.27.7
1898+ '@esbuild/netbsd-arm64': 0.27.7
1899+ '@esbuild/netbsd-x64': 0.27.7
1900+ '@esbuild/openbsd-arm64': 0.27.7
1901+ '@esbuild/openbsd-x64': 0.27.7
1902+ '@esbuild/openharmony-arm64': 0.27.7
1903+ '@esbuild/sunos-x64': 0.27.7
1904+ '@esbuild/win32-arm64': 0.27.7
1905+ '@esbuild/win32-ia32': 0.27.7
1906+ '@esbuild/win32-x64': 0.27.7
1907+
1908+ estree-walker@2.0.2: {}
1909+
1910+ fdir@6.5.0(picomatch@4.0.4):
1911+ optionalDependencies:
1912+ picomatch: 4.0.4
1913+
1914+ fsevents@2.3.3:
1915+ optional: true
1916+
1917+ magic-string@0.30.21:
1918+ dependencies:
1919+ '@jridgewell/sourcemap-codec': 1.5.5
1920+
1921+ muggle-string@0.4.1: {}
1922+
1923+ nanoid@3.3.11: {}
1924+
1925+ path-browserify@1.0.1: {}
1926+
1927+ picocolors@1.1.1: {}
1928+
1929+ picomatch@4.0.4: {}
1930+
1931+ postcss@8.5.8:
1932+ dependencies:
1933+ nanoid: 3.3.11
1934+ picocolors: 1.1.1
1935+ source-map-js: 1.2.1
1936+
1937+ rollup@4.60.1:
1938+ dependencies:
1939+ '@types/estree': 1.0.8
1940+ optionalDependencies:
1941+ '@rollup/rollup-android-arm-eabi': 4.60.1
1942+ '@rollup/rollup-android-arm64': 4.60.1
1943+ '@rollup/rollup-darwin-arm64': 4.60.1
1944+ '@rollup/rollup-darwin-x64': 4.60.1
1945+ '@rollup/rollup-freebsd-arm64': 4.60.1
1946+ '@rollup/rollup-freebsd-x64': 4.60.1
1947+ '@rollup/rollup-linux-arm-gnueabihf': 4.60.1
1948+ '@rollup/rollup-linux-arm-musleabihf': 4.60.1
1949+ '@rollup/rollup-linux-arm64-gnu': 4.60.1
1950+ '@rollup/rollup-linux-arm64-musl': 4.60.1
1951+ '@rollup/rollup-linux-loong64-gnu': 4.60.1
1952+ '@rollup/rollup-linux-loong64-musl': 4.60.1
1953+ '@rollup/rollup-linux-ppc64-gnu': 4.60.1
1954+ '@rollup/rollup-linux-ppc64-musl': 4.60.1
1955+ '@rollup/rollup-linux-riscv64-gnu': 4.60.1
1956+ '@rollup/rollup-linux-riscv64-musl': 4.60.1
1957+ '@rollup/rollup-linux-s390x-gnu': 4.60.1
1958+ '@rollup/rollup-linux-x64-gnu': 4.60.1
1959+ '@rollup/rollup-linux-x64-musl': 4.60.1
1960+ '@rollup/rollup-openbsd-x64': 4.60.1
1961+ '@rollup/rollup-openharmony-arm64': 4.60.1
1962+ '@rollup/rollup-win32-arm64-msvc': 4.60.1
1963+ '@rollup/rollup-win32-ia32-msvc': 4.60.1
1964+ '@rollup/rollup-win32-x64-gnu': 4.60.1
1965+ '@rollup/rollup-win32-x64-msvc': 4.60.1
1966+ fsevents: 2.3.3
1967+
1968+ source-map-js@1.2.1: {}
1969+
1970+ tinyglobby@0.2.15:
1971+ dependencies:
1972+ fdir: 6.5.0(picomatch@4.0.4)
1973+ picomatch: 4.0.4
1974+
1975 typescript@5.9.3: {}
1976+
1977+ vite@7.3.1:
1978+ dependencies:
1979+ esbuild: 0.27.7
1980+ fdir: 6.5.0(picomatch@4.0.4)
1981+ picomatch: 4.0.4
1982+ postcss: 8.5.8
1983+ rollup: 4.60.1
1984+ tinyglobby: 0.2.15
1985+ optionalDependencies:
1986+ fsevents: 2.3.3
1987+
1988+ vscode-uri@3.1.0: {}
1989+
1990+ vue-router@4.6.4(vue@3.5.32(typescript@5.9.3)):
1991+ dependencies:
1992+ '@vue/devtools-api': 6.6.4
1993+ vue: 3.5.32(typescript@5.9.3)
1994+
1995+ vue-tsc@3.2.6(typescript@5.9.3):
1996+ dependencies:
1997+ '@volar/typescript': 2.4.28
1998+ '@vue/language-core': 3.2.6
1999+ typescript: 5.9.3
2000+
2001+ vue@3.5.32(typescript@5.9.3):
2002+ dependencies:
2003+ '@vue/compiler-dom': 3.5.32
2004+ '@vue/compiler-sfc': 3.5.32
2005+ '@vue/runtime-dom': 3.5.32
2006+ '@vue/server-renderer': 3.5.32(vue@3.5.32(typescript@5.9.3))
2007+ '@vue/shared': 3.5.32
2008+ optionalDependencies:
2009+ typescript: 5.9.3
2010diff --git a/tasks/T-S070.md b/tasks/T-S070.md
2011index 520a6e9b389f2bb262f8a64c0ed5d6b596a2be0f..afdb83e53aa42efbe8848a64aa926ee2e73eae53 100644
2012--- a/tasks/T-S070.md
2013+++ b/tasks/T-S070.md
2014@@ -2,7 +2,7 @@
2015
2016 ## 状态
2017
2018-- 当前状态:`待开始`
2019+- 当前状态:`已完成`
2020 - 规模预估:`M`
2021 - 依赖任务:无
2022 - 建议执行者:`Codex`(涉及 monorepo 构建、静态资源托管和本地 API 路由接入)
2023@@ -21,7 +21,7 @@
2024
2025 - 仓库:`/Users/george/code/baa-conductor`
2026 - 分支基线:`main`
2027-- 提交:`b063524`
2028+- 提交:`68a85cf`
2029
2030 ## 分支与 worktree(强制)
2031
2032@@ -143,22 +143,42 @@
2033
2034 ### 开始执行
2035
2036-- 执行者:
2037-- 开始时间:
2038+- 执行者:`Codex`
2039+- 开始时间:`2026-04-03 14:25:00 CST`
2040 - 状态变更:`待开始` → `进行中`
2041
2042 ### 完成摘要
2043
2044-- 完成时间:
2045+- 完成时间:`2026-04-03 14:42:19 CST`
2046 - 状态变更:`进行中` → `已完成`
2047 - 修改了哪些文件:
2048+ - `apps/conductor-ui/package.json`
2049+ - `apps/conductor-ui/tsconfig.json`
2050+ - `apps/conductor-ui/vite.config.ts`
2051+ - `apps/conductor-ui/index.html`
2052+ - `apps/conductor-ui/src/*`
2053+ - `apps/conductor-daemon/package.json`
2054+ - `apps/conductor-daemon/src/local-api.ts`
2055+ - `apps/conductor-daemon/src/index.test.js`
2056+ - `pnpm-lock.yaml`
2057+ - `tasks/T-S070.md`
2058+ - `tasks/TASK_OVERVIEW.md`
2059 - 核心实现思路:
2060+ - 新建 `apps/conductor-ui/`,用 `Vue 3 + Vite + TypeScript + Vue Router` 提供最小工作台壳,固定 `base=/app/`,并在页面内明确标出“未接登录态”的占位状态
2061+ - 在 `conductor-daemon` 的 `local-api` 增加独立 `/app` 静态托管层,分别处理入口 HTML、`/app/assets/*` 构建产物和 `/app/*` history fallback,同时区分 `index.html no-store` 与 hashed assets 长缓存
2062+ - 扩展 `conductor-daemon` 测试,覆盖本地 handler 和真实 HTTP runtime 两层的 `/app`、`/app/control`、`/app/assets/*` 行为,确保不影响原有 `/v1/status/ui` 和 artifact 路由
2063 - 跑了哪些测试:
2064+ - `pnpm install`
2065+ - `pnpm -C apps/conductor-ui build`
2066+ - `pnpm -C apps/conductor-ui typecheck`
2067+ - `pnpm -C apps/conductor-daemon build`
2068+ - `pnpm -C apps/conductor-daemon test`
2069
2070 ### 执行过程中遇到的问题
2071
2072--
2073+- 新 worktree 初始没有安装前端依赖,先执行 `pnpm install` 更新 workspace 依赖和 lockfile 后才开始构建与测试
2074
2075 ### 剩余风险
2076
2077+- 当前 `/app` 仍是未鉴权的占位工作台壳;后续 `T-S071` 需要补浏览器 session 登录、登出和读写接口保护
2078 - 如果后续 UI 会话鉴权需要服务端注入运行时配置,可能还要在前端壳里补一层 `/app/config.json` 或内联 bootstrap 数据
2079diff --git a/tasks/TASK_OVERVIEW.md b/tasks/TASK_OVERVIEW.md
2080index 704310f5ec407bd56468c90df3690b3dac77e02d..d4dad569575247360594b93b32d0d7d3ca8d21af 100644
2081--- a/tasks/TASK_OVERVIEW.md
2082+++ b/tasks/TASK_OVERVIEW.md
2083@@ -3,7 +3,7 @@
2084 ## 当前基线
2085
2086 - 日期:`2026-04-03`
2087-- 主分支基线:`main@d8e883d`
2088+- 主分支基线:`main@68a85cf`
2089 - canonical local API:`http://100.71.210.78:4317`
2090 - canonical public host:`https://conductor.makefile.so`
2091 - 当前活跃任务卡和近期刚完成的任务卡保留在本目录;较早已完成任务归档到 [`./archive/README.md`](./archive/README.md)
2092@@ -61,6 +61,10 @@
2093 - renewal dispatcher
2094 - `browser.proxy_delivery`
2095 - 旧 watchdog / Safari a11y 续命方案已随 `T-S074` 从仓库移除
2096+- Conductor UI 基础设施已落地:
2097+ - 新增 `apps/conductor-ui/` Vue 3 + Vite + TypeScript + Vue Router 脚手架
2098+ - `conductor-daemon` 已开始同源托管 `/app`
2099+ - `/app`、`/app/control` 和 `/app/assets/*` 已具备最小 SPA 壳、静态资源缓存和 history fallback
2100
2101 ## 当前已确认的不一致
2102
2103@@ -104,6 +108,7 @@
2104 | [`T-S065`](./T-S065.md) | policy 配置化 | M | 无 | Codex | 已完成 |
2105 | [`T-S073`](./T-S073.md) | 移除 conductor 内的 stagit 仓库静态页能力 | M | 无 | Codex | 已完成 |
2106 | [`T-S074`](./T-S074.md) | 删除旧版 watchdog 与 Safari a11y 续命方案 | S | 无 | Codex | 已完成 |
2107+| [`T-S070`](./T-S070.md) | Conductor UI 基础设施:Vue 3 脚手架与 `/app` 静态托管 | M | 无 | Codex | 已完成 |
2108
2109 ### 当前下一波任务
2110
2111@@ -111,18 +116,17 @@
2112
2113 | 任务 | 标题 | 规模 | 依赖 | 建议 AI | 状态 |
2114 |---|---|---|---|---|---|
2115-| [`T-S070`](./T-S070.md) | Conductor UI 基础设施:Vue 3 脚手架与 `/app` 静态托管 | M | 无 | Codex | 待开始 |
2116 | [`T-S071`](./T-S071.md) | Conductor UI 会话鉴权:登录页与浏览器 session | M | T-S070 | Codex | 待开始 |
2117 | [`T-S072`](./T-S072.md) | Conductor UI `Control` 工作区首版 | L | T-S070, T-S071 | Codex | 待开始 |
2118
2119 当前没有 open bug / open opt。
2120
2121-如继续推进,建议直接回到 `T-S070 -> T-S071 -> T-S072` 的 Web UI 主线。
2122+如继续推进,建议直接进入 `T-S071 -> T-S072` 的 Web UI 主线。
2123
2124 说明:
2125
2126 - `T-S073` / `T-S074` 已完成,仓库边界收缩已结束
2127-- `T-S070` / `T-S071` / `T-S072` 仍是后续正式 Web UI 主线
2128+- `T-S070` 已完成,`T-S071` / `T-S072` 是后续正式 Web UI 主线
2129 - `Channels` 工作区和正式 `channel` 域模型继续留在 `T-S072` 之后再拆下一轮
2130
2131 ### 已完成但保留作参考
2132@@ -147,6 +151,7 @@
2133 | T-S048 | Gemini 投递适配器 | ✅ |
2134 | T-S049 | 开放 chatgpt/gemini target | ✅ |
2135 | T-S050 | stagit git 静态页面 | ✅ |
2136+| T-S070 | Conductor UI 基础设施:Vue 3 脚手架与 `/app` 静态托管 | ✅ |
2137 | T-S073 | 移除 conductor 内的 stagit 仓库静态页能力 | ✅ |
2138 | T-S074 | 删除旧版 watchdog 与 Safari a11y 续命方案 | ✅ |
2139 | T-S052 | D1 数据库初始化 | ✅ |
2140@@ -212,12 +217,12 @@
2141
2142 ## 当前主线判断
2143
2144-Phase 1(浏览器主链)、Artifact 静态服务,以及 timed-jobs + 续命主线都已完成收口。`T-S060`、`T-S061`、`T-S062`、`T-S063`、`T-S064`、`T-S065`、`T-S066`、`T-S067`、`T-S068`、`T-S069` 已全部落地;当前主线没有 open bug blocker,也没有 open opt,`T-S073` / `T-S074` 已完成,剩余待开始任务只剩后续 Web UI 主线。
2145+Phase 1(浏览器主链)、Artifact 静态服务、timed-jobs + 续命主线,以及 Web UI 基础设施都已完成收口。`T-S060`、`T-S061`、`T-S062`、`T-S063`、`T-S064`、`T-S065`、`T-S066`、`T-S067`、`T-S068`、`T-S069`、`T-S070` 已全部落地;当前主线没有 open bug blocker,也没有 open opt,`T-S073` / `T-S074` 已完成,剩余待开始任务只剩后续 Web UI 工作区与鉴权主线。
2146
2147 如果继续推进,建议:
2148
2149 - 远端 `baa-conductor-artifact` 已在 `2026-04-01` 应用 [`packages/d1-client/src/d1-setup.sql`](../packages/d1-client/src/d1-setup.sql) 的最新 schema;后续如新建或重置 D1 环境,仍需先执行同一脚本
2150-- 直接回到 `T-S070 -> T-S071 -> T-S072` 的正式 Web UI 工作台主线
2151+- 直接进入 `T-S071 -> T-S072` 的正式 Web UI 工作台主线
2152
2153 ## 现在该读什么
2154