opencode-magi 0.0.0-dev-20260525205150 → 0.0.0-dev-20260525213616

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/README.md CHANGED
@@ -17,7 +17,7 @@ OpenCode Magi recreates the review cycle humans already run on GitHub: multiple
17
17
  - Multi-agent reviews with an odd-number majority of 3 or more reviewers.
18
18
  - Optional unanimous approval policy for merge automation when every reviewer must approve before a PR is merged.
19
19
  - Finding-level voting before posting change requests, so only findings accepted by reviewer majority are submitted.
20
- - Multi-account review mode where each reviewer posts through its configured GitHub account, plus single-account review mode where one GitHub account posts the consensus result for multiple logical reviewers.
20
+ - Single-account identity mode by default, where one GitHub account posts consensus-backed review and triage results for multiple logical agents, plus multi-account mode for setups that need separate GitHub identities.
21
21
  - Re-review support for edited PRs: fixed threads are resolved, satisfied reviewers approve, and remaining issues are posted as additional comments.
22
22
  - Optional merge and close automation where an editor agent responds on behalf of the author, fixes changes it agrees with, pushes commits when needed, and repeats the reviewer/editor cycle until the PR can be approved, queued, merged, or closed.
23
23
  - Per-agent OpenCode permissions for reviewer, CI classifier, and editor child sessions.
@@ -61,6 +61,7 @@ Add the following content to the configuration file.
61
61
  ```json
62
62
  {
63
63
  "$schema": "https://raw.githubusercontent.com/magi-ai/opencode-magi/main/schema.json",
64
+ "account": "your-account",
64
65
  "agents": {
65
66
  "refs": {
66
67
  "account-1": {
@@ -75,7 +76,6 @@ Add the following content to the configuration file.
75
76
  }
76
77
  },
77
78
  "review": {
78
- "account": "your-account",
79
79
  "reviewers": [
80
80
  { "ref": "account-1" },
81
81
  { "ref": "account-2" },
@@ -85,14 +85,14 @@ Add the following content to the configuration file.
85
85
  }
86
86
  ```
87
87
 
88
- By default, `review.mode` is `"single"`. Magi uses one `review.account` to post reviewer-originated GitHub mutations while still running multiple logical reviewer agents and preserving majority voting, finding validation, and close reconsideration. The account must be authenticated with `gh auth token --user <account>`.
88
+ By default, `mode` is `"single"`. Magi uses one top-level `account` to post reviewer- and triage-originated GitHub mutations while still running multiple logical agents and preserving majority voting, finding validation, and close reconsideration. The account must be authenticated with `gh auth token --user <account>`.
89
89
 
90
- For team setups that need separate GitHub review identities, set `review.mode: "multi"` and configure a unique account for each reviewer.
90
+ For advanced team setups that need GitHub to see separate review or triage identities, set top-level `mode: "multi"` and configure unique accounts for each reviewer or triage voter.
91
91
 
92
92
  ```json
93
93
  {
94
+ "mode": "multi",
94
95
  "review": {
95
- "mode": "multi",
96
96
  "reviewers": [
97
97
  { "id": "general", "model": "openai/gpt-5.5", "account": "account-1" },
98
98
  {
@@ -121,6 +121,7 @@ Add the following content to the configuration file.
121
121
  ```json
122
122
  {
123
123
  "$schema": "https://raw.githubusercontent.com/magi-ai/opencode-magi/main/schema.json",
124
+ "account": "your-account",
124
125
  "github": {
125
126
  "owner": "your-owner",
126
127
  "repo": "your-repo"
@@ -128,16 +129,13 @@ Add the following content to the configuration file.
128
129
  "agents": {
129
130
  "refs": {
130
131
  "account-1": {
131
- "model": "openai/gpt-5.5",
132
- "account": "account-1"
132
+ "model": "openai/gpt-5.5"
133
133
  },
134
134
  "account-2": {
135
- "model": "anthropic/claude-opus-4-7",
136
- "account": "account-2"
135
+ "model": "anthropic/claude-opus-4-7"
137
136
  },
138
137
  "account-3": {
139
- "model": "opencode/kimi-k2-6",
140
- "account": "account-3"
138
+ "model": "opencode/kimi-k2-6"
141
139
  },
142
140
  "account-4": {
143
141
  "model": "openai/gpt-5.5",
@@ -182,7 +180,7 @@ Entries with `ref` are expanded from `agents.refs`. Fields set alongside `ref` o
182
180
  }
183
181
  ```
184
182
 
185
- After `refs` are expanded, `review.reviewers[].account` is the GitHub account used to post reviews and approvals in `multi` mode. Must be authenticated with `gh auth token --user <account>` and must be unique. In `single` mode, `review.account` is used for reviewer-originated review posts, approvals, change requests, close comments, reviewer replies, and reviewer thread resolutions. `merge.editor.account` is still used by `/magi:merge` to push fixes, close PRs, and merge PRs.
183
+ After `refs` are expanded, top-level `account` is the GitHub account used for reviewer- and triage-originated posts and mutations in `single` mode. In `multi` mode, `review.reviewers[].account` and `triage.voters[].account` are used instead and must be unique within their agent lists. `merge.editor.account` is still used by `/magi:merge` to push fixes, close PRs, and merge PRs.
186
184
 
187
185
  #### Validate config
188
186
 
@@ -114,6 +114,7 @@ export function resolveAgents(config) {
114
114
  const editor = config.merge?.editor;
115
115
  const creator = config.triage?.creator;
116
116
  const singleReviewAccount = config.review && config.mode !== "multi" ? config.account : undefined;
117
+ const singleTriageAccount = config.triage && config.mode !== "multi" ? config.account : undefined;
117
118
  return {
118
119
  editor: editor
119
120
  ? {
@@ -132,6 +133,7 @@ export function resolveAgents(config) {
132
133
  })),
133
134
  triage: (config.triage?.voters ?? []).map((agent, index) => ({
134
135
  ...agent,
136
+ account: singleTriageAccount ?? agent.account ?? "",
135
137
  key: triageAgentKey(agent, index),
136
138
  index,
137
139
  model: normalizedModel(agent.model),
@@ -140,7 +142,7 @@ export function resolveAgents(config) {
140
142
  triageCreator: creator
141
143
  ? {
142
144
  ...creator,
143
- account: creator.account ?? "",
145
+ account: singleTriageAccount ?? creator.account ?? "",
144
146
  model: normalizedModel(creator.model),
145
147
  permission: resolveTriageCreatorPermission(agents, creator),
146
148
  }
@@ -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);
@@ -480,14 +482,14 @@ function validateReviewIdentity(config, errors) {
480
482
  errors.push("account is required when mode is single");
481
483
  }
482
484
  }
483
- function validateResolvedTriageAgents(agents, path, errors) {
485
+ function validateResolvedTriageAgents(agents, path, errors, mode = "single") {
484
486
  const keys = new Set();
485
487
  const accounts = new Set();
486
488
  for (const agent of agents) {
487
489
  if (keys.has(agent.key))
488
490
  errors.push(`${path} has duplicate agent key: ${agent.key}`);
489
491
  keys.add(agent.key);
490
- if (accounts.has(agent.account))
492
+ if (mode === "multi" && accounts.has(agent.account))
491
493
  errors.push(`${path} has duplicate agent account: ${agent.account}`);
492
494
  accounts.add(agent.account);
493
495
  }
@@ -531,7 +533,7 @@ function validateEditor(editor, path, errors, catalog) {
531
533
  }
532
534
  }
533
535
  }
534
- function validateTriageCreator(creator, path, errors, catalog) {
536
+ function validateTriageCreator(creator, path, errors, catalog, mode = "single") {
535
537
  if (!creator)
536
538
  return;
537
539
  if (!isPlainObject(creator)) {
@@ -541,6 +543,8 @@ function validateTriageCreator(creator, path, errors, catalog) {
541
543
  validateKnownKeys(creator, path, TRIAGE_CREATOR_KEYS, errors);
542
544
  if (!creator.model)
543
545
  errors.push(`${path}.model is required`);
546
+ if (mode === "single" && creator.account)
547
+ errors.push(`${path}.account is not supported in single mode`);
544
548
  validateString(creator.account, `${path}.account`, errors);
545
549
  validateAndNormalizeModel(creator, `${path}.model`, errors, catalog);
546
550
  validateString(creator.persona, `${path}.persona`, errors);
@@ -829,6 +833,7 @@ function validatePromptObject(prompts, path, keys, errors) {
829
833
  }
830
834
  function validateTriage(config, errors, options) {
831
835
  const triage = config.triage;
836
+ const mode = reviewMode(config);
832
837
  if (!triage)
833
838
  return;
834
839
  if (!isPlainObject(triage)) {
@@ -843,7 +848,7 @@ function validateTriage(config, errors, options) {
843
848
  const safety = triage.safety;
844
849
  if (!triage.voters)
845
850
  errors.push("triage.voters is required");
846
- validateTriageAgentList(triage.voters, "triage.voters", errors, options.modelCatalog);
851
+ validateTriageAgentList(triage.voters, "triage.voters", errors, options.modelCatalog, mode);
847
852
  if (Array.isArray(triage.voters)) {
848
853
  const resolvedTriageAgents = triage.voters.map((agent, index) => ({
849
854
  account: agent && typeof agent === "object" && typeof agent.account === "string"
@@ -851,17 +856,21 @@ function validateTriage(config, errors, options) {
851
856
  : "",
852
857
  key: agent && typeof agent === "object" ? triageAgentKey(agent, index) : "",
853
858
  }));
854
- validateResolvedTriageAgents(resolvedTriageAgents, "triage.resolvedAgents", errors);
855
- if (reporter != null &&
859
+ validateResolvedTriageAgents(resolvedTriageAgents, "triage.resolvedAgents", errors, mode);
860
+ if (mode === "multi" &&
861
+ reporter != null &&
856
862
  !resolvedTriageAgents.some((agent) => agent.key === reporter)) {
857
863
  errors.push(`triage.reporter must match a triage voter key: ${reporter}`);
858
864
  }
859
865
  }
866
+ if (mode === "single" && reporter != null) {
867
+ errors.push("triage.reporter is not supported in single mode");
868
+ }
860
869
  validateString(triage.reporter, "triage.reporter", errors);
861
- validateTriageCreator(creator, "triage.creator", errors, options.modelCatalog);
870
+ validateTriageCreator(creator, "triage.creator", errors, options.modelCatalog, mode);
862
871
  if (automation?.create && !creator)
863
872
  errors.push("triage.creator is required when triage.automation.create is true");
864
- if (automation?.create && creator && !creator.account)
873
+ if (mode === "multi" && automation?.create && creator && !creator.account)
865
874
  errors.push("triage.creator.account is required when triage.automation.create is true");
866
875
  if (automation != null && !isPlainObject(automation)) {
867
876
  errors.push("triage.automation must be an object");
@@ -1041,6 +1050,8 @@ export async function validateConfig(config, options = {}) {
1041
1050
  validateString(config.$schema, "$schema", errors);
1042
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-magi",
3
- "version": "0.0.0-dev-20260525205150",
3
+ "version": "0.0.0-dev-20260525213616",
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
@@ -115,7 +115,7 @@
115
115
  "triageAgent": {
116
116
  "type": "object",
117
117
  "if": { "not": { "required": ["ref"] } },
118
- "then": { "required": ["model", "account"] },
118
+ "then": { "required": ["model"] },
119
119
  "additionalProperties": false,
120
120
  "properties": {
121
121
  "ref": { "type": "string", "minLength": 1 },