Conversation
- Capture ref values before cleanup in pdf-renderer useEffect - Add missing `t` dependency to hooks in pdf-renderer and pdf-viewer - Add missing `tHistory` dependency to useCompile hook Co-authored-by: Cursor <cursoragent@cursor.com>
… comments The rule was not configured in the project, causing ESLint to report "Definition for rule not found" errors that fail the build. Co-authored-by: Cursor <cursoragent@cursor.com>
Fixes the scan:chinese CI check that disallows non-i18n Chinese characters outside of messages/zh.json. Co-authored-by: Cursor <cursoragent@cursor.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 541502c313
ℹ️ 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".
| const { ownerId, search, limit = 50 } = body as { | ||
| ownerId?: string; | ||
| search?: string; | ||
| limit?: number; | ||
| }; | ||
|
|
||
| // Build where clause | ||
| const where: Record<string, unknown> = { | ||
| status: { not: "trashed" }, | ||
| }; | ||
|
|
||
| // Filter by owner if specified | ||
| if (ownerId) { | ||
| where.ownerId = ownerId; | ||
| } |
There was a problem hiding this comment.
Require owner scope for internal project listing
This endpoint returns all non-trashed projects when ownerId is omitted (the where clause is only constrained when ownerId is present). Because the new nanobot integration calls this internal API based on external chat input, a misconfiguration (e.g., missing NANOBOT_DEFAULT_LITEWRITE_USER_ID) lets any bot user enumerate every project across tenants, then access their IDs for further read/edit/compile calls. Consider making ownerId mandatory or enforcing a configured default owner in this handler so the internal list cannot be used without a user scope.
Useful? React with 👍 / 👎.
- Update ai-server chat API and services - Add file and project management API endpoints - Enhance nanobot agent tools for litewrite integration - Update feishu channel and skill documentation Co-authored-by: Cursor <cursoragent@cursor.com>
893375e to
541502c
Compare
added deepresearch feature, and enabled telegram bot connection
- Add SSRF protection to WebFetchTool (block private IPs, cloud metadata, Docker internal hosts; validate redirect targets) - Sandbox filesystem tools to workspace directory (prevent arbitrary file read/write outside workspace and ~/.nanobot) - Require ownerId in /api/internal/projects/list to prevent cross-tenant project enumeration - Add path traversal validation to internal file read/edit APIs - Return proper HTTP status codes (400/404) for validation errors - Fix Dockerfile data directory path (/root -> /home/nanobot) and matching docker-compose.prod.yml volume mount - Default restrict_to_workspace=True for shell exec tool - Fix file handle leak in Feishu channel _send_file - Add subagent concurrency limit (max 5) - Log warning when channel allow_from is empty (fail-open) - Fix allowlist pipe-delimiter bypass (only trust primary ID segment) - Log channel startup exceptions instead of silently swallowing them - Sanitize error messages sent to users (no internal details) - Fix env.example.oss: remove Chinese placeholder, default Telegram off Co-authored-by: Cursor <cursoragent@cursor.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. Swish! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
…nd config updates Include all local modifications and new files: - Updated agent tools, loop, context, and session management - Added project import endpoints (arxiv, github, upload) - Updated ws-server, docker config, and deployment docs - Added media handling for nanobot Co-authored-by: Cursor <cursoragent@cursor.com>
…le sending Port Telegram send improvements from feat/nanobot-integration: message splitting for 4096 char limit, file attachment sending, and better error handling. Add Telegram env vars to docker-compose configs and deployment docs. Co-authored-by: Cursor <cursoragent@cursor.com>
Resolve conflicts in 5 files: - app/api/internal/projects/create/route.ts: merge locale/template support with mainFileContent param - docker-compose.yml: add NANOBOT__LITEWRITE__AI_SERVER_URL env var - nanobot/nanobot/agent/loop.py: deduplicate LitewriteCreateProjectTool import and registration - nanobot/nanobot/channels/feishu.py: keep BytesIO approach for FD leak prevention - nanobot/nanobot/skills/litewrite/SKILL.md: keep comprehensive agent-based skill doc with LaTeX compilation rules Co-authored-by: Cursor <cursoragent@cursor.com>
feat: Enhanced Nanobot — Manager-Agent Architecture, Full Project Lifecycle, Multi-Channel Media, Session Management
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 88805b53fb
ℹ️ 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".
server/ws-server.ts
Outdated
| const docData = docs.get(roomName); | ||
|
|
||
| if (docData) { | ||
| // Document is in memory — replace Y.Text content in-place | ||
| // This will automatically sync to all connected browsers via the | ||
| // Yjs update broadcast, and persist to Redis via the bound persistence. | ||
| const ytext = docData.doc.getText("content"); | ||
| docData.doc.transact(() => { | ||
| ytext.delete(0, ytext.length); | ||
| ytext.insert(0, content); | ||
| }); | ||
| console.log( | ||
| `🔄 /replace: Updated in-memory doc ${projectId}/${decodedFileId}, ` + | ||
| `${content.length} chars, clients=${docData.clients.size}` | ||
| ); | ||
| } else { | ||
| // Document is NOT in memory (no active connections). | ||
| // Clear any stale Redis state so the next connection loads from S3 | ||
| // (which was already updated by the caller). | ||
| await clearPersistence(projectId, decodedFileId); |
There was a problem hiding this comment.
Cancel in-flight doc init when replacing content
The new /replace handler only checks docs.get(roomName) and, when absent, clears Redis persistence. If a client opens the document at the same time, initializingDocs may already be restoring the old Yjs state; because /replace does not cancel or wait for that initialization, the in-flight restore can still populate docs with stale content and re-bind persistence, effectively reintroducing the pre-edit state after the bot has written the new file. This race can make nanobot edits disappear for users who connect during the replace window; consider canceling/awaiting initializingDocs and then applying the new content before binding persistence.
Useful? React with 👍 / 👎.
The core issue: multiple HTTP endpoints (replace, clear, doc) only checked `docs.get()` for in-memory documents but ignored `initializingDocs`, a Map tracking documents being asynchronously initialized (restoring from Redis). This caused several critical bugs: - /replace: bot edits silently lost when a client connects during replace - /clear: deleted files "resurrect" because init promise still calls docs.set() - /clear-project: misses in-flight inits, stale content survives version restore - GET /doc: false 404 during initialization window - POST /doc: Keep All edits silently dropped during init Additional fixes: - getYDoc: wrap init in try-finally so initializingDocs is always cleaned up on failure (previously a rejected promise would stay forever, making the document permanently inaccessible) - WebSocket handler: add try-catch around getYDoc to prevent unhandled rejection crash; detect zombie clients (socket closed during await) to prevent memory leaks from unreachable client references Co-authored-by: Cursor <cursoragent@cursor.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 08443a551b
ℹ️ 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".
| try: | ||
| data: dict[str, Any] = {} | ||
| if owner_id: | ||
| data["ownerId"] = owner_id | ||
| result = await self.request("/api/internal/projects/list", data) | ||
| projects = result.get("data", {}).get("projects", []) | ||
| ids = {p["id"] for p in projects} | ||
| if project_id in ids: | ||
| return True, "" | ||
| available = ", ".join(f"{p['name']} [{p['id']}]" for p in projects) |
There was a problem hiding this comment.
Skip validation when project list call fails
The new validate_project treats any /api/internal/projects/list response as a usable list, even when the call itself fails. If NANOBOT_DEFAULT_LITEWRITE_USER_ID isn’t set, the list endpoint now returns a 400 (it requires ownerId), so projects becomes empty and this method returns “Project not found,” blocking compile/edit/agent tools even though validation failed. That contradicts the “If validation itself fails, let the call proceed” comment and will reject valid project IDs whenever the owner ID is missing or the list API errors. Consider checking result.get("success") and returning (True, "") (or skipping validation entirely) when the list call fails.
Useful? React with 👍 / 👎.
validate_project() treated any /api/internal/projects/list response as
a usable project list, even when the API returned a failure (e.g. 400
due to missing ownerId when NANOBOT_DEFAULT_LITEWRITE_USER_ID is unset).
The empty project list caused validation to always return "Project not
found", blocking litewrite_agent and litewrite_compile for all valid
project IDs.
Now check result.get("success") first — if the list API itself fails,
skip validation and let the downstream call proceed, consistent with
the existing exception handler's behavior.
Co-authored-by: Cursor <cursoragent@cursor.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 62b10edff9
ℹ️ 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".
| # Direct apply mode: when True, file edits are written directly to storage | ||
| # instead of creating shadow documents. Used by nanobot/API consumers. | ||
| directApply: bool = False |
There was a problem hiding this comment.
Honor directApply in /api/chat/run
The new directApply flag is documented as the switch to bypass shadow documents, but it is never read or forwarded into ChatService.run/ToolContext for the streaming /api/chat/run endpoint. As a result, API consumers that set directApply: true will still get shadow-document edits instead of direct writes, which contradicts the field’s contract and breaks the intended integration flow. This should be wired through to ChatService.run (or removed if unsupported) so the flag actually affects edit behavior.
Useful? React with 👍 / 👎.
| // Also await in-flight initialization so we don't 404 while a doc is loading. | ||
| let docData = docs.get(roomName); | ||
| if (!docData && initializingDocs.has(roomName)) { | ||
| docData = await initializingDocs.get(roomName)!; | ||
| } |
There was a problem hiding this comment.
Guard /doc against init failures
The new await initializingDocs.get(roomName) is not wrapped in any error handling inside the GET /doc/:projectId/:fileId handler. If getYDoc() rejects (e.g., Redis restore/persistence errors), this await will throw, the request will never send a response, and Node will surface an unhandled rejection. That can break TAP completion requests under transient storage failures. Consider wrapping the await in try/catch and returning a 5xx response to keep the server stable.
Useful? React with 👍 / 👎.
1. server/ws-server.ts: wrap `await initializingDocs.get()` in GET /doc handler with try-catch. Previously, if the init promise rejected (e.g. Redis failure), the HTTP response was never sent and Node.js surfaced an unhandled rejection, breaking TAP completion requests. 2. ai-server: wire the `directApply` flag end-to-end through the streaming /api/chat/run endpoint: - Add `direct_apply` parameter to ChatService.run() - Pass it into ToolContext creation - Forward request.directApply from the /run endpoint handler Previously the flag was defined in ChatRequest but never read, so API consumers setting directApply:true still got shadow-document edits instead of direct writes. Co-authored-by: Cursor <cursoragent@cursor.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0346ab4eed
ℹ️ 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".
ai-server/api/chat.py
Outdated
| mode=request.mode, # "ask" or "agent" | ||
| user_id=request.actorId, | ||
| direct_apply=request.directApply, |
There was a problem hiding this comment.
Restrict directApply to trusted callers
/api/chat/run now forwards directApply from the request into the chat service, which enables the edit tool to write directly to storage (bypassing shadow documents). This endpoint has no internal-auth guard, and the AI server is exposed on port 6612 in docker-compose, so any caller with network access can set directApply=true and cause unreviewed edits to be applied via the AI server’s internal secret. If this is intended only for nanobot/internal use, it should be gated (e.g., require X-Internal-Secret or ignore directApply on public traffic) to avoid allowing external clients to bypass the review flow.
Useful? React with 👍 / 👎.
The /api/chat/run endpoint now forwards directApply into the chat service, but this endpoint has no auth guard. Since port 6612 is exposed in production via Nginx, any network caller could set directApply=true to bypass the shadow-document review flow and write edits directly to storage. Changes: - Add _is_internal_request() helper using hmac.compare_digest to validate X-Internal-Secret header - /api/chat/run: only honour directApply when the caller provides a valid internal secret; silently ignore it otherwise - /api/chat/run-sync: require X-Internal-Secret since this endpoint always uses direct_apply=True (was previously unauthenticated) Co-authored-by: Cursor <cursoragent@cursor.com>
|
@cursor review |
|
Skipping Bugbot: Bugbot is disabled for this repository |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 92d2916ba8
ℹ️ 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".
| const response = await fetch(`${AI_SERVER_URL}/api/chat/run-sync`, { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify(aiServerPayload), |
There was a problem hiding this comment.
Include internal secret when proxying to ai-server
The proxy call to the ai-server /api/chat/run-sync endpoint omits the X-Internal-Secret header. The ai-server implementation explicitly rejects requests without this header and returns {success: false, error: "Unauthorized: X-Internal-Secret required"} (see ai-server/api/chat.py around the run-sync guard). In any environment where INTERNAL_API_SECRET is set (which the proxy itself requires), nanobot’s agent invocation will always fail with an unauthorized response even though the HTTP status is 200. This breaks the core nanobot agent flow; the fix is to forward the internal secret header when making the request.
Useful? React with 👍 / 👎.
The internal agent/run route proxies to ai-server's /api/chat/run-sync but omitted the X-Internal-Secret header. Since run-sync now requires this header for authentication, nanobot agent invocations would always fail with an unauthorized response. Co-authored-by: Cursor <cursoragent@cursor.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. More of your lovely PRs please. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
📋 Change type
Select the type of this PR:
📝 Description
What changed
Integrate nanobot as a new Docker service within Litewrite, enabling AI-powered project management via Feishu (Lark) bot.
Litewrite side (3 new API endpoints):
POST /api/internal/projects/list— List/search projects (by owner, name)POST /api/internal/projects/compile— Compile project and return PDF as base64POST /api/internal/files/edit— Full-file replacement with Yjs cache clearingnanobot service (
nanobot/directory):list_projects,list_files,read_file,edit_file,compileInfrastructure:
docker-compose.yml(dev) anddocker-compose.prod.yml(prod)scripts/setup.sh)nanobot/DEPLOYMENT.md)Context
Users need a way to interact with their Litewrite projects from messaging platforms. This integration allows a Feishu bot to receive natural language commands, operate on LaTeX projects (search, read, edit, compile), and send back compiled PDFs — all without opening the browser.
The E2E flow has been verified:
Related issue(s)
N/A (new feature)
🖼️ Screenshots / demo
E2E flow verified via Feishu bot:
litewrite_list_projectsand returns project info✅ Test checklist
Please confirm:
devprisma/schema.prisma(if applicable) — N/A📌 Notes
FEISHU_ENABLED=truein.envto activate.nanobot/DEPLOYMENT.mdfor step-by-step guide.minimax/minimax-m2.1via OpenRouter. Configurable via env var.ruff formatandruff check(pre-commit hooks)..envcontains sensitive keys (Feishu secrets, API keys) — already in.gitignore.New files summary:
nanobot/nanobot/main.pynanobot/Dockerfilenanobot/Dockerfile.devnanobot/DEPLOYMENT.mdapp/api/internal/projects/list/route.tsapp/api/internal/projects/compile/route.tsapp/api/internal/files/edit/route.tsscripts/setup.sh👀 Review focus
nanobot/nanobot/channels/feishu.py— Feishu WebSocket integration with async bridge (asyncio.to_thread+run_coroutine_threadsafe)nanobot/nanobot/agent/tools/litewrite.py— Litewrite API tools (HTTP client, 5 tools)app/api/internal/projects/compile/route.ts— Internal compile endpoint (returns base64 PDF)app/api/internal/files/edit/route.ts— Simplified write with Yjs cache clearing