mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-06-27 20:29:44 +02:00
Merge origin/dev into drive
Resolve conflicts: - apps/main/src/ipc.ts: import union (dev's gmail contacts/getAccountName + drive's google_docs/managed-picker imports). - apps/renderer/src/App.tsx: import union (dev's CodingRunBlock/KnowledgeViewMode + drive's GoogleDocPickerDialog). - apps/renderer/src/components/knowledge-view.tsx: keep the "Add Google Doc" button in the header next to the voice-note action; Search/Graph/New note are now dev's QuickActions / view-mode toggles.
This commit is contained in:
commit
462023f4c2
142 changed files with 17080 additions and 1347 deletions
|
|
@ -27,6 +27,10 @@ export type BackgroundTask = {
|
|||
instructions: string;
|
||||
active: boolean;
|
||||
triggers?: Triggers;
|
||||
// When set, this is a *coding* task: it implements code in the pinned code
|
||||
// project (a registered repo) via the `launch-code-task` tool, each launch
|
||||
// running in its own isolated worktree. Omit for ordinary OUTPUT/ACTION tasks.
|
||||
projectId?: string;
|
||||
model?: string;
|
||||
provider?: string;
|
||||
createdAt: string;
|
||||
|
|
@ -48,6 +52,7 @@ export type BackgroundTaskSummary = {
|
|||
instructions: string;
|
||||
active: boolean;
|
||||
triggers?: Triggers;
|
||||
projectId?: string;
|
||||
createdAt: string;
|
||||
lastAttemptAt?: string;
|
||||
lastRunId?: string;
|
||||
|
|
@ -56,11 +61,14 @@ export type BackgroundTaskSummary = {
|
|||
lastRunError?: string;
|
||||
};
|
||||
|
||||
// NOTE: keep `BackgroundTaskSummary` (above) and `BackgroundTask` (top) in sync.
|
||||
|
||||
export const BackgroundTaskSchema = z.object({
|
||||
name: z.string().min(1).describe('User-facing display name.'),
|
||||
instructions: z.string().min(1).describe('A persistent instruction in the user\'s words — what should this task keep doing? E.g. "Summarize my unread emails every morning into a brief digest." The agent re-reads instructions on every run and decides whether to rewrite index.md (OUTPUT mode) or perform a side-effect and journal it (ACTION mode) based on the verbs.'),
|
||||
active: z.boolean().default(true).describe('Set false to pause without deleting.'),
|
||||
triggers: TriggersSchema.optional().describe('When the agent fires. Omit for manual-only.'),
|
||||
projectId: z.string().optional().describe('When set, marks this as a coding task pinned to a registered code project (repo). The agent implements detected work via the launch-code-task tool, each launch in its own isolated worktree.'),
|
||||
model: z.string().optional().describe('ADVANCED — leave unset. Per-task model override.'),
|
||||
provider: z.string().optional().describe('ADVANCED — leave unset. Per-task provider name override.'),
|
||||
createdAt: z.string().describe('ISO timestamp set once at create-time.'),
|
||||
|
|
@ -77,6 +85,7 @@ export const BackgroundTaskSummarySchema = z.object({
|
|||
instructions: z.string(),
|
||||
active: z.boolean(),
|
||||
triggers: TriggersSchema.optional(),
|
||||
projectId: z.string().optional(),
|
||||
createdAt: z.string(),
|
||||
lastAttemptAt: z.string().optional(),
|
||||
lastRunId: z.string().optional(),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,26 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
export const BillingPlanSchema = z.enum(['free', 'starter', 'pro']);
|
||||
export type BillingPlan = z.infer<typeof BillingPlanSchema>;
|
||||
export const BillingPlanCategorySchema = z.enum(['free', 'starter', 'pro']);
|
||||
export type BillingPlanCategory = z.infer<typeof BillingPlanCategorySchema>;
|
||||
|
||||
export const BillingPlanIdSchema = z.string().min(1);
|
||||
export type BillingPlanId = z.infer<typeof BillingPlanIdSchema>;
|
||||
|
||||
export const BillingCatalogPlanSchema = z.object({
|
||||
id: BillingPlanIdSchema,
|
||||
category: BillingPlanCategorySchema,
|
||||
displayName: z.string(),
|
||||
monthlyCredits: z.number(),
|
||||
dailyCredits: z.number(),
|
||||
monthlyPriceCents: z.number().nullable(),
|
||||
archived: z.boolean().optional(),
|
||||
});
|
||||
export type BillingCatalogPlan = z.infer<typeof BillingCatalogPlanSchema>;
|
||||
|
||||
export const BillingCatalogSchema = z.object({
|
||||
plans: z.array(BillingCatalogPlanSchema),
|
||||
});
|
||||
export type BillingCatalog = z.infer<typeof BillingCatalogSchema>;
|
||||
|
||||
export const BillingUsageBucketSchema = z.object({
|
||||
sanctionedCredits: z.number(),
|
||||
|
|
@ -13,12 +32,21 @@ export type BillingUsageBucket = z.infer<typeof BillingUsageBucketSchema>;
|
|||
export const BillingInfoSchema = z.object({
|
||||
userEmail: z.string().nullable(),
|
||||
userId: z.string().nullable(),
|
||||
subscriptionPlan: BillingPlanSchema.nullable(),
|
||||
subscriptionPlanId: BillingPlanIdSchema.nullable(),
|
||||
subscriptionStatus: z.string().nullable(),
|
||||
trialExpiresAt: z.string().nullable(),
|
||||
catalog: BillingCatalogSchema,
|
||||
monthly: BillingUsageBucketSchema,
|
||||
daily: BillingUsageBucketSchema.extend({
|
||||
usageDay: z.string(),
|
||||
}),
|
||||
});
|
||||
export type BillingInfo = z.infer<typeof BillingInfoSchema>;
|
||||
|
||||
export function getBillingPlanData(
|
||||
catalog: BillingCatalog,
|
||||
planId: string | null | undefined,
|
||||
): BillingCatalogPlan | null {
|
||||
if (!planId) return null;
|
||||
return catalog.plans.find((plan) => plan.id === planId) ?? null;
|
||||
}
|
||||
|
|
|
|||
70
apps/x/packages/shared/src/code-mode.ts
Normal file
70
apps/x/packages/shared/src/code-mode.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import z from "zod";
|
||||
|
||||
// Shared zod schemas for the ACP code-mode engine. Single source of truth: the
|
||||
// core engine re-exports the inferred TS types, and runs.ts builds the RunEvent
|
||||
// variants that carry these to the renderer.
|
||||
|
||||
export const CodingAgent = z.enum(["claude", "codex"]);
|
||||
export type CodingAgent = z.infer<typeof CodingAgent>;
|
||||
|
||||
// How the permission broker answers the agent's requests before any per-tool
|
||||
// "always allow" memory is applied. `yolo` is the safe, scoped equivalent of
|
||||
// `claude --dangerously-skip-permissions` (our toggle, not a CLI flag).
|
||||
export const ApprovalPolicy = z.enum(["ask", "auto-approve-reads", "yolo"]);
|
||||
export type ApprovalPolicy = z.infer<typeof ApprovalPolicy>;
|
||||
|
||||
export const PermissionDecision = z.enum(["allow_once", "allow_always", "reject"]);
|
||||
export type PermissionDecision = z.infer<typeof PermissionDecision>;
|
||||
|
||||
// What the UI needs to render a permission card.
|
||||
export const PermissionAsk = z.object({
|
||||
toolCallId: z.string().optional(),
|
||||
title: z.string(),
|
||||
kind: z.string().optional(), // tool kind, e.g. "edit" | "execute" | "read"
|
||||
isRead: z.boolean(),
|
||||
});
|
||||
export type PermissionAsk = z.infer<typeof PermissionAsk>;
|
||||
|
||||
// Normalized per-run stream items. The engine maps raw ACP session/update
|
||||
// notifications onto this union; the renderer renders them.
|
||||
export const CodeRunEvent = z.discriminatedUnion("type", [
|
||||
// role distinguishes the agent's own output from replayed user turns
|
||||
// (loadSession streams the whole prior conversation back on resume).
|
||||
z.object({ type: z.literal("message"), role: z.enum(["agent", "user"]), text: z.string() }),
|
||||
z.object({ type: z.literal("thought") }),
|
||||
z.object({
|
||||
type: z.literal("tool_call"),
|
||||
id: z.string().optional(),
|
||||
title: z.string().optional(),
|
||||
kind: z.string().optional(),
|
||||
status: z.string().optional(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal("tool_call_update"),
|
||||
id: z.string().optional(),
|
||||
status: z.string().optional(),
|
||||
diffs: z.array(z.string()),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal("plan"),
|
||||
entries: z.array(z.object({
|
||||
content: z.string(),
|
||||
status: z.string().optional(),
|
||||
priority: z.string().optional(),
|
||||
})),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal("permission"),
|
||||
ask: PermissionAsk,
|
||||
decision: z.union([PermissionDecision, z.literal("cancelled")]),
|
||||
auto: z.boolean(),
|
||||
}),
|
||||
z.object({ type: z.literal("other"), sessionUpdate: z.string() }),
|
||||
]);
|
||||
export type CodeRunEvent = z.infer<typeof CodeRunEvent>;
|
||||
|
||||
export const RunPromptResult = z.object({
|
||||
stopReason: z.string(),
|
||||
sessionId: z.string(),
|
||||
});
|
||||
export type RunPromptResult = z.infer<typeof RunPromptResult>;
|
||||
94
apps/x/packages/shared/src/code-sessions.ts
Normal file
94
apps/x/packages/shared/src/code-sessions.ts
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
import z from "zod";
|
||||
import { CodingAgent, ApprovalPolicy } from "./code-mode.js";
|
||||
|
||||
// Shared zod schemas for the Code section: registered projects and coding
|
||||
// sessions. A coding session is backed by a run (session id == run id); the
|
||||
// mutable metadata below lives in its own per-session file.
|
||||
|
||||
export const CodeProject = z.object({
|
||||
id: z.string(),
|
||||
path: z.string(),
|
||||
name: z.string(),
|
||||
addedAt: z.iso.datetime(),
|
||||
});
|
||||
export type CodeProject = z.infer<typeof CodeProject>;
|
||||
|
||||
// Git facts about a project path, used to gate worktree creation in the UI.
|
||||
export const GitRepoInfo = z.object({
|
||||
isGitRepo: z.boolean(),
|
||||
branch: z.string().nullable(),
|
||||
hasCommits: z.boolean(),
|
||||
dirtyCount: z.number(),
|
||||
});
|
||||
export type GitRepoInfo = z.infer<typeof GitRepoInfo>;
|
||||
|
||||
// 'direct': the user's messages go straight to the ACP coding agent.
|
||||
// 'rowboat': Rowboat's copilot LLM orchestrates the agent via code_agent_run.
|
||||
export const CodeSessionMode = z.enum(["direct", "rowboat"]);
|
||||
export type CodeSessionMode = z.infer<typeof CodeSessionMode>;
|
||||
|
||||
// Derived live in the main process from the run event stream; not persisted.
|
||||
export const CodeSessionStatus = z.enum(["working", "needs-you", "idle"]);
|
||||
export type CodeSessionStatus = z.infer<typeof CodeSessionStatus>;
|
||||
|
||||
export const CodeWorktree = z.object({
|
||||
path: z.string(),
|
||||
branch: z.string(),
|
||||
// Branch the original checkout was on when the worktree was created;
|
||||
// merge-back targets whatever the checkout is on at merge time, this is
|
||||
// informational.
|
||||
baseBranch: z.string().nullable(),
|
||||
mergedAt: z.iso.datetime().optional(),
|
||||
removedAt: z.iso.datetime().optional(),
|
||||
});
|
||||
export type CodeWorktree = z.infer<typeof CodeWorktree>;
|
||||
|
||||
export const CodeSession = z.object({
|
||||
id: z.string(), // == runId
|
||||
projectId: z.string(),
|
||||
title: z.string(),
|
||||
agent: CodingAgent,
|
||||
mode: CodeSessionMode,
|
||||
policy: ApprovalPolicy,
|
||||
// Where the agent works: the project path, or the worktree path.
|
||||
cwd: z.string(),
|
||||
worktree: CodeWorktree.optional(),
|
||||
// The coding agent's own model + reasoning effort (applied to the ACP engine,
|
||||
// not the Rowboat-mode LLM). Values come from CODE_AGENT_MODELS /
|
||||
// CODE_AGENT_EFFORTS; unset (or 'default') leaves the engine's own default.
|
||||
agentModel: z.string().optional(),
|
||||
agentEffort: z.string().optional(),
|
||||
createdAt: z.iso.datetime(),
|
||||
lastActivityAt: z.iso.datetime().optional(),
|
||||
});
|
||||
export type CodeSession = z.infer<typeof CodeSession>;
|
||||
|
||||
// Model + effort choices for the ACP coding agents are discovered live from the
|
||||
// engine (the same list `/model` shows), not hardcoded — so they always reflect
|
||||
// whatever the provider currently offers. See the `codeMode:listModelOptions`
|
||||
// IPC and CodeModeManager.listModelOptions. 'default' is a synthetic sentinel
|
||||
// meaning "don't override the engine default".
|
||||
//
|
||||
// Claude exposes model and effort as two independent options; Codex folds the
|
||||
// reasoning effort into the model id ("gpt-5-codex[high]") and so reports no
|
||||
// separate effort list. The UI renders whatever each agent advertises.
|
||||
export const CodeAgentOption = z.object({ value: z.string(), label: z.string() });
|
||||
export type CodeAgentOption = z.infer<typeof CodeAgentOption>;
|
||||
|
||||
export const CodeAgentModelOptions = z.object({
|
||||
models: z.array(CodeAgentOption),
|
||||
efforts: z.array(CodeAgentOption),
|
||||
});
|
||||
export type CodeAgentModelOptions = z.infer<typeof CodeAgentModelOptions>;
|
||||
|
||||
export const GitFileState = z.enum(["modified", "added", "deleted", "untracked", "renamed"]);
|
||||
export type GitFileState = z.infer<typeof GitFileState>;
|
||||
|
||||
export const GitStatusFile = z.object({
|
||||
path: z.string(),
|
||||
state: GitFileState,
|
||||
// Null when git can't compute line counts (binary files).
|
||||
insertions: z.number().nullable(),
|
||||
deletions: z.number().nullable(),
|
||||
});
|
||||
export type GitStatusFile = z.infer<typeof GitStatusFile>;
|
||||
|
|
@ -17,4 +17,6 @@ export * as frontmatter from './frontmatter.js';
|
|||
export * as bases from './bases.js';
|
||||
export * as browserControl from './browser-control.js';
|
||||
export * as billing from './billing.js';
|
||||
export * as notificationSettings from './notification-settings.js';
|
||||
export * as codeSessions from './code-sessions.js';
|
||||
export { PrefixLogger };
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { z } from 'zod';
|
|||
import { RelPath, Encoding, Stat, DirEntry, ReaddirOptions, ReadFileResult, WorkspaceChangeEvent, WriteFileOptions, WriteFileResult, RemoveOptions } from './workspace.js';
|
||||
import { ListToolsResponse } from './mcp.js';
|
||||
import { AskHumanResponsePayload, CreateRunOptions, Run, ListRunsResponse, ToolPermissionAuthorizePayload } from './runs.js';
|
||||
import { LlmModelConfig } from './models.js';
|
||||
import { LlmModelConfig, LlmProvider } from './models.js';
|
||||
import { AgentScheduleConfig, AgentScheduleEntry } from './agent-schedule.js';
|
||||
import { AgentScheduleState } from './agent-schedule-state.js';
|
||||
import { ServiceEvent } from './service-events.js';
|
||||
|
|
@ -19,11 +19,41 @@ import { ZListToolkitsResponse } from './composio.js';
|
|||
import { BrowserStateSchema } from './browser-control.js';
|
||||
import { BillingInfoSchema } from './billing.js';
|
||||
import { EmailBlockSchema, GmailThreadSchema } from './blocks.js';
|
||||
import { PermissionDecision, ApprovalPolicy, CodingAgent } from './code-mode.js';
|
||||
import { NotificationSettingsSchema } from './notification-settings.js';
|
||||
import { CodeProject, CodeSession, CodeSessionMode, CodeSessionStatus, GitRepoInfo, GitStatusFile, CodeAgentModelOptions } from './code-sessions.js';
|
||||
|
||||
// ============================================================================
|
||||
// Runtime Validation Schemas (Single Source of Truth)
|
||||
// ============================================================================
|
||||
|
||||
const KnowledgeSourceScopeSchema = z.object({
|
||||
type: z.string(),
|
||||
id: z.string(),
|
||||
name: z.string().optional(),
|
||||
workspaceUrl: z.string().optional(),
|
||||
});
|
||||
|
||||
// Mirrors AgentSlackErrorKind in @x/core/slack/agent-slack-exec. Kept as a
|
||||
// standalone enum so the renderer can branch on failure cause without
|
||||
// importing core.
|
||||
const SlackErrorKindSchema = z.enum([
|
||||
'not_installed', 'timeout', 'parse_error',
|
||||
'not_authed', 'rate_limited', 'network', 'bad_channel', 'unknown',
|
||||
]);
|
||||
|
||||
const KnowledgeSourceConfigSchema = z.object({
|
||||
id: z.string(),
|
||||
provider: z.enum(['gmail', 'meeting', 'voice_memo', 'slack', 'github', 'linear']),
|
||||
enabled: z.boolean(),
|
||||
artifactDir: z.string(),
|
||||
syncMode: z.enum(['file', 'poll', 'event', 'manual']).default('file'),
|
||||
intervalMs: z.number().int().positive().optional(),
|
||||
scopes: z.array(KnowledgeSourceScopeSchema).default([]),
|
||||
instructions: z.string().optional(),
|
||||
filters: z.record(z.string(), z.unknown()).optional(),
|
||||
});
|
||||
|
||||
const ipcSchemas = {
|
||||
'app:getVersions': {
|
||||
req: z.null(),
|
||||
|
|
@ -160,6 +190,15 @@ const ipcSchemas = {
|
|||
bodyText: z.string(),
|
||||
inReplyTo: z.string().optional(),
|
||||
references: z.string().optional(),
|
||||
attachments: z
|
||||
.array(
|
||||
z.object({
|
||||
filename: z.string(),
|
||||
mimeType: z.string(),
|
||||
contentBase64: z.string(),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
}),
|
||||
res: z.object({
|
||||
messageId: z.string().optional(),
|
||||
|
|
@ -181,6 +220,12 @@ const ipcSchemas = {
|
|||
email: z.string().nullable(),
|
||||
}),
|
||||
},
|
||||
'gmail:getAccountName': {
|
||||
req: z.object({}),
|
||||
res: z.object({
|
||||
name: z.string().nullable(),
|
||||
}),
|
||||
},
|
||||
'gmail:archiveThread': {
|
||||
req: z.object({ threadId: z.string().min(1) }),
|
||||
res: z.object({ ok: z.boolean(), error: z.string().optional() }),
|
||||
|
|
@ -201,6 +246,21 @@ const ipcSchemas = {
|
|||
}),
|
||||
res: z.object({}),
|
||||
},
|
||||
'gmail:searchContacts': {
|
||||
req: z.object({
|
||||
query: z.string(),
|
||||
limit: z.number().int().positive().optional(),
|
||||
excludeEmails: z.array(z.string()).optional(),
|
||||
}),
|
||||
res: z.object({
|
||||
contacts: z.array(z.object({
|
||||
name: z.string(),
|
||||
email: z.string(),
|
||||
count: z.number(),
|
||||
lastSeenMs: z.number(),
|
||||
})),
|
||||
}),
|
||||
},
|
||||
'mcp:listTools': {
|
||||
req: z.object({
|
||||
serverName: z.string(),
|
||||
|
|
@ -230,6 +290,10 @@ const ipcSchemas = {
|
|||
voiceOutput: z.enum(['summary', 'full']).optional(),
|
||||
searchEnabled: z.boolean().optional(),
|
||||
codeMode: z.enum(['claude', 'codex']).optional(),
|
||||
// Code-section sessions pin the coding agent's working directory and
|
||||
// approval policy for the whole turn (see code_agent_run overrides).
|
||||
codeCwd: z.string().optional(),
|
||||
codePolicy: ApprovalPolicy.optional(),
|
||||
middlePaneContext: z.discriminatedUnion('kind', [
|
||||
z.object({
|
||||
kind: z.literal('note'),
|
||||
|
|
@ -339,6 +403,37 @@ const ipcSchemas = {
|
|||
error: z.string().optional(),
|
||||
}),
|
||||
},
|
||||
'models:listForProvider': {
|
||||
req: z.object({
|
||||
provider: LlmProvider,
|
||||
}),
|
||||
res: z.object({
|
||||
success: z.boolean(),
|
||||
models: z.array(z.string()).optional(),
|
||||
error: z.string().optional(),
|
||||
}),
|
||||
},
|
||||
'llm:getDefaultModel': {
|
||||
req: z.null(),
|
||||
res: z.object({
|
||||
model: z.string(),
|
||||
provider: z.string(),
|
||||
}),
|
||||
},
|
||||
'llm:generate': {
|
||||
req: z.object({
|
||||
prompt: z.string().min(1),
|
||||
system: z.string().optional(),
|
||||
model: z.string().optional(),
|
||||
provider: z.string().optional(),
|
||||
}),
|
||||
res: z.object({
|
||||
text: z.string().optional(),
|
||||
model: z.string().optional(),
|
||||
provider: z.string().optional(),
|
||||
error: z.string().optional(),
|
||||
}),
|
||||
},
|
||||
'models:saveConfig': {
|
||||
req: LlmModelConfig,
|
||||
res: z.object({
|
||||
|
|
@ -430,11 +525,23 @@ const ipcSchemas = {
|
|||
req: z.null(),
|
||||
res: z.object({
|
||||
enabled: z.boolean(),
|
||||
approvalPolicy: ApprovalPolicy.optional(),
|
||||
}),
|
||||
},
|
||||
'codeMode:setConfig': {
|
||||
req: z.object({
|
||||
enabled: z.boolean(),
|
||||
approvalPolicy: ApprovalPolicy.optional(),
|
||||
}),
|
||||
res: z.object({
|
||||
success: z.literal(true),
|
||||
}),
|
||||
},
|
||||
// Answer a mid-run permission request from a code_agent_run coding turn.
|
||||
'codeRun:resolvePermission': {
|
||||
req: z.object({
|
||||
requestId: z.string(),
|
||||
decision: PermissionDecision,
|
||||
}),
|
||||
res: z.object({
|
||||
success: z.literal(true),
|
||||
|
|
@ -447,6 +554,244 @@ const ipcSchemas = {
|
|||
codex: z.object({ installed: z.boolean(), signedIn: z.boolean() }),
|
||||
}),
|
||||
},
|
||||
// Download + install an agent's native engine (the Settings "Enable" action).
|
||||
// Streams progress over the 'codeMode:engineProgress' push channel while it runs.
|
||||
'codeMode:provisionEngine': {
|
||||
req: z.object({ agent: z.enum(['claude', 'codex']) }),
|
||||
res: z.object({ success: z.boolean(), error: z.string().optional() }),
|
||||
},
|
||||
// Push (main -> renderer): engine provisioning progress for the Settings UI.
|
||||
'codeMode:engineProgress': {
|
||||
req: z.object({
|
||||
agent: z.enum(['claude', 'codex']),
|
||||
phase: z.enum(['download', 'verify', 'extract', 'done']),
|
||||
receivedBytes: z.number().optional(),
|
||||
totalBytes: z.number().optional(),
|
||||
}),
|
||||
res: z.null(),
|
||||
},
|
||||
// ==========================================================================
|
||||
// Code section: project registry + coding sessions
|
||||
// ==========================================================================
|
||||
'codeProject:add': {
|
||||
req: z.object({
|
||||
path: z.string(),
|
||||
}),
|
||||
res: z.object({
|
||||
project: CodeProject,
|
||||
git: GitRepoInfo,
|
||||
}),
|
||||
},
|
||||
'codeProject:remove': {
|
||||
req: z.object({
|
||||
projectId: z.string(),
|
||||
}),
|
||||
res: z.object({
|
||||
success: z.literal(true),
|
||||
}),
|
||||
},
|
||||
'codeProject:list': {
|
||||
req: z.null(),
|
||||
res: z.object({
|
||||
projects: z.array(z.object({
|
||||
project: CodeProject,
|
||||
git: GitRepoInfo,
|
||||
})),
|
||||
}),
|
||||
},
|
||||
'codeSession:create': {
|
||||
req: z.object({
|
||||
projectId: z.string(),
|
||||
title: z.string().optional(),
|
||||
agent: CodingAgent,
|
||||
mode: CodeSessionMode,
|
||||
policy: ApprovalPolicy,
|
||||
isolation: z.enum(['in-repo', 'worktree']),
|
||||
// LLM for Rowboat-mode turns. Unset = the configured default. Like any
|
||||
// chat, the model is fixed once the session's run exists.
|
||||
model: z.string().optional(),
|
||||
provider: z.string().optional(),
|
||||
// The coding agent's own model + reasoning effort (ACP engine). Unlike the
|
||||
// Rowboat model these are re-applied each turn, so they stay editable.
|
||||
agentModel: z.string().optional(),
|
||||
agentEffort: z.string().optional(),
|
||||
}),
|
||||
res: z.object({
|
||||
session: CodeSession,
|
||||
}),
|
||||
},
|
||||
'codeSession:list': {
|
||||
req: z.null(),
|
||||
res: z.object({
|
||||
sessions: z.array(CodeSession),
|
||||
statuses: z.record(z.string(), CodeSessionStatus),
|
||||
}),
|
||||
},
|
||||
'codeSession:update': {
|
||||
req: z.object({
|
||||
sessionId: z.string(),
|
||||
patch: CodeSession.pick({ title: true, mode: true, policy: true, agent: true, agentModel: true, agentEffort: true }).partial(),
|
||||
}),
|
||||
res: z.object({
|
||||
session: CodeSession,
|
||||
}),
|
||||
},
|
||||
// Live model + effort choices for a coding agent, discovered from the engine
|
||||
// (cached per agent in the main process). Mirrors what `/model` would show.
|
||||
'codeMode:listModelOptions': {
|
||||
req: z.object({ agent: CodingAgent }),
|
||||
res: CodeAgentModelOptions,
|
||||
},
|
||||
'codeSession:delete': {
|
||||
req: z.object({
|
||||
sessionId: z.string(),
|
||||
removeWorktree: z.boolean().optional(),
|
||||
deleteBranch: z.boolean().optional(),
|
||||
}),
|
||||
res: z.object({
|
||||
success: z.literal(true),
|
||||
}),
|
||||
},
|
||||
// Direct-drive: send the user's message straight to the session's ACP agent
|
||||
// (no copilot LLM in between). Streams back over `runs:events`.
|
||||
'codeSession:sendMessage': {
|
||||
req: z.object({
|
||||
sessionId: z.string(),
|
||||
text: z.string().min(1),
|
||||
}),
|
||||
res: z.object({
|
||||
accepted: z.boolean(),
|
||||
error: z.string().optional(),
|
||||
}),
|
||||
},
|
||||
'codeSession:stop': {
|
||||
req: z.object({
|
||||
sessionId: z.string(),
|
||||
}),
|
||||
res: z.object({
|
||||
success: z.literal(true),
|
||||
}),
|
||||
},
|
||||
'codeSession:gitStatus': {
|
||||
req: z.object({
|
||||
sessionId: z.string(),
|
||||
}),
|
||||
res: z.object({
|
||||
isRepo: z.boolean(),
|
||||
branch: z.string().nullable(),
|
||||
hasCommits: z.boolean(),
|
||||
files: z.array(GitStatusFile),
|
||||
}),
|
||||
},
|
||||
'codeSession:fileDiff': {
|
||||
req: z.object({
|
||||
sessionId: z.string(),
|
||||
path: z.string(),
|
||||
}),
|
||||
res: z.object({
|
||||
oldText: z.string(),
|
||||
newText: z.string(),
|
||||
isBinary: z.boolean(),
|
||||
tooLarge: z.boolean(),
|
||||
}),
|
||||
},
|
||||
'codeSession:readdir': {
|
||||
req: z.object({
|
||||
sessionId: z.string(),
|
||||
relPath: z.string(),
|
||||
}),
|
||||
res: z.object({
|
||||
entries: z.array(z.object({
|
||||
name: z.string(),
|
||||
kind: z.enum(['file', 'dir']),
|
||||
size: z.number().optional(),
|
||||
})),
|
||||
}),
|
||||
},
|
||||
'codeSession:readFile': {
|
||||
req: z.object({
|
||||
sessionId: z.string(),
|
||||
relPath: z.string(),
|
||||
}),
|
||||
res: z.object({
|
||||
content: z.string(),
|
||||
isBinary: z.boolean(),
|
||||
tooLarge: z.boolean(),
|
||||
}),
|
||||
},
|
||||
'codeSession:mergeBack': {
|
||||
req: z.object({
|
||||
sessionId: z.string(),
|
||||
}),
|
||||
res: z.object({
|
||||
ok: z.boolean(),
|
||||
conflict: z.boolean().optional(),
|
||||
message: z.string(),
|
||||
}),
|
||||
},
|
||||
'codeSession:cleanupWorktree': {
|
||||
req: z.object({
|
||||
sessionId: z.string(),
|
||||
deleteBranch: z.boolean(),
|
||||
}),
|
||||
res: z.object({
|
||||
success: z.boolean(),
|
||||
error: z.string().optional(),
|
||||
}),
|
||||
},
|
||||
// main → renderer: live session status transitions from the status tracker.
|
||||
'codeSession:status': {
|
||||
req: z.object({
|
||||
sessionId: z.string(),
|
||||
status: CodeSessionStatus,
|
||||
}),
|
||||
res: z.null(),
|
||||
},
|
||||
// ==========================================================================
|
||||
// Embedded terminal (Code section): one PTY per coding session
|
||||
// ==========================================================================
|
||||
// Create-or-attach. Returns the scrollback backlog so a remounted view can
|
||||
// repaint what happened while it was closed.
|
||||
'terminal:ensure': {
|
||||
req: z.object({
|
||||
id: z.string(),
|
||||
cwd: z.string(),
|
||||
cols: z.number().int().positive(),
|
||||
rows: z.number().int().positive(),
|
||||
}),
|
||||
res: z.object({
|
||||
backlog: z.string(),
|
||||
running: z.boolean(),
|
||||
}),
|
||||
},
|
||||
'terminal:input': {
|
||||
req: z.object({
|
||||
id: z.string(),
|
||||
data: z.string(),
|
||||
}),
|
||||
res: z.object({ success: z.literal(true) }),
|
||||
},
|
||||
'terminal:resize': {
|
||||
req: z.object({
|
||||
id: z.string(),
|
||||
cols: z.number().int().positive(),
|
||||
rows: z.number().int().positive(),
|
||||
}),
|
||||
res: z.object({ success: z.literal(true) }),
|
||||
},
|
||||
'terminal:dispose': {
|
||||
req: z.object({ id: z.string() }),
|
||||
res: z.object({ success: z.literal(true) }),
|
||||
},
|
||||
// main → renderer streams
|
||||
'terminal:data': {
|
||||
req: z.object({ id: z.string(), data: z.string() }),
|
||||
res: z.null(),
|
||||
},
|
||||
'terminal:exit': {
|
||||
req: z.object({ id: z.string(), exitCode: z.number() }),
|
||||
res: z.null(),
|
||||
},
|
||||
'granola:setConfig': {
|
||||
req: z.object({
|
||||
enabled: z.boolean(),
|
||||
|
|
@ -471,11 +816,112 @@ const ipcSchemas = {
|
|||
success: z.literal(true),
|
||||
}),
|
||||
},
|
||||
'slack:cliStatus': {
|
||||
req: z.null(),
|
||||
res: z.object({
|
||||
available: z.boolean(),
|
||||
version: z.string().optional(),
|
||||
source: z.enum(['bundled', 'global', 'path']).optional(),
|
||||
}),
|
||||
},
|
||||
'slack:listWorkspaces': {
|
||||
req: z.null(),
|
||||
res: z.object({
|
||||
workspaces: z.array(z.object({ url: z.string(), name: z.string() })),
|
||||
error: z.string().optional(),
|
||||
errorKind: SlackErrorKindSchema.optional(),
|
||||
}),
|
||||
},
|
||||
'slack:importDesktopAuth': {
|
||||
req: z.null(),
|
||||
res: z.object({
|
||||
ok: z.boolean(),
|
||||
workspaces: z.array(z.object({ url: z.string(), name: z.string() })),
|
||||
error: z.string().optional(),
|
||||
errorKind: SlackErrorKindSchema.optional(),
|
||||
}),
|
||||
},
|
||||
'slack:quitAndImportDesktop': {
|
||||
req: z.null(),
|
||||
res: z.object({
|
||||
ok: z.boolean(),
|
||||
workspaces: z.array(z.object({ url: z.string(), name: z.string() })),
|
||||
error: z.string().optional(),
|
||||
errorKind: SlackErrorKindSchema.optional(),
|
||||
}),
|
||||
},
|
||||
'slack:parseCurlAuth': {
|
||||
req: z.object({ curl: z.string() }),
|
||||
res: z.object({
|
||||
ok: z.boolean(),
|
||||
workspaces: z.array(z.object({ url: z.string(), name: z.string() })),
|
||||
error: z.string().optional(),
|
||||
errorKind: SlackErrorKindSchema.optional(),
|
||||
}),
|
||||
},
|
||||
'slack:knowledgeStatus': {
|
||||
req: z.null(),
|
||||
res: z.object({
|
||||
cli: z.object({
|
||||
available: z.boolean(),
|
||||
version: z.string().optional(),
|
||||
source: z.enum(['bundled', 'global', 'path']).optional(),
|
||||
}),
|
||||
sources: z.array(z.object({
|
||||
id: z.string(),
|
||||
enabled: z.boolean(),
|
||||
lastSyncAt: z.string().optional(),
|
||||
lastStatus: z.enum(['ok', 'error']).optional(),
|
||||
lastError: z.object({ kind: z.string(), message: z.string() }).optional(),
|
||||
nextDueAt: z.string().optional(),
|
||||
})),
|
||||
}),
|
||||
},
|
||||
'slack:listChannels': {
|
||||
req: z.object({
|
||||
workspaceUrl: z.string(),
|
||||
}),
|
||||
res: z.object({
|
||||
channels: z.array(z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
isPrivate: z.boolean().optional(),
|
||||
isMember: z.boolean().optional(),
|
||||
})),
|
||||
error: z.string().optional(),
|
||||
}),
|
||||
},
|
||||
'slack:getRecentMessages': {
|
||||
req: z.object({
|
||||
limit: z.number().int().positive().max(20).optional(),
|
||||
}),
|
||||
res: z.object({
|
||||
enabled: z.boolean(),
|
||||
messages: z.array(z.object({
|
||||
id: z.string(),
|
||||
workspaceName: z.string().optional(),
|
||||
workspaceUrl: z.string().optional(),
|
||||
channelId: z.string().optional(),
|
||||
channelName: z.string().optional(),
|
||||
author: z.string().optional(),
|
||||
text: z.string(),
|
||||
ts: z.string(),
|
||||
url: z.string().optional(),
|
||||
})),
|
||||
error: z.string().optional(),
|
||||
errorKind: SlackErrorKindSchema.optional(),
|
||||
}),
|
||||
},
|
||||
'knowledgeSources:getConfig': {
|
||||
req: z.null(),
|
||||
res: z.object({
|
||||
sources: z.array(KnowledgeSourceConfigSchema),
|
||||
}),
|
||||
},
|
||||
'knowledgeSources:upsert': {
|
||||
req: KnowledgeSourceConfigSchema,
|
||||
res: z.object({
|
||||
sources: z.array(KnowledgeSourceConfigSchema),
|
||||
}),
|
||||
},
|
||||
'onboarding:getStatus': {
|
||||
|
|
@ -617,6 +1063,15 @@ const ipcSchemas = {
|
|||
path: z.string().nullable(),
|
||||
}),
|
||||
},
|
||||
'dialog:openFiles': {
|
||||
req: z.object({
|
||||
defaultPath: z.string().optional(),
|
||||
title: z.string().optional(),
|
||||
}),
|
||||
res: z.object({
|
||||
paths: z.array(z.string()),
|
||||
}),
|
||||
},
|
||||
// Knowledge version history channels
|
||||
'knowledge:history': {
|
||||
req: z.object({ path: RelPath }),
|
||||
|
|
@ -757,6 +1212,16 @@ const ipcSchemas = {
|
|||
mimeType: z.string(),
|
||||
}),
|
||||
},
|
||||
// Ensures the OS-level microphone permission is settled before capturing.
|
||||
// On first-ever use (macOS) the permission is 'not-determined'; resolving
|
||||
// the native prompt up front prevents the in-flight getUserMedia from
|
||||
// rejecting on the first mic click.
|
||||
'voice:ensureMicAccess': {
|
||||
req: z.null(),
|
||||
res: z.object({
|
||||
granted: z.boolean(),
|
||||
}),
|
||||
},
|
||||
'meeting:checkScreenPermission': {
|
||||
req: z.null(),
|
||||
res: z.object({
|
||||
|
|
@ -937,6 +1402,7 @@ const ipcSchemas = {
|
|||
name: z.string(),
|
||||
instructions: z.string(),
|
||||
triggers: TriggersSchema.optional(),
|
||||
projectId: z.string().optional(),
|
||||
model: z.string().optional(),
|
||||
provider: z.string().optional(),
|
||||
}),
|
||||
|
|
@ -1073,6 +1539,17 @@ const ipcSchemas = {
|
|||
req: z.null(),
|
||||
res: BillingInfoSchema,
|
||||
},
|
||||
// Notification settings channels
|
||||
'notifications:getSettings': {
|
||||
req: z.null(),
|
||||
res: NotificationSettingsSchema,
|
||||
},
|
||||
'notifications:setSettings': {
|
||||
req: NotificationSettingsSchema,
|
||||
res: z.object({
|
||||
success: z.literal(true),
|
||||
}),
|
||||
},
|
||||
} as const;
|
||||
|
||||
// ============================================================================
|
||||
|
|
|
|||
|
|
@ -17,10 +17,15 @@ export const LlmModelConfig = z.object({
|
|||
headers: z.record(z.string(), z.string()).optional(),
|
||||
model: z.string().optional(),
|
||||
models: z.array(z.string()).optional(),
|
||||
knowledgeGraphModel: z.string().optional(),
|
||||
meetingNotesModel: z.string().optional(),
|
||||
liveNoteAgentModel: z.string().optional(),
|
||||
autoPermissionDecisionModel: z.string().optional(),
|
||||
})).optional(),
|
||||
// Per-category model overrides (BYOK only — signed-in users always get
|
||||
// the curated gateway defaults). Read by helpers in core/models/defaults.ts.
|
||||
knowledgeGraphModel: z.string().optional(),
|
||||
meetingNotesModel: z.string().optional(),
|
||||
liveNoteAgentModel: z.string().optional(),
|
||||
autoPermissionDecisionModel: z.string().optional(),
|
||||
});
|
||||
|
|
|
|||
36
apps/x/packages/shared/src/notification-settings.ts
Normal file
36
apps/x/packages/shared/src/notification-settings.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Notification categories the user can independently toggle.
|
||||
*
|
||||
* - chat_completion: an agent finished generating a response
|
||||
* - new_email: a new email arrived during incremental Gmail sync
|
||||
* - agent_permission: an agent is requesting permission to run a tool
|
||||
*/
|
||||
export const NotificationCategorySchema = z.enum([
|
||||
'chat_completion',
|
||||
'new_email',
|
||||
'agent_permission',
|
||||
]);
|
||||
|
||||
export const NotificationCategoriesSchema = z.object({
|
||||
chat_completion: z.boolean(),
|
||||
new_email: z.boolean(),
|
||||
agent_permission: z.boolean(),
|
||||
});
|
||||
|
||||
export const NotificationSettingsSchema = z.object({
|
||||
categories: NotificationCategoriesSchema,
|
||||
});
|
||||
|
||||
export const DEFAULT_NOTIFICATION_SETTINGS: NotificationSettings = {
|
||||
categories: {
|
||||
chat_completion: true,
|
||||
new_email: true,
|
||||
agent_permission: true,
|
||||
},
|
||||
};
|
||||
|
||||
export type NotificationCategory = z.infer<typeof NotificationCategorySchema>;
|
||||
export type NotificationCategories = z.infer<typeof NotificationCategoriesSchema>;
|
||||
export type NotificationSettings = z.infer<typeof NotificationSettingsSchema>;
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
import { z } from 'zod';
|
||||
import { BillingCatalogSchema } from './billing.js';
|
||||
|
||||
export const RowboatApiConfig = z.object({
|
||||
appUrl: z.string(),
|
||||
websocketApiUrl: z.string(),
|
||||
supabaseUrl: z.string(),
|
||||
billing: BillingCatalogSchema,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { LlmStepStreamEvent } from "./llm-step-events.js";
|
||||
import { Message, ToolCallPart } from "./message.js";
|
||||
import { CodeRunEvent as CodeRunEventSchema, PermissionAsk } from "./code-mode.js";
|
||||
import z from "zod";
|
||||
|
||||
const BaseRunEvent = z.object({
|
||||
|
|
@ -21,6 +22,7 @@ export const StartEvent = BaseRunEvent.extend({
|
|||
agentName: z.string(),
|
||||
model: z.string(),
|
||||
provider: z.string(),
|
||||
permissionMode: z.enum(["manual", "auto"]).optional(),
|
||||
// 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([
|
||||
|
|
@ -29,6 +31,7 @@ export const StartEvent = BaseRunEvent.extend({
|
|||
"background_task_agent",
|
||||
"meeting_note",
|
||||
"knowledge_sync",
|
||||
"code_session",
|
||||
]).optional(),
|
||||
subUseCase: z.string().optional(),
|
||||
});
|
||||
|
|
@ -110,6 +113,32 @@ export const ToolPermissionResponseEvent = BaseRunEvent.extend({
|
|||
scope: z.enum(["once", "session", "always"]).optional(),
|
||||
});
|
||||
|
||||
// A structured item from a code_agent_run coding turn (tool call, diff, plan,
|
||||
// message chunk, resolved permission). Fire-and-forget — rendered live.
|
||||
export const CodeRunStreamEvent = BaseRunEvent.extend({
|
||||
type: z.literal("code-run-event"),
|
||||
toolCallId: z.string(),
|
||||
event: CodeRunEventSchema,
|
||||
});
|
||||
|
||||
// The coding agent is asking for permission mid-turn and the run is BLOCKED until
|
||||
// the user answers via `codeRun:resolvePermission` (keyed by requestId).
|
||||
export const CodeRunPermissionRequestEvent = BaseRunEvent.extend({
|
||||
type: z.literal("code-run-permission-request"),
|
||||
toolCallId: z.string(),
|
||||
requestId: z.string(),
|
||||
ask: PermissionAsk,
|
||||
});
|
||||
|
||||
export const ToolPermissionAutoDecisionEvent = BaseRunEvent.extend({
|
||||
type: z.literal("tool-permission-auto-decision"),
|
||||
toolCallId: z.string(),
|
||||
toolCall: ToolCallPart,
|
||||
permission: ToolPermissionMetadata.optional(),
|
||||
decision: z.enum(["allow", "deny"]),
|
||||
reason: z.string(),
|
||||
});
|
||||
|
||||
export const RunErrorEvent = BaseRunEvent.extend({
|
||||
type: z.literal("error"),
|
||||
error: z.string(),
|
||||
|
|
@ -134,6 +163,9 @@ export const RunEvent = z.union([
|
|||
AskHumanResponseEvent,
|
||||
ToolPermissionRequestEvent,
|
||||
ToolPermissionResponseEvent,
|
||||
CodeRunStreamEvent,
|
||||
CodeRunPermissionRequestEvent,
|
||||
ToolPermissionAutoDecisionEvent,
|
||||
RunErrorEvent,
|
||||
RunStoppedEvent,
|
||||
]);
|
||||
|
|
@ -157,6 +189,7 @@ export const UseCase = z.enum([
|
|||
"background_task_agent",
|
||||
"meeting_note",
|
||||
"knowledge_sync",
|
||||
"code_session",
|
||||
]);
|
||||
|
||||
export const Run = z.object({
|
||||
|
|
@ -166,6 +199,7 @@ export const Run = z.object({
|
|||
agentId: z.string(),
|
||||
model: z.string(),
|
||||
provider: z.string(),
|
||||
permissionMode: z.enum(["manual", "auto"]).optional(),
|
||||
useCase: UseCase.optional(),
|
||||
subUseCase: z.string().optional(),
|
||||
log: z.array(RunEvent),
|
||||
|
|
@ -177,6 +211,11 @@ export const ListRunsResponse = z.object({
|
|||
title: true,
|
||||
createdAt: true,
|
||||
agentId: true,
|
||||
useCase: true,
|
||||
}).extend({
|
||||
// Last-modified time of the run's log file (mtime), used to order the
|
||||
// chat history by recent activity rather than creation time.
|
||||
modifiedAt: z.iso.datetime(),
|
||||
})),
|
||||
nextCursor: z.string().optional(),
|
||||
});
|
||||
|
|
@ -185,6 +224,7 @@ export const CreateRunOptions = z.object({
|
|||
agentId: z.string(),
|
||||
model: z.string().optional(),
|
||||
provider: z.string().optional(),
|
||||
permissionMode: z.enum(["manual", "auto"]).optional(),
|
||||
useCase: UseCase.optional(),
|
||||
subUseCase: z.string().optional(),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ export const ServiceName = z.enum([
|
|||
'calendar',
|
||||
'fireflies',
|
||||
'granola',
|
||||
'slack',
|
||||
'voice_memo',
|
||||
'email_labeling',
|
||||
'note_tagging',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue