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 +41 -2
- package/dist/orchestrator/run-manager.js +54 -8
- package/package.json +1 -1
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 {
|
|
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
|
-
|
|
457
|
+
const execute = () => this.executeReview({
|
|
457
458
|
...input,
|
|
458
459
|
runId,
|
|
459
460
|
signal: controller.signal,
|
|
460
|
-
})
|
|
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
|
-
|
|
518
|
+
const execute = () => this.executeMerge({
|
|
515
519
|
...input,
|
|
516
520
|
runId,
|
|
517
521
|
signal: controller.signal,
|
|
518
|
-
})
|
|
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
|
-
|
|
578
|
+
const execute = () => this.executeTriage({
|
|
572
579
|
...input,
|
|
573
580
|
runId,
|
|
574
581
|
signal: controller.signal,
|
|
575
|
-
})
|
|
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-
|
|
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>",
|