Files
sabia/.opencode/plugin/openwolf/session.ts
T
Lucas e989413018 Esqueleto inicial: sabia-core + sabia-cli + UI local, OpenWolf
- sabia-core: descoberta de interfaces, controle de modo monitor/canal
  via iw (ponte até ter nl80211 nativo), captura crua 802.11 via
  AF_PACKET (sem libpcap), parsing de radiotap e quadros de
  gerenciamento (beacon/probe/EAPOL), agregação em tabela de APs/
  clientes, escrita de .pcap, trilha de auditoria encadeada por hash,
  auto-atualização via GitHub releases (curl, sem dependências de
  rede extras)
- sabia-cli: `sabia iface`, `sabia scan`, `sabia update`,
  `sabia audit-verify`, `sabia ui` (servidor local + navegador,
  protegido por token, mesma filosofia do Guará)
- OpenWolf inicializado para memória de projeto entre agentes
2026-10-23 08:22:04 -03:00

93 lines
3.8 KiB
TypeScript

import * as fs from "node:fs"
import * as path from "node:path"
import { getWolfDir, writeJSON, readJSON, appendMarkdown, timeShort, timestamp, readMarkdown, sessionFilePath, gcSessionFiles } from "./fs.js"
import type { SessionState } from "./types.js"
const sessions = new Map<string, SessionState>()
export function getSessionState(sessionId: string): SessionState | undefined {
return sessions.get(sessionId)
}
export function setSessionState(sessionId: string, state: SessionState): void {
sessions.set(sessionId, state)
}
export function deleteSession(sessionId: string): void {
sessions.delete(sessionId)
}
export function handleSessionStart(directory: string, sessionId: string): void {
const wolfDir = getWolfDir(directory)
if (!fs.existsSync(wolfDir)) return
const hooksDir = path.join(wolfDir, "hooks")
fs.mkdirSync(hooksDir, { recursive: true })
gcSessionFiles(hooksDir)
const sessionFile = sessionFilePath(hooksDir, sessionId)
const existing = readJSON<SessionState | null>(sessionFile, null)
if (existing?.session_id === sessionId) { sessions.set(sessionId, existing); return }
const state: SessionState = {
session_id: sessionId,
started: timestamp(),
files_read: {},
files_written: [],
edit_counts: {},
anatomy_hits: 0,
anatomy_misses: 0,
repeated_reads_warned: 0,
cerebrum_warnings: 0,
stop_count: 0,
}
sessions.set(sessionId, state)
writeJSON(sessionFile, state)
const memoryPath = path.join(wolfDir, "memory.md")
const now = new Date()
const header = `\n## Session: ${now.toISOString().slice(0, 10)} ${timeShort()}\n\n| Time | Action | File(s) | Outcome | ~Tokens |\n|------|--------|---------|---------|--------|\n`
appendMarkdown(memoryPath, header)
try {
const cerebrumPath = path.join(wolfDir, "cerebrum.md")
const cerebrumContent = fs.readFileSync(cerebrumPath, "utf-8")
const stat = fs.statSync(cerebrumPath)
const daysSinceUpdate = (Date.now() - stat.mtimeMs) / (1000 * 60 * 60 * 24)
const entryLines = cerebrumContent.split("\n").filter(l => {
const t = l.trim()
return t.startsWith("- ") || t.startsWith("* ") || (t.startsWith("[") && t.includes("]"))
})
if (entryLines.length < 3) {
console.warn(`💡 OpenWolf: cerebrum.md has only ${entryLines.length} entries. Learn from this session — record user preferences, project conventions, and mistakes to .wolf/cerebrum.md.`)
} else if (daysSinceUpdate > 3) {
console.warn(`💡 OpenWolf: cerebrum.md hasn't been updated in ${Math.floor(daysSinceUpdate)} days. Look for opportunities to add learnings this session.`)
}
} catch {}
try {
const buglogPath = path.join(wolfDir, "buglog.json")
const buglog = readJSON<{ bugs: unknown[] }>(buglogPath, { bugs: [] })
if (buglog.bugs.length === 0) {
console.warn(`📋 OpenWolf: buglog.json is empty. If you encounter or fix any bugs, errors, or failed tests this session, log them to .wolf/buglog.json.`)
}
} catch {}
// Count the new session in the lifetime totals. readJSON deep-merges the
// fallback, so a pre-2.0 ledger without `lifetime` still gets the default;
// the guard below covers a ledger where `lifetime` is not an object at all.
const ledgerPath = path.join(wolfDir, "token-ledger.json")
const ledger = readJSON<Record<string, unknown>>(ledgerPath, { version: 1, lifetime: { total_sessions: 0 } }) as {
version: number
lifetime: { total_sessions: number }
[key: string]: unknown
}
if (!ledger.lifetime || typeof ledger.lifetime !== "object") {
ledger.lifetime = { total_sessions: 0 }
}
if (typeof ledger.lifetime.total_sessions !== "number" || !isFinite(ledger.lifetime.total_sessions)) {
ledger.lifetime.total_sessions = 0
}
ledger.lifetime.total_sessions++
writeJSON(ledgerPath, ledger)
}