@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.
- package/dist/packages/core/src/state/workflow-runtime-state.d.ts +2 -0
- package/dist/packages/core/src/transitions/default-phase-transition.js +45 -0
- package/dist/packages/runtime/src/artifacts/file-system-artifact-evaluator.js +7 -3
- package/dist/packages/runtime/src/bootstrap/initialize-workflow.js +1 -0
- package/dist/packages/runtime/src/engine/default-workflow-engine.js +15 -0
- package/package.json +1 -1
|
@@ -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:
|
|
1144
|
-
hasBlockingSeverity:
|
|
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:
|
|
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.
|
|
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",
|