381 lines
16 KiB
TypeScript
381 lines
16 KiB
TypeScript
import test from "node:test";
|
||
import assert from "node:assert/strict";
|
||
import os from "node:os";
|
||
import path from "node:path";
|
||
import { mkdtemp, rm } from "node:fs/promises";
|
||
|
||
let runtimeRoot = "";
|
||
let readState: (typeof import("../src/lib/boss-data"))["readState"];
|
||
let writeState: (typeof import("../src/lib/boss-data"))["writeState"];
|
||
let applyProjectConflictDecision: (typeof import("../src/lib/boss-data"))["applyProjectConflictDecision"];
|
||
let getDeviceWorkspaceView: (typeof import("../src/lib/boss-projections"))["getDeviceWorkspaceView"];
|
||
let buildDeviceWorkspaceDetailCards: (typeof import("../src/components/app-ui"))["buildDeviceWorkspaceDetailCards"];
|
||
|
||
async function setup() {
|
||
if (runtimeRoot) return;
|
||
|
||
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-device-detail-route-"));
|
||
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
|
||
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
|
||
|
||
const [data, projections, ui] = await Promise.all([
|
||
import("../src/lib/boss-data.ts"),
|
||
import("../src/lib/boss-projections.ts"),
|
||
import("../src/components/app-ui.tsx"),
|
||
]);
|
||
|
||
readState = data.readState;
|
||
writeState = data.writeState;
|
||
applyProjectConflictDecision = data.applyProjectConflictDecision;
|
||
getDeviceWorkspaceView = projections.getDeviceWorkspaceView;
|
||
buildDeviceWorkspaceDetailCards = ui.buildDeviceWorkspaceDetailCards;
|
||
}
|
||
|
||
test.after(async () => {
|
||
if (runtimeRoot) {
|
||
await rm(runtimeRoot, { recursive: true, force: true });
|
||
}
|
||
});
|
||
|
||
test("device detail exposes gui cli capability state and preferred execution mode", async () => {
|
||
await setup();
|
||
|
||
const state = await readState();
|
||
const workspace = getDeviceWorkspaceView(state, "mac-studio");
|
||
const cards = buildDeviceWorkspaceDetailCards(workspace);
|
||
|
||
assert.equal(cards.capabilities.title, "执行能力");
|
||
assert.equal(cards.capabilities.items.gui, "GUI:已连接");
|
||
assert.equal(cards.capabilities.items.cli, "CLI:已连接");
|
||
assert.equal(cards.capabilities.items.preferredExecutionMode, "默认执行模式:CLI");
|
||
});
|
||
|
||
test("device detail exposes Codex App Server discovered model and extension summary", async () => {
|
||
await setup();
|
||
|
||
const state = await readState();
|
||
const device = state.devices.find((item) => item.id === "mac-studio");
|
||
assert.ok(device);
|
||
device!.capabilities = {
|
||
...(device!.capabilities ?? {}),
|
||
codexAppServer: {
|
||
connected: true,
|
||
lastSeenAt: "2026-05-31T10:00:00.000Z",
|
||
lastActiveProjectId: "",
|
||
metadata: {
|
||
models: [
|
||
{ id: "gpt-5.4", displayName: "GPT-5.4" },
|
||
{ id: "gpt-5.4-mini", displayName: "GPT-5.4 mini" },
|
||
],
|
||
defaultModelId: "gpt-5.4",
|
||
fastModelId: "gpt-5.4-mini",
|
||
deepModelId: "gpt-5.4",
|
||
skills: [{ name: "image2-ui-prototype" }],
|
||
plugins: [{ id: "github" }],
|
||
apps: [{ id: "canva" }],
|
||
experimentalFeatures: [
|
||
{ name: "multi_agent", stage: "stable", enabled: true },
|
||
{ name: "apps", stage: "beta", enabled: false },
|
||
],
|
||
collaborationModes: [{ id: "solo" }, { id: "plan" }],
|
||
permissionProfiles: [{ id: ":workspace" }],
|
||
mcpServers: [
|
||
{ name: "github", toolCount: 2, authStatus: "oAuth" },
|
||
{ name: "figma", toolCount: 0, authStatus: "notLoggedIn" },
|
||
],
|
||
accountSummary: { signedIn: true, authMode: "chatgpt", planType: "pro", requiresOpenaiAuth: true },
|
||
rateLimitSummary: { bucketCount: 2, maxUsedPercent: 42, reached: false },
|
||
appConfigSummary: {
|
||
appCount: 2,
|
||
enabledAppCount: 1,
|
||
defaultEnabled: true,
|
||
destructiveEnabled: false,
|
||
openWorldEnabled: false,
|
||
},
|
||
configRequirements: { managed: true, requirementCount: 2, warningCount: 1 },
|
||
externalAgentMigration: {
|
||
itemCount: 3,
|
||
homeItemCount: 1,
|
||
projectItemCount: 2,
|
||
itemTypes: ["AGENTS_MD", "MCP_SERVER_CONFIG", "SKILLS"],
|
||
},
|
||
skillExtraRootsSummary: {
|
||
configured: true,
|
||
status: "applied",
|
||
rootCount: 2,
|
||
rootLabels: ["boss-shared-skills", "team-skills"],
|
||
},
|
||
hookSummary: {
|
||
workspaceCount: 1,
|
||
hookCount: 2,
|
||
enabledHookCount: 1,
|
||
managedHookCount: 1,
|
||
trustedHookCount: 1,
|
||
modifiedHookCount: 1,
|
||
untrustedHookCount: 0,
|
||
warningCount: 1,
|
||
errorCount: 1,
|
||
eventNames: ["PreToolUse", "SessionStart"],
|
||
handlerTypes: ["command", "prompt"],
|
||
},
|
||
threadSummary: {
|
||
threadCount: 3,
|
||
loadedThreadCount: 2,
|
||
activeThreadCount: 1,
|
||
archivedThreadCount: 1,
|
||
latestUpdatedAt: "2026-06-03T08:20:00.000Z",
|
||
sourceKinds: ["app", "cli"],
|
||
visibleThreads: [
|
||
{
|
||
id: "thr-active",
|
||
name: "Boss App Server rollout",
|
||
sourceKind: "app",
|
||
status: "active",
|
||
archived: false,
|
||
loaded: true,
|
||
updatedAt: "2026-06-03T08:20:00.000Z",
|
||
},
|
||
],
|
||
},
|
||
threadTurnSummary: {
|
||
threadCount: 2,
|
||
totalTurnCount: 3,
|
||
runningTurnCount: 1,
|
||
completedTurnCount: 2,
|
||
latestUpdatedAt: "2026-06-03T08:21:00.000Z",
|
||
threads: [
|
||
{
|
||
threadId: "thr-active",
|
||
turnCount: 2,
|
||
runningTurnCount: 1,
|
||
completedTurnCount: 1,
|
||
latestTurnStatus: "running",
|
||
latestTurnUpdatedAt: "2026-06-03T08:21:00.000Z",
|
||
},
|
||
],
|
||
},
|
||
threadActionSummary: {
|
||
actionCount: 11,
|
||
lifecycleActionCount: 5,
|
||
metadataActionCount: 2,
|
||
liveTurnActionCount: 2,
|
||
shellActionAvailable: true,
|
||
userInitiatedOnly: true,
|
||
labels: ["归档", "恢复", "分叉", "压缩", "回滚", "改名", "元数据", "活跃干预", "中断", "Shell", "取消订阅"],
|
||
},
|
||
pluginGovernanceSummary: {
|
||
actionCount: 9,
|
||
lifecycleActionCount: 2,
|
||
shareActionCount: 4,
|
||
readActionCount: 3,
|
||
skillReadAvailable: true,
|
||
userInitiatedOnly: true,
|
||
labels: ["安装", "卸载", "读取", "Skill 读取", "共享保存", "共享拉取", "共享删除", "共享目标", "共享列表"],
|
||
},
|
||
accountGovernanceSummary: {
|
||
actionCount: 6,
|
||
loginActionCount: 3,
|
||
sessionActionCount: 1,
|
||
tokenRefreshAvailable: true,
|
||
billingNudgeAvailable: true,
|
||
userInitiatedOnly: true,
|
||
labels: ["登录开始", "登录取消", "登录完成", "退出登录", "刷新令牌", "额度提醒"],
|
||
},
|
||
configGovernanceSummary: {
|
||
actionCount: 5,
|
||
writeActionCount: 3,
|
||
reloadActionCount: 1,
|
||
readActionAvailable: true,
|
||
userInitiatedOnly: true,
|
||
labels: ["配置读取", "单项写入", "批量写入", "MCP 重载", "Skill 配置"],
|
||
},
|
||
fileSystemGovernanceSummary: {
|
||
actionCount: 9,
|
||
readActionCount: 3,
|
||
writeActionCount: 3,
|
||
destructiveActionCount: 1,
|
||
watchActionCount: 2,
|
||
userInitiatedOnly: true,
|
||
labels: ["读取文件", "读取目录", "元数据", "写入文件", "创建目录", "复制", "删除", "监听", "取消监听"],
|
||
},
|
||
commandSessionSummary: {
|
||
actionCount: 5,
|
||
controlActionCount: 3,
|
||
streamAvailable: true,
|
||
terminationAvailable: true,
|
||
sandboxedCommandAvailable: true,
|
||
userInitiatedOnly: true,
|
||
labels: ["执行命令", "写入 stdin", "调整 PTY", "终止命令", "输出流"],
|
||
},
|
||
externalAgentGovernanceSummary: {
|
||
actionCount: 3,
|
||
importActionCount: 1,
|
||
notificationActionCount: 1,
|
||
detectActionAvailable: true,
|
||
userInitiatedOnly: true,
|
||
labels: ["迁移检测", "迁移导入", "导入完成"],
|
||
},
|
||
marketplaceGovernanceSummary: {
|
||
actionCount: 3,
|
||
writeActionCount: 3,
|
||
upgradeAvailable: true,
|
||
userInitiatedOnly: true,
|
||
labels: ["添加市场", "移除市场", "升级市场"],
|
||
},
|
||
experimentalFeatureGovernanceSummary: {
|
||
actionCount: 2,
|
||
writeActionCount: 1,
|
||
listAvailable: true,
|
||
userInitiatedOnly: true,
|
||
labels: ["实验列表", "启用设置"],
|
||
},
|
||
reviewGovernanceSummary: {
|
||
actionCount: 1,
|
||
reviewStartAvailable: true,
|
||
userInitiatedOnly: true,
|
||
labels: ["启动审查"],
|
||
},
|
||
windowsSandboxGovernanceSummary: {
|
||
actionCount: 3,
|
||
setupActionCount: 1,
|
||
readinessAvailable: true,
|
||
notificationAvailable: true,
|
||
userInitiatedOnly: true,
|
||
labels: ["准备检查", "启动设置", "设置完成"],
|
||
},
|
||
fuzzyFileSearchSummary: {
|
||
eventCount: 2,
|
||
completedEventAvailable: true,
|
||
notificationOnly: true,
|
||
labels: ["搜索更新", "搜索完成"],
|
||
},
|
||
mcpGovernanceSummary: {
|
||
actionCount: 5,
|
||
oauthActionCount: 2,
|
||
resourceActionCount: 1,
|
||
toolActionCount: 1,
|
||
elicitationAvailable: true,
|
||
userInitiatedOnly: true,
|
||
labels: ["OAuth 登录", "OAuth 完成", "资源读取", "工具调用", "交互请求"],
|
||
},
|
||
userInteractionGovernanceSummary: {
|
||
actionCount: 1,
|
||
requestUserInputAvailable: true,
|
||
userInitiatedOnly: true,
|
||
labels: ["请求用户输入"],
|
||
},
|
||
guardianGovernanceSummary: {
|
||
actionCount: 2,
|
||
approvalActionCount: 1,
|
||
permissionRequestEventAvailable: true,
|
||
userInitiatedOnly: true,
|
||
labels: ["Guardian 放行", "权限请求"],
|
||
},
|
||
runtimeEventSummary: {
|
||
eventCount: 3,
|
||
processEventCount: 2,
|
||
rawResponseEventAvailable: true,
|
||
notificationOnly: true,
|
||
labels: ["进程输出", "进程退出", "原始响应完成"],
|
||
},
|
||
extensionEventSummary: {
|
||
eventCount: 2,
|
||
skillChangeEventAvailable: true,
|
||
pluginInstallEventAvailable: true,
|
||
notificationOnly: true,
|
||
labels: ["Skill 变更", "插件安装"],
|
||
},
|
||
threadLifecycleEventSummary: {
|
||
eventCount: 5,
|
||
archiveEventCount: 2,
|
||
nameEventAvailable: true,
|
||
closeEventAvailable: true,
|
||
notificationOnly: true,
|
||
labels: ["线程启动", "线程关闭", "已归档", "已恢复", "改名完成"],
|
||
},
|
||
},
|
||
},
|
||
};
|
||
await writeState(state);
|
||
|
||
const workspace = getDeviceWorkspaceView(await readState(), "mac-studio");
|
||
const cards = buildDeviceWorkspaceDetailCards(workspace);
|
||
|
||
assert.equal(cards.capabilities.items.codexAppServer, "Codex App Server:已连接");
|
||
assert.equal(cards.capabilities.items.codexModels, "模型:2 个 · 默认 gpt-5.4 · 快速 gpt-5.4-mini · 深度 gpt-5.4");
|
||
assert.equal(cards.capabilities.items.codexExtensions, "扩展:Skill 1 个 · Plugin 1 个 · App 1 个");
|
||
assert.equal(cards.capabilities.items.codexGovernance, "治理:实验特性 2 个 · 协作模式 2 个 · MCP 2 个 · 权限 1 个");
|
||
assert.equal(cards.capabilities.items.codexAccount, "账号:chatgpt · 套餐 pro · 额度 42%");
|
||
assert.equal(cards.capabilities.items.codexConfig, "配置:App 2 个 · 已启用 1 个 · 托管要求 2 个 · 外部迁移 3 项");
|
||
assert.equal(cards.capabilities.items.codexSkillRoots, "共享 Skill 根:2 个 · 已下发");
|
||
assert.equal(cards.capabilities.items.codexHooks, "Hook:2 个 · 启用 1 个 · 警告 1 个");
|
||
assert.equal(cards.capabilities.items.codexThreads, "线程:3 个 · 已加载 2 个 · 活跃 1 个 · 最新 2026-06-03 16:20");
|
||
assert.equal(cards.capabilities.items.codexTurns, "轮次:3 个 · 运行中 1 个 · 完成 2 个 · 最新 2026-06-03 16:21");
|
||
assert.equal(cards.capabilities.items.codexThreadActions, "线程操作:11 项 · 生命周期 5 项 · 活跃干预 2 项 · Shell 可用");
|
||
assert.equal(cards.capabilities.items.codexPluginGovernance, "插件治理:9 项 · 安装/卸载 2 项 · 共享 4 项 · Skill 读取可用");
|
||
assert.equal(cards.capabilities.items.codexAccountGovernance, "账号治理:6 项 · 登录 3 项 · 令牌刷新可用 · 额度提醒可用");
|
||
assert.equal(cards.capabilities.items.codexConfigGovernance, "配置治理:5 项 · 写入 3 项 · 重载 1 项 · 读取可用");
|
||
assert.equal(cards.capabilities.items.codexFileSystemGovernance, "文件治理:9 项 · 读 3 项 · 写 3 项 · 监听 2 项");
|
||
assert.equal(cards.capabilities.items.codexCommandSession, "命令会话:5 项 · 控制 3 项 · 输出流可用 · 可终止");
|
||
assert.equal(cards.capabilities.items.codexExternalAgentGovernance, "迁移治理:3 项 · 导入 1 项 · 检测可用");
|
||
assert.equal(cards.capabilities.items.codexMarketplaceGovernance, "市场治理:3 项 · 写入 3 项 · 升级可用");
|
||
assert.equal(cards.capabilities.items.codexExperimentalFeatureGovernance, "实验特性治理:2 项 · 写入 1 项 · 列表可用");
|
||
assert.equal(cards.capabilities.items.codexReviewGovernance, "审查治理:1 项 · 审查可启动");
|
||
assert.equal(cards.capabilities.items.codexWindowsSandboxGovernance, "Windows 沙箱:3 项 · 设置 1 项 · 准备检查可用");
|
||
assert.equal(cards.capabilities.items.codexFuzzyFileSearch, "文件搜索事件:2 项 · 完成事件可用");
|
||
assert.equal(cards.capabilities.items.codexMcpGovernance, "MCP 治理:5 项 · OAuth 2 项 · 工具 1 项 · 交互可用");
|
||
assert.equal(cards.capabilities.items.codexUserInteractionGovernance, "用户交互:1 项 · 输入请求可用");
|
||
assert.equal(cards.capabilities.items.codexGuardianGovernance, "Guardian 治理:2 项 · 审批 1 项 · 权限事件可用");
|
||
assert.equal(cards.capabilities.items.codexRuntimeEvents, "运行事件:3 项 · 进程 2 项 · 原始响应可用");
|
||
assert.equal(cards.capabilities.items.codexExtensionEvents, "扩展事件:2 项 · Skill 变更可用 · 插件安装可用");
|
||
assert.equal(cards.capabilities.items.codexThreadLifecycleEvents, "线程生命周期:5 项 · 归档 2 项 · 改名可用 · 关闭可用");
|
||
});
|
||
|
||
test("device detail exposes folder and project conflict skeleton from workspace policy", async () => {
|
||
await setup();
|
||
|
||
const state = await readState();
|
||
state.projectExecutionPolicies = [
|
||
{
|
||
deviceId: "mac-studio",
|
||
folderKey: "mac-studio:boss",
|
||
projectId: "thread-ui",
|
||
allowPolicy: "allow_always",
|
||
conflictState: "warning",
|
||
updatedAt: "2026-04-06T12:00:00.000Z",
|
||
},
|
||
];
|
||
await writeState(state);
|
||
|
||
const workspace = getDeviceWorkspaceView(await readState(), "mac-studio");
|
||
const cards = buildDeviceWorkspaceDetailCards(workspace);
|
||
|
||
assert.equal(cards.conflicts.title, "异常项目 / 文件夹冲突");
|
||
assert.equal(cards.conflicts.headerHint, "已接入,可直接调整");
|
||
assert.equal(cards.conflicts.items.device, "设备:Mac Studio");
|
||
assert.equal(cards.conflicts.items.folderKey, "文件夹:mac-studio:boss");
|
||
assert.equal(cards.conflicts.items.projectId, "项目:thread-ui");
|
||
assert.equal(cards.conflicts.items.allowPolicy, "当前策略:永久放行");
|
||
assert.equal(cards.conflicts.items.conflictState, "冲突态:存在并行风险");
|
||
});
|
||
|
||
test("device detail conflict card keeps project-scoped actions on the active folder only", async () => {
|
||
await setup();
|
||
|
||
await applyProjectConflictDecision({
|
||
deviceId: "mac-studio",
|
||
folderKey: "mac-studio:boss",
|
||
projectId: "thread-ui",
|
||
decision: "allow_once",
|
||
});
|
||
|
||
const workspace = getDeviceWorkspaceView(await readState(), "mac-studio");
|
||
const cards = buildDeviceWorkspaceDetailCards(workspace);
|
||
|
||
assert.equal(cards.conflicts.headerHint, "已接入,可直接调整");
|
||
assert.equal(cards.conflicts.items.allowPolicy, "当前策略:允许本次");
|
||
assert.equal(cards.conflicts.items.conflictState, "冲突态:存在并行风险");
|
||
assert.deepEqual(cards.conflicts.actions, ["禁止", "允许本次", "永久放行"]);
|
||
assert.equal(cards.conflicts.scopeLabel, "仅作用于当前异常项目 / 文件夹");
|
||
});
|