Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Notable changes to Papilot, newest first. The format follows [Keep a Changelog](

### Added

- **Codex can see the PDF.** New Codex tasks get a `view_pdf` tool: Codex asks for the pages where source lines or printed page numbers appear, in its worktree or in the accepted text, and Papilot compiles them and draws the pages as images that Codex opens with its image viewer. Codex uses it when the look of the page matters, such as line breaks and spacing, and the conversation shows each look. Only the chapters needed are compiled, and the review after the turn reuses the build.

- **Compilation problems beside Recompile.** A quiet log icon counts the last build's undefined references and citations, duplicate labels, missing files and errors; overfull lines and package remarks are listed but never counted. Open it to see each problem with its file, line and page, jump to the exact spot on the PDF (SyncTeX places it, down to the `??`), read the raw log, or prefill a Fix LaTeX warnings task. A failed rebuild keeps the last PDF and says so in one line.

- **One model menu for every agent.** The agent buttons and model list become one searchable menu, like OpenCode's: every available agent's models, grouped by agent with recent choices first, so one choice sets both the agent and the model. Type to search, or use the arrow keys and Enter; reasoning effort and fast mode follow the chosen model. An agent that cannot be used yet says why in its group.
Expand Down
2 changes: 1 addition & 1 deletion docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Each task runs on one agent and model, chosen from the model menu below the prom

CLI agents, including Claude when Claude Code is installed, use their own login and configuration on this host. The SDK-based agents (Cursor, GLM, and Claude without Claude Code) take their key from Papilot's environment and receive it only for their own service. Papilot never stores a credential. Every agent edits only the task's worktree and leaves compiling and publishing the review to Papilot.

- **Codex** runs through the Codex app-server with its workspace-write sandbox. Commands outside the sandbox and Codex's questions come to the conversation for approval.
- **Codex** runs through the Codex app-server with its workspace-write sandbox. Commands outside the sandbox and Codex's questions come to the conversation for approval. Codex can also look at the typeset paper: Papilot gives each new Codex task a `view_pdf` tool that compiles the task worktree as it is and draws the pages where given source lines, or given printed page numbers, appear, or the same places in the accepted text, as image files that Codex opens with its own image viewer. Codex decides when to use it, for example when line breaks, spacing, figures or formulas matter; it compiles only the chapters it needs when the paper allows partial builds, and the review after the turn reuses that build. When the PDF has no page-label metadata, Codex uses source locations instead of guessing printed numbers from physical page positions. The conversation shows each look as **Viewed the PDF**. This uses the app-server's experimental API; a Codex without it runs tasks without the tool.
- **Claude** runs on [Claude Code](https://code.claude.com) when it is installed: each turn is one `claude -p` process with your own Claude Code login, either your Claude plan, whose usage counts toward the plan's limits, or an API key. Sign in once with `claude auth login`. Papilot only reads whether Claude Code is signed in; the credential stays with Claude Code. Edits in the worktree are accepted and commands run in Claude Code's sandbox. `-p` cannot ask for approval, so anything that would need it, such as a write outside the worktree, is declined and noted in the conversation, and Claude asks its questions in its reply. A later turn resumes the same session. The model menu lists the models your Claude Code offers, such as Opus 5.5 and Sonnet 5.5, with their reasoning efforts and its default first; Papilot reads them from Claude Code without sending a request. Set `PAPILOT_CLAUDE_BIN` if the service cannot find `claude`.

Without Claude Code, Claude runs through the Claude Agent SDK, which an installed Papilot downloads into `agents` in the data folder the first time it starts with an API key. Through the SDK, anything that needs approval, including Claude's questions, comes to the conversation. The SDK needs an API key: Anthropic does not accept a Claude subscription login through the Agent SDK and refuses such requests with 403. Run Papilot with `ANTHROPIC_API_KEY` set (or with Claude Code configured for Amazon Bedrock, Google Vertex AI or Microsoft Foundry); usage is billed to that key or provider.
Expand Down
3 changes: 2 additions & 1 deletion src/server/codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ export class Codex extends EventEmitter {
};
child.once('error', failed);
child.once('exit', (code, signal) => failed(new Error(`Codex exited (${signal || code})`)));
await this.request('initialize', { clientInfo: { name: 'papilot', title: 'Papilot', version: VERSION } });
// The experimental API carries the tools Papilot gives its threads (see VIEW_PDF_TOOL).
await this.request('initialize', { clientInfo: { name: 'papilot', title: 'Papilot', version: VERSION }, capabilities: { experimentalApi: true } });
this.notify('initialized', {}); this.ready = true;
}
private receive(message: any) {
Expand Down
2 changes: 1 addition & 1 deletion src/server/review.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import path from 'node:path';
import { ENGINE } from './paths.ts';
import type { Task } from '../shared.ts';

export interface ReviewClient { url: string; token: string; get<T = any>(route: string): Promise<T>; post<T = any>(route: string, body?: unknown): Promise<T> }
export interface ReviewClient { url: string; token: string; get<T = any>(route: string): Promise<T>; post<T = any>(route: string, body?: unknown, timeout?: number): Promise<T> }
let modulePromise: Promise<{ connect: (root: string) => Promise<ReviewClient> }> | null = null;
export function reviewClient(root: string): Promise<ReviewClient> {
modulePromise ??= import(pathToFileURL(path.join(ENGINE, 'src/client.ts')).href);
Expand Down
Loading
Loading