Skip to content

fix(sessions): derive root agent labels from identity#259

Open
DrJsPBs wants to merge 4 commits intodaggerhashimoto:masterfrom
DrJLabs:fix/root-session-labels
Open

fix(sessions): derive root agent labels from identity#259
DrJsPBs wants to merge 4 commits intodaggerhashimoto:masterfrom
DrJLabs:fix/root-session-labels

Conversation

@DrJsPBs
Copy link
Copy Markdown

@DrJsPBs DrJsPBs commented Apr 11, 2026

What

Fix root-agent labeling so non-main top-level agents resolve from durable agent identity instead of transient session metadata.

Why

This fixes root-agent rows showing stale label / displayName values from session metadata instead of the actual agent identity. main already used a dedicated identity path, but other root agents did not.

Closes #258

How

  • hydrate non-main root sessions with identityName from /api/workspace/identity
  • parse Name: from each agent's IDENTITY.md
  • render top-level root labels as <identityName> (<rootId>)
  • keep canonical assignee/session values unchanged
  • add regression coverage for session labeling and update kanban assignee-label tests to match the new root-label contract

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to change)
  • 📝 Documentation update
  • 🔧 Refactor / chore (no functional change)

Checklist

  • npm run lint passes
  • npm run build && npm run build:server succeeds
  • npm test -- --run passes
  • New features include tests
  • UI changes include a screenshot or screen recording

Summary by CodeRabbit

  • New Features

    • Session labels now show identity names from workspace identity files as "Identity Name (agent-id)" for clearer agent identification.
    • Session identity lookups are cached to reduce redundant fetches.
  • Bug Fixes

    • Stale or unparsable identity labels are cleared so sidebar labels stay accurate after refresh.
  • Tests

    • Added tests covering identity extraction, label hydration, caching behavior, and refresh interactions.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 11, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 40642aaf-462e-40c2-b54b-91895089b00f

📥 Commits

Reviewing files that changed from the base of the PR and between b432f79 and a96e03e.

📒 Files selected for processing (2)
  • src/contexts/SessionContext.test.tsx
  • src/contexts/SessionContext.tsx
✅ Files skipped from review due to trivial changes (1)
  • src/contexts/SessionContext.test.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/contexts/SessionContext.tsx

📝 Walkthrough

Walkthrough

Session labeling now prefers durable agent identities: identity names are parsed from IDENTITY.md, cached per-root in SessionContext, used to override top-level session labels via a new identityName session field, and tests/UI updated to assert composite labels like Name (rootId).

Changes

Cohort / File(s) Summary
Types
src/types.ts
Added optional identityName?: string to Session to carry resolved identity names.
Session label utilities
src/features/sessions/sessionKeys.ts, src/features/sessions/sessionKeys.test.ts
Added extractIdentityName(content); changed getSessionDisplayLabel() to prefer identity-derived <identityName> (<rootId>) for top-level roots and adjusted tests to cover parsing and new precedence.
Session context
src/contexts/SessionContext.tsx, src/contexts/SessionContext.test.tsx
Added rootIdentityNames/rootIdentityMisses caches; effect to fetch /api/workspace/identity?agentId=<rootId> for unresolved roots (uses AbortController); builds displaySessions overriding identityName; tests added to validate hydration, stale-miss clearing, and no-refetch behavior on refresh.
Kanban UI tests
src/features/kanban/CreateTaskDialog.test.tsx, src/features/kanban/TaskDetailDrawer.test.tsx, src/features/kanban/lib/assigneeOptions.test.ts
Updated mocks and assertions to expect composite identity labels (e.g., Designer (designer)); adjusted assignee option label/value expectations and selection interactions.
Session context test helpers
src/contexts/SessionContext.test.tsx
Added test-only components SessionDisplayLabels and SessionRefreshProbe to read rendered labels and trigger refreshSessions() for tests.

Sequence Diagram(s)

sequenceDiagram
  participant SessionCtx as SessionContext (effect)
  participant Cache as rootIdentityNames / misses
  participant FetchAPI as /api/workspace/identity
  participant Consumer as UI / Tests

  rect rgba(200,200,255,0.5)
  SessionCtx->>Cache: derive missing rootIds from sessions
  end

  rect rgba(200,255,200,0.5)
  SessionCtx->>FetchAPI: fetch identity?agentId=<rootId> (concurrent, AbortController)
  FetchAPI-->>SessionCtx: identity content or error
  SessionCtx->>Cache: extractIdentityName() -> store hit or miss
  end

  rect rgba(255,200,200,0.5)
  SessionCtx->>Consumer: expose displaySessions (override identityName)
  Consumer-->>SessionCtx: may call refreshSessions()
  SessionCtx->>FetchAPI: on refresh, re-evaluate missing roots (skip cached misses)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I read IDENTITY.md beneath moonlight,

names stitched true and labels set right.
I hopped, I fetched, I cached with glee,
now each root wears its own identity.
Hooray — no stale names in sight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: deriving root agent labels from identity instead of transient session metadata.
Description check ✅ Passed The description comprehensively covers What/Why/How sections, addresses the linked issue, includes type classification, and confirms all checklist items passed.
Linked Issues check ✅ Passed The PR fully implements the objectives from issue #258: hydrating non-main roots with identity names from the API, parsing Name from IDENTITY.md, rendering labels as (rootId), and preserving canonical values.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing root-agent labeling per issue #258; no unrelated modifications or scope creep detected in the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/features/sessions/sessionKeys.test.ts (1)

24-28: Add one non-bulleted Name: parser test for robustness.
extractIdentityName is currently tested for bulleted formats; adding a plain-line variant (e.g., Name: Reviewer Prime) would harden coverage against common IDENTITY.md formatting differences.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/sessions/sessionKeys.test.ts` around lines 24 - 28, Add a new
unit test case for extractIdentityName that covers a non-bulleted plain-line
format (e.g., "Name: Reviewer Prime") so the parser handles unlisted Name:
lines; update the test suite in sessionKeys.test.ts by adding an it(...) that
calls extractIdentityName with a string containing "Name: Reviewer Prime" and
asserts the result equals "Reviewer Prime", referencing the existing
extractIdentityName function to locate where to add this case.
src/features/sessions/sessionKeys.ts (1)

133-136: Broaden identity-name parsing to accept non-bulleted Name: lines.
Current regex only matches list-item forms. Supporting both list and plain-line variants will reduce false fallbacks to root id when IDENTITY.md formatting differs.

💡 Suggested patch
 export function extractIdentityName(content: string): string | null {
   const normalized = content.replace(/\*\*/g, '');
-  const match = normalized.match(/^\s*[-*]\s*Name\s*:\s*(.+?)\s*$/im);
+  const match = normalized.match(/^\s*(?:[-*]\s*)?Name\s*:\s*(.+?)\s*$/im);
   return match?.[1]?.trim() || null;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/sessions/sessionKeys.ts` around lines 133 - 136, The
extractIdentityName function's regex only matches bulleted lines and misses
plain "Name: ..." lines; update the regex in extractIdentityName to accept an
optional list marker so it matches both bulleted and non-bulleted forms (e.g.
allow an optional leading "- " or "* " before "Name:"), keep the normalization
step, capture the same group, trim it and return or null as before; target the
extractIdentityName function to replace the current
/^\s*[-*]\s*Name\s*:\s*(.+?)\s*$/im with a pattern that allows the optional
bullet.
src/features/kanban/lib/assigneeOptions.test.ts (1)

23-24: Consider adding one identity-present option test to lock the primary label path.
These updates validate root-id fallback well, but this suite now lacks a direct assertion for the preferred identityName-driven label path in assignee options. Adding one case would prevent regressions where options silently fall back to root id despite identity hydration.

Also applies to: 53-53, 66-66, 80-80, 98-98

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/kanban/lib/assigneeOptions.test.ts` around lines 23 - 24, Add a
unit test in the assigneeOptions.test.ts suite that exercises getAssigneeOptions
(or the function that builds assignee options) with an identity object that
includes identityName present; assert that the resulting option for that
identity uses the identity-driven label path (e.g., value starts with
"identity:" and label equals the identityName like "designer") rather than
falling back to root-id format. Add similar assertions alongside the existing
cases around the current entries ({ value: 'agent:designer', label: 'designer'
}, { value: 'agent:reviewer', label: 'reviewer' }) and replicate for the other
mentioned spots so the preferred identityName path is explicitly locked in.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/contexts/SessionContext.tsx`:
- Around line 173-213: The effect repeatedly refetches identities for roots that
have no parseable "Name:" because those misses are never recorded; add a
miss-cache so negative lookups are treated as resolved and won't be retried
every refresh. Concretely, introduce a lightweight miss set (e.g.,
rootIdentityMisses: Set<string> or a Map<string, number> for TTL) and, inside
the Promise.all resolution, when extractIdentityName returns null record the
rootId into that miss set (or set a timestamp), and change the early-filtering
that builds rootAgentIds to exclude ids present in rootIdentityMisses (or
not-yet-expired entries). Also, when a subsequent successful identity is found
update setRootIdentityNames as before and remove that rootId from the miss set;
if you choose TTL, expire entries based on timestamps to allow retries.
Reference: rootAgentIds, extractIdentityName, setRootIdentityNames, sessions,
rootIdentityNames, and the AbortController handling in the effect.

---

Nitpick comments:
In `@src/features/kanban/lib/assigneeOptions.test.ts`:
- Around line 23-24: Add a unit test in the assigneeOptions.test.ts suite that
exercises getAssigneeOptions (or the function that builds assignee options) with
an identity object that includes identityName present; assert that the resulting
option for that identity uses the identity-driven label path (e.g., value starts
with "identity:" and label equals the identityName like "designer") rather than
falling back to root-id format. Add similar assertions alongside the existing
cases around the current entries ({ value: 'agent:designer', label: 'designer'
}, { value: 'agent:reviewer', label: 'reviewer' }) and replicate for the other
mentioned spots so the preferred identityName path is explicitly locked in.

In `@src/features/sessions/sessionKeys.test.ts`:
- Around line 24-28: Add a new unit test case for extractIdentityName that
covers a non-bulleted plain-line format (e.g., "Name: Reviewer Prime") so the
parser handles unlisted Name: lines; update the test suite in
sessionKeys.test.ts by adding an it(...) that calls extractIdentityName with a
string containing "Name: Reviewer Prime" and asserts the result equals "Reviewer
Prime", referencing the existing extractIdentityName function to locate where to
add this case.

In `@src/features/sessions/sessionKeys.ts`:
- Around line 133-136: The extractIdentityName function's regex only matches
bulleted lines and misses plain "Name: ..." lines; update the regex in
extractIdentityName to accept an optional list marker so it matches both
bulleted and non-bulleted forms (e.g. allow an optional leading "- " or "* "
before "Name:"), keep the normalization step, capture the same group, trim it
and return or null as before; target the extractIdentityName function to replace
the current /^\s*[-*]\s*Name\s*:\s*(.+?)\s*$/im with a pattern that allows the
optional bullet.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9fff8476-cc84-4b2f-bd0a-022bd22317a1

📥 Commits

Reviewing files that changed from the base of the PR and between a5f7973 and e92757f.

📒 Files selected for processing (8)
  • src/contexts/SessionContext.test.tsx
  • src/contexts/SessionContext.tsx
  • src/features/kanban/CreateTaskDialog.test.tsx
  • src/features/kanban/TaskDetailDrawer.test.tsx
  • src/features/kanban/lib/assigneeOptions.test.ts
  • src/features/sessions/sessionKeys.test.ts
  • src/features/sessions/sessionKeys.ts
  • src/types.ts

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/contexts/SessionContext.test.tsx (1)

461-531: Add one regression assertion for stale identityName on miss path.

This test validates no-refetch behavior, but it doesn’t verify rendered fallback when sessions.list includes a stale identityName and /api/workspace/identity returns no parseable Name:. Please add a case asserting the top-level label falls back to reviewer (rootId), not stale identity text.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contexts/SessionContext.test.tsx` around lines 461 - 531, Add one
assertion after the refresh that checks the rendered label falls back to the
rootId "reviewer" (not the stale identity text). After the existing await
waitFor that confirms rpcMock called 'sessions.list', query the rendered output
(e.g., using getByText or getByTestId from the same render result) to assert
that "reviewer" is present and that the stale label "stale reviewer label" is
not rendered; reference the test helpers/getters already in scope (fetchSpy,
rpcMock, getByTestId, SessionProvider, SessionRefreshProbe) to locate where to
insert the assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/contexts/SessionContext.tsx`:
- Around line 244-246: The code currently returns the original session when a
non-`main` root's name is unresolved, which leaves a stale
`session.identityName`; update the `identityName` handling in the block that
computes identityName (using `rootId`, `rootIdentityNames`, and `session`) so
that when `rootId !== 'main'` and `rootIdentityNames[rootId]` is falsy you
explicitly clear `session.identityName` (e.g., return a new session object with
`identityName` set to undefined/null) instead of returning the original session;
this ensures `getSessionDisplayLabel` will fall back to the `<rootId>` label
rather than showing stale text.

---

Nitpick comments:
In `@src/contexts/SessionContext.test.tsx`:
- Around line 461-531: Add one assertion after the refresh that checks the
rendered label falls back to the rootId "reviewer" (not the stale identity
text). After the existing await waitFor that confirms rpcMock called
'sessions.list', query the rendered output (e.g., using getByText or getByTestId
from the same render result) to assert that "reviewer" is present and that the
stale label "stale reviewer label" is not rendered; reference the test
helpers/getters already in scope (fetchSpy, rpcMock, getByTestId,
SessionProvider, SessionRefreshProbe) to locate where to insert the assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 218aa2ab-4181-4f98-a72d-23673487d499

📥 Commits

Reviewing files that changed from the base of the PR and between e92757f and b432f79.

📒 Files selected for processing (5)
  • src/contexts/SessionContext.test.tsx
  • src/contexts/SessionContext.tsx
  • src/features/kanban/lib/assigneeOptions.test.ts
  • src/features/sessions/sessionKeys.test.ts
  • src/features/sessions/sessionKeys.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/features/kanban/lib/assigneeOptions.test.ts
  • src/features/sessions/sessionKeys.test.ts
  • src/features/sessions/sessionKeys.ts

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.

[Bug] Non-main root agents can show stale session labels instead of agent identity

1 participant