Files
united-workforce/packages/workflow-agent-builtin/src/agent.ts
T
xingyue deac2336b6 feat: add @uncaged/workflow-agent-builtin package
Built-in role agent that uses workflow config models directly,
with its own tool-calling run loop. No external agent dependency.

- OpenAI-compatible chat completion client with tool_calls support
- P0 toolkit: read_file, write_file, run_command
- Integrates via createAgent factory from workflow-agent-kit
- CAS detail recording for each turn
- Path sandboxing and shell opt-in (UWF_BUILTIN_ALLOW_SHELL)
2026-05-23 15:29:55 +08:00

124 lines
3.2 KiB
TypeScript

import type { Store } from "@uncaged/json-cas";
import {
type AgentContext,
type AgentRunResult,
createAgent,
loadWorkflowConfig,
resolveModel,
resolveStorageRoot,
} from "@uncaged/workflow-agent-kit";
import { generateUlid } from "@uncaged/workflow-util";
import { storeBuiltinDetail } from "./detail.js";
import type { ChatMessage } from "./llm/index.js";
import { BUILTIN_CONTINUE_MAX_TURNS, BUILTIN_MAX_TURNS, runBuiltinLoop } from "./loop.js";
import { buildBuiltinPrompt } from "./prompt.js";
import type { BuiltinSessionState } from "./types.js";
const sessions = new Map<string, BuiltinSessionState>();
function getSession(sessionId: string): BuiltinSessionState {
const session = sessions.get(sessionId);
if (session === undefined) {
throw new Error(`builtin session not found: ${sessionId}`);
}
return session;
}
function buildToolContext(storageRoot: string): { cwd: string; storageRoot: string } {
return {
cwd: process.cwd(),
storageRoot,
};
}
async function runBuiltinWithMessages(
storageRoot: string,
provider: ReturnType<typeof resolveModel>,
messages: ChatMessage[],
session: BuiltinSessionState,
store: Store,
maxTurns: number,
): Promise<AgentRunResult> {
const loopResult = await runBuiltinLoop({
provider,
messages,
toolCtx: buildToolContext(storageRoot),
maxTurns,
existingTurns: session.turns,
});
session.messages = loopResult.messages;
session.turns = loopResult.turns;
const { detailHash, output } = await storeBuiltinDetail(
store,
session.sessionId,
session.model,
session.startedAtMs,
session.turns,
);
const finalOutput = output !== "" ? output : loopResult.finalText;
return { output: finalOutput, detailHash, sessionId: session.sessionId };
}
async function runBuiltin(ctx: AgentContext): Promise<AgentRunResult> {
const storageRoot = resolveStorageRoot();
const config = await loadWorkflowConfig(storageRoot);
const provider = resolveModel(config, config.defaultModel);
const sessionId = generateUlid(Date.now());
const systemPrompt = buildBuiltinPrompt(ctx);
const messages: ChatMessage[] = [{ role: "system", content: systemPrompt }];
const session: BuiltinSessionState = {
sessionId,
model: provider.model,
startedAtMs: Date.now(),
messages,
turns: [],
};
sessions.set(sessionId, session);
return runBuiltinWithMessages(
storageRoot,
provider,
messages,
session,
ctx.store,
BUILTIN_MAX_TURNS,
);
}
async function continueBuiltin(
sessionId: string,
message: string,
store: Store,
): Promise<AgentRunResult> {
const session = getSession(sessionId);
const storageRoot = resolveStorageRoot();
const config = await loadWorkflowConfig(storageRoot);
const provider = resolveModel(config, config.defaultModel);
const messages: ChatMessage[] = [...session.messages, { role: "user", content: message }];
return runBuiltinWithMessages(
storageRoot,
provider,
messages,
session,
store,
BUILTIN_CONTINUE_MAX_TURNS,
);
}
/** Agent CLI factory: built-in LLM loop with file/shell tools. */
export function createBuiltinAgent(): () => Promise<void> {
return createAgent({
name: "builtin",
run: runBuiltin,
continue: continueBuiltin,
});
}