mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-06-21 20:18:11 +02:00
tasks execute immediately
This commit is contained in:
parent
91030a5fca
commit
d291ceec80
8 changed files with 245 additions and 24 deletions
|
|
@ -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.
|
||||
`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue