site-mariage/_byan/mcp/byan-mcp-server/lib/copilot.js
Corentin Joguet bff653acd6 first commit
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 10:30:37 +02:00

148 lines
4.6 KiB
JavaScript

import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
const COPILOT_ROOT = process.env.BYAN_COPILOT_ROOT || path.join(os.homedir(), '.copilot', 'session-state');
function readJsonl(filePath, limit) {
if (!fs.existsSync(filePath)) return [];
const lines = fs.readFileSync(filePath, 'utf8').split('\n').filter(Boolean);
const out = [];
for (const line of lines) {
try {
out.push(JSON.parse(line));
} catch {
// skip malformed
}
if (typeof limit === 'number' && out.length >= limit) break;
}
return out;
}
function summarizeSession(sessionId) {
const eventsPath = path.join(COPILOT_ROOT, sessionId, 'events.jsonl');
if (!fs.existsSync(eventsPath)) return null;
const events = readJsonl(eventsPath);
if (events.length === 0) return null;
const start = events.find((e) => e.type === 'session.start');
const shutdown = events.find((e) => e.type === 'session.shutdown');
const agent = events.find((e) => e.type === 'subagent.selected');
const counts = {};
for (const e of events) {
counts[e.type] = (counts[e.type] || 0) + 1;
}
const userMessages = events.filter((e) => e.type === 'user.message');
const assistantMessages = events.filter((e) => e.type === 'assistant.message');
return {
sessionId,
startTime: start?.data?.startTime || null,
endTime: shutdown?.timestamp || null,
cwd: start?.data?.context?.cwd || null,
branch: start?.data?.context?.branch || null,
agent: agent?.data?.agentName || null,
event_count: events.length,
user_messages: userMessages.length,
assistant_messages: assistantMessages.length,
tool_calls: counts['tool.execution_start'] || 0,
event_type_counts: counts,
};
}
export function listSessions({ limit = 20, sinceIso = null, cwdFilter = null } = {}) {
if (!fs.existsSync(COPILOT_ROOT)) return { root: COPILOT_ROOT, sessions: [], total: 0, exists: false };
const dirs = fs
.readdirSync(COPILOT_ROOT, { withFileTypes: true })
.filter((d) => d.isDirectory())
.map((d) => d.name);
const summaries = [];
for (const id of dirs) {
const s = summarizeSession(id);
if (!s) continue;
if (sinceIso && s.startTime && Date.parse(s.startTime) < Date.parse(sinceIso)) continue;
if (cwdFilter && s.cwd && !s.cwd.includes(cwdFilter)) continue;
summaries.push(s);
}
summaries.sort((a, b) => {
const at = Date.parse(a.startTime || 0);
const bt = Date.parse(b.startTime || 0);
return bt - at;
});
return {
root: COPILOT_ROOT,
total: summaries.length,
exists: true,
sessions: summaries.slice(0, limit),
};
}
export function readSessionEvents({ sessionId, types = null, limit = 200 } = {}) {
if (!sessionId || typeof sessionId !== 'string') {
throw new Error('sessionId is required');
}
const eventsPath = path.join(COPILOT_ROOT, sessionId, 'events.jsonl');
if (!fs.existsSync(eventsPath)) {
throw new Error(`events.jsonl not found for session ${sessionId}`);
}
const allEvents = readJsonl(eventsPath);
const filtered = Array.isArray(types) && types.length > 0
? allEvents.filter((e) => types.includes(e.type))
: allEvents;
return {
sessionId,
total: allEvents.length,
returned: Math.min(filtered.length, limit),
filtered_by_type: Array.isArray(types) ? types : null,
events: filtered.slice(0, limit),
};
}
export function searchSessions({ query, types = ['user.message', 'assistant.message'], limit = 50 } = {}) {
if (!query || typeof query !== 'string') {
throw new Error('query is required');
}
if (!fs.existsSync(COPILOT_ROOT)) return { matches: [], total: 0 };
const q = query.toLowerCase();
const dirs = fs
.readdirSync(COPILOT_ROOT, { withFileTypes: true })
.filter((d) => d.isDirectory())
.map((d) => d.name);
const matches = [];
for (const sessionId of dirs) {
const eventsPath = path.join(COPILOT_ROOT, sessionId, 'events.jsonl');
if (!fs.existsSync(eventsPath)) continue;
const events = readJsonl(eventsPath);
for (const e of events) {
if (!types.includes(e.type)) continue;
const text = typeof e.data?.content === 'string'
? e.data.content
: typeof e.data?.text === 'string'
? e.data.text
: JSON.stringify(e.data || {});
if (text.toLowerCase().includes(q)) {
matches.push({
sessionId,
timestamp: e.timestamp,
type: e.type,
excerpt: text.slice(0, 300),
});
if (matches.length >= limit) break;
}
}
if (matches.length >= limit) break;
}
return { query, total: matches.length, matches };
}