834 lines
24 KiB
TypeScript
834 lines
24 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { chmod, mkdtemp, mkdir, writeFile } from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
|
|
const TEST_AGENT_USERNAME = "server-test-agent";
|
|
const TEST_AGENT_PASSWORD = "server-test-agent-password";
|
|
const testAuthByPort = new Map();
|
|
|
|
export function durableBlockedRuntime({ blocker, blockedLayer, queryResult, gates }) {
|
|
return {
|
|
adapter: "postgres",
|
|
durable: false,
|
|
durableRequested: true,
|
|
durableCapable: false,
|
|
ready: false,
|
|
status: "blocked",
|
|
blocker,
|
|
reason: `test durable runtime blocker ${blocker}`,
|
|
liveRuntimeEvidence: false,
|
|
fixtureEvidence: false,
|
|
connection: {
|
|
queryAttempted: true,
|
|
queryResult,
|
|
endpointRedacted: true,
|
|
valueRedacted: true,
|
|
errorCode: "TEST_BLOCKER"
|
|
},
|
|
schema: {
|
|
checked: gates.schema !== "not_checked",
|
|
ready: gates.schema === "ready",
|
|
missingTables: gates.schema === "blocked" ? ["gateway_sessions"] : [],
|
|
missingColumns: gates.schema === "blocked" ? ["gateway_sessions.gateway_session_json"] : []
|
|
},
|
|
migration: {
|
|
checked: gates.migration !== "not_checked",
|
|
ready: gates.migration === "ready",
|
|
missing: gates.migration !== "ready"
|
|
},
|
|
gates: Object.fromEntries(Object.entries(gates).map(([name, status]) => [
|
|
name,
|
|
{
|
|
checked: status !== "not_checked",
|
|
ready: status === "ready",
|
|
status,
|
|
blocker: status === "blocked" ? blocker : null
|
|
}
|
|
])),
|
|
durabilityContract: {
|
|
ready: false,
|
|
status: "blocked",
|
|
requiredEvidence: "runtime_adapter_schema_migration_read_query",
|
|
dbLiveEvidenceIsDurabilityEvidence: false,
|
|
liveRuntimeEvidence: false,
|
|
adapterQueryRequired: true,
|
|
blockedLayer,
|
|
blocker,
|
|
secretMaterialRead: false
|
|
},
|
|
safety: {
|
|
secretMaterialRead: false,
|
|
valuesRedacted: true,
|
|
endpointRedacted: true
|
|
},
|
|
counts: {}
|
|
};
|
|
}
|
|
|
|
export function durableReadyRuntime() {
|
|
return {
|
|
adapter: "postgres",
|
|
durable: true,
|
|
durableRequested: true,
|
|
durableCapable: true,
|
|
ready: true,
|
|
status: "ready",
|
|
blocker: null,
|
|
reason: "test durable runtime ready",
|
|
liveRuntimeEvidence: true,
|
|
fixtureEvidence: false,
|
|
connection: {
|
|
queryAttempted: true,
|
|
queryResult: "durable_readiness_ready",
|
|
endpointRedacted: true,
|
|
valueRedacted: true
|
|
},
|
|
schema: {
|
|
checked: true,
|
|
ready: true,
|
|
missingTables: [],
|
|
missingColumns: []
|
|
},
|
|
migration: {
|
|
checked: true,
|
|
ready: true,
|
|
missing: false
|
|
},
|
|
gates: Object.fromEntries(["ssl", "auth", "schema", "migration", "durability"].map((name) => [
|
|
name,
|
|
{
|
|
checked: true,
|
|
ready: true,
|
|
status: "ready",
|
|
blocker: null
|
|
}
|
|
])),
|
|
durabilityContract: {
|
|
ready: true,
|
|
status: "ready",
|
|
requiredEvidence: "runtime_adapter_schema_migration_read_query",
|
|
dbLiveEvidenceIsDurabilityEvidence: false,
|
|
liveRuntimeEvidence: true,
|
|
adapterQueryRequired: true,
|
|
blockedLayer: null,
|
|
blocker: null,
|
|
secretMaterialRead: false
|
|
},
|
|
safety: {
|
|
secretMaterialRead: false,
|
|
valuesRedacted: true,
|
|
endpointRedacted: true
|
|
},
|
|
counts: {}
|
|
};
|
|
}
|
|
|
|
export function layerStatus(gateStatus) {
|
|
if (gateStatus === "ready") return "pass";
|
|
if (gateStatus === "blocked") return "blocked";
|
|
return "not_proven";
|
|
}
|
|
|
|
export function layerForBlockedCase(blockedLayer) {
|
|
if (blockedLayer === "durability_query") return "durability";
|
|
return blockedLayer;
|
|
}
|
|
|
|
export async function m3ReadinessRequestJson(url, request = {}) {
|
|
const parsed = new URL(url);
|
|
const body = request.body ?? {};
|
|
if (url.startsWith("http://gateway-1") && parsed.pathname === "/status") {
|
|
return {
|
|
ok: true,
|
|
status: 200,
|
|
body: m3GatewayStatus({
|
|
gatewayId: "gwsimu_1",
|
|
gatewaySessionId: "gws_gwsimu_1",
|
|
boxId: "boxsimu_1",
|
|
resourceId: "res_boxsimu_1"
|
|
})
|
|
};
|
|
}
|
|
if (url.startsWith("http://gateway-1") && parsed.pathname === "/invoke") {
|
|
return {
|
|
ok: true,
|
|
status: 200,
|
|
body: {
|
|
accepted: true,
|
|
operationId: "op_m3_status_do1",
|
|
traceId: "trc_m3_status_do1",
|
|
boxId: "boxsimu_1",
|
|
resourceId: "res_boxsimu_1",
|
|
port: "DO1",
|
|
value: false,
|
|
state: {
|
|
port: "DO1",
|
|
direction: "output",
|
|
value: false,
|
|
source: "gateway-simu",
|
|
updatedAt: "2026-05-23T00:00:00.000Z"
|
|
},
|
|
auditId: "aud_m3_status_do1",
|
|
evidenceId: "evd_m3_status_do1"
|
|
}
|
|
};
|
|
}
|
|
if (url.startsWith("http://gateway-2") && parsed.pathname === "/status") {
|
|
return {
|
|
ok: true,
|
|
status: 200,
|
|
body: m3GatewayStatus({
|
|
gatewayId: "gwsimu_2",
|
|
gatewaySessionId: "gws_gwsimu_2",
|
|
boxId: "boxsimu_2",
|
|
resourceId: "res_boxsimu_2"
|
|
})
|
|
};
|
|
}
|
|
if (url.startsWith("http://gateway-2") && parsed.pathname === "/invoke") {
|
|
const value = request.body?.operationId?.includes("do_write") || request.body?.operationId?.includes("do-write")
|
|
? true
|
|
: false;
|
|
return {
|
|
ok: true,
|
|
status: 200,
|
|
body: {
|
|
accepted: true,
|
|
operationId: "op_m3_status_di1",
|
|
traceId: "trc_m3_status_di1",
|
|
boxId: "boxsimu_2",
|
|
resourceId: "res_boxsimu_2",
|
|
port: "DI1",
|
|
value,
|
|
state: {
|
|
port: "DI1",
|
|
direction: "input",
|
|
value,
|
|
source: "patch-panel",
|
|
sourceResourceId: "res_boxsimu_1",
|
|
sourcePort: "DO1",
|
|
propagatedBy: "hwlab-patch-panel",
|
|
updatedAt: "2026-05-23T00:00:00.000Z"
|
|
},
|
|
auditId: "aud_m3_status_di1",
|
|
evidenceId: "evd_m3_status_di1"
|
|
}
|
|
};
|
|
}
|
|
if (url.startsWith("http://patch-panel") && parsed.pathname === "/status") {
|
|
return {
|
|
ok: true,
|
|
status: 200,
|
|
body: {
|
|
serviceId: "hwlab-patch-panel",
|
|
state: "active",
|
|
activeConnections: [
|
|
{
|
|
fromResourceId: "res_boxsimu_1",
|
|
fromPort: "DO1",
|
|
toResourceId: "res_boxsimu_2",
|
|
toPort: "DI1"
|
|
}
|
|
]
|
|
}
|
|
};
|
|
}
|
|
if (url.startsWith("http://patch-panel") && parsed.pathname === "/wiring") {
|
|
return {
|
|
ok: true,
|
|
status: 200,
|
|
body: {
|
|
wiringConfigId: "wir_m3_do1_di1",
|
|
status: "active",
|
|
connections: [
|
|
{
|
|
from: {
|
|
resourceId: "res_boxsimu_1",
|
|
port: "DO1"
|
|
},
|
|
to: {
|
|
resourceId: "res_boxsimu_2",
|
|
port: "DI1"
|
|
},
|
|
mode: "exclusive"
|
|
}
|
|
]
|
|
}
|
|
};
|
|
}
|
|
if (url.startsWith("http://patch-panel") && parsed.pathname === "/sync/tick") {
|
|
return {
|
|
ok: true,
|
|
status: 200,
|
|
body: {
|
|
accepted: true,
|
|
propagatedBy: "hwlab-patch-panel",
|
|
routeCount: 1,
|
|
deliveryCount: 1,
|
|
routes: [
|
|
{
|
|
accepted: true,
|
|
deliveryCount: 1,
|
|
deliveries: [
|
|
{
|
|
resourceId: "res_boxsimu_2",
|
|
port: "DI1",
|
|
value: body.signals?.[0]?.value ?? false,
|
|
sourceResourceId: "res_boxsimu_1",
|
|
sourcePort: "DO1",
|
|
deliveryStatus: "applied"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
};
|
|
}
|
|
throw new Error(`unexpected M3 readiness request ${url}`);
|
|
}
|
|
|
|
export function m3GatewayStatus({ gatewayId, gatewaySessionId, boxId, resourceId }) {
|
|
return {
|
|
serviceId: "hwlab-gateway-simu",
|
|
gatewayId,
|
|
gatewaySessionId,
|
|
session: {
|
|
gatewayId,
|
|
gatewaySessionId
|
|
},
|
|
registry: {
|
|
gatewayId,
|
|
gatewaySessionId,
|
|
boxes: [
|
|
{
|
|
boxId,
|
|
resourceId,
|
|
state: "registered"
|
|
}
|
|
]
|
|
}
|
|
};
|
|
}
|
|
|
|
export function codexStdioReadyFixture({ workspace, codexHome }) {
|
|
return {
|
|
kind: "codex-app-server-stdio-runner",
|
|
provider: "codex-stdio",
|
|
backend: "hwlab-cloud-api/codex-app-server-stdio",
|
|
status: "feasible",
|
|
ready: true,
|
|
startupReady: true,
|
|
canStartLongLivedCodexStdio: true,
|
|
command: path.join(process.cwd(), "node_modules", ".bin", "codex"),
|
|
binaryOnPath: true,
|
|
binary: {
|
|
present: true,
|
|
executable: true,
|
|
nativeDependencyPresent: true,
|
|
versionDetected: true
|
|
},
|
|
workspace,
|
|
workspaceState: { exists: true, readable: true, writable: true, writeRequired: true },
|
|
codexHome,
|
|
codexHomeState: { exists: true, readable: true, writable: true, writeRequired: true },
|
|
sandbox: "workspace-write",
|
|
enabled: true,
|
|
supervisor: { configured: true, mode: "repo-owned-node-supervisor" },
|
|
tokenBoundary: { present: true, secretMaterialRead: false, valuesRedacted: true },
|
|
egress: { configured: true, directPublicOpenAi: false, valueRedacted: true },
|
|
protocol: {
|
|
status: "wired",
|
|
wired: true,
|
|
requiredTools: ["codex", "codex-reply"],
|
|
toolsObserved: ["codex", "codex-reply"],
|
|
missingTools: [],
|
|
command: "codex app-server --listen stdio://"
|
|
},
|
|
commandProbe: {
|
|
status: "ready",
|
|
ready: true,
|
|
probe: "workspace.pwd",
|
|
traceId: "trc_codex_stdio_command_probe",
|
|
toolCalls: [{
|
|
name: "pwd",
|
|
status: "completed",
|
|
cwd: workspace,
|
|
command: "pwd",
|
|
exitCode: 0,
|
|
stdoutSummary: "workspace path matched",
|
|
outputTruncated: false
|
|
}],
|
|
secretMaterialRead: false,
|
|
valuesRedacted: true
|
|
},
|
|
stdioProtocol: {
|
|
status: "wired",
|
|
wired: true,
|
|
requiredTools: ["codex", "codex-reply"],
|
|
toolsObserved: ["codex", "codex-reply"],
|
|
missingTools: [],
|
|
command: "codex app-server --listen stdio://"
|
|
},
|
|
sessionLifecycle: {
|
|
status: "present",
|
|
present: true,
|
|
create: true,
|
|
reuse: true,
|
|
cancel: true,
|
|
reap: true,
|
|
traceCapture: true,
|
|
idleTimeoutMs: 1800000
|
|
},
|
|
lifecycleSupervisor: {
|
|
status: "present",
|
|
present: true,
|
|
create: true,
|
|
reuse: true,
|
|
cancel: true,
|
|
reap: true,
|
|
traceCapture: true,
|
|
idleTimeoutMs: 1800000
|
|
},
|
|
runtimeContract: {
|
|
status: "ready",
|
|
ready: true,
|
|
binary: {
|
|
status: "present",
|
|
present: true,
|
|
executable: true,
|
|
nativeDependencyPresent: true,
|
|
versionDetected: true
|
|
},
|
|
stdioProtocol: {
|
|
status: "wired",
|
|
wired: true,
|
|
requiredTools: ["codex", "codex-reply"],
|
|
toolsObserved: ["codex", "codex-reply"],
|
|
missingTools: []
|
|
},
|
|
commandProbe: {
|
|
status: "ready",
|
|
ready: true,
|
|
probe: "workspace.pwd",
|
|
traceId: "trc_codex_stdio_command_probe",
|
|
toolCalls: [{
|
|
name: "pwd",
|
|
status: "completed",
|
|
cwd: workspace,
|
|
command: "pwd",
|
|
exitCode: 0,
|
|
stdoutSummary: "workspace path matched",
|
|
outputTruncated: false
|
|
}],
|
|
secretMaterialRead: false,
|
|
valuesRedacted: true
|
|
},
|
|
lifecycleSupervisor: {
|
|
status: "present",
|
|
present: true,
|
|
create: true,
|
|
reuse: true,
|
|
cancel: true,
|
|
reap: true,
|
|
traceCapture: true,
|
|
idleTimeoutMs: 1800000
|
|
},
|
|
workspaceMount: {
|
|
path: workspace,
|
|
status: "ready",
|
|
mounted: true,
|
|
readable: true,
|
|
writable: true,
|
|
sandbox: "workspace-write"
|
|
},
|
|
codexHome: {
|
|
path: codexHome,
|
|
status: "ready",
|
|
exists: true,
|
|
readable: true,
|
|
writable: true
|
|
},
|
|
cancelReapTraceReadiness: {
|
|
status: "ready",
|
|
cancel: true,
|
|
reap: true,
|
|
traceCapture: true,
|
|
idleTimeout: true
|
|
}
|
|
},
|
|
blockers: [],
|
|
blockerCodes: [],
|
|
safety: { secretMaterialRead: false, valuesRedacted: true }
|
|
};
|
|
}
|
|
|
|
export function codexStdioChatFixture({ workspace, codexHome, params }) {
|
|
const traceId = params.traceId;
|
|
const session = {
|
|
sessionId: "ses_server_stdio_pwd",
|
|
conversationId: params.conversationId,
|
|
status: "idle",
|
|
workspace,
|
|
sandbox: "workspace-write",
|
|
runnerKind: "codex-app-server-stdio-runner",
|
|
sessionMode: "codex-app-server-stdio-long-lived",
|
|
capabilityLevel: "long-lived-codex-stdio-session",
|
|
implementationType: "repo-owned-codex-app-server-stdio-session",
|
|
createdAt: "2026-05-23T00:00:00.000Z",
|
|
updatedAt: "2026-05-23T00:00:00.000Z",
|
|
idleTimeoutMs: 1800000,
|
|
expiresAt: "2026-05-23T00:30:00.000Z",
|
|
lastTraceId: traceId,
|
|
turn: 1,
|
|
threadId: "thread_server_stdio_pwd",
|
|
reused: false,
|
|
durable: true,
|
|
longLivedSession: true,
|
|
codexStdio: true,
|
|
writeCapable: true,
|
|
secretMaterialStored: false,
|
|
valuesRedacted: true
|
|
};
|
|
const feasibility = codexStdioReadyFixture({ workspace, codexHome });
|
|
return {
|
|
provider: "codex-stdio",
|
|
model: "gpt-test",
|
|
backend: "hwlab-cloud-api/codex-app-server-stdio",
|
|
content: `stdio pwd\n${workspace}`,
|
|
workspace,
|
|
sandbox: "workspace-write",
|
|
session,
|
|
sessionMode: "codex-app-server-stdio-long-lived",
|
|
sessionReuse: {
|
|
conversationId: params.conversationId,
|
|
sessionId: session.sessionId,
|
|
threadId: session.threadId,
|
|
mapped: true,
|
|
reused: false,
|
|
turn: 1,
|
|
previousTurns: 0,
|
|
workspace,
|
|
status: "idle",
|
|
idleTimeoutMs: 1800000
|
|
},
|
|
implementationType: "repo-owned-codex-app-server-stdio-session",
|
|
runnerLimitations: ["secret-values-redacted"],
|
|
codexStdioFeasibility: feasibility,
|
|
longLivedSessionGate: {
|
|
status: "pass",
|
|
pass: true,
|
|
provider: "codex-stdio",
|
|
runnerKind: "codex-app-server-stdio-runner",
|
|
sessionMode: "codex-app-server-stdio-long-lived",
|
|
implementationType: "repo-owned-codex-app-server-stdio-session",
|
|
blockers: []
|
|
},
|
|
toolCalls: [{
|
|
id: "tool_server_stdio_pwd",
|
|
type: "workspace-read",
|
|
name: "pwd",
|
|
status: "completed",
|
|
cwd: workspace,
|
|
command: "pwd",
|
|
exitCode: 0,
|
|
stdout: workspace,
|
|
stderrSummary: "",
|
|
outputTruncated: false,
|
|
traceId
|
|
}],
|
|
skills: {
|
|
status: "not_requested",
|
|
items: [],
|
|
count: 0,
|
|
blockers: []
|
|
},
|
|
runner: {
|
|
kind: "codex-app-server-stdio-runner",
|
|
provider: "codex-stdio",
|
|
backend: "hwlab-cloud-api/codex-app-server-stdio",
|
|
workspace,
|
|
sandbox: "workspace-write",
|
|
session: "codex-app-server-stdio-long-lived",
|
|
sessionMode: "codex-app-server-stdio-long-lived",
|
|
sessionId: session.sessionId,
|
|
implementationType: "repo-owned-codex-app-server-stdio-session",
|
|
codexStdio: true,
|
|
longLivedSession: true,
|
|
durableSession: true,
|
|
writeCapable: true,
|
|
readOnly: false,
|
|
capabilityLevel: "long-lived-codex-stdio-session"
|
|
},
|
|
runnerTrace: {
|
|
traceId,
|
|
runnerKind: "codex-app-server-stdio-runner",
|
|
workspace,
|
|
sandbox: "workspace-write",
|
|
sessionMode: "codex-app-server-stdio-long-lived",
|
|
sessionId: session.sessionId,
|
|
sessionStatus: "idle",
|
|
turn: 1,
|
|
implementationType: "repo-owned-codex-app-server-stdio-session",
|
|
events: ["stdio:acquire", "session:created", "stdio:ready", "tool:pwd:completed"],
|
|
outputTruncated: false,
|
|
valuesPrinted: false
|
|
},
|
|
capabilityLevel: "long-lived-codex-stdio-session",
|
|
providerTrace: {
|
|
transport: "stdio",
|
|
protocol: "codex-app-server-jsonrpc-stdio",
|
|
command: "codex app-server --listen stdio://",
|
|
toolName: "codex",
|
|
threadId: "thread_server_stdio_pwd",
|
|
valuesPrinted: false
|
|
}
|
|
};
|
|
}
|
|
|
|
export function delay(ms) {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
}
|
|
|
|
export async function postAgent(port, body) {
|
|
const nextBody = { ...body };
|
|
let auth = testAuthByPort.get(port) ?? null;
|
|
const manual = nextBody.sessionId ? null : await createManualAgentSession(port, {
|
|
conversationId: nextBody.conversationId,
|
|
projectId: nextBody.projectId,
|
|
providerProfile: nextBody.providerProfile
|
|
});
|
|
auth = auth ?? manual;
|
|
if (manual) {
|
|
nextBody.conversationId = manual.conversationId;
|
|
nextBody.sessionId = manual.sessionId;
|
|
}
|
|
const response = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, {
|
|
method: "POST",
|
|
headers: {
|
|
"content-type": "application/json",
|
|
...(auth?.cookie ? { cookie: auth.cookie } : {}),
|
|
...(nextBody.traceId ? { "x-trace-id": nextBody.traceId } : {})
|
|
},
|
|
body: JSON.stringify(nextBody)
|
|
});
|
|
assert.equal(response.status, 200);
|
|
return response.json();
|
|
}
|
|
|
|
export async function createManualAgentSession(port, input = {}) {
|
|
const auth = await ensureTestAgentAuth(port);
|
|
const response = await fetch(`http://127.0.0.1:${port}/v1/agent/sessions`, {
|
|
method: "POST",
|
|
headers: {
|
|
"content-type": "application/json",
|
|
cookie: auth.cookie
|
|
},
|
|
body: JSON.stringify({
|
|
conversationId: input.conversationId,
|
|
sessionId: input.sessionId,
|
|
projectId: input.projectId,
|
|
providerProfile: input.providerProfile,
|
|
updatedByClient: "server-agent-chat-test"
|
|
})
|
|
});
|
|
const body = await response.json();
|
|
assert.equal(response.status, 201);
|
|
assert.equal(body.ok, true);
|
|
assert.equal(body.session.usable, true);
|
|
return {
|
|
cookie: auth.cookie,
|
|
headers: {
|
|
"content-type": "application/json",
|
|
cookie: auth.cookie
|
|
},
|
|
session: body.session,
|
|
sessionId: body.session.sessionId,
|
|
conversationId: body.session.conversationId
|
|
};
|
|
}
|
|
|
|
export async function ensureTestAgentAuth(port) {
|
|
const cached = testAuthByPort.get(port);
|
|
if (cached?.cookie) return cached;
|
|
const setup = await fetch(`http://127.0.0.1:${port}/v1/setup/first-admin`, {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify({
|
|
username: TEST_AGENT_USERNAME,
|
|
displayName: "Server Test Agent",
|
|
password: TEST_AGENT_PASSWORD
|
|
})
|
|
});
|
|
let cookie = sessionCookieFromResponse(setup);
|
|
if (!cookie) {
|
|
const login = await fetch(`http://127.0.0.1:${port}/auth/login`, {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify({ username: TEST_AGENT_USERNAME, password: TEST_AGENT_PASSWORD })
|
|
});
|
|
cookie = sessionCookieFromResponse(login);
|
|
assert.equal(login.status, 200);
|
|
} else {
|
|
assert.equal(setup.status, 201);
|
|
}
|
|
assert.ok(cookie);
|
|
const auth = { cookie };
|
|
testAuthByPort.set(port, auth);
|
|
return auth;
|
|
}
|
|
|
|
function sessionCookieFromResponse(response) {
|
|
const value = response.headers.get("set-cookie") ?? "";
|
|
return value.split(";")[0] || "";
|
|
}
|
|
|
|
export async function pollAgentResult(port, traceId, init = {}) {
|
|
let last = null;
|
|
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
const response = await fetch(`http://127.0.0.1:${port}/v1/agent/chat/result/${encodeURIComponent(traceId)}`, init);
|
|
last = {
|
|
status: response.status,
|
|
body: await response.json()
|
|
};
|
|
if (response.status === 200) return last.body;
|
|
if (response.status !== 202) throw new Error(`Code Agent result polling failed: ${JSON.stringify(last)}`);
|
|
await delay(10);
|
|
}
|
|
throw new Error(`Code Agent result did not complete: ${JSON.stringify(last)}`);
|
|
}
|
|
|
|
export async function createFakeCodexCommand() {
|
|
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-fake-codex-"));
|
|
const packageRoot = path.join(root, "node_modules", "@openai", "codex");
|
|
const command = path.join(packageRoot, "bin", "codex.js");
|
|
const native = path.join(root, "node_modules", "@openai", "codex-linux-x64", "vendor", "x86_64-unknown-linux-musl", "codex", "codex");
|
|
await mkdir(path.dirname(command), { recursive: true });
|
|
await mkdir(path.dirname(native), { recursive: true });
|
|
await writeFile(path.join(packageRoot, "package.json"), JSON.stringify({ name: "@openai/codex", version: "0.128.0" }), "utf8");
|
|
await writeFile(command, "#!/usr/bin/env node\nconsole.log('codex 0.128.0');\n", "utf8");
|
|
await writeFile(native, "#!/bin/sh\nexit 0\n", "utf8");
|
|
await chmod(command, 0o755);
|
|
await chmod(native, 0o755);
|
|
return { root, command };
|
|
}
|
|
|
|
export async function prepareFakeCodexHome(codexHome) {
|
|
await mkdir(codexHome, { recursive: true });
|
|
await writeFile(path.join(codexHome, "config.toml"), "model = \"gpt-test\"\n", "utf8");
|
|
await writeFile(path.join(codexHome, "auth.json"), "{\"auth\":\"redacted-test-placeholder\"}\n", "utf8");
|
|
return codexHome;
|
|
}
|
|
|
|
export function createFakeAppServerClient({ text = "真实 Codex stdio 回复", delayMs = 0 } = {}) {
|
|
let notificationHandler = null;
|
|
return {
|
|
async initialize() {
|
|
return { initialized: true };
|
|
},
|
|
setNotificationHandler(handler) {
|
|
notificationHandler = typeof handler === "function" ? handler : null;
|
|
},
|
|
async startThread() {
|
|
notificationHandler?.({ method: "thread/started", params: { thread: { id: "thread_server_test_stdio" } } });
|
|
return { threadId: "thread_server_test_stdio" };
|
|
},
|
|
async resumeThread(args) {
|
|
notificationHandler?.({ method: "thread/started", params: { thread: { id: args.threadId } } });
|
|
return { threadId: args.threadId };
|
|
},
|
|
async startTurn() {
|
|
const turnId = "turn_server_test_stdio";
|
|
notificationHandler?.({ method: "turn/started", params: { turn: { id: turnId } } });
|
|
if (delayMs > 0) await delay(delayMs);
|
|
notificationHandler?.({ method: "item/agentMessage/delta", params: { itemId: "item_server_test_stdio", delta: text } });
|
|
notificationHandler?.({ method: "item/completed", params: { item: { id: "item_server_test_stdio", type: "agentMessage", text } } });
|
|
notificationHandler?.({ method: "turn/completed", params: { turn: { id: turnId, status: "completed" } } });
|
|
return { turnId };
|
|
},
|
|
close() {}
|
|
};
|
|
}
|
|
|
|
export async function postAgentRaw(port, body, headers = {}) {
|
|
const response = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, {
|
|
method: "POST",
|
|
headers: {
|
|
"content-type": "application/json",
|
|
...headers
|
|
},
|
|
body
|
|
});
|
|
return {
|
|
status: response.status,
|
|
body: await response.json()
|
|
};
|
|
}
|
|
|
|
export function createGreenM3StatusRuntimeStore({ blocker = null } = {}) {
|
|
return {
|
|
async readiness() {
|
|
return {
|
|
adapter: "postgres",
|
|
durable: true,
|
|
durableRequested: true,
|
|
durableCapable: true,
|
|
ready: true,
|
|
status: "ready",
|
|
blocker,
|
|
reason: "Postgres durable runtime adapter completed live schema, migration, and read readiness queries",
|
|
liveRuntimeEvidence: true,
|
|
fixtureEvidence: false
|
|
};
|
|
},
|
|
async queryAuditEvents() {
|
|
return {
|
|
events: [
|
|
{
|
|
auditId: "aud_m3_di_read_d5afd84b-3a9d-475f-894f-dff8ea5f49ff_succeeded",
|
|
traceId: "trc_hotfix_307_verify_1779527747043_read_false",
|
|
actorType: "user",
|
|
actorId: "usr_m3_operator",
|
|
action: "m3.io.di.read",
|
|
targetType: "hardware_operation",
|
|
targetId: "op_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f",
|
|
projectId: "prj_mvp_topology",
|
|
gatewaySessionId: "gws_gwsimu_2",
|
|
operationId: "op_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f",
|
|
serviceId: "hwlab-cloud-api",
|
|
outcome: "succeeded",
|
|
metadata: {
|
|
route: "res_boxsimu_1:DO1->hwlab-patch-panel->res_boxsimu_2:DI1"
|
|
},
|
|
environment: "dev",
|
|
occurredAt: "2026-05-23T00:00:00.000Z"
|
|
}
|
|
],
|
|
count: 1
|
|
};
|
|
},
|
|
async queryEvidenceRecords() {
|
|
return {
|
|
records: [
|
|
{
|
|
evidenceId: "evd_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f_succeeded",
|
|
projectId: "prj_mvp_topology",
|
|
operationId: "op_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f",
|
|
kind: "trace",
|
|
uri: "memory://hwlab-cloud-api/op_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f/m3-io-control.json",
|
|
sha256: "0".repeat(64),
|
|
serviceId: "hwlab-cloud-api",
|
|
environment: "dev",
|
|
metadata: {
|
|
traceId: "trc_hotfix_307_verify_1779527747043_read_false",
|
|
route: "res_boxsimu_1:DO1->hwlab-patch-panel->res_boxsimu_2:DI1"
|
|
},
|
|
createdAt: "2026-05-23T00:00:00.000Z"
|
|
}
|
|
],
|
|
count: 1
|
|
};
|
|
}
|
|
};
|
|
}
|