feat: ship enterprise control and desktop governance

This commit is contained in:
AI Bot
2026-05-11 14:59:26 +08:00
parent 0757d07521
commit a311280238
285 changed files with 48574 additions and 2428 deletions

View File

@@ -0,0 +1,103 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
const panelPath = new URL("../src/components/admin/admin-access-panel.tsx", import.meta.url);
async function readPanelSource() {
return readFile(panelPath, "utf8");
}
test("AdminAccessPanel is a client antd component without refine antd", async () => {
const source = await readPanelSource();
assert.match(source, /["']use client["']/);
assert.match(source, /export function AdminAccessPanel/);
assert.doesNotMatch(source, /@refinedev\/antd/);
for (const component of ["Form", "Card", "Table", "Button", "Select", "Checkbox", "Alert"]) {
assert.match(source, new RegExp(`\\b${component}\\b`));
}
});
test("AdminAccessPanel calls the access endpoint for refresh and mutations", async () => {
const source = await readPanelSource();
assert.match(source, /\/api\/v1\/admin\/access/);
assert.match(source, /method:\s*["']POST["']/);
assert.match(source, /cache:\s*["']no-store["']/);
for (const action of [
"upsert_company",
"set_company_status",
"assign_account_company",
"assign_device_company",
"preview_bulk_import_accounts",
"bulk_import_accounts",
"reclaim_account",
"reset_account_password",
"set_account_mfa_required",
"upsert_account",
"apply_template",
"grant_device",
"grant_project",
"grant_skill",
"revoke_grant",
"set_account_status",
]) {
assert.match(source, new RegExp(action));
}
});
test("AdminAccessPanel exposes company lifecycle controls", async () => {
const source = await readPanelSource();
assert.match(source, /公司管理/);
assert.match(source, /套餐等级/);
assert.match(source, /合同到期/);
assert.match(source, /客户成功/);
assert.match(source, /批量导入账号/);
assert.match(source, /预览导入/);
assert.match(source, /CSV/);
assert.match(source, /parseBulkAccountsCsv/);
assert.match(source, /离职回收/);
assert.match(source, /重置密码/);
assert.match(source, /所属公司/);
});
test("AdminAccessPanel exposes hifi access workspace structure", async () => {
const source = await readPanelSource();
assert.match(source, /权限概览/);
assert.match(source, /关键风险/);
assert.match(source, /配置负责人\/账号/);
assert.match(source, /权限删除/);
assert.match(source, /设备绑定 \/ 范围授权 \/ SLA 授权/);
assert.match(source, /adminDense/);
});
test("AdminAccessPanel wraps dangerous mutations with explicit confirmation", async () => {
const source = await readPanelSource();
assert.match(source, /confirmDangerousAction/);
assert.match(source, /window\.confirm/);
for (const action of ["set_company_status", "reclaim_account", "reset_account_password", "revoke_grant"]) {
assert.match(source, new RegExp(`confirmDangerousAction[\\s\\S]+${action}|${action}[\\s\\S]+confirmDangerousAction`));
}
});
test("AdminAccessPanel exposes account status controls", async () => {
const source = await readPanelSource();
assert.match(source, /账号列表/);
assert.match(source, /停用/);
assert.match(source, /启用/);
assert.match(source, /status:\s*["']disabled["']/);
assert.match(source, /status:\s*["']active["']/);
});
test("AdminAccessPanel only exposes member and admin account roles", async () => {
const source = await readPanelSource();
assert.match(source, /member/);
assert.match(source, /admin/);
assert.doesNotMatch(source, /highest_admin/);
});

View File

@@ -0,0 +1,145 @@
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";
import { NextRequest } from "next/server";
let runtimeRoot = "";
let data: typeof import("../src/lib/boss-data");
let authCookie = "";
let postAdminAccess: (typeof import("../src/app/api/v1/admin/access/route"))["POST"];
let baseState: Awaited<ReturnType<typeof import("../src/lib/boss-data")["readState"]>>;
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-admin-account-status-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [dataModule, authModule, routeModule] = await Promise.all([
import("../src/lib/boss-data.ts"),
import("../src/lib/boss-auth.ts"),
import("../src/app/api/v1/admin/access/route.ts"),
]);
data = dataModule;
authCookie = authModule.AUTH_SESSION_COOKIE;
postAdminAccess = routeModule.POST;
baseState = structuredClone(await data.readState());
}
test.after(async () => {
if (runtimeRoot) await rm(runtimeRoot, { recursive: true, force: true });
});
test.beforeEach(async () => {
await setup();
const state = structuredClone(baseState);
const now = "2026-04-27T12:00:00+08:00";
state.authAccounts = [
{
id: "account-owner",
account: "owner@acme.com",
passwordHash: "secret",
displayName: "Acme 老板",
role: "highest_admin",
createdAt: now,
updatedAt: now,
},
{
id: "account-worker",
account: "worker@acme.com",
passwordHash: "secret",
displayName: "Worker",
role: "member",
createdAt: now,
updatedAt: now,
},
];
state.authSessions = [];
state.permissionAuditLogs = [];
await data.writeState(state);
});
async function authedRequest(
account: string,
role: "member" | "admin" | "highest_admin",
body: Record<string, unknown>,
) {
const session = await data.createAuthSession({
account,
role,
displayName: account,
loginMethod: "password",
});
return new NextRequest("http://127.0.0.1:3000/api/v1/admin/access", {
method: "POST",
headers: {
"content-type": "application/json",
cookie: `${authCookie}=${session.sessionToken}`,
},
body: JSON.stringify(body),
});
}
async function adminPost(body: Record<string, unknown>) {
return postAdminAccess(await authedRequest("owner@acme.com", "highest_admin", body));
}
test("highest admin can disable a child account and revoke its active sessions", async () => {
const workerSession = await data.createAuthSession({
account: "worker@acme.com",
role: "member",
displayName: "Worker",
loginMethod: "password",
});
const response = await adminPost({
action: "set_account_status",
account: "worker@acme.com",
status: "disabled",
});
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.account.status, "disabled");
assert.equal(payload.account.passwordHash, undefined);
const state = await data.readState();
assert.equal(state.authAccounts.find((account) => account.account === "worker@acme.com")?.status, "disabled");
assert.equal(
state.authSessions.find((session) => session.sessionToken === workerSession.sessionToken)?.revokedAt !== undefined,
true,
);
assert.equal(await data.getAuthSession(workerSession.sessionToken), null);
assert.equal(state.permissionAuditLogs.at(0)?.action, "account.updated");
});
test("highest admin can re-enable a disabled child account", async () => {
await adminPost({
action: "set_account_status",
account: "worker@acme.com",
status: "disabled",
});
const response = await adminPost({
action: "set_account_status",
account: "worker@acme.com",
status: "active",
});
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.account.status, "active");
const state = await data.readState();
assert.equal(state.authAccounts.find((account) => account.account === "worker@acme.com")?.status, "active");
});
test("highest admin cannot disable a highest admin account", async () => {
const response = await adminPost({
action: "set_account_status",
account: "owner@acme.com",
status: "disabled",
});
assert.equal(response.status, 400);
const payload = await response.json();
assert.equal(payload.message, "CANNOT_DISABLE_HIGHEST_ADMIN");
});

View File

@@ -0,0 +1,264 @@
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";
import { NextRequest } from "next/server";
let runtimeRoot = "";
let data: typeof import("../src/lib/boss-data.ts");
let authCookie = "";
let getBackoffice: (typeof import("../src/app/api/v1/admin/backoffice/route.ts"))["GET"];
let baseState: Awaited<ReturnType<typeof import("../src/lib/boss-data.ts")["readState"]>>;
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-admin-backoffice-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [dataModule, authModule, routeModule] = await Promise.all([
import("../src/lib/boss-data.ts"),
import("../src/lib/boss-auth.ts"),
import("../src/app/api/v1/admin/backoffice/route.ts"),
]);
data = dataModule;
authCookie = authModule.AUTH_SESSION_COOKIE;
getBackoffice = routeModule.GET;
baseState = structuredClone(await data.readState());
}
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
}
});
test.beforeEach(async () => {
await setup();
const state = structuredClone(baseState);
const now = "2026-04-30T10:00:00+08:00";
state.adminCompanies = [
{
companyId: "acme",
name: "Acme 科技",
ownerAccount: "owner@acme.com",
successOwnerAccount: "cs@boss.com",
planTier: "enterprise",
contractExpiresAt: "2027-04-30T00:00:00+08:00",
status: "active",
createdAt: now,
updatedAt: now,
},
];
state.authAccounts = [
{
id: "account-owner",
account: "owner@acme.com",
passwordHash: "do-not-leak-owner-password-hash",
displayName: "Acme 老板",
role: "highest_admin",
status: "active",
companyId: "acme",
mfaSecret: "do-not-leak-mfa-secret",
primaryDeviceId: "mac-1",
createdAt: now,
updatedAt: now,
lastLoginAt: now,
},
{
id: "account-dev",
account: "dev@acme.com",
passwordHash: "do-not-leak-dev-password-hash",
displayName: "开发同事",
role: "member",
status: "active",
companyId: "acme",
primaryDeviceId: "win-1",
createdAt: now,
updatedAt: now,
},
];
state.authSessions = [];
state.devices = [
{
id: "mac-1",
name: "Acme Mac Studio",
avatar: "A",
account: "owner@acme.com",
companyId: "acme",
source: "production",
status: "online",
projects: ["project-acme"],
quota5h: 0,
quota7d: 0,
lastSeenAt: "2026-04-30T09:58:00+08:00",
preferredExecutionMode: "cli",
capabilities: {
gui: { connected: true, lastSeenAt: "2026-04-30T09:58:00+08:00" },
cli: { connected: true, lastSeenAt: "2026-04-30T09:58:00+08:00" },
browserAutomation: { connected: true, lastSeenAt: "2026-04-30T09:57:00+08:00" },
computerUse: { connected: true, lastSeenAt: "2026-04-30T09:57:00+08:00" },
},
},
{
id: "win-1",
name: "Acme Windows",
avatar: "W",
account: "dev@acme.com",
companyId: "acme",
source: "production",
status: "offline",
projects: ["project-acme"],
quota5h: 0,
quota7d: 0,
lastSeenAt: "2026-04-30T08:00:00+08:00",
preferredExecutionMode: "gui",
capabilities: {
gui: { connected: false },
cli: { connected: false },
browserAutomation: { connected: false },
computerUse: { connected: false },
},
},
];
state.projects = [
{
id: "project-acme",
name: "Acme 生产项目",
pinned: false,
deviceIds: ["mac-1", "win-1"],
preview: "企业生产项目",
updatedAt: now,
lastMessageAt: now,
isGroup: false,
threadMeta: {
projectId: "project-acme",
threadId: "thread-acme",
threadDisplayName: "Acme 线程",
folderName: "acme",
activityIconCount: 0,
updatedAt: now,
},
groupMembers: [],
createdByAgent: false,
collaborationMode: "development",
approvalState: "not_required",
unreadCount: 0,
riskLevel: "low",
messages: [],
goals: [],
versions: [],
},
];
state.deviceSkills = [
{
skillId: "mac-1:boss-server-debug",
deviceId: "mac-1",
name: "boss-server-debug",
description: "Boss 服务器调试",
path: "/Users/kris/.codex/skills/boss-server-debug/SKILL.md",
invocation: "$boss-server-debug",
category: "运维",
updatedAt: now,
},
];
state.opsFaults = [
{
faultId: "fault-1",
faultKey: "LOCAL_AGENT.HEARTBEAT_FAILED",
severity: "warning",
status: "opened",
nodeId: "win-1",
serviceName: "local-agent",
projectId: "project-acme",
traceId: "trace-1",
runbookId: "runbook-agent",
firstSeenAt: "2026-04-30T08:10:00+08:00",
lastSeenAt: "2026-04-30T08:30:00+08:00",
summary: "Windows 节点心跳失败",
suggestedNextAction: "检查 local-agent",
autoRepairable: true,
},
];
state.permissionAuditLogs = [
{
auditId: "audit-1",
actorAccount: "owner@acme.com",
action: "grant.created",
targetAccount: "dev@acme.com",
deviceId: "mac-1",
permissions: ["device.view"],
detail: "授权设备只读",
createdAt: now,
},
];
state.adminRiskTimeline = [
{
eventId: "risk-event-1",
riskId: "ops-fault:fault-1",
companyId: "acme",
action: "risk.created",
actorAccount: "system",
note: "发现节点风险",
createdAt: now,
},
];
await data.writeState(state);
});
async function authedRequest(account: string, role: "member" | "admin" | "highest_admin") {
const session = await data.createAuthSession({
account,
role,
displayName: account,
loginMethod: "password",
});
return new NextRequest("http://127.0.0.1:3000/api/v1/admin/backoffice", {
headers: {
cookie: `${authCookie}=${session.sessionToken}`,
},
});
}
test("backoffice bff rejects non highest admin accounts", async () => {
await setup();
const response = await getBackoffice(await authedRequest("dev@acme.com", "member"));
assert.equal(response.status, 403);
});
test("backoffice bff exposes yudao style management contract without secrets", async () => {
await setup();
const response = await getBackoffice(await authedRequest("owner@acme.com", "highest_admin"));
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.ok, true);
assert.deepEqual(
payload.menuTree.map((item: { label: string }) => item.label),
["工作台", "租户管理", "账号管理", "角色权限", "资源授权", "Skill 中心", "风险告警", "审计日志", "系统设置"],
);
assert.equal(payload.yudaoMapping.tenant, "adminCompanies");
assert.equal(payload.yudaoMapping.user, "authAccounts");
assert.equal(payload.yudaoMapping.role, "BOSS_PERMISSION_TEMPLATES");
assert.equal(payload.workbench.summary.companies >= 1, true);
assert.equal(
payload.tenants.some((tenant: { name: string }) => tenant.name === "Acme 科技"),
true,
);
assert.equal(payload.users[0].passwordHash, undefined);
assert.equal(payload.users[0].mfaSecret, undefined);
assert.equal(payload.roles.permissionTemplates.length >= 3, true);
assert.equal(payload.resourceGroups.devices.length, 2);
assert.equal(
payload.resourceGroups.projects.some((project: { name: string }) => project.name === "Acme 生产项目"),
true,
);
assert.equal(payload.resourceGroups.skills[0].name, "boss-server-debug");
assert.equal(payload.audit.permissionLogs.length, 1);
assert.equal(payload.audit.risks.length >= 1, true);
const serialized = JSON.stringify(payload);
assert.equal(serialized.includes("do-not-leak-owner-password-hash"), false);
assert.equal(serialized.includes("do-not-leak-mfa-secret"), false);
});

View File

@@ -0,0 +1,288 @@
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";
import { NextRequest } from "next/server";
let runtimeRoot = "";
let data: typeof import("../src/lib/boss-data");
let authCookie = "";
let getAdminAccess: (typeof import("../src/app/api/v1/admin/access/route"))["GET"];
let postAdminAccess: (typeof import("../src/app/api/v1/admin/access/route"))["POST"];
let baseState: Awaited<ReturnType<typeof import("../src/lib/boss-data")["readState"]>>;
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-admin-company-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [dataModule, authModule, routeModule] = await Promise.all([
import("../src/lib/boss-data.ts"),
import("../src/lib/boss-auth.ts"),
import("../src/app/api/v1/admin/access/route.ts"),
]);
data = dataModule;
authCookie = authModule.AUTH_SESSION_COOKIE;
getAdminAccess = routeModule.GET;
postAdminAccess = routeModule.POST;
baseState = structuredClone(await data.readState());
}
test.after(async () => {
if (runtimeRoot) await rm(runtimeRoot, { recursive: true, force: true });
});
test.beforeEach(async () => {
await setup();
const state = structuredClone(baseState);
const now = "2026-04-27T14:00:00+08:00";
state.authAccounts = [
{
id: "account-owner",
account: "owner@platform.com",
passwordHash: "secret",
displayName: "平台老板",
role: "highest_admin",
createdAt: now,
updatedAt: now,
},
{
id: "account-worker",
account: "worker@acme.com",
passwordHash: "secret",
displayName: "待分配成员",
role: "member",
createdAt: now,
updatedAt: now,
},
];
state.authSessions = [];
state.devices = [
{
id: "mac-1",
name: "客户 Mac",
avatar: "M",
account: "worker@acme.com",
source: "production",
status: "online",
projects: [],
quota5h: 0,
quota7d: 0,
lastSeenAt: now,
preferredExecutionMode: "cli",
capabilities: {
gui: { connected: true, lastSeenAt: now },
cli: { connected: true, lastSeenAt: now },
browserAutomation: { connected: false },
computerUse: { connected: false },
},
},
];
state.accountDeviceGrants = [
{
grantId: "grant-device",
account: "worker@acme.com",
deviceId: "mac-1",
permissions: ["device.view"],
grantedBy: "owner@platform.com",
grantedAt: now,
},
];
state.accountProjectGrants = [];
state.accountSkillGrants = [];
state.permissionAuditLogs = [];
await data.writeState(state);
});
async function authedRequest(body?: Record<string, unknown>, method = "POST") {
const session = await data.createAuthSession({
account: "owner@platform.com",
role: "highest_admin",
displayName: "平台老板",
loginMethod: "password",
});
return new NextRequest("http://127.0.0.1:3000/api/v1/admin/access", {
method,
headers: {
"content-type": "application/json",
cookie: `${authCookie}=${session.sessionToken}`,
},
body: body ? JSON.stringify(body) : undefined,
});
}
async function adminPost(body: Record<string, unknown>) {
return postAdminAccess(await authedRequest(body));
}
test("highest admin can manage companies and assign accounts and devices", async () => {
const createCompany = await adminPost({
action: "upsert_company",
companyId: "acme",
name: "Acme 客户",
ownerAccount: "owner@acme.com",
successOwnerAccount: "cs@platform.com",
planTier: "enterprise",
contractExpiresAt: "2026-12-31T23:59:59+08:00",
note: "第一批 To B 客户",
});
assert.equal(createCompany.status, 200);
const createPayload = await createCompany.json();
assert.equal(createPayload.company.companyId, "acme");
assert.equal(createPayload.company.name, "Acme 客户");
assert.equal(createPayload.company.planTier, "enterprise");
assert.equal(createPayload.company.successOwnerAccount, "cs@platform.com");
assert.equal(createPayload.company.contractExpiresAt, "2026-12-31T23:59:59+08:00");
const assignAccount = await adminPost({
action: "assign_account_company",
account: "worker@acme.com",
companyId: "acme",
});
assert.equal(assignAccount.status, 200);
assert.equal((await assignAccount.json()).account.companyId, "acme");
const assignDevice = await adminPost({
action: "assign_device_company",
deviceId: "mac-1",
companyId: "acme",
});
assert.equal(assignDevice.status, 200);
assert.equal((await assignDevice.json()).device.companyId, "acme");
const getResponse = await getAdminAccess(await authedRequest(undefined, "GET"));
const getPayload = await getResponse.json();
assert.equal(getPayload.companies.some((company: { companyId: string }) => company.companyId === "acme"), true);
assert.equal(getPayload.companies.find((company: { companyId: string }) => company.companyId === "acme")?.planTier, "enterprise");
assert.equal(getPayload.accounts.find((account: { account: string }) => account.account === "worker@acme.com")?.companyId, "acme");
assert.equal(getPayload.devices.find((device: { id: string }) => device.id === "mac-1")?.companyId, "acme");
});
test("highest admin can toggle account MFA without exposing the secret in access GET", async () => {
const response = await adminPost({
action: "set_account_mfa_required",
account: "worker@acme.com",
required: true,
});
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.account.mfaRequired, true);
assert.equal(typeof payload.mfaSetupSecret, "string");
assert.equal(payload.account.mfaSecret, undefined);
const getResponse = await getAdminAccess(await authedRequest(undefined, "GET"));
const getPayload = await getResponse.json();
const account = getPayload.accounts.find((item: { account: string }) => item.account === "worker@acme.com");
assert.equal(account.mfaRequired, true);
assert.equal(account.mfaSecret, undefined);
});
test("admin access GET returns safe project metadata without chat transcripts", async () => {
const state = await data.readState();
const now = "2026-04-27T14:05:00+08:00";
state.projects = [
{
id: "project-secret",
name: "敏感项目",
pinned: false,
deviceIds: ["mac-1"],
preview: "passwordHash should not leave through admin access",
updatedAt: now,
lastMessageAt: now,
isGroup: false,
threadMeta: {
projectId: "project-secret",
threadId: "thread-secret",
threadDisplayName: "敏感线程",
folderName: "secret",
activityIconCount: 1,
updatedAt: now,
},
groupMembers: [],
createdByAgent: false,
collaborationMode: "development",
approvalState: "not_required",
unreadCount: 0,
riskLevel: "low",
messages: [
{
id: "msg-secret",
role: "assistant",
author: "线程",
body: "passwordHash=secret should stay in project transcript only",
createdAt: now,
},
],
goals: [],
versions: [],
},
];
await data.writeState(state);
const response = await getAdminAccess(await authedRequest(undefined, "GET"));
assert.equal(response.status, 200);
const payload = await response.json();
const project = payload.projects.find((item: { id: string }) => item.id === "project-secret");
assert.equal(project.name, "敏感项目");
assert.deepEqual(project.deviceIds, ["mac-1"]);
assert.equal(project.messages, undefined);
assert.equal(project.preview, undefined);
assert.equal(JSON.stringify(payload.accounts).includes("passwordHash"), false);
assert.equal(JSON.stringify(payload.projects).includes("passwordHash"), false);
});
test("highest admin can bulk import accounts into a company", async () => {
await adminPost({ action: "upsert_company", companyId: "acme", name: "Acme 客户" });
const response = await adminPost({
action: "bulk_import_accounts",
companyId: "acme",
accounts: [
{ account: "pm@acme.com", displayName: "产品负责人", role: "admin", password: "pass-123" },
{ account: "dev2@acme.com", displayName: "开发二号", role: "member", password: "pass-456" },
],
});
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.imported.length, 2);
assert.equal(payload.imported.every((account: { passwordHash?: string }) => account.passwordHash === undefined), true);
const state = await data.readState();
assert.deepEqual(
state.authAccounts
.filter((account) => account.companyId === "acme")
.map((account) => account.account)
.sort(),
["dev2@acme.com", "pm@acme.com"],
);
});
test("highest admin can reclaim a leaving account and clear its grants and sessions", async () => {
const workerSession = await data.createAuthSession({
account: "worker@acme.com",
role: "member",
displayName: "待分配成员",
loginMethod: "password",
});
const response = await adminPost({
action: "reclaim_account",
account: "worker@acme.com",
reason: "员工离职",
});
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.account.status, "disabled");
assert.equal(payload.removedGrants.deviceGrants, 1);
const state = await data.readState();
assert.equal(state.authAccounts.find((account) => account.account === "worker@acme.com")?.status, "disabled");
assert.equal(state.accountDeviceGrants.some((grant) => grant.account === "worker@acme.com"), false);
assert.equal(
state.authSessions.find((session) => session.sessionToken === workerSession.sessionToken)?.revokedAt !== undefined,
true,
);
assert.equal(state.permissionAuditLogs.at(0)?.action, "account.reclaimed");
});

View File

@@ -0,0 +1,35 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
const appRootPath = new URL("../src/app/page.tsx", import.meta.url);
const appUiPath = new URL("../src/components/app-ui.tsx", import.meta.url);
const caddyfilePath = new URL("../deployment/Caddyfile", import.meta.url);
async function readSource(path: URL) {
return readFile(path, "utf8");
}
test("admin host root redirects to the platform admin console", async () => {
const source = await readSource(appRootPath);
assert.match(source, /headers/);
assert.match(source, /admin\.boss\.hyzq\.net/);
assert.match(source, /redirect\(session \? "\/admin" : "\/auth\/login"\)/);
});
test("web login returns admin host users to the admin console", async () => {
const source = await readSource(appUiPath);
assert.match(source, /admin\.boss\.hyzq\.net/);
assert.match(source, /navigateAfterLogin/);
assert.match(source, /\/admin/);
});
test("Caddy serves the platform admin subdomain", async () => {
const source = await readSource(caddyfilePath);
assert.match(source, /admin\.boss\.hyzq\.net/);
assert.match(source, /redir \/ \/admin/);
assert.match(source, /reverse_proxy 127\.0\.0\.1:3000/);
});

View File

@@ -0,0 +1,211 @@
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";
import { NextRequest } from "next/server";
let runtimeRoot = "";
let data: typeof import("../src/lib/boss-data");
let authCookie = "";
let postAdminAccess: (typeof import("../src/app/api/v1/admin/access/route"))["POST"];
let baseState: Awaited<ReturnType<typeof import("../src/lib/boss-data")["readState"]>>;
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-admin-enterprise-ops-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [dataModule, authModule, routeModule] = await Promise.all([
import("../src/lib/boss-data.ts"),
import("../src/lib/boss-auth.ts"),
import("../src/app/api/v1/admin/access/route.ts"),
]);
data = dataModule;
authCookie = authModule.AUTH_SESSION_COOKIE;
postAdminAccess = routeModule.POST;
baseState = structuredClone(await data.readState());
}
test.after(async () => {
if (runtimeRoot) await rm(runtimeRoot, { recursive: true, force: true });
});
test.beforeEach(async () => {
await setup();
const state = structuredClone(baseState);
const now = "2026-04-27T18:00:00+08:00";
state.adminCompanies = [
{
companyId: "tenant-a",
name: "Tenant A",
status: "active",
ownerAccount: "admin@tenant-a.com",
createdAt: now,
updatedAt: now,
},
];
state.authAccounts = [
{
id: "account-owner",
account: "owner@platform.com",
passwordHash: data.hashPassword("OwnerPass123"),
displayName: "平台老板",
role: "highest_admin",
createdAt: now,
updatedAt: now,
},
{
id: "account-admin",
account: "admin@tenant-a.com",
passwordHash: data.hashPassword("OldPass123"),
displayName: "客户管理员",
role: "admin",
companyId: "tenant-a",
createdAt: now,
updatedAt: now,
},
{
id: "account-worker",
account: "worker@tenant-a.com",
passwordHash: data.hashPassword("WorkerPass123"),
displayName: "客户成员",
role: "member",
companyId: "tenant-a",
createdAt: now,
updatedAt: now,
},
];
state.authSessions = [];
state.permissionAuditLogs = [];
await data.writeState(state);
});
async function authedRequest(body: Record<string, unknown>, extraHeaders: Record<string, string> = {}) {
const session = await data.createAuthSession({
account: "owner@platform.com",
role: "highest_admin",
displayName: "平台老板",
loginMethod: "password",
});
return new NextRequest("http://127.0.0.1:3000/api/v1/admin/access", {
method: "POST",
headers: {
"content-type": "application/json",
cookie: `${authCookie}=${session.sessionToken}`,
...extraHeaders,
},
body: JSON.stringify(body),
});
}
async function adminPost(body: Record<string, unknown>) {
return postAdminAccess(await authedRequest(body));
}
async function adminPostWithHeaders(body: Record<string, unknown>, headers: Record<string, string>) {
return postAdminAccess(await authedRequest(body, headers));
}
test("bulk import preview reports create and update rows without mutating accounts", async () => {
const response = await adminPost({
action: "preview_bulk_import_accounts",
companyId: "tenant-a",
accounts: [
{ account: "worker@tenant-a.com", displayName: "现有成员", role: "member" },
{ account: "new@tenant-a.com", displayName: "新成员", role: "admin" },
],
});
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.preview.summary.create, 1);
assert.equal(payload.preview.summary.update, 1);
assert.deepEqual(
payload.preview.rows.map((row: { account: string; operation: string }) => [row.account, row.operation]),
[
["worker@tenant-a.com", "update"],
["new@tenant-a.com", "create"],
],
);
const state = await data.readState();
assert.equal(state.authAccounts.some((account) => account.account === "new@tenant-a.com"), false);
});
test("highest admin can reset a child account password and revoke old sessions", async () => {
const oldSession = await data.loginAccount({
account: "worker@tenant-a.com",
password: "WorkerPass123",
method: "password",
});
const response = await adminPostWithHeaders(
{
action: "reset_account_password",
account: "worker@tenant-a.com",
password: "NewWorkerPass456",
},
{
"x-forwarded-for": "203.0.113.10, 10.0.0.1",
"user-agent": "BossAdminTest/1.0",
"x-request-id": "req-reset-001",
},
);
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.account.account, "worker@tenant-a.com");
assert.equal(payload.account.passwordHash, undefined);
await assert.rejects(
data.loginAccount({ account: "worker@tenant-a.com", password: "WorkerPass123", method: "password" }),
/INVALID_ACCOUNT_OR_PASSWORD/,
);
const login = await data.loginAccount({
account: "worker@tenant-a.com",
password: "NewWorkerPass456",
method: "password",
});
assert.equal(login.account, "worker@tenant-a.com");
assert.equal(await data.getAuthSession(oldSession.sessionToken), null);
const audit = (await data.readState()).permissionAuditLogs.at(0);
assert.equal(audit?.action, "account.password_reset");
assert.equal(audit?.ipAddress, "203.0.113.10");
assert.equal(audit?.userAgent, "BossAdminTest/1.0");
assert.equal(audit?.requestId, "req-reset-001");
assert.deepEqual(audit?.beforeJson, { account: "worker@tenant-a.com", status: "active", activeSessions: 1 });
assert.deepEqual(audit?.afterJson, { account: "worker@tenant-a.com", status: "active", revokedSessions: 1 });
});
test("disabling a company disables child accounts and revokes their sessions", async () => {
const workerSession = await data.createAuthSession({
account: "worker@tenant-a.com",
role: "member",
displayName: "客户成员",
loginMethod: "password",
});
const response = await adminPost({
action: "set_company_status",
companyId: "tenant-a",
status: "disabled",
});
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.company.status, "disabled");
assert.equal(payload.disabledAccounts, 2);
const state = await data.readState();
assert.equal(state.adminCompanies.find((company) => company.companyId === "tenant-a")?.status, "disabled");
assert.equal(
state.authAccounts
.filter((account) => account.companyId === "tenant-a")
.every((account) => account.status === "disabled"),
true,
);
assert.equal(
state.authSessions.find((session) => session.sessionToken === workerSession.sessionToken)?.revokedAt !== undefined,
true,
);
assert.equal(state.permissionAuditLogs.at(0)?.action, "company.status_updated");
});

View File

@@ -0,0 +1,133 @@
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";
import { NextRequest } from "next/server";
let runtimeRoot = "";
let data: typeof import("../src/lib/boss-data");
let authCookie = "";
let postDispatch: (typeof import("../src/app/api/v1/admin/notifications/dispatch/route"))["POST"];
let getOverview: (typeof import("../src/app/api/v1/admin/overview/route"))["GET"];
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-admin-notification-dispatch-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
process.env.BOSS_ADMIN_NOTIFICATION_MODE = "disabled";
const [dataModule, authModule, dispatchRoute, overviewRoute] = await Promise.all([
import("../src/lib/boss-data.ts"),
import("../src/lib/boss-auth.ts"),
import("../src/app/api/v1/admin/notifications/dispatch/route.ts"),
import("../src/app/api/v1/admin/overview/route.ts"),
]);
data = dataModule;
authCookie = authModule.AUTH_SESSION_COOKIE;
postDispatch = dispatchRoute.POST;
getOverview = overviewRoute.GET;
}
test.after(async () => {
if (runtimeRoot) await rm(runtimeRoot, { recursive: true, force: true });
});
test.beforeEach(async () => {
await setup();
const now = "2026-04-27T18:20:00+08:00";
const state = await data.readState();
await data.writeState({
...state,
adminCompanies: [
{
companyId: "tenant-a",
name: "Tenant A",
ownerAccount: "owner@tenant-a.com",
successOwnerAccount: "cs@platform.com",
status: "active",
createdAt: now,
updatedAt: now,
},
],
authAccounts: [
{
id: "account-platform",
account: "platform@example.com",
passwordHash: "hash",
displayName: "平台管理员",
role: "highest_admin",
createdAt: now,
updatedAt: now,
},
{
id: "account-owner",
account: "owner@tenant-a.com",
passwordHash: "hash",
displayName: "客户负责人",
role: "admin",
companyId: "tenant-a",
createdAt: now,
updatedAt: now,
},
],
adminNotifications: [
{
notificationId: "risk-sla-overdue:ops-fault:fault-a",
kind: "risk_sla_overdue",
severity: "critical",
companyId: "tenant-a",
riskId: "ops-fault:fault-a",
title: "风险 SLA 已超时local-agent",
body: "local-agent 离线超过 SLA",
status: "open",
createdAt: now,
},
],
adminRiskTimeline: [],
});
});
async function adminRequest(url: string, init: RequestInit = {}) {
const session = await data.createAuthSession({
account: "platform@example.com",
role: "highest_admin",
displayName: "平台管理员",
loginMethod: "password",
});
return new NextRequest(url, {
...init,
headers: {
"content-type": "application/json",
...(init.headers ?? {}),
cookie: `${authCookie}=${session.sessionToken}`,
},
});
}
test("highest admin can dispatch open risk notifications and persist delivery status", async () => {
const response = await postDispatch(await adminRequest("http://127.0.0.1:3000/api/v1/admin/notifications/dispatch", {
method: "POST",
}));
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.results.length, 1);
assert.equal(payload.results[0].notificationId, "risk-sla-overdue:ops-fault:fault-a");
assert.equal(payload.results[0].status, "disabled");
const state = await data.readState();
assert.equal(state.adminNotifications[0]?.deliveryStatus, "disabled");
assert.equal(state.adminNotifications[0]?.deliveryTarget?.includes("owner@tenant-a.com"), true);
assert.equal(state.adminRiskTimeline.some((event) => event.action === "notification_dispatch_disabled"), true);
});
test("admin overview exposes recent risk timeline events", async () => {
await postDispatch(await adminRequest("http://127.0.0.1:3000/api/v1/admin/notifications/dispatch", { method: "POST" }));
const response = await getOverview(await adminRequest("http://127.0.0.1:3000/api/v1/admin/overview"));
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.riskTimeline.length > 0, true);
assert.equal(payload.riskTimeline[0].riskId, "ops-fault:fault-a");
});

View File

@@ -0,0 +1,369 @@
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";
import { NextRequest } from "next/server";
let runtimeRoot = "";
let data: typeof import("../src/lib/boss-data.ts");
let authCookie = "";
let getAdminOverview: (typeof import("../src/app/api/v1/admin/overview/route.ts"))["GET"];
let baseState: Awaited<ReturnType<typeof import("../src/lib/boss-data.ts")["readState"]>>;
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-admin-overview-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [dataModule, authModule, routeModule] = await Promise.all([
import("../src/lib/boss-data.ts"),
import("../src/lib/boss-auth.ts"),
import("../src/app/api/v1/admin/overview/route.ts"),
]);
data = dataModule;
authCookie = authModule.AUTH_SESSION_COOKIE;
getAdminOverview = routeModule.GET;
baseState = structuredClone(await data.readState());
}
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
}
});
test.beforeEach(async () => {
await setup();
const state = structuredClone(baseState);
const now = "2026-04-27T10:00:00+08:00";
state.authAccounts = [
{
id: "account-owner",
account: "owner@acme.com",
passwordHash: "secret",
displayName: "Acme 老板",
role: "highest_admin",
primaryDeviceId: "mac-1",
createdAt: now,
updatedAt: now,
lastLoginAt: now,
},
{
id: "account-dev",
account: "dev@acme.com",
passwordHash: "secret",
displayName: "开发同事",
role: "member",
primaryDeviceId: "win-1",
createdAt: now,
updatedAt: now,
},
];
state.authSessions = [];
state.devices = [
{
id: "mac-1",
name: "Acme Mac Studio",
avatar: "A",
account: "owner@acme.com",
source: "production",
status: "online",
projects: ["project-acme"],
quota5h: 0,
quota7d: 0,
lastSeenAt: "2026-04-27T09:58:00+08:00",
preferredExecutionMode: "cli",
capabilities: {
gui: { connected: true, lastSeenAt: "2026-04-27T09:58:00+08:00" },
cli: { connected: true, lastSeenAt: "2026-04-27T09:58:00+08:00" },
browserAutomation: { connected: false },
computerUse: { connected: false },
},
},
{
id: "win-1",
name: "Acme Windows",
avatar: "W",
account: "dev@acme.com",
source: "production",
status: "offline",
projects: ["project-acme"],
quota5h: 0,
quota7d: 0,
lastSeenAt: "2026-04-27T08:00:00+08:00",
preferredExecutionMode: "gui",
capabilities: {
gui: { connected: false },
cli: { connected: false },
browserAutomation: { connected: false },
computerUse: { connected: false },
},
},
];
state.projects = [
{
id: "project-acme",
name: "Acme 生产项目",
pinned: false,
deviceIds: ["mac-1", "win-1"],
preview: "",
updatedAt: now,
lastMessageAt: now,
isGroup: false,
threadMeta: {
projectId: "project-acme",
threadId: "thread-acme",
threadDisplayName: "Acme 线程",
folderName: "acme",
activityIconCount: 0,
updatedAt: now,
},
groupMembers: [],
createdByAgent: false,
collaborationMode: "development",
approvalState: "not_required",
unreadCount: 0,
riskLevel: "low",
messages: [],
goals: [],
versions: [],
},
];
state.opsFaults = [
{
faultId: "fault-1",
faultKey: "LOCAL_AGENT.HEARTBEAT_FAILED",
severity: "critical",
status: "opened",
nodeId: "win-1",
serviceName: "local-agent",
projectId: "project-acme",
traceId: "trace-1",
runbookId: "runbook-agent",
firstSeenAt: "2026-04-27T09:00:00+08:00",
lastSeenAt: "2026-04-27T09:30:00+08:00",
summary: "Windows 节点心跳失败",
suggestedNextAction: "检查 local-agent",
autoRepairable: true,
},
];
state.threadContextAlerts = [
{
alertId: "alert-1",
projectId: "project-acme",
threadId: "thread-acme",
alertType: "context_critical",
alertStatus: "opened",
openedAt: "2026-04-27T09:20:00+08:00",
summary: "线程上下文接近耗尽",
masterActions: ["先固化版本记录"],
},
];
state.threadContextSnapshots = [
{
snapshotId: "snapshot-1",
projectId: "project-acme",
taskId: "task-context-1",
threadId: "thread-acme",
title: "Acme 线程",
summary: "上下文接近耗尽",
nodeId: "mac-1",
workerId: "worker-1",
sourceKind: "codex_sdk",
status: "running",
contextBudgetRemainingPct: 8,
contextBudgetLevel: "critical",
mustFinishBeforeCompaction: true,
estimatedRemainingTurns: 1,
estimatedRemainingLargeMessages: 0,
compactionCount: 0,
patchPending: true,
testsPending: true,
evidencePending: false,
checklist: ["固化项目目标", "补版本记录"],
capturedAt: "2026-04-27T09:19:00+08:00",
},
];
state.masterAgentTasks = [
{
taskId: "task-failed-1",
projectId: "project-acme",
taskType: "conversation_reply",
requestMessageId: "msg-1",
requestText: "帮我继续开发",
executionPrompt: "prompt",
requestedBy: "owner@acme.com",
requestedByAccount: "owner@acme.com",
deviceId: "mac-1",
status: "failed",
requestedAt: "2026-04-27T09:10:00+08:00",
completedAt: "2026-04-27T09:12:00+08:00",
errorMessage: "Master Codex Node 执行失败",
},
];
state.accountDeviceGrants = [
{
grantId: "grant-expired",
account: "dev@acme.com",
deviceId: "mac-1",
permissions: ["device.view"],
grantedBy: "owner@acme.com",
grantedAt: "2026-04-20T10:00:00+08:00",
expiresAt: "2026-04-21T10:00:00+08:00",
},
];
state.accountProjectGrants = [];
state.accountSkillGrants = [];
await data.writeState(state);
});
async function authedRequest(account: string, role: "member" | "admin" | "highest_admin") {
const session = await data.createAuthSession({
account,
role,
displayName: account,
loginMethod: "password",
});
return new NextRequest("http://127.0.0.1:3000/api/v1/admin/overview", {
headers: { cookie: `${authCookie}=${session.sessionToken}` },
});
}
test("admin overview requires highest admin", async () => {
await setup();
const unauthorized = await getAdminOverview(
new NextRequest("http://127.0.0.1:3000/api/v1/admin/overview"),
);
assert.equal(unauthorized.status, 401);
const forbidden = await getAdminOverview(await authedRequest("dev@acme.com", "member"));
assert.equal(forbidden.status, 403);
});
test("highest admin can read companies devices risks and grant summary", async () => {
await setup();
const response = await getAdminOverview(await authedRequest("owner@acme.com", "highest_admin"));
assert.equal(response.status, 200);
assert.equal(response.headers.get("cache-control"), "private, no-store, max-age=0");
const payload = await response.json();
assert.equal(payload.ok, true);
assert.equal(payload.summary.companies, 2);
assert.equal(payload.summary.accounts, 3);
assert.equal(payload.summary.devices, 2);
assert.equal(payload.summary.onlineDevices, 1);
assert.equal(payload.summary.openRisks, 4);
assert.equal(payload.summary.criticalRisks, 2);
const acme = payload.companies.find((company: { companyId: string }) => company.companyId === "acme.com");
assert.equal(acme.name, "acme.com");
assert.equal(acme.accountCount, 2);
assert.equal(acme.adminCount, 1);
assert.equal(acme.deviceCount, 2);
assert.equal(acme.onlineDeviceCount, 1);
assert.equal(acme.openRiskCount, 4);
assert.equal(payload.accounts[0].passwordHash, undefined);
const offlineRisk = payload.risks.find((risk: { kind: string }) => risk.kind === "device_offline");
assert.equal(offlineRisk.deviceId, "win-1");
assert.equal(offlineRisk.companyId, "acme.com");
assert.match(offlineRisk.title, /设备离线/);
const failedTaskRisk = payload.risks.find((risk: { kind: string }) => risk.kind === "master_agent_task_failed");
assert.match(failedTaskRisk.detail, /Master Codex Node/);
const winDevice = payload.devices.find((device: { id: string }) => device.id === "win-1");
assert.equal(winDevice.projectCount, 1);
assert.equal(winDevice.codexGuiOnline, false);
assert.equal(winDevice.codexCliOnline, false);
assert.equal(winDevice.openRiskCount, 2);
assert.deepEqual(payload.grantsSummary, {
deviceGrants: 1,
projectGrants: 0,
skillGrants: 0,
expiredGrants: 1,
});
});
test("admin overview folds repeated master-agent task failures for the same device and project", async () => {
const state = await data.readState();
const baseTask = state.masterAgentTasks[0];
assert.ok(baseTask);
state.masterAgentTasks = [
baseTask,
{
...baseTask,
taskId: "task-failed-2",
requestMessageId: "msg-2",
requestedAt: "2026-04-27T09:14:00+08:00",
completedAt: "2026-04-27T09:15:00+08:00",
errorMessage: "Master Codex Node 执行失败",
},
{
...baseTask,
taskId: "task-failed-3",
requestMessageId: "msg-3",
requestedAt: "2026-04-27T09:16:00+08:00",
completedAt: "2026-04-27T09:17:00+08:00",
errorMessage: "Master Codex Node 执行失败",
},
];
await data.writeState(state);
const response = await getAdminOverview(await authedRequest("owner@acme.com", "highest_admin"));
assert.equal(response.status, 200);
const payload = await response.json();
const masterTaskRisks = payload.risks.filter((risk: { kind: string }) => risk.kind === "master_agent_task_failed");
assert.equal(masterTaskRisks.length, 1);
assert.match(masterTaskRisks[0].detail, /已折叠 2 条同类失败/);
assert.equal(payload.summary.openRisks, 4);
});
test("admin overview prefers explicit company assignment over account domain", async () => {
const state = await data.readState();
const now = "2026-04-27T11:00:00+08:00";
state.adminCompanies = [
{
companyId: "tenant-hyzq",
name: "环宇智擎客户",
ownerAccount: "owner@acme.com",
status: "active",
note: "企业客户",
createdAt: now,
updatedAt: now,
},
];
state.authAccounts = state.authAccounts.map((account) => ({
...account,
companyId: account.account === "dev@acme.com" ? "tenant-hyzq" : account.companyId,
}));
state.devices = state.devices.map((device) => ({
...device,
companyId: device.id === "win-1" ? "tenant-hyzq" : device.companyId,
}));
await data.writeState(state);
const response = await getAdminOverview(await authedRequest("owner@acme.com", "highest_admin"));
assert.equal(response.status, 200);
const payload = await response.json();
const tenant = payload.companies.find((company: { companyId: string }) => company.companyId === "tenant-hyzq");
assert.equal(tenant.name, "环宇智擎客户");
assert.equal(tenant.accountCount, 1);
assert.equal(tenant.deviceCount, 1);
const devAccount = payload.accounts.find((account: { account: string }) => account.account === "dev@acme.com");
assert.equal(devAccount.companyId, "tenant-hyzq");
assert.equal(devAccount.companyName, "环宇智擎客户");
const winDevice = payload.devices.find((device: { id: string }) => device.id === "win-1");
assert.equal(winDevice.companyId, "tenant-hyzq");
assert.equal(winDevice.companyName, "环宇智擎客户");
const offlineRisk = payload.risks.find((risk: { kind: string }) => risk.kind === "device_offline");
assert.equal(offlineRisk.companyId, "tenant-hyzq");
});

View File

@@ -0,0 +1,68 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
const adminPagePath = new URL("../src/app/admin/page.tsx", import.meta.url);
const adminAppPath = new URL("../src/components/admin/boss-admin-app.tsx", import.meta.url);
const dataProviderPath = new URL("../src/components/admin/boss-admin-data-provider.ts", import.meta.url);
async function readSource(path: URL) {
return readFile(path, "utf8");
}
test("/admin page gates the refine shell behind highest_admin", async () => {
const source = await readSource(adminPagePath);
assert.match(source, /requirePageSession/);
assert.match(source, /BossAdminApp/);
assert.match(source, /session\.role\s*!==\s*["']highest_admin["']/);
assert.match(source, /仅最高管理员可用/);
});
test("BossAdminApp wires refine resources and enterprise overview sections", async () => {
const source = await readSource(adminAppPath);
assert.match(source, /['"]use client['"]/);
assert.doesNotMatch(source, /@refinedev\/antd/);
assert.match(source, /Refine/);
assert.match(source, /@refinedev\/core/);
assert.match(source, /ConfigProvider/);
assert.match(source, /Table/);
assert.match(source, /Alert/);
for (const resource of ["companies", "accounts", "devices", "risks", "auditLogs"]) {
assert.match(source, new RegExp(`name:\\s*["']${resource}["']`));
}
for (const title of ["平台运营驾驶舱", "客户与账号", "授权工作台", "风险与治理"]) {
assert.match(source, new RegExp(title));
}
for (const title of ["今日待处理", "客户健康排行", "关键风险队列", "节点健康", "最近事件"]) {
assert.match(source, new RegExp(title));
}
for (const riskAction of ["assign_owner", "set_sla", "负责人", "SLA"]) {
assert.match(source, new RegExp(riskAction));
}
assert.doesNotMatch(source, /window\.prompt/);
});
test("BossAdminApp uses the approved PC To B admin shell", async () => {
const source = await readSource(adminAppPath);
assert.match(source, /adminShell/);
assert.match(source, /adminSidebar/);
assert.match(source, /adminHeader/);
assert.match(source, /currentSubtitle/);
assert.match(source, /bg-\[#F3F5F2\]/);
assert.match(source, /highest_admin/);
assert.match(source, /开放风险/);
assert.match(source, /风险通知/);
});
test("admin data provider reads the overview endpoint and supports initialOverview", async () => {
const appSource = await readSource(adminAppPath);
const providerSource = await readSource(dataProviderPath);
assert.match(appSource, /initialOverview/);
assert.doesNotMatch(providerSource, /@refinedev\/antd/);
assert.match(providerSource, /\/api\/v1\/admin\/overview/);
assert.match(providerSource, /initialOverview/);
});

View File

@@ -0,0 +1,360 @@
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";
import { NextRequest } from "next/server";
let runtimeRoot = "";
let data: typeof import("../src/lib/boss-data");
let authCookie = "";
let postRiskAction: (typeof import("../src/app/api/v1/admin/risks/actions/route"))["POST"];
let subscribeBossEvents: (typeof import("../src/lib/boss-events"))["subscribeBossEvents"];
let baseState: Awaited<ReturnType<typeof import("../src/lib/boss-data")["readState"]>>;
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-admin-risk-actions-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [dataModule, authModule, routeModule, eventsModule] = await Promise.all([
import("../src/lib/boss-data.ts"),
import("../src/lib/boss-auth.ts"),
import("../src/app/api/v1/admin/risks/actions/route.ts"),
import("../src/lib/boss-events.ts"),
]);
data = dataModule;
authCookie = authModule.AUTH_SESSION_COOKIE;
postRiskAction = routeModule.POST;
subscribeBossEvents = eventsModule.subscribeBossEvents;
baseState = structuredClone(await data.readState());
}
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
}
});
test.beforeEach(async () => {
await setup();
const state = structuredClone(baseState);
const now = "2026-04-27T10:00:00+08:00";
state.authAccounts = [
{
id: "account-owner",
account: "owner@acme.com",
passwordHash: "secret",
displayName: "Acme 老板",
role: "highest_admin",
primaryDeviceId: "mac-1",
createdAt: now,
updatedAt: now,
},
{
id: "account-admin",
account: "admin@acme.com",
passwordHash: "secret",
displayName: "Acme 管理员",
role: "admin",
primaryDeviceId: "mac-1",
createdAt: now,
updatedAt: now,
},
];
state.authSessions = [];
state.devices = [
{
id: "mac-1",
name: "Acme Mac",
avatar: "A",
account: "owner@acme.com",
source: "production",
status: "online",
projects: ["project-acme"],
quota5h: 0,
quota7d: 0,
lastSeenAt: "2026-04-27T09:58:00+08:00",
preferredExecutionMode: "cli",
capabilities: {
gui: { connected: true, lastSeenAt: "2026-04-27T09:58:00+08:00" },
cli: { connected: true, lastSeenAt: "2026-04-27T09:58:00+08:00" },
browserAutomation: { connected: false },
computerUse: { connected: false },
},
},
];
state.projects = [
{
id: "project-acme",
name: "Acme 生产项目",
pinned: false,
deviceIds: ["mac-1"],
preview: "",
updatedAt: now,
lastMessageAt: now,
isGroup: false,
threadMeta: {
projectId: "project-acme",
threadId: "thread-acme",
threadDisplayName: "Acme 线程",
folderName: "acme",
activityIconCount: 0,
updatedAt: now,
},
groupMembers: [],
createdByAgent: false,
collaborationMode: "development",
approvalState: "not_required",
unreadCount: 0,
riskLevel: "low",
messages: [],
goals: [],
versions: [],
},
];
state.opsFaults = [
{
faultId: "fault-1",
faultKey: "LOCAL_AGENT.HEARTBEAT_FAILED",
severity: "critical",
status: "opened",
nodeId: "mac-1",
serviceName: "local-agent",
projectId: "project-acme",
traceId: "trace-1",
runbookId: "runbook-agent",
firstSeenAt: "2026-04-27T09:00:00+08:00",
lastSeenAt: "2026-04-27T09:30:00+08:00",
summary: "Mac 节点心跳失败",
suggestedNextAction: "检查 local-agent",
autoRepairable: true,
},
];
state.opsRepairTickets = [];
state.opsRepairVerifications = [];
state.threadContextAlerts = [
{
alertId: "alert-1",
projectId: "project-acme",
threadId: "thread-acme",
alertType: "context_critical",
alertStatus: "opened",
openedAt: "2026-04-27T09:20:00+08:00",
summary: "线程上下文接近耗尽",
masterActions: ["先固化版本记录"],
},
];
state.threadContextSnapshots = [
{
snapshotId: "snapshot-1",
projectId: "project-acme",
taskId: "task-context-1",
threadId: "thread-acme",
title: "Acme 线程",
summary: "上下文接近耗尽",
nodeId: "mac-1",
workerId: "worker-1",
sourceKind: "codex_sdk",
status: "running",
contextBudgetRemainingPct: 8,
contextBudgetLevel: "critical",
mustFinishBeforeCompaction: true,
estimatedRemainingTurns: 1,
estimatedRemainingLargeMessages: 0,
compactionCount: 0,
patchPending: true,
testsPending: true,
evidencePending: false,
checklist: ["固化项目目标"],
capturedAt: "2026-04-27T09:19:00+08:00",
},
];
await data.writeState(state);
});
async function authedRequest(
account: string,
role: "member" | "admin" | "highest_admin",
body: Record<string, unknown>,
) {
const session = await data.createAuthSession({
account,
role,
displayName: account,
loginMethod: "password",
});
return new NextRequest("http://127.0.0.1:3000/api/v1/admin/risks/actions", {
method: "POST",
headers: {
"content-type": "application/json",
cookie: `${authCookie}=${session.sessionToken}`,
},
body: JSON.stringify(body),
});
}
async function adminPost(body: Record<string, unknown>) {
return postRiskAction(await authedRequest("owner@acme.com", "highest_admin", body));
}
test("admin risk actions require highest admin", async () => {
await setup();
const unauthorized = await postRiskAction(
new NextRequest("http://127.0.0.1:3000/api/v1/admin/risks/actions", {
method: "POST",
body: JSON.stringify({ riskId: "ops-fault:fault-1", action: "ack" }),
}),
);
assert.equal(unauthorized.status, 401);
const forbidden = await postRiskAction(
await authedRequest("admin@acme.com", "admin", { riskId: "ops-fault:fault-1", action: "ack" }),
);
assert.equal(forbidden.status, 403);
});
test("highest admin can ack and resolve an ops fault", async () => {
const events: Array<{ event: string; payload: { projectId?: string; deviceId?: string; status?: string; note?: string } }> = [];
const unsubscribe = subscribeBossEvents((event, payload) => {
events.push({ event, payload });
});
const ackResponse = await adminPost({ riskId: "ops-fault:fault-1", action: "ack" });
assert.equal(ackResponse.status, 200);
const ackPayload = await ackResponse.json();
assert.equal(ackPayload.ok, true);
assert.equal(ackPayload.riskId, "ops-fault:fault-1");
assert.equal(ackPayload.action, "ack");
assert.equal(ackPayload.fault.status, "acked");
let state = await data.readState();
assert.equal(state.opsFaults.find((fault) => fault.faultId === "fault-1")?.status, "acked");
const ackEvent = events.at(-1);
assert.equal(ackEvent?.event, "project.context_risk.updated");
assert.equal(ackEvent?.payload.projectId, "project-acme");
assert.equal(ackEvent?.payload.deviceId, "mac-1");
assert.equal(ackEvent?.payload.status, "acked");
assert.equal(ackEvent?.payload.note, "ops-fault:fault-1");
const resolveResponse = await adminPost({ riskId: "ops-fault:fault-1", action: "resolve" });
unsubscribe();
assert.equal(resolveResponse.status, 200);
const resolvePayload = await resolveResponse.json();
assert.equal(resolvePayload.fault.status, "resolved");
state = await data.readState();
assert.equal(state.opsFaults.find((fault) => fault.faultId === "fault-1")?.status, "resolved");
});
test("highest admin can create and reuse an ops repair ticket for a fault", async () => {
const firstResponse = await adminPost({ riskId: "ops-fault:fault-1", action: "create_repair_ticket" });
assert.equal(firstResponse.status, 200);
const firstPayload = await firstResponse.json();
assert.equal(firstPayload.ok, true);
assert.equal(firstPayload.ticket.faultId, "fault-1");
assert.equal(firstPayload.ticket.approvalStatus, "pending");
assert.equal(firstPayload.ticket.executionStatus, "queued");
assert.equal(firstPayload.ticket.targetNodeId, "mac-1");
let state = await data.readState();
assert.equal(state.opsRepairTickets.length, 1);
assert.equal(state.opsRepairTickets[0]?.ticketId, firstPayload.ticket.ticketId);
const secondResponse = await adminPost({ riskId: "ops-fault:fault-1", action: "create_repair_ticket" });
assert.equal(secondResponse.status, 200);
const secondPayload = await secondResponse.json();
assert.equal(secondPayload.ticket.ticketId, firstPayload.ticket.ticketId);
state = await data.readState();
assert.equal(state.opsRepairTickets.length, 1);
});
test("highest admin can ack and resolve a thread context alert", async () => {
const ackResponse = await adminPost({ riskId: "thread-alert:alert-1", action: "ack" });
assert.equal(ackResponse.status, 200);
const ackPayload = await ackResponse.json();
assert.equal(ackPayload.alert.alertStatus, "acked");
assert.equal(ackPayload.alert.resolvedAt, undefined);
let state = await data.readState();
assert.equal(state.threadContextAlerts.find((alert) => alert.alertId === "alert-1")?.alertStatus, "acked");
const resolveResponse = await adminPost({ riskId: "thread-alert:alert-1", action: "resolve" });
assert.equal(resolveResponse.status, 200);
const resolvePayload = await resolveResponse.json();
assert.equal(resolvePayload.alert.alertStatus, "resolved");
assert.equal(typeof resolvePayload.alert.resolvedAt, "string");
state = await data.readState();
const alert = state.threadContextAlerts.find((item) => item.alertId === "alert-1");
assert.equal(alert?.alertStatus, "resolved");
assert.equal(typeof alert?.resolvedAt, "string");
});
test("highest admin can assign owners and SLA to risks", async () => {
const assignFaultResponse = await adminPost({
riskId: "ops-fault:fault-1",
action: "assign_owner",
ownerAccount: "admin@acme.com",
note: "请先处理心跳链路",
});
assert.equal(assignFaultResponse.status, 200);
const assignFaultPayload = await assignFaultResponse.json();
assert.equal(assignFaultPayload.ok, true);
assert.equal(assignFaultPayload.fault.ownerAccount, "admin@acme.com");
assert.equal(assignFaultPayload.fault.riskNote, "请先处理心跳链路");
const setFaultSlaResponse = await adminPost({
riskId: "ops-fault:fault-1",
action: "set_sla",
slaDueAt: "2026-04-27T18:00:00+08:00",
});
assert.equal(setFaultSlaResponse.status, 200);
const setFaultSlaPayload = await setFaultSlaResponse.json();
assert.equal(setFaultSlaPayload.fault.slaDueAt, "2026-04-27T18:00:00+08:00");
const assignAlertResponse = await adminPost({
riskId: "thread-alert:alert-1",
action: "assign_owner",
ownerAccount: "admin@acme.com",
});
assert.equal(assignAlertResponse.status, 200);
const assignAlertPayload = await assignAlertResponse.json();
assert.equal(assignAlertPayload.alert.ownerAccount, "admin@acme.com");
const state = await data.readState();
assert.equal(state.opsFaults.find((fault) => fault.faultId === "fault-1")?.ownerAccount, "admin@acme.com");
assert.equal(state.opsFaults.find((fault) => fault.faultId === "fault-1")?.slaDueAt, "2026-04-27T18:00:00+08:00");
assert.equal(state.threadContextAlerts.find((alert) => alert.alertId === "alert-1")?.ownerAccount, "admin@acme.com");
});
test("risk owner and SLA actions require target fields", async () => {
const assignResponse = await adminPost({ riskId: "ops-fault:fault-1", action: "assign_owner" });
assert.equal(assignResponse.status, 400);
const assignPayload = await assignResponse.json();
assert.equal(assignPayload.message, "RISK_OWNER_REQUIRED");
const slaResponse = await adminPost({ riskId: "ops-fault:fault-1", action: "set_sla" });
assert.equal(slaResponse.status, 400);
const slaPayload = await slaResponse.json();
assert.equal(slaPayload.message, "RISK_SLA_REQUIRED");
});
test("unsupported risk actions return 400", async () => {
const response = await adminPost({ riskId: "device-offline:mac-1", action: "ack" });
assert.equal(response.status, 400);
const payload = await response.json();
assert.equal(payload.ok, false);
assert.equal(payload.message, "RISK_ACTION_UNSUPPORTED");
});
test("missing risk targets return 404", async () => {
const response = await adminPost({ riskId: "ops-fault:missing", action: "ack" });
assert.equal(response.status, 404);
const payload = await response.json();
assert.equal(payload.ok, false);
});

View File

@@ -0,0 +1,147 @@
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";
import { NextRequest } from "next/server";
let runtimeRoot = "";
let data: typeof import("../src/lib/boss-data");
let authCookie = "";
let postScan: (typeof import("../src/app/api/v1/admin/risks/scan/route"))["POST"];
let getOverview: (typeof import("../src/app/api/v1/admin/overview/route"))["GET"];
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-risk-sla-notifications-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [dataModule, authModule, scanRoute, overviewRoute] = await Promise.all([
import("../src/lib/boss-data.ts"),
import("../src/lib/boss-auth.ts"),
import("../src/app/api/v1/admin/risks/scan/route.ts"),
import("../src/app/api/v1/admin/overview/route.ts"),
]);
data = dataModule;
authCookie = authModule.AUTH_SESSION_COOKIE;
postScan = scanRoute.POST;
getOverview = overviewRoute.GET;
}
test.after(async () => {
if (runtimeRoot) await rm(runtimeRoot, { recursive: true, force: true });
});
test.beforeEach(async () => {
await setup();
const now = "2026-04-27T17:30:00+08:00";
const state = await data.readState();
await data.writeState({
...state,
adminNotifications: [],
authAccounts: [
{
id: "account-owner",
account: "owner@example.com",
passwordHash: "hash",
displayName: "平台管理员",
role: "highest_admin",
createdAt: now,
updatedAt: now,
},
{
id: "account-customer",
account: "customer@example.com",
passwordHash: "hash",
displayName: "客户负责人",
role: "admin",
companyId: "tenant-a",
createdAt: now,
updatedAt: now,
},
],
devices: [
{
id: "mac-a",
name: "客户 Mac",
avatar: "M",
account: "customer@example.com",
companyId: "tenant-a",
source: "production",
status: "online",
projects: ["project-a"],
quota5h: 0,
quota7d: 0,
lastSeenAt: now,
},
],
projects: [],
opsFaults: [
{
faultId: "fault-overdue",
faultKey: "LOCAL_AGENT.OFFLINE",
severity: "critical",
status: "opened",
nodeId: "mac-a",
serviceName: "local-agent",
projectId: "project-a",
traceId: "trace-overdue",
runbookId: "runbook-local-agent",
firstSeenAt: "2026-04-27T12:00:00+08:00",
lastSeenAt: "2026-04-27T13:00:00+08:00",
ownerAccount: "customer@example.com",
slaDueAt: "2026-04-27T14:00:00+08:00",
summary: "local-agent 离线超过 SLA",
suggestedNextAction: "联系客户重启本地节点",
autoRepairable: false,
},
],
threadContextAlerts: [],
});
});
async function adminRequest(url: string, init: RequestInit = {}) {
const session = await data.createAuthSession({
account: "owner@example.com",
role: "highest_admin",
displayName: "平台管理员",
loginMethod: "password",
});
return new NextRequest(url, {
...init,
headers: {
...(init.headers ?? {}),
cookie: `${authCookie}=${session.sessionToken}`,
},
});
}
test("risk SLA scan creates idempotent overdue notifications", async () => {
const first = await postScan(await adminRequest("http://127.0.0.1:3000/api/v1/admin/risks/scan", {
method: "POST",
}));
assert.equal(first.status, 200);
const firstPayload = await first.json();
assert.equal(firstPayload.created.length, 1);
assert.equal(firstPayload.notifications[0].kind, "risk_sla_overdue");
assert.equal(firstPayload.notifications[0].companyId, "tenant-a");
const second = await postScan(await adminRequest("http://127.0.0.1:3000/api/v1/admin/risks/scan", {
method: "POST",
}));
assert.equal(second.status, 200);
const secondPayload = await second.json();
assert.equal(secondPayload.created.length, 0);
assert.equal(secondPayload.notifications.length, 1);
});
test("admin overview includes open risk notifications", async () => {
await postScan(await adminRequest("http://127.0.0.1:3000/api/v1/admin/risks/scan", { method: "POST" }));
const response = await getOverview(await adminRequest("http://127.0.0.1:3000/api/v1/admin/overview"));
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.summary.openNotifications, 1);
assert.equal(payload.notifications[0].riskId, "ops-fault:fault-overdue");
});

View File

@@ -0,0 +1,45 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import path from "node:path";
const componentPath = path.join(
process.cwd(),
"src/components/admin/admin-skill-lifecycle-panel.tsx",
);
test("admin skill lifecycle panel exposes the requested source contract", async () => {
const source = await readFile(componentPath, "utf8");
assert.match(source, /export function AdminSkillLifecyclePanel/);
assert.match(source, /\/api\/v1\/admin\/skills\/requests/);
assert.match(source, /method:\s*"POST"/);
assert.match(source, /method:\s*"GET"/);
for (const action of ["install", "update", "uninstall", "rollback", "version_lock"]) {
assert.match(source, new RegExp(`["']${action}["']`));
}
assert.doesNotMatch(source, /@refinedev\/antd/);
});
test("admin skill lifecycle panel keeps the hifi governance summaries", async () => {
const source = await readFile(componentPath, "utf8");
assert.match(source, /最近结果/);
assert.match(source, /校验信息/);
assert.match(source, /Skill 生命周期请求与执行结果/);
assert.match(source, /adminDense/);
});
test("admin skill lifecycle panel is catalog first instead of request form first", async () => {
const source = await readFile(componentPath, "utf8");
for (const title of ["Skill 中心", "Skill 目录", "Skill 详情", "授权对象", "执行轨迹", "安装向导"]) {
assert.match(source, new RegExp(title));
}
assert.doesNotMatch(source, /title="创建 Skill 生命周期请求"/);
assert.match(source, /selectedSkill/);
assert.match(source, /activeDeviceIds/);
});

View File

@@ -0,0 +1,23 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
async function readSource(path: string) {
return readFile(new URL(path, import.meta.url), "utf8");
}
test("enterprise admin route gates static Vue admin behind highest_admin", async () => {
const source = await readSource("../src/app/enterprise-admin/page.tsx");
assert.match(source, /requirePageSession/);
assert.match(source, /session\.role\s*!==\s*["']highest_admin["']/);
assert.match(source, /redirect\(["']\/admin-web\/index\.html["']\)/);
});
test("independent admin build publishes static assets under Next public admin-web", async () => {
const source = await readSource("../apps/boss-admin-web/vite.config.ts");
assert.match(source, /base:\s*["']\/admin-web\/["']/);
assert.match(source, /outDir:\s*["']\.\.\/\.\.\/public\/admin-web["']/);
assert.match(source, /emptyOutDir:\s*true/);
});

View File

@@ -44,7 +44,7 @@ test.after(async () => {
async function createAuthedJsonRequest(url: string, body: Record<string, unknown>) {
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
@@ -126,7 +126,7 @@ test("POST /api/v1/accounts/onboard/master-node upserts a master node account an
await createAuthedJsonRequest("http://127.0.0.1:3000/api/v1/accounts/onboard/master-node", {
label: "主 GPT",
displayName: "Mac 上的 Master Codex Node",
accountIdentifier: "17600003315",
accountIdentifier: "krisolo",
nodeId: "mac-studio",
nodeLabel: "Mac Studio",
model: "gpt-5.4",

View File

@@ -44,7 +44,7 @@ test.after(async () => {
async function createAuthedJsonRequest(url: string, method: "POST" | "PATCH", body: Record<string, unknown>) {
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",

View File

@@ -0,0 +1,101 @@
import test from "node:test";
import assert from "node:assert/strict";
import {
createAliyunOssStorageProvider,
getAliyunOssSignedDownloadUrl,
readAliyunOssObjectBuffer,
validateAliyunOssConfig,
} from "../src/lib/boss-storage-aliyun-oss.ts";
import { encryptStorageSecret } from "../src/lib/boss-storage-secrets.ts";
async function createConfig() {
process.env.BOSS_STORAGE_SECRET_KEY = "aliyun-oss-storage-test-key";
return {
enabled: true,
accessKeyId: "test-access-key",
accessKeySecretEncrypted: await encryptStorageSecret("test-secret"),
bucket: "boss-bucket",
endpoint: "oss-cn-hangzhou.aliyuncs.com",
region: "oss-cn-hangzhou",
prefix: "uploads",
};
}
test("aliyun oss storage uploads attachments through signed REST requests", async () => {
const config = await createConfig();
const originalFetch = globalThis.fetch;
const calls: Array<{ input: string; init?: RequestInit }> = [];
globalThis.fetch = (async (input, init) => {
calls.push({ input: String(input), init });
return new Response("", { status: 200 });
}) as typeof fetch;
try {
const provider = createAliyunOssStorageProvider(config);
const record = await provider.storeAttachment({
account: "kris@example.com",
messageId: "msg-1",
fileName: "report.txt",
mimeType: "text/plain",
buffer: Buffer.from("hello"),
});
assert.equal(record.storageBackend, "aliyun_oss");
assert.match(record.storagePath, /^uploads\/acct-[a-f0-9]{16}\/\d{4}\/\d{2}\/msg-1-report\.txt$/);
assert.equal(calls.length, 1);
assert.match(calls[0].input, /^https:\/\/boss-bucket\.oss-cn-hangzhou\.aliyuncs\.com\/uploads\/acct-/);
assert.equal(calls[0].init?.method, "PUT");
const headers = calls[0].init?.headers as Record<string, string>;
assert.equal(headers["content-type"], "text/plain");
assert.match(headers.authorization, /^OSS test-access-key:/);
assert.ok(headers["x-oss-date"]);
} finally {
globalThis.fetch = originalFetch;
}
});
test("aliyun oss signed download url is generated without network access", async () => {
const config = await createConfig();
const signedUrl = await getAliyunOssSignedDownloadUrl(config, "uploads/demo file.txt", 600);
const url = new URL(signedUrl);
assert.equal(url.hostname, "boss-bucket.oss-cn-hangzhou.aliyuncs.com");
assert.equal(url.pathname, "/uploads/demo%20file.txt");
assert.equal(url.searchParams.get("OSSAccessKeyId"), "test-access-key");
assert.ok(url.searchParams.get("Expires"));
assert.ok(url.searchParams.get("Signature"));
});
test("aliyun oss storage reads objects and validates bucket config", async () => {
const config = await createConfig();
const originalFetch = globalThis.fetch;
const calls: Array<{ input: string; init?: RequestInit }> = [];
globalThis.fetch = (async (input, init) => {
calls.push({ input: String(input), init });
if (String(input).endsWith("?bucketInfo")) {
return new Response("<BucketInfo><Bucket><Name>boss-bucket</Name></Bucket></BucketInfo>", {
status: 200,
});
}
return new Response("object-content", { status: 200 });
}) as typeof fetch;
try {
const buffer = await readAliyunOssObjectBuffer(config, "uploads/a.txt");
assert.equal(buffer.toString("utf8"), "object-content");
const validation = await validateAliyunOssConfig(config);
assert.deepEqual(validation, {
provider: "aliyun_oss",
bucket: "boss-bucket",
endpoint: "oss-cn-hangzhou.aliyuncs.com",
region: "oss-cn-hangzhou",
});
assert.equal(calls.length, 2);
assert.match(calls[1].input, /\/\?bucketInfo$/);
} finally {
globalThis.fetch = originalFetch;
}
});

View File

@@ -13,10 +13,9 @@ test("MainActivity debounces root realtime refresh bursts", async () => {
assert.match(source, /private boolean realtimeRefreshScheduled(?: = false)?;/);
assert.match(source, /private final Runnable realtimeRefreshRunnable = new Runnable\(\)/);
assert.match(source, /scheduleRealtimeRefresh\(\)/);
assert.doesNotMatch(
assert.match(
source,
/runOnUiThread\(this::refreshCurrentTab\)/,
"root page should coalesce bursts instead of refreshing immediately for each event",
/private void scheduleRealtimeRefresh\(\)\s*\{[\s\S]*?if \(realtimeRefreshScheduled\) \{[\s\S]*?return;[\s\S]*?\}[\s\S]*?realtimeRefreshScheduled = true;[\s\S]*?uiHandler\.postDelayed\(realtimeRefreshRunnable, REALTIME_REFRESH_DEBOUNCE_MS\);[\s\S]*?\}/,
);
});
@@ -28,9 +27,13 @@ test("ProjectDetailActivity debounces realtime chat reload bursts", async () =>
assert.match(source, /private boolean realtimeReloadRequiresFullSnapshot;/);
assert.match(source, /private final Runnable realtimeReloadRunnable = new Runnable\(\)/);
assert.match(source, /scheduleRealtimeReload\(boolean requireFullSnapshot\)/);
assert.match(
source,
/private void scheduleRealtimeReload\(boolean requireFullSnapshot\)\s*\{[\s\S]*?realtimeReloadRequiresFullSnapshot = realtimeReloadRequiresFullSnapshot \|\| requireFullSnapshot;[\s\S]*?if \(realtimeReloadScheduled\) \{[\s\S]*?return;[\s\S]*?\}[\s\S]*?realtimeReloadScheduled = true;[\s\S]*?uiHandler\.postDelayed\(realtimeReloadRunnable, REALTIME_REFRESH_DEBOUNCE_MS\);[\s\S]*?\}/,
);
assert.doesNotMatch(
source,
/runOnUiThread\(this::triggerRealtimeReload\)/,
/private void scheduleRealtimeReload\(boolean requireFullSnapshot\)\s*\{[\s\S]*?triggerRealtimeReload\(requireFullSnapshot\);[\s\S]*?\}/,
"chat page should debounce repeated realtime updates before reloading",
);
});

View File

@@ -0,0 +1,225 @@
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";
import { NextRequest } from "next/server";
let runtimeRoot = "";
let data: typeof import("../src/lib/boss-data");
let authCookie = "";
let getPermissionLogs: (typeof import("../src/app/api/v1/audits/permission-logs/route"))["GET"];
let baseState: Awaited<ReturnType<typeof import("../src/lib/boss-data")["readState"]>>;
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-audit-permission-logs-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [dataModule, authModule, routeModule] = await Promise.all([
import("../src/lib/boss-data.ts"),
import("../src/lib/boss-auth.ts"),
import("../src/app/api/v1/audits/permission-logs/route.ts"),
]);
data = dataModule;
authCookie = authModule.AUTH_SESSION_COOKIE;
getPermissionLogs = routeModule.GET;
baseState = structuredClone(await data.readState());
}
test.after(async () => {
if (runtimeRoot) await rm(runtimeRoot, { recursive: true, force: true });
});
test.beforeEach(async () => {
await setup();
const state = structuredClone(baseState);
state.permissionAuditLogs = [
{
auditId: "audit-008",
actorAccount: "krisolo",
action: "task.denied",
targetAccount: "member@example.com",
detail: "admin_route:/api/v1/admin/access",
createdAt: "2026-04-27T10:03:00.000Z",
},
{
auditId: "audit-007",
actorAccount: "krisolo",
action: "skill.lifecycle.completed",
deviceId: "mac-studio",
skillId: "mac-studio:boss-server-debug",
detail: "update:failed:git checkout failed",
createdAt: "2026-04-27T10:02:00.000Z",
},
{
auditId: "audit-006",
actorAccount: "krisolo",
action: "skill.assigned",
targetAccount: "worker@example.com",
deviceId: "mac-studio",
projectId: "master-agent",
skillId: "mac-studio:boss-server-debug",
permissions: ["skill.view", "skill.use"],
createdAt: "2026-04-27T10:01:05.000Z",
},
{
auditId: "audit-005",
actorAccount: "krisolo",
action: "grant.created",
targetAccount: "worker@example.com",
projectId: "master-agent",
permissions: ["project.view"],
createdAt: "2026-04-27T10:01:04.000Z",
},
{
auditId: "audit-004",
actorAccount: "krisolo",
action: "grant.created",
targetAccount: "worker@example.com",
deviceId: "mac-studio",
permissions: ["device.view"],
createdAt: "2026-04-27T10:01:03.000Z",
},
{
auditId: "audit-003",
actorAccount: "krisolo",
action: "grant.created",
targetAccount: "worker@example.com",
deviceId: "mac-studio",
permissions: ["device.view"],
createdAt: "2026-04-27T10:01:02.000Z",
},
{
auditId: "audit-002",
actorAccount: "krisolo",
action: "grant.created",
targetAccount: "worker@example.com",
deviceId: "mac-studio",
permissions: ["device.view"],
createdAt: "2026-04-27T10:01:01.000Z",
},
{
auditId: "audit-001",
actorAccount: "krisolo",
action: "grant.created",
targetAccount: "worker@example.com",
deviceId: "mac-studio",
permissions: ["device.view"],
createdAt: "2026-04-27T10:01:00.000Z",
},
];
state.accountDeviceGrants = [
{
grantId: "expired-device-grant",
account: "worker@example.com",
deviceId: "mac-studio",
permissions: ["device.view"],
grantedBy: "krisolo",
grantedAt: "2026-04-26T10:00:00.000Z",
expiresAt: "2026-04-27T09:00:00.000Z",
},
];
state.accountProjectGrants = [];
state.accountSkillGrants = [];
await data.writeState(state);
});
async function authedRequest(
account: string,
role: "member" | "admin" | "highest_admin",
url: string,
) {
const session = await data.createAuthSession({
account,
role,
displayName: account,
loginMethod: "password",
});
return new NextRequest(url, {
headers: {
cookie: `${authCookie}=${session.sessionToken}`,
},
});
}
test("highest admin can filter permission audit logs and page with a cursor", async () => {
const response = await getPermissionLogs(
await authedRequest(
"krisolo",
"highest_admin",
"http://127.0.0.1:3000/api/v1/audits/permission-logs?action=grant.created&actorAccount=krisolo&targetAccount=worker@example.com&deviceId=mac-studio&limit=2",
),
);
assert.equal(response.status, 200);
const payload = await response.json();
assert.deepEqual(
payload.logs.map((log: { auditId: string }) => log.auditId),
["audit-004", "audit-003"],
);
assert.equal(payload.nextCursor, "audit-003");
assert.equal(payload.riskSummary.totalAlerts >= 1, true);
const nextResponse = await getPermissionLogs(
await authedRequest(
"krisolo",
"highest_admin",
`http://127.0.0.1:3000/api/v1/audits/permission-logs?action=grant.created&actorAccount=krisolo&targetAccount=worker@example.com&deviceId=mac-studio&limit=2&cursor=${payload.nextCursor}`,
),
);
assert.equal(nextResponse.status, 200);
const nextPayload = await nextResponse.json();
assert.deepEqual(
nextPayload.logs.map((log: { auditId: string }) => log.auditId),
["audit-002", "audit-001"],
);
assert.equal(nextPayload.nextCursor, null);
});
test("highest admin can filter permission audit logs by project and skill", async () => {
const response = await getPermissionLogs(
await authedRequest(
"krisolo",
"highest_admin",
"http://127.0.0.1:3000/api/v1/audits/permission-logs?projectId=master-agent&skillId=mac-studio%3Aboss-server-debug",
),
);
assert.equal(response.status, 200);
const payload = await response.json();
assert.deepEqual(
payload.logs.map((log: { auditId: string }) => log.auditId),
["audit-006"],
);
});
test("permission audit risk summary is deterministic from current logs and grants", async () => {
const response = await getPermissionLogs(
await authedRequest(
"krisolo",
"highest_admin",
"http://127.0.0.1:3000/api/v1/audits/permission-logs",
),
);
assert.equal(response.status, 200);
const payload = await response.json();
assert.deepEqual(
payload.riskSummary.alerts.map((alert: { kind: string }) => alert.kind).sort(),
[
"admin_route_denied",
"expired_grant_present",
"rapid_permission_grants",
"skill_lifecycle_failed",
],
);
});
test("ordinary accounts cannot read permission audit logs", async () => {
const response = await getPermissionLogs(
await authedRequest(
"worker@example.com",
"member",
"http://127.0.0.1:3000/api/v1/audits/permission-logs",
),
);
assert.equal(response.status, 403);
});

View File

@@ -0,0 +1,112 @@
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";
import { NextRequest } from "next/server";
let runtimeRoot = "";
let data: typeof import("../src/lib/boss-data");
let postLogin: (typeof import("../src/app/api/auth/login/route"))["POST"];
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-auth-login-hardening-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [dataModule, routeModule] = await Promise.all([
import("../src/lib/boss-data.ts"),
import("../src/app/api/auth/login/route.ts"),
]);
data = dataModule;
postLogin = routeModule.POST;
}
test.after(async () => {
if (runtimeRoot) await rm(runtimeRoot, { recursive: true, force: true });
});
test.beforeEach(async () => {
await setup();
delete process.env.BOSS_AUTH_AUTO_LOGIN;
const state = await data.readState();
await data.writeState({
...state,
authSessions: [],
authAccounts: [
{
id: "account-owner",
account: "owner@example.com",
passwordHash: data.hashPassword("StrongPass123"),
displayName: "企业管理员",
role: "highest_admin",
status: "active",
createdAt: "2026-04-27T16:00:00+08:00",
updatedAt: "2026-04-27T16:00:00+08:00",
},
],
});
});
function loginRequest(body: Record<string, unknown>) {
return new NextRequest("http://127.0.0.1:3000/api/auth/login", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(body),
});
}
test("login does not allow temporary auto login unless explicitly enabled", async () => {
const response = await postLogin(loginRequest({}));
assert.equal(response.status, 400);
const payload = await response.json();
assert.equal(payload.ok, false);
assert.match(payload.message, /账号/);
});
test("login allows temporary auto login only with an explicit development switch", async () => {
process.env.BOSS_AUTH_AUTO_LOGIN = "1";
const response = await postLogin(loginRequest({}));
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.ok, true);
assert.equal(payload.role, "highest_admin");
assert.match(payload.message, /临时免验证/);
});
test("password login still creates a normal session when auto login is disabled", async () => {
const response = await postLogin(loginRequest({
account: "owner@example.com",
password: "StrongPass123",
method: "password",
}));
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.ok, true);
assert.equal(payload.account, "owner@example.com");
assert.equal(payload.role, "highest_admin");
assert.match(response.headers.get("set-cookie") ?? "", /boss_session=/);
});
test("code login requires a previously issued verification code even in fixed mode", async () => {
const direct = await postLogin(loginRequest({
account: "owner@example.com",
code: "000000",
method: "code",
}));
assert.equal(direct.status, 400);
assert.match((await direct.json()).message, /验证码/);
const issued = await data.issueVerificationCode("owner@example.com", "login");
const response = await postLogin(loginRequest({
account: "owner@example.com",
code: issued.code,
method: "code",
}));
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.ok, true);
assert.equal(payload.loginMethod, "code");
});

View File

@@ -0,0 +1,33 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
const authHelpPagePath = new URL("../src/app/auth/help/page.tsx", import.meta.url);
const loginPagePath = new URL("../src/app/auth/login/page.tsx", import.meta.url);
const securityPagePath = new URL("../src/app/me/security/page.tsx", import.meta.url);
const bossDataPath = new URL("../src/lib/boss-data.ts", import.meta.url);
async function readSource(path: URL) {
return readFile(path, "utf8");
}
test("auth-facing pages do not advertise temporary auto login", async () => {
const sources = await Promise.all([
readSource(authHelpPagePath),
readSource(loginPagePath),
readSource(securityPagePath),
]);
const combined = sources.join("\n");
assert.doesNotMatch(combined, /免验证|一键进入|直接创建最高管理员会话/);
assert.match(combined, /账号密码/);
assert.match(combined, /验证码/);
assert.match(combined, /krisolo/);
});
test("seeded OTA release notes do not describe login as one-click entry", async () => {
const source = await readSource(bossDataPath);
assert.doesNotMatch(source, /登录页改为原生一键进入/);
assert.match(source, /登录页改为原生账号登录/);
});

View File

@@ -0,0 +1,144 @@
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";
import { NextRequest } from "next/server";
let runtimeRoot = "";
let data: typeof import("../src/lib/boss-data");
let authCookie = "";
let postLogin: (typeof import("../src/app/api/auth/login/route"))["POST"];
let postRestore: (typeof import("../src/app/api/auth/restore/route"))["POST"];
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-auth-security-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [dataModule, authModule, loginRoute, restoreRoute] = await Promise.all([
import("../src/lib/boss-data.ts"),
import("../src/lib/boss-auth.ts"),
import("../src/app/api/auth/login/route.ts"),
import("../src/app/api/auth/restore/route.ts"),
]);
data = dataModule;
authCookie = authModule.AUTH_SESSION_COOKIE;
postLogin = loginRoute.POST;
postRestore = restoreRoute.POST;
}
test.after(async () => {
if (runtimeRoot) await rm(runtimeRoot, { recursive: true, force: true });
});
test.beforeEach(async () => {
await setup();
delete process.env.BOSS_AUTH_AUTO_LOGIN;
const now = "2026-04-27T18:00:00+08:00";
const state = await data.readState();
await data.writeState({
...state,
authSessions: [],
authAccounts: [
{
id: "account-owner",
account: "owner@example.com",
passwordHash: data.hashPassword("StrongPass123"),
displayName: "企业管理员",
role: "highest_admin",
status: "active",
createdAt: now,
updatedAt: now,
},
{
id: "account-mfa",
account: "mfa@example.com",
passwordHash: data.hashPassword("StrongPass123"),
displayName: "MFA 管理员",
role: "admin",
status: "active",
mfaRequired: true,
mfaSecret: "test-mfa-secret",
createdAt: now,
updatedAt: now,
},
],
});
});
function loginRequest(body: Record<string, unknown>, headers: Record<string, string> = {}) {
return new NextRequest("http://127.0.0.1:3000/api/auth/login", {
method: "POST",
headers: { "content-type": "application/json", ...headers },
body: JSON.stringify(body),
});
}
function restoreRequest(restoreToken: string) {
return new NextRequest("http://127.0.0.1:3000/api/auth/restore", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ restoreToken }),
});
}
test("auth mutations reject explicit cross-site browser posts", async () => {
const response = await postLogin(loginRequest(
{ account: "owner@example.com", password: "StrongPass123", method: "password" },
{ origin: "https://evil.example", "sec-fetch-site": "cross-site" },
));
assert.equal(response.status, 403);
assert.match((await response.json()).message, /CSRF/);
});
test("native app login is not blocked by browser CSRF headers", async () => {
const response = await postLogin(loginRequest(
{ account: "owner@example.com", password: "StrongPass123", method: "password" },
{ "x-boss-native-app": "1" },
));
assert.equal(response.status, 200);
});
test("restore token rotates on every session restore", async () => {
const session = await data.createAuthSession({
account: "owner@example.com",
role: "highest_admin",
displayName: "企业管理员",
loginMethod: "password",
});
const first = await postRestore(restoreRequest(session.restoreToken));
assert.equal(first.status, 200);
const firstPayload = await first.json();
const rotatedToken = firstPayload.session.restoreToken;
assert.notEqual(rotatedToken, session.restoreToken);
assert.match(first.headers.get("set-cookie") ?? "", new RegExp(`${authCookie}=`));
const oldToken = await postRestore(restoreRequest(session.restoreToken));
assert.equal(oldToken.status, 401);
const second = await postRestore(restoreRequest(rotatedToken));
assert.equal(second.status, 200);
});
test("MFA-protected accounts require a valid one-time code after password verification", async () => {
const missing = await postLogin(loginRequest({
account: "mfa@example.com",
password: "StrongPass123",
method: "password",
}));
assert.equal(missing.status, 400);
assert.match((await missing.json()).message, /MFA/);
const validCode = data.generateAuthAccountMfaCode("test-mfa-secret", new Date());
const passed = await postLogin(loginRequest({
account: "mfa@example.com",
password: "StrongPass123",
method: "password",
mfaCode: validCode,
}));
assert.equal(passed.status, 200);
});

View File

@@ -0,0 +1,152 @@
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";
import { NextRequest } from "next/server";
let runtimeRoot = "";
let data: typeof import("../src/lib/boss-data");
let authCookie = "";
let getSessions: (typeof import("../src/app/api/v1/auth/sessions/route"))["GET"];
let postSessions: (typeof import("../src/app/api/v1/auth/sessions/route"))["POST"];
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-auth-sessions-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [dataModule, authModule, routeModule] = await Promise.all([
import("../src/lib/boss-data.ts"),
import("../src/lib/boss-auth.ts"),
import("../src/app/api/v1/auth/sessions/route.ts"),
]);
data = dataModule;
authCookie = authModule.AUTH_SESSION_COOKIE;
getSessions = routeModule.GET;
postSessions = routeModule.POST;
}
test.after(async () => {
if (runtimeRoot) await rm(runtimeRoot, { recursive: true, force: true });
});
test.beforeEach(async () => {
await setup();
const state = await data.readState();
await data.writeState({
...state,
authSessions: [],
authAccounts: [
...state.authAccounts.filter((account) => account.account !== "worker@example.com"),
{
id: "account-worker",
account: "worker@example.com",
passwordHash: "scrypt$test",
displayName: "Worker",
role: "member",
createdAt: "2026-04-26T12:00:00+08:00",
updatedAt: "2026-04-26T12:00:00+08:00",
},
],
});
});
function requestWithSession(sessionToken: string, init: RequestInit = {}) {
return new NextRequest("http://127.0.0.1:3000/api/v1/auth/sessions", {
...init,
headers: {
...(init.headers ?? {}),
cookie: `${authCookie}=${sessionToken}`,
},
});
}
test("member can see and revoke only their own active sessions", async () => {
const first = await data.createAuthSession({
account: "worker@example.com",
role: "member",
displayName: "Worker",
loginMethod: "password",
});
const second = await data.createAuthSession({
account: "worker@example.com",
role: "member",
displayName: "Worker",
loginMethod: "code",
});
const admin = await data.createAuthSession({
account: "krisolo",
role: "highest_admin",
displayName: "Boss",
loginMethod: "password",
});
const getResponse = await getSessions(requestWithSession(second.sessionToken));
assert.equal(getResponse.status, 200);
const getPayload = await getResponse.json();
assert.deepEqual(
getPayload.sessions.map((session: { account: string }) => session.account),
["worker@example.com", "worker@example.com"],
);
assert.equal(getPayload.sessions.some((session: { sessionToken?: string }) => session.sessionToken), false);
assert.equal(getPayload.sessions.some((session: { restoreToken?: string }) => session.restoreToken), false);
const forbidden = await postSessions(requestWithSession(second.sessionToken, {
method: "POST",
body: JSON.stringify({ action: "revoke_session", sessionId: admin.sessionId }),
}));
assert.equal(forbidden.status, 403);
const revokeSelf = await postSessions(requestWithSession(second.sessionToken, {
method: "POST",
body: JSON.stringify({ action: "revoke_session", sessionId: first.sessionId }),
}));
assert.equal(revokeSelf.status, 200);
const after = await getSessions(requestWithSession(second.sessionToken));
const afterPayload = await after.json();
assert.deepEqual(
afterPayload.sessions.map((session: { sessionId: string }) => session.sessionId),
[second.sessionId],
);
});
test("highest admin can inspect and revoke all active sessions", async () => {
const worker = await data.createAuthSession({
account: "worker@example.com",
role: "member",
displayName: "Worker",
loginMethod: "password",
});
const admin = await data.createAuthSession({
account: "krisolo",
role: "highest_admin",
displayName: "Boss",
loginMethod: "password",
});
const getResponse = await getSessions(requestWithSession(admin.sessionToken));
assert.equal(getResponse.status, 200);
const getPayload = await getResponse.json();
assert.deepEqual(
getPayload.sessions.map((session: { account: string }) => session.account).sort(),
["krisolo", "worker@example.com"],
);
const revokeResponse = await postSessions(requestWithSession(admin.sessionToken, {
method: "POST",
body: JSON.stringify({ action: "revoke_session", sessionId: worker.sessionId }),
}));
assert.equal(revokeResponse.status, 200);
assert.equal(await data.getAuthSession(worker.sessionToken), null);
});
test("primary admin session uses the current production admin account", async () => {
const session = await data.createPrimaryAdminSession();
assert.equal(session.account, "krisolo");
const state = await data.readState();
assert.equal(state.user.account, "krisolo");
assert.equal(state.authAccounts.find((account) => account.account === "krisolo")?.isPrimary, true);
});

View File

@@ -0,0 +1,113 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
async function readSource(path: string) {
return readFile(new URL(path, import.meta.url), "utf8");
}
test("independent Boss admin web app declares Vue Ant Design Vue runtime", async () => {
const source = await readSource("../apps/boss-admin-web/package.json");
const pkg = JSON.parse(source);
assert.equal(pkg.name, "@boss/admin-web");
assert.equal(pkg.private, true);
assert.match(pkg.scripts.dev, /vite/);
assert.match(pkg.scripts.build, /vite build/);
assert.ok(pkg.dependencies.vue);
assert.ok(pkg.dependencies["ant-design-vue"]);
assert.ok(pkg.devDependencies.vite);
assert.ok(pkg.devDependencies["@vitejs/plugin-vue"]);
});
test("independent Boss admin web app uses the backoffice BFF with cookie session", async () => {
const apiSource = await readSource("../apps/boss-admin-web/src/api/bossAdmin.ts");
assert.match(apiSource, /\/api\/v1\/admin\/backoffice/);
assert.match(apiSource, /\/api\/v1\/admin\/access/);
assert.match(apiSource, /\/api\/v1\/admin\/risks\/actions/);
assert.match(apiSource, /\/api\/v1\/admin\/skills\/requests/);
assert.match(apiSource, /credentials:\s*["']include["']/);
assert.match(apiSource, /menuTree/);
assert.match(apiSource, /tenants/);
assert.match(apiSource, /resourceGroups/);
for (const fn of ["postAdminAccess", "postRiskAction", "postSkillLifecycleRequest"]) {
assert.match(apiSource, new RegExp(`function\\s+${fn}|const\\s+${fn}`));
}
});
test("independent Boss admin web app includes enterprise management sections", async () => {
const appSource = await readSource("../apps/boss-admin-web/src/App.vue");
for (const label of [
"Boss 企业后台",
"工作台",
"租户管理",
"账号管理",
"角色权限",
"资源授权",
"Skill 中心",
"风险告警",
"审计日志",
]) {
assert.match(appSource, new RegExp(label));
}
assert.match(appSource, /menuTree/);
assert.match(appSource, /workbench/);
assert.match(appSource, /tenants/);
});
test("independent Boss admin web app exposes management actions instead of read only tables", async () => {
const appSource = await readSource("../apps/boss-admin-web/src/App.vue");
for (const label of [
"新建租户",
"启用租户",
"停用租户",
"新建账号",
"重置密码",
"离职回收",
"分配资源",
"套用权限模板",
"撤销授权",
"指派负责人",
"设置 SLA",
"确认风险",
"关闭风险",
"创建工单",
"创建 Skill 请求",
]) {
assert.match(appSource, new RegExp(label));
}
for (const action of [
"upsert_company",
"set_company_status",
"upsert_account",
"reset_account_password",
"reclaim_account",
"apply_template",
"grant_device",
"grant_project",
"grant_skill",
"revoke_grant",
]) {
assert.match(appSource, new RegExp(action));
}
});
test("root Next project isolates the independent Vue admin workspace", async () => {
const [tsconfigSource, eslintSource, rootPkgSource] = await Promise.all([
readSource("../tsconfig.json"),
readSource("../eslint.config.mjs"),
readSource("../package.json"),
]);
const tsconfig = JSON.parse(tsconfigSource);
const rootPkg = JSON.parse(rootPkgSource);
assert.ok(tsconfig.exclude.includes("apps/boss-admin-web/**"));
assert.match(eslintSource, /apps\/boss-admin-web\/\*\*/);
assert.match(eslintSource, /public\/admin-web\/\*\*/);
assert.match(rootPkg.scripts["admin:web:dev"], /apps\/boss-admin-web/);
assert.match(rootPkg.scripts["admin:web:build"], /apps\/boss-admin-web/);
});

40
tests/boss-mail.test.ts Normal file
View File

@@ -0,0 +1,40 @@
import test from "node:test";
import assert from "node:assert/strict";
import { resolveSendmailSpawnCommand } from "../src/lib/boss-mail.ts";
test("resolveSendmailSpawnCommand keeps the executable static for build tracing", () => {
const originalPath = process.env.BOSS_SENDMAIL_PATH;
delete process.env.BOSS_SENDMAIL_PATH;
try {
const command = resolveSendmailSpawnCommand();
assert.equal(command.executable, "/usr/bin/env");
assert.deepEqual(command.args, ["--", "/usr/sbin/sendmail", "-t", "-i"]);
} finally {
if (originalPath === undefined) {
delete process.env.BOSS_SENDMAIL_PATH;
} else {
process.env.BOSS_SENDMAIL_PATH = originalPath;
}
}
});
test("resolveSendmailSpawnCommand preserves a configured sendmail path behind env", () => {
const originalPath = process.env.BOSS_SENDMAIL_PATH;
process.env.BOSS_SENDMAIL_PATH = "/opt/sendmail/bin/sendmail";
try {
const command = resolveSendmailSpawnCommand();
assert.equal(command.executable, "/usr/bin/env");
assert.deepEqual(command.args, ["--", "/opt/sendmail/bin/sendmail", "-t", "-i"]);
} finally {
if (originalPath === undefined) {
delete process.env.BOSS_SENDMAIL_PATH;
} else {
process.env.BOSS_SENDMAIL_PATH = originalPath;
}
}
});

View File

@@ -0,0 +1,189 @@
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 data: typeof import("../src/lib/boss-data");
let permissions: typeof import("../src/lib/boss-permissions");
let baseState: Awaited<ReturnType<typeof import("../src/lib/boss-data")["readState"]>>;
async function setup() {
if (!runtimeRoot) {
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-rbac-permissions-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
}
if (!data) {
data = await import("../src/lib/boss-data.ts");
baseState = structuredClone(await data.readState());
}
if (!permissions) {
permissions = await import("../src/lib/boss-permissions.ts");
}
}
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
}
});
test.beforeEach(async () => {
await setup();
await data.writeState({
...structuredClone(baseState),
accountDeviceGrants: [],
accountProjectGrants: [],
accountSkillGrants: [],
skillCatalog: [],
permissionAuditLogs: [],
});
});
test("highest admin can access every device and project without explicit grants", async () => {
const state = await data.readState();
const session = {
account: "krisolo",
role: "highest_admin" as const,
displayName: "Boss 超级管理员",
};
assert.equal(permissions.canAccessDevice(state, session, "mac-studio", "device.view"), true);
assert.equal(permissions.canAccessProject(state, session, "audit-collab", "project.view"), true);
});
test("device.view grant gives project read visibility but not thread chat", async () => {
const state = await data.readState();
state.accountDeviceGrants = [
{
grantId: "grant-device-view",
account: "worker@example.com",
deviceId: "mac-studio",
permissions: ["device.view"],
grantedBy: "krisolo",
grantedAt: "2026-04-26T12:00:00+08:00",
},
];
await data.writeState(state);
const next = await data.readState();
const session = {
account: "worker@example.com",
role: "member" as const,
displayName: "Worker",
};
assert.equal(permissions.canAccessDevice(next, session, "mac-studio", "device.view"), true);
assert.equal(permissions.canAccessProject(next, session, "master-agent", "project.view"), true);
assert.equal(permissions.canAccessProject(next, session, "master-agent", "thread.chat"), false);
});
test("explicit project thread.chat grant allows posting to that project", async () => {
const state = await data.readState();
state.accountProjectGrants = [
{
grantId: "grant-thread-chat",
account: "worker@example.com",
projectId: "master-agent",
permissions: ["project.view", "thread.chat", "master_agent.ask"],
grantedBy: "krisolo",
grantedAt: "2026-04-26T12:00:00+08:00",
},
];
await data.writeState(state);
const next = await data.readState();
const session = {
account: "worker@example.com",
role: "member" as const,
displayName: "Worker",
};
assert.equal(permissions.canAccessProject(next, session, "master-agent", "project.view"), true);
assert.equal(permissions.canAccessProject(next, session, "master-agent", "thread.chat"), true);
assert.equal(permissions.canAccessProject(next, session, "master-agent", "computer.control"), false);
});
test("expired grants are ignored", async () => {
const state = await data.readState();
state.accountDeviceGrants = [
{
grantId: "expired-device-grant",
account: "worker@example.com",
deviceId: "mac-studio",
permissions: ["device.view"],
grantedBy: "krisolo",
grantedAt: "2026-04-25T12:00:00+08:00",
expiresAt: "2000-01-01T00:00:00.000Z",
},
];
await data.writeState(state);
const next = await data.readState();
const session = {
account: "worker@example.com",
role: "member" as const,
displayName: "Worker",
};
assert.equal(permissions.canAccessDevice(next, session, "mac-studio", "device.view"), false);
});
test("legacy device account ownership remains a compatibility fallback", async () => {
const state = await data.readState();
state.devices.push({
id: "worker-mac",
name: "Worker Mac",
avatar: "W",
account: "worker@example.com",
source: "production",
status: "online",
projects: ["worker-project"],
quota5h: 0,
quota7d: 0,
lastSeenAt: "2026-04-26T12:00:00+08:00",
preferredExecutionMode: "cli",
});
state.projects.push({
id: "worker-project",
name: "Worker Project",
pinned: false,
systemPinned: false,
deviceIds: ["worker-mac"],
preview: "Owned by worker.",
updatedAt: "2026-04-26T12:00:00+08:00",
lastMessageAt: "2026-04-26T12:00:00+08:00",
isGroup: false,
threadMeta: {
projectId: "worker-project",
threadId: "thread-worker-project",
threadDisplayName: "Worker Project",
folderName: "Worker",
activityIconCount: 0,
updatedAt: "2026-04-26T12:00:00+08:00",
codexThreadRef: "thread-worker-project",
codexFolderRef: "worker",
},
groupMembers: [],
createdByAgent: true,
collaborationMode: "development",
approvalState: "not_required",
unreadCount: 0,
riskLevel: "low",
messages: [],
goals: [],
versions: [],
});
await data.writeState(state);
const next = await data.readState();
const session = {
account: "worker@example.com",
role: "member" as const,
displayName: "Worker",
};
assert.equal(permissions.canAccessDevice(next, session, "worker-mac", "device.view"), true);
assert.equal(permissions.canAccessProject(next, session, "worker-project", "project.view"), true);
});

View File

@@ -0,0 +1,109 @@
import test from "node:test";
import assert from "node:assert/strict";
import os from "node:os";
import path from "node:path";
import { mkdtemp, rm, readFile } from "node:fs/promises";
let runtimeRoot = "";
let data: typeof import("../src/lib/boss-data");
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-state-migrations-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
data = await import("../src/lib/boss-data.ts");
}
test.after(async () => {
if (runtimeRoot) await rm(runtimeRoot, { recursive: true, force: true });
});
test("migrates legacy unversioned state into the current schema metadata and RBAC/Skill arrays", async () => {
await setup();
const migrated = data.migrateBossState({
accountDeviceGrants: [
{
account: "worker@example.com",
deviceId: "mac-studio",
permissions: ["device.view", "device.view", "invalid.permission"],
},
],
accountProjectGrants: [
{
account: "worker@example.com",
projectId: "master-agent",
permissions: ["project.view", "thread.chat"],
},
{
account: "broken@example.com",
projectId: "master-agent",
permissions: ["not-real"],
},
],
accountSkillGrants: undefined,
skillLifecycleRequests: [
{
deviceId: "mac-studio",
sourceUrl: "https://example.com/skills/demo.git",
action: "not-a-real-action",
status: "not-a-real-status",
},
],
permissionAuditLogs: [
{
actorAccount: "krisolo",
action: "unexpected-action",
targetAccount: "worker@example.com",
permissions: ["device.view", "bad.permission"],
},
],
} as unknown as Partial<data.BossState>);
assert.equal(migrated.schemaVersion, data.CURRENT_BOSS_STATE_SCHEMA_VERSION);
assert.match(migrated.migratedAt, /^\d{4}-\d{2}-\d{2}T/);
assert.deepEqual(migrated.accountDeviceGrants[0]?.permissions, ["device.view"]);
assert.deepEqual(migrated.accountProjectGrants.map((grant) => grant.account), ["worker@example.com"]);
assert.deepEqual(migrated.accountSkillGrants, []);
assert.equal(migrated.skillLifecycleRequests[0]?.action, "install");
assert.equal(migrated.skillLifecycleRequests[0]?.status, "pending");
assert.equal(migrated.permissionAuditLogs[0]?.action, "grant.updated");
assert.deepEqual(migrated.permissionAuditLogs[0]?.permissions, ["device.view"]);
});
test("preserves current schema migration metadata instead of rewriting it on every normalize", async () => {
await setup();
const migrated = data.migrateBossState({
schemaVersion: data.CURRENT_BOSS_STATE_SCHEMA_VERSION,
migratedAt: "2026-04-20T08:00:00.000Z",
accountDeviceGrants: [],
accountProjectGrants: [],
accountSkillGrants: [],
skillLifecycleRequests: [],
permissionAuditLogs: [],
} as Partial<data.BossState>);
assert.equal(migrated.schemaVersion, data.CURRENT_BOSS_STATE_SCHEMA_VERSION);
assert.equal(migrated.migratedAt, "2026-04-20T08:00:00.000Z");
});
test("writeState persists schema metadata while keeping the state file JSON-compatible", async () => {
await setup();
const state = await data.readState();
assert.equal(state.schemaVersion, data.CURRENT_BOSS_STATE_SCHEMA_VERSION);
assert.match(state.migratedAt, /^\d{4}-\d{2}-\d{2}T/);
await data.writeState(state);
const persisted = JSON.parse(await readFile(process.env.BOSS_STATE_FILE as string, "utf8"));
assert.equal(persisted.schemaVersion, data.CURRENT_BOSS_STATE_SCHEMA_VERSION);
assert.equal(persisted.migratedAt, state.migratedAt);
assert.ok(Array.isArray(persisted.accountDeviceGrants));
assert.ok(Array.isArray(persisted.accountProjectGrants));
assert.ok(Array.isArray(persisted.accountSkillGrants));
assert.ok(Array.isArray(persisted.skillLifecycleRequests));
assert.ok(Array.isArray(persisted.permissionAuditLogs));
});

View File

@@ -0,0 +1,46 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
const schemaPath = new URL("../scripts/postgres-state-schema.sql", import.meta.url);
test("state store defaults to file mode", async () => {
delete process.env.BOSS_STATE_STORE;
const { describeBossStateStore } = await import("../src/lib/boss-state-store.ts");
const summary = describeBossStateStore();
assert.equal(summary.mode, "file");
assert.equal(summary.ready, true);
});
test("postgres state store fails closed without a database url", async () => {
process.env.BOSS_STATE_STORE = "postgres";
delete process.env.BOSS_DATABASE_URL;
const { describeBossStateStore, createBossStateStore } = await import("../src/lib/boss-state-store.ts");
const summary = describeBossStateStore();
assert.equal(summary.mode, "postgres");
assert.equal(summary.ready, false);
assert.match(summary.reason ?? "", /BOSS_DATABASE_URL/);
assert.throws(
() => createBossStateStore({ dataFile: "/tmp/boss-state.json", backupFile: "/tmp/boss-state.json.bak" }),
/BOSS_DATABASE_URL_REQUIRED/,
);
delete process.env.BOSS_STATE_STORE;
});
test("postgres state schema stores a single jsonb state snapshot", async () => {
const schema = await readFile(schemaPath, "utf8");
assert.match(schema, /CREATE TABLE IF NOT EXISTS boss_state_snapshots/);
assert.match(schema, /state JSONB NOT NULL/);
assert.match(schema, /PRIMARY KEY/);
});
test("state store loads postgres lazily so file mode works in standalone builds", async () => {
const source = await readFile(new URL("../src/lib/boss-state-store.ts", import.meta.url), "utf8");
assert.doesNotMatch(source, /import\s+\{\s*Client\s*\}\s+from\s+["']pg["']/);
assert.match(source, /await import\(["']pg["']\)/);
});

View File

@@ -0,0 +1,139 @@
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 appendProjectMessages: (typeof import("../src/lib/boss-data"))["appendProjectMessages"];
let completeMasterAgentTask: (typeof import("../src/lib/boss-data"))["completeMasterAgentTask"];
let queueMasterAgentTask: (typeof import("../src/lib/boss-data"))["queueMasterAgentTask"];
let readState: (typeof import("../src/lib/boss-data"))["readState"];
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-control-summary-task-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const data = await import("../src/lib/boss-data.ts");
appendProjectMessages = data.appendProjectMessages;
completeMasterAgentTask = data.completeMasterAgentTask;
queueMasterAgentTask = data.queueMasterAgentTask;
readState = data.readState;
}
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
}
});
test("completed browser control task mirrors a control summary message into master-agent conversation", async () => {
await setup();
const [requestMessage] = await appendProjectMessages({
projectId: "master-agent",
messages: [
{
senderLabel: "Boss 超级管理员",
body: "打开 https://example.com 看一下首页",
kind: "text",
},
],
});
const task = await queueMasterAgentTask({
projectId: "master-agent",
taskType: "browser_control",
requestMessageId: requestMessage.id,
requestText: "打开 https://example.com 看一下首页",
requestedBy: "Boss 超级管理员",
requestedByAccount: "krisolo",
deviceId: "mac-studio",
accountId: "openai-master",
accountLabel: "gpt-5.4-mini",
intentCategory: "browser_control",
runtimeKind: "browser-automation-runtime",
riskLevel: "medium",
confirmationPolicy: "light_confirm",
requiresUserConfirmation: true,
confirmationScopeKey: "mac-studio:master-agent",
});
await completeMasterAgentTask({
taskId: task.taskId,
deviceId: "mac-studio",
status: "completed",
replyBody: "浏览器控制已完成:打开 https://example.com 看一下首页",
targetUrl: "https://example.com",
});
const state = await readState();
const project = state.projects.find((item) => item.id === "master-agent");
const controlSummary = project?.messages.find((message) =>
message.kind === "control_summary" &&
message.body === "浏览器控制已完成:打开 https://example.com 看一下首页"
);
assert.ok(controlSummary);
assert.equal(controlSummary?.sender, "master");
assert.equal(controlSummary?.senderLabel, "主 Agent · gpt-5.4-mini");
assert.equal(controlSummary?.body, "浏览器控制已完成:打开 https://example.com 看一下首页");
assert.equal((controlSummary as { controlTarget?: string }).controlTarget, "https://example.com");
});
test("completed desktop control task mirrors a control summary message into master-agent conversation", async () => {
await setup();
const [requestMessage] = await appendProjectMessages({
projectId: "master-agent",
messages: [
{
senderLabel: "Boss 超级管理员",
body: "打开微信并准备切到聊天窗口",
kind: "text",
},
],
});
const task = await queueMasterAgentTask({
projectId: "master-agent",
taskType: "desktop_control",
requestMessageId: requestMessage.id,
requestText: "打开微信并准备切到聊天窗口",
requestedBy: "Boss 超级管理员",
requestedByAccount: "krisolo",
deviceId: "mac-studio",
accountId: "openai-master",
accountLabel: "gpt-5.4-mini",
intentCategory: "desktop_control",
runtimeKind: "computer-use-runtime",
riskLevel: "medium",
confirmationPolicy: "light_confirm",
requiresUserConfirmation: true,
confirmationScopeKey: "mac-studio:master-agent",
});
await completeMasterAgentTask({
taskId: task.taskId,
deviceId: "mac-studio",
status: "completed",
replyBody: "桌面控制已完成:打开微信并准备切到聊天窗口",
targetApp: "微信",
});
const state = await readState();
const project = state.projects.find((item) => item.id === "master-agent");
const controlSummary = project?.messages.find((message) =>
message.kind === "control_summary" &&
message.body === "桌面控制已完成:打开微信并准备切到聊天窗口"
);
assert.ok(controlSummary);
assert.equal(controlSummary?.sender, "master");
assert.equal(controlSummary?.senderLabel, "主 Agent · gpt-5.4-mini");
assert.equal(controlSummary?.body, "桌面控制已完成:打开微信并准备切到聊天窗口");
assert.equal((controlSummary as { controlTarget?: string }).controlTarget, "微信");
});

View File

@@ -0,0 +1,118 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
test("shipped local-agent configs include browser and desktop runtime smoke defaults", async () => {
const exampleConfig = JSON.parse(
await readFile(path.join(repoRoot, "local-agent", "config.example.json"), "utf8"),
);
const cloudConfig = JSON.parse(
await readFile(path.join(repoRoot, "local-agent", "config.cloud.json"), "utf8"),
);
assert.equal(exampleConfig.browserControlEnabled, true);
assert.equal(cloudConfig.browserControlEnabled, true);
assert.equal(exampleConfig.browserControlCommand, "node");
assert.equal(cloudConfig.browserControlCommand, "node");
assert.deepEqual(exampleConfig.browserControlArgs, ["scripts/browser-control-smoke.mjs"]);
assert.deepEqual(cloudConfig.browserControlArgs, ["scripts/browser-control-smoke.mjs"]);
assert.equal(exampleConfig.browserAutomationConnected, true);
assert.equal(cloudConfig.browserAutomationConnected, true);
assert.equal(exampleConfig.computerUseEnabled, true);
assert.equal(cloudConfig.computerUseEnabled, true);
assert.equal(exampleConfig.computerUseCommand, "node");
assert.equal(cloudConfig.computerUseCommand, "node");
assert.deepEqual(exampleConfig.computerUseArgs, ["scripts/computer-use-smoke.mjs"]);
assert.deepEqual(cloudConfig.computerUseArgs, ["scripts/computer-use-smoke.mjs"]);
assert.equal(exampleConfig.computerUseConnected, true);
assert.equal(cloudConfig.computerUseConnected, true);
assert.equal(exampleConfig.dialogGuardEnabled, true);
assert.equal(cloudConfig.dialogGuardEnabled, true);
assert.equal(exampleConfig.dialogGuardConsentRequired, true);
assert.equal(cloudConfig.dialogGuardConsentRequired, true);
assert.deepEqual(exampleConfig.dialogGuardPlatformAdapters, ["darwin", "win32"]);
assert.deepEqual(cloudConfig.dialogGuardPlatformAdapters, ["darwin", "win32"]);
assert.equal(exampleConfig.dialogGuardMacActionCommand, "");
assert.equal(cloudConfig.dialogGuardMacActionCommand, "");
assert.deepEqual(exampleConfig.dialogGuardMacActionArgs, []);
assert.deepEqual(cloudConfig.dialogGuardMacActionArgs, []);
assert.equal(exampleConfig.dialogGuardWindowsActionCommand, "");
assert.equal(cloudConfig.dialogGuardWindowsActionCommand, "");
assert.deepEqual(exampleConfig.dialogGuardWindowsActionArgs, []);
assert.deepEqual(cloudConfig.dialogGuardWindowsActionArgs, []);
assert.equal(exampleConfig.codexDesktopRefreshEnabled, true);
assert.equal(cloudConfig.codexDesktopRefreshEnabled, true);
assert.equal(exampleConfig.codexDesktopRefreshCommand, "node");
assert.equal(cloudConfig.codexDesktopRefreshCommand, "node");
assert.deepEqual(exampleConfig.codexDesktopRefreshArgs, ["scripts/codex-desktop-refresh-hint.mjs"]);
assert.deepEqual(cloudConfig.codexDesktopRefreshArgs, ["scripts/codex-desktop-refresh-hint.mjs"]);
assert.equal(exampleConfig.codexDesktopRefreshEndpoint, "http://127.0.0.1:4318/api/v1/codex-desktop/refresh");
assert.equal(cloudConfig.codexDesktopRefreshEndpoint, "http://127.0.0.1:4318/api/v1/codex-desktop/refresh");
assert.equal(exampleConfig.codexDesktopRefreshAppName, "Codex");
assert.equal(cloudConfig.codexDesktopRefreshAppName, "Codex");
assert.equal(exampleConfig.codexDesktopRefreshMode, "deeplink-reload");
assert.equal(cloudConfig.codexDesktopRefreshMode, "deeplink-reload");
assert.equal(exampleConfig.codexDesktopRefreshRetryCount, 2);
assert.equal(cloudConfig.codexDesktopRefreshRetryCount, 2);
assert.equal(exampleConfig.codexDesktopRefreshRetryDelayMs, 120);
assert.equal(cloudConfig.codexDesktopRefreshRetryDelayMs, 120);
});
test("repo ships browser and desktop smoke runtime scripts", async () => {
const browserSmoke = await readFile(path.join(repoRoot, "scripts", "browser-control-smoke.mjs"), "utf8");
const computerSmoke = await readFile(path.join(repoRoot, "scripts", "computer-use-smoke.mjs"), "utf8");
const codexDesktopRefreshHint = await readFile(
path.join(repoRoot, "scripts", "codex-desktop-refresh-hint.mjs"),
"utf8",
);
const codexDesktopRefreshBridgeDaemon = await readFile(
path.join(repoRoot, "scripts", "codex-desktop-refresh-bridge-daemon.mjs"),
"utf8",
);
const codexDesktopEventConsumer = await readFile(
path.join(repoRoot, "scripts", "codex-desktop-event-consumer.mjs"),
"utf8",
);
const codexDesktopIntegrationProbe = await readFile(
path.join(repoRoot, "scripts", "codex-desktop-integration-probe.mjs"),
"utf8",
);
const codexDesktopBridgeLaunchAgent = await readFile(
path.join(repoRoot, "deployment", "launchd", "com.hyzq.boss.codex-desktop-bridge.plist"),
"utf8",
);
assert.match(browserSmoke, /status/);
assert.match(browserSmoke, /replyBody/);
assert.match(browserSmoke, /BOSS_BROWSER_AUTOMATION_MODE/);
assert.match(computerSmoke, /status/);
assert.match(computerSmoke, /replyBody/);
assert.match(computerSmoke, /resolveOpenAppPrefixArgs/);
assert.match(computerSmoke, /BOSS_COMPUTER_USE_MODE/);
assert.match(computerSmoke, /osascript/);
assert.match(codexDesktopRefreshHint, /codex_desktop_refresh_hint/);
assert.match(codexDesktopRefreshHint, /osascript/);
assert.match(codexDesktopRefreshHint, /activate/);
assert.match(codexDesktopRefreshHint, /refreshMode/);
assert.match(codexDesktopRefreshHint, /codex:\/\/threads\//);
assert.match(codexDesktopRefreshHint, /BOSS_CODEX_DESKTOP_REFRESH_DRY_RUN/);
assert.match(codexDesktopRefreshHint, /key code 15/);
assert.match(codexDesktopRefreshBridgeDaemon, /api\/v1\/codex-desktop\/refresh/);
assert.match(codexDesktopRefreshBridgeDaemon, /api\/v1\/codex-desktop\/events/);
assert.match(codexDesktopRefreshBridgeDaemon, /text\/event-stream/);
assert.match(codexDesktopRefreshBridgeDaemon, /127\.0\.0\.1/);
assert.match(codexDesktopEventConsumer, /BOSS_CODEX_DESKTOP_EVENTS_URL/);
assert.match(codexDesktopEventConsumer, /BOSS_CODEX_DESKTOP_EVENTS_ONCE/);
assert.match(codexDesktopEventConsumer, /codex_desktop_refresh/);
assert.match(codexDesktopIntegrationProbe, /BOSS_CODEX_DESKTOP_APP_PATH/);
assert.match(codexDesktopIntegrationProbe, /codex:\/\/threads\/\{threadId\}/);
assert.match(codexDesktopIntegrationProbe, /packagePatch/);
assert.match(codexDesktopBridgeLaunchAgent, /codex-desktop-refresh-bridge-daemon\.mjs/);
assert.match(codexDesktopBridgeLaunchAgent, /BOSS_CODEX_DESKTOP_BRIDGE_PORT/);
});

View File

@@ -0,0 +1,938 @@
import test from "node:test";
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import fs from "node:fs/promises";
import http from "node:http";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
async function writeOpenMarkerScript(markerFile) {
const scriptDir = await fs.mkdtemp(path.join(os.tmpdir(), "boss-open-marker-script-"));
const scriptPath = path.join(scriptDir, "open-marker.mjs");
await fs.writeFile(
scriptPath,
`import fs from "node:fs";\nfs.writeFileSync(${JSON.stringify(markerFile)}, process.argv[2] || "", "utf8");\n`,
"utf8",
);
await fs.chmod(scriptPath, 0o755);
return { scriptDir, scriptPath };
}
async function writeArgumentMarkerCommand(markerFile, commandName = "open") {
const scriptDir = await fs.mkdtemp(path.join(os.tmpdir(), `boss-${commandName}-marker-script-`));
const scriptPath = path.join(scriptDir, commandName);
await fs.writeFile(
scriptPath,
[
"#!/usr/bin/env node",
'import fs from "node:fs";',
`fs.writeFileSync(${JSON.stringify(markerFile)}, JSON.stringify(process.argv.slice(2)), "utf8");`,
].join("\n"),
"utf8",
);
await fs.chmod(scriptPath, 0o755);
return { scriptDir, scriptPath };
}
async function writeBrowserAutomationScript(logFile) {
const scriptDir = await fs.mkdtemp(path.join(os.tmpdir(), "boss-browser-automation-script-"));
const scriptPath = path.join(scriptDir, "browser-automation.mjs");
await fs.writeFile(
scriptPath,
[
'#!/usr/bin/env node',
'import fs from "node:fs";',
`fs.appendFileSync(${JSON.stringify(logFile)}, JSON.stringify(process.argv.slice(2)) + "\\n", "utf8");`,
'if (process.argv.includes("eval")) {',
' process.stdout.write("Boss Automated Title\\n");',
'}',
].join("\n"),
"utf8",
);
await fs.chmod(scriptPath, 0o755);
return { scriptDir, scriptPath };
}
async function writeCodexHomePlaywrightWrapper(codexHome, logFile) {
const wrapperDir = path.join(codexHome, "skills", "playwright", "scripts");
await fs.mkdir(wrapperDir, { recursive: true });
const wrapperPath = path.join(wrapperDir, "playwright_cli.sh");
await fs.writeFile(
wrapperPath,
[
"#!/bin/zsh",
`printf '%s\\n' \"$(python3 -c 'import json,sys; args=sys.argv[1:]; print(json.dumps(args[1:] if args[:1] == [\"--\"] else args))' -- \"$@\")\" >> ${JSON.stringify(logFile)}`,
'if [[ " $* " == *" eval "* ]]; then',
' printf "Boss Auto Wrapper Title\\n"',
"fi",
].join("\n"),
"utf8",
);
await fs.chmod(wrapperPath, 0o755);
return wrapperPath;
}
async function runRuntimeWithServer(scriptPath, payload, options = {}) {
return new Promise((resolve, reject) => {
const child = spawn(process.execPath, [scriptPath], {
cwd: repoRoot,
env: {
...process.env,
...(options.env || {}),
},
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
child.stdout.setEncoding("utf8");
child.stderr.setEncoding("utf8");
child.stdout.on("data", (chunk) => {
stdout += chunk;
});
child.stderr.on("data", (chunk) => {
stderr += chunk;
});
child.on("error", reject);
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(stderr.trim() || `exit code ${code}`));
return;
}
try {
resolve(JSON.parse(stdout.trim().split(/\r?\n/).at(-1) || ""));
} catch (error) {
reject(error);
}
});
child.stdin.write(JSON.stringify(payload));
child.stdin.end();
});
}
async function runRuntime(scriptPath, payload, options = {}) {
return new Promise((resolve, reject) => {
const child = spawn(process.execPath, [scriptPath], {
cwd: repoRoot,
env: {
...process.env,
BOSS_BROWSER_AUTOMATION_MODE: "off",
...(options.env || {}),
},
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
child.stdout.setEncoding("utf8");
child.stderr.setEncoding("utf8");
child.stdout.on("data", (chunk) => {
stdout += chunk;
});
child.stderr.on("data", (chunk) => {
stderr += chunk;
});
child.on("error", reject);
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(stderr.trim() || `exit code ${code}`));
return;
}
try {
resolve(JSON.parse(stdout.trim().split(/\r?\n/).at(-1) || ""));
} catch (error) {
reject(error);
}
});
child.stdin.write(JSON.stringify(payload));
child.stdin.end();
});
}
test("browser smoke runtime returns normalized completed payload", async () => {
const result = await runRuntime(path.join(repoRoot, "scripts", "browser-control-smoke.mjs"), {
requestKind: "browser_control",
requestId: "browser-smoke-1",
objective: "打开 boss 控制台首页",
context: {
riskLevel: "medium",
},
});
assert.equal(result.status, "completed");
assert.equal(result.requestId, "browser-smoke-1");
assert.match(result.replyBody, /浏览器控制已完成/);
assert.match(result.replyBody, /打开 boss 控制台首页/);
});
test("browser smoke runtime emits target url when objective contains a website", async () => {
const result = await runRuntime(path.join(repoRoot, "scripts", "browser-control-smoke.mjs"), {
requestKind: "browser_control",
requestId: "browser-smoke-url",
objective: "打开 https://example.com 看一下首页",
context: {
riskLevel: "medium",
dryRun: true,
},
});
assert.equal(result.status, "completed");
assert.equal(result.targetUrl, "https://example.com");
assert.match(result.executionSummary, /open_url/);
});
test("browser smoke runtime can invoke configured browser automation command", async () => {
const markerDir = await fs.mkdtemp(path.join(os.tmpdir(), "boss-browser-automation-marker-"));
const markerFile = path.join(markerDir, "automation.log");
let automationScript;
try {
automationScript = await writeBrowserAutomationScript(markerFile);
const result = await runRuntime(
path.join(repoRoot, "scripts", "browser-control-smoke.mjs"),
{
requestKind: "browser_control",
requestId: "browser-automation-1",
objective: "打开 https://example.com 看一下首页",
context: {
riskLevel: "medium",
dryRun: false,
},
},
{
env: {
BOSS_BROWSER_AUTOMATION_MODE: "playwright",
BOSS_BROWSER_AUTOMATION_COMMAND: process.execPath,
BOSS_BROWSER_AUTOMATION_ARGS_JSON: JSON.stringify([automationScript.scriptPath]),
BOSS_BROWSER_AUTOMATION_SESSION: "boss-browser-test",
},
},
);
assert.equal(result.status, "completed");
assert.match(result.replyBody, /Boss Automated Title/);
const lines = (await fs.readFile(markerFile, "utf8")).trim().split(/\r?\n/).map((line) => JSON.parse(line));
assert.deepEqual(lines[0], ["--session", "boss-browser-test", "open", "https://example.com"]);
assert.deepEqual(lines[1], ["--session", "boss-browser-test", "eval", "document.title"]);
} finally {
if (automationScript?.scriptDir) {
await fs.rm(automationScript.scriptDir, { recursive: true, force: true });
}
await fs.rm(markerDir, { recursive: true, force: true });
}
});
test("browser smoke runtime auto-detects bundled playwright wrapper and uses request id as session", async () => {
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), "boss-browser-autodetect-"));
const logFile = path.join(tmpRoot, "wrapper.log");
try {
const codexHome = path.join(tmpRoot, ".codex");
await writeCodexHomePlaywrightWrapper(codexHome, logFile);
const result = await runRuntimeWithServer(
path.join(repoRoot, "scripts", "browser-control-smoke.mjs"),
{
requestKind: "browser_control",
requestId: "browser-auto-session",
objective: "打开 https://example.com 看一下首页",
context: {
riskLevel: "medium",
dryRun: false,
},
},
{
env: {
CODEX_HOME: codexHome,
},
},
);
assert.equal(result.status, "completed");
assert.match(result.replyBody, /Boss Auto Wrapper Title/);
const lines = (await fs.readFile(logFile, "utf8")).trim().split(/\r?\n/).map((line) => JSON.parse(line));
assert.deepEqual(lines[0], ["--session", "browser-auto-session", "open", "https://example.com"]);
assert.deepEqual(lines[1], ["--session", "browser-auto-session", "eval", "document.title"]);
} finally {
await fs.rm(tmpRoot, { recursive: true, force: true });
}
});
test("browser smoke runtime fetches page title for a reachable target url", async () => {
const server = http.createServer((_request, response) => {
response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
response.end("<html><head><title>Boss Browser Runtime Test</title></head><body>ok</body></html>");
});
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
const address = server.address();
const port = typeof address === "object" && address ? address.port : 0;
try {
const result = await runRuntimeWithServer(path.join(repoRoot, "scripts", "browser-control-smoke.mjs"), {
requestKind: "browser_control",
requestId: "browser-smoke-title",
objective: `打开 http://127.0.0.1:${port}/ 看一下首页`,
context: {
riskLevel: "medium",
dryRun: false,
},
}, {
env: {
BOSS_BROWSER_AUTOMATION_MODE: "fetch",
},
});
assert.equal(result.status, "completed");
assert.match(result.replyBody, /Boss Browser Runtime Test/);
assert.match(result.executionSummary, /title=Boss Browser Runtime Test/);
} finally {
await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
}
});
test("computer use smoke runtime returns normalized completed payload", async () => {
const result = await runRuntime(path.join(repoRoot, "scripts", "computer-use-smoke.mjs"), {
requestKind: "desktop_control",
requestId: "computer-smoke-1",
objective: "打开系统设置",
context: {
riskLevel: "high",
dryRun: true,
},
});
assert.equal(result.status, "completed");
assert.equal(result.requestId, "computer-smoke-1");
assert.match(result.replyBody, /桌面控制已完成/);
assert.match(result.replyBody, /打开系统设置/);
});
test("computer use smoke runtime emits target app when objective contains an app name", async () => {
const result = await runRuntime(path.join(repoRoot, "scripts", "computer-use-smoke.mjs"), {
requestKind: "desktop_control",
requestId: "computer-smoke-app",
objective: "打开微信并准备切到聊天窗口",
context: {
riskLevel: "medium",
dryRun: true,
},
});
assert.equal(result.status, "completed");
assert.equal(result.targetApp, "微信");
assert.match(result.executionSummary, /open_wechat|open_app/);
});
test("computer use smoke runtime auto-handles safe cross-platform dialog snapshots", async () => {
const result = await runRuntime(
path.join(repoRoot, "scripts", "computer-use-smoke.mjs"),
{
requestKind: "desktop_control",
requestId: "computer-dialog-safe",
objective: "打开 QQ",
context: {
riskLevel: "medium",
dryRun: true,
},
},
{
env: {
BOSS_DIALOG_GUARD_ENABLED: "true",
BOSS_DIALOG_GUARD_SNAPSHOT_JSON: JSON.stringify({
platform: "win32",
deviceId: "win-node",
appName: "QQ",
title: "Welcome",
text: "Welcome. Not now",
buttons: ["Get started", "Not now"],
}),
},
},
);
assert.equal(result.status, "completed");
assert.equal(result.dialogGuard?.disposition, "auto_action");
assert.equal(result.dialogGuard?.button, "Not now");
assert.match(result.executionSummary, /dialogGuard=auto_action/);
});
test("computer use smoke runtime invokes configured platform dialog action command for safe dialogs", async () => {
const markerDir = await fs.mkdtemp(path.join(os.tmpdir(), "boss-dialog-action-marker-"));
const markerFile = path.join(markerDir, "action.json");
let actionCommand;
try {
actionCommand = await writeArgumentMarkerCommand(markerFile, "dialog-action");
const result = await runRuntime(
path.join(repoRoot, "scripts", "computer-use-smoke.mjs"),
{
requestKind: "desktop_control",
requestId: "computer-dialog-action",
objective: "打开 QQ",
context: {
riskLevel: "medium",
dryRun: true,
},
},
{
env: {
BOSS_DIALOG_GUARD_ENABLED: "true",
BOSS_WINDOWS_DIALOG_GUARD_ACTION_COMMAND: actionCommand.scriptPath,
BOSS_DIALOG_GUARD_SNAPSHOT_JSON: JSON.stringify({
platform: "win32",
deviceId: "win-node",
appName: "QQ",
title: "Welcome",
text: "Welcome. Not now",
buttons: ["Get started", "Not now"],
}),
},
},
);
assert.equal(result.status, "completed");
assert.equal(result.dialogGuard?.actionApplied, true);
const args = JSON.parse(await fs.readFile(markerFile, "utf8"));
assert.ok(args.includes("--platform"));
assert.ok(args.includes("win32"));
assert.ok(args.includes("--button"));
assert.ok(args.includes("Not now"));
} finally {
if (actionCommand?.scriptDir) {
await fs.rm(actionCommand.scriptDir, { recursive: true, force: true });
}
await fs.rm(markerDir, { recursive: true, force: true });
}
});
test("computer use smoke runtime pauses before action for blocked system permission dialogs", async () => {
const result = await runRuntime(
path.join(repoRoot, "scripts", "computer-use-smoke.mjs"),
{
requestKind: "desktop_control",
requestId: "computer-dialog-blocked",
objective: "打开系统设置",
context: {
riskLevel: "high",
dryRun: false,
},
},
{
env: {
BOSS_DIALOG_GUARD_ENABLED: "true",
BOSS_DIALOG_GUARD_SNAPSHOT_JSON: JSON.stringify({
platform: "darwin",
deviceId: "mac-node",
appName: "System Settings",
title: "Screen Recording",
text: "BossComputerUseHelper would like to record this computer's screen",
buttons: ["Allow", "Don't Allow"],
}),
},
},
);
assert.equal(result.status, "needs_user_action");
assert.equal(result.kind, "dialog_intervention_required");
assert.equal(result.risk, "high");
assert.deepEqual(result.availableActions, ["handled_on_device", "cancel_task"]);
});
test("browser smoke runtime writes action artifact when BOSS_CONTROL_ARTIFACT_DIR is set", async () => {
const artifactDir = await fs.mkdtemp(path.join(os.tmpdir(), "boss-browser-artifacts-"));
try {
const result = await new Promise((resolve, reject) => {
const child = spawn(process.execPath, [path.join(repoRoot, "scripts", "browser-control-smoke.mjs")], {
cwd: repoRoot,
env: {
...process.env,
BOSS_CONTROL_ARTIFACT_DIR: artifactDir,
},
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
child.stdout.setEncoding("utf8");
child.stderr.setEncoding("utf8");
child.stdout.on("data", (chunk) => {
stdout += chunk;
});
child.stderr.on("data", (chunk) => {
stderr += chunk;
});
child.on("error", reject);
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(stderr.trim() || `exit code ${code}`));
return;
}
try {
resolve(JSON.parse(stdout.trim().split(/\r?\n/).at(-1) || ""));
} catch (error) {
reject(error);
}
});
child.stdin.write(
JSON.stringify({
requestKind: "browser_control",
requestId: "browser-artifact-1",
objective: "打开 https://example.com 看一下首页",
context: { dryRun: true },
}),
);
child.stdin.end();
});
assert.equal(result.status, "completed");
assert.ok(Array.isArray(result.artifacts));
assert.ok(result.artifacts[0]?.path);
const artifactText = await fs.readFile(result.artifacts[0].path, "utf8");
assert.match(artifactText, /https:\/\/example\.com/);
} finally {
await fs.rm(artifactDir, { recursive: true, force: true });
}
});
test("browser smoke runtime can execute an injected url opener when not dry-run", async () => {
const marker = await fs.mkdtemp(path.join(os.tmpdir(), "boss-browser-open-"));
let openerScript;
const markerFile = path.join(marker, "opened.txt");
try {
openerScript = await writeOpenMarkerScript(markerFile);
const result = await new Promise((resolve, reject) => {
const child = spawn(process.execPath, [path.join(repoRoot, "scripts", "browser-control-smoke.mjs")], {
cwd: repoRoot,
env: {
...process.env,
BOSS_BROWSER_AUTOMATION_MODE: "off",
BOSS_BROWSER_OPEN_COMMAND: process.execPath,
BOSS_BROWSER_OPEN_ARGS_JSON: JSON.stringify([openerScript.scriptPath]),
},
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
child.stdout.setEncoding("utf8");
child.stderr.setEncoding("utf8");
child.stdout.on("data", (chunk) => {
stdout += chunk;
});
child.stderr.on("data", (chunk) => {
stderr += chunk;
});
child.on("error", reject);
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(stderr.trim() || `exit code ${code}`));
return;
}
try {
resolve(JSON.parse(stdout.trim().split(/\r?\n/).at(-1) || ""));
} catch (error) {
reject(error);
}
});
child.stdin.write(
JSON.stringify({
requestKind: "browser_control",
requestId: "browser-open-1",
objective: "打开 https://example.com 看一下首页",
context: { dryRun: false },
}),
);
child.stdin.end();
});
assert.equal(result.status, "completed");
assert.equal(result.targetUrl, "https://example.com");
assert.match(result.executionSummary, /executed/);
const openedUrl = await fs.readFile(markerFile, "utf8");
assert.equal(openedUrl, "https://example.com");
} finally {
if (openerScript?.scriptDir) {
await fs.rm(openerScript.scriptDir, { recursive: true, force: true });
}
await fs.rm(marker, { recursive: true, force: true });
}
});
test("computer use smoke runtime can execute an injected app opener when not dry-run", async () => {
const marker = await fs.mkdtemp(path.join(os.tmpdir(), "boss-computer-open-"));
let openerScript;
const markerFile = path.join(marker, "opened.txt");
try {
openerScript = await writeOpenMarkerScript(markerFile);
const result = await new Promise((resolve, reject) => {
const child = spawn(process.execPath, [path.join(repoRoot, "scripts", "computer-use-smoke.mjs")], {
cwd: repoRoot,
env: {
...process.env,
BOSS_COMPUTER_USE_MODE: "open",
BOSS_COMPUTER_USE_OPEN_APP_COMMAND: process.execPath,
BOSS_COMPUTER_USE_OPEN_APP_ARGS_JSON: JSON.stringify([openerScript.scriptPath]),
},
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
child.stdout.setEncoding("utf8");
child.stderr.setEncoding("utf8");
child.stdout.on("data", (chunk) => {
stdout += chunk;
});
child.stderr.on("data", (chunk) => {
stderr += chunk;
});
child.on("error", reject);
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(stderr.trim() || `exit code ${code}`));
return;
}
try {
resolve(JSON.parse(stdout.trim().split(/\r?\n/).at(-1) || ""));
} catch (error) {
reject(error);
}
});
child.stdin.write(
JSON.stringify({
requestKind: "desktop_control",
requestId: "computer-open-1",
objective: "打开微信并准备切到聊天窗口",
context: { dryRun: false },
}),
);
child.stdin.end();
});
assert.equal(result.status, "completed");
assert.equal(result.targetApp, "微信");
assert.match(result.executionSummary, /executed/);
const openedApp = await fs.readFile(markerFile, "utf8");
assert.equal(openedApp, "微信");
} finally {
if (openerScript?.scriptDir) {
await fs.rm(openerScript.scriptDir, { recursive: true, force: true });
}
await fs.rm(marker, { recursive: true, force: true });
}
});
test("computer use smoke runtime defaults to open -a style args for macOS opener", async () => {
const marker = await fs.mkdtemp(path.join(os.tmpdir(), "boss-computer-open-default-"));
let openerCommand;
const markerFile = path.join(marker, "argv.json");
try {
openerCommand = await writeArgumentMarkerCommand(markerFile, "open");
const result = await new Promise((resolve, reject) => {
const child = spawn(process.execPath, [path.join(repoRoot, "scripts", "computer-use-smoke.mjs")], {
cwd: repoRoot,
env: {
...process.env,
BOSS_COMPUTER_USE_MODE: "open",
BOSS_COMPUTER_USE_OPEN_APP_COMMAND: openerCommand.scriptPath,
},
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
child.stdout.setEncoding("utf8");
child.stderr.setEncoding("utf8");
child.stdout.on("data", (chunk) => {
stdout += chunk;
});
child.stderr.on("data", (chunk) => {
stderr += chunk;
});
child.on("error", reject);
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(stderr.trim() || `exit code ${code}`));
return;
}
try {
resolve(JSON.parse(stdout.trim().split(/\r?\n/).at(-1) || ""));
} catch (error) {
reject(error);
}
});
child.stdin.write(
JSON.stringify({
requestKind: "desktop_control",
requestId: "computer-open-default",
objective: "打开微信并准备切到聊天窗口",
context: { dryRun: false },
}),
);
child.stdin.end();
});
assert.equal(result.status, "completed");
const argv = JSON.parse(await fs.readFile(markerFile, "utf8"));
assert.deepEqual(argv, ["-a", "微信"]);
} finally {
if (openerCommand?.scriptDir) {
await fs.rm(openerCommand.scriptDir, { recursive: true, force: true });
}
await fs.rm(marker, { recursive: true, force: true });
}
});
test("computer use smoke runtime can prepend configured open -a style args", async () => {
const marker = await fs.mkdtemp(path.join(os.tmpdir(), "boss-computer-open-default-"));
let openerCommand;
const markerFile = path.join(marker, "argv.json");
try {
openerCommand = await writeArgumentMarkerCommand(markerFile, "open");
const result = await new Promise((resolve, reject) => {
const child = spawn(process.execPath, [path.join(repoRoot, "scripts", "computer-use-smoke.mjs")], {
cwd: repoRoot,
env: {
...process.env,
BOSS_COMPUTER_USE_MODE: "open",
BOSS_COMPUTER_USE_OPEN_APP_COMMAND: openerCommand.scriptPath,
BOSS_COMPUTER_USE_OPEN_APP_ARGS_JSON: JSON.stringify(["-a"]),
},
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
child.stdout.setEncoding("utf8");
child.stderr.setEncoding("utf8");
child.stdout.on("data", (chunk) => {
stdout += chunk;
});
child.stderr.on("data", (chunk) => {
stderr += chunk;
});
child.on("error", reject);
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(stderr.trim() || `exit code ${code}`));
return;
}
try {
resolve(JSON.parse(stdout.trim().split(/\r?\n/).at(-1) || ""));
} catch (error) {
reject(error);
}
});
child.stdin.write(
JSON.stringify({
requestKind: "desktop_control",
requestId: "computer-open-default",
objective: "打开微信并准备切到聊天窗口",
context: { dryRun: false },
}),
);
child.stdin.end();
});
assert.equal(result.status, "completed");
const argv = JSON.parse(await fs.readFile(markerFile, "utf8"));
assert.deepEqual(argv, ["-a", "微信"]);
} finally {
if (openerCommand?.scriptDir) {
await fs.rm(openerCommand.scriptDir, { recursive: true, force: true });
}
await fs.rm(marker, { recursive: true, force: true });
}
});
test("computer use smoke runtime supports osascript mode via injected command", async () => {
const marker = await fs.mkdtemp(path.join(os.tmpdir(), "boss-computer-osascript-"));
let openerScript;
const markerFile = path.join(marker, "argv.json");
try {
openerScript = await writeArgumentMarkerCommand(markerFile, "osascript");
const result = await new Promise((resolve, reject) => {
const child = spawn(process.execPath, [path.join(repoRoot, "scripts", "computer-use-smoke.mjs")], {
cwd: repoRoot,
env: {
...process.env,
BOSS_COMPUTER_USE_MODE: "osascript",
PATH: `${openerScript.scriptDir}:${process.env.PATH || ""}`,
},
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
child.stdout.setEncoding("utf8");
child.stderr.setEncoding("utf8");
child.stdout.on("data", (chunk) => {
stdout += chunk;
});
child.stderr.on("data", (chunk) => {
stderr += chunk;
});
child.on("error", reject);
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(stderr.trim() || `exit code ${code}`));
return;
}
try {
resolve(JSON.parse(stdout.trim().split(/\r?\n/).at(-1) || ""));
} catch (error) {
reject(error);
}
});
child.stdin.write(
JSON.stringify({
requestKind: "desktop_control",
requestId: "computer-osascript",
objective: "打开微信并准备切到聊天窗口",
context: { dryRun: false },
}),
);
child.stdin.end();
});
assert.equal(result.status, "completed");
assert.match(result.executionSummary, /mode=osascript/);
const argv = JSON.parse(await fs.readFile(markerFile, "utf8"));
assert.equal(argv[0], "-e");
assert.match(argv[1], /tell application "微信"/);
} finally {
if (openerScript?.scriptDir) {
await fs.rm(openerScript.scriptDir, { recursive: true, force: true });
}
await fs.rm(marker, { recursive: true, force: true });
}
});
test("computer use smoke runtime types quoted text in osascript mode and can submit", async () => {
const marker = await fs.mkdtemp(path.join(os.tmpdir(), "boss-computer-osascript-type-"));
let openerScript;
const markerFile = path.join(marker, "argv.json");
try {
openerScript = await writeArgumentMarkerCommand(markerFile, "osascript");
const result = await new Promise((resolve, reject) => {
const child = spawn(process.execPath, [path.join(repoRoot, "scripts", "computer-use-smoke.mjs")], {
cwd: repoRoot,
env: {
...process.env,
BOSS_COMPUTER_USE_MODE: "osascript",
PATH: `${openerScript.scriptDir}:${process.env.PATH || ""}`,
},
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
child.stdout.setEncoding("utf8");
child.stderr.setEncoding("utf8");
child.stdout.on("data", (chunk) => {
stdout += chunk;
});
child.stderr.on("data", (chunk) => {
stderr += chunk;
});
child.on("error", reject);
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(stderr.trim() || `exit code ${code}`));
return;
}
try {
resolve(JSON.parse(stdout.trim().split(/\r?\n/).at(-1) || ""));
} catch (error) {
reject(error);
}
});
child.stdin.write(
JSON.stringify({
requestKind: "desktop_control",
requestId: "computer-osascript-type",
objective: "打开微信并输入“Boss 已接管当前线程”后发送",
context: { dryRun: false },
}),
);
child.stdin.end();
});
assert.equal(result.status, "completed");
assert.equal(result.typedText, "Boss 已接管当前线程");
const argv = JSON.parse(await fs.readFile(markerFile, "utf8"));
assert.equal(argv[0], "-e");
assert.match(argv[1], /keystroke "Boss 已接管当前线程"/);
assert.match(argv[1], /key code 36/);
} finally {
if (openerScript?.scriptDir) {
await fs.rm(openerScript.scriptDir, { recursive: true, force: true });
}
await fs.rm(marker, { recursive: true, force: true });
}
});
test("computer use smoke runtime writes action artifact when BOSS_CONTROL_ARTIFACT_DIR is set", async () => {
const artifactDir = await fs.mkdtemp(path.join(os.tmpdir(), "boss-desktop-artifacts-"));
try {
const result = await new Promise((resolve, reject) => {
const child = spawn(process.execPath, [path.join(repoRoot, "scripts", "computer-use-smoke.mjs")], {
cwd: repoRoot,
env: {
...process.env,
BOSS_CONTROL_ARTIFACT_DIR: artifactDir,
},
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
child.stdout.setEncoding("utf8");
child.stderr.setEncoding("utf8");
child.stdout.on("data", (chunk) => {
stdout += chunk;
});
child.stderr.on("data", (chunk) => {
stderr += chunk;
});
child.on("error", reject);
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(stderr.trim() || `exit code ${code}`));
return;
}
try {
resolve(JSON.parse(stdout.trim().split(/\r?\n/).at(-1) || ""));
} catch (error) {
reject(error);
}
});
child.stdin.write(
JSON.stringify({
requestKind: "desktop_control",
requestId: "desktop-artifact-1",
objective: "打开系统设置",
context: { dryRun: true },
}),
);
child.stdin.end();
});
assert.equal(result.status, "completed");
assert.ok(Array.isArray(result.artifacts));
assert.ok(result.artifacts[0]?.path);
const artifactText = await fs.readFile(result.artifacts[0].path, "utf8");
assert.match(artifactText, /系统设置/);
assert.match(artifactText, /mode/);
} finally {
await fs.rm(artifactDir, { recursive: true, force: true });
}
});

View File

@@ -0,0 +1,123 @@
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { spawn } from "node:child_process";
import { detectCodexDesktopIntegration } from "../scripts/codex-desktop-integration-probe.mjs";
const repoRoot = path.resolve(import.meta.dirname, "..");
async function makeFakeCodexApp() {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "boss-codex-app-"));
const appPath = path.join(tempDir, "Codex.app");
const contentsDir = path.join(appPath, "Contents");
const resourcesDir = path.join(contentsDir, "Resources");
await fs.mkdir(resourcesDir, { recursive: true });
await fs.writeFile(
path.join(contentsDir, "Info.plist"),
`<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>com.openai.codex</string>
<key>CFBundleShortVersionString</key>
<string>26.429.30905</string>
<key>CFBundleVersion</key>
<string>2345</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>Codex</string>
<key>CFBundleURLSchemes</key>
<array>
<string>codex</string>
</array>
</dict>
</array>
</dict>
</plist>
`,
"utf8",
);
await fs.writeFile(
path.join(resourcesDir, "app.asar"),
"function copyThreadLink(id){return `codex://threads/${id}`} // no plugin host export",
"utf8",
);
return { tempDir, appPath };
}
test("detectCodexDesktopIntegration reports stable deeplink and bridge capabilities without patching app", async () => {
const { tempDir, appPath } = await makeFakeCodexApp();
try {
const result = await detectCodexDesktopIntegration({
appPath,
bridgeEventsUrl: "http://127.0.0.1:4318/api/v1/codex-desktop/events",
});
assert.equal(result.ok, true);
assert.equal(result.app.bundleIdentifier, "com.openai.codex");
assert.equal(result.app.shortVersion, "26.429.30905");
assert.deepEqual(result.app.urlSchemes, ["codex"]);
assert.deepEqual(result.capabilities.threadDeepLink, {
supported: true,
template: "codex://threads/{threadId}",
evidence: "CFBundleURLSchemes contains codex and app resources contain codex://threads/",
});
assert.equal(result.capabilities.desktopBridgeSse.supported, true);
assert.equal(result.capabilities.desktopBridgeSse.url, "http://127.0.0.1:4318/api/v1/codex-desktop/events");
assert.equal(result.capabilities.inAppSubscription.supported, false);
assert.equal(result.capabilities.packagePatch.supported, false);
} finally {
await fs.rm(tempDir, { recursive: true, force: true });
}
});
test("codex desktop refresh bridge daemon exposes capabilities from the integration probe", async () => {
const { tempDir, appPath } = await makeFakeCodexApp();
const child = spawn(process.execPath, [path.join(repoRoot, "scripts/codex-desktop-refresh-bridge-daemon.mjs")], {
cwd: repoRoot,
env: {
...process.env,
BOSS_CODEX_DESKTOP_BRIDGE_HOST: "127.0.0.1",
BOSS_CODEX_DESKTOP_BRIDGE_PORT: "0",
BOSS_CODEX_DESKTOP_APP_PATH: appPath,
BOSS_CODEX_DESKTOP_REFRESH_DRY_RUN: "true",
},
stdio: ["ignore", "pipe", "pipe"],
});
try {
const ready = await new Promise((resolve, reject) => {
let buffer = "";
const timer = setTimeout(() => reject(new Error("daemon not ready")), 4000);
child.stdout.setEncoding("utf8");
child.stdout.on("data", (chunk) => {
buffer += chunk;
const line = buffer.trim().split(/\r?\n/).at(-1);
if (line) {
try {
clearTimeout(timer);
resolve(JSON.parse(line));
} catch {
// wait
}
}
});
child.on("error", reject);
});
const response = await fetch(`http://${ready.host}:${ready.port}/api/v1/codex-desktop/capabilities`);
const result = await response.json();
assert.equal(response.status, 200);
assert.equal(result.ok, true);
assert.equal(result.capabilities.threadDeepLink.supported, true);
assert.equal(result.capabilities.packagePatch.supported, false);
} finally {
child.kill("SIGTERM");
await fs.rm(tempDir, { recursive: true, force: true });
}
});

View File

@@ -0,0 +1,38 @@
import assert from "node:assert/strict";
import test from "node:test";
import { evaluatePermissionPolicyForTesting } from "@/lib/execution/permission-policy";
test("browser control medium risk requires confirmation but stays allowed", () => {
const result = evaluatePermissionPolicyForTesting({
project: {
id: "thread-browser",
isGroup: false,
collaborationMode: "development",
approvalState: "not_required",
},
requestedTool: "browser_control",
requestedRiskLevel: "medium",
});
assert.equal(result.allowed, true);
assert.equal(result.requiresApproval, true);
assert.deepEqual(result.toolPolicy.allowedTools.includes("browser_control"), true);
});
test("desktop control high risk is blocked until explicit confirmation", () => {
const result = evaluatePermissionPolicyForTesting({
project: {
id: "thread-desktop",
isGroup: false,
collaborationMode: "development",
approvalState: "not_required",
},
requestedTool: "desktop_control",
requestedRiskLevel: "high",
});
assert.equal(result.allowed, false);
assert.equal(result.requiresApproval, true);
assert.match(result.reason ?? "", /确认|高风险/);
assert.deepEqual(result.toolPolicy.deniedTools.includes("desktop_control"), true);
});

View File

@@ -0,0 +1,51 @@
import assert from "node:assert/strict";
import test from "node:test";
import type { ExecutionRequestKind } from "@/lib/execution/types";
import type { ExecutionToolName } from "@/lib/execution/tool-registry";
import type { MasterAgentTask } from "@/lib/boss-data";
test("execution request kinds support browser and desktop control", () => {
const browserKind: ExecutionRequestKind = "browser_control";
const desktopKind: ExecutionRequestKind = "desktop_control";
assert.equal(browserKind, "browser_control");
assert.equal(desktopKind, "desktop_control");
});
test("execution tool names support browser and desktop control", () => {
const browserTool: ExecutionToolName = "browser_control";
const desktopTool: ExecutionToolName = "desktop_control";
assert.equal(browserTool, "browser_control");
assert.equal(desktopTool, "desktop_control");
});
test("master agent task supports computer control metadata", () => {
const task: MasterAgentTask = {
taskId: "task-browser-control",
projectId: "master-agent",
taskType: "browser_control",
requestMessageId: "msg-1",
requestText: "打开后台首页",
executionPrompt: "请打开后台首页并检查顶部导航。",
requestedBy: "Boss 超级管理员",
requestedByAccount: "krisolo",
deviceId: "mac-studio",
status: "queued",
requestedAt: "2026-04-22T10:00:00.000Z",
intentCategory: "browser_control",
runtimeKind: "browser-automation-runtime",
riskLevel: "medium",
confirmationPolicy: "light_confirm",
requiresUserConfirmation: true,
confirmationScopeKey: "mac-studio:boss",
};
assert.equal(task.taskType, "browser_control");
assert.equal(task.intentCategory, "browser_control");
assert.equal(task.runtimeKind, "browser-automation-runtime");
assert.equal(task.riskLevel, "medium");
assert.equal(task.confirmationPolicy, "light_confirm");
assert.equal(task.requiresUserConfirmation, true);
assert.equal(task.confirmationScopeKey, "mac-studio:boss");
});

View File

@@ -88,7 +88,7 @@ test("upsertAttachmentStorageConfig publishes storage refresh event", async () =
});
await upsertAttachmentStorageConfig({
account: "17600003315",
account: "krisolo",
mode: "server_file",
updatedAt: "2026-04-07T10:20:00.000Z",
});
@@ -105,7 +105,7 @@ test("master agent prompt policy publishes master agent settings refresh event",
await updateMasterAgentPromptPolicy({
globalPrompt: "保持简洁并只输出有效内容。",
updatedBy: "17600003315",
updatedBy: "krisolo",
});
unsubscribe();
@@ -120,7 +120,7 @@ test("master agent memory writes publish master agent settings refresh event", a
});
await createUserMasterMemory({
account: "17600003315",
account: "krisolo",
scope: "global",
title: "用户偏好",
content: "群聊里默认一键通过。",
@@ -143,7 +143,7 @@ test("master agent takeover changes publish master agent settings refresh event"
{
globalTakeoverEnabled: true,
},
"17600003315",
"krisolo",
);
unsubscribe();

View File

@@ -6,17 +6,21 @@ import { mkdtemp, rm, writeFile } 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 updateConversationAction: (typeof import("../src/lib/boss-data"))["updateConversationAction"];
let getConversationHomeItems: (typeof import("../src/lib/boss-projections"))["getConversationHomeItems"];
let getConversationWebItems: (typeof import("../src/lib/boss-projections"))["getConversationWebItems"];
let getConversationHomeItemForProject: (typeof import("../src/lib/boss-projections"))["getConversationHomeItemForProject"];
let getConversationThreadItemForProject: (typeof import("../src/lib/boss-projections"))["getConversationThreadItemForProject"];
let getConversationFolderView: (typeof import("../src/lib/boss-projections"))["getConversationFolderView"];
let getProjectDetailView: (typeof import("../src/lib/boss-projections"))["getProjectDetailView"];
let buildProjectMessagesRealtimePayload: (typeof import("../src/lib/boss-projections"))["buildProjectMessagesRealtimePayload"];
let formatTimestampLabel: (typeof import("../src/lib/boss-projections"))["formatTimestampLabel"];
let getConversationListItemPresentation: (typeof import("../src/components/app-ui"))["getConversationListItemPresentation"];
let getConversationActionAvailability: (typeof import("../src/components/app-ui"))["getConversationActionAvailability"];
let getConversationActionsPath: (typeof import("../src/components/app-ui"))["getConversationActionsPath"];
let getConversationPinnedBadgeLabel: (typeof import("../src/components/app-ui"))["getConversationPinnedBadgeLabel"];
let seededStateSnapshot: Awaited<ReturnType<typeof import("../src/lib/boss-data").readState>> | null = null;
async function setup() {
if (runtimeRoot) return;
@@ -30,17 +34,28 @@ async function setup() {
import("../src/components/app-ui.tsx"),
]);
readState = data.readState;
writeState = data.writeState;
updateConversationAction = data.updateConversationAction;
getConversationHomeItems = projections.getConversationHomeItems;
getConversationWebItems = projections.getConversationWebItems;
getConversationHomeItemForProject = projections.getConversationHomeItemForProject;
getConversationThreadItemForProject = projections.getConversationThreadItemForProject;
getConversationFolderView = projections.getConversationFolderView;
getProjectDetailView = projections.getProjectDetailView;
buildProjectMessagesRealtimePayload = projections.buildProjectMessagesRealtimePayload;
formatTimestampLabel = projections.formatTimestampLabel;
getConversationListItemPresentation = ui.getConversationListItemPresentation;
getConversationActionAvailability = ui.getConversationActionAvailability;
getConversationActionsPath = ui.getConversationActionsPath;
getConversationPinnedBadgeLabel = ui.getConversationPinnedBadgeLabel;
seededStateSnapshot = structuredClone(await readState());
}
async function resetSeedState() {
if (!seededStateSnapshot) {
throw new Error("seeded state snapshot missing");
}
await writeState(structuredClone(seededStateSnapshot));
}
test.after(async () => {
@@ -165,20 +180,26 @@ test("folder archives use the latest thread preview/time while subtitle and cont
),
];
const folder = getConversationHomeItems(state).find((item) => item.conversationType === "folder_archive");
const originalNow = Date.now;
Date.now = () => new Date("2026-04-04T12:30:00+08:00").getTime();
try {
const folder = getConversationHomeItems(state).find((item) => item.conversationType === "folder_archive");
assert.ok(folder, "expected grouped folder archive item");
assert.equal(folder?.threadTitle, "Boss");
assert.equal(folder?.folderLabel, "2 个线程 · 最近:最新线程");
assert.equal(folder?.preview, "最近消息:最新线程");
assert.equal(folder?.lastMessagePreview, "最近消息:最新线程");
assert.equal(folder?.latestReplyAt, "2026-04-04T12:00:00+08:00");
assert.equal(folder?.latestReplyLabel, formatTimestampLabel("2026-04-04T12:00:00+08:00"));
assert.equal(folder?.contextBudgetIndicator.level, "critical");
assert.equal(folder?.contextBudgetIndicator.percent, 87);
assert.equal(folder?.mustFinishBeforeCompaction, true);
assert.equal(folder?.contextBudgetSourceNodeId, "node-urgent");
assert.equal(folder?.contextBudgetUpdatedAt, "2026-04-04T11:05:00+08:00");
assert.ok(folder, "expected grouped folder archive item");
assert.equal(folder?.threadTitle, "Boss");
assert.equal(folder?.folderLabel, "2 个线程 · 最近:最新线程");
assert.equal(folder?.preview, "最近消息:最新线程");
assert.equal(folder?.lastMessagePreview, "最近消息:最新线程");
assert.equal(folder?.latestReplyAt, "2026-04-04T12:00:00+08:00");
assert.equal(folder?.latestReplyLabel, formatTimestampLabel("2026-04-04T12:00:00+08:00"));
assert.equal(folder?.contextBudgetIndicator.level, "critical");
assert.equal(folder?.contextBudgetIndicator.percent, 87);
assert.equal(folder?.mustFinishBeforeCompaction, true);
assert.equal(folder?.contextBudgetSourceNodeId, "node-urgent");
assert.equal(folder?.contextBudgetUpdatedAt, "2026-04-04T11:05:00+08:00");
} finally {
Date.now = originalNow;
}
});
test("conversation home patch lookup returns the visible folder archive item for grouped threads", async () => {
@@ -388,61 +409,238 @@ test("folder archive context ring prefers newer latestReplyAt when mustFinishBef
assert.equal(folder?.contextBudgetUpdatedAt, "2026-04-04T12:05:00+08:00");
});
test("conversation home upgrades and downgrades a folder archive as thread count crosses 1 and 2", async () => {
test("conversation home stays visually empty after history is cleared", async () => {
await setup();
const state = await readState();
state.conversationHistoryClearedAt = "2026-04-24T10:00:00.000Z";
state.projects = state.projects.filter((project) => project.id === "master-agent");
const masterProject = state.projects.find((project) => project.id === "master-agent");
assert.ok(masterProject, "expected master-agent project");
if (masterProject) {
masterProject.preview = "";
masterProject.messages = [];
masterProject.unreadCount = 0;
}
state.threadContextSnapshots = [];
state.opsFaults = [];
state.auditRequests = [];
state.auditResults = [];
state.projects.push(
buildImportedThreadProject(
"mac-studio",
"boss-thread-clear-a",
"Boss",
"boss",
"线程 A",
"thread-clear-a",
"2026-04-24T09:00:00.000Z",
),
buildImportedThreadProject(
"mac-studio",
"boss-thread-clear-b",
"Boss",
"boss",
"线程 B",
"thread-clear-b",
"2026-04-24T08:00:00.000Z",
),
);
for (const project of state.projects) {
if (project.id !== "master-agent") {
project.preview = "";
project.messages = [];
project.unreadCount = 0;
}
}
const items = getConversationHomeItems(state);
const masterItem = items.find((item) => item.projectId === "master-agent");
const folderItem = items.find((item) => item.conversationType === "folder_archive");
assert.ok(masterItem, "expected master-agent home item");
assert.equal(masterItem?.preview, "");
assert.equal(masterItem?.lastMessagePreview, "");
assert.ok(folderItem, "expected folder archive item");
assert.equal(folderItem?.preview, "");
assert.equal(folderItem?.lastMessagePreview, "");
});
test("conversation home hides legacy process-like previews on direct thread rows", async () => {
await setup();
const state = await readState();
state.projects = state.projects.filter((project) => project.id === "master-agent");
state.projects.push(
buildImportedThreadProject(
state.projects.push({
...buildImportedThreadProject(
"mac-studio",
"boss-thread-1",
"legacy-process-preview-thread",
"Boss",
"boss",
"归档确认",
"Boss开发主线程",
"thread-legacy-process-preview",
"2026-04-24T18:30:00+08:00",
),
preview: "我继续把这条链路又往下收了一层,补的是“历史脏消息”的兼容,不只是新消息规则。",
messages: [
{
id: "legacy-process-preview-message",
sender: "device",
senderLabel: "Boss开发主线程",
body: "我继续把这条链路又往下收了一层,补的是“历史脏消息”的兼容,不只是新消息规则。",
sentAt: "2026-04-24T18:30:00+08:00",
kind: "thread_process",
},
],
unreadCount: 0,
});
const item = getConversationHomeItems(state).find((entry) => entry.projectId === "legacy-process-preview-thread");
assert.ok(item, "expected direct thread home item");
assert.equal(item?.preview, "");
assert.equal(item?.lastMessagePreview, "");
});
test("conversation home collapses multiline markdown-heavy previews into a short digest", async () => {
await setup();
const state = await readState();
state.projects = state.projects.filter((project) => project.id === "master-agent");
state.projects.push({
...buildImportedThreadProject(
"mac-studio",
"markdown-heavy-preview-thread",
"Boss",
"boss",
"Boss开发主线程",
"thread-markdown-heavy-preview",
"2026-04-24T18:31:00+08:00",
),
preview:
"这轮我继续把真机阻塞往下压了一层,而且拿到了比 xcodebuild.log 更强的设备侧证据。\n\n" +
"我先把设备 syslog 伴随采集正式接进了 run_ipad_harness.sh并先用失败断言锁住在 run-ipad-harness-source.test.ts。\n" +
"现在每次 harness 真机跑都会自动落盘 device-syslog.log 和 device-syslog-signals.log。",
messages: [
{
id: "markdown-heavy-preview-message",
sender: "device",
senderLabel: "Boss开发主线程",
body:
"这轮我继续把真机阻塞往下压了一层,而且拿到了比 xcodebuild.log 更强的设备侧证据。\n\n" +
"我先把设备 syslog 伴随采集正式接进了 run_ipad_harness.sh并先用失败断言锁住在 run-ipad-harness-source.test.ts。\n" +
"现在每次 harness 真机跑都会自动落盘 device-syslog.log 和 device-syslog-signals.log。",
sentAt: "2026-04-24T18:31:00+08:00",
kind: "text",
},
],
unreadCount: 1,
});
const item = getConversationHomeItems(state).find((entry) => entry.projectId === "markdown-heavy-preview-thread");
assert.ok(item, "expected direct thread home item");
assert.equal(item?.preview.includes("\n"), false);
assert.equal(item?.lastMessagePreview.includes("\n"), false);
assert.equal((item?.preview ?? "").length <= 72, true);
assert.match(item?.preview ?? "", /这轮我继续把真机阻塞往下压了一层/);
});
test("conversation home compacts structured project summary json previews into readable text", async () => {
await setup();
const state = await readState();
state.projects = state.projects.filter((project) => project.id === "master-agent");
state.projects.push({
...buildImportedThreadProject(
"mac-studio",
"summary-thread-1",
"luyinka",
"/Users/kris/code/luyinka",
"Android 对齐",
"thread-1",
"2026-04-04T10:00:00+08:00",
"2026-04-24T12:00:00+08:00",
),
);
await writeFile(process.env.BOSS_STATE_FILE as string, `${JSON.stringify(state, null, 2)}\n`);
preview: JSON.stringify({
projectGoal: "以安卓成熟版和正式截图为基准完成鸿蒙原生 SHMCI 的页面、录音卡链路与主要功能对齐。",
currentProgress: "主链路页面已对齐,正在收口录音卡和设备联调。",
technicalArchitecture: "鸿蒙原生 ArkUI + 音频链路适配。",
currentBlockers: "",
recommendedNextStep: "继续真机回归。",
}),
lastMessageAt: "2026-04-24T12:00:00+08:00",
});
let items = getConversationHomeItems(state);
let direct = items.find((item) => item.projectId === "boss-thread-1");
const item = getConversationHomeItems(state).find((entry) => entry.projectId === "summary-thread-1");
assert.ok(direct, "expected a single thread to remain direct");
assert.equal(direct?.conversationType, "single_device");
assert.ok(item, "expected json preview item");
assert.equal(item?.preview.startsWith("{"), false);
assert.match(item?.preview ?? "", /目标:以安卓成熟版和正式截图为基准完成鸿蒙原生 SHMCI/);
assert.equal((item?.preview ?? "").length <= 73, true);
});
state.projects.push(
buildImportedThreadProject(
"mac-studio",
"boss-thread-2",
"Boss",
"boss",
"发布回滚",
"thread-2",
"2026-04-04T11:00:00+08:00",
),
);
test("conversation home upgrades and downgrades a folder archive as thread count crosses 1 and 2", async () => {
await setup();
const state = await readState();
items = getConversationHomeItems(state);
const folder = items.find((item) => item.conversationType === "folder_archive" && item.folderKey === "mac-studio:boss");
try {
state.projects = state.projects.filter((project) => project.id === "master-agent");
state.projects.push(
buildImportedThreadProject(
"mac-studio",
"boss-thread-1",
"Boss",
"boss",
"归档确认",
"thread-1",
"2026-04-04T10:00:00+08:00",
),
);
await writeFile(process.env.BOSS_STATE_FILE as string, `${JSON.stringify(state, null, 2)}\n`);
assert.ok(folder, "expected folder archive once the folder has 2 threads");
assert.equal(folder?.threadCount, 2);
assert.equal(items.some((item) => item.projectId === "boss-thread-1"), false);
assert.equal(items.some((item) => item.projectId === "boss-thread-2"), false);
let items = getConversationHomeItems(state);
let direct = items.find((item) => item.projectId === "boss-thread-1");
state.projects = state.projects.filter((project) => project.id !== "boss-thread-2");
assert.ok(direct, "expected a single thread to remain direct");
assert.equal(direct?.conversationType, "single_device");
items = getConversationHomeItems(state);
direct = items.find((item) => item.projectId === "boss-thread-1");
state.projects.push(
buildImportedThreadProject(
"mac-studio",
"boss-thread-2",
"Boss",
"boss",
"发布回滚",
"thread-2",
"2026-04-04T11:00:00+08:00",
),
);
assert.ok(direct, "expected a single remaining thread to downgrade back to direct");
assert.equal(direct?.conversationType, "single_device");
assert.equal(
items.some((item) => item.conversationType === "folder_archive" && item.folderKey === "mac-studio:boss"),
false,
);
items = getConversationHomeItems(state);
const folder = items.find(
(item) => item.conversationType === "folder_archive" && item.folderKey === "mac-studio:boss",
);
assert.ok(folder, "expected folder archive once the folder has 2 threads");
assert.equal(folder?.threadCount, 2);
assert.equal(items.some((item) => item.projectId === "boss-thread-1"), false);
assert.equal(items.some((item) => item.projectId === "boss-thread-2"), false);
state.projects = state.projects.filter((project) => project.id !== "boss-thread-2");
items = getConversationHomeItems(state);
direct = items.find((item) => item.projectId === "boss-thread-1");
assert.ok(direct, "expected a single remaining thread to downgrade back to direct");
assert.equal(direct?.conversationType, "single_device");
assert.equal(
items.some((item) => item.conversationType === "folder_archive" && item.folderKey === "mac-studio:boss"),
false,
);
} finally {
await resetSeedState();
}
});
test("folder archive pin state follows child threads and folder toggle syncs all threads", async () => {
@@ -502,46 +700,50 @@ test("folder archive toggle_pin updates all threads that share the folder key",
await setup();
const state = await readState();
state.projects = state.projects.filter((project) => project.id === "master-agent");
state.projects.push(
buildImportedThreadProject(
"mac-studio",
"yuandi-thread-1",
"园地",
"/Users/kris/code/yuandi",
"线程一",
"thread-1",
"2026-04-05T10:00:00+08:00",
),
buildImportedThreadProject(
"mac-studio",
"yuandi-thread-2",
"园地",
"/Users/kris/code/yuandi",
"线程二",
"thread-2",
"2026-04-05T11:00:00+08:00",
),
);
await writeFile(process.env.BOSS_STATE_FILE as string, `${JSON.stringify(state, null, 2)}\n`);
try {
state.projects = state.projects.filter((project) => project.id === "master-agent");
state.projects.push(
buildImportedThreadProject(
"mac-studio",
"yuandi-thread-1",
"园地",
"/Users/kris/code/yuandi",
"线程一",
"thread-1",
"2026-04-05T10:00:00+08:00",
),
buildImportedThreadProject(
"mac-studio",
"yuandi-thread-2",
"园地",
"/Users/kris/code/yuandi",
"线程二",
"thread-2",
"2026-04-05T11:00:00+08:00",
),
);
await writeFile(process.env.BOSS_STATE_FILE as string, `${JSON.stringify(state, null, 2)}\n`);
await updateConversationAction("mac-studio:/users/kris/code/yuandi", "toggle_pin");
let nextState = await readState();
assert.deepEqual(
nextState.projects
.filter((project) => project.id.startsWith("yuandi-thread-"))
.map((project) => project.pinned),
[true, true],
);
await updateConversationAction("mac-studio:/users/kris/code/yuandi", "toggle_pin");
let nextState = await readState();
assert.deepEqual(
nextState.projects
.filter((project) => project.id.startsWith("yuandi-thread-"))
.map((project) => project.pinned),
[true, true],
);
await updateConversationAction("mac-studio:/users/kris/code/yuandi", "toggle_pin");
nextState = await readState();
assert.deepEqual(
nextState.projects
.filter((project) => project.id.startsWith("yuandi-thread-"))
.map((project) => project.pinned),
[false, false],
);
await updateConversationAction("mac-studio:/users/kris/code/yuandi", "toggle_pin");
nextState = await readState();
assert.deepEqual(
nextState.projects
.filter((project) => project.id.startsWith("yuandi-thread-"))
.map((project) => project.pinned),
[false, false],
);
} finally {
await resetSeedState();
}
});
test("conversation home groups multiple imported threads by folder while keeping single-thread projects direct", async () => {
@@ -709,6 +911,100 @@ test("conversation home compacts imported previews and trims local workspace pre
assert.equal(item?.lastMessagePreview, "已导入线程");
});
test("conversation home sanitizes leaked prompt titles to folder fallbacks", async () => {
await setup();
const state = await readState();
state.projects = state.projects.filter((project) => project.id === "master-agent");
state.projects.push(
buildImportedThreadProject(
"mac-studio",
"boss-thread-prompt",
"boss",
"/Users/kris/code/boss",
"你当前接手的项目根目录是:",
"thread-prompt",
"2026-04-24T10:00:00+08:00",
),
buildImportedThreadProject(
"mac-studio",
"yuandi-thread-prompt",
"yuandi",
"/Users/kris/code/yuandi",
"你现在接手的项目根目录是 /Users/kris/code/yuandi。",
"thread-prompt-2",
"2026-04-24T10:05:00+08:00",
),
);
const bossItem = getConversationHomeItems(state).find((item) => item.projectId === "boss-thread-prompt");
const yuandiItem = getConversationHomeItems(state).find((item) => item.projectId === "yuandi-thread-prompt");
assert.ok(bossItem, "expected prompt-leak boss item");
assert.equal(bossItem?.conversationType, "single_device");
assert.equal(bossItem?.projectTitle, "boss");
assert.equal(bossItem?.threadTitle, "boss");
assert.ok(yuandiItem, "expected prompt-leak yuandi item");
assert.equal(yuandiItem?.conversationType, "single_device");
assert.equal(yuandiItem?.projectTitle, "yuandi");
assert.equal(yuandiItem?.threadTitle, "yuandi");
});
test("project detail view sanitizes leaked prompt title for single-thread projects", async () => {
await setup();
await resetSeedState();
const state = await readState();
const project = buildImportedThreadProject(
"mac-studio",
"detail-sanitize-thread",
"boss",
"boss",
"你当前接手的项目根目录是:",
"detail-sanitize-thread-id",
"2026-04-24T12:00:00+08:00",
);
state.projects.push(project);
project.name = "你当前接手的项目根目录是:";
project.threadMeta.threadDisplayName = "你当前接手的项目根目录是:";
project.threadMeta.folderName = "boss";
project.threadMeta.codexFolderRef = "boss";
const detail = getProjectDetailView(state, project.id);
assert.ok(detail, "expected project detail");
assert.equal(detail?.project.name, "boss");
assert.equal(detail?.project.threadMeta.threadDisplayName, "boss");
});
test("project messages realtime payload sanitizes leaked prompt title for thread chat header", async () => {
await setup();
await resetSeedState();
const state = await readState();
const project = buildImportedThreadProject(
"mac-studio",
"messages-sanitize-thread",
"yuandi",
"yuandi",
"你现在接手的项目根目录是 /Users/kris/code/yuandi。",
"messages-sanitize-thread-id",
"2026-04-24T12:01:00+08:00",
);
state.projects.push(project);
project.name = "你现在接手的项目根目录是 /Users/kris/code/yuandi。";
project.threadMeta.threadDisplayName = "你现在接手的项目根目录是 /Users/kris/code/yuandi。";
project.threadMeta.folderName = "";
project.threadMeta.codexFolderRef = "yuandi";
const payload = buildProjectMessagesRealtimePayload(state, project.id);
assert.ok(payload, "expected realtime payload");
assert.equal(payload?.project.name, "yuandi");
assert.equal(payload?.project.threadMeta.threadDisplayName, "yuandi");
});
test("folder archive homepage rows do not expose pin toggles in the Web surface", async () => {
await setup();
const state = await readState();
@@ -971,10 +1267,10 @@ test("conversation items hide context ring when no thread snapshot exists", asyn
assert.equal(directThread?.contextBudgetIndicator.level, undefined);
});
test("conversation items prefer latest observed codex activity over stale last message time", async () => {
test("conversation items keep latest reply ordering anchored to actual message time", async () => {
await setup();
const state = await readState();
const baseProject = buildImportedThreadProject(
const staleProject = buildImportedThreadProject(
"mac-studio",
"stale-thread",
"Talking",
@@ -983,24 +1279,47 @@ test("conversation items prefer latest observed codex activity over stale last m
"thread-stale",
"2026-04-04T06:12:00+08:00",
);
const freshProject = buildImportedThreadProject(
"mac-studio",
"fresh-thread",
"Fresh",
"fresh",
"最近真回复",
"thread-fresh",
"2026-04-04T12:10:00+08:00",
);
state.projects = state.projects.filter((project) => project.id === "master-agent");
state.projects.push(
freshProject,
{
...baseProject,
...staleProject,
threadMeta: {
...baseProject.threadMeta,
...staleProject.threadMeta,
lastObservedCodexActivityAt: "2026-04-04T11:48:00+08:00",
},
projectUnderstanding: {
projectGoal: "保持会话列表稳定",
currentProgress: "后台只更新状态文档",
technicalArchitecture: "Boss 会话页",
currentBlockers: "",
recommendedNextStep: "不要让非消息事件抬升排序",
updatedAt: "2026-04-04T12:08:00+08:00",
},
},
);
const items = getConversationHomeItems(state);
const thread = items.find((item) => item.projectId === "stale-thread");
const freshThreadIndex = items.findIndex((item) => item.projectId === "fresh-thread");
const staleThreadIndex = items.findIndex((item) => item.projectId === "stale-thread");
assert.ok(thread);
assert.equal(thread?.latestReplyAt, "2026-04-04T11:48:00+08:00");
assert.equal(thread?.latestReplyLabel, formatTimestampLabel("2026-04-04T11:48:00+08:00"));
assert.ok(freshThreadIndex >= 0);
assert.ok(staleThreadIndex >= 0);
assert.ok(freshThreadIndex < staleThreadIndex, "后台活动不应把旧会话抬到真实新消息前面");
assert.equal(thread?.latestReplyAt, "2026-04-04T06:12:00+08:00");
assert.equal(thread?.latestReplyLabel, formatTimestampLabel("2026-04-04T06:12:00+08:00"));
});
test("conversation items mark stale context-backed timestamps as waiting for sync", async () => {
@@ -1047,6 +1366,7 @@ test("default seeded conversations no longer expose Boss 移动控制台", async
const items = getConversationHomeItems(state);
assert.ok(items.some((item) => item.projectId === "master-agent"), "expected master-agent to remain available");
assert.ok(items.some((item) => item.projectId === "audit-collab"), "expected audit collaboration to remain available");
assert.equal(
items.some((item) => item.projectId === "boss-console" || item.threadTitle === "Boss 移动控制台"),
false,

View File

@@ -21,7 +21,7 @@ test("deployment Caddyfile keeps boss and gptpluscontrol routes in a single site
"expected deployment Caddyfile to continue proxying boss-web to port 3000",
);
assert.equal(
(source.match(/boss\.hyzq\.net\s*\{/g) ?? []).length,
(source.match(/^boss\.hyzq\.net\s*\{/gm) ?? []).length,
1,
"expected deployment Caddyfile to avoid duplicate boss.hyzq.net site definitions",
);

View File

@@ -0,0 +1,283 @@
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";
import { NextRequest } from "next/server";
let runtimeRoot = "";
let data: typeof import("../src/lib/boss-data");
let completeTaskRoute: typeof import("../src/app/api/v1/master-agent/tasks/[taskId]/complete/route");
let decisionRoute: typeof import("../src/app/api/v1/dialog-guard/interventions/[interventionId]/decision/route");
let events: typeof import("../src/lib/boss-events");
let authCookie = "";
let baseState: Awaited<ReturnType<typeof import("../src/lib/boss-data")["readState"]>>;
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-dialog-guard-backend-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [dataModule, completeRouteModule, decisionRouteModule, eventsModule, authModule] =
await Promise.all([
import("../src/lib/boss-data.ts"),
import("../src/app/api/v1/master-agent/tasks/[taskId]/complete/route.ts"),
import("../src/app/api/v1/dialog-guard/interventions/[interventionId]/decision/route.ts"),
import("../src/lib/boss-events.ts"),
import("../src/lib/boss-auth.ts"),
]);
data = dataModule;
completeTaskRoute = completeRouteModule;
decisionRoute = decisionRouteModule;
events = eventsModule;
authCookie = authModule.AUTH_SESSION_COOKIE;
baseState = structuredClone(await data.readState());
}
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
}
});
test.beforeEach(async () => {
await setup();
const state = structuredClone(baseState);
state.masterAgentTasks = [];
state.permissionAuditLogs = [];
state.dialogGuardInterventions = [];
await data.writeState(state);
});
function deviceRequest(taskId: string, body: Record<string, unknown>) {
return new NextRequest(
`http://127.0.0.1:3000/api/v1/master-agent/tasks/${taskId}/complete`,
{
method: "POST",
headers: {
"content-type": "application/json",
"x-boss-device-token": "boss-mac-studio-token",
},
body: JSON.stringify(body),
},
);
}
async function authedDecisionRequest(interventionId: string, body: Record<string, unknown>) {
const session = await data.createAuthSession({
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
});
return new NextRequest(
`http://127.0.0.1:3000/api/v1/dialog-guard/interventions/${interventionId}/decision`,
{
method: "POST",
headers: {
"content-type": "application/json",
cookie: `${authCookie}=${session.sessionToken}`,
},
body: JSON.stringify(body),
},
);
}
async function queueDesktopTask() {
const [requestMessage] = await data.appendProjectMessages({
projectId: "master-agent",
messages: [
{
senderLabel: "Boss 超级管理员",
body: "打开微信发送一句测试消息",
kind: "text",
},
],
});
return data.queueMasterAgentTask({
projectId: "master-agent",
taskType: "desktop_control",
requestMessageId: requestMessage.id,
requestText: "打开微信发送一句测试消息",
executionPrompt: "打开微信发送一句测试消息",
requestedBy: "Boss 超级管理员",
requestedByAccount: "krisolo",
deviceId: "mac-studio",
accountId: "openai-master",
accountLabel: "gpt-5.4-mini",
intentCategory: "desktop_control",
runtimeKind: "computer-use-runtime",
riskLevel: "medium",
confirmationPolicy: "light_confirm",
});
}
test("needs_user_action task complete creates pending dialog intervention audit log and realtime event", async () => {
await setup();
const task = await queueDesktopTask();
const seenEvents: Array<{
event: string;
payload: {
interventionId?: string;
projectId?: string;
appName?: string;
taskId?: string;
status?: string;
};
}> = [];
const unsubscribe = events.subscribeBossEvents((event, payload) => {
seenEvents.push({ event, payload });
});
try {
const response = await completeTaskRoute.POST(
deviceRequest(task.taskId, {
deviceId: "mac-studio",
status: "needs_user_action",
kind: "dialog_intervention_required",
requestId: "runtime-request-001",
dialogId: "dialog-wechat-send-confirm",
appName: "微信",
platform: "darwin",
risk: "high",
summary: "微信即将发送外部可见消息,需要用户确认。",
recommendedAction: "allow_once",
availableActions: ["allow_once", "deny", "cancel_task"],
}),
{ params: Promise.resolve({ taskId: task.taskId }) },
);
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.ok, true);
assert.equal(payload.task.status, "needs_user_action");
const state = await data.readState();
const intervention = state.dialogGuardInterventions.at(0);
assert.ok(intervention, "expected dialog intervention to be persisted");
assert.equal(intervention.taskId, task.taskId);
assert.equal(intervention.dialogId, "dialog-wechat-send-confirm");
assert.equal(intervention.requestId, "runtime-request-001");
assert.equal(intervention.deviceId, "mac-studio");
assert.equal(intervention.projectId, "master-agent");
assert.equal(intervention.appName, "微信");
assert.equal(intervention.platform, "darwin");
assert.equal(intervention.risk, "high");
assert.equal(intervention.status, "pending");
assert.deepEqual(intervention.availableActions, ["allow_once", "deny", "cancel_task"]);
assert.equal(
state.permissionAuditLogs.some(
(log) =>
log.action === "dialog_guard.intervention_required" &&
log.deviceId === "mac-studio" &&
log.projectId === "master-agent" &&
log.requestId === "runtime-request-001",
),
true,
);
assert.equal(
seenEvents.some(
(item) =>
item.event === "desktop.dialog_guard.intervention_required" &&
item.payload.interventionId === intervention.interventionId &&
item.payload.projectId === "master-agent" &&
item.payload.appName === "微信" &&
item.payload.taskId === task.taskId &&
item.payload.status === "pending",
),
true,
);
} finally {
unsubscribe();
}
});
test("decision route resolves pending intervention writes audit log and emits resolved event", async () => {
await setup();
const task = await queueDesktopTask();
await completeTaskRoute.POST(
deviceRequest(task.taskId, {
deviceId: "mac-studio",
status: "needs_user_action",
kind: "dialog_intervention_required",
requestId: "runtime-request-002",
dialogId: "dialog-open-file",
appName: "Finder",
platform: "darwin",
risk: "medium",
summary: "Finder 要打开一个本地文件。",
recommendedAction: "allow_once",
availableActions: ["allow_once", "deny", "handled_on_device"],
}),
{ params: Promise.resolve({ taskId: task.taskId }) },
);
const pending = (await data.readState()).dialogGuardInterventions.at(0);
assert.ok(pending, "expected setup to create a pending intervention");
const seenEvents: Array<{
event: string;
payload: {
interventionId?: string;
projectId?: string;
decision?: string;
status?: string;
};
}> = [];
const unsubscribe = events.subscribeBossEvents((event, payload) => {
seenEvents.push({ event, payload });
});
try {
const response = await decisionRoute.POST(
await authedDecisionRequest(pending.interventionId, {
decision: "allow_once",
note: "本次允许。",
}),
{ params: Promise.resolve({ interventionId: pending.interventionId }) },
);
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.ok, true);
assert.equal(payload.intervention.status, "resolved");
assert.equal(payload.intervention.decision, "allow_once");
const state = await data.readState();
const resolved = state.dialogGuardInterventions.find(
(item) => item.interventionId === pending.interventionId,
);
assert.equal(resolved?.status, "resolved");
assert.equal(resolved?.decision, "allow_once");
assert.ok(resolved?.resolvedAt);
assert.equal(
state.permissionAuditLogs.some(
(log) =>
log.action === "dialog_guard.intervention_resolved" &&
log.actorAccount === "krisolo" &&
log.deviceId === "mac-studio" &&
log.projectId === "master-agent" &&
log.requestId === "runtime-request-002",
),
true,
);
assert.equal(
seenEvents.some(
(item) =>
item.event === "desktop.dialog_guard.intervention_resolved" &&
item.payload.interventionId === pending.interventionId &&
item.payload.projectId === "master-agent" &&
item.payload.decision === "allow_once" &&
item.payload.status === "resolved",
),
true,
);
} finally {
unsubscribe();
}
});

View File

@@ -0,0 +1,61 @@
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 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-computer-control-capabilities-"));
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;
getDeviceWorkspaceView = projections.getDeviceWorkspaceView;
buildDeviceWorkspaceDetailCards = ui.buildDeviceWorkspaceDetailCards;
}
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
}
});
test("device detail exposes browser automation and computer use capability state", async () => {
await setup();
const state = await readState();
const macStudio = state.devices.find((device) => device.id === "mac-studio");
assert.ok(macStudio, "expected seeded mac-studio");
macStudio.capabilities.browserAutomation = {
connected: true,
lastSeenAt: "2026-04-22T10:00:00.000Z",
lastActiveProjectId: "master-agent",
};
macStudio.capabilities.computerUse = {
connected: false,
lastSeenAt: "2026-04-22T10:00:00.000Z",
lastActiveProjectId: "",
};
await writeState(state);
const workspace = getDeviceWorkspaceView(await readState(), "mac-studio");
const cards = buildDeviceWorkspaceDetailCards(workspace);
assert.equal(cards.capabilities.items.browserAutomation, "浏览器自动化:已连接");
assert.equal(cards.capabilities.items.computerUse, "桌面控制:未连接");
});

View File

@@ -193,7 +193,7 @@ test("claimNextMasterAgentTask keeps conversation replies queued when the device
requestText: "继续推进当前线程任务",
executionPrompt: "请继续推进当前线程任务",
requestedBy: "Boss 超级管理员",
requestedByAccount: "17600003315",
requestedByAccount: "krisolo",
deviceId: "mac-studio",
taskType: "conversation_reply",
targetProjectId: project.id,
@@ -224,7 +224,7 @@ test("heartbeat external activity on an active cli folder blocks the next claim
requestText: "先推进一轮",
executionPrompt: "请先推进一轮",
requestedBy: "Boss 超级管理员",
requestedByAccount: "17600003315",
requestedByAccount: "krisolo",
deviceId: "mac-studio",
taskType: "conversation_reply",
targetProjectId: project.id,
@@ -240,7 +240,7 @@ test("heartbeat external activity on an active cli folder blocks the next claim
deviceId: "mac-studio",
name: "Mac Studio",
avatar: "M",
account: "17600003315",
account: "krisolo",
status: "online",
quota5h: 72,
quota7d: 86,
@@ -272,7 +272,7 @@ test("heartbeat external activity on an active cli folder blocks the next claim
requestText: "继续推进第二轮",
executionPrompt: "请继续推进第二轮",
requestedBy: "Boss 超级管理员",
requestedByAccount: "17600003315",
requestedByAccount: "krisolo",
deviceId: "mac-studio",
taskType: "conversation_reply",
targetProjectId: project.id,
@@ -341,7 +341,7 @@ test("stale blocked policy does not keep queued conversation replies stuck forev
requestText: "继续推进这个线程",
executionPrompt: "请继续推进这个线程",
requestedBy: "Boss 超级管理员",
requestedByAccount: "17600003315",
requestedByAccount: "krisolo",
deviceId: "mac-studio",
taskType: "conversation_reply",
targetProjectId: project.id,
@@ -361,3 +361,80 @@ test("stale blocked policy does not keep queued conversation replies stuck forev
assert.equal(policy?.activeCliExecution, true);
assert.equal(policy?.recentExternalActivityAt, undefined);
});
test("claimNextMasterAgentTask reclaims stale running conversation replies for the same device", async () => {
await setup();
const project = await getCliProject();
const task = await queueMasterAgentTask({
projectId: project.id,
requestMessageId: "msg-stale-running-reply",
requestText: "请继续推进当前线程",
executionPrompt: "请继续推进当前线程",
requestedBy: "Boss 超级管理员",
requestedByAccount: "krisolo",
deviceId: "mac-studio",
taskType: "conversation_reply",
targetProjectId: project.id,
targetThreadId: project.threadMeta.threadId,
targetThreadDisplayName: project.threadMeta.threadDisplayName,
targetCodexThreadRef: project.threadMeta.codexThreadRef,
targetCodexFolderRef: project.threadMeta.codexFolderRef,
});
const initialClaim = await claimNextMasterAgentTask("mac-studio");
assert.equal(initialClaim?.taskId, task.taskId);
const state = await readState();
const runningTask = state.masterAgentTasks.find((item) => item.taskId === task.taskId);
assert.equal(runningTask?.status, "running");
runningTask!.claimedAt = "2026-04-01T00:00:00.000Z";
await writeState(state);
const reclaimed = await claimNextMasterAgentTask("mac-studio");
assert.equal(reclaimed?.taskId, task.taskId);
const nextState = await readState();
const reclaimedTask = nextState.masterAgentTasks.find((item) => item.taskId === task.taskId);
assert.equal(reclaimedTask?.status, "running");
assert.notEqual(reclaimedTask?.claimedAt, "2026-04-01T00:00:00.000Z");
});
test("claimNextMasterAgentTask does not automatically reclaim stale running dispatch_execution tasks", async () => {
await setup();
const project = await getCliProject();
const task = await queueMasterAgentTask({
projectId: project.id,
requestMessageId: "msg-stale-running-dispatch",
requestText: "请执行修复任务",
executionPrompt: "请执行修复任务",
requestedBy: "Boss 超级管理员",
requestedByAccount: "krisolo",
deviceId: "mac-studio",
taskType: "dispatch_execution",
dispatchExecutionId: "dispatch-exec-stale-1",
targetProjectId: project.id,
targetThreadId: project.threadMeta.threadId,
targetThreadDisplayName: project.threadMeta.threadDisplayName,
targetCodexThreadRef: project.threadMeta.codexThreadRef,
targetCodexFolderRef: project.threadMeta.codexFolderRef,
});
const initialClaim = await claimNextMasterAgentTask("mac-studio");
assert.equal(initialClaim?.taskId, task.taskId);
const state = await readState();
const runningTask = state.masterAgentTasks.find((item) => item.taskId === task.taskId);
assert.equal(runningTask?.status, "running");
runningTask!.claimedAt = "2026-04-01T00:00:00.000Z";
await writeState(state);
const reclaimed = await claimNextMasterAgentTask("mac-studio");
assert.equal(reclaimed, null);
const nextState = await readState();
const unchangedTask = nextState.masterAgentTasks.find((item) => item.taskId === task.taskId);
assert.equal(unchangedTask?.status, "running");
assert.equal(unchangedTask?.claimedAt, "2026-04-01T00:00:00.000Z");
});

View File

@@ -171,7 +171,7 @@ test("device heartbeat persists gui cli capability state on the same physical de
deviceId: "mac-studio",
name: "Mac Studio",
avatar: "M",
account: "17600003315",
account: "krisolo",
status: "online",
quota5h: 72,
quota7d: 86,
@@ -214,7 +214,7 @@ test("device heartbeat does not overwrite the preferred execution mode chosen in
deviceId: "mac-studio",
name: "Mac Studio",
avatar: "M",
account: "17600003315",
account: "krisolo",
status: "online",
quota5h: 72,
quota7d: 86,
@@ -276,7 +276,7 @@ test("device heartbeat without capability payload refreshes stale gui cli lastSe
deviceId: "mac-studio",
name: "Mac Studio",
avatar: "M",
account: "17600003315",
account: "krisolo",
status: "online",
quota5h: 72,
quota7d: 86,

View File

@@ -0,0 +1,522 @@
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 upsertDeviceHeartbeat: (typeof import("../src/lib/boss-data"))["upsertDeviceHeartbeat"];
let writeState: (typeof import("../src/lib/boss-data"))["writeState"];
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-device-heartbeat-message-sync-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const data = await import("../src/lib/boss-data.ts");
readState = data.readState;
upsertDeviceHeartbeat = data.upsertDeviceHeartbeat;
writeState = data.writeState;
}
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
}
});
test("device heartbeat mirrors recent codex desktop replies into the matching thread conversation once", async () => {
await setup();
const seedHeartbeat = {
deviceId: "device-message-sync",
token: "device-message-sync-token",
name: "Mac Studio",
avatar: "M",
account: "krisolo",
status: "online" as const,
quota5h: 76,
quota7d: 85,
projects: [],
endpoint: "mac://kris.local",
projectCandidates: [
{
folderName: "boss",
folderRef: "/Users/kris/code/boss",
threadId: "thread-boss-main",
threadDisplayName: "Boss开发主线程",
codexFolderRef: "/Users/kris/code/boss",
codexThreadRef: "thread-boss-main",
lastActiveAt: "2026-04-20T10:00:00.000Z",
suggestedImport: true,
},
],
};
await upsertDeviceHeartbeat(seedHeartbeat);
await upsertDeviceHeartbeat(seedHeartbeat);
const initialState = await readState();
const importedProject = initialState.projects.find(
(project) => project.threadMeta.codexThreadRef === "thread-boss-main",
);
assert.ok(importedProject, "expected heartbeat auto-import to create the thread conversation");
await upsertDeviceHeartbeat({
...seedHeartbeat,
projectCandidates: [
{
...seedHeartbeat.projectCandidates[0],
recentAssistantMessages: [
{
messageId: "codex-thread:thread-boss-main:2026-04-20T10:02:10.000Z:reply-1",
body: "桌面 Codex 已经把会话实时同步链路修好了。",
sentAt: "2026-04-20T10:02:10.000Z",
},
],
},
],
});
let nextState = await readState();
let nextProject = nextState.projects.find((project) => project.id === importedProject?.id);
const mirroredMessage = nextProject?.messages.find(
(message) => message.externalMessageId === "codex-thread:thread-boss-main:2026-04-20T10:02:10.000Z:reply-1",
);
assert.ok(mirroredMessage);
assert.equal(mirroredMessage?.sender, "device");
assert.equal(mirroredMessage?.senderLabel, "Boss开发主线程");
assert.equal(mirroredMessage?.body, "桌面 Codex 已经把会话实时同步链路修好了。");
assert.equal(nextProject?.lastMessageAt, "2026-04-20T10:02:10.000Z");
assert.equal(nextProject?.preview, "桌面 Codex 已经把会话实时同步链路修好了。");
assert.equal(nextProject?.unreadCount, 1);
await upsertDeviceHeartbeat({
...seedHeartbeat,
projectCandidates: [
{
...seedHeartbeat.projectCandidates[0],
recentAssistantMessages: [
{
messageId: "codex-thread:thread-boss-main:2026-04-20T10:02:10.000Z:reply-1",
body: "桌面 Codex 已经把会话实时同步链路修好了。",
sentAt: "2026-04-20T10:02:10.000Z",
},
],
},
],
});
nextState = await readState();
nextProject = nextState.projects.find((project) => project.id === importedProject?.id);
const mirroredCopies = nextProject?.messages.filter(
(message) => message.externalMessageId === "codex-thread:thread-boss-main:2026-04-20T10:02:10.000Z:reply-1",
);
assert.equal(mirroredCopies?.length, 1);
assert.equal(nextProject?.unreadCount, 1);
});
test("device heartbeat does not duplicate a reply already written by task completion", async () => {
await setup();
const seedHeartbeat = {
deviceId: "device-message-completion-dedupe",
token: "device-message-completion-dedupe-token",
name: "Mac Studio",
avatar: "M",
account: "krisolo",
status: "online" as const,
quota5h: 76,
quota7d: 85,
projects: [],
endpoint: "mac://kris.local",
projectCandidates: [
{
folderName: "boss",
folderRef: "/Users/kris/code/boss",
threadId: "thread-boss-completion-dedupe",
threadDisplayName: "Boss开发主线程",
codexFolderRef: "/Users/kris/code/boss",
codexThreadRef: "thread-boss-completion-dedupe",
lastActiveAt: "2026-04-20T10:00:00.000Z",
suggestedImport: true,
},
],
};
await upsertDeviceHeartbeat(seedHeartbeat);
await upsertDeviceHeartbeat(seedHeartbeat);
const initialState = await readState();
const importedProject = initialState.projects.find(
(project) => project.threadMeta.codexThreadRef === "thread-boss-completion-dedupe",
);
assert.ok(importedProject);
const directReplyBody = "BOSS回归APP消息已收到。";
const targetProject = initialState.projects.find((project) => project.id === importedProject?.id);
assert.ok(targetProject);
targetProject!.messages = [
{
id: "msg-direct-completion",
sender: "device",
senderLabel: "Boss开发主线程",
body: directReplyBody,
sentAt: "2026-04-20T10:02:10.000Z",
kind: "text",
},
];
targetProject!.preview = directReplyBody;
targetProject!.lastMessageAt = "2026-04-20T10:02:10.000Z";
targetProject!.unreadCount = 1;
await writeState(initialState);
await upsertDeviceHeartbeat({
...seedHeartbeat,
projectCandidates: [
{
...seedHeartbeat.projectCandidates[0],
recentAssistantMessages: [
{
messageId: "codex-thread:thread-boss-completion-dedupe:2026-04-20T10:02:10.001Z:reply-1",
body: directReplyBody,
sentAt: "2026-04-20T10:02:10.001Z",
phase: "final_answer",
},
],
},
],
});
const nextState = await readState();
const nextProject = nextState.projects.find((project) => project.id === importedProject?.id);
const matchingReplies = nextProject?.messages.filter((message) => message.body === directReplyBody);
assert.equal(matchingReplies?.length, 1);
assert.equal(matchingReplies?.[0]?.externalMessageId, undefined);
assert.equal(nextProject?.unreadCount, 1);
assert.equal(nextProject?.preview, directReplyBody);
});
test("device heartbeat does not duplicate a takeover reply already written by master agent", async () => {
await setup();
const seedHeartbeat = {
deviceId: "device-message-master-dedupe",
token: "device-message-master-dedupe-token",
name: "Mac Studio",
avatar: "M",
account: "krisolo",
status: "online" as const,
quota5h: 76,
quota7d: 85,
projects: [],
endpoint: "mac://kris.local",
projectCandidates: [
{
folderName: "boss",
folderRef: "/Users/kris/code/boss",
threadId: "thread-boss-master-dedupe",
threadDisplayName: "Boss开发主线程",
codexFolderRef: "/Users/kris/code/boss",
codexThreadRef: "thread-boss-master-dedupe",
lastActiveAt: "2026-04-20T10:00:00.000Z",
suggestedImport: true,
},
],
};
await upsertDeviceHeartbeat(seedHeartbeat);
await upsertDeviceHeartbeat(seedHeartbeat);
const initialState = await readState();
const importedProject = initialState.projects.find(
(project) => project.threadMeta.codexThreadRef === "thread-boss-master-dedupe",
);
assert.ok(importedProject);
const replyBody = "主Agent托管链路已收到。";
const targetProject = initialState.projects.find((project) => project.id === importedProject?.id);
assert.ok(targetProject);
targetProject!.messages = [
{
id: "msg-master-completion",
sender: "master",
senderLabel: "主 Agent",
body: replyBody,
sentAt: "2026-04-20T10:02:10.000Z",
kind: "text",
},
];
targetProject!.preview = replyBody;
targetProject!.lastMessageAt = "2026-04-20T10:02:10.000Z";
targetProject!.unreadCount = 1;
await writeState(initialState);
await upsertDeviceHeartbeat({
...seedHeartbeat,
projectCandidates: [
{
...seedHeartbeat.projectCandidates[0],
recentAssistantMessages: [
{
messageId: "codex-thread:thread-boss-master-dedupe:2026-04-20T10:02:10.001Z:reply-1",
body: replyBody,
sentAt: "2026-04-20T10:02:10.001Z",
phase: "final_answer",
},
],
},
],
});
const nextState = await readState();
const nextProject = nextState.projects.find((project) => project.id === importedProject?.id);
const matchingReplies = nextProject?.messages.filter((message) => message.body === replyBody);
assert.equal(matchingReplies?.length, 1);
assert.equal(matchingReplies?.[0]?.sender, "master");
assert.equal(matchingReplies?.[0]?.externalMessageId, undefined);
assert.equal(nextProject?.unreadCount, 1);
assert.equal(nextProject?.preview, replyBody);
});
test("device heartbeat does not count commentary replies as unread and keeps only the final result unread", async () => {
await setup();
const seedHeartbeat = {
deviceId: "device-message-phase",
token: "device-message-phase-token",
name: "Mac Studio",
avatar: "M",
account: "krisolo",
status: "online" as const,
quota5h: 76,
quota7d: 85,
projects: [],
endpoint: "mac://kris.local",
projectCandidates: [
{
folderName: "boss",
folderRef: "/Users/kris/code/boss",
threadId: "thread-boss-phase",
threadDisplayName: "Boss开发主线程",
codexFolderRef: "/Users/kris/code/boss",
codexThreadRef: "thread-boss-phase",
lastActiveAt: "2026-04-20T10:00:00.000Z",
suggestedImport: true,
},
],
};
await upsertDeviceHeartbeat(seedHeartbeat);
await upsertDeviceHeartbeat(seedHeartbeat);
await upsertDeviceHeartbeat({
...seedHeartbeat,
projectCandidates: [
{
...seedHeartbeat.projectCandidates[0],
recentAssistantMessages: [
{
messageId: "codex-thread:thread-boss-phase:2026-04-20T10:03:00.000Z:commentary-1",
body: "我先检查聊天折叠链路,确认过程消息不会直接展开。",
sentAt: "2026-04-20T10:03:00.000Z",
phase: "commentary",
},
{
messageId: "codex-thread:thread-boss-phase:2026-04-20T10:05:00.000Z:final-1",
body: "这轮已经完成折叠修复,未读现在只会算最终结果。",
sentAt: "2026-04-20T10:05:00.000Z",
phase: "final_answer",
},
],
},
],
});
const nextState = await readState();
const nextProject = nextState.projects.find(
(project) => project.threadMeta.codexThreadRef === "thread-boss-phase",
);
const processMessage = nextProject?.messages.find(
(message) =>
message.externalMessageId === "codex-thread:thread-boss-phase:2026-04-20T10:03:00.000Z:commentary-1",
);
const finalMessage = nextProject?.messages.find(
(message) =>
message.externalMessageId === "codex-thread:thread-boss-phase:2026-04-20T10:05:00.000Z:final-1",
);
assert.ok(nextProject);
assert.equal(processMessage?.kind, "thread_process");
assert.equal(finalMessage?.kind, "text");
assert.equal(nextProject?.preview, "这轮已经完成折叠修复,未读现在只会算最终结果。");
assert.equal(nextProject?.unreadCount, 1);
});
test("device heartbeat does not replay old desktop replies after conversation history is cleared", async () => {
await setup();
const seedHeartbeat = {
deviceId: "device-message-reset",
token: "device-message-reset-token",
name: "Mac Studio",
avatar: "M",
account: "krisolo",
status: "online" as const,
quota5h: 76,
quota7d: 85,
projects: [],
endpoint: "mac://kris.local",
projectCandidates: [
{
folderName: "boss",
folderRef: "/Users/kris/code/boss",
threadId: "thread-boss-reset",
threadDisplayName: "Boss开发主线程",
codexFolderRef: "/Users/kris/code/boss",
codexThreadRef: "thread-boss-reset",
lastActiveAt: "2026-04-20T10:00:00.000Z",
suggestedImport: true,
},
],
};
await upsertDeviceHeartbeat(seedHeartbeat);
await upsertDeviceHeartbeat(seedHeartbeat);
const initialState = await readState();
const importedProject = initialState.projects.find(
(project) => project.threadMeta.codexThreadRef === "thread-boss-reset",
);
assert.ok(importedProject);
initialState.conversationHistoryClearedAt = "2026-04-20T10:10:00.000Z";
const targetProject = initialState.projects.find((project) => project.id === importedProject?.id);
assert.ok(targetProject);
targetProject!.messages = [];
targetProject!.preview = "";
targetProject!.unreadCount = 0;
await writeState(initialState);
await upsertDeviceHeartbeat({
...seedHeartbeat,
projectCandidates: [
{
...seedHeartbeat.projectCandidates[0],
lastActiveAt: "2026-04-20T10:12:00.000Z",
recentAssistantMessages: [
{
messageId: "codex-thread:thread-boss-reset:2026-04-20T10:05:00.000Z:old-final",
body: "这条旧回复不应该在清空历史后被重新导回。",
sentAt: "2026-04-20T10:05:00.000Z",
phase: "final_answer",
},
{
messageId: "codex-thread:thread-boss-reset:2026-04-20T10:11:00.000Z:new-final",
body: "这条新回复应该继续同步回来。",
sentAt: "2026-04-20T10:11:00.000Z",
phase: "final_answer",
},
],
},
],
});
const nextState = await readState();
const nextProject = nextState.projects.find((project) => project.id === importedProject?.id);
assert.ok(nextProject);
assert.equal(
nextProject?.messages.some(
(message) =>
message.externalMessageId === "codex-thread:thread-boss-reset:2026-04-20T10:05:00.000Z:old-final",
),
false,
);
assert.equal(
nextProject?.messages.some(
(message) =>
message.externalMessageId === "codex-thread:thread-boss-reset:2026-04-20T10:11:00.000Z:new-final",
),
true,
);
assert.equal(nextProject?.preview, "这条新回复应该继续同步回来。");
assert.equal(nextProject?.unreadCount, 1);
});
test("device heartbeat legacy process text is normalized to thread_process and does not become preview", async () => {
await setup();
const seedHeartbeat = {
deviceId: "device-message-legacy-process",
token: "device-message-legacy-process-token",
name: "Mac Studio",
avatar: "M",
account: "krisolo",
status: "online" as const,
quota5h: 76,
quota7d: 85,
projects: [],
endpoint: "mac://kris.local",
projectCandidates: [
{
folderName: "boss",
folderRef: "/Users/kris/code/boss",
threadId: "thread-boss-legacy-process",
threadDisplayName: "Boss开发主线程",
codexFolderRef: "/Users/kris/code/boss",
codexThreadRef: "thread-boss-legacy-process",
lastActiveAt: "2026-04-20T10:00:00.000Z",
suggestedImport: true,
},
],
};
await upsertDeviceHeartbeat(seedHeartbeat);
await upsertDeviceHeartbeat(seedHeartbeat);
const resetState = await readState();
resetState.conversationHistoryClearedAt = undefined;
await writeState(resetState);
await upsertDeviceHeartbeat({
...seedHeartbeat,
projectCandidates: [
{
...seedHeartbeat.projectCandidates[0],
recentAssistantMessages: [
{
messageId: "codex-thread:thread-boss-legacy-process:2026-04-20T10:03:00.000Z:commentary-legacy",
body: "我继续把这条链路又往下收了一层,补的是“历史脏消息”的兼容,不只是新消息规则。",
sentAt: "2026-04-20T10:03:00.000Z",
phase: "commentary",
},
{
messageId: "codex-thread:thread-boss-legacy-process:2026-04-20T10:05:00.000Z:final-1",
body: "这轮已经完成折叠修复,未读现在只会算最终结果。",
sentAt: "2026-04-20T10:05:00.000Z",
phase: "final_answer",
},
],
},
],
});
const nextState = await readState();
const nextProject = nextState.projects.find(
(project) => project.threadMeta.codexThreadRef === "thread-boss-legacy-process",
);
const legacyProcessMessage = nextProject?.messages.find(
(message) =>
message.externalMessageId ===
"codex-thread:thread-boss-legacy-process:2026-04-20T10:03:00.000Z:commentary-legacy",
);
assert.ok(nextProject);
assert.equal(legacyProcessMessage?.kind, "thread_process");
assert.equal(nextProject?.preview, "这轮已经完成折叠修复,未读现在只会算最终结果。");
assert.equal(nextProject?.unreadCount, 1);
});

View File

@@ -44,7 +44,7 @@ function buildHeartbeatPayload(deviceId: string, projectCandidates: Array<{
token: `${deviceId}-token`,
name: "Mac Studio",
avatar: "M",
account: "17600003315",
account: "krisolo",
status: "online" as const,
quota5h: 68,
quota7d: 81,

View File

@@ -37,7 +37,7 @@ test.after(async () => {
async function createAuthedSessionCookie() {
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
@@ -54,7 +54,7 @@ test("auto-sync import keeps long-prefix project candidates distinct", async ()
id: "mac-studio",
name: "Mac Studio",
avatar: "M",
account: "17600003315",
account: "krisolo",
source: "production",
status: "online",
projects: [],
@@ -79,7 +79,7 @@ test("auto-sync import keeps long-prefix project candidates distinct", async ()
token: "boss-mac-studio-token",
name: "Mac Studio",
avatar: "M",
account: "17600003315",
account: "krisolo",
status: "online",
quota5h: 68,
quota7d: 81,

View File

@@ -84,8 +84,8 @@ test("device import draft copy shows pending agent review when task is queued",
requestMessageId: "draft-1",
requestText: "请审核设备导入",
executionPrompt: "prompt",
requestedBy: "17600003315",
requestedByAccount: "17600003315",
requestedBy: "krisolo",
requestedByAccount: "krisolo",
deviceId: "mac-studio",
deviceImportDraftId: "draft-1",
status: "queued",

View File

@@ -56,7 +56,7 @@ test.after(async () => {
});
async function createAuthedRequest(url: string, method: "GET" | "POST", body?: unknown) {
return createAuthedRequestFor("17600003315", "highest_admin", url, method, body);
return createAuthedRequestFor("krisolo", "highest_admin", url, method, body);
}
async function createAuthedRequestFor(
@@ -90,7 +90,7 @@ test("device import draft review queues only the resolution task, then completio
await createAuthedRequest("http://127.0.0.1:3000/api/v1/devices/enrollments", "POST", {
name: "MacBook Pro",
avatar: "P",
account: "17600003315",
account: "krisolo",
endpoint: "mac://mbp.local",
note: "待导入新设备",
}),
@@ -110,7 +110,7 @@ test("device import draft review queues only the resolution task, then completio
pairingCode: enrollmentPayload.enrollment.pairingCode,
name: "MacBook Pro",
avatar: "P",
account: "17600003315",
account: "krisolo",
status: "online",
quota5h: 72,
quota7d: 88,
@@ -317,7 +317,7 @@ test("device import draft review queues only the resolution task, then completio
pairingCode: enrollmentPayload.enrollment.pairingCode,
name: "Mac mini",
avatar: "M",
account: "17600003315",
account: "krisolo",
status: "online",
quota5h: 73,
quota7d: 84,
@@ -364,7 +364,7 @@ test("imported thread projects append progress events on newer activity without
await createAuthedRequest("http://127.0.0.1:3000/api/v1/devices/enrollments", "POST", {
name: "Mac mini",
avatar: "M",
account: "17600003315",
account: "krisolo",
endpoint: "mac://mini.local",
note: "project sync follow-up",
}),
@@ -380,7 +380,7 @@ test("imported thread projects append progress events on newer activity without
pairingCode: enrollmentPayload.enrollment.pairingCode,
name: "Mac mini",
avatar: "M",
account: "17600003315",
account: "krisolo",
status: "online" as const,
quota5h: 73,
quota7d: 84,
@@ -580,7 +580,7 @@ test("heartbeat candidates no longer auto-create chat windows from legacy projec
await createAuthedRequest("http://127.0.0.1:3000/api/v1/devices/enrollments", "POST", {
name: "ThinkPad",
avatar: "T",
account: "17600003315",
account: "krisolo",
endpoint: "pc://thinkpad.local",
note: "legacy projects should not auto import",
}),
@@ -603,7 +603,7 @@ test("heartbeat candidates no longer auto-create chat windows from legacy projec
pairingCode: enrollmentPayload.enrollment.pairingCode,
name: "ThinkPad",
avatar: "T",
account: "17600003315",
account: "krisolo",
status: "online",
quota5h: 60,
quota7d: 75,
@@ -641,7 +641,7 @@ test("device import apply is idempotent and heartbeat preserves applied status",
await createAuthedRequest("http://127.0.0.1:3000/api/v1/devices/enrollments", "POST", {
name: "Studio Mac",
avatar: "S",
account: "17600003315",
account: "krisolo",
endpoint: "mac://studio.local",
note: "idempotent import apply",
}),
@@ -657,7 +657,7 @@ test("device import apply is idempotent and heartbeat preserves applied status",
pairingCode: enrollmentPayload.enrollment.pairingCode,
name: "Studio Mac",
avatar: "S",
account: "17600003315",
account: "krisolo",
status: "online" as const,
quota5h: 68,
quota7d: 82,
@@ -801,7 +801,7 @@ test("clearing device import selection resets draft back to pending_selection an
await createAuthedRequest("http://127.0.0.1:3000/api/v1/devices/enrollments", "POST", {
name: "Review Mac",
avatar: "R",
account: "17600003315",
account: "krisolo",
endpoint: "mac://review.local",
note: "selection reset",
}),
@@ -823,7 +823,7 @@ test("clearing device import selection resets draft back to pending_selection an
pairingCode: enrollmentPayload.enrollment.pairingCode,
name: "Review Mac",
avatar: "R",
account: "17600003315",
account: "krisolo",
status: "online",
quota5h: 66,
quota7d: 79,
@@ -913,7 +913,7 @@ test("device import routes reject unrelated logged-in members", async () => {
await createAuthedRequest("http://127.0.0.1:3000/api/v1/devices/enrollments", "POST", {
name: "Build Mac",
avatar: "B",
account: "17600003315",
account: "krisolo",
endpoint: "mac://build.local",
note: "route auth test",
}),
@@ -935,7 +935,7 @@ test("device import routes reject unrelated logged-in members", async () => {
pairingCode: enrollmentPayload.enrollment.pairingCode,
name: "Build Mac",
avatar: "B",
account: "17600003315",
account: "krisolo",
status: "online",
quota5h: 71,
quota7d: 80,
@@ -984,7 +984,7 @@ test("existing bound production devices auto-sync suggested candidates into conv
token: "boss-mac-studio-token",
name: "Mac Studio",
avatar: "M",
account: "17600003315",
account: "krisolo",
status: "online",
quota5h: 68,
quota7d: 81,
@@ -1047,7 +1047,7 @@ test("existing bound production devices auto-sync suggested candidates into conv
token: "boss-mac-studio-token",
name: "Mac Studio",
avatar: "M",
account: "17600003315",
account: "krisolo",
status: "online",
quota5h: 68,
quota7d: 81,

View File

@@ -52,7 +52,7 @@ test.after(async () => {
async function createAuthedRequest(url: string, method: "POST", body: unknown) {
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
@@ -135,7 +135,7 @@ async function createConfirmedDispatchExecution() {
const groupProject = await createProjectGroupChat({
sourceProjectId: memberProjects[0].id,
memberProjectIds: [memberProjects[1].id],
createdBy: "17600003315",
createdBy: "krisolo",
});
const messageResponse = await postMessageRoute(

View File

@@ -120,7 +120,7 @@ function buildDispatchableThreadProject({
async function createAuthedRequest(url: string, method: "GET" | "POST" | "PATCH", body?: unknown) {
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
@@ -173,7 +173,7 @@ async function createDispatchPlanForTest() {
const groupProject = await createProjectGroupChat({
sourceProjectId: memberProjects[0].id,
memberProjectIds: [memberProjects[1].id],
createdBy: "17600003315",
createdBy: "krisolo",
});
const response = await postMessageRoute(

View File

@@ -0,0 +1,18 @@
let input = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (chunk) => {
input += chunk;
});
process.stdin.on("end", () => {
const payload = JSON.parse(input || "{}");
process.stdout.write(
`${JSON.stringify({
status: "completed",
replyBody: `浏览器运行时已执行:${payload.objective || "未提供目标"}`,
executionSummary: "browser-runtime-ok",
requestId: payload.requestId,
})}\n`,
);
});

View File

@@ -0,0 +1,47 @@
#!/usr/bin/env node
import fs from "node:fs/promises";
const chunks = [];
for await (const chunk of process.stdin) {
chunks.push(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
}
const payload = JSON.parse(chunks.join("") || "{}");
const stateFile = process.env.BOSS_CODEX_REFRESH_FLAKY_STATE;
let count = 0;
if (stateFile) {
try {
const state = JSON.parse(await fs.readFile(stateFile, "utf8"));
count = Number.isFinite(state.count) ? state.count : 0;
} catch {
count = 0;
}
}
count += 1;
if (stateFile) {
await fs.writeFile(stateFile, `${JSON.stringify({ count })}\n`, "utf8");
}
if (count === 1) {
process.stdout.write(
`${JSON.stringify({
status: "failed",
targetThreadRef: payload.targetThreadRef,
appName: payload.appName,
error: "transient desktop refresh failure",
})}\n`,
);
process.exit(0);
}
process.stdout.write(
`${JSON.stringify({
status: "completed",
targetThreadRef: payload.targetThreadRef,
appName: payload.appName,
deepLink: `codex://threads/${payload.targetThreadRef}`,
detail: `flaky refresh accepted after ${count} attempts`,
attemptCount: count,
})}\n`,
);

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env node
const chunks = [];
for await (const chunk of process.stdin) {
chunks.push(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
}
const payload = JSON.parse(chunks.join("") || "{}");
process.stdout.write(
`${JSON.stringify({
status: "completed",
targetThreadRef: payload.targetThreadRef,
appName: payload.appName,
detail: `refresh hint accepted: ${payload.refreshMode}`,
})}\n`,
);

18
tests/fixtures/computer-use-runtime.mjs vendored Normal file
View File

@@ -0,0 +1,18 @@
let input = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (chunk) => {
input += chunk;
});
process.stdin.on("end", () => {
const payload = JSON.parse(input || "{}");
process.stdout.write(
`${JSON.stringify({
status: "completed",
replyBody: `桌面运行时已执行:${payload.objective || "未提供目标"}`,
executionSummary: "computer-use-runtime-ok",
requestId: payload.requestId,
})}\n`,
);
});

View File

@@ -103,7 +103,7 @@ function buildDispatchableThreadProject({
async function createAuthedRequest(projectId: string, body: { body: string; kind?: string }) {
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
@@ -156,7 +156,7 @@ test("POST /api/v1/projects/[projectId]/messages returns a dispatch plan for gro
const groupProject = await createIndependentGroupChat({
memberProjectIds: [memberProjects[0].id, memberProjects[1].id],
createdBy: "17600003315",
createdBy: "krisolo",
});
const response = await POST(await createAuthedRequest(groupProject.id, { body: "请大家汇总今天的阻塞点" }), {
@@ -275,6 +275,57 @@ test("POST /api/v1/projects/master-agent/messages returns a dispatch plan for th
assert.ok(queuedDispatchTask, "expected master-agent thread-op request to enqueue a dispatch recommendation task");
});
test("POST /api/v1/projects/master-agent/messages routes named project summary sync to the target thread understanding task", async () => {
await setup();
const [primaryProject] = await ensureTwoSingleThreadProjects();
assert.ok(primaryProject, "expected seeded single-thread project");
const response = await POST(
await createAuthedRequest("master-agent", {
body: "请同步一下北区试产线回归当前项目目标和版本记录",
}),
{ params: Promise.resolve({ projectId: "master-agent" }) },
);
assert.equal(response.status, 200);
const payload = (await response.json()) as {
ok: boolean;
message: { id: string; body: string };
replyMessage?: { body: string };
task?: { taskId: string; taskType: string; status: string } | null;
dispatchPlan: null | { planId: string };
masterReply?: { ok: boolean; masterReplyState?: string };
};
assert.equal(payload.ok, true);
assert.equal(payload.dispatchPlan, null);
assert.equal(payload.masterReply?.masterReplyState, "queued");
assert.ok(payload.replyMessage?.body.includes("北区试产线回归"));
assert.ok(payload.replyMessage?.body.includes("项目目标"));
assert.ok(payload.replyMessage?.body.includes("版本记录"));
assert.ok(!/OTA|MVP|设备在线|运行时/.test(payload.replyMessage?.body ?? ""));
const nextState = await readState();
const syncTask = nextState.masterAgentTasks.find(
(task) =>
task.projectId === "master-agent" &&
task.projectUnderstandingTargetProjectId === primaryProject.id &&
task.requestText.includes(primaryProject.name),
);
assert.ok(syncTask, "expected target project understanding sync task");
assert.equal(payload.task?.taskId, syncTask?.taskId);
assert.match(syncTask!.executionPrompt, /只输出 JSON/);
assert.match(syncTask!.executionPrompt, /不要把全局 OTA 可用状态/);
const genericDispatchTask = nextState.masterAgentTasks.find(
(task) =>
task.projectId === "master-agent" &&
task.requestMessageId === payload.message.id &&
task.taskType === "group_dispatch_plan",
);
assert.equal(genericDispatchTask, undefined, "summary sync should not create a generic dispatch plan");
});
test("POST /api/v1/projects/[projectId]/messages marks approval_required groups as pending user approval", async () => {
await setup();
const memberProjects = await ensureTwoSingleThreadProjects();
@@ -282,7 +333,7 @@ test("POST /api/v1/projects/[projectId]/messages marks approval_required groups
const groupProject = await createIndependentGroupChat({
memberProjectIds: [memberProjects[0].id, memberProjects[1].id],
createdBy: "17600003315",
createdBy: "krisolo",
});
const state = await readState();
@@ -343,7 +394,7 @@ test("POST /api/v1/projects/[projectId]/messages blocks new approval_required re
const groupProject = await createIndependentGroupChat({
memberProjectIds: [memberProjects[0].id, memberProjects[1].id],
createdBy: "17600003315",
createdBy: "krisolo",
});
const state = await readState();
@@ -413,7 +464,7 @@ test("POST /api/v1/projects/[projectId]/messages keeps message success when grou
const groupProject = await createIndependentGroupChat({
memberProjectIds: [memberProjects[0].id, memberProjects[1].id],
createdBy: "17600003315",
createdBy: "krisolo",
});
const state = await readState();
@@ -495,7 +546,7 @@ test("POST /api/v1/projects/[projectId]/messages excludes master-agent from grou
const groupProject = await createIndependentGroupChat({
memberProjectIds: [memberProjects[0].id, memberProjects[1].id],
createdBy: "17600003315",
createdBy: "krisolo",
});
const state = await readState();
@@ -552,7 +603,7 @@ test("createIndependentGroupChat rejects non-thread members like master-agent",
() =>
createIndependentGroupChat({
memberProjectIds: ["master-agent", realThread.id],
createdBy: "17600003315",
createdBy: "krisolo",
}),
/GROUP_CHAT_MEMBER_NOT_THREAD/,
);

View File

@@ -26,6 +26,51 @@ const originalEnv = {
BOSS_OMX_TIMEOUT_MS: process.env.BOSS_OMX_TIMEOUT_MS,
};
function buildDispatchableProject(id: string, name: string, threadId: string) {
return {
id,
name,
pinned: false,
systemPinned: false,
deviceIds: ["mac-studio"],
preview: "等待群聊下发。",
updatedAt: "2026-04-03T10:00:00+08:00",
lastMessageAt: "2026-04-03T10:00:00+08:00",
isGroup: false,
unreadCount: 0,
riskLevel: "low" as const,
contextBudgetPct: 80,
contextBudgetLabel: "80%",
threadMeta: {
projectId: id,
threadId,
threadDisplayName: name,
folderName: "boss",
activityIconCount: 0,
updatedAt: "2026-04-03T10:00:00+08:00",
codexFolderRef: "/Users/kris/code/boss",
codexThreadRef: threadId,
},
groupMembers: [],
messages: [
{
id: `msg-${id}`,
sender: "device" as const,
senderLabel: "Mac Studio / Codex",
body: "等待群聊下发。",
sentAt: "2026-04-03T10:00:00+08:00",
kind: "text" as const,
},
],
goals: [],
versions: [],
createdByAgent: true,
collaborationMode: "development" as const,
approvalState: "not_required" as const,
lightDispatchReminderEnabled: false,
};
}
async function setup() {
if (runtimeRoot) return;
@@ -56,7 +101,7 @@ async function setup() {
async function authedRequest(url: string, method: "GET" | "PATCH" | "POST", body?: unknown) {
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
@@ -78,34 +123,13 @@ async function ensureTwoSingleThreadProjects() {
if (singles.length >= 2) {
return singles;
}
assert.ok(singles[0], "expected at least one dispatchable project");
const seed = singles[0];
const clone = {
...seed,
id: "omx-thread-b",
name: "Boss OMX 副线程",
threadMeta: {
...seed.threadMeta,
projectId: "omx-thread-b",
threadId: "thread-omx-b",
threadDisplayName: "OMX 副线程",
codexThreadRef: "thread-omx-b",
codexFolderRef: "/Users/kris/code/boss",
},
messages: [
{
id: "msg-omx-seed",
sender: "device" as const,
senderLabel: "Mac Studio / Codex",
body: "等待群聊下发。",
sentAt: "2026-04-03T10:00:00+08:00",
kind: "text" as const,
},
],
};
const seeded = [
buildDispatchableProject("omx-thread-a", "Boss OMX 主线程", "thread-omx-a"),
buildDispatchableProject("omx-thread-b", "Boss OMX 副线程", "thread-omx-b"),
].slice(singles.length);
await writeState({
...state,
projects: [...state.projects, clone],
projects: [...state.projects, ...seeded],
});
const nextState = await readState();
return nextState.projects.filter((project) => isDispatchableThreadProject(project));
@@ -145,7 +169,7 @@ test("GET orchestration backend returns null requested backend for default group
const group = await createProjectGroupChat({
sourceProjectId: singles[0].id,
memberProjectIds: [singles[1].id],
createdBy: "17600003315",
createdBy: "krisolo",
});
const response = await getRoute(
@@ -172,7 +196,7 @@ test("PATCH orchestration backend rejects omx when runtime is unavailable", asyn
const group = await createProjectGroupChat({
sourceProjectId: singles[0].id,
memberProjectIds: [singles[1].id],
createdBy: "17600003315",
createdBy: "krisolo",
});
const response = await patchRoute(
@@ -204,7 +228,7 @@ test("group dispatch plans and executions carry omx backend when selected and av
const group = await createProjectGroupChat({
sourceProjectId: singles[0].id,
memberProjectIds: [singles[1].id],
createdBy: "17600003315",
createdBy: "krisolo",
});
const saveResponse = await patchRoute(

View File

@@ -14,6 +14,51 @@ let readState: (typeof import("../src/lib/boss-data"))["readState"];
let writeState: (typeof import("../src/lib/boss-data"))["writeState"];
let AUTH_SESSION_COOKIE = "";
function buildSingleThreadProject(id: string, threadDisplayName: string) {
return {
id,
name: threadDisplayName,
pinned: false,
systemPinned: false,
deviceIds: ["mac-studio"],
preview: "等待线程参与群聊。",
updatedAt: "2026-04-03T10:00:00+08:00",
lastMessageAt: "2026-04-03T10:00:00+08:00",
isGroup: false,
unreadCount: 0,
riskLevel: "low" as const,
contextBudgetPct: 80,
contextBudgetLabel: "80%",
threadMeta: {
projectId: id,
threadId: id,
threadDisplayName,
folderName: "repair-folder",
activityIconCount: 0,
updatedAt: "2026-04-03T10:00:00+08:00",
codexFolderRef: "/Users/kris/code/repair-folder",
codexThreadRef: id,
},
groupMembers: [],
messages: [
{
id: `msg-${id}`,
sender: "device" as const,
senderLabel: "Mac Studio / Codex",
body: "等待线程参与群聊。",
sentAt: "2026-04-03T10:00:00+08:00",
kind: "text" as const,
},
],
goals: [],
versions: [],
createdByAgent: true,
collaborationMode: "development" as const,
approvalState: "not_required" as const,
lightDispatchReminderEnabled: false,
};
}
async function setup() {
if (runtimeRoot) return;
@@ -44,7 +89,7 @@ test.after(async () => {
async function createAuthedRequest(url: string, method: "GET" | "POST", body?: unknown) {
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
@@ -66,28 +111,14 @@ async function ensureTwoSingleThreadProjects() {
if (singles.length >= 2) {
return singles;
}
assert.ok(singles[0], "expected seeded single-thread project");
const seed = singles[0];
const clone = {
...seed,
id: "repair-thread-clone",
name: "Repair Thread Clone",
deviceIds: ["mac-studio"],
threadMeta: {
...seed.threadMeta,
projectId: "repair-thread-clone",
threadId: "repair-thread-clone",
threadDisplayName: "维修回归线程",
folderName: "repair-folder",
codexThreadRef: "repair-thread-clone",
codexFolderRef: "repair-folder",
},
};
const seeded = [
buildSingleThreadProject("repair-thread-a", "维修主线程"),
buildSingleThreadProject("repair-thread-b", "维修回归线程"),
].slice(singles.length);
await writeState({
...state,
projects: [...state.projects, clone],
projects: [...state.projects, ...seeded],
});
const nextState = await readState();
return nextState.projects.filter((project) => project.id !== "master-agent" && !project.isGroup);
@@ -99,7 +130,7 @@ test("GET /api/v1/projects/[projectId]/participants marks dirty groups as repair
const groupProject = await createProjectGroupChat({
sourceProjectId: singles[0].id,
memberProjectIds: [singles[1].id],
createdBy: "17600003315",
createdBy: "krisolo",
});
const state = await readState();
@@ -153,7 +184,7 @@ test("POST /api/v1/projects/[projectId]/participants replaces dirty members with
const groupProject = await createProjectGroupChat({
sourceProjectId: singles[0].id,
memberProjectIds: [singles[1].id],
createdBy: "17600003315",
createdBy: "krisolo",
});
const state = await readState();

View File

@@ -157,7 +157,7 @@ test.after(async () => {
async function createAuthedRequest(url: string) {
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",

View File

@@ -0,0 +1,123 @@
import test from "node:test";
import assert from "node:assert/strict";
import path from "node:path";
import { fileURLToPath } from "node:url";
import {
buildBrowserControlTaskExecution,
canHandleBrowserControlTask,
executeBrowserControlTask,
getBrowserControlTaskRunnerConfig,
parseBrowserControlTaskResult,
} from "../local-agent/browser-control-task-runner.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
test("browser control runner handles browser_control tasks", async () => {
assert.equal(
canHandleBrowserControlTask({
taskType: "browser_control",
requestText: "打开后台首页",
}),
true,
);
});
test("browser control runner derives config from explicit values", () => {
const config = getBrowserControlTaskRunnerConfig({}, {
browserControlEnabled: true,
browserControlCommand: "node",
browserControlArgs: ["tests/fixtures/browser-control-runtime.mjs"],
browserControlWorkdir: repoRoot,
browserControlTimeoutMs: 12000,
});
assert.equal(config.enabled, true);
assert.equal(config.command, "node");
assert.deepEqual(config.args, ["tests/fixtures/browser-control-runtime.mjs"]);
assert.equal(config.cwd, repoRoot);
assert.equal(config.timeoutMs, 12000);
});
test("browser control runner builds normalized stdin payload", () => {
const execution = buildBrowserControlTaskExecution(
{
enabled: true,
command: "node",
args: ["tests/fixtures/browser-control-runtime.mjs"],
cwd: repoRoot,
timeoutMs: 3000,
},
{
taskId: "browser-task-1",
taskType: "browser_control",
requestText: "打开后台首页",
projectId: "boss-console",
threadId: "thread-1",
requestedByAccount: "17600001111",
confirmationScopeKey: "thread:1",
riskLevel: "medium",
},
);
assert.equal(execution.command, "node");
assert.equal(execution.cwd, repoRoot);
assert.equal(execution.timeoutMs, 3000);
assert.equal(execution.stdinPayload.requestKind, "browser_control");
assert.equal(execution.stdinPayload.requestId, "browser-task-1");
assert.equal(execution.stdinPayload.objective, "打开后台首页");
assert.equal(execution.stdinPayload.context.projectId, "boss-console");
assert.equal(execution.stdinPayload.context.threadId, "thread-1");
assert.equal(execution.stdinPayload.context.confirmationScopeKey, "thread:1");
assert.equal(execution.stdinPayload.context.riskLevel, "medium");
});
test("browser control runner parses completed runtime payload", () => {
const result = parseBrowserControlTaskResult(
'{"status":"completed","replyBody":"已打开后台首页","executionSummary":"browser ok"}',
);
assert.equal(result.status, "completed");
assert.equal(result.replyBody, "已打开后台首页");
assert.equal(result.executionSummary, "browser ok");
});
test("browser control runner parses failed runtime payload", () => {
const result = parseBrowserControlTaskResult('{"status":"failed","error":"BROWSER_DENIED"}');
assert.equal(result.status, "failed");
assert.equal(result.errorMessage, "BROWSER_DENIED");
});
test("browser control runner executes configured runtime command", async () => {
const result = await executeBrowserControlTask(
{
taskId: "browser-task-exec",
taskType: "browser_control",
requestText: "打开用户后台",
projectId: "boss-console",
threadId: "thread-browser",
requestedByAccount: "17600002222",
},
{
browserControlEnabled: true,
browserControlCommand: process.execPath,
browserControlArgs: ["tests/fixtures/browser-control-runtime.mjs"],
browserControlWorkdir: repoRoot,
browserControlTimeoutMs: 4000,
},
);
assert.equal(result.status, "completed");
assert.match(result.replyBody ?? "", /浏览器运行时已执行/);
assert.match(result.replyBody ?? "", /打开用户后台/);
});
test("browser control runner reports disabled runtime instead of pretending browser work completed", async () => {
const result = await executeBrowserControlTask({
taskId: "task-browser-control",
requestText: "打开后台首页",
}, {});
assert.equal(result.status, "failed");
assert.equal(result.errorMessage, "BROWSER_CONTROL_RUNTIME_DISABLED");
});

View File

@@ -0,0 +1,620 @@
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs/promises";
import { createServer } from "node:http";
import http from "node:http";
import os from "node:os";
import path from "node:path";
import { spawn } from "node:child_process";
import {
buildCodexDesktopRefreshExecution,
executeCodexDesktopRefreshBridge,
getCodexDesktopRefreshBridgeConfig,
} from "../local-agent/codex-desktop-refresh-bridge.mjs";
const repoRoot = path.resolve(import.meta.dirname, "..");
function runRefreshHintDryRun(payload) {
return new Promise((resolve, reject) => {
const child = spawn(process.execPath, [path.join(repoRoot, "scripts/codex-desktop-refresh-hint.mjs")], {
cwd: repoRoot,
env: {
...process.env,
BOSS_CODEX_DESKTOP_REFRESH_DRY_RUN: "true",
},
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
child.stdout.setEncoding("utf8");
child.stderr.setEncoding("utf8");
child.stdout.on("data", (chunk) => {
stdout += chunk;
});
child.stderr.on("data", (chunk) => {
stderr += chunk;
});
child.on("error", reject);
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(stderr.trim() || `refresh hint dry run exited ${code}`));
return;
}
resolve(JSON.parse(stdout.trim().split(/\r?\n/).at(-1)));
});
child.stdin.end(JSON.stringify(payload));
});
}
function closeServer(server) {
return new Promise((resolve, reject) => {
server.close((error) => {
if (error) {
reject(error);
return;
}
resolve();
});
});
}
function readRequestJson(request) {
return new Promise((resolve, reject) => {
let raw = "";
request.setEncoding("utf8");
request.on("data", (chunk) => {
raw += chunk;
});
request.on("end", () => {
try {
resolve(JSON.parse(raw || "{}"));
} catch (error) {
reject(error);
}
});
request.on("error", reject);
});
}
function listen(server) {
return new Promise((resolve) => {
server.listen(0, "127.0.0.1", () => {
resolve(server.address());
});
});
}
function waitForJsonLine(stream, timeoutMs = 4000) {
return new Promise((resolve, reject) => {
let buffer = "";
const timer = setTimeout(() => {
cleanup();
reject(new Error("TIMED_OUT_WAITING_FOR_JSON_LINE"));
}, timeoutMs);
function cleanup() {
clearTimeout(timer);
stream.off("data", handleData);
stream.off("error", handleError);
}
function handleError(error) {
cleanup();
reject(error);
}
function handleData(chunk) {
buffer += chunk;
const lines = buffer.split(/\r?\n/);
buffer = lines.pop() ?? "";
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) {
continue;
}
try {
cleanup();
resolve(JSON.parse(trimmed));
return;
} catch {
// Ignore non-JSON startup noise.
}
}
}
stream.setEncoding("utf8");
stream.on("data", handleData);
stream.on("error", handleError);
});
}
function subscribeToRefreshEvents(baseUrl, timeoutMs = 4000) {
const events = [];
let request;
let response;
let buffer = "";
let currentEvent = {};
let resolveNext;
let nextTimer;
function cleanup() {
clearTimeout(nextTimer);
request?.destroy();
response?.destroy();
}
function finishEvent() {
if (!currentEvent.event || currentEvent.event === "ready") {
currentEvent = {};
return;
}
const event = {
event: currentEvent.event,
id: currentEvent.id,
data: currentEvent.data ? JSON.parse(currentEvent.data) : {},
};
if (resolveNext) {
const resolve = resolveNext;
resolveNext = undefined;
clearTimeout(nextTimer);
resolve(event);
} else {
events.push(event);
}
currentEvent = {};
}
function handleLine(line) {
if (line === "") {
finishEvent();
return;
}
if (line.startsWith("event:")) {
currentEvent.event = line.slice("event:".length).trim();
} else if (line.startsWith("id:")) {
currentEvent.id = line.slice("id:".length).trim();
} else if (line.startsWith("data:")) {
currentEvent.data = `${currentEvent.data || ""}${line.slice("data:".length).trim()}`;
}
}
const ready = new Promise((resolve, reject) => {
const readyTimer = setTimeout(() => {
cleanup();
reject(new Error("TIMED_OUT_WAITING_FOR_SSE_READY"));
}, timeoutMs);
const url = new URL("/api/v1/codex-desktop/events", baseUrl);
request = http.get(url, (incoming) => {
response = incoming;
incoming.setEncoding("utf8");
incoming.on("data", (chunk) => {
buffer += chunk;
const lines = buffer.split(/\r?\n/);
buffer = lines.pop() ?? "";
for (const line of lines) {
if (line.startsWith("event: ready")) {
clearTimeout(readyTimer);
resolve();
}
handleLine(line);
}
});
incoming.on("error", (error) => {
clearTimeout(readyTimer);
reject(error);
});
});
request.on("error", (error) => {
clearTimeout(readyTimer);
reject(error);
});
});
return {
ready,
events,
nextEvent() {
if (events.length > 0) {
return Promise.resolve(events.shift());
}
return new Promise((resolve, reject) => {
resolveNext = resolve;
nextTimer = setTimeout(() => {
reject(new Error("TIMED_OUT_WAITING_FOR_SSE_EVENT"));
}, timeoutMs);
});
},
close: cleanup,
};
}
test("Codex desktop refresh bridge is skipped when disabled", async () => {
const result = await executeCodexDesktopRefreshBridge(
{
targetThreadRef: "019d-thread-refresh",
sourceMessageId: "msg-refresh",
rolloutPath: "/tmp/rollout.jsonl",
},
{
codexDesktopRefreshEnabled: false,
},
);
assert.deepEqual(result, {
status: "skipped",
reason: "disabled",
});
});
test("Codex desktop refresh bridge daemon exposes a local persistent refresh endpoint", async () => {
const child = spawn(process.execPath, [path.join(repoRoot, "scripts/codex-desktop-refresh-bridge-daemon.mjs")], {
cwd: repoRoot,
env: {
...process.env,
BOSS_CODEX_DESKTOP_BRIDGE_HOST: "127.0.0.1",
BOSS_CODEX_DESKTOP_BRIDGE_PORT: "0",
BOSS_CODEX_DESKTOP_REFRESH_DRY_RUN: "true",
},
stdio: ["ignore", "pipe", "pipe"],
});
try {
const ready = await waitForJsonLine(child.stdout);
assert.equal(ready.status, "ready");
assert.equal(ready.host, "127.0.0.1");
assert.equal(typeof ready.port, "number");
const response = await fetch(`http://${ready.host}:${ready.port}/api/v1/codex-desktop/refresh`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
kind: "codex_desktop_refresh_hint",
targetThreadRef: "019d-thread-refresh",
sourceMessageId: "msg-refresh-daemon",
appName: "Codex",
refreshMode: "deeplink-reload",
message: "must not be reflected",
}),
});
const result = await response.json();
assert.equal(response.status, 200);
assert.equal(result.status, "completed");
assert.equal(result.targetThreadRef, "019d-thread-refresh");
assert.equal(result.deepLink, "codex://threads/019d-thread-refresh");
assert.doesNotMatch(result.detail, /must not be reflected/);
} finally {
child.kill("SIGTERM");
}
});
test("Codex desktop refresh bridge daemon broadcasts safe realtime events over SSE", async () => {
const child = spawn(process.execPath, [path.join(repoRoot, "scripts/codex-desktop-refresh-bridge-daemon.mjs")], {
cwd: repoRoot,
env: {
...process.env,
BOSS_CODEX_DESKTOP_BRIDGE_HOST: "127.0.0.1",
BOSS_CODEX_DESKTOP_BRIDGE_PORT: "0",
BOSS_CODEX_DESKTOP_REFRESH_DRY_RUN: "true",
},
stdio: ["ignore", "pipe", "pipe"],
});
let subscription;
try {
const ready = await waitForJsonLine(child.stdout);
const baseUrl = `http://${ready.host}:${ready.port}`;
subscription = subscribeToRefreshEvents(baseUrl);
await subscription.ready;
const response = await fetch(`${baseUrl}/api/v1/codex-desktop/refresh`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
kind: "codex_desktop_refresh_hint",
targetThreadRef: "019d-thread-refresh",
sourceMessageId: "msg-refresh-sse",
appName: "Codex",
refreshMode: "deeplink-reload",
message: "must not be broadcast",
executionPrompt: "must not be broadcast",
}),
});
assert.equal(response.status, 200);
const event = await subscription.nextEvent();
assert.equal(event.event, "codex_desktop_refresh");
assert.equal(event.data.targetThreadRef, "019d-thread-refresh");
assert.equal(event.data.sourceMessageId, "msg-refresh-sse");
assert.equal(event.data.status, "completed");
assert.equal(event.data.deepLink, "codex://threads/019d-thread-refresh");
assert.equal(Object.prototype.hasOwnProperty.call(event.data, "message"), false);
assert.equal(Object.prototype.hasOwnProperty.call(event.data, "executionPrompt"), false);
const recentResponse = await fetch(`${baseUrl}/api/v1/codex-desktop/events/recent`);
const recent = await recentResponse.json();
assert.equal(recent.ok, true);
assert.equal(recent.events.at(-1).sourceMessageId, "msg-refresh-sse");
} finally {
subscription?.close();
child.kill("SIGTERM");
}
});
test("Codex desktop event consumer receives one safe refresh event from the local bridge", async () => {
const daemon = spawn(process.execPath, [path.join(repoRoot, "scripts/codex-desktop-refresh-bridge-daemon.mjs")], {
cwd: repoRoot,
env: {
...process.env,
BOSS_CODEX_DESKTOP_BRIDGE_HOST: "127.0.0.1",
BOSS_CODEX_DESKTOP_BRIDGE_PORT: "0",
BOSS_CODEX_DESKTOP_REFRESH_DRY_RUN: "true",
},
stdio: ["ignore", "pipe", "pipe"],
});
let consumer;
try {
const ready = await waitForJsonLine(daemon.stdout);
const baseUrl = `http://${ready.host}:${ready.port}`;
consumer = spawn(process.execPath, [path.join(repoRoot, "scripts/codex-desktop-event-consumer.mjs")], {
cwd: repoRoot,
env: {
...process.env,
BOSS_CODEX_DESKTOP_EVENTS_URL: `${baseUrl}/api/v1/codex-desktop/events`,
BOSS_CODEX_DESKTOP_EVENTS_ONCE: "true",
},
stdio: ["ignore", "pipe", "pipe"],
});
await new Promise((resolve) => setTimeout(resolve, 200));
const response = await fetch(`${baseUrl}/api/v1/codex-desktop/refresh`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
kind: "codex_desktop_refresh_hint",
targetThreadRef: "019d-thread-consumer",
sourceMessageId: "msg-refresh-consumer",
appName: "Codex",
refreshMode: "deeplink-reload",
message: "must not reach consumer",
executionPrompt: "must not reach consumer",
}),
});
assert.equal(response.status, 200);
const consumed = await waitForJsonLine(consumer.stdout);
assert.equal(consumed.eventType, "codex_desktop_refresh");
assert.equal(consumed.targetThreadRef, "019d-thread-consumer");
assert.equal(consumed.sourceMessageId, "msg-refresh-consumer");
assert.equal(consumed.deepLink, "codex://threads/019d-thread-consumer");
assert.equal(Object.prototype.hasOwnProperty.call(consumed, "message"), false);
assert.equal(Object.prototype.hasOwnProperty.call(consumed, "executionPrompt"), false);
} finally {
consumer?.kill("SIGTERM");
daemon.kill("SIGTERM");
}
});
test("Codex desktop refresh bridge can use a persistent local endpoint without a command", async () => {
const receivedPayloads = [];
const server = createServer(async (request, response) => {
assert.equal(request.method, "POST");
assert.equal(request.url, "/api/v1/codex-desktop/refresh");
const payload = await readRequestJson(request);
receivedPayloads.push(payload);
response.writeHead(200, { "Content-Type": "application/json" });
response.end(
`${JSON.stringify({
status: "completed",
targetThreadRef: payload.targetThreadRef,
appName: payload.appName,
deepLink: `codex://threads/${payload.targetThreadRef}`,
detail: "persistent bridge accepted refresh hint",
})}\n`,
);
});
const address = await listen(server);
try {
const config = getCodexDesktopRefreshBridgeConfig(
{},
{
codexDesktopRefreshEnabled: true,
codexDesktopRefreshEndpoint: `http://${address.address}:${address.port}/api/v1/codex-desktop/refresh`,
codexDesktopRefreshTimeoutMs: 4000,
codexDesktopRefreshAppName: "Codex",
codexDesktopRefreshMode: "deeplink-reload",
},
);
assert.equal(config.endpoint, `http://${address.address}:${address.port}/api/v1/codex-desktop/refresh`);
const result = await executeCodexDesktopRefreshBridge(
{
targetThreadRef: "019d-thread-refresh",
sourceMessageId: "msg-refresh-endpoint",
rolloutPath: "/tmp/rollout.jsonl",
threadTouchStatus: "updated",
},
config,
);
assert.deepEqual(result, {
status: "completed",
targetThreadRef: "019d-thread-refresh",
appName: "Codex",
deepLink: "codex://threads/019d-thread-refresh",
detail: "persistent bridge accepted refresh hint",
});
assert.equal(receivedPayloads.length, 1);
assert.equal(receivedPayloads[0].kind, "codex_desktop_refresh_hint");
assert.equal(receivedPayloads[0].targetThreadRef, "019d-thread-refresh");
assert.equal(Object.prototype.hasOwnProperty.call(receivedPayloads[0], "message"), false);
assert.equal(Object.prototype.hasOwnProperty.call(receivedPayloads[0], "executionPrompt"), false);
} finally {
await closeServer(server);
}
});
test("Codex desktop refresh bridge falls back to command when the local endpoint is unavailable", async () => {
const config = getCodexDesktopRefreshBridgeConfig(
{},
{
codexDesktopRefreshEnabled: true,
codexDesktopRefreshEndpoint: "http://127.0.0.1:9/api/v1/codex-desktop/refresh",
codexDesktopRefreshCommand: process.execPath,
codexDesktopRefreshArgs: ["tests/fixtures/codex-desktop-refresh-runtime.mjs"],
codexDesktopRefreshWorkdir: repoRoot,
codexDesktopRefreshTimeoutMs: 4000,
codexDesktopRefreshRetryCount: 0,
codexDesktopRefreshRetryDelayMs: 1,
codexDesktopRefreshAppName: "Codex",
codexDesktopRefreshMode: "deeplink-reload",
},
);
const result = await executeCodexDesktopRefreshBridge(
{
targetThreadRef: "019d-thread-refresh",
sourceMessageId: "msg-refresh-fallback",
rolloutPath: "/tmp/rollout.jsonl",
threadTouchStatus: "updated",
},
config,
);
assert.deepEqual(result, {
status: "completed",
targetThreadRef: "019d-thread-refresh",
appName: "Codex",
detail: "refresh hint accepted: deeplink-reload",
});
});
test("Codex desktop refresh bridge sends a safe refresh hint to the configured runtime", async () => {
const config = getCodexDesktopRefreshBridgeConfig(
{},
{
codexDesktopRefreshEnabled: true,
codexDesktopRefreshCommand: process.execPath,
codexDesktopRefreshArgs: ["tests/fixtures/codex-desktop-refresh-runtime.mjs"],
codexDesktopRefreshWorkdir: repoRoot,
codexDesktopRefreshTimeoutMs: 4000,
codexDesktopRefreshAppName: "Codex",
codexDesktopRefreshMode: "deeplink-reload",
},
);
const execution = buildCodexDesktopRefreshExecution(config, {
targetThreadRef: "019d-thread-refresh",
sourceMessageId: "msg-refresh",
rolloutPath: "/tmp/rollout.jsonl",
threadTouchStatus: "updated",
});
assert.equal(execution.command, process.execPath);
assert.deepEqual(execution.args, [path.join(repoRoot, "tests/fixtures/codex-desktop-refresh-runtime.mjs")]);
assert.equal(execution.stdinPayload.kind, "codex_desktop_refresh_hint");
assert.equal(execution.stdinPayload.targetThreadRef, "019d-thread-refresh");
assert.equal(execution.stdinPayload.sourceMessageId, "msg-refresh");
assert.equal(execution.stdinPayload.rolloutPath, "/tmp/rollout.jsonl");
assert.equal(execution.stdinPayload.appName, "Codex");
assert.equal(execution.stdinPayload.refreshMode, "deeplink-reload");
assert.equal(Object.prototype.hasOwnProperty.call(execution.stdinPayload, "message"), false);
assert.equal(Object.prototype.hasOwnProperty.call(execution.stdinPayload, "executionPrompt"), false);
const result = await executeCodexDesktopRefreshBridge(
{
targetThreadRef: "019d-thread-refresh",
sourceMessageId: "msg-refresh",
rolloutPath: "/tmp/rollout.jsonl",
threadTouchStatus: "updated",
},
config,
);
assert.deepEqual(result, {
status: "completed",
targetThreadRef: "019d-thread-refresh",
appName: "Codex",
detail: "refresh hint accepted: deeplink-reload",
});
});
test("Codex desktop refresh bridge retries a failed runtime and reports attempt count", async () => {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "boss-codex-refresh-"));
const stateFile = path.join(tempDir, "flaky-state.json");
const previousStateFile = process.env.BOSS_CODEX_REFRESH_FLAKY_STATE;
process.env.BOSS_CODEX_REFRESH_FLAKY_STATE = stateFile;
try {
const config = getCodexDesktopRefreshBridgeConfig(
{
BOSS_CODEX_DESKTOP_REFRESH_RETRY_COUNT: "2",
BOSS_CODEX_DESKTOP_REFRESH_RETRY_DELAY_MS: "1",
},
{
codexDesktopRefreshEnabled: true,
codexDesktopRefreshCommand: process.execPath,
codexDesktopRefreshArgs: ["tests/fixtures/codex-desktop-refresh-flaky-runtime.mjs"],
codexDesktopRefreshWorkdir: repoRoot,
codexDesktopRefreshTimeoutMs: 4000,
codexDesktopRefreshAppName: "Codex",
codexDesktopRefreshMode: "deeplink-reload",
},
);
assert.equal(config.retryCount, 2);
assert.equal(config.retryDelayMs, 1);
const result = await executeCodexDesktopRefreshBridge(
{
targetThreadRef: "019d-thread-refresh",
sourceMessageId: "msg-refresh-flaky",
rolloutPath: "/tmp/rollout.jsonl",
threadTouchStatus: "updated",
},
config,
);
assert.deepEqual(result, {
status: "completed",
targetThreadRef: "019d-thread-refresh",
appName: "Codex",
deepLink: "codex://threads/019d-thread-refresh",
detail: "flaky refresh accepted after 2 attempts",
attemptCount: 2,
});
const state = JSON.parse(await fs.readFile(stateFile, "utf8"));
assert.equal(state.count, 2);
} finally {
if (previousStateFile === undefined) {
delete process.env.BOSS_CODEX_REFRESH_FLAKY_STATE;
} else {
process.env.BOSS_CODEX_REFRESH_FLAKY_STATE = previousStateFile;
}
await fs.rm(tempDir, { recursive: true, force: true });
}
});
test("Codex desktop refresh hint can target a concrete desktop thread deeplink without sending message text", async () => {
const result = await runRefreshHintDryRun({
kind: "codex_desktop_refresh_hint",
targetThreadRef: "019d-thread-refresh",
sourceMessageId: "msg-refresh",
appName: "Codex",
refreshMode: "deeplink-reload",
message: "this must not be interpreted by the desktop bridge",
});
assert.equal(result.status, "completed");
assert.equal(result.targetThreadRef, "019d-thread-refresh");
assert.equal(result.deepLink, "codex://threads/019d-thread-refresh");
assert.match(result.detail, /would open codex:\/\/threads\/019d-thread-refresh/);
assert.doesNotMatch(result.detail, /this must not be interpreted/);
});

View File

@@ -2,6 +2,7 @@ import test from "node:test";
import assert from "node:assert/strict";
import os from "node:os";
import path from "node:path";
import crypto from "node:crypto";
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
import { DatabaseSync } from "node:sqlite";
@@ -196,6 +197,7 @@ test("discoverCodexProjectCandidates prefers Codex sqlite indexes and session na
});
assert.deepEqual(discovered.projects, ["boss", "yuandi"]);
assert.equal(discovered.guiConnected, true);
assert.equal(discovered.projectCandidates.length, 2);
const bossSession = discovered.projectCandidates.find((item) => item.threadId === "019d3bossmain");
@@ -213,6 +215,190 @@ test("discoverCodexProjectCandidates prefers Codex sqlite indexes and session na
assert.equal(yuandiSession?.codexFolderRef, "/Users/kris/code/yuandi");
});
test("discoverCodexProjectCandidates merges session-only Codex threads when state db is partially stale", async () => {
await setup();
const codexRoot = path.join(runtimeRoot, ".codex-session-merge");
const now = new Date("2026-05-02T10:00:00+08:00");
await mkdir(codexRoot, { recursive: true });
const stateDbPath = path.join(codexRoot, "state_5.sqlite");
const logsDbPath = path.join(codexRoot, "logs_1.sqlite");
const sessionIndexPath = path.join(codexRoot, "session_index.jsonl");
const globalStatePath = path.join(codexRoot, ".codex-global-state.json");
const sessionsDir = path.join(codexRoot, "sessions");
const sessionOnlyDir = path.join(sessionsDir, "2026", "05", "02");
await mkdir(sessionOnlyDir, { recursive: true });
const stateDb = new DatabaseSync(stateDbPath);
stateDb.exec(`
CREATE TABLE threads (
id TEXT PRIMARY KEY,
rollout_path TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
source TEXT NOT NULL,
model_provider TEXT NOT NULL,
cwd TEXT NOT NULL,
title TEXT NOT NULL,
sandbox_policy TEXT NOT NULL,
approval_mode TEXT NOT NULL,
tokens_used INTEGER NOT NULL DEFAULT 0,
has_user_event INTEGER NOT NULL DEFAULT 0,
archived INTEGER NOT NULL DEFAULT 0,
archived_at INTEGER,
git_sha TEXT,
git_branch TEXT,
git_origin_url TEXT,
cli_version TEXT NOT NULL DEFAULT '',
first_user_message TEXT NOT NULL DEFAULT '',
agent_nickname TEXT,
agent_role TEXT,
memory_mode TEXT NOT NULL DEFAULT 'enabled',
model TEXT,
reasoning_effort TEXT,
agent_path TEXT
);
`);
stateDb.prepare(`
INSERT INTO threads (
id, rollout_path, created_at, updated_at, source, model_provider, cwd, title,
sandbox_policy, approval_mode, tokens_used, has_user_event, archived,
cli_version, first_user_message, agent_nickname, agent_role, memory_mode, model, reasoning_effort
) VALUES (?, ?, 1777686800, 1777686800, 'vscode', 'openai', ?, 'Boss 主线程', 'workspace-write', 'never', 0, 1, 0, '0.118.0', '', '', '', 'enabled', 'gpt-5.4', 'medium')
`).run(
"019d-state-thread",
path.join(sessionsDir, "2026/05/02/rollout-state-thread.jsonl"),
"/Users/kris/code/boss",
);
stateDb.close();
const logsDb = new DatabaseSync(logsDbPath);
logsDb.exec(`
CREATE TABLE logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ts INTEGER NOT NULL,
ts_nanos INTEGER NOT NULL,
level TEXT NOT NULL,
target TEXT NOT NULL,
feedback_log_body TEXT,
module_path TEXT,
file TEXT,
line INTEGER,
thread_id TEXT,
process_uuid TEXT,
estimated_bytes INTEGER NOT NULL DEFAULT 0
);
`);
logsDb.prepare(`
INSERT INTO logs (ts, ts_nanos, level, target, thread_id, estimated_bytes)
VALUES (1777686800, 0, 'info', 'codex', '019d-state-thread', 0)
`).run();
logsDb.close();
await writeFile(sessionIndexPath, "", "utf8");
await writeFile(
globalStatePath,
JSON.stringify({ "thread-workspace-root-hints": {} }, null, 2),
"utf8",
);
await writeFile(
path.join(sessionOnlyDir, "rollout-2026-05-02T09-53-45-019d-session-only.jsonl"),
`${JSON.stringify({
timestamp: "2026-05-02T01:53:45.000Z",
type: "session_meta",
payload: {
id: "019d-session-only",
cwd: "/Users/kris/code/boss-regression-smoke",
timestamp: "2026-05-02T01:53:45.000Z",
},
})}\n`,
"utf8",
);
const discovered = await discoverCodexProjectCandidates({
stateDbPath,
logsDbPath,
sessionIndexPath,
globalStatePath,
sessionsDir,
lookbackHours: 24,
now,
});
assert.deepEqual(discovered.projects, ["boss", "boss-regression-smoke"]);
assert.equal(discovered.guiConnected, true);
assert.ok(
discovered.projectCandidates.some((item) => item.threadId === "019d-session-only"),
"expected session-only Codex threads to be imported even when state db has other rows",
);
});
test("discoverCodexProjectCandidates collapses duplicate final assistant records from the same rollout turn", async () => {
await setup();
const codexRoot = path.join(runtimeRoot, ".codex-rollout-dedupe");
const sessionsDir = path.join(codexRoot, "sessions");
const rolloutDir = path.join(sessionsDir, "2026", "05", "02");
const rolloutPath = path.join(
rolloutDir,
"rollout-2026-05-02T10-10-00-019drolloutdedupe.jsonl",
);
await mkdir(rolloutDir, { recursive: true });
const replyBody = "BOSS回归APP消息已收到。";
await writeFile(
rolloutPath,
[
JSON.stringify({
timestamp: "2026-05-02T02:10:00.000Z",
type: "session_meta",
payload: {
id: "019drolloutdedupe",
cwd: "/Users/kris/code/boss-regression-smoke",
timestamp: "2026-05-02T02:10:00.000Z",
},
}),
JSON.stringify({
timestamp: "2026-05-02T02:10:36.311Z",
type: "event_msg",
payload: {
type: "agent_message",
message: replyBody,
phase: "final_answer",
},
}),
JSON.stringify({
timestamp: "2026-05-02T02:10:36.312Z",
type: "response_item",
payload: {
type: "message",
role: "assistant",
content: [{ type: "output_text", text: replyBody }],
},
}),
].join("\n") + "\n",
"utf8",
);
const discovered = await discoverCodexProjectCandidates({
sessionsDir,
stateDbPath: path.join(codexRoot, "missing-state.sqlite"),
logsDbPath: path.join(codexRoot, "missing-logs.sqlite"),
sessionIndexPath: path.join(codexRoot, "missing-session-index.jsonl"),
globalStatePath: path.join(codexRoot, "missing-global-state.json"),
lookbackHours: 24,
now: new Date("2026-05-02T10:30:00+08:00"),
});
const smokeThread = discovered.projectCandidates.find(
(candidate) => candidate.threadId === "019drolloutdedupe",
);
assert.ok(smokeThread, "expected rollout-only smoke thread to be discovered");
assert.equal(smokeThread?.recentAssistantMessages?.length, 1);
assert.equal(smokeThread?.recentAssistantMessages?.[0]?.body, replyBody);
assert.equal(smokeThread?.recentAssistantMessages?.[0]?.phase, "final_answer");
});
test("discoverCodexProjectCandidates excludes read-only threads even when they are the newest primary thread", async () => {
await setup();
@@ -342,3 +528,393 @@ test("discoverCodexProjectCandidates excludes read-only threads even when they a
assert.equal(discovered.projectCandidates[0]?.threadId, "019d-boss-writable");
assert.equal(discovered.projectCandidates[0]?.threadDisplayName, "Boss 可写线程");
});
test("discoverCodexProjectCandidates falls back to workspace folder when thread title leaks internal prompt text", async () => {
await setup();
const codexRoot = path.join(runtimeRoot, ".codex-prompt-title");
const now = new Date("2026-04-24T11:00:00+08:00");
await mkdir(codexRoot, { recursive: true });
const stateDbPath = path.join(codexRoot, "state_5.sqlite");
const logsDbPath = path.join(codexRoot, "logs_1.sqlite");
const sessionIndexPath = path.join(codexRoot, "session_index.jsonl");
const globalStatePath = path.join(codexRoot, ".codex-global-state.json");
const stateDb = new DatabaseSync(stateDbPath);
stateDb.exec(`
CREATE TABLE threads (
id TEXT PRIMARY KEY,
rollout_path TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
source TEXT NOT NULL,
model_provider TEXT NOT NULL,
cwd TEXT NOT NULL,
title TEXT NOT NULL,
sandbox_policy TEXT NOT NULL,
approval_mode TEXT NOT NULL,
tokens_used INTEGER NOT NULL DEFAULT 0,
has_user_event INTEGER NOT NULL DEFAULT 0,
archived INTEGER NOT NULL DEFAULT 0,
archived_at INTEGER,
git_sha TEXT,
git_branch TEXT,
git_origin_url TEXT,
cli_version TEXT NOT NULL DEFAULT '',
first_user_message TEXT NOT NULL DEFAULT '',
agent_nickname TEXT,
agent_role TEXT,
memory_mode TEXT NOT NULL DEFAULT 'enabled',
model TEXT,
reasoning_effort TEXT,
agent_path TEXT
);
`);
stateDb.prepare(`
INSERT INTO threads (
id, rollout_path, created_at, updated_at, source, model_provider, cwd, title,
sandbox_policy, approval_mode, tokens_used, has_user_event, archived,
cli_version, first_user_message, memory_mode, model, reasoning_effort
) VALUES (?, ?, ?, ?, 'desktop', 'openai', ?, ?, 'workspace-write', 'never', 0, 1, 0, '0.118.0', '', 'enabled', 'gpt-5.4', 'medium')
`).run(
"019d-prompt-main",
path.join(codexRoot, "sessions/2026/04/24/rollout-prompt-main.jsonl"),
1776998400,
1776998460,
"/Users/kris/code/boss",
"你当前接手的项目根目录是:",
);
stateDb.close();
const logsDb = new DatabaseSync(logsDbPath);
logsDb.exec(`
CREATE TABLE logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ts INTEGER NOT NULL,
ts_nanos INTEGER NOT NULL,
level TEXT NOT NULL,
target TEXT NOT NULL,
feedback_log_body TEXT,
module_path TEXT,
file TEXT,
line INTEGER,
thread_id TEXT,
process_uuid TEXT,
estimated_bytes INTEGER NOT NULL DEFAULT 0
);
`);
logsDb.prepare(`
INSERT INTO logs (ts, ts_nanos, level, target, thread_id, estimated_bytes)
VALUES (?, 0, 'info', 'codex', ?, 0)
`).run(1776998460, "019d-prompt-main");
logsDb.close();
await writeFile(
sessionIndexPath,
JSON.stringify({
id: "019d-prompt-main",
thread_name: "你现在接手的项目根目录是 /Users/kris/code/boss。",
updated_at: "2026-04-24T03:21:00.000000Z",
}) + "\n",
"utf8",
);
await writeFile(
globalStatePath,
JSON.stringify({ "thread-workspace-root-hints": {} }, null, 2),
"utf8",
);
const discovered = await discoverCodexProjectCandidates({
stateDbPath,
logsDbPath,
sessionIndexPath,
globalStatePath,
lookbackHours: 24,
now,
});
assert.deepEqual(discovered.projects, ["boss"]);
assert.equal(discovered.projectCandidates.length, 1);
assert.equal(discovered.projectCandidates[0]?.threadDisplayName, "boss");
});
test("discoverCodexProjectCandidates mirrors recent desktop assistant replies from rollout files", async () => {
await setup();
const codexRoot = path.join(runtimeRoot, ".codex-message-sync");
const now = new Date("2026-04-20T18:00:00+08:00");
await mkdir(path.join(codexRoot, "sessions/2026/04/20"), { recursive: true });
const stateDbPath = path.join(codexRoot, "state_5.sqlite");
const logsDbPath = path.join(codexRoot, "logs_1.sqlite");
const rolloutPath = path.join(codexRoot, "sessions/2026/04/20/rollout-boss-main.jsonl");
const stateDb = new DatabaseSync(stateDbPath);
stateDb.exec(`
CREATE TABLE threads (
id TEXT PRIMARY KEY,
rollout_path TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
source TEXT NOT NULL,
model_provider TEXT NOT NULL,
cwd TEXT NOT NULL,
title TEXT NOT NULL,
sandbox_policy TEXT NOT NULL,
approval_mode TEXT NOT NULL,
tokens_used INTEGER NOT NULL DEFAULT 0,
has_user_event INTEGER NOT NULL DEFAULT 0,
archived INTEGER NOT NULL DEFAULT 0,
archived_at INTEGER,
git_sha TEXT,
git_branch TEXT,
git_origin_url TEXT,
cli_version TEXT NOT NULL DEFAULT '',
first_user_message TEXT NOT NULL DEFAULT '',
agent_nickname TEXT,
agent_role TEXT,
memory_mode TEXT NOT NULL DEFAULT 'enabled',
model TEXT,
reasoning_effort TEXT,
agent_path TEXT
);
`);
stateDb.prepare(`
INSERT INTO threads (
id, rollout_path, created_at, updated_at, source, model_provider, cwd, title,
sandbox_policy, approval_mode, tokens_used, has_user_event, archived,
cli_version, first_user_message, memory_mode, model, reasoning_effort
) VALUES (?, ?, ?, ?, 'desktop', 'openai', ?, ?, 'workspace-write', 'never', 0, 1, 0, '0.118.0', '', 'enabled', 'gpt-5.4', 'medium')
`).run(
"019d-message-main",
rolloutPath,
1776680000,
1776680100,
"/Users/kris/code/boss",
"Boss 主线程",
);
stateDb.close();
const logsDb = new DatabaseSync(logsDbPath);
logsDb.exec(`
CREATE TABLE logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ts INTEGER NOT NULL,
ts_nanos INTEGER NOT NULL,
level TEXT NOT NULL,
target TEXT NOT NULL,
feedback_log_body TEXT,
module_path TEXT,
file TEXT,
line INTEGER,
thread_id TEXT,
process_uuid TEXT,
estimated_bytes INTEGER NOT NULL DEFAULT 0
);
`);
logsDb
.prepare("INSERT INTO logs (ts, ts_nanos, level, target, thread_id, estimated_bytes) VALUES (?, 0, 'info', 'codex', ?, 0)")
.run(1776680100, "019d-message-main");
logsDb.close();
const assistantText = "桌面线程已经完成实时同步修复。";
const assistantSentAt = "2026-04-20T09:34:56.000Z";
await writeFile(
rolloutPath,
[
JSON.stringify({
type: "session_meta",
payload: {
id: "019d-message-main",
cwd: "/Users/kris/code/boss",
timestamp: "2026-04-20T09:30:00.000Z",
},
}),
JSON.stringify({
timestamp: assistantSentAt,
type: "event_msg",
payload: {
type: "agent_message",
message: assistantText,
},
}),
JSON.stringify({
timestamp: assistantSentAt,
type: "response_item",
payload: {
type: "message",
role: "assistant",
content: [{ type: "output_text", text: assistantText }],
},
}),
].join("\n") + "\n",
"utf8",
);
const discovered = await discoverCodexProjectCandidates({
stateDbPath,
logsDbPath,
lookbackHours: 24,
now,
});
assert.equal(discovered.projectCandidates.length, 1);
const candidate = discovered.projectCandidates[0];
assert.ok(candidate);
assert.deepEqual(candidate?.recentAssistantMessages, [
{
messageId: `codex-thread:019d-message-main:${assistantSentAt}:${crypto.createHash("sha1").update(assistantText).digest("hex").slice(0, 12)}`,
body: assistantText,
sentAt: assistantSentAt,
},
]);
});
test("discoverCodexProjectCandidates preserves assistant reply phase for process folding", async () => {
await setup();
const codexRoot = path.join(runtimeRoot, ".codex-message-phase");
const now = new Date("2026-04-20T18:30:00+08:00");
await mkdir(path.join(codexRoot, "sessions/2026/04/20"), { recursive: true });
const stateDbPath = path.join(codexRoot, "state_5.sqlite");
const logsDbPath = path.join(codexRoot, "logs_1.sqlite");
const rolloutPath = path.join(codexRoot, "sessions/2026/04/20/rollout-boss-main.jsonl");
const stateDb = new DatabaseSync(stateDbPath);
stateDb.exec(`
CREATE TABLE threads (
id TEXT PRIMARY KEY,
rollout_path TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
source TEXT NOT NULL,
model_provider TEXT NOT NULL,
cwd TEXT NOT NULL,
title TEXT NOT NULL,
sandbox_policy TEXT NOT NULL,
approval_mode TEXT NOT NULL,
tokens_used INTEGER NOT NULL DEFAULT 0,
has_user_event INTEGER NOT NULL DEFAULT 0,
archived INTEGER NOT NULL DEFAULT 0,
archived_at INTEGER,
git_sha TEXT,
git_branch TEXT,
git_origin_url TEXT,
cli_version TEXT NOT NULL DEFAULT '',
first_user_message TEXT NOT NULL DEFAULT '',
agent_nickname TEXT,
agent_role TEXT,
memory_mode TEXT NOT NULL DEFAULT 'enabled',
model TEXT,
reasoning_effort TEXT,
agent_path TEXT
);
`);
stateDb.prepare(`
INSERT INTO threads (
id, rollout_path, created_at, updated_at, source, model_provider, cwd, title,
sandbox_policy, approval_mode, tokens_used, has_user_event, archived,
cli_version, first_user_message, memory_mode, model, reasoning_effort
) VALUES (?, ?, ?, ?, 'desktop', 'openai', ?, ?, 'workspace-write', 'never', 0, 1, 0, '0.118.0', '', 'enabled', 'gpt-5.4', 'medium')
`).run(
"019d-message-phase",
rolloutPath,
1776680000,
1776681000,
"/Users/kris/code/boss",
"Boss 主线程",
);
stateDb.close();
const logsDb = new DatabaseSync(logsDbPath);
logsDb.exec(`
CREATE TABLE logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ts INTEGER NOT NULL,
ts_nanos INTEGER NOT NULL,
level TEXT NOT NULL,
target TEXT NOT NULL,
feedback_log_body TEXT,
module_path TEXT,
file TEXT,
line INTEGER,
thread_id TEXT,
process_uuid TEXT,
estimated_bytes INTEGER NOT NULL DEFAULT 0
);
`);
logsDb
.prepare("INSERT INTO logs (ts, ts_nanos, level, target, thread_id, estimated_bytes) VALUES (?, 0, 'info', 'codex', ?, 0)")
.run(1776681000, "019d-message-phase");
logsDb.close();
const processText = "我先检查聊天折叠链路,确认过程消息不会直接展开。";
const finalText = "已完成折叠修复,过程消息会收进按钮里,未读只增加一次。";
const processSentAt = "2026-04-20T10:28:10.000Z";
const finalSentAt = "2026-04-20T10:29:30.000Z";
await writeFile(
rolloutPath,
[
JSON.stringify({
type: "session_meta",
payload: {
id: "019d-message-phase",
cwd: "/Users/kris/code/boss",
timestamp: "2026-04-20T10:20:00.000Z",
},
}),
JSON.stringify({
timestamp: processSentAt,
type: "event_msg",
payload: {
type: "agent_message",
message: processText,
},
}),
JSON.stringify({
timestamp: processSentAt,
type: "response_item",
payload: {
type: "message",
role: "assistant",
content: [{ type: "output_text", text: processText }],
phase: "commentary",
},
}),
JSON.stringify({
timestamp: finalSentAt,
type: "response_item",
payload: {
type: "message",
role: "assistant",
content: [{ type: "output_text", text: finalText }],
phase: "final_answer",
},
}),
].join("\n") + "\n",
"utf8",
);
const discovered = await discoverCodexProjectCandidates({
stateDbPath,
logsDbPath,
lookbackHours: 24,
now,
});
const candidate = discovered.projectCandidates[0];
assert.ok(candidate);
assert.deepEqual(candidate.recentAssistantMessages, [
{
messageId: `codex-thread:019d-message-phase:${processSentAt}:${crypto.createHash("sha1").update(processText).digest("hex").slice(0, 12)}`,
body: processText,
sentAt: processSentAt,
phase: "commentary",
},
{
messageId: `codex-thread:019d-message-phase:${finalSentAt}:${crypto.createHash("sha1").update(finalText).digest("hex").slice(0, 12)}`,
body: finalText,
sentAt: finalSentAt,
phase: "final_answer",
},
]);
});

View File

@@ -0,0 +1,275 @@
import test from "node:test";
import assert from "node:assert/strict";
import os from "node:os";
import path from "node:path";
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { DatabaseSync } from "node:sqlite";
import { appendBossUserMessageToCodexThreadRollout } from "../local-agent/codex-thread-rollout-writer.mjs";
let runtimeRoot = "";
async function ensureRuntimeRoot() {
if (!runtimeRoot) {
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-codex-rollout-writer-"));
}
return runtimeRoot;
}
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
}
});
async function createThreadBinding({ threadId, rolloutPath }) {
const root = await ensureRuntimeRoot();
const dbPath = path.join(root, `state-${Math.random().toString(16).slice(2)}.sqlite`);
const db = new DatabaseSync(dbPath);
db.exec(`
CREATE TABLE threads (
id TEXT PRIMARY KEY,
rollout_path TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
updated_at_ms INTEGER,
source TEXT NOT NULL,
model_provider TEXT NOT NULL,
cwd TEXT NOT NULL,
title TEXT NOT NULL,
sandbox_policy TEXT NOT NULL,
approval_mode TEXT NOT NULL,
tokens_used INTEGER NOT NULL DEFAULT 0,
has_user_event INTEGER NOT NULL DEFAULT 0,
archived INTEGER NOT NULL DEFAULT 0,
archived_at INTEGER,
git_sha TEXT,
git_branch TEXT,
git_origin_url TEXT,
cli_version TEXT NOT NULL DEFAULT '',
first_user_message TEXT NOT NULL DEFAULT '',
agent_nickname TEXT,
agent_role TEXT,
memory_mode TEXT NOT NULL DEFAULT 'enabled',
model TEXT,
reasoning_effort TEXT,
agent_path TEXT
);
`);
db.prepare(`
INSERT INTO threads (
id, rollout_path, created_at, updated_at, updated_at_ms, source, model_provider, cwd, title,
sandbox_policy, approval_mode, tokens_used, has_user_event, archived,
cli_version, first_user_message, agent_nickname, agent_role, memory_mode, model, reasoning_effort
) VALUES (?, ?, 1774845600, 1774845618, 1774845618000, 'desktop', 'openai', ?, ?, '{"type":"workspace-write"}', 'never', 0, 0, 0, '0.118.0', '', '', '', 'enabled', 'gpt-5.4', 'medium')
`).run(threadId, rolloutPath, root, threadId);
db.close();
return dbPath;
}
async function createThreadsDbWithoutBinding() {
const root = await ensureRuntimeRoot();
const dbPath = path.join(root, `state-empty-${Math.random().toString(16).slice(2)}.sqlite`);
const db = new DatabaseSync(dbPath);
db.exec(`
CREATE TABLE threads (
id TEXT PRIMARY KEY,
rollout_path TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
updated_at_ms INTEGER,
source TEXT NOT NULL,
model_provider TEXT NOT NULL,
cwd TEXT NOT NULL,
title TEXT NOT NULL,
sandbox_policy TEXT NOT NULL,
approval_mode TEXT NOT NULL,
tokens_used INTEGER NOT NULL DEFAULT 0,
has_user_event INTEGER NOT NULL DEFAULT 0,
archived INTEGER NOT NULL DEFAULT 0,
archived_at INTEGER,
git_sha TEXT,
git_branch TEXT,
git_origin_url TEXT,
cli_version TEXT NOT NULL DEFAULT '',
first_user_message TEXT NOT NULL DEFAULT '',
agent_nickname TEXT,
agent_role TEXT,
memory_mode TEXT NOT NULL DEFAULT 'enabled',
model TEXT,
reasoning_effort TEXT,
agent_path TEXT
);
`);
db.close();
return dbPath;
}
test("appendBossUserMessageToCodexThreadRollout writes one user_message event and dedupes by source message id", async () => {
const root = await ensureRuntimeRoot();
const rolloutPath = path.join(root, "rollout-thread-real.jsonl");
await writeFile(
rolloutPath,
`${JSON.stringify({
timestamp: "2026-04-21T08:59:00.000Z",
type: "session_meta",
payload: {
id: "019d-thread-real",
cwd: "/Users/kris/code/boss",
},
})}\n`,
"utf8",
);
const stateDbPath = await createThreadBinding({
threadId: "019d-thread-real",
rolloutPath,
});
const beforeTouchDb = new DatabaseSync(stateDbPath, { readonly: true });
const beforeTouchRow = beforeTouchDb
.prepare("SELECT updated_at, updated_at_ms, has_user_event FROM threads WHERE id = ?")
.get("019d-thread-real");
beforeTouchDb.close();
const first = await appendBossUserMessageToCodexThreadRollout({
stateDbPath,
targetThreadRef: "019d-thread-real",
sourceMessageId: "msg-1",
message: "请继续推进",
sentAt: "2026-04-21T09:00:00.000Z",
});
const second = await appendBossUserMessageToCodexThreadRollout({
stateDbPath,
targetThreadRef: "019d-thread-real",
sourceMessageId: "msg-1",
message: "请继续推进",
sentAt: "2026-04-21T09:00:00.000Z",
});
assert.equal(first.status, "written");
assert.equal(second.status, "duplicate");
const raw = await readFile(rolloutPath, "utf8");
const lines = raw.trim().split("\n").map((line) => JSON.parse(line));
const mirrored = lines.filter(
(entry) =>
entry?.type === "event_msg" &&
entry?.payload?.type === "user_message" &&
entry?.payload?.metadata?.bossSourceMessageId === "msg-1",
);
const mirroredResponseItems = lines.filter(
(entry) =>
entry?.type === "response_item" &&
entry?.payload?.type === "message" &&
entry?.payload?.role === "user" &&
entry?.payload?.content?.[0]?.type === "input_text" &&
entry?.payload?.content?.[0]?.text === "请继续推进",
);
assert.equal(mirrored.length, 1);
assert.equal(mirroredResponseItems.length, 1);
assert.equal(mirrored[0]?.payload?.message, "请继续推进");
assert.equal(mirrored[0]?.timestamp, "2026-04-21T09:00:00.000Z");
const afterTouchDb = new DatabaseSync(stateDbPath, { readonly: true });
const afterTouchRow = afterTouchDb
.prepare("SELECT updated_at, updated_at_ms, has_user_event FROM threads WHERE id = ?")
.get("019d-thread-real");
afterTouchDb.close();
assert.equal(afterTouchRow?.has_user_event, 1);
assert.ok(
Number(afterTouchRow?.updated_at) > Number(beforeTouchRow?.updated_at),
"expected mirrored write to refresh updated_at",
);
assert.ok(
Number(afterTouchRow?.updated_at_ms) > Number(beforeTouchRow?.updated_at_ms),
"expected mirrored write to refresh updated_at_ms",
);
});
test("appendBossUserMessageToCodexThreadRollout falls back to sessions dir when the state db is unavailable", async () => {
const root = await ensureRuntimeRoot();
const sessionsDir = path.join(root, "sessions");
const nestedSessionDir = path.join(sessionsDir, "2026", "04", "21");
await mkdir(nestedSessionDir, { recursive: true });
const threadId = "019d-session-only";
const rolloutPath = path.join(
nestedSessionDir,
`rollout-2026-04-21T20-33-36-${threadId}.jsonl`,
);
await writeFile(
rolloutPath,
`${JSON.stringify({
timestamp: "2026-04-21T12:33:36.000Z",
type: "session_meta",
payload: {
id: threadId,
cwd: "/tmp/boss-codex-desktop-sync-smoke",
},
})}\n`,
"utf8",
);
const result = await appendBossUserMessageToCodexThreadRollout({
stateDbPath: path.join(root, "missing-state.sqlite"),
sessionsDir,
targetThreadRef: threadId,
sourceMessageId: "msg-session-only",
message: "从 APP 发起的一条消息",
sentAt: "2026-04-21T12:34:00.000Z",
});
assert.equal(result.status, "written");
assert.equal(result.threadTouch.status, "skipped");
const raw = await readFile(rolloutPath, "utf8");
const lines = raw.trim().split("\n").map((line) => JSON.parse(line));
assert.ok(
lines.some(
(entry) =>
entry?.type === "event_msg" &&
entry?.payload?.type === "user_message" &&
entry?.payload?.metadata?.bossSourceMessageId === "msg-session-only",
),
);
});
test("appendBossUserMessageToCodexThreadRollout skips thread touch when rollout is found in sessions but the db has no matching thread row", async () => {
const root = await ensureRuntimeRoot();
const sessionsDir = path.join(root, "sessions-touch-skip");
const nestedSessionDir = path.join(sessionsDir, "2026", "04", "21");
await mkdir(nestedSessionDir, { recursive: true });
const threadId = "019d-session-without-thread-row";
const rolloutPath = path.join(
nestedSessionDir,
`rollout-2026-04-21T20-35-36-${threadId}.jsonl`,
);
await writeFile(
rolloutPath,
`${JSON.stringify({
timestamp: "2026-04-21T12:35:36.000Z",
type: "session_meta",
payload: {
id: threadId,
cwd: "/tmp/boss-codex-desktop-sync-smoke",
},
})}\n`,
"utf8",
);
const stateDbPath = await createThreadsDbWithoutBinding();
const result = await appendBossUserMessageToCodexThreadRollout({
stateDbPath,
sessionsDir,
targetThreadRef: threadId,
sourceMessageId: "msg-touch-skipped",
message: "这条消息应该只写 rollout不误报 thread touch",
sentAt: "2026-04-21T12:36:00.000Z",
});
assert.equal(result.status, "written");
assert.deepEqual(result.threadTouch, {
status: "skipped",
reason: "thread-not-found",
});
});

View File

@@ -2,7 +2,7 @@ import test from "node:test";
import assert from "node:assert/strict";
import os from "node:os";
import path from "node:path";
import { mkdtemp, mkdir, rm } from "node:fs/promises";
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
import { DatabaseSync } from "node:sqlite";
import {
@@ -90,6 +90,10 @@ test("conversation reply resumes the real Codex thread when thread ref is availa
{
taskType: "conversation_reply",
executionPrompt: "请回复用户",
sourceMessageId: "msg-1",
sourceMessageBody: "请回复用户",
sourceMessageSentAt: "2026-04-21T09:00:00.000Z",
mirrorBossUserMessageToCodexDesktop: true,
targetCodexThreadRef: "019d-thread-real",
targetCodexFolderRef: "/Users/kris/code/meiyesaas",
},
@@ -109,6 +113,13 @@ test("conversation reply resumes the real Codex thread when thread ref is availa
"019d-thread-real",
"请回复用户",
]);
assert.deepEqual(execution.desktopMirror, {
enabled: true,
targetThreadRef: "019d-thread-real",
sourceMessageId: "msg-1",
sourceMessageBody: "请回复用户",
sourceMessageSentAt: "2026-04-21T09:00:00.000Z",
});
});
test("dispatch execution falls back to targetThreadId when codex thread ref is missing", () => {
@@ -167,6 +178,38 @@ test("master agent reply without target thread stays on ephemeral exec", () => {
"gpt-5.4",
"你是主 Agent",
]);
assert.deepEqual(execution.desktopMirror, { enabled: false });
});
test("relay conversation reply mirrors the clean Boss user message into the desktop child thread", () => {
const execution = buildCodexTaskExecution(
{
masterAgentWorkdir: "/Users/kris/code/boss",
masterAgentSandbox: "workspace-write",
masterAgentModel: "gpt-5.4",
},
{
taskType: "conversation_reply",
executionPrompt: "你是主 Agent",
relayViaMasterAgent: true,
sourceMessageId: "msg-relay",
sourceMessageBody: "帮我推进当前线程",
sourceMessageSentAt: "2026-04-21T09:10:00.000Z",
mirrorBossUserMessageToCodexDesktop: true,
targetCodexThreadRef: "019d-thread-real",
targetCodexFolderRef: "/Users/kris/code/meiyesaas",
},
"/tmp/master.txt",
);
assert.deepEqual(execution.desktopMirror, {
enabled: true,
targetThreadRef: "019d-thread-real",
sourceMessageId: "msg-relay",
sourceMessageBody: "帮我推进当前线程",
sourceMessageSentAt: "2026-04-21T09:10:00.000Z",
});
assert.notEqual(execution.desktopMirror.sourceMessageBody, execution.args.at(-1));
});
test("conversation reply preflight fails closed when target cwd is missing", async () => {
@@ -199,6 +242,55 @@ test("conversation reply preflight fails closed when target cwd is missing", asy
assert.match(result.error.message, /missing-workdir/);
});
test("conversation reply preflight accepts session-only Codex threads when state db is stale", async () => {
const root = await ensureRuntimeRoot();
const validCwd = path.join(root, "session-only-project");
const sessionsDir = path.join(root, "sessions-only", "2026", "05", "02");
const threadId = "019d-session-only-task";
await mkdir(validCwd, { recursive: true });
await mkdir(sessionsDir, { recursive: true });
await writeFile(
path.join(sessionsDir, `rollout-2026-05-02T10-10-00-${threadId}.jsonl`),
`${JSON.stringify({
timestamp: "2026-05-02T02:10:00.000Z",
type: "session_meta",
payload: {
id: threadId,
cwd: validCwd,
},
})}\n`,
"utf8",
);
const stateDbPath = await createCodexStateDb([
{
id: "019d-thread-other",
cwd: validCwd,
title: "Other thread",
},
]);
const result = await prepareCodexTaskExecution(
{
masterAgentWorkdir: "/Users/kris/code/boss",
masterAgentSandbox: "workspace-write",
codexStateDbPath: stateDbPath,
codexSessionsDir: path.join(root, "sessions-only"),
},
{
taskType: "conversation_reply",
executionPrompt: "请回复用户",
targetCodexThreadRef: threadId,
targetCodexFolderRef: validCwd,
},
"/tmp/reply.txt",
);
assert.equal(result.ok, true);
assert.equal(result.execution.mode, "resume");
assert.equal(result.execution.cwd, validCwd);
assert.deepEqual(result.execution.args.slice(-2), [threadId, "请回复用户"]);
});
test("dispatch execution preflight fails closed when target thread ref is missing", async () => {
const result = await prepareCodexTaskExecution(
{

View File

@@ -0,0 +1,197 @@
import test from "node:test";
import assert from "node:assert/strict";
import path from "node:path";
import { fileURLToPath } from "node:url";
import {
buildComputerUseTaskExecution,
canHandleComputerUseTask,
executeComputerUseTask,
getComputerUseTaskRunnerConfig,
parseComputerUseTaskResult,
} from "../local-agent/computer-use-task-runner.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
test("computer use runner handles desktop_control tasks", async () => {
assert.equal(
canHandleComputerUseTask({
taskType: "desktop_control",
requestText: "打开系统设置",
}),
true,
);
});
test("computer use runner derives config from explicit values", () => {
const config = getComputerUseTaskRunnerConfig({}, {
computerUseEnabled: true,
computerUseCommand: "node",
computerUseArgs: ["tests/fixtures/computer-use-runtime.mjs"],
computerUseWorkdir: repoRoot,
computerUseTimeoutMs: 12000,
dialogGuardEnabled: true,
dialogGuardConsentRequired: true,
dialogGuardPlatformAdapters: ["darwin", "win32"],
dialogGuardMacActionCommand: "/usr/local/bin/boss-mac-dialog-helper",
dialogGuardMacActionArgs: ["click-dialog"],
dialogGuardWindowsActionCommand: "powershell.exe",
dialogGuardWindowsActionArgs: ["-File", "C:/Boss/dialog-helper.ps1"],
});
assert.equal(config.enabled, true);
assert.equal(config.command, "node");
assert.deepEqual(config.args, ["tests/fixtures/computer-use-runtime.mjs"]);
assert.equal(config.cwd, repoRoot);
assert.equal(config.timeoutMs, 12000);
assert.equal(config.dialogGuardEnabled, true);
assert.equal(config.dialogGuardConsentRequired, true);
assert.deepEqual(config.dialogGuardPlatformAdapters, ["darwin", "win32"]);
assert.equal(config.dialogGuardMacActionCommand, "/usr/local/bin/boss-mac-dialog-helper");
assert.deepEqual(config.dialogGuardMacActionArgs, ["click-dialog"]);
assert.equal(config.dialogGuardWindowsActionCommand, "powershell.exe");
assert.deepEqual(config.dialogGuardWindowsActionArgs, ["-File", "C:/Boss/dialog-helper.ps1"]);
});
test("computer use runner builds normalized stdin payload", () => {
const execution = buildComputerUseTaskExecution(
{
enabled: true,
command: "node",
args: ["tests/fixtures/computer-use-runtime.mjs"],
cwd: repoRoot,
timeoutMs: 3000,
},
{
taskId: "desktop-task-1",
taskType: "desktop_control",
requestText: "打开系统设置",
projectId: "boss-console",
threadId: "thread-desktop",
requestedByAccount: "17600001111",
confirmationScopeKey: "thread:desktop",
riskLevel: "high",
},
);
assert.equal(execution.command, "node");
assert.equal(execution.cwd, repoRoot);
assert.equal(execution.timeoutMs, 3000);
assert.equal(execution.stdinPayload.requestKind, "desktop_control");
assert.equal(execution.stdinPayload.requestId, "desktop-task-1");
assert.equal(execution.stdinPayload.objective, "打开系统设置");
assert.equal(execution.stdinPayload.context.projectId, "boss-console");
assert.equal(execution.stdinPayload.context.threadId, "thread-desktop");
assert.equal(execution.stdinPayload.context.confirmationScopeKey, "thread:desktop");
assert.equal(execution.stdinPayload.context.riskLevel, "high");
});
test("computer use runner passes dialog guard config to runtime env", () => {
const execution = buildComputerUseTaskExecution(
{
enabled: true,
command: "node",
args: ["tests/fixtures/computer-use-runtime.mjs"],
cwd: repoRoot,
timeoutMs: 3000,
dialogGuardEnabled: true,
dialogGuardConsentRequired: true,
dialogGuardPlatformAdapters: ["darwin", "win32"],
dialogGuardMacActionCommand: "/usr/local/bin/boss-mac-dialog-helper",
dialogGuardMacActionArgs: ["click-dialog"],
dialogGuardWindowsActionCommand: "powershell.exe",
dialogGuardWindowsActionArgs: ["-File", "C:/Boss/dialog-helper.ps1"],
},
{
taskId: "desktop-dialog-env",
taskType: "desktop_control",
requestText: "打开 QQ",
},
);
assert.equal(execution.env.BOSS_DIALOG_GUARD_ENABLED, "true");
assert.equal(execution.env.BOSS_DIALOG_GUARD_CONSENT_REQUIRED, "true");
assert.equal(execution.env.BOSS_DIALOG_GUARD_PLATFORM_ADAPTERS, "darwin,win32");
assert.equal(execution.env.BOSS_MAC_DIALOG_GUARD_ACTION_COMMAND, "/usr/local/bin/boss-mac-dialog-helper");
assert.equal(execution.env.BOSS_MAC_DIALOG_GUARD_ACTION_ARGS_JSON, JSON.stringify(["click-dialog"]));
assert.equal(execution.env.BOSS_WINDOWS_DIALOG_GUARD_ACTION_COMMAND, "powershell.exe");
assert.equal(
execution.env.BOSS_WINDOWS_DIALOG_GUARD_ACTION_ARGS_JSON,
JSON.stringify(["-File", "C:/Boss/dialog-helper.ps1"]),
);
});
test("computer use runner parses completed runtime payload", () => {
const result = parseComputerUseTaskResult(
'{"status":"completed","replyBody":"已打开系统设置","executionSummary":"desktop ok"}',
);
assert.equal(result.status, "completed");
assert.equal(result.replyBody, "已打开系统设置");
assert.equal(result.executionSummary, "desktop ok");
});
test("computer use runner parses failed runtime payload", () => {
const result = parseComputerUseTaskResult('{"status":"failed","error":"COMPUTER_USE_DENIED"}');
assert.equal(result.status, "failed");
assert.equal(result.errorMessage, "COMPUTER_USE_DENIED");
});
test("computer use runner parses dialog intervention runtime payload", () => {
const result = parseComputerUseTaskResult(
JSON.stringify({
status: "needs_user_action",
requestId: "desktop-task-dialog",
kind: "dialog_intervention_required",
dialogId: "dialog-1",
risk: "medium",
summary: "QQ 弹窗需要确认",
recommendedAction: "review",
availableActions: ["allow_once", "deny"],
platform: "darwin",
appName: "QQ",
}),
);
assert.equal(result.status, "needs_user_action");
assert.equal(result.requestId, "desktop-task-dialog");
assert.equal(result.kind, "dialog_intervention_required");
assert.equal(result.dialogId, "dialog-1");
assert.equal(result.risk, "medium");
assert.equal(result.summary, "QQ 弹窗需要确认");
assert.deepEqual(result.availableActions, ["allow_once", "deny"]);
});
test("computer use runner executes configured runtime command", async () => {
const result = await executeComputerUseTask(
{
taskId: "desktop-task-exec",
taskType: "desktop_control",
requestText: "打开飞书",
projectId: "boss-console",
threadId: "thread-desktop",
requestedByAccount: "17600002222",
},
{
computerUseEnabled: true,
computerUseCommand: process.execPath,
computerUseArgs: ["tests/fixtures/computer-use-runtime.mjs"],
computerUseWorkdir: repoRoot,
computerUseTimeoutMs: 4000,
},
);
assert.equal(result.status, "completed");
assert.match(result.replyBody ?? "", /桌面运行时已执行/);
assert.match(result.replyBody ?? "", /打开飞书/);
});
test("computer use runner reports disabled runtime instead of pretending desktop work completed", async () => {
const result = await executeComputerUseTask({
taskId: "task-desktop-control",
requestText: "打开系统设置",
}, {});
assert.equal(result.status, "failed");
assert.equal(result.errorMessage, "COMPUTER_USE_RUNTIME_DISABLED");
});

View File

@@ -0,0 +1,116 @@
import test from "node:test";
import assert from "node:assert/strict";
import {
buildDialogInterventionResult,
createDialogSignature,
evaluateDialogSnapshot,
normalizeDialogSnapshot,
readDialogSnapshotFromEnv,
} from "../local-agent/desktop-dialog-guard.mjs";
test("dialog guard auto-handles safe welcome prompts on macOS and Windows", () => {
for (const platform of ["darwin", "win32"]) {
const decision = evaluateDialogSnapshot({
platform,
appName: platform === "darwin" ? "Google Chrome" : "Microsoft Edge",
title: "Welcome",
text: "Welcome. Not now",
buttons: ["Get started", "Not now"],
});
assert.equal(decision.disposition, "auto_action");
assert.equal(decision.action, "click_button");
assert.equal(decision.button, "Not now");
assert.equal(decision.risk, "low");
}
});
test("dialog guard pauses for sensitive permission prompts", () => {
const decision = evaluateDialogSnapshot({
platform: "darwin",
appName: "System Settings",
title: "Screen Recording",
text: "BossComputerUseHelper would like to record this computer's screen",
buttons: ["Allow", "Don't Allow"],
});
assert.equal(decision.disposition, "needs_user_action");
assert.equal(decision.risk, "high");
assert.equal(decision.kind, "permission_required");
});
test("dialog guard generates stable signatures from normalized content", () => {
const a = createDialogSignature({
platform: "darwin",
deviceId: "macbook-air",
appBundleId: "com.google.Chrome",
title: " Welcome ",
text: "Not now",
buttons: ["Not now", "OK"],
});
const b = createDialogSignature({
platform: "darwin",
deviceId: "macbook-air",
appBundleId: "com.google.Chrome",
title: "Welcome",
text: " Not now ",
buttons: ["Not now", "OK"],
});
assert.equal(a.id, b.id);
assert.equal(a.scopeKey, "darwin:macbook-air:com.google.Chrome");
});
test("dialog guard emits app-safe intervention payload", () => {
const snapshot = normalizeDialogSnapshot({
platform: "win32",
deviceId: "win-node",
appName: "Installer",
title: "User Account Control",
text: "Do you want to allow this app to make changes to your device?",
buttons: ["Yes", "No"],
});
const decision = evaluateDialogSnapshot(snapshot);
const result = buildDialogInterventionResult({
requestId: "desktop-task-1",
snapshot,
decision,
});
assert.equal(result.status, "needs_user_action");
assert.equal(result.kind, "dialog_intervention_required");
assert.equal(result.risk, "high");
assert.equal(result.recommendedAction, "handled_on_device");
assert.deepEqual(result.availableActions, ["handled_on_device", "cancel_task"]);
assert.match(result.summary, /Installer/);
});
test("dialog guard reads platform-specific macOS and Windows snapshots from env", () => {
const mac = readDialogSnapshotFromEnv(
{
BOSS_MAC_DIALOG_GUARD_SNAPSHOT_JSON: JSON.stringify({
appName: "System Settings",
title: "Accessibility",
text: "Accessibility permission",
buttons: ["Open Settings"],
}),
},
"darwin",
);
const windows = readDialogSnapshotFromEnv(
{
BOSS_WINDOWS_DIALOG_GUARD_SNAPSHOT_JSON: JSON.stringify({
appName: "Windows Security",
title: "User Account Control",
text: "Do you want to allow this app to make changes to your device?",
buttons: ["Yes", "No"],
}),
},
"win32",
);
assert.equal(mac.platform, "darwin");
assert.equal(mac.appName, "System Settings");
assert.equal(windows.platform, "win32");
assert.equal(windows.appName, "Windows Security");
});

View File

@@ -0,0 +1,158 @@
import test from "node:test";
import assert from "node:assert/strict";
import { createServer } from "node:http";
import { spawn } from "node:child_process";
import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
async function startMockControlPlane() {
let resolveHeartbeat;
const heartbeatReceived = new Promise((resolve) => {
resolveHeartbeat = resolve;
});
const server = createServer(async (request, response) => {
const chunks = [];
for await (const chunk of request) {
chunks.push(chunk);
}
if (request.method === "POST" && request.url === "/api/device-heartbeat") {
resolveHeartbeat(JSON.parse(Buffer.concat(chunks).toString("utf8")));
}
response.writeHead(200, { "content-type": "application/json" });
response.end(JSON.stringify({ ok: true }));
});
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
const address = server.address();
if (!address || typeof address === "string") {
throw new Error("failed to bind mock control plane");
}
return { server, port: address.port, heartbeatReceived };
}
test("local-agent heartbeat reports browser automation and computer use capabilities", async () => {
const runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-local-agent-computer-capabilities-"));
const skillsDir = path.join(runtimeRoot, "skills");
await mkdir(skillsDir, { recursive: true });
const mockControlPlane = await startMockControlPlane();
const exampleConfig = JSON.parse(
await readFile(path.join(repoRoot, "local-agent", "config.example.json"), "utf8"),
);
const configPath = path.join(runtimeRoot, "config.json");
await writeFile(
configPath,
JSON.stringify(
{
...exampleConfig,
bindHost: "127.0.0.1",
port: 0,
controlPlaneUrl: `http://127.0.0.1:${mockControlPlane.port}`,
heartbeatIntervalMs: 60_000,
masterAgentPollIntervalMs: 60_000,
masterAgentEnabled: false,
codexSessionDiscoveryEnabled: false,
projects: [],
projectCandidates: [],
skillsDir,
browserAutomationConnected: true,
computerUseConnected: false,
},
null,
2,
),
"utf8",
);
const child = spawn(process.execPath, ["local-agent/server.mjs", configPath], {
cwd: repoRoot,
stdio: ["ignore", "pipe", "pipe"],
});
try {
const payload = await Promise.race([
mockControlPlane.heartbeatReceived,
new Promise((_, reject) => {
setTimeout(() => reject(new Error("timed out waiting for heartbeat")), 8000);
}),
]);
assert.equal(payload.capabilities.browserAutomation.connected, true);
assert.equal(payload.capabilities.computerUse.connected, true);
} finally {
child.kill("SIGTERM");
await new Promise((resolve) => child.once("close", resolve)).catch(() => null);
await new Promise((resolve) => mockControlPlane.server.close(resolve));
await rm(runtimeRoot, { recursive: true, force: true });
}
});
test("local-agent heartbeat derives browser and computer control capabilities from runtime config", async () => {
const runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-local-agent-runtime-capabilities-"));
const skillsDir = path.join(runtimeRoot, "skills");
await mkdir(skillsDir, { recursive: true });
const mockControlPlane = await startMockControlPlane();
const exampleConfig = JSON.parse(
await readFile(path.join(repoRoot, "local-agent", "config.example.json"), "utf8"),
);
const configPath = path.join(runtimeRoot, "config.json");
await writeFile(
configPath,
JSON.stringify(
{
...exampleConfig,
bindHost: "127.0.0.1",
port: 0,
controlPlaneUrl: `http://127.0.0.1:${mockControlPlane.port}`,
heartbeatIntervalMs: 60_000,
masterAgentPollIntervalMs: 60_000,
masterAgentEnabled: false,
codexSessionDiscoveryEnabled: false,
projects: [],
projectCandidates: [],
skillsDir,
browserAutomationConnected: false,
computerUseConnected: false,
browserControlEnabled: true,
browserControlCommand: process.execPath,
browserControlArgs: ["tests/fixtures/browser-control-runtime.mjs"],
computerUseEnabled: true,
computerUseCommand: process.execPath,
computerUseArgs: ["tests/fixtures/computer-use-runtime.mjs"],
},
null,
2,
),
"utf8",
);
const child = spawn(process.execPath, ["local-agent/server.mjs", configPath], {
cwd: repoRoot,
stdio: ["ignore", "pipe", "pipe"],
});
try {
const payload = await Promise.race([
mockControlPlane.heartbeatReceived,
new Promise((_, reject) => {
setTimeout(() => reject(new Error("timed out waiting for heartbeat")), 8000);
}),
]);
assert.equal(payload.capabilities.browserAutomation.connected, true);
assert.equal(payload.capabilities.computerUse.connected, true);
} finally {
child.kill("SIGTERM");
await new Promise((resolve) => child.once("close", resolve)).catch(() => null);
await new Promise((resolve) => mockControlPlane.server.close(resolve));
await rm(runtimeRoot, { recursive: true, force: true });
}
});

View File

@@ -16,6 +16,12 @@ test("shipped local-agent configs use the faster heartbeat default", async () =>
assert.equal(exampleConfig.heartbeatIntervalMs, 15_000);
assert.equal(cloudConfig.heartbeatIntervalMs, 15_000);
assert.equal(exampleConfig.masterAgentPollIntervalMs, 1_000);
assert.equal(cloudConfig.masterAgentPollIntervalMs, 1_000);
assert.equal(exampleConfig.skillLifecyclePollIntervalMs, 5_000);
assert.equal(cloudConfig.skillLifecyclePollIntervalMs, 5_000);
assert.equal(exampleConfig.skillLifecycleEnabled, true);
assert.equal(cloudConfig.skillLifecycleEnabled, true);
});
test("device enrollment snippet advertises the faster heartbeat default", async () => {
@@ -26,4 +32,25 @@ test("device enrollment snippet advertises the faster heartbeat default", async
test("local-agent runtime falls back to the faster heartbeat default", async () => {
const source = await readFile(path.join(repoRoot, "local-agent", "server.mjs"), "utf8");
assert.match(source, /heartbeatIntervalMs\s*\?\?\s*15000/);
assert.match(source, /masterAgentPollIntervalMs\s*\?\?\s*1000/);
assert.match(source, /skillLifecyclePollIntervalMs\s*\?\?\s*5000/);
});
test("android reply wait loop polls with sub-second cadence", async () => {
const source = await readFile(
path.join(
repoRoot,
"android",
"app",
"src",
"main",
"java",
"com",
"hyzq",
"boss",
"ProjectDetailActivity.java",
),
"utf8",
);
assert.match(source, /REPLY_WAIT_POLL_INTERVAL_MS\s*=\s*800L/);
});

View File

@@ -0,0 +1,42 @@
import test from "node:test";
import assert from "node:assert/strict";
import {
MASTER_CODEX_NODE_OUTPUT_LEAKED,
sanitizeSensitiveTaskFailureDetailForLog,
sanitizeSensitiveTaskFailureDetailForTransport,
shouldBlockSensitiveMasterAgentOutput,
} from "../local-agent/master-task-output-sanitizer.mjs";
const leakedPrompt = [
"管理员全局主提示词:",
"你是 Boss 控制台的主 Agent。",
"默认只说和当前问题直接相关的判断、动作和风险。",
"",
"用户私有主提示词:",
"默认中文回复。",
"",
"当前对话附加提示词:",
"同步项目目标和版本记录后记得告诉我。",
"",
"当前消息:",
"同步完成记得要和我说,以后也是这样。",
].join("\n");
test("local-agent 会阻断包含执行提示词片段的主 Agent 输出", () => {
assert.equal(shouldBlockSensitiveMasterAgentOutput(leakedPrompt), true);
assert.equal(
sanitizeSensitiveTaskFailureDetailForTransport(leakedPrompt),
MASTER_CODEX_NODE_OUTPUT_LEAKED,
);
assert.match(
sanitizeSensitiveTaskFailureDetailForLog(leakedPrompt) ?? "",
/已拦截内部执行日志|原始内容不再展示/,
);
});
test("local-agent 会保留普通失败信息", () => {
const error = "THREAD_BINDING_REQUIRED";
assert.equal(shouldBlockSensitiveMasterAgentOutput(error), false);
assert.equal(sanitizeSensitiveTaskFailureDetailForTransport(error), error);
assert.equal(sanitizeSensitiveTaskFailureDetailForLog(error), error);
});

View File

@@ -0,0 +1,289 @@
import test from "node:test";
import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { createHash } from "node:crypto";
import os from "node:os";
import path from "node:path";
import { promisify } from "node:util";
import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
import {
executeSkillLifecycleRequest,
getSkillLifecycleRunnerConfig,
slugifySkillName,
} from "../local-agent/skill-lifecycle-runner.mjs";
const execFileAsync = promisify(execFile);
async function git(args, cwd) {
await execFileAsync("git", args, { cwd });
}
async function createGitSkillRepo(tmp, name = "remote-skill") {
const repo = path.join(tmp, `${name}-repo`);
await mkdir(repo, { recursive: true });
await git(["init"], repo);
await git(["config", "user.email", "boss-tests@example.com"], repo);
await git(["config", "user.name", "Boss Tests"], repo);
await writeFile(path.join(repo, "SKILL.md"), "---\ndescription: old\n---\nold\n", "utf8");
await git(["add", "SKILL.md"], repo);
await git(["commit", "-m", "old"], repo);
const oldCommit = (await execFileAsync("git", ["rev-parse", "HEAD"], { cwd: repo })).stdout.trim();
await writeFile(path.join(repo, "SKILL.md"), "---\ndescription: new\n---\nnew\n", "utf8");
await git(["add", "SKILL.md"], repo);
await git(["commit", "-m", "new"], repo);
const newCommit = (await execFileAsync("git", ["rev-parse", "HEAD"], { cwd: repo })).stdout.trim();
return { repo, oldCommit, newCommit };
}
function sha256(value) {
return createHash("sha256").update(value).digest("hex");
}
test("skill lifecycle runner derives enabled config from local-agent config", () => {
const config = getSkillLifecycleRunnerConfig({}, {
skillLifecycleEnabled: true,
skillsDir: "/tmp/boss-skills",
skillLifecycleTimeoutMs: 1234,
skillLifecycleAllowedSources: ["https://example.com/boss-skills/"],
});
assert.equal(config.enabled, true);
assert.equal(config.skillsDir, "/tmp/boss-skills");
assert.equal(config.timeoutMs, 1234);
assert.deepEqual(config.allowedSources, ["https://example.com/boss-skills/"]);
});
test("skill lifecycle runner writes version lock file", async () => {
const tmp = await mkdtemp(path.join(os.tmpdir(), "boss-skill-lock-"));
try {
const result = await executeSkillLifecycleRequest(
{
requestId: "request-lock",
action: "version_lock",
status: "running",
deviceId: "mac-studio",
skillId: "mac-studio:demo-skill",
lockedVersion: "1.2.3",
},
{
skillsDir: tmp,
skillLifecycleEnabled: true,
},
{ lastSkills: [] },
);
assert.equal(result.status, "completed");
const locks = JSON.parse(await readFile(path.join(tmp, ".boss-skill-locks.json"), "utf8"));
assert.equal(locks["mac-studio:demo-skill"].lockedVersion, "1.2.3");
} finally {
await rm(tmp, { recursive: true, force: true });
}
});
test("skill lifecycle runner uninstalls only skills inside the configured skills directory", async () => {
const tmp = await mkdtemp(path.join(os.tmpdir(), "boss-skill-uninstall-"));
const skillDir = path.join(tmp, "demo-skill");
await mkdir(skillDir, { recursive: true });
await writeFile(path.join(skillDir, "SKILL.md"), "---\ndescription: demo\n---\n", "utf8");
try {
const result = await executeSkillLifecycleRequest(
{
requestId: "request-uninstall",
action: "uninstall",
status: "running",
deviceId: "mac-studio",
skillId: "mac-studio:demo-skill",
},
{
skillsDir: tmp,
skillLifecycleEnabled: true,
},
{
lastSkills: [
{
name: "demo-skill",
path: path.join(skillDir, "SKILL.md"),
},
],
},
);
assert.equal(result.status, "completed");
await assert.rejects(() => readFile(path.join(skillDir, "SKILL.md"), "utf8"));
} finally {
await rm(tmp, { recursive: true, force: true });
}
});
test("skill lifecycle runner rejects deleting a skill path outside skillsDir", async () => {
const tmp = await mkdtemp(path.join(os.tmpdir(), "boss-skill-safe-"));
const outside = await mkdtemp(path.join(os.tmpdir(), "boss-skill-outside-"));
await writeFile(path.join(outside, "SKILL.md"), "---\ndescription: outside\n---\n", "utf8");
try {
const result = await executeSkillLifecycleRequest(
{
requestId: "request-uninstall-outside",
action: "uninstall",
status: "running",
deviceId: "mac-studio",
skillId: "mac-studio:outside-skill",
},
{
skillsDir: tmp,
skillLifecycleEnabled: true,
},
{
lastSkills: [
{
name: "outside-skill",
path: path.join(outside, "SKILL.md"),
},
],
},
);
assert.equal(result.status, "failed");
assert.equal(result.error, "SKILL_PATH_OUTSIDE_SKILLS_DIR");
assert.equal(await readFile(path.join(outside, "SKILL.md"), "utf8"), "---\ndescription: outside\n---\n");
} finally {
await rm(tmp, { recursive: true, force: true });
await rm(outside, { recursive: true, force: true });
}
});
test("skill lifecycle runner rejects install sourceUrl when no source allowlist or trusted source is configured", async () => {
const tmp = await mkdtemp(path.join(os.tmpdir(), "boss-skill-install-source-"));
const { repo } = await createGitSkillRepo(tmp, "blocked-skill");
const skillsDir = path.join(tmp, "skills");
try {
const result = await executeSkillLifecycleRequest(
{
requestId: "request-install-blocked-source",
action: "install",
status: "running",
deviceId: "mac-studio",
sourceUrl: repo,
},
{
skillsDir,
skillLifecycleEnabled: true,
},
{ lastSkills: [] },
);
assert.equal(result.status, "failed");
assert.equal(result.error, "SKILL_SOURCE_NOT_ALLOWED");
} finally {
await rm(tmp, { recursive: true, force: true });
}
});
test("skill lifecycle runner rejects source urls that only share an allowlist prefix", async () => {
const tmp = await mkdtemp(path.join(os.tmpdir(), "boss-skill-install-prefix-"));
const { repo } = await createGitSkillRepo(tmp, "trusted-evil");
const skillsDir = path.join(tmp, "skills");
try {
const result = await executeSkillLifecycleRequest(
{
requestId: "request-install-prefix-bypass",
action: "install",
status: "running",
deviceId: "mac-studio",
sourceUrl: repo,
},
{
skillsDir,
skillLifecycleEnabled: true,
skillLifecycleAllowedSources: [path.join(tmp, "trusted")],
},
{ lastSkills: [] },
);
assert.equal(result.status, "failed");
assert.equal(result.error, "SKILL_SOURCE_NOT_ALLOWED");
} finally {
await rm(tmp, { recursive: true, force: true });
}
});
test("skill lifecycle runner removes a newly cloned skill when checksum verification fails", async () => {
const tmp = await mkdtemp(path.join(os.tmpdir(), "boss-skill-checksum-install-"));
const { repo } = await createGitSkillRepo(tmp, "checksum-skill");
const skillsDir = path.join(tmp, "skills");
try {
const result = await executeSkillLifecycleRequest(
{
requestId: "request-install-bad-checksum",
action: "install",
status: "running",
deviceId: "mac-studio",
sourceUrl: repo,
expectedChecksum: sha256("not the installed skill"),
},
{
skillsDir,
skillLifecycleEnabled: true,
skillLifecycleAllowedSources: [repo],
},
{ lastSkills: [] },
);
assert.equal(result.status, "failed");
assert.equal(result.error, "SKILL_CHECKSUM_MISMATCH");
await assert.rejects(() => readFile(path.join(skillsDir, "remote", "checksum-skill-repo", "SKILL.md"), "utf8"));
} finally {
await rm(tmp, { recursive: true, force: true });
}
});
test("skill lifecycle runner backs up and restores an existing skill when update checksum verification fails", async () => {
const tmp = await mkdtemp(path.join(os.tmpdir(), "boss-skill-checksum-update-"));
const { repo, oldCommit } = await createGitSkillRepo(tmp, "update-skill");
const skillsDir = path.join(tmp, "skills");
const skillDir = path.join(skillsDir, "update-skill");
await mkdir(skillsDir, { recursive: true });
await git(["clone", repo, skillDir], tmp);
await git(["reset", "--hard", oldCommit], skillDir);
try {
const result = await executeSkillLifecycleRequest(
{
requestId: "request-update-bad-checksum",
action: "update",
status: "running",
deviceId: "mac-studio",
skillId: "mac-studio:update-skill",
expectedChecksum: sha256("wrong expected skill"),
},
{
skillsDir,
skillLifecycleEnabled: true,
},
{
lastSkills: [
{
name: "update-skill",
path: path.join(skillDir, "SKILL.md"),
},
],
},
);
assert.equal(result.status, "failed");
assert.equal(result.error, "SKILL_CHECKSUM_MISMATCH");
assert.equal(await readFile(path.join(skillDir, "SKILL.md"), "utf8"), "---\ndescription: old\n---\nold\n");
const backups = await readdir(path.join(skillsDir, ".boss-skill-backups"));
assert.equal(backups.length, 1);
} finally {
await rm(tmp, { recursive: true, force: true });
}
});
test("skill lifecycle slug matches server skill id convention", () => {
assert.equal(slugifySkillName("Boss Server Debug"), "boss-server-debug");
});

View File

@@ -171,7 +171,7 @@ test("master-agent 对话控制路由可读写并回显到项目详情", async (
try {
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
@@ -286,7 +286,7 @@ test("master-agent 对话控制按当前账号隔离,不会串到其他用户"
await setup();
const adminSession = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
@@ -346,7 +346,7 @@ test("master-agent 对话控制路由单字段更新不会清掉另一字段", a
await setup();
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
@@ -397,7 +397,7 @@ test("全局接管默认会透传到普通线程会话详情", async () => {
await setup();
const projectId = await ensureOrdinaryProject("ordinary-takeover-project");
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
@@ -444,7 +444,7 @@ test("普通线程会话可以单独关闭主 Agent 协同接管并覆盖全局
await setup();
const projectId = await ensureOrdinaryProject("ordinary-project-override");
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
@@ -477,7 +477,7 @@ test("普通线程会话可以单独关闭主 Agent 协同接管并覆盖全局
);
assert.equal(response.status, 200);
const detail = getProjectDetailView(await readState(), projectId, "17600003315");
const detail = getProjectDetailView(await readState(), projectId, "krisolo");
assert.equal(detail?.agentControls?.effectiveTakeoverEnabled, false);
assert.equal(detail?.agentControls?.takeoverInheritedFromGlobal, false);
});
@@ -486,7 +486,7 @@ test("master-agent 对话控制 POST 清空后仍稳定回传 controls null", as
await setup();
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
@@ -536,7 +536,7 @@ test("普通线程项目详情会回传接管控制占位", async () => {
const ordinaryProjectId = await ensureOrdinaryProject();
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
@@ -603,7 +603,7 @@ test("master-agent 对话控制 POST 会稳定拒绝非法 modelOverride", async
await setup();
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
@@ -637,7 +637,7 @@ test("master-agent 对话控制 POST 会稳定拒绝 malformed JSON 和空对象
await setup();
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
@@ -843,7 +843,7 @@ test("GET /agent-controls returns 404 for missing project", async () => {
await setup();
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
@@ -888,7 +888,7 @@ test(
const route = routeModule.default ?? routeModule;
const session = await data.createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
@@ -965,7 +965,7 @@ test("GET /agent-controls 在未显式设置 BOSS_STATE_FILE 时仍可正常读
});
const session = await data.createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
@@ -1011,7 +1011,7 @@ test("GET /agent-controls supports ordinary projects for takeover settings", asy
const ordinaryProjectId = await ensureOrdinaryProject();
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
@@ -1035,7 +1035,7 @@ test("POST /agent-controls rejects unknown-key payload and preserves controls",
await setup();
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
@@ -1080,7 +1080,7 @@ test("master-agent 对话控制 POST 会稳定拒绝非法 backendOverride", asy
await setup();
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",

View File

@@ -104,9 +104,9 @@ test("主 Agent 执行配置会合成管理员提示词、用户提示词和当
await updateMasterAgentPromptPolicy({
globalPrompt: "全局主提示词",
updatedBy: "17600003315",
updatedBy: "krisolo",
});
await updateUserMasterPrompt("17600003315", "用户私有主提示词");
await updateUserMasterPrompt("krisolo", "用户私有主提示词");
await updateProjectAgentControls("master-agent", {
modelOverride: "gpt-5.4",
reasoningEffortOverride: "high",
@@ -118,7 +118,7 @@ test("主 Agent 执行配置会合成管理员提示词、用户提示词和当
assert.equal(resolved.promptPolicy?.globalPrompt, "全局主提示词");
assert.equal(resolved.userPrompt?.content, "用户私有主提示词");
assert.equal(resolved.projectPromptOverride, "当前对话提示词");
assert.equal(resolved.promptPolicy?.updatedBy, "17600003315");
assert.equal(resolved.promptPolicy?.updatedBy, "krisolo");
});
test("主 Agent 执行 prompt 会明确声明管理员全局提示词不可覆盖,并带出项目记忆来源", async () => {
@@ -138,14 +138,14 @@ test("主 Agent 执行 prompt 会明确声明管理员全局提示词不可覆
await updateMasterAgentPromptPolicy({
globalPrompt: "系统级主提示词",
updatedBy: "17600003315",
updatedBy: "krisolo",
});
await updateUserMasterPrompt("17600003315", "用户私有主提示词");
await updateUserMasterPrompt("krisolo", "用户私有主提示词");
await updateProjectAgentControls("master-agent", {
promptOverride: "当前对话提示词",
});
await createUserMasterMemory({
account: "17600003315",
account: "krisolo",
scope: "project",
projectId: "boss-main",
title: "boss 项目进度",
@@ -154,7 +154,7 @@ test("主 Agent 执行 prompt 会明确声明管理员全局提示词不可覆
tags: ["boss", "会话"],
});
await createUserMasterMemory({
account: "17600003315",
account: "krisolo",
scope: "project",
projectId: "project-wenshenapp",
title: "wenshenapp 项目进度",
@@ -165,7 +165,7 @@ test("主 Agent 执行 prompt 会明确声明管理员全局提示词不可覆
const resolved = await resolveMasterAgentExecutionConfig(
"master-agent",
"17600003315",
"krisolo",
"继续推进 boss 项目的会话归档逻辑",
);

View File

@@ -0,0 +1,29 @@
import assert from "node:assert/strict";
import test from "node:test";
import { classifyMasterAgentControlIntentForTesting } from "@/lib/boss-master-agent";
test("routes ordinary product discussion to discussion_only", () => {
const result = classifyMasterAgentControlIntentForTesting("帮我总结一下这个项目当前目标");
assert.equal(result.intentCategory, "discussion_only");
assert.equal(result.executionMode, "discussion");
});
test("routes development ask to project_development", () => {
const result = classifyMasterAgentControlIntentForTesting("继续开发这个项目,修掉登录闪退并跑测试");
assert.equal(result.intentCategory, "project_development");
assert.equal(result.executionMode, "development");
});
test("routes browser asks to browser_control", () => {
const result = classifyMasterAgentControlIntentForTesting("打开 Chrome 去后台看一下订单页");
assert.equal(result.intentCategory, "browser_control");
assert.equal(result.executionMode, "browser");
assert.equal(result.riskLevel, "medium");
});
test("routes desktop gui asks to desktop_control", () => {
const result = classifyMasterAgentControlIntentForTesting("打开微信并切到和产品经理的聊天窗口");
assert.equal(result.intentCategory, "desktop_control");
assert.equal(result.executionMode, "desktop");
assert.equal(result.riskLevel, "medium");
});

View File

@@ -8,6 +8,8 @@ let runtimeRoot = "";
let queueMasterAgentTask: (typeof import("../src/lib/boss-data"))["queueMasterAgentTask"];
let completeMasterAgentTask: (typeof import("../src/lib/boss-data"))["completeMasterAgentTask"];
let listUserMasterMemories: (typeof import("../src/lib/boss-data"))["listUserMasterMemories"];
let readState: (typeof import("../src/lib/boss-data"))["readState"];
let writeState: (typeof import("../src/lib/boss-data"))["writeState"];
async function setup() {
if (runtimeRoot) return;
@@ -20,6 +22,54 @@ async function setup() {
queueMasterAgentTask = data.queueMasterAgentTask;
completeMasterAgentTask = data.completeMasterAgentTask;
listUserMasterMemories = data.listUserMasterMemories;
readState = data.readState;
writeState = data.writeState;
}
async function ensureBossProject() {
const state = await readState();
if (state.projects.some((project) => project.id === "boss")) {
return;
}
await writeState({
...state,
projects: [
...state.projects,
{
id: "boss",
name: "boss",
pinned: false,
systemPinned: false,
deviceIds: ["mac-studio"],
preview: "等待项目同步。",
updatedAt: "2026-04-03T10:00:00+08:00",
lastMessageAt: "2026-04-03T10:00:00+08:00",
isGroup: false,
unreadCount: 0,
riskLevel: "low",
contextBudgetPct: 80,
contextBudgetLabel: "80%",
threadMeta: {
projectId: "boss",
threadId: "boss-thread",
threadDisplayName: "Boss开发主线程",
folderName: "boss",
activityIconCount: 0,
updatedAt: "2026-04-03T10:00:00+08:00",
codexFolderRef: "/Users/kris/code/boss",
codexThreadRef: "boss-thread",
},
groupMembers: [],
messages: [],
goals: [],
versions: [],
createdByAgent: true,
collaborationMode: "development",
approvalState: "not_required",
lightDispatchReminderEnabled: false,
},
],
});
}
test.after(async () => {
@@ -30,14 +80,15 @@ test.after(async () => {
test("主 Agent 完成对话后会自动沉淀用户偏好和项目记忆", async () => {
await setup();
await ensureBossProject();
const task = await queueMasterAgentTask({
projectId: "master-agent",
requestMessageId: "msg-user-1",
requestText: "boss 项目后续都按微信式交互来做,并且默认中文回复。",
executionPrompt: "prompt",
requestedBy: "17600003315",
requestedByAccount: "17600003315",
requestedBy: "krisolo",
requestedByAccount: "krisolo",
deviceId: "master-agent-openai",
});
@@ -48,9 +99,9 @@ test("主 Agent 完成对话后会自动沉淀用户偏好和项目记忆", asyn
replyBody: "boss 项目当前进度已更新:会话页会继续按微信式交互推进。",
});
const memories = await listUserMasterMemories("17600003315", { includeArchived: false });
const memories = await listUserMasterMemories("krisolo", { includeArchived: false });
const globalMemory = memories.find((memory) => memory.scope === "global");
const projectMemory = memories.find((memory) => memory.scope === "project" && memory.projectId === "boss-console");
const projectMemory = memories.find((memory) => memory.scope === "project" && memory.projectId === "boss");
assert.ok(globalMemory, "expected a global user memory");
assert.ok(projectMemory, "expected a project-scoped memory");
@@ -66,8 +117,8 @@ test("主 Agent 不会把低价值短句和瞬时安排自动写入记忆", asyn
requestMessageId: "msg-user-2",
requestText: "好的,先这样,稍后我再看。",
executionPrompt: "prompt",
requestedBy: "17600003315",
requestedByAccount: "17600003315",
requestedBy: "krisolo",
requestedByAccount: "krisolo",
deviceId: "master-agent-openai",
});
@@ -78,7 +129,7 @@ test("主 Agent 不会把低价值短句和瞬时安排自动写入记忆", asyn
replyBody: "好的,稍后继续。",
});
const memories = await listUserMasterMemories("17600003315", { includeArchived: false });
const memories = await listUserMasterMemories("krisolo", { includeArchived: false });
const noisyMemory = memories.find(
(memory) => (memory.content ?? "").includes("先这样") || (memory.content ?? "").includes("稍后继续"),
);

View File

@@ -9,6 +9,7 @@ let runtimeRoot = "";
let POST: (typeof import("../src/app/api/v1/projects/[projectId]/messages/route"))["POST"];
let saveAiAccount: (typeof import("../src/lib/boss-data"))["saveAiAccount"];
let updateProjectAgentControls: (typeof import("../src/lib/boss-data"))["updateProjectAgentControls"];
let updateAiAccountHealth: (typeof import("../src/lib/boss-data"))["updateAiAccountHealth"];
let readState: (typeof import("../src/lib/boss-data"))["readState"];
let createAuthSession: (typeof import("../src/lib/boss-data"))["createAuthSession"];
let appendProjectMessages: (typeof import("../src/lib/boss-data"))["appendProjectMessages"];
@@ -32,6 +33,7 @@ async function setup() {
POST = messageRoute.POST;
saveAiAccount = data.saveAiAccount;
updateProjectAgentControls = data.updateProjectAgentControls;
updateAiAccountHealth = data.updateAiAccountHealth;
readState = data.readState;
createAuthSession = data.createAuthSession;
appendProjectMessages = data.appendProjectMessages;
@@ -40,7 +42,7 @@ async function setup() {
async function createAuthedRequest(projectId: string, body: unknown) {
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
@@ -187,6 +189,7 @@ test("POST /api/v1/projects/master-agent/messages 快速返回队列态并在异
const mirroredReply = masterProject?.messages.at(-1);
assert.ok(mirroredReply, "expected the async reply to be written back to the master-agent ledger");
assert.match(mirroredReply?.body ?? "", /已切到异步队列回复/);
assert.equal(masterProject?.unreadCount, 1);
assert.equal(fetchCalls.length, 1);
assert.equal(fetchCalls[0]?.url, "https://api.openai.com/v1/responses");
@@ -201,6 +204,54 @@ test("POST /api/v1/projects/master-agent/messages 快速返回队列态并在异
}
});
test("POST /api/v1/projects/master-agent/messages returns browser control task metadata for browser asks", async () => {
await saveAiAccount({
accountId: "openai-master-agent-browser-control",
label: "API 容灾",
role: "api_fallback",
provider: "openai_api",
displayName: "OpenAI API Browser Control",
model: "gpt-5.4-mini",
apiKey: "sk-test-openai-browser-control",
enabled: true,
setActive: true,
loginStatusNote: "用于 browser control 测试。",
});
const response = await POST(
await createAuthedRequest("master-agent", {
body: "打开 Chrome 去后台看一下订单页",
}),
{ params: Promise.resolve({ projectId: "master-agent" }) },
);
assert.equal(response.status, 200);
const payload = (await response.json()) as {
ok: boolean;
task?: { taskId: string; taskType: string; status: string } | null;
executionMode?: string;
riskLevel?: string;
requiresConfirmation?: boolean;
};
assert.equal(payload.ok, true);
assert.equal(payload.task?.taskType, "browser_control");
assert.equal(payload.task?.status, "queued");
assert.equal(payload.executionMode, "browser");
assert.equal(payload.riskLevel, "medium");
assert.equal(payload.requiresConfirmation, false);
const state = await readState();
const task = state.masterAgentTasks.find((item) => item.taskId === payload.task?.taskId);
assert.equal(task?.taskType, "browser_control");
assert.equal(task?.intentCategory, "browser_control");
assert.equal(task?.runtimeKind, "browser-automation-runtime");
assert.equal(task?.riskLevel, "medium");
assert.equal(task?.confirmationPolicy, "light_confirm");
assert.equal(task?.requiresUserConfirmation, undefined);
});
test("POST /api/v1/projects/master-agent/messages 在快速反应模式下会对简单问题走同步快路径", async () => {
await saveAiAccount({
accountId: "openai-master-agent-fast-sync",
@@ -359,6 +410,125 @@ test("POST /api/v1/projects/master-agent/messages 对模型状态类问题会本
}
});
test("POST /api/v1/projects/master-agent/messages 本地快反问候会保持职业经理人口吻且不调用模型", async () => {
await saveAiAccount({
accountId: "master-codex-primary-local-greeting",
label: "主 GPT",
role: "primary",
provider: "master_codex_node",
displayName: "在线 Master Codex Node",
nodeId: "mac-studio",
nodeLabel: "Mac Studio",
model: "gpt-5.4",
enabled: true,
setActive: true,
loginStatusNote: "在线主节点。",
});
await updateProjectAgentControls("master-agent", {
modelOverride: "gpt-5.4-mini",
reasoningEffortOverride: "low",
fastModelOverride: "gpt-5.4-mini",
deepModelOverride: "gpt-5.4",
});
const originalFetch = globalThis.fetch;
let fetchCalled = false;
globalThis.fetch = (async () => {
fetchCalled = true;
throw new Error("model call should not happen for local greeting replies");
}) as typeof fetch;
try {
const response = await POST(
await createAuthedRequest("master-agent", {
body: "你好",
}),
{ params: Promise.resolve({ projectId: "master-agent" }) },
);
assert.equal(response.status, 200);
const payload = (await response.json()) as {
ok: boolean;
task?: { taskId: string } | null;
masterReplyState?: "queued" | "running" | "completed" | null;
masterReply?: { requestId?: string; effectiveModel?: string } | null;
replyMessage?: { body?: string } | null;
};
assert.equal(payload.ok, true);
assert.equal(payload.task ?? null, null);
assert.equal(payload.masterReplyState, "completed");
assert.equal(payload.masterReply?.requestId, "local-fast-path");
assert.equal(payload.masterReply?.effectiveModel, "gpt-5.4-mini");
assert.match(payload.replyMessage?.body ?? "", /我在/);
assert.match(payload.replyMessage?.body ?? "", /先给你结论/);
assert.match(payload.replyMessage?.body ?? "", /需要我协调线程|需要我直接推进/);
assert.doesNotMatch(payload.replyMessage?.body ?? "", /简单问题我会快速回复/);
assert.equal(fetchCalled, false);
} finally {
globalThis.fetch = originalFetch;
}
});
test("POST /api/v1/projects/master-agent/messages 英文问候也走本地职业经理人快反", async () => {
await saveAiAccount({
accountId: "master-codex-primary-local-english-greeting",
label: "主 GPT",
role: "primary",
provider: "master_codex_node",
displayName: "在线 Master Codex Node",
nodeId: "mac-studio",
nodeLabel: "Mac Studio",
model: "gpt-5.4",
enabled: true,
setActive: true,
loginStatusNote: "在线主节点。",
});
await updateProjectAgentControls("master-agent", {
modelOverride: "gpt-5.4-mini",
reasoningEffortOverride: "low",
fastModelOverride: "gpt-5.4-mini",
deepModelOverride: "gpt-5.4",
});
const originalFetch = globalThis.fetch;
let fetchCalled = false;
globalThis.fetch = (async () => {
fetchCalled = true;
throw new Error("model call should not happen for local English greeting replies");
}) as typeof fetch;
try {
const response = await POST(
await createAuthedRequest("master-agent", {
body: "hello",
}),
{ params: Promise.resolve({ projectId: "master-agent" }) },
);
assert.equal(response.status, 200);
const payload = (await response.json()) as {
ok: boolean;
task?: { taskId: string } | null;
masterReplyState?: "queued" | "running" | "completed" | null;
masterReply?: { requestId?: string; effectiveModel?: string } | null;
replyMessage?: { body?: string } | null;
};
assert.equal(payload.ok, true);
assert.equal(payload.task ?? null, null);
assert.equal(payload.masterReplyState, "completed");
assert.equal(payload.masterReply?.requestId, "local-fast-path");
assert.match(payload.replyMessage?.body ?? "", /我在/);
assert.match(payload.replyMessage?.body ?? "", /先给你结论/);
assert.equal(fetchCalled, false);
} finally {
globalThis.fetch = originalFetch;
}
});
test("POST /api/v1/projects/master-agent/messages 对可用模型查询会本地秒回并返回模式配置", async () => {
await saveAiAccount({
accountId: "hyzq-fast-local-list",
@@ -475,7 +645,7 @@ test("POST /api/v1/projects/master-agent/messages 对深度思考切换请求会
const controls = await readState().then((state) =>
state.userProjectAgentControls.find(
(entry) => entry.projectId === "master-agent" && entry.account === "17600003315",
(entry) => entry.projectId === "master-agent" && entry.account === "krisolo",
)?.controls,
);
assert.equal(controls?.modelOverride, "gpt-5.4");
@@ -898,6 +1068,57 @@ test("master-agent enqueue 在首选主节点离线时会回退到可用的备
assert.equal(task?.deviceId, "mac-studio");
});
test("master-agent enqueue 会继续使用设备在线但账号处于 degraded 的 Master Codex Node", async () => {
await saveAiAccount({
accountId: "master-codex-primary",
label: "主 GPT",
role: "primary",
provider: "master_codex_node",
displayName: "在线但历史失败的 Master Codex Node",
nodeId: "mac-studio",
nodeLabel: "Mac Studio",
model: "gpt-5.4",
enabled: true,
setActive: true,
loginStatusNote: "历史失败后等待自动恢复的主节点",
});
await updateAiAccountHealth({
accountId: "master-codex-primary",
status: "degraded",
lastError: "MASTER_CODEX_NODE_EXEC_FAILED",
lastValidatedAt: new Date().toISOString(),
});
const response = await POST(
await createAuthedRequest("master-agent", {
body: "请把当前托管任务转交给主节点执行,并在完成后回写。",
}),
{ params: Promise.resolve({ projectId: "master-agent" }) },
);
assert.equal(response.status, 200);
const payload = (await response.json()) as {
ok: boolean;
task?: { taskId: string; taskType: string; status: string } | null;
masterReplyState?: "queued" | "running" | "completed";
masterReply?: { accountId?: string } | null;
};
assert.equal(payload.ok, true);
assert.equal(payload.masterReplyState, "queued");
assert.equal(payload.masterReply?.accountId, "master-codex-primary");
assert.equal(payload.task?.taskType, "conversation_reply");
const state = await readState();
const task = state.masterAgentTasks.find((item) => item.taskId === payload.task?.taskId);
assert.equal(task?.accountId, "master-codex-primary");
assert.equal(task?.deviceId, "mac-studio");
const account = state.aiAccounts.find((item) => item.accountId === "master-codex-primary");
assert.equal(account?.status, "ready");
const masterProject = state.projects.find((project) => project.id === "master-agent");
assert.doesNotMatch(masterProject?.messages.at(-1)?.body ?? "", /当前没有可用的 master 节点账号/);
});
test("master-agent enqueue 会在首个 API 候选失败后切到下一条备用链并重写任务账号", async () => {
await saveAiAccount({
accountId: "openai-primary-queue",

View File

@@ -101,7 +101,7 @@ test("replyToMasterAgentUserMessage falls back to a runnable OpenAI API account
requestMessageId: "msg-master-fallback",
requestText: "请只回复主Agent链路正常。",
requestedBy: "Boss 超级管理员",
requestedByAccount: "17600003315",
requestedByAccount: "krisolo",
});
assert.equal(result.ok, true);
@@ -179,7 +179,7 @@ test("replyToMasterAgentUserMessage can retry the same degraded API account when
requestMessageId: "msg-openai-degraded-retry",
requestText: "请只回复:仍然可以重试同一个 API 账号。",
requestedBy: "Boss 超级管理员",
requestedByAccount: "17600003315",
requestedByAccount: "krisolo",
});
assert.equal(result.ok, true);
@@ -242,7 +242,7 @@ test("replyToMasterAgentUserMessage falls back to a runnable aliyun qwen backup
requestMessageId: "msg-master-aliyun-fallback",
requestText: "请只回复:阿里备用链路正常。",
requestedBy: "Boss 超级管理员",
requestedByAccount: "17600003315",
requestedByAccount: "krisolo",
});
assert.equal(result.ok, true);
@@ -314,7 +314,7 @@ test("replyToMasterAgentUserMessage retries the next ready API backup when the f
requestMessageId: "msg-master-api-chain",
requestText: "请只回复:阿里备用接管成功。",
requestedBy: "Boss 超级管理员",
requestedByAccount: "17600003315",
requestedByAccount: "krisolo",
});
assert.equal(result.ok, true);
@@ -375,7 +375,7 @@ test("replyToMasterAgentUserMessage 在快速反应模式遇到复杂请求时
requestMessageId: "msg-master-smart-upgrade",
requestText: "请深入分析当前主 Agent 架构,并给出分阶段实现方案、风险和回归测试建议。",
requestedBy: "Boss 超级管理员",
requestedByAccount: "17600003315",
requestedByAccount: "krisolo",
mode: "smart",
});
@@ -436,7 +436,7 @@ test("replyToMasterAgentUserMessage falls back to a ready backup master node acc
requestMessageId: "msg-master-node-backup-fallback",
requestText: "请切到备用主节点。",
requestedBy: "Boss 超级管理员",
requestedByAccount: "17600003315",
requestedByAccount: "krisolo",
mode: "enqueue",
});

View File

@@ -43,7 +43,7 @@ async function setup() {
promptProfileRoute = loadedPromptProfileRoute;
}
async function createAuthedRequest(account = "17600003315", role: "member" | "admin" | "highest_admin" = "highest_admin") {
async function createAuthedRequest(account = "krisolo", role: "member" | "admin" | "highest_admin" = "highest_admin") {
await setup();
const session = await createAuthSession({
account,
@@ -146,7 +146,7 @@ test("master-agent 记忆页会返回当前用户所有项目记忆", async () =
headers: adminRequest.headers,
body: JSON.stringify({
scope: "project",
projectId: "boss-console",
projectId: "boss",
title: "Boss 进度",
content: "Boss 项目聊天主链已接通。",
memoryType: "project_progress",
@@ -183,7 +183,7 @@ test("master-agent 记忆页会返回当前用户所有项目记忆", async () =
assert.equal(payload.ok, true);
assert.deepEqual(
payload.memories.project.map((memory) => memory.projectId).sort(),
["boss-console", "master-agent", "wenshenapp"].sort(),
["boss", "master-agent", "wenshenapp"].sort(),
);
});

View File

@@ -45,12 +45,12 @@ test("主 Agent 提示词与用户记忆可读写", async () => {
await updateMasterAgentPromptPolicy({
globalPrompt: "全局主提示词",
updatedBy: "17600003315",
updatedBy: "krisolo",
});
await updateUserMasterPrompt("17600003315", "用户私有主提示词");
await updateUserMasterPrompt("krisolo", "用户私有主提示词");
const created = await createUserMasterMemory({
account: "17600003315",
account: "krisolo",
scope: "project",
projectId: "master-agent",
title: "项目进度",
@@ -59,14 +59,14 @@ test("主 Agent 提示词与用户记忆可读写", async () => {
tags: ["聊天", "主链"],
});
await updateUserMasterMemory(created.memoryId, "17600003315", {
await updateUserMasterMemory(created.memoryId, "krisolo", {
content: "当前主链优先打通主 Agent 聊天闭环。",
tags: ["聊天", "主Agent"],
});
const policy = await getMasterAgentPromptPolicy();
const userPrompt = await getUserMasterPrompt("17600003315");
const memories = await listUserMasterMemories("17600003315", {
const userPrompt = await getUserMasterPrompt("krisolo");
const memories = await listUserMasterMemories("krisolo", {
includeArchived: false,
});
@@ -76,9 +76,9 @@ test("主 Agent 提示词与用户记忆可读写", async () => {
assert.equal(memories[0]?.content, "当前主链优先打通主 Agent 聊天闭环。");
assert.deepEqual(memories[0]?.tags, ["聊天", "主Agent"]);
await archiveUserMasterMemory(created.memoryId, "17600003315");
const visible = await listUserMasterMemories("17600003315", { includeArchived: false });
const all = await listUserMasterMemories("17600003315", { includeArchived: true });
await archiveUserMasterMemory(created.memoryId, "krisolo");
const visible = await listUserMasterMemories("krisolo", { includeArchived: false });
const all = await listUserMasterMemories("krisolo", { includeArchived: true });
const archived = all.find((item) => item.memoryId === created.memoryId);
assert.equal(visible.length, 0);

View File

@@ -56,7 +56,7 @@ test("主 Agent 执行 prompt 命中线程时只读取相关状态文档和最
label: "主 GPT",
role: "primary",
provider: "master_codex_node",
displayName: "17600003315 · Master Codex Node",
displayName: "krisolo · Master Codex Node",
nodeId: "mac-studio",
nodeLabel: "Mac Studio",
enabled: true,
@@ -66,14 +66,14 @@ test("主 Agent 执行 prompt 命中线程时只读取相关状态文档和最
});
await updateMasterAgentPromptPolicy({
globalPrompt: "管理员全局主提示词",
updatedBy: "17600003315",
updatedBy: "krisolo",
});
await updateUserMasterPrompt("17600003315", "用户私有主提示词");
await updateUserMasterPrompt("krisolo", "用户私有主提示词");
await updateProjectAgentControls("master-agent", {
promptOverride: "当前对话提示词",
});
await createUserMasterMemory({
account: "17600003315",
account: "krisolo",
scope: "project",
projectId: "master-agent",
title: "项目记忆",
@@ -190,7 +190,7 @@ test("主 Agent 执行 prompt 命中线程时只读取相关状态文档和最
const resolved = await resolveMasterAgentExecutionConfig(
"master-agent",
"17600003315",
"krisolo",
"审计对话,请继续推进线程状态同步",
);
assert.ok(resolved.projectMemories.length > 0);
@@ -208,7 +208,7 @@ test("主 Agent 执行 prompt 命中线程时只读取相关状态文档和最
const reply = await replyToMasterAgentUserMessage({
requestText: "审计对话,请继续推进线程状态同步",
requestedBy: "Boss 超级管理员",
requestedByAccount: "17600003315",
requestedByAccount: "krisolo",
mode: "enqueue",
});
assert.equal(reply.ok, true);
@@ -332,7 +332,7 @@ test("主 Agent 执行 prompt 在未命中时退回最近活跃项目,且不
const reply = await replyToMasterAgentUserMessage({
requestText: "请继续推进线程状态同步(仅深拉兜底)",
requestedBy: "Boss 超级管理员",
requestedByAccount: "17600003315",
requestedByAccount: "krisolo",
mode: "enqueue",
});
assert.equal(reply.ok, true);
@@ -369,7 +369,7 @@ test("主 Agent 执行 prompt 在没有线程状态文档和进展事件时才
const reply = await replyToMasterAgentUserMessage({
requestText: "请继续推进线程状态同步",
requestedBy: "Boss 超级管理员",
requestedByAccount: "17600003315",
requestedByAccount: "krisolo",
mode: "enqueue",
});
assert.equal(reply.ok, true);
@@ -382,6 +382,149 @@ test("主 Agent 执行 prompt 在没有线程状态文档和进展事件时才
assert.ok(queuedTask?.executionPrompt.includes("深拉兜底目标"));
});
test("非主会话里 @主Agent 时,运行时摘要只允许读取当前项目上下文,不允许串到最近活跃项目", async () => {
await setup();
await saveAiAccount({
accountId: "master-codex-primary-thread-scope",
label: "主 GPT",
role: "primary",
provider: "master_codex_node",
displayName: "krisolo · Master Codex Node",
nodeId: "mac-studio",
nodeLabel: "Mac Studio",
enabled: true,
setActive: true,
status: "ready",
loginStatusNote: "主节点可用。",
});
const state = await readState();
const auditProject = state.projects.find((project) => project.id === "audit-collab");
assert.ok(auditProject, "expected seeded audit-collab project");
state.projects.push({
id: "aitoukui-thread",
name: "AItoukui",
pinned: false,
deviceIds: ["mac-studio"],
preview: "等待同步",
updatedAt: "2026-04-05T11:59:00+08:00",
lastMessageAt: "2026-04-05T11:59:00+08:00",
isGroup: false,
threadMeta: {
projectId: "aitoukui-thread",
threadId: "thread-aitoukui-main",
threadDisplayName: "AItoukui 主线程",
folderName: "AItoukui",
activityIconCount: 1,
updatedAt: "2026-04-05T11:59:00+08:00",
lastObservedCodexActivityAt: "2026-04-05T11:59:00+08:00",
codexThreadRef: "thread-aitoukui-main",
codexFolderRef: "aitoukui",
},
groupMembers: [],
createdByAgent: true,
collaborationMode: "development",
approvalState: "not_required",
unreadCount: 0,
riskLevel: "low",
messages: [],
goals: [],
versions: [],
});
state.threadStatusDocuments = [
{
documentId: "thread-status-doc-current-project",
projectId: "audit-collab",
threadId: "thread-audit-chief",
threadDisplayName: "审计对话",
folderName: "审计群聊",
deviceId: "mac-studio",
projectGoal: "当前项目目标",
currentPhase: "当前项目阶段",
currentProgress: "当前项目进度",
technicalArchitecture: "当前项目架构",
currentBlockers: "当前项目阻塞",
recommendedNextStep: "当前项目下一步",
keyFiles: ["src/lib/current-thread.ts"],
keyCommands: ["npm run lint"],
updatedAt: "2026-04-05T10:00:00+08:00",
sourceTaskId: "task-current-project",
sourceKind: "incremental_sync",
},
{
documentId: "thread-status-doc-other-project",
projectId: "aitoukui-thread",
threadId: "thread-aitoukui-main",
threadDisplayName: "AItoukui 主线程",
folderName: "AItoukui",
deviceId: "mac-studio",
projectGoal: "别的项目目标",
currentPhase: "别的项目阶段",
currentProgress: "别的项目进度",
technicalArchitecture: "别的项目架构",
currentBlockers: "别的项目阻塞",
recommendedNextStep: "别的项目下一步",
keyFiles: ["src/lib/aitoukui-thread.ts"],
keyCommands: ["npm run build"],
updatedAt: "2026-04-05T12:00:00+08:00",
sourceTaskId: "task-other-project",
sourceKind: "incremental_sync",
},
];
state.threadProgressEvents = [
{
eventId: "thread-progress-current-project",
projectId: "audit-collab",
threadId: "thread-audit-chief",
threadDisplayName: "审计对话",
deviceId: "mac-studio",
eventType: "progress_updated",
summary: "当前项目进展摘要",
phase: "当前项目阶段",
blockerDelta: "当前项目阻塞变化",
nextStepDelta: "当前项目下一步变化",
createdAt: "2026-04-05T10:02:00+08:00",
sourceTaskId: "task-current-progress",
},
{
eventId: "thread-progress-other-project",
projectId: "aitoukui-thread",
threadId: "thread-aitoukui-main",
threadDisplayName: "AItoukui 主线程",
deviceId: "mac-studio",
eventType: "progress_updated",
summary: "别的项目进展摘要",
phase: "别的项目阶段",
blockerDelta: "别的项目阻塞变化",
nextStepDelta: "别的项目下一步变化",
createdAt: "2026-04-05T12:01:00+08:00",
sourceTaskId: "task-other-progress",
},
];
await writeState(state);
const reply = await replyToMasterAgentUserMessage({
requestText: "请你看一下当前项目下一步怎么推进",
requestedBy: "Boss 超级管理员",
requestedByAccount: "krisolo",
projectId: "audit-collab",
mode: "enqueue",
});
assert.equal(reply.ok, true);
const queuedTask = (await readState()).masterAgentTasks.find(
(task) =>
task.projectId === "audit-collab" &&
task.requestText === "请你看一下当前项目下一步怎么推进",
);
assert.ok(queuedTask, "expected a current-thread master-agent task to be queued");
assert.ok(queuedTask?.executionPrompt.includes("当前项目目标"));
assert.ok(queuedTask?.executionPrompt.includes("当前项目进展摘要"));
assert.ok(!queuedTask?.executionPrompt.includes("别的项目目标"));
assert.ok(!queuedTask?.executionPrompt.includes("别的项目进展摘要"));
});
test("项目理解同步 prompt 强制线程先基于本地文档和代码汇总,并允许回写版本记录摘要", async () => {
await setup();
@@ -465,3 +608,183 @@ test("项目理解同步 prompt 强制线程先基于本地文档和代码汇总
);
assert.equal(refreshedProject!.versions[0]?.summary, "项目理解同步已改成先读本地文档和代码,再回写结构化摘要。");
});
test("主 Agent 总结项目目标和版本记录时不注入 OTA 与设备运行态噪音", async () => {
await setup();
await saveAiAccount({
accountId: "master-codex-primary",
label: "主 GPT",
role: "primary",
provider: "master_codex_node",
displayName: "krisolo · Master Codex Node",
nodeId: "mac-studio",
nodeLabel: "Mac Studio",
enabled: true,
setActive: true,
status: "ready",
loginStatusNote: "主节点可用。",
});
const state = await readState();
const auditProject = state.projects.find((project) => project.id === "audit-collab");
assert.ok(auditProject, "expected seeded audit-collab project");
auditProject!.projectUnderstanding = {
projectGoal: "审计线程目标",
currentProgress: "正在补齐版本记录摘要",
technicalArchitecture: "Next.js 控制面 + Android 原生客户端 + local-agent",
currentBlockers: "",
recommendedNextStep: "继续同步版本记录入口",
sourceTaskId: "task-audit-summary",
updatedAt: "2026-04-05T10:00:00+08:00",
sourceKind: "thread_sync",
};
state.threadStatusDocuments = [
{
documentId: "thread-status-doc-audit-summary",
projectId: "audit-collab",
threadId: "thread-audit-chief",
threadDisplayName: "审计对话",
folderName: "审计群聊",
deviceId: "mac-studio",
projectGoal: "审计线程目标",
currentPhase: "整理项目目标与版本记录",
currentProgress: "版本记录入口正在统一",
technicalArchitecture: "Next.js 控制面 + Android 原生客户端 + local-agent",
currentBlockers: "",
recommendedNextStep: "继续回填版本记录摘要",
keyFiles: ["src/lib/boss-master-agent.ts"],
keyCommands: ["npm run test"],
updatedAt: "2026-04-05T10:01:00+08:00",
sourceTaskId: "task-audit-summary-doc",
sourceKind: "incremental_sync",
},
];
state.threadProgressEvents = [
{
eventId: "thread-progress-event-audit-summary",
projectId: "audit-collab",
threadId: "thread-audit-chief",
threadDisplayName: "审计对话",
deviceId: "mac-studio",
eventType: "progress_updated",
summary: "项目目标和版本记录已开始重新汇总",
phase: "整理项目目标与版本记录",
blockerDelta: "",
nextStepDelta: "同步到会话顶部入口",
createdAt: "2026-04-05T10:02:00+08:00",
sourceTaskId: "task-audit-summary-event",
},
];
state.appLogs = [
{
logId: "app-log-summary-noise",
deviceId: "mac-studio",
category: "sync",
message: "这条日志不应该出现在项目目标总结 prompt 里",
createdAt: "2026-04-05T10:03:00+08:00",
},
];
state.otaUpdates = [
{
releaseId: "ota-summary-noise",
version: "v2.1.0",
currentVersion: "v2.0.0",
channel: "stable",
packageType: "android_shell",
status: "available",
summary: ["这条 OTA 不应该污染项目总结"],
targetScope: "Boss Android 原生客户端",
requiredRole: "highest_admin",
publishedAt: "2026-04-05T10:04:00+08:00",
},
];
await writeState(state);
const requestText = "请总结审计对话当前的项目目标和版本记录";
const reply = await replyToMasterAgentUserMessage({
requestText,
requestedBy: "Boss 超级管理员",
requestedByAccount: "krisolo",
mode: "enqueue",
});
assert.equal(reply.ok, true);
const queuedTask = (await readState()).masterAgentTasks.find(
(task) => task.projectId === "master-agent" && task.requestText === requestText,
);
assert.ok(queuedTask, "expected summary task to be queued");
assert.ok(queuedTask?.executionPrompt.includes("线程状态文档:"));
assert.ok(queuedTask?.executionPrompt.includes("审计线程目标"));
assert.ok(!queuedTask?.executionPrompt.includes("最新 APP 日志:"));
assert.ok(!queuedTask?.executionPrompt.includes("高风险线程:"));
assert.ok(!queuedTask?.executionPrompt.includes("在线设备:"));
assert.ok(!queuedTask?.executionPrompt.includes("认证状态:"));
assert.ok(!queuedTask?.executionPrompt.includes("可用 OTA"));
assert.ok(queuedTask?.executionPrompt.includes("回复风格:像专业职业经理人"));
assert.ok(queuedTask?.executionPrompt.includes("先给结论,再给推进动作"));
assert.ok(queuedTask?.executionPrompt.includes("不要堆背景,不要重复系统状态"));
});
test("主 Agent 处理 OTA 和设备运行态问题时保留相关运行时摘要", async () => {
await setup();
await saveAiAccount({
accountId: "master-codex-primary",
label: "主 GPT",
role: "primary",
provider: "master_codex_node",
displayName: "krisolo · Master Codex Node",
nodeId: "mac-studio",
nodeLabel: "Mac Studio",
enabled: true,
setActive: true,
status: "ready",
loginStatusNote: "主节点可用。",
});
const state = await readState();
state.appLogs = [
{
logId: "app-log-runtime-detail",
deviceId: "mac-studio",
category: "ota",
message: "检测到 Android OTA 可更新",
createdAt: "2026-04-05T11:00:00+08:00",
},
];
state.otaUpdates = [
{
releaseId: "ota-runtime-detail",
version: "v2.2.0",
currentVersion: "v2.1.0",
channel: "stable",
packageType: "android_shell",
status: "available",
summary: ["加入新的设备运行态提示"],
targetScope: "Boss Android 原生客户端",
requiredRole: "highest_admin",
publishedAt: "2026-04-05T11:01:00+08:00",
},
];
await writeState(state);
const requestText = "帮我看一下当前可用 OTA、在线设备状态和最近 APP 日志";
const reply = await replyToMasterAgentUserMessage({
requestText,
requestedBy: "Boss 超级管理员",
requestedByAccount: "krisolo",
mode: "enqueue",
});
assert.equal(reply.ok, true);
const queuedTask = (await readState()).masterAgentTasks.find(
(task) => task.projectId === "master-agent" && task.requestText === requestText,
);
assert.ok(queuedTask, "expected runtime-detail task to be queued");
assert.ok(queuedTask?.executionPrompt.includes("最新 APP 日志:"));
assert.ok(queuedTask?.executionPrompt.includes("在线设备:"));
assert.ok(queuedTask?.executionPrompt.includes("认证状态:"));
assert.ok(queuedTask?.executionPrompt.includes("可用 OTA"));
assert.ok(queuedTask?.executionPrompt.includes("v2.2.0 -> Boss Android 原生客户端"));
});

View File

@@ -11,6 +11,7 @@ let toggleGoal: (typeof import("../src/lib/boss-data"))["toggleGoal"];
let updateGoalText: (typeof import("../src/lib/boss-data"))["updateGoalText"];
let createGoal: (typeof import("../src/lib/boss-data"))["createGoal"];
let updateProjectAgentControls: (typeof import("../src/lib/boss-data"))["updateProjectAgentControls"];
let queueMasterAgentTask: (typeof import("../src/lib/boss-data"))["queueMasterAgentTask"];
let completeMasterAgentTask: (typeof import("../src/lib/boss-data"))["completeMasterAgentTask"];
let forceProjectUnderstandingSyncTask: (typeof import("../src/lib/boss-data"))["forceProjectUnderstandingSyncTask"];
let subscribeBossEvents: (typeof import("../src/lib/boss-events"))["subscribeBossEvents"];
@@ -32,6 +33,7 @@ async function setup() {
updateGoalText = data.updateGoalText;
createGoal = data.createGoal;
updateProjectAgentControls = data.updateProjectAgentControls;
queueMasterAgentTask = data.queueMasterAgentTask;
completeMasterAgentTask = data.completeMasterAgentTask;
forceProjectUnderstandingSyncTask = data.forceProjectUnderstandingSyncTask;
subscribeBossEvents = events.subscribeBossEvents;
@@ -52,6 +54,7 @@ async function resetGoalState() {
},
];
project.versions = [];
project.projectUnderstanding = undefined;
project.messages = [];
project.lastMessageAt = "2026-04-07T10:00:00.000Z";
state.projects = state.projects.filter((item) => item.id !== "project-goal-events");
@@ -170,3 +173,135 @@ test("project understanding sync completion also publishes project goal refresh
);
assert.ok(goalRefreshEvent, "expected project understanding sync to publish a goal refresh marker");
});
test("takeover conversation summary reply writes project goal and version record back to the current conversation", async () => {
const events: Array<{ event: string; payload: { projectId?: string; note?: string } }> = [];
const unsubscribe = subscribeBossEvents((event, payload) => {
events.push({ event, payload });
});
const task = await queueMasterAgentTask({
projectId: "project-goal-events",
requestMessageId: "message-summary-request",
requestText: "请汇总当前项目目标和版本记录,并同步到当前对话顶部入口",
executionPrompt: "请汇总当前项目目标和版本记录",
requestedBy: "Boss 超级管理员",
requestedByAccount: "krisolo",
deviceId: "mac-studio",
accountLabel: "主 GPT",
relayViaMasterAgent: true,
});
await completeMasterAgentTask({
taskId: task.taskId,
deviceId: "mac-studio",
status: "completed",
replyBody: [
"项目目标:完成主 Agent 汇总内容自动回写到当前对话。",
"当前进度:已确认普通接管回复需要同步项目摘要。",
"技术架构Boss 文件账本保存项目理解Android 对话页通过实时事件刷新。",
"当前阻塞:无。",
"建议下一步:继续做真机回归。",
"版本记录:新增主 Agent 接管汇总回复自动写入项目目标和版本记录。",
].join("\n"),
});
unsubscribe();
const refreshedProject = (await readState()).projects.find((project) => project.id === "project-goal-events");
assert.ok(refreshedProject, "expected project to exist");
assert.equal(
refreshedProject!.projectUnderstanding?.projectGoal,
"完成主 Agent 汇总内容自动回写到当前对话。",
);
assert.equal(
refreshedProject!.projectUnderstanding?.currentProgress,
"已确认普通接管回复需要同步项目摘要。",
);
assert.equal(
refreshedProject!.versions[0]?.summary,
"新增主 Agent 接管汇总回复自动写入项目目标和版本记录。",
);
assert.ok(
events.some(
(item) =>
item.event === "conversation.updated" &&
item.payload.projectId === "project-goal-events" &&
item.payload.note === "project_goals.updated",
),
"expected project goal refresh marker",
);
assert.ok(
events.some(
(item) =>
item.event === "conversation.updated" &&
item.payload.projectId === "project-goal-events" &&
item.payload.note === "project_versions.updated",
),
"expected project version refresh marker",
);
});
test("takeover conversation summary wording writes project goal and version record back to the current conversation", async () => {
const events: Array<{ event: string; payload: { projectId?: string; note?: string } }> = [];
const unsubscribe = subscribeBossEvents((event, payload) => {
events.push({ event, payload });
});
const state = await readState();
const project = state.projects.find((item) => item.id === "project-goal-events");
assert.ok(project, "expected project to exist");
project!.projectUnderstanding = undefined;
project!.versions = [];
await writeState(state);
const task = await queueMasterAgentTask({
projectId: "project-goal-events",
requestMessageId: "message-summary-wording-request",
requestText: "请总结当前项目目标和版本记录",
executionPrompt: "请总结当前项目目标和版本记录",
requestedBy: "Boss 超级管理员",
requestedByAccount: "krisolo",
deviceId: "mac-studio",
accountLabel: "主 GPT",
relayViaMasterAgent: true,
});
await completeMasterAgentTask({
taskId: task.taskId,
deviceId: "mac-studio",
status: "completed",
replyBody: [
"项目目标:稳定总结类请求的项目目标回写。",
"当前进度:已进入总结语义回归。",
"技术架构Boss 文件账本保存项目理解。",
"当前阻塞:无。",
"建议下一步:继续跑真机回归。",
"版本记录:总结类请求也能写入版本记录。",
].join("\n"),
});
unsubscribe();
const refreshedProject = (await readState()).projects.find((item) => item.id === "project-goal-events");
assert.equal(refreshedProject!.projectUnderstanding?.projectGoal, "稳定总结类请求的项目目标回写。");
assert.equal(refreshedProject!.versions[0]?.summary, "总结类请求也能写入版本记录。");
assert.ok(
events.some(
(item) =>
item.event === "conversation.updated" &&
item.payload.projectId === "project-goal-events" &&
item.payload.note === "project_goals.updated",
),
"expected project goal refresh marker for summary wording",
);
assert.ok(
events.some(
(item) =>
item.event === "conversation.updated" &&
item.payload.projectId === "project-goal-events" &&
item.payload.note === "project_versions.updated",
),
"expected project version refresh marker for summary wording",
);
});

View File

@@ -0,0 +1,154 @@
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";
import { NextRequest } from "next/server";
let runtimeRoot = "";
let readState: (typeof import("../src/lib/boss-data"))["readState"];
let writeState: (typeof import("../src/lib/boss-data"))["writeState"];
let deleteProjectMessage: (typeof import("../src/lib/boss-data"))["deleteProjectMessage"];
let createAuthSession: (typeof import("../src/lib/boss-data"))["createAuthSession"];
let deleteMessageRoute: (typeof import("../src/app/api/v1/projects/[projectId]/messages/route"))["DELETE"];
let subscribeBossEvents: (typeof import("../src/lib/boss-events"))["subscribeBossEvents"];
let AUTH_SESSION_COOKIE = "";
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-project-message-delete-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [data, route, auth, events] = await Promise.all([
import("../src/lib/boss-data.ts"),
import("../src/app/api/v1/projects/[projectId]/messages/route.ts"),
import("../src/lib/boss-auth.ts"),
import("../src/lib/boss-events.ts"),
]);
readState = data.readState;
writeState = data.writeState;
deleteProjectMessage = data.deleteProjectMessage;
createAuthSession = data.createAuthSession;
deleteMessageRoute = route.DELETE;
subscribeBossEvents = events.subscribeBossEvents;
AUTH_SESSION_COOKIE = auth.AUTH_SESSION_COOKIE;
}
async function seedProject() {
const state = await readState();
const baseProject = state.projects.find((project) => project.id !== "master-agent") ?? state.projects[0];
const project = structuredClone(baseProject);
project.id = "message-delete-project";
project.name = "消息删除测试";
project.preview = "第二条回复";
project.lastMessageAt = "2026-04-26T10:05:00.000Z";
project.unreadCount = 2;
project.messages = [
{
id: "msg-1",
sender: "user",
senderLabel: "你",
body: "第一条请求",
sentAt: "2026-04-26T10:00:00.000Z",
kind: "text",
},
{
id: "msg-process",
sender: "device",
senderLabel: "线程",
body: "我先检查代码。",
sentAt: "2026-04-26T10:03:00.000Z",
kind: "thread_process",
},
{
id: "msg-2",
sender: "master",
senderLabel: "主 Agent",
body: "第二条回复",
sentAt: "2026-04-26T10:05:00.000Z",
kind: "text",
},
];
state.projects = state.projects.filter((item) => item.id !== project.id);
state.projects.unshift(project);
await writeState(state);
}
test.beforeEach(async () => {
await setup();
await seedProject();
});
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
}
});
test("deleteProjectMessage removes a message and refreshes project preview events", async () => {
const events: Array<{ event: string; payload: { projectId?: string } }> = [];
const unsubscribe = subscribeBossEvents((event, payload) => {
events.push({ event, payload });
});
const result = await deleteProjectMessage({
projectId: "message-delete-project",
messageId: "msg-2",
});
unsubscribe();
assert.equal(result.deletedMessage.id, "msg-2");
assert.equal(result.projectId, "message-delete-project");
const project = (await readState()).projects.find((item) => item.id === "message-delete-project");
assert.ok(project);
assert.deepEqual(project!.messages.map((message) => message.id), ["msg-1", "msg-process"]);
assert.equal(project!.preview, "第一条请求");
assert.equal(project!.lastMessageAt, "2026-04-26T10:03:00.000Z");
assert.ok(
events.some(
(item) =>
item.event === "project.messages.updated" &&
item.payload.projectId === "message-delete-project",
),
);
assert.ok(
events.some(
(item) =>
item.event === "conversation.updated" &&
item.payload.projectId === "message-delete-project",
),
);
});
test("DELETE /api/v1/projects/[projectId]/messages deletes the requested message", async () => {
const session = await createAuthSession({
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
});
const request = new NextRequest(
"http://127.0.0.1:3000/api/v1/projects/message-delete-project/messages?messageId=msg-2",
{
method: "DELETE",
headers: {
cookie: `${AUTH_SESSION_COOKIE}=${session.sessionToken}`,
},
},
);
const response = await deleteMessageRoute(request, {
params: Promise.resolve({ projectId: "message-delete-project" }),
});
const payload = (await response.json()) as {
ok: boolean;
deletedMessage?: { id: string };
};
assert.equal(response.status, 200);
assert.equal(payload.ok, true);
assert.equal(payload.deletedMessage?.id, "msg-2");
});

View File

@@ -0,0 +1,20 @@
import assert from "node:assert/strict";
import test from "node:test";
test("message response payload shape supports execution mode metadata", () => {
const payload: {
ok: boolean;
executionMode?: "discussion" | "thread" | "development" | "browser" | "desktop";
riskLevel?: "low" | "medium" | "high";
requiresConfirmation?: boolean;
} = {
ok: true,
executionMode: "browser",
riskLevel: "medium",
requiresConfirmation: false,
};
assert.equal(payload.executionMode, "browser");
assert.equal(payload.riskLevel, "medium");
assert.equal(payload.requiresConfirmation, false);
});

View File

@@ -88,7 +88,7 @@ function buildSingleThreadProject(projectId: string) {
async function createAuthedRequest(projectId: string) {
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
@@ -113,7 +113,7 @@ test("GET /api/v1/projects/[projectId]/messages returns a lightweight chat paylo
id: "device-message-lite",
name: "Mac Studio",
avatar: "M",
account: "17600003315",
account: "krisolo",
source: "production",
status: "online",
projects: [project.id],

View File

@@ -33,7 +33,7 @@ async function setup() {
async function createAuthedHeaders() {
await setup();
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",

View File

@@ -0,0 +1,261 @@
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";
import { NextRequest } from "next/server";
let runtimeRoot = "";
let data: typeof import("../src/lib/boss-data");
let authCookie = "";
let getAdminAccess: (typeof import("../src/app/api/v1/admin/access/route"))["GET"];
let postAdminAccess: (typeof import("../src/app/api/v1/admin/access/route"))["POST"];
let baseState: Awaited<ReturnType<typeof import("../src/lib/boss-data")["readState"]>>;
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-rbac-admin-access-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [dataModule, authModule, routeModule] = await Promise.all([
import("../src/lib/boss-data.ts"),
import("../src/lib/boss-auth.ts"),
import("../src/app/api/v1/admin/access/route.ts"),
]);
data = dataModule;
authCookie = authModule.AUTH_SESSION_COOKIE;
getAdminAccess = routeModule.GET;
postAdminAccess = routeModule.POST;
baseState = structuredClone(await data.readState());
}
test.after(async () => {
if (runtimeRoot) await rm(runtimeRoot, { recursive: true, force: true });
});
test.beforeEach(async () => {
await setup();
const state = structuredClone(baseState);
state.accountDeviceGrants = [];
state.accountProjectGrants = [];
state.accountSkillGrants = [];
state.permissionAuditLogs = [];
if (!state.devices.some((device) => device.id === "win-gpu-01")) {
state.devices.push({
id: "win-gpu-01",
name: "Windows GPU",
avatar: "W",
account: "gpu@example.com",
source: "production",
status: "online",
projects: [],
quota5h: 0,
quota7d: 0,
lastSeenAt: "2026-04-26T12:00:00+08:00",
preferredExecutionMode: "cli",
});
}
state.deviceSkills = [
{
skillId: "mac-studio:boss-server-debug",
deviceId: "mac-studio",
name: "boss-server-debug",
description: "服务器调试",
path: "/Users/kris/.codex/skills/boss-server-debug/SKILL.md",
invocation: "$boss-server-debug",
category: "Mac Studio",
updatedAt: "2026-04-26T12:00:00+08:00",
},
{
skillId: "win-gpu-01:boss-server-debug",
deviceId: "win-gpu-01",
name: "boss-server-debug",
description: "Windows 服务器调试",
path: "C:/Users/kris/.codex/skills/boss-server-debug/SKILL.md",
invocation: "$boss-server-debug",
category: "Windows GPU",
updatedAt: "2026-04-26T12:01:00+08:00",
},
];
await data.writeState(state);
});
async function authedRequest(
account: string,
role: "member" | "admin" | "highest_admin",
url: string,
init: RequestInit = {},
) {
const session = await data.createAuthSession({
account,
role,
displayName: account,
loginMethod: "password",
});
return new NextRequest(url, {
...init,
headers: {
...(init.headers ?? {}),
cookie: `${authCookie}=${session.sessionToken}`,
},
});
}
async function adminPost(body: Record<string, unknown>) {
return postAdminAccess(
await authedRequest("krisolo", "highest_admin", "http://127.0.0.1:3000/api/v1/admin/access", {
method: "POST",
body: JSON.stringify(body),
}),
);
}
test("member cannot read or mutate access management", async () => {
const getResponse = await getAdminAccess(
await authedRequest("worker@example.com", "member", "http://127.0.0.1:3000/api/v1/admin/access"),
);
assert.equal(getResponse.status, 403);
const postResponse = await postAdminAccess(
await authedRequest("worker@example.com", "member", "http://127.0.0.1:3000/api/v1/admin/access", {
method: "POST",
body: JSON.stringify({ action: "grant_device" }),
}),
);
assert.equal(postResponse.status, 403);
});
test("highest admin can create a member and grant scoped device project and skill access", async () => {
const accountResponse = await adminPost({
action: "upsert_account",
account: "worker@example.com",
displayName: "Worker",
role: "member",
password: "worker-pass",
});
assert.equal(accountResponse.status, 200);
const accountPayload = await accountResponse.json();
assert.equal(accountPayload.account.account, "worker@example.com");
assert.equal(accountPayload.account.passwordHash, undefined);
const deviceResponse = await adminPost({
action: "grant_device",
account: "worker@example.com",
deviceId: "mac-studio",
permissions: ["device.view"],
note: "Mac 只读",
});
assert.equal(deviceResponse.status, 200);
const devicePayload = await deviceResponse.json();
assert.equal(devicePayload.grant.deviceId, "mac-studio");
const projectResponse = await adminPost({
action: "grant_project",
account: "worker@example.com",
projectId: "master-agent",
permissions: ["project.view", "master_agent.ask"],
});
assert.equal(projectResponse.status, 200);
const skillResponse = await adminPost({
action: "grant_skill",
account: "worker@example.com",
skillId: "mac-studio:boss-server-debug",
deviceId: "mac-studio",
permissions: ["skill.view", "skill.use"],
});
assert.equal(skillResponse.status, 200);
const state = await data.readState();
assert.equal(state.accountDeviceGrants.length, 1);
assert.equal(state.accountProjectGrants.length, 1);
assert.equal(state.accountSkillGrants.length, 1);
assert.equal(state.permissionAuditLogs.length, 4);
const getResponse = await getAdminAccess(
await authedRequest("krisolo", "highest_admin", "http://127.0.0.1:3000/api/v1/admin/access"),
);
assert.equal(getResponse.status, 200);
const getPayload = await getResponse.json();
assert.equal(
getPayload.accounts.some((account: { passwordHash?: string }) => Boolean(account.passwordHash)),
false,
);
assert.equal(getPayload.grants.devices.length, 1);
assert.equal(getPayload.grants.projects.length, 1);
assert.equal(getPayload.grants.skills.length, 1);
const bossServerDebugCatalog = getPayload.skillCatalog.find(
(item: { name: string }) => item.name === "boss-server-debug",
);
assert.equal(bossServerDebugCatalog.deviceCount, 2);
assert.deepEqual(
bossServerDebugCatalog.devices.map((device: { deviceId: string }) => device.deviceId).sort(),
["mac-studio", "win-gpu-01"],
);
});
test("highest admin can revoke a grant", async () => {
const grantResponse = await adminPost({
action: "grant_device",
account: "worker@example.com",
deviceId: "mac-studio",
permissions: ["device.view"],
});
const grantPayload = await grantResponse.json();
const revokeResponse = await adminPost({
action: "revoke_grant",
grantId: grantPayload.grant.grantId,
});
assert.equal(revokeResponse.status, 200);
const state = await data.readState();
assert.equal(state.accountDeviceGrants.length, 0);
assert.equal(state.permissionAuditLogs.at(0)?.action, "grant.revoked");
});
test("highest admin can apply a permission template across device project and skill scopes", async () => {
await adminPost({
action: "upsert_account",
account: "developer@example.com",
displayName: "Developer",
role: "member",
password: "developer-pass",
});
const getResponse = await getAdminAccess(
await authedRequest("krisolo", "highest_admin", "http://127.0.0.1:3000/api/v1/admin/access"),
);
assert.equal(getResponse.status, 200);
const getPayload = await getResponse.json();
assert.deepEqual(
getPayload.permissionTemplates.map((template: { templateId: string }) => template.templateId),
["viewer", "developer", "operator"],
);
const applyResponse = await adminPost({
action: "apply_template",
account: "developer@example.com",
templateId: "developer",
deviceIds: ["mac-studio"],
projectIds: ["master-agent"],
skillIds: ["mac-studio:boss-server-debug"],
});
assert.equal(applyResponse.status, 200);
const applyPayload = await applyResponse.json();
assert.equal(applyPayload.grants.devices.length, 1);
assert.equal(applyPayload.grants.projects.length, 1);
assert.equal(applyPayload.grants.skills.length, 1);
const state = await data.readState();
assert.deepEqual(state.accountDeviceGrants.at(0)?.permissions, ["device.view"]);
assert.deepEqual(state.accountProjectGrants.at(0)?.permissions, [
"project.view",
"thread.chat",
"master_agent.ask",
]);
assert.deepEqual(state.accountSkillGrants.at(0)?.permissions, ["skill.view", "skill.use"]);
assert.equal(
state.permissionAuditLogs.some((log) => log.action === "grant.updated" && log.detail === "template:developer"),
true,
);
});

View File

@@ -0,0 +1,302 @@
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 data: typeof import("../src/lib/boss-data");
let masterAgent: typeof import("../src/lib/boss-master-agent");
let baseState: Awaited<ReturnType<typeof import("../src/lib/boss-data")["readState"]>>;
async function setup() {
if (!runtimeRoot) {
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-rbac-master-scope-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
}
if (!data) {
data = await import("../src/lib/boss-data.ts");
baseState = structuredClone(await data.readState());
}
if (!masterAgent) {
masterAgent = await import("../src/lib/boss-master-agent.ts");
}
}
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
}
});
test.beforeEach(async () => {
await setup();
const state = structuredClone(baseState);
state.devices.push({
id: "cloud-backup",
name: "Cloud Backup Secret Mac",
avatar: "C",
account: "other@example.com",
source: "production",
status: "online",
projects: ["cloud-only-project"],
quota5h: 0,
quota7d: 0,
lastSeenAt: "2026-04-26T12:00:00+08:00",
preferredExecutionMode: "cli",
});
state.projects.push({
id: "cloud-only-project",
name: "Unauthorized Secret Project",
pinned: false,
systemPinned: false,
deviceIds: ["cloud-backup"],
preview: "UNAUTHORIZED_PROJECT_PREVIEW_SHOULD_NOT_LEAK",
updatedAt: "2026-04-26T12:00:00+08:00",
lastMessageAt: "2026-04-26T12:00:00+08:00",
isGroup: false,
threadMeta: {
projectId: "cloud-only-project",
threadId: "thread-cloud-only",
threadDisplayName: "Unauthorized Secret Thread",
folderName: "Unauthorized Folder",
activityIconCount: 0,
updatedAt: "2026-04-26T12:00:00+08:00",
codexThreadRef: "thread-cloud-only",
codexFolderRef: "unauthorized-folder",
},
groupMembers: [],
createdByAgent: true,
collaborationMode: "development",
approvalState: "not_required",
unreadCount: 0,
riskLevel: "low",
messages: [
{
id: "secret-message",
sender: "assistant",
senderLabel: "Codex",
body: "UNAUTHORIZED_SECRET_MESSAGE_SHOULD_NOT_LEAK",
sentAt: "2026-04-26T12:00:00+08:00",
kind: "text",
},
],
goals: [],
versions: [],
});
state.threadStatusDocuments = [
{
documentId: "visible-status",
projectId: "master-agent",
threadId: "visible-thread",
threadDisplayName: "Visible Status",
folderName: "Visible Folder",
deviceId: "mac-studio",
projectGoal: "VISIBLE_STATUS_DOCUMENT",
currentPhase: "联调",
currentProgress: "",
technicalArchitecture: "",
currentBlockers: "",
recommendedNextStep: "",
keyFiles: [],
keyCommands: [],
updatedAt: "2026-04-26T12:00:00+08:00",
sourceTaskId: "visible-task",
sourceKind: "full_sync",
},
{
documentId: "secret-status",
projectId: "cloud-only-project",
threadId: "thread-cloud-only",
threadDisplayName: "Secret Status",
folderName: "Unauthorized Folder",
deviceId: "cloud-backup",
projectGoal: "UNAUTHORIZED_STATUS_DOCUMENT_SHOULD_NOT_LEAK",
currentPhase: "秘密阶段",
currentProgress: "",
technicalArchitecture: "",
currentBlockers: "",
recommendedNextStep: "",
keyFiles: [],
keyCommands: [],
updatedAt: "2026-04-26T12:00:00+08:00",
sourceTaskId: "secret-task",
sourceKind: "full_sync",
},
{
documentId: "cross-device-secret-status",
projectId: "master-agent",
threadId: "thread-cross-device-secret",
threadDisplayName: "Cross Device Secret Status",
folderName: "Visible Project Unauthorized Device",
deviceId: "cloud-backup",
projectGoal: "UNAUTHORIZED_DEVICE_STATUS_DOCUMENT_SHOULD_NOT_LEAK",
currentPhase: "秘密设备阶段",
currentProgress: "",
technicalArchitecture: "",
currentBlockers: "",
recommendedNextStep: "",
keyFiles: [],
keyCommands: [],
updatedAt: "2026-04-26T12:00:00+08:00",
sourceTaskId: "cross-device-secret-task",
sourceKind: "full_sync",
},
];
state.threadProgressEvents = [
{
eventId: "secret-progress",
projectId: "cloud-only-project",
threadId: "thread-cloud-only",
threadDisplayName: "Secret Progress",
deviceId: "cloud-backup",
summary: "UNAUTHORIZED_PROGRESS_EVENT_SHOULD_NOT_LEAK",
eventType: "progress_updated",
createdAt: "2026-04-26T12:00:00+08:00",
sourceTaskId: "secret-task",
},
{
eventId: "cross-device-secret-progress",
projectId: "master-agent",
threadId: "thread-cross-device-secret",
threadDisplayName: "Cross Device Secret Progress",
deviceId: "cloud-backup",
summary: "UNAUTHORIZED_DEVICE_PROGRESS_EVENT_SHOULD_NOT_LEAK",
eventType: "progress_updated",
createdAt: "2026-04-26T12:00:00+08:00",
sourceTaskId: "cross-device-secret-task",
},
];
state.threadContextSnapshots = [
{
snapshotId: "secret-snapshot",
projectId: "cloud-only-project",
taskId: "secret-task",
threadId: "thread-cloud-only",
title: "Unauthorized Secret Context",
summary: "UNAUTHORIZED_CONTEXT_SNAPSHOT_SHOULD_NOT_LEAK",
nodeId: "cloud-backup",
workerId: "worker-secret",
sourceKind: "worker_estimator",
status: "context_urgent",
contextBudgetRemainingPct: 7,
contextBudgetLevel: "critical",
mustFinishBeforeCompaction: true,
estimatedRemainingTurns: 1,
estimatedRemainingLargeMessages: 1,
compactionCount: 0,
patchPending: false,
testsPending: false,
evidencePending: false,
checklist: [],
capturedAt: "2026-04-26T12:00:00+08:00",
},
{
snapshotId: "cross-device-secret-snapshot",
projectId: "master-agent",
taskId: "cross-device-secret-task",
threadId: "thread-cross-device-secret",
title: "Cross Device Secret Context",
summary: "UNAUTHORIZED_DEVICE_CONTEXT_SNAPSHOT_SHOULD_NOT_LEAK",
nodeId: "cloud-backup",
workerId: "worker-cross-device-secret",
sourceKind: "worker_estimator",
status: "context_urgent",
contextBudgetRemainingPct: 9,
contextBudgetLevel: "critical",
mustFinishBeforeCompaction: true,
estimatedRemainingTurns: 1,
estimatedRemainingLargeMessages: 1,
compactionCount: 0,
patchPending: false,
testsPending: false,
evidencePending: false,
checklist: [],
capturedAt: "2026-04-26T12:00:00+08:00",
},
];
state.deviceSkills = [
{
skillId: "mac-studio:boss-server-debug",
deviceId: "mac-studio",
name: "boss-server-debug",
description: "VISIBLE_SKILL",
path: "/Users/kris/.codex/skills/boss-server-debug/SKILL.md",
invocation: "$boss-server-debug",
category: "Mac Studio",
updatedAt: "2026-04-26T12:00:00+08:00",
},
{
skillId: "cloud-backup:secret-skill",
deviceId: "cloud-backup",
name: "secret-skill",
description: "UNAUTHORIZED_SKILL_SHOULD_NOT_LEAK",
path: "/tmp/secret/SKILL.md",
invocation: "$secret-skill",
category: "Secret",
updatedAt: "2026-04-26T12:00:00+08:00",
},
];
state.accountDeviceGrants = [
{
grantId: "grant-worker-mac-view",
account: "worker@example.com",
deviceId: "mac-studio",
permissions: ["device.view"],
grantedBy: "krisolo",
grantedAt: "2026-04-26T12:00:00+08:00",
},
];
state.accountProjectGrants = [
{
grantId: "grant-worker-master-ask",
account: "worker@example.com",
projectId: "master-agent",
permissions: ["project.view", "master_agent.ask"],
grantedBy: "krisolo",
grantedAt: "2026-04-26T12:00:00+08:00",
},
];
state.accountSkillGrants = [
{
grantId: "grant-worker-visible-skill",
account: "worker@example.com",
skillId: "mac-studio:boss-server-debug",
deviceId: "mac-studio",
permissions: ["skill.view", "skill.use"],
grantedBy: "krisolo",
grantedAt: "2026-04-26T12:00:00+08:00",
},
];
await data.writeState(state);
});
test("main agent prompt is built from authorized devices projects and skills only", async () => {
const state = await data.readState();
const result = masterAgent.buildAuthorizedMasterAgentPromptForTest({
state,
session: {
account: "worker@example.com",
role: "member",
displayName: "Worker",
},
projectId: "master-agent",
requestText: "总结我能看到的项目和运行状态",
});
assert.deepEqual(result.authorizedDeviceIds, ["mac-studio"]);
assert.equal(result.authorizedProjectIds.includes("master-agent"), true);
assert.equal(result.authorizedProjectIds.includes("cloud-only-project"), false);
assert.deepEqual(result.authorizedSkillIds, ["mac-studio:boss-server-debug"]);
assert.equal(result.prompt.includes("VISIBLE_STATUS_DOCUMENT"), true);
assert.equal(result.prompt.includes("UNAUTHORIZED_PROJECT_PREVIEW_SHOULD_NOT_LEAK"), false);
assert.equal(result.prompt.includes("UNAUTHORIZED_STATUS_DOCUMENT_SHOULD_NOT_LEAK"), false);
assert.equal(result.prompt.includes("UNAUTHORIZED_DEVICE_STATUS_DOCUMENT_SHOULD_NOT_LEAK"), false);
assert.equal(result.prompt.includes("UNAUTHORIZED_PROGRESS_EVENT_SHOULD_NOT_LEAK"), false);
assert.equal(result.prompt.includes("UNAUTHORIZED_DEVICE_PROGRESS_EVENT_SHOULD_NOT_LEAK"), false);
assert.equal(result.prompt.includes("UNAUTHORIZED_CONTEXT_SNAPSHOT_SHOULD_NOT_LEAK"), false);
assert.equal(result.prompt.includes("UNAUTHORIZED_DEVICE_CONTEXT_SNAPSHOT_SHOULD_NOT_LEAK"), false);
assert.equal(result.prompt.includes("UNAUTHORIZED_SKILL_SHOULD_NOT_LEAK"), false);
assert.equal(result.prompt.includes("Cloud Backup Secret Mac"), false);
});

View File

@@ -0,0 +1,412 @@
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";
import { NextRequest } from "next/server";
let runtimeRoot = "";
let data: typeof import("../src/lib/boss-data");
let authCookie = "";
let getDevices: (typeof import("../src/app/api/v1/devices/route"))["GET"];
let getConversations: (typeof import("../src/app/api/v1/conversations/route"))["GET"];
let getProject: (typeof import("../src/app/api/v1/projects/[projectId]/route"))["GET"];
let getMessages: (typeof import("../src/app/api/v1/projects/[projectId]/messages/route"))["GET"];
let postMessages: (typeof import("../src/app/api/v1/projects/[projectId]/messages/route"))["POST"];
let deleteMessage: (typeof import("../src/app/api/v1/projects/[projectId]/messages/route"))["DELETE"];
let getDeviceSkills: (typeof import("../src/app/api/v1/devices/[deviceId]/skills/route"))["GET"];
let baseState: Awaited<ReturnType<typeof import("../src/lib/boss-data")["readState"]>>;
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-rbac-route-filtering-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [dataModule, authModule, devicesRoute, conversationsRoute, projectRoute, messagesRoute, skillsRoute] =
await Promise.all([
import("../src/lib/boss-data.ts"),
import("../src/lib/boss-auth.ts"),
import("../src/app/api/v1/devices/route.ts"),
import("../src/app/api/v1/conversations/route.ts"),
import("../src/app/api/v1/projects/[projectId]/route.ts"),
import("../src/app/api/v1/projects/[projectId]/messages/route.ts"),
import("../src/app/api/v1/devices/[deviceId]/skills/route.ts"),
]);
data = dataModule;
authCookie = authModule.AUTH_SESSION_COOKIE;
getDevices = devicesRoute.GET;
getConversations = conversationsRoute.GET;
getProject = projectRoute.GET;
getMessages = messagesRoute.GET;
postMessages = messagesRoute.POST;
deleteMessage = messagesRoute.DELETE;
getDeviceSkills = skillsRoute.GET;
baseState = structuredClone(await data.readState());
}
test.after(async () => {
if (runtimeRoot) await rm(runtimeRoot, { recursive: true, force: true });
});
test.beforeEach(async () => {
await setup();
const state = structuredClone(baseState);
state.projects.push({
id: "cloud-only-project",
name: "Cloud Only Project",
pinned: false,
systemPinned: false,
deviceIds: ["cloud-backup"],
preview: "只绑定未授权设备的项目。",
updatedAt: "2026-04-26T12:00:00+08:00",
lastMessageAt: "2026-04-26T12:00:00+08:00",
isGroup: false,
threadMeta: {
projectId: "cloud-only-project",
threadId: "thread-cloud-only",
threadDisplayName: "Cloud Only",
folderName: "Cloud",
activityIconCount: 0,
updatedAt: "2026-04-26T12:00:00+08:00",
codexThreadRef: "thread-cloud-only",
codexFolderRef: "cloud",
},
groupMembers: [],
createdByAgent: true,
collaborationMode: "development",
approvalState: "not_required",
unreadCount: 0,
riskLevel: "low",
messages: [
{
id: "cloud-only-message",
sender: "assistant",
senderLabel: "Codex",
body: "这个项目不应该被 worker 看到。",
sentAt: "2026-04-26T12:00:00+08:00",
kind: "text",
},
],
goals: [],
versions: [],
});
state.projects.push({
id: "rbac-thread",
name: "RBAC Authorized Thread",
pinned: false,
systemPinned: false,
deviceIds: ["mac-studio"],
preview: "授权线程。",
updatedAt: "2026-04-26T12:00:00+08:00",
lastMessageAt: "2026-04-26T12:00:00+08:00",
isGroup: false,
threadMeta: {
projectId: "rbac-thread",
threadId: "thread-rbac-authorized",
threadDisplayName: "RBAC Authorized Thread",
folderName: "RBAC",
activityIconCount: 0,
updatedAt: "2026-04-26T12:00:00+08:00",
codexThreadRef: "thread-rbac-authorized",
codexFolderRef: "rbac",
},
groupMembers: [],
createdByAgent: true,
collaborationMode: "development",
approvalState: "not_required",
unreadCount: 0,
riskLevel: "low",
messages: [
{
id: "rbac-thread-message",
sender: "assistant",
senderLabel: "Codex",
body: "这条消息用于验证授权删除。",
sentAt: "2026-04-26T12:00:00+08:00",
kind: "text",
},
],
goals: [],
versions: [],
});
state.accountDeviceGrants = [
{
grantId: "grant-worker-mac-view",
account: "worker@example.com",
deviceId: "mac-studio",
permissions: ["device.view"],
grantedBy: "krisolo",
grantedAt: "2026-04-26T12:00:00+08:00",
},
];
state.accountProjectGrants = [];
state.accountSkillGrants = [];
state.skillCatalog = [];
state.permissionAuditLogs = [];
await data.writeState(state);
});
async function authedRequest(
account: string,
role: "member" | "admin" | "highest_admin",
url: string,
init: RequestInit = {},
) {
const session = await data.createAuthSession({
account,
role,
displayName: account,
loginMethod: "password",
});
return new NextRequest(url, {
...init,
headers: {
...(init.headers ?? {}),
cookie: `${authCookie}=${session.sessionToken}`,
},
});
}
test("device list only includes devices visible to member", async () => {
const response = await getDevices(
await authedRequest("worker@example.com", "member", "http://127.0.0.1:3000/api/v1/devices"),
);
assert.equal(response.status, 200);
const body = await response.json();
assert.deepEqual(
body.devices.map((device: { id: string }) => device.id),
["mac-studio"],
);
});
test("conversation list only includes projects visible to member", async () => {
const response = await getConversations(
await authedRequest("worker@example.com", "member", "http://127.0.0.1:3000/api/v1/conversations"),
);
assert.equal(response.status, 200);
const body = await response.json();
assert.equal(
body.conversations.some((item: { projectId: string }) => item.projectId === "master-agent"),
true,
);
assert.equal(
body.conversations.some((item: { projectId: string }) => item.projectId === "cloud-only-project"),
false,
);
});
test("project detail returns 403 when member lacks project view", async () => {
const response = await getProject(
await authedRequest(
"worker@example.com",
"member",
"http://127.0.0.1:3000/api/v1/projects/cloud-only-project",
),
{ params: Promise.resolve({ projectId: "cloud-only-project" }) },
);
assert.equal(response.status, 403);
});
test("messages GET requires project view", async () => {
const response = await getMessages(
await authedRequest(
"worker@example.com",
"member",
"http://127.0.0.1:3000/api/v1/projects/cloud-only-project/messages",
),
{ params: Promise.resolve({ projectId: "cloud-only-project" }) },
);
assert.equal(response.status, 403);
});
test("messages POST requires explicit thread.chat or master_agent.ask", async () => {
const denied = await postMessages(
await authedRequest(
"worker@example.com",
"member",
"http://127.0.0.1:3000/api/v1/projects/master-agent/messages",
{
method: "POST",
body: JSON.stringify({ body: "总结一下当前进度" }),
},
),
{ params: Promise.resolve({ projectId: "master-agent" }) },
);
assert.equal(denied.status, 403);
const state = await data.readState();
state.accountProjectGrants.push({
grantId: "grant-master-chat",
account: "worker@example.com",
projectId: "master-agent",
permissions: ["project.view", "thread.chat", "master_agent.ask"],
grantedBy: "krisolo",
grantedAt: "2026-04-26T12:30:00+08:00",
});
await data.writeState(state);
const allowed = await postMessages(
await authedRequest(
"worker@example.com",
"member",
"http://127.0.0.1:3000/api/v1/projects/master-agent/messages",
{
method: "POST",
body: JSON.stringify({ body: "总结一下当前进度" }),
},
),
{ params: Promise.resolve({ projectId: "master-agent" }) },
);
assert.notEqual(allowed.status, 403);
});
test("authorized single thread can chat and unauthorized single thread is rejected", async () => {
const state = await data.readState();
state.accountProjectGrants = [
{
grantId: "grant-thread-ui-chat",
account: "worker@example.com",
projectId: "rbac-thread",
permissions: ["project.view", "thread.chat"],
grantedBy: "krisolo",
grantedAt: "2026-04-26T12:30:00+08:00",
},
];
await data.writeState(state);
const allowed = await postMessages(
await authedRequest(
"worker@example.com",
"member",
"http://127.0.0.1:3000/api/v1/projects/rbac-thread/messages",
{
method: "POST",
body: JSON.stringify({ body: "继续处理授权线程" }),
},
),
{ params: Promise.resolve({ projectId: "rbac-thread" }) },
);
assert.notEqual(allowed.status, 403);
const allowedPayload = await allowed.json();
assert.equal(allowedPayload.ok, true);
assert.equal(allowedPayload.message.body, "继续处理授权线程");
const denied = await postMessages(
await authedRequest(
"worker@example.com",
"member",
"http://127.0.0.1:3000/api/v1/projects/cloud-only-project/messages",
{
method: "POST",
body: JSON.stringify({ body: "尝试访问未授权线程" }),
},
),
{ params: Promise.resolve({ projectId: "cloud-only-project" }) },
);
assert.equal(denied.status, 403);
});
test("message delete requires explicit thread.chat permission", async () => {
const state = await data.readState();
state.accountProjectGrants = [
{
grantId: "grant-thread-ui-view-only",
account: "worker@example.com",
projectId: "rbac-thread",
permissions: ["project.view"],
grantedBy: "krisolo",
grantedAt: "2026-04-26T12:30:00+08:00",
},
];
await data.writeState(state);
const messageId = state.projects.find((project) => project.id === "rbac-thread")?.messages[0]?.id;
assert.ok(messageId, "expected seeded rbac-thread message");
const denied = await deleteMessage(
await authedRequest(
"worker@example.com",
"member",
`http://127.0.0.1:3000/api/v1/projects/rbac-thread/messages?messageId=${messageId}`,
{ method: "DELETE" },
),
{ params: Promise.resolve({ projectId: "rbac-thread" }) },
);
assert.equal(denied.status, 403);
const nextState = await data.readState();
nextState.accountProjectGrants = [
{
grantId: "grant-thread-ui-chat-delete",
account: "worker@example.com",
projectId: "rbac-thread",
permissions: ["project.view", "thread.chat"],
grantedBy: "krisolo",
grantedAt: "2026-04-26T12:40:00+08:00",
},
];
await data.writeState(nextState);
const allowed = await deleteMessage(
await authedRequest(
"worker@example.com",
"member",
`http://127.0.0.1:3000/api/v1/projects/rbac-thread/messages?messageId=${messageId}`,
{ method: "DELETE" },
),
{ params: Promise.resolve({ projectId: "rbac-thread" }) },
);
assert.equal(allowed.status, 200);
});
test("device skills route only returns skills granted to member", async () => {
const state = await data.readState();
state.deviceSkills = [
{
skillId: "mac-studio:boss-server-debug",
deviceId: "mac-studio",
name: "boss-server-debug",
description: "服务器调试",
path: "/Users/kris/.codex/skills/boss-server-debug/SKILL.md",
invocation: "$boss-server-debug",
category: "Mac Studio",
updatedAt: "2026-04-26T12:00:00+08:00",
},
{
skillId: "mac-studio:gitea-version-upload",
deviceId: "mac-studio",
name: "gitea-version-upload",
description: "Gitea 上传",
path: "/Users/kris/.codex/skills/gitea-version-upload/SKILL.md",
invocation: "$gitea-version-upload",
category: "Mac Studio",
updatedAt: "2026-04-26T12:00:00+08:00",
},
];
state.accountSkillGrants = [
{
grantId: "grant-skill-view",
account: "worker@example.com",
skillId: "mac-studio:boss-server-debug",
deviceId: "mac-studio",
permissions: ["skill.view", "skill.use"],
grantedBy: "krisolo",
grantedAt: "2026-04-26T12:30:00+08:00",
},
];
await data.writeState(state);
const response = await getDeviceSkills(
await authedRequest(
"worker@example.com",
"member",
"http://127.0.0.1:3000/api/v1/devices/mac-studio/skills",
),
{ params: Promise.resolve({ deviceId: "mac-studio" }) },
);
assert.equal(response.status, 200);
const body = await response.json();
assert.deepEqual(
body.skills.map((skill: { skillId: string }) => skill.skillId),
["mac-studio:boss-server-debug"],
);
});

View File

@@ -0,0 +1,221 @@
import test from "node:test";
import assert from "node:assert/strict";
import type { BossState } from "../src/lib/boss-data";
import { canAccessDevice, canAccessProject } from "../src/lib/boss-permissions";
function baseState(): BossState {
const now = "2026-04-27T17:00:00+08:00";
return {
schemaVersion: 1,
migratedAt: now,
user: {} as BossState["user"],
devices: [
{
id: "device-a",
name: "A 公司 Mac",
avatar: "A",
account: "owner-a@example.com",
companyId: "tenant-a",
source: "production",
status: "online",
projects: ["project-a"],
quota5h: 0,
quota7d: 0,
lastSeenAt: now,
},
{
id: "device-b",
name: "B 公司 Mac",
avatar: "B",
account: "owner-b@example.com",
companyId: "tenant-b",
source: "production",
status: "online",
projects: ["project-b"],
quota5h: 0,
quota7d: 0,
lastSeenAt: now,
},
{
id: "legacy-device",
name: "历史设备",
avatar: "L",
account: "legacy@example.com",
source: "production",
status: "online",
projects: ["legacy-project"],
quota5h: 0,
quota7d: 0,
lastSeenAt: now,
},
],
projects: [
{
id: "project-b",
name: "B 公司项目",
pinned: false,
deviceIds: ["device-b"],
preview: "",
updatedAt: now,
lastMessageAt: now,
isGroup: false,
threadMeta: {
projectId: "project-b",
threadId: "thread-b",
threadDisplayName: "B 线程",
folderName: "b",
activityIconCount: 1,
updatedAt: now,
},
groupMembers: [],
createdByAgent: false,
collaborationMode: "development",
approvalState: "not_required",
unreadCount: 0,
riskLevel: "low",
messages: [],
goals: [],
versions: [],
},
{
id: "legacy-project",
name: "历史项目",
pinned: false,
deviceIds: ["legacy-device"],
preview: "",
updatedAt: now,
lastMessageAt: now,
isGroup: false,
threadMeta: {
projectId: "legacy-project",
threadId: "thread-legacy",
threadDisplayName: "历史线程",
folderName: "legacy",
activityIconCount: 1,
updatedAt: now,
},
groupMembers: [],
createdByAgent: false,
collaborationMode: "development",
approvalState: "not_required",
unreadCount: 0,
riskLevel: "low",
messages: [],
goals: [],
versions: [],
},
],
conversationHistoryClearedAt: undefined,
verificationCodes: [],
verificationDispatches: [],
adminCompanies: [],
authAccounts: [
{
id: "account-a",
account: "worker-a@example.com",
passwordHash: "hash",
displayName: "A 员工",
role: "member",
companyId: "tenant-a",
createdAt: now,
updatedAt: now,
},
{
id: "account-admin",
account: "root@example.com",
passwordHash: "hash",
displayName: "平台管理员",
role: "highest_admin",
createdAt: now,
updatedAt: now,
},
],
authSessions: [],
accountDeviceGrants: [
{
grantId: "cross-device-grant",
account: "worker-a@example.com",
deviceId: "device-b",
permissions: ["device.view", "computer.control"],
grantedBy: "root@example.com",
grantedAt: now,
},
{
grantId: "legacy-device-grant",
account: "worker-a@example.com",
deviceId: "legacy-device",
permissions: ["device.view"],
grantedBy: "root@example.com",
grantedAt: now,
},
],
accountProjectGrants: [
{
grantId: "cross-project-grant",
account: "worker-a@example.com",
projectId: "project-b",
permissions: ["project.view", "thread.chat"],
grantedBy: "root@example.com",
grantedAt: now,
},
],
accountSkillGrants: [],
skillCatalog: [],
skillLifecycleRequests: [],
permissionAuditLogs: [],
aiAccounts: [],
aiAccountSwitchHistory: [],
masterAgentTasks: [],
dispatchPlans: [],
dispatchExecutions: [],
deviceImportDrafts: [],
deviceImportResolutions: [],
threadStatusDocuments: [],
threadProgressEvents: [],
otaUpdates: [],
otaUpdateLogs: [],
deviceSkills: [],
appLogs: [],
userAttachmentStorageConfigs: [],
masterAgentPromptPolicy: null,
userMasterPrompts: [],
masterAgentMemories: [],
userProjectAgentControls: [],
threadContextSnapshots: [],
threadHandoffPackages: [],
threadContextAlerts: [],
deviceEnrollments: [],
opsFaults: [],
opsRepairTickets: [],
opsRepairVerifications: [],
auditRequests: [],
auditResults: [],
capabilities: [],
projectExecutionPolicies: [],
};
}
test("tenant guard blocks accidental cross-company grants for ordinary accounts", () => {
const state = baseState();
const session = { account: "worker-a@example.com", role: "member" as const, displayName: "A 员工" };
assert.equal(canAccessDevice(state, session, "device-b", "device.view"), false);
assert.equal(canAccessProject(state, session, "project-b", "project.view"), false);
assert.equal(canAccessProject(state, session, "project-b", "thread.chat"), false);
});
test("highest admin remains globally visible across tenants", () => {
const state = baseState();
const session = { account: "root@example.com", role: "highest_admin" as const, displayName: "平台管理员" };
assert.equal(canAccessDevice(state, session, "device-b", "device.view"), true);
assert.equal(canAccessProject(state, session, "project-b", "project.view"), true);
});
test("legacy unassigned devices keep explicit grant compatibility", () => {
const state = baseState();
const session = { account: "worker-a@example.com", role: "member" as const, displayName: "A 员工" };
assert.equal(canAccessDevice(state, session, "legacy-device", "device.view"), true);
assert.equal(canAccessProject(state, session, "legacy-project", "project.view"), true);
});

View File

@@ -16,10 +16,30 @@ test("RemoteRuntimeAdapter 会把 local-agent 回写标准化成统一结果", (
assert.equal(normalized.dispatchExecutionId, "dx-1");
assert.equal(normalized.targetProjectId, "project-1");
assert.equal(normalized.targetThreadId, "thread-1");
assert.equal(normalized.targetUrl, undefined);
assert.equal(normalized.targetApp, undefined);
assert.equal(normalized.rawThreadReply, "链路正常");
assert.equal(normalized.replyBody, "主 Agent 汇总:链路正常");
});
test("RemoteRuntimeAdapter 会保留 browser/desktop 控制结果的目标信息", () => {
const browser = normalizeRemoteExecutionResultForTesting({
status: "completed",
replyBody: "浏览器控制已完成",
targetUrl: " https://example.com/dashboard ",
});
const desktop = normalizeRemoteExecutionResultForTesting({
status: "completed",
replyBody: "桌面控制已完成",
targetApp: " 微信 ",
});
assert.equal(browser.targetUrl, "https://example.com/dashboard");
assert.equal(browser.targetApp, undefined);
assert.equal(desktop.targetUrl, undefined);
assert.equal(desktop.targetApp, "微信");
});
test("RemoteRuntimeAdapter 会忽略空白字段并保留失败状态", () => {
const normalized = normalizeRemoteExecutionResultForTesting({
status: "failed",
@@ -51,6 +71,56 @@ test("RemoteRuntimeAdapter 会把线程环境脏回复改写成失败", () => {
assert.match(normalized.errorMessage ?? "", /THREAD_ENVIRONMENT_INVALID/);
});
test("RemoteRuntimeAdapter 会把 Codex CLI 启动日志泄漏改写成内部执行失败", () => {
const normalized = normalizeRemoteExecutionResultForTesting({
status: "failed",
errorMessage: [
"OpenAI Codex v0.114.0 (research preview)",
"--------",
"workdir: /Users/kris/code/boss",
"model: gpt-5.4",
"provider: openai",
"approval: never",
"sandbox: workspace-write [workdir, /tmp, $TMPDIR, /Users/kris/.codex/memories]",
"session id: 019da4e5-9b1d-7dc1-8aa5-a74a74b6b021",
"--------",
"user",
"同步完成记得要和我说,以后也是这样。",
"mcp: chrome-devtools starting",
].join("\n"),
});
assert.equal(normalized.status, "failed");
assert.equal(normalized.replyBody, undefined);
assert.equal(normalized.rawThreadReply, undefined);
assert.equal(normalized.errorMessage, "MASTER_CODEX_NODE_OUTPUT_LEAKED");
});
test("RemoteRuntimeAdapter 会把执行提示词片段泄漏改写成内部执行失败", () => {
const normalized = normalizeRemoteExecutionResultForTesting({
status: "failed",
errorMessage: [
"管理员全局主提示词:",
"你是 Boss 控制台的主 Agent。",
"回复风格:像专业职业经理人,先给结论,再给推进动作。",
"",
"用户私有主提示词:",
"默认中文回复。",
"",
"当前对话附加提示词:",
"同步项目目标和版本记录后记得告诉我。",
"",
"当前消息:",
"同步完成记得要和我说,以后也是这样。",
].join("\n"),
});
assert.equal(normalized.status, "failed");
assert.equal(normalized.replyBody, undefined);
assert.equal(normalized.rawThreadReply, undefined);
assert.equal(normalized.errorMessage, "MASTER_CODEX_NODE_OUTPUT_LEAKED");
});
test("RemoteRuntimeAdapter 不会误杀包含路径和 sandbox 描述的有效线程回复", () => {
const normalized = normalizeRemoteExecutionResultForTesting({
status: "completed",

View File

@@ -0,0 +1,213 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
claimWork,
detectStaleClaims,
handoffWork,
markClaimStealable,
releaseClaim,
type BossWorkClaim,
} from "@/lib/boss-work-claims";
import {
canUseCapabilityGroup,
DEFAULT_CAPABILITY_GROUPS,
explainCapabilityGroupDecision,
} from "@/lib/boss-capability-groups";
import {
assertDeviceTrustEnvelope,
evaluateDeviceTrust,
verifyDeviceTrustEnvelope,
type BossDeviceTrustProfile,
} from "@/lib/boss-device-trust";
test("work claims block conflicting actors and emit deterministic events", () => {
const first = claimWork({
claims: [],
resourceId: "project:alpha",
actorId: "user:kris",
actorKind: "user",
now: "2026-05-10T10:00:00.000Z",
ttlMs: 60_000,
});
assert.equal(first.ok, true);
assert.equal(first.claim?.resourceId, "project:alpha");
assert.equal(first.events[0]?.type, "claim_acquired");
const second = claimWork({
claims: first.claim ? [first.claim] : [],
resourceId: "project:alpha",
actorId: "device:mac-studio",
actorKind: "device",
now: "2026-05-10T10:00:10.000Z",
ttlMs: 60_000,
});
assert.equal(second.ok, false);
assert.equal(second.reason, "claim_conflict");
assert.equal(second.events[0]?.type, "claim_conflict");
assert.equal(second.conflictingClaim?.actorId, "user:kris");
});
test("work claims support release, handoff, stealable marking, and stale detection", () => {
const original: BossWorkClaim = {
claimId: "claim-1",
resourceId: "project:alpha",
actorId: "main-agent",
actorKind: "agent",
acquiredAt: "2026-05-10T10:00:00.000Z",
expiresAt: "2026-05-10T10:05:00.000Z",
status: "active",
};
const marked = markClaimStealable({
claim: original,
actorId: "main-agent",
now: "2026-05-10T10:01:00.000Z",
reason: "waiting for device",
});
assert.equal(marked.ok, true);
assert.equal(marked.claim?.status, "stealable");
assert.equal(marked.events[0]?.type, "claim_marked_stealable");
const handed = handoffWork({
claim: marked.claim!,
fromActorId: "main-agent",
toActorId: "device:mac-studio",
toActorKind: "device",
now: "2026-05-10T10:02:00.000Z",
ttlMs: 120_000,
});
assert.equal(handed.ok, true);
assert.equal(handed.claim?.actorId, "device:mac-studio");
assert.equal(handed.events.map((event) => event.type).join(","), "claim_handoff_released,claim_handoff_acquired");
const stale = detectStaleClaims({
claims: [handed.claim!],
now: "2026-05-10T10:05:01.000Z",
});
assert.equal(stale.staleClaims.length, 1);
assert.equal(stale.events[0]?.type, "claim_stale_detected");
const released = releaseClaim({
claim: handed.claim!,
actorId: "device:mac-studio",
now: "2026-05-10T10:03:00.000Z",
});
assert.equal(released.ok, true);
assert.equal(released.claim?.status, "released");
assert.equal(released.events[0]?.type, "claim_released");
});
test("capability groups enforce account permissions, skill scope, and device scope", () => {
assert.deepEqual(
DEFAULT_CAPABILITY_GROUPS.map((group) => group.id),
["computer_control", "codex_development", "browser_automation", "skill_operations", "admin_ops"],
);
const allowed = canUseCapabilityGroup({
groupId: "browser_automation",
accountPermissions: ["computer.control"],
skillIds: ["browser-use:browser"],
deviceScopes: ["browserAutomation"],
requestedSkillId: "browser-use:browser",
requestedDeviceScope: "browserAutomation",
});
assert.equal(allowed.allowed, true);
const denied = canUseCapabilityGroup({
groupId: "skill_operations",
accountPermissions: ["skill.use"],
skillIds: ["browser-use:browser"],
deviceScopes: ["skillLifecycle"],
requestedSkillId: "skill-installer",
requestedDeviceScope: "skillLifecycle",
});
assert.equal(denied.allowed, false);
assert.equal(denied.reason, "skill_scope_missing");
assert.match(explainCapabilityGroupDecision(denied), /skill_scope_missing/);
});
test("device trust rejects over-budget, over-hop, low-trust, and invalid signed envelopes", () => {
const device: BossDeviceTrustProfile = {
deviceId: "mac-studio",
trustTier: "managed",
publicKeyFingerprint: "sha256:abc",
maxMessageBudget: 3,
maxHopCount: 2,
allowedCapabilities: ["computer_control", "browser_automation"],
};
assert.equal(
evaluateDeviceTrust({
device,
requiredTrustTier: "verified",
capabilityGroupId: "browser_automation",
messageBudgetUsed: 2,
hopCount: 2,
}).allowed,
true,
);
assert.equal(
evaluateDeviceTrust({
device,
requiredTrustTier: "federated",
capabilityGroupId: "browser_automation",
messageBudgetUsed: 2,
hopCount: 2,
}).reason,
"trust_tier_too_low",
);
assert.equal(
evaluateDeviceTrust({
device,
requiredTrustTier: "verified",
capabilityGroupId: "browser_automation",
messageBudgetUsed: 4,
hopCount: 2,
}).reason,
"message_budget_exceeded",
);
assert.equal(
evaluateDeviceTrust({
device,
requiredTrustTier: "verified",
capabilityGroupId: "browser_automation",
messageBudgetUsed: 2,
hopCount: 3,
}).reason,
"hop_limit_exceeded",
);
const envelope = {
deviceId: "mac-studio",
payload: { taskId: "task-1" },
signature: "sig-1",
keyFingerprint: "sha256:abc",
timestamp: "2026-05-10T10:00:00.000Z",
nonce: "nonce-1",
};
assert.equal(
verifyDeviceTrustEnvelope(envelope, {
device,
now: "2026-05-10T10:01:00.000Z",
maxClockSkewMs: 120_000,
verifySignature: ({ canonicalPayload, signature }) =>
signature === "sig-1" && canonicalPayload.includes("\"taskId\":\"task-1\""),
}).valid,
true,
);
assert.throws(() =>
assertDeviceTrustEnvelope(
{ ...envelope, keyFingerprint: "sha256:other" },
{
device,
now: "2026-05-10T10:01:00.000Z",
maxClockSkewMs: 120_000,
verifySignature: () => true,
},
),
);
});

View File

@@ -0,0 +1,123 @@
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"];
const leakedPrompt = [
"管理员全局主提示词:",
"你是 Boss 控制台的主 Agent。",
"默认只说和当前问题直接相关的判断、动作和风险。",
"",
"用户私有主提示词:",
"默认中文回复。",
"",
"当前对话附加提示词:",
"同步项目目标和版本记录后记得告诉我。",
"",
"当前消息:",
"同步完成记得要和我说,以后也是这样。",
].join("\n");
async function setup() {
if (runtimeRoot) {
return;
}
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-runtime-leak-redaction-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const data = await import("../src/lib/boss-data.ts");
readState = data.readState;
writeState = data.writeState;
}
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
}
});
test("读取已有状态时会清洗历史提示词泄漏内容", async () => {
await setup();
const state = await readState();
const masterProject = state.projects.find((project) => project.id === "master-agent");
assert.ok(masterProject, "expected a master-agent project");
masterProject.messages.push({
id: "msg-leak-1",
sender: "ops",
senderLabel: "主 Agent Relay",
body: `Master Codex Node 执行失败:\n${leakedPrompt}`,
sentAt: "2026-04-19T08:35:01.079Z",
kind: "text",
});
masterProject.preview = leakedPrompt;
state.masterAgentTasks.unshift({
taskId: "task-leak-1",
projectId: "master-agent",
taskType: "conversation_reply",
requestMessageId: "msg-user-1",
requestText: "同步完成记得要和我说,以后也是这样。",
executionPrompt: leakedPrompt,
requestedBy: "Boss 超级管理员",
requestedByAccount: "krisolo",
deviceId: "mac-studio",
status: "failed",
requestedAt: "2026-04-19T08:35:01.079Z",
errorMessage: leakedPrompt,
});
state.appLogs.unshift({
logId: "log-leak-1",
deviceId: "mac-studio",
projectId: "master-agent",
level: "error",
source: "local_agent",
category: "local_agent.master_agent_task_failed",
message: "Master Codex Node 执行主 Agent 任务失败task-leak-1",
detail: leakedPrompt,
mirroredToProject: true,
createdAt: "2026-04-19T08:35:01.079Z",
});
await writeState(state);
const nextState = await readState();
const nextMasterProject = nextState.projects.find((project) => project.id === "master-agent");
assert.ok(nextMasterProject, "expected a reloaded master-agent project");
const leakedMessage = nextMasterProject.messages.find((message) => message.id === "msg-leak-1");
assert.ok(leakedMessage, "expected the historical message to remain");
assert.equal(
/管理员全局主提示词:|用户私有主提示词:|当前对话附加提示词:/.test(leakedMessage.body),
false,
);
assert.match(leakedMessage.body, /已拦截内部执行日志|原始内容已隐藏/);
assert.equal(
/管理员全局主提示词:|用户私有主提示词:|当前对话附加提示词:/.test(
nextMasterProject.preview ?? "",
),
false,
);
const sanitizedTask = nextState.masterAgentTasks.find((task) => task.taskId === "task-leak-1");
assert.equal(sanitizedTask?.errorMessage, "MASTER_CODEX_NODE_OUTPUT_LEAKED");
const sanitizedLog = nextState.appLogs.find((log) => log.logId === "log-leak-1");
assert.ok(sanitizedLog, "expected the historical app log to remain");
assert.equal(
/管理员全局主提示词:|用户私有主提示词:|当前对话附加提示词:/.test(
sanitizedLog?.detail ?? "",
),
false,
);
assert.match(sanitizedLog?.detail ?? "", /已拦截内部执行日志|原始内容不再展示/);
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,278 @@
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";
import { NextRequest } from "next/server";
let runtimeRoot = "";
let data: typeof import("../src/lib/boss-data");
let authCookie = "";
let getSkillRequests: (typeof import("../src/app/api/v1/admin/skills/requests/route"))["GET"];
let postSkillRequest: (typeof import("../src/app/api/v1/admin/skills/requests/route"))["POST"];
let claimSkillRequest: (typeof import("../src/app/api/v1/devices/[deviceId]/skill-requests/claim/route"))["POST"];
let completeSkillRequest: (typeof import("../src/app/api/v1/devices/[deviceId]/skill-requests/[requestId]/complete/route"))["POST"];
let baseState: Awaited<ReturnType<typeof import("../src/lib/boss-data")["readState"]>>;
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-skill-lifecycle-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [dataModule, authModule, routeModule, claimRouteModule, completeRouteModule] = await Promise.all([
import("../src/lib/boss-data.ts"),
import("../src/lib/boss-auth.ts"),
import("../src/app/api/v1/admin/skills/requests/route.ts"),
import("../src/app/api/v1/devices/[deviceId]/skill-requests/claim/route.ts"),
import("../src/app/api/v1/devices/[deviceId]/skill-requests/[requestId]/complete/route.ts"),
]);
data = dataModule;
authCookie = authModule.AUTH_SESSION_COOKIE;
getSkillRequests = routeModule.GET;
postSkillRequest = routeModule.POST;
claimSkillRequest = claimRouteModule.POST;
completeSkillRequest = completeRouteModule.POST;
baseState = structuredClone(await data.readState());
}
test.after(async () => {
if (runtimeRoot) await rm(runtimeRoot, { recursive: true, force: true });
});
test.beforeEach(async () => {
await setup();
const state = structuredClone(baseState);
state.skillLifecycleRequests = [];
state.deviceSkills = [
{
skillId: "mac-studio:boss-server-debug",
deviceId: "mac-studio",
name: "boss-server-debug",
description: "服务器调试",
path: "/Users/kris/.codex/skills/boss-server-debug/SKILL.md",
invocation: "$boss-server-debug",
category: "Mac Studio",
updatedAt: "2026-04-26T12:00:00+08:00",
},
];
await data.writeState(state);
});
async function authedRequest(
account: string,
role: "member" | "admin" | "highest_admin",
url: string,
init: RequestInit = {},
) {
const session = await data.createAuthSession({
account,
role,
displayName: account,
loginMethod: "password",
});
return new NextRequest(url, {
...init,
headers: {
...(init.headers ?? {}),
cookie: `${authCookie}=${session.sessionToken}`,
},
});
}
async function adminPost(body: Record<string, unknown>) {
return postSkillRequest(
await authedRequest("krisolo", "highest_admin", "http://127.0.0.1:3000/api/v1/admin/skills/requests", {
method: "POST",
body: JSON.stringify(body),
}),
);
}
async function devicePost(
deviceId: string,
url: string,
body: Record<string, unknown> = {},
) {
return new NextRequest(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-boss-device-token": deviceId === "mac-studio" ? "boss-mac-studio-token" : `${deviceId}-token`,
},
body: JSON.stringify(body),
});
}
test("member cannot create or list skill lifecycle requests", async () => {
const getResponse = await getSkillRequests(
await authedRequest("worker@example.com", "member", "http://127.0.0.1:3000/api/v1/admin/skills/requests"),
);
assert.equal(getResponse.status, 403);
const postResponse = await postSkillRequest(
await authedRequest("worker@example.com", "member", "http://127.0.0.1:3000/api/v1/admin/skills/requests", {
method: "POST",
body: JSON.stringify({
action: "install",
deviceId: "mac-studio",
sourceUrl: "https://git.example.com/org/skill.git",
}),
}),
);
assert.equal(postResponse.status, 403);
});
test("highest admin can create install update uninstall rollback and version lock requests", async () => {
const cases = [
{ action: "install", deviceId: "mac-studio", sourceUrl: "https://git.example.com/org/new-skill.git" },
{ action: "update", deviceId: "mac-studio", skillId: "mac-studio:boss-server-debug", targetVersion: "1.2.0" },
{ action: "uninstall", deviceId: "mac-studio", skillId: "mac-studio:boss-server-debug" },
{ action: "rollback", deviceId: "mac-studio", skillId: "mac-studio:boss-server-debug", rollbackToVersion: "1.1.0" },
{ action: "version_lock", deviceId: "mac-studio", skillId: "mac-studio:boss-server-debug", lockedVersion: "1.1.0" },
];
for (const item of cases) {
const response = await adminPost(item);
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.ok, true);
assert.equal(payload.request.status, "pending");
assert.equal(payload.request.deviceId, "mac-studio");
assert.equal(payload.request.requestedBy, "krisolo");
}
const listResponse = await getSkillRequests(
await authedRequest("krisolo", "highest_admin", "http://127.0.0.1:3000/api/v1/admin/skills/requests"),
);
assert.equal(listResponse.status, 200);
const listPayload = await listResponse.json();
assert.deepEqual(
listPayload.requests.map((request: { action: string }) => request.action),
["version_lock", "rollback", "uninstall", "update", "install"],
);
const state = await data.readState();
assert.equal(state.skillLifecycleRequests.length, 5);
assert.equal(
state.permissionAuditLogs.filter((log) => log.action === "skill.lifecycle.requested").length,
5,
);
});
test("skill lifecycle request must bind a device and a skill id or source url", async () => {
const missingDevice = await adminPost({
action: "install",
sourceUrl: "https://git.example.com/org/new-skill.git",
});
assert.equal(missingDevice.status, 400);
const missingTarget = await adminPost({
action: "install",
deviceId: "mac-studio",
});
assert.equal(missingTarget.status, 400);
const invalidAction = await adminPost({
action: "enable",
deviceId: "mac-studio",
skillId: "mac-studio:boss-server-debug",
});
assert.equal(invalidAction.status, 400);
});
test("skill lifecycle request preserves trusted source and checksum for device claim", async () => {
const createResponse = await adminPost({
action: "install",
deviceId: "mac-studio",
trustedSourceId: "company-skillhub",
expectedChecksum: "abc123",
});
assert.equal(createResponse.status, 200);
const createPayload = await createResponse.json();
assert.equal(createPayload.request.trustedSourceId, "company-skillhub");
assert.equal(createPayload.request.expectedChecksum, "abc123");
const claimResponse = await claimSkillRequest(
await devicePost("mac-studio", "http://127.0.0.1:3000/api/v1/devices/mac-studio/skill-requests/claim"),
{ params: Promise.resolve({ deviceId: "mac-studio" }) },
);
assert.equal(claimResponse.status, 200);
const claimPayload = await claimResponse.json();
assert.equal(claimPayload.request.trustedSourceId, "company-skillhub");
assert.equal(claimPayload.request.expectedChecksum, "abc123");
});
test("skill lifecycle request rejects unknown devices and existing skill mismatches", async () => {
const missingDevice = await adminPost({
action: "install",
deviceId: "missing-device",
sourceUrl: "https://git.example.com/org/new-skill.git",
});
assert.equal(missingDevice.status, 404);
assert.equal((await missingDevice.json()).message, "DEVICE_NOT_FOUND");
const missingSkill = await adminPost({
action: "update",
deviceId: "mac-studio",
skillId: "mac-studio:missing-skill",
});
assert.equal(missingSkill.status, 404);
assert.equal((await missingSkill.json()).message, "SKILL_NOT_FOUND");
});
test("device can claim and complete only its own skill lifecycle requests", async () => {
const createResponse = await adminPost({
action: "update",
deviceId: "mac-studio",
skillId: "mac-studio:boss-server-debug",
targetVersion: "1.2.0",
});
assert.equal(createResponse.status, 200);
const claimResponse = await claimSkillRequest(
await devicePost("mac-studio", "http://127.0.0.1:3000/api/v1/devices/mac-studio/skill-requests/claim"),
{ params: Promise.resolve({ deviceId: "mac-studio" }) },
);
assert.equal(claimResponse.status, 200);
const claimPayload = await claimResponse.json();
assert.equal(claimPayload.ok, true);
assert.equal(claimPayload.request.action, "update");
assert.equal(claimPayload.request.status, "running");
assert.equal(claimPayload.request.claimedByDeviceId, "mac-studio");
const emptyClaimResponse = await claimSkillRequest(
await devicePost("mac-studio", "http://127.0.0.1:3000/api/v1/devices/mac-studio/skill-requests/claim"),
{ params: Promise.resolve({ deviceId: "mac-studio" }) },
);
assert.equal(emptyClaimResponse.status, 200);
assert.equal((await emptyClaimResponse.json()).request, null);
const completeResponse = await completeSkillRequest(
await devicePost(
"mac-studio",
`http://127.0.0.1:3000/api/v1/devices/mac-studio/skill-requests/${claimPayload.request.requestId}/complete`,
{
status: "completed",
resultSummary: "Skill 已更新到 1.2.0",
},
),
{
params: Promise.resolve({
deviceId: "mac-studio",
requestId: claimPayload.request.requestId,
}),
},
);
assert.equal(completeResponse.status, 200);
const completePayload = await completeResponse.json();
assert.equal(completePayload.ok, true);
assert.equal(completePayload.request.status, "completed");
assert.equal(completePayload.request.resultSummary, "Skill 已更新到 1.2.0");
const state = await data.readState();
assert.equal(state.skillLifecycleRequests[0]?.status, "completed");
assert.equal(
state.permissionAuditLogs.some((log) => log.action === "skill.lifecycle.completed"),
true,
);
});

View File

@@ -0,0 +1,56 @@
import test from "node:test";
import assert from "node:assert/strict";
import os from "node:os";
import path from "node:path";
import { execFile } from "node:child_process";
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { promisify } from "node:util";
const execFileAsync = promisify(execFile);
const scriptPath = new URL("../scripts/boss-state-store-maintenance.mjs", import.meta.url);
test("state store maintenance script exposes migration, backup, export, and rollback commands", async () => {
const source = await readFile(scriptPath, "utf8");
for (const command of [
"describe",
"backup-file",
"export-file",
"migrate-file-to-postgres",
"rollback-postgres-to-file",
]) {
assert.match(source, new RegExp(command));
}
assert.match(source, /BOSS_DATABASE_URL/);
assert.match(source, /boss_state_snapshots/);
});
test("backup-file dry run validates local state without requiring postgres", async () => {
const root = await mkdtemp(path.join(os.tmpdir(), "boss-state-maintenance-"));
try {
const stateFile = path.join(root, "boss-state.json");
await writeFile(stateFile, JSON.stringify({ schemaVersion: 1, migratedAt: "2026-04-27T00:00:00.000Z" }), "utf8");
const { stdout } = await execFileAsync(process.execPath, [
scriptPath.pathname,
"backup-file",
"--input",
stateFile,
"--dry-run",
], {
env: {
...process.env,
BOSS_STATE_FILE: stateFile,
},
});
const payload = JSON.parse(stdout);
assert.equal(payload.ok, true);
assert.equal(payload.action, "backup-file");
assert.equal(payload.dryRun, true);
assert.equal(payload.source, stateFile);
assert.equal(payload.bytes > 0, true);
} finally {
await rm(root, { recursive: true, force: true });
}
});

View File

@@ -0,0 +1,467 @@
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";
import { NextRequest } from "next/server";
let runtimeRoot = "";
let readState: (typeof import("../src/lib/boss-data"))["readState"];
let writeState: (typeof import("../src/lib/boss-data"))["writeState"];
let handleTelegramWebhookRequest: (typeof import("../src/lib/telegram-gateway"))["handleTelegramWebhookRequest"];
let completeTaskRoute: (typeof import("../src/app/api/v1/master-agent/tasks/[taskId]/complete/route"))["POST"];
let baseState: Awaited<ReturnType<typeof import("../src/lib/boss-data")["readState"]>>;
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-telegram-gateway-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [data, telegramGateway, taskCompleteRoute] = await Promise.all([
import("../src/lib/boss-data.ts"),
import("../src/lib/telegram-gateway.ts"),
import("../src/app/api/v1/master-agent/tasks/[taskId]/complete/route.ts"),
]);
readState = data.readState;
writeState = data.writeState;
handleTelegramWebhookRequest = telegramGateway.handleTelegramWebhookRequest;
completeTaskRoute = taskCompleteRoute.POST;
baseState = structuredClone(await data.readState());
}
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
}
});
test.beforeEach(async () => {
await setup();
await writeState(structuredClone(baseState));
const state = await readState();
state.telegramIntegration = {
enabled: true,
mode: "webhook",
botToken: "bot-token-demo",
botUsername: "boss_demo_bot",
dmPolicy: "allowlist",
allowFrom: ["123456"],
groupPolicy: "allowlist",
groups: [],
requireMentionInGroups: true,
defaultProjectId: "master-agent",
webhookSecret: "boss-telegram-secret",
lastConfiguredAt: "2026-04-19T10:00:00+08:00",
lastConfiguredBy: "krisolo",
processedUpdateIds: [],
};
await writeState(state);
});
test("Telegram webhook 会拒绝未通过 allowlist 的私聊消息", async () => {
await setup();
const response = await handleTelegramWebhookRequest({
request: new NextRequest("http://127.0.0.1:3000/api/v1/integrations/telegram/webhook", {
method: "POST",
headers: {
"content-type": "application/json",
"x-telegram-bot-api-secret-token": "boss-telegram-secret",
},
body: JSON.stringify({
update_id: 1001,
message: {
message_id: 301,
date: 1_761_000_000,
chat: { id: 987654, type: "private" },
from: { id: 999999, is_bot: false, first_name: "Guest" },
text: "你好",
},
}),
}),
});
assert.equal(response.status, 403);
const payload = (await response.json()) as { ok: boolean; message: string };
assert.equal(payload.ok, false);
assert.equal(payload.message, "TELEGRAM_SENDER_FORBIDDEN");
});
test("Telegram webhook 对 allowlist 私聊会走主 Agent 快速回复并调用 sendMessage", async () => {
await setup();
const originalFetch = globalThis.fetch;
const outboundCalls: Array<{ url: string; body: unknown }> = [];
globalThis.fetch = (async (input, init) => {
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
if (url === "https://api.telegram.org/botbot-token-demo/sendMessage") {
outboundCalls.push({
url,
body: JSON.parse(String(init?.body ?? "{}")),
});
return new Response(JSON.stringify({ ok: true, result: { message_id: 9001 } }), {
status: 200,
headers: { "content-type": "application/json" },
});
}
throw new Error(`unexpected fetch: ${url}`);
}) as typeof fetch;
try {
const response = await handleTelegramWebhookRequest({
request: new NextRequest("http://127.0.0.1:3000/api/v1/integrations/telegram/webhook", {
method: "POST",
headers: {
"content-type": "application/json",
"x-telegram-bot-api-secret-token": "boss-telegram-secret",
},
body: JSON.stringify({
update_id: 1002,
message: {
message_id: 302,
date: 1_761_000_001,
chat: { id: 123456, type: "private" },
from: { id: 123456, is_bot: false, first_name: "Kris" },
text: "hello",
},
}),
}),
});
assert.equal(response.status, 200);
const payload = (await response.json()) as { ok: boolean; delivery: string };
assert.equal(payload.ok, true);
assert.equal(payload.delivery, "sent");
assert.equal(outboundCalls.length, 1);
assert.equal((outboundCalls[0]?.body as { chat_id: number }).chat_id, 123456);
assert.match(String((outboundCalls[0]?.body as { text: string }).text), /主 Agent 可以开始协调/);
const state = await readState();
const project = state.projects.find((item) => item.id === "master-agent");
assert.ok(project?.messages.some((message) => message.senderLabel === "Telegram · Kris"));
} finally {
globalThis.fetch = originalFetch;
}
});
test("Telegram webhook 对需要排队的消息会记录 externalReplyTarget任务完成后自动回推 Telegram", async () => {
await setup();
const originalFetch = globalThis.fetch;
const outboundCalls: Array<{ url: string; body: unknown }> = [];
globalThis.fetch = (async (input, init) => {
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
if (url === "https://api.telegram.org/botbot-token-demo/sendMessage") {
outboundCalls.push({
url,
body: JSON.parse(String(init?.body ?? "{}")),
});
return new Response(JSON.stringify({ ok: true, result: { message_id: 9002 } }), {
status: 200,
headers: { "content-type": "application/json" },
});
}
throw new Error(`unexpected fetch: ${url}`);
}) as typeof fetch;
try {
const webhookResponse = await handleTelegramWebhookRequest({
request: new NextRequest("http://127.0.0.1:3000/api/v1/integrations/telegram/webhook", {
method: "POST",
headers: {
"content-type": "application/json",
"x-telegram-bot-api-secret-token": "boss-telegram-secret",
},
body: JSON.stringify({
update_id: 1003,
message: {
message_id: 303,
date: 1_761_000_002,
chat: { id: 123456, type: "private" },
from: { id: 123456, is_bot: false, first_name: "Kris" },
text: "请深入分析当前项目并给出迁移方案",
},
}),
}),
});
assert.equal(webhookResponse.status, 200);
const webhookPayload = (await webhookResponse.json()) as { ok: boolean; delivery: string; taskId?: string };
assert.equal(webhookPayload.ok, true);
assert.equal(webhookPayload.delivery, "queued");
assert.ok(webhookPayload.taskId);
const queuedState = await readState();
const task = queuedState.masterAgentTasks.find((item) => item.taskId === webhookPayload.taskId);
assert.ok(task, "expected queued task");
assert.equal(task?.externalReplyTarget?.provider, "telegram");
assert.equal(task?.externalReplyTarget?.chatId, "123456");
const completeResponse = await completeTaskRoute(
new NextRequest(`http://127.0.0.1:3000/api/v1/master-agent/tasks/${task?.taskId}/complete`, {
method: "POST",
headers: {
"content-type": "application/json",
"x-boss-device-token": "boss-mac-studio-token",
},
body: JSON.stringify({
deviceId: "mac-studio",
status: "completed",
replyBody: "已经整理好迁移方案,稍后我会按模块推进。",
}),
}),
{ params: Promise.resolve({ taskId: task!.taskId }) },
);
assert.equal(completeResponse.status, 200);
assert.equal(outboundCalls.length, 2);
assert.match(String((outboundCalls[1]?.body as { text: string }).text), /已经整理好迁移方案/);
const completedState = await readState();
const completedTask = completedState.masterAgentTasks.find((item) => item.taskId === task?.taskId);
assert.equal(completedTask?.externalReplyTarget?.deliveredAt?.includes("T"), true);
} finally {
globalThis.fetch = originalFetch;
}
});
test("Telegram 群聊在 requireMentionInGroups 开启时,没有 @Bot 不允许进入主 Agent", async () => {
await setup();
const state = await readState();
state.telegramIntegration = {
...state.telegramIntegration!,
botUsername: "boss_demo_bot",
groupPolicy: "allowlist",
groups: ["-100200300"],
requireMentionInGroups: true,
};
await writeState(state);
const response = await handleTelegramWebhookRequest({
request: new NextRequest("http://127.0.0.1:3000/api/v1/integrations/telegram/webhook", {
method: "POST",
headers: {
"content-type": "application/json",
"x-telegram-bot-api-secret-token": "boss-telegram-secret",
},
body: JSON.stringify({
update_id: 1004,
message: {
message_id: 304,
date: 1_761_000_003,
chat: { id: -100200300, type: "supergroup", title: "Boss 协作群" },
from: { id: 123456, is_bot: false, first_name: "Kris" },
text: "请总结一下今天进展",
},
}),
}),
});
assert.equal(response.status, 400);
const payload = (await response.json()) as { ok: boolean; message: string };
assert.equal(payload.ok, false);
assert.equal(payload.message, "TELEGRAM_GROUP_MENTION_REQUIRED");
});
test("Telegram 群聊命中 @Bot 时会清洗 mention 后再进入主 Agent", async () => {
await setup();
const state = await readState();
state.telegramIntegration = {
...state.telegramIntegration!,
botUsername: "boss_demo_bot",
groupPolicy: "allowlist",
groups: ["-100200300"],
requireMentionInGroups: true,
};
await writeState(state);
const originalFetch = globalThis.fetch;
const outboundCalls: Array<{ url: string; body: unknown }> = [];
globalThis.fetch = (async (input, init) => {
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
if (url === "https://api.telegram.org/botbot-token-demo/sendMessage") {
outboundCalls.push({
url,
body: JSON.parse(String(init?.body ?? "{}")),
});
return new Response(JSON.stringify({ ok: true, result: { message_id: 9003 } }), {
status: 200,
headers: { "content-type": "application/json" },
});
}
throw new Error(`unexpected fetch: ${url}`);
}) as typeof fetch;
try {
const response = await handleTelegramWebhookRequest({
request: new NextRequest("http://127.0.0.1:3000/api/v1/integrations/telegram/webhook", {
method: "POST",
headers: {
"content-type": "application/json",
"x-telegram-bot-api-secret-token": "boss-telegram-secret",
},
body: JSON.stringify({
update_id: 1005,
message: {
message_id: 305,
date: 1_761_000_004,
chat: { id: -100200300, type: "supergroup", title: "Boss 协作群" },
from: { id: 123456, is_bot: false, first_name: "Kris" },
text: "@boss_demo_bot hello",
},
}),
}),
});
assert.equal(response.status, 200);
assert.equal(outboundCalls.length, 1);
const nextState = await readState();
const project = nextState.projects.find((item) => item.id === "master-agent");
const telegramMessage = project?.messages.find((item) => item.senderLabel === "Telegram · Boss 协作群 · Kris");
assert.equal(telegramMessage?.body, "hello");
} finally {
globalThis.fetch = originalFetch;
}
});
test("Telegram 群聊回复 Bot 上一条消息时,即使没有 @Bot 也允许进入主 Agent", async () => {
await setup();
const state = await readState();
state.telegramIntegration = {
...state.telegramIntegration!,
botUsername: "boss_demo_bot",
groupPolicy: "allowlist",
groups: ["-100200300"],
requireMentionInGroups: true,
};
await writeState(state);
const originalFetch = globalThis.fetch;
const outboundCalls: Array<{ url: string; body: unknown }> = [];
globalThis.fetch = (async (input, init) => {
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
if (url === "https://api.telegram.org/botbot-token-demo/sendMessage") {
outboundCalls.push({
url,
body: JSON.parse(String(init?.body ?? "{}")),
});
return new Response(JSON.stringify({ ok: true, result: { message_id: 9004 } }), {
status: 200,
headers: { "content-type": "application/json" },
});
}
throw new Error(`unexpected fetch: ${url}`);
}) as typeof fetch;
try {
const response = await handleTelegramWebhookRequest({
request: new NextRequest("http://127.0.0.1:3000/api/v1/integrations/telegram/webhook", {
method: "POST",
headers: {
"content-type": "application/json",
"x-telegram-bot-api-secret-token": "boss-telegram-secret",
},
body: JSON.stringify({
update_id: 1006,
message: {
message_id: 306,
date: 1_761_000_005,
chat: { id: -100200300, type: "supergroup", title: "Boss 协作群" },
from: { id: 123456, is_bot: false, first_name: "Kris" },
reply_to_message: {
message_id: 299,
date: 1_761_000_000,
chat: { id: -100200300, type: "supergroup", title: "Boss 协作群" },
from: { id: 777888, is_bot: true, username: "boss_demo_bot", first_name: "Boss" },
text: "上一条 bot 回复",
},
text: "继续展开这个方案",
},
}),
}),
});
assert.equal(response.status, 200);
assert.equal(outboundCalls.length, 1);
const nextState = await readState();
const project = nextState.projects.find((item) => item.id === "master-agent");
const telegramMessage = project?.messages.find((item) => item.body === "继续展开这个方案");
assert.equal(telegramMessage?.senderLabel, "Telegram · Boss 协作群 · Kris");
} finally {
globalThis.fetch = originalFetch;
}
});
test("Telegram 群聊可按 chat id 路由到指定 Boss 项目", async () => {
await setup();
const state = await readState();
state.telegramIntegration = {
...state.telegramIntegration!,
botUsername: "boss_demo_bot",
groupPolicy: "allowlist",
groups: ["-100200300"],
requireMentionInGroups: true,
defaultProjectId: "master-agent",
groupProjectRoutes: [
{
chatId: "-100200300",
projectId: "audit-collab",
label: "审计 Telegram 群",
},
],
};
await writeState(state);
const originalFetch = globalThis.fetch;
globalThis.fetch = (async (input) => {
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
if (url === "https://api.telegram.org/botbot-token-demo/sendMessage") {
return new Response(JSON.stringify({ ok: true, result: { message_id: 9005 } }), {
status: 200,
headers: { "content-type": "application/json" },
});
}
throw new Error(`unexpected fetch: ${url}`);
}) as typeof fetch;
try {
const response = await handleTelegramWebhookRequest({
request: new NextRequest("http://127.0.0.1:3000/api/v1/integrations/telegram/webhook", {
method: "POST",
headers: {
"content-type": "application/json",
"x-telegram-bot-api-secret-token": "boss-telegram-secret",
},
body: JSON.stringify({
update_id: 1007,
message: {
message_id: 307,
date: 1_761_000_006,
chat: { id: -100200300, type: "supergroup", title: "Boss 协作群" },
from: { id: 123456, is_bot: false, first_name: "Kris" },
text: "@boss_demo_bot 汇总审计群今天的风险",
},
}),
}),
});
assert.equal(response.status, 200);
const nextState = await readState();
const masterProject = nextState.projects.find((item) => item.id === "master-agent");
const auditProject = nextState.projects.find((item) => item.id === "audit-collab");
assert.equal(
masterProject?.messages.some((message) => message.body === "汇总审计群今天的风险"),
false,
);
assert.equal(
auditProject?.messages.some((message) => message.body === "汇总审计群今天的风险"),
true,
);
} finally {
globalThis.fetch = originalFetch;
}
});

View File

@@ -0,0 +1,225 @@
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";
import { NextRequest } from "next/server";
let runtimeRoot = "";
let getRoute: (typeof import("../src/app/api/v1/integrations/telegram/route"))["GET"];
let postRoute: (typeof import("../src/app/api/v1/integrations/telegram/route"))["POST"];
let createAuthSession: (typeof import("../src/lib/boss-data"))["createAuthSession"];
let AUTH_SESSION_COOKIE = "";
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-telegram-route-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [routeModule, data, auth] = await Promise.all([
import("../src/app/api/v1/integrations/telegram/route.ts"),
import("../src/lib/boss-data.ts"),
import("../src/lib/boss-auth.ts"),
]);
getRoute = routeModule.GET;
postRoute = routeModule.POST;
createAuthSession = data.createAuthSession;
AUTH_SESSION_COOKIE = auth.AUTH_SESSION_COOKIE;
}
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
}
});
async function createAuthedRequest(url: string, method: "GET" | "POST", body?: unknown) {
const session = await createAuthSession({
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
});
return new NextRequest(url, {
method,
headers: {
cookie: `${AUTH_SESSION_COOKIE}=${session.sessionToken}`,
...(body ? { "content-type": "application/json" } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
}
test("Telegram 配置接口返回脱敏配置视图", async () => {
await setup();
const saveResponse = await postRoute(
await createAuthedRequest("http://127.0.0.1:3000/api/v1/integrations/telegram", "POST", {
enabled: true,
botToken: "123:abc",
allowFrom: ["123456"],
webhookSecret: "secret-demo",
}),
);
assert.equal(saveResponse.status, 200);
const getResponse = await getRoute(
await createAuthedRequest("http://127.0.0.1:3000/api/v1/integrations/telegram", "GET"),
);
assert.equal(getResponse.status, 200);
const payload = (await getResponse.json()) as {
ok: boolean;
telegram: {
botTokenConfigured: boolean;
webhookSecretConfigured: boolean;
allowFrom: string[];
};
};
assert.equal(payload.ok, true);
assert.equal(payload.telegram.botTokenConfigured, true);
assert.equal(payload.telegram.webhookSecretConfigured, true);
assert.deepEqual(payload.telegram.allowFrom, ["123456"]);
assert.equal("botToken" in payload.telegram, false);
});
test("Telegram 配置保存到 webhook 模式时会自动调用 setWebhook", async () => {
await setup();
const originalFetch = globalThis.fetch;
const calls: Array<{ url: string; body?: unknown }> = [];
globalThis.fetch = (async (input, init) => {
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
calls.push({
url,
body: init?.body ? JSON.parse(String(init.body)) : undefined,
});
if (url === "https://api.telegram.org/bot123:abc/setWebhook") {
return new Response(JSON.stringify({ ok: true, result: true }), {
status: 200,
headers: { "content-type": "application/json" },
});
}
throw new Error(`unexpected fetch: ${url}`);
}) as typeof fetch;
try {
const response = await postRoute(
await createAuthedRequest("http://127.0.0.1:3000/api/v1/integrations/telegram", "POST", {
enabled: true,
mode: "webhook",
botToken: "123:abc",
webhookSecret: "boss-telegram-secret",
webhookUrl: "https://boss.hyzq.net/api/v1/integrations/telegram/webhook",
}),
);
assert.equal(response.status, 200);
const payload = (await response.json()) as {
ok: boolean;
webhookSync?: { ok: boolean; action: string };
};
assert.equal(payload.ok, true);
assert.equal(payload.webhookSync?.ok, true);
assert.equal(payload.webhookSync?.action, "set_webhook");
assert.equal(calls.length, 1);
assert.equal(calls[0]?.url, "https://api.telegram.org/bot123:abc/setWebhook");
assert.deepEqual(calls[0]?.body, {
url: "https://boss.hyzq.net/api/v1/integrations/telegram/webhook",
secret_token: "boss-telegram-secret",
drop_pending_updates: false,
});
} finally {
globalThis.fetch = originalFetch;
}
});
test("Telegram 配置切回 polling 时会自动调用 deleteWebhook", async () => {
await setup();
const originalFetch = globalThis.fetch;
const calls: string[] = [];
globalThis.fetch = (async (input) => {
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
calls.push(url);
if (url === "https://api.telegram.org/bot123:abc/deleteWebhook") {
return new Response(JSON.stringify({ ok: true, result: true }), {
status: 200,
headers: { "content-type": "application/json" },
});
}
throw new Error(`unexpected fetch: ${url}`);
}) as typeof fetch;
try {
const response = await postRoute(
await createAuthedRequest("http://127.0.0.1:3000/api/v1/integrations/telegram", "POST", {
enabled: true,
mode: "polling",
botToken: "123:abc",
}),
);
assert.equal(response.status, 200);
const payload = (await response.json()) as {
ok: boolean;
webhookSync?: { ok: boolean; action: string };
};
assert.equal(payload.ok, true);
assert.equal(payload.webhookSync?.ok, true);
assert.equal(payload.webhookSync?.action, "delete_webhook");
assert.deepEqual(calls, ["https://api.telegram.org/bot123:abc/deleteWebhook"]);
} finally {
globalThis.fetch = originalFetch;
}
});
test("Telegram 配置接口可以保存群聊到项目的路由表", async () => {
await setup();
const originalFetch = globalThis.fetch;
globalThis.fetch = (async (input) => {
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
if (url === "https://api.telegram.org/bot123:abc/deleteWebhook") {
return new Response(JSON.stringify({ ok: true, result: true }), {
status: 200,
headers: { "content-type": "application/json" },
});
}
throw new Error(`unexpected fetch: ${url}`);
}) as typeof fetch;
try {
const response = await postRoute(
await createAuthedRequest("http://127.0.0.1:3000/api/v1/integrations/telegram", "POST", {
enabled: true,
groupProjectRoutes: [
{
chatId: "-100200300",
projectId: "audit-collab",
label: "审计群",
},
{
chatId: "-100200300",
threadId: 12,
projectId: "master-agent",
label: "主控 Topic",
},
],
}),
);
assert.equal(response.status, 200);
const payload = (await response.json()) as {
ok: boolean;
telegram: {
groupProjectRoutes: Array<{ chatId: string; threadId?: number; projectId: string; label?: string }>;
};
};
assert.equal(payload.ok, true);
assert.deepEqual(payload.telegram.groupProjectRoutes, [
{ chatId: "-100200300", projectId: "audit-collab", label: "审计群" },
{ chatId: "-100200300", threadId: 12, projectId: "master-agent", label: "主控 Topic" },
]);
} finally {
globalThis.fetch = originalFetch;
}
});

View File

@@ -79,7 +79,7 @@ function buildSingleThreadProject(projectId: string) {
async function createAuthedRequest(projectId: string, body: { body: string }) {
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",

View File

@@ -40,7 +40,7 @@ test.after(async () => {
async function createAuthedRequest(url: string) {
const session = await createAuthSession({
account: "17600003315",
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",

View File

@@ -3,18 +3,20 @@ import assert from "node:assert/strict";
import os from "node:os";
import path from "node:path";
import { mkdtemp, rm } from "node:fs/promises";
import type { BossState, Project, ThreadProgressEvent, ThreadStatusDocument } from "../src/lib/boss-data.ts";
import type { BossState, MasterAgentTask, Project, ThreadProgressEvent, ThreadStatusDocument } from "../src/lib/boss-data.ts";
let runtimeRoot = "";
let readState: (typeof import("../src/lib/boss-data"))["readState"];
let writeState: (typeof import("../src/lib/boss-data"))["writeState"];
let appendProjectMessage: (typeof import("../src/lib/boss-data"))["appendProjectMessage"];
let updateProjectAgentControls: (typeof import("../src/lib/boss-data"))["updateProjectAgentControls"];
type MutableBossState = BossState & {
threadStatusDocuments: ThreadStatusDocument[];
threadProgressEvents: ThreadProgressEvent[];
projects: Project[];
};
threadStatusDocuments: ThreadStatusDocument[];
threadProgressEvents: ThreadProgressEvent[];
masterAgentTasks: MasterAgentTask[];
projects: Project[];
};
async function setup() {
if (runtimeRoot) return;
@@ -27,6 +29,7 @@ async function setup() {
readState = data.readState;
writeState = data.writeState;
appendProjectMessage = data.appendProjectMessage;
updateProjectAgentControls = data.updateProjectAgentControls;
}
test.after(async () => {
@@ -208,3 +211,149 @@ test("thread replies append lightweight progress events and skip redundant under
false,
);
});
test("turning off single-thread takeover clears pending project understanding sync tasks for that thread", async () => {
await setup();
const projectId = "thread-sync-takeover-clear";
const state = (await readState()) as MutableBossState;
state.projects = state.projects.filter((project) => project.id !== projectId);
state.userProjectAgentControls = state.userProjectAgentControls.filter(
(item) => item.projectId !== projectId,
);
state.masterAgentTasks = state.masterAgentTasks.filter(
(task) => task.projectUnderstandingTargetProjectId !== projectId,
);
state.projects.push({
id: projectId,
name: "接管关闭清理演示",
pinned: false,
deviceIds: ["mac-studio"],
preview: "等待同步",
updatedAt: "2026-04-04T18:00:00+08:00",
lastMessageAt: "2026-04-04T18:00:00+08:00",
isGroup: false,
threadMeta: {
projectId,
threadId: "thread-sync-takeover-clear-thread",
threadDisplayName: "接管关闭清理演示",
folderName: "演示文件夹",
activityIconCount: 1,
updatedAt: "2026-04-04T18:00:00+08:00",
codexThreadRef: "thread-sync-takeover-clear-thread",
codexFolderRef: "thread-sync-takeover-clear-folder",
},
groupMembers: [],
createdByAgent: false,
collaborationMode: "development",
approvalState: "not_required",
unreadCount: 0,
riskLevel: "low",
messages: [],
goals: [],
versions: [],
} as Project);
state.userProjectAgentControls.push({
account: "krisolo",
projectId,
controls: {
takeoverEnabled: true,
updatedAt: "2026-04-04T18:00:00+08:00",
},
});
state.masterAgentTasks.unshift(
{
taskId: "queued-understanding-clear",
projectId: "master-agent",
taskType: "conversation_reply",
requestMessageId: "message-understanding-clear",
requestText: "请同步项目状态",
executionPrompt: "你正在向主 Agent 同步当前项目状态。",
requestedBy: "krisolo",
requestedByAccount: "krisolo",
deviceId: "mac-studio",
targetProjectId: projectId,
targetThreadId: "thread-sync-takeover-clear-thread",
targetThreadDisplayName: "接管关闭清理演示",
projectUnderstandingTargetProjectId: projectId,
projectUnderstandingReason: "heartbeat_activity",
status: "queued",
requestedAt: "2026-04-04T18:01:00+08:00",
},
{
taskId: "completed-understanding-kept",
projectId: "master-agent",
taskType: "conversation_reply",
requestMessageId: "message-understanding-completed",
requestText: "请同步项目状态",
executionPrompt: "你正在向主 Agent 同步当前项目状态。",
requestedBy: "krisolo",
requestedByAccount: "krisolo",
deviceId: "mac-studio",
targetProjectId: projectId,
targetThreadId: "thread-sync-takeover-clear-thread",
targetThreadDisplayName: "接管关闭清理演示",
projectUnderstandingTargetProjectId: projectId,
projectUnderstandingReason: "heartbeat_activity",
status: "completed",
requestedAt: "2026-04-04T18:00:30+08:00",
completedAt: "2026-04-04T18:00:45+08:00",
replyBody: "{}",
},
);
await writeState(state);
const controls = await updateProjectAgentControls(projectId, { takeoverEnabled: false }, "krisolo");
assert.equal(controls?.effectiveTakeoverEnabled, false);
const after = (await readState()) as MutableBossState;
assert.equal(
after.masterAgentTasks.some((task) => task.taskId === "queued-understanding-clear"),
false,
);
assert.equal(
after.masterAgentTasks.some((task) => task.taskId === "completed-understanding-kept"),
true,
);
});
test("turning off global takeover clears pending project understanding sync tasks", async () => {
await setup();
const projectId = "thread-sync-global-clear";
const state = (await readState()) as MutableBossState;
state.masterAgentTasks = state.masterAgentTasks.filter(
(task) => task.projectUnderstandingTargetProjectId !== projectId,
);
state.masterAgentTasks.unshift({
taskId: "running-global-understanding-clear",
projectId: "master-agent",
taskType: "conversation_reply",
requestMessageId: "message-global-understanding-clear",
requestText: "请同步项目状态",
executionPrompt: "你正在向主 Agent 同步当前项目状态。",
requestedBy: "krisolo",
requestedByAccount: "krisolo",
deviceId: "mac-studio",
targetProjectId: projectId,
targetThreadId: "thread-sync-global-clear-thread",
targetThreadDisplayName: "全局接管清理演示",
projectUnderstandingTargetProjectId: projectId,
projectUnderstandingReason: "heartbeat_activity",
status: "running",
requestedAt: "2026-04-04T18:02:00+08:00",
claimedAt: "2026-04-04T18:02:05+08:00",
});
await writeState(state);
await updateProjectAgentControls("master-agent", { globalTakeoverEnabled: true }, "krisolo");
const controls = await updateProjectAgentControls("master-agent", { globalTakeoverEnabled: false }, "krisolo");
assert.equal(controls?.globalTakeoverEnabled, false);
const after = (await readState()) as MutableBossState;
assert.equal(
after.masterAgentTasks.some((task) => task.taskId === "running-global-understanding-clear"),
false,
);
});