跳转至

P0.UA13-input-output-device-config-panel · ADR-13 LeftDock § Engine 扩展(议题 1+2 device 选择 UI)

Worker:ClaudeA · 前端 / 预计:1.0d / 优先级:P1 / 状态:dispatched 隔离:🧵 文件隔离(同 worktree 同 branch · 与 P0.UH12 + P1.A14.F4-module-library + ADR-14 active 文件路径完全正交)


🔍 触发与解锁链

触发 状态 影响
ADR-AIOS-13 v1.0 accepted(2026-06-01 19:21) ✅ user accept 8 fork ready · Phase 2 fork 5
fork 1 P5.UA13-realtime-session-service zombie(92445f5) ✅ 后端 API + WS 已就位 本 fork 调 /api/audio/devices/{inputs,outputs} + 填 store 字段
fork 2 P5.UA13-builtin-link-registry zombie(d8f0677) ✅ BuiltinLinkRegistry + realtime-loopback.json frozen loopback mode 加载 builtin link
fork 4 P0.UA13-realtime-run-store-toolbar zombie(0111d16) ✅ realtimeRunStore Pinia 7 字段 + 4 actions 本 fork 双向绑定 inputDeviceId/outputDeviceId/signalConfig
用户 6/2 11:21 双 stop fork 2+4 解锁本 fork(blocked → 可派) ✅ Phase 2 fork 5 可派 dispatched 11:50

→ 本 fork zombie 解锁:Phase 3 fork 8 P_e2e.UA13-truth(议题 1 + 议题 2 端到端 e2e · 需 selectInputDevice/selectOutputDevice/selectSignal UI 支撑)。


任务定义(基于 ADR-AIOS-13 v1.0 §2.5 + §3.1 ① + §3.2 ①)

扩展 LeftDock § Engine 段(stages/xitest/drawers/EnginePanel.vue · ADR-12 §5.3 b4a8ea2 已落 § Engine + Session + Snapshots 三段)· 追加 § Device + § Signal Config 两段 · 完成议题 1 + 议题 2 device 选择 UI 闭环。

核心范围: 1. types schema(types/audio-device.ts):InputDevice + OutputDevice + SignalConfig + AudioDevicesError schema(对齐 fork 1 后端 schema · 复用 ADR-13 §2.4 + §3.2 ① signalConfig) 2. useAudioDevices composable(composables/useAudioDevices.ts 新建):loadInputs() GET /api/audio/devices/inputs · loadOutputs() GET /api/audio/devices/outputs · refresh() · structured error · 模块级缓存(避免 panel 反复挂载重复请求) 3. EnginePanel.vue 扩展(行号正交 · 仅在末尾追加 § Device + § Signal Config 段 · 不动 § Engine + Session + Snapshots): - § Device 段:Input Device 下拉(必填) + Output Device 下拉(可选) + 错误显示 + Refresh 按钮 - mode auto-detect:<div class="xy-mode-badge"> computed:仅 input → "hardware-direct" · input + output → "loopback" · 显示在段顶部 - § Signal Config 段:仅 loopback mode 可见 · type 下拉(sine/pink/mls/multitone/sweep)+ frequency 数字输入(仅 sine 可见)+ amplitude_dbfs 数字输入(默认 -20)+ duration 数字输入 4. 双向绑定 realtimeRunStore:UI 选择 → realtimeRunStore.inputDeviceId / outputDeviceId / signalConfig · store 字段更新 → UI 显示同步(fork 4 store 已落 · 本 fork 仅消费) 5. vitest 单测:useAudioDevices(load/error/cache/refresh ≥ 5 用例)+ EnginePanel(device 渲染 + auto-detect + signalConfig 条件显示 + store 双向绑定 ≥ 5 用例) 6. Ximind 兼容性 5 项(ADR §11):AudioDevice schema 含 description 字段(自然语言)+ camelCase + structured error(code+message+recoveryHints)+ 大模型可读 device 列表 + signalConfig 含 type 语义

不在本 fork 范围: - ❌ 不实施顶栏 ▶/■(已 fork 4 落) - ❌ 不实施 capture/recapture 弹框(留 fork 7) - ❌ 不动 realtimeRunStore.ts(本 fork 仅消费 · 不改 schema) - ❌ 不动后端 API(fork 1+2 已落) - ❌ 不实施真硬件联调 e2e(留 fork 8) - ❌ 不动 ADR-12 §3 7 类 MeasurementNode 业务行为契约 / 不动 § Engine + Session + Snapshots 三段(b4a8ea2 已落 · 仅末尾追加)


完整 prompt(直接复制粘贴 worker 终端)

[U-thread]   P0.UA13-input-output-device-config-panel
[部门]       前端 P0-xishell · 推荐 skill: vuejs-typescript-best-practices
[Worker CWD] d:/work/25_claude/workspace/AlgoDepartment/04_development/
[Occupies]   P0.K-xitest-engine-panel(写 · EnginePanel.vue § Device + § Signal Config 末尾段追加)
[隔离]       🧵 文件隔离 · 仅写:
             - frontend_vue3/src/types/audio-device.ts(新建/扩展 · InputDevice + OutputDevice + SignalConfig + AudioDevicesError)
             - frontend_vue3/src/composables/useAudioDevices.ts(新建 · GET /api/audio/devices/{inputs,outputs} · 缓存 + 错误结构化)
             - frontend_vue3/src/stages/xitest/drawers/EnginePanel.vue(扩展 · 仅末尾追加 § Device + § Signal Config 两段 · 不动 § Engine + Session + Snapshots b4a8ea2 已落)
             - frontend_vue3/src/composables/__tests__/useAudioDevices.spec.ts(新建 vitest)
             - frontend_vue3/src/stages/xitest/drawers/__tests__/EnginePanel.spec.ts(新建/扩展 vitest)
             与 P0.UH12(同 stages/xitest/ 但 BottomDock + tab 体系 行号正交)+ P1.A14.F4-module-library(components/widget-library/)+ ADR-14 4 active 文件路径完全正交
[优先级]     P1 · 1.0d · Phase 2 关键路径(解锁 fork 8 e2e)
[ADR]        d:/work/25_claude/workspace/AlgoDepartment/06_docs/site-build/docs/08-implementation/40-aios/ADR/ADR-AIOS-13-xitest-realtime-dual-mode.md
             (必读 §2.5 LeftDock § Engine 扩展段 + §3.1 ① 议题 1 hardware mode device 契约 + §3.2 ① 议题 2 loopback mode signalConfig 契约 + §11 Ximind 兼容性)
[业务行为契约引用]
  ADR-13 §3.1 ①(议题 1 hardware mode):POST /api/realtime/start body 含 mode="hardware" + inputDeviceId
  ADR-13 §3.1 ④ Step 2-3:LeftDock § Engine 选 Input Device → 系统自动识别 = 物理设备 → mode auto-detect = "hardware-direct"
  ADR-13 §3.2 ①(议题 2 loopback mode):POST /api/realtime/start body 含 mode="loopback" + inputDeviceId + outputDeviceId + signalConfig{ type, frequency?, amplitude_dbfs }
  ADR-13 §3.2 ④ Step 1-3:选 input + output + 信号源 sine 1kHz -20dBFS · 系统识别 loopback mode
  ADR-13 §3.2 ③ 失败回退:input === output 红警 "Loopback requires distinct input/output device"
[参考文档](绝对路径 · worker 必读)
  - ADR-13:d:/work/25_claude/workspace/AlgoDepartment/06_docs/site-build/docs/08-implementation/40-aios/ADR/ADR-AIOS-13-xitest-realtime-dual-mode.md
    · §2.5 LeftDock § Engine 扩展段(line 316:沿用 b4a8ea2 三段 + 新增 input/output device 选择 + mode auto-detect)
    · §3.1 ①+④:hardware mode 输入契约 + 用户操作流(本 fork 实施 Step 2-3)
    · §3.2 ①+④:loopback mode 输入契约 + 用户操作流(本 fork 实施 Step 1-3)
    · §11.1+§11.2 Ximind:AudioDevice description + signal type 语义 + structured error
  - 范式参考(已 zombie · ClaudeA 同部门标本):
    · d:/work/25_claude/workspace/AlgoDepartment/06_docs/site-build/docs/08-implementation/40-aios/prompts/done/ADR-AIOS-12/P0.U-engine-session-snapshots--b4a8ea2.md
      (⭐ EnginePanel.vue § Engine + Session + Snapshots 三段范式 · 本 fork 追加 § Device + § Signal Config 两段 · 严格沿用其 design-token + 响应式横竖屏)
    · d:/work/25_claude/workspace/AlgoDepartment/06_docs/site-build/docs/08-implementation/40-aios/prompts/done/ADR-AIOS-13/P0.UA13-realtime-run-store-toolbar--0111d16.md
      (⭐ realtimeRunStore 7 字段 + 4 actions + composition API + fetch + WS 范式 · 本 fork 双向绑定 store)
  - parent zombie(必读 · 本 fork 调用其 API):
    · d:/work/25_claude/workspace/AlgoDepartment/06_docs/site-build/docs/08-implementation/40-aios/prompts/done/ADR-AIOS-13/P5.UA13-realtime-session-service--92445f5.md
      (后端 RealtimeSessionService · /api/audio/devices/{inputs,outputs} · POST /api/realtime/start request body schema)
    · d:/work/25_claude/workspace/AlgoDepartment/06_docs/site-build/docs/08-implementation/40-aios/prompts/done/ADR-AIOS-13/P5.UA13-builtin-link-registry--d8f0677.md
      (BuiltinLinkRegistry + realtime-loopback.json frozen · GET /api/builtin-links/realtime-loopback)
  - 现状必读(主仓真值核查 · Step 1 必跑):
    · frontend_vue3/src/stages/xitest/drawers/EnginePanel.vue(全文必读 · ⭐ 看 § Engine + Session + Snapshots 三段当前结构 · 末尾追加位置 · template + script + style 范式)
    · frontend_vue3/src/stores/realtimeRunStore.ts(全文必读 · ⭐ 看 7 state 字段:isRunning/activeMode/inputDeviceId/outputDeviceId/signalConfig/error/wsConnected · 双向绑定目标)
    · frontend_vue3/src/composables/(看 useAudioEngine 或类似 composable 范式 · 决定 useAudioDevices 风格)
    · 后端 backend_csharp/Routes/AudioDeviceRoutes.cs 或 Routes/RealtimeApiRoutes.cs(grep 'devices/inputs' · 确认实际 endpoint 路径 + response schema · 字段名 camelCase)

【背景】
ADR-AIOS-13 v1.0 accepted(2026-06-01 19:21)。议题 1 hardware mode + 议题 2 loopback mode 业务行为契约 ① 输入端:都需要前端 LeftDock § Engine 段提供 input device 选择(hardware/loopback 都需)+ output device 选择(loopback 需)+ signalConfig(loopback 需)+ mode auto-detect 显示。

依赖现状:
  - fork 1 P5.UA13-realtime-session-service zombie(`92445f5` · 后端 API + WS 完整 · /api/audio/devices/{inputs,outputs} 已就位)
  - fork 2 P5.UA13-builtin-link-registry zombie(`d8f0677` · realtime-loopback.json frozen + /api/builtin-links/* · loopback 模式可读取)
  - fork 4 P0.UA13-realtime-run-store-toolbar zombie(`0111d16` · realtimeRunStore Pinia 7 state + 4 actions 完整)
  - 范式参考 b4a8ea2 已落 EnginePanel.vue § Engine + Session + Snapshots 三段 · 本 fork 追加两段

3 路真值核查锚点(Step 1 必跑):
  ① EnginePanel.vue 现有结构(b4a8ea2):看末尾追加位置 + template/script/style 风格 + design-token 用法
  ② realtimeRunStore.ts(0111d16):7 state 字段精确名 + signalConfig 字段类型 · 本 fork 双向绑定不能错
  ③ /api/audio/devices/{inputs,outputs} 后端 endpoint(grep backend_csharp/Routes/* · 字段 camelCase)+ AudioDevice response schema(deviceId / friendlyName / channels / sampleRate / description?)

本 fork 核心:扩展 EnginePanel.vue 末尾 + 落 useAudioDevices composable + types schema + vitest · 0 破坏 b4a8ea2 三段已落业务 · 0 改 fork 4 store schema · 0 改后端。

【执行步骤】(7 步 · 总 1.0d)

Step 1 · 真值核查 + git pull(必跑 · 0.1d)
  - git status / git pull origin xistudio --no-rebase
  - cat frontend_vue3/src/stages/xitest/drawers/EnginePanel.vue(全文 · 输出:① § Engine 段当前结构 ② § Session 段 ③ § Snapshots 段末尾位置 ④ template 整体框架 ⑤ script setup 风格(composition API)⑥ design-token 用法实例)
  - cat frontend_vue3/src/stores/realtimeRunStore.ts(全文 · 输出:① 7 state 字段精确名 ② signalConfig 字段 type 接口 ③ activeMode union literal type)
  - grep -r 'audio/devices' backend_csharp/Routes/(输出:① 实际 endpoint 路径 ② AudioDevice response schema 字段名 ③ 是否已有 InputDevice/OutputDevice 区分 type)
  - cat frontend_vue3/src/composables/useAudioEngine.ts 或类似(若存在 · 看 composable 风格)
  - 输出真值核查到 commit message body:
    · EnginePanel.vue 现有段 = ???
    · 末尾追加位置 = line ???
    · realtimeRunStore.ts signalConfig type = ???
    · /api/audio/devices/{inputs,outputs} schema = ???
    · 后端 InputDevice / OutputDevice 区分 = ???

Step 2 · 落 types/audio-device.ts(0.15d)
  - 抄 ADR §3.1 ① + §3.2 ① · 对齐后端 schema:
    export interface AudioDevice {
      deviceId: string
      friendlyName: string
      channels: number              // device 总通道数
      sampleRates: number[]         // 支持的采样率 [44100, 48000, 96000]
      description?: string          // ⭐ Ximind 自然语言描述
    }
    export interface InputDevice extends AudioDevice {
      kind: 'input'                 // discriminated union
      isLoopbackCapable?: boolean   // XiAudioLoopback 标记
    }
    export interface OutputDevice extends AudioDevice {
      kind: 'output'
    }
    export type AnyAudioDevice = InputDevice | OutputDevice

    // ADR §3.2 ①:loopback mode 的 signalConfig
    export type SignalType = 'sine' | 'pink' | 'mls' | 'multitone' | 'sweep'
    export interface SignalConfig {
      type: SignalType
      frequency?: number              // sine 才有
      amplitudeDbfs: number           // 默认 -20
      duration?: number               // ms · sweep 才有
      description?: string            // ⭐ Ximind
    }

    // 错误结构化
    export interface AudioDevicesError {
      code: 'DEVICES_LOAD_FAILED' | 'DEVICE_NOT_FOUND' | 'NETWORK_ERROR' | string
      message: string
      recoveryHints: string[]
    }

Step 3 · 落 useAudioDevices composable(0.15d)
  - frontend_vue3/src/composables/useAudioDevices.ts(新建):
    import { ref, computed } from 'vue'
    import type { InputDevice, OutputDevice, AudioDevicesError } from '@/types/audio-device'

    // 模块级缓存(panel 反复挂载不重复请求)
    const cache = {
      inputs: ref<InputDevice[] | null>(null),
      outputs: ref<OutputDevice[] | null>(null),
      error: ref<AudioDevicesError | null>(null),
      loading: ref(false),
    }

    export function useAudioDevices() {
      async function loadInputs(force = false): Promise<InputDevice[]> {
        if (!force && cache.inputs.value) return cache.inputs.value
        cache.loading.value = true
        try {
          const res = await fetch('/api/audio/devices/inputs')
          if (!res.ok) throw new Error(`HTTP ${res.status}`)
          const data = await res.json()
          cache.inputs.value = data
          cache.error.value = null
          return data
        } catch (e) {
          cache.error.value = {
            code: 'DEVICES_LOAD_FAILED',
            message: 'Failed to load input devices',
            recoveryHints: ['Check backend connection', 'Click Refresh to retry'],
          }
          throw e
        } finally {
          cache.loading.value = false
        }
      }
      async function loadOutputs(force = false): Promise<OutputDevice[]> { /* 同上 · /api/audio/devices/outputs */ }
      async function refresh(): Promise<void> {
        await Promise.all([loadInputs(true), loadOutputs(true)])
      }
      return {
        inputs: computed(() => cache.inputs.value ?? []),
        outputs: computed(() => cache.outputs.value ?? []),
        error: computed(() => cache.error.value),
        loading: computed(() => cache.loading.value),
        loadInputs,
        loadOutputs,
        refresh,
      }
    }

  - 关键点:
    · 模块级缓存 · panel 多实例共享
    · structured error · code + message + recoveryHints[](Ximind)
    · refresh() force 刷新 · 用户手动点 Refresh 按钮触发
    · endpoint 路径严格对齐 Step 1 真值核查结果

Step 4 · 扩展 EnginePanel.vue 末尾追加 § Device + § Signal Config(0.35d)
  - 仅在 b4a8ea2 已落 § Snapshots 段之后追加 · 严禁动 § Engine + Session + Snapshots 任何一行
  - § Device 段(template):
    <section class="xy-engine-section">
      <h3>Device</h3>
      <div class="xy-mode-badge" :data-mode="activeMode">{{ modeLabel }}</div>
      <label>Input Device</label>
      <select v-model="store.inputDeviceId">
        <option :value="null">— Select —</option>
        <option v-for="d in inputs" :key="d.deviceId" :value="d.deviceId">{{ d.friendlyName }}</option>
      </select>
      <label>Output Device <span class="xy-hint">(optional · loopback only)</span></label>
      <select v-model="store.outputDeviceId">
        <option :value="null">— None (hardware mode) —</option>
        <option v-for="d in outputs" :key="d.deviceId" :value="d.deviceId">{{ d.friendlyName }}</option>
      </select>
      <button @click="refresh" :disabled="loading">Refresh</button>
      <p v-if="loopbackError" class="xy-error">⚠️ Loopback requires distinct input/output device</p>
      <p v-if="error" class="xy-error">⚠️ {{ error.message }}<br>{{ error.recoveryHints.join(' · ') }}</p>
    </section>
  - § Signal Config 段(仅 loopback mode 可见):
    <section class="xy-engine-section" v-if="activeMode === 'loopback'">
      <h3>Signal Config</h3>
      <label>Type</label>
      <select v-model="store.signalConfig.type">
        <option value="sine">Sine</option><option value="pink">Pink Noise</option>
        <option value="mls">MLS</option><option value="multitone">Multitone (WAV)</option><option value="sweep">Sweep</option>
      </select>
      <label v-if="store.signalConfig.type === 'sine'">Frequency (Hz)</label>
      <input v-if="store.signalConfig.type === 'sine'" type="number" v-model.number="store.signalConfig.frequency" min="20" max="20000" step="1" />
      <label>Amplitude (dBFS)</label>
      <input type="number" v-model.number="store.signalConfig.amplitudeDbfs" min="-60" max="0" step="0.1" />
    </section>
  - script setup:
    import { computed, watchEffect } from 'vue'
    import { useRealtimeRunStore } from '@/stores/realtimeRunStore'
    import { useAudioDevices } from '@/composables/useAudioDevices'

    const store = useRealtimeRunStore()
    const { inputs, outputs, error, loading, refresh, loadInputs, loadOutputs } = useAudioDevices()

    // mode auto-detect(ADR §3.1 ④ Step 3 + §3.2 ④ Step 2)
    const activeMode = computed(() => {
      if (!store.inputDeviceId) return null
      if (store.outputDeviceId) return 'loopback'
      return 'hardware'
    })
    const modeLabel = computed(() => {
      if (activeMode.value === 'hardware') return 'Mode: Hardware Direct'
      if (activeMode.value === 'loopback') return 'Mode: Loopback'
      return 'Mode: — (select input device)'
    })

    // ADR §3.2 ③ failure: input === output
    const loopbackError = computed(() =>
      activeMode.value === 'loopback' && store.inputDeviceId === store.outputDeviceId
    )

    // 同步 store.activeMode(可选 · 让 store 知道当前 panel 推导的 mode)
    watchEffect(() => { if (activeMode.value) store.activeMode = activeMode.value })

    // signalConfig 默认值(loopback mode 切入时初始化)
    watchEffect(() => {
      if (activeMode.value === 'loopback' && !store.signalConfig) {
        store.signalConfig = { type: 'sine', frequency: 1000, amplitudeDbfs: -20 }
      }
    })

    onMounted(() => {
      loadInputs().catch(() => {})
      loadOutputs().catch(() => {})
    })
  - style:沿用 b4a8ea2 design-token · CSS class 复用 .xy-engine-section / .xy-error / .xy-hint / .xy-mode-badge(可能需新增) · 严禁硬编码 hex

  ⚠️ 行号正交:仅末尾追加 + script setup 内的 import + computed + onMounted 段 · 不动 b4a8ea2 已落三段任何代码

Step 5 · vitest 单测(0.2d)
  - frontend_vue3/src/composables/__tests__/useAudioDevices.spec.ts(新建):
    · loadInputs 成功 · 缓存命中第二次不重新请求
    · loadInputs 失败 · error structured(code/message/recoveryHints)
    · loadOutputs 同上
    · refresh force 刷新 · 重新请求
    · 多实例共享缓存(模块级状态)
    · ≥ 5 用例
  - frontend_vue3/src/stages/xitest/drawers/__tests__/EnginePanel.spec.ts(新建/扩展):
    · § Device 段渲染 input/output 下拉
    · 选 input → store.inputDeviceId 更新(双向绑定)
    · 选 input → activeMode = 'hardware' · modeLabel 显示
    · 选 input + output → activeMode = 'loopback' · § Signal Config 显示
    · input === output → loopbackError 显示
    · § Signal Config sine 类型显示 frequency 输入 · 其他类型隐藏
    · ≥ 5 用例
  - mock fetch:vi.spyOn(global, 'fetch') · mock realtimeRunStore:createPinia + setActivePinia
  - 期望 +10~15 用例

Step 6 · build + typecheck + 浏览器手动验证(0.1d)
  - cd frontend_vue3
  - npm run typecheck(零错误)
  - npm run test:unit(基线 +10~15)
  - 浏览器手动:
    · npm run dev + 启后端
    · 打开 xitest stage · 切到 LeftDock § Engine drawer
    · 滚到末尾看到 § Device + § Signal Config 段(b4a8ea2 三段在前 · 不能丢)
    · 选 Input Device(下拉应该列出后端真实设备)· mode badge → "Hardware Direct"
    · 加选 Output Device · mode badge → "Loopback" · § Signal Config 段出现
    · § Signal Config:type=sine · frequency=1000 · amplitude_dbfs=-20(默认)
    · 切 type 到 pink · frequency 输入隐藏
    · input === output(选同一个 device)· loopbackError 红警显示
    · DevTools Network → /api/audio/devices/inputs + /outputs 200
    · 点 Refresh → 二次 fetch
    · 关后端 → error structured 显示("Check backend connection" recoveryHints)
  - ⚠️ 真硬件测量联调留 fork 8 e2e

Step 7 · commit + push(0.1d)
  - git add frontend_vue3/src/types/audio-device.ts frontend_vue3/src/composables/useAudioDevices.ts frontend_vue3/src/stages/xitest/drawers/EnginePanel.vue frontend_vue3/src/composables/__tests__/useAudioDevices.spec.ts frontend_vue3/src/stages/xitest/drawers/__tests__/EnginePanel.spec.ts
  - git commit(三元组 trailer 见下) · git push origin xistudio

【验收】

形式合规:
- [ ] npm run typecheck 零错误
- [ ] npm run test:unit 全绿(基线 +10~15)
- [ ] 仅 5 文件修改/新建(1 types + 1 composable + 1 stage 扩展 + 2 测试)
- [ ] 不动 b4a8ea2 已落 EnginePanel.vue § Engine + Session + Snapshots 三段
- [ ] 不动 0111d16 realtimeRunStore.ts schema(本 fork 仅消费)
- [ ] 不动后端 / contracts / dsp_algo
- [ ] 不引入新 NPM 依赖(fetch 原生 + Pinia + Vue 内置)
- [ ] § Signal Config 段 v-if="activeMode === 'loopback'" 严守(hardware mode 不可见)

业务行为契约(ADR §3.1 ① + §3.2 ①):
- [ ] §3.1 ①:hardware mode · 选 input device → store.inputDeviceId 填入(顶栏 ▶ 调 start 时 mode='hardware')
- [ ] §3.1 ④ Step 2-3:LeftDock § Engine 选 Input Device → 系统自动识别 = "hardware-direct"(modeLabel 显示)
- [ ] §3.2 ①:loopback mode · 选 input + output + signalConfig → store 全字段填入(顶栏 ▶ 调 start 时 mode='loopback' 完整 payload)
- [ ] §3.2 ④ Step 1-3:选 input + output + signal sine 1kHz -20dBFS → store.signalConfig 完整
- [ ] §3.2 ③:input === output → loopbackError 红警显示(Loopback requires distinct input/output device)
- [ ] mode auto-detect:仅 input → "hardware" · input + output → "loopback" · 切换响应式

Ximind 兼容性(ADR §11):
- [ ] AudioDevice schema 含 description 字段(自然语言)
- [ ] 字段名 camelCase(deviceId / friendlyName / sampleRates / amplitudeDbfs · 无缩写歧义)
- [ ] AudioDevicesError structured(code + message + recoveryHints[])
- [ ] SignalType union 含语义(sine/pink/mls/multitone/sweep · 大模型可解释)
- [ ] modeLabel 含自然语言("Hardware Direct" / "Loopback" / "select input device")

【commit】
subject:`feat(P0.UA13-input-output-device-config-panel): LeftDock § Engine 扩展 § Device + § Signal Config + mode auto-detect · ADR-13 §2.5 §3.1 §3.2 §11`

trailer(必须精确 · 三元组):
[step=7/7] [pid=P0] [uid=UA13-input-output-device-config-panel] [occupies=P0.K-xitest-engine-panel]
[files=frontend_vue3/src/types/audio-device.ts, frontend_vue3/src/composables/useAudioDevices.ts, frontend_vue3/src/stages/xitest/drawers/EnginePanel.vue, frontend_vue3/src/composables/__tests__/useAudioDevices.spec.ts, frontend_vue3/src/stages/xitest/drawers/__tests__/EnginePanel.spec.ts]
[isolation] file(同 worktree 同 branch · 与 P0.UH12 + P1.A14.F4-module-library + ADR-14 active 文件路径完全正交)
[derived_from] ADR-AIOS-13 v1.0 §2.5 + §3.1 + §3.2 + §11 · parent zombie 92445f5(fork 1 RealtimeSessionService API)+ d8f0677(fork 2 BuiltinLinkRegistry)+ 0111d16(fork 4 realtimeRunStore)· 范式 b4a8ea2(EnginePanel 三段)
[truth-check] EnginePanel 现有段=??? · realtimeRunStore signalConfig type=??? · /api/audio/devices schema=???(Step 1 输出)
[acceptance] § Device + § Signal Config 末尾追加 · mode auto-detect computed · loopback v-if · loopbackError 红警 · vitest +10~15 全绿 · typecheck 0 错
[ximind] 5 项检查清单全过(camelCase + AudioDevice description + SignalType 语义 + AudioDevicesError structured + modeLabel 自然语言)

【禁止】(7 条红线)
1. ❌ 不动 b4a8ea2 已落 EnginePanel.vue § Engine + Session + Snapshots 三段任何一行
2. ❌ 不动 0111d16 realtimeRunStore.ts schema(本 fork 仅消费)
3. ❌ 不实施顶栏 ▶/■(已 fork 4)/ 不实施 capture/recapture(留 fork 7)
4. ❌ 不动后端 / contracts / dsp_algo
5. ❌ 不动 ADR-12 §3 7 类 MeasurementNode 业务行为契约
6. ❌ 不实施真硬件 e2e(留 fork 8)/ 不引入新 NPM 依赖
7. ❌ 不省略 Step 1 真值核查报告(commit body 必含 EnginePanel 现状 + signalConfig type + audio devices API schema)

解锁链(本任务 zombie 后)

  • ✅ Phase 3 fork 8 P_e2e.UA13-truth(议题 1 + 议题 2 端到端 e2e · 含 selectInputDevice/selectOutputDevice/selectSignal · 本 fork UI 支撑)
  • ✅ ADR-13 §3.1 ④ + §3.2 ④ 用户操作流业务行为契约闭环(Phase 1 后端 + Phase 2 UI 全栈就位)

风险评估

风险 缓解
⚠️ /api/audio/devices/{inputs,outputs} 后端 endpoint 路径与 fork 1 实现不一致 Step 1 grep backend_csharp/Routes/* 真值核查 + 严格对齐 schema
⚠️ realtimeRunStore.signalConfig 类型与 fork 4 实施不一致 Step 1 全文读 realtimeRunStore.ts · types/audio-device.ts SignalConfig 严格对齐 · typecheck 强制
⚠️ EnginePanel.vue 末尾追加破坏 b4a8ea2 三段 严守"仅末尾追加 + script setup 内段" · git diff Step 7 前 review 不动旧段
⚠️ 模块级缓存导致 dev mode HMR 状态污染 useAudioDevices 内部用 ref · vitest mock + setActivePinia 重置 · Step 6 浏览器手测 reload 验证
⚠️ mode auto-detect computed 无限循环(watchEffect 写 store + computed 读 store) watchEffect 仅写 store.activeMode(单向 · 推导端) · computed 不写 store · 单测 verify 不抖动
⚠️ loopback 模式 input === output 实测可能合法(虚拟 loopback device) 红警仅文案提示 · 不强制阻止(用户拍板权 · 后端 fork 1 已 422 校验) · 文案文字"Loopback requires distinct input/output device"
⚠️ design-token 用法与 b4a8ea2 不一致 Step 1 真值核查 b4a8ea2 已落 .xy-engine-section + .xy-error 等 class · 复用不重新发明 · CSS variable 不硬编码 hex

历史

时间 事件 hash
2026-06-02 11:50 dispatched · 用户 11:21 双 stop fork 2+4 解锁本 fork(blocked → 可派)· 11:37 用户拍板:先 done/ 归档分类(7 子目录 ADR-AIOS-XX)+ DASHBOARD 链接批量更新(49/49 修复)· 11:50 派发 fork 5 · ClaudeA 1.0d 🧵 file · 范式参考 EnginePanel b4a8ea2(三段)+ realtimeRunStore 0111d16(7 字段)· 解锁 Phase 3 fork 8 e2e