diff --git a/packages/editor/components/AnnotateAgentTerminalPanel.tsx b/packages/editor/components/AnnotateAgentTerminalPanel.tsx
index b08477457..73d78493e 100644
--- a/packages/editor/components/AnnotateAgentTerminalPanel.tsx
+++ b/packages/editor/components/AnnotateAgentTerminalPanel.tsx
@@ -251,12 +251,8 @@ export const AnnotateAgentTerminalPanel = forwardRef<
stopRequestedRef.current = true;
const session = sessionRef.current;
if (!session) {
- if (startedAgentId) {
- closeAfterStopRef.current = closeAfterStop;
- setStatus("stopping");
- return;
- }
clearTimers();
+ closeAfterStopRef.current = false;
stopRequestedRef.current = false;
setStartedAgentId(null);
setStatus("idle");
@@ -276,7 +272,7 @@ export const AnnotateAgentTerminalPanel = forwardRef<
sessionRef.current?.pty.kill();
if (closeAfterStopRef.current) onClose();
}, 1400));
- }, [clearTimers, onClose, onSessionActiveChange, onSessionReadyChange, startedAgentId]);
+ }, [clearTimers, onClose, onSessionActiveChange, onSessionReadyChange]);
const sendMessage = useCallback((message: string) => {
const text = message.trim();
@@ -348,7 +344,12 @@ export const AnnotateAgentTerminalPanel = forwardRef<
-
+
+ {status === "starting" && (
+
+ Starting terminal...
+
+ )}
{
diff --git a/packages/server/reference-handlers.test.ts b/packages/server/reference-handlers.test.ts
index 389e05374..2e3413982 100644
--- a/packages/server/reference-handlers.test.ts
+++ b/packages/server/reference-handlers.test.ts
@@ -219,4 +219,35 @@ describe("handleFileBrowserFiles", () => {
expect(data.workspaceStatus.totals.files).toBe(0);
expect(data.workspaceStatus.files).toEqual({});
});
+
+ test("caps large folder walks", async () => {
+ const root = makeTempDir("plannotator-files-cap-");
+ writeTempFile(root, "docs/a.md", "a\n");
+ writeTempFile(root, "docs/b.md", "b\n");
+ writeTempFile(root, "docs/c.md", "c\n");
+ const previousLimit = process.env.PLANNOTATOR_FILE_BROWSER_MAX_FILES;
+ process.env.PLANNOTATOR_FILE_BROWSER_MAX_FILES = "2";
+
+ try {
+ const url = new URL("http://localhost/api/reference/files");
+ url.searchParams.set("dirPath", root);
+ const res = await handleFileBrowserFiles(new Request(url.toString()));
+ const data = await res.json() as {
+ tree: VaultNode[];
+ truncated: boolean;
+ fileLimit: number;
+ };
+
+ expect(res.status).toBe(200);
+ expect(flattenTree(data.tree)).toHaveLength(2);
+ expect(data.truncated).toBe(true);
+ expect(data.fileLimit).toBe(2);
+ } finally {
+ if (previousLimit === undefined) {
+ delete process.env.PLANNOTATOR_FILE_BROWSER_MAX_FILES;
+ } else {
+ process.env.PLANNOTATOR_FILE_BROWSER_MAX_FILES = previousLimit;
+ }
+ }
+ });
});
diff --git a/packages/server/reference-handlers.ts b/packages/server/reference-handlers.ts
index 53c4676d3..a3332c6ef 100644
--- a/packages/server/reference-handlers.ts
+++ b/packages/server/reference-handlers.ts
@@ -543,12 +543,34 @@ export async function handleObsidianDoc(req: Request): Promise {
// --- File Browser ---
const FILE_BROWSER_EXTENSIONS = /\.(mdx?|txt|html?)$/i;
+const DEFAULT_FILE_BROWSER_MAX_FILES = 5_000;
function includeWorkspaceFile(relativePath: string, _change: WorkspaceFileChange): boolean {
return FILE_BROWSER_EXTENSIONS.test(relativePath) && !isFileBrowserExcludedPath(relativePath);
}
-async function walkFileBrowserFiles(dir: string, root: string, files: Set): Promise {
+function getFileBrowserMaxFiles(): number {
+ const value = Number.parseInt(process.env.PLANNOTATOR_FILE_BROWSER_MAX_FILES ?? "", 10);
+ return Number.isFinite(value) && value > 0 ? value : DEFAULT_FILE_BROWSER_MAX_FILES;
+}
+
+type FileBrowserWalkState = {
+ files: Set;
+ limit: number;
+ truncated: boolean;
+};
+
+function addFileBrowserFile(state: FileBrowserWalkState, relativePath: string): void {
+ if (state.files.has(relativePath)) return;
+ if (state.files.size >= state.limit) {
+ state.truncated = true;
+ return;
+ }
+ state.files.add(relativePath);
+}
+
+async function walkFileBrowserFiles(dir: string, root: string, state: FileBrowserWalkState): Promise {
+ if (state.truncated) return;
let entries;
try {
entries = await readdir(dir, { withFileTypes: true });
@@ -557,14 +579,15 @@ async function walkFileBrowserFiles(dir: string, root: string, files: Set {
}
try {
- const files = new Set();
- await walkFileBrowserFiles(resolvedDir, resolvedDir, files);
+ const state: FileBrowserWalkState = {
+ files: new Set(),
+ limit: getFileBrowserMaxFiles(),
+ truncated: false,
+ };
+ await walkFileBrowserFiles(resolvedDir, resolvedDir, state);
const workspaceStatus = filterWorkspaceStatusForDirectory(await getWorkspaceStatusForDirectory(resolvedDir), resolvedDir, includeWorkspaceFile);
for (const match of getWorkspaceStatusRelativePaths(workspaceStatus, resolvedDir, includeWorkspaceFile)) {
- files.add(match);
+ addFileBrowserFile(state, match);
+ if (state.truncated) break;
}
- const sortedFiles = [...files].sort();
+ const sortedFiles = [...state.files].sort();
const tree = buildFileTree(sortedFiles);
- return Response.json({ tree, workspaceStatus });
+ return Response.json({
+ tree,
+ workspaceStatus,
+ truncated: state.truncated,
+ fileLimit: state.limit,
+ });
} catch {
return Response.json(
{ error: "Failed to list directory files" },
diff --git a/packages/shared/reference-common.test.ts b/packages/shared/reference-common.test.ts
index 4217506ad..14e3012de 100644
--- a/packages/shared/reference-common.test.ts
+++ b/packages/shared/reference-common.test.ts
@@ -9,6 +9,8 @@ describe("isFileBrowserExcludedPath", () => {
expect(isFileBrowserExcludedPath("docs/node_modules/pkg/readme.md")).toBe(true);
expect(isFileBrowserExcludedPath("docs/dist")).toBe(true);
expect(isFileBrowserExcludedPath("docs/dist/generated.md")).toBe(true);
+ expect(isFileBrowserExcludedPath(".claude/worktrees/session/plan.md")).toBe(true);
+ expect(isFileBrowserExcludedPath("packages/app/.agents/skills/review/SKILL.md")).toBe(true);
});
test("matches exact path segments only", () => {
diff --git a/packages/shared/reference-common.ts b/packages/shared/reference-common.ts
index de8430742..ce5557282 100644
--- a/packages/shared/reference-common.ts
+++ b/packages/shared/reference-common.ts
@@ -3,6 +3,8 @@
export const FILE_BROWSER_EXCLUDED = [
"node_modules/",
".git/",
+ ".claude/",
+ ".agents/",
"dist/",
"build/",
".next/",