feat: background tasks

Adds Background Tasks — recurring background agents the user can set up to
either keep a digest current (daily email summary, top HN stories, weather
brief) or perform a recurring action (draft a reply, post to Slack, call an
API). Each task is a persistent set of instructions plus optional triggers
(schedule, time-of-day window, or matching incoming Gmail / calendar event).
The agent reads the verbs in the instructions on every run and picks the
right mode automatically.

User-facing surfaces:
- New "Background tasks" entry in the sidebar, with a table listing every
  task, its schedule, last run, and an active toggle.
- A detail page per task with a max-width reader showing the task's
  current output and a control sidebar for editing instructions, triggers,
  and reviewing run history.
- "New task" can open in a free-form box where the user describes what they
  want and Copilot sets it up end-to-end, or in a structured form for
  manual setup.
- "Edit with Copilot" hand-off from the detail view, pre-seeded with the
  task's context.

Under the hood:
- The event pipeline that previously powered live-notes is now a generic
  consumer registry. Live-notes and background tasks both subscribe;
  incoming events are routed to candidates from both concurrently.
- Schedule helpers and the agent-message trigger block are factored out of
  live-notes into shared modules. Both features use the same building
  blocks now.
- Copilot's proactive routing is reframed: anything recurring (cadence
  words, watch / monitor verbs, action verbs, event-conditional asks) now
  flows to background tasks. Live-notes load only on explicit mention.
- A small reliability fix for the run-creation fallback chain: an
  empty-string model/provider passed by an LLM tool call now correctly
  falls through to the default instead of being persisted as a real value.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ramnique Singh 2026-05-12 17:43:25 +05:30
parent 13fa80c687
commit b01af12148
45 changed files with 4025 additions and 594 deletions

View file

@ -7,6 +7,12 @@ import { AgentScheduleConfig, AgentScheduleEntry } from './agent-schedule.js';
import { AgentScheduleState } from './agent-schedule-state.js';
import { ServiceEvent } from './service-events.js';
import { LiveNoteAgentEvent, LiveNoteSchema } from './live-note.js';
import {
BackgroundTaskAgentEvent,
BackgroundTaskSchema,
BackgroundTaskSummarySchema,
TriggersSchema,
} from './background-task.js';
import { UserMessageContent } from './message.js';
import { RowboatApiConfig } from './rowboat-account.js';
import { ZListToolkitsResponse } from './composio.js';
@ -218,6 +224,10 @@ const ipcSchemas = {
req: LiveNoteAgentEvent,
res: z.null(),
},
'bg-task-agent:events': {
req: BackgroundTaskAgentEvent,
res: z.null(),
},
'models:list': {
req: z.null(),
res: z.object({
@ -691,6 +701,95 @@ const ipcSchemas = {
})),
}),
},
// Background-task channels
'bg-task:run': {
req: z.object({
slug: z.string(),
context: z.string().optional(),
}),
res: z.object({
success: z.boolean(),
runId: z.string().nullable().optional(),
summary: z.string().nullable().optional(),
error: z.string().optional(),
}),
},
'bg-task:get': {
req: z.object({
slug: z.string(),
}),
res: z.object({
success: z.boolean(),
task: BackgroundTaskSchema.nullable().optional(),
error: z.string().optional(),
}),
},
'bg-task:patch': {
req: z.object({
slug: z.string(),
partial: BackgroundTaskSchema.partial(),
}),
res: z.object({
success: z.boolean(),
task: BackgroundTaskSchema.nullable().optional(),
error: z.string().optional(),
}),
},
'bg-task:create': {
req: z.object({
name: z.string(),
instructions: z.string(),
triggers: TriggersSchema.optional(),
model: z.string().optional(),
provider: z.string().optional(),
}),
res: z.object({
success: z.boolean(),
slug: z.string().optional(),
error: z.string().optional(),
}),
},
'bg-task:delete': {
req: z.object({
slug: z.string(),
}),
res: z.object({
success: z.boolean(),
error: z.string().optional(),
}),
},
'bg-task:stop': {
req: z.object({
slug: z.string(),
}),
res: z.object({
success: z.boolean(),
error: z.string().optional(),
}),
},
'bg-task:list': {
req: z.object({
offset: z.number().int().nonnegative().optional(),
limit: z.number().int().positive().optional(),
sort: z.enum(['createdAt:desc', 'createdAt:asc', 'name:asc']).optional(),
}),
res: z.object({
items: z.array(BackgroundTaskSummarySchema),
total: z.number().int().nonnegative(),
}),
},
// Returns the runIds recorded in `bg-tasks/<slug>/runs.log` (newest first).
// The renderer turns each id into a full Run via the existing `runs:fetch`
// channel — bg-task transcripts now live at the global $WorkDir/runs/.
'bg-task:listRunIds': {
req: z.object({
slug: z.string(),
limit: z.number().int().positive().optional(),
}),
res: z.object({
runIds: z.array(z.string()),
}),
},
// Embedded browser (WebContentsView) channels
'browser:setBounds': {
req: z.object({