opencode-magi 0.0.0-dev-20260519083642 → 0.0.0-dev-20260519084318

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.
@@ -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(`GH_TOKEN=${JSON.stringify(token)} gh api${ghHostOption(config)} repos/${config.github?.owner}/${config.github?.repo} --jq .permissions`);
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) {
@@ -80,6 +80,9 @@ 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
+ }
83
86
  export async function fetchPullRequest(exec, repository, pr) {
84
87
  const json = await exec(`gh pr view ${pr} --repo ${shellQuote(repoSpecifier(repository))} --json number,title,url,isDraft,baseRefOid,headRefOid,baseRefName,headRefName,headRepository,headRepositoryOwner`);
85
88
  return JSON.parse(json);
@@ -247,14 +250,14 @@ export async function removeBranch(exec, branch) {
247
250
  }
248
251
  export async function postApproval(exec, repository, pr, account) {
249
252
  const token = await ghToken(exec, repository, account);
250
- return exec(`GH_TOKEN=${shellQuote(token)} gh pr review ${pr} --repo ${shellQuote(repoSpecifier(repository))} --approve`);
253
+ return exec(`gh pr review ${pr} --repo ${shellQuote(repoSpecifier(repository))} --approve`, ghTokenEnv(token));
251
254
  }
252
255
  export async function postCloseComment(exec, repository, pr, account, body) {
253
256
  const token = await ghToken(exec, repository, account);
254
257
  const payloadPath = join(tmpdir(), `magi-close-${process.pid}-${Date.now()}.json`);
255
258
  await writeFile(payloadPath, JSON.stringify({ body, event: "COMMENT" }));
256
259
  try {
257
- return await exec(`GH_TOKEN=${shellQuote(token)} gh api${ghHostOption(repository)} repos/${repository.github.owner}/${repository.github.repo}/pulls/${pr}/reviews --method POST --input ${shellQuote(payloadPath)} --jq .html_url`);
260
+ 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
261
  }
259
262
  finally {
260
263
  await rm(payloadPath, { force: true });
@@ -285,7 +288,7 @@ export async function postChangesRequested(exec, repository, pr, account, findin
285
288
  event: "REQUEST_CHANGES",
286
289
  }));
287
290
  try {
288
- return await exec(`GH_TOKEN=${shellQuote(token)} gh api${ghHostOption(repository)} repos/${repository.github.owner}/${repository.github.repo}/pulls/${pr}/reviews --method POST --input ${shellQuote(payloadPath)} --jq .html_url`);
291
+ 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
292
  }
290
293
  finally {
291
294
  await rm(payloadPath, { force: true });
@@ -303,7 +306,7 @@ export async function mergePullRequest(exec, repository, pr, account) {
303
306
  const mergeFlags = repository.merge.mergeQueue
304
307
  ? ""
305
308
  : ` ${methodFlag}${autoFlag}${deleteFlag}`;
306
- return exec(`GH_TOKEN=${shellQuote(token)} gh pr merge ${pr} --repo ${shellQuote(repoSpecifier(repository))}${mergeFlags}`);
309
+ return exec(`gh pr merge ${pr} --repo ${shellQuote(repoSpecifier(repository))}${mergeFlags}`, ghTokenEnv(token));
307
310
  }
308
311
  export async function fetchPullRequestMergeStatus(exec, repository, pr) {
309
312
  const json = await exec(`gh pr view ${pr} --repo ${shellQuote(repoSpecifier(repository))} --json state,mergeStateStatus,autoMergeRequest`);
@@ -322,12 +325,23 @@ export async function waitForMergeQueue(exec, repository, pr, intervalMs = 30_00
322
325
  }
323
326
  export async function closePullRequest(exec, repository, pr, account) {
324
327
  const token = await ghToken(exec, repository, account);
325
- return exec(`GH_TOKEN=${shellQuote(token)} gh pr close ${pr} --repo ${shellQuote(repoSpecifier(repository))}`);
328
+ return exec(`gh pr close ${pr} --repo ${shellQuote(repoSpecifier(repository))}`, ghTokenEnv(token));
326
329
  }
327
330
  export async function pushHead(exec, repository, worktreePath, account, head) {
328
331
  const token = await ghToken(exec, repository, account);
329
332
  const url = repositoryGitUrl(repository, head.owner, head.repo);
330
- await exec(`git -c credential.helper= -c credential.helper=${shellQuote(`!f() { echo username=x-access-token; echo password=${token}; }; f`)} push ${shellQuote(url)} ${shellQuote(`HEAD:refs/heads/${head.ref}`)}`, { cwd: worktreePath });
333
+ await exec(`git push ${shellQuote(url)} ${shellQuote(`HEAD:refs/heads/${head.ref}`)}`, {
334
+ cwd: worktreePath,
335
+ env: {
336
+ GIT_CONFIG_COUNT: "2",
337
+ GIT_CONFIG_KEY_0: "credential.helper",
338
+ GIT_CONFIG_KEY_1: "credential.helper",
339
+ GIT_CONFIG_VALUE_0: "",
340
+ GIT_CONFIG_VALUE_1: "!f() { echo username=x-access-token; echo password=$GIT_PASSWORD; }; f",
341
+ GIT_PASSWORD: token,
342
+ GIT_TERMINAL_PROMPT: "0",
343
+ },
344
+ });
331
345
  }
332
346
  export async function configureGitIdentity(exec, worktreePath, identity) {
333
347
  if (identity.name) {
@@ -392,7 +406,7 @@ export async function postReply(exec, repository, pr, account, commentId, body)
392
406
  const payloadPath = join(tmpdir(), `magi-reply-${process.pid}-${Date.now()}-${commentId}.json`);
393
407
  await writeFile(payloadPath, JSON.stringify({ body }));
394
408
  try {
395
- return await exec(`GH_TOKEN=${shellQuote(token)} gh api${ghHostOption(repository)} repos/${repository.github.owner}/${repository.github.repo}/pulls/${pr}/comments/${commentId}/replies --method POST --input ${shellQuote(payloadPath)} --jq .html_url`);
409
+ 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
410
  }
397
411
  finally {
398
412
  await rm(payloadPath, { force: true });
@@ -401,5 +415,5 @@ export async function postReply(exec, repository, pr, account, commentId, body)
401
415
  export async function resolveThread(exec, repository, account, threadId) {
402
416
  const token = await ghToken(exec, repository, account);
403
417
  const query = `mutation($threadId: ID!) { resolveReviewThread(input: { threadId: $threadId }) { thread { id } } }`;
404
- await exec(`GH_TOKEN=${shellQuote(token)} gh api${ghHostOption(repository)} graphql -f query=${shellQuote(query)} -F threadId=${shellQuote(threadId)}`);
418
+ await exec(`gh api${ghHostOption(repository)} graphql -f query=${shellQuote(query)} -F threadId=${shellQuote(threadId)}`, ghTokenEnv(token));
405
419
  }
@@ -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 = error instanceof Error ? error.message : String(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-20260519083642",
3
+ "version": "0.0.0-dev-20260519084318",
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>",