Merge branch 'main' into perf/async-memory-save-18240015948353785856

This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2026-02-18 09:48:42 -05:00
commit 0dfbf3f86f
46 files changed
+1755 -633

No files matched your search

-61
View File
@@ -1,61 +0,0 @@
import {
loadTorchConfig,
getRelays,
getNamespace,
} from './torch-config.mjs';
import { getRoster } from './roster.mjs';
import { queryLocks } from './lock-ops.mjs';
import { todayDateStr } from './utils.mjs';
export async function cmdCheck(cadence, deps = {}) {
const {
loadTorchConfigFn = loadTorchConfig,
getRelaysFn = getRelays,
getNamespaceFn = getNamespace,
getRosterFn = getRoster,
queryLocksFn = queryLocks,
todayDateStrFn = todayDateStr,
} = deps;
const relays = await getRelaysFn();
const namespace = await getNamespaceFn();
const dateStr = todayDateStrFn();
const config = await loadTorchConfigFn();
const pausedAgents = (cadence === 'daily' ? config.scheduler.paused.daily : config.scheduler.paused.weekly) || [];
console.error(`Checking locks: namespace=${namespace}, cadence=${cadence}, date=${dateStr}`);
console.error(`Relays: ${relays.join(', ')}`);
if (pausedAgents.length > 0) {
console.error(`Paused agents: ${pausedAgents.join(', ')}`);
}
const locks = await queryLocksFn(relays, cadence, dateStr, namespace);
const lockedAgents = [...new Set(locks.map((l) => l.agent).filter(Boolean))];
const roster = await getRosterFn(cadence);
const excludedAgents = [...new Set([...lockedAgents, ...pausedAgents])];
const unknownLockedAgents = lockedAgents.filter((agent) => !roster.includes(agent));
const available = roster.filter((a) => !excludedAgents.includes(a));
const result = {
namespace,
cadence,
date: dateStr,
locked: lockedAgents.sort(),
paused: pausedAgents.sort(),
excluded: excludedAgents.sort(),
available: available.sort(),
lockCount: locks.length,
unknownLockedAgents: unknownLockedAgents.sort(),
locks: locks.map((l) => ({
agent: l.agent,
eventId: l.eventId,
createdAt: l.createdAtIso,
expiresAt: l.expiresAtIso,
platform: l.platform,
})),
};
console.log(JSON.stringify(result, null, 2));
return result;
}
-122
View File
@@ -1,122 +0,0 @@
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import {
getRelays,
getNamespace,
getTtl,
} from './torch-config.mjs';
import {
RACE_CHECK_DELAY_MS,
KIND_APP_DATA,
} from './constants.mjs';
import { getRoster } from './roster.mjs';
import { queryLocks, publishLock } from './lock-ops.mjs';
import { todayDateStr, nowUnix } from './utils.mjs';
import { ExitError } from './errors.mjs';
export async function cmdLock(agent, cadence, dryRun = false) {
const relays = await getRelays();
const namespace = await getNamespace();
const dateStr = todayDateStr();
const ttl = await getTtl();
const now = nowUnix();
const expiresAt = now + ttl;
console.error(`Locking: namespace=${namespace}, agent=${agent}, cadence=${cadence}, date=${dateStr}`);
console.error(`TTL: ${ttl}s, expires: ${new Date(expiresAt * 1000).toISOString()}`);
console.error(`Relays: ${relays.join(', ')}`);
const roster = await getRoster(cadence);
if (!roster.includes(agent)) {
console.error(`ERROR: agent "${agent}" is not in the ${cadence} roster`);
console.error(`Allowed ${cadence} agents: ${roster.join(', ')}`);
throw new ExitError(1, 'Agent not in roster');
}
console.error('Step 1: Checking for existing locks...');
const existingLocks = await queryLocks(relays, cadence, dateStr, namespace);
const conflicting = existingLocks.filter((l) => l.agent === agent);
if (conflicting.length > 0) {
const earliest = conflicting.sort((a, b) => a.createdAt - b.createdAt)[0];
console.error(
`LOCK DENIED: ${agent} already locked by event ${earliest.eventId} ` +
`(created ${earliest.createdAtIso}, platform: ${earliest.platform})`,
);
console.log('LOCK_STATUS=denied');
console.log('LOCK_REASON=already_locked');
console.log(`LOCK_EXISTING_EVENT=${earliest.eventId}`);
throw new ExitError(3, 'Lock denied');
}
console.error('Step 2: Generating ephemeral keypair...');
const sk = generateSecretKey();
const pk = getPublicKey(sk);
console.error(` Ephemeral pubkey: ${pk.slice(0, 16)}...`);
console.error('Step 3: Building lock event...');
const event = finalizeEvent(
{
kind: KIND_APP_DATA,
created_at: now,
tags: [
['d', `${namespace}-lock/${cadence}/${agent}/${dateStr}`],
['t', `${namespace}-agent-lock`],
['t', `${namespace}-lock-${cadence}`],
['t', `${namespace}-lock-${cadence}-${dateStr}`],
['expiration', String(expiresAt)],
],
content: JSON.stringify({
agent,
cadence,
status: 'started',
namespace,
date: dateStr,
platform: process.env.AGENT_PLATFORM || 'unknown',
lockedAt: new Date(now * 1000).toISOString(),
expiresAt: new Date(expiresAt * 1000).toISOString(),
}),
},
sk,
);
console.error(` Event ID: ${event.id}`);
if (dryRun) {
console.error('Step 4: [DRY RUN] Skipping publish — event built but not sent');
console.error('RACE CHECK: won (dry run — no real contention possible)');
} else {
console.error('Step 4: Publishing to relays...');
await publishLock(relays, event);
console.error('Step 5: Race check...');
await new Promise((resolve) => setTimeout(resolve, RACE_CHECK_DELAY_MS));
const postLocks = await queryLocks(relays, cadence, dateStr, namespace);
const racingLocks = postLocks
.filter((l) => l.agent === agent)
.sort((a, b) => a.createdAt - b.createdAt);
if (racingLocks.length > 1 && racingLocks[0].eventId !== event.id) {
const winner = racingLocks[0];
console.error(
`RACE CHECK: lost (earlier lock by event ${winner.eventId}, created ${winner.createdAtIso})`,
);
console.log('LOCK_STATUS=race_lost');
console.log('LOCK_REASON=earlier_claim_exists');
console.log(`LOCK_WINNER_EVENT=${winner.eventId}`);
throw new ExitError(3, 'Race check lost');
}
console.error('RACE CHECK: won');
}
console.log('LOCK_STATUS=ok');
console.log(`LOCK_EVENT_ID=${event.id}`);
console.log(`LOCK_PUBKEY=${pk}`);
console.log(`LOCK_AGENT=${agent}`);
console.log(`LOCK_CADENCE=${cadence}`);
console.log(`LOCK_DATE=${dateStr}`);
console.log(`LOCK_EXPIRES=${expiresAt}`);
console.log(`LOCK_EXPIRES_ISO=${new Date(expiresAt * 1000).toISOString()}`);
return { status: 'ok', eventId: event.id };
}
@@ -0,0 +1,16 @@
# Context for Run 2026-02-17T16-36-25Z
- Agent: todo-triage-agent
- Cadence: daily
- Run-start: 2026-02-17T16-36-25Z
## Goal
Scan the repository for TODO/FIXME/XXX markers and triage them.
## Scope
src/, docs/, bin/, scripts/ directories.
## Constraints
Only fix trivial items. Report others.
## Active Prompt
src/prompts/daily/todo-triage-agent.md
@@ -0,0 +1,12 @@
---
agent: design-system-audit-agent
cadence: daily
session: session-2026-02-18T02:35:22Z
run-start: 2026-02-18T02:35:22Z
prompt: src/prompts/daily/design-system-audit-agent.md
---
# Context
- Goal: Audit design system compliance.
- Scope: Daily audit of src/ and dashboard/.
- Constraints: Non-blocking, reporting only.
+36 -17
View File
@@ -7,6 +7,13 @@ import { fileURLToPath } from 'node:url';
import { DEFAULT_DASHBOARD_PORT } from './constants.mjs';
import { getDashboardAuth, parseTorchConfig } from './torch-config.mjs';
const SECURITY_HEADERS = {
'Content-Security-Policy': "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' wss:; object-src 'none'; base-uri 'self';",
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'Referrer-Policy': 'strict-origin-when-cross-origin'
};
function timingSafeCompare(a, b) {
const bufA = Buffer.from(a);
const bufB = Buffer.from(b);
@@ -30,7 +37,7 @@ export async function cmdDashboard(port = DEFAULT_DASHBOARD_PORT, host = '127.0.
if (auth) {
const authHeader = req.headers.authorization;
if (!authHeader) {
res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="TORCH Dashboard"' });
res.writeHead(401, { ...SECURITY_HEADERS, 'WWW-Authenticate': 'Basic realm="TORCH Dashboard"' });
res.end('Authentication required');
return;
}
@@ -48,7 +55,7 @@ export async function cmdDashboard(port = DEFAULT_DASHBOARD_PORT, host = '127.0.
}
if (!isValid) {
res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="TORCH Dashboard"' });
res.writeHead(401, { ...SECURITY_HEADERS, 'WWW-Authenticate': 'Basic realm="TORCH Dashboard"' });
res.end('Invalid credentials');
return;
}
@@ -60,7 +67,7 @@ export async function cmdDashboard(port = DEFAULT_DASHBOARD_PORT, host = '127.0.
// Redirect / to /dashboard/
if (pathname === '/' || pathname === '/dashboard') {
res.writeHead(302, { 'Location': '/dashboard/' });
res.writeHead(302, { ...SECURITY_HEADERS, 'Location': '/dashboard/' });
res.end();
return;
}
@@ -105,31 +112,43 @@ export async function cmdDashboard(port = DEFAULT_DASHBOARD_PORT, host = '127.0.
const rawConfig = JSON.parse(configContent);
const safeConfig = parseTorchConfig(rawConfig);
// Security: Sanitize sensitive fields
if (safeConfig.dashboard) {
delete safeConfig.dashboard.auth;
}
delete safeConfig.configPath;
// Security: Whitelist only fields required by the dashboard frontend
const publicConfig = {
dashboard: safeConfig.dashboard ? { ...safeConfig.dashboard } : {},
nostrLock: safeConfig.nostrLock
? {
namespace: safeConfig.nostrLock.namespace,
relays: safeConfig.nostrLock.relays,
}
: {},
};
res.writeHead(200, { 'Content-Type': 'application/json' });
// Remove sensitive dashboard auth
if (publicConfig.dashboard.auth) {
delete publicConfig.dashboard.auth;
}
res.writeHead(200, { ...SECURITY_HEADERS, 'Content-Type': 'application/json' });
res.end(JSON.stringify(safeConfig, null, 2));
return;
} catch (err) {
console.error('Error parsing torch-config.json:', err);
res.writeHead(500);
res.writeHead(500, SECURITY_HEADERS);
res.end('Internal Server Error');
return;
}
} else {
res.writeHead(404);
res.writeHead(404, SECURITY_HEADERS);
res.end('Not Found');
return;
}
}
// Security check: prevent directory traversal
const safePath = path.normalize(pathname).replace(new RegExp('^(\\.\\.[\\/\\\\])+'), '');
let filePath = path.join(packageRoot, safePath);
// Resolve path relative to packageRoot.
// We strip the leading slash from pathname (which comes from URL) to treat it as relative.
const relativePath = pathname.replace(/^\//, '');
let filePath = path.resolve(packageRoot, relativePath);
// Security check: restrict access to allowed paths
const allowedPaths = [
@@ -146,7 +165,7 @@ export async function cmdDashboard(port = DEFAULT_DASHBOARD_PORT, host = '127.0.
});
if (!isAllowed) {
res.writeHead(403);
res.writeHead(403, SECURITY_HEADERS);
res.end('Forbidden');
return;
}
@@ -160,7 +179,7 @@ export async function cmdDashboard(port = DEFAULT_DASHBOARD_PORT, host = '127.0.
// Check if file exists and is a file
if (!fileStat || !fileStat.isFile()) {
res.writeHead(404);
res.writeHead(404, SECURITY_HEADERS);
res.end('Not Found');
return;
}
@@ -181,12 +200,12 @@ export async function cmdDashboard(port = DEFAULT_DASHBOARD_PORT, host = '127.0.
case '.md': contentType = 'text/markdown'; break;
}
res.writeHead(200, { 'Content-Type': contentType });
res.writeHead(200, { ...SECURITY_HEADERS, 'Content-Type': contentType });
fs.createReadStream(filePath).pipe(res);
} catch (err) {
console.error('Dashboard Server Error:', err);
if (!res.headersSent) {
res.writeHead(500);
res.writeHead(500, SECURITY_HEADERS);
res.end('Internal Server Error');
}
}
@@ -0,0 +1,13 @@
# Decisions for Run 2026-02-17T16-36-25Z
- Agent: todo-triage-agent
- Cadence: daily
- Run-start: 2026-02-17T16-36-25Z
## Decision
No changes required.
## Rationale
No actionable TODOs were found in the codebase. The scan returned only documentation references and test data (mktemp usage).
Agent: todo-triage-agent
Prompt: src/prompts/daily/todo-triage-agent.md
@@ -0,0 +1,11 @@
---
agent: design-system-audit-agent
cadence: daily
session: session-2026-02-18T02:35:22Z
run-start: 2026-02-18T02:35:22Z
prompt: src/prompts/daily/design-system-audit-agent.md
---
# Decisions
- Decision: Use automated script for audit.
- Rationale: No existing tool covers all requirements.
+102 -39
View File
@@ -73,10 +73,27 @@ function relayListLabel(relays) {
return relays.join(', ');
}
const PUBLISH_ERROR_CODES = {
TIMEOUT: 'publish_timeout',
DNS: 'dns_resolution',
TCP: 'tcp_connect_timeout',
TLS: 'tls_handshake',
WEBSOCKET: 'websocket_open_failure',
NETWORK: 'network_timeout',
CONNECTION_RESET: 'connection_reset',
RELAY_UNAVAILABLE: 'relay_unavailable',
PERMANENT: 'permanent_validation_error',
};
const PUBLISH_FAILURE_CATEGORIES = {
QUORUM_FAILURE: 'relay_publish_quorum_failure',
NON_RETRYABLE: 'relay_publish_non_retryable',
};
function classifyPublishError(message) {
const normalized = String(message || '').toLowerCase();
if (normalized.includes('publish timed out after') || normalized.includes('publish timeout')) {
return 'publish_timeout';
return PUBLISH_ERROR_CODES.TIMEOUT;
}
if (
normalized.includes('enotfound')
@@ -84,14 +101,14 @@ function classifyPublishError(message) {
|| normalized.includes('getaddrinfo')
|| (normalized.includes('dns') && normalized.includes('websocket'))
) {
return 'dns_resolution';
return PUBLISH_ERROR_CODES.DNS;
}
if (
normalized.includes('connect etimedout')
|| normalized.includes('tcp connect timed out')
|| normalized.includes('connect timeout')
) {
return 'tcp_connect_timeout';
return PUBLISH_ERROR_CODES.TCP;
}
if (
normalized.includes('tls')
@@ -99,20 +116,20 @@ function classifyPublishError(message) {
|| normalized.includes('certificate')
|| normalized.includes('handshake')
) {
return 'tls_handshake';
return PUBLISH_ERROR_CODES.TLS;
}
if (
normalized.includes('websocket')
|| normalized.includes('bad response')
|| normalized.includes('unexpected server response')
) {
return 'websocket_open_failure';
return PUBLISH_ERROR_CODES.WEBSOCKET;
}
if (normalized.includes('timed out') || normalized.includes('timeout') || normalized.includes('etimedout')) {
return 'network_timeout';
return PUBLISH_ERROR_CODES.NETWORK;
}
if (normalized.includes('econnreset') || normalized.includes('connection reset') || normalized.includes('socket hang up')) {
return 'connection_reset';
return PUBLISH_ERROR_CODES.CONNECTION_RESET;
}
if (
normalized.includes('unavailable')
@@ -122,21 +139,21 @@ function classifyPublishError(message) {
|| normalized.includes('enotfound')
|| normalized.includes('503')
) {
return 'relay_unavailable';
return PUBLISH_ERROR_CODES.RELAY_UNAVAILABLE;
}
return 'permanent_validation_error';
return PUBLISH_ERROR_CODES.PERMANENT;
}
function isTransientPublishCategory(category) {
return [
'publish_timeout',
'dns_resolution',
'tcp_connect_timeout',
'tls_handshake',
'websocket_open_failure',
'network_timeout',
'connection_reset',
'relay_unavailable',
PUBLISH_ERROR_CODES.TIMEOUT,
PUBLISH_ERROR_CODES.DNS,
PUBLISH_ERROR_CODES.TCP,
PUBLISH_ERROR_CODES.TLS,
PUBLISH_ERROR_CODES.WEBSOCKET,
PUBLISH_ERROR_CODES.NETWORK,
PUBLISH_ERROR_CODES.CONNECTION_RESET,
PUBLISH_ERROR_CODES.RELAY_UNAVAILABLE,
].includes(category);
}
@@ -188,6 +205,8 @@ export class RelayHealthManager {
constructor() {
this.metricsByRelay = new Map();
this.lastSnapshotAt = 0;
this._metricsVersion = 0;
this._sortedCache = null;
}
ensureMetrics(relay, config) {
@@ -203,31 +222,72 @@ export class RelayHealthManager {
lastResultAt: null,
};
this.metricsByRelay.set(relay, metrics);
this._metricsVersion += 1;
}
return metrics;
}
rankRelays(relays, config, nowMs = Date.now()) {
const summaries = relays.map((relay) => {
const metrics = this.ensureMetrics(relay, config);
const summary = summarizeRelayMetrics(metrics, nowMs);
return {
relay,
summary,
score: computeRelayScore(summary),
};
});
for (const relay of relays) {
this.ensureMetrics(relay, config);
}
return summaries.sort((a, b) => {
if (b.score !== a.score) return b.score - a.score;
if (a.summary.quarantined !== b.summary.quarantined) return a.summary.quarantined ? 1 : -1;
if (a.summary.averageLatencyMs !== b.summary.averageLatencyMs) {
if (a.summary.averageLatencyMs === null) return 1;
if (b.summary.averageLatencyMs === null) return -1;
return a.summary.averageLatencyMs - b.summary.averageLatencyMs;
if (
!this._sortedCache
|| this._sortedCache.version !== this._metricsVersion
|| nowMs >= this._sortedCache.validUntil
) {
let minQuarantineUntil = Infinity;
const entries = [];
for (const metrics of this.metricsByRelay.values()) {
const summary = summarizeRelayMetrics(metrics, nowMs);
const score = computeRelayScore(summary);
if (metrics.quarantineUntil > nowMs && metrics.quarantineUntil < minQuarantineUntil) {
minQuarantineUntil = metrics.quarantineUntil;
}
entries.push({
relay: metrics.relay,
summary,
score,
metrics,
});
}
return a.relay.localeCompare(b.relay);
});
entries.sort((a, b) => {
if (b.score !== a.score) return b.score - a.score;
if (a.summary.quarantined !== b.summary.quarantined) return a.summary.quarantined ? 1 : -1;
if (a.summary.averageLatencyMs !== b.summary.averageLatencyMs) {
if (a.summary.averageLatencyMs === null) return 1;
if (b.summary.averageLatencyMs === null) return -1;
return a.summary.averageLatencyMs - b.summary.averageLatencyMs;
}
return a.relay.localeCompare(b.relay);
});
this._sortedCache = {
version: this._metricsVersion,
validUntil: minQuarantineUntil,
entries,
};
}
const requestedSet = new Set(relays);
return this._sortedCache.entries
.filter((entry) => requestedSet.has(entry.relay))
.map((entry) => {
const quarantineRemainingMs = entry.metrics.quarantineUntil > nowMs
? entry.metrics.quarantineUntil - nowMs
: 0;
return {
relay: entry.relay,
score: entry.score,
summary: {
...entry.summary,
quarantineRemainingMs,
},
};
});
}
prioritizeRelays(relays, config, nowMs = Date.now()) {
@@ -275,6 +335,7 @@ export class RelayHealthManager {
metrics.quarantineUntil = nowMs + metrics.cooldownMs;
metrics.cooldownMs = Math.min(config.maxQuarantineCooldownMs, Math.floor(metrics.cooldownMs * 1.5));
}
this._metricsVersion += 1;
}
collectSnapshot(relays, config, nowMs = Date.now()) {
@@ -300,6 +361,8 @@ export class RelayHealthManager {
reset() {
this.metricsByRelay.clear();
this.lastSnapshotAt = 0;
this._metricsVersion = 0;
this._sortedCache = null;
}
}
@@ -495,7 +558,7 @@ export class LockPublisher {
let lastAttemptState = null;
const retryTimeline = [];
const overallStartedAt = Date.now();
let terminalFailureCategory = 'relay_publish_non_retryable';
let terminalFailureCategory = PUBLISH_FAILURE_CATEGORIES.NON_RETRYABLE;
for (let attemptNumber = 1; attemptNumber <= this.maxAttempts; attemptNumber += 1) {
const startedAtMs = Date.now();
@@ -583,11 +646,11 @@ export class LockPublisher {
determineFailureCategory(hasTransientFailure, hasPermanentFailure, attemptNumber) {
if (hasTransientFailure && !hasPermanentFailure && attemptNumber >= this.maxAttempts) {
return 'relay_publish_quorum_failure';
return PUBLISH_FAILURE_CATEGORIES.QUORUM_FAILURE;
} else if (hasPermanentFailure) {
return 'relay_publish_non_retryable';
return PUBLISH_FAILURE_CATEGORIES.NON_RETRYABLE;
} else {
return 'relay_publish_quorum_failure';
return PUBLISH_FAILURE_CATEGORIES.QUORUM_FAILURE;
}
}
+20 -10
View File
@@ -5,6 +5,7 @@ import crypto from 'node:crypto';
import readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';
import { DEFAULT_RELAYS } from './constants.mjs';
import { ensureDir } from './utils.mjs';
const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
@@ -36,12 +37,6 @@ function getPaths(root, installDirName) {
};
}
function ensureDir(dir) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
}
function copyDir(src, dest) {
if (fs.existsSync(src)) {
fs.cpSync(src, dest, { recursive: true });
@@ -152,11 +147,26 @@ async function interactiveInit(cwd) {
}
}
async function resolveConfiguration(cwd, mockAnswers) {
if (mockAnswers) {
return mockAnswers;
function validateInstallDir(dir) {
if (dir === '.') return;
// Strict validation to prevent command injection
// Only allow alphanumeric, hyphens, underscores, slashes, and periods.
if (!/^[a-zA-Z0-9_\-\/\.]+$/.test(dir)) {
throw new Error(`Invalid directory name: "${dir}". Only alphanumeric characters, hyphens, underscores, slashes, and periods are allowed.`);
}
return await interactiveInit(cwd);
}
async function resolveConfiguration(cwd, mockAnswers) {
let config;
if (mockAnswers) {
config = mockAnswers;
} else {
config = await interactiveInit(cwd);
}
validateInstallDir(config.installDir);
return config;
}
function ensureInstallDirectory(paths, force, installDir) {
+17 -7
View File
@@ -53,20 +53,20 @@ const FALLBACK_ROSTER = {
let cachedCanonicalRoster = null;
function loadCanonicalRoster() {
function loadCanonicalRoster(fsModule = fs) {
if (cachedCanonicalRoster) return cachedCanonicalRoster;
let rosterPath = ROSTER_FILE;
// Prefer user-managed roster if present
if (fs.existsSync(USER_ROSTER_FILE)) {
if (fsModule.existsSync(USER_ROSTER_FILE)) {
rosterPath = USER_ROSTER_FILE;
} else if (fs.existsSync(CWD_ROSTER_FILE)) {
} else if (fsModule.existsSync(CWD_ROSTER_FILE)) {
rosterPath = CWD_ROSTER_FILE;
}
try {
const parsed = JSON.parse(fs.readFileSync(rosterPath, 'utf8'));
const parsed = JSON.parse(fsModule.readFileSync(rosterPath, 'utf8'));
const daily = Array.isArray(parsed.daily) ? parsed.daily.map((item) => String(item).trim()).filter(Boolean) : [];
const weekly = Array.isArray(parsed.weekly)
? parsed.weekly.map((item) => String(item).trim()).filter(Boolean)
@@ -86,6 +86,11 @@ function loadCanonicalRoster() {
return cachedCanonicalRoster;
}
/** @internal */
export function _resetRosterCache() {
cachedCanonicalRoster = null;
}
function parseEnvRoster(value) {
if (!value) return null;
return value
@@ -94,11 +99,16 @@ function parseEnvRoster(value) {
.filter(Boolean);
}
export async function getRoster(cadence) {
const config = await loadTorchConfig();
export async function getRoster(cadence, deps = {}) {
const {
loadTorchConfig: loadTorchConfigFn = loadTorchConfig,
fs: fsModule = fs,
} = deps;
const config = await loadTorchConfigFn();
const dailyFromEnv = parseEnvRoster(process.env.NOSTR_LOCK_DAILY_ROSTER);
const weeklyFromEnv = parseEnvRoster(process.env.NOSTR_LOCK_WEEKLY_ROSTER);
const canonical = loadCanonicalRoster();
const canonical = loadCanonicalRoster(fsModule);
const dailyFromConfig = config.nostrLock.dailyRoster;
const weeklyFromConfig = config.nostrLock.weeklyRoster;
+1
View File
@@ -7,6 +7,7 @@ import { getMemoryPruneMode, isMemoryIngestEnabled } from './feature-flags.js';
import fs from 'node:fs';
import path from 'node:path';
import util from 'node:util';
import { ensureDir } from '../../utils.mjs';
const MEMORY_FILE_PATH = path.join(process.cwd(), '.scheduler-memory', 'memory-store.json');
const debug = util.debuglog('torch-memory');
+21 -15
View File
@@ -67,22 +67,29 @@ function evaluateCandidate(memory, policy) {
* @param {{ getEmbedding?: (memory: import('./schema.js').MemoryRecord) => number[] | null | undefined, similarityThreshold: number, duplicateWindowMs: number }} options
*/
function groupNearDuplicates(memories, options) {
// Sort by created_at to enable early exit in the inner loop
const sortedMemories = [...memories].sort((a, b) => a.created_at - b.created_at);
const groups = [];
const consumed = new Set();
for (let i = 0; i < memories.length; i += 1) {
const base = memories[i];
for (let i = 0; i < sortedMemories.length; i += 1) {
const base = sortedMemories[i];
if (consumed.has(base.id) || base.merged_into) continue;
const baseEmbedding = options.getEmbedding?.(base);
const group = [base];
for (let j = i + 1; j < memories.length; j += 1) {
const probe = memories[j];
for (let j = i + 1; j < sortedMemories.length; j += 1) {
const probe = sortedMemories[j];
// Optimization: Since sorted by created_at, if the time difference exceeds the window,
// all subsequent items will also be outside the window.
if (probe.created_at - base.created_at > options.duplicateWindowMs) break;
if (consumed.has(probe.id) || probe.merged_into || probe.id === base.id) continue;
const withinWindow = Math.abs(base.created_at - probe.created_at) <= options.duplicateWindowMs;
if (!withinWindow || !hasTagOverlap(base.tags, probe.tags)) continue;
if (!hasTagOverlap(base.tags, probe.tags)) continue;
const similarity = cosineSimilarity(baseEmbedding, options.getEmbedding?.(probe));
if (similarity >= options.similarityThreshold) {
@@ -189,12 +196,13 @@ export async function createLifecyclePlan(memories, options) {
});
const templates = loadMemoryPromptTemplates({ templateDir: options.templateDir });
const condensedGroups = [];
const condensedGroups = await Promise.all(
groups.map((group) => condenseGroup(group, options.generateSummary, templates.condense))
);
const mergedIntoMap = new Map();
for (const group of groups) {
const condensed = await condenseGroup(group, options.generateSummary, templates.condense);
condensedGroups.push(condensed);
for (const condensed of condensedGroups) {
for (const mergedId of condensed.mergedIds) {
mergedIntoMap.set(mergedId, condensed.merged.id);
}
@@ -265,9 +273,7 @@ export async function createLifecyclePlan(memories, options) {
* @param {Awaited<ReturnType<typeof createLifecyclePlan>>} plan
*/
export async function applyLifecycleActions(repository, plan) {
const results = [];
for (const decision of plan.actions) {
const results = await Promise.all(plan.actions.map(async (decision) => {
if (decision.merged_into && typeof repository.markMerged === 'function') {
await repository.markMerged(decision.id, decision.merged_into);
}
@@ -280,8 +286,8 @@ export async function applyLifecycleActions(repository, plan) {
await repository.keepMemory?.(decision.id, decision.reason);
}
results.push(decision);
}
return decision;
}));
return {
applied: results,
+15 -9
View File
@@ -31,17 +31,20 @@ function computeRecencyDecay(timestamp, now, halfLifeMs) {
return Math.exp(-(age / halfLifeMs));
}
function textMatchSimilarity(query, memory) {
const normalizedQuery = String(query ?? '').trim().toLowerCase();
if (!normalizedQuery) return 0;
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
const haystack = `${memory.summary} ${memory.content}`.toLowerCase();
if (haystack.includes(normalizedQuery)) return 1;
function textMatchSimilarity(queryTerms, queryRegexes, memory) {
if (!queryTerms || queryTerms.length === 0) return 0;
const queryTerms = normalizedQuery.split(/\s+/).filter(Boolean);
if (queryTerms.length === 0) return 0;
let matchedTerms = 0;
for (const regex of queryRegexes) {
if (regex.test(memory.summary) || regex.test(memory.content)) {
matchedTerms++;
}
}
const matchedTerms = queryTerms.filter((term) => haystack.includes(term)).length;
return matchedTerms / queryTerms.length;
}
@@ -102,8 +105,11 @@ async function buildSimilarityIndex(memories, params) {
}
}
const queryTerms = normalizedQuery.toLowerCase().split(/\s+/).filter(Boolean);
const queryRegexes = queryTerms.map((t) => new RegExp(escapeRegExp(t), 'i'));
for (const memory of memories) {
const lexicalScore = textMatchSimilarity(normalizedQuery, memory);
const lexicalScore = textMatchSimilarity(queryTerms, queryRegexes, memory);
if (lexicalScore > 0) {
similarityById.set(memory.id, lexicalScore);
}
@@ -0,0 +1,10 @@
# Test Logs for Run 2026-02-17T16-36-25Z
- Agent: todo-triage-agent
- Cadence: daily
- Run-start: 2026-02-17T16-36-25Z
- Command: Scan for TODOs
Result: 0 actionable items found.
Agent: todo-triage-agent
Prompt: src/prompts/daily/todo-triage-agent.md
@@ -0,0 +1,11 @@
---
agent: design-system-audit-agent
cadence: daily
session: session-2026-02-18T02:35:22Z
run-start: 2026-02-18T02:35:22Z
prompt: src/prompts/daily/design-system-audit-agent.md
---
# Test Log
- Command: npm run lint
- Result: Passed (with warnings)
+13
View File
@@ -0,0 +1,13 @@
# Todo List for Run 2026-02-17T16-36-25Z
- Agent: todo-triage-agent
- Cadence: daily
- Run-start: 2026-02-17T16-36-25Z
## Completed
- [x] Scan codebase for TODOs (todo-triage-agent)
- [x] Triage findings (0 found)
## Pending
None.
Ref: src/prompts/daily/todo-triage-agent.md
+12
View File
@@ -0,0 +1,12 @@
---
agent: design-system-audit-agent
cadence: daily
session: session-2026-02-18T02:35:22Z
run-start: 2026-02-18T02:35:22Z
prompt: src/prompts/daily/design-system-audit-agent.md
---
# Todo
## Pending Tasks
- [ ] Review unsanctioned colors.
+8
View File
@@ -1,3 +1,5 @@
import fs from 'node:fs';
export function todayDateStr() {
return new Date().toISOString().slice(0, 10);
}
@@ -34,3 +36,9 @@ export function detectPlatform() {
if (process.env.GOOSE_API_KEY || process.env.GOOSE_SESSION_ID) return 'goose';
return null;
}
export function ensureDir(dir) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
}