@fkqfkq123/opencode-autopilot 0.1.6 → 0.1.8

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.
@@ -1,4 +1,5 @@
1
1
  export type RecoveryState = "idle" | "recovering";
2
+ export type PhaseDispatchAttempts = Partial<Record<"spec_refinement" | "plan" | "develop" | "review" | "test", number>>;
2
3
  export interface WorkflowRuntimeState {
3
4
  workflowId: string;
4
5
  preferredForegroundSessionId?: string | null;
@@ -11,4 +12,5 @@ export interface WorkflowRuntimeState {
11
12
  refinementLastDispatchSummary?: string | null;
12
13
  refinementEscalationReason?: string | null;
13
14
  lastContinuationAt?: string;
15
+ phaseDispatchAttempts?: PhaseDispatchAttempts;
14
16
  }
@@ -1,4 +1,24 @@
1
1
  const MAX_SPEC_REFINEMENT_SELF_REPAIR_ATTEMPTS = 1;
2
+ const MAX_CONSECUTIVE_FAILURES = 3;
3
+ const MAX_REVIEW_OR_TEST_UNKNOWN_DISPATCH_ATTEMPTS = 3;
4
+ function getPhaseDispatchAttempts(runtime, phase) {
5
+ return runtime.phaseDispatchAttempts?.[phase] ?? 0;
6
+ }
7
+ function shouldEscalateUnknownConclusion(input, phase) {
8
+ if (getPhaseDispatchAttempts(input.runtime, phase) < MAX_REVIEW_OR_TEST_UNKNOWN_DISPATCH_ATTEMPTS) {
9
+ return null;
10
+ }
11
+ const action = {
12
+ type: "blocked",
13
+ workflowId: input.workflow.workflowId,
14
+ phase,
15
+ reason: `${phase} exceeded dispatch retry budget and needs human decision`,
16
+ required: true,
17
+ createdAt: new Date().toISOString(),
18
+ ...(input.artifact.summary ? { summary: input.artifact.summary } : {}),
19
+ };
20
+ return { type: "wait_human", action };
21
+ }
2
22
  function nextPhaseFor(current) {
3
23
  if (current === "spec_refinement")
4
24
  return "plan";
@@ -68,6 +88,9 @@ export class DefaultPhaseTransition {
68
88
  return { type: "stop", reason: "Background work still in progress" };
69
89
  }
70
90
  if (session.status === "failed") {
91
+ if (runtime.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
92
+ return { type: "recover", reason: "Exceeded consecutive failure retry budget" };
93
+ }
71
94
  return { type: "recover", reason: "Relevant session failed" };
72
95
  }
73
96
  if (workflow.phase === "spec_refinement"
@@ -141,6 +164,17 @@ export class DefaultPhaseTransition {
141
164
  reason: `Continue phase ${workflow.phase}`,
142
165
  };
143
166
  }
167
+ if (artifact.reportStatus === "unknown" && workflow.status === "in_progress" && (session.status === "idle" || session.status === "stale")) {
168
+ const escalation = shouldEscalateUnknownConclusion(input, "review");
169
+ if (escalation) {
170
+ return escalation;
171
+ }
172
+ return {
173
+ type: "dispatch",
174
+ phase: workflow.phase,
175
+ reason: "Review conclusion is still ambiguous; set an explicit PASS or FAIL conclusion",
176
+ };
177
+ }
144
178
  }
145
179
  if (workflow.phase === "test") {
146
180
  if (artifact.valid && artifact.missing.length === 0 && artifact.reportStatus === "pass") {
@@ -171,6 +205,17 @@ export class DefaultPhaseTransition {
171
205
  reason: `Continue phase ${workflow.phase}`,
172
206
  };
173
207
  }
208
+ if (artifact.reportStatus === "unknown" && workflow.status === "in_progress" && (session.status === "idle" || session.status === "stale")) {
209
+ const escalation = shouldEscalateUnknownConclusion(input, "test");
210
+ if (escalation) {
211
+ return escalation;
212
+ }
213
+ return {
214
+ type: "dispatch",
215
+ phase: workflow.phase,
216
+ reason: "Test conclusion is still ambiguous; set an explicit PASS or FAIL conclusion",
217
+ };
218
+ }
174
219
  }
175
220
  if (artifact.readyForNextPhase) {
176
221
  const nextPhase = nextPhaseFor(workflow.phase);
@@ -145,6 +145,7 @@ const extractSectionBody = (content, heading, allHeadings) => {
145
145
  return stripComments(content.slice(afterHeading, end));
146
146
  };
147
147
  const sectionHasContent = (content, heading, allHeadings) => extractSectionBody(content, heading, allHeadings).length > 0;
148
+ const isOptionalSection = (heading) => heading.includes("(如适用)") || heading.includes("(非阻塞,可选)");
148
149
  const sanitizeSummaryBody = (body) => body
149
150
  .split("\n")
150
151
  .map((line) => line.trim())
@@ -1090,6 +1091,9 @@ export class FileSystemArtifactEvaluator {
1090
1091
  missing.push(sectionRule.title);
1091
1092
  }
1092
1093
  for (const section of sectionRule.sections) {
1094
+ if (isOptionalSection(section)) {
1095
+ continue;
1096
+ }
1093
1097
  if (!content.includes(section) || !sectionHasContent(content, section, sectionRule.sections)) {
1094
1098
  missing.push(section);
1095
1099
  }
@@ -1140,8 +1144,8 @@ export class FileSystemArtifactEvaluator {
1140
1144
  readyForNextPhase: false,
1141
1145
  missing,
1142
1146
  summary: "审查报告结构不完整,暂不能决定 pass/fail",
1143
- reportStatus: "unknown",
1144
- hasBlockingSeverity: false,
1147
+ reportStatus: getReportStatus(content),
1148
+ hasBlockingSeverity: hasBlockingSeverity(content),
1145
1149
  };
1146
1150
  }
1147
1151
  if (phase === "test") {
@@ -1150,7 +1154,7 @@ export class FileSystemArtifactEvaluator {
1150
1154
  readyForNextPhase: false,
1151
1155
  missing,
1152
1156
  summary: "测试报告证据不足,暂不能决定 pass/fail",
1153
- reportStatus: "unknown",
1157
+ reportStatus: getReportStatus(content),
1154
1158
  hasBlockingSeverity: false,
1155
1159
  };
1156
1160
  }
@@ -26,6 +26,7 @@ export async function initializeWorkflow(args) {
26
26
  refinementAttempts: 0,
27
27
  refinementLastDispatchSummary: null,
28
28
  refinementEscalationReason: null,
29
+ phaseDispatchAttempts: {},
29
30
  };
30
31
  await stateStore.saveWorkflow(workflow);
31
32
  await stateStore.saveRuntime(runtime);
@@ -195,6 +195,12 @@ export class DefaultWorkflowEngine {
195
195
  });
196
196
  const waitRuntimePatch = {
197
197
  waitingHumanActionId: record.id,
198
+ phaseDispatchAttempts: {
199
+ ...(runtime.phaseDispatchAttempts ?? {}),
200
+ ...(workflow.phase === "spec_refinement" || workflow.phase === "plan" || workflow.phase === "develop" || workflow.phase === "review" || workflow.phase === "test"
201
+ ? { [workflow.phase]: 0 }
202
+ : {}),
203
+ },
198
204
  ...(workflow.phase === "spec_refinement" && (runtime.refinementAttempts ?? 0) > 0
199
205
  ? { refinementEscalationReason: "Autonomous refinement retry budget exhausted" }
200
206
  : {}),
@@ -229,6 +235,8 @@ export class DefaultWorkflowEngine {
229
235
  await this.deps.humanActionStore.markConsumed(currentHumanAction.id);
230
236
  await this.deps.stateStore.updateRuntime(workflowId, {
231
237
  waitingHumanActionId: null,
238
+ consecutiveFailures: 0,
239
+ phaseDispatchAttempts: {},
232
240
  ...(workflow.phase === "spec_refinement"
233
241
  ? {
234
242
  refinementAttempts: 0,
@@ -275,6 +283,13 @@ export class DefaultWorkflowEngine {
275
283
  : 0;
276
284
  const runtimePatch = {
277
285
  lastContinuationAt: new Date().toISOString(),
286
+ consecutiveFailures: 0,
287
+ phaseDispatchAttempts: {
288
+ ...(runtime.phaseDispatchAttempts ?? {}),
289
+ ...(action.phase === "spec_refinement" || action.phase === "plan" || action.phase === "develop" || action.phase === "review" || action.phase === "test"
290
+ ? { [action.phase]: (runtime.phaseDispatchAttempts?.[action.phase] ?? 0) + 1 }
291
+ : {}),
292
+ },
278
293
  ...(action.phase === "spec_refinement"
279
294
  ? {
280
295
  refinementAttempts: nextAttempt,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fkqfkq123/opencode-autopilot",
3
3
  "private": false,
4
- "version": "0.1.6",
4
+ "version": "0.1.8",
5
5
  "description": "An OpenCode plugin for attached-session workflow execution with refinement, planning, development, review, and test phases.",
6
6
  "type": "module",
7
7
  "packageManager": "bun@1.3.5",