101 lines
3.5 KiB
TypeScript
101 lines
3.5 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
import { NextRequest } from "next/server";
|
|
|
|
let runtimeRoot = "";
|
|
let data: typeof import("../src/lib/boss-data.ts");
|
|
let getControlState: (typeof import("../src/app/api/v1/master-agent/tasks/[taskId]/control-state/route.ts"))["GET"];
|
|
|
|
async function setup() {
|
|
if (runtimeRoot) return;
|
|
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-master-task-control-state-"));
|
|
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/v1/master-agent/tasks/[taskId]/control-state/route.ts"),
|
|
]);
|
|
data = dataModule;
|
|
getControlState = routeModule.GET;
|
|
}
|
|
|
|
test.beforeEach(async () => {
|
|
await setup();
|
|
await rm(runtimeRoot, { recursive: true, force: true });
|
|
});
|
|
|
|
test.after(async () => {
|
|
if (runtimeRoot) await rm(runtimeRoot, { recursive: true, force: true });
|
|
});
|
|
|
|
test("GET task control-state lets the owning device see cancellation without exposing prompt text", async () => {
|
|
const task = await data.queueMasterAgentTask({
|
|
taskId: "control-state-task",
|
|
projectId: "master-agent",
|
|
taskType: "conversation_reply",
|
|
requestMessageId: "msg-control-state",
|
|
requestText: "private user request should not leak",
|
|
executionPrompt: "private execution prompt should not leak",
|
|
requestedBy: "krisolo",
|
|
requestedByAccount: "krisolo",
|
|
deviceId: "mac-studio",
|
|
targetCodexThreadRef: "thread-control-state",
|
|
});
|
|
await data.claimNextMasterAgentTask("mac-studio");
|
|
await data.cancelMasterAgentTask({
|
|
taskId: task.taskId,
|
|
actorAccount: "krisolo",
|
|
reason: "用户在 APP 取消了当前任务",
|
|
});
|
|
|
|
const response = await getControlState(
|
|
new NextRequest(`http://127.0.0.1:3000/api/v1/master-agent/tasks/${task.taskId}/control-state`, {
|
|
method: "GET",
|
|
headers: {
|
|
"x-boss-device-token": "boss-mac-studio-token",
|
|
},
|
|
}),
|
|
{ params: Promise.resolve({ taskId: task.taskId }) },
|
|
);
|
|
const payload = await response.json();
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(payload.ok, true);
|
|
assert.equal(payload.taskId, "control-state-task");
|
|
assert.equal(payload.status, "canceled");
|
|
assert.equal(payload.canceled, true);
|
|
assert.equal(payload.cancelReason, "用户在 APP 取消了当前任务");
|
|
assert.equal(JSON.stringify(payload).includes("private user request"), false);
|
|
assert.equal(JSON.stringify(payload).includes("private execution prompt"), false);
|
|
});
|
|
|
|
test("GET task control-state rejects a different device token", async () => {
|
|
const task = await data.queueMasterAgentTask({
|
|
taskId: "control-state-forbidden-task",
|
|
projectId: "master-agent",
|
|
taskType: "conversation_reply",
|
|
requestMessageId: "msg-control-state-forbidden",
|
|
requestText: "继续",
|
|
executionPrompt: "继续",
|
|
requestedBy: "krisolo",
|
|
requestedByAccount: "krisolo",
|
|
deviceId: "mac-studio",
|
|
});
|
|
|
|
const response = await getControlState(
|
|
new NextRequest(`http://127.0.0.1:3000/api/v1/master-agent/tasks/${task.taskId}/control-state`, {
|
|
method: "GET",
|
|
headers: {
|
|
"x-boss-device-token": "wrong-token",
|
|
},
|
|
}),
|
|
{ params: Promise.resolve({ taskId: task.taskId }) },
|
|
);
|
|
|
|
assert.equal(response.status, 403);
|
|
});
|