opencode-magi 0.0.0-dev-20260721051539 → 0.0.0-dev-20260721055255
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/dist/prompts/index.js +9 -4
- package/dist/tools/merge/action.js +2 -2
- package/dist/tools/merge/editor.js +4 -6
- package/dist/tools/review/action.js +2 -3
- package/dist/tools/review/check.js +2 -3
- package/dist/tools/review/reviewer.js +15 -15
- package/dist/utils/function.js +7 -5
- package/package.json +1 -1
package/dist/prompts/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Ajv from "ajv";
|
|
2
2
|
import { readFile } from "fs/promises";
|
|
3
3
|
import { join } from "path";
|
|
4
|
-
import { isString } from "@/utils";
|
|
4
|
+
import { isString, isUndefined } from "@/utils";
|
|
5
5
|
export class Prompt {
|
|
6
6
|
magi;
|
|
7
7
|
config;
|
|
@@ -43,11 +43,16 @@ export class Prompt {
|
|
|
43
43
|
].join("\n\n");
|
|
44
44
|
return Object.entries(values).reduce((prev, [key, value]) => prev.replaceAll(`{${key}}`, value), content);
|
|
45
45
|
}
|
|
46
|
-
async repair() {
|
|
46
|
+
async repair(e) {
|
|
47
47
|
const instructions = [
|
|
48
|
-
"Your previous output
|
|
48
|
+
"Your previous output failed validation. Regenerate the result.",
|
|
49
|
+
!isUndefined(e)
|
|
50
|
+
? `Validation error: ${e instanceof Error ? e.message : "Invalid output."}`
|
|
51
|
+
: undefined,
|
|
49
52
|
"Return only a JSON object matching the output contract below. Do not include analysis, explanation, apologies, markdown, or any text before or after the JSON object.",
|
|
50
|
-
]
|
|
53
|
+
]
|
|
54
|
+
.filter((instruction) => !isUndefined(instruction))
|
|
55
|
+
.join("\n\n");
|
|
51
56
|
try {
|
|
52
57
|
const outputContract = await readFile(join(this.pathname, "output-contract.md"), "utf-8");
|
|
53
58
|
return [
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { MagiError } from "@/magi";
|
|
2
2
|
import { retry } from "@/utils";
|
|
3
|
-
export async function editCycles(
|
|
3
|
+
export async function editCycles(cb) {
|
|
4
4
|
this.context.abort.throwIfAborted();
|
|
5
5
|
const error = new Error("Continue edit cycle.");
|
|
6
6
|
const verdict = await retry(async (cycle) => {
|
|
7
|
-
const verdict = await
|
|
7
|
+
const verdict = await cb(cycle);
|
|
8
8
|
if (verdict === "CHANGES_REQUESTED")
|
|
9
9
|
throw error;
|
|
10
10
|
return verdict;
|
|
@@ -23,9 +23,8 @@ export async function edit() {
|
|
|
23
23
|
repo: this.config.github.repo,
|
|
24
24
|
worktreePath: this.state.worktree.path,
|
|
25
25
|
});
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
const raw = await this.magi.promptSession(this.state.editor.sessionId, count === 1 ? taskMessage : repairMessage, this.context.abort);
|
|
26
|
+
const output = await retry(async (count, e) => {
|
|
27
|
+
const raw = await this.magi.promptSession(this.state.editor.sessionId, count === 1 ? taskMessage : await prompt.repair(e), this.context.abort);
|
|
29
28
|
await this.createAgentFile("edit", "editor", raw, count, cycle);
|
|
30
29
|
const parsed = prompt.parse(raw);
|
|
31
30
|
if (!prompt.validate(parsed))
|
|
@@ -111,9 +110,8 @@ export async function resolveConflict() {
|
|
|
111
110
|
repo: this.config.github.repo,
|
|
112
111
|
worktreePath: this.state.worktree.path,
|
|
113
112
|
});
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
const raw = await this.magi.promptSession(this.state.editor.sessionId, count === 1 ? taskMessage : repairMessage, this.context.abort);
|
|
113
|
+
const output = await retry(async (count, e) => {
|
|
114
|
+
const raw = await this.magi.promptSession(this.state.editor.sessionId, count === 1 ? taskMessage : await prompt.repair(e), this.context.abort);
|
|
117
115
|
await this.createAgentFile("conflict", "editor", raw, count, cycle);
|
|
118
116
|
const parsed = prompt.parse(raw);
|
|
119
117
|
if (!prompt.validate(parsed))
|
|
@@ -78,9 +78,8 @@ export async function postReviews() {
|
|
|
78
78
|
repo: this.config.github.repo,
|
|
79
79
|
verdict: this.state.pr.verdict,
|
|
80
80
|
});
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
const raw = await this.magi.promptSession(this.state.operator.sessionId, count === 1 ? taskMessage : repairMessage, this.context.abort);
|
|
81
|
+
const output = await retry(async (count, e) => {
|
|
82
|
+
const raw = await this.magi.promptSession(this.state.operator.sessionId, count === 1 ? taskMessage : await prompt.repair(e), this.context.abort);
|
|
84
83
|
await this.createAgentFile("comment", "operator", raw, count);
|
|
85
84
|
const parsed = prompt.parse(raw);
|
|
86
85
|
if (!prompt.validate(parsed))
|
|
@@ -101,13 +101,12 @@ export async function classifyChecks() {
|
|
|
101
101
|
repo: this.config.github.repo,
|
|
102
102
|
worktreePath: this.state.worktree.path,
|
|
103
103
|
});
|
|
104
|
-
const repairMessage = await prompt.repair();
|
|
105
104
|
await Promise.all(Object.entries(this.state.reviewers ?? {}).map(([id, { sessionId }]) => worker.run(async () => {
|
|
106
105
|
if (!sessionId)
|
|
107
106
|
throw new Error(`No session ID found for reviewer ${id}.`);
|
|
108
107
|
await this.updateEvent(`Classifying CI checks with reviewer ${id}.`);
|
|
109
|
-
const output = await retry(async (count) => {
|
|
110
|
-
const raw = await this.magi.promptSession(sessionId, count === 1 ? taskMessage :
|
|
108
|
+
const output = await retry(async (count, e) => {
|
|
109
|
+
const raw = await this.magi.promptSession(sessionId, count === 1 ? taskMessage : await prompt.repair(e), this.context.abort);
|
|
111
110
|
await this.createAgentFile("ci-classification", id, raw, count);
|
|
112
111
|
const parsed = prompt.parse(raw);
|
|
113
112
|
if (!prompt.validate(parsed))
|
|
@@ -70,10 +70,10 @@ export async function review() {
|
|
|
70
70
|
}), null, 2);
|
|
71
71
|
tags.push(["previous_review", previousReviewContext]);
|
|
72
72
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
tags.push([
|
|
74
|
+
"unresolved_threads",
|
|
75
|
+
JSON.stringify(unresolvedThreads, null, 2),
|
|
76
|
+
]);
|
|
77
77
|
if (failedChecks.length) {
|
|
78
78
|
const ciFailureContext = JSON.stringify(failedChecks, null, 2);
|
|
79
79
|
tags.push(["ci_failure", ciFailureContext]);
|
|
@@ -94,9 +94,8 @@ export async function review() {
|
|
|
94
94
|
repo: this.config.github.repo,
|
|
95
95
|
worktreePath: this.state.worktree.path,
|
|
96
96
|
}));
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
const raw = await this.magi.promptSession(sessionId, count === 1 ? taskMessage : repairMessage, this.context.abort);
|
|
97
|
+
const output = await retry(async (count, e) => {
|
|
98
|
+
const raw = await this.magi.promptSession(sessionId, count === 1 ? taskMessage : await prompt.repair(e), this.context.abort);
|
|
100
99
|
await this.createAgentFile(rereview ? "rereview" : "review", id, raw, count, cycle);
|
|
101
100
|
const parsed = prompt.parse(raw);
|
|
102
101
|
if (!prompt.validate(parsed))
|
|
@@ -199,10 +198,9 @@ export async function reconsiderClose() {
|
|
|
199
198
|
pr: this.number.toString(),
|
|
200
199
|
repo: this.config.github.repo,
|
|
201
200
|
});
|
|
202
|
-
const repairMessage = await prompt.repair();
|
|
203
201
|
await this.updateEvent(`Reconsidering close verdict with reviewer ${id}.`);
|
|
204
|
-
const output = await retry(async (count) => {
|
|
205
|
-
const raw = await this.magi.promptSession(sessionId, count === 1 ? taskMessage :
|
|
202
|
+
const output = await retry(async (count, e) => {
|
|
203
|
+
const raw = await this.magi.promptSession(sessionId, count === 1 ? taskMessage : await prompt.repair(e), this.context.abort);
|
|
206
204
|
await this.createAgentFile("close-reconsideration", id, raw, count, cycle);
|
|
207
205
|
const parsed = prompt.parse(raw);
|
|
208
206
|
if (!prompt.validate(parsed))
|
|
@@ -267,9 +265,8 @@ async function collectAcceptedFindings(findings) {
|
|
|
267
265
|
pr: this.number.toString(),
|
|
268
266
|
repo: this.config.github.repo,
|
|
269
267
|
});
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
const raw = await this.magi.promptSession(sessionId, count === 1 ? taskMessage : repairMessage, this.context.abort);
|
|
268
|
+
const output = await retry(async (count, e) => {
|
|
269
|
+
const raw = await this.magi.promptSession(sessionId, count === 1 ? taskMessage : await prompt.repair(e), this.context.abort);
|
|
273
270
|
await this.createAgentFile("finding-validation", id, raw, count);
|
|
274
271
|
const parsed = prompt.parse(raw);
|
|
275
272
|
if (!prompt.validate(parsed))
|
|
@@ -386,12 +383,15 @@ function validateInlineCommentTargets(status, output, inlineCommentTargets) {
|
|
|
386
383
|
}
|
|
387
384
|
function validateThreadTargets({ followUps, resolves }, threads) {
|
|
388
385
|
const targets = new Map(threads.flatMap(({ comments, id }) => comments.flatMap(({ databaseId }) => databaseId == null ? [] : [[databaseId, id]])));
|
|
386
|
+
const allowedTargets = [...targets.entries()]
|
|
387
|
+
.map(([commentId, threadId]) => `- ${commentId}:${threadId}`)
|
|
388
|
+
.join("\n") || "none";
|
|
389
389
|
if (followUps)
|
|
390
390
|
for (const [index, { commentId }] of followUps.entries())
|
|
391
391
|
if (!targets.has(commentId))
|
|
392
|
-
throw new Error(`followUps[${index}].commentId must target an unresolved thread owned by the reviewer
|
|
392
|
+
throw new Error(`followUps[${index}].commentId must target an unresolved thread owned by the reviewer.\n\nAllowed targets:\n${allowedTargets}`);
|
|
393
393
|
if (resolves)
|
|
394
394
|
for (const [index, { commentId, threadId }] of resolves.entries())
|
|
395
395
|
if (targets.get(commentId) !== threadId)
|
|
396
|
-
throw new Error(`resolves[${index}] must target an unresolved thread owned by the reviewer
|
|
396
|
+
throw new Error(`resolves[${index}] must target an unresolved thread owned by the reviewer.\n\nAllowed targets:\n${allowedTargets}`);
|
|
397
397
|
}
|
package/dist/utils/function.js
CHANGED
|
@@ -12,24 +12,26 @@ export function wait(ms = 0, signal) {
|
|
|
12
12
|
signal?.addEventListener("abort", abort, { once: true });
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
|
-
export async function loop(
|
|
15
|
+
export async function loop(cb, ms = 0) {
|
|
16
16
|
for (;;) {
|
|
17
|
-
const value = await
|
|
17
|
+
const value = await cb();
|
|
18
18
|
if (value != null)
|
|
19
19
|
return value;
|
|
20
20
|
await wait(ms);
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
-
export async function retry(
|
|
23
|
+
export async function retry(cb, { error: errorCb, retries = 1, signal }) {
|
|
24
24
|
let count = 1;
|
|
25
|
+
let error;
|
|
25
26
|
while (count <= retries)
|
|
26
27
|
try {
|
|
27
28
|
signal?.throwIfAborted();
|
|
28
|
-
return await
|
|
29
|
+
return await cb(count, error);
|
|
29
30
|
}
|
|
30
31
|
catch (e) {
|
|
31
32
|
signal?.throwIfAborted();
|
|
32
|
-
await
|
|
33
|
+
await errorCb?.(e, count);
|
|
34
|
+
error = e;
|
|
33
35
|
count += 1;
|
|
34
36
|
}
|
|
35
37
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-magi",
|
|
3
|
-
"version": "0.0.0-dev-
|
|
3
|
+
"version": "0.0.0-dev-20260721055255",
|
|
4
4
|
"description": "Multi-agent PR review and merge orchestration plugin for OpenCode.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Hirotomo Yamada <hirotomo.yamada@avap.co.jp>",
|