mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-16 18:25:17 +02:00
fix recurring tasks
This commit is contained in:
parent
5e1debad6a
commit
e561616c75
5 changed files with 162 additions and 66 deletions
|
|
@ -973,6 +973,7 @@ export function MarkdownEditor({
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Call the copilot assistant for both one-time and recurring tasks
|
||||||
const result = await window.ipc.invoke('inline-task:process', {
|
const result = await window.ipc.invoke('inline-task:process', {
|
||||||
instruction,
|
instruction,
|
||||||
noteContent: editorContent,
|
noteContent: editorContent,
|
||||||
|
|
@ -986,11 +987,13 @@ export function MarkdownEditor({
|
||||||
if (!node) return
|
if (!node) return
|
||||||
|
|
||||||
if (result.schedule) {
|
if (result.schedule) {
|
||||||
// Scheduled task: keep the block, update with schedule info
|
// Recurring/scheduled task: update block with schedule, write target tags to disk
|
||||||
|
const targetId = Math.random().toString(36).slice(2, 10)
|
||||||
const updatedData: Record<string, unknown> = {
|
const updatedData: Record<string, unknown> = {
|
||||||
instruction,
|
instruction: result.instruction,
|
||||||
schedule: result.schedule,
|
schedule: result.schedule,
|
||||||
'schedule-label': result.scheduleLabel,
|
'schedule-label': result.scheduleLabel,
|
||||||
|
targetId,
|
||||||
}
|
}
|
||||||
const tr = editor.state.tr.setNodeMarkup(currentPos, undefined, {
|
const tr = editor.state.tr.setNodeMarkup(currentPos, undefined, {
|
||||||
data: JSON.stringify(updatedData),
|
data: JSON.stringify(updatedData),
|
||||||
|
|
@ -1004,24 +1007,40 @@ export function MarkdownEditor({
|
||||||
onFrontmatterChange(buildFrontmatter(fields))
|
onFrontmatterChange(buildFrontmatter(fields))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert response text below the block if any
|
// Write target tags directly to the file on disk after a short delay
|
||||||
if (result.response) {
|
// to let the editor save the updated content first
|
||||||
let afterPos: number | null = null
|
if (notePath) {
|
||||||
editor.state.doc.descendants((n, p) => {
|
setTimeout(async () => {
|
||||||
if (afterPos !== null) return false
|
try {
|
||||||
if (n.type.name === 'taskBlock') {
|
const file = await window.ipc.invoke('workspace:readFile', { path: notePath })
|
||||||
try {
|
const content = file.data
|
||||||
const data = JSON.parse(n.attrs.data || '{}')
|
const openTag = `<!--task-target:${targetId}-->`
|
||||||
if (data.instruction === instruction && !data.processing) {
|
const closeTag = `<!--/task-target:${targetId}-->`
|
||||||
afterPos = p + n.nodeSize
|
|
||||||
return false
|
// Only add if not already present
|
||||||
|
if (content.includes(openTag)) return
|
||||||
|
|
||||||
|
// Find the task block in the raw markdown and insert target tags after it
|
||||||
|
const blockJson = JSON.stringify(updatedData)
|
||||||
|
const blockStart = content.indexOf('```task\n' + blockJson)
|
||||||
|
if (blockStart !== -1) {
|
||||||
|
const blockEnd = content.indexOf('\n```', blockStart + 8)
|
||||||
|
if (blockEnd !== -1) {
|
||||||
|
const insertAt = blockEnd + 4 // after the closing ```
|
||||||
|
const before = content.slice(0, insertAt)
|
||||||
|
const after = content.slice(insertAt)
|
||||||
|
const updated = before + '\n\n' + openTag + '\n' + closeTag + after
|
||||||
|
await window.ipc.invoke('workspace:writeFile', {
|
||||||
|
path: notePath,
|
||||||
|
data: updated,
|
||||||
|
opts: { encoding: 'utf8' },
|
||||||
|
})
|
||||||
}
|
}
|
||||||
} catch { /* skip */ }
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[RowboatAdd] Failed to write target tags:', err)
|
||||||
}
|
}
|
||||||
})
|
}, 500)
|
||||||
if (afterPos !== null) {
|
|
||||||
editor.chain().insertContentAt(afterPos, result.response).run()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// One-time task: remove the processing block, insert response in its place
|
// One-time task: remove the processing block, insert response in its place
|
||||||
|
|
|
||||||
|
|
@ -19,42 +19,74 @@ ${toolEntries}
|
||||||
---
|
---
|
||||||
# Task
|
# Task
|
||||||
|
|
||||||
You are an inline task execution agent. You receive a @rowboat instruction from within a knowledge note and execute it.
|
You are an inline task execution agent. You receive a @rowboat instruction from within a knowledge note and either execute it immediately or set it up as a recurring task.
|
||||||
|
|
||||||
# Instructions
|
# Two Modes
|
||||||
|
|
||||||
1. You will receive the full content of a knowledge note and a specific instruction extracted from a \`@rowboat <instruction>\` line in that note.
|
## 1. One-Time Tasks (no scheduling intent)
|
||||||
2. Execute the instruction using your full workspace tool set. You have access to read files, edit files, search, run commands, etc.
|
For instructions that should be executed immediately (e.g., "summarize this note", "look up the weather"):
|
||||||
3. Use the surrounding note content as context for the task.
|
- Execute the instruction using your full workspace tool set
|
||||||
4. Your response will be inserted directly into the note below the @rowboat instruction. Write your output as note content — it must read naturally as part of the document.
|
- Return the result as markdown content
|
||||||
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.
|
- Do NOT include any schedule or instruction markers
|
||||||
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
|
## 2. Recurring/Scheduled Tasks (has scheduling intent)
|
||||||
|
For instructions that imply a recurring or future-scheduled task (e.g., "every morning at 8am check emails", "remind me tomorrow at 3pm"):
|
||||||
|
- Do NOT execute the task — only set up the schedule
|
||||||
|
- You MUST include BOTH markers described below
|
||||||
|
- Do NOT include any other content besides the markers
|
||||||
|
|
||||||
If the instruction implies a recurring or future-scheduled task, you MUST include a schedule marker in your response using this exact format:
|
# Markers for Scheduled Tasks
|
||||||
|
|
||||||
|
When the instruction has scheduling intent, your response MUST contain these markers and nothing else:
|
||||||
|
|
||||||
|
## Schedule Marker (required)
|
||||||
<!--rowboat-schedule:{"type":"...","label":"..."}-->
|
<!--rowboat-schedule:{"type":"...","label":"..."}-->
|
||||||
|
|
||||||
Place this marker at the very beginning of your response, on its own line, before any other content.
|
|
||||||
|
|
||||||
Schedule types:
|
Schedule types:
|
||||||
1. "cron" — recurring schedule: <!--rowboat-schedule:{"type":"cron","expression":"<5-field cron>","startDate":"<ISO>","endDate":"<ISO>","label":"<human readable>"}-->
|
1. "cron" — recurring: \`<!--rowboat-schedule:{"type":"cron","expression":"<5-field cron>","startDate":"<ISO>","endDate":"<ISO>","label":"<label>"}-->\`
|
||||||
"startDate" defaults to now (${nowISO}). "endDate" defaults to 7 days from now (${defaultEndISO}).
|
"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' })}"}-->
|
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>"}-->
|
2. "window" — recurring with time window: \`<!--rowboat-schedule:{"type":"window","cron":"<cron>","startTime":"HH:MM","endTime":"HH:MM","startDate":"<ISO>","endDate":"<ISO>","label":"<label>"}-->\`
|
||||||
|
|
||||||
3. "once" — run once at a specific future time: <!--rowboat-schedule:{"type":"once","runAt":"<ISO 8601>","label":"<human readable>"}-->
|
3. "once" — future one-time: \`<!--rowboat-schedule:{"type":"once","runAt":"<ISO 8601>","label":"<label>"}-->\`
|
||||||
|
|
||||||
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").
|
The "label" must be a short plain-English description starting with "runs" (e.g., "runs daily at 8 AM until Mar 24").
|
||||||
|
|
||||||
|
## Instruction Marker (required for scheduled tasks)
|
||||||
|
<!--rowboat-instruction:the refined instruction text-->
|
||||||
|
|
||||||
|
This is the instruction that will be executed on each scheduled run. You may refine/clarify the original instruction to make it more specific and actionable for the background agent that will execute it. For example:
|
||||||
|
- User says "check my emails every morning" → \`<!--rowboat-instruction:Check for new emails and summarize any important ones.-->\`
|
||||||
|
- User says "news about claude daily" → \`<!--rowboat-instruction:Search for the latest news about Anthropic's Claude AI and list the top stories with sources.-->\`
|
||||||
|
|
||||||
|
If the instruction is already clear and actionable, you can keep it as-is.
|
||||||
|
|
||||||
|
# Context
|
||||||
|
|
||||||
Current local time: ${localNow}
|
Current local time: ${localNow}
|
||||||
Timezone: ${tz}
|
Timezone: ${tz}
|
||||||
Current UTC time: ${nowISO}
|
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.
|
# Output Rules
|
||||||
If the instruction has BOTH scheduling intent AND something to execute immediately, include the schedule marker AND your response content.
|
|
||||||
|
- For one-time tasks: write output as note content — it must read naturally as part of the document. NEVER include meta-commentary. Keep concise and well-formatted in markdown.
|
||||||
|
- For scheduled tasks: output ONLY the two markers (schedule + instruction), nothing else.
|
||||||
|
- Do not modify the original note file — the system handles all insertions.
|
||||||
|
|
||||||
|
# Target Regions
|
||||||
|
|
||||||
|
For recurring/scheduled tasks, the note will contain a **target region** delimited by HTML comment tags:
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
<!--task-target:TARGETID-->
|
||||||
|
...existing content...
|
||||||
|
<!--/task-target:TARGETID-->
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
When you see a target region associated with your task (during a scheduled run), your response MUST be the replacement content for that region. You should:
|
||||||
|
- Write content that replaces whatever is currently between the tags
|
||||||
|
- Use the existing content as context (e.g., to update rather than regenerate from scratch if appropriate)
|
||||||
|
- Do NOT include the target tags themselves in your response
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,8 @@ interface InlineTask {
|
||||||
startLine: number;
|
startLine: number;
|
||||||
/** Line index of the closing ``` fence */
|
/** Line index of the closing ``` fence */
|
||||||
endLine: number;
|
endLine: number;
|
||||||
|
/** Target region ID for recurring tasks */
|
||||||
|
targetId: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -183,7 +185,7 @@ interface InlineTask {
|
||||||
* Returns { instruction, schedule } or null if not valid JSON.
|
* Returns { instruction, schedule } or null if not valid JSON.
|
||||||
* Also supports legacy @rowboat format.
|
* Also supports legacy @rowboat format.
|
||||||
*/
|
*/
|
||||||
function parseBlockContent(contentLines: string[]): { instruction: string; schedule: InlineTaskSchedule | null; lastRunAt: string | null } | null {
|
function parseBlockContent(contentLines: string[]): { instruction: string; schedule: InlineTaskSchedule | null; lastRunAt: string | null; targetId: string | null } | null {
|
||||||
const raw = contentLines.join('\n').trim();
|
const raw = contentLines.join('\n').trim();
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(raw);
|
const data = JSON.parse(raw);
|
||||||
|
|
@ -193,6 +195,7 @@ function parseBlockContent(contentLines: string[]): { instruction: string; sched
|
||||||
instruction: parsed.data.instruction,
|
instruction: parsed.data.instruction,
|
||||||
schedule: parsed.data.schedule ? { ...parsed.data.schedule, label: parsed.data['schedule-label'] ?? '' } as InlineTaskSchedule : null,
|
schedule: parsed.data.schedule ? { ...parsed.data.schedule, label: parsed.data['schedule-label'] ?? '' } as InlineTaskSchedule : null,
|
||||||
lastRunAt: parsed.data.lastRunAt ?? null,
|
lastRunAt: parsed.data.lastRunAt ?? null,
|
||||||
|
targetId: parsed.data.targetId ?? null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// Fallback for blocks that have instruction but don't fully match schema
|
// Fallback for blocks that have instruction but don't fully match schema
|
||||||
|
|
@ -201,6 +204,7 @@ function parseBlockContent(contentLines: string[]): { instruction: string; sched
|
||||||
instruction: data.instruction,
|
instruction: data.instruction,
|
||||||
schedule: data.schedule ?? null,
|
schedule: data.schedule ?? null,
|
||||||
lastRunAt: data.lastRunAt ?? null,
|
lastRunAt: data.lastRunAt ?? null,
|
||||||
|
targetId: data.targetId ?? null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -227,7 +231,7 @@ function parseBlockContent(contentLines: string[]): { instruction: string; sched
|
||||||
const rawInstruction = firstRowboatLine?.trim() ?? instructionLines.join('\n').trim();
|
const rawInstruction = firstRowboatLine?.trim() ?? instructionLines.join('\n').trim();
|
||||||
const instruction = rawInstruction.replace(/^@rowboat:?\s*/, '');
|
const instruction = rawInstruction.replace(/^@rowboat:?\s*/, '');
|
||||||
if (!instruction) return null;
|
if (!instruction) return null;
|
||||||
return { instruction, schedule, lastRunAt: null };
|
return { instruction, schedule, lastRunAt: null, targetId: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -308,16 +312,16 @@ function findPendingTasks(body: string): InlineTask[] {
|
||||||
|
|
||||||
const parsed = parseBlockContent(contentLines);
|
const parsed = parseBlockContent(contentLines);
|
||||||
if (parsed) {
|
if (parsed) {
|
||||||
const { instruction, schedule, lastRunAt } = parsed;
|
const { instruction, schedule, lastRunAt, targetId } = parsed;
|
||||||
|
|
||||||
if (schedule) {
|
if (schedule) {
|
||||||
if (isScheduledTaskDue(schedule, lastRunAt)) {
|
if (isScheduledTaskDue(schedule, lastRunAt)) {
|
||||||
tasks.push({ instruction, schedule, startLine, endLine });
|
tasks.push({ instruction, schedule, startLine, endLine, targetId });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// One-time task: skip if already ran
|
// One-time task: skip if already ran
|
||||||
if (!lastRunAt) {
|
if (!lastRunAt) {
|
||||||
tasks.push({ instruction, schedule: null, startLine, endLine });
|
tasks.push({ instruction, schedule: null, startLine, endLine, targetId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -339,6 +343,32 @@ function insertResultBelow(body: string, endLine: number, result: string): strin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace content inside a target region identified by targetId.
|
||||||
|
* If the target region exists, replaces its content.
|
||||||
|
* If it doesn't exist, creates the target region below the task block,
|
||||||
|
* wrapping any existing content between the block and the next block/heading.
|
||||||
|
*/
|
||||||
|
function replaceTargetRegion(body: string, targetId: string, result: string, endLine: number): string {
|
||||||
|
const openTag = `<!--task-target:${targetId}-->`;
|
||||||
|
const closeTag = `<!--/task-target:${targetId}-->`;
|
||||||
|
const openIdx = body.indexOf(openTag);
|
||||||
|
const closeIdx = body.indexOf(closeTag);
|
||||||
|
|
||||||
|
if (openIdx !== -1 && closeIdx !== -1 && closeIdx > openIdx) {
|
||||||
|
// Target region exists — replace content between the tags
|
||||||
|
const before = body.slice(0, openIdx + openTag.length);
|
||||||
|
const after = body.slice(closeIdx);
|
||||||
|
return before + '\n' + result + '\n' + after;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Target region doesn't exist yet — create it below the task block's closing fence
|
||||||
|
const lines = body.split('\n');
|
||||||
|
const taggedResult = `${openTag}\n${result}\n${closeTag}`;
|
||||||
|
lines.splice(endLine + 1, 0, '', taggedResult);
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a note has any "live" tell-rowboat tasks.
|
* Determine if a note has any "live" tell-rowboat tasks.
|
||||||
* A task is live if:
|
* A task is live if:
|
||||||
|
|
@ -495,7 +525,13 @@ async function processInlineTasks(): Promise<void> {
|
||||||
|
|
||||||
const result = await extractAgentResponse(run.id);
|
const result = await extractAgentResponse(run.id);
|
||||||
if (result) {
|
if (result) {
|
||||||
currentBody = insertResultBelow(currentBody, task.endLine, result);
|
if (task.targetId) {
|
||||||
|
// Recurring task with target region — replace content inside the region
|
||||||
|
currentBody = replaceTargetRegion(currentBody, task.targetId, result, task.endLine);
|
||||||
|
} else {
|
||||||
|
// No target region — insert below the block
|
||||||
|
currentBody = insertResultBelow(currentBody, task.endLine, result);
|
||||||
|
}
|
||||||
// Update the block JSON with lastRunAt
|
// Update the block JSON with lastRunAt
|
||||||
const timestamp = new Date().toISOString();
|
const timestamp = new Date().toISOString();
|
||||||
currentBody = updateBlockData(currentBody, task.startLine, task.endLine, timestamp);
|
currentBody = updateBlockData(currentBody, task.startLine, task.endLine, timestamp);
|
||||||
|
|
@ -536,22 +572,25 @@ async function processInlineTasks(): Promise<void> {
|
||||||
* The agent can execute one-off tasks and/or detect scheduling intent.
|
* The agent can execute one-off tasks and/or detect scheduling intent.
|
||||||
* Returns schedule info (if any), a schedule label, and optional response text.
|
* Returns schedule info (if any), a schedule label, and optional response text.
|
||||||
*/
|
*/
|
||||||
|
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 };
|
||||||
|
|
||||||
export async function processRowboatInstruction(
|
export async function processRowboatInstruction(
|
||||||
instruction: string,
|
instruction: string,
|
||||||
noteContent: string,
|
noteContent: string,
|
||||||
notePath: string,
|
notePath: string,
|
||||||
): Promise<{
|
): Promise<{
|
||||||
schedule: { type: 'cron'; expression: string; startDate: string; endDate: string }
|
instruction: string;
|
||||||
| { type: 'window'; cron: string; startTime: string; endTime: string; startDate: string; endDate: string }
|
schedule: ScheduleWithoutLabel | null;
|
||||||
| { type: 'once'; runAt: string }
|
|
||||||
| null;
|
|
||||||
scheduleLabel: string | null;
|
scheduleLabel: string | null;
|
||||||
response: string | null;
|
response: string | null;
|
||||||
}> {
|
}> {
|
||||||
const run = await createRun({ agentId: INLINE_TASK_AGENT });
|
const run = await createRun({ agentId: INLINE_TASK_AGENT });
|
||||||
|
|
||||||
const message = [
|
const message = [
|
||||||
`Execute the following instruction from the note "${notePath}":`,
|
`Process the following @rowboat instruction from the note "${notePath}":`,
|
||||||
'',
|
'',
|
||||||
`**Instruction:** ${instruction}`,
|
`**Instruction:** ${instruction}`,
|
||||||
'',
|
'',
|
||||||
|
|
@ -566,41 +605,45 @@ export async function processRowboatInstruction(
|
||||||
|
|
||||||
const rawResponse = await extractAgentResponse(run.id);
|
const rawResponse = await extractAgentResponse(run.id);
|
||||||
if (!rawResponse) {
|
if (!rawResponse) {
|
||||||
return { schedule: null, scheduleLabel: null, response: null };
|
return { instruction, schedule: null, scheduleLabel: null, response: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse out the schedule marker if present
|
// Parse out the schedule marker if present (allow multiline JSON)
|
||||||
const scheduleMarkerRegex = /<!--rowboat-schedule:(.*?)-->/;
|
const scheduleMarkerRegex = /<!--rowboat-schedule:([\s\S]*?)-->/;
|
||||||
const match = rawResponse.match(scheduleMarkerRegex);
|
const scheduleMatch = rawResponse.match(scheduleMarkerRegex);
|
||||||
|
|
||||||
type ScheduleWithoutLabel =
|
// Parse out the instruction marker if present
|
||||||
| { type: 'cron'; expression: string; startDate: string; endDate: string }
|
const instructionMarkerRegex = /<!--rowboat-instruction:([\s\S]*?)-->/;
|
||||||
| { type: 'window'; cron: string; startTime: string; endTime: string; startDate: string; endDate: string }
|
const instructionMatch = rawResponse.match(instructionMarkerRegex);
|
||||||
| { type: 'once'; runAt: string };
|
|
||||||
|
|
||||||
let schedule: ScheduleWithoutLabel | null = null;
|
let schedule: ScheduleWithoutLabel | null = null;
|
||||||
let scheduleLabel: string | null = null;
|
let scheduleLabel: string | null = null;
|
||||||
let response: string | null = null;
|
let refinedInstruction = instruction;
|
||||||
|
|
||||||
if (match) {
|
if (instructionMatch) {
|
||||||
|
refinedInstruction = instructionMatch[1].trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scheduleMatch) {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(match[1]);
|
const parsed = JSON.parse(scheduleMatch[1]);
|
||||||
if (parsed && typeof parsed === 'object' && parsed.type) {
|
if (parsed && typeof parsed === 'object' && parsed.type) {
|
||||||
scheduleLabel = parsed.label || null;
|
scheduleLabel = parsed.label || null;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const { label: _, ...rest } = parsed;
|
const { label: _, ...rest } = parsed;
|
||||||
schedule = rest as ScheduleWithoutLabel;
|
schedule = rest as ScheduleWithoutLabel;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Invalid JSON in marker — ignore
|
// Invalid JSON in marker — ignore
|
||||||
}
|
}
|
||||||
// Remove the marker from the response text
|
|
||||||
const cleaned = rawResponse.replace(scheduleMarkerRegex, '').trim();
|
// Scheduled task — no response content (agent only returns markers)
|
||||||
response = cleaned || null;
|
return { instruction: refinedInstruction, schedule, scheduleLabel, response: null };
|
||||||
} else {
|
|
||||||
response = rawResponse.trim() || null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { schedule, scheduleLabel, response };
|
// One-time task — the full response is the content
|
||||||
|
const response = rawResponse.trim() || null;
|
||||||
|
return { instruction: refinedInstruction, schedule: null, scheduleLabel: null, response };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ export const InlineTaskBlockSchema = z.object({
|
||||||
'schedule-label': z.string().optional(),
|
'schedule-label': z.string().optional(),
|
||||||
lastRunAt: z.string().optional(),
|
lastRunAt: z.string().optional(),
|
||||||
processing: z.boolean().optional(),
|
processing: z.boolean().optional(),
|
||||||
|
targetId: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type InlineTaskBlock = z.infer<typeof InlineTaskBlockSchema>;
|
export type InlineTaskBlock = z.infer<typeof InlineTaskBlockSchema>;
|
||||||
|
|
|
||||||
|
|
@ -529,6 +529,7 @@ const ipcSchemas = {
|
||||||
notePath: z.string(),
|
notePath: z.string(),
|
||||||
}),
|
}),
|
||||||
res: z.object({
|
res: z.object({
|
||||||
|
instruction: z.string(),
|
||||||
schedule: z.union([
|
schedule: z.union([
|
||||||
z.object({ type: z.literal('cron'), expression: z.string(), startDate: z.string(), endDate: z.string() }),
|
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('window'), cron: z.string(), startTime: z.string(), endTime: z.string(), startDate: z.string(), endDate: z.string() }),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue