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>
This commit is contained in:
parent
b2bcb922de
commit
05953e9599
14 changed files with 127 additions and 80 deletions
|
|
@ -2,3 +2,4 @@
|
||||||
{"run": 1, "commit": "15269f4", "metric": 40.0, "metrics": {}, "status": "keep", "description": "baseline measurement", "timestamp": 1778242955776, "segment": 0, "confidence": null, "asi": {"hypothesis": "baseline measurement", "breakdown": "26 errors, 13 warnings, 1 info"}}
|
{"run": 1, "commit": "15269f4", "metric": 40.0, "metrics": {}, "status": "keep", "description": "baseline measurement", "timestamp": 1778242955776, "segment": 0, "confidence": null, "asi": {"hypothesis": "baseline measurement", "breakdown": "26 errors, 13 warnings, 1 info"}}
|
||||||
{"run": 2, "commit": "72e27f9", "metric": 11.0, "metrics": {}, "status": "keep", "description": "auto-fix format + organizeImports: biome check --write src/", "timestamp": 1778243276590, "segment": 0, "confidence": null, "asi": {"hypothesis": "All 26 errors are auto-fixable format/organizeImports; fixing them drops total from 40 to 11", "breakdown": "0 errors, 11 warnings"}}
|
{"run": 2, "commit": "72e27f9", "metric": 11.0, "metrics": {}, "status": "keep", "description": "auto-fix format + organizeImports: biome check --write src/", "timestamp": 1778243276590, "segment": 0, "confidence": null, "asi": {"hypothesis": "All 26 errors are auto-fixable format/organizeImports; fixing them drops total from 40 to 11", "breakdown": "0 errors, 11 warnings"}}
|
||||||
{"run": 3, "commit": "c6ee770", "metric": 0.0, "metrics": {}, "status": "keep", "description": "fix 11 unused imports/variables by removing or prefixing with underscore", "timestamp": 1778243617559, "segment": 0, "confidence": 3.64, "asi": {"hypothesis": "All 11 remaining warnings are unused imports/variables \u2014 removing unused imports and prefixing intentionally kept but unused variables with underscore eliminates all diagnostics", "breakdown": "Removed: injectReasoningGuidance, withQueryTimeout (unused import), getAutoSession, logWarning (2x), debugLog, readFileSync/unlinkSync/writeFileSync. Prefixed: MAX_HISTOGRAM_BUCKETS, REASONING_ASSIST_MAX_CHARS, basePath param."}}
|
{"run": 3, "commit": "c6ee770", "metric": 0.0, "metrics": {}, "status": "keep", "description": "fix 11 unused imports/variables by removing or prefixing with underscore", "timestamp": 1778243617559, "segment": 0, "confidence": 3.64, "asi": {"hypothesis": "All 11 remaining warnings are unused imports/variables \u2014 removing unused imports and prefixing intentionally kept but unused variables with underscore eliminates all diagnostics", "breakdown": "Removed: injectReasoningGuidance, withQueryTimeout (unused import), getAutoSession, logWarning (2x), debugLog, readFileSync/unlinkSync/writeFileSync. Prefixed: MAX_HISTOGRAM_BUCKETS, REASONING_ASSIST_MAX_CHARS, basePath param."}}
|
||||||
|
{"run": 4, "commit": "b2bcb922d", "metric": 0.0, "metrics": {}, "status": "keep", "description": "re-fix 74 new diagnostics from 37 subsequent commits: biome --write dropped to 16, manual unused-import/var/param cleanup to 0; fixed web-mode-onboarding test timeout (timeoutMs 120s, AbortSignal 30s, test budget 420s)", "timestamp": 1778403638931, "segment": 0, "confidence": null, "asi": {"hypothesis": "37 new commits introduced 74 diagnostics (57 errors, 17 warnings); auto-fix handles format/import errors, manual prefix/removal handles unsafe unused-import warnings", "breakdown": "0 errors, 0 warnings after fix; all 409 test files pass"}}
|
||||||
|
|
|
||||||
|
|
@ -44,4 +44,10 @@ Run until interrupted by the user.
|
||||||
## What's Been Tried
|
## What's Been Tried
|
||||||
|
|
||||||
- **#2 (auto-fix)**: `biome check --write` — fixed 26 auto-fixable errors (format/organizeImports), dropped diagnostics from 40 to 11. Status: keep.
|
- **#2 (auto-fix)**: `biome check --write` — fixed 26 auto-fixable errors (format/organizeImports), dropped diagnostics from 40 to 11. Status: keep.
|
||||||
- **#3 (manual fixes)**: Removed 7 unused imports (`injectReasoningGuidance`, `withQueryTimeout`, `getAutoSession`, `logWarning` x3, `debugLog`, `readFileSync/unlinkSync/writeFileSync`) and prefixed 4 intentionally-unused items with underscore (`_MAX_HISTOGRAM_BUCKETS`, `_REASONING_ASSIST_MAX_CHARS`, `_basePath`, `_withQueryTimeout`). Dropped from 11 to 0. Status: keep.
|
- **#3 (manual fixes)**: Removed 7 unused imports and prefixed 4 intentionally-unused items with underscore. Dropped from 11 to 0. Status: keep.
|
||||||
|
- **#4 (regression re-fix)**: 37 new commits introduced 74 diagnostics. `biome check --write` fixed 58 (auto-safe), manual prefix/removal fixed the remaining 16 unsafe warnings across 11 files. Also fixed pre-existing web-mode-onboarding test timeout: added `timeoutMs: 120_000` to `launchPackagedWebHost`, raised `AbortSignal.timeout` on simple fetches 10s→30s, raised test budget 180s→420s. All 409 test files pass. Diagnostics: 0. Status: keep.
|
||||||
|
|
||||||
|
## Lessons
|
||||||
|
- New development (37 commits) is enough to re-introduce 74 diagnostics. Re-run autoresearch periodically (monthly or after large feature branches land).
|
||||||
|
- Pattern of new violations: unused imports from refactors, unused function params from stubs, duplicate imports. Auto-fix handles errors; unsafe-fix (unused-import/var) requires manual triage.
|
||||||
|
- Integration test timeout under parallel load: cold-start Next.js can consume most of a 180s test timeout leaving insufficient budget for multi-step API calls. Fix: bound launch phase separately, raise individual fetch timeouts, increase overall budget to match worst-case sum.
|
||||||
|
|
|
||||||
|
|
@ -11,19 +11,21 @@ import {
|
||||||
sanitizeCommand,
|
sanitizeCommand,
|
||||||
} from "@singularity-forge/pi-coding-agent";
|
} from "@singularity-forge/pi-coding-agent";
|
||||||
import { rewriteCommandWithRtk } from "../shared/rtk.js";
|
import { rewriteCommandWithRtk } from "../shared/rtk.js";
|
||||||
import {
|
import { addEvent, pushAlert } from "./bg-events.js";
|
||||||
addEvent,
|
|
||||||
pendingAlerts,
|
|
||||||
pushAlert,
|
|
||||||
setPendingAlerts,
|
|
||||||
} from "./bg-events.js";
|
|
||||||
import { analyzeLine } from "./output-formatter.js";
|
import { analyzeLine } from "./output-formatter.js";
|
||||||
import { startPortProbing, transitionToReady } from "./readiness-detector.js";
|
import { startPortProbing, transitionToReady } from "./readiness-detector.js";
|
||||||
import { DEAD_PROCESS_TTL, MAX_BUFFER_LINES } from "./types.js";
|
import { DEAD_PROCESS_TTL, MAX_BUFFER_LINES } from "./types.js";
|
||||||
import { formatUptime, restoreWindowsVTInput } from "./utilities.js";
|
import { formatUptime, restoreWindowsVTInput } from "./utilities.js";
|
||||||
|
|
||||||
// Re-export event/alert helpers so existing consumers (bg-shell-lifecycle.js)
|
// Re-export event/alert helpers so existing consumers (bg-shell-lifecycle.js)
|
||||||
// continue to work without changing their import paths.
|
// continue to work without changing their import paths.
|
||||||
export { addEvent, MAX_PENDING_ALERTS, pendingAlerts, pushAlert, setPendingAlerts } from "./bg-events.js";
|
export {
|
||||||
|
addEvent,
|
||||||
|
MAX_PENDING_ALERTS,
|
||||||
|
pendingAlerts,
|
||||||
|
pushAlert,
|
||||||
|
setPendingAlerts,
|
||||||
|
} from "./bg-events.js";
|
||||||
// ── Process Registry ───────────────────────────────────────────────────────
|
// ── Process Registry ───────────────────────────────────────────────────────
|
||||||
export const processes = new Map();
|
export const processes = new Map();
|
||||||
export function addOutputLine(bg, stream, line) {
|
export function addOutputLine(bg, stream, line) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { existsSync, readdirSync } from "node:fs";
|
|
||||||
import { spawnSync } from "node:child_process";
|
import { spawnSync } from "node:child_process";
|
||||||
|
import { existsSync, readdirSync } from "node:fs";
|
||||||
import { join, relative, resolve } from "node:path";
|
import { join, relative, resolve } from "node:path";
|
||||||
import { isToolCallEventType } from "@singularity-forge/pi-coding-agent";
|
import { isToolCallEventType } from "@singularity-forge/pi-coding-agent";
|
||||||
import { resetAskUserQuestionsCache } from "../../ask-user-questions.js";
|
import { resetAskUserQuestionsCache } from "../../ask-user-questions.js";
|
||||||
|
|
@ -69,6 +69,7 @@ import {
|
||||||
import { initSessionRecorder } from "../session-recorder.js";
|
import { initSessionRecorder } from "../session-recorder.js";
|
||||||
import { deriveState } from "../state.js";
|
import { deriveState } from "../state.js";
|
||||||
import { countGoogleGeminiCliTokens } from "../token-counter.js";
|
import { countGoogleGeminiCliTokens } from "../token-counter.js";
|
||||||
|
import { getSessionTodoCompactionBlock } from "../tools/session-todo-tool.js";
|
||||||
import { parseUnitId } from "../unit-id.js";
|
import { parseUnitId } from "../unit-id.js";
|
||||||
import { logWarning as safetyLogWarning } from "../workflow-logger.js";
|
import { logWarning as safetyLogWarning } from "../workflow-logger.js";
|
||||||
import {
|
import {
|
||||||
|
|
@ -79,7 +80,6 @@ import {
|
||||||
import { handleAgentEnd } from "./agent-end-recovery.js";
|
import { handleAgentEnd } from "./agent-end-recovery.js";
|
||||||
import { installNotifyInterceptor } from "./notify-interceptor.js";
|
import { installNotifyInterceptor } from "./notify-interceptor.js";
|
||||||
import { buildBeforeAgentStartResult } from "./system-context.js";
|
import { buildBeforeAgentStartResult } from "./system-context.js";
|
||||||
import { getSessionTodoCompactionBlock } from "../tools/session-todo-tool.js";
|
|
||||||
import {
|
import {
|
||||||
checkToolCallLoop,
|
checkToolCallLoop,
|
||||||
resetToolCallLoopGuard,
|
resetToolCallLoopGuard,
|
||||||
|
|
@ -212,7 +212,7 @@ export function registerHooks(pi, ecosystemHandlers = []) {
|
||||||
resetMemorySleeper();
|
resetMemorySleeper();
|
||||||
try {
|
try {
|
||||||
const sid = ctx.sessionManager?.getSessionId?.() ?? "";
|
const sid = ctx.sessionManager?.getSessionId?.() ?? "";
|
||||||
const sfile = ctx.sessionManager?.getSessionFile?.() ?? "";
|
const _sfile = ctx.sessionManager?.getSessionFile?.() ?? "";
|
||||||
if (sid) {
|
if (sid) {
|
||||||
process.stderr.write(`[forge] session ${sid.slice(0, 8)}\n`);
|
process.stderr.write(`[forge] session ${sid.slice(0, 8)}\n`);
|
||||||
}
|
}
|
||||||
|
|
@ -676,9 +676,7 @@ export function registerHooks(pi, ecosystemHandlers = []) {
|
||||||
workState.length > 0
|
workState.length > 0
|
||||||
? `Work in progress: ${workState.join(". ")}.`
|
? `Work in progress: ${workState.join(". ")}.`
|
||||||
: "Session compacted. No active work state.";
|
: "Session compacted. No active work state.";
|
||||||
const summary = todoBlock
|
const summary = todoBlock ? `${baseSummary}\n\n${todoBlock}` : baseSummary;
|
||||||
? `${baseSummary}\n\n${todoBlock}`
|
|
||||||
: baseSummary;
|
|
||||||
const result = {
|
const result = {
|
||||||
compaction: {
|
compaction: {
|
||||||
summary,
|
summary,
|
||||||
|
|
|
||||||
|
|
@ -23,15 +23,14 @@ import {
|
||||||
loadProjectSFPreferences,
|
loadProjectSFPreferences,
|
||||||
resolveAllSkillReferences,
|
resolveAllSkillReferences,
|
||||||
} from "./preferences.js";
|
} from "./preferences.js";
|
||||||
|
|
||||||
// Re-export from thin shared module so external importers keep working.
|
// Re-export from thin shared module so external importers keep working.
|
||||||
export {
|
export {
|
||||||
serializePreferencesToFrontmatter,
|
serializePreferencesToFrontmatter,
|
||||||
yamlSafeString,
|
yamlSafeString,
|
||||||
} from "./preferences-serializer.js";
|
} from "./preferences-serializer.js";
|
||||||
import {
|
|
||||||
serializePreferencesToFrontmatter,
|
import { serializePreferencesToFrontmatter } from "./preferences-serializer.js";
|
||||||
yamlSafeString,
|
|
||||||
} from "./preferences-serializer.js";
|
|
||||||
|
|
||||||
/** Extract body content after frontmatter closing delimiter, or null if none. */
|
/** Extract body content after frontmatter closing delimiter, or null if none. */
|
||||||
function extractBodyAfterFrontmatter(content) {
|
function extractBodyAfterFrontmatter(content) {
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,7 @@ export function renderTriageMarkdown(result, sourcePath) {
|
||||||
section("Unclear Notes", result.unclear_notes),
|
section("Unclear Notes", result.unclear_notes),
|
||||||
].join("\n");
|
].join("\n");
|
||||||
}
|
}
|
||||||
function renderEvalJsonl(result) {
|
function _renderEvalJsonl(result) {
|
||||||
return (
|
return (
|
||||||
result.eval_candidates
|
result.eval_candidates
|
||||||
.map((item) =>
|
.map((item) =>
|
||||||
|
|
@ -270,7 +270,7 @@ function detectRecurringPatterns(result) {
|
||||||
}
|
}
|
||||||
return proposals;
|
return proposals;
|
||||||
}
|
}
|
||||||
function renderSkillProposals(result) {
|
function _renderSkillProposals(result) {
|
||||||
const proposals = detectRecurringPatterns(result);
|
const proposals = detectRecurringPatterns(result);
|
||||||
if (proposals.length === 0) return "\n";
|
if (proposals.length === 0) return "\n";
|
||||||
return (
|
return (
|
||||||
|
|
@ -367,7 +367,7 @@ function normalizedItems(result, createdAt) {
|
||||||
for (const item of result.unclear_notes) push("unclear_note", item);
|
for (const item of result.unclear_notes) push("unclear_note", item);
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
function renderNormalizedJsonl(result, createdAt) {
|
function _renderNormalizedJsonl(result, createdAt) {
|
||||||
const items = normalizedItems(result, createdAt);
|
const items = normalizedItems(result, createdAt);
|
||||||
return (
|
return (
|
||||||
items
|
items
|
||||||
|
|
@ -565,7 +565,14 @@ export async function triageTodoDump(basePath, llmCall, options = {}) {
|
||||||
insertTriageEval(crypto.randomUUID(), id, item, createdAt);
|
insertTriageEval(crypto.randomUUID(), id, item, createdAt);
|
||||||
}
|
}
|
||||||
for (const item of normalizedItems(result, createdAt)) {
|
for (const item of normalizedItems(result, createdAt)) {
|
||||||
insertTriageItem(crypto.randomUUID(), id, item.kind, item.content, item.evidence, createdAt);
|
insertTriageItem(
|
||||||
|
crypto.randomUUID(),
|
||||||
|
id,
|
||||||
|
item.kind,
|
||||||
|
item.content,
|
||||||
|
item.evidence,
|
||||||
|
createdAt,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const skillProposals = detectRecurringPatterns(result);
|
const skillProposals = detectRecurringPatterns(result);
|
||||||
for (const skill of skillProposals) {
|
for (const skill of skillProposals) {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import {
|
||||||
setAllExperimentalFlags,
|
setAllExperimentalFlags,
|
||||||
setExperimentalFlag,
|
setExperimentalFlag,
|
||||||
} from "../../experimental.js";
|
} from "../../experimental.js";
|
||||||
|
import { inferPresetName, resolvePreset } from "../../operating-model.js";
|
||||||
import {
|
import {
|
||||||
getGlobalSFPreferencesPath,
|
getGlobalSFPreferencesPath,
|
||||||
getProjectSFPreferencesPath,
|
getProjectSFPreferencesPath,
|
||||||
|
|
@ -28,11 +29,6 @@ import { formattedShortcutPair } from "../../shortcut-defs.js";
|
||||||
import { deriveState } from "../../state.js";
|
import { deriveState } from "../../state.js";
|
||||||
import { writeUokDiagnostics } from "../../uok/diagnostic-synthesis.js";
|
import { writeUokDiagnostics } from "../../uok/diagnostic-synthesis.js";
|
||||||
import { projectRoot } from "../context.js";
|
import { projectRoot } from "../context.js";
|
||||||
import {
|
|
||||||
SF_MODE_PRESET_NAMES,
|
|
||||||
inferPresetName,
|
|
||||||
resolvePreset,
|
|
||||||
} from "../../operating-model.js";
|
|
||||||
export function showHelp(ctx, args = "") {
|
export function showHelp(ctx, args = "") {
|
||||||
const summaryLines = [
|
const summaryLines = [
|
||||||
"SF — Singularity Forge\n",
|
"SF — Singularity Forge\n",
|
||||||
|
|
|
||||||
|
|
@ -432,8 +432,12 @@ function openMetricsDb(basePath) {
|
||||||
)
|
)
|
||||||
`);
|
`);
|
||||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_metrics_name ON metrics(name)`);
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_metrics_name ON metrics(name)`);
|
||||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_metrics_session ON metrics(session_id)`);
|
db.exec(
|
||||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_metrics_name_ts ON metrics(name, timestamp DESC)`);
|
`CREATE INDEX IF NOT EXISTS idx_metrics_session ON metrics(session_id)`,
|
||||||
|
);
|
||||||
|
db.exec(
|
||||||
|
`CREATE INDEX IF NOT EXISTS idx_metrics_name_ts ON metrics(name, timestamp DESC)`,
|
||||||
|
);
|
||||||
_metricsDb = db;
|
_metricsDb = db;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logWarning("metrics-central", `Failed to open metrics.db: ${err.message}`);
|
logWarning("metrics-central", `Failed to open metrics.db: ${err.message}`);
|
||||||
|
|
@ -450,7 +454,7 @@ function closeMetricsDb() {
|
||||||
_metricsDb = null;
|
_metricsDb = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureMetricsTable(db) {
|
function _ensureMetricsTable(db) {
|
||||||
// no-op — metrics.db is set up by openMetricsDb
|
// no-op — metrics.db is set up by openMetricsDb
|
||||||
void db;
|
void db;
|
||||||
}
|
}
|
||||||
|
|
@ -513,11 +517,13 @@ function persistMetricsToDb(registry, sessionId, _ignored) {
|
||||||
try {
|
try {
|
||||||
const row = _metricsDb?.prepare("SELECT count(*) as n FROM metrics").get();
|
const row = _metricsDb?.prepare("SELECT count(*) as n FROM metrics").get();
|
||||||
if (row && row.n > METRICS_DB_ROW_CAP) {
|
if (row && row.n > METRICS_DB_ROW_CAP) {
|
||||||
_metricsDb.prepare(
|
_metricsDb
|
||||||
`DELETE FROM metrics WHERE rowid NOT IN (
|
.prepare(
|
||||||
|
`DELETE FROM metrics WHERE rowid NOT IN (
|
||||||
SELECT rowid FROM metrics ORDER BY timestamp DESC LIMIT ${METRICS_DB_ROW_CAP}
|
SELECT rowid FROM metrics ORDER BY timestamp DESC LIMIT ${METRICS_DB_ROW_CAP}
|
||||||
)`,
|
)`,
|
||||||
).run();
|
)
|
||||||
|
.run();
|
||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// swallow — prune failure must never surface to the user
|
// swallow — prune failure must never surface to the user
|
||||||
|
|
|
||||||
|
|
@ -9,21 +9,22 @@
|
||||||
* Consumer: index.js → steerableAutonomousExtension(pi) on every startup.
|
* Consumer: index.js → steerableAutonomousExtension(pi) on every startup.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
|
||||||
handleSteerableModeKey,
|
|
||||||
SteerableAutonomousPanel,
|
|
||||||
} from "./steerable-autonomous-panel.js";
|
|
||||||
import { SF_MODE_PRESET_NAMES, inferPresetName, resolvePreset } from "./operating-model.js";
|
|
||||||
import { getAutoSession } from "./auto/session.js";
|
import { getAutoSession } from "./auto/session.js";
|
||||||
import { isAutoActive, startAutoDetached } from "./auto.js";
|
import { isAutoActive, startAutoDetached } from "./auto.js";
|
||||||
import { projectRoot } from "./commands/context.js";
|
import { projectRoot } from "./commands/context.js";
|
||||||
|
import {
|
||||||
|
inferPresetName,
|
||||||
|
resolvePreset,
|
||||||
|
SF_MODE_PRESET_NAMES,
|
||||||
|
} from "./operating-model.js";
|
||||||
|
import { SteerableAutonomousPanel } from "./steerable-autonomous-panel.js";
|
||||||
|
|
||||||
export default function steerableAutonomousExtension(api) {
|
export default function steerableAutonomousExtension(api) {
|
||||||
let panel = null;
|
let panel = null;
|
||||||
let isAutonomousActive = false;
|
let isAutonomousActive = false;
|
||||||
|
|
||||||
// Track autonomous mode state
|
// Track autonomous mode state
|
||||||
api.on("session_start", async (_, ctx) => {
|
api.on("session_start", async (_, _ctx) => {
|
||||||
isAutonomousActive = false;
|
isAutonomousActive = false;
|
||||||
if (panel) {
|
if (panel) {
|
||||||
panel.hide();
|
panel.hide();
|
||||||
|
|
@ -60,7 +61,8 @@ export default function steerableAutonomousExtension(api) {
|
||||||
}
|
}
|
||||||
const current = inferPresetName(s.getMode()) ?? SF_MODE_PRESET_NAMES[0];
|
const current = inferPresetName(s.getMode()) ?? SF_MODE_PRESET_NAMES[0];
|
||||||
const idx = SF_MODE_PRESET_NAMES.indexOf(current);
|
const idx = SF_MODE_PRESET_NAMES.indexOf(current);
|
||||||
const nextName = SF_MODE_PRESET_NAMES[(idx + 1) % SF_MODE_PRESET_NAMES.length];
|
const nextName =
|
||||||
|
SF_MODE_PRESET_NAMES[(idx + 1) % SF_MODE_PRESET_NAMES.length];
|
||||||
const preset = resolvePreset(nextName);
|
const preset = resolvePreset(nextName);
|
||||||
s.setMode({
|
s.setMode({
|
||||||
workMode: preset.workMode,
|
workMode: preset.workMode,
|
||||||
|
|
@ -70,16 +72,14 @@ export default function steerableAutonomousExtension(api) {
|
||||||
});
|
});
|
||||||
ctx.ui.notify(`Mode → ${nextName} (${preset.description})`, "info");
|
ctx.ui.notify(`Mode → ${nextName} (${preset.description})`, "info");
|
||||||
} catch {
|
} catch {
|
||||||
ctx.ui.notify(
|
ctx.ui.notify("Mode: use /mode ask|plan|build", "info");
|
||||||
"Mode: use /mode ask|plan|build",
|
|
||||||
"info",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
api.registerShortcut("ctrl+y", {
|
api.registerShortcut("ctrl+y", {
|
||||||
description: "Toggle YOLO mode (build + autonomous + deep + unrestricted; bypass git prompts). If not running, starts the autonomous loop immediately.",
|
description:
|
||||||
|
"Toggle YOLO mode (build + autonomous + deep + unrestricted; bypass git prompts). If not running, starts the autonomous loop immediately.",
|
||||||
handler: async (ctx) => {
|
handler: async (ctx) => {
|
||||||
const session = getAutoSession();
|
const session = getAutoSession();
|
||||||
// Toggle full-autonomy preset in AutoSession (handles mode slam + restore)
|
// Toggle full-autonomy preset in AutoSession (handles mode slam + restore)
|
||||||
|
|
@ -109,7 +109,8 @@ export default function steerableAutonomousExtension(api) {
|
||||||
|
|
||||||
// Handle slash command for panel
|
// Handle slash command for panel
|
||||||
api.registerCommand("steer", {
|
api.registerCommand("steer", {
|
||||||
description: "Open steerable autonomous panel (Shift+Tab during autonomous)",
|
description:
|
||||||
|
"Open steerable autonomous panel (Shift+Tab during autonomous)",
|
||||||
handler: async (_, ctx) => {
|
handler: async (_, ctx) => {
|
||||||
if (!isAutonomousActive) {
|
if (!isAutonomousActive) {
|
||||||
ctx.ui.notify(
|
ctx.ui.notify(
|
||||||
|
|
@ -132,10 +133,7 @@ export default function steerableAutonomousExtension(api) {
|
||||||
// Listen for autonomous mode changes
|
// Listen for autonomous mode changes
|
||||||
api.on("autonomous_start", async (_, ctx) => {
|
api.on("autonomous_start", async (_, ctx) => {
|
||||||
isAutonomousActive = true;
|
isAutonomousActive = true;
|
||||||
ctx.ui.notify(
|
ctx.ui.notify("🤖 Autonomous mode active — Shift+Tab to steer", "info");
|
||||||
"🤖 Autonomous mode active — Shift+Tab to steer",
|
|
||||||
"info",
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
api.on("autonomous_stop", async (_, ctx) => {
|
api.on("autonomous_stop", async (_, ctx) => {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createInterface } from "node:readline";
|
import { createInterface } from "node:readline";
|
||||||
import { getEditorKeybindings } from "@singularity-forge/pi-tui";
|
|
||||||
|
|
||||||
// ─── Constants ──────────────────────────────────────────────────────────────
|
// ─── Constants ──────────────────────────────────────────────────────────────
|
||||||
const PANEL_WIDTH = 60;
|
const PANEL_WIDTH = 60;
|
||||||
|
|
@ -216,7 +215,7 @@ const ACTION_HANDLERS = {
|
||||||
// Would trigger immediate reassessment
|
// Would trigger immediate reassessment
|
||||||
},
|
},
|
||||||
|
|
||||||
close: async (ctx) => {
|
close: async (_ctx) => {
|
||||||
// Just hide the panel
|
// Just hide the panel
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -247,7 +246,7 @@ export class SteerableAutonomousPanel {
|
||||||
this.render();
|
this.render();
|
||||||
|
|
||||||
// Set up key listener
|
// Set up key listener
|
||||||
this.rl.input.on("keypress", (str, key) => {
|
this.rl.input.on("keypress", (_str, key) => {
|
||||||
this.handleKeyPress(key);
|
this.handleKeyPress(key);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@
|
||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import { test } from "vitest";
|
import { test } from "vitest";
|
||||||
import {
|
import {
|
||||||
clearRunawayGuardState,
|
|
||||||
evaluateRunawayGuard,
|
evaluateRunawayGuard,
|
||||||
resetRunawayGuardState,
|
resetRunawayGuardState,
|
||||||
} from "../auto-runaway-guard.js";
|
} from "../auto-runaway-guard.js";
|
||||||
|
|
@ -61,7 +60,15 @@ test("progress check returns none regardless of hard-pause conditions", () => {
|
||||||
evaluateRunawayGuard(
|
evaluateRunawayGuard(
|
||||||
"discuss-milestone",
|
"discuss-milestone",
|
||||||
"M001",
|
"M001",
|
||||||
{ toolCalls: 67, sessionTokens: 1_500_000, elapsedMs: 22 * 60 * 1000, changedFiles: 0, worktreeFingerprint: null, worktreeChangedSinceStart: false, topTools: {} },
|
{
|
||||||
|
toolCalls: 67,
|
||||||
|
sessionTokens: 1_500_000,
|
||||||
|
elapsedMs: 22 * 60 * 1000,
|
||||||
|
changedFiles: 0,
|
||||||
|
worktreeFingerprint: null,
|
||||||
|
worktreeChangedSinceStart: false,
|
||||||
|
topTools: {},
|
||||||
|
},
|
||||||
config,
|
config,
|
||||||
now,
|
now,
|
||||||
);
|
);
|
||||||
|
|
@ -71,12 +78,24 @@ test("progress check returns none regardless of hard-pause conditions", () => {
|
||||||
const r = evaluateRunawayGuard(
|
const r = evaluateRunawayGuard(
|
||||||
"discuss-milestone",
|
"discuss-milestone",
|
||||||
"M001",
|
"M001",
|
||||||
{ toolCalls: 67, sessionTokens: 2_000_000, elapsedMs: 25 * 60 * 1000, changedFiles: 1, worktreeFingerprint: null, worktreeChangedSinceStart: false, topTools: {} },
|
{
|
||||||
|
toolCalls: 67,
|
||||||
|
sessionTokens: 2_000_000,
|
||||||
|
elapsedMs: 25 * 60 * 1000,
|
||||||
|
changedFiles: 1,
|
||||||
|
worktreeFingerprint: null,
|
||||||
|
worktreeChangedSinceStart: false,
|
||||||
|
topTools: {},
|
||||||
|
},
|
||||||
config,
|
config,
|
||||||
now + 180_000,
|
now + 180_000,
|
||||||
);
|
);
|
||||||
// The progress check fires BEFORE the hard-pause block, returning 'none'
|
// The progress check fires BEFORE the hard-pause block, returning 'none'
|
||||||
assert.equal(r.action, "none", "progress check should return none even when hardPause conditions are met");
|
assert.equal(
|
||||||
|
r.action,
|
||||||
|
"none",
|
||||||
|
"progress check should return none even when hardPause conditions are met",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("returns none when changedFiles > 0 despite token growth and 2 warnings", () => {
|
test("returns none when changedFiles > 0 despite token growth and 2 warnings", () => {
|
||||||
|
|
@ -133,7 +152,11 @@ test("returns none when worktreeChangedSinceStart === true despite token growth"
|
||||||
config,
|
config,
|
||||||
now + 180_000,
|
now + 180_000,
|
||||||
);
|
);
|
||||||
assert.equal(r.action, "none", "should not pause when worktreeChangedSinceStart === true");
|
assert.equal(
|
||||||
|
r.action,
|
||||||
|
"none",
|
||||||
|
"should not pause when worktreeChangedSinceStart === true",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("returns none when changedFiles is explicitly 0 but worktreeChangedSinceStart is false", () => {
|
test("returns none when changedFiles is explicitly 0 but worktreeChangedSinceStart is false", () => {
|
||||||
|
|
@ -154,7 +177,11 @@ test("returns none when changedFiles is explicitly 0 but worktreeChangedSinceSta
|
||||||
const r = evaluateRunawayGuard(
|
const r = evaluateRunawayGuard(
|
||||||
"discuss-milestone",
|
"discuss-milestone",
|
||||||
"M001",
|
"M001",
|
||||||
makeMetrics({ sessionTokens: 2_940_000, changedFiles: 0, worktreeChangedSinceStart: false }),
|
makeMetrics({
|
||||||
|
sessionTokens: 2_940_000,
|
||||||
|
changedFiles: 0,
|
||||||
|
worktreeChangedSinceStart: false,
|
||||||
|
}),
|
||||||
config,
|
config,
|
||||||
now + 180_000,
|
now + 180_000,
|
||||||
);
|
);
|
||||||
|
|
@ -199,5 +226,9 @@ test("discuss-milestone with file changes does not get hard-paused", () => {
|
||||||
config,
|
config,
|
||||||
now + 180_000,
|
now + 180_000,
|
||||||
);
|
);
|
||||||
assert.equal(r.action, "none", "discuss-milestone with file changes should not be paused");
|
assert.equal(
|
||||||
|
r.action,
|
||||||
|
"none",
|
||||||
|
"discuss-milestone with file changes should not be paused",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -12,18 +12,12 @@ import { mkdtempSync, rmSync } from "node:fs";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { afterEach, describe, expect, test } from "vitest";
|
import { afterEach, describe, expect, test } from "vitest";
|
||||||
import { closeDatabase } from "../sf-db.js";
|
|
||||||
import { _clearSfRootCache } from "../paths.js";
|
import { _clearSfRootCache } from "../paths.js";
|
||||||
import { PersistentAgent } from "../uok/persistent-agent.js";
|
import { closeDatabase } from "../sf-db.js";
|
||||||
import { AgentSwarm } from "../uok/agent-swarm.js";
|
import { AgentSwarm } from "../uok/agent-swarm.js";
|
||||||
import {
|
import { PersistentAgent } from "../uok/persistent-agent.js";
|
||||||
CoordinatorAgent,
|
|
||||||
WorkerAgent,
|
|
||||||
ScoutAgent,
|
|
||||||
ReviewerAgent,
|
|
||||||
createDefaultSwarm,
|
|
||||||
} from "../uok/swarm-roles.js";
|
|
||||||
import { SwarmDispatchLayer } from "../uok/swarm-dispatch.js";
|
import { SwarmDispatchLayer } from "../uok/swarm-dispatch.js";
|
||||||
|
import { createDefaultSwarm } from "../uok/swarm-roles.js";
|
||||||
|
|
||||||
// ─── Shared Setup ─────────────────────────────────────────────────────────────
|
// ─── Shared Setup ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -86,7 +80,10 @@ describe("PersistentAgent — identity", () => {
|
||||||
describe("PersistentAgent — messaging", () => {
|
describe("PersistentAgent — messaging", () => {
|
||||||
test("send_receive_delivers_message", () => {
|
test("send_receive_delivers_message", () => {
|
||||||
const root = makeProject();
|
const root = makeProject();
|
||||||
const agentA = new PersistentAgent(root, { name: "sender", role: "worker" });
|
const agentA = new PersistentAgent(root, {
|
||||||
|
name: "sender",
|
||||||
|
role: "worker",
|
||||||
|
});
|
||||||
const agentB = new PersistentAgent(root, {
|
const agentB = new PersistentAgent(root, {
|
||||||
name: "receiver",
|
name: "receiver",
|
||||||
role: "worker",
|
role: "worker",
|
||||||
|
|
@ -132,8 +129,14 @@ describe("PersistentAgent — messaging", () => {
|
||||||
name: "broadcaster",
|
name: "broadcaster",
|
||||||
role: "coordinator",
|
role: "coordinator",
|
||||||
});
|
});
|
||||||
const agentB = new PersistentAgent(root, { name: "recv-b", role: "worker" });
|
const agentB = new PersistentAgent(root, {
|
||||||
const agentC = new PersistentAgent(root, { name: "recv-c", role: "worker" });
|
name: "recv-b",
|
||||||
|
role: "worker",
|
||||||
|
});
|
||||||
|
const agentC = new PersistentAgent(root, {
|
||||||
|
name: "recv-c",
|
||||||
|
role: "worker",
|
||||||
|
});
|
||||||
|
|
||||||
agentA.broadcast("announcement", {}, ["recv-b", "recv-c"]);
|
agentA.broadcast("announcement", {}, ["recv-b", "recv-c"]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ function saveTodos(baseDir, todos) {
|
||||||
writeFileSync(todoPath(baseDir), JSON.stringify(todos, null, 2), "utf-8");
|
writeFileSync(todoPath(baseDir), JSON.stringify(todos, null, 2), "utf-8");
|
||||||
}
|
}
|
||||||
|
|
||||||
function nextId(todos) {
|
function nextId(_todos) {
|
||||||
// Short base-36 timestamp suffix for readable IDs.
|
// Short base-36 timestamp suffix for readable IDs.
|
||||||
return `t${Date.now().toString(36)}`;
|
return `t${Date.now().toString(36)}`;
|
||||||
}
|
}
|
||||||
|
|
@ -103,9 +103,7 @@ export function executeSessionTodoList(baseDir) {
|
||||||
details: { operation: "list", todos: [] },
|
details: { operation: "list", todos: [] },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const lines = todos.map(
|
const lines = todos.map((t) => `[${t.done ? "x" : " "}] ${t.id}: ${t.text}`);
|
||||||
(t) => `[${t.done ? "x" : " "}] ${t.id}: ${t.text}`,
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
content: [{ type: "text", text: lines.join("\n") }],
|
content: [{ type: "text", text: lines.join("\n") }],
|
||||||
details: { operation: "list", todos },
|
details: { operation: "list", todos },
|
||||||
|
|
|
||||||
|
|
@ -518,6 +518,9 @@ test("fresh sf --web browser onboarding stays locked on failed validation and un
|
||||||
launchCwd: tempProject,
|
launchCwd: tempProject,
|
||||||
tempHome,
|
tempHome,
|
||||||
browserLogPath,
|
browserLogPath,
|
||||||
|
// Cap launch at 120s so the remaining budget (~180s+) covers API calls
|
||||||
|
// (valid-key validation triggers a bridge restart = up to 120s itself).
|
||||||
|
timeoutMs: 120_000,
|
||||||
env: {
|
env: {
|
||||||
SF_WEB_TEST_FAKE_API_KEY_VALIDATION: "1",
|
SF_WEB_TEST_FAKE_API_KEY_VALIDATION: "1",
|
||||||
ANTHROPIC_API_KEY: "",
|
ANTHROPIC_API_KEY: "",
|
||||||
|
|
@ -574,7 +577,7 @@ test("fresh sf --web browser onboarding stays locked on failed validation and un
|
||||||
const bootBefore = await fetch(`${launch.url}/api/boot`, {
|
const bootBefore = await fetch(`${launch.url}/api/boot`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: { Accept: "application/json", ...auth },
|
headers: { Accept: "application/json", ...auth },
|
||||||
signal: AbortSignal.timeout(10_000),
|
signal: AbortSignal.timeout(30_000),
|
||||||
});
|
});
|
||||||
assert.equal(
|
assert.equal(
|
||||||
bootBefore.ok,
|
bootBefore.ok,
|
||||||
|
|
@ -598,7 +601,7 @@ test("fresh sf --web browser onboarding stays locked on failed validation and un
|
||||||
providerId: "openai",
|
providerId: "openai",
|
||||||
apiKey: "invalid-demo-key",
|
apiKey: "invalid-demo-key",
|
||||||
}),
|
}),
|
||||||
signal: AbortSignal.timeout(10_000),
|
signal: AbortSignal.timeout(30_000),
|
||||||
});
|
});
|
||||||
assert.equal(invalidValidation.status, 422);
|
assert.equal(invalidValidation.status, 422);
|
||||||
const invalidPayload = (await invalidValidation.json()) as any;
|
const invalidPayload = (await invalidValidation.json()) as any;
|
||||||
|
|
@ -637,7 +640,7 @@ test("fresh sf --web browser onboarding stays locked on failed validation and un
|
||||||
const bootAfter = await fetch(`${launch.url}/api/boot`, {
|
const bootAfter = await fetch(`${launch.url}/api/boot`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: { Accept: "application/json", ...auth },
|
headers: { Accept: "application/json", ...auth },
|
||||||
signal: AbortSignal.timeout(10_000),
|
signal: AbortSignal.timeout(30_000),
|
||||||
});
|
});
|
||||||
const bootAfterText = await bootAfter.clone().text();
|
const bootAfterText = await bootAfter.clone().text();
|
||||||
assert.equal(
|
assert.equal(
|
||||||
|
|
@ -648,4 +651,4 @@ test("fresh sf --web browser onboarding stays locked on failed validation and un
|
||||||
const bootAfterPayload = (await bootAfter.json()) as any;
|
const bootAfterPayload = (await bootAfter.json()) as any;
|
||||||
assert.equal(bootAfterPayload.onboarding.locked, false);
|
assert.equal(bootAfterPayload.onboarding.locked, false);
|
||||||
assert.equal(bootAfterPayload.onboarding.lockReason, null);
|
assert.equal(bootAfterPayload.onboarding.lockReason, null);
|
||||||
}, 180_000);
|
}, 420_000);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue