papilot 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +16 -4
  2. package/README.zh-CN.md +15 -3
  3. package/bin/doctor.mjs +239 -0
  4. package/bin/papilot.mjs +96 -87
  5. package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  6. package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  7. package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  8. package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  9. package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  10. package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  11. package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  12. package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  13. package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  14. package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  15. package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  16. package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  17. package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  18. package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  19. package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  20. package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  21. package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  22. package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  23. package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  24. package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  25. package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  26. package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  27. package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  28. package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  29. package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  30. package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  31. package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  32. package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  33. package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  34. package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  35. package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  36. package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  37. package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  38. package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  39. package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  40. package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  41. package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  42. package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  43. package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  44. package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  45. package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  46. package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  47. package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  48. package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  49. package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  50. package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  51. package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  52. package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  53. package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  54. package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  55. package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  56. package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  57. package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  58. package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  59. package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  60. package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  61. package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  62. package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  63. package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  64. package/dist/assets/index-CqhlgnOu.css +1 -0
  65. package/dist/assets/index-Czf1eCno.js +185 -0
  66. package/dist/assets/math-DEcVZfaU.css +1 -0
  67. package/dist/assets/math-Ebyrobf9.js +263 -0
  68. package/dist/assets/{pdf-view-DJo0X38w.js → pdf-view-BhJZr4qw.js} +18 -18
  69. package/dist/index.html +11 -2
  70. package/examples/papilot-paper/preamble.tex +17 -11
  71. package/examples/papilot-paper/sections/02-using.tex +3 -3
  72. package/package.json +12 -2
  73. package/scripts/service.ts +8 -2
  74. package/skills/check-math.md +12 -0
  75. package/skills/consistency.md +11 -0
  76. package/skills/latex-warnings.md +12 -0
  77. package/skills/polish.md +13 -0
  78. package/skills/proofread.md +12 -0
  79. package/skills/reviewer-response.md +11 -0
  80. package/skills/shorten.md +12 -0
  81. package/src/conversation.ts +37 -0
  82. package/src/notes.ts +5 -0
  83. package/src/server/agents.ts +4 -3
  84. package/src/server/codex.ts +2 -2
  85. package/src/server/main.ts +21 -7
  86. package/src/server/paths.ts +2 -1
  87. package/src/server/review.ts +1 -1
  88. package/src/server/service-environment.ts +22 -0
  89. package/src/server/skills.ts +57 -0
  90. package/src/server/workspace.ts +43 -19
  91. package/src/shared.ts +10 -2
  92. package/vendor/latex-review/dist/web/assets/index-Db7pq4Cj.css +1 -0
  93. package/vendor/latex-review/dist/web/assets/index-DqkrTw0t.js +19 -0
  94. package/vendor/latex-review/dist/web/index.html +2 -2
  95. package/vendor/latex-review/src/client.ts +10 -3
  96. package/vendor/latex-review/src/core/build.ts +53 -7
  97. package/vendor/latex-review/src/core/outline.ts +6 -3
  98. package/vendor/latex-review/src/core/publish.ts +8 -6
  99. package/vendor/latex-review/src/runtime.ts +14 -0
  100. package/vendor/latex-review/src/server.ts +2 -1
  101. package/vendor/latex-review/src/types.ts +16 -1
  102. package/vendor/latex-review/src/web/ChangeMap.tsx +21 -9
  103. package/vendor/latex-review/src/web/PdfStrip.tsx +5 -1
  104. package/vendor/latex-review/src/web/i18n.ts +48 -0
  105. package/vendor/latex-review/src/web/model.ts +23 -4
  106. package/vendor/latex-review/src/web/overview.tsx +25 -20
  107. package/vendor/latex-review/src/web/review.tsx +387 -143
  108. package/vendor/latex-review/src/web/style.css +106 -0
  109. package/vendor/latex-review/src/web/zh-CN.ts +186 -0
  110. package/dist/assets/index-BhQFCMTx.js +0 -131
  111. package/dist/assets/index-CAV3-87U.css +0 -1
  112. package/vendor/latex-review/dist/web/assets/index-DyzYnIGk.css +0 -1
  113. package/vendor/latex-review/dist/web/assets/index-HxBK-dls.js +0 -19
@@ -8,7 +8,10 @@ import { Store } from './store.ts';
8
8
  import { Codex, RpcResponseError } from './codex.ts';
9
9
  import { AGENT_NAMES, AntigravityAgent, ClaudeAgent, type AgentRuntime } from './agents.ts';
10
10
  import { reviewClient, taskRoute, confirmedReviewResponse, type ReviewClient } from './review.ts';
11
- import type { Task, Run, TimelineItem, PendingRequest, RuntimeStatus, Snapshot, PendingReview, AgentModel, Usage, AgentId } from '../shared.ts';
11
+ import { VERSION } from './paths.ts';
12
+ import { findSkill, type SkillFile } from './skills.ts';
13
+ import { reviewInstructions, skillPrompt, summarizeFeedback } from '../conversation.ts';
14
+ import type { Task, Run, TimelineItem, PendingRequest, RuntimeStatus, Snapshot, PendingReview, AgentModel, Usage, AgentId, MessageMeta, FeedbackSummary } from '../shared.ts';
12
15
 
13
16
  const execute = promisify(execFile);
14
17
  const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
@@ -83,7 +86,7 @@ export class Workspace extends EventEmitter {
83
86
  if (id === 'codex') this.loaded.clear();
84
87
  this.emit('change');
85
88
  }
86
- snapshot(): Snapshot { return { version: '0.1.0', projects: this.store.projects(), tasks: this.store.tasks(), requests: [...this.requests.values()].map(({ rpcId: _, agent: __, ...r }) => r), runtime: this.runtime, runtimes: this.runtimes }; }
89
+ snapshot(): Snapshot { return { version: VERSION, projects: this.store.projects(), tasks: this.store.tasks(), requests: [...this.requests.values()].map(({ rpcId: _, agent: __, ...r }) => r), runtime: this.runtime, runtimes: this.runtimes }; }
87
90
  async connect() {
88
91
  if (this.connecting) return this.connecting;
89
92
  this.connecting = Promise.all(AGENTS.map(id => this.connectAgent(id))).then(() => { this.emit('change'); this.schedule(); }).finally(() => { this.connecting = null; });
@@ -121,25 +124,30 @@ export class Workspace extends EventEmitter {
121
124
  const project = { ...this.store.project(projectId), name };
122
125
  this.store.saveProject(project); return project;
123
126
  }
124
- async createTask(input: { projectId: string; title: string; instructions: string; agent?: AgentId; model?: string; effort?: string; id: string }) {
127
+ // A task can start from a skill; its instructions are then the author's own words, possibly none.
128
+ async createTask(input: { projectId: string; title: string; instructions: string; skill?: string; agent?: AgentId; model?: string; effort?: string; id: string }) {
125
129
  if (!/^[\w-]{16,64}$/.test(input.id)) throw new Error('Invalid task ID.');
126
130
  if (this.store.taskDeleted(input.id)) throw new Error('This task was deleted. Create a new task instead.');
127
131
  const existing = this.store.tasks().find(t => t.id === input.id); if (existing) return existing;
128
132
  if (this.preparing.has(input.id)) throw new Error('This task is being created. Refresh in a moment.');
129
133
  this.preparing.add(input.id);
130
134
  try {
131
- const project = this.store.project(input.projectId), client = await this.connectReview(project.path);
135
+ const project = this.store.project(input.projectId), skill = input.skill ? findSkill(project.path, input.skill) : undefined;
136
+ if (!skill && !input.instructions.trim()) throw new Error('Describe the revision, or choose a skill.');
137
+ const client = await this.connectReview(project.path);
132
138
  const name = 'p-' + input.id.toLowerCase().replace(/[^a-z0-9-]/g, '').slice(0, 48);
133
139
  const info = await client.post('/api/tasks', { name, title: input.title });
134
- const task: Task = { id: input.id, projectId: project.id, name, title: input.title, instructions: input.instructions,
140
+ const task: Task = { id: input.id, projectId: project.id, name, title: input.title, instructions: skill ? skillPrompt(skill, input.instructions) : input.instructions,
135
141
  worktree: info.worktree, threadId: null, turnId: null, agent: input.agent || 'codex', model: input.model || null, effort: input.effort || null, status: 'idle',
136
142
  createdAt: Date.now(), updatedAt: Date.now(), feedbackCursor: 0, error: null, review: null };
137
143
  this.store.saveTask(task);
138
- await this.enqueue(task.id, input.instructions, 'initial-' + task.id, false);
144
+ await this.enqueue(task.id, input.instructions, 'initial-' + task.id, false, { skill });
139
145
  return this.store.task(task.id);
140
146
  } finally { this.preparing.delete(input.id); }
141
147
  }
142
- async enqueue(taskId: string, text: string, requestKey: string, includeFeedback = true) {
148
+ // The author's message, a skill run with or without one, or the review
149
+ // feedback alone. The chat keeps what the author did; the agent gets the prompt.
150
+ async enqueue(taskId: string, text: string, requestKey: string, includeFeedback = true, options: { skill?: SkillFile; feedback?: boolean } = {}) {
143
151
  const task = this.store.task(taskId);
144
152
  if (task.status === 'deleting') throw new Error('This task is being deleted.');
145
153
  const existing = this.store.runByKey(requestKey);
@@ -147,15 +155,18 @@ export class Workspace extends EventEmitter {
147
155
  if (task.status === 'uncertain') throw new Error('Check the task status before sending more instructions.');
148
156
  const pauseVersion = task.pauseVersion || 0;
149
157
  if (this.store.runs().some(r => r.taskId === taskId && r.status === 'queued')) throw new Error('This task already has queued instructions. Wait or pause it first.');
150
- let prompt = text.trim(), cursor = task.feedbackCursor;
158
+ let prompt = options.feedback ? reviewInstructions : options.skill ? skillPrompt(options.skill, text) : text.trim(), cursor = task.feedbackCursor;
159
+ let summary: FeedbackSummary | undefined;
151
160
  if (includeFeedback) {
152
161
  const client = await this.connectReview(this.store.project(task.projectId).path);
153
162
  const feedback = await client.get(`${taskRoute(task.name)}/feedback?after=${task.feedbackCursor}`);
154
- cursor = feedback.cursor;
163
+ cursor = feedback.cursor; summary = summarizeFeedback(feedback);
164
+ if (options.feedback && !summary) throw new Error('There is no new review feedback to send.');
155
165
  if (feedback.feedback?.length || feedback.build_error) prompt += '\n\nPapilot review feedback (bound to the specified review round and source changes):\n' + JSON.stringify(feedback, null, 2);
156
166
  if (task.error) prompt += '\n\nLatest task/review error:\n' + task.error + '\nThe latest accepted manuscript is at ' + this.store.project(task.projectId).path + '. You may read it to understand accepted changes, but must only edit your isolated task worktree. Prepare a revised proposal for another review; never write to or accept changes in the manuscript checkout.';
157
167
  }
158
168
  const run: Run = { id: randomUUID(), taskId, requestKey, prompt, feedbackCursor: cursor, status: 'queued', createdAt: Date.now(), finishedAt: null };
169
+ const meta: MessageMeta = { ...(options.skill ? { skill: { id: options.skill.id, name: options.skill.name } } : {}), ...(summary ? { feedback: summary } : {}), ...(options.feedback ? { sendsFeedback: true } : {}) };
159
170
  const saved = this.store.transaction(() => {
160
171
  // Feedback loading yields to other requests. Recheck identity, queue, and
161
172
  // task status together before committing; none of the earlier reads own a lock.
@@ -171,7 +182,7 @@ export class Workspace extends EventEmitter {
171
182
  if (current.status === 'uncertain') throw new Error('Check the task status before sending more instructions.');
172
183
  this.store.saveRun(run);
173
184
  if (!BUSY.has(current.status)) this.store.updateTask(taskId, { status: 'queued', error: null });
174
- this.store.saveItem({ id: run.id, taskId, type: 'user', text: text.trim(), createdAt: run.createdAt });
185
+ this.store.saveItem({ id: run.id, taskId, type: 'user', text: options.feedback ? '' : text.trim(), createdAt: run.createdAt, ...(Object.keys(meta).length ? { meta } : {}) });
175
186
  return run;
176
187
  });
177
188
  this.schedule();
@@ -199,7 +210,7 @@ export class Workspace extends EventEmitter {
199
210
  Your task is already created. Your editable working directory is ${task.worktree}.
200
211
  Original task: ${task.instructions}
201
212
  Only edit this isolated task worktree. Never edit the manuscript checkout, other task worktrees, or Papilot's own source/state. Never commit, push, stash, or accept/reject/undo review decisions.
202
- Papilot owns the LaTeX review lifecycle: it compiles and publishes automatically AFTER your turn finishes, and delivers review feedback in a new turn. Do not create another review task, invoke publish, or wait on feedback --wait. This application workflow replaces the standalone latex-review skill's publish-and-wait loop. End your turn after completing the requested edits, with a concise summary.
213
+ Papilot owns the LaTeX review lifecycle: it compiles and publishes automatically AFTER your turn finishes, and delivers review feedback in a new turn. Do not create another review task, invoke publish, or wait on feedback --wait. This application workflow replaces the standalone latex-review skill's publish-and-wait loop. End your turn after completing the requested edits, with a concise summary for the author of what you changed and why; do not describe Papilot's review workflow.
203
214
  Preserve meaning and mathematical correctness. Read relevant project instructions. Treat reviewer comments as instructions tied to their round, file and changes. Rejected edits must not be reintroduced unless explicitly requested. If there is a conflict with accepted text, explain it and propose a revision for a new review round; never overwrite the accepted manuscript.
204
215
  ${this.agentOf(task) === 'antigravity' ? 'Shell commands are not available to you here and any attempt is refused, which ends your turn: read, search and edit files in the task worktree with your file tools only.' : 'You may run tools in the task worktree to inspect and validate your changes.'} If a decision requires the user, ask through the available user-input tool or explain it in your final response. Use English for your progress updates, questions, and final summaries unless the user explicitly requests another language. Preserve the manuscript language unless asked to translate it.`;
205
216
  }
@@ -459,7 +470,7 @@ ${this.agentOf(task) === 'antigravity' ? 'Shell commands are not available to yo
459
470
  if (this.closing) return;
460
471
  const current = await client.get(`/api/jobs/${job.job}`);
461
472
  const summary = await client.get(taskRoute(task.name));
462
- this.store.updateTask(taskId, { review: { state: summary.state, round: summary.round, counts: summary.counts, job: current, unsent: this.store.task(taskId).review?.unsent } });
473
+ this.store.updateTask(taskId, { review: { ...this.store.task(taskId).review, state: summary.state, round: summary.round, counts: summary.counts, job: current } });
463
474
  if (current.status === 'done' || current.status === 'completed' || current.status === 'succeeded') break;
464
475
  if (current.status === 'failed') throw new Error([current.error, ...(current.details || [])].filter(Boolean).join('\n') || 'PDF build failed.');
465
476
  await delay(1500);
@@ -472,19 +483,22 @@ ${this.agentOf(task) === 'antigravity' ? 'Shell commands are not available to yo
472
483
  try {
473
484
  const task = this.store.task(taskId);
474
485
  if (task.status === 'deleting') return;
475
- const client = await this.connectReview(this.store.project(task.projectId).path);
486
+ const project = this.store.project(task.projectId), client = await this.connectReview(project.path);
476
487
  const summary = await client.get(taskRoute(task.name));
477
- const unsent = await this.unsentComments(client, this.store.task(taskId));
488
+ const { unsent, comments } = await this.unsentReviewFeedback(client, this.store.task(taskId));
489
+ const files = await acceptedFiles(project.path, task.name);
478
490
  const current = this.store.task(taskId);
479
- if (current.status !== 'deleting') this.store.updateTask(taskId, { review: { state: summary.state, round: summary.round, counts: summary.counts, job: current.review?.job, unsent } });
491
+ if (current.status !== 'deleting') this.store.updateTask(taskId, { review: { state: summary.state, round: summary.round, counts: summary.counts, job: current.review?.job, unsent, comments, files } });
480
492
  } catch { /* The task remains visible even if its project is temporarily unavailable. */ }
481
493
  }
482
- // Review comments Codex has not received yet. A queued or in-flight message
483
- // already carries the feedback up to its own cursor.
484
- private async unsentComments(client: ReviewClient, task: Task) {
494
+ // Review decisions, comments and undo updates the agent has not received yet.
495
+ // The engine excludes decisions undone before delivery. A queued or in-flight message
496
+ // already carries the feedback up to its own cursor. Comments and notes ask for a revision.
497
+ private async unsentReviewFeedback(client: ReviewClient, task: Task) {
485
498
  const after = Math.max(task.feedbackCursor, ...this.store.runs().filter(r => r.taskId === task.id && ['queued', 'running', 'uncertain'].includes(r.status)).map(r => r.feedbackCursor || 0));
486
499
  const feedback = await client.get<{ feedback?: { kind?: string }[] }>(`${taskRoute(task.name)}/feedback?after=${after}`);
487
- return (feedback.feedback || []).filter(item => item.kind === 'comment').length;
500
+ const items = feedback.feedback || [];
501
+ return { unsent: items.length, comments: items.filter(item => item.kind === 'comment' || item.kind === 'note').length };
488
502
  }
489
503
  renameTask(taskId: string, title: string) {
490
504
  if (this.store.task(taskId).status === 'deleting') throw new Error('This task is being deleted.');
@@ -527,3 +541,13 @@ ${this.agentOf(task) === 'antigravity' ? 'Shell commands are not available to yo
527
541
  }
528
542
  close() { this.closing = true; for (const timer of this.usageTimers.values()) clearTimeout(timer); this.flush(); for (const id of AGENTS) this.agents[id].close(); }
529
543
  }
544
+
545
+ // The review engine records each task's accepted text as a commit beside the
546
+ // one it started from; their difference is what the task put in the
547
+ // manuscript. A rename counts as both of its paths.
548
+ export async function acceptedFiles(root: string, name: string) {
549
+ try {
550
+ const { stdout } = await execute('git', ['-C', root, 'diff', '--name-only', '--no-renames', '-z', `refs/latex-review/${name}/base`, `refs/latex-review/${name}/accepted`, '--']);
551
+ return stdout.split('\0').filter(Boolean);
552
+ } catch { return []; }
553
+ }
package/src/shared.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  export type TaskStatus = 'idle' | 'queued' | 'running' | 'waiting_input' | 'building' | 'reviewing' | 'interrupted' | 'failed' | 'uncertain' | 'deleting';
2
2
  export interface Project { id: string; name: string; path: string; createdAt: number }
3
- export interface ReviewSummary { state: string; round: number | null; counts: Record<string, number>; unsent?: number; next?: string; job?: { phase: string; status: string; error?: string; details?: string[] } | null }
3
+ // unsent counts review updates the agent has not received; comments are the
4
+ // ones that ask for a revision. files are the manuscript files this task's
5
+ // accepted changes touched, relative to the repository root.
6
+ export interface ReviewSummary { state: string; round: number | null; counts: Record<string, number>; unsent?: number; comments?: number; files?: string[]; next?: string; job?: { phase: string; status: string; error?: string; details?: string[] } | null }
4
7
  export interface PendingReview { route: string; body: Record<string, unknown>; previousStatus: TaskStatus }
5
8
  export interface Task {
6
9
  id: string; projectId: string; name: string; title: string; instructions: string;
@@ -11,7 +14,12 @@ export interface Task {
11
14
  pendingReview?: PendingReview | null;
12
15
  }
13
16
  export interface Run { id: string; taskId: string; requestKey: string; prompt: string; feedbackCursor?: number; status: 'queued' | 'running' | 'completed' | 'failed' | 'interrupted' | 'uncertain'; createdAt: number; finishedAt: number | null }
14
- export interface TimelineItem { id: string; taskId: string; type: string; text: string; detail?: string; status?: string; createdAt: number }
17
+ export interface TimelineItem { id: string; taskId: string; type: string; text: string; detail?: string; status?: string; createdAt: number; meta?: MessageMeta }
18
+ // A user message can run a skill or carry review feedback; the chat shows
19
+ // those as what they are rather than as the prompt the agent receives.
20
+ export interface MessageMeta { skill?: { id: string; name: string }; feedback?: FeedbackSummary; sendsFeedback?: boolean }
21
+ export interface FeedbackSummary { round: number | null; accepted: number; rejected: number; undone: number; comments: { where: string; text: string }[]; buildError?: boolean }
22
+ export interface Skill { id: string; name: string; description: string; hint: string; source: 'builtin' | 'user' | 'paper' }
15
23
  export interface PendingRequest { id: string; taskId: string; method: string; params: Record<string, any>; createdAt: number }
16
24
  export type AgentId = 'codex' | 'claude' | 'antigravity';
17
25
  export interface AgentModel { id: string; name: string; isDefault: boolean; efforts: string[]; defaultEffort: string | null }
@@ -0,0 +1 @@
1
+ @font-face{font-family:Review Serif;src:url(/assets/TeXGyrePagellaX-Regular-DQJjZE_L.otf)format("opentype");font-weight:400;font-display:swap}@font-face{font-family:Review Serif;src:url(/assets/TeXGyrePagellaX-Bold-CEtYdOXj.otf)format("opentype");font-weight:700;font-display:swap}:root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--serif:"Review Serif", Palatino, "Palatino Linotype", "Songti SC", Georgia, serif;--sans:-apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Noto Sans", sans-serif;--frame:#fff;--bar:#fff;--ink:#171717;--muted:#666;--line:#e5e5e5;--paper:#fff;--accent:#356aa0;--accept:#1d7448;--reject:#b0313b;--comment:#356aa0;--before:#fbeeee;--before-edge:#d17c82;--after:#eef8f1;--after-edge:#68ab82;--head:#fafafa;--shadow:0 1px 2px #0000000f, 0 4px 18px #0000000d;--paper-top:24px;font:14px/1.5 var(--sans);color:var(--ink);background:var(--frame)}*{box-sizing:border-box}body{background:var(--frame);text-rendering:optimizelegibility;grid-template-rows:auto 1fr;height:100dvh;margin:0;display:grid;overflow:hidden}button,input,select,textarea{font:inherit;color:inherit}button{cursor:pointer}button:disabled{cursor:default;opacity:.38}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible{outline:2px solid var(--accent);outline-offset:3px}[hidden]{display:none!important}.muted{color:var(--muted)}a{color:var(--accent);text-underline-offset:.18em}h1,h2,.brand{font-family:var(--serif);font-weight:400;line-height:1.3}.brand{letter-spacing:.02em;color:var(--ink);font-size:20px;font-variant-caps:small-caps;text-decoration:none}kbd{border:1px solid var(--line);color:var(--muted);background:var(--frame);vertical-align:1px;border-radius:4px;margin-left:6px;padding:2px 4px;font:600 10px/1 ui-monospace,SFMono-Regular,Menlo,monospace}.bar{background:var(--bar);border-bottom:1px solid var(--line);z-index:3;align-items:center;gap:18px;min-height:62px;padding:10px 24px;display:flex}.identity{flex:1;align-items:baseline;gap:10px;min-width:0;display:flex}.identity h1{letter-spacing:.02em;white-space:nowrap;text-overflow:ellipsis;margin:0;font-size:20px;font-variant-caps:small-caps;overflow:hidden}.identity span{white-space:nowrap;font-size:13px}.meter{color:var(--muted);white-space:nowrap;font-variant-numeric:tabular-nums;align-items:center;gap:10px;font-size:12px;display:flex}.track{background:var(--line);width:120px;height:2px;overflow:hidden}.track i{background:#999;width:0;height:100%;transition:width .3s;display:block}.bar-end{align-items:center;gap:8px;display:flex}.status:empty{display:none}.status{background:var(--head);color:var(--muted);white-space:nowrap;border-radius:999px;padding:3px 9px;font-size:12px}.status[data-state=build_failed],.status[data-state=offline]{color:var(--reject)}.ghost{color:var(--muted);background:0 0;border:1px solid #0000;border-radius:7px;padding:5px 9px;font-size:13px}.ghost:hover{border-color:var(--line);color:var(--ink);text-underline-offset:.18em;text-decoration:underline}.accept-all,.reject-all{color:var(--ink);border-color:var(--line);border-radius:999px;box-shadow:0 1px 3px #0000000d}.overview-link{color:var(--accent);flex:none;text-decoration:none}.ghost.round{border-color:var(--line);border-radius:50%;width:30px;height:30px;padding:0}#paper{padding:var(--paper-top) 24px 90px;overflow-anchor:none;outline:none;scroll-padding:70px 0 90px;overflow:auto}.document{max-width:980px;margin:0 auto}.empty{text-align:center;color:var(--muted);padding:60px 16px}.part+.part{margin-top:30px}.part-title{color:var(--muted);letter-spacing:.02em;margin:14px 0 18px;font-size:19px;font-variant-caps:small-caps;font-weight:400}.part-title .number{font-variant-numeric:tabular-nums;margin-right:8px}.sheet{background:var(--paper);box-shadow:var(--shadow);border-radius:3px;padding:10px 0}.context,.side{line-height:0}.document img{width:100%;height:auto;display:block}.fold{color:#9a9d98;letter-spacing:4px;background:repeating-linear-gradient(90deg,#0000 0 6px,#ececec 6px 12px) 50%/100% 1px no-repeat;justify-content:center;align-items:center;height:22px;margin:4px 0;font-size:16px;display:flex}.change{color:var(--ink);outline:none;scroll-margin:70px;position:relative}.change.standalone{background:var(--paper);box-shadow:var(--shadow);border-radius:3px;margin-bottom:26px}.change.inline{margin:6px 0}.change-head{background:var(--head);color:var(--muted);border-top:1px solid var(--line);border-bottom:1px solid var(--line);align-items:baseline;gap:10px;padding:8px 12px;font-size:12px;line-height:1.6;display:flex}.change-number{color:var(--ink);white-space:nowrap;font-weight:400}.change-where{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;font:11px/1.8 ui-monospace,SFMono-Regular,Menlo,monospace;overflow:hidden}.change-kind{white-space:nowrap}.badge{white-space:nowrap;font-weight:600}.badge:empty{display:none}.link{color:var(--accent);background:0 0;border:0;padding:0 2px;font-size:12px}.link.active{font-weight:600;text-decoration:underline}.edits{white-space:nowrap}.side{border-left:2px solid #0000;position:relative}.side.before{background:var(--before);border-left-color:var(--before-edge)}.side.after{background:var(--after);border-left-color:var(--after-edge)}.side.after.identical{border-left-color:var(--line);background:0 0}.side img{mix-blend-mode:multiply}.side:after{font:10px/1 var(--sans);letter-spacing:.06em;text-transform:uppercase;color:var(--before-edge);opacity:.9;pointer-events:none;position:absolute;top:4px;right:8px}.side.before:after{content:"before"}.side.after:after{content:"after";color:var(--after-edge)}:where(:lang(zh-CN)) .side.before:after{content:"修改前"}:where(:lang(zh-CN)) .side.after:after{content:"修改后"}.side.after.identical:after,.change[data-resolved] .side:after{content:none}.caption{color:var(--muted);margin:0;padding:4px 12px 8px;font-size:12px}.change.current{box-shadow:0 0 0 2px var(--accent);z-index:1}.change.current .change-head{background:color-mix(in srgb, var(--accent) 8%, var(--head));color:var(--ink)}.change[data-state=accepted] .badge{color:var(--accept)}.change[data-state=rejected] .badge{color:var(--reject)}.change[data-state=commented] .badge,.change[data-state=mixed] .badge{color:var(--comment)}.change[data-resolved=after] .side.before,.change[data-resolved=before] .side.after:not(.identical){display:none}.change[data-resolved] .side{background:0 0;border-left-color:#0000}.change[data-resolved] .caption{display:none}.remark,.histories{padding:8px 14px;font-size:13px;line-height:1.55}.remark{background:color-mix(in srgb, var(--comment) 8%, #fff);border-top:1px solid var(--line);color:var(--ink)}.remark p,.histories p{white-space:pre-wrap;margin:0}.histories{border-bottom:1px solid var(--line);color:var(--muted);background:#f6f7fb}.source{background:var(--paper);border-top:1px solid var(--line);padding:10px 14px 12px;line-height:1.5}.source-unit+.source-unit{margin-top:12px}.source-title{color:#555;align-items:center;gap:8px;margin-bottom:6px;font:11px ui-monospace,SFMono-Regular,Menlo,monospace;display:flex}.unit-actions{font:13px var(--sans);align-items:center;gap:2px;margin-left:auto;display:flex}.unit-actions button{border:1px solid var(--line);background:var(--paper);border-radius:0;padding:1px 8px;font-weight:600}.unit-actions button:not(:disabled):hover{background:var(--frame)}.unit-actions .accept{color:var(--accept)}.unit-actions .reject{color:var(--reject)}.unit-actions .comment{color:var(--comment)}.unit-state{font-weight:600}.source-unit[data-state=accepted] .unit-state{color:var(--accept)}.source-unit[data-state=rejected] .unit-state{color:var(--reject)}.source-unit[data-state=commented] .unit-state{color:var(--comment)}.source-code{border:1px solid var(--line);white-space:pre-wrap;overflow-wrap:anywhere;color:#2b2f33;background:#fff;border-radius:5px;max-height:420px;margin:0;padding:10px 12px;font:12.5px/1.65 ui-monospace,SFMono-Regular,Menlo,monospace;overflow:auto}.source-code del{color:#8e1d28;background:#fadfe1;-webkit-text-decoration:line-through #c9636b;text-decoration:line-through #c9636b}.source-code ins{color:#11603a;background:#d6f0df;text-decoration:none}.banner{background:color-mix(in srgb, var(--accent) 6%, var(--paper));border:1px solid var(--line);border-radius:8px;max-width:980px;margin:0 auto 14px;padding:10px 14px;font-size:13px}.banner p{margin:3px 0}.banner .details{background:var(--bar);white-space:pre-wrap;border-radius:5px;max-height:220px;margin:6px 0 2px;padding:8px 10px;font:11.5px/1.5 ui-monospace,Menlo,monospace;overflow:auto}.banner summary{cursor:pointer;color:var(--muted)}.waiting{text-align:center;padding:90px 16px}.waiting p{margin:6px 0}.pulse{background:var(--accent);border-radius:50%;width:12px;height:12px;margin:0 auto 18px;animation:1.6s ease-in-out infinite pulse}@keyframes pulse{0%,to{opacity:.25;transform:scale(.8)}50%{opacity:1;transform:scale(1.15)}}.task-list{background:var(--bar);border:1px solid var(--line);box-shadow:var(--shadow);border-radius:8px;margin:0;padding:0;list-style:none}.task-list li+li{border-top:1px solid var(--line)}.index .document{max-width:880px}.overview-heading{margin:32px 0}.overview-heading h1{letter-spacing:.02em;margin:0 0 6px;font-size:34px;font-variant-caps:small-caps}.overview-heading p{margin:6px 0}.overview-summary{color:var(--muted);font-size:13px}.task-tools{gap:10px;margin-bottom:16px;display:flex}.task-tools input,.task-tools select{font:inherit;color:var(--ink);background:var(--bar);border:1px solid var(--line);border-radius:7px;padding:9px 12px}.task-tools input{flex:1;min-width:0}.task-tools input::placeholder{color:var(--muted)}.task-card{color:var(--ink);border-radius:7px;padding:24px 20px;text-decoration:none;display:block}.task-card:hover{background:var(--head)}.task-card:focus-visible,.overview-link:focus-visible,.task-tools :focus-visible{outline:2px solid var(--accent);outline-offset:2px}.task-heading{justify-content:space-between;align-items:baseline;gap:10px;display:flex}.task-heading h2{overflow-wrap:anywhere;margin:0;font-size:21px;font-weight:400}.task-meta{color:var(--muted);flex-wrap:wrap;gap:4px 14px;margin-top:5px;font-size:12px;display:flex}.task-name{overflow-wrap:anywhere;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.task-progress{color:var(--muted);align-items:center;gap:10px;margin-top:14px;font-size:12px;display:flex}.task-progress .track{width:140px}.task-footer{justify-content:space-between;align-items:baseline;gap:10px;margin-top:9px;font-size:12px;display:flex}.task-counts{color:var(--muted);flex-wrap:wrap;gap:4px 14px;display:flex}.task-counts [data-kind=pending]{color:var(--accent);font-weight:600}.task-counts [data-kind=commented]{color:var(--comment)}.task-open{color:var(--accent);flex:none;font-weight:600}.actions{z-index:4;background:var(--bar);border:1px solid var(--line);max-width:calc(100vw - 24px);box-shadow:var(--shadow);border-radius:12px;align-items:center;gap:2px;padding:5px;display:flex;position:fixed;bottom:22px;left:50%;transform:translate(-50%)}.actions button{white-space:nowrap;background:0 0;border:0;border-radius:8px;padding:9px 14px;font-size:14px}.actions button:not(:disabled):hover{background:var(--head)}.actions .nav{color:var(--muted);padding:6px 12px;font-size:20px;line-height:1}.actions .accept{color:var(--accept)}.actions .reject{color:var(--reject)}.actions .comment{color:var(--comment)}.actions .skip{font-weight:600}.actions .undo{color:var(--muted);border-left:1px solid var(--line);border-radius:0 8px 8px 0;margin-left:4px;font-size:13px}.actions .working{opacity:.55}.position{text-align:center;min-width:52px;color:var(--muted);font-variant-numeric:tabular-nums;font-size:12px}.complete{color:var(--muted);white-space:nowrap;padding:0 14px;font-size:13px}.change.current>.change-head{top:calc(-1 * var(--paper-top));z-index:4;position:sticky}.change-head .dock{flex:none;align-items:center;margin:0;display:flex}.change-head .actions{background:var(--bar);border:1px solid var(--line);border-radius:10px;gap:0;max-width:none;padding:3px;position:static;transform:none;box-shadow:0 1px 2px #00000014,0 6px 20px #0000001f}.change-head .actions button{padding:4px 8px;font-size:13px;line-height:1.5}.change-head .actions .nav{padding:2px 8px;font-size:17px}.change-head .actions .undo{margin-left:3px;padding:4px 10px;font-size:12px}.change-head .position{min-width:40px;font-size:11.5px}.change-head .complete{padding:0 8px;font-size:12px}.change-head .composer{width:min(380px,100vw - 24px);position:absolute;inset:calc(100% + 6px) 8px auto auto;transform:none}.composer{z-index:5;background:var(--bar);border:1px solid var(--line);border-radius:12px;width:min(540px,100vw - 24px);padding:12px;position:fixed;bottom:84px;left:50%;transform:translate(-50%);box-shadow:0 6px 24px #00000014}.composer label{color:var(--ink);margin-bottom:6px;font-size:17px;font-weight:400;display:block}.composer textarea{resize:vertical;border:1px solid var(--line);background:var(--paper);color:#1f2328;border-radius:7px;width:100%;min-height:84px;padding:8px 10px}.composer textarea:focus{outline:2px solid color-mix(in srgb, var(--comment) 45%, transparent);border-color:#0000}.composer-row{justify-content:space-between;align-items:center;margin-top:8px;font-size:12px;display:flex}.composer-row kbd{margin-left:0}.composer button{border:1px solid var(--line);background:var(--paper);color:var(--ink);border-radius:7px;padding:6px 16px}.toast{z-index:6;border:1px solid var(--line);background:var(--paper);max-width:min(640px,100vw - 24px);color:var(--ink);white-space:pre-wrap;padding:9px 14px;font-size:13px;position:fixed;top:74px;left:50%;transform:translate(-50%);box-shadow:0 3px 12px #0000000f}.toast.error{background:#7f2b31}dialog{border:1px solid var(--line);box-shadow:var(--shadow);background:var(--bar);color:var(--ink);border-radius:12px;max-width:min(460px,100vw - 32px);padding:20px 24px}dialog::backdrop{background:#00000040}dialog h2{margin:0 0 12px;font-size:23px}dialog dl{grid-template-columns:auto 1fr;gap:8px 16px;margin:0 0 12px;font-size:13px;display:grid}dialog dt{white-space:nowrap}dialog dt kbd{margin:0 2px 0 0}dialog dd{margin:0}dialog form{text-align:right}dialog button{border:1px solid var(--line);background:var(--frame);border-radius:7px;padding:5px 14px}@media (width<=720px){.bar{grid-template-columns:auto 1fr auto;gap:8px;padding:12px 16px;display:grid}.identity{grid-area:1/1/auto/-1}.bar .meter{justify-self:center;font-size:11px}.bar-end{gap:2px}.bar .ghost{padding:4px 2px;font-size:12px}.side{min-height:20px}.track{width:70px}.actions button{padding:9px}.change-head{flex-wrap:wrap}.change-head .dock{flex-basis:100%;order:9;justify-content:center;margin:4px 0 2px}.change-head .composer{width:calc(100vw - 24px);right:0}:root{--paper-top:10px}#paper{padding:var(--paper-top) 6px 90px}#paper.index{padding:10px 14px 40px}.overview-heading{margin-top:16px}.task-tools{flex-wrap:wrap}.task-tools input{flex-basis:100%}.task-tools select{width:100%}.task-card{padding:14px}.task-heading{flex-direction:column;gap:6px}}@media (prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important;animation:none!important}}body{display:block}#root{grid-template-rows:auto 1fr;height:100dvh;display:grid;overflow:hidden}.review-body{min-height:0;position:relative}.review-body>#paper{scrollbar-width:none;height:100%;padding-right:54px}.review-body>#paper::-webkit-scrollbar{display:none}.change-map{z-index:5;border:1px solid var(--line);touch-action:none;-webkit-user-select:none;user-select:none;background:#fafafa;border-radius:7px;width:22px;position:absolute;top:12px;bottom:12px;right:18px;box-shadow:0 1px 4px #0000000d}.change-map-viewport{cursor:grab;background:#6a6f741c;border:1px solid #c6c9cc;border-radius:5px;position:absolute;left:0;right:0}.change-map-viewport:active{cursor:grabbing}.change-map-mark{--mark:#b18a3c;background:var(--mark);border:0;border-radius:2px;width:calc(100% - 10px);min-height:0;padding:0;position:absolute;left:5px}.change-map-mark[data-state=accepted]{--mark:var(--accept)}.change-map-mark[data-state=rejected]{--mark:var(--reject)}.change-map-mark[data-state=commented]{--mark:var(--comment)}.change-map-mark[data-state=mixed]{--mark:#796195}.change-map-mark[aria-current]{outline:2px solid var(--ink);outline-offset:2px}.change-map-mark:hover,.change-map-mark:focus-visible{z-index:1;outline:2px solid var(--accent);outline-offset:2px}.change-map-tooltip{border:1px solid var(--line);background:var(--paper);width:max-content;max-width:min(360px,100vw - 64px);box-shadow:var(--shadow);color:var(--ink);font:12px/1.5 var(--sans);text-align:left;overflow-wrap:anywhere;pointer-events:none;visibility:hidden;border-radius:7px;padding:7px 10px;position:absolute;top:0;right:calc(100% + 12px)}.change-map-mark:hover .change-map-tooltip,.change-map-mark:focus-visible .change-map-tooltip{visibility:visible}@media (width<=720px){.review-body>#paper{padding-right:46px}.change-map{width:18px;right:16px}.change-map-mark{width:calc(100% - 6px);left:3px}.change-head .actions{flex-wrap:wrap;justify-content:center;max-width:100%}.change-head .actions button{padding:4px 6px;font-size:12px}.change-head .actions .nav{padding:2px 6px}.change-head .position{min-width:32px}}.pdf-strip{isolation:isolate;width:100%;position:relative;container-type:inline-size}.pdf-strip img{-webkit-user-select:none;user-select:none;pointer-events:none;position:absolute;inset:0}.pdf-text{color:#0000;-webkit-user-select:text;user-select:text;cursor:text;line-height:1;position:absolute;inset:0;overflow:hidden}.pdf-text-line{white-space:pre}.pdf-glyph{white-space:pre;transform-origin:0 0;color:#0000;font-family:Times New Roman,serif;font-weight:400;line-height:1;position:absolute}.pdf-glyph::selection{color:#0000;background:#437ee759}.copy-reference{white-space:nowrap}.change-head{flex-wrap:wrap}.change-head .change-where{min-width:160px}.document .source-unit button{line-height:1.5}.help-backdrop{z-index:8;background:#00000040;place-items:center;display:grid;position:fixed;inset:0}.help-backdrop dialog{margin:0;position:static}.composer{line-height:1.5}@media (width<=900px){.change-head .dock{flex-basis:100%;order:9;justify-content:center;margin:4px 0 2px}}#root:has(>.review-mode){grid-template-rows:auto auto minmax(0,1fr)}.reviewed-change,.change.reviewed-change.current{box-shadow:none;background:0 0;border:0}.reviewed-change>.change-head,.reviewed-change>.histories,.reviewed-change>.remark,.reviewed-change>.source,.reviewed-change>.caption,.mixed-reviewed>.side{display:none}.reviewed-placeholder{color:var(--muted);font:13px/1.5 var(--sans);text-align:center;margin:0;padding:10px 14px}.review-mode{border-bottom:1px solid var(--line);background:var(--paper);font:13px/1.4 var(--sans);flex-wrap:wrap;align-items:center;gap:8px;padding:6px 14px;display:flex}.review-mode>span,.review-mode>label{color:var(--muted);margin-right:auto}.review-mode label{align-items:center;gap:8px;display:flex}.review-mode select{border:1px solid var(--line);background:var(--paper);max-width:160px;font:inherit;color:var(--ink);border-radius:6px;padding:4px 8px}.review-mode .ghost{white-space:nowrap;padding:4px 8px;font-size:13px}.review-mode .undo{border:1px solid var(--line);border-radius:6px}.review-complete{max-width:var(--page-width);border:1px solid var(--line);background:var(--paper);font:14px/1.5 var(--sans);border-radius:10px;flex-wrap:wrap;align-items:baseline;gap:2px 8px;margin:0 auto 14px;padding:9px 14px;display:flex}.review-complete strong{font-weight:600}.review-complete span{color:var(--muted)}@media (width<=680px){.review-mode{padding:5px 8px}.review-mode .ghost{min-height:36px}.review-mode label{font-size:12px}.review-mode select{max-width:135px}}