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:
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.',
|
||||||
|
|||||||
@@ -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.');
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
11
src/index.ts
11
src/index.ts
@@ -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 });
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user