mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-22 18:45:19 +02:00
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>
175 lines
4.4 KiB
TypeScript
175 lines
4.4 KiB
TypeScript
import { LlmStepStreamEvent } from "./llm-step-events.js";
|
|
import { Message, ToolCallPart } from "./message.js";
|
|
import z from "zod";
|
|
|
|
const BaseRunEvent = z.object({
|
|
runId: z.string(),
|
|
ts: z.iso.datetime().optional(),
|
|
subflow: z.array(z.string()),
|
|
});
|
|
|
|
export const RunProcessingStartEvent = BaseRunEvent.extend({
|
|
type: z.literal("run-processing-start"),
|
|
});
|
|
|
|
export const RunProcessingEndEvent = BaseRunEvent.extend({
|
|
type: z.literal("run-processing-end"),
|
|
});
|
|
|
|
export const StartEvent = BaseRunEvent.extend({
|
|
type: z.literal("start"),
|
|
agentName: z.string(),
|
|
model: z.string(),
|
|
provider: z.string(),
|
|
// useCase/subUseCase tag the run for analytics. Optional on read so legacy
|
|
// run files written before these fields existed still parse cleanly.
|
|
useCase: z.enum([
|
|
"copilot_chat",
|
|
"live_note_agent",
|
|
"background_task_agent",
|
|
"meeting_note",
|
|
"knowledge_sync",
|
|
]).optional(),
|
|
subUseCase: z.string().optional(),
|
|
});
|
|
|
|
export const SpawnSubFlowEvent = BaseRunEvent.extend({
|
|
type: z.literal("spawn-subflow"),
|
|
agentName: z.string(),
|
|
toolCallId: z.string(),
|
|
});
|
|
|
|
export const LlmStreamEvent = BaseRunEvent.extend({
|
|
type: z.literal("llm-stream-event"),
|
|
event: LlmStepStreamEvent,
|
|
});
|
|
|
|
export const MessageEvent = BaseRunEvent.extend({
|
|
type: z.literal("message"),
|
|
messageId: z.string(),
|
|
message: Message,
|
|
});
|
|
|
|
export const ToolInvocationEvent = BaseRunEvent.extend({
|
|
type: z.literal("tool-invocation"),
|
|
toolCallId: z.string().optional(),
|
|
toolName: z.string(),
|
|
input: z.string(),
|
|
});
|
|
|
|
export const ToolResultEvent = BaseRunEvent.extend({
|
|
type: z.literal("tool-result"),
|
|
toolCallId: z.string().optional(),
|
|
toolName: z.string(),
|
|
result: z.any(),
|
|
});
|
|
|
|
export const ToolOutputStreamEvent = BaseRunEvent.extend({
|
|
type: z.literal("tool-output-stream"),
|
|
toolCallId: z.string(),
|
|
toolName: z.string(),
|
|
output: z.string(),
|
|
});
|
|
|
|
export const AskHumanRequestEvent = BaseRunEvent.extend({
|
|
type: z.literal("ask-human-request"),
|
|
toolCallId: z.string(),
|
|
query: z.string(),
|
|
});
|
|
|
|
export const AskHumanResponseEvent = BaseRunEvent.extend({
|
|
type: z.literal("ask-human-response"),
|
|
toolCallId: z.string(),
|
|
response: z.string(),
|
|
});
|
|
|
|
export const ToolPermissionRequestEvent = BaseRunEvent.extend({
|
|
type: z.literal("tool-permission-request"),
|
|
toolCall: ToolCallPart,
|
|
});
|
|
|
|
export const ToolPermissionResponseEvent = BaseRunEvent.extend({
|
|
type: z.literal("tool-permission-response"),
|
|
toolCallId: z.string(),
|
|
response: z.enum(["approve", "deny"]),
|
|
scope: z.enum(["once", "session", "always"]).optional(),
|
|
});
|
|
|
|
export const RunErrorEvent = BaseRunEvent.extend({
|
|
type: z.literal("error"),
|
|
error: z.string(),
|
|
});
|
|
|
|
export const RunStoppedEvent = BaseRunEvent.extend({
|
|
type: z.literal("run-stopped"),
|
|
reason: z.enum(["user-requested", "force-stopped"]).optional(),
|
|
});
|
|
|
|
export const RunEvent = z.union([
|
|
RunProcessingStartEvent,
|
|
RunProcessingEndEvent,
|
|
StartEvent,
|
|
SpawnSubFlowEvent,
|
|
LlmStreamEvent,
|
|
MessageEvent,
|
|
ToolInvocationEvent,
|
|
ToolResultEvent,
|
|
ToolOutputStreamEvent,
|
|
AskHumanRequestEvent,
|
|
AskHumanResponseEvent,
|
|
ToolPermissionRequestEvent,
|
|
ToolPermissionResponseEvent,
|
|
RunErrorEvent,
|
|
RunStoppedEvent,
|
|
]);
|
|
|
|
export const ToolPermissionAuthorizePayload = ToolPermissionResponseEvent.pick({
|
|
subflow: true,
|
|
toolCallId: true,
|
|
response: true,
|
|
scope: true,
|
|
});
|
|
|
|
export const AskHumanResponsePayload = AskHumanResponseEvent.pick({
|
|
subflow: true,
|
|
toolCallId: true,
|
|
response: true,
|
|
});
|
|
|
|
export const UseCase = z.enum([
|
|
"copilot_chat",
|
|
"live_note_agent",
|
|
"background_task_agent",
|
|
"meeting_note",
|
|
"knowledge_sync",
|
|
]);
|
|
|
|
export const Run = z.object({
|
|
id: z.string(),
|
|
title: z.string().optional(),
|
|
createdAt: z.iso.datetime(),
|
|
agentId: z.string(),
|
|
model: z.string(),
|
|
provider: z.string(),
|
|
useCase: UseCase.optional(),
|
|
subUseCase: z.string().optional(),
|
|
log: z.array(RunEvent),
|
|
});
|
|
|
|
export const ListRunsResponse = z.object({
|
|
runs: z.array(Run.pick({
|
|
id: true,
|
|
title: true,
|
|
createdAt: true,
|
|
agentId: true,
|
|
})),
|
|
nextCursor: z.string().optional(),
|
|
});
|
|
|
|
export const CreateRunOptions = z.object({
|
|
agentId: z.string(),
|
|
model: z.string().optional(),
|
|
provider: z.string().optional(),
|
|
useCase: UseCase.optional(),
|
|
subUseCase: z.string().optional(),
|
|
});
|