Refactor workflows to use compileWorkflowSpec from nerve-daemon
Remove workflows/_shared; wire createLlmExtractFn, zodMeta, and createCursorAdapter(mode ask). Plan/implement/publish compile inner specs via daemon. Made-with: Cursor
This commit is contained in:
parent
56ce22fb1b
commit
70fd064bad
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@ -112,6 +112,9 @@ importers:
|
||||
'@uncaged/nerve-core':
|
||||
specifier: link:../../../repos/nerve/packages/core
|
||||
version: link:../../../repos/nerve/packages/core
|
||||
'@uncaged/nerve-daemon':
|
||||
specifier: link:../../../repos/nerve/packages/daemon
|
||||
version: link:../../../repos/nerve/packages/daemon
|
||||
'@uncaged/nerve-workflow-utils':
|
||||
specifier: link:../../../repos/nerve/packages/workflow-utils
|
||||
version: link:../../../repos/nerve/packages/workflow-utils
|
||||
@ -140,6 +143,9 @@ importers:
|
||||
'@uncaged/nerve-core':
|
||||
specifier: link:../../../repos/nerve/packages/core
|
||||
version: link:../../../repos/nerve/packages/core
|
||||
'@uncaged/nerve-daemon':
|
||||
specifier: link:../../../repos/nerve/packages/daemon
|
||||
version: link:../../../repos/nerve/packages/daemon
|
||||
'@uncaged/nerve-workflow-utils':
|
||||
specifier: link:../../../repos/nerve/packages/workflow-utils
|
||||
version: link:../../../repos/nerve/packages/workflow-utils
|
||||
@ -168,6 +174,9 @@ importers:
|
||||
'@uncaged/nerve-core':
|
||||
specifier: link:../../../repos/nerve/packages/core
|
||||
version: link:../../../repos/nerve/packages/core
|
||||
'@uncaged/nerve-daemon':
|
||||
specifier: link:../../../repos/nerve/packages/daemon
|
||||
version: link:../../../repos/nerve/packages/daemon
|
||||
'@uncaged/nerve-workflow-utils':
|
||||
specifier: link:../../../repos/nerve/packages/workflow-utils
|
||||
version: link:../../../repos/nerve/packages/workflow-utils
|
||||
|
||||
@ -1,46 +0,0 @@
|
||||
import { cursorAgent } from "@uncaged/nerve-adapter-cursor";
|
||||
import type { AgentFn, WorkflowContext } from "@uncaged/nerve-core";
|
||||
|
||||
const DEFAULT_MS = 300_000;
|
||||
|
||||
function throwCursorError(error: {
|
||||
kind: string;
|
||||
exitCode?: number;
|
||||
stdout?: string;
|
||||
stderr?: string;
|
||||
message?: string;
|
||||
}): never {
|
||||
if (error.kind === "non_zero_exit") {
|
||||
throw new Error(
|
||||
`cursor-agent: exitCode=${error.exitCode} stdout=${error.stdout} stderr=${error.stderr}`,
|
||||
);
|
||||
}
|
||||
if (error.kind === "timeout") {
|
||||
throw new Error("cursor-agent: timeout");
|
||||
}
|
||||
if (error.kind === "aborted") {
|
||||
throw new Error("cursor-agent: aborted");
|
||||
}
|
||||
throw new Error(`cursor-agent: ${error.message ?? error.kind}`);
|
||||
}
|
||||
|
||||
/** Cursor CLI in `--mode=ask` (planner-style roles). */
|
||||
export function createAskCursorAdapter(config: { model: string; timeout: number | null }): AgentFn {
|
||||
const timeoutMs = config.timeout ?? DEFAULT_MS;
|
||||
return async (prompt: string, context: WorkflowContext): Promise<string> => {
|
||||
const run = await cursorAgent({
|
||||
prompt,
|
||||
mode: "ask",
|
||||
model: config.model,
|
||||
cwd: context.workdir,
|
||||
env: null,
|
||||
timeoutMs,
|
||||
dryRun: context.start.meta.dryRun,
|
||||
abortSignal: context.signal,
|
||||
});
|
||||
if (!run.ok) {
|
||||
throwCursorError(run.error);
|
||||
}
|
||||
return run.value;
|
||||
};
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
import type {
|
||||
Role,
|
||||
RoleSpec,
|
||||
StartStep,
|
||||
WorkflowContext,
|
||||
WorkflowMessage,
|
||||
} from "@uncaged/nerve-core";
|
||||
import type { LlmProvider } from "@uncaged/nerve-workflow-utils";
|
||||
import { extractMetaOrThrow, type ZodMetaSchema } from "@uncaged/nerve-workflow-utils";
|
||||
import type { z } from "zod";
|
||||
|
||||
export function zodMeta<T>(zod: z.ZodType<T>): ZodMetaSchema<T> {
|
||||
return { witness: null, zod };
|
||||
}
|
||||
|
||||
export type CompileRoleSpecDeps = {
|
||||
provider: LlmProvider;
|
||||
createContext: (start: StartStep, messages: WorkflowMessage[]) => WorkflowContext;
|
||||
};
|
||||
|
||||
/**
|
||||
* RFC-003 RoleSpec → runtime Role (extract uses global nerve.yaml provider via `provider`;
|
||||
* dryRun follows each invocation's start frame).
|
||||
*/
|
||||
export function compileRoleSpec<M extends Record<string, unknown>>(
|
||||
spec: RoleSpec<M>,
|
||||
deps: CompileRoleSpecDeps,
|
||||
): Role<M> {
|
||||
return async (start, messages) => {
|
||||
const ctx = deps.createContext(start, messages);
|
||||
const promptText =
|
||||
typeof spec.prompt === "string" ? spec.prompt : await spec.prompt(start, messages);
|
||||
const raw = await spec.adapter(promptText, ctx);
|
||||
const zod = (spec.meta as ZodMetaSchema<M>).zod;
|
||||
const meta = await extractMetaOrThrow(raw, zod, {
|
||||
provider: deps.provider,
|
||||
dryRun: start.meta.dryRun,
|
||||
});
|
||||
return { content: raw, meta };
|
||||
};
|
||||
}
|
||||
|
||||
export function defaultAgentCreateContext(
|
||||
workdir: string,
|
||||
): (start: StartStep, messages: WorkflowMessage[]) => WorkflowContext {
|
||||
return (start, messages) => ({
|
||||
start,
|
||||
messages,
|
||||
workdir,
|
||||
signal: new AbortController().signal,
|
||||
});
|
||||
}
|
||||
@ -1,10 +1,17 @@
|
||||
import type { StartStep, WorkflowDefinition } from "@uncaged/nerve-core";
|
||||
import type {
|
||||
Schema,
|
||||
StartStep,
|
||||
WorkflowContext,
|
||||
WorkflowDefinition,
|
||||
WorkflowMessage,
|
||||
WorkflowSpec,
|
||||
} from "@uncaged/nerve-core";
|
||||
import { END } from "@uncaged/nerve-core";
|
||||
import { compileWorkflowSpec, type CompileWorkflowSpecDeps } from "@uncaged/nerve-daemon";
|
||||
import { createCursorAdapter } from "@uncaged/nerve-adapter-cursor";
|
||||
import { hermesAdapter } from "@uncaged/nerve-adapter-hermes";
|
||||
import type { LlmProvider } from "@uncaged/nerve-workflow-utils";
|
||||
|
||||
import { compileRoleSpec, defaultAgentCreateContext, zodMeta } from "../_shared/rfc003-compile.js";
|
||||
import { createAskCursorAdapter } from "../_shared/cursor-ask-adapter.js";
|
||||
import { createLlmExtractFn, zodMeta } from "@uncaged/nerve-workflow-utils";
|
||||
|
||||
import { coderPrompt } from "./roles/coder/prompt.js";
|
||||
import { coderMetaSchema } from "./roles/coder/index.js";
|
||||
@ -25,49 +32,76 @@ export type BuildSenseGeneratorDeps = {
|
||||
|
||||
const CURSOR_TIMEOUT_MS = 300_000;
|
||||
|
||||
type SenseAgentMeta = Pick<SenseMeta, "planner" | "coder" | "reviewer" | "tester">;
|
||||
|
||||
function defaultAgentCreateContext(
|
||||
workdir: string,
|
||||
): (start: StartStep, messages: WorkflowMessage[]) => WorkflowContext {
|
||||
return (start, messages) => ({
|
||||
start,
|
||||
messages,
|
||||
workdir,
|
||||
signal: new AbortController().signal,
|
||||
});
|
||||
}
|
||||
|
||||
export function buildSenseGenerator({
|
||||
provider,
|
||||
cwd,
|
||||
}: BuildSenseGeneratorDeps): WorkflowDefinition<SenseMeta> {
|
||||
const createContext = defaultAgentCreateContext(cwd);
|
||||
const deps = { provider, createContext };
|
||||
|
||||
const agentRoles = {
|
||||
planner: {
|
||||
adapter: createAskCursorAdapter({ model: "auto", timeout: CURSOR_TIMEOUT_MS }),
|
||||
prompt: async (start: StartStep) => plannerPrompt({ threadId: start.meta.threadId }),
|
||||
meta: zodMeta(plannerMetaSchema),
|
||||
},
|
||||
coder: {
|
||||
adapter: createCursorAdapter({
|
||||
type: "cursor",
|
||||
model: "auto",
|
||||
timeout: CURSOR_TIMEOUT_MS,
|
||||
}),
|
||||
prompt: async (start: StartStep) => coderPrompt({ threadId: start.meta.threadId }),
|
||||
meta: zodMeta(coderMetaSchema),
|
||||
},
|
||||
reviewer: {
|
||||
adapter: hermesAdapter,
|
||||
prompt: async (start: StartStep) =>
|
||||
reviewerPrompt({ threadId: start.meta.threadId, nerveRoot: cwd }),
|
||||
meta: zodMeta(reviewerMetaSchema),
|
||||
},
|
||||
tester: {
|
||||
adapter: hermesAdapter,
|
||||
prompt: async (start: StartStep) =>
|
||||
testerPrompt({ threadId: start.meta.threadId, nerveRoot: cwd }),
|
||||
meta: zodMeta(testerMetaSchema),
|
||||
},
|
||||
const compileDeps: CompileWorkflowSpecDeps = {
|
||||
extractFn: async <T>(raw: string, schema: Schema<T>, dryRun: boolean) =>
|
||||
createLlmExtractFn<T>({ provider, dryRun })(raw, schema),
|
||||
createContext: defaultAgentCreateContext(cwd),
|
||||
};
|
||||
|
||||
const agentSpec: WorkflowSpec<SenseAgentMeta> = {
|
||||
name: "develop-sense",
|
||||
roles: {
|
||||
planner: {
|
||||
adapter: createCursorAdapter({
|
||||
type: "cursor",
|
||||
model: "auto",
|
||||
timeout: CURSOR_TIMEOUT_MS,
|
||||
mode: "ask",
|
||||
}),
|
||||
prompt: async (start: StartStep) => plannerPrompt({ threadId: start.meta.threadId }),
|
||||
meta: zodMeta(plannerMetaSchema),
|
||||
},
|
||||
coder: {
|
||||
adapter: createCursorAdapter({
|
||||
type: "cursor",
|
||||
model: "auto",
|
||||
timeout: CURSOR_TIMEOUT_MS,
|
||||
}),
|
||||
prompt: async (start: StartStep) => coderPrompt({ threadId: start.meta.threadId }),
|
||||
meta: zodMeta(coderMetaSchema),
|
||||
},
|
||||
reviewer: {
|
||||
adapter: hermesAdapter,
|
||||
prompt: async (start: StartStep) =>
|
||||
reviewerPrompt({ threadId: start.meta.threadId, nerveRoot: cwd }),
|
||||
meta: zodMeta(reviewerMetaSchema),
|
||||
},
|
||||
tester: {
|
||||
adapter: hermesAdapter,
|
||||
prompt: async (start: StartStep) =>
|
||||
testerPrompt({ threadId: start.meta.threadId, nerveRoot: cwd }),
|
||||
meta: zodMeta(testerMetaSchema),
|
||||
},
|
||||
},
|
||||
moderator: () => END,
|
||||
};
|
||||
|
||||
const compiled = compileWorkflowSpec(agentSpec, compileDeps);
|
||||
|
||||
return {
|
||||
name: "develop-sense",
|
||||
roles: {
|
||||
planner: compileRoleSpec(agentRoles.planner, deps),
|
||||
coder: compileRoleSpec(agentRoles.coder, deps),
|
||||
reviewer: compileRoleSpec(agentRoles.reviewer, deps),
|
||||
tester: compileRoleSpec(agentRoles.tester, deps),
|
||||
planner: compiled.roles.planner,
|
||||
coder: compiled.roles.coder,
|
||||
reviewer: compiled.roles.reviewer,
|
||||
tester: compiled.roles.tester,
|
||||
committer: buildCommitterRole({ nerveRoot: cwd }),
|
||||
},
|
||||
moderator,
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
"@uncaged/nerve-adapter-cursor": "latest",
|
||||
"@uncaged/nerve-adapter-hermes": "latest",
|
||||
"@uncaged/nerve-core": "latest",
|
||||
"@uncaged/nerve-daemon": "latest",
|
||||
"@uncaged/nerve-workflow-utils": "latest",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
|
||||
@ -1,10 +1,17 @@
|
||||
import type { StartStep, WorkflowDefinition } from "@uncaged/nerve-core";
|
||||
import type {
|
||||
Schema,
|
||||
StartStep,
|
||||
WorkflowContext,
|
||||
WorkflowDefinition,
|
||||
WorkflowMessage,
|
||||
WorkflowSpec,
|
||||
} from "@uncaged/nerve-core";
|
||||
import { END } from "@uncaged/nerve-core";
|
||||
import { compileWorkflowSpec, type CompileWorkflowSpecDeps } from "@uncaged/nerve-daemon";
|
||||
import { createCursorAdapter } from "@uncaged/nerve-adapter-cursor";
|
||||
import { hermesAdapter } from "@uncaged/nerve-adapter-hermes";
|
||||
import type { LlmProvider } from "@uncaged/nerve-workflow-utils";
|
||||
|
||||
import { compileRoleSpec, defaultAgentCreateContext, zodMeta } from "../_shared/rfc003-compile.js";
|
||||
import { createAskCursorAdapter } from "../_shared/cursor-ask-adapter.js";
|
||||
import { createLlmExtractFn, zodMeta } from "@uncaged/nerve-workflow-utils";
|
||||
|
||||
import { coderPrompt } from "./roles/coder/prompt.js";
|
||||
import { coderMetaSchema } from "./roles/coder/index.js";
|
||||
@ -25,49 +32,76 @@ export type BuildWorkflowGeneratorDeps = {
|
||||
|
||||
const CURSOR_TIMEOUT_MS = 300_000;
|
||||
|
||||
type WorkflowAgentMeta = Pick<WorkflowMeta, "planner" | "coder" | "reviewer" | "tester">;
|
||||
|
||||
function defaultAgentCreateContext(
|
||||
workdir: string,
|
||||
): (start: StartStep, messages: WorkflowMessage[]) => WorkflowContext {
|
||||
return (start, messages) => ({
|
||||
start,
|
||||
messages,
|
||||
workdir,
|
||||
signal: new AbortController().signal,
|
||||
});
|
||||
}
|
||||
|
||||
export function buildWorkflowGenerator({
|
||||
provider,
|
||||
nerveRoot,
|
||||
}: BuildWorkflowGeneratorDeps): WorkflowDefinition<WorkflowMeta> {
|
||||
const createContext = defaultAgentCreateContext(nerveRoot);
|
||||
const deps = { provider, createContext };
|
||||
|
||||
const agentRoles = {
|
||||
planner: {
|
||||
adapter: createAskCursorAdapter({ model: "auto", timeout: CURSOR_TIMEOUT_MS }),
|
||||
prompt: async (start: StartStep) => plannerPrompt({ threadId: start.meta.threadId }),
|
||||
meta: zodMeta(plannerMetaSchema),
|
||||
},
|
||||
coder: {
|
||||
adapter: createCursorAdapter({
|
||||
type: "cursor",
|
||||
model: "auto",
|
||||
timeout: CURSOR_TIMEOUT_MS,
|
||||
}),
|
||||
prompt: async (start: StartStep) => coderPrompt({ threadId: start.meta.threadId }),
|
||||
meta: zodMeta(coderMetaSchema),
|
||||
},
|
||||
reviewer: {
|
||||
adapter: hermesAdapter,
|
||||
prompt: async (start: StartStep) =>
|
||||
reviewerPrompt({ threadId: start.meta.threadId, nerveRoot }),
|
||||
meta: zodMeta(reviewerMetaSchema),
|
||||
},
|
||||
tester: {
|
||||
adapter: hermesAdapter,
|
||||
prompt: async (start: StartStep) =>
|
||||
testerPrompt({ threadId: start.meta.threadId, nerveRoot }),
|
||||
meta: zodMeta(testerMetaSchema),
|
||||
},
|
||||
const compileDeps: CompileWorkflowSpecDeps = {
|
||||
extractFn: async <T>(raw: string, schema: Schema<T>, dryRun: boolean) =>
|
||||
createLlmExtractFn<T>({ provider, dryRun })(raw, schema),
|
||||
createContext: defaultAgentCreateContext(nerveRoot),
|
||||
};
|
||||
|
||||
const agentSpec: WorkflowSpec<WorkflowAgentMeta> = {
|
||||
name: "develop-workflow",
|
||||
roles: {
|
||||
planner: {
|
||||
adapter: createCursorAdapter({
|
||||
type: "cursor",
|
||||
model: "auto",
|
||||
timeout: CURSOR_TIMEOUT_MS,
|
||||
mode: "ask",
|
||||
}),
|
||||
prompt: async (start: StartStep) => plannerPrompt({ threadId: start.meta.threadId }),
|
||||
meta: zodMeta(plannerMetaSchema),
|
||||
},
|
||||
coder: {
|
||||
adapter: createCursorAdapter({
|
||||
type: "cursor",
|
||||
model: "auto",
|
||||
timeout: CURSOR_TIMEOUT_MS,
|
||||
}),
|
||||
prompt: async (start: StartStep) => coderPrompt({ threadId: start.meta.threadId }),
|
||||
meta: zodMeta(coderMetaSchema),
|
||||
},
|
||||
reviewer: {
|
||||
adapter: hermesAdapter,
|
||||
prompt: async (start: StartStep) =>
|
||||
reviewerPrompt({ threadId: start.meta.threadId, nerveRoot }),
|
||||
meta: zodMeta(reviewerMetaSchema),
|
||||
},
|
||||
tester: {
|
||||
adapter: hermesAdapter,
|
||||
prompt: async (start: StartStep) =>
|
||||
testerPrompt({ threadId: start.meta.threadId, nerveRoot }),
|
||||
meta: zodMeta(testerMetaSchema),
|
||||
},
|
||||
},
|
||||
moderator: () => END,
|
||||
};
|
||||
|
||||
const compiled = compileWorkflowSpec(agentSpec, compileDeps);
|
||||
|
||||
return {
|
||||
name: "develop-workflow",
|
||||
roles: {
|
||||
planner: compileRoleSpec(agentRoles.planner, deps),
|
||||
coder: compileRoleSpec(agentRoles.coder, deps),
|
||||
reviewer: compileRoleSpec(agentRoles.reviewer, deps),
|
||||
tester: compileRoleSpec(agentRoles.tester, deps),
|
||||
planner: compiled.roles.planner,
|
||||
coder: compiled.roles.coder,
|
||||
reviewer: compiled.roles.reviewer,
|
||||
tester: compiled.roles.tester,
|
||||
committer: buildCommitterRole({ nerveRoot }),
|
||||
},
|
||||
moderator,
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
"@uncaged/nerve-adapter-cursor": "latest",
|
||||
"@uncaged/nerve-adapter-hermes": "latest",
|
||||
"@uncaged/nerve-core": "latest",
|
||||
"@uncaged/nerve-daemon": "latest",
|
||||
"@uncaged/nerve-workflow-utils": "latest",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
|
||||
@ -1,8 +1,16 @@
|
||||
import type { StartStep, WorkflowDefinition } from "@uncaged/nerve-core";
|
||||
import type {
|
||||
Schema,
|
||||
StartStep,
|
||||
WorkflowContext,
|
||||
WorkflowDefinition,
|
||||
WorkflowMessage,
|
||||
WorkflowSpec,
|
||||
} from "@uncaged/nerve-core";
|
||||
import { END } from "@uncaged/nerve-core";
|
||||
import { compileWorkflowSpec, type CompileWorkflowSpecDeps } from "@uncaged/nerve-daemon";
|
||||
import { hermesAdapter } from "@uncaged/nerve-adapter-hermes";
|
||||
import type { LlmProvider } from "@uncaged/nerve-workflow-utils";
|
||||
|
||||
import { compileRoleSpec, defaultAgentCreateContext, zodMeta } from "../_shared/rfc003-compile.js";
|
||||
import { createLlmExtractFn, zodMeta } from "@uncaged/nerve-workflow-utils";
|
||||
|
||||
import { moderator } from "./moderator.js";
|
||||
import type { WorkflowMeta } from "./moderator.js";
|
||||
@ -23,43 +31,65 @@ export type BuildSolveIssueDeps = {
|
||||
provider: LlmProvider;
|
||||
};
|
||||
|
||||
export function buildSolveIssue({ nerveRoot, provider }: BuildSolveIssueDeps): WorkflowDefinition<WorkflowMeta> {
|
||||
const createContext = defaultAgentCreateContext(nerveRoot);
|
||||
const deps = { provider, createContext };
|
||||
type SolveHermesAgentMeta = Pick<WorkflowMeta, "read-issue" | "prepare" | "review" | "test">;
|
||||
|
||||
const agentRoles = {
|
||||
"read-issue": {
|
||||
adapter: hermesAdapter,
|
||||
prompt: async (start: StartStep) => readIssuePrompt({ threadId: start.meta.threadId }),
|
||||
meta: zodMeta(readIssueMetaSchema),
|
||||
},
|
||||
prepare: {
|
||||
adapter: hermesAdapter,
|
||||
prompt: async (start: StartStep) => preparePrompt({ threadId: start.meta.threadId }),
|
||||
meta: zodMeta(prepareMetaSchema),
|
||||
},
|
||||
review: {
|
||||
adapter: hermesAdapter,
|
||||
prompt: async (start: StartStep) =>
|
||||
reviewPrompt({ threadId: start.meta.threadId, nerveRoot }),
|
||||
meta: zodMeta(reviewMetaSchema),
|
||||
},
|
||||
test: {
|
||||
adapter: hermesAdapter,
|
||||
prompt: async (start: StartStep) => testPrompt({ threadId: start.meta.threadId }),
|
||||
meta: zodMeta(testMetaSchema),
|
||||
},
|
||||
function defaultAgentCreateContext(
|
||||
workdir: string,
|
||||
): (start: StartStep, messages: WorkflowMessage[]) => WorkflowContext {
|
||||
return (start, messages) => ({
|
||||
start,
|
||||
messages,
|
||||
workdir,
|
||||
signal: new AbortController().signal,
|
||||
});
|
||||
}
|
||||
|
||||
export function buildSolveIssue({ nerveRoot, provider }: BuildSolveIssueDeps): WorkflowDefinition<WorkflowMeta> {
|
||||
const compileDeps: CompileWorkflowSpecDeps = {
|
||||
extractFn: async <T>(raw: string, schema: Schema<T>, dryRun: boolean) =>
|
||||
createLlmExtractFn<T>({ provider, dryRun })(raw, schema),
|
||||
createContext: defaultAgentCreateContext(nerveRoot),
|
||||
};
|
||||
|
||||
const agentSpec: WorkflowSpec<SolveHermesAgentMeta> = {
|
||||
name: "solve-issue",
|
||||
roles: {
|
||||
"read-issue": {
|
||||
adapter: hermesAdapter,
|
||||
prompt: async (start: StartStep) => readIssuePrompt({ threadId: start.meta.threadId }),
|
||||
meta: zodMeta(readIssueMetaSchema),
|
||||
},
|
||||
prepare: {
|
||||
adapter: hermesAdapter,
|
||||
prompt: async (start: StartStep) => preparePrompt({ threadId: start.meta.threadId }),
|
||||
meta: zodMeta(prepareMetaSchema),
|
||||
},
|
||||
review: {
|
||||
adapter: hermesAdapter,
|
||||
prompt: async (start: StartStep) =>
|
||||
reviewPrompt({ threadId: start.meta.threadId, nerveRoot }),
|
||||
meta: zodMeta(reviewMetaSchema),
|
||||
},
|
||||
test: {
|
||||
adapter: hermesAdapter,
|
||||
prompt: async (start: StartStep) => testPrompt({ threadId: start.meta.threadId }),
|
||||
meta: zodMeta(testMetaSchema),
|
||||
},
|
||||
},
|
||||
moderator: () => END,
|
||||
};
|
||||
|
||||
const compiled = compileWorkflowSpec(agentSpec, compileDeps);
|
||||
|
||||
return {
|
||||
name: "solve-issue",
|
||||
roles: {
|
||||
"read-issue": compileRoleSpec(agentRoles["read-issue"], deps),
|
||||
prepare: compileRoleSpec(agentRoles.prepare, deps),
|
||||
"read-issue": compiled.roles["read-issue"],
|
||||
prepare: compiled.roles.prepare,
|
||||
plan: buildPlanRole({ provider, nerveRoot }),
|
||||
implement: buildImplementRole({ provider, nerveRoot }),
|
||||
review: compileRoleSpec(agentRoles.review, deps),
|
||||
test: compileRoleSpec(agentRoles.test, deps),
|
||||
review: compiled.roles.review,
|
||||
test: compiled.roles.test,
|
||||
publish: buildPublishRole({ provider, nerveRoot }),
|
||||
},
|
||||
moderator,
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
"@uncaged/nerve-adapter-cursor": "latest",
|
||||
"@uncaged/nerve-adapter-hermes": "latest",
|
||||
"@uncaged/nerve-core": "latest",
|
||||
"@uncaged/nerve-daemon": "latest",
|
||||
"@uncaged/nerve-workflow-utils": "latest",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import type { Role, RoleResult, RoleSpec, StartStep, WorkflowMessage } from "@uncaged/nerve-core";
|
||||
import type { Role, RoleResult, Schema, StartStep, WorkflowMessage } from "@uncaged/nerve-core";
|
||||
import { END } from "@uncaged/nerve-core";
|
||||
import { compileWorkflowSpec } from "@uncaged/nerve-daemon";
|
||||
import { createCursorAdapter } from "@uncaged/nerve-adapter-cursor";
|
||||
import type { LlmProvider } from "@uncaged/nerve-workflow-utils";
|
||||
import { createLlmExtractFn, zodMeta } from "@uncaged/nerve-workflow-utils";
|
||||
import { z } from "zod";
|
||||
|
||||
import { compileRoleSpec, zodMeta } from "../../../_shared/rfc003-compile.js";
|
||||
|
||||
import { resolveRepoCwd } from "../../lib/repo-context.js";
|
||||
import { buildImplementPrompt } from "./prompt.js";
|
||||
|
||||
@ -21,20 +22,6 @@ export type BuildImplementDeps = {
|
||||
const CURSOR_TIMEOUT_MS = 300_000;
|
||||
|
||||
export function buildImplementRole({ provider, nerveRoot }: BuildImplementDeps): Role<ImplementMeta> {
|
||||
const innerSpec = {
|
||||
adapter: createCursorAdapter({
|
||||
type: "cursor",
|
||||
model: "auto",
|
||||
timeout: CURSOR_TIMEOUT_MS,
|
||||
}),
|
||||
prompt: async (start: StartStep) =>
|
||||
buildImplementPrompt({
|
||||
threadId: start.meta.threadId,
|
||||
nerveRoot,
|
||||
}),
|
||||
meta: zodMeta(implementMetaSchema),
|
||||
} satisfies RoleSpec<ImplementMeta>;
|
||||
|
||||
return async (start: StartStep, messages: WorkflowMessage[]): Promise<RoleResult<ImplementMeta>> => {
|
||||
const cwd = resolveRepoCwd(messages);
|
||||
if (cwd === null) {
|
||||
@ -44,18 +31,42 @@ export function buildImplementRole({ provider, nerveRoot }: BuildImplementDeps):
|
||||
};
|
||||
}
|
||||
|
||||
const run = compileRoleSpec(innerSpec, {
|
||||
provider,
|
||||
createContext: (s, m) => ({
|
||||
start: s,
|
||||
messages: m,
|
||||
workdir: cwd,
|
||||
signal: new AbortController().signal,
|
||||
}),
|
||||
});
|
||||
const innerSpec = {
|
||||
main: {
|
||||
adapter: createCursorAdapter({
|
||||
type: "cursor",
|
||||
model: "auto",
|
||||
timeout: CURSOR_TIMEOUT_MS,
|
||||
}),
|
||||
prompt: async (start: StartStep) =>
|
||||
buildImplementPrompt({
|
||||
threadId: start.meta.threadId,
|
||||
nerveRoot,
|
||||
}),
|
||||
meta: zodMeta(implementMetaSchema),
|
||||
},
|
||||
};
|
||||
|
||||
const compiled = compileWorkflowSpec(
|
||||
{
|
||||
name: "_implement-inner",
|
||||
roles: innerSpec,
|
||||
moderator: () => END,
|
||||
},
|
||||
{
|
||||
extractFn: async <T>(raw: string, schema: Schema<T>, dryRun: boolean) =>
|
||||
createLlmExtractFn<T>({ provider, dryRun })(raw, schema),
|
||||
createContext: (s, m) => ({
|
||||
start: s,
|
||||
messages: m,
|
||||
workdir: cwd,
|
||||
signal: new AbortController().signal,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
return await run(start, messages);
|
||||
return await compiled.roles.main(start, messages);
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : String(e);
|
||||
return {
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import type { Role, RoleResult, RoleSpec, StartStep, WorkflowMessage } from "@uncaged/nerve-core";
|
||||
import { createAskCursorAdapter } from "../../../_shared/cursor-ask-adapter.js";
|
||||
import { compileRoleSpec, zodMeta } from "../../../_shared/rfc003-compile.js";
|
||||
import type { Role, RoleResult, Schema, StartStep, WorkflowMessage } from "@uncaged/nerve-core";
|
||||
import { END } from "@uncaged/nerve-core";
|
||||
import { compileWorkflowSpec } from "@uncaged/nerve-daemon";
|
||||
import { createCursorAdapter } from "@uncaged/nerve-adapter-cursor";
|
||||
import type { LlmProvider } from "@uncaged/nerve-workflow-utils";
|
||||
import { createLlmExtractFn, zodMeta } from "@uncaged/nerve-workflow-utils";
|
||||
import { z } from "zod";
|
||||
|
||||
import { resolveRepoCwd } from "../../lib/repo-context.js";
|
||||
@ -20,16 +22,6 @@ export type BuildPlanDeps = {
|
||||
const CURSOR_TIMEOUT_MS = 300_000;
|
||||
|
||||
export function buildPlanRole({ provider, nerveRoot }: BuildPlanDeps): Role<PlanMeta> {
|
||||
const innerSpec = {
|
||||
adapter: createAskCursorAdapter({ model: "auto", timeout: CURSOR_TIMEOUT_MS }),
|
||||
prompt: async (start: StartStep) =>
|
||||
buildPlanPrompt({
|
||||
threadId: start.meta.threadId,
|
||||
nerveRoot,
|
||||
}),
|
||||
meta: zodMeta(planMetaSchema),
|
||||
} satisfies RoleSpec<PlanMeta>;
|
||||
|
||||
return async (start: StartStep, messages: WorkflowMessage[]): Promise<RoleResult<PlanMeta>> => {
|
||||
const cwd = resolveRepoCwd(messages);
|
||||
if (cwd === null) {
|
||||
@ -39,18 +31,43 @@ export function buildPlanRole({ provider, nerveRoot }: BuildPlanDeps): Role<Plan
|
||||
};
|
||||
}
|
||||
|
||||
const run = compileRoleSpec(innerSpec, {
|
||||
provider,
|
||||
createContext: (s, m) => ({
|
||||
start: s,
|
||||
messages: m,
|
||||
workdir: cwd,
|
||||
signal: new AbortController().signal,
|
||||
}),
|
||||
});
|
||||
const innerSpec = {
|
||||
main: {
|
||||
adapter: createCursorAdapter({
|
||||
type: "cursor",
|
||||
model: "auto",
|
||||
timeout: CURSOR_TIMEOUT_MS,
|
||||
mode: "ask",
|
||||
}),
|
||||
prompt: async (start: StartStep) =>
|
||||
buildPlanPrompt({
|
||||
threadId: start.meta.threadId,
|
||||
nerveRoot,
|
||||
}),
|
||||
meta: zodMeta(planMetaSchema),
|
||||
},
|
||||
};
|
||||
|
||||
const compiled = compileWorkflowSpec(
|
||||
{
|
||||
name: "_plan-inner",
|
||||
roles: innerSpec,
|
||||
moderator: () => END,
|
||||
},
|
||||
{
|
||||
extractFn: async <T>(raw: string, schema: Schema<T>, dryRun: boolean) =>
|
||||
createLlmExtractFn<T>({ provider, dryRun })(raw, schema),
|
||||
createContext: (s, m) => ({
|
||||
start: s,
|
||||
messages: m,
|
||||
workdir: cwd,
|
||||
signal: new AbortController().signal,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
return await run(start, messages);
|
||||
return await compiled.roles.main(start, messages);
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : String(e);
|
||||
return {
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { mkdirSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import type { Role, RoleResult, RoleSpec, StartStep, WorkflowMessage } from "@uncaged/nerve-core";
|
||||
import type { Role, RoleResult, Schema, StartStep, WorkflowMessage } from "@uncaged/nerve-core";
|
||||
import { END } from "@uncaged/nerve-core";
|
||||
import { compileWorkflowSpec } from "@uncaged/nerve-daemon";
|
||||
import { hermesAdapter } from "@uncaged/nerve-adapter-hermes";
|
||||
import type { LlmProvider } from "@uncaged/nerve-workflow-utils";
|
||||
import { isDryRun } from "@uncaged/nerve-workflow-utils";
|
||||
import { createLlmExtractFn, isDryRun, zodMeta } from "@uncaged/nerve-workflow-utils";
|
||||
import { z } from "zod";
|
||||
|
||||
import { compileRoleSpec, defaultAgentCreateContext, zodMeta } from "../../../_shared/rfc003-compile.js";
|
||||
|
||||
import { buildPublishPrompt } from "./prompt.js";
|
||||
|
||||
export const publishMetaSchema = z.object({
|
||||
@ -20,21 +20,38 @@ export type BuildPublishDeps = {
|
||||
nerveRoot: string;
|
||||
};
|
||||
|
||||
function defaultAgentCreateContext(nerveRoot: string) {
|
||||
return (start: StartStep, messages: WorkflowMessage[]) => ({
|
||||
start,
|
||||
messages,
|
||||
workdir: nerveRoot,
|
||||
signal: new AbortController().signal,
|
||||
});
|
||||
}
|
||||
|
||||
function logPath(nerveRoot: string): string {
|
||||
return join(nerveRoot, "logs", `solve-issue-publish-${Date.now()}.log`);
|
||||
}
|
||||
|
||||
export function buildPublishRole({ provider, nerveRoot }: BuildPublishDeps): Role<PublishMeta> {
|
||||
const innerSpec = {
|
||||
adapter: hermesAdapter,
|
||||
prompt: async (start: StartStep) => buildPublishPrompt({ threadId: start.meta.threadId, nerveRoot }),
|
||||
meta: zodMeta(publishMetaSchema),
|
||||
} satisfies RoleSpec<PublishMeta>;
|
||||
|
||||
const runHermes = compileRoleSpec(innerSpec, {
|
||||
provider,
|
||||
createContext: defaultAgentCreateContext(nerveRoot),
|
||||
});
|
||||
const runHermes = compileWorkflowSpec(
|
||||
{
|
||||
name: "_publish-inner",
|
||||
roles: {
|
||||
main: {
|
||||
adapter: hermesAdapter,
|
||||
prompt: async (start: StartStep) => buildPublishPrompt({ threadId: start.meta.threadId, nerveRoot }),
|
||||
meta: zodMeta(publishMetaSchema),
|
||||
},
|
||||
},
|
||||
moderator: () => END,
|
||||
},
|
||||
{
|
||||
extractFn: async <T>(raw: string, schema: Schema<T>, dryRun: boolean) =>
|
||||
createLlmExtractFn<T>({ provider, dryRun })(raw, schema),
|
||||
createContext: defaultAgentCreateContext(nerveRoot),
|
||||
},
|
||||
).roles.main;
|
||||
|
||||
return async (start: StartStep, messages: WorkflowMessage[]): Promise<RoleResult<PublishMeta>> => {
|
||||
const file = logPath(nerveRoot);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user