mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-17 09:02:08 +00:00
fix(plan-mode): handle non-tool steps and clean up todo text
- Non-tool turns (analysis, explanation) now mark step complete at turn_end - Clean up extracted step text: remove markdown, truncate to 50 chars - Remove redundant action words (Use, Run, Execute, etc.) - Track toolsCalledThisTurn flag to distinguish tool vs non-tool turns
This commit is contained in:
parent
fdcc044491
commit
a169029a16
1 changed files with 60 additions and 5 deletions
|
|
@ -137,19 +137,51 @@ interface TodoItem {
|
||||||
completed: boolean;
|
completed: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up extracted step text for display.
|
||||||
|
*/
|
||||||
|
function cleanStepText(text: string): string {
|
||||||
|
let cleaned = text
|
||||||
|
// Remove markdown bold/italic
|
||||||
|
.replace(/\*{1,2}([^*]+)\*{1,2}/g, "$1")
|
||||||
|
// Remove markdown code
|
||||||
|
.replace(/`([^`]+)`/g, "$1")
|
||||||
|
// Remove leading action words that are redundant
|
||||||
|
.replace(/^(Use|Run|Execute|Create|Write|Read|Check|Verify|Update|Modify|Add|Remove|Delete|Install)\s+(the\s+)?/i, "")
|
||||||
|
// Clean up extra whitespace
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
// Capitalize first letter
|
||||||
|
if (cleaned.length > 0) {
|
||||||
|
cleaned = cleaned.charAt(0).toUpperCase() + cleaned.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Truncate if too long
|
||||||
|
if (cleaned.length > 50) {
|
||||||
|
cleaned = cleaned.slice(0, 47) + "...";
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleaned;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract todo items from assistant message.
|
* Extract todo items from assistant message.
|
||||||
*/
|
*/
|
||||||
function extractTodoItems(message: string): TodoItem[] {
|
function extractTodoItems(message: string): TodoItem[] {
|
||||||
const items: TodoItem[] = [];
|
const items: TodoItem[] = [];
|
||||||
|
|
||||||
// Match numbered lists: "1. Task" or "1) Task"
|
// Match numbered lists: "1. Task" or "1) Task" - also handle **bold** prefixes
|
||||||
const numberedPattern = /^\s*(\d+)[.)]\s+\*{0,2}([^*\n]+)/gm;
|
const numberedPattern = /^\s*(\d+)[.)]\s+\*{0,2}([^*\n]+)/gm;
|
||||||
for (const match of message.matchAll(numberedPattern)) {
|
for (const match of message.matchAll(numberedPattern)) {
|
||||||
let text = match[2].trim();
|
let text = match[2].trim();
|
||||||
text = text.replace(/\*{1,2}$/, "").trim();
|
text = text.replace(/\*{1,2}$/, "").trim();
|
||||||
|
// Skip if too short or looks like code/command
|
||||||
if (text.length > 5 && !text.startsWith("`") && !text.startsWith("/") && !text.startsWith("-")) {
|
if (text.length > 5 && !text.startsWith("`") && !text.startsWith("/") && !text.startsWith("-")) {
|
||||||
items.push({ step: items.length + 1, text, completed: false });
|
const cleaned = cleanStepText(text);
|
||||||
|
if (cleaned.length > 3) {
|
||||||
|
items.push({ step: items.length + 1, text: cleaned, completed: false });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,7 +192,10 @@ function extractTodoItems(message: string): TodoItem[] {
|
||||||
let text = match[1].trim();
|
let text = match[1].trim();
|
||||||
text = text.replace(/\*{1,2}$/, "").trim();
|
text = text.replace(/\*{1,2}$/, "").trim();
|
||||||
if (text.length > 10 && !text.startsWith("`")) {
|
if (text.length > 10 && !text.startsWith("`")) {
|
||||||
items.push({ step: items.length + 1, text, completed: false });
|
const cleaned = cleanStepText(text);
|
||||||
|
if (cleaned.length > 3) {
|
||||||
|
items.push({ step: items.length + 1, text: cleaned, completed: false });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -172,6 +207,7 @@ function extractTodoItems(message: string): TodoItem[] {
|
||||||
|
|
||||||
export default function planModeHook(pi: HookAPI) {
|
export default function planModeHook(pi: HookAPI) {
|
||||||
let planModeEnabled = false;
|
let planModeEnabled = false;
|
||||||
|
let toolsCalledThisTurn = false;
|
||||||
let executionMode = false;
|
let executionMode = false;
|
||||||
let todoItems: TodoItem[] = [];
|
let todoItems: TodoItem[] = [];
|
||||||
|
|
||||||
|
|
@ -278,13 +314,15 @@ export default function planModeHook(pi: HookAPI) {
|
||||||
|
|
||||||
// Track step completion based on tool results
|
// Track step completion based on tool results
|
||||||
pi.on("tool_result", async (_event, ctx) => {
|
pi.on("tool_result", async (_event, ctx) => {
|
||||||
|
toolsCalledThisTurn = true;
|
||||||
|
|
||||||
if (!executionMode || todoItems.length === 0) return;
|
if (!executionMode || todoItems.length === 0) return;
|
||||||
|
|
||||||
// Mark the first uncompleted step as done when any tool succeeds
|
// Mark the first uncompleted step as done when any tool succeeds
|
||||||
const nextStep = todoItems.find((t) => !t.completed);
|
const nextStep = todoItems.find((t) => !t.completed);
|
||||||
if (nextStep) {
|
if (nextStep) {
|
||||||
nextStep.completed = true;
|
nextStep.completed = true;
|
||||||
console.error(`[plan-mode] Marked step ${nextStep.step} complete: ${nextStep.text}`);
|
console.error(`[plan-mode] Marked step ${nextStep.step} complete (tool): ${nextStep.text}`);
|
||||||
updateStatus(ctx);
|
updateStatus(ctx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -496,12 +534,29 @@ Execute each step in order.`,
|
||||||
updateStatus(ctx);
|
updateStatus(ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Persist state
|
// Reset tool tracking at start of each turn and persist state
|
||||||
pi.on("turn_start", async () => {
|
pi.on("turn_start", async () => {
|
||||||
|
toolsCalledThisTurn = false;
|
||||||
pi.appendEntry("plan-mode", {
|
pi.appendEntry("plan-mode", {
|
||||||
enabled: planModeEnabled,
|
enabled: planModeEnabled,
|
||||||
todos: todoItems,
|
todos: todoItems,
|
||||||
executing: executionMode,
|
executing: executionMode,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle non-tool turns (e.g., analysis, explanation steps)
|
||||||
|
pi.on("turn_end", async (_event, ctx) => {
|
||||||
|
if (!executionMode || todoItems.length === 0) return;
|
||||||
|
|
||||||
|
// If no tools were called this turn, the agent was doing analysis/explanation
|
||||||
|
// Mark the next uncompleted step as done
|
||||||
|
if (!toolsCalledThisTurn) {
|
||||||
|
const nextStep = todoItems.find((t) => !t.completed);
|
||||||
|
if (nextStep) {
|
||||||
|
nextStep.completed = true;
|
||||||
|
console.error(`[plan-mode] Marked step ${nextStep.step} complete (no-tool turn): ${nextStep.text}`);
|
||||||
|
updateStatus(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue