fix: wire up cron job --deliver flag to actually deliver responses (#290)
Co-authored-by: letta-code <248085862+letta-code@users.noreply.github.com> Co-authored-by: Cameron <cpfiffer@users.noreply.github.com> Co-authored-by: Cameron <cameron@pfiffer.org>
This commit is contained in:
committed by
GitHub
parent
6ef987a04f
commit
b6bfd14cd9
@@ -5,7 +5,7 @@ description: Create and manage scheduled tasks (cron jobs) that send you message
|
|||||||
|
|
||||||
# Cron Jobs
|
# Cron Jobs
|
||||||
|
|
||||||
Schedule tasks that send you messages at specified times. Jobs are scheduled immediately when created.
|
Schedule tasks that send messages to the agent at specified times. Jobs are scheduled immediately when created.
|
||||||
|
|
||||||
## Quick Reference
|
## Quick Reference
|
||||||
|
|
||||||
@@ -30,20 +30,27 @@ lettabot-cron create \
|
|||||||
**Options:**
|
**Options:**
|
||||||
- `-n, --name` - Job name (required)
|
- `-n, --name` - Job name (required)
|
||||||
- `-s, --schedule` - Cron expression (required)
|
- `-s, --schedule` - Cron expression (required)
|
||||||
- `-m, --message` - Message sent to you when job runs (required)
|
- `-m, --message` - Prompt sent to the agent when the job fires (required)
|
||||||
- `-d, --deliver` - Where to send response (format: `channel:chatId`). **Defaults to last messaged chat.**
|
- `-d, --deliver` - Auto-deliver the agent's response to a channel (format: `channel:chatId`). Without this flag, the job runs in **silent mode** (see below).
|
||||||
- `--disabled` - Create disabled
|
- `--disabled` - Create in disabled state
|
||||||
|
|
||||||
|
## Silent Mode vs Delivery Mode
|
||||||
|
|
||||||
|
Cron jobs run in one of two modes:
|
||||||
|
|
||||||
|
- **Silent mode** (no `--deliver`): The agent receives the message and can act on it (e.g., update memory, run tools), but the response is NOT automatically sent to any chat. If the agent wants to send a message, it must explicitly use `lettabot-message send`.
|
||||||
|
- **Delivery mode** (`--deliver channel:chatId`): The agent's response is automatically delivered to the specified channel/chat after execution.
|
||||||
|
|
||||||
## Message Format
|
## Message Format
|
||||||
|
|
||||||
When a cron job runs, you receive a message like:
|
When a cron job runs, the agent receives a message like:
|
||||||
|
|
||||||
```
|
```
|
||||||
[cron:cron-123abc Morning Briefing] Good morning! Review tasks for today.
|
[cron:cron-123abc Morning Briefing] Good morning! Review tasks for today.
|
||||||
Current time: 1/27/2026, 8:00:00 AM (America/Los_Angeles)
|
Current time: 1/27/2026, 8:00:00 AM (America/Los_Angeles)
|
||||||
```
|
```
|
||||||
|
|
||||||
This tells you:
|
This tells the agent:
|
||||||
- The message came from a cron job (not a user)
|
- The message came from a cron job (not a user)
|
||||||
- The job ID and name
|
- The job ID and name
|
||||||
- The current time
|
- The current time
|
||||||
@@ -87,9 +94,18 @@ lettabot-cron create \
|
|||||||
-d slack:C1234567890
|
-d slack:C1234567890
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Background task (silent mode - agent acts but no auto-delivery):**
|
||||||
|
```bash
|
||||||
|
lettabot-cron create \
|
||||||
|
-n "Email Check" \
|
||||||
|
-s "0 */2 * * *" \
|
||||||
|
-m "Check for new emails and summarize anything important."
|
||||||
|
```
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Jobs schedule immediately when created (no restart needed)
|
- Jobs schedule immediately when created (no restart needed)
|
||||||
- Use `lettabot-cron list` to see next run times and last run status
|
- Use `lettabot-cron list` to see next run times and last run status
|
||||||
- Jobs persist in `cron-jobs.json`
|
- Jobs persist in `cron-jobs.json`
|
||||||
- Logs written to `cron-log.jsonl`
|
- Logs written to `cron-log.jsonl`
|
||||||
|
- Without `--deliver`, the agent must use `lettabot-message send` to communicate results to users
|
||||||
|
|||||||
@@ -191,7 +191,17 @@ function createJob(args: string[]): void {
|
|||||||
enabled = false;
|
enabled = false;
|
||||||
} else if ((arg === '--deliver' || arg === '-d') && next) {
|
} else if ((arg === '--deliver' || arg === '-d') && next) {
|
||||||
// Format: channel:chatId (e.g., telegram:123456789 or discord:123456789012345678)
|
// Format: channel:chatId (e.g., telegram:123456789 or discord:123456789012345678)
|
||||||
const [ch, id] = next.split(':');
|
const [ch, ...rest] = next.split(':');
|
||||||
|
const id = rest.join(':'); // Rejoin in case chatId contains colons
|
||||||
|
const validChannels = ['telegram', 'telegram-mtproto', 'slack', 'discord', 'whatsapp', 'signal'];
|
||||||
|
if (!validChannels.includes(ch)) {
|
||||||
|
console.error(`Error: invalid channel "${ch}". Must be one of: ${validChannels.join(', ')}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
if (!id) {
|
||||||
|
console.error('Error: --deliver requires format channel:chatId (e.g., telegram:123456789)');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
deliverChannel = ch;
|
deliverChannel = ch;
|
||||||
deliverChatId = id;
|
deliverChatId = id;
|
||||||
i++;
|
i++;
|
||||||
@@ -349,8 +359,8 @@ Create options:
|
|||||||
--name, -n <name> Task name (required)
|
--name, -n <name> Task name (required)
|
||||||
--schedule, -s <cron> Cron expression for recurring tasks
|
--schedule, -s <cron> Cron expression for recurring tasks
|
||||||
--at, -a <datetime> ISO datetime for one-off reminder (auto-deletes after)
|
--at, -a <datetime> ISO datetime for one-off reminder (auto-deletes after)
|
||||||
--message, -m <msg> Message to send (required)
|
--message, -m <msg> Prompt sent to agent when job fires (required)
|
||||||
--deliver, -d <target> Delivery target (channel:chatId)
|
--deliver, -d <target> Auto-deliver response to channel:chatId (omit for silent mode)
|
||||||
--disabled Create in disabled state
|
--disabled Create in disabled state
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|||||||
@@ -390,10 +390,27 @@ export class CronService {
|
|||||||
`Current time: ${formattedTime} (${timezone})`,
|
`Current time: ${formattedTime} (${timezone})`,
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
// Send message to agent (SILENT MODE - response NOT auto-delivered)
|
// Send message to agent
|
||||||
// Agent must use `lettabot-message` CLI to send messages explicitly
|
|
||||||
const response = await this.bot.sendToAgent(messageWithMetadata);
|
const response = await this.bot.sendToAgent(messageWithMetadata);
|
||||||
|
|
||||||
|
// Deliver response to channel if configured
|
||||||
|
const deliverMode = job.deliver ? 'deliver' : 'silent';
|
||||||
|
if (job.deliver && response) {
|
||||||
|
try {
|
||||||
|
await this.bot.deliverToChannel(job.deliver.channel, job.deliver.chatId, { text: response });
|
||||||
|
console.log(`[Cron] 📬 Delivered response to ${job.deliver.channel}:${job.deliver.chatId}`);
|
||||||
|
} catch (deliverError) {
|
||||||
|
console.error(`[Cron] Failed to deliver response to ${job.deliver.channel}:${job.deliver.chatId}:`, deliverError);
|
||||||
|
logEvent('job_deliver_failed', {
|
||||||
|
id: job.id,
|
||||||
|
name: job.name,
|
||||||
|
channel: job.deliver.channel,
|
||||||
|
chatId: job.deliver.chatId,
|
||||||
|
error: deliverError instanceof Error ? deliverError.message : String(deliverError),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
job.state.lastRunAt = new Date();
|
job.state.lastRunAt = new Date();
|
||||||
job.state.lastStatus = 'ok';
|
job.state.lastStatus = 'ok';
|
||||||
@@ -410,16 +427,21 @@ export class CronService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log(`\n${'='.repeat(50)}`);
|
console.log(`\n${'='.repeat(50)}`);
|
||||||
console.log(`[Cron] ✅ JOB COMPLETED: ${job.name} [SILENT MODE]`);
|
console.log(`[Cron] ✅ JOB COMPLETED: ${job.name} [${deliverMode.toUpperCase()} MODE]`);
|
||||||
console.log(` Response: ${response?.slice(0, 200)}${(response?.length || 0) > 200 ? '...' : ''}`);
|
console.log(` Response: ${response?.slice(0, 200)}${(response?.length || 0) > 200 ? '...' : ''}`);
|
||||||
console.log(` (Response NOT auto-delivered - agent uses lettabot-message CLI)`);
|
if (deliverMode === 'silent') {
|
||||||
|
console.log(` (Response NOT auto-delivered - agent uses lettabot-message CLI)`);
|
||||||
|
} else {
|
||||||
|
console.log(` (Response delivered to ${job.deliver!.channel}:${job.deliver!.chatId})`);
|
||||||
|
}
|
||||||
console.log(`${'='.repeat(50)}\n`);
|
console.log(`${'='.repeat(50)}\n`);
|
||||||
|
|
||||||
logEvent('job_completed', {
|
logEvent('job_completed', {
|
||||||
id: job.id,
|
id: job.id,
|
||||||
name: job.name,
|
name: job.name,
|
||||||
status: 'ok',
|
status: 'ok',
|
||||||
mode: 'silent',
|
mode: deliverMode,
|
||||||
|
deliverTarget: job.deliver ? `${job.deliver.channel}:${job.deliver.chatId}` : undefined,
|
||||||
nextRun: job.state.nextRunAt?.toISOString(),
|
nextRun: job.state.nextRunAt?.toISOString(),
|
||||||
responseLength: response?.length || 0,
|
responseLength: response?.length || 0,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user