mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-03 23:53:29 +08:00
fix: clean up observer sessions on lifecycle end
This commit is contained in:
175
scripts/lib/observer-sessions.js
Normal file
175
scripts/lib/observer-sessions.js
Normal file
@@ -0,0 +1,175 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const { spawnSync } = require('child_process');
|
||||
const { getClaudeDir, ensureDir, sanitizeSessionId } = require('./utils');
|
||||
|
||||
function getHomunculusDir() {
|
||||
return path.join(getClaudeDir(), 'homunculus');
|
||||
}
|
||||
|
||||
function getProjectsDir() {
|
||||
return path.join(getHomunculusDir(), 'projects');
|
||||
}
|
||||
|
||||
function getProjectRegistryPath() {
|
||||
return path.join(getHomunculusDir(), 'projects.json');
|
||||
}
|
||||
|
||||
function readProjectRegistry() {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(getProjectRegistryPath(), 'utf8'));
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function runGit(args, cwd) {
|
||||
const result = spawnSync('git', args, {
|
||||
cwd,
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'ignore']
|
||||
});
|
||||
if (result.status !== 0) return '';
|
||||
return (result.stdout || '').trim();
|
||||
}
|
||||
|
||||
function stripRemoteCredentials(remoteUrl) {
|
||||
if (!remoteUrl) return '';
|
||||
return String(remoteUrl).replace(/:\/\/[^@]+@/, '://');
|
||||
}
|
||||
|
||||
function resolveProjectRoot(cwd = process.cwd()) {
|
||||
const envRoot = process.env.CLAUDE_PROJECT_DIR;
|
||||
if (envRoot && fs.existsSync(envRoot)) {
|
||||
return path.resolve(envRoot);
|
||||
}
|
||||
|
||||
const gitRoot = runGit(['rev-parse', '--show-toplevel'], cwd);
|
||||
if (gitRoot) return path.resolve(gitRoot);
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function computeProjectId(projectRoot) {
|
||||
const remoteUrl = stripRemoteCredentials(runGit(['remote', 'get-url', 'origin'], projectRoot));
|
||||
return crypto.createHash('sha256').update(remoteUrl || projectRoot).digest('hex').slice(0, 12);
|
||||
}
|
||||
|
||||
function resolveProjectContext(cwd = process.cwd()) {
|
||||
const projectRoot = resolveProjectRoot(cwd);
|
||||
if (!projectRoot) {
|
||||
const projectDir = getHomunculusDir();
|
||||
ensureDir(projectDir);
|
||||
return { projectId: 'global', projectRoot: '', projectDir, isGlobal: true };
|
||||
}
|
||||
|
||||
const registry = readProjectRegistry();
|
||||
const registryEntry = Object.values(registry).find(entry => entry && path.resolve(entry.root || '') === projectRoot);
|
||||
const projectId = registryEntry?.id || computeProjectId(projectRoot);
|
||||
const projectDir = path.join(getProjectsDir(), projectId);
|
||||
ensureDir(projectDir);
|
||||
|
||||
return { projectId, projectRoot, projectDir, isGlobal: false };
|
||||
}
|
||||
|
||||
function getObserverPidFile(context) {
|
||||
return path.join(context.projectDir, '.observer.pid');
|
||||
}
|
||||
|
||||
function getObserverSignalCounterFile(context) {
|
||||
return path.join(context.projectDir, '.observer-signal-counter');
|
||||
}
|
||||
|
||||
function getObserverActivityFile(context) {
|
||||
return path.join(context.projectDir, '.observer-last-activity');
|
||||
}
|
||||
|
||||
function getSessionLeaseDir(context) {
|
||||
return path.join(context.projectDir, '.observer-sessions');
|
||||
}
|
||||
|
||||
function resolveSessionId(rawSessionId = process.env.CLAUDE_SESSION_ID) {
|
||||
return sanitizeSessionId(rawSessionId || '') || '';
|
||||
}
|
||||
|
||||
function getSessionLeaseFile(context, rawSessionId = process.env.CLAUDE_SESSION_ID) {
|
||||
const sessionId = resolveSessionId(rawSessionId);
|
||||
if (!sessionId) return '';
|
||||
return path.join(getSessionLeaseDir(context), `${sessionId}.json`);
|
||||
}
|
||||
|
||||
function writeSessionLease(context, rawSessionId = process.env.CLAUDE_SESSION_ID, extra = {}) {
|
||||
const leaseFile = getSessionLeaseFile(context, rawSessionId);
|
||||
if (!leaseFile) return '';
|
||||
|
||||
ensureDir(getSessionLeaseDir(context));
|
||||
const payload = {
|
||||
sessionId: resolveSessionId(rawSessionId),
|
||||
cwd: process.cwd(),
|
||||
pid: process.pid,
|
||||
updatedAt: new Date().toISOString(),
|
||||
...extra
|
||||
};
|
||||
fs.writeFileSync(leaseFile, JSON.stringify(payload, null, 2) + '\n');
|
||||
return leaseFile;
|
||||
}
|
||||
|
||||
function removeSessionLease(context, rawSessionId = process.env.CLAUDE_SESSION_ID) {
|
||||
const leaseFile = getSessionLeaseFile(context, rawSessionId);
|
||||
if (!leaseFile) return false;
|
||||
try {
|
||||
fs.rmSync(leaseFile, { force: true });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function listSessionLeases(context) {
|
||||
const leaseDir = getSessionLeaseDir(context);
|
||||
if (!fs.existsSync(leaseDir)) return [];
|
||||
return fs.readdirSync(leaseDir)
|
||||
.filter(name => name.endsWith('.json'))
|
||||
.map(name => path.join(leaseDir, name));
|
||||
}
|
||||
|
||||
function stopObserverForContext(context) {
|
||||
const pidFile = getObserverPidFile(context);
|
||||
if (!fs.existsSync(pidFile)) return false;
|
||||
|
||||
const pid = (fs.readFileSync(pidFile, 'utf8') || '').trim();
|
||||
if (!/^[0-9]+$/.test(pid) || pid === '0' || pid === '1') {
|
||||
fs.rmSync(pidFile, { force: true });
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
process.kill(Number(pid), 0);
|
||||
} catch {
|
||||
fs.rmSync(pidFile, { force: true });
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
process.kill(Number(pid), 'SIGTERM');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
fs.rmSync(pidFile, { force: true });
|
||||
fs.rmSync(getObserverSignalCounterFile(context), { force: true });
|
||||
return true;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
resolveProjectContext,
|
||||
getObserverActivityFile,
|
||||
getObserverPidFile,
|
||||
getSessionLeaseDir,
|
||||
writeSessionLease,
|
||||
removeSessionLease,
|
||||
listSessionLeases,
|
||||
stopObserverForContext,
|
||||
resolveSessionId
|
||||
};
|
||||
Reference in New Issue
Block a user