Skip to content

feat(daemon): add link code folder support for agent context#455

Merged
mrcfps merged 5 commits intonexu-io:mainfrom
encyc:feat/link-code-folder
May 5, 2026
Merged

feat(daemon): add link code folder support for agent context#455
mrcfps merged 5 commits intonexu-io:mainfrom
encyc:feat/link-code-folder

Conversation

@encyc
Copy link
Copy Markdown
Contributor

@encyc encyc commented May 4, 2026

Summary

Enables the "Link code folder" import menu item so users can link local code directories to a project. The AI agent then reads the linked source code via --add-dir when generating designs, giving it full context of the user's codebase.

  • Daemon: New POST /api/dialog/open-folder endpoint opens a native OS folder picker (osascript on macOS, zenity on Linux, PowerShell on Windows). Linked directories are validated (absolute path, exists on disk, is a directory, not a sensitive system path), stored in metadata.linkedDirs on the project record, and appended to extraAllowedDirs in startChatRun so the agent receives them via --add-dir.
  • Web: The "Link code folder" import item is now clickable. After the user picks a folder, it appears as a removable chip below the chat input. Chips show the folder basename, display the full path on hover, and can be removed with an X button.
  • Contracts: linkedDirs?: string[] added to ProjectMetadata.
  • i18n: Strings added for all 16 locales with native translations.
  • Tests: 8 unit tests for validateLinkedDirs covering valid paths, relative paths, non-existent dirs, files, duplicates, and normalization.

No new dependencies. No database migration (uses the freeform metadata_json column). No changes to agent adapter code — the existing extraAllowedDirs--add-dir pipeline is reused.

Test plan

  • pnpm --filter @open-design/daemon test — 436 tests pass (including 8 new)
  • pnpm --filter @open-design/web typecheck — clean
  • Manual: open project chat → click Import → Link code folder → native picker appears
  • Manual: select a directory → chip appears below input → send message → agent reads linked files
  • Manual: click X on chip → directory unlinked → PATCH confirmed in network tab
  • Manual: verify on macOS, Linux, Windows that native folder dialog opens
  • Verify no regression on other import menu items (all still disabled as before)

Users can now link local code directories to a project so the AI agent
reads their source code via --add-dir when generating designs. The
import menu's "Link code folder" item opens a native OS folder picker,
and linked folders appear as removable chips below the chat input.

- Add linkedDirs field to ProjectMetadata contract
- Add POST /api/dialog/open-folder endpoint (osascript/zenity/PowerShell)
- Add validateLinkedDirs with path safety checks (absolute, exists, blocklist)
- Append linked dirs to extraAllowedDirs in startChatRun
- Add system prompt hint listing linked code folders
- Render linked folder chips in ChatComposer with add/remove
- Add i18n strings for all 16 locales
- Add 8 unit tests for validateLinkedDirs
@lefarcen lefarcen self-requested a review May 4, 2026 14:10
@lefarcen lefarcen added the feature New feature or enhancement label May 4, 2026
@lefarcen
Copy link
Copy Markdown
Contributor

lefarcen commented May 4, 2026

Hi @encyc! 🎉

Thanks for the contribution — this is a well-scoped feature with solid testing and i18n coverage.
I will run a deep review and get back to you within 24h.

Thanks for making open-design better!
— open-design team

Copy link
Copy Markdown
Contributor

@mrcfps mrcfps left a comment

Choose a reason for hiding this comment

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

@encyc thanks for the well-scoped feature work here. I found one build-blocking issue in the new daemon source that needs a small typing fix before this can merge.

Generated by Looper 0.5.4 · runner=reviewer · agent=opencode

Comment thread apps/daemon/src/linked-dirs.ts Outdated
@encyc
Copy link
Copy Markdown
Contributor Author

encyc commented May 4, 2026

Add import -> Link code folder
image
Test feature
image

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 79442739ce

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/components/ChatComposer.tsx Outdated
Comment thread apps/web/src/components/ProjectView.tsx Outdated
Comment thread apps/daemon/src/linked-dirs.ts Outdated
- Add JSDoc type annotations to validateLinkedDirs for strict mode
- Check path.isAbsolute before resolve to catch relative inputs
- Allow linking when projectMetadata is undefined (default to prototype)
- Remove redundant PATCH in ProjectView callback
Copy link
Copy Markdown
Contributor

@mrcfps mrcfps left a comment

Choose a reason for hiding this comment

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

@encyc thanks for tightening up the link-folder flow and addressing the earlier feedback. One TypeScript build blocker still remains in the new daemon helper, so I’m requesting one more small change before merge. 🙂

Generated by Looper 0.5.4 · runner=reviewer · agent=opencode

Comment thread apps/daemon/src/linked-dirs.ts Outdated
Copy link
Copy Markdown
Contributor

@lefarcen lefarcen left a comment

Choose a reason for hiding this comment

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

Thanks @encyc for addressing the typing issue! The JSDoc annotations should satisfy TypeScript strict mode.

Code review (Lens A):

  • Security: Solid validation — absolute path enforcement, blocked system directories, existence + isDirectory checks, and deduplication. No path traversal risk.
  • Tests: 8 unit tests cover the critical edge cases (relative paths, non-existent, files vs dirs, duplicates, normalization).
  • Error handling: The validation returns structured errors with clear messages.
  • Architecture: Clean separation (validateLinkedDirs helper → server.ts PATCH endpoint → web UI chips). Reuses existing extraAllowedDirs pipeline.

Minor observation (non-blocking): linked-dirs.ts line 18 checks !path.isAbsolute(d) then line 20 does path.resolve(d), which always produces an absolute path. The isAbsolute check before resolve is technically redundant (resolve already handles relative inputs), but keeping it makes the error message clearer for users who pass relative paths, so it's fine as-is.

Overall this is production-ready. The typing fix addresses mrcfps's blocking feedback. No security or correctness issues found.

Looks good to me; deferring final approval to a maintainer.

Copy link
Copy Markdown
Contributor

@lefarcen lefarcen left a comment

Choose a reason for hiding this comment

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

Perfect! The TypeScript annotations are now inline as requested.

Current head ():

  • ✅ Explicit TS parameter type: dirs: unknown
  • ✅ Explicit TS return type: { dirs: string[]; error?: undefined } | { error: string; dirs?: undefined }
  • ✅ Validated array typed: const validated: string[] = []

This resolves mrcfps's blocking feedback. The daemon package will now type-check correctly with strict mode enabled. All prior review points (security validation, test coverage, architecture) remain solid.

Looks good to me; deferring final approval to a maintainer.

Copy link
Copy Markdown
Contributor

@lefarcen lefarcen left a comment

Choose a reason for hiding this comment

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

Perfect! The TypeScript annotations are now inline as requested.

Current head (cead90b):

  • ✅ Explicit TS parameter type: dirs: unknown
  • ✅ Explicit TS return type: { dirs: string[]; error?: undefined } | { error: string; dirs?: undefined }
  • ✅ Validated array typed: const validated: string[] = []

This resolves mrcfps's blocking feedback. The daemon package will now type-check correctly with strict mode enabled. All prior review points (security validation, test coverage, architecture) remain solid.

Looks good to me; deferring final approval to a maintainer.

@encyc encyc requested a review from mrcfps May 4, 2026 14:51
Copy link
Copy Markdown
Contributor

@mrcfps mrcfps left a comment

Choose a reason for hiding this comment

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

@encyc thanks for the quick follow-up on the earlier typing and UI-state feedback. I found two linked-folder validation gaps that can still widen agent filesystem access beyond the intended policy, so I’m requesting changes before merge. 🙂

Generated by Looper 0.5.4 · runner=reviewer · agent=opencode

Comment thread apps/daemon/src/linked-dirs.ts Outdated
Comment thread apps/daemon/src/server.ts Outdated
@lefarcen
Copy link
Copy Markdown
Contributor

lefarcen commented May 4, 2026

mrcfps's latest review (14:59) found two filesystem security gaps that I missed earlier — both are P1. The inline comments on linked-dirs.ts and server.ts point to the exact validation bypasses. These need to be fixed before merge.

Sorry for the earlier "production-ready" assessment — I should have caught the root/symlink bypass and the project-creation validation gap.

- Resolve symlinks with realpathSync.native before checking blocklist
- Reject filesystem root (/) and drive roots as linked dirs
- Canonicalize blocklist entries to handle macOS /etc -> /private/etc
- Validate linkedDirs on project creation, not just PATCH
- Re-validate persisted linkedDirs in startChatRun before use
- Add tests for root, symlink-to-blocked-dir, and realpath resolution
@encyc encyc requested a review from mrcfps May 4, 2026 15:15
@lefarcen
Copy link
Copy Markdown
Contributor

lefarcen commented May 4, 2026

Both P1 security fixes look correct on c68c125:

Path validation bypass (mrcfps comment 3182461516):
✅ Now uses fs.realpathSync.native() to canonicalize paths and follow symlinks
✅ Rejects filesystem roots (/, C:\)
isBlocked() now checks both descendants AND ancestors (line 28: p.startsWith(realPath + path.sep))
BLOCKED_CANONICAL pre-canonicalizes blocked paths to handle macOS /etc/private/etc
✅ Tests cover: root rejection, symlink-to-blocked-dir, symlink resolution

Project creation bypass (mrcfps comment 3182461522):
✅ Project creation validates linkedDirs (server.ts:822-832), drops invalid entries
startChatRun re-validates before use (server.ts:2502-2505), defensive double-check
✅ Invalid paths never reach extraAllowedDirs

Waiting for mrcfps to re-review the current head.

Copy link
Copy Markdown
Contributor

@mrcfps mrcfps left a comment

Choose a reason for hiding this comment

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

Approved.

@mrcfps
Copy link
Copy Markdown
Contributor

mrcfps commented May 5, 2026

Hi @encyc! Thanks for the work here — looks like CI is currently failing on Validate workspace. Could you take a look when you get a chance? 🙏

@encyc
Copy link
Copy Markdown
Contributor Author

encyc commented May 5, 2026

Hi @encyc! Thanks for the work here — looks like CI is currently failing on Validate workspace. Could you take a look when you get a chance? 🙏

fix in latest commit d99282a, please try again, thanks ^ ^.

@lefarcen
Copy link
Copy Markdown
Contributor

lefarcen commented May 5, 2026

Hi @encyc — I verified the latest push (d99282a) and it still has unguarded result.error accesses in the test file:

  • Line 26: result.error.includes('does not exist')
  • Line 35: result.error.includes('not a directory')

These need result.error! or an explicit guard before calling .includes(...) to pass CI's strict TypeScript check. The return type is { dirs, error?: undefined } | { error, dirs?: undefined }, so TS treats error as possibly undefined unless narrowed.

Quick fix: change those lines to result.error!.includes(...) (or wrap in if (result.error) { assert.ok(result.error.includes(...)); } if you prefer the guard style).

Copy link
Copy Markdown
Contributor

@mrcfps mrcfps left a comment

Choose a reason for hiding this comment

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

Thanks again @encyc for the thoughtful work and the quick follow-ups here. I really appreciate how promptly you addressed the CI/typecheck issue and tightened the validation details along the way. CI is green now, and this looks good to me — nice work pushing this feature through! 🙌

@mrcfps mrcfps merged commit cc8add4 into nexu-io:main May 5, 2026
1 check passed
@lefarcen
Copy link
Copy Markdown
Contributor

lefarcen commented May 5, 2026

Thanks again @encyc for the thoughtful iteration on this feature and the quick security/typing fixes throughout the review! 🎉 The link-folder validation is now solid, and the pipeline gives users full codebase context. Really appreciate the careful follow-through here.

— open-design team

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

Labels

feature New feature or enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants