feat(tui): enhance LLM interaction with max_tokens and temperature overrides; add tests for parallel delegation and workflow script parsing
This commit is contained in:
@@ -50,7 +50,8 @@ export class ParallelDelegate implements Tool {
|
||||
}))
|
||||
.filter((d) => d.directive !== "");
|
||||
} else {
|
||||
directives = fallbackSplit(task, maxParallel);
|
||||
// Let the LLM decide the decomposition instead of guessing keywords.
|
||||
directives = await aiDecomposeTask(task, maxParallel);
|
||||
}
|
||||
|
||||
if (directives.length === 0) {
|
||||
@@ -73,22 +74,67 @@ export class ParallelDelegate implements Tool {
|
||||
}
|
||||
}
|
||||
|
||||
/** Fallback splitting when LLM is unavailable. */
|
||||
function fallbackSplit(task: string, maxParallel: number): Array<{ directive: string; access: string }> {
|
||||
const directives: Array<{ directive: string; access: string }> = [];
|
||||
if (task.includes("backend") || task.includes("api") || task.includes("server")) {
|
||||
directives.push({ directive: `Implement the backend/API components for: ${task}`, access: "write" });
|
||||
/**
|
||||
* Have the LLM decompose a task into focused, non-overlapping parallel
|
||||
* sub-directives. This is the AI deciding how to split the work — no keyword
|
||||
* guessing. On LLM failure (no provider / error), degrades to a single honest
|
||||
* full-task directive rather than fabricated "part N" stubs.
|
||||
*/
|
||||
async function aiDecomposeTask(
|
||||
task: string,
|
||||
maxParallel: number,
|
||||
): Promise<Array<{ directive: string; access: string }>> {
|
||||
try {
|
||||
const { resolveConfig } = await import("../../../subagent/infrastructure/config_resolver.ts");
|
||||
const { buildProviderService } = await import("../../../subagent/infrastructure/http_provider.ts");
|
||||
const { baseUrl, apiKey, model } = await resolveConfig();
|
||||
const svc = buildProviderService(baseUrl, apiKey, model);
|
||||
|
||||
const systemPrompt =
|
||||
"You decompose a large task into independent, non-overlapping subtasks that can be worked on " +
|
||||
"in parallel. Return a JSON array of up to the requested count of objects, each with a " +
|
||||
"'directive' (a concrete, self-contained action) and an 'access' tier of exactly one of " +
|
||||
"read | write | full. read = inspect/search only; write = edit files; full = edit + run shell. " +
|
||||
"Prefer fewer, genuinely parallel directives over many that touch the same files. " +
|
||||
"Return ONLY the JSON array, no prose.";
|
||||
|
||||
const { message } = await svc.chat(
|
||||
[
|
||||
{ role: "system", content: systemPrompt },
|
||||
{ role: "user", content: `Task: ${task}\nMax subtasks: ${maxParallel}\n` },
|
||||
],
|
||||
undefined,
|
||||
1024,
|
||||
0.3,
|
||||
);
|
||||
|
||||
const parsed = parseDirectivesJson(message.content ?? "");
|
||||
if (parsed.length > 0) return parsed.slice(0, maxParallel);
|
||||
} catch {
|
||||
/* fall through to the single-directive fallback */
|
||||
}
|
||||
// Honest fallback: run the whole task as one agent rather than guessing a split.
|
||||
return [{ directive: task, access: "full" }];
|
||||
}
|
||||
|
||||
/** Parse an LLM JSON array of directives, tolerant of stray prose/markdown fences. */
|
||||
export function parseDirectivesJson(raw: string): Array<{ directive: string; access: string }> {
|
||||
const fenced = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
||||
const body = fenced ? fenced[1]! : raw;
|
||||
const start = body.indexOf("[");
|
||||
const end = body.lastIndexOf("]");
|
||||
if (start < 0 || end <= start) return [];
|
||||
try {
|
||||
const arr = JSON.parse(body.slice(start, end + 1)) as unknown;
|
||||
if (!Array.isArray(arr)) return [];
|
||||
return arr
|
||||
.filter((d): d is Record<string, unknown> => typeof d === "object" && d !== null)
|
||||
.map((d) => ({
|
||||
directive: typeof d.directive === "string" ? d.directive.trim() : "",
|
||||
access: ["read", "write", "full"].includes(String(d.access)) ? String(d.access) : "write",
|
||||
}))
|
||||
.filter((d) => d.directive !== "");
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
if (task.includes("frontend") || task.includes("ui") || task.includes("client")) {
|
||||
directives.push({ directive: `Implement the frontend/UI components for: ${task}`, access: "write" });
|
||||
}
|
||||
if (task.includes("test") || task.includes("unit")) {
|
||||
directives.push({ directive: `Write unit tests for: ${task}`, access: "read" });
|
||||
}
|
||||
if (directives.length === 0) {
|
||||
for (let i = 0; i < maxParallel; i++) {
|
||||
directives.push({ directive: `Part ${i + 1} of parallel task: ${task}`, access: "write" });
|
||||
}
|
||||
}
|
||||
return directives.slice(0, maxParallel);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user