Files
2026-07-13 12:20:32 +08:00

162 lines
5.4 KiB
JavaScript

function normalizeWorkUnits(units, fallbackItems = []) {
const source = Array.isArray(units) && units.length
? units
: fallbackItems.map((item, idx) => ({
id: `step-${idx + 1}`,
title: `Step ${idx + 1}`,
role: "worker",
objective: item,
expected_output: "Concise result, facts gathered, files changed, blockers, and next step.",
}));
return source
.map((unit, idx) => {
const objective = String(unit.objective || unit.task || unit.title || "").trim();
if (!objective) return null;
return {
id: String(unit.id || `step-${idx + 1}`).replace(/[^a-zA-Z0-9._-]/g, "_"),
title: String(unit.title || `Step ${idx + 1}`).trim().slice(0, 80),
role: String(unit.role || "worker").trim().slice(0, 40),
objective,
allowed_tools: Array.isArray(unit.allowed_tools)
? unit.allowed_tools.map((tool) => String(tool || "").trim()).filter(Boolean)
: [],
expected_output: String(
unit.expected_output ||
"Concise result, facts gathered, files changed, blockers, and next step.",
).trim(),
depends_on: Array.isArray(unit.depends_on)
? unit.depends_on.map((dep) => String(dep || "").trim()).filter(Boolean)
: [],
requires_user_approval: unit.requires_user_approval === true,
};
})
.filter(Boolean)
.slice(0, 8);
}
function shouldDelegateFromPrompt(prompt) {
const text = String(prompt || "").toLowerCase();
return /\b(subagent|subagents|delegate|delegation|worker|workers|sequential|split (this|it|the task))\b/.test(text);
}
function isTinyPrompt(prompt) {
const text = String(prompt || "").trim().toLowerCase();
if (!text) return true;
if (/^(hi|hey|hello|yo|sup|thanks|thank you|ok|okay|yes|no)[.!?]*$/.test(text)) {
return true;
}
const words = text.split(/\s+/).filter(Boolean);
return words.length <= 4 && !/[.,;:]/.test(text);
}
function shouldAutoDelegateFromPrompt(prompt) {
const text = String(prompt || "").toLowerCase();
if (isTinyPrompt(text)) return false;
if (shouldDelegateFromPrompt(text)) return true;
const substantiveSignals = [
/\b(open|visit|browse|research|find|summarize|compare|analyze|investigate|implement|fix|debug|refactor|create|generate|save|download|upload|install|test|verify)\b/,
/\b(pdf|docx|xlsx|deliverable|screenshot|report|article|articles|top \d+|multiple|several|then|and save|ask for filename)\b/,
/\b(browser|hackernews|google|website|page|file|files|desktop|app)\b/,
];
const signalCount = substantiveSignals.filter((re) => re.test(text)).length;
if (signalCount >= 1 && text.split(/\s+/).length >= 8) return true;
return false;
}
function buildDelegationPrompt(prompt, planItems = []) {
const planText = normalizeWorkUnits([], planItems)
.map((unit) => `- ${unit.objective}`)
.join("\n");
return `Break this user task into a short sequential subagent queue. Call propose_delegation exactly once.
Requirements:
- Use 1-6 work units.
- Each work unit must be independently understandable by a fresh child agent.
- Keep units sequential and local-model friendly; do not create parallel work.
- Use role names like scout, researcher, worker, reviewer, or synthesizer when helpful.
- Do not execute the task.
${planText ? `Approved plan items:\n${planText}\n` : ""}
User task:
${prompt}`;
}
function buildSubagentPrompt({ originalPrompt, unit, previousOutputs = [] }) {
const prior = previousOutputs.length
? previousOutputs
.map((output, idx) => {
const title = output.title || output.id || `Step ${idx + 1}`;
return `### ${title}\n${output.output || "(no output)"}`;
})
.join("\n\n")
: "(none)";
return `You are a focused child subagent. Complete only your assigned subtask, then stop.
Original user task:
${originalPrompt}
Your role:
${unit.role}
Your subtask:
${unit.objective}
Expected output:
${unit.expected_output}
Previous subagent outputs:
${prior}
Rules:
- Do not spawn or propose subagents.
- Use tools only when needed for this subtask.
- If the subtask involves articles, search results, or external pages, open the actual link/href with open_browser or page_open_link before summarizing. Do not summarize from a listing page alone.
- Do not ask the user unless blocked by a decision that changes the output or workflow.
- End with a compact handoff using these headings:
Result
Facts gathered
Files changed or deliverables
Blockers
Suggested next step`;
}
function buildSynthesisPrompt({ originalPrompt, units = [], outputs = [] }) {
const unitText = units
.map((unit, idx) => `${idx + 1}. ${unit.title}: ${unit.objective}`)
.join("\n");
const outputText = outputs
.map((output, idx) => {
const title = output.title || output.id || `Step ${idx + 1}`;
return `## ${title}\n${output.output || "(no output)"}`;
})
.join("\n\n");
return `Assemble the final response for the user from sequential child subagent outputs.
Original user task:
${originalPrompt}
Delegated work units:
${unitText || "(none)"}
Child outputs:
${outputText || "(none)"}
Write the final answer or final deliverable instructions. Be concise, do not invent missing facts, and mention any blockers or incomplete child work.`;
}
module.exports = {
buildDelegationPrompt,
buildSubagentPrompt,
buildSynthesisPrompt,
normalizeWorkUnits,
shouldAutoDelegateFromPrompt,
shouldDelegateFromPrompt,
};