@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/client/edit-diff-data.test.ts +21 -0
- package/client/edit-diff-data.ts +12 -0
- package/client/edit-diff.tsx +66 -0
- package/client/file-tree.tsx +2 -21
- package/client/git-history.tsx +649 -0
- package/client/review.tsx +2 -2
- package/client/split-diff.tsx +3 -3
- package/client/workspace-commit.tsx +381 -0
- package/client/wrap-toggle.tsx +30 -0
- package/index.client.tsx +16 -0
- package/index.server.ts +4 -0
- package/package.json +4 -4
- package/paseo-plugin.json +1 -1
- package/server/git-history.ts +83 -0
- package/server/paseo-api-types.ts +1 -1
- package/server/review.ts +2 -1
- package/server/verify.ts +4 -4
- package/server/workspace-commit.ts +79 -0
- package/shared/git-history.ts +21 -0
- package/shared/workspace-commit.ts +29 -0
package/server/verify.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentPermissionRequest,
|
|
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 }
|
|
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.
|
|
188
|
-
const provider = snapshot.
|
|
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
|
+
});
|