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:
asepharyana
2026-09-03 16:38:21 +07:00
parent 7e2eda3bb4
commit 5c65f8f6ab
13 changed files with 306 additions and 125 deletions
@@ -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);
}