@yuangc/vibe-review 0.0.0 → 1.1.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.
package/server/verify.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AgentPermissionRequest, AgentStreamEvent, AgentTimelineItem } from "@getpaseo/protocol/agent-types";
1
+ import type { AgentPermissionRequest, AgentTimelineItem } from "@getpaseo/protocol/agent-types";
2
2
  import type { PluginHandlerContext } from "@getpaseo/plugin/server";
3
3
  import type { VerificationConfig } from "../shared/settings";
4
4
  import { DEFAULT_VERIFICATION_PROMPT, renderPrompt } from "../shared/prompts.ts";
@@ -135,7 +135,7 @@ export async function verifyWithPaseoProvider(
135
135
  await control?.onAgentCreated(agent.id);
136
136
  if (control?.isCanceled()) return { status: "unavailable", summary: "验证已取消。", evidence: [], provider };
137
137
  if (control?.onProgress && agent.timeline.subscribe) {
138
- const subscription = agent.timeline.subscribe(({ event }: { event: AgentStreamEvent }) => {
138
+ const subscription = agent.timeline.subscribe(({ event }) => {
139
139
  if (event.type !== "timeline" || event.item.type !== "tool_call") return;
140
140
  const stage = verificationToolStage(event.item.name);
141
141
  if (stage) void Promise.resolve(control.onProgress?.(stage)).catch((error) =>
@@ -184,8 +184,8 @@ export async function verifyWithPaseoProvider(
184
184
  export async function recoverFinishedVerification(paseo: PluginHandlerContext["paseo"], verifierId: string): Promise<VerificationResult | null> {
185
185
  const agent = paseo.agents.ref(verifierId);
186
186
  const snapshot = (await agent.refresh())?.agent ?? agent.current();
187
- if (!snapshot || snapshot.activeTurn || snapshot.status === "running" || snapshot.status === "permission") return null;
188
- const provider = snapshot.config?.provider ?? "";
187
+ if (!snapshot || snapshot.activeTurn || snapshot.status === "running" || snapshot.pendingPermissions?.length) return null;
188
+ const provider = snapshot.provider;
189
189
  const run = await agent.waitForFinish(1000);
190
190
  if (run.status !== "idle") return { status: "unavailable", summary: run.error || `验证任务状态:${run.status}`, evidence: [], provider };
191
191
  const timeline = await agent.timeline.refetch({ limit: 200 });
@@ -0,0 +1,79 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import type { PluginServerContext, PluginHandlerContext } from "@getpaseo/plugin/server";
4
+ import { workspaceGitCommitRpc, workspaceGitDiffRpc, workspaceGitPushRpc, workspaceGitTrackRpc, workspaceGitStatusRpc } from "../shared/workspace-commit";
5
+
6
+ const runFile = promisify(execFile);
7
+ const git = async (cwd: string, args: string[]) => (await runFile("git", args, { cwd, encoding: "utf8", maxBuffer: 16 * 1024 * 1024 })).stdout;
8
+
9
+ async function directory(paseo: PluginHandlerContext["paseo"], workspaceId: string) {
10
+ const workspace = await paseo.workspaces.ref(workspaceId).refresh();
11
+ if (!workspace?.workspaceDirectory) throw new Error("工作区目录不可用");
12
+ await git(workspace.workspaceDirectory, ["rev-parse", "--show-toplevel"]);
13
+ return workspace.workspaceDirectory;
14
+ }
15
+
16
+ async function status(cwd: string) {
17
+ const raw = await git(cwd, ["status", "--porcelain=v1", "-z", "--no-renames", "--untracked-files=all"]);
18
+ return raw.split("\0").filter(Boolean).map((entry) => {
19
+ const code = entry.slice(0, 2);
20
+ return { path: entry.slice(3), status: code, staged: code[0] !== " " && code[0] !== "?", unstaged: code[1] !== " " && code[1] !== "?", untracked: code === "??" };
21
+ });
22
+ }
23
+
24
+ async function checkedPath(cwd: string, path: string) {
25
+ if (!path || path.startsWith("/") || path.includes("\0") || path.split("/").includes("..")) throw new Error("无效文件路径");
26
+ if (!(await status(cwd)).some((file) => file.path === path)) throw new Error("文件状态已变化,请刷新");
27
+ return path;
28
+ }
29
+
30
+ export function registerWorkspaceCommit(server: PluginServerContext) {
31
+ server.handle(workspaceGitStatusRpc, async ({ workspaceId }, { paseo }) => {
32
+ const cwd = await directory(paseo, workspaceId);
33
+ const branch = (await git(cwd, ["branch", "--show-current"])).trim();
34
+ return { files: await status(cwd), branch };
35
+ });
36
+ server.handle(workspaceGitDiffRpc, async ({ workspaceId, path, staged }, { paseo }) => {
37
+ const cwd = await directory(paseo, workspaceId);
38
+ await checkedPath(cwd, path);
39
+ const file = (await status(cwd)).find((item) => item.path === path)!;
40
+ if (file.untracked) {
41
+ try { return { diff: await git(cwd, ["diff", "--no-index", "--", "/dev/null", path]) }; }
42
+ catch (error) {
43
+ const result = error as { code?: number; stdout?: string };
44
+ if (result.code === 1) return { diff: result.stdout ?? "" };
45
+ throw error;
46
+ }
47
+ }
48
+ return { diff: await git(cwd, ["diff", "--no-ext-diff", "--no-renames", ...(staged ? ["--cached"] : []), "--", path]) };
49
+ });
50
+ server.handle(workspaceGitTrackRpc, async ({ workspaceId, paths }, { paseo }) => {
51
+ const cwd = await directory(paseo, workspaceId);
52
+ const current = await status(cwd);
53
+ for (const path of paths) {
54
+ await checkedPath(cwd, path);
55
+ if (!current.some((file) => file.path === path && file.untracked)) throw new Error("只能将未跟踪文件加入 Git");
56
+ }
57
+ await git(cwd, ["add", "--", ...paths]);
58
+ return { ok: true as const };
59
+ });
60
+ server.handle(workspaceGitCommitRpc, async ({ workspaceId, message, paths }, { paseo }) => {
61
+ const cwd = await directory(paseo, workspaceId);
62
+ const current = await status(cwd);
63
+ for (const path of paths) await checkedPath(cwd, path);
64
+ const untracked = paths.filter((path) => current.some((file) => file.path === path && file.untracked));
65
+ if (untracked.length) await git(cwd, ["add", "-N", "--", ...untracked]);
66
+ const summary = (await git(cwd, ["commit", "--only", "-m", message, "--", ...paths])).trim();
67
+ return { summary };
68
+ });
69
+ server.handle(workspaceGitPushRpc, async ({ workspaceId }, { paseo }) => {
70
+ const cwd = await directory(paseo, workspaceId);
71
+ const branch = (await git(cwd, ["branch", "--show-current"])).trim();
72
+ if (!branch) throw new Error("当前工作区没有可推送的分支");
73
+ await runFile("git", ["push"], {
74
+ cwd, encoding: "utf8", maxBuffer: 16 * 1024 * 1024, timeout: 120_000,
75
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
76
+ });
77
+ return { summary: `已推送 ${branch}` };
78
+ });
79
+ }
@@ -0,0 +1,21 @@
1
+ import { defineRpc } from "@getpaseo/plugin";
2
+ import { z } from "zod";
3
+
4
+ const commit = z.object({ hash: z.string(), parents: z.array(z.string()), author: z.string(), email: z.string(), date: z.string(), subject: z.string(), refs: z.array(z.string()) });
5
+ const changedFile = z.object({ path: z.string(), status: z.string() });
6
+
7
+ export const gitHistoryRpc = defineRpc({
8
+ name: "vibe-review.git-history",
9
+ input: z.object({ workspaceId: z.string(), branch: z.string(), query: z.string(), skip: z.number().int().nonnegative() }),
10
+ output: z.object({ commits: z.array(commit), hasMore: z.boolean(), branches: z.array(z.object({ name: z.string(), group: z.enum(["local", "remote", "tag"]), ahead: z.number(), behind: z.number() })), currentBranch: z.string(), currentGitUser: z.object({ name: z.string(), email: z.string() }) }),
11
+ });
12
+ export const gitCommitDetailRpc = defineRpc({
13
+ name: "vibe-review.git-commit-detail",
14
+ input: z.object({ workspaceId: z.string(), hash: z.string() }),
15
+ output: z.object({ message: z.string(), files: z.array(changedFile), containingBranches: z.array(z.string()) }),
16
+ });
17
+ export const gitCommitFileDiffRpc = defineRpc({
18
+ name: "vibe-review.git-commit-file-diff",
19
+ input: z.object({ workspaceId: z.string(), hash: z.string(), path: z.string() }),
20
+ output: z.object({ diff: z.string() }),
21
+ });
@@ -0,0 +1,29 @@
1
+ import { defineRpc } from "@getpaseo/plugin";
2
+ import { z } from "zod";
3
+
4
+ const file = z.object({ path: z.string(), status: z.string(), staged: z.boolean(), unstaged: z.boolean(), untracked: z.boolean() });
5
+ export const workspaceGitStatusRpc = defineRpc({
6
+ name: "vibe-review.workspace-git-status",
7
+ input: z.object({ workspaceId: z.string() }),
8
+ output: z.object({ files: z.array(file), branch: z.string() }),
9
+ });
10
+ export const workspaceGitDiffRpc = defineRpc({
11
+ name: "vibe-review.workspace-git-diff",
12
+ input: z.object({ workspaceId: z.string(), path: z.string(), staged: z.boolean() }),
13
+ output: z.object({ diff: z.string() }),
14
+ });
15
+ export const workspaceGitTrackRpc = defineRpc({
16
+ name: "vibe-review.workspace-git-track",
17
+ input: z.object({ workspaceId: z.string(), paths: z.array(z.string()).min(1) }),
18
+ output: z.object({ ok: z.literal(true) }),
19
+ });
20
+ export const workspaceGitCommitRpc = defineRpc({
21
+ name: "vibe-review.workspace-git-commit",
22
+ input: z.object({ workspaceId: z.string(), message: z.string().trim().min(1), paths: z.array(z.string()).min(1) }),
23
+ output: z.object({ summary: z.string() }),
24
+ });
25
+ export const workspaceGitPushRpc = defineRpc({
26
+ name: "vibe-review.workspace-git-push",
27
+ input: z.object({ workspaceId: z.string() }),
28
+ output: z.object({ summary: z.string() }),
29
+ });