Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3119c96
feat/nanobot-integration
LarFii Feb 7, 2026
985b142
fix lint
LarFii Feb 7, 2026
01754d7
fix: resolve react-hooks/exhaustive-deps warnings breaking CI build
LarFii Feb 7, 2026
6d4b29d
fix: remove invalid @typescript-eslint/no-explicit-any eslint-disableโ€ฆ
LarFii Feb 7, 2026
541502c
fix: replace Chinese text in SKILL.md example with English
LarFii Feb 7, 2026
f038f57
feat: nanobot integration with litewrite tools and API endpoints
Feb 7, 2026
893375e
added deepresearch feature, and enabled telegram bot connection
zzhtx258 Feb 8, 2026
7158cd1
fix lint
LarFii Feb 8, 2026
2be6057
Merge pull request #7 from HKUDS/feat/nanobot-deep-research
LarFii Feb 8, 2026
36bfa9c
fix: security hardening for nanobot integration
LarFii Feb 8, 2026
2ca39fc
fix lint
LarFii Feb 8, 2026
0edf7f8
feat: nanobot bingxi local changes - tools, session, media, import, aโ€ฆ
Feb 8, 2026
536d2c3
feat: complete Telegram channel support with message splitting and fiโ€ฆ
LarFii Feb 8, 2026
662aaee
Update telegram.py
LarFii Feb 9, 2026
7c7c714
Merge origin/feat/nanobot-integration into feat/nanobot-bingxi
Feb 9, 2026
ee15111
Merge pull request #8 from HKUDS/feat/nanobot-bingxi
LarFii Feb 9, 2026
88805b5
fix
Feb 9, 2026
08443a5
fix: resolve race conditions between initializingDocs and HTTP handlers
LarFii Feb 9, 2026
62b10ed
fix: skip project validation when list API fails (missing ownerId)
LarFii Feb 9, 2026
0346ab4
fix: guard GET /doc init await & wire directApply through /api/chat/run
LarFii Feb 9, 2026
92d2916
fix: restrict directApply to authenticated internal callers
LarFii Feb 9, 2026
184e7fb
fix: forward X-Internal-Secret when proxying to ai-server /run-sync
LarFii Feb 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,11 @@ jobs:
docker-compose -f docker-compose.prod.yml config >/dev/null
fi

- name: Docker build (web, ws, ai-server, compile)
- name: Docker build (web, ws, ai-server, nanobot, compile)
run: |
set -euo pipefail
docker build -f Dockerfile .
docker build -f Dockerfile.ws .
docker build -f ai-server/Dockerfile ai-server
docker build -f nanobot/Dockerfile nanobot
docker build -f compile-server/Dockerfile compile-server
124 changes: 124 additions & 0 deletions app/api/internal/files/edit/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* Internal API: Edit File (Full Replacement)
* =============================================
*
* Internal endpoint for nanobot to replace a file's entire content.
* Simpler than the shadow-document based files/write endpoint.
* Writes directly to storage and clears Yjs cache.
*
* This is NOT exposed to the public - protected by INTERNAL_API_SECRET.
*/

import { NextRequest, NextResponse } from "next/server";
import { getStorage, StoragePaths } from "@/lib/storage";

// Verify internal API secret
function verifyInternalAuth(request: NextRequest): boolean {
const secret = request.headers.get("X-Internal-Secret");
const expectedSecret = process.env.INTERNAL_API_SECRET;

if (!expectedSecret) {
console.warn("[Internal API] INTERNAL_API_SECRET not configured");
return false;
}

return secret === expectedSecret;
}

/**
* Clear Yjs in-memory document cache on the WS server.
* This ensures the next time a user opens the file in the editor,
* they get the updated content from storage.
*/
async function clearYjsCache(
projectId: string,
filePath: string
): Promise<void> {
const wsServerUrl =
process.env.WS_SERVER_URL ||
process.env.NEXT_PUBLIC_WS_URL?.replace(/^wss?:\/\//, (m) =>
m === "wss://" ? "https://" : "http://"
) ||
"http://localhost:1234";

try {
const base = wsServerUrl.replace(/\/+$/, "");
await fetch(
`${base}/clear/${projectId}/${encodeURIComponent(filePath)}`,
{
method: "POST",
headers: process.env.INTERNAL_API_SECRET
? { "x-internal-secret": process.env.INTERNAL_API_SECRET }
: undefined,
}
);
console.log(
`[Internal/EditFile] Cleared Yjs cache for ${projectId}/${filePath}`
);
} catch (err) {
// Non-fatal: WS server may be unavailable
console.warn(
`[Internal/EditFile] Failed to clear Yjs cache for ${filePath}:`,
err
);
}
}

export async function POST(request: NextRequest) {
// Verify authentication
if (!verifyInternalAuth(request)) {
return NextResponse.json(
{ success: false, error: "Unauthorized" },
{ status: 401 }
);
}

try {
const body = await request.json();
const { projectId, filePath, content } = body as {
projectId: string;
filePath: string;
content: string;
};

if (!projectId || !filePath) {
return NextResponse.json({
success: false,
error: "projectId and filePath are required",
});
}

if (typeof content !== "string") {
return NextResponse.json({
success: false,
error: "content must be a string",
});
}

const storage = await getStorage();
const key = StoragePaths.projectFile(projectId, filePath);

// Write content to storage (full replacement)
await storage.upload(key, content, "text/plain");
console.log(
`[Internal/EditFile] Written ${content.length} chars to ${projectId}/${filePath}`
);

// Clear Yjs cache so the editor picks up the new content
await clearYjsCache(projectId, filePath);

return NextResponse.json({
success: true,
data: {
filePath,
length: content.length,
},
});
} catch (error) {
console.error("[Internal/EditFile] Error:", error);
return NextResponse.json({
success: false,
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
Loading