feat: add read-only Code Agent runner

This commit is contained in:
Code Queue Review
2026-05-23 08:13:07 +00:00
parent 78f2ab242e
commit db07268d6b
7 changed files with 1231 additions and 41 deletions
+86 -4
View File
@@ -8,6 +8,7 @@ import {
validateCodeAgentChatSchema
} from "../internal/cloud/code-agent-chat.mjs";
import {
classifyCodexRunnerCapability,
classifyCodeAgentChatReadiness,
isHttpNon2xxStatus,
summarizeCodeAgentPayload
@@ -116,10 +117,11 @@ async function runLocalContractSmoke() {
assert.match(failed.error.message, /Codex CLI command is not available/);
assert.deepEqual(failed.error.missingCommands, ["codex"]);
assert.ok(failed.error.missingEnv.includes("OPENAI_API_KEY"));
assert.equal(failed.availability.status, "blocked");
assert.match(failed.availability.blocker, /凭证缺口|OPENAI_API_KEY/u);
assert.equal(failed.availability.reason, "provider_unavailable");
assert.match(failed.availability.summary, /真实后端已接入/u);
assert.equal(failed.availability.status, "available");
assert.equal(failed.availability.blocker, null);
assert.equal(failed.availability.reason, null);
assert.equal(failed.availability.runner.ready, true);
assert.match(failed.availability.summary, /受控只读 runner/u);
assert.match(failed.availability.summary, /hwlab-code-agent-provider\/openai-api-key/u);
assert.equal(JSON.stringify(failed).includes("sk-"), false);
assert.equal(Object.hasOwn(failed, "reply"), false);
@@ -157,6 +159,86 @@ async function runLocalContractSmoke() {
model: "gpt-5.5",
backend: "hwlab-cloud-api/openai-responses"
};
const runnerPwd = await handleCodeAgentChat(
{
conversationId: "cnv_code-agent-chat-runner-pwd",
traceId: "trc_code-agent-chat-runner-pwd",
message: "用pwd列出你当前的工作目录"
},
{
now: () => "2026-05-22T00:02:00.000Z",
env: {
PATH: process.env.PATH,
HWLAB_CODE_AGENT_WORKSPACE: process.cwd()
}
}
);
validateCodeAgentChatSchema(runnerPwd);
assert.equal(runnerPwd.status, "completed");
assert.equal(runnerPwd.provider, "codex-readonly-runner");
assert.equal(runnerPwd.workspace, process.cwd());
assert.equal(runnerPwd.sandbox, "read-only");
assert.equal(runnerPwd.capabilityLevel, "read-only-tools");
assert.equal(runnerPwd.toolCalls[0].name, "pwd");
assert.equal(runnerPwd.toolCalls[0].status, "completed");
const runnerCapability = classifyCodexRunnerCapability(runnerPwd, { httpStatus: 200 });
assert.equal(runnerCapability.status, "pass");
assert.equal(runnerCapability.capabilityPass, true);
logOk("read-only runner pwd capability");
const fallbackCapability = classifyCodexRunnerCapability(completedLivePayload, { httpStatus: 200 });
assert.equal(fallbackCapability.status, "blocked");
assert.equal(fallbackCapability.blocker, "openai-fallback-not-runner");
assert.equal(fallbackCapability.capabilityPass, false);
logOk("OpenAI fallback does not satisfy Codex runner capability");
const missingSkills = await handleCodeAgentChat(
{
conversationId: "cnv_code-agent-chat-missing-skills",
traceId: "trc_code-agent-chat-missing-skills",
message: "列出你可用的skills"
},
{
now: () => "2026-05-22T00:03:00.000Z",
env: {
PATH: process.env.PATH,
HWLAB_CODE_AGENT_WORKSPACE: process.cwd(),
HWLAB_CODE_AGENT_SKILLS_DIRS: "/tmp/hwlab-code-agent-smoke-missing-skills"
},
skillsDirs: ["/tmp/hwlab-code-agent-smoke-missing-skills"],
skillsDirsExact: true
}
);
validateCodeAgentChatSchema(missingSkills);
assert.equal(missingSkills.status, "failed");
assert.equal(missingSkills.error.code, "skills_unavailable");
assert.equal(missingSkills.skills.status, "blocked");
assert.equal(missingSkills.skills.blockers[0].sourceIssue, "pikasTech/HWLAB#136");
assert.equal(classifyCodexRunnerCapability(missingSkills, { httpStatus: 200 }).capabilityPass, false);
logOk("missing skills is structured blocker");
const toolUnavailable = await handleCodeAgentChat(
{
conversationId: "cnv_code-agent-chat-tool-unavailable",
traceId: "trc_code-agent-chat-tool-unavailable",
message: "请用cat读取 package.json"
},
{
now: () => "2026-05-22T00:04:00.000Z",
env: {
PATH: process.env.PATH,
HWLAB_CODE_AGENT_WORKSPACE: process.cwd()
}
}
);
validateCodeAgentChatSchema(toolUnavailable);
assert.equal(toolUnavailable.status, "failed");
assert.equal(toolUnavailable.error.code, "tool_unavailable");
assert.equal(toolUnavailable.toolCalls[0].status, "blocked");
assert.equal(classifyCodexRunnerCapability(toolUnavailable, { httpStatus: 200 }).capabilityPass, false);
logOk("tool unavailable is blocked, not completed runner capability");
const completedHttp200Readiness = classifyCodeAgentChatReadiness(completedLivePayload, { realDevLive: true, httpStatus: 200 });
assert.equal(completedHttp200Readiness.status, "pass");
assert.equal(completedHttp200Readiness.devLiveReplyPass, true);