tasks execute immediately

This commit is contained in:
Arjun 2026-03-19 00:04:19 +05:30
parent 91030a5fca
commit d291ceec80
8 changed files with 245 additions and 24 deletions

View file

@ -5,6 +5,13 @@ export function getRaw(): string {
.map(name => ` ${name}:\n type: builtin\n name: ${name}`)
.join('\n');
const now = new Date();
const defaultEnd = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
const localNow = now.toLocaleString('en-US', { dateStyle: 'full', timeStyle: 'long' });
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
const nowISO = now.toISOString();
const defaultEndISO = defaultEnd.toISOString();
return `---
model: gpt-5.2
tools:
@ -23,5 +30,31 @@ You are an inline task execution agent. You receive a @rowboat instruction from
5. NEVER include meta-commentary, thinking out loud, or narration about what you're doing. No "Let me look that up", "Here are the details", "I found the following", etc. Just write the content itself.
6. Keep the result concise and well-formatted in markdown.
7. Do not modify the original note file the service will handle inserting your response.
# Schedule Classification
If the instruction implies a recurring or future-scheduled task, you MUST include a schedule marker in your response using this exact format:
<!--rowboat-schedule:{"type":"...","label":"..."}-->
Place this marker at the very beginning of your response, on its own line, before any other content.
Schedule types:
1. "cron" recurring schedule: <!--rowboat-schedule:{"type":"cron","expression":"<5-field cron>","startDate":"<ISO>","endDate":"<ISO>","label":"<human readable>"}-->
"startDate" defaults to now (${nowISO}). "endDate" defaults to 7 days from now (${defaultEndISO}).
Example: "every morning at 8am" <!--rowboat-schedule:{"type":"cron","expression":"0 8 * * *","startDate":"${nowISO}","endDate":"${defaultEndISO}","label":"runs daily at 8 AM until ${defaultEnd.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}"}-->
2. "window" recurring with a time window: <!--rowboat-schedule:{"type":"window","cron":"<cron>","startTime":"HH:MM","endTime":"HH:MM","startDate":"<ISO>","endDate":"<ISO>","label":"<human readable>"}-->
3. "once" run once at a specific future time: <!--rowboat-schedule:{"type":"once","runAt":"<ISO 8601>","label":"<human readable>"}-->
The "label" field must be a short plain-English description starting with "runs" (e.g. "runs every 2 minutes until Mar 12", "runs daily at 8 AM until Mar 12", "runs once on Mar 20 at 3 PM").
Current local time: ${localNow}
Timezone: ${tz}
Current UTC time: ${nowISO}
If the instruction is a one-time immediate task with no scheduling intent, do NOT include the schedule marker. Just execute and return the result.
If the instruction has BOTH scheduling intent AND something to execute immediately, include the schedule marker AND your response content.
`;
}

View file

@ -531,6 +531,78 @@ async function processInlineTasks(): Promise<void> {
}
}
/**
* Process a @rowboat instruction via the inline task agent.
* The agent can execute one-off tasks and/or detect scheduling intent.
* Returns schedule info (if any), a schedule label, and optional response text.
*/
export async function processRowboatInstruction(
instruction: string,
noteContent: string,
notePath: string,
): Promise<{
schedule: { type: 'cron'; expression: string; startDate: string; endDate: string }
| { type: 'window'; cron: string; startTime: string; endTime: string; startDate: string; endDate: string }
| { type: 'once'; runAt: string }
| null;
scheduleLabel: string | null;
response: string | null;
}> {
const run = await createRun({ agentId: INLINE_TASK_AGENT });
const message = [
`Execute the following instruction from the note "${notePath}":`,
'',
`**Instruction:** ${instruction}`,
'',
'**Full note content for context:**',
'```markdown',
noteContent,
'```',
].join('\n');
await createMessage(run.id, message);
await waitForRunCompletion(run.id);
const rawResponse = await extractAgentResponse(run.id);
if (!rawResponse) {
return { schedule: null, scheduleLabel: null, response: null };
}
// Parse out the schedule marker if present
const scheduleMarkerRegex = /<!--rowboat-schedule:(.*?)-->/;
const match = rawResponse.match(scheduleMarkerRegex);
type ScheduleWithoutLabel =
| { type: 'cron'; expression: string; startDate: string; endDate: string }
| { type: 'window'; cron: string; startTime: string; endTime: string; startDate: string; endDate: string }
| { type: 'once'; runAt: string };
let schedule: ScheduleWithoutLabel | null = null;
let scheduleLabel: string | null = null;
let response: string | null = null;
if (match) {
try {
const parsed = JSON.parse(match[1]);
if (parsed && typeof parsed === 'object' && parsed.type) {
scheduleLabel = parsed.label || null;
const { label: _, ...rest } = parsed;
schedule = rest as ScheduleWithoutLabel;
}
} catch {
// Invalid JSON in marker — ignore
}
// Remove the marker from the response text
const cleaned = rawResponse.replace(scheduleMarkerRegex, '').trim();
response = cleaned || null;
} else {
response = rawResponse.trim() || null;
}
return { schedule, scheduleLabel, response };
}
/**
* Classify whether an instruction contains a scheduling intent using the user's configured LLM.
* Returns a schedule object or null for one-time tasks.

View file

@ -28,6 +28,7 @@ export const InlineTaskBlockSchema = z.object({
schedule: InlineTaskScheduleSchema.optional(),
'schedule-label': z.string().optional(),
lastRunAt: z.string().optional(),
processing: z.boolean().optional(),
});
export type InlineTaskBlock = z.infer<typeof InlineTaskBlockSchema>;

View file

@ -522,6 +522,22 @@ const ipcSchemas = {
]).nullable(),
}),
},
'inline-task:process': {
req: z.object({
instruction: z.string(),
noteContent: z.string(),
notePath: z.string(),
}),
res: z.object({
schedule: z.union([
z.object({ type: z.literal('cron'), expression: z.string(), startDate: z.string(), endDate: z.string() }),
z.object({ type: z.literal('window'), cron: z.string(), startTime: z.string(), endTime: z.string(), startDate: z.string(), endDate: z.string() }),
z.object({ type: z.literal('once'), runAt: z.string() }),
]).nullable(),
scheduleLabel: z.string().nullable(),
response: z.string().nullable(),
}),
},
// Billing channels
'billing:getInfo': {
req: z.null(),