baa-conductor


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