Skip to content

fix(agent): sync session project dir#1554

Merged
zerob13 merged 1 commit intodevfrom
codex-sync-session-project-dir
Apr 28, 2026
Merged

fix(agent): sync session project dir#1554
zerob13 merged 1 commit intodevfrom
codex-sync-session-project-dir

Conversation

@zhangmo8
Copy link
Copy Markdown
Collaborator

@zhangmo8 zhangmo8 commented Apr 28, 2026

Summary by CodeRabbit

  • New Features
    • Sessions now support project/workspace directory configuration for agent context.
    • Queued inputs automatically include the current workspace directory.
    • System prompt cache updates when project directory changes.
    • Project directories persist and restore with session data.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

📝 Walkthrough

Walkthrough

Session-level project directory management is introduced through a new setSessionProjectDir API. The presenter normalizes and caches project directories, invalidates system prompt caches on changes, and threads projectDir through pending input queues to agent implementations. Project directories are lazily populated from persisted storage on session initialization.

Changes

Cohort / File(s) Summary
Agent Runtime Presenter
src/main/presenter/agentRuntimePresenter/index.ts
New setSessionProjectDir method normalizes and stores session project directories with cache invalidation on changes. resolveProjectDir now lazily loads persisted values from SQLite project_dir field with error handling and fallback logic.
Agent Session Presenter
src/main/presenter/agentSessionPresenter/index.ts
All queuePendingInput call sites now include projectDir in options. Enhanced setSessionProjectDir validates directories (ACP sessions), persists to session record, syncs environment paths, and propagates changes to agent runtime and ACP workdir state.
Agent Interface Types
src/shared/types/agent-interface.d.ts
Added optional projectDir property to QueuePendingInputOptions and new optional setSessionProjectDir method hook to IAgentImplementation for agent implementations to handle directory updates.
Runtime Presenter Tests
test/main/presenter/agentRuntimePresenter/agentRuntimePresenter.test.ts
Test double updated to emit WORKDIR in system prompt. New tests verify prompt cache invalidation on directory changes and session restoration from DB with persisted project_dir values.
Session Presenter Tests
test/main/presenter/agentSessionPresenter/agentSessionPresenter.test.ts
Tests validate projectDir propagation in queued/first message handling and new setSessionProjectDir test suite covers persistence, agent runtime forwarding, and environment table syncing.

Sequence Diagram

sequenceDiagram
    actor User
    participant SessionPresenter as AgentSessionPresenter
    participant RuntimePresenter as AgentRuntimePresenter
    participant Agent as Agent Implementation
    participant DB as SQLite

    User->>SessionPresenter: setSessionProjectDir(sessionId, path)
    SessionPresenter->>RuntimePresenter: setSessionProjectDir(sessionId, path)
    RuntimePresenter->>RuntimePresenter: Normalize & validate projectDir
    RuntimePresenter->>RuntimePresenter: Invalidate system prompt cache
    RuntimePresenter->>DB: Store projectDir in session
    RuntimePresenter->>Agent: setSessionProjectDir(sessionId, path)
    Agent-->>RuntimePresenter: OK

    User->>SessionPresenter: queuePendingInput(input, options)
    SessionPresenter->>SessionPresenter: Resolve projectDir from session
    SessionPresenter->>RuntimePresenter: processMessage(..., projectDir)
    RuntimePresenter->>RuntimePresenter: Resolve projectDir (with DB fallback)
    RuntimePresenter->>Agent: processMessage(..., projectDir)
    Agent-->>RuntimePresenter: Result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • PR #1483: Directly modifies session project directory handling through setSessionProjectDir and projectDir propagation in queued/first messages, creating parallel implementation paths that should be reviewed for consistency.
  • PR #1333: Extends the session projectDir flow to tool definition loading via loadToolDefinitionsForSession and uses the directory when resolving tool definitions during message processing.
  • PR #1252: Builds on session-level projectDir propagation by using it within agent and file-system tool call implementations, representing downstream consumers of this directory context.

Poem

🐰 A burrow of dirs, now neatly arrayed,
Sessions remember their workspace made—
Cache clears when paths shift, prompts leap anew,
From queue to agent, the path flows through!

🚥 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(agent): sync session project dir' accurately describes the main change—adding session-scoped project directory synchronization across agent presenters and interfaces.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex-sync-session-project-dir

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
Contributor

@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: 2

🧹 Nitpick comments (3)
test/main/presenter/agentSessionPresenter/agentSessionPresenter.test.ts (1)

924-951: Good end-to-end sync test; consider adding clear-project-dir coverage.

This test validates persistence + runtime forwarding + environment sync for a non-null path. Adding a companion case for projectDir = null would help guard against stale workspace state when users clear the directory.

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

In `@test/main/presenter/agentSessionPresenter/agentSessionPresenter.test.ts`
around lines 924 - 951, Add a complementary test in the same
describe('setSessionProjectDir') block that covers clearing the project dir
(project_dir = null): simulate an existing session row with a non-null
project_dir, mock sqlitePresenter.newSessionsTable.get and update to reflect
clearing the field, call presenter.setSessionProjectDir('s1', null), and assert
that sqlitePresenter.newSessionsTable.update was called with project_dir: null,
deepChatAgent.setSessionProjectDir was called with ('s1', null), and
sqlitePresenter.newEnvironmentsTable.syncPath was called with null (or cleared
indicator) to ensure workspace state is cleared in runtime and environment sync.
test/main/presenter/agentRuntimePresenter/agentRuntimePresenter.test.ts (2)

1322-1365: Nice coverage for project-dir update/rehydration; add the null-reset case too.

These tests validate the happy path well. Since the API allows projectDir: string | null, please also cover setSessionProjectDir('s1', null) to ensure prompt cache invalidation and workspace/tool context reset do not leave stale directory state.

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

In `@test/main/presenter/agentRuntimePresenter/agentRuntimePresenter.test.ts`
around lines 1322 - 1365, Add a new test that after initializing a session and
setting a project dir then resetting it to null via setSessionProjectDir('s1',
null) verifies the prompt cache is invalidated and workspace/tool context is
cleared: call agent.initSession('s1', ...),
agent.setSessionProjectDir('s1','/tmp/workspace'), agent.processMessage('s1',
...), then agent.setSessionProjectDir('s1', null) and agent.processMessage('s1',
...); assert buildSystemEnvPrompt (or buildSystemEnvPrompt mock) was invoked
again, inspect the later processStream call (processStream.mock.calls) to ensure
the latest messages do NOT contain 'WORKDIR:' (or do not contain the previous
path), and assert toolPresenter.getAllToolDefinitions was called with an
objectContaining where agentWorkspacePath is null or undefined to confirm tools
were reset. Ensure you reference setSessionProjectDir, processMessage,
buildSystemEnvPrompt, processStream, and toolPresenter.getAllToolDefinitions in
the test.

60-75: Align the buildSystemEnvPrompt test double with production output semantics.

At Line 72 the mock emits WORKDIR: and uses empty-string fallback, while the real builder emits Working directory: ... and resolves a default workdir (src/main/lib/agentRuntime/systemEnvPromptBuilder.ts Line 137-152). This mismatch can hide prompt-shape regressions.

♻️ Suggested test-double alignment
-      return [
-        'ENV_BLOCK',
-        `MODEL:${providerId}/${modelId}`,
-        `WORKDIR:${options?.workdir ?? ''}`,
-        `DATE:${dateText}`
-      ].join('\n')
+      const resolvedWorkdir = options?.workdir ?? process.cwd()
+      return [
+        'ENV_BLOCK',
+        `MODEL:${providerId}/${modelId}`,
+        `Working directory: ${resolvedWorkdir}`,
+        `DATE:${dateText}`
+      ].join('\n')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/main/presenter/agentRuntimePresenter/agentRuntimePresenter.test.ts`
around lines 60 - 75, The test double for buildSystemEnvPrompt currently emits
"WORKDIR:" with an empty-string fallback; update it to match production
semantics by emitting "Working directory: <value>" (exact phrase/casing) and
when options.workdir is omitted or null resolve the same default workdir used by
the real builder (the logic in systemEnvPromptBuilder that supplies the
default), so the mock mirrors buildSystemEnvPrompt's output format and
defaulting behavior exactly.
🤖 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/main/presenter/agentSessionPresenter/index.ts`:
- Around line 1718-1728: The code currently updates persistent session state via
sessionManager.update(sessionId, { projectDir: normalizedProjectDir }) before
calling syncAcpSessionWorkdir(...), which can leave the DB in a
partially-applied state if the ACP/runtime sync fails; fix this by performing
the external syncs first (call
sqlitePresenter.newEnvironmentsTable.syncPath(normalizedProjectDir), await
agent.setSessionProjectDir(sessionId, normalizedProjectDir) if present, and
await syncAcpSessionWorkdir(providerId, sessionId, session.agentId,
normalizedProjectDir)) and only call sessionManager.update(...) after those
awaitable operations succeed; if you cannot reorder, implement a try/catch
around the sync calls and on error rollback the previous projectDir via
sessionManager.update(sessionId, { projectDir: previousProjectDir }) and rethrow
the error.
- Around line 1718-1723: When updating the session's projectDir you must also
resync the old path so it doesn't remain stale: before calling
this.sessionManager.update(sessionId, { projectDir: normalizedProjectDir }) read
the existing session (e.g., const prev = this.sessionManager.get(sessionId) or
equivalent) and if prev?.projectDir and prev.projectDir !== normalizedProjectDir
call this.sqlitePresenter.newEnvironmentsTable.syncPath(prev.projectDir); then
proceed to update and still call syncPath(normalizedProjectDir) for the new
value (or call syncPath(null) when clearing) so both old and new aggregate rows
are refreshed.

---

Nitpick comments:
In `@test/main/presenter/agentRuntimePresenter/agentRuntimePresenter.test.ts`:
- Around line 1322-1365: Add a new test that after initializing a session and
setting a project dir then resetting it to null via setSessionProjectDir('s1',
null) verifies the prompt cache is invalidated and workspace/tool context is
cleared: call agent.initSession('s1', ...),
agent.setSessionProjectDir('s1','/tmp/workspace'), agent.processMessage('s1',
...), then agent.setSessionProjectDir('s1', null) and agent.processMessage('s1',
...); assert buildSystemEnvPrompt (or buildSystemEnvPrompt mock) was invoked
again, inspect the later processStream call (processStream.mock.calls) to ensure
the latest messages do NOT contain 'WORKDIR:' (or do not contain the previous
path), and assert toolPresenter.getAllToolDefinitions was called with an
objectContaining where agentWorkspacePath is null or undefined to confirm tools
were reset. Ensure you reference setSessionProjectDir, processMessage,
buildSystemEnvPrompt, processStream, and toolPresenter.getAllToolDefinitions in
the test.
- Around line 60-75: The test double for buildSystemEnvPrompt currently emits
"WORKDIR:" with an empty-string fallback; update it to match production
semantics by emitting "Working directory: <value>" (exact phrase/casing) and
when options.workdir is omitted or null resolve the same default workdir used by
the real builder (the logic in systemEnvPromptBuilder that supplies the
default), so the mock mirrors buildSystemEnvPrompt's output format and
defaulting behavior exactly.

In `@test/main/presenter/agentSessionPresenter/agentSessionPresenter.test.ts`:
- Around line 924-951: Add a complementary test in the same
describe('setSessionProjectDir') block that covers clearing the project dir
(project_dir = null): simulate an existing session row with a non-null
project_dir, mock sqlitePresenter.newSessionsTable.get and update to reflect
clearing the field, call presenter.setSessionProjectDir('s1', null), and assert
that sqlitePresenter.newSessionsTable.update was called with project_dir: null,
deepChatAgent.setSessionProjectDir was called with ('s1', null), and
sqlitePresenter.newEnvironmentsTable.syncPath was called with null (or cleared
indicator) to ensure workspace state is cleared in runtime and environment sync.
🪄 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: d8635461-72ba-4a34-99ac-8a1b3a67d353

📥 Commits

Reviewing files that changed from the base of the PR and between 18b985b and 3235407.

📒 Files selected for processing (5)
  • src/main/presenter/agentRuntimePresenter/index.ts
  • src/main/presenter/agentSessionPresenter/index.ts
  • src/shared/types/agent-interface.d.ts
  • test/main/presenter/agentRuntimePresenter/agentRuntimePresenter.test.ts
  • test/main/presenter/agentSessionPresenter/agentSessionPresenter.test.ts

Comment on lines +1718 to +1728
this.sessionManager.update(sessionId, { projectDir: normalizedProjectDir })

// Sync environment for new project dir
if (projectDir) {
this.sqlitePresenter.newEnvironmentsTable.syncPath(projectDir)
if (normalizedProjectDir) {
this.sqlitePresenter.newEnvironmentsTable.syncPath(normalizedProjectDir)
}

if (agent.setSessionProjectDir) {
await agent.setSessionProjectDir(sessionId, normalizedProjectDir)
}
await this.syncAcpSessionWorkdir(providerId, sessionId, session.agentId, normalizedProjectDir)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid committing the new workdir before downstream sync can fail.

If syncAcpSessionWorkdir() rejects, this method still leaves new_sessions.project_dir updated
from Line 1718, so callers see a failure even though subsequent reads return the new directory.
Either perform the ACP/runtime sync before persisting, or rollback the previous projectDir on
error.

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

In `@src/main/presenter/agentSessionPresenter/index.ts` around lines 1718 - 1728,
The code currently updates persistent session state via
sessionManager.update(sessionId, { projectDir: normalizedProjectDir }) before
calling syncAcpSessionWorkdir(...), which can leave the DB in a
partially-applied state if the ACP/runtime sync fails; fix this by performing
the external syncs first (call
sqlitePresenter.newEnvironmentsTable.syncPath(normalizedProjectDir), await
agent.setSessionProjectDir(sessionId, normalizedProjectDir) if present, and
await syncAcpSessionWorkdir(providerId, sessionId, session.agentId,
normalizedProjectDir)) and only call sessionManager.update(...) after those
awaitable operations succeed; if you cannot reorder, implement a try/catch
around the sync calls and on error rollback the previous projectDir via
sessionManager.update(sessionId, { projectDir: previousProjectDir }) and rethrow
the error.

Comment on lines +1718 to +1723
this.sessionManager.update(sessionId, { projectDir: normalizedProjectDir })

// Sync environment for new project dir
if (projectDir) {
this.sqlitePresenter.newEnvironmentsTable.syncPath(projectDir)
if (normalizedProjectDir) {
this.sqlitePresenter.newEnvironmentsTable.syncPath(normalizedProjectDir)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Resync the previous environment path too.

syncPath() only recomputes the path you pass in. After Line 1718 moves a session from /old to
/new or clears the directory, the aggregate row for /old is left stale because only the new path
is refreshed here.

♻️ Suggested fix
+    const previousProjectDir = session.projectDir ?? null
     this.sessionManager.update(sessionId, { projectDir: normalizedProjectDir })

-    if (normalizedProjectDir) {
+    if (previousProjectDir !== normalizedProjectDir) {
+      this.sqlitePresenter.newEnvironmentsTable.syncPath(previousProjectDir)
+    }
+    if (normalizedProjectDir) {
       this.sqlitePresenter.newEnvironmentsTable.syncPath(normalizedProjectDir)
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/agentSessionPresenter/index.ts` around lines 1718 - 1723,
When updating the session's projectDir you must also resync the old path so it
doesn't remain stale: before calling this.sessionManager.update(sessionId, {
projectDir: normalizedProjectDir }) read the existing session (e.g., const prev
= this.sessionManager.get(sessionId) or equivalent) and if prev?.projectDir and
prev.projectDir !== normalizedProjectDir call
this.sqlitePresenter.newEnvironmentsTable.syncPath(prev.projectDir); then
proceed to update and still call syncPath(normalizedProjectDir) for the new
value (or call syncPath(null) when clearing) so both old and new aggregate rows
are refreshed.

@zerob13 zerob13 merged commit c8dfd53 into dev Apr 28, 2026
3 checks passed
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.

2 participants