opencode-magi 0.0.0-dev-20260520173258 → 0.0.0-dev-20260520180659
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.
|
@@ -164,6 +164,13 @@ function editorFailureText(input) {
|
|
|
164
164
|
const repairs = repairAttemptsText(input.repairAttempts);
|
|
165
165
|
return `**Editor** failed editing ${input.pr}${repairs}: ${input.error}`;
|
|
166
166
|
}
|
|
167
|
+
function triageCreatorFailureText(input) {
|
|
168
|
+
const repairs = repairAttemptsText(input.repairAttempts);
|
|
169
|
+
return `**Triage creator** failed creating an implementation PR for ${input.issue}${repairs}: ${input.error}`;
|
|
170
|
+
}
|
|
171
|
+
function triageDecisionNotification(input) {
|
|
172
|
+
return `Triage decided ${input.issue}: ${input.result}. Planned action: ${input.action}.`;
|
|
173
|
+
}
|
|
167
174
|
function repairAttemptsText(attempts) {
|
|
168
175
|
if (!attempts)
|
|
169
176
|
return "";
|
|
@@ -536,6 +543,14 @@ export class MagiRunManager {
|
|
|
536
543
|
])),
|
|
537
544
|
runId,
|
|
538
545
|
status: "preparing",
|
|
546
|
+
triageCreator: input.repository.agents.triageCreator
|
|
547
|
+
? {
|
|
548
|
+
account: input.repository.agents.triageCreator.account,
|
|
549
|
+
repairAttempts: 0,
|
|
550
|
+
status: "pending",
|
|
551
|
+
toolCalls: 0,
|
|
552
|
+
}
|
|
553
|
+
: undefined,
|
|
539
554
|
updatedAt: createdAt,
|
|
540
555
|
};
|
|
541
556
|
this.active.set(runId, state);
|
|
@@ -626,6 +641,17 @@ export class MagiRunManager {
|
|
|
626
641
|
.abort?.({ path: { id: state.editor.sessionId } })
|
|
627
642
|
.catch(() => undefined);
|
|
628
643
|
}
|
|
644
|
+
if (state.triageCreator?.status === "pending" ||
|
|
645
|
+
state.triageCreator?.status === "running" ||
|
|
646
|
+
state.triageCreator?.status === "repairing" ||
|
|
647
|
+
state.triageCreator?.status === "blocked") {
|
|
648
|
+
state.triageCreator.status = "cancelled";
|
|
649
|
+
}
|
|
650
|
+
if (state.triageCreator?.sessionId) {
|
|
651
|
+
await this.input.client.session
|
|
652
|
+
.abort?.({ path: { id: state.triageCreator.sessionId } })
|
|
653
|
+
.catch(() => undefined);
|
|
654
|
+
}
|
|
629
655
|
for (const reviewer of Object.values(state.reviewers)) {
|
|
630
656
|
if (reviewer.status === "pending" ||
|
|
631
657
|
reviewer.status === "running" ||
|
|
@@ -919,7 +945,7 @@ export class MagiRunManager {
|
|
|
919
945
|
if (!existing) {
|
|
920
946
|
await this.notify(state, questionWaitText({
|
|
921
947
|
agent: mapping.agent,
|
|
922
|
-
pr:
|
|
948
|
+
pr: runLabel(state),
|
|
923
949
|
question,
|
|
924
950
|
}), { reply: true });
|
|
925
951
|
}
|
|
@@ -938,7 +964,7 @@ export class MagiRunManager {
|
|
|
938
964
|
agent.error = `Permission ${permission?.permission ?? "request"} is waiting for approval.`;
|
|
939
965
|
markUpdated(true);
|
|
940
966
|
dirty = true;
|
|
941
|
-
await this.notify(state, `Magi ${mapping.agent} is waiting for permission on ${
|
|
967
|
+
await this.notify(state, `Magi ${mapping.agent} is waiting for permission on ${runLabel(state)}: ${agent.error}`, { reply: true });
|
|
942
968
|
}
|
|
943
969
|
}
|
|
944
970
|
if (input.event.type === "question.asked") {
|
|
@@ -961,7 +987,7 @@ export class MagiRunManager {
|
|
|
961
987
|
dirty = true;
|
|
962
988
|
await this.notify(state, questionWaitText({
|
|
963
989
|
agent: mapping.agent,
|
|
964
|
-
pr:
|
|
990
|
+
pr: runLabel(state),
|
|
965
991
|
question,
|
|
966
992
|
}), { reply: true });
|
|
967
993
|
}
|
|
@@ -1031,6 +1057,9 @@ export class MagiRunManager {
|
|
|
1031
1057
|
const editorLine = state.editor
|
|
1032
1058
|
? this.formatAgentLine("editor", state.editor, options)
|
|
1033
1059
|
: undefined;
|
|
1060
|
+
const triageCreatorLine = state.triageCreator
|
|
1061
|
+
? this.formatAgentLine("triageCreator", state.triageCreator, options)
|
|
1062
|
+
: undefined;
|
|
1034
1063
|
const reviewerLines = Object.entries(state.reviewers).map(([key, reviewer]) => {
|
|
1035
1064
|
return this.formatAgentLine(key, reviewer, options);
|
|
1036
1065
|
});
|
|
@@ -1038,6 +1067,7 @@ export class MagiRunManager {
|
|
|
1038
1067
|
const lines = [
|
|
1039
1068
|
options.verbose ? `Run: ${state.runId}` : undefined,
|
|
1040
1069
|
state.pr == null ? undefined : `PR: #${state.pr}`,
|
|
1070
|
+
state.issue == null ? undefined : `Issue: #${state.issue}`,
|
|
1041
1071
|
`Command: ${state.command}`,
|
|
1042
1072
|
state.dryRun ? "Dry run: true" : undefined,
|
|
1043
1073
|
`Status: ${state.status}`,
|
|
@@ -1058,6 +1088,7 @@ export class MagiRunManager {
|
|
|
1058
1088
|
? `Report: ${state.reportPath}`
|
|
1059
1089
|
: undefined,
|
|
1060
1090
|
editorLine,
|
|
1091
|
+
triageCreatorLine,
|
|
1061
1092
|
...classifierLines,
|
|
1062
1093
|
...reviewerLines,
|
|
1063
1094
|
];
|
|
@@ -1084,6 +1115,7 @@ export class MagiRunManager {
|
|
|
1084
1115
|
collectSessionIds(state) {
|
|
1085
1116
|
const ids = [
|
|
1086
1117
|
state.editor?.sessionId,
|
|
1118
|
+
state.triageCreator?.sessionId,
|
|
1087
1119
|
...Object.values(state.reviewers).map((reviewer) => reviewer.sessionId),
|
|
1088
1120
|
...Object.values(state.ciClassifiers ?? {}).map((classifier) => classifier.sessionId),
|
|
1089
1121
|
...Object.values(state.sessionIds ?? {}),
|
|
@@ -1126,13 +1158,22 @@ export class MagiRunManager {
|
|
|
1126
1158
|
agentState(state, key) {
|
|
1127
1159
|
if (key.startsWith("ci:"))
|
|
1128
1160
|
return state.ciClassifiers?.[key.slice(3)];
|
|
1129
|
-
|
|
1161
|
+
if (key === "editor")
|
|
1162
|
+
return state.editor;
|
|
1163
|
+
if (key === "triageCreator")
|
|
1164
|
+
return state.triageCreator;
|
|
1165
|
+
return state.reviewers[key];
|
|
1130
1166
|
}
|
|
1131
1167
|
agentEntries(state) {
|
|
1132
1168
|
return [
|
|
1133
1169
|
...(state.editor
|
|
1134
1170
|
? [["editor", state.editor]]
|
|
1135
1171
|
: []),
|
|
1172
|
+
...(state.triageCreator
|
|
1173
|
+
? [
|
|
1174
|
+
["triageCreator", state.triageCreator],
|
|
1175
|
+
]
|
|
1176
|
+
: []),
|
|
1136
1177
|
...Object.entries(state.ciClassifiers ?? {}).map(([key, value]) => [`ci:${key}`, value]),
|
|
1137
1178
|
...Object.entries(state.reviewers),
|
|
1138
1179
|
];
|
|
@@ -1152,10 +1193,10 @@ export class MagiRunManager {
|
|
|
1152
1193
|
if (!matches.length) {
|
|
1153
1194
|
return key
|
|
1154
1195
|
? `No pending ${kind} request found for ${key}.`
|
|
1155
|
-
: `No pending ${kind} request found for ${
|
|
1196
|
+
: `No pending ${kind} request found for ${runLabel(state)}.`;
|
|
1156
1197
|
}
|
|
1157
1198
|
if (matches.length > 1) {
|
|
1158
|
-
return `Multiple pending ${kind} requests found for ${
|
|
1199
|
+
return `Multiple pending ${kind} requests found for ${runLabel(state)}. Specify agent or requestId.`;
|
|
1159
1200
|
}
|
|
1160
1201
|
return { key: matches[0][0], state: matches[0][1] };
|
|
1161
1202
|
}
|
|
@@ -1266,6 +1307,7 @@ export class MagiRunManager {
|
|
|
1266
1307
|
dryRun: input.dryRun,
|
|
1267
1308
|
exec: withGitHubApiRetry(this.input.exec, input.config.github?.apiRetryAttempts ?? 3),
|
|
1268
1309
|
issue: input.issue,
|
|
1310
|
+
onProgress: (progress) => this.applyTriageProgress(input.runId, progress),
|
|
1269
1311
|
repository: input.repository,
|
|
1270
1312
|
runId: input.runId,
|
|
1271
1313
|
signal: input.signal,
|
|
@@ -1284,6 +1326,9 @@ export class MagiRunManager {
|
|
|
1284
1326
|
if (agent.status === "pending")
|
|
1285
1327
|
agent.status = "completed";
|
|
1286
1328
|
}
|
|
1329
|
+
if (completed.triageCreator?.status === "pending") {
|
|
1330
|
+
completed.triageCreator.status = "skipped";
|
|
1331
|
+
}
|
|
1287
1332
|
await this.persist(completed);
|
|
1288
1333
|
await this.notify(completed, [
|
|
1289
1334
|
`Finished triage for ${issueMarkdownLink(completed)}.`,
|
|
@@ -1293,6 +1338,189 @@ export class MagiRunManager {
|
|
|
1293
1338
|
this.active.delete(input.runId);
|
|
1294
1339
|
this.controllers.delete(input.runId);
|
|
1295
1340
|
}
|
|
1341
|
+
async applyTriageProgress(runId, progress) {
|
|
1342
|
+
const state = this.active.get(runId);
|
|
1343
|
+
if (!state)
|
|
1344
|
+
return;
|
|
1345
|
+
const issue = issueMarkdownLink(state);
|
|
1346
|
+
const creatorState = () => state.triageCreator ??
|
|
1347
|
+
(state.triageCreator = {
|
|
1348
|
+
account: "triageCreator",
|
|
1349
|
+
repairAttempts: 0,
|
|
1350
|
+
status: "pending",
|
|
1351
|
+
toolCalls: 0,
|
|
1352
|
+
});
|
|
1353
|
+
state.updatedAt = now();
|
|
1354
|
+
if (progress.type === "phase") {
|
|
1355
|
+
state.phase = progress.phase;
|
|
1356
|
+
state.status = "running";
|
|
1357
|
+
}
|
|
1358
|
+
if (progress.type === "decision") {
|
|
1359
|
+
state.phase = `decision: ${progress.result.disposition}`;
|
|
1360
|
+
state.verdict = JSON.stringify(progress.result);
|
|
1361
|
+
}
|
|
1362
|
+
if (progress.type === "comment_posting") {
|
|
1363
|
+
state.phase = "posting triage comment";
|
|
1364
|
+
state.status = "posting";
|
|
1365
|
+
}
|
|
1366
|
+
if (progress.type === "comment_posted") {
|
|
1367
|
+
state.status = "running";
|
|
1368
|
+
}
|
|
1369
|
+
if (progress.type === "pr_creation_started") {
|
|
1370
|
+
state.phase = "creating implementation PR";
|
|
1371
|
+
state.status = "running";
|
|
1372
|
+
}
|
|
1373
|
+
if (progress.type === "worktree_created") {
|
|
1374
|
+
state.worktreePath = progress.worktreePath;
|
|
1375
|
+
state.worktreeBranch = progress.branch;
|
|
1376
|
+
}
|
|
1377
|
+
if (progress.type === "triage_agent_started") {
|
|
1378
|
+
const reviewer = state.reviewers[progress.reviewer];
|
|
1379
|
+
if (reviewer)
|
|
1380
|
+
reviewer.status = "running";
|
|
1381
|
+
}
|
|
1382
|
+
if (progress.type === "triage_agent_session") {
|
|
1383
|
+
const reviewer = state.reviewers[progress.reviewer];
|
|
1384
|
+
if (reviewer) {
|
|
1385
|
+
if (progress.options)
|
|
1386
|
+
this.input.setSessionOptions?.(progress.sessionId, progress.options);
|
|
1387
|
+
reviewer.sessionId = progress.sessionId;
|
|
1388
|
+
reviewer.status = "running";
|
|
1389
|
+
reviewer.lastUpdate = now();
|
|
1390
|
+
this.sessionToRun.set(progress.sessionId, {
|
|
1391
|
+
agent: progress.reviewer,
|
|
1392
|
+
runId,
|
|
1393
|
+
});
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
if (progress.type === "triage_agent_repair") {
|
|
1397
|
+
const reviewer = state.reviewers[progress.reviewer];
|
|
1398
|
+
if (reviewer) {
|
|
1399
|
+
reviewer.status = "repairing";
|
|
1400
|
+
reviewer.repairAttempts += 1;
|
|
1401
|
+
reviewer.lastUpdate = now();
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
if (progress.type === "triage_agent_response") {
|
|
1405
|
+
const reviewer = state.reviewers[progress.reviewer];
|
|
1406
|
+
if (reviewer) {
|
|
1407
|
+
reviewer.sessionId = progress.sessionId;
|
|
1408
|
+
reviewer.lastUpdate = now();
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
if (progress.type === "triage_agent_completed") {
|
|
1412
|
+
const reviewer = state.reviewers[progress.reviewer];
|
|
1413
|
+
if (reviewer) {
|
|
1414
|
+
reviewer.sessionId = progress.sessionId;
|
|
1415
|
+
reviewer.status = "completed";
|
|
1416
|
+
reviewer.verdict = progress.vote;
|
|
1417
|
+
reviewer.rawPath = join(state.outputDir, `${progress.reviewer}.${progress.phase}.raw.txt`);
|
|
1418
|
+
reviewer.parsedPath = join(state.outputDir, `${progress.reviewer}.${progress.phase}.json`);
|
|
1419
|
+
reviewer.lastUpdate = now();
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
if (progress.type === "triage_agent_failed") {
|
|
1423
|
+
const reviewer = state.reviewers[progress.reviewer];
|
|
1424
|
+
if (reviewer) {
|
|
1425
|
+
reviewer.status = "failed";
|
|
1426
|
+
reviewer.error = redactSecrets(progress.error);
|
|
1427
|
+
reviewer.lastUpdate = now();
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
if (progress.type === "triage_creator_started") {
|
|
1431
|
+
creatorState().status = "running";
|
|
1432
|
+
}
|
|
1433
|
+
if (progress.type === "triage_creator_session") {
|
|
1434
|
+
const creator = creatorState();
|
|
1435
|
+
if (progress.options)
|
|
1436
|
+
this.input.setSessionOptions?.(progress.sessionId, progress.options);
|
|
1437
|
+
creator.sessionId = progress.sessionId;
|
|
1438
|
+
creator.status = "running";
|
|
1439
|
+
creator.lastUpdate = now();
|
|
1440
|
+
this.sessionToRun.set(progress.sessionId, {
|
|
1441
|
+
agent: "triageCreator",
|
|
1442
|
+
runId,
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
if (progress.type === "triage_creator_repair") {
|
|
1446
|
+
const creator = creatorState();
|
|
1447
|
+
creator.status = "repairing";
|
|
1448
|
+
creator.repairAttempts += 1;
|
|
1449
|
+
creator.lastUpdate = now();
|
|
1450
|
+
}
|
|
1451
|
+
if (progress.type === "triage_creator_response") {
|
|
1452
|
+
const creator = creatorState();
|
|
1453
|
+
creator.sessionId = progress.sessionId;
|
|
1454
|
+
creator.lastUpdate = now();
|
|
1455
|
+
}
|
|
1456
|
+
if (progress.type === "triage_creator_completed") {
|
|
1457
|
+
const creator = creatorState();
|
|
1458
|
+
creator.sessionId = progress.sessionId;
|
|
1459
|
+
creator.status = "completed";
|
|
1460
|
+
creator.parsedPath = join(state.outputDir, "create-pr.json");
|
|
1461
|
+
creator.lastUpdate = now();
|
|
1462
|
+
}
|
|
1463
|
+
if (progress.type === "triage_creator_failed") {
|
|
1464
|
+
const creator = creatorState();
|
|
1465
|
+
creator.status = "failed";
|
|
1466
|
+
creator.error = redactSecrets(progress.error);
|
|
1467
|
+
creator.lastUpdate = now();
|
|
1468
|
+
}
|
|
1469
|
+
await this.persist(state);
|
|
1470
|
+
if (progress.type === "phase") {
|
|
1471
|
+
await this.notify(state, `Triage phase for ${issue}: ${progress.phase}.`);
|
|
1472
|
+
}
|
|
1473
|
+
if (progress.type === "decision") {
|
|
1474
|
+
await this.notify(state, triageDecisionNotification({
|
|
1475
|
+
action: progress.action,
|
|
1476
|
+
issue,
|
|
1477
|
+
result: JSON.stringify(progress.result),
|
|
1478
|
+
}));
|
|
1479
|
+
}
|
|
1480
|
+
if (progress.type === "triage_agent_started") {
|
|
1481
|
+
await this.notify(state, `**Triage agent ${progress.reviewer}** started ${progress.phase} for ${issue}.`);
|
|
1482
|
+
}
|
|
1483
|
+
if (progress.type === "triage_agent_repair") {
|
|
1484
|
+
await this.notify(state, `**Triage agent ${progress.reviewer}** started JSON regeneration for ${issue}.`);
|
|
1485
|
+
}
|
|
1486
|
+
if (progress.type === "triage_agent_completed") {
|
|
1487
|
+
await this.notify(state, `**Triage agent ${progress.reviewer}** completed ${progress.phase} for ${issue}: ${progress.vote}.`);
|
|
1488
|
+
}
|
|
1489
|
+
if (progress.type === "triage_agent_failed") {
|
|
1490
|
+
await this.notify(state, `**Triage agent ${progress.reviewer}** failed ${progress.phase} for ${issue}: ${redactSecrets(progress.error)}`);
|
|
1491
|
+
}
|
|
1492
|
+
if (progress.type === "comment_posting") {
|
|
1493
|
+
await this.notify(state, `Posting triage comment for ${issue}.`);
|
|
1494
|
+
}
|
|
1495
|
+
if (progress.type === "comment_posted") {
|
|
1496
|
+
await this.notify(state, `Posted triage comment for ${issue}: ${progress.url}`);
|
|
1497
|
+
}
|
|
1498
|
+
if (progress.type === "pr_creation_started") {
|
|
1499
|
+
await this.notify(state, `Started implementation PR creation for ${issue}.`);
|
|
1500
|
+
}
|
|
1501
|
+
if (progress.type === "worktree_created") {
|
|
1502
|
+
await this.notify(state, `Worktree is ready for ${issue}.`);
|
|
1503
|
+
}
|
|
1504
|
+
if (progress.type === "triage_creator_started") {
|
|
1505
|
+
await this.notify(state, `**Triage creator** started creating an implementation PR for ${issue}.`);
|
|
1506
|
+
}
|
|
1507
|
+
if (progress.type === "triage_creator_repair") {
|
|
1508
|
+
await this.notify(state, `**Triage creator** started JSON regeneration for ${issue}.`);
|
|
1509
|
+
}
|
|
1510
|
+
if (progress.type === "triage_creator_completed") {
|
|
1511
|
+
await this.notify(state, `**Triage creator** completed implementation changes for ${issue}.`);
|
|
1512
|
+
}
|
|
1513
|
+
if (progress.type === "triage_creator_failed") {
|
|
1514
|
+
await this.notify(state, triageCreatorFailureText({
|
|
1515
|
+
error: redactSecrets(progress.error),
|
|
1516
|
+
issue,
|
|
1517
|
+
repairAttempts: state.triageCreator?.repairAttempts ?? 0,
|
|
1518
|
+
}));
|
|
1519
|
+
}
|
|
1520
|
+
if (progress.type === "pr_created") {
|
|
1521
|
+
await this.notify(state, `Created implementation PR for ${issue}: ${progress.url}`);
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1296
1524
|
async applyReviewProgress(runId, progress) {
|
|
1297
1525
|
const state = this.active.get(runId);
|
|
1298
1526
|
if (!state)
|
|
@@ -6,7 +6,7 @@ import { assignIssue, closeIssue, closePullRequest, configureGitIdentity, create
|
|
|
6
6
|
import { composeTriageAcceptancePrompt, composeTriageActionPrompt, composeTriageCategoryPrompt, composeTriageCommentClassificationPrompt, composeTriageCommentPrompt, composeTriageCreatePrPrompt, composeTriageDuplicatePrompt, composeTriageExistingPrPrompt, composeTriageQuestionPrompt, composeTriageReconsiderPrompt, } from "../prompts/compose";
|
|
7
7
|
import { parseTriageActionOutput, parseTriageBinaryOutput, parseTriageCategoryOutput, parseTriageCommentClassificationOutput, parseTriageCreatePrOutput, parseTriageDuplicateOutput, parseTriageExistingPrOutput, } from "../prompts/output";
|
|
8
8
|
import { aggregateStringMajority, majorityThreshold } from "./majority";
|
|
9
|
-
import { runModelText, runModelWithRepair } from "./model";
|
|
9
|
+
import { runModelText, runModelWithRepair, } from "./model";
|
|
10
10
|
const MARKER_PREFIX = "opencode-magi:triage";
|
|
11
11
|
const BINARY_VOTES = ["ASK", "NO", "YES"];
|
|
12
12
|
const DUPLICATE_VOTES = ["DUPLICATE", "NOT_DUPLICATE"];
|
|
@@ -98,6 +98,35 @@ function issueContext(input) {
|
|
|
98
98
|
async function writeJson(path, value) {
|
|
99
99
|
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
100
100
|
}
|
|
101
|
+
async function emitProgress(input, progress) {
|
|
102
|
+
await input.onProgress?.(progress);
|
|
103
|
+
}
|
|
104
|
+
async function emitTriageModelProgress(input) {
|
|
105
|
+
if (input.progress.type === "session_created") {
|
|
106
|
+
await emitProgress(input.run, {
|
|
107
|
+
options: input.progress.options,
|
|
108
|
+
phase: input.phase,
|
|
109
|
+
reviewer: input.reviewer,
|
|
110
|
+
sessionId: input.progress.sessionId,
|
|
111
|
+
type: "triage_agent_session",
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (input.progress.type === "repair") {
|
|
115
|
+
await emitProgress(input.run, {
|
|
116
|
+
phase: input.phase,
|
|
117
|
+
reviewer: input.reviewer,
|
|
118
|
+
type: "triage_agent_repair",
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (input.progress.type === "response") {
|
|
122
|
+
await emitProgress(input.run, {
|
|
123
|
+
phase: input.phase,
|
|
124
|
+
reviewer: input.reviewer,
|
|
125
|
+
sessionId: input.progress.sessionId,
|
|
126
|
+
type: "triage_agent_response",
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
101
130
|
async function runVote(input) {
|
|
102
131
|
const prompt = await input.prompt({
|
|
103
132
|
context: input.context,
|
|
@@ -106,17 +135,47 @@ async function runVote(input) {
|
|
|
106
135
|
repository: input.repository,
|
|
107
136
|
reviewer: input.agent,
|
|
108
137
|
});
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
138
|
+
await emitProgress(input.run, {
|
|
139
|
+
phase: input.phase,
|
|
140
|
+
reviewer: input.agent.key,
|
|
141
|
+
type: "triage_agent_started",
|
|
142
|
+
});
|
|
143
|
+
let result;
|
|
144
|
+
try {
|
|
145
|
+
result = await runModelWithRepair({
|
|
146
|
+
client: input.client,
|
|
147
|
+
model: input.agent.model,
|
|
148
|
+
onProgress: (progress) => emitTriageModelProgress({
|
|
149
|
+
phase: input.phase,
|
|
150
|
+
progress,
|
|
151
|
+
reviewer: input.agent.key,
|
|
152
|
+
run: input.run,
|
|
153
|
+
}),
|
|
154
|
+
options: input.agent.options,
|
|
155
|
+
parse: input.parse,
|
|
156
|
+
permission: input.agent.permission,
|
|
157
|
+
prompt,
|
|
158
|
+
repairAttempts: 3,
|
|
159
|
+
schemaName: input.schemaName,
|
|
160
|
+
signal: input.signal,
|
|
161
|
+
title: `Magi triage ${input.schemaName} #${input.issue} (${input.agent.key})`,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
await emitProgress(input.run, {
|
|
166
|
+
error: error instanceof Error ? error.message : String(error),
|
|
167
|
+
phase: input.phase,
|
|
168
|
+
reviewer: input.agent.key,
|
|
169
|
+
type: "triage_agent_failed",
|
|
170
|
+
});
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
await emitProgress(input.run, {
|
|
174
|
+
phase: input.phase,
|
|
175
|
+
reviewer: input.agent.key,
|
|
176
|
+
sessionId: result.sessionId,
|
|
177
|
+
type: "triage_agent_completed",
|
|
178
|
+
vote: result.value.vote,
|
|
120
179
|
});
|
|
121
180
|
return {
|
|
122
181
|
...result.value,
|
|
@@ -155,6 +214,7 @@ async function runDuplicateVote(input) {
|
|
|
155
214
|
const agents = input.input.repository.agents.triage;
|
|
156
215
|
if (!agents?.length)
|
|
157
216
|
throw new Error("triage.agents is required");
|
|
217
|
+
await emitProgress(input.input, { phase: "duplicate", type: "phase" });
|
|
158
218
|
const outputs = await Promise.all(agents.map((agent) => runVote({
|
|
159
219
|
agent,
|
|
160
220
|
client: input.input.client,
|
|
@@ -162,8 +222,10 @@ async function runDuplicateVote(input) {
|
|
|
162
222
|
directory: input.input.directory,
|
|
163
223
|
issue: input.input.issue,
|
|
164
224
|
parse: parseTriageDuplicateOutput,
|
|
225
|
+
phase: "duplicate",
|
|
165
226
|
prompt: composeTriageDuplicatePrompt,
|
|
166
227
|
repository: input.input.repository,
|
|
228
|
+
run: input.input,
|
|
167
229
|
schemaName: "triage duplicate",
|
|
168
230
|
signal: input.input.signal,
|
|
169
231
|
})));
|
|
@@ -194,6 +256,7 @@ async function runPhaseVote(input) {
|
|
|
194
256
|
const agents = input.input.repository.agents.triage;
|
|
195
257
|
if (!agents?.length)
|
|
196
258
|
throw new Error("triage.agents is required");
|
|
259
|
+
await emitProgress(input.input, { phase: input.phase, type: "phase" });
|
|
197
260
|
const outputs = await Promise.all(agents.map((agent) => runVote({
|
|
198
261
|
agent,
|
|
199
262
|
client: input.input.client,
|
|
@@ -201,8 +264,10 @@ async function runPhaseVote(input) {
|
|
|
201
264
|
directory: input.input.directory,
|
|
202
265
|
issue: input.input.issue,
|
|
203
266
|
parse: input.parse,
|
|
267
|
+
phase: input.phase,
|
|
204
268
|
prompt: input.prompt,
|
|
205
269
|
repository: input.input.repository,
|
|
270
|
+
run: input.input,
|
|
206
271
|
schemaName: input.schemaName,
|
|
207
272
|
signal: input.input.signal,
|
|
208
273
|
})));
|
|
@@ -544,6 +609,11 @@ async function finishWithResult(input) {
|
|
|
544
609
|
if (!triage)
|
|
545
610
|
throw new Error("triage configuration is required");
|
|
546
611
|
const plan = input.plan ?? actionPlan({ result: input.result, triage });
|
|
612
|
+
await emitProgress(input.input, {
|
|
613
|
+
action: plan.action,
|
|
614
|
+
result: input.result,
|
|
615
|
+
type: "decision",
|
|
616
|
+
});
|
|
547
617
|
await runActionPrompt({
|
|
548
618
|
context: input.context,
|
|
549
619
|
input: input.input,
|
|
@@ -565,7 +635,8 @@ async function finishWithResult(input) {
|
|
|
565
635
|
: undefined;
|
|
566
636
|
if (!input.input.dryRun) {
|
|
567
637
|
if (comment) {
|
|
568
|
-
await
|
|
638
|
+
await emitProgress(input.input, { type: "comment_posting" });
|
|
639
|
+
const posted = await postMarkedIssueComment({
|
|
569
640
|
account: triage.account ?? "",
|
|
570
641
|
body: comment,
|
|
571
642
|
exec: input.input.exec,
|
|
@@ -573,6 +644,10 @@ async function finishWithResult(input) {
|
|
|
573
644
|
outputDir: input.outputDir,
|
|
574
645
|
repository: input.input.repository,
|
|
575
646
|
});
|
|
647
|
+
await emitProgress(input.input, {
|
|
648
|
+
type: "comment_posted",
|
|
649
|
+
url: posted.url,
|
|
650
|
+
});
|
|
576
651
|
}
|
|
577
652
|
if (plan.clearLabels) {
|
|
578
653
|
const clearLabels = existingClearLabels(input.issue, triage.automation.clear);
|
|
@@ -597,8 +672,10 @@ async function finishWithResult(input) {
|
|
|
597
672
|
issue: input.issue,
|
|
598
673
|
outputDir: input.outputDir,
|
|
599
674
|
});
|
|
600
|
-
if (prUrl)
|
|
675
|
+
if (prUrl) {
|
|
601
676
|
await writeJson(join(input.outputDir, "pr.json"), { url: prUrl });
|
|
677
|
+
await emitProgress(input.input, { type: "pr_created", url: prUrl });
|
|
678
|
+
}
|
|
602
679
|
}
|
|
603
680
|
if (input.previousMarker && prUrl) {
|
|
604
681
|
await persistProcessedMarker({
|
|
@@ -659,48 +736,86 @@ async function createImplementationPr(input) {
|
|
|
659
736
|
const triage = input.input.repository.triage;
|
|
660
737
|
if (!triage?.account)
|
|
661
738
|
throw new Error("triage.account is required");
|
|
662
|
-
await
|
|
663
|
-
|
|
664
|
-
const worktreePath = join(worktreeBaseDir(input.input.directory, input.input.config, "issue"), `issue-${input.issue.number}`);
|
|
665
|
-
await mkdir(dirname(worktreePath), { recursive: true });
|
|
666
|
-
await input.input.exec(`git worktree add -b ${shellQuote(branch)} ${shellQuote(worktreePath)}`);
|
|
739
|
+
await emitProgress(input.input, { type: "pr_creation_started" });
|
|
740
|
+
await emitProgress(input.input, { type: "triage_creator_started" });
|
|
667
741
|
try {
|
|
668
|
-
await
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
742
|
+
await assignIssue(input.input.exec, input.input.repository, input.issue.number, triage.account);
|
|
743
|
+
const branch = `magi/issue-${input.issue.number}-${Date.now().toString(36)}`;
|
|
744
|
+
const worktreePath = join(worktreeBaseDir(input.input.directory, input.input.config, "issue"), `issue-${input.issue.number}`);
|
|
745
|
+
await mkdir(dirname(worktreePath), { recursive: true });
|
|
746
|
+
await input.input.exec(`git worktree add -b ${shellQuote(branch)} ${shellQuote(worktreePath)}`);
|
|
747
|
+
await emitProgress(input.input, {
|
|
748
|
+
branch,
|
|
749
|
+
type: "worktree_created",
|
|
674
750
|
worktreePath,
|
|
675
751
|
});
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
752
|
+
try {
|
|
753
|
+
await configureGitIdentity(input.input.exec, worktreePath, creator.author);
|
|
754
|
+
const prompt = await composeTriageCreatePrPrompt({
|
|
755
|
+
context: input.context,
|
|
756
|
+
directory: input.input.directory,
|
|
757
|
+
issue: input.issue.number,
|
|
758
|
+
repository: input.input.repository,
|
|
759
|
+
worktreePath,
|
|
760
|
+
});
|
|
761
|
+
const result = await runModelWithRepair({
|
|
762
|
+
client: input.input.client,
|
|
763
|
+
model: creator.model,
|
|
764
|
+
onProgress: async (progress) => {
|
|
765
|
+
if (progress.type === "session_created") {
|
|
766
|
+
await emitProgress(input.input, {
|
|
767
|
+
options: progress.options,
|
|
768
|
+
sessionId: progress.sessionId,
|
|
769
|
+
type: "triage_creator_session",
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
if (progress.type === "repair") {
|
|
773
|
+
await emitProgress(input.input, { type: "triage_creator_repair" });
|
|
774
|
+
}
|
|
775
|
+
if (progress.type === "response") {
|
|
776
|
+
await emitProgress(input.input, {
|
|
777
|
+
sessionId: progress.sessionId,
|
|
778
|
+
type: "triage_creator_response",
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
},
|
|
782
|
+
options: creator.options,
|
|
783
|
+
parse: parseTriageCreatePrOutput,
|
|
784
|
+
permission: creator.permission,
|
|
785
|
+
prompt,
|
|
786
|
+
repairAttempts: 3,
|
|
787
|
+
schemaName: "edit",
|
|
788
|
+
signal: input.input.signal,
|
|
789
|
+
title: `Magi triage create PR #${input.issue.number}`,
|
|
790
|
+
});
|
|
791
|
+
await emitProgress(input.input, {
|
|
792
|
+
sessionId: result.sessionId,
|
|
793
|
+
type: "triage_creator_completed",
|
|
794
|
+
});
|
|
795
|
+
await writeJson(join(input.outputDir, "create-pr.json"), result.value);
|
|
796
|
+
if (result.value.mode !== "EDITED")
|
|
797
|
+
return undefined;
|
|
798
|
+
await pushHead(input.input.exec, input.input.repository, worktreePath, creator.account, {
|
|
799
|
+
owner: input.input.repository.github.owner,
|
|
800
|
+
ref: branch,
|
|
801
|
+
repo: input.input.repository.github.repo,
|
|
802
|
+
});
|
|
803
|
+
return createPullRequest(input.input.exec, input.input.repository, creator.account, {
|
|
804
|
+
body: `Closes #${input.issue.number}`,
|
|
805
|
+
head: branch,
|
|
806
|
+
title: `fix: address issue #${input.issue.number}`,
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
finally {
|
|
810
|
+
await removeWorktree(input.input.exec, worktreePath).catch(() => undefined);
|
|
811
|
+
}
|
|
701
812
|
}
|
|
702
|
-
|
|
703
|
-
await
|
|
813
|
+
catch (error) {
|
|
814
|
+
await emitProgress(input.input, {
|
|
815
|
+
error: error instanceof Error ? error.message : String(error),
|
|
816
|
+
type: "triage_creator_failed",
|
|
817
|
+
});
|
|
818
|
+
throw error;
|
|
704
819
|
}
|
|
705
820
|
}
|
|
706
821
|
export async function runTriage(input) {
|
|
@@ -718,7 +833,12 @@ export async function runTriage(input) {
|
|
|
718
833
|
runId,
|
|
719
834
|
});
|
|
720
835
|
await mkdir(outputDir, { recursive: true });
|
|
836
|
+
await emitProgress(input, { phase: "fetching issue", type: "phase" });
|
|
721
837
|
const issue = await fetchIssue(input.exec, input.repository, input.issue);
|
|
838
|
+
await emitProgress(input, {
|
|
839
|
+
phase: "scanning issue relationships",
|
|
840
|
+
type: "phase",
|
|
841
|
+
});
|
|
722
842
|
const relationship = await relationshipScan(input, issue);
|
|
723
843
|
const block = safetyBlocked(input, issue, Boolean(relationship.previousMarker));
|
|
724
844
|
await writeJson(join(outputDir, "issue.json"), issue);
|
|
@@ -738,6 +858,7 @@ export async function runTriage(input) {
|
|
|
738
858
|
}
|
|
739
859
|
let context = issueContext({ issue, relationship });
|
|
740
860
|
await writeFile(join(outputDir, "context.md"), `${context}\n`);
|
|
861
|
+
await emitProgress(input, { phase: "triaging", type: "phase" });
|
|
741
862
|
let processed = relationship.previousMarker?.processed ?? [];
|
|
742
863
|
let result;
|
|
743
864
|
if (relationship.previousMarker) {
|
|
@@ -848,6 +969,11 @@ export async function runTriage(input) {
|
|
|
848
969
|
createPr: false,
|
|
849
970
|
postComment: true,
|
|
850
971
|
};
|
|
972
|
+
await emitProgress(input, {
|
|
973
|
+
action: plan.action,
|
|
974
|
+
result: relatedPrDecision,
|
|
975
|
+
type: "decision",
|
|
976
|
+
});
|
|
851
977
|
await runActionPrompt({
|
|
852
978
|
context,
|
|
853
979
|
input,
|
|
@@ -865,7 +991,8 @@ export async function runTriage(input) {
|
|
|
865
991
|
result: relatedPrDecision,
|
|
866
992
|
});
|
|
867
993
|
if (!input.dryRun) {
|
|
868
|
-
await
|
|
994
|
+
await emitProgress(input, { type: "comment_posting" });
|
|
995
|
+
const posted = await postMarkedIssueComment({
|
|
869
996
|
account: triage.account,
|
|
870
997
|
body,
|
|
871
998
|
exec: input.exec,
|
|
@@ -873,6 +1000,7 @@ export async function runTriage(input) {
|
|
|
873
1000
|
outputDir,
|
|
874
1001
|
repository: input.repository,
|
|
875
1002
|
});
|
|
1003
|
+
await emitProgress(input, { type: "comment_posted", url: posted.url });
|
|
876
1004
|
const clearLabels = existingClearLabels(issue, triage.automation.clear);
|
|
877
1005
|
if (clearLabels.length) {
|
|
878
1006
|
await removeIssueLabels(input.exec, input.repository, issue.number, clearLabels, triage.account);
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bash": {
|
|
3
|
+
"bun *": "allow",
|
|
4
|
+
"bunx *": "allow",
|
|
5
|
+
"corepack *": "allow",
|
|
3
6
|
"git add*": "allow",
|
|
4
|
-
"git commit*": "allow"
|
|
7
|
+
"git commit*": "allow",
|
|
8
|
+
"npm *": "allow",
|
|
9
|
+
"npx *": "allow",
|
|
10
|
+
"pnpm *": "allow",
|
|
11
|
+
"yarn *": "allow"
|
|
5
12
|
},
|
|
6
13
|
"edit": "allow"
|
|
7
14
|
}
|
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-20260520180659",
|
|
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>",
|