From d5ab12b8c5bc73dd4b04904336ae4fa175c69e15 Mon Sep 17 00:00:00 2001 From: Arjun <6592213+arkml@users.noreply.github.com> Date: Wed, 21 Jan 2026 12:00:06 +0530 Subject: [PATCH 1/3] change graph builder to run every 30 seconds --- apps/x/packages/core/src/knowledge/build_graph.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/x/packages/core/src/knowledge/build_graph.ts b/apps/x/packages/core/src/knowledge/build_graph.ts index 82f8ba88..e5a40d34 100644 --- a/apps/x/packages/core/src/knowledge/build_graph.ts +++ b/apps/x/packages/core/src/knowledge/build_graph.ts @@ -23,7 +23,7 @@ const NOTES_OUTPUT_DIR = path.join(WorkDir, 'knowledge'); const NOTE_CREATION_AGENT = 'note_creation'; // Configuration for the graph builder service -const SYNC_INTERVAL_MS = 5 * 60 * 1000; // Check every 5 minutes (reduced frequency) +const SYNC_INTERVAL_MS = 30 * 1000; // Check every 30 seconds const SOURCE_FOLDERS = [ 'gmail_sync', 'fireflies_transcripts', From 90f0f9d05a461b29574ebf34fa6b3fa45abdc4e2 Mon Sep 17 00:00:00 2001 From: Arjun <6592213+arkml@users.noreply.github.com> Date: Wed, 21 Jan 2026 12:55:27 +0530 Subject: [PATCH 2/3] added collaborate on doc skill --- .../src/application/assistant/instructions.ts | 2 + .../assistant/skills/doc-collab/skill.ts | 232 ++++++++++++++++++ .../src/application/assistant/skills/index.ts | 8 + 3 files changed, 242 insertions(+) create mode 100644 apps/x/packages/core/src/application/assistant/skills/doc-collab/skill.ts diff --git a/apps/x/packages/core/src/application/assistant/instructions.ts b/apps/x/packages/core/src/application/assistant/instructions.ts index 9605d00a..3814064e 100644 --- a/apps/x/packages/core/src/application/assistant/instructions.ts +++ b/apps/x/packages/core/src/application/assistant/instructions.ts @@ -26,6 +26,8 @@ Rowboat is an agentic assistant for everyday work - emails, meetings, projects, **Meeting Prep:** When users ask you to prepare for a meeting, prep for a call, or brief them on attendees, load the \`meeting-prep\` skill first. It provides structured guidance for gathering context about attendees from the knowledge base and creating useful meeting briefs. +**Document Collaboration:** When users ask you to work on a document, collaborate on writing, create a new document, edit/refine existing notes, or say things like "let's work on [X]", "help me write [X]", "create a doc for [X]", or "let's draft [X]", you MUST load the \`doc-collab\` skill first. This is required for any document creation or editing task. The skill provides structured guidance for creating, editing, and refining documents in the knowledge base. + ## Memory That Compounds Unlike other AI assistants that start cold every session, you have access to a live knowledge graph that updates itself from Gmail, calendar, and meeting notes (Google Meet, Granola, Fireflies). This isn't just summaries - it's structured extraction of decisions, commitments, open questions, and context, routed to long-lived notes for each person, project, and topic. diff --git a/apps/x/packages/core/src/application/assistant/skills/doc-collab/skill.ts b/apps/x/packages/core/src/application/assistant/skills/doc-collab/skill.ts new file mode 100644 index 00000000..ba17f545 --- /dev/null +++ b/apps/x/packages/core/src/application/assistant/skills/doc-collab/skill.ts @@ -0,0 +1,232 @@ +export const skill = String.raw` +# Document Collaboration Skill + +You are an expert document assistant helping the user create, edit, and refine documents in their knowledge base. + +## FIRST: Ask About Edit Mode + +**Before doing anything else, ask the user:** +"Should I make edits directly, or show you changes first for approval?" + +- **Direct mode:** Make edits immediately, confirm after +- **Approval mode:** Show proposed changes, wait for approval before editing + +**Strictly follow their choice for the entire session.** Don't switch modes without asking. + +## Core Principles + +**Be concise and direct:** +- Don't be verbose or overly chatty +- Don't propose outlines or structures unless asked +- Don't explain what you're about to do - just do it or ask a simple question + +**Don't assume, ask simply:** +- If something is unclear, ask ONE simple question +- Don't offer multiple options or explain the options +- Don't guess or make assumptions about what the user wants + +**Respect edit mode:** +- In direct mode: make edits immediately, then confirm briefly +- In approval mode: show the exact change you'll make, wait for "yes"/"ok"/"do it" before editing + +**Use knowledge context:** +- When the user mentions people, organizations, or projects, search the knowledge base for context +- Link to relevant notes using [[wiki-link]] syntax +- Pull in relevant facts and history + +## Workflow + +### Step 1: Find the Document + +**IMPORTANT: Always search thoroughly before saying a document doesn't exist.** + +When the user mentions a document name, search for it using multiple approaches: + +1. **Search by name pattern** (handles partial matches, different cases): +\`\`\` +workspace-glob({ pattern: "knowledge/**/*[name]*", path: "knowledge/" }) +\`\`\` + +2. **Search by content** (finds docs that mention the topic): +\`\`\` +workspace-grep({ pattern: "[name]", path: "knowledge/" }) +\`\`\` + +3. **Try common variations:** + - With/without hyphens: "show-hn" vs "showhn" vs "show hn" + - With/without spaces + - Different capitalizations + - In subfolders: knowledge/, knowledge/Projects/, knowledge/Topics/ + +**Only say "document doesn't exist" if ALL searches return nothing.** + +**If found:** Read it and proceed +**If NOT found after thorough search:** Ask "I couldn't find [name]. Shall I create it?" + +**If document is NOT specified:** +- Ask: "Which document would you like to work on?" + +**Creating new documents:** +1. Ask simply: "Shall I create [filename]?" (don't ask about location - default to \`knowledge/\` root) +2. Create it with just a title - don't pre-populate with structure or outlines +3. Ask: "What would you like in this?" + +\`\`\` +workspace-createFile({ + path: "knowledge/[Document Name].md", + content: "# [Document Title]\n\n" +}) +\`\`\` + +**WRONG approach:** +- "Should this be in Projects/ or Topics/?" - don't ask, just use root +- "Here's a proposed outline..." - don't propose, let the user guide +- "I'll create a structure with sections for X, Y, Z" - don't assume structure + +**RIGHT approach:** +- "Shall I create knowledge/roadmap.md?" +- *creates file with just the title* +- "Created. What would you like in this?" + +### Step 2: Understand the Request + +**Types of requests:** + +1. **Direct edits** - "Change the title to X", "Add a bullet point about Y", "Remove the pricing section" + → Make the edit immediately using workspace-editFile + +2. **Content generation** - "Write an intro", "Draft the executive summary", "Add a section about our approach" + → Generate the content and add it to the document + +3. **Review/feedback** - "What do you think?", "Is this clear?", "Any suggestions?" + → Read the document and provide thoughtful feedback + +4. **Research-backed additions** - "Add context about [Person]", "Include what we discussed with [Company]" + → Search knowledge base first, then add relevant context + +### Step 3: Execute Changes + +**For edits, use workspace-editFile:** +\`\`\` +workspace-editFile({ + path: "knowledge/[path].md", + old_string: "[exact text to replace]", + new_string: "[new text]" +}) +\`\`\` + +**For additions at the end:** +\`\`\` +workspace-editFile({ + path: "knowledge/[path].md", + old_string: "[last line or section]", + new_string: "[last line or section]\n\n[new content]" +}) +\`\`\` + +**For new sections:** +Find the right place in the document structure and insert the new section. + +### Step 4: Confirm and Continue + +After making changes: +- Briefly confirm what you did: "Added the executive summary section" +- Ask if they want to continue: "What's next?" or "Anything else to adjust?" +- Don't read back the entire document unless asked + +## Searching Knowledge for Context + +When the user mentions people, companies, or projects: + +**Search for relevant notes:** +\`\`\` +workspace-grep({ pattern: "[Name]", path: "knowledge/" }) +\`\`\` + +**Read relevant notes:** +\`\`\` +workspace-readFile("knowledge/People/[Person].md") +workspace-readFile("knowledge/Organizations/[Company].md") +workspace-readFile("knowledge/Projects/[Project].md") +\`\`\` + +**Use the context:** +- Reference specific facts, dates, and details +- Use [[wiki-links]] to connect to other notes +- Include relevant history and background + +## Document Locations + +Documents are stored in \`~/.rowboat/knowledge/\` with subfolders: +- \`People/\` - Notes about individuals +- \`Organizations/\` - Notes about companies, teams +- \`Projects/\` - Project documentation +- \`Topics/\` - Subject matter notes +- Root level for general documents + +## Best Practices + +**Writing style:** +- Match the user's tone and style in the document +- Be concise but complete +- Use markdown formatting (headers, bullets, bold, etc.) + +**Editing:** +- Make surgical edits - change only what's needed +- Preserve the user's voice and structure +- Don't reorganize unless asked + +**Collaboration:** +- Think of yourself as a writing partner +- Suggest but don't force changes +- Be responsive to feedback + +**Wiki-links:** +- Use \`[[Person Name]]\` to link to people +- Use \`[[Organization Name]]\` to link to companies +- Use \`[[Project Name]]\` to link to projects +- Only link to notes that exist or that you'll create + +## Example Interactions + +**Starting a session:** +**User:** "Let's work on the investor update" +**You:** "Should I make edits directly, or show you changes first?" +**User:** "directly is fine" +**You:** *Search for it, read it* +"Found knowledge/Investor Update Q1.md. What would you like to change?" + +**Direct mode - making edits:** +**User:** "Add a section about our new partnership with Acme Corp" +**You:** *Search knowledge for Acme Corp context, make the edit* +"Added the partnership section. Anything else?" + +**Approval mode - showing changes first:** +**User:** "Add a section about Acme Corp" +**You:** "I'll add this after the Overview section: +\`\`\` +## Partnership with Acme Corp +[content based on knowledge...] +\`\`\` +Ok to add?" +**User:** "yes" +**You:** *Makes the edit* +"Done. What's next?" + +**Creating a new doc:** +**User:** "Create a doc for the roadmap" +**You:** "Shall I create knowledge/roadmap.md?" +**User:** "yes" +**You:** *Creates file with just title* +"Created. What would you like in this?" + +**WRONG examples - don't do this:** +- "Nice, new doc time! Quick clarifier: should this be standalone or in Projects/?" ❌ +- "Here's a proposed outline for the doc..." ❌ +- "I'll assume this is a project-style doc and sketch an initial structure" ❌ +- "In the meantime, let me propose some sections..." ❌ +- Switching from approval mode to direct mode without asking ❌ +- In approval mode: making edits without showing the change first ❌ +`; + +export default skill; diff --git a/apps/x/packages/core/src/application/assistant/skills/index.ts b/apps/x/packages/core/src/application/assistant/skills/index.ts index 69ee89ad..ab2ca83f 100644 --- a/apps/x/packages/core/src/application/assistant/skills/index.ts +++ b/apps/x/packages/core/src/application/assistant/skills/index.ts @@ -2,6 +2,7 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; import builtinToolsSkill from "./builtin-tools/skill.js"; import deletionGuardrailsSkill from "./deletion-guardrails/skill.js"; +import docCollabSkill from "./doc-collab/skill.js"; import draftEmailsSkill from "./draft-emails/skill.js"; import mcpIntegrationSkill from "./mcp-integration/skill.js"; import meetingPrepSkill from "./meeting-prep/skill.js"; @@ -28,6 +29,13 @@ type ResolvedSkill = { }; const definitions: SkillDefinition[] = [ + { + id: "doc-collab", + title: "Document Collaboration", + folder: "doc-collab", + summary: "Collaborate on documents - create, edit, and refine notes and documents in the knowledge base.", + content: docCollabSkill, + }, { id: "draft-emails", title: "Draft Emails", From 9ca143aa463ac16b6602fc875b13855fc22256b9 Mon Sep 17 00:00:00 2001 From: Arjun <6592213+arkml@users.noreply.github.com> Date: Wed, 21 Jan 2026 13:00:31 +0530 Subject: [PATCH 3/3] added prompt suggestions --- apps/x/apps/renderer/src/App.tsx | 25 ++++++ .../components/ai-elements/suggestions.tsx | 76 +++++++++++++++++++ .../renderer/src/components/chat-sidebar.tsx | 11 +++ 3 files changed, 112 insertions(+) create mode 100644 apps/x/apps/renderer/src/components/ai-elements/suggestions.tsx diff --git a/apps/x/apps/renderer/src/App.tsx b/apps/x/apps/renderer/src/App.tsx index fe0089e7..8bb42a42 100644 --- a/apps/x/apps/renderer/src/App.tsx +++ b/apps/x/apps/renderer/src/App.tsx @@ -38,6 +38,7 @@ import { Shimmer } from '@/components/ai-elements/shimmer'; import { Tool, ToolContent, ToolHeader, ToolInput, ToolOutput } from '@/components/ai-elements/tool'; import { PermissionRequest } from '@/components/ai-elements/permission-request'; import { AskHumanRequest } from '@/components/ai-elements/ask-human-request'; +import { Suggestions } from '@/components/ai-elements/suggestions'; import { ToolPermissionRequestEvent, AskHumanRequestEvent } from '@x/shared/src/runs.js'; import { SidebarInset, @@ -278,16 +279,28 @@ const collectFilePaths = (nodes: TreeNode[]): string[] => interface ChatInputInnerProps { onSubmit: (message: PromptInputMessage, mentions?: FileMention[]) => void isProcessing: boolean + presetMessage?: string + onPresetMessageConsumed?: () => void } function ChatInputInner({ onSubmit, isProcessing, + presetMessage, + onPresetMessageConsumed, }: ChatInputInnerProps) { const controller = usePromptInputController() const message = controller.textInput.value const canSubmit = Boolean(message.trim()) && !isProcessing + // Handle preset message from suggestions + useEffect(() => { + if (presetMessage) { + controller.textInput.setInput(presetMessage) + onPresetMessageConsumed?.() + } + }, [presetMessage, controller.textInput, onPresetMessageConsumed]) + const handleSubmit = useCallback(() => { if (!canSubmit) return onSubmit({ text: message.trim(), files: [] }, controller.mentions.mentions) @@ -334,6 +347,8 @@ interface ChatInputWithMentionsProps { visibleFiles: string[] onSubmit: (message: PromptInputMessage, mentions?: FileMention[]) => void isProcessing: boolean + presetMessage?: string + onPresetMessageConsumed?: () => void } function ChatInputWithMentions({ @@ -342,12 +357,16 @@ function ChatInputWithMentions({ visibleFiles, onSubmit, isProcessing, + presetMessage, + onPresetMessageConsumed, }: ChatInputWithMentionsProps) { return ( ) @@ -386,6 +405,7 @@ function App() { const [runId, setRunId] = useState(null) const [isProcessing, setIsProcessing] = useState(false) const [agentId] = useState('copilot') + const [presetMessage, setPresetMessage] = useState(undefined) // Runs history state type RunListItem = { id: string; title?: string; createdAt: string; agentId: string } @@ -1598,12 +1618,17 @@ function App() {
+ {!hasConversation && ( + + )} setPresetMessage(undefined)} />
diff --git a/apps/x/apps/renderer/src/components/ai-elements/suggestions.tsx b/apps/x/apps/renderer/src/components/ai-elements/suggestions.tsx new file mode 100644 index 00000000..cd07ed7a --- /dev/null +++ b/apps/x/apps/renderer/src/components/ai-elements/suggestions.tsx @@ -0,0 +1,76 @@ +import { Mail, Calendar, FolderOpen, FileText } from 'lucide-react' +import { cn } from '@/lib/utils' + +export interface Suggestion { + id: string + label: string + prompt: string + icon: React.ReactNode +} + +const defaultSuggestions: Suggestion[] = [ + { + id: 'email-draft', + label: 'Draft an email', + prompt: "Let's draft an email response to [name]", + icon: , + }, + { + id: 'meeting-prep', + label: 'Prep for a meeting', + prompt: 'Help me prep for my next meeting with [name]', + icon: , + }, + { + id: 'doc-collab', + label: 'Work on a document', + prompt: "Let's work on [document name]", + icon: , + }, + { + id: 'organize-files', + label: 'Organize files', + prompt: 'Help me organize [folder or files]', + icon: , + }, +] + +interface SuggestionsProps { + suggestions?: Suggestion[] + onSelect: (prompt: string) => void + className?: string + vertical?: boolean +} + +export function Suggestions({ + suggestions = defaultSuggestions, + onSelect, + className, + vertical = false, +}: SuggestionsProps) { + return ( +
+ {suggestions.map((suggestion) => ( + + ))} +
+ ) +} diff --git a/apps/x/apps/renderer/src/components/chat-sidebar.tsx b/apps/x/apps/renderer/src/components/chat-sidebar.tsx index 5c5f7713..0d9a7d7e 100644 --- a/apps/x/apps/renderer/src/components/chat-sidebar.tsx +++ b/apps/x/apps/renderer/src/components/chat-sidebar.tsx @@ -23,6 +23,7 @@ import { Shimmer } from '@/components/ai-elements/shimmer' import { Tool, ToolContent, ToolHeader, ToolInput, ToolOutput } from '@/components/ai-elements/tool' import { PermissionRequest } from '@/components/ai-elements/permission-request' import { AskHumanRequest } from '@/components/ai-elements/ask-human-request' +import { Suggestions } from '@/components/ai-elements/suggestions' import { type PromptInputMessage, type FileMention } from '@/components/ai-elements/prompt-input' import { useMentionDetection } from '@/hooks/use-mention-detection' import { MentionPopover } from '@/components/mention-popover' @@ -544,6 +545,16 @@ export function ChatSidebar({ {/* Input area - responsive to sidebar width, matches floating bar position exactly */}
+ {!hasConversation && ( + { + onMessageChange(prompt) + setTimeout(() => textareaRef.current?.focus(), 0) + }} + vertical + className="mb-3" + /> + )}
{mentionHighlights.hasHighlights && (