Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion src/gep/a2aProtocol.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const { getGepAssetsDir } = require('./paths');
const { getGepAssetsDir, getEvolverLogPath } = require('./paths');
const { computeAssetId } = require('./contentHash');
const { captureEnvFingerprint } = require('./envFingerprint');
const os = require('os');
Expand Down Expand Up @@ -547,6 +547,28 @@ function sendHeartbeat() {
_latestAvailableWork = data.available_work;
}
_heartbeatConsecutiveFailures = 0;
try {
var logPath = getEvolverLogPath();
fs.mkdirSync(path.dirname(logPath), { recursive: true });
var now = new Date();
try {
fs.utimesSync(logPath, now, now);
} catch (e) {
if (e && e.code === 'ENOENT') {
try {
var fd = fs.openSync(logPath, 'a');
fs.closeSync(fd);
fs.utimesSync(logPath, now, now);
} catch (innerErr) {
console.warn('[Heartbeat] Failed to create evolver_loop.log: ' + innerErr.message);
}
} else {
console.warn('[Heartbeat] Failed to touch evolver_loop.log: ' + e.message);
}
}
} catch (outerErr) {
console.warn('[Heartbeat] Failed to ensure evolver_loop.log: ' + outerErr.message);
}
return { ok: true, response: data };
})
.catch(function (err) {
Expand Down
5 changes: 5 additions & 0 deletions src/gep/paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ function getLogsDir() {
return process.env.EVOLVER_LOGS_DIR || path.join(getWorkspaceRoot(), 'logs');
}

function getEvolverLogPath() {
return path.join(getLogsDir(), 'evolver_loop.log');
}

function getMemoryDir() {
return process.env.MEMORY_DIR || path.join(getWorkspaceRoot(), 'memory');
}
Expand Down Expand Up @@ -96,6 +100,7 @@ module.exports = {
getRepoRoot,
getWorkspaceRoot,
getLogsDir,
getEvolverLogPath,
getMemoryDir,
getEvolutionDir,
getGepAssetsDir,
Expand Down
4 changes: 2 additions & 2 deletions src/ops/lifecycle.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
const fs = require('fs');
const path = require('path');
const { execSync, spawn } = require('child_process');
const { getRepoRoot, getWorkspaceRoot, getLogsDir } = require('../gep/paths');
const { getRepoRoot, getWorkspaceRoot, getEvolverLogPath } = require('../gep/paths');

var WORKSPACE_ROOT = getWorkspaceRoot();
var LOG_FILE = path.join(getLogsDir(), 'evolver_loop.log');
var LOG_FILE = getEvolverLogPath();
var PID_FILE = path.join(WORKSPACE_ROOT, 'memory', 'evolver_loop.pid');
var MAX_SILENCE_MS = 30 * 60 * 1000;

Expand Down
67 changes: 66 additions & 1 deletion test/a2aProtocol.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
const { describe, it } = require('node:test');
const { describe, it, before, after } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const os = require('os');
const path = require('path');
const {
PROTOCOL_NAME,
PROTOCOL_VERSION,
Expand All @@ -13,6 +16,7 @@ const {
buildRevoke,
isValidProtocolMessage,
unwrapAssetFromMessage,
sendHeartbeat,
} = require('../src/gep/a2aProtocol');

describe('protocol constants', () => {
Expand Down Expand Up @@ -132,3 +136,64 @@ describe('unwrapAssetFromMessage', () => {
assert.equal(unwrapAssetFromMessage('string'), null);
});
});

describe('sendHeartbeat log touch', () => {
var tmpDir;
var originalFetch;
var originalHubUrl;
var originalLogsDir;

before(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'evolver-hb-test-'));
originalHubUrl = process.env.A2A_HUB_URL;
originalLogsDir = process.env.EVOLVER_LOGS_DIR;
process.env.A2A_HUB_URL = 'http://localhost:19999';
process.env.EVOLVER_LOGS_DIR = tmpDir;
originalFetch = global.fetch;
});

after(() => {
global.fetch = originalFetch;
if (originalHubUrl === undefined) {
delete process.env.A2A_HUB_URL;
} else {
process.env.A2A_HUB_URL = originalHubUrl;
}
if (originalLogsDir === undefined) {
delete process.env.EVOLVER_LOGS_DIR;
} else {
process.env.EVOLVER_LOGS_DIR = originalLogsDir;
}
fs.rmSync(tmpDir, { recursive: true, force: true });
});

it('updates mtime of existing evolver_loop.log on successful heartbeat', async () => {
var logPath = path.join(tmpDir, 'evolver_loop.log');
fs.writeFileSync(logPath, '');
var oldTime = new Date(Date.now() - 5000);
fs.utimesSync(logPath, oldTime, oldTime);

global.fetch = async () => ({
json: async () => ({ status: 'ok' }),
});

var result = await sendHeartbeat();
assert.ok(result.ok, 'heartbeat should succeed');

var mtime = fs.statSync(logPath).mtimeMs;
assert.ok(mtime > oldTime.getTime(), 'mtime should be newer than the pre-set old time');
});

it('creates evolver_loop.log when it does not exist on successful heartbeat', async () => {
var logPath = path.join(tmpDir, 'evolver_loop.log');
if (fs.existsSync(logPath)) fs.unlinkSync(logPath);

global.fetch = async () => ({
json: async () => ({ status: 'ok' }),
});

var result = await sendHeartbeat();
assert.ok(result.ok, 'heartbeat should succeed');
assert.ok(fs.existsSync(logPath), 'evolver_loop.log should be created when missing');
});
});
Loading