opencode-magi 0.0.0-dev-20260522141520 → 0.0.0-dev-20260522141851

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.
@@ -136,7 +136,6 @@ const MERGE_PROMPT_KEYS = new Set([
136
136
  "editGuidelines",
137
137
  ]);
138
138
  const TRIAGE_PROMPT_KEYS = new Set([
139
- "action",
140
139
  "acceptance",
141
140
  "category",
142
141
  "comment",
@@ -97,15 +97,26 @@ export async function createModelSession(input) {
97
97
  }
98
98
  export async function promptModelText(input) {
99
99
  throwIfAborted(input.signal);
100
- const result = await input.client.session.prompt({
101
- body: {
102
- model: modelBody(input.model),
103
- parts: [{ type: "text", text: input.prompt }],
104
- },
105
- path: { id: input.sessionId },
106
- });
107
- throwIfAborted(input.signal);
108
- return extractText(result, input.allowEmpty);
100
+ const abort = () => {
101
+ void input.client.session
102
+ .abort?.({ path: { id: input.sessionId } })
103
+ .catch(() => undefined);
104
+ };
105
+ input.signal?.addEventListener("abort", abort, { once: true });
106
+ try {
107
+ const result = await input.client.session.prompt({
108
+ body: {
109
+ model: modelBody(input.model),
110
+ parts: [{ type: "text", text: input.prompt }],
111
+ },
112
+ path: { id: input.sessionId },
113
+ });
114
+ throwIfAborted(input.signal);
115
+ return extractText(result, input.allowEmpty);
116
+ }
117
+ finally {
118
+ input.signal?.removeEventListener("abort", abort);
119
+ }
109
120
  }
110
121
  async function sendPrompt(client, sessionId, model, prompt, signal) {
111
122
  return promptModelText({ client, model, prompt, sessionId, signal });
@@ -676,53 +676,7 @@ export class MagiRunManager {
676
676
  state.status = "cancelled";
677
677
  state.phase = "cancelled";
678
678
  state.completedAt = now();
679
- if (state.editor?.status === "pending" ||
680
- state.editor?.status === "running" ||
681
- state.editor?.status === "repairing") {
682
- state.editor.status = "cancelled";
683
- }
684
- if (state.editor?.sessionId) {
685
- await this.input.client.session
686
- .abort?.({ path: { id: state.editor.sessionId } })
687
- .catch(() => undefined);
688
- }
689
- if (state.triageCreator?.status === "pending" ||
690
- state.triageCreator?.status === "running" ||
691
- state.triageCreator?.status === "repairing" ||
692
- state.triageCreator?.status === "blocked") {
693
- state.triageCreator.status = "cancelled";
694
- }
695
- if (state.triageCreator?.sessionId) {
696
- await this.input.client.session
697
- .abort?.({ path: { id: state.triageCreator.sessionId } })
698
- .catch(() => undefined);
699
- }
700
- for (const reviewer of Object.values(state.reviewers)) {
701
- if (reviewer.status === "pending" ||
702
- reviewer.status === "running" ||
703
- reviewer.status === "repairing" ||
704
- reviewer.status === "blocked") {
705
- reviewer.status = "cancelled";
706
- }
707
- if (reviewer.sessionId) {
708
- await this.input.client.session
709
- .abort?.({ path: { id: reviewer.sessionId } })
710
- .catch(() => undefined);
711
- }
712
- }
713
- for (const classifier of Object.values(state.ciClassifiers ?? {})) {
714
- if (classifier.status === "pending" ||
715
- classifier.status === "running" ||
716
- classifier.status === "repairing" ||
717
- classifier.status === "blocked") {
718
- classifier.status = "cancelled";
719
- }
720
- if (classifier.sessionId) {
721
- await this.input.client.session
722
- .abort?.({ path: { id: classifier.sessionId } })
723
- .catch(() => undefined);
724
- }
725
- }
679
+ await this.finishActiveAgents(state, "cancelled");
726
680
  if (state.worktreePath) {
727
681
  await removeWorktree(this.input.exec, state.worktreePath).catch(() => undefined);
728
682
  }
@@ -1223,6 +1177,26 @@ export class MagiRunManager {
1223
1177
  ...Object.entries(state.reviewers),
1224
1178
  ];
1225
1179
  }
1180
+ isActiveAgent(agent) {
1181
+ return (agent.status === "pending" ||
1182
+ agent.status === "running" ||
1183
+ agent.status === "repairing" ||
1184
+ agent.status === "blocked");
1185
+ }
1186
+ async finishActiveAgents(state, status, error) {
1187
+ for (const [, agent] of this.agentEntries(state)) {
1188
+ if (this.isActiveAgent(agent)) {
1189
+ agent.status = status;
1190
+ if (error != null)
1191
+ agent.error = error;
1192
+ }
1193
+ if (agent.sessionId) {
1194
+ await this.input.client.session
1195
+ .abort?.({ path: { id: agent.sessionId } })
1196
+ .catch(() => undefined);
1197
+ }
1198
+ }
1199
+ }
1226
1200
  selectPendingAgent(state, kind, key, requestId) {
1227
1201
  const entries = key
1228
1202
  ? this.agentState(state, key)
@@ -1966,46 +1940,7 @@ export class MagiRunManager {
1966
1940
  state.phase = "failed";
1967
1941
  state.completedAt = now();
1968
1942
  state.error = errorMessage(error);
1969
- if (state.editor?.status === "pending" ||
1970
- state.editor?.status === "running" ||
1971
- state.editor?.status === "repairing" ||
1972
- state.editor?.status === "blocked") {
1973
- state.editor.status = "failed";
1974
- state.editor.error = state.error;
1975
- }
1976
- if (state.editor?.sessionId) {
1977
- await this.input.client.session
1978
- .abort?.({ path: { id: state.editor.sessionId } })
1979
- .catch(() => undefined);
1980
- }
1981
- for (const reviewer of Object.values(state.reviewers)) {
1982
- if (reviewer.status === "pending" ||
1983
- reviewer.status === "running" ||
1984
- reviewer.status === "repairing" ||
1985
- reviewer.status === "blocked") {
1986
- reviewer.status = "failed";
1987
- reviewer.error = state.error;
1988
- }
1989
- if (reviewer.sessionId) {
1990
- await this.input.client.session
1991
- .abort?.({ path: { id: reviewer.sessionId } })
1992
- .catch(() => undefined);
1993
- }
1994
- }
1995
- for (const classifier of Object.values(state.ciClassifiers ?? {})) {
1996
- if (classifier.status === "pending" ||
1997
- classifier.status === "running" ||
1998
- classifier.status === "repairing" ||
1999
- classifier.status === "blocked") {
2000
- classifier.status = "failed";
2001
- classifier.error = state.error;
2002
- }
2003
- if (classifier.sessionId) {
2004
- await this.input.client.session
2005
- .abort?.({ path: { id: classifier.sessionId } })
2006
- .catch(() => undefined);
2007
- }
2008
- }
1943
+ await this.finishActiveAgents(state, "failed", state.error);
2009
1944
  await this.persist(state);
2010
1945
  await this.notify(state, `Magi ${state.command} failed for ${runLabel(state)}: ${state.error}`, { reply: true });
2011
1946
  this.active.delete(runId);
@@ -3,8 +3,8 @@ import { dirname, join } from "node:path";
3
3
  import { issueRunOutputDir } from "../config/output";
4
4
  import { worktreeBaseDir } from "../config/worktree";
5
5
  import { assignIssue, closeIssue, closePullRequest, configureGitIdentity, createPullRequest, fetchIssue, fetchIssueComments, fetchRelatedPullRequests, postIssueComment, pushHead, removeIssueLabels, removeWorktree, searchDuplicateIssues, shellQuote, updateIssueComment, } from "../github/commands";
6
- import { composeTriageAcceptancePrompt, composeTriageActionPrompt, composeTriageCategoryPrompt, composeTriageCommentClassificationPrompt, composeTriageCreatePrPrompt, composeTriageDuplicatePrompt, composeTriageExistingPrPrompt, composeTriageReconsiderPrompt, } from "../prompts/compose";
7
- import { parseTriageActionOutput, parseTriageBinaryOutput, parseTriageCategoryOutput, parseTriageCommentClassificationOutput, parseTriageCreatePrOutput, parseTriageDuplicateOutput, parseTriageExistingPrOutput, } from "../prompts/output";
6
+ import { composeTriageAcceptancePrompt, composeTriageCategoryPrompt, composeTriageCommentClassificationPrompt, composeTriageCreatePrPrompt, composeTriageDuplicatePrompt, composeTriageExistingPrPrompt, composeTriageReconsiderPrompt, } from "../prompts/compose";
7
+ import { parseTriageBinaryOutput, parseTriageCategoryOutput, parseTriageCommentClassificationOutput, parseTriageCreatePrOutput, parseTriageDuplicateOutput, parseTriageExistingPrOutput, } from "../prompts/output";
8
8
  import { aggregateStringMajority, majorityThreshold } from "./majority";
9
9
  import { runModelWithRepair, } from "./model";
10
10
  const MARKER_PREFIX = "opencode-magi:triage";
@@ -456,42 +456,6 @@ function previousAutomationPlan(input) {
456
456
  postComment: false,
457
457
  };
458
458
  }
459
- async function runActionPrompt(input) {
460
- const agent = input.input.repository.agents.triage?.[0];
461
- if (!agent)
462
- throw new Error("triage.agents is required");
463
- const context = JSON.stringify({
464
- allowedActions: input.plan.allowedActions,
465
- deterministicPlan: input.plan,
466
- result: input.result,
467
- triageContext: input.context,
468
- }, null, 2);
469
- const prompt = await composeTriageActionPrompt({
470
- context,
471
- directory: input.input.directory,
472
- issue: input.input.issue,
473
- repository: input.input.repository,
474
- reviewer: agent,
475
- });
476
- const result = await runModelWithRepair({
477
- client: input.input.client,
478
- model: agent.model,
479
- options: agent.options,
480
- parentSessionId: input.input.parentSessionId,
481
- parse: parseTriageActionOutput,
482
- permission: agent.permission,
483
- prompt,
484
- repairAttempts: 3,
485
- schemaName: "triage action",
486
- signal: input.input.signal,
487
- title: `Magi triage action #${input.input.issue}`,
488
- });
489
- await writeJson(join(input.outputDir, "action.json"), {
490
- model: result.value,
491
- plan: input.plan,
492
- });
493
- return result.value;
494
- }
495
459
  async function classifyMentionReplies(input) {
496
460
  const agent = input.input.repository.agents.triage?.[0];
497
461
  if (!agent)
@@ -663,13 +627,6 @@ async function finishWithResult(input) {
663
627
  result: input.result,
664
628
  type: "decision",
665
629
  });
666
- await runActionPrompt({
667
- context: input.context,
668
- input: input.input,
669
- outputDir: input.outputDir,
670
- plan,
671
- result: input.result,
672
- });
673
630
  let prUrl;
674
631
  const reporter = triageReporter(input.input.repository, input.issue.number);
675
632
  const comment = plan.postComment && input.result.disposition !== "ask"
@@ -1,7 +1,7 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import { isAbsolute, join } from "node:path";
4
- import { ciClassificationAfterEditOutputContract, ciClassificationOutputContract, closeReconsiderationOutputContract, editOutputContract, findingValidationOutputContract, rereviewCloseReconsiderationOutputContract, rereviewOutputContract, reviewOutputContract, triageActionOutputContract, triageCommentClassificationOutputContract, triageCreatePrOutputContract, triageDuplicateOutputContract, triageVoteOutputContract, } from "./contracts";
4
+ import { ciClassificationAfterEditOutputContract, ciClassificationOutputContract, closeReconsiderationOutputContract, editOutputContract, findingValidationOutputContract, rereviewCloseReconsiderationOutputContract, rereviewOutputContract, reviewOutputContract, triageCommentClassificationOutputContract, triageCreatePrOutputContract, triageDuplicateOutputContract, triageVoteOutputContract, } from "./contracts";
5
5
  async function readOptionalPrompt(directory, path, values = {}) {
6
6
  if (!path)
7
7
  return "";
@@ -339,14 +339,6 @@ async function composeTriageVotePrompt(input) {
339
339
  .filter(Boolean)
340
340
  .join("\n\n");
341
341
  }
342
- export async function composeTriageActionPrompt(input) {
343
- return composeTriageVotePrompt({
344
- ...input,
345
- builtin: "action",
346
- customPath: input.repository.triage?.prompts.action,
347
- outputContract: triageActionOutputContract,
348
- });
349
- }
350
342
  export async function composeTriageCommentPrompt(input) {
351
343
  const values = triageValues(input);
352
344
  const task = await taskBlock({
@@ -256,24 +256,6 @@ The object must match this shape:
256
256
  ]
257
257
  }
258
258
  </output_contract>`.trim();
259
- export const triageActionOutputContract = `
260
- <output_contract>
261
- Return exactly one JSON object and nothing else. Do not wrap it in markdown.
262
-
263
- The object must match this shape:
264
- {
265
- "action": "ASK" | "COMMENT" | "CLOSE" | "PR" | "CLEAR_ONLY",
266
- "reason": "Short rationale."
267
- }
268
-
269
- Rules:
270
- - Choose only an action listed as allowed in the task context.
271
- - ASK means post an author-mentioned question and do not close, create a PR, or clear labels.
272
- - COMMENT means post a decision comment only.
273
- - CLOSE means post a decision comment and close the issue.
274
- - PR means post a decision comment and create an implementation PR.
275
- - CLEAR_ONLY means clear labels without posting a comment.
276
- </output_contract>`.trim();
277
259
  const outputContractsBySchemaName = {
278
260
  "CI classification": ciClassificationOutputContract,
279
261
  "close reconsideration": closeReconsiderationOutputContract,
@@ -282,7 +264,6 @@ const outputContractsBySchemaName = {
282
264
  rereview: rereviewOutputContract,
283
265
  "rereview close reconsideration": rereviewCloseReconsiderationOutputContract,
284
266
  review: reviewOutputContract,
285
- "triage action": triageActionOutputContract,
286
267
  "triage acceptance": triageVoteOutputContract('"YES" | "NO" | "ASK"'),
287
268
  "triage category": triageVoteOutputContract('"ASK" or one of the configured category IDs'),
288
269
  "triage create PR": triageCreatePrOutputContract,
@@ -154,21 +154,6 @@ export function parseTriageCommentClassificationOutput(text) {
154
154
  }),
155
155
  };
156
156
  }
157
- export function parseTriageActionOutput(text) {
158
- const data = extractJson(text);
159
- if (!data || typeof data !== "object")
160
- throw new Error("triage action output must be an object");
161
- return {
162
- action: requireOneOf(data.action, "action", [
163
- "ASK",
164
- "CLEAR_ONLY",
165
- "CLOSE",
166
- "COMMENT",
167
- "PR",
168
- ]),
169
- reason: requireString(data.reason, "reason"),
170
- };
171
- }
172
157
  export function parseReviewOutput(text) {
173
158
  const data = extractJson(text);
174
159
  if (!data || typeof data !== "object")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-magi",
3
- "version": "0.0.0-dev-20260522141520",
3
+ "version": "0.0.0-dev-20260522141851",
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>",
package/schema.json CHANGED
@@ -229,7 +229,6 @@
229
229
  "duplicate": { "type": "string" },
230
230
  "category": { "type": "string" },
231
231
  "acceptance": { "type": "string" },
232
- "action": { "type": "string" },
233
232
  "question": { "type": "string" },
234
233
  "comment": { "type": "string" },
235
234
  "commentClassification": { "type": "string" },
@@ -1,5 +0,0 @@
1
- Decide the next action for issue #{issue} in {owner}/{repo} from the provided triage result and allowed actions.
2
-
3
- <context>
4
- {context}
5
- </context>