refactor: extract @uncaged/workflow-util-agent + smart prompt
- New package: spawn-cli + build-agent-prompt shared utils - Smart prompt: start + meta summaries for middle steps + last step full - Cursor/Hermes adapters now import from util-agent (no duplicate code) - 109 tests pass, biome clean Closes #14 小橘 <xiaoju@shazhou.work>
This commit is contained in:
@@ -1,26 +1,5 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { START, type ThreadContext } from "@uncaged/workflow";
|
import { createCursorAgent, validateCursorAgentConfig } from "../src/index.js";
|
||||||
|
|
||||||
import { buildAgentPrompt, createCursorAgent, validateCursorAgentConfig } from "../src/index.js";
|
|
||||||
|
|
||||||
function makeCtx(): ThreadContext {
|
|
||||||
return {
|
|
||||||
start: {
|
|
||||||
role: START,
|
|
||||||
content: "user task",
|
|
||||||
meta: { maxRounds: 5 },
|
|
||||||
timestamp: 1,
|
|
||||||
},
|
|
||||||
steps: [
|
|
||||||
{
|
|
||||||
role: "coder",
|
|
||||||
content: "first draft",
|
|
||||||
meta: {},
|
|
||||||
timestamp: 2,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("validateCursorAgentConfig", () => {
|
describe("validateCursorAgentConfig", () => {
|
||||||
test("accepts valid config", () => {
|
test("accepts valid config", () => {
|
||||||
@@ -54,16 +33,6 @@ describe("validateCursorAgentConfig", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("buildAgentPrompt", () => {
|
|
||||||
test("includes system prompt, start, and steps", () => {
|
|
||||||
const text = buildAgentPrompt(makeCtx(), "Be helpful.");
|
|
||||||
expect(text).toContain("Be helpful.");
|
|
||||||
expect(text).toContain("user task");
|
|
||||||
expect(text).toContain("coder");
|
|
||||||
expect(text).toContain("first draft");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("createCursorAgent", () => {
|
describe("createCursorAgent", () => {
|
||||||
test("returns an AgentFn", () => {
|
test("returns an AgentFn", () => {
|
||||||
const agent = createCursorAgent({
|
const agent = createCursorAgent({
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"test": "bun test"
|
"test": "bun test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@uncaged/workflow": "workspace:*"
|
"@uncaged/workflow": "workspace:*",
|
||||||
|
"@uncaged/workflow-util-agent": "workspace:*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
import type { ThreadContext } from "@uncaged/workflow";
|
|
||||||
|
|
||||||
/** Combines the role system prompt with thread start content and prior role outputs. */
|
|
||||||
export function buildAgentPrompt(ctx: ThreadContext, systemPrompt: string): string {
|
|
||||||
const blocks: string[] = [];
|
|
||||||
blocks.push("# System instructions");
|
|
||||||
blocks.push(systemPrompt);
|
|
||||||
blocks.push("");
|
|
||||||
blocks.push("# Thread");
|
|
||||||
blocks.push("## Start");
|
|
||||||
blocks.push(ctx.start.content);
|
|
||||||
for (const step of ctx.steps) {
|
|
||||||
blocks.push(`## Role: ${step.role}`);
|
|
||||||
blocks.push(step.content);
|
|
||||||
}
|
|
||||||
return blocks.join("\n");
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
import type { AgentFn } from "@uncaged/workflow";
|
import type { AgentFn } from "@uncaged/workflow";
|
||||||
|
import { buildAgentPrompt, type SpawnCliError, spawnCli } from "@uncaged/workflow-util-agent";
|
||||||
|
|
||||||
import { buildAgentPrompt } from "./build-agent-prompt.js";
|
|
||||||
import { type SpawnCliError, spawnCli } from "./spawn-cli.js";
|
|
||||||
import type { CursorAgentConfig } from "./types.js";
|
import type { CursorAgentConfig } from "./types.js";
|
||||||
import { validateCursorAgentConfig } from "./validate-config.js";
|
import { validateCursorAgentConfig } from "./validate-config.js";
|
||||||
|
|
||||||
export { buildAgentPrompt } from "./build-agent-prompt.js";
|
export { buildAgentPrompt } from "@uncaged/workflow-util-agent";
|
||||||
export type { CursorAgentConfig } from "./types.js";
|
export type { CursorAgentConfig } from "./types.js";
|
||||||
export { validateCursorAgentConfig } from "./validate-config.js";
|
export { validateCursorAgentConfig } from "./validate-config.js";
|
||||||
|
|
||||||
@@ -39,7 +38,7 @@ export function createCursorAgent(config: CursorAgentConfig): AgentFn {
|
|||||||
const timeoutMs = config.timeout;
|
const timeoutMs = config.timeout;
|
||||||
|
|
||||||
return async (ctx, systemPrompt) => {
|
return async (ctx, systemPrompt) => {
|
||||||
const fullPrompt = buildAgentPrompt(ctx, systemPrompt);
|
const fullPrompt = buildAgentPrompt(systemPrompt, ctx);
|
||||||
const args = [
|
const args = [
|
||||||
"-p",
|
"-p",
|
||||||
fullPrompt,
|
fullPrompt,
|
||||||
|
|||||||
@@ -6,5 +6,5 @@
|
|||||||
"composite": true
|
"composite": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts"],
|
"include": ["src/**/*.ts"],
|
||||||
"references": [{ "path": "../workflow" }]
|
"references": [{ "path": "../workflow" }, { "path": "../workflow-util-agent" }]
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,19 +1,5 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { START, type ThreadContext } from "@uncaged/workflow";
|
import { createHermesAgent, validateHermesAgentConfig } from "../src/index.js";
|
||||||
|
|
||||||
import { buildAgentPrompt, createHermesAgent, validateHermesAgentConfig } from "../src/index.js";
|
|
||||||
|
|
||||||
function makeCtx(): ThreadContext {
|
|
||||||
return {
|
|
||||||
start: {
|
|
||||||
role: START,
|
|
||||||
content: "plan the migration",
|
|
||||||
meta: { maxRounds: 8 },
|
|
||||||
timestamp: 1,
|
|
||||||
},
|
|
||||||
steps: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("validateHermesAgentConfig", () => {
|
describe("validateHermesAgentConfig", () => {
|
||||||
test("accepts valid config", () => {
|
test("accepts valid config", () => {
|
||||||
@@ -36,14 +22,6 @@ describe("validateHermesAgentConfig", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("buildAgentPrompt", () => {
|
|
||||||
test("includes system and thread start", () => {
|
|
||||||
const text = buildAgentPrompt(makeCtx(), "You are a planner.");
|
|
||||||
expect(text).toContain("You are a planner.");
|
|
||||||
expect(text).toContain("plan the migration");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("createHermesAgent", () => {
|
describe("createHermesAgent", () => {
|
||||||
test("returns an AgentFn", () => {
|
test("returns an AgentFn", () => {
|
||||||
const agent = createHermesAgent({
|
const agent = createHermesAgent({
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"test": "bun test"
|
"test": "bun test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@uncaged/workflow": "workspace:*"
|
"@uncaged/workflow": "workspace:*",
|
||||||
|
"@uncaged/workflow-util-agent": "workspace:*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
import type { ThreadContext } from "@uncaged/workflow";
|
|
||||||
|
|
||||||
/** Combines the role system prompt with thread start content and prior role outputs. */
|
|
||||||
export function buildAgentPrompt(ctx: ThreadContext, systemPrompt: string): string {
|
|
||||||
const blocks: string[] = [];
|
|
||||||
blocks.push("# System instructions");
|
|
||||||
blocks.push(systemPrompt);
|
|
||||||
blocks.push("");
|
|
||||||
blocks.push("# Thread");
|
|
||||||
blocks.push("## Start");
|
|
||||||
blocks.push(ctx.start.content);
|
|
||||||
for (const step of ctx.steps) {
|
|
||||||
blocks.push(`## Role: ${step.role}`);
|
|
||||||
blocks.push(step.content);
|
|
||||||
}
|
|
||||||
return blocks.join("\n");
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
import type { AgentFn } from "@uncaged/workflow";
|
import type { AgentFn } from "@uncaged/workflow";
|
||||||
|
import { buildAgentPrompt, type SpawnCliError, spawnCli } from "@uncaged/workflow-util-agent";
|
||||||
|
|
||||||
import { buildAgentPrompt } from "./build-agent-prompt.js";
|
|
||||||
import { type SpawnCliError, spawnCli } from "./spawn-cli.js";
|
|
||||||
import type { HermesAgentConfig } from "./types.js";
|
import type { HermesAgentConfig } from "./types.js";
|
||||||
import { validateHermesAgentConfig } from "./validate-config.js";
|
import { validateHermesAgentConfig } from "./validate-config.js";
|
||||||
|
|
||||||
const HERMES_DEFAULT_MAX_TURNS = 90;
|
const HERMES_DEFAULT_MAX_TURNS = 90;
|
||||||
|
|
||||||
export { buildAgentPrompt } from "./build-agent-prompt.js";
|
export { buildAgentPrompt } from "@uncaged/workflow-util-agent";
|
||||||
export type { HermesAgentConfig } from "./types.js";
|
export type { HermesAgentConfig } from "./types.js";
|
||||||
export { validateHermesAgentConfig } from "./validate-config.js";
|
export { validateHermesAgentConfig } from "./validate-config.js";
|
||||||
|
|
||||||
@@ -36,7 +35,7 @@ export function createHermesAgent(config: HermesAgentConfig): AgentFn {
|
|||||||
const timeoutMs = config.timeout;
|
const timeoutMs = config.timeout;
|
||||||
|
|
||||||
return async (ctx, systemPrompt) => {
|
return async (ctx, systemPrompt) => {
|
||||||
const fullPrompt = buildAgentPrompt(ctx, systemPrompt);
|
const fullPrompt = buildAgentPrompt(systemPrompt, ctx);
|
||||||
const args = [
|
const args = [
|
||||||
"chat",
|
"chat",
|
||||||
"-q",
|
"-q",
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
import { spawn } from "node:child_process";
|
|
||||||
|
|
||||||
import { err, ok, type Result } from "@uncaged/workflow";
|
|
||||||
|
|
||||||
export type SpawnCliError =
|
|
||||||
| { kind: "non_zero_exit"; exitCode: number | null; stdout: string; stderr: string }
|
|
||||||
| { kind: "timeout" }
|
|
||||||
| { kind: "spawn_failed"; message: string };
|
|
||||||
|
|
||||||
export function spawnCli(
|
|
||||||
command: string,
|
|
||||||
args: string[],
|
|
||||||
options: { cwd: string | null; timeoutMs: number | null },
|
|
||||||
): Promise<Result<string, SpawnCliError>> {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const child = spawn(command, args, {
|
|
||||||
cwd: options.cwd === null ? undefined : options.cwd,
|
|
||||||
shell: false,
|
|
||||||
stdio: ["ignore", "pipe", "pipe"],
|
|
||||||
});
|
|
||||||
|
|
||||||
let stdout = "";
|
|
||||||
let stderr = "";
|
|
||||||
child.stdout?.on("data", (chunk: Buffer) => {
|
|
||||||
stdout += chunk.toString();
|
|
||||||
});
|
|
||||||
child.stderr?.on("data", (chunk: Buffer) => {
|
|
||||||
stderr += chunk.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
let timedOut = false;
|
|
||||||
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
||||||
if (options.timeoutMs !== null) {
|
|
||||||
timeoutId = setTimeout(() => {
|
|
||||||
timedOut = true;
|
|
||||||
child.kill("SIGTERM");
|
|
||||||
}, options.timeoutMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
child.on("error", (cause) => {
|
|
||||||
if (timeoutId !== null) {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
}
|
|
||||||
const message = cause instanceof Error ? cause.message : String(cause);
|
|
||||||
resolve(err({ kind: "spawn_failed", message }));
|
|
||||||
});
|
|
||||||
|
|
||||||
child.on("close", (code) => {
|
|
||||||
if (timeoutId !== null) {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
}
|
|
||||||
if (timedOut) {
|
|
||||||
resolve(err({ kind: "timeout" }));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (code === 0) {
|
|
||||||
resolve(ok(stdout));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
resolve(err({ kind: "non_zero_exit", exitCode: code, stdout, stderr }));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -6,5 +6,5 @@
|
|||||||
"composite": true
|
"composite": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts"],
|
"include": ["src/**/*.ts"],
|
||||||
"references": [{ "path": "../workflow" }]
|
"references": [{ "path": "../workflow" }, { "path": "../workflow-util-agent" }]
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,112 @@
|
|||||||
|
import { describe, expect, test } from "bun:test";
|
||||||
|
import { START, type ThreadContext } from "@uncaged/workflow";
|
||||||
|
|
||||||
|
import { buildAgentPrompt } from "../src/index.js";
|
||||||
|
|
||||||
|
function startTask(content: string): ThreadContext["start"] {
|
||||||
|
return {
|
||||||
|
role: START,
|
||||||
|
content,
|
||||||
|
meta: { maxRounds: 5 },
|
||||||
|
timestamp: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("buildAgentPrompt", () => {
|
||||||
|
test("includes system prompt and full task; omits tools when there are no steps", () => {
|
||||||
|
const ctx: ThreadContext = {
|
||||||
|
start: startTask("fix the bug"),
|
||||||
|
steps: [],
|
||||||
|
};
|
||||||
|
const text = buildAgentPrompt("You are an agent.", ctx);
|
||||||
|
expect(text).toContain("You are an agent.");
|
||||||
|
expect(text).toContain("## Task");
|
||||||
|
expect(text).toContain("fix the bug");
|
||||||
|
expect(text).not.toContain("## Tools");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("single step shows full content and meta, and includes tools", () => {
|
||||||
|
const ctx: ThreadContext = {
|
||||||
|
start: startTask("user task"),
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
role: "coder",
|
||||||
|
content: "only step full body",
|
||||||
|
meta: { files: ["a.ts"] },
|
||||||
|
timestamp: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const text = buildAgentPrompt("Be helpful.", ctx);
|
||||||
|
expect(text).toContain("## Task");
|
||||||
|
expect(text).toContain("user task");
|
||||||
|
expect(text).toContain("## Step: coder");
|
||||||
|
expect(text).toContain("only step full body");
|
||||||
|
expect(text).toContain('Meta: {"files":["a.ts"]}');
|
||||||
|
expect(text).toContain("## Tools");
|
||||||
|
expect(text).toContain("uncaged-workflow thread <threadId>");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("two or more steps: previous steps are meta-only; latest step is full", () => {
|
||||||
|
const ctx: ThreadContext = {
|
||||||
|
start: startTask("first message full: task content here"),
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
role: "planner",
|
||||||
|
content: "PLANNER_SECRET_FULL_TEXT",
|
||||||
|
meta: { plan: "short" },
|
||||||
|
timestamp: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "coder",
|
||||||
|
content: "last step full content",
|
||||||
|
meta: { done: true },
|
||||||
|
timestamp: 3,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const text = buildAgentPrompt("System.", ctx);
|
||||||
|
expect(text).toContain("first message full: task content here");
|
||||||
|
expect(text).toContain("## Previous Steps");
|
||||||
|
expect(text).toContain("### Step 1: planner");
|
||||||
|
expect(text).toContain('Summary: {"plan":"short"}');
|
||||||
|
expect(text).not.toContain("PLANNER_SECRET_FULL_TEXT");
|
||||||
|
expect(text).toContain("## Latest Step: coder");
|
||||||
|
expect(text).toContain("last step full content");
|
||||||
|
expect(text).toContain('Meta: {"done":true}');
|
||||||
|
expect(text).toContain("## Tools");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("middle steps show meta summary only, not full content", () => {
|
||||||
|
const ctx: ThreadContext = {
|
||||||
|
start: startTask("start"),
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
role: "a",
|
||||||
|
content: "HIDDEN_A",
|
||||||
|
meta: { n: 1 },
|
||||||
|
timestamp: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "b",
|
||||||
|
content: "HIDDEN_B_MIDDLE",
|
||||||
|
meta: { n: 2 },
|
||||||
|
timestamp: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "c",
|
||||||
|
content: "VISIBLE_LAST",
|
||||||
|
meta: { n: 3 },
|
||||||
|
timestamp: 4,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const text = buildAgentPrompt("S", ctx);
|
||||||
|
expect(text).not.toContain("HIDDEN_A");
|
||||||
|
expect(text).not.toContain("HIDDEN_B_MIDDLE");
|
||||||
|
expect(text).toContain('Summary: {"n":1}');
|
||||||
|
expect(text).toContain('Summary: {"n":2}');
|
||||||
|
expect(text).toContain("VISIBLE_LAST");
|
||||||
|
expect(text).toContain("## Latest Step: c");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { describe, expect, test } from "bun:test";
|
||||||
|
|
||||||
|
import { spawnCli } from "../src/index.js";
|
||||||
|
|
||||||
|
const noTimeout = { cwd: null, timeoutMs: null } as const;
|
||||||
|
|
||||||
|
describe("spawnCli", () => {
|
||||||
|
test("resolves ok stdout on zero exit", async () => {
|
||||||
|
const run = await spawnCli("echo", ["spawn-cli-ok"], { ...noTimeout });
|
||||||
|
expect(run.ok).toBe(true);
|
||||||
|
if (run.ok) {
|
||||||
|
expect(run.value.trim()).toBe("spawn-cli-ok");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("resolves err on non-zero exit", async () => {
|
||||||
|
const run = await spawnCli("false", [], { ...noTimeout });
|
||||||
|
expect(run.ok).toBe(false);
|
||||||
|
if (!run.ok) {
|
||||||
|
expect(run.error.kind).toBe("non_zero_exit");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("resolves err on timeout", async () => {
|
||||||
|
const run = await spawnCli("sleep", ["10"], { cwd: null, timeoutMs: 80 });
|
||||||
|
expect(run.ok).toBe(false);
|
||||||
|
if (!run.ok) {
|
||||||
|
expect(run.error.kind).toBe("timeout");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("resolves err when spawn fails", async () => {
|
||||||
|
const run = await spawnCli("definitely-missing-executable-7f2a9c1b", [], { ...noTimeout });
|
||||||
|
expect(run.ok).toBe(false);
|
||||||
|
if (!run.ok) {
|
||||||
|
expect(run.error.kind).toBe("spawn_failed");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "@uncaged/workflow-util-agent",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"type": "module",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"types": "src/index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "echo 'TODO'",
|
||||||
|
"test": "bun test"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@uncaged/workflow": "workspace:*"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import type { ThreadContext } from "@uncaged/workflow";
|
||||||
|
|
||||||
|
/** Builds the full agent prompt: system instructions plus summarized thread history. */
|
||||||
|
export function buildAgentPrompt(systemPrompt: string, ctx: ThreadContext): string {
|
||||||
|
const lines: string[] = [];
|
||||||
|
lines.push(systemPrompt);
|
||||||
|
lines.push("");
|
||||||
|
lines.push("## Task");
|
||||||
|
lines.push(ctx.start.content);
|
||||||
|
|
||||||
|
const { steps } = ctx;
|
||||||
|
if (steps.length === 0) {
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (steps.length === 1) {
|
||||||
|
const s = steps[0];
|
||||||
|
lines.push("");
|
||||||
|
lines.push(`## Step: ${s.role}`);
|
||||||
|
lines.push("");
|
||||||
|
lines.push(s.content);
|
||||||
|
lines.push("");
|
||||||
|
lines.push(`Meta: ${JSON.stringify(s.meta)}`);
|
||||||
|
} else {
|
||||||
|
lines.push("");
|
||||||
|
lines.push("## Previous Steps");
|
||||||
|
for (let i = 0; i < steps.length - 1; i++) {
|
||||||
|
const s = steps[i];
|
||||||
|
lines.push("");
|
||||||
|
lines.push(`### Step ${i + 1}: ${s.role}`);
|
||||||
|
lines.push(`Summary: ${JSON.stringify(s.meta)}`);
|
||||||
|
}
|
||||||
|
const last = steps[steps.length - 1];
|
||||||
|
lines.push("");
|
||||||
|
lines.push(`## Latest Step: ${last.role}`);
|
||||||
|
lines.push("");
|
||||||
|
lines.push(last.content);
|
||||||
|
lines.push("");
|
||||||
|
lines.push(`Meta: ${JSON.stringify(last.meta)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push("");
|
||||||
|
lines.push("## Tools");
|
||||||
|
lines.push("Use `uncaged-workflow thread <threadId>` to read full details of any previous step.");
|
||||||
|
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export { buildAgentPrompt } from "./build-agent-prompt.js";
|
||||||
|
export type { SpawnCliConfig, SpawnCliError, SpawnCliResult } from "./spawn-cli.js";
|
||||||
|
export { spawnCli } from "./spawn-cli.js";
|
||||||
+9
-2
@@ -7,11 +7,18 @@ export type SpawnCliError =
|
|||||||
| { kind: "timeout" }
|
| { kind: "timeout" }
|
||||||
| { kind: "spawn_failed"; message: string };
|
| { kind: "spawn_failed"; message: string };
|
||||||
|
|
||||||
|
export type SpawnCliConfig = {
|
||||||
|
cwd: string | null;
|
||||||
|
timeoutMs: number | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SpawnCliResult = Result<string, SpawnCliError>;
|
||||||
|
|
||||||
export function spawnCli(
|
export function spawnCli(
|
||||||
command: string,
|
command: string,
|
||||||
args: string[],
|
args: string[],
|
||||||
options: { cwd: string | null; timeoutMs: number | null },
|
options: SpawnCliConfig,
|
||||||
): Promise<Result<string, SpawnCliError>> {
|
): Promise<SpawnCliResult> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const child = spawn(command, args, {
|
const child = spawn(command, args, {
|
||||||
cwd: options.cwd === null ? undefined : options.cwd,
|
cwd: options.cwd === null ? undefined : options.cwd,
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "dist",
|
||||||
|
"composite": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"],
|
||||||
|
"references": [{ "path": "../workflow" }]
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -22,6 +22,7 @@
|
|||||||
{ "path": "packages/workflow-role-reviewer" },
|
{ "path": "packages/workflow-role-reviewer" },
|
||||||
{ "path": "packages/workflow-agent-cursor" },
|
{ "path": "packages/workflow-agent-cursor" },
|
||||||
{ "path": "packages/workflow-agent-hermes" },
|
{ "path": "packages/workflow-agent-hermes" },
|
||||||
|
{ "path": "packages/workflow-util-agent" },
|
||||||
{ "path": "packages/cli-workflow" },
|
{ "path": "packages/cli-workflow" },
|
||||||
{ "path": "packages/workflow-template-solve-issue" }
|
{ "path": "packages/workflow-template-solve-issue" }
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user