sf snapshot: uncommitted changes after 60m inactivity
This commit is contained in:
parent
f66555456f
commit
15185c2e7d
10 changed files with 64 additions and 12 deletions
BIN
.sf/backups/db/sf.db.2026-05-09T22-35-23-473Z
Normal file
BIN
.sf/backups/db/sf.db.2026-05-09T22-35-23-473Z
Normal file
Binary file not shown.
BIN
.sf/backups/db/sf.db.2026-05-09T22-51-01-800Z
Normal file
BIN
.sf/backups/db/sf.db.2026-05-09T22-51-01-800Z
Normal file
Binary file not shown.
BIN
.sf/metrics.db
BIN
.sf/metrics.db
Binary file not shown.
BIN
.sf/metrics.db-shm
Normal file
BIN
.sf/metrics.db-shm
Normal file
Binary file not shown.
BIN
.sf/metrics.db-wal
Normal file
BIN
.sf/metrics.db-wal
Normal file
Binary file not shown.
|
|
@ -162,7 +162,7 @@ export function registerHooks(pi, ecosystemHandlers = []) {
|
|||
const sid = ctx.sessionManager?.getSessionId?.() ?? "";
|
||||
const sfile = ctx.sessionManager?.getSessionFile?.() ?? "";
|
||||
if (sid) {
|
||||
process.stderr.write(`[forge] session ${sid.slice(0, 8)} · ${sfile}\n`);
|
||||
process.stderr.write(`[forge] session ${sid.slice(0, 8)}\n`);
|
||||
}
|
||||
// Establish the session row so all subsequent turns have a parent.
|
||||
// Git context (repo, branch) is patched in before_agent_start once the
|
||||
|
|
|
|||
|
|
@ -8,18 +8,16 @@
|
|||
*/
|
||||
import { formatCost, getLedger, loadLedgerFromDisk } from "./metrics.js";
|
||||
import { queryMetrics } from "./metrics-central.js";
|
||||
import { getDatabase } from "./sf-db.js";
|
||||
|
||||
export async function handleCost(args, ctx, basePath) {
|
||||
const showSession = args.includes("--session");
|
||||
const showAll = args.includes("--all");
|
||||
const showPrometheus = args.includes("--prometheus");
|
||||
|
||||
// Try metrics-central DB first
|
||||
const db = getDatabase();
|
||||
if (db && (showSession || showAll)) {
|
||||
// Try metrics-central DB first (queryMetrics uses its own metrics.db connection)
|
||||
if (showSession || showAll) {
|
||||
const sessionId = showSession ? extractSessionId() : null;
|
||||
const rows = queryMetrics(db, sessionId, "sf_cost_total", 1000);
|
||||
const rows = queryMetrics(null, sessionId, "sf_cost_total", 1000);
|
||||
if (rows.length > 0) {
|
||||
const totalCost = rows.reduce((sum, r) => sum + (r.value || 0), 0);
|
||||
const lines = [
|
||||
|
|
|
|||
|
|
@ -659,7 +659,7 @@ function updateMetricsSystemHealth() {
|
|||
"Database connection status (1=connected, 0=disconnected)",
|
||||
["project_path"],
|
||||
)
|
||||
.set({ project_path: _basePath || "unknown" }, _dbAdapter ? 1 : 0);
|
||||
.set({ project_path: _basePath || "unknown" }, _metricsDb ? 1 : 0);
|
||||
|
||||
// Record in-memory metrics count
|
||||
let totalMetrics = 0;
|
||||
|
|
@ -910,8 +910,8 @@ export function readMetricsFile(basePath) {
|
|||
* @param {number} [limit] — max rows to return
|
||||
* @returns {Array} — metric rows
|
||||
*/
|
||||
export function queryMetrics(db, sessionId = null, name = null, limit = 1000) {
|
||||
if (!db) return [];
|
||||
export function queryMetrics(_db, sessionId = null, name = null, limit = 1000) {
|
||||
if (!_metricsDb) return [];
|
||||
try {
|
||||
let sql = "SELECT * FROM metrics WHERE 1=1";
|
||||
const params = [];
|
||||
|
|
@ -925,7 +925,7 @@ export function queryMetrics(db, sessionId = null, name = null, limit = 1000) {
|
|||
}
|
||||
sql += " ORDER BY timestamp DESC LIMIT ?";
|
||||
params.push(limit);
|
||||
const stmt = db.prepare(sql);
|
||||
const stmt = _metricsDb.prepare(sql);
|
||||
return stmt.all(...params);
|
||||
} catch (err) {
|
||||
logWarning("metrics-central", `Query failed: ${err.message}`);
|
||||
|
|
|
|||
|
|
@ -244,7 +244,7 @@ function performDatabaseMaintenance(rawDb, path) {
|
|||
);
|
||||
}
|
||||
}
|
||||
const SCHEMA_VERSION = 56;
|
||||
const SCHEMA_VERSION = 57;
|
||||
function indexExists(db, name) {
|
||||
return !!db
|
||||
.prepare(
|
||||
|
|
@ -3225,6 +3225,19 @@ function migrateSchema(db) {
|
|||
":applied_at": new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
if (currentVersion < 57) {
|
||||
// Schema v57: add archived_at to sessions for soft-delete / archive support.
|
||||
db.exec(`ALTER TABLE sessions ADD COLUMN archived_at TEXT DEFAULT NULL`);
|
||||
db.exec(
|
||||
`CREATE INDEX IF NOT EXISTS idx_sessions_archived ON sessions(archived_at) WHERE archived_at IS NOT NULL`,
|
||||
);
|
||||
db.prepare(
|
||||
"INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)",
|
||||
).run({
|
||||
":version": 57,
|
||||
":applied_at": new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
db.exec("COMMIT");
|
||||
} catch (err) {
|
||||
db.exec("ROLLBACK");
|
||||
|
|
@ -7252,6 +7265,38 @@ export function upsertSession(entry) {
|
|||
":now": now,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a session as archived. Archived sessions are hidden from default
|
||||
* session listings but retained for search and audit.
|
||||
*
|
||||
* Purpose: soft-delete sessions without losing their turn history or refs.
|
||||
* Consumer: /sf sessions --archive <id>, autonomous cleanup.
|
||||
*/
|
||||
export function archiveSession(sessionId) {
|
||||
if (!currentDb) return;
|
||||
currentDb
|
||||
.prepare(
|
||||
`UPDATE sessions SET archived_at = :now, updated_at = :now WHERE session_id = :session_id`,
|
||||
)
|
||||
.run({ ":session_id": sessionId, ":now": new Date().toISOString() });
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore an archived session to active status.
|
||||
*
|
||||
* Purpose: undo an accidental archive without data loss.
|
||||
* Consumer: /sf sessions --unarchive <id>.
|
||||
*/
|
||||
export function unarchiveSession(sessionId) {
|
||||
if (!currentDb) return;
|
||||
currentDb
|
||||
.prepare(
|
||||
`UPDATE sessions SET archived_at = NULL, updated_at = :now WHERE session_id = :session_id`,
|
||||
)
|
||||
.run({ ":session_id": sessionId, ":now": new Date().toISOString() });
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a turn row for a session. Returns the new turn's integer id so the
|
||||
* caller can link subsequent file-touches and refs to it.
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import {
|
|||
} from "./steerable-autonomous-panel.js";
|
||||
import { SF_MODE_PRESET_NAMES, inferPresetName, resolvePreset } from "./operating-model.js";
|
||||
import { getAutoSession } from "./auto/session.js";
|
||||
import { isAutoActive, startAutoDetached } from "./auto.js";
|
||||
import { projectRoot } from "./commands/context.js";
|
||||
|
||||
export default function steerableAutonomousExtension(api) {
|
||||
let panel = null;
|
||||
|
|
@ -77,7 +79,7 @@ export default function steerableAutonomousExtension(api) {
|
|||
});
|
||||
|
||||
api.registerShortcut("ctrl+y", {
|
||||
description: "Toggle YOLO mode (build + autonomous + deep + unrestricted; bypass git prompts)",
|
||||
description: "Toggle YOLO mode (build + autonomous + deep + unrestricted; bypass git prompts). If not running, starts the autonomous loop immediately.",
|
||||
handler: async (ctx) => {
|
||||
const session = getAutoSession();
|
||||
// Toggle full-autonomy preset in AutoSession (handles mode slam + restore)
|
||||
|
|
@ -92,6 +94,13 @@ export default function steerableAutonomousExtension(api) {
|
|||
? "🚀 YOLO — Build mode · no git prompts · no confirmation dialogs"
|
||||
: "🚀 YOLO — no git prompts · no confirmation dialogs · deep model";
|
||||
ctx.ui.notify(msg, "success");
|
||||
// Start autonomous loop immediately if not already running
|
||||
if (!isAutoActive()) {
|
||||
startAutoDetached(ctx, api, projectRoot(), false, {
|
||||
canAskUser: false,
|
||||
skipModeGate: true,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
ctx.ui.notify("YOLO OFF — Build mode (git prompts restored)", "info");
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue