mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-01 03:16:29 +02:00
Merge origin/dev into feature/composio-tools-library
Integrate 58 commits from dev including: - Composio gateway proxy (routes through Rowboat API when signed in) - Gmail/Calendar sync triggers on OAuth callback - Account, Connected Accounts, Note Tagging settings tabs - Rowboat model gateway support - app-navigation and save-to-memory builtin tools - Voice mode, billing, inline tasks, agent notes Resolved conflicts by keeping both sides: - types.ts: z.string().optional() (resilient + optional) - client.ts: updated listToolkitToolsDetailed to use new auth pattern - builtin-tools.ts: kept composio dynamic tool registration imports - instructions.ts: kept both new tools and composio tools prompt - composio-handler.ts: merged all imports from both sides - ipc.ts: kept tools library + useComposioForGoogle handlers - settings-dialog.tsx: kept all new tabs (account, connected-accounts, note-tagging) alongside tools library tab Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
9e6683984c
118 changed files with 17836 additions and 4842 deletions
22
apps/x/packages/shared/src/bases.ts
Normal file
22
apps/x/packages/shared/src/bases.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* Shared types for the Bases view (saved filtered views over the knowledge graph).
|
||||
*/
|
||||
|
||||
export type SortDir = 'asc' | 'desc';
|
||||
|
||||
export interface ActiveFilter {
|
||||
category: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface BaseConfig {
|
||||
filters: ActiveFilter[];
|
||||
columns: string[];
|
||||
sort?: { field: string; dir: SortDir };
|
||||
search?: string;
|
||||
}
|
||||
|
||||
export const DEFAULT_BASE_CONFIG: BaseConfig = {
|
||||
filters: [],
|
||||
columns: [],
|
||||
};
|
||||
76
apps/x/packages/shared/src/blocks.ts
Normal file
76
apps/x/packages/shared/src/blocks.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
export const ImageBlockSchema = z.object({
|
||||
src: z.string(),
|
||||
alt: z.string().optional(),
|
||||
caption: z.string().optional(),
|
||||
});
|
||||
|
||||
export type ImageBlock = z.infer<typeof ImageBlockSchema>;
|
||||
|
||||
export const EmbedBlockSchema = z.object({
|
||||
provider: z.enum(['youtube', 'figma', 'generic']),
|
||||
url: z.string().url(),
|
||||
caption: z.string().optional(),
|
||||
});
|
||||
|
||||
export type EmbedBlock = z.infer<typeof EmbedBlockSchema>;
|
||||
|
||||
export const ChartBlockSchema = z.object({
|
||||
chart: z.enum(['line', 'bar', 'pie']),
|
||||
title: z.string().optional(),
|
||||
data: z.array(z.record(z.string(), z.unknown())).optional(),
|
||||
source: z.string().optional(),
|
||||
x: z.string(),
|
||||
y: z.string(),
|
||||
});
|
||||
|
||||
export type ChartBlock = z.infer<typeof ChartBlockSchema>;
|
||||
|
||||
export const TableBlockSchema = z.object({
|
||||
columns: z.array(z.string()),
|
||||
data: z.array(z.record(z.string(), z.unknown())),
|
||||
title: z.string().optional(),
|
||||
});
|
||||
|
||||
export type TableBlock = z.infer<typeof TableBlockSchema>;
|
||||
|
||||
export const CalendarEventSchema = z.object({
|
||||
summary: z.string().optional(),
|
||||
start: z.object({
|
||||
dateTime: z.string().optional(),
|
||||
date: z.string().optional(),
|
||||
}).optional(),
|
||||
end: z.object({
|
||||
dateTime: z.string().optional(),
|
||||
date: z.string().optional(),
|
||||
}).optional(),
|
||||
location: z.string().optional(),
|
||||
htmlLink: z.string().optional(),
|
||||
conferenceLink: z.string().optional(),
|
||||
source: z.string().optional(),
|
||||
});
|
||||
|
||||
export type CalendarEvent = z.infer<typeof CalendarEventSchema>;
|
||||
|
||||
export const CalendarBlockSchema = z.object({
|
||||
title: z.string().optional(),
|
||||
events: z.array(CalendarEventSchema),
|
||||
showJoinButton: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export type CalendarBlock = z.infer<typeof CalendarBlockSchema>;
|
||||
|
||||
export const EmailBlockSchema = z.object({
|
||||
threadId: z.string().optional(),
|
||||
subject: z.string().optional(),
|
||||
from: z.string().optional(),
|
||||
to: z.string().optional(),
|
||||
date: z.string().optional(),
|
||||
latest_email: z.string(),
|
||||
past_summary: z.string().optional(),
|
||||
draft_response: z.string().optional(),
|
||||
response_mode: z.enum(['inline', 'assistant', 'both']).optional(),
|
||||
});
|
||||
|
||||
export type EmailBlock = z.infer<typeof EmailBlockSchema>;
|
||||
60
apps/x/packages/shared/src/frontmatter.ts
Normal file
60
apps/x/packages/shared/src/frontmatter.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
* Frontmatter parsing utilities for knowledge base markdown files.
|
||||
* Used by core (get-base-state) to scan knowledge files for available properties.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parse a markdown file's YAML frontmatter into key-value pairs.
|
||||
* Handles both scalar values (`key: value`) and list values (`key:\n - item`).
|
||||
* Returns `{ fields, body }` where fields maps keys to string or string[].
|
||||
*/
|
||||
export function parseFrontmatter(content: string): { fields: Record<string, string | string[]>; body: string } {
|
||||
if (!content.startsWith('---')) {
|
||||
return { fields: {}, body: content };
|
||||
}
|
||||
const endIndex = content.indexOf('\n---', 3);
|
||||
if (endIndex === -1) {
|
||||
return { fields: {}, body: content };
|
||||
}
|
||||
|
||||
const rawBlock = content.slice(4, endIndex); // skip opening '---\n'
|
||||
const body = content.slice(endIndex + 4).replace(/^\n/, '');
|
||||
const fields: Record<string, string | string[]> = {};
|
||||
let currentKey: string | null = null;
|
||||
|
||||
for (const line of rawBlock.split('\n')) {
|
||||
if (line.trim() === '' || line === '---') {
|
||||
currentKey = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Top-level key: value
|
||||
const topMatch = line.match(/^(\w[\w\s]*\w|\w+):\s*(.*)$/);
|
||||
if (topMatch) {
|
||||
const key = topMatch[1];
|
||||
const value = topMatch[2].trim();
|
||||
if (value) {
|
||||
fields[key] = value;
|
||||
currentKey = null;
|
||||
} else {
|
||||
// List will follow
|
||||
currentKey = key;
|
||||
fields[key] = [];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// List item under current key
|
||||
if (currentKey) {
|
||||
const itemMatch = line.match(/^\s+-\s+(.+)$/);
|
||||
if (itemMatch) {
|
||||
const arr = fields[currentKey];
|
||||
if (Array.isArray(arr)) {
|
||||
arr.push(itemMatch[1].trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { fields, body };
|
||||
}
|
||||
|
|
@ -6,5 +6,9 @@ export * as workspace from './workspace.js';
|
|||
export * as mcp from './mcp.js';
|
||||
export * as agentSchedule from './agent-schedule.js';
|
||||
export * as agentScheduleState from './agent-schedule-state.js';
|
||||
export * as serviceEvents from './service-events.js';
|
||||
export * as serviceEvents from './service-events.js'
|
||||
export * as inlineTask from './inline-task.js';
|
||||
export * as blocks from './blocks.js';
|
||||
export * as frontmatter from './frontmatter.js';
|
||||
export * as bases from './bases.js';
|
||||
export { PrefixLogger };
|
||||
|
|
|
|||
35
apps/x/packages/shared/src/inline-task.ts
Normal file
35
apps/x/packages/shared/src/inline-task.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
export const InlineTaskScheduleSchema = z.discriminatedUnion('type', [
|
||||
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(),
|
||||
}),
|
||||
]);
|
||||
|
||||
export type InlineTaskSchedule = z.infer<typeof InlineTaskScheduleSchema>;
|
||||
|
||||
export const InlineTaskBlockSchema = z.object({
|
||||
instruction: z.string(),
|
||||
schedule: InlineTaskScheduleSchema.optional(),
|
||||
'schedule-label': z.string().optional(),
|
||||
lastRunAt: z.string().optional(),
|
||||
processing: z.boolean().optional(),
|
||||
targetId: z.string().optional(),
|
||||
});
|
||||
|
||||
export type InlineTaskBlock = z.infer<typeof InlineTaskBlockSchema>;
|
||||
|
|
@ -7,6 +7,7 @@ import { AgentScheduleConfig, AgentScheduleEntry } from './agent-schedule.js';
|
|||
import { AgentScheduleState } from './agent-schedule-state.js';
|
||||
import { ServiceEvent } from './service-events.js';
|
||||
import { UserMessageContent } from './message.js';
|
||||
import { RowboatApiConfig } from './rowboat-account.js';
|
||||
|
||||
// ============================================================================
|
||||
// Runtime Validation Schemas (Single Source of Truth)
|
||||
|
|
@ -130,6 +131,9 @@ const ipcSchemas = {
|
|||
req: z.object({
|
||||
runId: z.string(),
|
||||
message: UserMessageContent,
|
||||
voiceInput: z.boolean().optional(),
|
||||
voiceOutput: z.enum(['summary', 'full']).optional(),
|
||||
searchEnabled: z.boolean().optional(),
|
||||
}),
|
||||
res: z.object({
|
||||
messageId: z.string(),
|
||||
|
|
@ -249,6 +253,14 @@ const ipcSchemas = {
|
|||
})),
|
||||
}),
|
||||
},
|
||||
'account:getRowboat': {
|
||||
req: z.null(),
|
||||
res: z.object({
|
||||
signedIn: z.boolean(),
|
||||
accessToken: z.string().nullable(),
|
||||
config: RowboatApiConfig.nullable(),
|
||||
}),
|
||||
},
|
||||
'oauth:didConnect': {
|
||||
req: z.object({
|
||||
provider: z.string(),
|
||||
|
|
@ -271,6 +283,29 @@ const ipcSchemas = {
|
|||
success: z.literal(true),
|
||||
}),
|
||||
},
|
||||
'slack:getConfig': {
|
||||
req: z.null(),
|
||||
res: z.object({
|
||||
enabled: z.boolean(),
|
||||
workspaces: z.array(z.object({ url: z.string(), name: z.string() })),
|
||||
}),
|
||||
},
|
||||
'slack:setConfig': {
|
||||
req: z.object({
|
||||
enabled: z.boolean(),
|
||||
workspaces: z.array(z.object({ url: z.string(), name: z.string() })),
|
||||
}),
|
||||
res: z.object({
|
||||
success: z.literal(true),
|
||||
}),
|
||||
},
|
||||
'slack:listWorkspaces': {
|
||||
req: z.null(),
|
||||
res: z.object({
|
||||
workspaces: z.array(z.object({ url: z.string(), name: z.string() })),
|
||||
error: z.string().optional(),
|
||||
}),
|
||||
},
|
||||
'onboarding:getStatus': {
|
||||
req: z.null(),
|
||||
res: z.object({
|
||||
|
|
@ -349,9 +384,21 @@ const ipcSchemas = {
|
|||
input: z.record(z.string(), z.unknown()),
|
||||
}),
|
||||
res: z.object({
|
||||
success: z.boolean(),
|
||||
data: z.unknown(),
|
||||
error: z.string().optional(),
|
||||
successful: z.boolean(),
|
||||
error: z.string().nullable(),
|
||||
}),
|
||||
},
|
||||
'composio:use-composio-for-google': {
|
||||
req: z.null(),
|
||||
res: z.object({
|
||||
enabled: z.boolean(),
|
||||
}),
|
||||
},
|
||||
'composio:use-composio-for-google-calendar': {
|
||||
req: z.null(),
|
||||
res: z.object({
|
||||
enabled: z.boolean(),
|
||||
}),
|
||||
},
|
||||
'composio:didConnect': {
|
||||
|
|
@ -377,9 +424,9 @@ const ipcSchemas = {
|
|||
tools_count: z.number(),
|
||||
triggers_count: z.number(),
|
||||
}),
|
||||
no_auth: z.boolean(),
|
||||
auth_schemes: z.array(z.string()),
|
||||
composio_managed_auth_schemes: z.array(z.string()),
|
||||
no_auth: z.boolean().optional(),
|
||||
auth_schemes: z.array(z.string()).optional(),
|
||||
composio_managed_auth_schemes: z.array(z.string()).optional(),
|
||||
})),
|
||||
nextCursor: z.string().nullable(),
|
||||
totalItems: z.number(),
|
||||
|
|
@ -512,6 +559,86 @@ const ipcSchemas = {
|
|||
})),
|
||||
}),
|
||||
},
|
||||
// Voice mode channels
|
||||
'voice:getConfig': {
|
||||
req: z.null(),
|
||||
res: z.object({
|
||||
deepgram: z.object({ apiKey: z.string() }).nullable(),
|
||||
elevenlabs: z.object({ apiKey: z.string(), voiceId: z.string().optional() }).nullable(),
|
||||
}),
|
||||
},
|
||||
'voice:synthesize': {
|
||||
req: z.object({
|
||||
text: z.string(),
|
||||
}),
|
||||
res: z.object({
|
||||
audioBase64: z.string(),
|
||||
mimeType: z.string(),
|
||||
}),
|
||||
},
|
||||
'meeting:summarize': {
|
||||
req: z.object({
|
||||
transcript: z.string(),
|
||||
meetingStartTime: z.string().optional(),
|
||||
calendarEventJson: z.string().optional(),
|
||||
}),
|
||||
res: z.object({
|
||||
notes: z.string(),
|
||||
}),
|
||||
},
|
||||
// Inline task schedule classification
|
||||
'export:note': {
|
||||
req: z.object({
|
||||
markdown: z.string(),
|
||||
format: z.enum(['md', 'pdf', 'docx']),
|
||||
title: z.string(),
|
||||
}),
|
||||
res: z.object({
|
||||
success: z.boolean(),
|
||||
error: z.string().optional(),
|
||||
}),
|
||||
},
|
||||
'inline-task:classifySchedule': {
|
||||
req: z.object({
|
||||
instruction: z.string(),
|
||||
}),
|
||||
res: z.object({
|
||||
schedule: z.union([
|
||||
z.object({ type: z.literal('cron'), expression: z.string(), startDate: z.string(), endDate: z.string(), label: z.string() }),
|
||||
z.object({ type: z.literal('window'), cron: z.string(), startTime: z.string(), endTime: z.string(), startDate: z.string(), endDate: z.string(), label: z.string() }),
|
||||
z.object({ type: z.literal('once'), runAt: z.string(), label: z.string() }),
|
||||
]).nullable(),
|
||||
}),
|
||||
},
|
||||
'inline-task:process': {
|
||||
req: z.object({
|
||||
instruction: z.string(),
|
||||
noteContent: z.string(),
|
||||
notePath: z.string(),
|
||||
}),
|
||||
res: z.object({
|
||||
instruction: z.string(),
|
||||
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(),
|
||||
res: z.object({
|
||||
userEmail: z.string().nullable(),
|
||||
userId: z.string().nullable(),
|
||||
subscriptionPlan: z.string().nullable(),
|
||||
subscriptionStatus: z.string().nullable(),
|
||||
sanctionedCredits: z.number(),
|
||||
availableCredits: z.number(),
|
||||
}),
|
||||
},
|
||||
} as const;
|
||||
|
||||
// ============================================================================
|
||||
|
|
|
|||
|
|
@ -12,4 +12,5 @@ export const LlmModelConfig = z.object({
|
|||
model: z.string(),
|
||||
models: z.array(z.string()).optional(),
|
||||
knowledgeGraphModel: z.string().optional(),
|
||||
meetingNotesModel: z.string().optional(),
|
||||
});
|
||||
|
|
|
|||
7
apps/x/packages/shared/src/rowboat-account.ts
Normal file
7
apps/x/packages/shared/src/rowboat-account.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
export const RowboatApiConfig = z.object({
|
||||
appUrl: z.string(),
|
||||
websocketApiUrl: z.string(),
|
||||
supabaseUrl: z.string(),
|
||||
});
|
||||
|
|
@ -7,6 +7,9 @@ export const ServiceName = z.enum([
|
|||
'fireflies',
|
||||
'granola',
|
||||
'voice_memo',
|
||||
'email_labeling',
|
||||
'note_tagging',
|
||||
'agent_notes',
|
||||
]);
|
||||
|
||||
const ServiceEventBase = z.object({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue