diff --git a/packages/workflow-agent-cursor/__tests__/cursor-agent.test.ts b/packages/workflow-agent-cursor/__tests__/cursor-agent.test.ts index b08301c..9de068c 100644 --- a/packages/workflow-agent-cursor/__tests__/cursor-agent.test.ts +++ b/packages/workflow-agent-cursor/__tests__/cursor-agent.test.ts @@ -4,6 +4,7 @@ import { createCursorAgent, validateCursorAgentConfig } from "../src/index.js"; describe("validateCursorAgentConfig", () => { test("accepts valid config with explicit workspace", () => { const r = validateCursorAgentConfig({ + command: "/usr/local/bin/cursor-agent", model: null, timeout: 0, workspace: "/tmp/test-project", @@ -14,6 +15,7 @@ describe("validateCursorAgentConfig", () => { test("accepts valid config with null workspace and llmProvider", () => { const r = validateCursorAgentConfig({ + command: "/usr/local/bin/cursor-agent", model: null, timeout: 0, workspace: null, @@ -22,8 +24,23 @@ describe("validateCursorAgentConfig", () => { expect(r.ok).toBe(true); }); + test("rejects non-absolute command", () => { + const r = validateCursorAgentConfig({ + command: "cursor-agent", + model: null, + timeout: 0, + workspace: "/tmp/test-project", + llmProvider: null, + }); + expect(r.ok).toBe(false); + if (!r.ok) { + expect(r.error).toContain("absolute path"); + } + }); + test("rejects empty workspace string", () => { const r = validateCursorAgentConfig({ + command: "/usr/local/bin/cursor-agent", model: null, timeout: 0, workspace: "", @@ -37,6 +54,7 @@ describe("validateCursorAgentConfig", () => { test("rejects null workspace without llmProvider", () => { const r = validateCursorAgentConfig({ + command: "/usr/local/bin/cursor-agent", model: null, timeout: 0, workspace: null, @@ -50,6 +68,7 @@ describe("validateCursorAgentConfig", () => { test("rejects negative timeout", () => { const r = validateCursorAgentConfig({ + command: "/usr/local/bin/cursor-agent", model: null, timeout: -1, workspace: "/tmp/test-project", @@ -62,6 +81,7 @@ describe("validateCursorAgentConfig", () => { describe("createCursorAgent", () => { test("returns an AgentFn with explicit workspace", () => { const agent = createCursorAgent({ + command: "/usr/local/bin/cursor-agent", model: null, timeout: 0, workspace: "/tmp/test-project", @@ -72,6 +92,7 @@ describe("createCursorAgent", () => { test("returns an AgentFn with null workspace and llmProvider", () => { const agent = createCursorAgent({ + command: "/usr/local/bin/cursor-agent", model: null, timeout: 0, workspace: null, @@ -83,6 +104,7 @@ describe("createCursorAgent", () => { test("throws on invalid config at construction", () => { expect(() => createCursorAgent({ + command: "/usr/local/bin/cursor-agent", model: null, timeout: -1, workspace: "/tmp/test-project", @@ -94,6 +116,7 @@ describe("createCursorAgent", () => { test("throws when null workspace without llmProvider", () => { expect(() => createCursorAgent({ + command: "/usr/local/bin/cursor-agent", model: null, timeout: 0, workspace: null, diff --git a/packages/workflow-agent-cursor/src/index.ts b/packages/workflow-agent-cursor/src/index.ts index 4278b38..9963651 100644 --- a/packages/workflow-agent-cursor/src/index.ts +++ b/packages/workflow-agent-cursor/src/index.ts @@ -71,7 +71,7 @@ export function createCursorAgent(config: CursorAgentConfig): AgentFn { "--trust", "--force", ]; - const run = await spawnCli(config.command ?? "cursor-agent", args, { + const run = await spawnCli(config.command, args, { cwd: workspace, timeoutMs, }); diff --git a/packages/workflow-agent-cursor/src/types.ts b/packages/workflow-agent-cursor/src/types.ts index cf6c7bb..58735b2 100644 --- a/packages/workflow-agent-cursor/src/types.ts +++ b/packages/workflow-agent-cursor/src/types.ts @@ -1,7 +1,8 @@ import type { LlmProvider } from "@uncaged/workflow-protocol"; export type CursorAgentConfig = { - command: string | null; + /** Absolute path to the cursor-agent CLI binary. */ + command: string; model: string | null; timeout: number; /** Explicit workspace path. When `null`, the agent extracts workspace from AgentContext via a ReAct LLM call. */ diff --git a/packages/workflow-agent-cursor/src/validate-config.ts b/packages/workflow-agent-cursor/src/validate-config.ts index 05e17df..fc667ba 100644 --- a/packages/workflow-agent-cursor/src/validate-config.ts +++ b/packages/workflow-agent-cursor/src/validate-config.ts @@ -1,8 +1,13 @@ +import { isAbsolute } from "node:path"; + import { err, ok, type Result } from "@uncaged/workflow-protocol"; import type { CursorAgentConfig } from "./types.js"; export function validateCursorAgentConfig(config: CursorAgentConfig): Result { + if (!isAbsolute(config.command)) { + return err("command must be an absolute path to the cursor-agent CLI binary"); + } if (config.workspace !== null && config.workspace.length === 0) { return err("workspace must be a non-empty string (absolute path) or null for auto-detection"); } diff --git a/packages/workflow-agent-hermes/__tests__/hermes-agent.test.ts b/packages/workflow-agent-hermes/__tests__/hermes-agent.test.ts index 5a821af..ca6a5e8 100644 --- a/packages/workflow-agent-hermes/__tests__/hermes-agent.test.ts +++ b/packages/workflow-agent-hermes/__tests__/hermes-agent.test.ts @@ -4,14 +4,28 @@ import { createHermesAgent, validateHermesAgentConfig } from "../src/index.js"; describe("validateHermesAgentConfig", () => { test("accepts valid config", () => { const r = validateHermesAgentConfig({ + command: "/usr/local/bin/hermes", model: null, timeout: null, }); expect(r.ok).toBe(true); }); + test("rejects non-absolute command", () => { + const r = validateHermesAgentConfig({ + command: "hermes", + model: null, + timeout: null, + }); + expect(r.ok).toBe(false); + if (!r.ok) { + expect(r.error).toContain("absolute path"); + } + }); + test("rejects negative timeout", () => { const r = validateHermesAgentConfig({ + command: "/usr/local/bin/hermes", model: null, timeout: -5, }); @@ -25,6 +39,7 @@ describe("validateHermesAgentConfig", () => { describe("createHermesAgent", () => { test("returns an AgentFn", () => { const agent = createHermesAgent({ + command: "/usr/local/bin/hermes", model: null, timeout: null, }); diff --git a/packages/workflow-agent-hermes/src/index.ts b/packages/workflow-agent-hermes/src/index.ts index 82e324b..3c3d088 100644 --- a/packages/workflow-agent-hermes/src/index.ts +++ b/packages/workflow-agent-hermes/src/index.ts @@ -47,7 +47,7 @@ export function createHermesAgent(config: HermesAgentConfig): AgentFn { if (config.model !== null) { args.push("--model", config.model); } - const run = await spawnCli(config.command ?? "hermes", args, { + const run = await spawnCli(config.command, args, { cwd: null, timeoutMs, }); diff --git a/packages/workflow-agent-hermes/src/types.ts b/packages/workflow-agent-hermes/src/types.ts index 27f59df..453a463 100644 --- a/packages/workflow-agent-hermes/src/types.ts +++ b/packages/workflow-agent-hermes/src/types.ts @@ -1,5 +1,6 @@ export type HermesAgentConfig = { - command: string | null; + /** Absolute path to the hermes CLI binary. */ + command: string; model: string | null; timeout: number | null; }; diff --git a/packages/workflow-agent-hermes/src/validate-config.ts b/packages/workflow-agent-hermes/src/validate-config.ts index 1d79add..4edcd17 100644 --- a/packages/workflow-agent-hermes/src/validate-config.ts +++ b/packages/workflow-agent-hermes/src/validate-config.ts @@ -1,8 +1,13 @@ +import { isAbsolute } from "node:path"; + import { err, ok, type Result } from "@uncaged/workflow-runtime"; import type { HermesAgentConfig } from "./types.js"; export function validateHermesAgentConfig(config: HermesAgentConfig): Result { + if (!isAbsolute(config.command)) { + return err("command must be an absolute path to the hermes CLI binary"); + } if (config.timeout !== null && config.timeout < 0) { return err("timeout must be null or a non-negative number (milliseconds)"); } diff --git a/packages/workflow-template-develop/bundle-entry.ts b/packages/workflow-template-develop/bundle-entry.ts index 0f3f7f5..82073a9 100644 --- a/packages/workflow-template-develop/bundle-entry.ts +++ b/packages/workflow-template-develop/bundle-entry.ts @@ -31,7 +31,7 @@ const llmProvider = { }; const agent = createCursorAgent({ - command: optionalEnv("WORKFLOW_CURSOR_COMMAND"), + command: requireEnv("WORKFLOW_CURSOR_COMMAND"), model: optionalEnv("WORKFLOW_CURSOR_MODEL"), timeout: optionalEnv("WORKFLOW_CURSOR_TIMEOUT") ? Number(optionalEnv("WORKFLOW_CURSOR_TIMEOUT"))