@chenmk/superflow 0.4.0 → 0.5.0
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/INSTALL.en.md +54 -12
- package/INSTALL.md +59 -33
- package/README.md +178 -54
- package/README.zh-CN.md +156 -51
- package/assets/brand/managed-review-loop.png +0 -0
- package/assets/manifest.json +10 -5
- package/assets/scripts/superflow-coding-ready.mjs +135 -0
- package/assets/scripts/superflow-dependency-update-hook.sh +81 -8
- package/assets/scripts/superflow-document-audit.mjs +240 -0
- package/assets/scripts/superflow-environment-preflight.mjs +398 -0
- package/assets/scripts/superflow-hook-guard.sh +40 -1
- package/assets/scripts/superflow-managed-command-guard.py +309 -0
- package/assets/scripts/superflow-managed-executor-preflight.mjs +421 -0
- package/assets/scripts/superflow-managed-owner-verification.sh +117 -0
- package/assets/scripts/superflow-managed-work-check.mjs +287 -26
- package/assets/scripts/superflow-managed-work-guard.sh +6 -92
- package/assets/skills/superflow-clarify/SKILL.md +14 -0
- package/assets/skills/superflow-design/SKILL.md +38 -13
- package/assets/skills/superflow-docs/SKILL.md +93 -2
- package/assets/skills/superflow-pipeline/SKILL.md +167 -10
- package/assets/skills/superflow-pipeline/references/batch-prompt-template.md +17 -9
- package/assets/skills/superflow-pipeline/references/document-readiness.md +157 -0
- package/assets/skills/superflow-pipeline/references/document-templates.md +9 -5
- package/assets/skills/superflow-pipeline/references/feature-gated-workflow.md +14 -0
- package/assets/skills/superflow-pipeline/references/implementation-prompt-template.md +20 -11
- package/assets/skills/superflow-pipeline/references/managed-work.md +123 -15
- package/assets/skills/superflow-pipeline/references/orchestration.md +2 -2
- package/assets/skills/superflow-pipeline/references/p0-baseline-template.md +3 -2
- package/assets/skills/superflow-pipeline/references/quality-gate.md +7 -5
- package/assets/skills/superflow-pipeline/references/reviewer-checklist.md +7 -0
- package/assets/skills/superflow-pipeline/scripts/superflow-guard.sh +205 -9
- package/assets/skills/superflow-pipeline/scripts/superflow-handoff.sh +7 -4
- package/assets/skills/superflow-requirement-review/SKILL.md +178 -0
- package/assets/skills/superflow-requirement-review/agents/openai.yaml +4 -0
- package/assets/skills-en/superflow-clarify/SKILL.md +17 -0
- package/assets/skills-en/superflow-design/SKILL.md +31 -12
- package/assets/skills-en/superflow-docs/SKILL.md +99 -0
- package/assets/skills-en/superflow-pipeline/SKILL.md +163 -9
- package/assets/skills-en/superflow-pipeline/references/batch-prompt-template.md +4 -0
- package/assets/skills-en/superflow-pipeline/references/document-readiness.md +125 -0
- package/assets/skills-en/superflow-pipeline/references/document-templates.md +10 -0
- package/assets/skills-en/superflow-pipeline/references/feature-gated-workflow.md +7 -0
- package/assets/skills-en/superflow-pipeline/references/implementation-prompt-template.md +7 -0
- package/assets/skills-en/superflow-pipeline/references/managed-work.md +160 -10
- package/assets/skills-en/superflow-pipeline/references/quality-gate.md +12 -0
- package/assets/skills-en/superflow-pipeline/references/reviewer-checklist.md +7 -0
- package/assets/skills-en/superflow-pipeline/scripts/superflow-guard.sh +205 -9
- package/assets/skills-en/superflow-pipeline/scripts/superflow-handoff.sh +7 -4
- package/assets/skills-en/superflow-requirement-review/SKILL.md +99 -0
- package/assets/skills-en/superflow-requirement-review/agents/openai.yaml +4 -0
- package/dist/app/cli.js +64 -9
- package/dist/app/cli.js.map +1 -1
- package/dist/app/commands/change-guard.js +19 -2
- package/dist/app/commands/change-guard.js.map +1 -1
- package/dist/app/commands/check.js +107 -45
- package/dist/app/commands/check.js.map +1 -1
- package/dist/app/commands/docs.js +7 -3
- package/dist/app/commands/docs.js.map +1 -1
- package/dist/app/commands/doctor.js +174 -198
- package/dist/app/commands/doctor.js.map +1 -1
- package/dist/app/commands/eval.js +64 -0
- package/dist/app/commands/eval.js.map +1 -0
- package/dist/app/commands/implement.js +7 -3
- package/dist/app/commands/implement.js.map +1 -1
- package/dist/app/commands/init.js +12 -24
- package/dist/app/commands/init.js.map +1 -1
- package/dist/app/commands/mcp.js +167 -0
- package/dist/app/commands/mcp.js.map +1 -0
- package/dist/app/commands/pipeline.js +652 -18
- package/dist/app/commands/pipeline.js.map +1 -1
- package/dist/app/commands/requirement-review.js +6 -0
- package/dist/app/commands/requirement-review.js.map +1 -0
- package/dist/app/commands/status.js +66 -3
- package/dist/app/commands/status.js.map +1 -1
- package/dist/app/commands/uninstall.js +3 -3
- package/dist/app/commands/uninstall.js.map +1 -1
- package/dist/app/commands/update.js +83 -9
- package/dist/app/commands/update.js.map +1 -1
- package/dist/domains/agent.js +3 -11
- package/dist/domains/agent.js.map +1 -1
- package/dist/domains/config/cli-help.js +54 -12
- package/dist/domains/config/cli-help.js.map +1 -1
- package/dist/domains/config/i18n.js +6 -8
- package/dist/domains/config/i18n.js.map +1 -1
- package/dist/domains/config/manifest.js +0 -3
- package/dist/domains/config/manifest.js.map +1 -1
- package/dist/domains/deps.js +68 -7
- package/dist/domains/deps.js.map +1 -1
- package/dist/domains/managed-work/completion-policy.js +174 -0
- package/dist/domains/managed-work/completion-policy.js.map +1 -0
- package/dist/domains/managed-work/context-manifest.js +208 -0
- package/dist/domains/managed-work/context-manifest.js.map +1 -0
- package/dist/domains/managed-work/contract.js +135 -4
- package/dist/domains/managed-work/contract.js.map +1 -1
- package/dist/domains/managed-work/control-signals.js +34 -0
- package/dist/domains/managed-work/control-signals.js.map +1 -0
- package/dist/domains/managed-work/control.js +628 -0
- package/dist/domains/managed-work/control.js.map +1 -0
- package/dist/domains/managed-work/evaluation.js +497 -0
- package/dist/domains/managed-work/evaluation.js.map +1 -0
- package/dist/domains/managed-work/evidence-path.js +14 -0
- package/dist/domains/managed-work/evidence-path.js.map +1 -0
- package/dist/domains/managed-work/execution-contract.js +167 -0
- package/dist/domains/managed-work/execution-contract.js.map +1 -0
- package/dist/domains/managed-work/executor-policy.js +65 -0
- package/dist/domains/managed-work/executor-policy.js.map +1 -0
- package/dist/domains/managed-work/failure.js +65 -0
- package/dist/domains/managed-work/failure.js.map +1 -0
- package/dist/domains/managed-work/host-review.js +77 -0
- package/dist/domains/managed-work/host-review.js.map +1 -0
- package/dist/domains/managed-work/human-messages.js +31 -0
- package/dist/domains/managed-work/human-messages.js.map +1 -0
- package/dist/domains/managed-work/input.js +29 -3
- package/dist/domains/managed-work/input.js.map +1 -1
- package/dist/domains/managed-work/journal.js +77 -3
- package/dist/domains/managed-work/journal.js.map +1 -1
- package/dist/domains/managed-work/lock.js +67 -1
- package/dist/domains/managed-work/lock.js.map +1 -1
- package/dist/domains/managed-work/notifications.js +0 -15
- package/dist/domains/managed-work/notifications.js.map +1 -1
- package/dist/domains/managed-work/pair-admission.js +108 -0
- package/dist/domains/managed-work/pair-admission.js.map +1 -0
- package/dist/domains/managed-work/prompts.js +257 -22
- package/dist/domains/managed-work/prompts.js.map +1 -1
- package/dist/domains/managed-work/redaction.js +90 -0
- package/dist/domains/managed-work/redaction.js.map +1 -0
- package/dist/domains/managed-work/review-convergence.js +41 -0
- package/dist/domains/managed-work/review-convergence.js.map +1 -0
- package/dist/domains/managed-work/review-facts.js +181 -0
- package/dist/domains/managed-work/review-facts.js.map +1 -0
- package/dist/domains/managed-work/rule-selection.js +84 -0
- package/dist/domains/managed-work/rule-selection.js.map +1 -0
- package/dist/domains/managed-work/runner.js +1967 -175
- package/dist/domains/managed-work/runner.js.map +1 -1
- package/dist/domains/managed-work/schemas.js +72 -2
- package/dist/domains/managed-work/schemas.js.map +1 -1
- package/dist/domains/managed-work/service.js +82 -13
- package/dist/domains/managed-work/service.js.map +1 -1
- package/dist/domains/managed-work/state.js +195 -5
- package/dist/domains/managed-work/state.js.map +1 -1
- package/dist/domains/managed-work/storage.js +66 -24
- package/dist/domains/managed-work/storage.js.map +1 -1
- package/dist/domains/managed-work/types.js +12 -9
- package/dist/domains/managed-work/types.js.map +1 -1
- package/dist/domains/managed-work/verification-categories.js +39 -0
- package/dist/domains/managed-work/verification-categories.js.map +1 -0
- package/dist/domains/managed-work/verification-policy.js +116 -0
- package/dist/domains/managed-work/verification-policy.js.map +1 -0
- package/dist/domains/managed-work/wait.js +21 -3
- package/dist/domains/managed-work/wait.js.map +1 -1
- package/dist/domains/managed-work/workspace-binding.js +133 -0
- package/dist/domains/managed-work/workspace-binding.js.map +1 -0
- package/dist/domains/sdd-readiness.js +44 -0
- package/dist/domains/sdd-readiness.js.map +1 -0
- package/dist/domains/skill/deploy.js +0 -6
- package/dist/domains/skill/deploy.js.map +1 -1
- package/dist/domains/skill/scripts.js +0 -8
- package/dist/domains/skill/scripts.js.map +1 -1
- package/dist/domains/state.js +1 -2
- package/dist/domains/state.js.map +1 -1
- package/dist/mcp/runtime-identity.js +21 -0
- package/dist/mcp/runtime-identity.js.map +1 -0
- package/dist/mcp/server.js +369 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/platform/agent-process.js +499 -38
- package/dist/platform/agent-process.js.map +1 -1
- package/dist/platform/executable.js +28 -0
- package/dist/platform/executable.js.map +1 -0
- package/dist/platform/paths.js +1 -17
- package/dist/platform/paths.js.map +1 -1
- package/dist/platform/process-tree.js +44 -0
- package/dist/platform/process-tree.js.map +1 -0
- package/dist/platform/process.js +1 -1
- package/dist/platform/process.js.map +1 -1
- package/package.json +16 -7
- package/scripts/check-design-principles.mjs +107 -0
- package/scripts/check-managed-work-design.mjs +507 -0
- package/scripts/clean-dist.js +9 -0
- package/scripts/ensure-bin-modes.js +11 -0
- package/dist/cli/index.js +0 -193
- package/dist/cli/index.js.map +0 -1
- package/dist/commands/archive.js +0 -6
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/clarify.js +0 -6
- package/dist/commands/clarify.js.map +0 -1
- package/dist/commands/design.js +0 -28
- package/dist/commands/design.js.map +0 -1
- package/dist/commands/docs.js +0 -6
- package/dist/commands/docs.js.map +0 -1
- package/dist/commands/doctor.js +0 -494
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/implement.js +0 -6
- package/dist/commands/implement.js.map +0 -1
- package/dist/commands/init.js +0 -520
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/pipeline.js +0 -6
- package/dist/commands/pipeline.js.map +0 -1
- package/dist/commands/scan.js +0 -63
- package/dist/commands/scan.js.map +0 -1
- package/dist/commands/status.js +0 -173
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/uninstall.js +0 -214
- package/dist/commands/uninstall.js.map +0 -1
- package/dist/commands/update.js +0 -193
- package/dist/commands/update.js.map +0 -1
- package/dist/commands/verify.js +0 -6
- package/dist/commands/verify.js.map +0 -1
- package/dist/core/assets.js +0 -27
- package/dist/core/assets.js.map +0 -1
- package/dist/core/cli-help.js +0 -98
- package/dist/core/cli-help.js.map +0 -1
- package/dist/core/context.js +0 -123
- package/dist/core/context.js.map +0 -1
- package/dist/core/dependencies.js +0 -146
- package/dist/core/dependencies.js.map +0 -1
- package/dist/core/detect.js +0 -64
- package/dist/core/detect.js.map +0 -1
- package/dist/core/i18n.js +0 -105
- package/dist/core/i18n.js.map +0 -1
- package/dist/core/integrity.js +0 -46
- package/dist/core/integrity.js.map +0 -1
- package/dist/core/manifest.js +0 -21
- package/dist/core/manifest.js.map +0 -1
- package/dist/core/prompts.js +0 -20
- package/dist/core/prompts.js.map +0 -1
- package/dist/core/registry.js +0 -134
- package/dist/core/registry.js.map +0 -1
- package/dist/core/rules.js +0 -17
- package/dist/core/rules.js.map +0 -1
- package/dist/core/scripts.js +0 -48
- package/dist/core/scripts.js.map +0 -1
- package/dist/core/skill-check.js +0 -32
- package/dist/core/skill-check.js.map +0 -1
- package/dist/core/skills.js +0 -62
- package/dist/core/skills.js.map +0 -1
- package/dist/core/state.js +0 -54
- package/dist/core/state.js.map +0 -1
- package/dist/utils/path.js +0 -11
- package/dist/utils/path.js.map +0 -1
- package/dist/utils/shell.js +0 -29
- package/dist/utils/shell.js.map +0 -1
|
@@ -1,23 +1,38 @@
|
|
|
1
1
|
import { checkSkillDeployment, } from "../../domains/skill/check.js";
|
|
2
|
-
import { createManagedTaskContract } from "../../domains/managed-work/contract.js";
|
|
2
|
+
import { calculateManagedContractHash, createManagedTaskContract, validateManagedTaskContract, } from "../../domains/managed-work/contract.js";
|
|
3
3
|
import { appendManagedEvent } from "../../domains/managed-work/journal.js";
|
|
4
4
|
import { ensureManagedService } from "../../domains/managed-work/service.js";
|
|
5
5
|
import { createManagedTaskFiles, loadManagedRun, loadManagedTask, loadRegistry, saveManagedRun, saveManagedTask, upsertRegistryEntry, } from "../../domains/managed-work/storage.js";
|
|
6
6
|
import { initManagedRunState } from "../../domains/managed-work/state.js";
|
|
7
|
+
import { managedRunDir } from "../../domains/managed-work/paths.js";
|
|
8
|
+
import { effectiveExecutorInvocations, effectiveTotalAgentInvocations, } from "../../domains/managed-work/state.js";
|
|
9
|
+
import { submitExternalHostReviewResult } from "../../domains/managed-work/host-review.js";
|
|
7
10
|
import { resolveManagedInput } from "../../domains/managed-work/input.js";
|
|
8
11
|
import { managedTaskReportPath, waitForManagedTask, } from "../../domains/managed-work/wait.js";
|
|
9
12
|
import path from "path";
|
|
10
|
-
import { existsSync, realpathSync } from "fs";
|
|
13
|
+
import { existsSync, readFileSync, realpathSync, writeFileSync } from "fs";
|
|
11
14
|
import { resolveRuntimeLanguage } from "../../domains/config/cli-help.js";
|
|
12
15
|
import { managedText } from "../../domains/managed-work/i18n.js";
|
|
16
|
+
import { evaluateCompletion } from "../../domains/managed-work/completion-policy.js";
|
|
17
|
+
import { isProcessAlive } from "../../platform/process-liveness.js";
|
|
18
|
+
import { assertCodingReadyForPrompt } from "../../domains/sdd-readiness.js";
|
|
19
|
+
import { assertManagedAgentPair } from "../../domains/managed-work/pair-admission.js";
|
|
20
|
+
import { submitHumanDirectedDelivery } from "../../domains/managed-work/control.js";
|
|
21
|
+
import { writeManagedExecutorHandoff } from "../../domains/managed-work/runner.js";
|
|
13
22
|
const SKILL_NAME = "superflow-pipeline";
|
|
14
23
|
export async function pipelineCommand(request, options = {}) {
|
|
15
24
|
const language = resolveRuntimeLanguage(options.language);
|
|
25
|
+
if (options.submitManualDelivery) {
|
|
26
|
+
const delivery = JSON.parse(readFileSync(options.submitManualDelivery, "utf8"));
|
|
27
|
+
const state = await submitHumanDirectedDelivery(options.resumeTask ?? "", delivery);
|
|
28
|
+
console.log(`人工交付已通过统一门禁,当前状态:${state.status}`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
16
31
|
if (options.resumeTask) {
|
|
17
32
|
await resumeManagedTask(options.resumeTask, options, language);
|
|
18
33
|
return;
|
|
19
34
|
}
|
|
20
|
-
if (options.managed) {
|
|
35
|
+
if (options.managed || options.manual) {
|
|
21
36
|
await submitManagedTask(request, options, language);
|
|
22
37
|
return;
|
|
23
38
|
}
|
|
@@ -33,35 +48,89 @@ async function submitManagedTask(request, options, language) {
|
|
|
33
48
|
profile: options.profile,
|
|
34
49
|
language,
|
|
35
50
|
});
|
|
51
|
+
if (input.source === "sdd" && input.taskPromptPath && !options.dryRun) {
|
|
52
|
+
assertCodingReadyForPrompt(input.taskPromptPath);
|
|
53
|
+
}
|
|
54
|
+
const agents = resolveManagedAgents(options);
|
|
36
55
|
const contract = createManagedTaskContract({
|
|
37
56
|
request: input.request,
|
|
38
57
|
projectRoot: input.projectRoot,
|
|
39
58
|
relatedProjectRoots: input.relatedProjectRoots,
|
|
40
59
|
profile: input.profile,
|
|
41
|
-
supervisorAgent:
|
|
42
|
-
executorAgent:
|
|
60
|
+
supervisorAgent: agents.supervisorAgent,
|
|
61
|
+
executorAgent: agents.executorAgent,
|
|
43
62
|
source: input.source,
|
|
44
63
|
taskPromptPath: input.taskPromptPath,
|
|
45
64
|
language,
|
|
65
|
+
executionMode: options.manual ? "human_directed" : "delegated",
|
|
46
66
|
});
|
|
47
67
|
const state = initManagedRunState(contract);
|
|
48
68
|
if (options.dryRun) {
|
|
49
69
|
console.log(JSON.stringify({ contract, run: state }, null, 2));
|
|
50
70
|
return;
|
|
51
71
|
}
|
|
72
|
+
assertManagedAgentPair(agents.supervisorAgent, agents.executorAgent);
|
|
73
|
+
// CLI and MCP share the same control-plane contract: establish the runtime
|
|
74
|
+
// before persistence, then rely on atomic task/registry rollback on failure.
|
|
75
|
+
const service = options.manual
|
|
76
|
+
? null
|
|
77
|
+
: ensureManagedService(process.argv[1], process.env, language);
|
|
52
78
|
createManagedTaskFiles(contract, state);
|
|
53
79
|
appendManagedEvent(state, {
|
|
54
80
|
eventType: "run.created",
|
|
55
81
|
actor: "superflow-pipeline",
|
|
56
82
|
role: "runner",
|
|
57
|
-
summary: managedText(language, "已创建托管任务并冻结最小任务合同", "Created managed task and froze the minimal task contract"),
|
|
83
|
+
summary: managedText(language, options.manual ? "已创建人工执行任务并冻结统一执行合同" : "已创建托管任务并冻结最小任务合同", options.manual ? "Created human-directed task and froze the shared execution contract" : "Created managed task and froze the minimal task contract"),
|
|
58
84
|
});
|
|
85
|
+
if (options.manual) {
|
|
86
|
+
const handoff = writeManagedExecutorHandoff(contract, state, []);
|
|
87
|
+
console.log(`请把冻结交接包交给 ${contract.executorAgent} 执行:${handoff}`);
|
|
88
|
+
console.log(`交付 JSON 格式参见:${path.join(contract.projectRoot, ".superflow", "tasks", contract.taskId, "execution-contract.md")}`);
|
|
89
|
+
console.log(`执行完成后,用 --resume-task ${contract.taskId} --submit-manual-delivery <交付JSON> 提交;系统会复用托管门禁与独立评审。`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
59
92
|
console.log(managedText(language, `已创建托管任务:${contract.taskId}`, `Managed task created: ${contract.taskId}`));
|
|
60
93
|
console.log(managedText(language, `任务目录:${contract.projectRoot}/.superflow/tasks/${contract.taskId}`, `Task directory: ${contract.projectRoot}/.superflow/tasks/${contract.taskId}`));
|
|
61
|
-
const service = ensureManagedService();
|
|
62
94
|
console.log(managedText(language, `后台服务已接收任务,PID:${service.pid}`, `Background service accepted the task, PID: ${service.pid}`));
|
|
63
95
|
await waitAndReport(contract.projectRoot, contract.taskId, state.runId, language);
|
|
64
96
|
}
|
|
97
|
+
export function resolveManagedAgents(options, env = process.env) {
|
|
98
|
+
const supervisorSelector = options.supervisor ?? "current";
|
|
99
|
+
const executorSelector = options.executor;
|
|
100
|
+
const needsCurrent = [supervisorSelector, executorSelector].includes("current") ||
|
|
101
|
+
[supervisorSelector, executorSelector].includes("peer") ||
|
|
102
|
+
executorSelector === undefined;
|
|
103
|
+
const current = needsCurrent ? detectCurrentHostAgent(env) : null;
|
|
104
|
+
const supervisorAgent = resolveAgentSelector(supervisorSelector, current);
|
|
105
|
+
const executorAgent = executorSelector
|
|
106
|
+
? resolveAgentSelector(executorSelector, current)
|
|
107
|
+
: oppositeManagedAgent(supervisorAgent);
|
|
108
|
+
if (supervisorAgent === executorAgent) {
|
|
109
|
+
throw new Error(managedText(undefined, "监督 Agent 和执行 Agent 不能相同", "Supervisor and executor agents must be different"));
|
|
110
|
+
}
|
|
111
|
+
return { supervisorAgent, executorAgent };
|
|
112
|
+
}
|
|
113
|
+
function detectCurrentHostAgent(env) {
|
|
114
|
+
const configured = env.SUPERFLOW_HOST_AGENT?.trim().toLowerCase();
|
|
115
|
+
if (configured === "codex" || configured === "claude")
|
|
116
|
+
return configured;
|
|
117
|
+
const claude = Boolean(env.CLAUDECODE || env.CLAUDE_CODE_ENTRYPOINT);
|
|
118
|
+
const codex = Boolean(env.CODEX_THREAD_ID || env.CODEX_SANDBOX);
|
|
119
|
+
if (claude !== codex)
|
|
120
|
+
return claude ? "claude" : "codex";
|
|
121
|
+
throw new Error(managedText(undefined, "无法识别当前 Host Agent;请设置 SUPERFLOW_HOST_AGENT=codex|claude,或显式传入实际角色", "Unable to detect the current host Agent; set SUPERFLOW_HOST_AGENT=codex|claude or pass explicit roles"));
|
|
122
|
+
}
|
|
123
|
+
function resolveAgentSelector(selector, current) {
|
|
124
|
+
if (selector === "codex" || selector === "claude")
|
|
125
|
+
return selector;
|
|
126
|
+
if (!current) {
|
|
127
|
+
throw new Error("Current host Agent is required for current/peer selectors");
|
|
128
|
+
}
|
|
129
|
+
return selector === "current" ? current : oppositeManagedAgent(current);
|
|
130
|
+
}
|
|
131
|
+
function oppositeManagedAgent(agent) {
|
|
132
|
+
return agent === "codex" ? "claude" : "codex";
|
|
133
|
+
}
|
|
65
134
|
async function resumeManagedTask(taskId, options, requestedLanguage) {
|
|
66
135
|
const registry = loadRegistry();
|
|
67
136
|
const entry = registry.tasks.find((item) => item.taskId === taskId);
|
|
@@ -75,19 +144,175 @@ async function resumeManagedTask(taskId, options, requestedLanguage) {
|
|
|
75
144
|
const contract = loadManagedTask(entry.projectRoot, taskId);
|
|
76
145
|
const language = contract.language ?? requestedLanguage;
|
|
77
146
|
const state = loadManagedRun(entry.projectRoot, taskId, entry.activeRunId);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
147
|
+
if (options.submitHostReview) {
|
|
148
|
+
const resultPath = submitExternalHostReview(contract, state, options.submitHostReview, language);
|
|
149
|
+
appendManagedEvent(state, {
|
|
150
|
+
eventType: "review.external_submitted",
|
|
151
|
+
actor: contract.supervisorAgent,
|
|
152
|
+
role: "supervisor",
|
|
153
|
+
summary: managedText(language, "当前 Host Codex 已提交结构化评审结果;CLI 宿主未暴露本轮 Host usage,按未知记录", "The current Codex host submitted a structured review; CLI Host usage is unavailable and recorded as unknown"),
|
|
154
|
+
evidencePaths: [resultPath],
|
|
155
|
+
});
|
|
156
|
+
const service = ensureManagedService(process.argv[1], process.env, language);
|
|
157
|
+
console.log(managedText(language, `已接收 Host 评审并恢复托管任务:${taskId},后台服务 PID:${service.pid}`, `Accepted the host review and resumed task ${taskId}; background service PID: ${service.pid}`));
|
|
158
|
+
await waitAndReport(entry.projectRoot, taskId, state.runId, language);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (shouldAttachRunningTask(state)) {
|
|
162
|
+
if (hasRunningResumeMutations(options)) {
|
|
163
|
+
throw new Error(managedText(language, "任务仍由存活的 Executor 执行;当前只能安全接入等待,不能同时修改预算、会话或恢复参数", "The task still has a live Executor; this command may only attach and wait, not mutate budgets, sessions, or recovery options"));
|
|
164
|
+
}
|
|
165
|
+
const service = ensureManagedService(process.argv[1], process.env, language);
|
|
166
|
+
console.log(managedText(language, `已安全接入正在运行的托管任务:${taskId},未修改状态;后台服务 PID:${service.pid}`, `Safely attached to running task ${taskId} without mutating state; background service PID: ${service.pid}`));
|
|
167
|
+
await waitAndReport(entry.projectRoot, taskId, state.runId, language);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const reopenedDelivery = reopenAwaitingDelivery(state, options.reopenDelivery, language);
|
|
171
|
+
if (reopenedDelivery) {
|
|
172
|
+
const reviewFile = path.join(managedRunDir(entry.projectRoot, taskId, state.runId), `review-reopen-${state.reviewRound + 1}-${Date.now()}.json`);
|
|
173
|
+
writeFileSync(reviewFile, JSON.stringify(reopenedDelivery, null, 2));
|
|
174
|
+
state.lastReviewResult = reviewFile;
|
|
175
|
+
saveManagedRun(state);
|
|
176
|
+
appendManagedEvent(state, {
|
|
177
|
+
eventType: "run.delivery_reopened",
|
|
178
|
+
actor: "superflow-pipeline",
|
|
179
|
+
role: "runner",
|
|
180
|
+
summary: managedText(language, `用户驳回交付并要求继续整改:${options.reopenDelivery.trim()}`, `The user rejected the delivery and requested correction: ${options.reopenDelivery.trim()}`),
|
|
181
|
+
evidencePaths: [reviewFile],
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
const budgetChange = applyManagedBudgetIncrease(contract, state, options, language);
|
|
185
|
+
const unlimitedBudgetEnabled = applyUnlimitedAgentBudget(contract, state, options, language);
|
|
186
|
+
const executorWindow = applyExecutorInvocationWindow(contract, state, options, language);
|
|
187
|
+
if (!canResumeManagedTask(state, contract, options)) {
|
|
85
188
|
throw new Error(managedText(language, `任务当前状态 ${state.status} 不允许恢复`, `Task status ${state.status} cannot be resumed`));
|
|
86
189
|
}
|
|
190
|
+
const staleExecutor = recoverStaleRunningExecutor(state);
|
|
191
|
+
if (staleExecutor) {
|
|
192
|
+
saveManagedRun(state);
|
|
193
|
+
appendManagedEvent(state, {
|
|
194
|
+
eventType: "executor.stale_process_recovered",
|
|
195
|
+
actor: "superflow-pipeline",
|
|
196
|
+
role: "runner",
|
|
197
|
+
summary: managedText(language, `检测到断电或重启后遗留的运行状态,执行进程 ${staleExecutor.pid} 已不存在;已停止计算中断期间的活跃耗时并进入安全恢复`, `Detected a stale running state after shutdown or restart; executor process ${staleExecutor.pid} no longer exists. Interrupted downtime is excluded from active runtime and safe recovery may continue`),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
const sessionRecoveryOptions = [
|
|
201
|
+
options.replaceExecutorSession,
|
|
202
|
+
options.resetExecutorSession,
|
|
203
|
+
options.providerSwitched,
|
|
204
|
+
].filter((value) => value !== undefined);
|
|
205
|
+
if (sessionRecoveryOptions.length > 1) {
|
|
206
|
+
throw new Error(managedText(language, "替换会话、重置会话与供应商切换恢复不能同时使用", "Session replacement, session reset, and provider-switch recovery cannot be combined"));
|
|
207
|
+
}
|
|
208
|
+
const providerSwitch = confirmProviderSwitchForRecovery(state, options.providerSwitched, language);
|
|
209
|
+
const previousExecutorSession = replaceExecutorSessionForRecovery(state, options.replaceExecutorSession, language);
|
|
210
|
+
const resetExecutorSession = resetExecutorSessionForRecovery(state, options.resetExecutorSession, language);
|
|
211
|
+
const blockedExecutorRetry = retryBlockedExecutorForRecovery(state, options.retryBlockedExecutor, language);
|
|
212
|
+
if (blockedExecutorRetry) {
|
|
213
|
+
const retryFile = path.join(managedRunDir(entry.projectRoot, taskId, state.runId), `review-human-retry-${state.reviewRound}-${Date.now()}.json`);
|
|
214
|
+
writeFileSync(retryFile, JSON.stringify(blockedExecutorRetry, null, 2));
|
|
215
|
+
state.lastReviewResult = retryFile;
|
|
216
|
+
state.lastExecutorResult = null;
|
|
217
|
+
saveManagedRun(state);
|
|
218
|
+
appendManagedEvent(state, {
|
|
219
|
+
eventType: "executor.blocked_retry_requested",
|
|
220
|
+
actor: "superflow-pipeline",
|
|
221
|
+
role: "runner",
|
|
222
|
+
summary: managedText(language, `人工要求被阻塞的执行者继续完成可执行工作:${options.retryBlockedExecutor.trim()}`, `Human requested the blocked executor to continue locally executable work: ${options.retryBlockedExecutor.trim()}`),
|
|
223
|
+
evidencePaths: [retryFile],
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
const credited = applyInfrastructureInvocationCredits(state, options.creditInfrastructureInvocations, language);
|
|
227
|
+
if (credited > 0) {
|
|
228
|
+
saveManagedRun(state);
|
|
229
|
+
appendManagedEvent(state, {
|
|
230
|
+
eventType: "budget.infrastructure_credited",
|
|
231
|
+
actor: "superflow-pipeline",
|
|
232
|
+
role: "runner",
|
|
233
|
+
summary: managedText(language, `人工确认抵扣 ${credited} 次基础设施执行调用;有效执行 ${effectiveExecutorInvocations(state)}/${contract.budgets.maxExecutorInvocations},有效总调用 ${effectiveTotalAgentInvocations(state)}/${contract.budgets.maxTotalAgentInvocations}`, `Human-approved credit for ${credited} infrastructure executor calls; effective executor ${effectiveExecutorInvocations(state)}/${contract.budgets.maxExecutorInvocations}, effective total ${effectiveTotalAgentInvocations(state)}/${contract.budgets.maxTotalAgentInvocations}`),
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
if (budgetChange) {
|
|
237
|
+
saveManagedRun(state);
|
|
238
|
+
saveManagedTask(contract);
|
|
239
|
+
appendManagedEvent(state, {
|
|
240
|
+
eventType: "budget.limit_increased",
|
|
241
|
+
actor: "superflow-pipeline",
|
|
242
|
+
role: "runner",
|
|
243
|
+
summary: managedText(language, `人工提高托管预算:执行 ${budgetChange.before.maxExecutorInvocations}→${contract.budgets.maxExecutorInvocations},评审 ${budgetChange.before.maxReviewRounds}→${contract.budgets.maxReviewRounds},总调用 ${budgetChange.before.maxTotalAgentInvocations}→${contract.budgets.maxTotalAgentInvocations};原因:${budgetChange.reason}`, `Human-approved managed budget increase: executor ${budgetChange.before.maxExecutorInvocations}→${contract.budgets.maxExecutorInvocations}, review ${budgetChange.before.maxReviewRounds}→${contract.budgets.maxReviewRounds}, total ${budgetChange.before.maxTotalAgentInvocations}→${contract.budgets.maxTotalAgentInvocations}; reason: ${budgetChange.reason}`),
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
if (unlimitedBudgetEnabled) {
|
|
247
|
+
saveManagedRun(state);
|
|
248
|
+
saveManagedTask(contract);
|
|
249
|
+
appendManagedEvent(state, {
|
|
250
|
+
eventType: "budget.unlimited_enabled",
|
|
251
|
+
actor: "superflow-pipeline",
|
|
252
|
+
role: "runner",
|
|
253
|
+
summary: managedText(language, `人工临时取消 Agent 调用次数限制;原因:${options.budgetOverrideReason.trim()}`, `Human-approved temporary removal of Agent invocation limits; reason: ${options.budgetOverrideReason.trim()}`),
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
if (executorWindow) {
|
|
257
|
+
saveManagedRun(state);
|
|
258
|
+
saveManagedTask(contract);
|
|
259
|
+
appendManagedEvent(state, {
|
|
260
|
+
eventType: "budget.executor_window_set",
|
|
261
|
+
actor: "superflow-pipeline",
|
|
262
|
+
role: "runner",
|
|
263
|
+
summary: managedText(language, `人工设置 Claude 临时调用窗口:物理调用 ${executorWindow.before}→${executorWindow.stopAt},新增 ${executorWindow.count} 次;原因:${executorWindow.reason}`, `Human-approved temporary Claude invocation window: physical calls ${executorWindow.before}→${executorWindow.stopAt}, ${executorWindow.count} additional calls; reason: ${executorWindow.reason}`),
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
if (previousExecutorSession) {
|
|
267
|
+
saveManagedRun(state);
|
|
268
|
+
appendManagedEvent(state, {
|
|
269
|
+
eventType: "executor.session_replaced",
|
|
270
|
+
actor: "superflow-pipeline",
|
|
271
|
+
role: "runner",
|
|
272
|
+
summary: managedText(language, `人工确认跨模型恢复,执行会话从 ${previousExecutorSession} 替换为 ${state.executorSession.sessionId}`, `Human-approved cross-model recovery replaced executor session ${previousExecutorSession} with ${state.executorSession.sessionId}`),
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
if (resetExecutorSession) {
|
|
276
|
+
saveManagedRun(state);
|
|
277
|
+
appendManagedEvent(state, {
|
|
278
|
+
eventType: "executor.session_reset",
|
|
279
|
+
actor: "superflow-pipeline",
|
|
280
|
+
role: "runner",
|
|
281
|
+
summary: managedText(language, `人工确认放弃异常执行会话 ${resetExecutorSession.previousSessionId},下次调用将创建全新 Claude 会话;原因:${resetExecutorSession.reason}`, `Human-approved reset of unhealthy executor session ${resetExecutorSession.previousSessionId}; the next invocation will create a fresh Claude session; reason: ${resetExecutorSession.reason}`),
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
if (providerSwitch) {
|
|
285
|
+
saveManagedRun(state);
|
|
286
|
+
appendManagedEvent(state, {
|
|
287
|
+
eventType: "provider.switch_confirmed",
|
|
288
|
+
actor: "superflow-pipeline",
|
|
289
|
+
role: "runner",
|
|
290
|
+
summary: managedText(language, `用户确认已切换底层供应商,放弃异常会话 ${providerSwitch.previousSessionId} 并从全新会话恢复;原因:${providerSwitch.reason}`, `The user confirmed a provider switch, discarded unhealthy session ${providerSwitch.previousSessionId}, and will recover in a fresh session; reason: ${providerSwitch.reason}`),
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
const recoveredExecutorResult = recoverAcceptedExecutorResult(contract, state);
|
|
294
|
+
if (recoveredExecutorResult) {
|
|
295
|
+
saveManagedRun(state);
|
|
296
|
+
appendManagedEvent(state, {
|
|
297
|
+
eventType: "executor.result_recovered",
|
|
298
|
+
actor: "superflow-pipeline",
|
|
299
|
+
role: "runner",
|
|
300
|
+
summary: managedText(language, "已按当前规则重新裁决既有执行结果,无需再次调用研发 Agent", "The persisted executor result passed current rules; no new executor invocation is required"),
|
|
301
|
+
evidencePaths: [recoveredExecutorResult],
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
const resumeBlockedExecutor = shouldResumeBlockedExecutor(state);
|
|
305
|
+
const reopenBlockedReview = blockedExecutorRetry
|
|
306
|
+
? false
|
|
307
|
+
: shouldReopenBlockedReview(state);
|
|
87
308
|
state.status = "queued";
|
|
88
309
|
state.currentStep = "recovering";
|
|
89
310
|
state.blocker = null;
|
|
90
311
|
state.activeSince = null;
|
|
312
|
+
if (resumeBlockedExecutor)
|
|
313
|
+
state.lastExecutorResult = null;
|
|
314
|
+
if (reopenBlockedReview)
|
|
315
|
+
state.lastReviewResult = null;
|
|
91
316
|
contract.status = "queued";
|
|
92
317
|
saveManagedRun(state);
|
|
93
318
|
saveManagedTask(contract);
|
|
@@ -100,27 +325,436 @@ async function resumeManagedTask(taskId, options, requestedLanguage) {
|
|
|
100
325
|
eventType: "recovery.started",
|
|
101
326
|
actor: "superflow-pipeline",
|
|
102
327
|
role: "runner",
|
|
103
|
-
summary: managedText(language,
|
|
328
|
+
summary: managedText(language, resumeBlockedExecutor
|
|
329
|
+
? "已确认人工处理,从当前工作区和全新短会话继续未完成工作"
|
|
330
|
+
: providerSwitch
|
|
331
|
+
? "已确认供应商切换,从任务记录、当前工作区和全新执行会话继续"
|
|
332
|
+
: resetExecutorSession
|
|
333
|
+
? "已确认人工处理,将从任务记录、当前工作区和全新执行会话继续"
|
|
334
|
+
: "已请求从任务记录、当前工作区和全新短会话恢复", resumeBlockedExecutor
|
|
335
|
+
? "Human action confirmed; continuing unfinished work from the current workspace in a fresh short session"
|
|
336
|
+
: providerSwitch
|
|
337
|
+
? "Provider switch confirmed; continuing from task records, the current workspace, and a fresh executor session"
|
|
338
|
+
: resetExecutorSession
|
|
339
|
+
? "Human action confirmed; continuing from task records, the current workspace, and a fresh executor session"
|
|
340
|
+
: "Requested recovery from task records, the current workspace, and a fresh short session"),
|
|
104
341
|
});
|
|
105
|
-
|
|
342
|
+
if (reopenBlockedReview) {
|
|
343
|
+
appendManagedEvent(state, {
|
|
344
|
+
eventType: "review.reopened",
|
|
345
|
+
actor: "superflow-pipeline",
|
|
346
|
+
role: "runner",
|
|
347
|
+
summary: managedText(language, "人工恢复被阻塞的监督评审,将基于当前工作区与最新门禁重新检查", "Human recovery reopened the blocked supervisor review against the current workspace and latest gates"),
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
const service = ensureManagedService(process.argv[1], process.env, language);
|
|
106
351
|
console.log(managedText(language, `已恢复托管任务:${taskId},后台服务 PID:${service.pid}`, `Managed task resumed: ${taskId}; background service PID: ${service.pid}`));
|
|
107
352
|
await waitAndReport(entry.projectRoot, taskId, state.runId, language);
|
|
108
353
|
}
|
|
354
|
+
export function submitExternalHostReview(contract, state, reviewPath, language = "zh", env = process.env) {
|
|
355
|
+
const resolvedReviewPath = path.resolve(reviewPath);
|
|
356
|
+
const roots = [contract.projectRoot, ...contract.relatedProjectRoots].map((root) => path.resolve(root));
|
|
357
|
+
if (roots.some((root) => resolvedReviewPath === root ||
|
|
358
|
+
resolvedReviewPath.startsWith(`${root}${path.sep}`))) {
|
|
359
|
+
throw new Error(managedText(language, "Host 评审提交文件必须放在全部目标项目目录之外,避免污染 changedFiles", "The host review submission file must be outside every target project to avoid polluting changedFiles"));
|
|
360
|
+
}
|
|
361
|
+
const result = JSON.parse(readFileSync(resolvedReviewPath, "utf-8"));
|
|
362
|
+
return submitExternalHostReviewResult(contract, state, result, language, env);
|
|
363
|
+
}
|
|
364
|
+
export function applyInfrastructureInvocationCredits(state, value, language = "zh") {
|
|
365
|
+
if (value === undefined)
|
|
366
|
+
return 0;
|
|
367
|
+
const count = Number(value);
|
|
368
|
+
if (!Number.isInteger(count) || count <= 0) {
|
|
369
|
+
throw new Error(managedText(language, "基础设施调用抵扣必须是正整数", "Infrastructure invocation credits must be a positive integer"));
|
|
370
|
+
}
|
|
371
|
+
const executorCredits = (state.executorInvocationCredits ?? 0) + count;
|
|
372
|
+
const totalCredits = (state.totalAgentInvocationCredits ?? 0) + count;
|
|
373
|
+
if (executorCredits > state.executorInvocations ||
|
|
374
|
+
totalCredits > state.totalAgentInvocations) {
|
|
375
|
+
throw new Error(managedText(language, "基础设施调用抵扣不能超过已发生的物理调用数", "Infrastructure invocation credits cannot exceed physical calls"));
|
|
376
|
+
}
|
|
377
|
+
state.executorInvocationCredits = executorCredits;
|
|
378
|
+
state.totalAgentInvocationCredits = totalCredits;
|
|
379
|
+
return count;
|
|
380
|
+
}
|
|
381
|
+
export function applyManagedBudgetIncrease(contract, state, options, language = "zh") {
|
|
382
|
+
const values = [
|
|
383
|
+
options.maxExecutorInvocations,
|
|
384
|
+
options.maxReviewRounds,
|
|
385
|
+
options.maxTotalAgentInvocations,
|
|
386
|
+
];
|
|
387
|
+
if (values.every((value) => value === undefined))
|
|
388
|
+
return null;
|
|
389
|
+
const reason = options.budgetOverrideReason?.trim();
|
|
390
|
+
if (!reason) {
|
|
391
|
+
throw new Error(managedText(language, "提高托管预算时必须提供审计原因", "An audit reason is required when increasing managed task budgets"));
|
|
392
|
+
}
|
|
393
|
+
const before = { ...contract.budgets };
|
|
394
|
+
const next = {
|
|
395
|
+
...before,
|
|
396
|
+
maxExecutorInvocations: parseIncreasedBudget(options.maxExecutorInvocations, before.maxExecutorInvocations, "maxExecutorInvocations", language),
|
|
397
|
+
maxReviewRounds: parseIncreasedBudget(options.maxReviewRounds, before.maxReviewRounds, "maxReviewRounds", language),
|
|
398
|
+
maxTotalAgentInvocations: parseIncreasedBudget(options.maxTotalAgentInvocations, before.maxTotalAgentInvocations, "maxTotalAgentInvocations", language),
|
|
399
|
+
};
|
|
400
|
+
if (next.maxTotalAgentInvocations <
|
|
401
|
+
next.maxExecutorInvocations + next.maxReviewRounds) {
|
|
402
|
+
throw new Error(managedText(language, "总 Agent 调用上限不能小于执行上限与评审上限之和", "The total Agent limit cannot be lower than executor plus review limits"));
|
|
403
|
+
}
|
|
404
|
+
contract.budgets = next;
|
|
405
|
+
contract.contractHash = calculateManagedContractHash(contract);
|
|
406
|
+
state.contractHash = contract.contractHash;
|
|
407
|
+
validateManagedTaskContract(contract);
|
|
408
|
+
return { before, reason };
|
|
409
|
+
}
|
|
410
|
+
export function applyUnlimitedAgentBudget(contract, state, options, language = "zh") {
|
|
411
|
+
if (!options.unlimitedAgentBudget)
|
|
412
|
+
return false;
|
|
413
|
+
if (contract.budgets.unlimitedAgentInvocations === true)
|
|
414
|
+
return false;
|
|
415
|
+
const reason = options.budgetOverrideReason?.trim();
|
|
416
|
+
if (!reason) {
|
|
417
|
+
throw new Error(managedText(language, "取消 Agent 调用限制时必须提供审计原因", "An audit reason is required when removing Agent invocation limits"));
|
|
418
|
+
}
|
|
419
|
+
contract.budgets = {
|
|
420
|
+
...contract.budgets,
|
|
421
|
+
unlimitedAgentInvocations: true,
|
|
422
|
+
};
|
|
423
|
+
contract.contractHash = calculateManagedContractHash(contract);
|
|
424
|
+
state.contractHash = contract.contractHash;
|
|
425
|
+
validateManagedTaskContract(contract);
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
428
|
+
export function applyExecutorInvocationWindow(contract, state, options, language = "zh") {
|
|
429
|
+
if (options.additionalExecutorInvocations === undefined)
|
|
430
|
+
return null;
|
|
431
|
+
const count = Number(options.additionalExecutorInvocations);
|
|
432
|
+
if (!Number.isInteger(count) || count <= 0) {
|
|
433
|
+
throw new Error(managedText(language, "新增 Claude 调用窗口必须是正整数", "The additional Claude invocation window must be a positive integer"));
|
|
434
|
+
}
|
|
435
|
+
const reason = options.budgetOverrideReason?.trim();
|
|
436
|
+
if (!reason) {
|
|
437
|
+
throw new Error(managedText(language, "设置 Claude 调用窗口时必须提供审计原因", "An audit reason is required when setting a Claude invocation window"));
|
|
438
|
+
}
|
|
439
|
+
const before = state.executorInvocations;
|
|
440
|
+
const stopAt = before + count;
|
|
441
|
+
contract.budgets = {
|
|
442
|
+
...contract.budgets,
|
|
443
|
+
executorPhysicalStopAt: stopAt,
|
|
444
|
+
};
|
|
445
|
+
contract.contractHash = calculateManagedContractHash(contract);
|
|
446
|
+
state.contractHash = contract.contractHash;
|
|
447
|
+
validateManagedTaskContract(contract);
|
|
448
|
+
return { before, stopAt, count, reason };
|
|
449
|
+
}
|
|
450
|
+
function parseIncreasedBudget(value, current, name, language) {
|
|
451
|
+
if (value === undefined)
|
|
452
|
+
return current;
|
|
453
|
+
const parsed = Number(value);
|
|
454
|
+
if (!Number.isInteger(parsed) || parsed < current) {
|
|
455
|
+
throw new Error(managedText(language, `${name} 必须是大于或等于当前值 ${current} 的整数`, `${name} must be an integer greater than or equal to ${current}`));
|
|
456
|
+
}
|
|
457
|
+
return parsed;
|
|
458
|
+
}
|
|
459
|
+
export function hasAvailableInvocationBudget(state, contract) {
|
|
460
|
+
if (contract.budgets.unlimitedAgentInvocations === true)
|
|
461
|
+
return true;
|
|
462
|
+
if (contract.budgets.executorPhysicalStopAt !== undefined &&
|
|
463
|
+
state.executorInvocations < contract.budgets.executorPhysicalStopAt) {
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
466
|
+
return (effectiveExecutorInvocations(state) <
|
|
467
|
+
contract.budgets.maxExecutorInvocations &&
|
|
468
|
+
effectiveTotalAgentInvocations(state) <
|
|
469
|
+
contract.budgets.maxTotalAgentInvocations);
|
|
470
|
+
}
|
|
471
|
+
export function hasAvailableReviewBudget(state, contract) {
|
|
472
|
+
return state.reviewInvocations < contract.budgets.maxReviewRounds;
|
|
473
|
+
}
|
|
474
|
+
export function canResumeManagedTask(state, contract, options) {
|
|
475
|
+
const normallyResumable = [
|
|
476
|
+
"queued",
|
|
477
|
+
"waiting_for_human",
|
|
478
|
+
"waiting_for_connectivity",
|
|
479
|
+
"paused",
|
|
480
|
+
"running",
|
|
481
|
+
];
|
|
482
|
+
if (normallyResumable.includes(state.status))
|
|
483
|
+
return true;
|
|
484
|
+
const budgetAvailable = Boolean(options.creditInfrastructureInvocations) ||
|
|
485
|
+
hasAvailableInvocationBudget(state, contract) ||
|
|
486
|
+
hasAvailableReviewBudget(state, contract) ||
|
|
487
|
+
options.unlimitedAgentBudget === true;
|
|
488
|
+
if (!budgetAvailable)
|
|
489
|
+
return false;
|
|
490
|
+
return ["budget_exhausted", "review_exhausted", "repair_pending"].includes(state.status);
|
|
491
|
+
}
|
|
492
|
+
export function replaceExecutorSessionForRecovery(state, replacement, language = "zh") {
|
|
493
|
+
if (!replacement)
|
|
494
|
+
return null;
|
|
495
|
+
if (state.status !== "waiting_for_human") {
|
|
496
|
+
throw new Error(managedText(language, "只有 waiting_for_human 状态允许替换执行会话", "The executor session can only be replaced from waiting_for_human"));
|
|
497
|
+
}
|
|
498
|
+
if (!/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i.test(replacement)) {
|
|
499
|
+
throw new Error(managedText(language, "替换执行会话必须使用完整 UUID", "The replacement executor session must be a complete UUID"));
|
|
500
|
+
}
|
|
501
|
+
const previous = state.executorSession.sessionId;
|
|
502
|
+
if (!previous || previous === replacement)
|
|
503
|
+
return null;
|
|
504
|
+
state.executorSession = {
|
|
505
|
+
...state.executorSession,
|
|
506
|
+
sessionId: replacement,
|
|
507
|
+
createdAt: new Date().toISOString(),
|
|
508
|
+
lastResumedRound: state.reviewRound,
|
|
509
|
+
status: "active",
|
|
510
|
+
};
|
|
511
|
+
return previous;
|
|
512
|
+
}
|
|
513
|
+
export function resetExecutorSessionForRecovery(state, auditReason, language = "zh") {
|
|
514
|
+
if (auditReason === undefined)
|
|
515
|
+
return null;
|
|
516
|
+
if (state.status !== "waiting_for_human") {
|
|
517
|
+
throw new Error(managedText(language, "只有 waiting_for_human 状态允许重置执行会话", "The executor session can only be reset from waiting_for_human"));
|
|
518
|
+
}
|
|
519
|
+
const reason = auditReason.trim();
|
|
520
|
+
if (!reason) {
|
|
521
|
+
throw new Error(managedText(language, "重置执行会话时必须提供审计原因", "An audit reason is required when resetting the executor session"));
|
|
522
|
+
}
|
|
523
|
+
const previousSessionId = state.executorSession.sessionId;
|
|
524
|
+
if (!previousSessionId) {
|
|
525
|
+
throw new Error(managedText(language, "当前没有可重置的执行会话", "There is no executor session to reset"));
|
|
526
|
+
}
|
|
527
|
+
state.executorSession = {
|
|
528
|
+
...state.executorSession,
|
|
529
|
+
sessionId: null,
|
|
530
|
+
createdAt: null,
|
|
531
|
+
status: "pending",
|
|
532
|
+
};
|
|
533
|
+
state.consecutiveTransientProviderFailures = 0;
|
|
534
|
+
return { previousSessionId, reason };
|
|
535
|
+
}
|
|
536
|
+
export function recoverStaleRunningExecutor(state, processAlive = isProcessAlive) {
|
|
537
|
+
const pid = state.runningAgentPid;
|
|
538
|
+
if (state.status !== "running" || !pid || processAlive(pid))
|
|
539
|
+
return null;
|
|
540
|
+
state.status = "waiting_for_human";
|
|
541
|
+
state.currentStep = "stale_executor_recovery_required";
|
|
542
|
+
state.blocker = `Executor process ${pid} is no longer running`;
|
|
543
|
+
state.runningAgentPid = null;
|
|
544
|
+
state.activeSince = null;
|
|
545
|
+
return { pid };
|
|
546
|
+
}
|
|
547
|
+
export function shouldAttachRunningTask(state, processAlive = isProcessAlive) {
|
|
548
|
+
if (state.status !== "running")
|
|
549
|
+
return false;
|
|
550
|
+
if (!state.runningAgentPid)
|
|
551
|
+
return true;
|
|
552
|
+
return processAlive(state.runningAgentPid);
|
|
553
|
+
}
|
|
554
|
+
function hasRunningResumeMutations(options) {
|
|
555
|
+
return [
|
|
556
|
+
options.reopenDelivery,
|
|
557
|
+
options.replaceExecutorSession,
|
|
558
|
+
options.resetExecutorSession,
|
|
559
|
+
options.retryBlockedExecutor,
|
|
560
|
+
options.providerSwitched,
|
|
561
|
+
options.creditInfrastructureInvocations,
|
|
562
|
+
options.maxExecutorInvocations,
|
|
563
|
+
options.maxReviewRounds,
|
|
564
|
+
options.maxTotalAgentInvocations,
|
|
565
|
+
options.budgetOverrideReason,
|
|
566
|
+
options.additionalExecutorInvocations,
|
|
567
|
+
].some((value) => value !== undefined) || options.unlimitedAgentBudget === true;
|
|
568
|
+
}
|
|
569
|
+
export function confirmProviderSwitchForRecovery(state, auditReason, language = "zh") {
|
|
570
|
+
if (auditReason === undefined)
|
|
571
|
+
return null;
|
|
572
|
+
if (state.status !== "waiting_for_human"
|
|
573
|
+
|| state.currentStep !== "provider_change_required") {
|
|
574
|
+
throw new Error(managedText(language, "只有 provider_change_required 状态允许确认供应商切换", "A provider switch can only be confirmed from provider_change_required"));
|
|
575
|
+
}
|
|
576
|
+
const reason = auditReason.trim();
|
|
577
|
+
if (!reason) {
|
|
578
|
+
throw new Error(managedText(language, "供应商切换恢复必须提供审计原因", "Provider-switch recovery requires an audit reason"));
|
|
579
|
+
}
|
|
580
|
+
const previousSessionId = state.executorSession.sessionId;
|
|
581
|
+
if (!previousSessionId) {
|
|
582
|
+
throw new Error(managedText(language, "当前没有可放弃的执行会话", "There is no executor session to discard"));
|
|
583
|
+
}
|
|
584
|
+
state.executorSession = {
|
|
585
|
+
...state.executorSession,
|
|
586
|
+
sessionId: null,
|
|
587
|
+
createdAt: null,
|
|
588
|
+
status: "pending",
|
|
589
|
+
previousSessionIds: [
|
|
590
|
+
...(state.executorSession.previousSessionIds ?? []),
|
|
591
|
+
previousSessionId,
|
|
592
|
+
],
|
|
593
|
+
};
|
|
594
|
+
state.consecutiveTransientProviderFailures = 0;
|
|
595
|
+
return { previousSessionId, reason };
|
|
596
|
+
}
|
|
597
|
+
export function retryBlockedExecutorForRecovery(state, reason, language = "zh") {
|
|
598
|
+
if (reason === undefined)
|
|
599
|
+
return null;
|
|
600
|
+
if (state.status !== "waiting_for_human") {
|
|
601
|
+
throw new Error(managedText(language, "只有 waiting_for_human 状态允许重试被阻塞的执行者", "A blocked executor can only be retried from waiting_for_human"));
|
|
602
|
+
}
|
|
603
|
+
const auditReason = reason.trim();
|
|
604
|
+
if (!auditReason) {
|
|
605
|
+
throw new Error(managedText(language, "重试被阻塞的执行者时必须提供审计原因", "An audit reason is required when retrying a blocked executor"));
|
|
606
|
+
}
|
|
607
|
+
if (!state.lastReviewResult || !existsSync(state.lastReviewResult)) {
|
|
608
|
+
if (!state.lastExecutorResult && state.blocker) {
|
|
609
|
+
return {
|
|
610
|
+
result: "needs_fix",
|
|
611
|
+
summary: auditReason,
|
|
612
|
+
findings: [
|
|
613
|
+
{
|
|
614
|
+
id: `HUMAN-RETRY-${state.reviewRound}`,
|
|
615
|
+
severity: "high",
|
|
616
|
+
blocking: true,
|
|
617
|
+
category: "基础设施恢复",
|
|
618
|
+
target: "Agent 启动与当前工作区",
|
|
619
|
+
evidence: state.blocker,
|
|
620
|
+
risk: managedText(language, "首次调用失败后若无法恢复,任务会在尚未产生研发结果时永久停止", "Without recovery after an initial invocation failure, the task remains stopped before producing any development result"),
|
|
621
|
+
requiredFix: auditReason,
|
|
622
|
+
acceptanceChecks: [auditReason],
|
|
623
|
+
},
|
|
624
|
+
],
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
throw new Error(managedText(language, "没有可用于重试的阻塞评审结果", "No blocked review result is available for retry"));
|
|
628
|
+
}
|
|
629
|
+
const previous = JSON.parse(readFileSync(state.lastReviewResult, "utf-8"));
|
|
630
|
+
const postReviewGateFailure = previous.result === "pass" &&
|
|
631
|
+
/(?:Host 评审通过|Host review passed)[\s\S]*(?:重验仍失败|revalidation still failed)/i.test(state.blocker ?? "");
|
|
632
|
+
if (!["blocked", "needs_fix"].includes(previous.result) &&
|
|
633
|
+
!postReviewGateFailure) {
|
|
634
|
+
throw new Error(managedText(language, "仅允许基于 blocked 或 needs_fix 评审结果重试执行者", "The executor can only be retried from a blocked or needs_fix review result"));
|
|
635
|
+
}
|
|
636
|
+
return {
|
|
637
|
+
result: "needs_fix",
|
|
638
|
+
summary: auditReason,
|
|
639
|
+
findings: [
|
|
640
|
+
...previous.findings,
|
|
641
|
+
{
|
|
642
|
+
id: `HUMAN-RETRY-${state.reviewRound}`,
|
|
643
|
+
severity: "high",
|
|
644
|
+
blocking: true,
|
|
645
|
+
category: "人工恢复",
|
|
646
|
+
target: "当前工作区与交付证据",
|
|
647
|
+
evidence: auditReason,
|
|
648
|
+
risk: managedText(language, "若不继续执行,可在仍有本地收口工作时错误停止", "Stopping now could leave locally executable delivery work unfinished"),
|
|
649
|
+
requiredFix: auditReason,
|
|
650
|
+
acceptanceChecks: [auditReason],
|
|
651
|
+
},
|
|
652
|
+
],
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
export function shouldResumeBlockedExecutor(state) {
|
|
656
|
+
if (state.status !== "waiting_for_human" || !state.lastExecutorResult) {
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
try {
|
|
660
|
+
const result = JSON.parse(readFileSync(state.lastExecutorResult, "utf-8"));
|
|
661
|
+
return result.status === "failed";
|
|
662
|
+
}
|
|
663
|
+
catch {
|
|
664
|
+
return false;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
function recoverAcceptedExecutorResult(contract, state) {
|
|
668
|
+
if (state.lastExecutorResult || state.executorInvocations <= 0)
|
|
669
|
+
return null;
|
|
670
|
+
const resultPath = path.join(managedRunDir(state.projectRoot, state.taskId, state.runId), `executor-result-${state.executorInvocations}.json`);
|
|
671
|
+
if (!existsSync(resultPath))
|
|
672
|
+
return null;
|
|
673
|
+
try {
|
|
674
|
+
const result = JSON.parse(readFileSync(resultPath, "utf-8"));
|
|
675
|
+
if (result.status !== "ready_for_review")
|
|
676
|
+
return null;
|
|
677
|
+
const decision = evaluateCompletion(contract, result);
|
|
678
|
+
if (!decision.localReady)
|
|
679
|
+
return null;
|
|
680
|
+
state.lastExecutorResult = resultPath;
|
|
681
|
+
state.deliveryProgress = decision.progress;
|
|
682
|
+
return resultPath;
|
|
683
|
+
}
|
|
684
|
+
catch {
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
export function shouldReopenBlockedReview(state) {
|
|
689
|
+
if (state.status !== "waiting_for_human" || !state.lastReviewResult) {
|
|
690
|
+
return false;
|
|
691
|
+
}
|
|
692
|
+
try {
|
|
693
|
+
const result = JSON.parse(readFileSync(state.lastReviewResult, "utf-8"));
|
|
694
|
+
return result.result === "blocked";
|
|
695
|
+
}
|
|
696
|
+
catch {
|
|
697
|
+
return false;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
export function reopenAwaitingDelivery(state, reason, language = "zh") {
|
|
701
|
+
if (reason === undefined)
|
|
702
|
+
return null;
|
|
703
|
+
if (![
|
|
704
|
+
"awaiting_git_approval",
|
|
705
|
+
"local_delivery_ready",
|
|
706
|
+
"environment_validation_blocked",
|
|
707
|
+
"release_ready",
|
|
708
|
+
].includes(state.status)) {
|
|
709
|
+
throw new Error(managedText(language, "只有交付就绪状态允许重新打开交付", "Only a delivery-ready state can be reopened"));
|
|
710
|
+
}
|
|
711
|
+
if (!reason.trim()) {
|
|
712
|
+
throw new Error(managedText(language, "重新打开交付必须提供整改原因", "Reopening a delivery requires a correction reason"));
|
|
713
|
+
}
|
|
714
|
+
state.status = "waiting_for_human";
|
|
715
|
+
state.currentStep = "repair_prompt_ready";
|
|
716
|
+
state.completedAt = null;
|
|
717
|
+
state.lastExecutorResult = null;
|
|
718
|
+
state.blocker = reason.trim();
|
|
719
|
+
return {
|
|
720
|
+
result: "needs_fix",
|
|
721
|
+
summary: reason.trim(),
|
|
722
|
+
findings: [
|
|
723
|
+
{
|
|
724
|
+
id: `HUMAN-REOPEN-${state.reviewRound + 1}`,
|
|
725
|
+
severity: "high",
|
|
726
|
+
blocking: true,
|
|
727
|
+
category: "completion",
|
|
728
|
+
target: "OpenSpec tasks.md",
|
|
729
|
+
evidence: reason.trim(),
|
|
730
|
+
risk: managedText(language, "任务可能在 OpenSpec 清单仍有未完成项时被错误交付", "The task may have been delivered while OpenSpec checklist items remained incomplete"),
|
|
731
|
+
requiredFix: managedText(language, "运行 openspec instructions apply,逐项核对未完成任务;已完成项补证据并勾选,本地未完成项继续实现,真实环境项保持诚实阻塞", "Run openspec instructions apply and audit every pending task; check off evidenced work, complete remaining local work, and keep real-environment items honestly blocked"),
|
|
732
|
+
acceptanceChecks: [
|
|
733
|
+
managedText(language, "OpenSpec 进度与源码、测试及阻塞证据一致", "OpenSpec progress matches source, test, and blocker evidence"),
|
|
734
|
+
managedText(language, "不存在未完成却被宣称完成的本地任务", "No incomplete local task is claimed complete"),
|
|
735
|
+
],
|
|
736
|
+
},
|
|
737
|
+
],
|
|
738
|
+
};
|
|
739
|
+
}
|
|
109
740
|
function canonicalPath(value) {
|
|
110
741
|
const resolved = path.resolve(value);
|
|
111
742
|
return existsSync(resolved) ? realpathSync(resolved) : resolved;
|
|
112
743
|
}
|
|
113
744
|
async function waitAndReport(projectRoot, taskId, runId, language) {
|
|
114
745
|
const result = await waitForManagedTask(projectRoot, taskId, runId, {
|
|
115
|
-
ensureService: () => ensureManagedService(),
|
|
746
|
+
ensureService: () => ensureManagedService(process.argv[1], process.env, language),
|
|
116
747
|
onProgress: (state) => {
|
|
117
|
-
console.log(managedText(language, `[${state.status}] ${state.currentStep} |
|
|
748
|
+
console.log(managedText(language, `[${state.status}] ${state.currentStep} | 当前 ${state.executorActiveStage ?? state.executorStage ?? "-"} | 里程碑 ${state.executorStage ?? "-"} | Host ${state.reviewRound} | 有效研发 ${effectiveExecutorInvocations(state)} | 物理 ${state.executorInvocations} | 抵扣 ${state.executorInvocationCredits ?? 0}`, `[${state.status}] ${state.currentStep} | active ${state.executorActiveStage ?? state.executorStage ?? "-"} | milestone ${state.executorStage ?? "-"} | Host ${state.reviewRound} | effective executor ${effectiveExecutorInvocations(state)} | physical ${state.executorInvocations} | credited ${state.executorInvocationCredits ?? 0}`));
|
|
118
749
|
},
|
|
119
750
|
});
|
|
120
751
|
console.log(managedText(language, `任务状态:${result.status}`, `Task status: ${result.status}`));
|
|
121
752
|
if (result.blocker) {
|
|
122
753
|
console.log(managedText(language, `阻塞:${result.blocker}`, `Blocker: ${result.blocker}`));
|
|
123
754
|
}
|
|
755
|
+
if (result.pendingExternalReview) {
|
|
756
|
+
console.log(managedText(language, `Host 评审 Prompt:${result.pendingExternalReview.promptPath}`, `Host review prompt: ${result.pendingExternalReview.promptPath}`));
|
|
757
|
+
}
|
|
124
758
|
console.log(managedText(language, `任务报告:${managedTaskReportPath(result)}`, `Task report: ${managedTaskReportPath(result)}`));
|
|
125
759
|
}
|
|
126
760
|
//# sourceMappingURL=pipeline.js.map
|