opencode-magi 0.0.0-dev-20260525165911 → 0.0.0-dev-20260525211234
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/config/resolve.js +6 -8
- package/dist/config/validate.js +33 -23
- package/dist/orchestrator/merge.js +3 -3
- package/dist/orchestrator/review.js +13 -11
- package/package.json +1 -1
- package/schema.json +3 -3
package/dist/config/resolve.js
CHANGED
|
@@ -113,9 +113,8 @@ export function resolveAgents(config) {
|
|
|
113
113
|
const agents = config.agents ?? {};
|
|
114
114
|
const editor = config.merge?.editor;
|
|
115
115
|
const creator = config.triage?.creator;
|
|
116
|
-
const singleReviewAccount = config.review && config.
|
|
117
|
-
|
|
118
|
-
: undefined;
|
|
116
|
+
const singleReviewAccount = config.review && config.mode !== "multi" ? config.account : undefined;
|
|
117
|
+
const singleTriageAccount = config.triage && config.mode !== "multi" ? config.account : undefined;
|
|
119
118
|
return {
|
|
120
119
|
editor: editor
|
|
121
120
|
? {
|
|
@@ -134,6 +133,7 @@ export function resolveAgents(config) {
|
|
|
134
133
|
})),
|
|
135
134
|
triage: (config.triage?.voters ?? []).map((agent, index) => ({
|
|
136
135
|
...agent,
|
|
136
|
+
account: singleTriageAccount ?? agent.account ?? "",
|
|
137
137
|
key: triageAgentKey(agent, index),
|
|
138
138
|
index,
|
|
139
139
|
model: normalizedModel(agent.model),
|
|
@@ -142,7 +142,7 @@ export function resolveAgents(config) {
|
|
|
142
142
|
triageCreator: creator
|
|
143
143
|
? {
|
|
144
144
|
...creator,
|
|
145
|
-
account: creator.account ?? "",
|
|
145
|
+
account: singleTriageAccount ?? creator.account ?? "",
|
|
146
146
|
model: normalizedModel(creator.model),
|
|
147
147
|
permission: resolveTriageCreatorPermission(agents, creator),
|
|
148
148
|
}
|
|
@@ -188,6 +188,8 @@ export function resolveRepository(config) {
|
|
|
188
188
|
repo: config.github.repo,
|
|
189
189
|
},
|
|
190
190
|
language: config.language,
|
|
191
|
+
account: config.account,
|
|
192
|
+
mode: config.mode ?? "single",
|
|
191
193
|
merge: {
|
|
192
194
|
approvalPolicy: config.review?.merge?.approvalPolicy ?? "majority",
|
|
193
195
|
method: config.review?.merge?.method ?? "squash",
|
|
@@ -208,10 +210,6 @@ export function resolveRepository(config) {
|
|
|
208
210
|
review: config.review?.prompts?.review,
|
|
209
211
|
reviewGuidelines: config.review?.prompts?.reviewGuidelines,
|
|
210
212
|
},
|
|
211
|
-
review: {
|
|
212
|
-
account: config.review?.account,
|
|
213
|
-
mode: config.review?.mode ?? "single",
|
|
214
|
-
},
|
|
215
213
|
reviewAutomation: {
|
|
216
214
|
close: config.review?.automation?.close ?? false,
|
|
217
215
|
merge: config.review?.automation?.merge ?? true,
|
package/dist/config/validate.js
CHANGED
|
@@ -13,11 +13,13 @@ const TRIAGE_CATEGORY_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
|
13
13
|
const RESERVED_TRIAGE_CATEGORY_IDS = new Set(["ASK", "none"]);
|
|
14
14
|
const CONFIG_KEYS = new Set([
|
|
15
15
|
"$schema",
|
|
16
|
+
"account",
|
|
16
17
|
"agents",
|
|
17
18
|
"clear",
|
|
18
19
|
"github",
|
|
19
20
|
"language",
|
|
20
21
|
"merge",
|
|
22
|
+
"mode",
|
|
21
23
|
"output",
|
|
22
24
|
"review",
|
|
23
25
|
"triage",
|
|
@@ -54,12 +56,10 @@ const TRIAGE_CREATOR_KEYS = new Set([
|
|
|
54
56
|
const AUTHOR_KEYS = new Set(["email", "name"]);
|
|
55
57
|
const GITHUB_KEYS = new Set(["apiRetryAttempts", "host", "owner", "repo"]);
|
|
56
58
|
const REVIEW_KEYS = new Set([
|
|
57
|
-
"account",
|
|
58
59
|
"automation",
|
|
59
60
|
"checks",
|
|
60
61
|
"concurrency",
|
|
61
62
|
"merge",
|
|
62
|
-
"mode",
|
|
63
63
|
"output",
|
|
64
64
|
"prompts",
|
|
65
65
|
"reviewers",
|
|
@@ -421,7 +421,7 @@ function validateReviewerList(reviewers, path, errors, catalog, mode = "single")
|
|
|
421
421
|
}
|
|
422
422
|
});
|
|
423
423
|
}
|
|
424
|
-
function validateTriageAgentList(voters, path, errors, catalog) {
|
|
424
|
+
function validateTriageAgentList(voters, path, errors, catalog, mode = "single") {
|
|
425
425
|
if (voters == null)
|
|
426
426
|
return;
|
|
427
427
|
if (!Array.isArray(voters)) {
|
|
@@ -441,8 +441,10 @@ function validateTriageAgentList(voters, path, errors, catalog) {
|
|
|
441
441
|
if (!agent.model)
|
|
442
442
|
errors.push(`${path}[${index}].model is required`);
|
|
443
443
|
validateAndNormalizeModel(agent, `${path}[${index}].model`, errors, catalog);
|
|
444
|
-
if (!agent.account)
|
|
444
|
+
if (mode === "multi" && !agent.account)
|
|
445
445
|
errors.push(`${path}[${index}].account is required`);
|
|
446
|
+
if (mode === "single" && agent.account)
|
|
447
|
+
errors.push(`${path}[${index}].account is not supported in single mode`);
|
|
446
448
|
validateString(agent.account, `${path}[${index}].account`, errors);
|
|
447
449
|
validateString(agent.persona, `${path}[${index}].persona`, errors);
|
|
448
450
|
validatePermissionConfig(agent.permissions, `${path}[${index}].permissions`, errors);
|
|
@@ -469,26 +471,25 @@ function validateResolvedReviewers(reviewers, path, errors, mode = "single") {
|
|
|
469
471
|
}
|
|
470
472
|
}
|
|
471
473
|
function reviewMode(config) {
|
|
472
|
-
return config.
|
|
474
|
+
return config.mode === "multi" ? "multi" : "single";
|
|
473
475
|
}
|
|
474
476
|
function validateReviewIdentity(config, errors) {
|
|
475
|
-
const mode = config.
|
|
477
|
+
const mode = config.mode;
|
|
476
478
|
if (mode != null && mode !== "multi" && mode !== "single") {
|
|
477
|
-
errors.push("
|
|
479
|
+
errors.push("mode must be multi or single");
|
|
478
480
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
errors.push("review.account is required when review.mode is single");
|
|
481
|
+
if ((mode == null || mode === "single") && !config.account) {
|
|
482
|
+
errors.push("account is required when mode is single");
|
|
482
483
|
}
|
|
483
484
|
}
|
|
484
|
-
function validateResolvedTriageAgents(agents, path, errors) {
|
|
485
|
+
function validateResolvedTriageAgents(agents, path, errors, mode = "single") {
|
|
485
486
|
const keys = new Set();
|
|
486
487
|
const accounts = new Set();
|
|
487
488
|
for (const agent of agents) {
|
|
488
489
|
if (keys.has(agent.key))
|
|
489
490
|
errors.push(`${path} has duplicate agent key: ${agent.key}`);
|
|
490
491
|
keys.add(agent.key);
|
|
491
|
-
if (accounts.has(agent.account))
|
|
492
|
+
if (mode === "multi" && accounts.has(agent.account))
|
|
492
493
|
errors.push(`${path} has duplicate agent account: ${agent.account}`);
|
|
493
494
|
accounts.add(agent.account);
|
|
494
495
|
}
|
|
@@ -532,7 +533,7 @@ function validateEditor(editor, path, errors, catalog) {
|
|
|
532
533
|
}
|
|
533
534
|
}
|
|
534
535
|
}
|
|
535
|
-
function validateTriageCreator(creator, path, errors, catalog) {
|
|
536
|
+
function validateTriageCreator(creator, path, errors, catalog, mode = "single") {
|
|
536
537
|
if (!creator)
|
|
537
538
|
return;
|
|
538
539
|
if (!isPlainObject(creator)) {
|
|
@@ -542,6 +543,8 @@ function validateTriageCreator(creator, path, errors, catalog) {
|
|
|
542
543
|
validateKnownKeys(creator, path, TRIAGE_CREATOR_KEYS, errors);
|
|
543
544
|
if (!creator.model)
|
|
544
545
|
errors.push(`${path}.model is required`);
|
|
546
|
+
if (mode === "single" && creator.account)
|
|
547
|
+
errors.push(`${path}.account is not supported in single mode`);
|
|
545
548
|
validateString(creator.account, `${path}.account`, errors);
|
|
546
549
|
validateAndNormalizeModel(creator, `${path}.model`, errors, catalog);
|
|
547
550
|
validateString(creator.persona, `${path}.persona`, errors);
|
|
@@ -830,6 +833,7 @@ function validatePromptObject(prompts, path, keys, errors) {
|
|
|
830
833
|
}
|
|
831
834
|
function validateTriage(config, errors, options) {
|
|
832
835
|
const triage = config.triage;
|
|
836
|
+
const mode = reviewMode(config);
|
|
833
837
|
if (!triage)
|
|
834
838
|
return;
|
|
835
839
|
if (!isPlainObject(triage)) {
|
|
@@ -844,7 +848,7 @@ function validateTriage(config, errors, options) {
|
|
|
844
848
|
const safety = triage.safety;
|
|
845
849
|
if (!triage.voters)
|
|
846
850
|
errors.push("triage.voters is required");
|
|
847
|
-
validateTriageAgentList(triage.voters, "triage.voters", errors, options.modelCatalog);
|
|
851
|
+
validateTriageAgentList(triage.voters, "triage.voters", errors, options.modelCatalog, mode);
|
|
848
852
|
if (Array.isArray(triage.voters)) {
|
|
849
853
|
const resolvedTriageAgents = triage.voters.map((agent, index) => ({
|
|
850
854
|
account: agent && typeof agent === "object" && typeof agent.account === "string"
|
|
@@ -852,17 +856,21 @@ function validateTriage(config, errors, options) {
|
|
|
852
856
|
: "",
|
|
853
857
|
key: agent && typeof agent === "object" ? triageAgentKey(agent, index) : "",
|
|
854
858
|
}));
|
|
855
|
-
validateResolvedTriageAgents(resolvedTriageAgents, "triage.resolvedAgents", errors);
|
|
856
|
-
if (
|
|
859
|
+
validateResolvedTriageAgents(resolvedTriageAgents, "triage.resolvedAgents", errors, mode);
|
|
860
|
+
if (mode === "multi" &&
|
|
861
|
+
reporter != null &&
|
|
857
862
|
!resolvedTriageAgents.some((agent) => agent.key === reporter)) {
|
|
858
863
|
errors.push(`triage.reporter must match a triage voter key: ${reporter}`);
|
|
859
864
|
}
|
|
860
865
|
}
|
|
866
|
+
if (mode === "single" && reporter != null) {
|
|
867
|
+
errors.push("triage.reporter is not supported in single mode");
|
|
868
|
+
}
|
|
861
869
|
validateString(triage.reporter, "triage.reporter", errors);
|
|
862
|
-
validateTriageCreator(creator, "triage.creator", errors, options.modelCatalog);
|
|
870
|
+
validateTriageCreator(creator, "triage.creator", errors, options.modelCatalog, mode);
|
|
863
871
|
if (automation?.create && !creator)
|
|
864
872
|
errors.push("triage.creator is required when triage.automation.create is true");
|
|
865
|
-
if (automation?.create && creator && !creator.account)
|
|
873
|
+
if (mode === "multi" && automation?.create && creator && !creator.account)
|
|
866
874
|
errors.push("triage.creator.account is required when triage.automation.create is true");
|
|
867
875
|
if (automation != null && !isPlainObject(automation)) {
|
|
868
876
|
errors.push("triage.automation must be an object");
|
|
@@ -924,8 +932,8 @@ async function validateAuth(config, exec, errors) {
|
|
|
924
932
|
const accounts = new Set();
|
|
925
933
|
const agents = resolveAgents(config);
|
|
926
934
|
if (reviewMode(config) === "single") {
|
|
927
|
-
if (config.
|
|
928
|
-
accounts.add(config.
|
|
935
|
+
if (config.account)
|
|
936
|
+
accounts.add(config.account);
|
|
929
937
|
}
|
|
930
938
|
else {
|
|
931
939
|
for (const reviewer of agents.reviewers)
|
|
@@ -978,8 +986,8 @@ async function validateRepositoryPermissions(config, exec, errors, warnings) {
|
|
|
978
986
|
return;
|
|
979
987
|
const agents = resolveAgents(config);
|
|
980
988
|
const reviewAccounts = reviewMode(config) === "single"
|
|
981
|
-
? config.
|
|
982
|
-
? [config.
|
|
989
|
+
? config.account
|
|
990
|
+
? [config.account]
|
|
983
991
|
: []
|
|
984
992
|
: agents.reviewers.map((reviewer) => reviewer.account);
|
|
985
993
|
await Promise.all(reviewAccounts.map(async (account) => {
|
|
@@ -1040,7 +1048,10 @@ export async function validateConfig(config, options = {}) {
|
|
|
1040
1048
|
validateJsonSchema(config, errors);
|
|
1041
1049
|
validateKnownKeys(config, "config", CONFIG_KEYS, errors);
|
|
1042
1050
|
validateString(config.$schema, "$schema", errors);
|
|
1051
|
+
validateString(config.account, "account", errors);
|
|
1043
1052
|
validateString(config.language, "language", errors);
|
|
1053
|
+
if (config.review || config.triage)
|
|
1054
|
+
validateReviewIdentity(config, errors);
|
|
1044
1055
|
if (config.agents != null && !isPlainObject(config.agents)) {
|
|
1045
1056
|
errors.push("agents must be an object");
|
|
1046
1057
|
}
|
|
@@ -1059,7 +1070,6 @@ export async function validateConfig(config, options = {}) {
|
|
|
1059
1070
|
else {
|
|
1060
1071
|
validateKnownKeys(config.review, "review", REVIEW_KEYS, errors);
|
|
1061
1072
|
}
|
|
1062
|
-
validateReviewIdentity(config, errors);
|
|
1063
1073
|
if (!config.review.reviewers)
|
|
1064
1074
|
errors.push("review.reviewers is required");
|
|
1065
1075
|
validateReviewerList(config.review.reviewers, "review.reviewers", errors, options.modelCatalog, mode);
|
|
@@ -213,7 +213,7 @@ async function runRereview(input, worktreePath, previousHeadSha, cycle, sessionI
|
|
|
213
213
|
worktreePath,
|
|
214
214
|
});
|
|
215
215
|
const artifactDir = outputDir(input);
|
|
216
|
-
const singleReviewMode = input.repository.
|
|
216
|
+
const singleReviewMode = input.repository.mode !== "multi";
|
|
217
217
|
const reviewerKeys = input.repository.agents.reviewers.map((reviewer) => reviewer.key);
|
|
218
218
|
const singleModeThreads = singleReviewMode
|
|
219
219
|
? assignThreadsByReviewFindingMarker({
|
|
@@ -221,7 +221,7 @@ async function runRereview(input, worktreePath, previousHeadSha, cycle, sessionI
|
|
|
221
221
|
pr: input.pr,
|
|
222
222
|
reviewerKeys,
|
|
223
223
|
threads: options.dryRunThreads == null
|
|
224
|
-
? await fetchUnresolvedThreads(input.exec, input.repository, input.pr, input.repository.
|
|
224
|
+
? await fetchUnresolvedThreads(input.exec, input.repository, input.pr, input.repository.account ?? "")
|
|
225
225
|
: Object.values(options.dryRunThreads).flat(),
|
|
226
226
|
})
|
|
227
227
|
: undefined;
|
|
@@ -412,7 +412,7 @@ async function runRereview(input, worktreePath, previousHeadSha, cycle, sessionI
|
|
|
412
412
|
? { consensus: `dry-run:would-post-single-review:${verdict}` }
|
|
413
413
|
: {
|
|
414
414
|
consensus: await (async () => {
|
|
415
|
-
const account = input.repository.
|
|
415
|
+
const account = input.repository.account ?? "";
|
|
416
416
|
await Promise.all(entries.flatMap((entry) => entry.output.resolve.map((item) => resolveThread(input.exec, input.repository, account, item.threadId))));
|
|
417
417
|
await Promise.all(entries.flatMap((entry) => entry.output.followUps.map((item) => postReply(input.exec, input.repository, input.pr, account, item.commentId, [
|
|
418
418
|
`**Reviewer:** ${entry.reviewer}`,
|
|
@@ -16,12 +16,14 @@ import { formatReviewReport } from "./report";
|
|
|
16
16
|
import { buildReviewContextSnapshot, renderReviewContext, } from "./review-context";
|
|
17
17
|
import { checkSafetyGate, hasSafetyGate } from "./safety";
|
|
18
18
|
function resolvedReviewMode(repository) {
|
|
19
|
-
return repository.
|
|
19
|
+
return repository.mode === "multi" ? "multi" : "single";
|
|
20
20
|
}
|
|
21
21
|
export function reviewPostingAccount(repository, reviewer) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
if (resolvedReviewMode(repository) !== "single")
|
|
23
|
+
return reviewer.account;
|
|
24
|
+
if (repository.account)
|
|
25
|
+
return repository.account;
|
|
26
|
+
throw new Error("account is required for single review mode");
|
|
25
27
|
}
|
|
26
28
|
function reviewAssignmentKey(repository, reviewer) {
|
|
27
29
|
return resolvedReviewMode(repository) === "single"
|
|
@@ -579,9 +581,9 @@ function singleFindingBody(input) {
|
|
|
579
581
|
].join("\n");
|
|
580
582
|
}
|
|
581
583
|
export async function postSingleConsensusReview(input) {
|
|
582
|
-
const account = input.repository.
|
|
584
|
+
const account = input.repository.account;
|
|
583
585
|
if (!account)
|
|
584
|
-
throw new Error("
|
|
586
|
+
throw new Error("account is required for single review mode");
|
|
585
587
|
const body = singleReviewBody(input);
|
|
586
588
|
if (input.verdict === "MERGE") {
|
|
587
589
|
return postApproval(input.exec, input.repository, input.pr, account, body);
|
|
@@ -927,7 +929,7 @@ export async function runReview(input) {
|
|
|
927
929
|
const reviewerAccounts = input.repository.agents.reviewers.map((reviewer) => reviewer.account);
|
|
928
930
|
const preliminaryMode = singleReviewMode
|
|
929
931
|
? resolveSingleAccountReviewMode({
|
|
930
|
-
account: input.repository.
|
|
932
|
+
account: input.repository.account ?? "",
|
|
931
933
|
current: freshnessTarget,
|
|
932
934
|
pr: input.pr,
|
|
933
935
|
reviewerKeys,
|
|
@@ -942,7 +944,7 @@ export async function runReview(input) {
|
|
|
942
944
|
return (preliminaryMode.assignments.get(reviewAssignmentKey(input.repository, reviewer))?.type === "skip");
|
|
943
945
|
});
|
|
944
946
|
if (singleReviewMode) {
|
|
945
|
-
const account = input.repository.
|
|
947
|
+
const account = input.repository.account ?? "";
|
|
946
948
|
const threads = await fetchUnresolvedThreads(exec, input.repository, input.pr, account);
|
|
947
949
|
const assigned = assignThreadsByReviewFindingMarker({
|
|
948
950
|
fallbackReviewerKeys: reviewerKeys,
|
|
@@ -973,7 +975,7 @@ export async function runReview(input) {
|
|
|
973
975
|
const mode = singleReviewMode
|
|
974
976
|
? pendingThreadReplyReviewers.size
|
|
975
977
|
? resolveSingleAccountReviewMode({
|
|
976
|
-
account: input.repository.
|
|
978
|
+
account: input.repository.account ?? "",
|
|
977
979
|
current: freshnessTarget,
|
|
978
980
|
pendingReviewers: pendingThreadReplyReviewers,
|
|
979
981
|
pr: input.pr,
|
|
@@ -1368,7 +1370,7 @@ export async function runReview(input) {
|
|
|
1368
1370
|
consensus: input.dryRun
|
|
1369
1371
|
? `dry-run:would-post-single-review:${verdict}`
|
|
1370
1372
|
: await (async () => {
|
|
1371
|
-
const account = input.repository.
|
|
1373
|
+
const account = input.repository.account ?? "";
|
|
1372
1374
|
await Promise.all(Object.values(activeOutputs).flatMap((output) => {
|
|
1373
1375
|
if (!("resolve" in output))
|
|
1374
1376
|
return [];
|
|
@@ -1414,7 +1416,7 @@ export async function runReview(input) {
|
|
|
1414
1416
|
]))),
|
|
1415
1417
|
};
|
|
1416
1418
|
const automationAccount = singleReviewMode
|
|
1417
|
-
? input.repository.
|
|
1419
|
+
? input.repository.account
|
|
1418
1420
|
: input.repository.agents.reviewers[0]?.account;
|
|
1419
1421
|
const enableReviewAutomation = input.enableReviewAutomation ?? true;
|
|
1420
1422
|
if (enableReviewAutomation &&
|
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-20260525211234",
|
|
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>",
|
package/schema.json
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
"additionalProperties": false,
|
|
6
6
|
"properties": {
|
|
7
7
|
"$schema": { "type": "string" },
|
|
8
|
+
"account": { "type": "string", "minLength": 1 },
|
|
8
9
|
"agents": {
|
|
9
10
|
"type": "object",
|
|
10
11
|
"additionalProperties": false,
|
|
@@ -44,6 +45,7 @@
|
|
|
44
45
|
},
|
|
45
46
|
"language": { "type": "string" },
|
|
46
47
|
"merge": { "$ref": "#/$defs/merge" },
|
|
48
|
+
"mode": { "enum": ["multi", "single"], "default": "single" },
|
|
47
49
|
"output": {
|
|
48
50
|
"type": "object",
|
|
49
51
|
"additionalProperties": false,
|
|
@@ -113,7 +115,7 @@
|
|
|
113
115
|
"triageAgent": {
|
|
114
116
|
"type": "object",
|
|
115
117
|
"if": { "not": { "required": ["ref"] } },
|
|
116
|
-
"then": { "required": ["model"
|
|
118
|
+
"then": { "required": ["model"] },
|
|
117
119
|
"additionalProperties": false,
|
|
118
120
|
"properties": {
|
|
119
121
|
"ref": { "type": "string", "minLength": 1 },
|
|
@@ -366,8 +368,6 @@
|
|
|
366
368
|
"type": "object",
|
|
367
369
|
"additionalProperties": false,
|
|
368
370
|
"properties": {
|
|
369
|
-
"mode": { "enum": ["multi", "single"], "default": "single" },
|
|
370
|
-
"account": { "type": "string", "minLength": 1 },
|
|
371
371
|
"reviewers": {
|
|
372
372
|
"type": "array",
|
|
373
373
|
"minItems": 3,
|