984d93a6f5
Implement thread-level and edge-level working directory management:
- Thread-level cwd (required, defaults to process.cwd())
- Captured at uwf thread start time
- Stored in StartNodePayload
- Inherited by all steps unless overridden
- Edge-level location (optional, supports mustache templates)
- New location: string | null field on Target type
- Resolved by moderator using previous step's output
- Example: location: "{{{repoPath}}}"
- Step audit trail
- Each StepNodePayload records actual cwd where agent executed
Changes:
- workflow-protocol: Add cwd to StartNodePayload & StepRecord, location to Target
- cli-workflow: Thread start captures cwd, moderator resolves location, step execution uses resolved cwd
- workflow-util-agent: Expose cwd in agent context
Tests:
- Protocol type tests (3 scenarios)
- Moderator location resolution tests (5 scenarios)
- Thread-location integration tests (3 scenarios)
All tests pass. Build successful. Backward compatible.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
55 lines
1.5 KiB
TypeScript
55 lines
1.5 KiB
TypeScript
import type { Target } from "@uncaged/workflow-protocol";
|
|
import mustache from "mustache";
|
|
|
|
import type { EvaluateResult, Result } from "./types.js";
|
|
|
|
// Disable HTML escaping — prompts are plain text, not HTML.
|
|
mustache.escape = (text: string) => text;
|
|
|
|
const START_ROLE = "$START";
|
|
const UNIT_STATUS = "_";
|
|
|
|
type LastOutput = Record<string, unknown>;
|
|
|
|
const STATUS_KEY = "$status";
|
|
|
|
export function evaluate(
|
|
graph: Record<string, Record<string, Target>>,
|
|
lastRole: string,
|
|
lastOutput: LastOutput,
|
|
): Result<EvaluateResult, Error> {
|
|
const status =
|
|
lastRole === START_ROLE
|
|
? UNIT_STATUS
|
|
: typeof lastOutput[STATUS_KEY] === "string"
|
|
? (lastOutput[STATUS_KEY] as string)
|
|
: UNIT_STATUS;
|
|
|
|
const roleTargets = graph[lastRole];
|
|
if (roleTargets === undefined) {
|
|
return {
|
|
ok: false,
|
|
error: new Error(`no transitions defined for role "${lastRole}"`),
|
|
};
|
|
}
|
|
|
|
const target = roleTargets[status];
|
|
if (target === undefined) {
|
|
return {
|
|
ok: false,
|
|
error: new Error(`no transition for role "${lastRole}" with status "${status}"`),
|
|
};
|
|
}
|
|
|
|
try {
|
|
const prompt = mustache.render(target.prompt, lastOutput);
|
|
const location = target.location !== null ? mustache.render(target.location, lastOutput) : null;
|
|
return { ok: true, value: { role: target.role, prompt, location } };
|
|
} catch (error) {
|
|
return {
|
|
ok: false,
|
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
};
|
|
}
|
|
}
|