opencode-magi 0.0.0-dev-20260521143330 → 0.0.0-dev-20260521143913

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/index.js CHANGED
@@ -95,12 +95,17 @@ export function parseRunArguments(value, dryRun = false, command = "review") {
95
95
  const tokens = value.split(/[\s,]+/).filter(Boolean);
96
96
  const configOverrides = {};
97
97
  const prTokens = [];
98
+ let sync = false;
98
99
  for (let index = 0; index < tokens.length; index++) {
99
100
  const token = tokens[index];
100
101
  if (token === "--dry-run") {
101
102
  dryRun = true;
102
103
  continue;
103
104
  }
105
+ if (token === "--sync") {
106
+ sync = true;
107
+ continue;
108
+ }
104
109
  switch (token) {
105
110
  case "--language":
106
111
  setConfigOverride(configOverrides, ["language"], nextFlagValue(tokens, ++index, token));
@@ -146,18 +151,23 @@ export function parseRunArguments(value, dryRun = false, command = "review") {
146
151
  prTokens.push(token);
147
152
  }
148
153
  }
149
- return { configOverrides, dryRun, prs: parsePrs(prTokens.join(" ")) };
154
+ return { configOverrides, dryRun, prs: parsePrs(prTokens.join(" ")), sync };
150
155
  }
151
156
  export function parseIssueRunArguments(value, dryRun = false) {
152
157
  const tokens = value.split(/[\s,]+/).filter(Boolean);
153
158
  const configOverrides = {};
154
159
  const issueTokens = [];
160
+ let sync = false;
155
161
  for (let index = 0; index < tokens.length; index++) {
156
162
  const token = tokens[index];
157
163
  if (token === "--dry-run") {
158
164
  dryRun = true;
159
165
  continue;
160
166
  }
167
+ if (token === "--sync") {
168
+ sync = true;
169
+ continue;
170
+ }
161
171
  switch (token) {
162
172
  case "--language":
163
173
  setConfigOverride(configOverrides, ["language"], nextFlagValue(tokens, ++index, token));
@@ -195,7 +205,12 @@ export function parseIssueRunArguments(value, dryRun = false) {
195
205
  issueTokens.push(token);
196
206
  }
197
207
  }
198
- return { configOverrides, dryRun, issues: parseIssues(issueTokens.join(" ")) };
208
+ return {
209
+ configOverrides,
210
+ dryRun,
211
+ issues: parseIssues(issueTokens.join(" ")),
212
+ sync,
213
+ };
199
214
  }
200
215
  function nextFlagValue(tokens, index, flag) {
201
216
  const value = tokens[index];
@@ -203,6 +218,15 @@ function nextFlagValue(tokens, index, flag) {
203
218
  throw new Error(`${flag} requires a value.`);
204
219
  return value;
205
220
  }
221
+ async function syncResult(runManager, states) {
222
+ const output = await runManager.formatStatesWithReports(states, {
223
+ verbose: true,
224
+ });
225
+ const failed = states.filter((state) => state.status !== "completed");
226
+ if (failed.length)
227
+ throw new Error(output);
228
+ return output;
229
+ }
206
230
  function parseIntegerFlag(value, flag, minimum) {
207
231
  const parsed = Number.parseInt(value, 10);
208
232
  if (!Number.isInteger(parsed) ||
@@ -432,6 +456,7 @@ export const MagiPlugin = async ({ client, directory }) => {
432
456
  args: {
433
457
  prs: tool.schema.string(),
434
458
  dryRun: tool.schema.boolean().optional(),
459
+ sync: tool.schema.boolean().optional(),
435
460
  },
436
461
  async execute(args, context) {
437
462
  const parsed = parseRunArguments(args.prs, args.dryRun ?? false, "merge");
@@ -448,6 +473,7 @@ export const MagiPlugin = async ({ client, directory }) => {
448
473
  if (!validation.ok)
449
474
  return JSON.stringify(validation, null, 2);
450
475
  const repository = resolveRepository(config);
476
+ const sync = parsed.sync || args.sync === true;
451
477
  const states = await mapPool(parsed.prs, repository.concurrency.runs, (pr) => runManager.startMerge({
452
478
  config,
453
479
  dryRun: parsed.dryRun,
@@ -455,7 +481,10 @@ export const MagiPlugin = async ({ client, directory }) => {
455
481
  pr,
456
482
  parentSessionId: context.sessionID,
457
483
  signal: context.abort,
484
+ sync,
458
485
  }), { signal: context.abort });
486
+ if (sync)
487
+ return syncResult(runManager, states);
459
488
  return states
460
489
  .map((state) => `Started reviewing ${prMarkdownLink(repository, state.pr)}.`)
461
490
  .join("\n");
@@ -469,6 +498,7 @@ export const MagiPlugin = async ({ client, directory }) => {
469
498
  args: {
470
499
  prs: tool.schema.string(),
471
500
  dryRun: tool.schema.boolean().optional(),
501
+ sync: tool.schema.boolean().optional(),
472
502
  },
473
503
  async execute(args, context) {
474
504
  const parsed = parseRunArguments(args.prs, args.dryRun ?? false);
@@ -484,6 +514,7 @@ export const MagiPlugin = async ({ client, directory }) => {
484
514
  if (!validation.ok)
485
515
  return JSON.stringify(validation, null, 2);
486
516
  const repository = resolveRepository(config);
517
+ const sync = parsed.sync || args.sync === true;
487
518
  const states = await mapPool(parsed.prs, repository.concurrency.runs, (pr) => runManager.startReview({
488
519
  config,
489
520
  dryRun: parsed.dryRun,
@@ -491,7 +522,10 @@ export const MagiPlugin = async ({ client, directory }) => {
491
522
  pr,
492
523
  parentSessionId: context.sessionID,
493
524
  signal: context.abort,
525
+ sync,
494
526
  }), { signal: context.abort });
527
+ if (sync)
528
+ return syncResult(runManager, states);
495
529
  return states
496
530
  .map((state) => `Started reviewing ${prMarkdownLink(repository, state.pr)}.`)
497
531
  .join("\n");
@@ -502,6 +536,7 @@ export const MagiPlugin = async ({ client, directory }) => {
502
536
  args: {
503
537
  issues: tool.schema.string(),
504
538
  dryRun: tool.schema.boolean().optional(),
539
+ sync: tool.schema.boolean().optional(),
505
540
  },
506
541
  async execute(args, context) {
507
542
  const parsed = parseIssueRunArguments(args.issues, args.dryRun ?? false);
@@ -523,6 +558,7 @@ export const MagiPlugin = async ({ client, directory }) => {
523
558
  const repository = resolveRepository(config);
524
559
  if (!repository.triage)
525
560
  return JSON.stringify({ errors: ["triage configuration is required"], ok: false }, null, 2);
561
+ const sync = parsed.sync || args.sync === true;
526
562
  const states = await mapPool(parsed.issues, repository.triage.concurrency.runs, (issue) => runManager.startTriage({
527
563
  config,
528
564
  dryRun: parsed.dryRun,
@@ -530,7 +566,10 @@ export const MagiPlugin = async ({ client, directory }) => {
530
566
  parentSessionId: context.sessionID,
531
567
  repository,
532
568
  signal: context.abort,
569
+ sync,
533
570
  }), { signal: context.abort });
571
+ if (sync)
572
+ return syncResult(runManager, states);
534
573
  return states
535
574
  .map((state) => `Started triaging ${issueMarkdownLink(repository, state.issue)}.`)
536
575
  .join("\n");
@@ -15,6 +15,7 @@ const DEFAULT_CLEAR_OPTIONS = {
15
15
  session: true,
16
16
  worktree: true,
17
17
  };
18
+ const SYNC_RUN_TIMEOUT_MS = 600_000;
18
19
  function createRunId() {
19
20
  return `run-${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
20
21
  }
@@ -453,11 +454,14 @@ export class MagiRunManager {
453
454
  await this.notify(state, `Started Magi review for ${prMarkdownLink(state)}.`);
454
455
  const controller = new AbortController();
455
456
  this.controllers.set(runId, controller);
456
- void this.executeReview({
457
+ const execute = () => this.executeReview({
457
458
  ...input,
458
459
  runId,
459
460
  signal: controller.signal,
460
- }).catch(async (error) => {
461
+ });
462
+ if (input.sync)
463
+ return this.executeSync(state, controller, execute);
464
+ void execute().catch(async (error) => {
461
465
  await this.failRun(runId, error);
462
466
  });
463
467
  return state;
@@ -511,11 +515,14 @@ export class MagiRunManager {
511
515
  await this.notify(state, `Started Magi merge for ${prMarkdownLink(state)}.`);
512
516
  const controller = new AbortController();
513
517
  this.controllers.set(runId, controller);
514
- void this.executeMerge({
518
+ const execute = () => this.executeMerge({
515
519
  ...input,
516
520
  runId,
517
521
  signal: controller.signal,
518
- }).catch(async (error) => {
522
+ });
523
+ if (input.sync)
524
+ return this.executeSync(state, controller, execute);
525
+ void execute().catch(async (error) => {
519
526
  await this.failRun(runId, error);
520
527
  });
521
528
  return state;
@@ -568,11 +575,14 @@ export class MagiRunManager {
568
575
  await this.notify(state, `Started Magi triage for ${issueMarkdownLink(state)}.`);
569
576
  const controller = new AbortController();
570
577
  this.controllers.set(runId, controller);
571
- void this.executeTriage({
578
+ const execute = () => this.executeTriage({
572
579
  ...input,
573
580
  runId,
574
581
  signal: controller.signal,
575
- }).catch(async (error) => {
582
+ });
583
+ if (input.sync)
584
+ return this.executeSync(state, controller, execute);
585
+ void execute().catch(async (error) => {
576
586
  await this.failRun(runId, error);
577
587
  });
578
588
  return state;
@@ -1210,6 +1220,36 @@ export class MagiRunManager {
1210
1220
  hasBlockedAgents(state) {
1211
1221
  return this.agentEntries(state).some(([, agent]) => agent.status === "blocked");
1212
1222
  }
1223
+ async executeSync(state, controller, execute) {
1224
+ let timeout;
1225
+ const timeoutPromise = new Promise((resolve) => {
1226
+ timeout = setTimeout(() => resolve("timeout"), SYNC_RUN_TIMEOUT_MS);
1227
+ });
1228
+ try {
1229
+ const result = await Promise.race([
1230
+ execute().then(() => "completed"),
1231
+ timeoutPromise,
1232
+ ]);
1233
+ if (result === "timeout") {
1234
+ controller.abort();
1235
+ await this.failRun(state.runId, new Error("Magi sync run timed out after 600 seconds."));
1236
+ }
1237
+ }
1238
+ catch (error) {
1239
+ controller.abort();
1240
+ await this.failRun(state.runId, error);
1241
+ }
1242
+ finally {
1243
+ if (timeout)
1244
+ clearTimeout(timeout);
1245
+ }
1246
+ return (await this.readStateByRunId(state.runId)) ?? state;
1247
+ }
1248
+ assertSuccessfulSyncFollowUp(state) {
1249
+ if (state.status === "completed")
1250
+ return;
1251
+ throw new Error(`Synchronous follow-up ${state.command} run ${state.runId} finished with status ${state.status}.`);
1252
+ }
1213
1253
  async executeReview(input) {
1214
1254
  const result = await runReview({
1215
1255
  approvalPolicy: input.repository.merge.approvalPolicy,
@@ -1347,24 +1387,30 @@ export class MagiRunManager {
1347
1387
  : undefined;
1348
1388
  const triageAutomation = input.repository.triage?.automation;
1349
1389
  if (followUpPr != null && triageAutomation?.merge) {
1350
- await this.startMerge({
1390
+ const followUp = await this.startMerge({
1351
1391
  config: input.config,
1352
1392
  dryRun: input.dryRun,
1353
1393
  parentSessionId: input.parentSessionId,
1354
1394
  pr: followUpPr,
1355
1395
  repository: input.repository,
1356
1396
  signal: input.signal,
1397
+ sync: input.sync,
1357
1398
  });
1399
+ if (input.sync)
1400
+ this.assertSuccessfulSyncFollowUp(followUp);
1358
1401
  }
1359
1402
  else if (followUpPr != null && triageAutomation?.review) {
1360
- await this.startReview({
1403
+ const followUp = await this.startReview({
1361
1404
  config: input.config,
1362
1405
  dryRun: input.dryRun,
1363
1406
  parentSessionId: input.parentSessionId,
1364
1407
  pr: followUpPr,
1365
1408
  repository: input.repository,
1366
1409
  signal: input.signal,
1410
+ sync: input.sync,
1367
1411
  });
1412
+ if (input.sync)
1413
+ this.assertSuccessfulSyncFollowUp(followUp);
1368
1414
  }
1369
1415
  this.active.delete(input.runId);
1370
1416
  this.controllers.delete(input.runId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-magi",
3
- "version": "0.0.0-dev-20260521143330",
3
+ "version": "0.0.0-dev-20260521143913",
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>",