Skip to content

feat: enrich chat search results with full session context#3959

Merged
yujonglee merged 6 commits intomainfrom
feat/chat-search-session-context
Feb 14, 2026
Merged

feat: enrich chat search results with full session context#3959
yujonglee merged 6 commits intomainfrom
feat/chat-search-session-context

Conversation

@yujonglee
Copy link
Contributor

@yujonglee yujonglee commented Feb 14, 2026

Summary

  • Add load_session_content command to the fs-sync plugin to read session meta, memo, transcript, and notes from disk
  • Add session-context-hydrator that transforms raw fs data into rich SessionContext with resolved participants and speaker-labeled transcript segments
  • Add ToolSearchSessions template and reusable session_context_details macro so search results are rendered with full context for the chat model
  • Refactor ChatSystem to remove relatedSessions field; simplify prompt-context logic
  • Rename SupportSystemSupportContext in template bindings

Updates since last revision

  • Fixed null reference bug in session-context-hydrator.ts: added optional chaining (?.map()) on payload.meta?.participants to prevent crash when meta is null (from Graphite review)
  • Updated inline snapshot for test_chat_system_with_context to match actual template output (extra blank line from session_context_details macro)

Review & Testing Checklist for Human

  • Verify nothing downstream still references relatedSessions on ChatSystem — this field was removed entirely. Search codebase for any remaining usage.
  • Verify SupportSystemSupportContext rename is complete — check that support-block.ts and all template bindings use the new key (supportContext not supportSystem)
  • Spot-check session-context-hydrator.ts transcript building logic — the speaker hint resolution and word-index mapping is complex and not covered by automated tests. Manually verify with a real session that has speaker hints.
  • Test load_session_content with sessions that have missing files (e.g. no _meta.json, no transcript.json) — verify graceful fallback to null fields
  • Test chat search tool end-to-end — verify search results in chat include enriched session context (participants, transcript segments, event info)

Notes

  • The ci gate job (exit 1) was failing because the desktop_ci snapshot test failed — now fixed.
  • The parseSessionContext function in context-item.ts does runtime parsing of unknown values from tool output. If the tool output shape changes, this will silently return nulls rather than error.

Made with Cursor


Link to Devin run: https://app.devin.ai/sessions/ceaec159fd40472aa2dd1d774cd0594c
Requested by: @yujonglee


Open with Devin

yujonglee and others added 4 commits February 14, 2026 13:58
Add load_session_content command to fs-sync plugin to read session data
(meta, memo, transcript, notes) from disk. Hydrate search results with
resolved participants, speaker-labeled transcripts, and rendered template
output so the chat model receives rich context for referenced sessions.

Co-authored-by: Cursor <cursoragent@cursor.com>
@netlify
Copy link

netlify bot commented Feb 14, 2026

Deploy Preview for hyprnote-storybook canceled.

Name Link
🔨 Latest commit d957b41
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote-storybook/deploys/69901ac13f7685000852e01f

@netlify
Copy link

netlify bot commented Feb 14, 2026

Deploy Preview for hyprnote canceled.

Name Link
🔨 Latest commit d957b41
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote/deploys/69901ac1727cb90008a0411d

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 7 additional findings.

Open in Devin Review

@yujonglee yujonglee merged commit f06b15c into main Feb 14, 2026
11 of 12 checks passed
@yujonglee yujonglee deleted the feat/chat-search-session-context branch February 14, 2026 06:48
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment on lines +100 to +103
const isHumanAssignment =
!!store &&
typeof speakerId === "string" &&
Boolean(store.getRow("humans", speakerId));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Boolean(store.getRow(...)) always returns true for non-existent rows

In session-context-hydrator.ts:100-103, the check Boolean(store.getRow("humans", speakerId)) is used to determine whether a speakerId corresponds to a real human in the store. However, TinyBase's getRow returns an empty object {} when the row doesn't exist, and Boolean({}) is always true in JavaScript. This means isHumanAssignment will be true for every speaker hint where store exists and speakerId is a string, even when the speakerId doesn't correspond to an actual human.

Root Cause and Impact

The code at line 103:

Boolean(store.getRow("humans", speakerId))

always evaluates to true because {} is truthy. This causes every speaker hint to be classified as user_speaker_assignment instead of provider_speaker_index, producing incorrect speaker labels in hydrated transcripts.

Notably, the correct pattern is already used at session-context-hydrator.ts:172-173 for participant resolution:

const row = store?.getRow("humans", participant.humanId);
if (!row || typeof row.name !== "string" || !row.name) {
  return null;
}

Here, the code correctly handles the empty-object case by checking for row.name. The speaker hint check should use a similar approach.

Impact: All transcript speaker labels will be resolved through the human-assignment path (looking up a human name), which will fail for provider-assigned speaker indices. This leads to wrong or missing speaker names in the rendered transcript context sent to the chat model.

Suggested change
const isHumanAssignment =
!!store &&
typeof speakerId === "string" &&
Boolean(store.getRow("humans", speakerId));
const isHumanAssignment =
!!store &&
typeof speakerId === "string" &&
Object.keys(store.getRow("humans", speakerId)).length > 0;
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +74 to +86
let frontmatter_session_id = frontmatter
.get("session_id")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();

if frontmatter_session_id != session_id {
continue;
}

if name == SESSION_MEMO_FILE {
content.raw_memo_tiptap_json = Some(tiptap_json);
continue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Session memo file matching uses session_id frontmatter check that may exclude valid memos

In session_content.rs:80-82, markdown files are skipped if frontmatter_session_id != session_id. The session_id is read from frontmatter at line 74-78 with a fallback to empty string. If a memo file's frontmatter lacks a session_id key entirely, frontmatter_session_id becomes "" and won't match the actual session ID, causing the memo to be silently skipped. However, this is handled for _memo.md specifically — the name == SESSION_MEMO_FILE check at line 84 comes after the session_id check at line 80, so a memo without a session_id in its frontmatter would indeed be dropped. If real _memo.md files don't always include session_id in frontmatter, this could cause data loss in the hydrated context.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant