diff --git a/apps/x/apps/renderer/src/components/sidebar-content.tsx b/apps/x/apps/renderer/src/components/sidebar-content.tsx index 0508f34d..af0d1896 100644 --- a/apps/x/apps/renderer/src/components/sidebar-content.tsx +++ b/apps/x/apps/renderer/src/components/sidebar-content.tsx @@ -183,6 +183,7 @@ function VoiceNoteButton({ onNoteCreated }: { onNoteCreated?: (path: string) => const chunksRef = React.useRef([]) const notePathRef = React.useRef(null) const timestampRef = React.useRef(null) + const relativePathRef = React.useRef(null) const startRecording = async () => { try { @@ -195,6 +196,9 @@ function VoiceNoteButton({ onNoteCreated }: { onNoteCreated?: (path: string) => timestampRef.current = timestamp notePathRef.current = notePath + // Relative path for linking (from knowledge/ root, without .md extension) + const relativePath = `Voice Memos/${dateStr}/${noteName}` + relativePathRef.current = relativePath // Create the note immediately with a "Recording..." placeholder await window.ipc.invoke('workspace:mkdir', { @@ -206,6 +210,7 @@ function VoiceNoteButton({ onNoteCreated }: { onNoteCreated?: (path: string) => **Type:** voice memo **Recorded:** ${now.toLocaleString()} +**Path:** ${relativePath} ## Transcript @@ -264,11 +269,13 @@ function VoiceNoteButton({ onNoteCreated }: { onNoteCreated?: (path: string) => // Update note to show transcribing status const currentNotePath = notePathRef.current - if (currentNotePath) { + const currentRelativePath = relativePathRef.current + if (currentNotePath && currentRelativePath) { const transcribingContent = `# Voice Memo **Type:** voice memo **Recorded:** ${new Date().toLocaleString()} +**Path:** ${currentRelativePath} ## Transcript @@ -283,12 +290,13 @@ function VoiceNoteButton({ onNoteCreated }: { onNoteCreated?: (path: string) => // Transcribe and update the note with the transcript const transcript = await transcribeWithDeepgram(blob) - if (currentNotePath) { + if (currentNotePath && currentRelativePath) { const finalContent = transcript ? `# Voice Memo **Type:** voice memo **Recorded:** ${new Date().toLocaleString()} +**Path:** ${currentRelativePath} ## Transcript @@ -298,6 +306,7 @@ ${transcript} **Type:** voice memo **Recorded:** ${new Date().toLocaleString()} +**Path:** ${currentRelativePath} ## Transcript diff --git a/apps/x/packages/core/src/knowledge/build_graph.ts b/apps/x/packages/core/src/knowledge/build_graph.ts index 05c23989..4f3283de 100644 --- a/apps/x/packages/core/src/knowledge/build_graph.ts +++ b/apps/x/packages/core/src/knowledge/build_graph.ts @@ -30,90 +30,87 @@ const SOURCE_FOLDERS = [ 'granola_notes', ]; -// Voice memos are handled separately - they get moved to knowledge/Voice Memos// -const VOICE_MEMOS_DIR = path.join(WorkDir, 'voice_memos'); +// Voice memos are now created directly in knowledge/Voice Memos// const VOICE_MEMOS_KNOWLEDGE_DIR = path.join(NOTES_OUTPUT_DIR, 'Voice Memos'); /** - * Extract date from voice memo filename - * Filename format: voice-memo-2026-02-03T04-56-53-182Z.txt - * Returns date string like "2026-02-03" + * Get unprocessed voice memo files from knowledge/Voice Memos/ + * Voice memos are created directly in this directory by the UI. + * Returns paths to files that need entity extraction. */ -function extractDateFromVoiceMemoFilename(filename: string): string { - // Match the date pattern: YYYY-MM-DD from voice-memo-YYYY-MM-DDTHH-MM-SS-MMMZ - const match = filename.match(/voice-memo-(\d{4}-\d{2}-\d{2})T/); - if (match) { - return match[1]; - } - // Fallback to current date if pattern doesn't match - return new Date().toISOString().split('T')[0]; -} +function getUnprocessedVoiceMemos(state: GraphState): string[] { + console.log(`[VoiceMemos] Checking directory: ${VOICE_MEMOS_KNOWLEDGE_DIR}`); -/** - * Move voice memo transcripts to knowledge/Voice Memos// - * Returns info about moved files for processing. - */ -function moveVoiceMemosToKnowledge(state: GraphState): { sourcePath: string; targetPath: string }[] { - if (!fs.existsSync(VOICE_MEMOS_DIR)) { + if (!fs.existsSync(VOICE_MEMOS_KNOWLEDGE_DIR)) { + console.log(`[VoiceMemos] Directory does not exist`); return []; } - const movedFiles: { sourcePath: string; targetPath: string }[] = []; - const entries = fs.readdirSync(VOICE_MEMOS_DIR); + const unprocessedFiles: string[] = []; - for (const entry of entries) { - // Only process .txt transcript files - if (!entry.endsWith('.txt')) { + // Scan date folders (e.g., 2026-02-03) + const dateFolders = fs.readdirSync(VOICE_MEMOS_KNOWLEDGE_DIR); + console.log(`[VoiceMemos] Found ${dateFolders.length} date folders: ${dateFolders.join(', ')}`); + + for (const dateFolder of dateFolders) { + const dateFolderPath = path.join(VOICE_MEMOS_KNOWLEDGE_DIR, dateFolder); + + // Skip if not a directory + try { + if (!fs.statSync(dateFolderPath).isDirectory()) { + continue; + } + } catch (err) { + console.log(`[VoiceMemos] Error checking ${dateFolderPath}:`, err); continue; } - const sourcePath = path.join(VOICE_MEMOS_DIR, entry); + // Scan markdown files in this date folder + const files = fs.readdirSync(dateFolderPath); + console.log(`[VoiceMemos] Found ${files.length} files in ${dateFolder}: ${files.join(', ')}`); - // Skip if already processed (check by source path) - if (state.processedFiles[sourcePath]) { - continue; + for (const file of files) { + // Only process voice memo markdown files + if (!file.endsWith('.md') || !file.startsWith('voice-memo-')) { + console.log(`[VoiceMemos] Skipping ${file} - not a voice memo file`); + continue; + } + + const filePath = path.join(dateFolderPath, file); + + // Skip if already processed + if (state.processedFiles[filePath]) { + console.log(`[VoiceMemos] Skipping ${file} - already processed`); + continue; + } + + // Check if the file has actual content (not still recording/transcribing) + try { + const content = fs.readFileSync(filePath, 'utf-8'); + // Skip files that are still recording or transcribing + if (content.includes('*Recording in progress...*')) { + console.log(`[VoiceMemos] Skipping ${file} - still recording`); + continue; + } + if (content.includes('*Transcribing...*')) { + console.log(`[VoiceMemos] Skipping ${file} - still transcribing`); + continue; + } + if (content.includes('*Transcription failed')) { + console.log(`[VoiceMemos] Skipping ${file} - transcription failed`); + continue; + } + console.log(`[VoiceMemos] Found unprocessed voice memo: ${file}`); + unprocessedFiles.push(filePath); + } catch (err) { + console.log(`[VoiceMemos] Error reading ${file}:`, err); + continue; + } } - - // Extract date and create target directory - const date = extractDateFromVoiceMemoFilename(entry); - const targetDir = path.join(VOICE_MEMOS_KNOWLEDGE_DIR, date); - - if (!fs.existsSync(targetDir)) { - fs.mkdirSync(targetDir, { recursive: true }); - } - - // Convert .txt to .md and create a proper markdown file - const baseName = entry.replace('.txt', ''); - const targetPath = path.join(targetDir, `${baseName}.md`); - - // Read original content and wrap in markdown format - const originalContent = fs.readFileSync(sourcePath, 'utf-8'); - - // Create a relative path for linking (from knowledge/ root) - const relativePath = `Voice Memos/${date}/${baseName}`; - - const mdContent = `# Voice Memo - ${date} - -**Type:** voice memo -**Recorded:** ${date} -**Path:** ${relativePath} - -## Transcript - -${originalContent} -`; - - // Write to knowledge directory - fs.writeFileSync(targetPath, mdContent); - console.log(`[VoiceMemos] Moved transcript to ${targetPath}`); - - // Delete the original .txt file (it's been moved) - fs.unlinkSync(sourcePath); - - movedFiles.push({ sourcePath, targetPath }); } - return movedFiles; + console.log(`[VoiceMemos] Total unprocessed files: ${unprocessedFiles.length}`); + return unprocessedFiles; } /** @@ -272,23 +269,26 @@ export async function buildGraph(sourceDir: string): Promise { } /** - * Process voice memos and run entity extraction on them + * Process voice memos from knowledge/Voice Memos/ and run entity extraction on them + * Voice memos are now created directly in the knowledge directory by the UI. */ async function processVoiceMemosForKnowledge(): Promise { + console.log(`[VoiceMemos] Starting voice memo processing...`); const state = loadState(); - // Move voice memos to knowledge directory - const movedFiles = moveVoiceMemosToKnowledge(state); + // Get unprocessed voice memos from knowledge/Voice Memos/ + const unprocessedFiles = getUnprocessedVoiceMemos(state); - if (movedFiles.length === 0) { + if (unprocessedFiles.length === 0) { + console.log(`[VoiceMemos] No unprocessed voice memos found`); return false; } - console.log(`[VoiceMemos] Processing ${movedFiles.length} voice memo transcripts for entity extraction...`); + console.log(`[VoiceMemos] Processing ${unprocessedFiles.length} voice memo transcripts for entity extraction...`); + console.log(`[VoiceMemos] Files to process: ${unprocessedFiles.map(f => path.basename(f)).join(', ')}`); - // Read the moved files - const targetPaths = movedFiles.map(f => f.targetPath); - const contentFiles = await readFileContents(targetPaths); + // Read the files + const contentFiles = await readFileContents(unprocessedFiles); if (contentFiles.length === 0) { return false; @@ -300,7 +300,6 @@ async function processVoiceMemosForKnowledge(): Promise { for (let i = 0; i < contentFiles.length; i += BATCH_SIZE) { const batch = contentFiles.slice(i, i + BATCH_SIZE); - const batchMovedFiles = movedFiles.slice(i, i + BATCH_SIZE); const batchNumber = Math.floor(i / BATCH_SIZE) + 1; try { @@ -313,11 +312,9 @@ async function processVoiceMemosForKnowledge(): Promise { await createNotesFromBatch(batch, batchNumber, indexForPrompt); console.log(`[VoiceMemos] Batch ${batchNumber}/${totalBatches} complete`); - // Mark files as processed using SOURCE path as key (to prevent reprocessing) - for (const { sourcePath, targetPath } of batchMovedFiles) { - markFileAsProcessed(targetPath, state); - // Also track by source path so we don't reprocess if a new file with same name appears - state.processedFiles[sourcePath] = state.processedFiles[targetPath]; + // Mark files as processed + for (const file of batch) { + markFileAsProcessed(file.path, state); } // Save state after each batch @@ -392,7 +389,7 @@ async function processAllSources(): Promise { */ export async function init() { console.log('[GraphBuilder] Starting Knowledge Graph Builder Service...'); - console.log(`[GraphBuilder] Monitoring folders: ${SOURCE_FOLDERS.join(', ')}, voice_memos`); + console.log(`[GraphBuilder] Monitoring folders: ${SOURCE_FOLDERS.join(', ')}, knowledge/Voice Memos`); console.log(`[GraphBuilder] Will check for new content every ${SYNC_INTERVAL_MS / 1000} seconds`); // Initial run diff --git a/apps/x/packages/core/src/knowledge/note_creation_high.ts b/apps/x/packages/core/src/knowledge/note_creation_high.ts index 2e8c6a65..ce15c324 100644 --- a/apps/x/packages/core/src/knowledge/note_creation_high.ts +++ b/apps/x/packages/core/src/knowledge/note_creation_high.ts @@ -7,15 +7,21 @@ tools: workspace-readFile: type: builtin name: workspace-readFile + workspace-edit: + type: builtin + name: workspace-edit workspace-readdir: type: builtin name: workspace-readdir workspace-mkdir: type: builtin name: workspace-mkdir - executeCommand: + workspace-grep: type: builtin - name: executeCommand + name: workspace-grep + workspace-glob: + type: builtin + name: workspace-glob --- # Task @@ -70,20 +76,51 @@ When you need to: # Tools Available -You have access to \`executeCommand\` to run shell commands: +You have access to these tools: + +**For reading files:** \`\`\` -executeCommand("ls {path}") # List directory contents -executeCommand("cat {path}") # Read file contents -executeCommand("head -50 {path}") # Read first 50 lines -executeCommand("write {path} {content}") # Create or overwrite file +workspace-readFile({ path: "knowledge/People/Sarah Chen.md" }) \`\`\` -**Important:** Use shell escaping for paths with spaces: +**For creating NEW files:** \`\`\` -executeCommand("cat 'knowledge_folder/People/Sarah Chen.md'") +workspace-writeFile({ path: "knowledge/People/Sarah Chen.md", data: "# Sarah Chen\\n\\n..." }) \`\`\` -**NOTE:** Do NOT use grep to search for entities. Use the provided knowledge_index instead. +**For editing EXISTING files (preferred for updates):** +\`\`\` +workspace-edit({ + path: "knowledge/People/Sarah Chen.md", + oldString: "## Activity\\n", + newString: "## Activity\\n- **2026-02-03** (meeting): New activity entry\\n" +}) +\`\`\` + +**For listing directories:** +\`\`\` +workspace-readdir({ path: "knowledge/People" }) +\`\`\` + +**For creating directories:** +\`\`\` +workspace-mkdir({ path: "knowledge/Projects", recursive: true }) +\`\`\` + +**For searching files:** +\`\`\` +workspace-grep({ pattern: "Acme Corp", searchPath: "knowledge", fileGlob: "*.md" }) +\`\`\` + +**For finding files by pattern:** +\`\`\` +workspace-glob({ pattern: "**/*.md", cwd: "knowledge/People" }) +\`\`\` + +**IMPORTANT:** +- Use \`workspace-edit\` for updating existing notes (adding activity, updating fields) +- Use \`workspace-writeFile\` only for creating new notes +- Prefer the knowledge_index for entity resolution (it's faster than grep) # Output @@ -113,7 +150,7 @@ Either: Read the source file and determine if it's a meeting or email. \`\`\` -executeCommand("cat '{source_file}'") +workspace-readFile({ path: "{source_file}" }) \`\`\` **Meeting indicators:** @@ -307,9 +344,9 @@ If someone only appears in your memory as "CC'd on outreach emails from [Sender] ## Email-Specific Filtering For emails, check if sender/recipients have existing notes: -\`\`\`bash -executeCommand("grep -r -i -l '{sender email}' '{knowledge_folder}/'") -executeCommand("grep -r -i -l '{sender name}' '{knowledge_folder}/People/'") +\`\`\` +workspace-grep({ pattern: "{sender email}", searchPath: "{knowledge_folder}" }) +workspace-grep({ pattern: "{sender name}", searchPath: "{knowledge_folder}/People" }) \`\`\` **If no existing note found:** @@ -349,7 +386,7 @@ If processing, continue to Step 2. # Step 2: Read and Parse Source File \`\`\` -executeCommand("cat '{source_file}'") +workspace-readFile({ path: "{source_file}" }) \`\`\` Extract metadata: @@ -446,7 +483,7 @@ From index, find matches for: Only read the full note content when you need details not in the index (e.g., activity logs, open items): \`\`\`bash -executeCommand("cat '{knowledge_folder}/People/Sarah Chen.md'") +workspace-readFile({ path: "{knowledge_folder}/People/Sarah Chen.md" }) \`\`\` **Why read these notes:** @@ -530,27 +567,27 @@ Resolution Map: When multiple candidates match a variant, disambiguate: **By organization (strongest signal):** -\`\`\`bash +\`\`\` # "David" could be David Kim or David Chen -executeCommand("grep -i 'Acme' '{knowledge_folder}/People/David Kim.md'") +workspace-grep({ pattern: "Acme", searchPath: "{knowledge_folder}/People/David Kim.md" }) # Output: **Organization:** [[Acme Corp]] -executeCommand("grep -i 'Acme' '{knowledge_folder}/People/David Chen.md'") +workspace-grep({ pattern: "Acme", searchPath: "{knowledge_folder}/People/David Chen.md" }) # Output: **Organization:** [[Other Corp]] # Source is from Acme context → "David" = "David Kim" \`\`\` **By email (definitive):** -\`\`\`bash -executeCommand("grep -i 'david@acme.com' '{knowledge_folder}/People/David Kim.md'") +\`\`\` +workspace-grep({ pattern: "david@acme.com", searchPath: "{knowledge_folder}/People/David Kim.md" }) # Exact email match is definitive \`\`\` **By role:** -\`\`\`bash +\`\`\` # Source mentions "their CTO" -executeCommand("grep -r -i 'Role.*CTO' '{knowledge_folder}/People/'") +workspace-grep({ pattern: "Role.*CTO", searchPath: "{knowledge_folder}/People" }) # Filter results by organization context \`\`\` @@ -979,8 +1016,8 @@ STATE CHANGES: Before writing, compare extracted content against existing notes. ## Check Activity Log -\`\`\`bash -executeCommand("grep '2025-01-15' '{knowledge_folder}/People/Sarah Chen.md'") +\`\`\` +workspace-grep({ pattern: "2025-01-15", searchPath: "{knowledge_folder}/People/Sarah Chen.md" }) \`\`\` If an entry for this date/source already exists, this may have been processed. Skip or verify different interaction. @@ -1010,28 +1047,28 @@ If new info contradicts existing: **IMPORTANT: Write sequentially, one file at a time.** - Generate content for exactly one note. -- Issue exactly one \`write\` command. +- Issue exactly one write/edit command. - Wait for the tool to return before generating the next note. -- Do NOT batch multiple \`write\` commands in a single response. +- Do NOT batch multiple write commands in a single response. -**For new entities (meetings only):** -\`\`\`bash -executeCommand("write '{knowledge_folder}/People/Jennifer.md' '{content}'") +**For NEW entities (use workspace-writeFile):** +\`\`\` +workspace-writeFile({ + path: "{knowledge_folder}/People/Jennifer.md", + data: "# Jennifer\\n\\n## Summary\\n..." +}) \`\`\` -**For existing entities:** -- Read current content first -- Add activity entry at TOP of Activity section (reverse chronological) -- Update "Last seen" date -- Add new key facts (skip duplicates) -- Add new open items -- Add new decisions -- Add new relationships -- Update summary ONLY if significant new understanding -\`\`\`bash -executeCommand("cat '{knowledge_folder}/People/Sarah Chen.md'") -# ... modify content ... -executeCommand("write '{knowledge_folder}/People/Sarah Chen.md' '{full_updated_content}'") +**For EXISTING entities (use workspace-edit):** +- Read current content first with workspace-readFile +- Use workspace-edit to add activity entry at TOP (reverse chronological) +- Update fields using targeted edits +\`\`\` +workspace-edit({ + path: "{knowledge_folder}/People/Sarah Chen.md", + oldString: "## Activity\\n", + newString: "## Activity\\n- **2026-02-03** (meeting): Met to discuss project timeline\\n" +}) \`\`\` ## 9b: Emails — Update Existing Notes Only @@ -1054,7 +1091,7 @@ For each state change identified in Step 7: ### Update Project Status \`\`\`bash # Read current project note -executeCommand("cat '{knowledge_folder}/Projects/Acme Integration.md'") +workspace-readFile({ path: "{knowledge_folder}/Projects/Acme Integration.md" }) # Update the Status field # Change: **Status:** planning @@ -1064,7 +1101,7 @@ executeCommand("cat '{knowledge_folder}/Projects/Acme Integration.md'") ### Mark Open Items Complete \`\`\`bash # Read current note -executeCommand("cat '{knowledge_folder}/People/Sarah Chen.md'") +workspace-readFile({ path: "{knowledge_folder}/People/Sarah Chen.md" }) # Find matching open item and update # Change: - [ ] Send API documentation — by Friday @@ -1074,7 +1111,7 @@ executeCommand("cat '{knowledge_folder}/People/Sarah Chen.md'") ### Update Role \`\`\`bash # Read current person note -executeCommand("cat '{knowledge_folder}/People/Sarah Chen.md'") +workspace-readFile({ path: "{knowledge_folder}/People/Sarah Chen.md" }) # Update role field # Change: **Role:** Engineering Lead @@ -1084,7 +1121,7 @@ executeCommand("cat '{knowledge_folder}/People/Sarah Chen.md'") ### Update Relationship \`\`\`bash # Read current org note -executeCommand("cat '{knowledge_folder}/Organizations/Acme Corp.md'") +workspace-readFile({ path: "{knowledge_folder}/Organizations/Acme Corp.md" }) # Update relationship field # Change: **Relationship:** prospect @@ -1149,8 +1186,8 @@ This ensures: ## Check Each New Link If you added \`[[People/Jennifer]]\` to \`Organizations/Acme Corp.md\`: -\`\`\`bash -executeCommand("grep 'Acme Corp' '{knowledge_folder}/People/Jennifer.md'") +\`\`\` +workspace-grep({ pattern: "Acme Corp", searchPath: "{knowledge_folder}/People/Jennifer.md" }) \`\`\` If not found, update Jennifer.md to add the link. @@ -1384,11 +1421,11 @@ Not mass email, not automated. Continue. - Variants: "Sarah Chen", "sarah@acme.com", "David Kim", "David", "Jennifer", "CTO", "Acme", "the pilot" ### Step 3: Search Existing Notes -\`\`\`bash -executeCommand("grep -r -i -l 'Sarah Chen' 'knowledge/'") +\`\`\` +workspace-grep({ pattern: "Sarah Chen", searchPath: "knowledge" }) # Output: (none) -executeCommand("grep -r -i -l 'acme' 'knowledge/'") +workspace-grep({ pattern: "acme", searchPath: "knowledge" }) # Output: (none) \`\`\` @@ -1527,8 +1564,8 @@ VP Engineering, Acme Corp ### Step 1: Filter Check for existing relationship: -\`\`\`bash -executeCommand("grep -r -i -l 'sarah@acme.com' 'knowledge/'") +\`\`\` +workspace-grep({ pattern: "sarah@acme.com", searchPath: "knowledge" }) # Output: notes/People/Sarah Chen.md \`\`\` @@ -1663,11 +1700,11 @@ John Smith ### Step 1: Filter Check for existing relationship: -\`\`\`bash -executeCommand("grep -r -i -l 'randomvendor' 'knowledge/'") +\`\`\` +workspace-grep({ pattern: "randomvendor", searchPath: "knowledge" }) # Output: (none) -executeCommand("grep -r -i -l 'John Smith' 'knowledge/'") +workspace-grep({ pattern: "John Smith", searchPath: "knowledge" }) # Output: (none) \`\`\` @@ -1710,8 +1747,8 @@ David ### Step 1: Filter Check for sender: -\`\`\`bash -executeCommand("grep -r -i -l 'david@friendly.vc' 'knowledge/'") +\`\`\` +workspace-grep({ pattern: "david@friendly.vc", searchPath: "knowledge" }) # Output: notes/People/David Park.md \`\`\` diff --git a/apps/x/packages/core/src/knowledge/note_creation_low.ts b/apps/x/packages/core/src/knowledge/note_creation_low.ts index 26d32bca..29922fce 100644 --- a/apps/x/packages/core/src/knowledge/note_creation_low.ts +++ b/apps/x/packages/core/src/knowledge/note_creation_low.ts @@ -7,15 +7,21 @@ tools: workspace-readFile: type: builtin name: workspace-readFile + workspace-edit: + type: builtin + name: workspace-edit workspace-readdir: type: builtin name: workspace-readdir workspace-mkdir: type: builtin name: workspace-mkdir - executeCommand: + workspace-grep: type: builtin - name: executeCommand + name: workspace-grep + workspace-glob: + type: builtin + name: workspace-glob --- # Task @@ -70,20 +76,51 @@ When you need to: # Tools Available -You have access to \`executeCommand\` to run shell commands: +You have access to these tools: + +**For reading files:** \`\`\` -executeCommand("ls {path}") # List directory contents -executeCommand("cat {path}") # Read file contents -executeCommand("head -50 {path}") # Read first 50 lines -executeCommand("write {path} {content}") # Create or overwrite file +workspace-readFile({ path: "knowledge/People/Sarah Chen.md" }) \`\`\` -**Important:** Use shell escaping for paths with spaces: +**For creating NEW files:** \`\`\` -executeCommand("cat 'knowledge_folder/People/Sarah Chen.md'") +workspace-writeFile({ path: "knowledge/People/Sarah Chen.md", data: "# Sarah Chen\\n\\n..." }) \`\`\` -**NOTE:** Do NOT use grep to search for entities. Use the provided knowledge_index instead. +**For editing EXISTING files (preferred for updates):** +\`\`\` +workspace-edit({ + path: "knowledge/People/Sarah Chen.md", + oldString: "## Activity\\n", + newString: "## Activity\\n- **2026-02-03** (meeting): New activity entry\\n" +}) +\`\`\` + +**For listing directories:** +\`\`\` +workspace-readdir({ path: "knowledge/People" }) +\`\`\` + +**For creating directories:** +\`\`\` +workspace-mkdir({ path: "knowledge/Projects", recursive: true }) +\`\`\` + +**For searching files:** +\`\`\` +workspace-grep({ pattern: "Acme Corp", searchPath: "knowledge", fileGlob: "*.md" }) +\`\`\` + +**For finding files by pattern:** +\`\`\` +workspace-glob({ pattern: "**/*.md", cwd: "knowledge/People" }) +\`\`\` + +**IMPORTANT:** +- Use \`workspace-edit\` for updating existing notes (adding activity, updating fields) +- Use \`workspace-writeFile\` only for creating new notes +- Prefer the knowledge_index for entity resolution (it's faster than grep) # Output @@ -120,7 +157,7 @@ This mode prioritizes comprehensive capture over selectivity. The goal is to nev Read the source file and determine if it's a meeting or email. \`\`\` -executeCommand("cat '{source_file}'") +workspace-readFile({ path: "{source_file}" }) \`\`\` **Meeting indicators:** @@ -210,7 +247,7 @@ If processing, continue to Step 2. # Step 2: Read and Parse Source File \`\`\` -executeCommand("cat '{source_file}'") +workspace-readFile({ path: "{source_file}" }) \`\`\` Extract metadata: @@ -297,8 +334,8 @@ From index, find matches for: ## 3d: Read Full Notes When Needed Only read the full note content when you need details not in the index (e.g., activity logs, open items): -\`\`\`bash -executeCommand("cat '{knowledge_folder}/People/Sarah Chen.md'") +\`\`\` +workspace-readFile({ path: "{knowledge_folder}/People/Sarah Chen.md" }) \`\`\` **Why read these notes:** @@ -566,21 +603,29 @@ Before writing: **IMPORTANT: Write sequentially, one file at a time.** - Generate content for exactly one note. -- Issue exactly one \`write\` command. +- Issue exactly one write/edit command. - Wait for the tool to return before generating the next note. -- Do NOT batch multiple \`write\` commands in a single response. +- Do NOT batch multiple write commands in a single response. -**For new entities:** -\`\`\`bash -executeCommand("write '{knowledge_folder}/People/Jennifer.md' '{content}'") +**For NEW entities (use workspace-writeFile):** +\`\`\` +workspace-writeFile({ + path: "{knowledge_folder}/People/Jennifer.md", + data: "# Jennifer\\n\\n## Summary\\n..." +}) \`\`\` -**For existing entities:** -- Read current content first -- Add activity entry at TOP (reverse chronological) -- Update "Last seen" date -- Add new key facts (skip duplicates) -- Add new open items +**For EXISTING entities (use workspace-edit):** +- Read current content first with workspace-readFile +- Use workspace-edit to add activity entry at TOP (reverse chronological) +- Update fields using targeted edits +\`\`\` +workspace-edit({ + path: "{knowledge_folder}/People/Sarah Chen.md", + oldString: "## Activity\\n", + newString: "## Activity\\n- **2026-02-03** (meeting): Met to discuss project timeline\\n" +}) +\`\`\` ## 9b: Apply State Changes diff --git a/apps/x/packages/core/src/knowledge/note_creation_medium.ts b/apps/x/packages/core/src/knowledge/note_creation_medium.ts index 40336c42..434078c4 100644 --- a/apps/x/packages/core/src/knowledge/note_creation_medium.ts +++ b/apps/x/packages/core/src/knowledge/note_creation_medium.ts @@ -7,15 +7,21 @@ tools: workspace-readFile: type: builtin name: workspace-readFile + workspace-edit: + type: builtin + name: workspace-edit workspace-readdir: type: builtin name: workspace-readdir workspace-mkdir: type: builtin name: workspace-mkdir - executeCommand: + workspace-grep: type: builtin - name: executeCommand + name: workspace-grep + workspace-glob: + type: builtin + name: workspace-glob --- # Task @@ -70,20 +76,51 @@ When you need to: # Tools Available -You have access to \`executeCommand\` to run shell commands: +You have access to these tools: + +**For reading files:** \`\`\` -executeCommand("ls {path}") # List directory contents -executeCommand("cat {path}") # Read file contents -executeCommand("head -50 {path}") # Read first 50 lines -executeCommand("write {path} {content}") # Create or overwrite file +workspace-readFile({ path: "knowledge/People/Sarah Chen.md" }) \`\`\` -**Important:** Use shell escaping for paths with spaces: +**For creating NEW files:** \`\`\` -executeCommand("cat 'knowledge_folder/People/Sarah Chen.md'") +workspace-writeFile({ path: "knowledge/People/Sarah Chen.md", data: "# Sarah Chen\\n\\n..." }) \`\`\` -**NOTE:** Do NOT use grep to search for entities. Use the provided knowledge_index instead. +**For editing EXISTING files (preferred for updates):** +\`\`\` +workspace-edit({ + path: "knowledge/People/Sarah Chen.md", + oldString: "## Activity\\n", + newString: "## Activity\\n- **2026-02-03** (meeting): New activity entry\\n" +}) +\`\`\` + +**For listing directories:** +\`\`\` +workspace-readdir({ path: "knowledge/People" }) +\`\`\` + +**For creating directories:** +\`\`\` +workspace-mkdir({ path: "knowledge/Projects", recursive: true }) +\`\`\` + +**For searching files:** +\`\`\` +workspace-grep({ pattern: "Acme Corp", searchPath: "knowledge", fileGlob: "*.md" }) +\`\`\` + +**For finding files by pattern:** +\`\`\` +workspace-glob({ pattern: "**/*.md", cwd: "knowledge/People" }) +\`\`\` + +**IMPORTANT:** +- Use \`workspace-edit\` for updating existing notes (adding activity, updating fields) +- Use \`workspace-writeFile\` only for creating new notes +- Prefer the knowledge_index for entity resolution (it's faster than grep) # Output @@ -119,7 +156,7 @@ Either: Read the source file and determine if it's a meeting or email. \`\`\` -executeCommand("cat '{source_file}'") +workspace-readFile({ path: "{source_file}" }) \`\`\` **Meeting indicators:** @@ -350,7 +387,7 @@ If processing, continue to Step 2. # Step 2: Read and Parse Source File \`\`\` -executeCommand("cat '{source_file}'") +workspace-readFile({ path: "{source_file}" }) \`\`\` Extract metadata: @@ -447,7 +484,7 @@ From index, find matches for: Only read the full note content when you need details not in the index (e.g., activity logs, open items): \`\`\`bash -executeCommand("cat '{knowledge_folder}/People/Sarah Chen.md'") +workspace-readFile({ path: "{knowledge_folder}/People/Sarah Chen.md" }) \`\`\` **Why read these notes:** @@ -531,27 +568,27 @@ Resolution Map: When multiple candidates match a variant, disambiguate: **By organization (strongest signal):** -\`\`\`bash +\`\`\` # "David" could be David Kim or David Chen -executeCommand("grep -i 'Acme' '{knowledge_folder}/People/David Kim.md'") +workspace-grep({ pattern: "Acme", searchPath: "{knowledge_folder}/People/David Kim.md" }) # Output: **Organization:** [[Acme Corp]] -executeCommand("grep -i 'Acme' '{knowledge_folder}/People/David Chen.md'") +workspace-grep({ pattern: "Acme", searchPath: "{knowledge_folder}/People/David Chen.md" }) # Output: **Organization:** [[Other Corp]] # Source is from Acme context → "David" = "David Kim" \`\`\` **By email (definitive):** -\`\`\`bash -executeCommand("grep -i 'david@acme.com' '{knowledge_folder}/People/David Kim.md'") +\`\`\` +workspace-grep({ pattern: "david@acme.com", searchPath: "{knowledge_folder}/People/David Kim.md" }) # Exact email match is definitive \`\`\` **By role:** -\`\`\`bash +\`\`\` # Source mentions "their CTO" -executeCommand("grep -r -i 'Role.*CTO' '{knowledge_folder}/People/'") +workspace-grep({ pattern: "Role.*CTO", searchPath: "{knowledge_folder}/People" }) # Filter results by organization context \`\`\` @@ -888,8 +925,8 @@ STATE CHANGES: Before writing, compare extracted content against existing notes. ## Check Activity Log -\`\`\`bash -executeCommand("grep '2025-01-15' '{knowledge_folder}/People/Sarah Chen.md'") +\`\`\` +workspace-grep({ pattern: "2025-01-15", searchPath: "{knowledge_folder}/People/Sarah Chen.md" }) \`\`\` If an entry for this date/source already exists, this may have been processed. Skip or verify different interaction. @@ -919,28 +956,28 @@ If new info contradicts existing: **IMPORTANT: Write sequentially, one file at a time.** - Generate content for exactly one note. -- Issue exactly one \`write\` command. +- Issue exactly one write/edit command. - Wait for the tool to return before generating the next note. -- Do NOT batch multiple \`write\` commands in a single response. +- Do NOT batch multiple write commands in a single response. -**For new entities (meetings and qualifying emails):** -\`\`\`bash -executeCommand("write '{knowledge_folder}/People/Jennifer.md' '{content}'") +**For NEW entities (use workspace-writeFile):** +\`\`\` +workspace-writeFile({ + path: "{knowledge_folder}/People/Jennifer.md", + data: "# Jennifer\\n\\n## Summary\\n..." +}) \`\`\` -**For existing entities:** -- Read current content first -- Add activity entry at TOP of Activity section (reverse chronological) -- Update "Last seen" date -- Add new key facts (skip duplicates) -- Add new open items -- Add new decisions -- Add new relationships -- Update summary ONLY if significant new understanding -\`\`\`bash -executeCommand("cat '{knowledge_folder}/People/Sarah Chen.md'") -# ... modify content ... -executeCommand("write '{knowledge_folder}/People/Sarah Chen.md' '{full_updated_content}'") +**For EXISTING entities (use workspace-edit):** +- Read current content first with workspace-readFile +- Use workspace-edit to add activity entry at TOP (reverse chronological) +- Update fields using targeted edits +\`\`\` +workspace-edit({ + path: "{knowledge_folder}/People/Sarah Chen.md", + oldString: "## Activity\\n", + newString: "## Activity\\n- **2026-02-03** (meeting): Met to discuss project timeline\\n" +}) \`\`\` ## 9b: Apply State Changes