Skip to content

feat/nanobot-integration#6

Merged
LarFii merged 22 commits intomainfrom
feat/nanobot-integration
Feb 9, 2026
Merged

feat/nanobot-integration#6
LarFii merged 22 commits intomainfrom
feat/nanobot-integration

Conversation

@LarFii
Copy link
Contributor

@LarFii LarFii commented Feb 7, 2026

📋 Change type

Select the type of this PR:

  • ✨ Feature (feature)
  • 🐛 Bug fix (bugfix)
  • 🔧 Refactor (refactor)
  • 📝 Docs (docs)
  • 🎨 Style (style)
  • 🧪 Tests (test)
  • 🔨 Chore / tooling (chore)

📝 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 base64
  • POST /api/internal/files/edit — Full-file replacement with Yjs cache clearing

nanobot service (nanobot/ directory):

  • Python-based AI agent gateway with Feishu WebSocket channel
  • 5 Litewrite-specific tools: list_projects, list_files, read_file, edit_file, compile
  • LLM-powered agent that understands natural language commands (e.g., "把标题改成中文,编译发我")
  • Skill system with Litewrite-specific instructions (compiler selection, CJK support)
  • Full file sending support (compiled PDF → Feishu file message)

Infrastructure:

  • nanobot added to docker-compose.yml (dev) and docker-compose.prod.yml (prod)
  • CI updated to build nanobot Docker image
  • One-click setup script (scripts/setup.sh)
  • Deployment documentation (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:

  1. User sends "帮我编译RAGAnything,然后把pdf发给我" via Feishu
  2. nanobot agent finds the project, triggers compilation, downloads PDF
  3. PDF is uploaded to Feishu and sent as a file message to the user

Related issue(s)

N/A (new feature)


🖼️ Screenshots / demo

E2E flow verified via Feishu bot:

  • Project listing: agent calls litewrite_list_projects and returns project info
  • Compile + send: agent compiles with pdflatex/xelatex and sends 9.6MB PDF via Feishu file message
  • Edit + compile: agent modifies title/abstract to Chinese, adds xeCJK package, compiles with xelatex, sends PDF

✅ Test checklist

Please confirm:

  • Tested locally
  • Rebased/merged the latest dev
  • No ESLint or TypeScript errors
  • New code has necessary comments
  • Translation files updated (if UI copy changed) — N/A (no UI changes)
  • Tested dark/light theme compatibility (if styling changed) — N/A
  • Database changes synced to prisma/schema.prisma (if applicable) — N/A

📌 Notes

  • nanobot is disabled by default. Set FEISHU_ENABLED=true in .env to activate.
  • Feishu app must be created manually on open.feishu.cn — see nanobot/DEPLOYMENT.md for step-by-step guide.
  • The default LLM model is minimax/minimax-m2.1 via OpenRouter. Configurable via env var.
  • All nanobot Python code passes ruff format and ruff check (pre-commit hooks).
  • .env contains sensitive keys (Feishu secrets, API keys) — already in .gitignore.

New files summary:

Path Description
nanobot/ Complete nanobot Python service (agent, channels, tools, skills)
nanobot/main.py Gateway entry point
nanobot/Dockerfile Production Docker image
nanobot/Dockerfile.dev Development Docker image
nanobot/DEPLOYMENT.md Deployment and configuration guide
app/api/internal/projects/list/route.ts Internal API: list projects
app/api/internal/projects/compile/route.ts Internal API: compile project
app/api/internal/files/edit/route.ts Internal API: edit file (full replacement)
scripts/setup.sh One-click setup script

👀 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
  • Docker Compose changes — nanobot service definition and networking

LarFii and others added 5 commits February 7, 2026 15:01
- 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>
@LarFii
Copy link
Contributor Author

LarFii commented Feb 7, 2026

@codex review

@HKUDS HKUDS deleted a comment from cursor bot Feb 7, 2026
@HKUDS HKUDS deleted a comment from cursor bot Feb 7, 2026
@HKUDS HKUDS deleted a comment from cursor bot Feb 7, 2026
@HKUDS HKUDS deleted a comment from cursor bot Feb 7, 2026
@HKUDS HKUDS deleted a comment from cursor bot Feb 7, 2026
@HKUDS HKUDS deleted a comment from cursor bot Feb 7, 2026
Copy link

@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: 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".

Comment on lines +38 to +52
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;
}

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Bingxi Zhao and others added 2 commits February 8, 2026 01:41
- 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>
@zzhtx258 zzhtx258 force-pushed the feat/nanobot-integration branch from 893375e to 541502c Compare February 8, 2026 12:36
LarFii and others added 4 commits February 8, 2026 20:45
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>
@LarFii
Copy link
Contributor Author

LarFii commented Feb 8, 2026

@codex review

@chatgpt-codex-connector
Copy link

Codex Review: Didn't find any major issues. Swish!

ℹ️ 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".

Bingxi Zhao and others added 6 commits February 9, 2026 02:22
…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
@LarFii
Copy link
Contributor Author

LarFii commented Feb 9, 2026

@codex review

Copy link

@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: 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".

Comment on lines +386 to +405
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);

Choose a reason for hiding this comment

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

P2 Badge 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>
@LarFii
Copy link
Contributor Author

LarFii commented Feb 9, 2026

@codex review

Copy link

@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: 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".

Comment on lines +46 to +55
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)

Choose a reason for hiding this comment

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

P2 Badge 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>
@LarFii
Copy link
Contributor Author

LarFii commented Feb 9, 2026

@codex review

Copy link

@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: 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".

Comment on lines +116 to +118
# 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

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +478 to +482
// 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)!;
}

Choose a reason for hiding this comment

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

P1 Badge 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>
@LarFii
Copy link
Contributor Author

LarFii commented Feb 9, 2026

@codex review

Copy link

@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: 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".

Comment on lines +241 to +243
mode=request.mode, # "ask" or "agent"
user_id=request.actorId,
direct_apply=request.directApply,

Choose a reason for hiding this comment

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

P1 Badge 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>
@LarFii
Copy link
Contributor Author

LarFii commented Feb 9, 2026

@cursor review

@cursor
Copy link

cursor bot commented Feb 9, 2026

Skipping Bugbot: Bugbot is disabled for this repository

@LarFii
Copy link
Contributor Author

LarFii commented Feb 9, 2026

@codex review

Copy link

@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: 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".

Comment on lines +170 to +173
const response = await fetch(`${AI_SERVER_URL}/api/chat/run-sync`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(aiServerPayload),

Choose a reason for hiding this comment

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

P1 Badge 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>
@LarFii
Copy link
Contributor Author

LarFii commented Feb 9, 2026

@codex review

@chatgpt-codex-connector
Copy link

Codex Review: Didn't find any major issues. More of your lovely PRs please.

ℹ️ 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".

@LarFii LarFii merged commit a6b40e2 into main Feb 9, 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