diff --git a/packages/cli-workflow/src/commands/setup.ts b/packages/cli-workflow/src/commands/setup.ts index aa4ef61..1014a60 100644 --- a/packages/cli-workflow/src/commands/setup.ts +++ b/packages/cli-workflow/src/commands/setup.ts @@ -137,6 +137,72 @@ 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 { + 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(); + + 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(); + + for (const path of paths) { + const basename = path.split("/").pop(); + if (basename && 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. */ diff --git a/packages/workflow-agent-claude-code/src/claude-code.ts b/packages/workflow-agent-claude-code/src/claude-code.ts index 8a34a60..a5af640 100644 --- a/packages/workflow-agent-claude-code/src/claude-code.ts +++ b/packages/workflow-agent-claude-code/src/claude-code.ts @@ -51,6 +51,7 @@ export function buildClaudeCodePrompt(ctx: AgentContext): string { if (historyBlock !== "") { parts.push("", historyBlock); } + parts.push("", "## Current Instruction", "", ctx.edgePrompt); return parts.join("\n"); } @@ -134,6 +135,8 @@ async function processClaudeOutput(stdout: string, store: Store): Promise { 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);