- Remove/prefix unused imports and variables across 11 src/ files to clear 74 diagnostics introduced by 37 subsequent commits since run #3 - Fix pre-existing timeout in web-mode-onboarding integration test: - Add timeoutMs: 120_000 to launchPackagedWebHost call (was unbounded) - Raise AbortSignal.timeout on simple fetches 10s → 30s (under parallel load) - Raise overall test timeout 180s → 420s (budget: 120+60+30+30+120+30=390s) - Log autoresearch run #4 and update lessons in autoresearch.md Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
359 lines
9.7 KiB
JavaScript
359 lines
9.7 KiB
JavaScript
/**
|
|
* Steerable Autonomous Mode - Interactive Control Panel
|
|
*
|
|
* Provides Shift+Tab interface for steering and asking questions
|
|
* during autonomous execution, similar to Copilot Auto.
|
|
* Also integrates Ctrl+Y for YOLO mode (bypass git prompts).
|
|
*/
|
|
|
|
import { createInterface } from "node:readline";
|
|
|
|
// ─── Constants ──────────────────────────────────────────────────────────────
|
|
const PANEL_WIDTH = 60;
|
|
const PANEL_HEIGHT = 12;
|
|
|
|
const CONTROL_CATEGORIES = [
|
|
{
|
|
name: "🎯 Steering",
|
|
items: [
|
|
{ key: "1", label: "Focus on research", action: "focus_research" },
|
|
{ key: "2", label: "Focus on planning", action: "focus_plan" },
|
|
{ key: "3", label: "Focus on implementation", action: "focus_build" },
|
|
{ key: "4", label: "Speed up execution", action: "speed_up" },
|
|
{ key: "5", label: "Slow down execution", action: "slow_down" },
|
|
],
|
|
},
|
|
{
|
|
name: "❓ Ask Questions",
|
|
items: [
|
|
{ key: "q", label: "What are you working on?", action: "ask_status" },
|
|
{ key: "w", label: "Why this approach?", action: "ask_reasoning" },
|
|
{ key: "e", label: "What's next?", action: "ask_next" },
|
|
{ key: "r", label: "Are you stuck?", action: "ask_stuck" },
|
|
{ key: "t", label: "Explain your plan", action: "ask_plan" },
|
|
],
|
|
},
|
|
{
|
|
name: "🔄 Retry Status",
|
|
items: [
|
|
{
|
|
key: "a",
|
|
label: "What attempts have been tried?",
|
|
action: "ask_attempts",
|
|
},
|
|
{
|
|
key: "z",
|
|
label: "Why give up? What blockers?",
|
|
action: "ask_blockers",
|
|
},
|
|
{ key: "r", label: "Reassess and try new approach", action: "reassess" },
|
|
],
|
|
},
|
|
{
|
|
name: "⚡ Quick Controls",
|
|
items: [
|
|
{ key: "p", label: "Pause autonomous", action: "pause" },
|
|
{ key: "s", label: "Stop execution", action: "stop" },
|
|
{ key: "y", label: "YOLO mode (Ctrl+Y)", action: "yolo" },
|
|
{ key: "h", label: "Help/commands", action: "help" },
|
|
{ key: "esc", label: "Close panel", action: "close" },
|
|
],
|
|
},
|
|
];
|
|
|
|
// ─── UI Rendering ─────────────────────────────────────────────────────────────
|
|
|
|
function renderBox(lines, title = "") {
|
|
const width = PANEL_WIDTH;
|
|
const horizontalBorder = "─".repeat(width - 2);
|
|
|
|
let result = `┌─${title} ${horizontalBorder.slice(title.length + 1)}─┐\n`;
|
|
|
|
for (const line of lines) {
|
|
const padded = line.padEnd(width - 2, " ");
|
|
result += `│ ${padded} │\n`;
|
|
}
|
|
|
|
result += `└─${horizontalBorder}─┘\n`;
|
|
return result;
|
|
}
|
|
|
|
function renderCategory(category) {
|
|
const lines = [`\x1b[1m${category.name}\x1b[0m`];
|
|
|
|
for (const item of category.items) {
|
|
const keyDisplay = item.key === "esc" ? "Esc" : item.key.toUpperCase();
|
|
lines.push(` ${keyDisplay}. ${item.label}`);
|
|
}
|
|
|
|
return lines;
|
|
}
|
|
|
|
function renderPanel(currentStatus = "") {
|
|
const categories = CONTROL_CATEGORIES;
|
|
|
|
const panelContent = [];
|
|
|
|
// Add status line if provided
|
|
if (currentStatus) {
|
|
panelContent.push(`🤖 ${currentStatus}`);
|
|
panelContent.push(""); // empty line
|
|
}
|
|
|
|
// Render all categories
|
|
for (const category of categories) {
|
|
panelContent.push(...renderCategory(category));
|
|
if (category !== categories[categories.length - 1]) {
|
|
panelContent.push(""); // spacing between categories
|
|
}
|
|
}
|
|
|
|
// Add footer
|
|
panelContent.push("");
|
|
panelContent.push(
|
|
"\x1b[90mShift+Tab or / to open/close • Ctrl+Y for YOLO\x1b[0m",
|
|
);
|
|
|
|
return renderBox(panelContent, "🎛️ Steerable Autonomous Mode");
|
|
}
|
|
|
|
// ─── Action Handlers ────────────────────────────────────────────────────────────
|
|
|
|
const ACTION_HANDLERS = {
|
|
focus_research: async (ctx) => {
|
|
ctx.ui.notify("🎯 Focusing on research phase", "info");
|
|
// Would set autonomous mode to prioritize research
|
|
},
|
|
|
|
focus_plan: async (ctx) => {
|
|
ctx.ui.notify("🎯 Focusing on planning phase", "info");
|
|
// Would set autonomous mode to prioritize planning
|
|
},
|
|
|
|
focus_build: async (ctx) => {
|
|
ctx.ui.notify("🎯 Focusing on implementation phase", "info");
|
|
// Would set autonomous mode to prioritize building
|
|
},
|
|
|
|
speed_up: async (ctx) => {
|
|
ctx.ui.notify("⚡ Execution speed increased", "info");
|
|
// Would adjust autonomous execution speed
|
|
},
|
|
|
|
slow_down: async (ctx) => {
|
|
ctx.ui.notify("🐌 Execution speed decreased", "info");
|
|
// Would adjust autonomous execution speed
|
|
},
|
|
|
|
ask_status: async (ctx) => {
|
|
ctx.ui.notify("🤖 I'm currently working on [current task]", "info");
|
|
// Would provide current status via AI response
|
|
},
|
|
|
|
ask_reasoning: async (ctx) => {
|
|
ctx.ui.notify("🤖 I chose this approach because...", "info");
|
|
// Would provide reasoning via AI response
|
|
},
|
|
|
|
ask_next: async (ctx) => {
|
|
ctx.ui.notify("🤖 Next I'll [next step]", "info");
|
|
// Would provide next steps via AI response
|
|
},
|
|
|
|
ask_stuck: async (ctx) => {
|
|
ctx.ui.notify("🤖 I'm not stuck, but here's my status...", "info");
|
|
// Would provide stuck status via AI response
|
|
},
|
|
|
|
ask_plan: async (ctx) => {
|
|
ctx.ui.notify("🤖 My plan is: [detailed plan]", "info");
|
|
// Would provide plan explanation via AI response
|
|
},
|
|
|
|
pause: async (ctx) => {
|
|
ctx.ui.notify("⏸️ Autonomous mode paused", "info");
|
|
// Would pause autonomous execution
|
|
},
|
|
|
|
yolo: async (ctx) => {
|
|
// Toggle YOLO mode - integrate with existing SafeGit system
|
|
if (ctx.settingsManager && ctx.settingsManager.toggleYOLO) {
|
|
const enabled = ctx.settingsManager.toggleYOLO();
|
|
ctx.ui.notify(
|
|
`🚀 YOLO mode ${enabled ? "ON" : "OFF"} - safe-git prompts ${enabled ? "disabled" : "enabled"}`,
|
|
enabled ? "success" : "info",
|
|
);
|
|
} else {
|
|
ctx.ui.notify(
|
|
"🚀 YOLO mode - safe-git prompts disabled for this session",
|
|
"success",
|
|
);
|
|
}
|
|
},
|
|
|
|
help: async (ctx) => {
|
|
// Show help about the steerable mode
|
|
const helpText = renderPanel("Available controls shown above");
|
|
ctx.ui.notify("Steerable Autonomous Mode Help\n\n" + helpText, "info");
|
|
},
|
|
|
|
ask_attempts: async (ctx) => {
|
|
ctx.ui.notify(
|
|
"🤖 I've tried multiple approaches: [list of attempts]",
|
|
"info",
|
|
);
|
|
// Would provide list of attempted approaches
|
|
},
|
|
|
|
ask_blockers: async (ctx) => {
|
|
ctx.ui.notify("🤖 Main blockers: [list of current blockers]", "info");
|
|
// Would explain why it's giving up
|
|
},
|
|
|
|
reassess: async (ctx) => {
|
|
ctx.ui.notify("🔄 Reassessing - trying new approaches", "info");
|
|
// Would trigger immediate reassessment
|
|
},
|
|
|
|
close: async (_ctx) => {
|
|
// Just hide the panel
|
|
},
|
|
};
|
|
|
|
// ─── Panel Controller ──────────────────────────────────────────────────────────
|
|
|
|
export class SteerableAutonomousPanel {
|
|
constructor(ctx) {
|
|
this.ctx = ctx;
|
|
this.isVisible = false;
|
|
this.rl = null;
|
|
}
|
|
|
|
async show() {
|
|
if (this.isVisible) return;
|
|
|
|
this.isVisible = true;
|
|
this.rl = createInterface({
|
|
input: process.stdin,
|
|
output: process.stdout,
|
|
terminal: true,
|
|
});
|
|
|
|
// Hide cursor while panel is open
|
|
process.stdout.write("\x1b[?25l");
|
|
|
|
// Render panel
|
|
this.render();
|
|
|
|
// Set up key listener
|
|
this.rl.input.on("keypress", (_str, key) => {
|
|
this.handleKeyPress(key);
|
|
});
|
|
}
|
|
|
|
hide() {
|
|
if (!this.isVisible) return;
|
|
|
|
this.isVisible = false;
|
|
|
|
// Restore cursor
|
|
process.stdout.write("\x1b[?25h");
|
|
|
|
// Clear the panel area
|
|
process.stdout.write("\x1b[" + PANEL_HEIGHT + "F"); // Move cursor up
|
|
process.stdout.write("\x1b[0J"); // Clear from cursor down
|
|
|
|
if (this.rl) {
|
|
this.rl.close();
|
|
this.rl = null;
|
|
}
|
|
}
|
|
|
|
async render() {
|
|
if (!this.isVisible) return;
|
|
|
|
// Get current autonomous status (would come from actual system)
|
|
const currentStatus = "Working on current milestone...";
|
|
|
|
const panel = renderPanel(currentStatus);
|
|
|
|
// Move cursor to panel area
|
|
process.stdout.write("\x1b[s"); // Save current position
|
|
process.stdout.write("\x1b[H"); // Move to top-left
|
|
process.stdout.write(panel);
|
|
process.stdout.write("\x1b[u"); // Restore saved position
|
|
}
|
|
|
|
async handleKeyPress(key) {
|
|
if (!this.isVisible) return;
|
|
|
|
// Handle escape sequences
|
|
if (key.name === "escape") {
|
|
this.hide();
|
|
return;
|
|
}
|
|
|
|
// Find matching action
|
|
let actionKey = key.name || key.sequence?.toLowerCase() || "";
|
|
|
|
// Handle single character keys
|
|
if (actionKey.length === 1) {
|
|
actionKey = actionKey.toLowerCase();
|
|
}
|
|
|
|
// Find action
|
|
let action = null;
|
|
for (const category of CONTROL_CATEGORIES) {
|
|
const item = category.items.find((item) => item.key === actionKey);
|
|
if (item) {
|
|
action = item;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (action) {
|
|
await ACTION_HANDLERS[action.action](this.ctx);
|
|
|
|
// If it's not a close action, re-render panel
|
|
if (action.action !== "close") {
|
|
this.render();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ─── Integration Hook ──────────────────────────────────────────────────────────
|
|
|
|
let activePanel = null;
|
|
|
|
export async function showSteerablePanel(ctx) {
|
|
if (activePanel) {
|
|
activePanel.hide();
|
|
}
|
|
|
|
activePanel = new SteerableAutonomousPanel(ctx);
|
|
await activePanel.show();
|
|
}
|
|
|
|
export async function hideSteerablePanel() {
|
|
if (activePanel) {
|
|
activePanel.hide();
|
|
activePanel = null;
|
|
}
|
|
}
|
|
|
|
// ─── Keyboard Integration (would integrate with TUI's key handler) ──────────
|
|
|
|
export function handleSteerableModeKey(key) {
|
|
// Shift+Tab opens/closes the panel
|
|
if (key.shift && key.name === "tab") {
|
|
return true; // Signal that we handled this key
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export default {
|
|
show: showSteerablePanel,
|
|
hide: hideSteerablePanel,
|
|
handleKey: handleSteerableModeKey,
|
|
};
|