Remove redundant comments throughout codebase

Keep only comments that explain non-obvious behavior or add context
not apparent from reading the code.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-02-01 16:00:44 +02:00
parent 732c624e6b
commit f25e0f9a10
8 changed files with 11 additions and 66 deletions

View File

@@ -52,11 +52,8 @@ function log(message: string): void {
console.error(`[agent-runner] ${message}`); console.error(`[agent-runner] ${message}`);
} }
/**
* Find session summary from sessions-index.json
*/
function getSessionSummary(sessionId: string, transcriptPath: string): string | null { function getSessionSummary(sessionId: string, transcriptPath: string): string | null {
// The sessions-index.json is in the same directory as the transcript // sessions-index.json is in the same directory as the transcript
const projectDir = path.dirname(transcriptPath); const projectDir = path.dirname(transcriptPath);
const indexPath = path.join(projectDir, 'sessions-index.json'); const indexPath = path.join(projectDir, 'sessions-index.json');
@@ -101,7 +98,6 @@ function createPreCompactHook(): HookCallback {
return {}; return {};
} }
// Get summary from sessions-index.json for the filename
const summary = getSessionSummary(sessionId, transcriptPath); const summary = getSessionSummary(sessionId, transcriptPath);
const name = summary ? sanitizeFilename(summary) : generateFallbackName(); const name = summary ? sanitizeFilename(summary) : generateFallbackName();
@@ -124,9 +120,6 @@ function createPreCompactHook(): HookCallback {
}; };
} }
/**
* Sanitize a summary string into a valid filename.
*/
function sanitizeFilename(summary: string): string { function sanitizeFilename(summary: string): string {
return summary return summary
.toLowerCase() .toLowerCase()
@@ -165,7 +158,6 @@ function parseTranscript(content: string): ParsedMessage[] {
if (text) messages.push({ role: 'assistant', content: text }); if (text) messages.push({ role: 'assistant', content: text });
} }
} catch { } catch {
// Skip malformed lines
} }
} }

View File

@@ -19,14 +19,12 @@ export interface IpcMcpContext {
} }
function writeIpcFile(dir: string, data: object): string { function writeIpcFile(dir: string, data: object): string {
// Ensure directory exists
fs.mkdirSync(dir, { recursive: true }); fs.mkdirSync(dir, { recursive: true });
// Use timestamp + random suffix for unique filename
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json`; const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json`;
const filepath = path.join(dir, filename); const filepath = path.join(dir, filename);
// Write atomically: write to temp file, then rename // Atomic write: temp file then rename
const tempPath = `${filepath}.tmp`; const tempPath = `${filepath}.tmp`;
fs.writeFileSync(tempPath, JSON.stringify(data, null, 2)); fs.writeFileSync(tempPath, JSON.stringify(data, null, 2));
fs.renameSync(tempPath, filepath); fs.renameSync(tempPath, filepath);
@@ -41,7 +39,6 @@ export function createIpcMcp(ctx: IpcMcpContext) {
name: 'nanoclaw', name: 'nanoclaw',
version: '1.0.0', version: '1.0.0',
tools: [ tools: [
// Send a message to the WhatsApp group
tool( tool(
'send_message', 'send_message',
'Send a message to the current WhatsApp group. Use this to proactively share information or updates.', 'Send a message to the current WhatsApp group. Use this to proactively share information or updates.',
@@ -68,7 +65,6 @@ export function createIpcMcp(ctx: IpcMcpContext) {
} }
), ),
// Schedule a new task
tool( tool(
'schedule_task', 'schedule_task',
'Schedule a recurring or one-time task. The task will run as a full agent with access to all tools.', 'Schedule a recurring or one-time task. The task will run as a full agent with access to all tools.',
@@ -104,13 +100,12 @@ export function createIpcMcp(ctx: IpcMcpContext) {
} }
), ),
// List tasks (reads from a mounted file that host keeps updated) // Reads from current_tasks.json which host keeps updated
tool( tool(
'list_tasks', 'list_tasks',
'List all scheduled tasks. From main: shows all tasks. From other groups: shows only that group\'s tasks.', 'List all scheduled tasks. From main: shows all tasks. From other groups: shows only that group\'s tasks.',
{}, {},
async () => { async () => {
// Host process writes current tasks to this file
const tasksFile = path.join(IPC_DIR, 'current_tasks.json'); const tasksFile = path.join(IPC_DIR, 'current_tasks.json');
try { try {
@@ -125,7 +120,6 @@ export function createIpcMcp(ctx: IpcMcpContext) {
const allTasks = JSON.parse(fs.readFileSync(tasksFile, 'utf-8')); const allTasks = JSON.parse(fs.readFileSync(tasksFile, 'utf-8'));
// Filter to current group unless main
const tasks = isMain const tasks = isMain
? allTasks ? allTasks
: allTasks.filter((t: { groupFolder: string }) => t.groupFolder === groupFolder); : allTasks.filter((t: { groupFolder: string }) => t.groupFolder === groupFolder);
@@ -160,7 +154,6 @@ export function createIpcMcp(ctx: IpcMcpContext) {
} }
), ),
// Pause a task
tool( tool(
'pause_task', 'pause_task',
'Pause a scheduled task. It will not run until resumed.', 'Pause a scheduled task. It will not run until resumed.',
@@ -187,7 +180,6 @@ export function createIpcMcp(ctx: IpcMcpContext) {
} }
), ),
// Resume a task
tool( tool(
'resume_task', 'resume_task',
'Resume a paused task.', 'Resume a paused task.',
@@ -214,7 +206,6 @@ export function createIpcMcp(ctx: IpcMcpContext) {
} }
), ),
// Cancel a task
tool( tool(
'cancel_task', 'cancel_task',
'Cancel and delete a scheduled task.', 'Cancel and delete a scheduled task.',

View File

@@ -28,7 +28,6 @@ async function authenticate(): Promise<void> {
const { state, saveCreds } = await useMultiFileAuthState(AUTH_DIR); const { state, saveCreds } = await useMultiFileAuthState(AUTH_DIR);
// Check if already authenticated
if (state.creds.registered) { if (state.creds.registered) {
console.log('✓ Already authenticated with WhatsApp'); console.log('✓ Already authenticated with WhatsApp');
console.log(' To re-authenticate, delete the store/auth folder and run again.'); console.log(' To re-authenticate, delete the store/auth folder and run again.');

View File

@@ -2,18 +2,17 @@ import path from 'path';
export const ASSISTANT_NAME = process.env.ASSISTANT_NAME || 'Andy'; export const ASSISTANT_NAME = process.env.ASSISTANT_NAME || 'Andy';
export const POLL_INTERVAL = 2000; export const POLL_INTERVAL = 2000;
export const SCHEDULER_POLL_INTERVAL = 60000; // Check for due tasks every minute export const SCHEDULER_POLL_INTERVAL = 60000;
// Use absolute paths for container mounts // Absolute paths needed for container mounts
const PROJECT_ROOT = process.cwd(); const PROJECT_ROOT = process.cwd();
export const STORE_DIR = path.resolve(PROJECT_ROOT, 'store'); export const STORE_DIR = path.resolve(PROJECT_ROOT, 'store');
export const GROUPS_DIR = path.resolve(PROJECT_ROOT, 'groups'); export const GROUPS_DIR = path.resolve(PROJECT_ROOT, 'groups');
export const DATA_DIR = path.resolve(PROJECT_ROOT, 'data'); export const DATA_DIR = path.resolve(PROJECT_ROOT, 'data');
export const MAIN_GROUP_FOLDER = 'main'; export const MAIN_GROUP_FOLDER = 'main';
// Container configuration
export const CONTAINER_IMAGE = process.env.CONTAINER_IMAGE || 'nanoclaw-agent:latest'; export const CONTAINER_IMAGE = process.env.CONTAINER_IMAGE || 'nanoclaw-agent:latest';
export const CONTAINER_TIMEOUT = parseInt(process.env.CONTAINER_TIMEOUT || '300000', 10); // 5 minutes default export const CONTAINER_TIMEOUT = parseInt(process.env.CONTAINER_TIMEOUT || '300000', 10);
export const IPC_POLL_INTERVAL = 1000; // Check IPC directories every second export const IPC_POLL_INTERVAL = 1000;
export const TRIGGER_PATTERN = new RegExp(`^@${ASSISTANT_NAME}\\b`, 'i'); export const TRIGGER_PATTERN = new RegExp(`^@${ASSISTANT_NAME}\\b`, 'i');

View File

@@ -90,7 +90,6 @@ function buildVolumeMounts(group: RegisteredGroup, isMain: boolean): VolumeMount
}); });
} }
// Gmail MCP credentials
const gmailDir = path.join(homeDir, '.gmail-mcp'); const gmailDir = path.join(homeDir, '.gmail-mcp');
if (fs.existsSync(gmailDir)) { if (fs.existsSync(gmailDir)) {
mounts.push({ mounts.push({
@@ -100,7 +99,6 @@ function buildVolumeMounts(group: RegisteredGroup, isMain: boolean): VolumeMount
}); });
} }
// IPC directory for messages and tasks
const ipcDir = path.join(DATA_DIR, 'ipc'); const ipcDir = path.join(DATA_DIR, 'ipc');
fs.mkdirSync(path.join(ipcDir, 'messages'), { recursive: true }); fs.mkdirSync(path.join(ipcDir, 'messages'), { recursive: true });
fs.mkdirSync(path.join(ipcDir, 'tasks'), { recursive: true }); fs.mkdirSync(path.join(ipcDir, 'tasks'), { recursive: true });
@@ -115,7 +113,6 @@ function buildVolumeMounts(group: RegisteredGroup, isMain: boolean): VolumeMount
fs.mkdirSync(envDir, { recursive: true }); fs.mkdirSync(envDir, { recursive: true });
const envFile = path.join(projectRoot, '.env'); const envFile = path.join(projectRoot, '.env');
if (fs.existsSync(envFile)) { if (fs.existsSync(envFile)) {
// Copy .env to the env directory as a plain file called 'env'
fs.copyFileSync(envFile, path.join(envDir, 'env')); fs.copyFileSync(envFile, path.join(envDir, 'env'));
mounts.push({ mounts.push({
hostPath: envDir, hostPath: envDir,
@@ -124,10 +121,8 @@ function buildVolumeMounts(group: RegisteredGroup, isMain: boolean): VolumeMount
}); });
} }
// Additional mounts from group config
if (group.containerConfig?.additionalMounts) { if (group.containerConfig?.additionalMounts) {
for (const mount of group.containerConfig.additionalMounts) { for (const mount of group.containerConfig.additionalMounts) {
// Resolve home directory in path
const hostPath = mount.hostPath.startsWith('~') const hostPath = mount.hostPath.startsWith('~')
? path.join(homeDir, mount.hostPath.slice(1)) ? path.join(homeDir, mount.hostPath.slice(1))
: mount.hostPath; : mount.hostPath;
@@ -150,8 +145,7 @@ function buildVolumeMounts(group: RegisteredGroup, isMain: boolean): VolumeMount
function buildContainerArgs(mounts: VolumeMount[]): string[] { function buildContainerArgs(mounts: VolumeMount[]): string[] {
const args: string[] = ['run', '-i', '--rm']; const args: string[] = ['run', '-i', '--rm'];
// Add volume mounts // Apple Container: --mount for readonly, -v for read-write
// Apple Container: use --mount for readonly, -v for read-write
for (const mount of mounts) { for (const mount of mounts) {
if (mount.readonly) { if (mount.readonly) {
args.push('--mount', `type=bind,source=${mount.hostPath},target=${mount.containerPath},readonly`); args.push('--mount', `type=bind,source=${mount.hostPath},target=${mount.containerPath},readonly`);
@@ -160,7 +154,6 @@ function buildContainerArgs(mounts: VolumeMount[]): string[] {
} }
} }
// Add the image name
args.push(CONTAINER_IMAGE); args.push(CONTAINER_IMAGE);
return args; return args;
@@ -172,15 +165,12 @@ export async function runContainerAgent(
): Promise<ContainerOutput> { ): Promise<ContainerOutput> {
const startTime = Date.now(); const startTime = Date.now();
// Ensure group directory exists
const groupDir = path.join(GROUPS_DIR, group.folder); const groupDir = path.join(GROUPS_DIR, group.folder);
fs.mkdirSync(groupDir, { recursive: true }); fs.mkdirSync(groupDir, { recursive: true });
// Build volume mounts
const mounts = buildVolumeMounts(group, input.isMain); const mounts = buildVolumeMounts(group, input.isMain);
const containerArgs = buildContainerArgs(mounts); const containerArgs = buildContainerArgs(mounts);
// Log detailed mount info at debug level
logger.debug({ logger.debug({
group: group.name, group: group.name,
mounts: mounts.map(m => `${m.hostPath} -> ${m.containerPath}${m.readonly ? ' (ro)' : ''}`), mounts: mounts.map(m => `${m.hostPath} -> ${m.containerPath}${m.readonly ? ' (ro)' : ''}`),
@@ -193,7 +183,6 @@ export async function runContainerAgent(
isMain: input.isMain isMain: input.isMain
}, 'Spawning container agent'); }, 'Spawning container agent');
// Create logs directory for this group
const logsDir = path.join(GROUPS_DIR, group.folder, 'logs'); const logsDir = path.join(GROUPS_DIR, group.folder, 'logs');
fs.mkdirSync(logsDir, { recursive: true }); fs.mkdirSync(logsDir, { recursive: true });
@@ -205,7 +194,6 @@ export async function runContainerAgent(
let stdout = ''; let stdout = '';
let stderr = ''; let stderr = '';
// Send input JSON to container stdin
container.stdin.write(JSON.stringify(input)); container.stdin.write(JSON.stringify(input));
container.stdin.end(); container.stdin.end();
@@ -215,14 +203,12 @@ export async function runContainerAgent(
container.stderr.on('data', (data) => { container.stderr.on('data', (data) => {
stderr += data.toString(); stderr += data.toString();
// Log container stderr in real-time
const lines = data.toString().trim().split('\n'); const lines = data.toString().trim().split('\n');
for (const line of lines) { for (const line of lines) {
if (line) logger.debug({ container: group.folder }, line); if (line) logger.debug({ container: group.folder }, line);
} }
}); });
// Timeout handler
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
logger.error({ group: group.name }, 'Container timeout, killing'); logger.error({ group: group.name }, 'Container timeout, killing');
container.kill('SIGKILL'); container.kill('SIGKILL');
@@ -237,12 +223,10 @@ export async function runContainerAgent(
clearTimeout(timeout); clearTimeout(timeout);
const duration = Date.now() - startTime; const duration = Date.now() - startTime;
// Write container log file
const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const logFile = path.join(logsDir, `container-${timestamp}.log`); const logFile = path.join(logsDir, `container-${timestamp}.log`);
const isVerbose = process.env.LOG_LEVEL === 'debug' || process.env.LOG_LEVEL === 'trace'; const isVerbose = process.env.LOG_LEVEL === 'debug' || process.env.LOG_LEVEL === 'trace';
// Build log content - only include full input/output in verbose mode
const logLines = [ const logLines = [
`=== Container Run Log ===`, `=== Container Run Log ===`,
`Timestamp: ${new Date().toISOString()}`, `Timestamp: ${new Date().toISOString()}`,
@@ -254,7 +238,6 @@ export async function runContainerAgent(
]; ];
if (isVerbose) { if (isVerbose) {
// Full content logging only in debug/trace mode
logLines.push( logLines.push(
`=== Input ===`, `=== Input ===`,
JSON.stringify(input, null, 2), JSON.stringify(input, null, 2),
@@ -272,7 +255,6 @@ export async function runContainerAgent(
stdout stdout
); );
} else { } else {
// Minimal logging by default - no message content
logLines.push( logLines.push(
`=== Input Summary ===`, `=== Input Summary ===`,
`Prompt length: ${input.prompt.length} chars`, `Prompt length: ${input.prompt.length} chars`,
@@ -283,7 +265,6 @@ export async function runContainerAgent(
`` ``
); );
// Only include stderr/stdout if there was an error
if (code !== 0) { if (code !== 0) {
logLines.push( logLines.push(
`=== Stderr (last 500 chars) ===`, `=== Stderr (last 500 chars) ===`,
@@ -313,9 +294,8 @@ export async function runContainerAgent(
return; return;
} }
// Parse JSON output from stdout
try { try {
// Find the JSON line (last non-empty line should be the output) // Last non-empty line is the JSON output
const lines = stdout.trim().split('\n'); const lines = stdout.trim().split('\n');
const jsonLine = lines[lines.length - 1]; const jsonLine = lines[lines.length - 1];
const output: ContainerOutput = JSON.parse(jsonLine); const output: ContainerOutput = JSON.parse(jsonLine);
@@ -355,7 +335,6 @@ export async function runContainerAgent(
}); });
} }
// Export task snapshot for container IPC
export function writeTasksSnapshot(tasks: Array<{ export function writeTasksSnapshot(tasks: Array<{
id: string; id: string;
groupFolder: string; groupFolder: string;

View File

@@ -129,8 +129,6 @@ export function getMessagesSince(chatJid: string, sinceTimestamp: string): NewMe
return db.prepare(sql).all(chatJid, sinceTimestamp) as NewMessage[]; return db.prepare(sql).all(chatJid, sinceTimestamp) as NewMessage[];
} }
// Scheduled Tasks
export function createTask(task: Omit<ScheduledTask, 'last_run' | 'last_result'>): void { export function createTask(task: Omit<ScheduledTask, 'last_run' | 'last_result'>): void {
db.prepare(` db.prepare(`
INSERT INTO scheduled_tasks (id, group_folder, chat_jid, prompt, schedule_type, schedule_value, next_run, status, created_at) INSERT INTO scheduled_tasks (id, group_folder, chat_jid, prompt, schedule_type, schedule_value, next_run, status, created_at)

View File

@@ -81,11 +81,10 @@ async function processMessage(msg: NewMessage): Promise<void> {
if (!TRIGGER_PATTERN.test(content)) return; if (!TRIGGER_PATTERN.test(content)) return;
// Get messages since last agent interaction to catch up the session // Get all messages since last agent interaction so the session has full context
const sinceTimestamp = lastAgentTimestamp[msg.chat_jid] || ''; const sinceTimestamp = lastAgentTimestamp[msg.chat_jid] || '';
const missedMessages = getMessagesSince(msg.chat_jid, sinceTimestamp); const missedMessages = getMessagesSince(msg.chat_jid, sinceTimestamp);
// Build prompt with conversation history
const lines = missedMessages.map(m => { const lines = missedMessages.map(m => {
const d = new Date(m.timestamp); const d = new Date(m.timestamp);
const date = d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); const date = d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
@@ -102,7 +101,6 @@ async function processMessage(msg: NewMessage): Promise<void> {
const response = await runAgent(group, prompt, msg.chat_jid); const response = await runAgent(group, prompt, msg.chat_jid);
await setTyping(msg.chat_jid, false); await setTyping(msg.chat_jid, false);
// Update last agent timestamp
lastAgentTimestamp[msg.chat_jid] = msg.timestamp; lastAgentTimestamp[msg.chat_jid] = msg.timestamp;
if (response) { if (response) {
@@ -135,7 +133,6 @@ async function runAgent(group: RegisteredGroup, prompt: string, chatJid: string)
isMain isMain
}); });
// Update session if changed
if (output.newSessionId) { if (output.newSessionId) {
sessions[group.folder] = output.newSessionId; sessions[group.folder] = output.newSessionId;
saveJson(path.join(DATA_DIR, 'sessions.json'), sessions); saveJson(path.join(DATA_DIR, 'sessions.json'), sessions);
@@ -162,7 +159,6 @@ async function sendMessage(jid: string, text: string): Promise<void> {
} }
} }
// IPC watcher for container messages and tasks
function startIpcWatcher(): void { function startIpcWatcher(): void {
const messagesDir = path.join(DATA_DIR, 'ipc', 'messages'); const messagesDir = path.join(DATA_DIR, 'ipc', 'messages');
const tasksDir = path.join(DATA_DIR, 'ipc', 'tasks'); const tasksDir = path.join(DATA_DIR, 'ipc', 'tasks');
@@ -171,7 +167,6 @@ function startIpcWatcher(): void {
fs.mkdirSync(tasksDir, { recursive: true }); fs.mkdirSync(tasksDir, { recursive: true });
const processIpcFiles = async () => { const processIpcFiles = async () => {
// Process pending messages
try { try {
const messageFiles = fs.readdirSync(messagesDir).filter(f => f.endsWith('.json')); const messageFiles = fs.readdirSync(messagesDir).filter(f => f.endsWith('.json'));
for (const file of messageFiles) { for (const file of messageFiles) {
@@ -195,7 +190,6 @@ function startIpcWatcher(): void {
logger.error({ err }, 'Error reading IPC messages directory'); logger.error({ err }, 'Error reading IPC messages directory');
} }
// Process pending task operations
try { try {
const taskFiles = fs.readdirSync(tasksDir).filter(f => f.endsWith('.json')); const taskFiles = fs.readdirSync(tasksDir).filter(f => f.endsWith('.json'));
for (const file of taskFiles) { for (const file of taskFiles) {
@@ -241,7 +235,6 @@ async function processTaskIpc(data: {
if (data.prompt && data.schedule_type && data.schedule_value && data.groupFolder && data.chatJid) { if (data.prompt && data.schedule_type && data.schedule_value && data.groupFolder && data.chatJid) {
const scheduleType = data.schedule_type as 'cron' | 'interval' | 'once'; const scheduleType = data.schedule_type as 'cron' | 'interval' | 'once';
// Calculate next run time
let nextRun: string | null = null; let nextRun: string | null = null;
if (scheduleType === 'cron') { if (scheduleType === 'cron') {
const interval = CronExpressionParser.parse(data.schedule_value); const interval = CronExpressionParser.parse(data.schedule_value);
@@ -395,11 +388,9 @@ async function startMessageLoop(): Promise<void> {
function ensureContainerSystemRunning(): void { function ensureContainerSystemRunning(): void {
try { try {
// Check if container system is already running
execSync('container system status', { stdio: 'pipe' }); execSync('container system status', { stdio: 'pipe' });
logger.debug('Apple Container system already running'); logger.debug('Apple Container system already running');
} catch { } catch {
// Not running, try to start it
logger.info('Starting Apple Container system...'); logger.info('Starting Apple Container system...');
try { try {
execSync('container system start', { stdio: 'pipe', timeout: 30000 }); execSync('container system start', { stdio: 'pipe', timeout: 30000 });

View File

@@ -24,7 +24,6 @@ async function runTask(task: ScheduledTask, deps: SchedulerDependencies): Promis
logger.info({ taskId: task.id, group: task.group_folder }, 'Running scheduled task'); logger.info({ taskId: task.id, group: task.group_folder }, 'Running scheduled task');
// Find the group config for this task
const groups = deps.registeredGroups(); const groups = deps.registeredGroups();
const group = Object.values(groups).find(g => g.folder === task.group_folder); const group = Object.values(groups).find(g => g.folder === task.group_folder);
@@ -78,7 +77,6 @@ async function runTask(task: ScheduledTask, deps: SchedulerDependencies): Promis
const durationMs = Date.now() - startTime; const durationMs = Date.now() - startTime;
// Log the run
logTaskRun({ logTaskRun({
task_id: task.id, task_id: task.id,
run_at: new Date().toISOString(), run_at: new Date().toISOString(),
@@ -88,7 +86,6 @@ async function runTask(task: ScheduledTask, deps: SchedulerDependencies): Promis
error error
}); });
// Calculate next run
let nextRun: string | null = null; let nextRun: string | null = null;
if (task.schedule_type === 'cron') { if (task.schedule_type === 'cron') {
const interval = CronExpressionParser.parse(task.schedule_value); const interval = CronExpressionParser.parse(task.schedule_value);
@@ -97,9 +94,8 @@ async function runTask(task: ScheduledTask, deps: SchedulerDependencies): Promis
const ms = parseInt(task.schedule_value, 10); const ms = parseInt(task.schedule_value, 10);
nextRun = new Date(Date.now() + ms).toISOString(); nextRun = new Date(Date.now() + ms).toISOString();
} }
// 'once' tasks don't have a next run // 'once' tasks have no next run
// Update task
const resultSummary = error ? `Error: ${error}` : (result ? result.slice(0, 200) : 'Completed'); const resultSummary = error ? `Error: ${error}` : (result ? result.slice(0, 200) : 'Completed');
updateTaskAfterRun(task.id, nextRun, resultSummary); updateTaskAfterRun(task.id, nextRun, resultSummary);
} }