539 lines
14 KiB
JavaScript
539 lines
14 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import readline from "node:readline";
|
|
|
|
const rl = readline.createInterface({ input: process.stdin });
|
|
const received = [];
|
|
let injectedItems = [];
|
|
let overloadedTurnStartEmitted = false;
|
|
|
|
function send(message) {
|
|
process.stdout.write(`${JSON.stringify(message)}\n`);
|
|
}
|
|
|
|
rl.on("line", (line) => {
|
|
const message = JSON.parse(line);
|
|
received.push(message);
|
|
|
|
if (message.method === "initialize") {
|
|
send({
|
|
id: message.id,
|
|
result: {
|
|
userAgent: "boss-test-codex-app-server",
|
|
platformFamily: "mac",
|
|
platformOs: "darwin",
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (message.method === "initialized") {
|
|
return;
|
|
}
|
|
|
|
if (message.method === "model/list") {
|
|
send({
|
|
id: message.id,
|
|
result: {
|
|
data: [
|
|
{
|
|
id: "gpt-5.4",
|
|
model: "gpt-5.4",
|
|
displayName: "GPT-5.4",
|
|
description: "Deep reasoning model",
|
|
hidden: false,
|
|
supportedReasoningEfforts: ["low", "medium", "high"],
|
|
defaultReasoningEffort: "medium",
|
|
inputModalities: ["text", "image"],
|
|
supportsPersonality: true,
|
|
serviceTiers: [{ id: "default", displayName: "Default" }],
|
|
defaultServiceTier: "default",
|
|
isDefault: true,
|
|
},
|
|
{
|
|
id: "gpt-5.4-mini",
|
|
model: "gpt-5.4-mini",
|
|
displayName: "GPT-5.4 mini",
|
|
description: "Fast response model",
|
|
hidden: false,
|
|
supportedReasoningEfforts: ["none", "low"],
|
|
defaultReasoningEffort: "none",
|
|
inputModalities: ["text"],
|
|
supportsPersonality: true,
|
|
serviceTiers: [],
|
|
defaultServiceTier: null,
|
|
isDefault: false,
|
|
},
|
|
],
|
|
nextCursor: null,
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (message.method === "modelProvider/capabilities/read") {
|
|
send({
|
|
id: message.id,
|
|
result: {
|
|
namespaceTools: true,
|
|
imageGeneration: true,
|
|
webSearch: true,
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (message.method === "skills/list") {
|
|
send({
|
|
id: message.id,
|
|
result: {
|
|
data: [
|
|
{
|
|
cwd: "/Users/kris/code/boss",
|
|
skills: [
|
|
{
|
|
name: "image2-ui-prototype",
|
|
description: "Generate high fidelity UI prototypes",
|
|
path: "/Users/kris/.codex/skills/image2-ui-prototype/SKILL.md",
|
|
scope: "user",
|
|
enabled: true,
|
|
},
|
|
],
|
|
errors: [],
|
|
},
|
|
],
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (message.method === "plugin/list") {
|
|
send({
|
|
id: message.id,
|
|
result: {
|
|
marketplaces: [
|
|
{
|
|
name: "local",
|
|
path: "/Users/kris/.codex/plugins/marketplace.json",
|
|
interface: null,
|
|
plugins: [
|
|
{
|
|
id: "github",
|
|
remotePluginId: null,
|
|
localVersion: "1.0.0",
|
|
name: "GitHub",
|
|
installed: true,
|
|
enabled: true,
|
|
keywords: ["repo"],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
marketplaceLoadErrors: [],
|
|
featuredPluginIds: ["github"],
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (message.method === "app/list") {
|
|
send({
|
|
id: message.id,
|
|
result: {
|
|
data: [
|
|
{
|
|
id: "canva",
|
|
name: "Canva",
|
|
description: "Design app",
|
|
isAccessible: true,
|
|
isEnabled: true,
|
|
pluginDisplayNames: ["Canva"],
|
|
},
|
|
],
|
|
nextCursor: null,
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (message.method === "thread/resume") {
|
|
send({
|
|
id: message.id,
|
|
result: {
|
|
thread: {
|
|
id: message.params?.threadId ?? "thread-fixture",
|
|
name: "fixture thread",
|
|
},
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (message.method === "thread/read") {
|
|
send({
|
|
id: message.id,
|
|
result: {
|
|
thread: {
|
|
id: message.params?.threadId,
|
|
name: "源线程",
|
|
},
|
|
items: [
|
|
{
|
|
type: "message",
|
|
role: "assistant",
|
|
content: [
|
|
{
|
|
type: "output_text",
|
|
text: "源线程最近结论:优先使用 app-server 协议。",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (message.method === "thread/inject_items") {
|
|
injectedItems = message.params?.items ?? [];
|
|
send({
|
|
id: message.id,
|
|
result: {
|
|
injected: injectedItems.length,
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (message.method === "thread/start") {
|
|
send({
|
|
id: message.id,
|
|
result: {
|
|
thread: {
|
|
id: "thread-started-fixture",
|
|
name: "new fixture thread",
|
|
},
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (message.method === "turn/start") {
|
|
if (
|
|
process.env.BOSS_CODEX_APP_SERVER_FIXTURE_OVERLOAD_ON_TURN_START === "1" &&
|
|
!overloadedTurnStartEmitted
|
|
) {
|
|
overloadedTurnStartEmitted = true;
|
|
send({
|
|
id: message.id,
|
|
error: {
|
|
code: -32001,
|
|
message: "Server overloaded; retry later.",
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
const text = message.params?.input?.find?.((item) => item?.type === "text")?.text ?? "";
|
|
send({
|
|
id: message.id,
|
|
result: {
|
|
turn: {
|
|
id: "turn-fixture",
|
|
threadId: message.params?.threadId,
|
|
},
|
|
},
|
|
});
|
|
if (process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EXIT_AFTER_TURN_START === "1") {
|
|
process.exit(0);
|
|
}
|
|
if (process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_PROGRESS === "1") {
|
|
send({
|
|
method: "turn/plan/updated",
|
|
params: {
|
|
threadId: message.params?.threadId,
|
|
turnId: "turn-fixture",
|
|
plan: [
|
|
{ text: "读取 Codex 官方 app-server 协议", status: "completed" },
|
|
{ text: "执行 targeted/full test", status: "in_progress" },
|
|
],
|
|
},
|
|
});
|
|
send({
|
|
method: "turn/diff/updated",
|
|
params: {
|
|
threadId: message.params?.threadId,
|
|
turnId: "turn-fixture",
|
|
diff: {
|
|
changedFiles: 3,
|
|
additions: 181,
|
|
deletions: 52,
|
|
},
|
|
},
|
|
});
|
|
send({
|
|
method: "item/completed",
|
|
params: {
|
|
threadId: message.params?.threadId,
|
|
turnId: "turn-fixture",
|
|
item: {
|
|
type: "fileChange",
|
|
id: "file-change-1",
|
|
changes: [
|
|
{ path: "docs/protocol-snapshots/codex-app-server/codex-app-server-protocol-0.135.0.json" },
|
|
],
|
|
},
|
|
},
|
|
});
|
|
send({
|
|
method: "thread/started",
|
|
params: {
|
|
thread: {
|
|
id: "subagent-thread",
|
|
source: {
|
|
subAgent: {
|
|
thread_spawn: {
|
|
agent_nickname: "Mendel",
|
|
agent_role: "explorer",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
if (process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_GUARDIAN_EVENTS === "1") {
|
|
send({
|
|
id: "cmd-approval-1",
|
|
method: "item/commandExecution/requestApproval",
|
|
params: {
|
|
threadId: message.params?.threadId,
|
|
turnId: "turn-fixture",
|
|
itemId: "command-item-1",
|
|
startedAtMs: Date.now(),
|
|
reason: "需要确认命令执行",
|
|
command: "npm run build -- --token sk-secret-should-not-leak",
|
|
cwd: "/Users/kris/code/boss",
|
|
},
|
|
});
|
|
send({
|
|
method: "item/autoApprovalReview/started",
|
|
params: {
|
|
threadId: message.params?.threadId,
|
|
turnId: "turn-fixture",
|
|
reviewId: "review-1",
|
|
startedAtMs: Date.now(),
|
|
targetItemId: "command-item-1",
|
|
action: {
|
|
type: "command",
|
|
command: "npm run build -- --token sk-secret-should-not-leak",
|
|
cwd: "/Users/kris/code/boss",
|
|
},
|
|
review: {
|
|
status: "running",
|
|
riskLevel: "medium",
|
|
userAuthorization: null,
|
|
rationale: "contains a fake secret that must not be surfaced",
|
|
},
|
|
},
|
|
});
|
|
send({
|
|
method: "guardianWarning",
|
|
params: {
|
|
threadId: message.params?.threadId,
|
|
message: "检测到需要用户确认的命令执行。",
|
|
},
|
|
});
|
|
send({
|
|
method: "item/fileChange/patchUpdated",
|
|
params: {
|
|
threadId: message.params?.threadId,
|
|
turnId: "turn-fixture",
|
|
itemId: "file-change-item-1",
|
|
changes: [
|
|
{
|
|
path: "src/app/page.tsx",
|
|
kind: "update",
|
|
diff: "+ const secret = 'sk-secret-should-not-leak'",
|
|
},
|
|
{
|
|
path: "docs/architecture/codex_server_progress_card_cn.md",
|
|
kind: "add",
|
|
diff: "+ internal prompt should not leak",
|
|
},
|
|
],
|
|
},
|
|
});
|
|
send({
|
|
method: "item/autoApprovalReview/completed",
|
|
params: {
|
|
threadId: message.params?.threadId,
|
|
turnId: "turn-fixture",
|
|
reviewId: "review-1",
|
|
startedAtMs: Date.now(),
|
|
completedAtMs: Date.now(),
|
|
targetItemId: "command-item-1",
|
|
decisionSource: "auto_review",
|
|
action: {
|
|
type: "command",
|
|
command: "npm run build -- --token sk-secret-should-not-leak",
|
|
cwd: "/Users/kris/code/boss",
|
|
},
|
|
review: {
|
|
status: "approved",
|
|
riskLevel: "medium",
|
|
userAuthorization: null,
|
|
rationale: "contains a fake secret that must not be surfaced",
|
|
},
|
|
},
|
|
});
|
|
send({
|
|
method: "serverRequest/resolved",
|
|
params: {
|
|
requestId: "cmd-approval-1",
|
|
threadId: message.params?.threadId,
|
|
},
|
|
});
|
|
}
|
|
if (process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_REALTIME_EVENTS === "1") {
|
|
send({
|
|
method: "thread/status/changed",
|
|
params: {
|
|
threadId: message.params?.threadId,
|
|
status: {
|
|
type: "active",
|
|
activeFlags: ["waitingOnApproval", "waitingOnUserInput"],
|
|
},
|
|
},
|
|
});
|
|
send({
|
|
method: "thread/realtime/started",
|
|
params: {
|
|
threadId: message.params?.threadId,
|
|
realtimeSessionId: "rt-session-1",
|
|
version: "v2",
|
|
},
|
|
});
|
|
send({
|
|
method: "thread/realtime/sdp",
|
|
params: {
|
|
threadId: message.params?.threadId,
|
|
sdp: "v=0 secret-sk-should-not-leak",
|
|
},
|
|
});
|
|
send({
|
|
method: "thread/realtime/transcript/delta",
|
|
params: {
|
|
threadId: message.params?.threadId,
|
|
role: "assistant",
|
|
delta: "正在分析 Codex ",
|
|
},
|
|
});
|
|
send({
|
|
method: "thread/realtime/transcript/done",
|
|
params: {
|
|
threadId: message.params?.threadId,
|
|
role: "assistant",
|
|
text: "正在分析 Codex App Server 实时事件。",
|
|
},
|
|
});
|
|
send({
|
|
method: "thread/realtime/outputAudio/delta",
|
|
params: {
|
|
threadId: message.params?.threadId,
|
|
audio: {
|
|
data: "audio-secret-payload",
|
|
sampleRate: 24000,
|
|
numChannels: 1,
|
|
samplesPerChannel: 480,
|
|
itemId: "audio-item-1",
|
|
},
|
|
},
|
|
});
|
|
send({
|
|
method: "thread/realtime/itemAdded",
|
|
params: {
|
|
threadId: message.params?.threadId,
|
|
item: {
|
|
type: "message",
|
|
text: "raw realtime item should not be persisted",
|
|
},
|
|
},
|
|
});
|
|
send({
|
|
method: "thread/realtime/closed",
|
|
params: {
|
|
threadId: message.params?.threadId,
|
|
reason: "completed",
|
|
},
|
|
});
|
|
}
|
|
send({
|
|
method: "item/agentMessage/delta",
|
|
params: {
|
|
threadId: message.params?.threadId,
|
|
turnId: "turn-fixture",
|
|
delta:
|
|
process.env.BOSS_CODEX_APP_SERVER_FIXTURE_INTER_THREAD === "1"
|
|
? `INTER_THREAD_INJECTED:${JSON.stringify(injectedItems)}`
|
|
: `APP_SERVER_REPLY:${text}`,
|
|
},
|
|
});
|
|
send({
|
|
method: "turn/completed",
|
|
params: {
|
|
threadId: message.params?.threadId,
|
|
turn: {
|
|
id: "turn-fixture",
|
|
status: "completed",
|
|
},
|
|
},
|
|
});
|
|
process.stderr.write(`${JSON.stringify({ received })}\n`);
|
|
return;
|
|
}
|
|
|
|
if (message.method === "turn/steer") {
|
|
const text = message.params?.input?.find?.((item) => item?.type === "text")?.text ?? "";
|
|
send({
|
|
id: message.id,
|
|
result: {
|
|
turn: {
|
|
id: message.params?.expectedTurnId,
|
|
threadId: message.params?.threadId,
|
|
},
|
|
},
|
|
});
|
|
send({
|
|
method: "item/agentMessage/delta",
|
|
params: {
|
|
threadId: message.params?.threadId,
|
|
turnId: message.params?.expectedTurnId,
|
|
delta:
|
|
process.env.BOSS_CODEX_APP_SERVER_FIXTURE_STEER === "1"
|
|
? `STEERED:${text}`
|
|
: `APP_SERVER_STEERED:${text}`,
|
|
},
|
|
});
|
|
send({
|
|
method: "turn/completed",
|
|
params: {
|
|
threadId: message.params?.threadId,
|
|
turn: {
|
|
id: message.params?.expectedTurnId,
|
|
status: "completed",
|
|
},
|
|
},
|
|
});
|
|
process.stderr.write(`${JSON.stringify({ received })}\n`);
|
|
return;
|
|
}
|
|
|
|
send({
|
|
id: message.id,
|
|
error: {
|
|
code: -32601,
|
|
message: `unknown method ${message.method}`,
|
|
},
|
|
});
|
|
});
|