Compare commits

..

3 Commits

Author SHA1 Message Date
xingyue 5209cfa7ac fix(cli): disable YAML anchor/alias + fix biome errors in setup.ts
- Disable aliasDuplicateObjects in YAML stringify to prevent &a1/*a1
  anchors when multiple steps have identical output
- Fix unused discoverAgents function (prefixed with _) and format issue
  in setup.ts
2026-05-23 19:07:36 +08:00
xiaomo c1f04929f4 Merge pull request 'feat(builtin-agent): persist ReAct loop turns as session JSONL' (#434) from feat/turn-jsonl-session into main 2026-05-23 10:48:49 +00:00
xiaoju 211f38bc8d fix(claude-code): include edge prompt in agent prompt as Current Instruction
buildClaudeCodePrompt was dropping ctx.edgePrompt entirely — the graph
transition instruction (e.g. 'Implement the plan') never reached the agent.
Now appended as '## Current Instruction' at the end of the prompt.
2026-05-23 09:46:17 +00:00
4 changed files with 74 additions and 2 deletions
@@ -137,6 +137,75 @@ function apiKeyEnvName(providerName: string): string {
return `${providerName.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_API_KEY`;
}
/**
* Discover uwf-* agent binaries in PATH.
* Returns sorted list of binary names (e.g., ["uwf-hermes", "uwf-claude-code"]).
*/
async function _discoverAgents(): Promise<string[]> {
try {
// Use which -a to find all uwf-* binaries in PATH
const proc = Bun.spawn(["which", "-a", "uwf-hermes", "uwf-claude-code", "uwf-cursor"], {
stdout: "pipe",
stderr: "pipe",
});
const text = await new Response(proc.stdout).text();
await proc.exited;
if (proc.exitCode !== 0) {
// Try alternative approach: search PATH directories manually
const pathEnv = process.env.PATH || "";
const pathDirs = pathEnv.split(":").filter((d) => d.length > 0);
const agents = new Set<string>();
for (const dir of pathDirs) {
try {
if (!existsSync(dir)) continue;
const { readdirSync, statSync } = await import("node:fs");
const entries = readdirSync(dir);
for (const entry of entries) {
if (!entry.startsWith("uwf-") || entry === "uwf") continue;
const fullPath = join(dir, entry);
try {
const stat = statSync(fullPath);
// Check if executable (owner, group, or other has execute bit)
if (stat.isFile() && (stat.mode & 0o111) !== 0) {
agents.add(entry);
}
} catch {
// Skip if can't stat
}
}
} catch {
// Skip inaccessible directories
}
}
return Array.from(agents).sort();
}
// Parse which output - each line is a path to a binary
const paths = text
.trim()
.split("\n")
.filter((line) => line.length > 0);
const agents = new Set<string>();
for (const path of paths) {
const basename = path.split("/").pop();
if (basename?.startsWith("uwf-") && basename !== "uwf") {
agents.add(basename);
}
}
return Array.from(agents).sort();
} catch {
// If all fails, return empty array
return [];
}
}
/**
* Merge setup args into config.yaml structure. Non-destructive — preserves existing entries.
*/
+1 -1
View File
@@ -440,7 +440,7 @@ function collectOrderedSteps(
}
function formatYaml(value: unknown): string {
return stringify(value).trimEnd();
return stringify(value, { aliasDuplicateObjects: false }).trimEnd();
}
function formatCompactStep(index: number, item: OrderedStepItem, outputYaml: string): string {
+1 -1
View File
@@ -7,6 +7,6 @@ export function formatOutput(data: unknown, format: OutputFormat): string {
case "json":
return JSON.stringify(data);
case "yaml":
return stringify(data).trimEnd();
return stringify(data, { aliasDuplicateObjects: false }).trimEnd();
}
}
@@ -49,6 +49,7 @@ export function buildClaudeCodePrompt(ctx: AgentContext): string {
if (historyBlock !== "") {
parts.push("", historyBlock);
}
parts.push("", "## Current Instruction", "", ctx.edgePrompt);
return parts.join("\n");
}
@@ -132,6 +133,8 @@ async function processClaudeOutput(stdout: string, store: Store): Promise<AgentR
async function runClaudeCode(ctx: AgentContext): Promise<AgentRunResult> {
const fullPrompt = buildClaudeCodePrompt(ctx);
log("K7R2M4N8", `prompt for role=${ctx.role} (length=${fullPrompt.length}):\n${fullPrompt}`);
// Try resuming a cached session for re-entry scenarios (e.g. reviewer reject → developer re-entry).
if (!ctx.isFirstVisit) {
const cachedSessionId = await getCachedSessionId(ctx.threadId, ctx.role);