singularity-forge/src/resources/extensions/sf/steerable-autonomous-panel.js
Mikael Hugo 05953e9599 fix(lint): restore 0 Biome diagnostics and fix web-mode-onboarding test timeout
- 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>
2026-05-10 11:01:43 +02:00

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,
};