opencode-magi 0.0.0-dev-20260522220245 → 0.0.0-dev-20260522220355

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.
@@ -392,19 +392,43 @@ export async function removeIssueLabels(exec, repository, issue, labels, account
392
392
  return removed;
393
393
  }
394
394
  export async function fetchPullRequestReviews(exec, repository, pr) {
395
- const query = `query($owner: String!, $repo: String!, $pr: Int!) { repository(owner: $owner, name: $repo) { pullRequest(number: $pr) { reviews(first: 100) { nodes { author { login } submittedAt state body commit { oid } comments(first: 100) { nodes { body path line startLine } } } } } } }`;
396
- const raw = await exec(`gh api${ghHostOption(repository)} graphql -f query=${shellQuote(query)} -F owner=${shellQuote(repository.github.owner)} -F repo=${shellQuote(repository.github.repo)} -F pr=${pr}`);
397
- const data = JSON.parse(raw);
398
- return data.data.repository.pullRequest.reviews.nodes.map((review) => ({
395
+ const query = `query($owner: String!, $repo: String!, $pr: Int!, $cursor: String) { repository(owner: $owner, name: $repo) { pullRequest(number: $pr) { reviews(first: 100, after: $cursor) { nodes { author { login } submittedAt state body commit { oid } comments(first: 100) { nodes { body path line startLine } } } pageInfo { hasNextPage endCursor } } } } }`;
396
+ const reviews = [];
397
+ let cursor;
398
+ for (;;) {
399
+ const cursorFlag = cursor ? ` -F cursor=${shellQuote(cursor)}` : "";
400
+ const raw = await exec(`gh api${ghHostOption(repository)} graphql -f query=${shellQuote(query)} -F owner=${shellQuote(repository.github.owner)} -F repo=${shellQuote(repository.github.repo)} -F pr=${pr}${cursorFlag}`);
401
+ const data = JSON.parse(raw);
402
+ const connection = data.data.repository.pullRequest.reviews;
403
+ reviews.push(...connection.nodes);
404
+ if (!connection.pageInfo?.hasNextPage)
405
+ break;
406
+ cursor = connection.pageInfo.endCursor;
407
+ if (!cursor)
408
+ throw new Error("GitHub reviews page was truncated");
409
+ }
410
+ return reviews.map((review) => ({
399
411
  ...review,
400
412
  comments: review.comments?.nodes ?? [],
401
413
  }));
402
414
  }
403
415
  export async function fetchPullRequestCommits(exec, repository, pr) {
404
- const query = `query($owner: String!, $repo: String!, $pr: Int!) { repository(owner: $owner, name: $repo) { pullRequest(number: $pr) { commits(first: 100) { nodes { commit { oid committedDate parents { totalCount } } } } } } }`;
405
- const raw = await exec(`gh api${ghHostOption(repository)} graphql -f query=${shellQuote(query)} -F owner=${shellQuote(repository.github.owner)} -F repo=${shellQuote(repository.github.repo)} -F pr=${pr}`);
406
- const data = JSON.parse(raw);
407
- return data.data.repository.pullRequest.commits.nodes.map(({ commit }) => ({
416
+ const query = `query($owner: String!, $repo: String!, $pr: Int!, $cursor: String) { repository(owner: $owner, name: $repo) { pullRequest(number: $pr) { commits(first: 100, after: $cursor) { nodes { commit { oid committedDate parents { totalCount } } } pageInfo { hasNextPage endCursor } } } } }`;
417
+ const commits = [];
418
+ let cursor;
419
+ for (;;) {
420
+ const cursorFlag = cursor ? ` -F cursor=${shellQuote(cursor)}` : "";
421
+ const raw = await exec(`gh api${ghHostOption(repository)} graphql -f query=${shellQuote(query)} -F owner=${shellQuote(repository.github.owner)} -F repo=${shellQuote(repository.github.repo)} -F pr=${pr}${cursorFlag}`);
422
+ const data = JSON.parse(raw);
423
+ const connection = data.data.repository.pullRequest.commits;
424
+ commits.push(...connection.nodes);
425
+ if (!connection.pageInfo?.hasNextPage)
426
+ break;
427
+ cursor = connection.pageInfo.endCursor;
428
+ if (!cursor)
429
+ throw new Error("GitHub commits page was truncated");
430
+ }
431
+ return commits.map(({ commit }) => ({
408
432
  committedDate: commit.committedDate,
409
433
  oid: commit.oid,
410
434
  parentCount: commit.parents.totalCount,
@@ -664,11 +688,49 @@ export async function configureGitIdentity(exec, worktreePath, identity) {
664
688
  }
665
689
  }
666
690
  export async function fetchUnresolvedThreads(exec, repository, pr, author) {
667
- const query = `query($owner: String!, $repo: String!, $pr: Int!) { repository(owner: $owner, name: $repo) { pullRequest(number: $pr) { reviewThreads(first: 100) { nodes { id isResolved comments(first: 50) { nodes { databaseId author { login } path line body createdAt } } } } } } }`;
668
- const raw = await exec(`gh api${ghHostOption(repository)} graphql -f query=${shellQuote(query)} -F owner=${shellQuote(repository.github.owner)} -F repo=${shellQuote(repository.github.repo)} -F pr=${pr}`);
669
- const data = JSON.parse(raw);
670
- const threads = data.data.repository.pullRequest.reviewThreads
671
- .nodes;
691
+ const threadQuery = `query($owner: String!, $repo: String!, $pr: Int!, $cursor: String) { repository(owner: $owner, name: $repo) { pullRequest(number: $pr) { reviewThreads(first: 100, after: $cursor) { nodes { id isResolved comments(first: 100) { nodes { databaseId author { login } path line body createdAt } pageInfo { hasNextPage endCursor } } } pageInfo { hasNextPage endCursor } } } } }`;
692
+ const commentQuery = `query($threadId: ID!, $cursor: String) { node(id: $threadId) { ... on PullRequestReviewThread { comments(first: 100, after: $cursor) { nodes { databaseId author { login } path line body createdAt } pageInfo { hasNextPage endCursor } } } } }`;
693
+ const threads = [];
694
+ let cursor;
695
+ async function fetchRemainingComments(threadId, initialCursor) {
696
+ const comments = [];
697
+ let commentsCursor = initialCursor;
698
+ for (;;) {
699
+ const raw = await exec(`gh api${ghHostOption(repository)} graphql -f query=${shellQuote(commentQuery)} -F threadId=${shellQuote(threadId)} -F cursor=${shellQuote(commentsCursor)}`);
700
+ const data = JSON.parse(raw);
701
+ const connection = data.data.node?.comments;
702
+ if (!connection)
703
+ throw new Error("GitHub review thread comments were missing");
704
+ comments.push(...connection.nodes);
705
+ if (!connection.pageInfo?.hasNextPage)
706
+ break;
707
+ const nextCursor = connection.pageInfo.endCursor;
708
+ if (!nextCursor)
709
+ throw new Error("GitHub review thread comments page was truncated");
710
+ commentsCursor = nextCursor;
711
+ }
712
+ return comments;
713
+ }
714
+ for (;;) {
715
+ const cursorFlag = cursor ? ` -F cursor=${shellQuote(cursor)}` : "";
716
+ const raw = await exec(`gh api${ghHostOption(repository)} graphql -f query=${shellQuote(threadQuery)} -F owner=${shellQuote(repository.github.owner)} -F repo=${shellQuote(repository.github.repo)} -F pr=${pr}${cursorFlag}`);
717
+ const data = JSON.parse(raw);
718
+ const connection = data.data.repository.pullRequest.reviewThreads;
719
+ for (const thread of connection.nodes) {
720
+ const commentsCursor = thread.comments.pageInfo?.endCursor;
721
+ if (thread.comments.pageInfo?.hasNextPage) {
722
+ if (!commentsCursor)
723
+ throw new Error("GitHub review thread comments page was truncated");
724
+ thread.comments.nodes.push(...(await fetchRemainingComments(thread.id, commentsCursor)));
725
+ }
726
+ }
727
+ threads.push(...connection.nodes);
728
+ if (!connection.pageInfo?.hasNextPage)
729
+ break;
730
+ cursor = connection.pageInfo.endCursor;
731
+ if (!cursor)
732
+ throw new Error("GitHub review threads page was truncated");
733
+ }
672
734
  return threads.flatMap((thread) => {
673
735
  if (thread.isResolved || !thread.comments.nodes.length)
674
736
  return [];
@@ -10,7 +10,7 @@ export function aggregateStringMajority(results, votes) {
10
10
  const voters = Object.fromEntries(votes.map((vote) => [vote, []]));
11
11
  for (const result of results) {
12
12
  counts[result.vote] += 1;
13
- voters[result.vote].push(result.reviewer);
13
+ voters[result.vote].push(result.voter);
14
14
  }
15
15
  const threshold = majorityThreshold(results.length);
16
16
  const vote = votes.find((item) => counts[item] >= threshold);
@@ -1507,56 +1507,56 @@ export class MagiRunManager {
1507
1507
  });
1508
1508
  }
1509
1509
  if (progress.type === "triage_agent_started") {
1510
- const reviewer = state.reviewers[progress.reviewer];
1511
- if (reviewer)
1512
- reviewer.status = "running";
1510
+ const voter = state.reviewers[progress.voter];
1511
+ if (voter)
1512
+ voter.status = "running";
1513
1513
  }
1514
1514
  if (progress.type === "triage_agent_session") {
1515
- const reviewer = state.reviewers[progress.reviewer];
1516
- if (reviewer) {
1515
+ const voter = state.reviewers[progress.voter];
1516
+ if (voter) {
1517
1517
  if (progress.options)
1518
1518
  this.input.setSessionOptions?.(progress.sessionId, progress.options);
1519
- reviewer.sessionId = progress.sessionId;
1520
- reviewer.status = "running";
1521
- reviewer.lastUpdate = now();
1519
+ voter.sessionId = progress.sessionId;
1520
+ voter.status = "running";
1521
+ voter.lastUpdate = now();
1522
1522
  this.sessionToRun.set(progress.sessionId, {
1523
- agent: progress.reviewer,
1523
+ agent: progress.voter,
1524
1524
  runId,
1525
1525
  });
1526
1526
  }
1527
1527
  }
1528
1528
  if (progress.type === "triage_agent_repair") {
1529
- const reviewer = state.reviewers[progress.reviewer];
1530
- if (reviewer) {
1531
- reviewer.status = "repairing";
1532
- reviewer.repairAttempts += 1;
1533
- reviewer.lastUpdate = now();
1529
+ const voter = state.reviewers[progress.voter];
1530
+ if (voter) {
1531
+ voter.status = "repairing";
1532
+ voter.repairAttempts += 1;
1533
+ voter.lastUpdate = now();
1534
1534
  }
1535
1535
  }
1536
1536
  if (progress.type === "triage_agent_response") {
1537
- const reviewer = state.reviewers[progress.reviewer];
1538
- if (reviewer) {
1539
- reviewer.sessionId = progress.sessionId;
1540
- reviewer.lastUpdate = now();
1537
+ const voter = state.reviewers[progress.voter];
1538
+ if (voter) {
1539
+ voter.sessionId = progress.sessionId;
1540
+ voter.lastUpdate = now();
1541
1541
  }
1542
1542
  }
1543
1543
  if (progress.type === "triage_agent_completed") {
1544
- const reviewer = state.reviewers[progress.reviewer];
1545
- if (reviewer) {
1546
- reviewer.sessionId = progress.sessionId;
1547
- reviewer.status = "completed";
1548
- reviewer.verdict = progress.vote;
1549
- reviewer.rawPath = join(state.outputDir, `${progress.reviewer}.${progress.phase}.raw.txt`);
1550
- reviewer.parsedPath = join(state.outputDir, `${progress.reviewer}.${progress.phase}.json`);
1551
- reviewer.lastUpdate = now();
1544
+ const voter = state.reviewers[progress.voter];
1545
+ if (voter) {
1546
+ voter.sessionId = progress.sessionId;
1547
+ voter.status = "completed";
1548
+ voter.verdict = progress.vote;
1549
+ voter.rawPath = join(state.outputDir, `${progress.voter}.${progress.phase}.raw.txt`);
1550
+ voter.parsedPath = join(state.outputDir, `${progress.voter}.${progress.phase}.json`);
1551
+ voter.lastUpdate = now();
1552
1552
  }
1553
1553
  }
1554
1554
  if (progress.type === "triage_agent_failed") {
1555
- const reviewer = state.reviewers[progress.reviewer];
1556
- if (reviewer) {
1557
- reviewer.status = "failed";
1558
- reviewer.error = redactSecrets(progress.error);
1559
- reviewer.lastUpdate = now();
1555
+ const voter = state.reviewers[progress.voter];
1556
+ if (voter) {
1557
+ voter.status = "failed";
1558
+ voter.error = redactSecrets(progress.error);
1559
+ voter.lastUpdate = now();
1560
1560
  }
1561
1561
  }
1562
1562
  if (progress.type === "triage_creator_started") {
@@ -1610,16 +1610,16 @@ export class MagiRunManager {
1610
1610
  }));
1611
1611
  }
1612
1612
  if (progress.type === "triage_agent_started") {
1613
- await this.notify(state, `**Triage agent ${progress.reviewer}** started ${progress.phase} for ${issue}.`);
1613
+ await this.notify(state, `**Triage agent ${progress.voter}** started ${progress.phase} for ${issue}.`);
1614
1614
  }
1615
1615
  if (progress.type === "triage_agent_repair") {
1616
- await this.notify(state, `**Triage agent ${progress.reviewer}** started JSON regeneration for ${issue}.`);
1616
+ await this.notify(state, `**Triage agent ${progress.voter}** started JSON regeneration for ${issue}.`);
1617
1617
  }
1618
1618
  if (progress.type === "triage_agent_completed") {
1619
- await this.notify(state, `**Triage agent ${progress.reviewer}** completed ${progress.phase} for ${issue}: ${progress.vote}.`);
1619
+ await this.notify(state, `**Triage agent ${progress.voter}** completed ${progress.phase} for ${issue}: ${progress.vote}.`);
1620
1620
  }
1621
1621
  if (progress.type === "triage_agent_failed") {
1622
- await this.notify(state, `**Triage agent ${progress.reviewer}** failed ${progress.phase} for ${issue}: ${redactSecrets(progress.error)}`);
1622
+ await this.notify(state, `**Triage agent ${progress.voter}** failed ${progress.phase} for ${issue}: ${redactSecrets(progress.error)}`);
1623
1623
  }
1624
1624
  if (progress.type === "comment_posting") {
1625
1625
  await this.notify(state, `Posting triage comment for ${issue}.`);
@@ -106,24 +106,24 @@ async function emitTriageModelProgress(input) {
106
106
  await emitProgress(input.run, {
107
107
  options: input.progress.options,
108
108
  phase: input.phase,
109
- reviewer: input.reviewer,
110
109
  sessionId: input.progress.sessionId,
111
110
  type: "triage_agent_session",
111
+ voter: input.voter,
112
112
  });
113
113
  }
114
114
  if (input.progress.type === "repair") {
115
115
  await emitProgress(input.run, {
116
116
  phase: input.phase,
117
- reviewer: input.reviewer,
118
117
  type: "triage_agent_repair",
118
+ voter: input.voter,
119
119
  });
120
120
  }
121
121
  if (input.progress.type === "response") {
122
122
  await emitProgress(input.run, {
123
123
  phase: input.phase,
124
- reviewer: input.reviewer,
125
124
  sessionId: input.progress.sessionId,
126
125
  type: "triage_agent_response",
126
+ voter: input.voter,
127
127
  });
128
128
  }
129
129
  }
@@ -133,12 +133,12 @@ async function runVote(input) {
133
133
  directory: input.directory,
134
134
  issue: input.issue,
135
135
  repository: input.repository,
136
- reviewer: input.agent,
136
+ voter: input.agent,
137
137
  });
138
138
  await emitProgress(input.run, {
139
139
  phase: input.phase,
140
- reviewer: input.agent.key,
141
140
  type: "triage_agent_started",
141
+ voter: input.agent.key,
142
142
  });
143
143
  let result;
144
144
  try {
@@ -148,8 +148,8 @@ async function runVote(input) {
148
148
  onProgress: (progress) => emitTriageModelProgress({
149
149
  phase: input.phase,
150
150
  progress,
151
- reviewer: input.agent.key,
152
151
  run: input.run,
152
+ voter: input.agent.key,
153
153
  }),
154
154
  options: input.agent.options,
155
155
  parentSessionId: input.run.parentSessionId,
@@ -166,28 +166,28 @@ async function runVote(input) {
166
166
  await emitProgress(input.run, {
167
167
  error: error instanceof Error ? error.message : String(error),
168
168
  phase: input.phase,
169
- reviewer: input.agent.key,
170
169
  type: "triage_agent_failed",
170
+ voter: input.agent.key,
171
171
  });
172
172
  throw error;
173
173
  }
174
174
  await emitProgress(input.run, {
175
175
  phase: input.phase,
176
- reviewer: input.agent.key,
177
176
  sessionId: result.sessionId,
178
177
  type: "triage_agent_completed",
178
+ voter: input.agent.key,
179
179
  vote: result.value.vote,
180
180
  });
181
181
  return {
182
182
  ...result.value,
183
183
  promptText: prompt,
184
184
  raw: result.raw,
185
- reviewer: input.agent.key,
186
185
  sessionId: result.sessionId,
186
+ voter: input.agent.key,
187
187
  };
188
188
  }
189
189
  async function writeVoteArtifacts(input) {
190
- const base = join(input.outputDir, `${input.reviewer}.${input.phase}`);
190
+ const base = join(input.outputDir, `${input.voter}.${input.phase}`);
191
191
  await writeFile(`${base}.prompt.txt`, `${input.output.promptText}\n`);
192
192
  await writeFile(`${base}.raw.txt`, `${input.output.raw}\n`);
193
193
  await writeJson(`${base}.json`, {
@@ -233,14 +233,14 @@ async function runDuplicateVote(input) {
233
233
  signal: input.input.signal,
234
234
  })));
235
235
  const majority = aggregateStringMajority(outputs.map((output, index) => ({
236
- reviewer: agents[index].key,
236
+ voter: agents[index].key,
237
237
  vote: output.vote,
238
238
  })), DUPLICATE_VOTES);
239
239
  await Promise.all(outputs.map((output, index) => writeVoteArtifacts({
240
240
  output,
241
241
  outputDir: input.outputDir,
242
242
  phase: "duplicate",
243
- reviewer: agents[index].key,
243
+ voter: agents[index].key,
244
244
  })));
245
245
  await Promise.all(outputs.map((output, index) => writeJson(join(input.outputDir, `${agents[index].key}.duplicate.json`), {
246
246
  duplicateOf: output.duplicateOf,
@@ -275,14 +275,14 @@ async function runPhaseVote(input) {
275
275
  signal: input.input.signal,
276
276
  })));
277
277
  const majority = aggregateStringMajority(outputs.map((output, index) => ({
278
- reviewer: agents[index].key,
278
+ voter: agents[index].key,
279
279
  vote: output.vote,
280
280
  })), input.votes);
281
281
  await Promise.all(outputs.map((output, index) => writeVoteArtifacts({
282
282
  output,
283
283
  outputDir: input.outputDir,
284
284
  phase: input.phase,
285
- reviewer: agents[index].key,
285
+ voter: agents[index].key,
286
286
  })));
287
287
  await writeJson(join(input.outputDir, `${input.phase}-majority.json`), majority);
288
288
  return { outputs, vote: majority.vote };
@@ -463,7 +463,7 @@ async function classifyMentionReplies(input) {
463
463
  directory: input.input.directory,
464
464
  issue: input.input.issue,
465
465
  repository: input.input.repository,
466
- reviewer: agent,
466
+ voter: agent,
467
467
  });
468
468
  const result = await runModelWithRepair({
469
469
  client: input.input.client,
@@ -533,12 +533,11 @@ function askOutputs(outputs) {
533
533
  return (outputs ?? []).filter((output) => output.vote === "ASK");
534
534
  }
535
535
  function chooseDecisionReason(input) {
536
- return (input.outputs?.find((output) => output.reviewer === input.reporter.key &&
536
+ return (input.outputs?.find((output) => output.voter === input.reporter.key &&
537
537
  output.vote === input.vote &&
538
538
  output.reason)?.reason ??
539
539
  input.outputs?.find((output) => output.vote === input.vote)?.reason ??
540
- input.outputs?.find((output) => output.reviewer === input.reporter.key)
541
- ?.reason);
540
+ input.outputs?.find((output) => output.voter === input.reporter.key)?.reason);
542
541
  }
543
542
  async function postMarkedIssueComment(input) {
544
543
  const posted = await postIssueComment(input.exec, input.repository, input.issue, input.account, input.body);
@@ -586,7 +585,7 @@ async function persistProcessedMarker(input) {
586
585
  async function postAskComments(input) {
587
586
  const urls = [];
588
587
  for (const output of askOutputs(input.outputs)) {
589
- const agent = agentForKey(input.repository, output.reviewer);
588
+ const agent = agentForKey(input.repository, output.voter);
590
589
  const body = input.mark
591
590
  ? `${output.body}\n\n${marker({
592
591
  action: input.action,
@@ -333,7 +333,7 @@ async function composeTriageVotePrompt(input) {
333
333
  return [
334
334
  task,
335
335
  languageBlock(input.repository.language),
336
- personaBlock(input.reviewer.persona),
336
+ personaBlock(input.voter.persona),
337
337
  input.outputContract,
338
338
  ]
339
339
  .filter(Boolean)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-magi",
3
- "version": "0.0.0-dev-20260522220245",
3
+ "version": "0.0.0-dev-20260522220355",
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>",