opencode-magi 0.0.0-dev-20260519083642 → 0.0.0-dev-20260519091322
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/config/validate.js
CHANGED
|
@@ -475,7 +475,7 @@ async function validateAuth(config, exec, errors) {
|
|
|
475
475
|
}
|
|
476
476
|
async function fetchPermissions(config, exec, account) {
|
|
477
477
|
const token = (await exec(`gh auth token${ghHostOption(config)} --user ${JSON.stringify(account)}`)).trim();
|
|
478
|
-
const raw = await exec(`
|
|
478
|
+
const raw = await exec(`gh api${ghHostOption(config)} repos/${config.github?.owner}/${config.github?.repo} --jq .permissions`, { env: { GH_TOKEN: token } });
|
|
479
479
|
return JSON.parse(raw);
|
|
480
480
|
}
|
|
481
481
|
async function validateRepositoryPermissions(config, exec, errors, warnings) {
|
package/dist/github/commands.js
CHANGED
|
@@ -80,6 +80,19 @@ export function ghHostOption(repository) {
|
|
|
80
80
|
export async function ghToken(exec, repository, account) {
|
|
81
81
|
return (await exec(`gh auth token${ghHostOption(repository)} --user ${shellQuote(account)}`)).trim();
|
|
82
82
|
}
|
|
83
|
+
function ghTokenEnv(token) {
|
|
84
|
+
return { env: { GH_TOKEN: token } };
|
|
85
|
+
}
|
|
86
|
+
async function fetchPullRequestQueueInput(exec, repository, pr, token) {
|
|
87
|
+
const query = `query($owner: String!, $repo: String!, $pr: Int!) { repository(owner: $owner, name: $repo) { pullRequest(number: $pr) { id headRefOid } } }`;
|
|
88
|
+
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}`, ghTokenEnv(token));
|
|
89
|
+
const data = JSON.parse(raw);
|
|
90
|
+
const pullRequest = data.data?.repository?.pullRequest;
|
|
91
|
+
if (!pullRequest?.id || !pullRequest.headRefOid) {
|
|
92
|
+
throw new Error(`Could not fetch pull request queue metadata for #${pr}`);
|
|
93
|
+
}
|
|
94
|
+
return { headRefOid: pullRequest.headRefOid, id: pullRequest.id };
|
|
95
|
+
}
|
|
83
96
|
export async function fetchPullRequest(exec, repository, pr) {
|
|
84
97
|
const json = await exec(`gh pr view ${pr} --repo ${shellQuote(repoSpecifier(repository))} --json number,title,url,isDraft,baseRefOid,headRefOid,baseRefName,headRefName,headRepository,headRepositoryOwner`);
|
|
85
98
|
return JSON.parse(json);
|
|
@@ -247,14 +260,14 @@ export async function removeBranch(exec, branch) {
|
|
|
247
260
|
}
|
|
248
261
|
export async function postApproval(exec, repository, pr, account) {
|
|
249
262
|
const token = await ghToken(exec, repository, account);
|
|
250
|
-
return exec(`
|
|
263
|
+
return exec(`gh pr review ${pr} --repo ${shellQuote(repoSpecifier(repository))} --approve`, ghTokenEnv(token));
|
|
251
264
|
}
|
|
252
265
|
export async function postCloseComment(exec, repository, pr, account, body) {
|
|
253
266
|
const token = await ghToken(exec, repository, account);
|
|
254
267
|
const payloadPath = join(tmpdir(), `magi-close-${process.pid}-${Date.now()}.json`);
|
|
255
268
|
await writeFile(payloadPath, JSON.stringify({ body, event: "COMMENT" }));
|
|
256
269
|
try {
|
|
257
|
-
return await exec(`
|
|
270
|
+
return await exec(`gh api${ghHostOption(repository)} repos/${repository.github.owner}/${repository.github.repo}/pulls/${pr}/reviews --method POST --input ${shellQuote(payloadPath)} --jq .html_url`, ghTokenEnv(token));
|
|
258
271
|
}
|
|
259
272
|
finally {
|
|
260
273
|
await rm(payloadPath, { force: true });
|
|
@@ -285,7 +298,7 @@ export async function postChangesRequested(exec, repository, pr, account, findin
|
|
|
285
298
|
event: "REQUEST_CHANGES",
|
|
286
299
|
}));
|
|
287
300
|
try {
|
|
288
|
-
return await exec(`
|
|
301
|
+
return await exec(`gh api${ghHostOption(repository)} repos/${repository.github.owner}/${repository.github.repo}/pulls/${pr}/reviews --method POST --input ${shellQuote(payloadPath)} --jq .html_url`, ghTokenEnv(token));
|
|
289
302
|
}
|
|
290
303
|
finally {
|
|
291
304
|
await rm(payloadPath, { force: true });
|
|
@@ -293,6 +306,11 @@ export async function postChangesRequested(exec, repository, pr, account, findin
|
|
|
293
306
|
}
|
|
294
307
|
export async function mergePullRequest(exec, repository, pr, account) {
|
|
295
308
|
const token = await ghToken(exec, repository, account);
|
|
309
|
+
if (repository.merge.mergeQueue) {
|
|
310
|
+
const queueInput = await fetchPullRequestQueueInput(exec, repository, pr, token);
|
|
311
|
+
const query = `mutation($pullRequestId: ID!, $expectedHeadOid: GitObjectID!) { enqueuePullRequest(input: { pullRequestId: $pullRequestId, expectedHeadOid: $expectedHeadOid }) { mergeQueueEntry { id } } }`;
|
|
312
|
+
return exec(`gh api${ghHostOption(repository)} graphql -f query=${shellQuote(query)} -F pullRequestId=${shellQuote(queueInput.id)} -F expectedHeadOid=${shellQuote(queueInput.headRefOid)} --jq .data.enqueuePullRequest.mergeQueueEntry.id`, ghTokenEnv(token));
|
|
313
|
+
}
|
|
296
314
|
const methodFlag = repository.merge.method === "merge"
|
|
297
315
|
? "--merge"
|
|
298
316
|
: repository.merge.method === "rebase"
|
|
@@ -300,21 +318,29 @@ export async function mergePullRequest(exec, repository, pr, account) {
|
|
|
300
318
|
: "--squash";
|
|
301
319
|
const autoFlag = repository.merge.auto ? " --auto" : "";
|
|
302
320
|
const deleteFlag = repository.merge.deleteBranch ? " --delete-branch" : "";
|
|
303
|
-
|
|
304
|
-
? ""
|
|
305
|
-
: ` ${methodFlag}${autoFlag}${deleteFlag}`;
|
|
306
|
-
return exec(`GH_TOKEN=${shellQuote(token)} gh pr merge ${pr} --repo ${shellQuote(repoSpecifier(repository))}${mergeFlags}`);
|
|
321
|
+
return exec(`gh pr merge ${pr} --repo ${shellQuote(repoSpecifier(repository))} ${methodFlag}${autoFlag}${deleteFlag}`, ghTokenEnv(token));
|
|
307
322
|
}
|
|
308
323
|
export async function fetchPullRequestMergeStatus(exec, repository, pr) {
|
|
309
324
|
const json = await exec(`gh pr view ${pr} --repo ${shellQuote(repoSpecifier(repository))} --json state,mergeStateStatus,autoMergeRequest`);
|
|
310
325
|
return JSON.parse(json);
|
|
311
326
|
}
|
|
327
|
+
export async function fetchPullRequestQueueStatus(exec, repository, pr) {
|
|
328
|
+
const query = `query($owner: String!, $repo: String!, $pr: Int!) { repository(owner: $owner, name: $repo) { pullRequest(number: $pr) { state isInMergeQueue mergeQueueEntry { id } } } }`;
|
|
329
|
+
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}`);
|
|
330
|
+
const data = JSON.parse(raw);
|
|
331
|
+
const status = data.data?.repository?.pullRequest;
|
|
332
|
+
if (!status)
|
|
333
|
+
throw new Error(`Could not fetch merge queue status for #${pr}`);
|
|
334
|
+
return status;
|
|
335
|
+
}
|
|
312
336
|
export async function waitForMergeQueue(exec, repository, pr, intervalMs = 30_000) {
|
|
313
337
|
for (;;) {
|
|
314
|
-
const status = await
|
|
338
|
+
const status = await fetchPullRequestQueueStatus(exec, repository, pr);
|
|
315
339
|
if (status.state === "MERGED")
|
|
316
340
|
return "merged";
|
|
317
|
-
if (status.state === "OPEN" &&
|
|
341
|
+
if (status.state === "OPEN" &&
|
|
342
|
+
!status.isInMergeQueue &&
|
|
343
|
+
status.mergeQueueEntry == null) {
|
|
318
344
|
return "dequeued";
|
|
319
345
|
}
|
|
320
346
|
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
@@ -322,12 +348,23 @@ export async function waitForMergeQueue(exec, repository, pr, intervalMs = 30_00
|
|
|
322
348
|
}
|
|
323
349
|
export async function closePullRequest(exec, repository, pr, account) {
|
|
324
350
|
const token = await ghToken(exec, repository, account);
|
|
325
|
-
return exec(`
|
|
351
|
+
return exec(`gh pr close ${pr} --repo ${shellQuote(repoSpecifier(repository))}`, ghTokenEnv(token));
|
|
326
352
|
}
|
|
327
353
|
export async function pushHead(exec, repository, worktreePath, account, head) {
|
|
328
354
|
const token = await ghToken(exec, repository, account);
|
|
329
355
|
const url = repositoryGitUrl(repository, head.owner, head.repo);
|
|
330
|
-
await exec(`git
|
|
356
|
+
await exec(`git push ${shellQuote(url)} ${shellQuote(`HEAD:refs/heads/${head.ref}`)}`, {
|
|
357
|
+
cwd: worktreePath,
|
|
358
|
+
env: {
|
|
359
|
+
GIT_CONFIG_COUNT: "2",
|
|
360
|
+
GIT_CONFIG_KEY_0: "credential.helper",
|
|
361
|
+
GIT_CONFIG_KEY_1: "credential.helper",
|
|
362
|
+
GIT_CONFIG_VALUE_0: "",
|
|
363
|
+
GIT_CONFIG_VALUE_1: "!f() { echo username=x-access-token; echo password=$GIT_PASSWORD; }; f",
|
|
364
|
+
GIT_PASSWORD: token,
|
|
365
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
366
|
+
},
|
|
367
|
+
});
|
|
331
368
|
}
|
|
332
369
|
export async function configureGitIdentity(exec, worktreePath, identity) {
|
|
333
370
|
if (identity.name) {
|
|
@@ -392,7 +429,7 @@ export async function postReply(exec, repository, pr, account, commentId, body)
|
|
|
392
429
|
const payloadPath = join(tmpdir(), `magi-reply-${process.pid}-${Date.now()}-${commentId}.json`);
|
|
393
430
|
await writeFile(payloadPath, JSON.stringify({ body }));
|
|
394
431
|
try {
|
|
395
|
-
return await exec(`
|
|
432
|
+
return await exec(`gh api${ghHostOption(repository)} repos/${repository.github.owner}/${repository.github.repo}/pulls/${pr}/comments/${commentId}/replies --method POST --input ${shellQuote(payloadPath)} --jq .html_url`, ghTokenEnv(token));
|
|
396
433
|
}
|
|
397
434
|
finally {
|
|
398
435
|
await rm(payloadPath, { force: true });
|
|
@@ -401,5 +438,5 @@ export async function postReply(exec, repository, pr, account, commentId, body)
|
|
|
401
438
|
export async function resolveThread(exec, repository, account, threadId) {
|
|
402
439
|
const token = await ghToken(exec, repository, account);
|
|
403
440
|
const query = `mutation($threadId: ID!) { resolveReviewThread(input: { threadId: $threadId }) { thread { id } } }`;
|
|
404
|
-
await exec(`
|
|
441
|
+
await exec(`gh api${ghHostOption(repository)} graphql -f query=${shellQuote(query)} -F threadId=${shellQuote(threadId)}`, ghTokenEnv(token));
|
|
405
442
|
}
|
|
@@ -20,6 +20,14 @@ function createRunId() {
|
|
|
20
20
|
function now() {
|
|
21
21
|
return new Date().toISOString();
|
|
22
22
|
}
|
|
23
|
+
export function redactSecrets(value) {
|
|
24
|
+
return value
|
|
25
|
+
.replace(/\b(GH_TOKEN|GITHUB_TOKEN|GH_ENTERPRISE_TOKEN)=('[^']*'|"[^"]*"|\S+)/g, "$1=<redacted>")
|
|
26
|
+
.replace(/(password=)([^;'\s]+)/g, "$1<redacted>");
|
|
27
|
+
}
|
|
28
|
+
function errorMessage(error) {
|
|
29
|
+
return redactSecrets(error instanceof Error ? error.message : String(error));
|
|
30
|
+
}
|
|
23
31
|
function isActiveStatus(status) {
|
|
24
32
|
return (status === "blocked" ||
|
|
25
33
|
status === "preparing" ||
|
|
@@ -927,7 +935,7 @@ export class MagiRunManager {
|
|
|
927
935
|
}
|
|
928
936
|
if (input.event.type === "session.error") {
|
|
929
937
|
agent.status = "failed";
|
|
930
|
-
agent.error = JSON.stringify(input.event.properties?.error ?? "session error");
|
|
938
|
+
agent.error = redactSecrets(JSON.stringify(input.event.properties?.error ?? "session error"));
|
|
931
939
|
markUpdated(true);
|
|
932
940
|
dirty = true;
|
|
933
941
|
}
|
|
@@ -1226,7 +1234,7 @@ export class MagiRunManager {
|
|
|
1226
1234
|
if (progress.type === "ci_classifier_failed") {
|
|
1227
1235
|
const classifier = state.ciClassifiers?.[progress.reviewer];
|
|
1228
1236
|
if (classifier) {
|
|
1229
|
-
classifier.error = progress.error;
|
|
1237
|
+
classifier.error = redactSecrets(progress.error);
|
|
1230
1238
|
classifier.status = "failed";
|
|
1231
1239
|
classifier.lastUpdate = now();
|
|
1232
1240
|
}
|
|
@@ -1282,7 +1290,7 @@ export class MagiRunManager {
|
|
|
1282
1290
|
if (!reviewer)
|
|
1283
1291
|
return;
|
|
1284
1292
|
reviewer.status = "failed";
|
|
1285
|
-
reviewer.error = progress.error;
|
|
1293
|
+
reviewer.error = redactSecrets(progress.error);
|
|
1286
1294
|
reviewer.lastUpdate = now();
|
|
1287
1295
|
}
|
|
1288
1296
|
if (progress.type === "reviewer_completed") {
|
|
@@ -1328,7 +1336,7 @@ export class MagiRunManager {
|
|
|
1328
1336
|
await this.notify(state, `**CI classifier ${progress.reviewer}** completed for ${prMarkdownLink(state)}: ${progress.classification} - ${progress.reason}`);
|
|
1329
1337
|
}
|
|
1330
1338
|
if (progress.type === "ci_classifier_failed") {
|
|
1331
|
-
await this.notify(state, `**CI classifier ${progress.reviewer}** failed for ${prMarkdownLink(state)}: ${progress.error}`);
|
|
1339
|
+
await this.notify(state, `**CI classifier ${progress.reviewer}** failed for ${prMarkdownLink(state)}: ${redactSecrets(progress.error)}`);
|
|
1332
1340
|
}
|
|
1333
1341
|
if (progress.type === "worktree_created") {
|
|
1334
1342
|
await this.notify(state, `Worktree is ready for ${prMarkdownLink(state)}.`);
|
|
@@ -1344,7 +1352,7 @@ export class MagiRunManager {
|
|
|
1344
1352
|
}
|
|
1345
1353
|
if (progress.type === "reviewer_failed") {
|
|
1346
1354
|
await this.notify(state, reviewerFailureText({
|
|
1347
|
-
error: progress.error,
|
|
1355
|
+
error: redactSecrets(progress.error),
|
|
1348
1356
|
pr: prMarkdownLink(state),
|
|
1349
1357
|
repairAttempts: state.reviewers[progress.reviewer]?.repairAttempts ?? 0,
|
|
1350
1358
|
reviewer: progress.reviewer,
|
|
@@ -1451,7 +1459,7 @@ export class MagiRunManager {
|
|
|
1451
1459
|
}
|
|
1452
1460
|
if (progress.type === "editor_failed") {
|
|
1453
1461
|
editor.status = "failed";
|
|
1454
|
-
editor.error = progress.error;
|
|
1462
|
+
editor.error = redactSecrets(progress.error);
|
|
1455
1463
|
editor.lastUpdate = now();
|
|
1456
1464
|
}
|
|
1457
1465
|
if (progress.type === "editor_completed") {
|
|
@@ -1475,7 +1483,7 @@ export class MagiRunManager {
|
|
|
1475
1483
|
}
|
|
1476
1484
|
if (progress.type === "editor_failed") {
|
|
1477
1485
|
await this.notify(state, editorFailureText({
|
|
1478
|
-
error: progress.error,
|
|
1486
|
+
error: redactSecrets(progress.error),
|
|
1479
1487
|
pr: prMarkdownLink(state),
|
|
1480
1488
|
repairAttempts: state.editor?.repairAttempts ?? 0,
|
|
1481
1489
|
}));
|
|
@@ -1493,7 +1501,7 @@ export class MagiRunManager {
|
|
|
1493
1501
|
state.status = "failed";
|
|
1494
1502
|
state.phase = "failed";
|
|
1495
1503
|
state.completedAt = now();
|
|
1496
|
-
state.error =
|
|
1504
|
+
state.error = errorMessage(error);
|
|
1497
1505
|
if (state.editor?.status === "pending" ||
|
|
1498
1506
|
state.editor?.status === "running" ||
|
|
1499
1507
|
state.editor?.status === "repairing" ||
|
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-20260519091322",
|
|
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>",
|