feat: add read-only Code Agent runner
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user