fix: seed caserun hwpod tools
This commit is contained in:
@@ -237,6 +237,8 @@ bun tools/hwlab-cli/bin/hwlab-cli.ts case run logs <runId> --tail 8000
|
||||
|
||||
`case run` 在当前版本只负责编排流程和记录原始证据,不做 evidence 自动评价、自动打分或门禁判断。流程是:prepare 先在 subject repo 本地 checkout 下创建 `.worktree/caserun-<runId>` 隔离 worktree并写入 run-local spec,然后生成 Code Agent prompt/context,创建或调用 HWLAB Code Agent session,把 isolated worktree、run-local HWPOD spec、任务目标和约束交给 Code Agent;agent 阶段结束或超时后,CaseRun 记录 agent identity、terminal result 里已有的终态字段、trace lookup hint、subject worktree 的 `git status` / `git diff --stat` / `git diff --binary`,再按 case 定义执行 runner 后置 `hwpod-cli build` 和 `cmd.run` Keil 异步 job-status 查询,最后把原始证据和可读归档写入 case registry repo 的 `runs/<caseId>/<runId>/`。固定归档包括 `evidence.json`、`summary.md`、`run.json`、`result.json`、`agent-prompt.md`、`agent-diff.patch`、run-local `.hwlab/hwpod-spec.yaml`、worker stdout/stderr、`artifact-manifest.json`,以及 `agent-messages.json`、`agent-trace.md`、`agent-transcript.md` 和 `final-response.md`。`agent-trace.md` / `agent-transcript.md` 必须包含 trace lookup hint、renderer 标识和 subject diff 区块;如果归档了完整 rendered rows,其语义必须来自共享 Web/CLI trace renderer,不能由 CaseRun 自行发明。`final-response.md` 在 terminal result 存在权威 finalResponse 时保存正文,不存在时显式记录 `finalResponse=null`、terminal/error rows 和缺失原因。下载不作为 compile-only CaseRun 的隐式步骤;后续需要下载时,应作为独立 action 显式加入 case 定义和流程记录。
|
||||
|
||||
CaseRun 下发给 AgentRun 的 workspace 必须带 run-local spec 和当前 source tree 中的 HWPOD 工具种子:`tools/hwpod-cli.ts`、`tools/hwpod-ctl.ts`、`tools/hwpod-compiler-cli.ts`、`tools/src/hwpod-harness-lib.ts`、相关合同/endpoint resolver,以及 `.agents/skills/hwpod-cli|hwpod-ctl|hwlab-agent-runtime/SKILL.md`。这不是把 prompt 写成答案,而是保证 Code Agent 看到的 `hwpod` 能力、help、别名和 `cmd.run` 组合语义与当前 `v0.2` 源码一致;不得只发送 `.hwlab/hwpod-spec.yaml` 后依赖运行面预装旧工具或旧 skill 缓存。若 agent 报告标准 `hwpod` 命令缺失或 help 明显过时,优先修 CaseRun workspace seed 或 HWPOD CLI/skill 装配,不新增 hwpod-node 专用 op,也不把业务动作旁路到 shell 脚本。
|
||||
|
||||
CaseRun 的 trace 处理必须遵循 [spec-v02-code-agent-trace.md](spec-v02-code-agent-trace.md) 的组合优先原则:CaseRun 只负责稳定返回和归档 `traceId`、`sessionId`、`conversationId`、`threadId`、AgentRun 执行线索以及 `traceLookup.commands.*`,完整 trace/result/session 读取继续使用已有 `hwlab-cli client agent trace/result/inspect`。CaseRun 不应在自身内部再实现一套 `/v1/agent/chat/trace/{traceId}` 查询器、row renderer、session 反查或 final response 归因逻辑;如果已有 agent CLI 的 trace 可见性不足,先改进 `client agent trace/result/inspect`,再让 CaseRun 输出新的 ID 或 hint。归档中的 `agent-trace.json` 可以是 `lookupOnly=true` 的 identity manifest,表示“用这些 ID 调既有 CLI 查完整 trace”,不能伪装成完整 trace dump。
|
||||
|
||||
长时间 CaseRun 必须优先使用 `case run start/status/result/logs` 短连接形态,而不是用 shell/nohup 包装同步 `case run`。`start` 只负责创建 `.state/hwlab-cli/caserun/<runId>/`、启动后台 worker 并立即返回 `runId`、PID、stdout/stderr 路径和下一条查询命令;`status`、`result`、`logs` 都是短查询。`run.json` 是阶段状态权威,阶段开始时就要刷新 `status`、`stage`、`traceId/sessionId/conversationId/threadId`、AgentRun/trace 轮询信息、stdout/stderr byte count 和下一条轮询命令;长等待期间不得出现无输出、无 trace、无状态文件更新的黑洞窗口。
|
||||
|
||||
@@ -22,12 +22,16 @@ The HWPOD spec is code-agent-side state. The default path is `.hwlab/hwpod-spec.
|
||||
## Commands
|
||||
|
||||
- Inspect the HWPOD path: `hwpod inspect --spec .hwlab/hwpod-spec.yaml`.
|
||||
- Workspace read: `hwpod workspace ls . --spec .hwlab/hwpod-spec.yaml`.
|
||||
- Build/download/reset: use `hwpod build`, `hwpod download`, and `hwpod reset`; the compiler must translate these to `debug.build`, `debug.download`, and `debug.reset` node ops.
|
||||
- Workspace read/edit: use `hwpod workspace ls|cat|read|write|replace|insert-after|apply-patch --spec .hwlab/hwpod-spec.yaml`. `read` is an alias for `cat`; `--file` and `--remote-path` are accepted as path aliases.
|
||||
- Build/download/reset: use `hwpod build`, `hwpod download`, and `hwpod reset`; the compiler must translate these through existing file ops and `cmd.run` where possible.
|
||||
- Keil async status: after build/download returns a job id, use `hwpod job status <jobId> --spec .hwlab/hwpod-spec.yaml` instead of shell polling wrappers.
|
||||
- UART read: use `hwpod uart read --spec .hwlab/hwpod-spec.yaml --port uart1`; the compiler runs the configured serial-monitor CLI through `cmd.run`.
|
||||
- For early debug bindings, put command bindings in `.hwlab/hwpod-spec.yaml` with `spec.workspace.buildCommand`, `spec.debugProbe.downloadCommand`, or `spec.debugProbe.resetCommand`.
|
||||
|
||||
## Boundaries
|
||||
|
||||
- Do not pass manual API URLs in locked AgentRun runtimes. Let `HWLAB_RUNTIME_API_URL` / `HWLAB_RUNTIME_NAMESPACE` select the current lane.
|
||||
- Do not put full `hwpod-spec` on `hwpod-node`. The node only accepts `hwpod-node-ops`.
|
||||
- Treat `cmd.run` as the full-capability host execution op. Do not request new hwpod-node ops for build, download, job status, UART, PowerShell/cmd passthrough, or tool maintenance; improve `hwpod-compiler-cli` / `hwpod-cli` combinations first.
|
||||
- Use workspace file operations for subject text edits. Do not use PowerShell/cmd heredocs for normal source edits unless debugging `cmd.run` itself.
|
||||
- If HWPOD returns a blocker, report and fix that blocker in this path.
|
||||
|
||||
@@ -474,6 +474,20 @@ test("case run orchestrates agent prompt, diff capture, and compile evidence wit
|
||||
assert.equal(agentChatRequest.body.workspaceFiles[0].path, ".hwlab/hwpod-spec.yaml");
|
||||
assert.equal(agentChatRequest.body.workspaceFiles[0].encoding, "utf8");
|
||||
assert.match(agentChatRequest.body.workspaceFiles[0].content, /HWLAB-CASE-F103.*caserun-run-agent-flow/u);
|
||||
const workspaceFilePaths = agentChatRequest.body.workspaceFiles.map((item: any) => item.path);
|
||||
assert.equal(workspaceFilePaths.includes("tools/hwpod-cli.ts"), true);
|
||||
assert.equal(workspaceFilePaths.includes("tools/hwpod-ctl.ts"), true);
|
||||
assert.equal(workspaceFilePaths.includes("tools/hwpod-compiler-cli.ts"), true);
|
||||
assert.equal(workspaceFilePaths.includes("tools/src/hwpod-harness-lib.ts"), true);
|
||||
assert.equal(workspaceFilePaths.includes("tools/src/hwpod-node-ops-contract.ts"), true);
|
||||
assert.equal(workspaceFilePaths.includes("tools/src/runtime-endpoint-resolver.ts"), true);
|
||||
assert.equal(workspaceFilePaths.includes(".agents/skills/hwpod-cli/SKILL.md"), true);
|
||||
assert.equal(workspaceFilePaths.includes(".agents/skills/hwpod-ctl/SKILL.md"), true);
|
||||
assert.equal(workspaceFilePaths.includes(".agents/skills/hwlab-agent-runtime/SKILL.md"), true);
|
||||
const seededHwpodLib = agentChatRequest.body.workspaceFiles.find((item: any) => item.path === "tools/src/hwpod-harness-lib.ts");
|
||||
assert.match(seededHwpodLib.content, /hwpod-cli\.job\.help/u);
|
||||
const seededHwpodSkill = agentChatRequest.body.workspaceFiles.find((item: any) => item.path === ".agents/skills/hwpod-cli/SKILL.md");
|
||||
assert.match(seededHwpodSkill.content, /cmd\.run/u);
|
||||
assert.doesNotMatch(agentChatRequest.body.message, /path: "F:\\Work\\HWLAB-CASE-F103\\\.worktree\\caserun-run-agent-flow"/u);
|
||||
assert.equal(requests.some((item) => item.url === "http://web.test/v1/agent/chat/result/" + result.payload.agent.traceId), true);
|
||||
assert.equal(requests.some((item) => item.url === "http://web.test/v1/agent/chat/trace/" + result.payload.agent.traceId), false);
|
||||
|
||||
@@ -192,6 +192,40 @@ test("hwpod-cli dry-run invokes hwpod-compiler-cli and exposes hwpod-node-ops pl
|
||||
}
|
||||
});
|
||||
|
||||
test("hwpod-cli exposes subcommand help before required args and accepts workspace path aliases", async () => {
|
||||
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-cli-help-"));
|
||||
const specPath = path.join(root, ".hwlab", "hwpod-spec.yaml");
|
||||
try {
|
||||
const workspaceHelp = await runHwpodCli(["workspace", "--help"], { now: () => NOW });
|
||||
assert.equal(workspaceHelp.exitCode, 0);
|
||||
assert.equal(workspaceHelp.payload.action, "hwpod-cli.workspace.help");
|
||||
assert.match(JSON.stringify(workspaceHelp.payload.usage), /workspace cat --file <path>/u);
|
||||
|
||||
const writeHelp = await runHwpodCli(["workspace", "write", "--help"], { now: () => NOW });
|
||||
assert.equal(writeHelp.exitCode, 0);
|
||||
assert.equal(writeHelp.payload.action, "hwpod-cli.workspace.help");
|
||||
assert.match(JSON.stringify(writeHelp.payload.aliases), /remotePath/u);
|
||||
|
||||
const jobHelp = await runHwpodCli(["job", "status", "--help"], { now: () => NOW });
|
||||
assert.equal(jobHelp.exitCode, 0);
|
||||
assert.equal(jobHelp.payload.action, "hwpod-cli.job.help");
|
||||
assert.match(JSON.stringify(jobHelp.payload.usage), /hwpod job status <jobId>/u);
|
||||
|
||||
await runHwpodCtl(["spec", "init", "--spec", specPath, "--node", "pc-host-1"], { now: () => NOW });
|
||||
const catByFile = await runHwpodCli(["workspace", "cat", "--spec", specPath, "--file", "projects/01_baseline/User/main.c", "--dry-run"], { now: () => NOW });
|
||||
assert.equal(catByFile.exitCode, 0);
|
||||
assert.equal(catByFile.payload.plan.ops[0].op, "workspace.cat");
|
||||
assert.equal(catByFile.payload.plan.ops[0].args.path, "projects/01_baseline/User/main.c");
|
||||
|
||||
const writeByRemotePath = await runHwpodCli(["workspace", "write", "--spec", specPath, "--remote-path", "projects/01_baseline/User/main.c", "--content", "int main(void){}", "--dry-run"], { now: () => NOW });
|
||||
assert.equal(writeByRemotePath.exitCode, 0);
|
||||
assert.equal(writeByRemotePath.payload.plan.ops[0].op, "workspace.write");
|
||||
assert.equal(writeByRemotePath.payload.plan.ops[0].args.path, "projects/01_baseline/User/main.c");
|
||||
} finally {
|
||||
await rm(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("hwpod-cli submits compiled node ops to hwlab-api when not dry-run", async () => {
|
||||
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-cli-submit-"));
|
||||
const specPath = path.join(root, ".hwlab", "hwpod-spec.yaml");
|
||||
|
||||
@@ -3,6 +3,7 @@ import { createHash, randomUUID } from "node:crypto";
|
||||
import { closeSync, openSync } from "node:fs";
|
||||
import { copyFile, mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { traceDisplayRows, traceNoiseEventCount, type TraceEventRow } from "./hwlab-cli/trace-renderer.ts";
|
||||
import { readHwpodSpec } from "./hwpod-harness-lib.ts";
|
||||
@@ -19,6 +20,18 @@ const HWLAB_API_KEY_PREFIX = "hwl_live_";
|
||||
const STANDARD_CASE_REPO_PATH = "/root/hwlab-case-registry";
|
||||
const REMOVED_CASE_REPO_PATH = "/root/hwpod-cases";
|
||||
const CASE_TRACE_RENDERER = "tools/src/hwlab-cli/trace-renderer:traceDisplayRows";
|
||||
const SOURCE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
||||
const AGENT_WORKSPACE_SEED_FILES = [
|
||||
{ source: "tools/hwpod-cli.ts", target: "tools/hwpod-cli.ts" },
|
||||
{ source: "tools/hwpod-ctl.ts", target: "tools/hwpod-ctl.ts" },
|
||||
{ source: "tools/hwpod-compiler-cli.ts", target: "tools/hwpod-compiler-cli.ts" },
|
||||
{ source: "tools/src/hwpod-harness-lib.ts", target: "tools/src/hwpod-harness-lib.ts" },
|
||||
{ source: "tools/src/hwpod-node-ops-contract.ts", target: "tools/src/hwpod-node-ops-contract.ts" },
|
||||
{ source: "tools/src/runtime-endpoint-resolver.ts", target: "tools/src/runtime-endpoint-resolver.ts" },
|
||||
{ source: "skills/hwpod-cli/SKILL.md", target: ".agents/skills/hwpod-cli/SKILL.md" },
|
||||
{ source: "skills/hwpod-ctl/SKILL.md", target: ".agents/skills/hwpod-ctl/SKILL.md" },
|
||||
{ source: "skills/hwlab-agent-runtime/SKILL.md", target: ".agents/skills/hwlab-agent-runtime/SKILL.md" }
|
||||
] as const;
|
||||
|
||||
type EnvLike = Record<string, string | undefined>;
|
||||
type ParsedArgs = Record<string, unknown> & { _: string[] };
|
||||
@@ -911,7 +924,17 @@ function agentTraceIdentityArtifact(run: PreparedCaseRun, agent: AgentRunStage)
|
||||
|
||||
async function agentWorkspaceFilesForRun(run: PreparedCaseRun): Promise<AgentWorkspaceFile[]> {
|
||||
const content = await readFile(run.specPath, "utf8");
|
||||
return [{ path: ".hwlab/hwpod-spec.yaml", content, encoding: "utf8" }];
|
||||
const files: AgentWorkspaceFile[] = [{ path: ".hwlab/hwpod-spec.yaml", content, encoding: "utf8" }];
|
||||
for (const entry of AGENT_WORKSPACE_SEED_FILES) {
|
||||
const sourcePath = path.join(SOURCE_ROOT, entry.source);
|
||||
try {
|
||||
files.push({ path: entry.target, content: await readFile(sourcePath, "utf8"), encoding: "utf8" });
|
||||
} catch (error) {
|
||||
if (error?.code === "ENOENT") throw cliError("caserun_agent_workspace_seed_missing", "CaseRun agent workspace seed file is missing", { source: entry.source, sourcePath });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
async function pollAgentResult(context: CaseContext, input: { baseUrl: string; traceId: string; acceptedBody: any; resultUrl?: string; run?: PreparedCaseRun; agent?: AgentRunStage }) {
|
||||
@@ -1016,6 +1039,7 @@ async function renderAgentTaskPrompt(run: PreparedCaseRun, agentTask: PreparedAg
|
||||
"只能修改 isolated subject worktree,不得修改 case registry repo。",
|
||||
"不得修改原 subject repo checkout;所有源码修改必须落在 subjectWorktreePath。",
|
||||
"CaseRun 已在 AgentRun workspace 预装 .hwlab/hwpod-spec.yaml;必须使用这个 run-local HWPOD spec,不要从 prompt 重建 spec。",
|
||||
"CaseRun 已随本次 run 注入当前 v0.2 的 hwpod/hwpod-ctl/hwpod-compiler 工具文件和 HWPOD skill;若标准 hwpod 命令能力缺失,报告 workspace tool seed 问题,不要改走旁路。",
|
||||
"如果 .hwlab/hwpod-spec.yaml 缺失或内容明显不是本次 case,请报告 CaseRun workspace setup 错误,不要自行编造或迁移 spec。",
|
||||
"若 case prompt 要求源码修改,必须只改 subjectWorktreePath;若 prompt 未要求源码修改,则保持 subject 源码不变。",
|
||||
"不要运行 CaseRun 答案执行器;你本人必须通过 hwpod/hwpod-ctl 标准入口触发编译验证。",
|
||||
@@ -1033,7 +1057,7 @@ async function renderAgentTaskPrompt(run: PreparedCaseRun, agentTask: PreparedAg
|
||||
`verificationMode: compile-only build check; no download or runtime smoke unless the case explicitly asks for it`,
|
||||
``,
|
||||
`## Run-local HWPOD spec`,
|
||||
`CaseRun has already placed the run-local spec at \`.hwlab/hwpod-spec.yaml\` in this AgentRun workspace before your turn starts. Treat that file as the authority for workspace/toolchain/debug/IO bindings. Do not create or overwrite it from this prompt.`,
|
||||
`CaseRun has already placed the run-local spec at \`.hwlab/hwpod-spec.yaml\` in this AgentRun workspace before your turn starts, and has seeded the current v0.2 hwpod/hwpod-ctl/hwpod-compiler tool files and HWPOD skills. Treat the run-local spec as the authority for workspace/toolchain/debug/IO bindings. Do not create or overwrite it from this prompt.`,
|
||||
``,
|
||||
`## Task`,
|
||||
agentTask.prompt,
|
||||
|
||||
@@ -101,6 +101,7 @@ export async function runHwpodCli(argv: string[], options: { env?: EnvLike; fetc
|
||||
const parsed = parseOptions(argv);
|
||||
const command = parsed._[0] || "help";
|
||||
if (["help", "--help", "-h"].includes(command)) return result(0, cliHelp(), now);
|
||||
if (parsed.help === true || parsed.h === true) return result(0, hwpodCliCommandHelp(command, parsed), now);
|
||||
if (command === "closeout") return result(0, closeout(parsed), now);
|
||||
|
||||
const specPath = specPathFrom(parsed);
|
||||
@@ -223,6 +224,85 @@ function cliHelp() {
|
||||
});
|
||||
}
|
||||
|
||||
function hwpodCliCommandHelp(command: string, parsed: ParsedArgs) {
|
||||
if (command === "workspace") return hwpodWorkspaceHelp(text(parsed._[1]));
|
||||
if (command === "job") return hwpodJobHelp(text(parsed._[1]));
|
||||
if (command === "build") return hwpodBuildHelp();
|
||||
if (command === "download") return hwpodDownloadHelp();
|
||||
if (command === "uart") return hwpodUartHelp(text(parsed._[1]));
|
||||
if (command === "cmd") return hwpodCmdHelp();
|
||||
return cliHelp();
|
||||
}
|
||||
|
||||
function hwpodWorkspaceHelp(subcommand = "") {
|
||||
return ok("hwpod-cli.workspace.help", {
|
||||
contractVersion: HWPOD_NODE_OPS_CONTRACT_VERSION,
|
||||
command: "workspace",
|
||||
subcommand: subcommand || null,
|
||||
usage: [
|
||||
"hwpod workspace ls [path] --spec .hwlab/hwpod-spec.yaml",
|
||||
"hwpod workspace cat <path> --spec .hwlab/hwpod-spec.yaml",
|
||||
"hwpod workspace read <path> --spec .hwlab/hwpod-spec.yaml",
|
||||
"hwpod workspace cat --file <path> --spec .hwlab/hwpod-spec.yaml",
|
||||
"hwpod workspace cat --remote-path <path> --spec .hwlab/hwpod-spec.yaml",
|
||||
"hwpod workspace write --path <path> --content <text> --expected-sha <sha>",
|
||||
"hwpod workspace replace --path <path> --find <text> --replace <text> --expected-sha <sha>",
|
||||
"hwpod workspace insert-after --path <path> --anchor <text> --line <text> --expected-sha <sha>",
|
||||
"cat patch.txt | hwpod workspace apply-patch --spec .hwlab/hwpod-spec.yaml --reason <reason>"
|
||||
],
|
||||
aliases: {
|
||||
read: "workspace.cat",
|
||||
file: "--path",
|
||||
remotePath: "--path"
|
||||
},
|
||||
boundary: "Workspace file operations are the standard editing path; do not use shell quoting for subject text edits unless debugging cmd.run itself."
|
||||
});
|
||||
}
|
||||
|
||||
function hwpodJobHelp(subcommand = "") {
|
||||
return ok("hwpod-cli.job.help", {
|
||||
contractVersion: HWPOD_NODE_OPS_CONTRACT_VERSION,
|
||||
command: "job",
|
||||
subcommand: subcommand || null,
|
||||
usage: ["hwpod job status <jobId> --spec .hwlab/hwpod-spec.yaml"],
|
||||
boundary: "Job status compiles to cmd.run against the configured Keil CLI; it is not a separate hwpod-node op."
|
||||
});
|
||||
}
|
||||
|
||||
function hwpodBuildHelp() {
|
||||
return ok("hwpod-cli.build.help", {
|
||||
contractVersion: HWPOD_NODE_OPS_CONTRACT_VERSION,
|
||||
usage: ["hwpod build --spec .hwlab/hwpod-spec.yaml", "hwpod build --spec .hwlab/hwpod-spec.yaml --dry-run"],
|
||||
boundary: "Build is a short HWPOD CLI invocation that normally starts an async Keil job through cmd.run."
|
||||
});
|
||||
}
|
||||
|
||||
function hwpodDownloadHelp() {
|
||||
return ok("hwpod-cli.download.help", {
|
||||
contractVersion: HWPOD_NODE_OPS_CONTRACT_VERSION,
|
||||
usage: ["hwpod download --spec .hwlab/hwpod-spec.yaml", "hwpod download --spec .hwlab/hwpod-spec.yaml --dry-run"],
|
||||
boundary: "Download compiles to a Keil flash cmd.run plan; do not add a download-specific hwpod-node op."
|
||||
});
|
||||
}
|
||||
|
||||
function hwpodUartHelp(subcommand = "") {
|
||||
return ok("hwpod-cli.uart.help", {
|
||||
contractVersion: HWPOD_NODE_OPS_CONTRACT_VERSION,
|
||||
command: "uart",
|
||||
subcommand: subcommand || null,
|
||||
usage: ["hwpod uart read --spec .hwlab/hwpod-spec.yaml --port uart1"],
|
||||
boundary: "UART read is compiled to cmd.run against the configured serial-monitor CLI."
|
||||
});
|
||||
}
|
||||
|
||||
function hwpodCmdHelp() {
|
||||
return ok("hwpod-cli.cmd.help", {
|
||||
contractVersion: HWPOD_NODE_OPS_CONTRACT_VERSION,
|
||||
usage: ["hwpod cmd <command> [...argv] --spec .hwlab/hwpod-spec.yaml"],
|
||||
boundary: "cmd.run is the full-capability host execution op; add compiler/CLI combinations before considering new hwpod-node ops."
|
||||
});
|
||||
}
|
||||
|
||||
async function initSpec(parsed: ParsedArgs, now: () => string) {
|
||||
const specPath = specPathFrom(parsed);
|
||||
const document = defaultSpecDocument(parsed, now);
|
||||
@@ -339,12 +419,12 @@ function commandToIntent(parsed: ParsedArgs, stdinText?: string) {
|
||||
if (command === "workspace") {
|
||||
const subcommand = parsed._[1] || "ls";
|
||||
if (subcommand === "ls") return { intent: "workspace.ls", args: { path: text(parsed.path ?? parsed._[2] ?? ".") } };
|
||||
if (subcommand === "cat" || subcommand === "read") return { intent: "workspace.cat", args: { path: requiredText(parsed.path ?? parsed._[2], "path") } };
|
||||
if (subcommand === "cat" || subcommand === "read") return { intent: "workspace.cat", args: { path: requiredWorkspacePath(parsed, 2) } };
|
||||
if (subcommand === "rg") return { intent: "workspace.rg", args: { pattern: requiredText(parsed.pattern ?? parsed._[2], "pattern"), path: text(parsed.path ?? parsed._[3] ?? "."), ignoreCase: parsed.ignoreCase === true } };
|
||||
if (subcommand === "apply-patch") return { intent: "workspace.apply-patch", args: { patch: patchText(parsed.patch ?? parsed.patchText ?? stdinText), patchBase64: text(parsed.patchBase64), reason: text(parsed.reason) } };
|
||||
if (subcommand === "write") return { intent: "workspace.write", args: clean({ path: requiredText(parsed.path ?? parsed._[2], "path"), content: patchText(parsed.content ?? parsed.contentText ?? stdinText), contentBase64: text(parsed.contentBase64), expectedSha: text(parsed.expectedSha), lineEnding: text(parsed.lineEnding), finalNewline: parsed.finalNewline === true, dryRun: parsed.dryRun === true, reason: text(parsed.reason) }) };
|
||||
if (subcommand === "replace") return { intent: "workspace.replace", args: cleanTextEditArgs({ path: requiredText(parsed.path ?? parsed._[2], "path"), find: requiredRawString(parsed.find ?? parsed._[3], "find"), replace: rawString(parsed.replace ?? parsed.replacement ?? parsed._[4]), expectedSha: text(parsed.expectedSha), all: parsed.all === true, dryRun: parsed.dryRun === true, reason: text(parsed.reason) }) };
|
||||
if (subcommand === "insert-after") return { intent: "workspace.insert-after", args: cleanTextEditArgs({ path: requiredText(parsed.path ?? parsed._[2], "path"), anchor: requiredRawString(parsed.anchor ?? parsed._[3], "anchor"), line: requiredRawString(parsed.line ?? parsed.insert ?? parsed.text ?? parsed._[4], "line"), expectedSha: text(parsed.expectedSha), allowMultiple: parsed.allowMultiple === true, dryRun: parsed.dryRun === true, reason: text(parsed.reason) }) };
|
||||
if (subcommand === "write") return { intent: "workspace.write", args: clean({ path: requiredWorkspacePath(parsed, 2), content: patchText(parsed.content ?? parsed.contentText ?? stdinText), contentBase64: text(parsed.contentBase64), expectedSha: text(parsed.expectedSha), lineEnding: text(parsed.lineEnding), finalNewline: parsed.finalNewline === true, dryRun: parsed.dryRun === true, reason: text(parsed.reason) }) };
|
||||
if (subcommand === "replace") return { intent: "workspace.replace", args: cleanTextEditArgs({ path: requiredWorkspacePath(parsed, 2), find: requiredRawString(parsed.find ?? parsed._[3], "find"), replace: rawString(parsed.replace ?? parsed.replacement ?? parsed._[4]), expectedSha: text(parsed.expectedSha), all: parsed.all === true, dryRun: parsed.dryRun === true, reason: text(parsed.reason) }) };
|
||||
if (subcommand === "insert-after") return { intent: "workspace.insert-after", args: cleanTextEditArgs({ path: requiredWorkspacePath(parsed, 2), anchor: requiredRawString(parsed.anchor ?? parsed._[3], "anchor"), line: requiredRawString(parsed.line ?? parsed.insert ?? parsed.text ?? parsed._[4], "line"), expectedSha: text(parsed.expectedSha), allowMultiple: parsed.allowMultiple === true, dryRun: parsed.dryRun === true, reason: text(parsed.reason) }) };
|
||||
throw cliError("unsupported_workspace_command", `unsupported workspace command: ${subcommand}`);
|
||||
}
|
||||
if (command === "build") return { intent: "debug.build", args: clean({ target: text(parsed.target), command: text(parsed.command ?? parsed.commandLine), wait: parsed.wait === true, timeoutMs: numberValue(parsed.timeoutMs), reason: text(parsed.reason) }) };
|
||||
@@ -966,6 +1046,10 @@ function specPathFrom(parsed: ParsedArgs) {
|
||||
return text(parsed.spec ?? parsed.specPath) || DEFAULT_HWPOD_SPEC_PATH;
|
||||
}
|
||||
|
||||
function requiredWorkspacePath(parsed: ParsedArgs, positionalIndex: number) {
|
||||
return requiredText(parsed.path ?? parsed.file ?? parsed.remotePath ?? parsed.remote ?? parsed._[positionalIndex], "path");
|
||||
}
|
||||
|
||||
function result(exitCode: number, payload: any, now: () => string) {
|
||||
return { exitCode, payload: { ...payload, observedAt: now() } };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user