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:
tusharmagar 2026-03-27 17:03:03 +05:30
commit 9e6683984c
118 changed files with 17836 additions and 4842 deletions

View 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: [],
};

View 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>;

View 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 };
}

View file

@ -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 };

View 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>;

View file

@ -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;
// ============================================================================

View file

@ -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(),
});

View file

@ -0,0 +1,7 @@
import { z } from 'zod';
export const RowboatApiConfig = z.object({
appUrl: z.string(),
websocketApiUrl: z.string(),
supabaseUrl: z.string(),
});

View file

@ -7,6 +7,9 @@ export const ServiceName = z.enum([
'fireflies',
'granola',
'voice_memo',
'email_labeling',
'note_tagging',
'agent_notes',
]);
const ServiceEventBase = z.object({