Files
boss/docs/superpowers/plans/2026-04-06-device-gui-cli-capability-and-conflict.md

26 KiB
Raw Permalink Blame History

设备 GUI+CLI 双能力接入与并行冲突控制 Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 让 Boss 支持同一台 Mac/Windows 设备同时接入 Codex GUI 与 CLI并在同项目 GUI/CLI 并行写入风险出现时默认阻断,由用户在当前异常项目/文件夹级别选择禁止、允许本次或永久放行。

Architecture: 继续沿用现有单设备模型,不拆成两个虚拟设备。设备层新增 gui/cli 双能力状态和默认执行模式;项目/文件夹层新增并行冲突状态与放行策略;执行链在进入 CLI 写入任务前先做规则层冲突检测,命中后返回结构化风险卡,等待用户确认。

Tech Stack: Next.js App Router、TypeScript、文件型状态存储 data/boss-state.json、现有 local-agent heartbeat 链路、Android 原生客户端、Node test runner、Gradle unit tests


文件结构

状态模型与执行冲突检测

  • Modify: /Users/kris/code/boss/src/lib/boss-data.ts
  • Test: /Users/kris/code/boss/tests/device-gui-cli-capabilities.test.ts
  • Test: /Users/kris/code/boss/tests/device-execution-conflict.test.ts

职责:

  • Device 增加 gui/cli 能力状态
  • 给项目/文件夹增加并行冲突策略与状态
  • 在进入 CLI 写入型任务前做规则层冲突检测
  • 禁止 / 允许本次 / 永久放行 限定在当前异常项目/文件夹

设备详情与设备 API

  • Modify: /Users/kris/code/boss/src/lib/boss-projections.ts
  • Modify: /Users/kris/code/boss/src/app/api/v1/devices/[deviceId]/route.ts
  • Test: /Users/kris/code/boss/tests/device-detail-capabilities-route.test.ts

职责:

  • 把设备的 GUI/CLI 双能力与默认执行模式暴露给 Web/Android
  • 允许设备详情页更新 preferredExecutionMode

本地 agent 心跳能力上报

  • Modify: /Users/kris/code/boss/local-agent/server.mjs
  • Modify: /Users/kris/code/boss/local-agent/config.example.json
  • Test: /Users/kris/code/boss/tests/local-agent-heartbeat-capabilities.test.mjs

职责:

  • heartbeat 上报当前设备的 gui/cli 能力状态
  • 不改变现有 local-agent -> codex exec resume 主链

Android 设备详情与冲突提示

  • Modify: /Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/BossApiClient.java
  • Modify: /Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/DeviceDetailActivity.java
  • Modify: /Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java
  • Test: /Users/kris/code/boss/android/app/src/test/java/com/hyzq/boss/DeviceDetailActivityTest.java
  • Test: /Users/kris/code/boss/android/app/src/test/java/com/hyzq/boss/BossApiClientDeviceModeTest.java

职责:

  • 在设备详情页显示 GUI / CLI 状态
  • 增加默认执行模式切换
  • 展示当前项目/文件夹级冲突状态和放行策略

Web 设备详情与冲突提示

  • Modify: /Users/kris/code/boss/src/components/app-ui.tsx
  • Modify: /Users/kris/code/boss/src/app/devices/page.tsx
  • Test: /Users/kris/code/boss/tests/device-detail-capabilities-route.test.ts

职责:

  • Web 端设备详情显示双能力与默认执行模式
  • 当前异常项目/文件夹的冲突卡三动作可操作

文档与回归

  • Modify: /Users/kris/code/boss/README.md
  • Modify: /Users/kris/code/boss/docs/architecture/current_runtime_and_deploy_status_cn.md

职责:

  • 记录 GUI/CLI 双能力设备模型
  • 记录项目/文件夹级冲突控制行为

Task 1: 先定义设备双能力与项目级冲突状态

Files:

  • Modify: /Users/kris/code/boss/src/lib/boss-data.ts

  • Test: /Users/kris/code/boss/tests/device-gui-cli-capabilities.test.ts

  • Step 1: 写失败测试,锁住设备双能力默认值与局部冲突策略

import test from "node:test";
import assert from "node:assert/strict";
import { readState, writeStateForTests } from "@/lib/boss-data";

test("device stores gui and cli capabilities without splitting the physical device", async () => {
  const state = await readState();
  const device = state.devices.find((item) => item.id === "mac-studio");
  assert.ok(device);
  assert.equal(device.capabilities?.gui?.connected, true);
  assert.equal(device.capabilities?.cli?.connected, true);
  assert.equal(device.preferredExecutionMode, "cli");
});

test("conflict policy is scoped to the active folder instead of the whole device", async () => {
  const state = await readState();
  state.projectExecutionPolicies = [
    {
      deviceId: "mac-studio",
      folderKey: "mac-studio:boss",
      projectId: "thread-ui",
      allowPolicy: "allow_always",
      conflictState: "warning",
      updatedAt: "2026-04-06T10:00:00.000Z",
    },
  ];
  await writeStateForTests(state);

  const nextState = await readState();
  const bossPolicy = nextState.projectExecutionPolicies.find((item) => item.folderKey === "mac-studio:boss");
  const otherPolicy = nextState.projectExecutionPolicies.find((item) => item.folderKey === "mac-studio:talking");
  assert.equal(bossPolicy?.allowPolicy, "allow_always");
  assert.equal(otherPolicy, undefined);
});
  • Step 2: 跑测试,确认先失败

Run:

npx --yes tsx --test /Users/kris/code/boss/tests/device-gui-cli-capabilities.test.ts

Expected:

  • FAIL提示 Device 没有 capabilitiespreferredExecutionMode

  • Step 3: 在状态模型里增加设备能力与冲突策略类型

/Users/kris/code/boss/src/lib/boss-data.ts 增加最小实现:

export type DeviceExecutionMode = "gui" | "cli";
export type ProjectConflictAllowPolicy = "forbid" | "allow_once" | "allow_always";
export type ProjectConflictState = "none" | "warning" | "blocked";

export interface DeviceCapabilityState {
  connected: boolean;
  lastSeenAt?: string;
  lastActiveProjectId?: string;
}

export interface DeviceCapabilities {
  gui: DeviceCapabilityState;
  cli: DeviceCapabilityState;
}

export interface ProjectExecutionPolicy {
  deviceId: string;
  folderKey?: string;
  projectId: string;
  allowPolicy: ProjectConflictAllowPolicy;
  conflictState: ProjectConflictState;
  activeCliExecution?: boolean;
  recentExternalActivityAt?: string;
  updatedAt: string;
}

export interface Device {
  id: string;
  name: string;
  avatar: string;
  account: string;
  source: DeviceSource;
  status: DeviceStatus;
  projects: string[];
  quota5h: number;
  quota7d: number;
  lastSeenAt: string;
  endpoint?: string;
  token?: string;
  note?: string;
  capabilities?: DeviceCapabilities;
  preferredExecutionMode?: DeviceExecutionMode;
}
  • Step 4: 给初始种子设备补默认值

/Users/kris/code/boss/src/lib/boss-data.ts 的种子设备里补:

capabilities: {
  gui: { connected: true, lastSeenAt: "2026-04-06T09:00:00+08:00", lastActiveProjectId: "master-agent" },
  cli: { connected: true, lastSeenAt: "2026-04-06T09:00:00+08:00", lastActiveProjectId: "master-agent" },
},
preferredExecutionMode: "cli",

Windows demo 设备补:

capabilities: {
  gui: { connected: true, lastSeenAt: "2026-04-06T08:50:00+08:00", lastActiveProjectId: "audit-collab" },
  cli: { connected: false, lastSeenAt: "2026-04-06T08:40:00+08:00", lastActiveProjectId: "" },
},
preferredExecutionMode: "gui",
  • Step 5: 给状态恢复逻辑补兼容默认值

readState() 的 normalize 阶段补:

capabilities: {
  gui: {
    connected: Boolean(device.capabilities?.gui?.connected),
    lastSeenAt: device.capabilities?.gui?.lastSeenAt || device.lastSeenAt,
    lastActiveProjectId: device.capabilities?.gui?.lastActiveProjectId || "",
  },
  cli: {
    connected: Boolean(device.capabilities?.cli?.connected),
    lastSeenAt: device.capabilities?.cli?.lastSeenAt || device.lastSeenAt,
    lastActiveProjectId: device.capabilities?.cli?.lastActiveProjectId || "",
  },
},
preferredExecutionMode:
  device.preferredExecutionMode === "gui" || device.preferredExecutionMode === "cli"
    ? device.preferredExecutionMode
    : "cli",
  • Step 6: 重跑测试,确认通过

Run:

npx --yes tsx --test /Users/kris/code/boss/tests/device-gui-cli-capabilities.test.ts

Expected:

  • PASS

  • Step 7: Commit

git add /Users/kris/code/boss/src/lib/boss-data.ts /Users/kris/code/boss/tests/device-gui-cli-capabilities.test.ts
git commit -m "feat: add gui cli device capability state"

Task 2: 在规则层实现项目/文件夹级并行冲突检测

Files:

  • Modify: /Users/kris/code/boss/src/lib/boss-data.ts

  • Test: /Users/kris/code/boss/tests/device-execution-conflict.test.ts

  • Step 1: 写失败测试,锁住默认阻断、允许本次、永久放行都只作用于当前 folder

import test from "node:test";
import assert from "node:assert/strict";
import {
  applyProjectConflictDecision,
  detectProjectExecutionConflict,
  readState,
  writeStateForTests,
} from "@/lib/boss-data";

test("detectProjectExecutionConflict blocks cli execution when the same folder has new external activity", async () => {
  const state = await readState();
  state.projectExecutionPolicies = [];
  await writeStateForTests(state);

  const result = await detectProjectExecutionConflict({
    deviceId: "mac-studio",
    folderKey: "mac-studio:boss",
    projectId: "thread-ui",
    executionMode: "cli",
    activityAt: "2026-04-06T10:05:00.000Z",
    externalActivityAt: "2026-04-06T10:04:00.000Z",
  });

  assert.equal(result.blocked, true);
  assert.equal(result.policy.allowPolicy, "forbid");
});

test("allow_once only clears the active folder conflict after a single execution", async () => {
  await applyProjectConflictDecision({
    deviceId: "mac-studio",
    folderKey: "mac-studio:boss",
    projectId: "thread-ui",
    decision: "allow_once",
  });

  let result = await detectProjectExecutionConflict({
    deviceId: "mac-studio",
    folderKey: "mac-studio:boss",
    projectId: "thread-ui",
    executionMode: "cli",
    activityAt: "2026-04-06T10:10:00.000Z",
    externalActivityAt: "2026-04-06T10:09:00.000Z",
  });
  assert.equal(result.blocked, false);

  result = await detectProjectExecutionConflict({
    deviceId: "mac-studio",
    folderKey: "mac-studio:boss",
    projectId: "thread-ui",
    executionMode: "cli",
    activityAt: "2026-04-06T10:20:00.000Z",
    externalActivityAt: "2026-04-06T10:19:00.000Z",
  });
  assert.equal(result.blocked, true);
});
  • Step 2: 跑测试,确认先失败

Run:

npx --yes tsx --test /Users/kris/code/boss/tests/device-execution-conflict.test.ts

Expected:

  • FAIL提示函数不存在或策略未生效

  • Step 3: 增加冲突检测与决策函数

/Users/kris/code/boss/src/lib/boss-data.ts 增加:

export async function detectProjectExecutionConflict(input: {
  deviceId: string;
  folderKey?: string;
  projectId: string;
  executionMode: DeviceExecutionMode;
  activityAt: string;
  externalActivityAt?: string;
}) {
  const state = await readState();
  const policy = matchProjectExecutionPolicy(state, input);
  const hasConflict =
    input.executionMode === "cli" &&
    Boolean(input.externalActivityAt) &&
    input.externalActivityAt! <= input.activityAt;

  if (!hasConflict) {
    return { blocked: false, policy };
  }

  if (policy?.allowPolicy === "allow_always") {
    return { blocked: false, policy };
  }

  if (policy?.allowPolicy === "allow_once") {
    await clearAllowOncePolicy(input);
    return { blocked: false, policy };
  }

  const nextPolicy = await upsertProjectExecutionPolicy({
    deviceId: input.deviceId,
    folderKey: input.folderKey,
    projectId: input.projectId,
    allowPolicy: "forbid",
    conflictState: "blocked",
    activeCliExecution: true,
    recentExternalActivityAt: input.externalActivityAt,
  });
  return { blocked: true, policy: nextPolicy };
}

export async function applyProjectConflictDecision(input: {
  deviceId: string;
  folderKey?: string;
  projectId: string;
  decision: ProjectConflictAllowPolicy;
}) {
  return upsertProjectExecutionPolicy({
    deviceId: input.deviceId,
    folderKey: input.folderKey,
    projectId: input.projectId,
    allowPolicy: input.decision,
    conflictState: input.decision === "forbid" ? "blocked" : "warning",
  });
}
  • Step 4: 确保匹配范围只落在当前异常项目/文件夹

匹配函数必须先按:

deviceId + folderKey

再退化到:

deviceId + projectId

不得在设备级匹配整个设备的默认策略。

  • Step 5: 重跑测试,确认通过

Run:

npx --yes tsx --test /Users/kris/code/boss/tests/device-execution-conflict.test.ts

Expected:

  • PASS

  • Step 6: Commit

git add /Users/kris/code/boss/src/lib/boss-data.ts /Users/kris/code/boss/tests/device-execution-conflict.test.ts
git commit -m "feat: add folder scoped gui cli conflict guard"

Task 3: 让设备详情 API 与投影视图暴露 GUI/CLI 双能力

Files:

  • Modify: /Users/kris/code/boss/src/lib/boss-projections.ts

  • Modify: /Users/kris/code/boss/src/app/api/v1/devices/[deviceId]/route.ts

  • Test: /Users/kris/code/boss/tests/device-detail-capabilities-route.test.ts

  • Step 1: 写失败测试,锁住设备详情返回 GUI/CLI 能力与默认执行模式

import test from "node:test";
import assert from "node:assert/strict";
import { GET, PATCH } from "@/app/api/v1/devices/[deviceId]/route";

test("device detail exposes gui cli capabilities and preferred mode", async () => {
  const response = await GET(new Request("http://localhost/api/v1/devices/mac-studio"), {
    params: Promise.resolve({ deviceId: "mac-studio" }),
  });
  const data = await response.json();
  const device = data.workspace.selectedDevice;
  assert.equal(device.capabilities.gui.connected, true);
  assert.equal(device.capabilities.cli.connected, true);
  assert.equal(device.preferredExecutionMode, "cli");
});

test("device detail patch updates preferred execution mode only", async () => {
  const response = await PATCH(
    new Request("http://localhost/api/v1/devices/mac-studio", {
      method: "PATCH",
      body: JSON.stringify({ preferredExecutionMode: "gui" }),
      headers: { "Content-Type": "application/json" },
    }),
    { params: Promise.resolve({ deviceId: "mac-studio" }) },
  );
  const data = await response.json();
  assert.equal(data.device.preferredExecutionMode, "gui");
});
  • Step 2: 跑测试,确认先失败

Run:

npx --yes tsx --test /Users/kris/code/boss/tests/device-detail-capabilities-route.test.ts

Expected:

  • FAIL提示返回字段缺失

  • Step 3: 在投影视图里暴露 GUI/CLI 状态

/Users/kris/code/boss/src/lib/boss-projections.tsDeviceWorkspaceView 构造里把:

selectedDevice: {
  ...device,
  capabilities: device.capabilities,
  preferredExecutionMode: device.preferredExecutionMode ?? "cli",
},

稳定输出。

  • Step 4: 在设备 PATCH 路由里支持只更新默认执行模式

/Users/kris/code/boss/src/app/api/v1/devices/[deviceId]/route.ts 收窄允许字段:

const body = await request.json();
const payload = {
  preferredExecutionMode:
    body.preferredExecutionMode === "gui" || body.preferredExecutionMode === "cli"
      ? body.preferredExecutionMode
      : undefined,
};
const device = await updateDevice(deviceId, payload);
  • Step 5: 重跑测试,确认通过

Run:

npx --yes tsx --test /Users/kris/code/boss/tests/device-detail-capabilities-route.test.ts

Expected:

  • PASS

  • Step 6: Commit

git add /Users/kris/code/boss/src/lib/boss-projections.ts /Users/kris/code/boss/src/app/api/v1/devices/[deviceId]/route.ts /Users/kris/code/boss/tests/device-detail-capabilities-route.test.ts
git commit -m "feat: expose gui cli device capability details"

Task 4: 让 local-agent heartbeat 上报 CLI 能力

Files:

  • Modify: /Users/kris/code/boss/local-agent/server.mjs

  • Modify: /Users/kris/code/boss/local-agent/config.example.json

  • Test: /Users/kris/code/boss/tests/local-agent-heartbeat-capabilities.test.mjs

  • Step 1: 写失败测试,锁住 heartbeat 载荷包含 capabilities.cli

import test from "node:test";
import assert from "node:assert/strict";
import { buildHeartbeatPayload } from "../local-agent/server.mjs";

test("heartbeat payload includes cli capability state", async () => {
  const payload = await buildHeartbeatPayload({
    deviceId: "mac-studio",
    name: "Mac Studio",
    cliConnected: true,
    guiConnected: false,
  });

  assert.equal(payload.device.capabilities.cli.connected, true);
  assert.equal(payload.device.capabilities.gui.connected, false);
});
  • Step 2: 跑测试,确认先失败

Run:

node --test /Users/kris/code/boss/tests/local-agent-heartbeat-capabilities.test.mjs

Expected:

  • FAIL提示 heartbeat payload 未包含该字段

  • Step 3: 在 heartbeat 载荷里新增双能力状态

/Users/kris/code/boss/local-agent/server.mjs 的 heartbeat payload 里补:

capabilities: {
  gui: {
    connected: Boolean(config.guiConnected),
    lastSeenAt: now,
    lastActiveProjectId: "",
  },
  cli: {
    connected: true,
    lastSeenAt: now,
    lastActiveProjectId: "",
  },
},
preferredExecutionMode: config.preferredExecutionMode || "cli",
  • Step 4: 在示例配置中补默认模式

/Users/kris/code/boss/local-agent/config.example.json 补:

"preferredExecutionMode": "cli",
"guiConnected": false
  • Step 5: 重跑测试,确认通过

Run:

node --test /Users/kris/code/boss/tests/local-agent-heartbeat-capabilities.test.mjs

Expected:

  • PASS

  • Step 6: Commit

git add /Users/kris/code/boss/local-agent/server.mjs /Users/kris/code/boss/local-agent/config.example.json /Users/kris/code/boss/tests/local-agent-heartbeat-capabilities.test.mjs
git commit -m "feat: report gui cli capabilities in heartbeat"

Task 5: Android 设备详情页显示 GUI/CLI 双能力并支持默认模式切换

Files:

  • Modify: /Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/BossApiClient.java

  • Modify: /Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/DeviceDetailActivity.java

  • Modify: /Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java

  • Test: /Users/kris/code/boss/android/app/src/test/java/com/hyzq/boss/DeviceDetailActivityTest.java

  • Test: /Users/kris/code/boss/android/app/src/test/java/com/hyzq/boss/BossApiClientDeviceModeTest.java

  • Step 1: 写失败测试,锁住设备详情显示 GUI/CLI 状态与默认模式

@Test
public void renderDeviceShowsGuiCliCapabilitiesAndPreferredMode() {
    TestDeviceDetailActivity activity = Robolectric
            .buildActivity(TestDeviceDetailActivity.class, new Intent()
                    .putExtra(DeviceDetailActivity.EXTRA_DEVICE_ID, "device-1")
                    .putExtra(DeviceDetailActivity.EXTRA_DEVICE_NAME, "Mac Studio"))
            .setup()
            .get();

    View content = activity.findViewById(R.id.screen_content);
    assertTrue(viewTreeContainsText(content, "GUI"));
    assertTrue(viewTreeContainsText(content, "CLI"));
    assertTrue(viewTreeContainsText(content, "默认执行模式"));
    assertTrue(viewTreeContainsText(content, "CLI"));
}
  • Step 2: 跑测试,确认先失败

Run:

cd /Users/kris/code/boss/android && ./gradlew testDebugUnitTest --tests com.hyzq.boss.DeviceDetailActivityTest --no-daemon

Expected:

  • FAIL提示页面文本不存在

  • Step 3: 给 Android client 增加更新默认模式接口

/Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/BossApiClient.java 加:

public ApiResponse updateDeviceExecutionMode(String deviceId, String mode) throws IOException, JSONException {
    JSONObject payload = new JSONObject();
    payload.put("preferredExecutionMode", mode);
    return patch("/api/v1/devices/" + encodePathSegment(deviceId), payload);
}
  • Step 4: 在设备详情页新增能力状态与模式切换

/Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/DeviceDetailActivity.javarenderDevice(...) 中追加:

JSONObject capabilities = device.optJSONObject("capabilities");
JSONObject gui = capabilities == null ? null : capabilities.optJSONObject("gui");
JSONObject cli = capabilities == null ? null : capabilities.optJSONObject("cli");
String preferredExecutionMode = device.optString("preferredExecutionMode", "cli");

appendContent(BossUi.buildWechatMenuRow(
        this,
        "GUI",
        gui != null && gui.optBoolean("connected", false) ? "已连接" : "未连接",
        null,
        null,
        null
));
appendContent(BossUi.buildWechatMenuRow(
        this,
        "CLI",
        cli != null && cli.optBoolean("connected", false) ? "已连接" : "未连接",
        null,
        null,
        null
));
appendContent(BossUi.buildMenuRow(
        this,
        "默认执行模式",
        "当前:" + ("gui".equals(preferredExecutionMode) ? "GUI" : "CLI"),
        null,
        v -> openExecutionModeDialog(preferredExecutionMode)
));
  • Step 5: 实现切换弹窗和保存

openExecutionModeDialog(...) 里:

String[] items = new String[] { "GUI", "CLI" };
new AlertDialog.Builder(this)
        .setTitle("默认执行模式")
        .setSingleChoiceItems(items, "gui".equals(currentMode) ? 0 : 1, null)
        .setNegativeButton("取消", null)
        .setPositiveButton("保存", (dialog, which) -> saveExecutionMode(selectedMode))
        .show();
  • Step 6: 重跑测试,确认通过

Run:

cd /Users/kris/code/boss/android && ./gradlew testDebugUnitTest --tests com.hyzq.boss.DeviceDetailActivityTest --tests com.hyzq.boss.BossApiClientDeviceModeTest --no-daemon

Expected:

  • PASS

  • Step 7: Commit

git add /Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/BossApiClient.java /Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/DeviceDetailActivity.java /Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java /Users/kris/code/boss/android/app/src/test/java/com/hyzq/boss/DeviceDetailActivityTest.java /Users/kris/code/boss/android/app/src/test/java/com/hyzq/boss/BossApiClientDeviceModeTest.java
git commit -m "feat: surface gui cli capabilities on android devices"

Task 6: Web 设备详情页显示双能力并支持冲突策略

Files:

  • Modify: /Users/kris/code/boss/src/components/app-ui.tsx

  • Modify: /Users/kris/code/boss/src/app/devices/page.tsx

  • Test: /Users/kris/code/boss/tests/device-detail-capabilities-route.test.ts

  • Step 1: 写失败测试,锁住设备详情展示 GUI/CLI 与默认模式

test("device page renders gui cli capability badges and preferred mode", async () => {
  const view = getDeviceWorkspaceView(await readState(), "mac-studio");
  assert.equal(view.selectedDevice?.capabilities?.gui.connected, true);
  assert.equal(view.selectedDevice?.capabilities?.cli.connected, true);
  assert.equal(view.selectedDevice?.preferredExecutionMode, "cli");
});
  • Step 2: 跑测试,确认先失败

Run:

npx --yes tsx --test /Users/kris/code/boss/tests/device-detail-capabilities-route.test.ts

Expected:

  • FAIL

  • Step 3: 在 Web 设备详情区域渲染能力与默认模式

/Users/kris/code/boss/src/components/app-ui.tsx 的设备详情块加入:

<div className="text-xs text-zinc-500">
  GUI{device.capabilities?.gui?.connected ? "已连接" : "未连接"} · CLI
  {device.capabilities?.cli?.connected ? "已连接" : "未连接"}
</div>
<button onClick={() => openDeviceExecutionMode(device.id)}>
  默认执行模式:{device.preferredExecutionMode === "gui" ? "GUI" : "CLI"}
</button>
  • Step 4: 为冲突卡预留当前项目/文件夹级入口

在同一区块加入:

{policy ? (
  <div>
    <div>当前项目并行策略:{policy.allowPolicy}</div>
    <button>禁止</button>
    <button>允许本次</button>
    <button>永久放行</button>
  </div>
) : null}

先把数据通道和入口铺上,不在这一任务里完成所有交互。

  • Step 5: 重跑测试,确认通过

Run:

npx --yes tsx --test /Users/kris/code/boss/tests/device-detail-capabilities-route.test.ts

Expected:

  • PASS

  • Step 6: Commit

git add /Users/kris/code/boss/src/components/app-ui.tsx /Users/kris/code/boss/src/app/devices/page.tsx /Users/kris/code/boss/tests/device-detail-capabilities-route.test.ts
git commit -m "feat: show gui cli capability state on web devices"

Task 7: 完整验证并同步文档

Files:

  • Modify: /Users/kris/code/boss/README.md

  • Modify: /Users/kris/code/boss/docs/architecture/current_runtime_and_deploy_status_cn.md

  • Step 1: 跑服务端与 Android 关键回归

Run:

cd /Users/kris/code/boss && npx --yes tsx --test tests/device-gui-cli-capabilities.test.ts tests/device-execution-conflict.test.ts tests/device-detail-capabilities-route.test.ts
cd /Users/kris/code/boss/android && ./gradlew testDebugUnitTest --tests com.hyzq.boss.DeviceDetailActivityTest --tests com.hyzq.boss.BossApiClientDeviceModeTest --no-daemon

Expected:

  • PASS

  • Step 2: 跑 lint / build / release

Run:

cd /Users/kris/code/boss && npm run lint
cd /Users/kris/code/boss && npm run build
cd /Users/kris/code/boss/android && ./gradlew assembleRelease --no-daemon

Expected:

  • 全部 PASS

  • Step 3: 更新文档

/Users/kris/code/boss/README.md/Users/kris/code/boss/docs/architecture/current_runtime_and_deploy_status_cn.md 增加:

- 当前设备模型已支持同一台设备同时接入 Codex GUI 与 CLI
- 设备详情页可查看 GUI / CLI 状态,并切换默认执行模式
- 同项目 GUI/CLI 并行写入风险默认阻断,用户可在当前异常项目/文件夹级别选择 禁止 / 允许本次 / 永久放行
  • Step 4: 提交文档与收口代码
git add /Users/kris/code/boss/README.md /Users/kris/code/boss/docs/architecture/current_runtime_and_deploy_status_cn.md
git commit -m "docs: describe gui cli device capability workflow"
  • Step 5: 推分支并部署

Run:

cd /Users/kris/code/boss && git push gitea codex/wechat-native-ui-rollback
cd /Users/kris/code/boss && ./scripts/deploy-server.sh

Expected:

  • 分支推送成功
  • 服务器健康检查正常